diff --git a/src/modules/discounts/constants/index.ts b/src/modules/discounts/constants/index.ts index f842723..fe71d92 100644 --- a/src/modules/discounts/constants/index.ts +++ b/src/modules/discounts/constants/index.ts @@ -1,10 +1,20 @@ export const DISCOUNT = Object.freeze({ + // Queue names DISCOUNT_QUEUE_NAME: "discount", + + // Job names DISCOUNT_DEACTIVATE_JOB_NAME: "deactivateDiscount", - // + + // Job configurations DISCOUNT_DEACTIVATE_JOB_PRIORITY: 1, // high priority DISCOUNT_DEACTIVATE_JOB_ATTEMPTS: 3, // 3 times DISCOUNT_DEACTIVATE_JOB_BACKOFF: 5 * 1000, // ms DISCOUNT_DEACTIVATE_JOB_TIMEOUT: 10000, // timeout after 10 seconds - // + + // Discount deactivation messages + DISCOUNT_DEACTIVATION_SUCCESS: "Discount successfully deactivated", + DISCOUNT_DEACTIVATION_ERROR: "Failed to deactivate discount", + + // Discount application types + DISCOUNT_APPLICATION_DIRECT: "DIRECT", }); diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index c667be2..06b2361 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -114,7 +114,6 @@ export class InvoicesService { //********************************** */ - //TODO: fix this async updateInvoiceAdmin(invoiceId: string, updateDto: UpdateInvoiceDto) { const queryRunner = this.dataSource.createQueryRunner(); @@ -158,6 +157,16 @@ export class InvoicesService { const user = await this.usersService.findOneByIdWithQueryRunner(updateDto.userId, queryRunner); invoice.user = user; } + + // Save the updated invoice + await queryRunner.manager.save(this.invoiceRepository.target, invoice); + + await queryRunner.commitTransaction(); + + return { + message: InvoiceMessage.INVOICE_UPDATED, + invoice, + }; } catch (error) { await queryRunner.rollbackTransaction(); throw error; @@ -168,39 +177,48 @@ export class InvoicesService { //********************************** */ async approveInvoiceRequest(invoiceId: string, userId: string) { - const { user } = await this.usersService.findOneById(userId); + const queryRunner = this.dataSource.createQueryRunner(); - const invoice = await this.invoiceRepository.findOne({ where: { id: invoiceId, user: { id: userId } }, relations: { items: true } }); - if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); - if (invoice.status === InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); - if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID); - if (dayjs().isAfter(dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day"))) { - invoice.status = InvoiceStatus.EXPIRED; - await this.invoiceRepository.save(invoice); - throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); - } + const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner); - if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_APPROVED); + const invoice = await this.validateInvoiceForApproval(invoiceId, userId, queryRunner); + + const existCode = await this.otpService.checkExistOtp(user.phone, "INVOICE_VERIFY"); + if (existCode) { + return { + message: AuthMessage.OTP_ALREADY_SENT, + ttlSecond: existCode, + }; + } + + // Generate and send OTP + const otpCode = await this.otpService.generateAndSetInCache(user.phone, "INVOICE_VERIFY"); + const items = invoice.items.map((item) => item.name).join(", "); + + await this.smsService.sendInvoiceVerifyCode( + user.phone, + otpCode, + invoice.numericId, + new Decimal(invoice.totalPrice).toNumber(), + items, + ); + + await queryRunner.commitTransaction(); - const existCode = await this.otpService.checkExistOtp(user.phone, "INVOICE_VERIFY"); - if (existCode) { return { - message: AuthMessage.OTP_ALREADY_SENT, - ttlSecond: existCode, + message: AuthMessage.OTP_SENT, + otpCode, }; + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); } - - const otpCode = await this.otpService.generateAndSetInCache(user.phone, "INVOICE_VERIFY"); - const items = invoice.items.map((item) => item.name).join(", "); - // - await this.smsService.sendInvoiceVerifyCode(user.phone, otpCode, invoice.numericId, new Decimal(invoice.totalPrice).toNumber(), items); - this.logger.debug(`OTP sent to ${user.phone}: ${otpCode}`); - - return { - message: AuthMessage.OTP_SENT, - otpCode, - }; } //********************************** */ @@ -213,27 +231,12 @@ export class InvoicesService { await queryRunner.startTransaction(); const { code } = verifyOtpDto; - const invoice = await queryRunner.manager.findOne(Invoice, { - where: { id: invoiceId, user: { id: userId } }, - relations: { items: true }, - }); - if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); - const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner); const isValid = await this.otpService.verifyOtp(user.phone, code, "INVOICE_VERIFY"); if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); - if (invoice.status === InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); - if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID); - // - if (dayjs().isAfter(dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day"))) { - invoice.status = InvoiceStatus.EXPIRED; - await this.invoiceRepository.save(invoice); - throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); - } - // - if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_APPROVED); + const invoice = await this.validateInvoiceForApproval(invoiceId, userId, queryRunner); invoice.status = InvoiceStatus.WAIT_PAYMENT; await queryRunner.manager.save(Invoice, invoice); @@ -267,7 +270,33 @@ export class InvoicesService { await queryRunner.release(); } } + + //********************************** */ + private async validateInvoiceForApproval(invoiceId: string, userId: string, queryRunner: QueryRunner): Promise { + const invoice = await queryRunner.manager.findOne(Invoice, { + where: { id: invoiceId, user: { id: userId } }, + relations: { items: true }, + }); + + if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); + + if (invoice.status === InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); + + if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID); + + if (dayjs().isAfter(dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day"))) { + invoice.status = InvoiceStatus.EXPIRED; + await queryRunner.manager.save(Invoice, invoice); + throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); + } + + if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_APPROVED); + + return invoice; + } + ///********************************** */ + async createInvoiceForSubscription(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner) { const discount = plan.directDiscount; const originalPrice = plan.originalPrice || plan.price;