Compare commits

..

9 Commits

Author SHA1 Message Date
3b295ea79f ZOD-344-Add QR code validation error handling and localization support
- Introduced new error handling for already used or expired QR codes in JuniorService.
- Added corresponding localization entries in Arabic and English app.json files for QR code validation messages.
2025-11-02 10:52:43 +03:00
5ffe18ede3 Merge pull request #54 from HamzaSha1/fix/verfy-email
Implement OTP generation and email verification logic in UserService
2025-10-28 16:17:51 +03:00
a3a61b4923 Implement OTP generation and email verification logic in UserService 2025-10-28 15:52:24 +03:00
39d5fc1869 Merge pull request #52 from HamzaSha1/ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view
Enhance weekly summary functionality to accept optional date range pa…
2025-10-28 11:22:52 +03:00
05a6ad2d84 Enhance weekly summary functionality to accept optional date range parameters in CardService, TransactionService, JuniorService, and JuniorController. Update API documentation to reflect new query parameters for start and end dates. 2025-10-28 11:20:49 +03:00
5649d24724 Merge pull request #50 from HamzaSha1/ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view
git checkout -b ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view
2025-10-26 16:05:00 +03:00
bbeece9e03 git checkout -b ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view 2025-10-26 13:14:35 +03:00
596562f6dc Merge pull request #48 from HamzaSha1/feat/neoleap-integration
Feat/neoleap integration
2025-10-21 14:56:38 +03:00
2714255dd1 Merge pull request #45 from HamzaSha1/ZOD-341-junior-a-child-can-edit-their-email-to-an-existing-email-causing-multiple-child-accounts-to-share-the-same-login
ZOD-341 Add email uniqueness validation to prevent duplicate emails
2025-10-20 14:31:11 +03:00
7 changed files with 54 additions and 11 deletions

View File

@ -163,8 +163,8 @@ export class CardService {
return finalAmount.toNumber();
}
getWeeklySummary(juniorId: string) {
return this.transactionService.getWeeklySummary(juniorId);
getWeeklySummary(juniorId: string, startDate?: Date, endDate?: Date) {
return this.transactionService.getWeeklySummary(juniorId, startDate, endDate);
}
fundIban(iban: string, amount: number) {

View File

@ -84,15 +84,28 @@ export class TransactionService {
return existingTransaction;
}
async getWeeklySummary(juniorId: string) {
const startOfWeek = moment().startOf('week').toDate();
const endOfWeek = moment().endOf('week').toDate();
async getWeeklySummary(juniorId: string, startDate?: Date, endDate?: Date) {
let startOfWeek: Date;
let endOfWeek: Date;
if (startDate && endDate) {
startOfWeek = startDate;
endOfWeek = endDate;
} else {
const now = moment();
const dayOfWeek = now.day();
startOfWeek = moment().subtract(dayOfWeek, 'days').startOf('day').toDate();
endOfWeek = moment().add(6 - dayOfWeek, 'days').endOf('day').toDate();
}
const transactions = await this.transactionRepository.getTransactionsForCardWithinDateRange(
juniorId,
startOfWeek,
endOfWeek,
);
const summary = {
startOfWeek: startOfWeek,
endOfWeek: endOfWeek,

View File

@ -19,6 +19,10 @@
"TOKEN_EXPIRED": "رمز المستخدم منتهي الصلاحية."
},
"QR": {
"CODE_USED_OR_EXPIRED": "تم استخدام رمز QR مسبقًا أو انتهت صلاحيته."
},
"USER": {
"PHONE_ALREADY_VERIFIED": "تم التحقق من رقم الهاتف بالفعل.",
"EMAIL_ALREADY_VERIFIED": "تم التحقق من عنوان البريد الإلكتروني بالفعل.",

View File

@ -19,6 +19,10 @@
"TOKEN_EXPIRED": "The user token has expired."
},
"QR": {
"CODE_USED_OR_EXPIRED": "The QR code has already been used or expired."
},
"USER": {
"PHONE_ALREADY_VERIFIED": "The phone number has already been verified.",
"EMAIL_ALREADY_VERIFIED": "The email address has already been verified.",

View File

@ -151,11 +151,17 @@ export class JuniorController {
@UseGuards(RolesGuard)
@AllowedRoles(Roles.GUARDIAN)
@ApiDataResponse(WeeklySummaryResponseDto)
@ApiQuery({ name: 'startUtc', required: false, type: String, example: '2025-10-20T00:00:00.000Z', description: 'Start date (defaults to start of current week)' })
@ApiQuery({ name: 'endUtc', required: false, type: String, example: '2025-10-26T23:59:59.999Z', description: 'End date (defaults to end of current week)' })
async getWeeklySummary(
@Param('juniorId', CustomParseUUIDPipe) juniorId: string,
@AuthenticatedUser() user: IJwtPayload,
@Query('startUtc') startUtc?: string,
@Query('endUtc') endUtc?: string,
) {
const summary = await this.juniorService.getWeeklySummary(juniorId, user.sub);
const startDate = startUtc ? new Date(startUtc) : undefined;
const endDate = endUtc ? new Date(endUtc) : undefined;
const summary = await this.juniorService.getWeeklySummary(juniorId, user.sub, startDate, endDate);
return ResponseFactory.data(summary);
}

View File

@ -5,6 +5,7 @@ import { Roles } from '~/auth/enums';
import { CardService, TransactionService } from '~/card/services';
import { NeoLeapService } from '~/common/modules/neoleap/services';
import { PageOptionsRequestDto } from '~/core/dtos';
import { ErrorCategory } from '~/core/enums';
import { setIf } from '~/core/utils';
import { CustomerService } from '~/customer/services';
import { DocumentService, OciService } from '~/document/services';
@ -158,7 +159,14 @@ export class JuniorService {
async validateToken(token: string) {
this.logger.log(`Validating token ${token}`);
const juniorId = await this.userTokenService.validateToken(token, UserType.JUNIOR);
return this.findJuniorById(juniorId!, true);
const junior = await this.findJuniorById(juniorId!, true);
if (junior.customer?.user?.password) {
this.logger.error(`Token ${token} already used for junior ${juniorId}`);
throw new BadRequestException({ message: 'QR.CODE_USED_OR_EXPIRED', category: ErrorCategory.BUSINESS_ERROR });
}
return junior;
}
async generateToken(juniorId: string) {
@ -212,8 +220,8 @@ export class JuniorService {
this.logger.log(`Junior ${juniorId} deleted successfully`);
}
getWeeklySummary(juniorId: string, guardianId: string) {
const doesBelong = this.doesJuniorBelongToGuardian(guardianId, juniorId);
async getWeeklySummary(juniorId: string, guardianId: string, startDate?: Date, endDate?: Date) {
const doesBelong = await this.doesJuniorBelongToGuardian(guardianId, juniorId);
if (!doesBelong) {
this.logger.error(`Junior ${juniorId} does not belong to guardian ${guardianId}`);
@ -221,7 +229,7 @@ export class JuniorService {
}
this.logger.log(`Getting weekly summary for junior ${juniorId}`);
return this.cardService.getWeeklySummary(juniorId);
return this.cardService.getWeeklySummary(juniorId, startDate, endDate);
}
async getJuniorHome(juniorId: string, userId: string, size: number): Promise<JuniorHomeResponseDto> {

View File

@ -226,7 +226,15 @@ export class UserService {
if (userWithEmail) {
if (userWithEmail.id === userId) {
return;
this.logger.log(`Generating OTP for current email ${email} for user ${userId}`);
await this.userRepository.update(userId, { isEmailVerified: false });
return this.otpService.generateAndSendOtp({
userId,
recipient: email,
otpType: OtpType.EMAIL,
scope: OtpScope.VERIFY_EMAIL,
});
}
this.logger.error(`Email ${email} is already taken by another user`);