From cfaac752d4907eecb6e1a0ecba8d94a5abc8c0a1 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sat, 31 May 2025 11:17:30 +0330 Subject: [PATCH] update: the invoice update by admin logic --- .../invoices/providers/invoices.service.ts | 162 ++++++++++++------ 1 file changed, 109 insertions(+), 53 deletions(-) diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index faaa500..4fa168f 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -50,30 +50,6 @@ export class InvoicesService { ///********************************** */ - private validateInvoiceItems(items: InvoiceItemDto[]): void { - if (!items || items.length === 0) throw new BadRequestException(InvoiceMessage.ITEMS_REQUIRED); - - items.forEach((item) => { - if (item.unitPrice <= 0) throw new BadRequestException(InvoiceMessage.UNIT_PRICE_MUST_BE_POSITIVE); - if (item.count <= 0) throw new BadRequestException(InvoiceMessage.COUNT_MUST_BE_POSITIVE); - if (item.discount && (item.discount < 0 || item.discount > 100)) { - throw new BadRequestException(InvoiceMessage.DISCOUNT_MUST_BE_BETWEEN_0_AND_100); - } - }); - } - ///********************************** */ - - private validateRecurringInvoice(createDto: CreateInvoiceDto): void { - if (createDto.isRecurring) { - if (!createDto.recurringPeriod) throw new BadRequestException(InvoiceMessage.RECURRING_PERIOD_REQUIRED); - - if (createDto.maxRecurringCycles && createDto.maxRecurringCycles < 1) { - throw new BadRequestException(InvoiceMessage.MAX_RECURRING_CYCLES_MUST_BE_POSITIVE); - } - } - } - ///********************************** */ - async createInvoiceAdmin(createDto: CreateInvoiceDto) { const queryRunner = this.dataSource.createQueryRunner(); @@ -167,6 +143,8 @@ export class InvoicesService { if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_UPDATE); if (updateDto.items) { + this.validateInvoiceItems(updateDto.items); + const invoiceItemsData = updateDto.items.map((item) => ({ name: item.name, count: item.count, @@ -176,6 +154,9 @@ export class InvoicesService { })); // calculate total price and tax const totalPrice = invoiceItemsData.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0)); + + if (totalPrice.lessThanOrEqualTo(0)) throw new BadRequestException(InvoiceMessage.TOTAL_PRICE_MUST_BE_POSITIVE); + const tax = totalPrice.mul(0.1); invoice.totalPrice = totalPrice.add(tax); invoice.tax = tax; @@ -194,6 +175,15 @@ export class InvoicesService { invoice.user = user; } + if (updateDto.isRecurring) { + this.validateRecurringInvoice(updateDto); + await this.updateScheduleInvoiceJobs(invoice, updateDto); + + invoice.isRecurring = updateDto.isRecurring; + invoice.recurringPeriod = updateDto.recurringPeriod; + invoice.maxRecurringCycles = updateDto.maxRecurringCycles ? updateDto.maxRecurringCycles : invoice.maxRecurringCycles; + } + // Save the updated invoice await queryRunner.manager.save(this.invoiceRepository.target, invoice); @@ -678,34 +668,6 @@ export class InvoicesService { return invoiceCount; } - //********************************** */ - - private async calculateRecurringDelay(recurringPeriod: RecurringPeriodEnum) { - let delayMs: number; - switch (recurringPeriod) { - case RecurringPeriodEnum.WEEKLY: - delayMs = dayjs().add(1, "week").subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()); - break; - case RecurringPeriodEnum.MONTHLY: - delayMs = dayjs().add(1, "month").subtract(7, "day").diff(dayjs()); - break; - case RecurringPeriodEnum.QUARTERLY: - delayMs = dayjs().add(3, "month").subtract(7, "day").diff(dayjs()); - break; - case RecurringPeriodEnum.BIANNUALLY: - delayMs = dayjs().add(6, "month").subtract(7, "day").diff(dayjs()); - break; - case RecurringPeriodEnum.ANNUALLY: - delayMs = dayjs().add(1, "year").subtract(7, "day").diff(dayjs()); - break; - default: - delayMs = dayjs().add(1, "month").subtract(7, "day").diff(dayjs()); - } - - this.logger.log(`Calculated recurring delay: ${delayMs}ms for period: ${recurringPeriod}`); - return delayMs; - } - //*********************************** */ async applyDiscount(invoiceId: string, discountCode: string, userId: string) { const queryRunner = this.dataSource.createQueryRunner(); @@ -800,7 +762,7 @@ export class InvoicesService { } //*********************************** */ - private async scheduleInvoiceJobs(invoice: Invoice, createDto?: CreateInvoiceDto) { + private async scheduleInvoiceJobs(invoice: Invoice, createDto?: CreateInvoiceDto | UpdateInvoiceDto) { // Add to queue for billing reminder await this.invoiceQueue.add( INVOICE.REMINDER_JOB_NAME, @@ -834,6 +796,25 @@ export class InvoicesService { this.logger.log(`Scheduled recurring invoice for user ${createDto.userId} with interval ${createDto.recurringPeriod}`); } } + + //*********************************** */ + private async updateScheduleInvoiceJobs(invoice: Invoice, updateDto: UpdateInvoiceDto) { + // + await this.removeRecurringInvoiceJobs(invoice.id); + await this.removeReminderInvoiceJobs(invoice.id); + + if (updateDto.isRecurring) { + if (!updateDto.recurringPeriod) throw new BadRequestException(InvoiceMessage.RECURRING_PERIOD_REQUIRED); + + if (updateDto.maxRecurringCycles && updateDto.maxRecurringCycles < 1) { + throw new BadRequestException(InvoiceMessage.MAX_RECURRING_CYCLES_MUST_BE_POSITIVE); + } + + await this.scheduleInvoiceJobs(invoice, updateDto); + + this.logger.log(`Updated recurring invoice for user ${updateDto.userId} with interval ${updateDto.recurringPeriod}`); + } + } //*********************************** */ private async addNotifyAdminForSubscriptionPaymentToQueue(invoice: Invoice) { await this.invoiceQueue.add( @@ -859,4 +840,79 @@ export class InvoicesService { userEmail: user.email, }); } + //*********************************** */ + + private async removeRecurringInvoiceJobs(invoiceId: string) { + const existingJobs = await this.invoiceQueue.getJobs(["delayed", "waiting", "active"]); + for (const job of existingJobs) { + if (job.name === INVOICE.RECURRING_JOB_NAME && job.data.invoiceId === invoiceId) { + await job.remove(); + } + } + } + //*********************************** */ + + private async removeReminderInvoiceJobs(invoiceId: string) { + const existingJobs = await this.invoiceQueue.getJobs(["delayed", "waiting", "active"]); + for (const job of existingJobs) { + if (job.name === INVOICE.REMINDER_JOB_NAME && job.data.invoiceId === invoiceId) { + await job.remove(); + } + } + } + + ///********************************** */ + + private validateInvoiceItems(items: InvoiceItemDto[]): void { + if (!items || items.length === 0) throw new BadRequestException(InvoiceMessage.ITEMS_REQUIRED); + + items.forEach((item) => { + if (item.unitPrice <= 0) throw new BadRequestException(InvoiceMessage.UNIT_PRICE_MUST_BE_POSITIVE); + if (item.count <= 0) throw new BadRequestException(InvoiceMessage.COUNT_MUST_BE_POSITIVE); + if (item.discount && (item.discount < 0 || item.discount > 100)) { + throw new BadRequestException(InvoiceMessage.DISCOUNT_MUST_BE_BETWEEN_0_AND_100); + } + }); + } + ///********************************** */ + + private validateRecurringInvoice(createDto: CreateInvoiceDto | UpdateInvoiceDto) { + if (createDto.isRecurring) { + if (!createDto.recurringPeriod) throw new BadRequestException(InvoiceMessage.RECURRING_PERIOD_REQUIRED); + + if (createDto.maxRecurringCycles && createDto.maxRecurringCycles < 1) { + throw new BadRequestException(InvoiceMessage.MAX_RECURRING_CYCLES_MUST_BE_POSITIVE); + } + return true; + } + return false; + } + + //********************************** */ + + private async calculateRecurringDelay(recurringPeriod: RecurringPeriodEnum) { + let delayMs: number; + switch (recurringPeriod) { + case RecurringPeriodEnum.WEEKLY: + delayMs = dayjs().add(1, "week").subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()); + break; + case RecurringPeriodEnum.MONTHLY: + delayMs = dayjs().add(1, "month").subtract(7, "day").diff(dayjs()); + break; + case RecurringPeriodEnum.QUARTERLY: + delayMs = dayjs().add(3, "month").subtract(7, "day").diff(dayjs()); + break; + case RecurringPeriodEnum.BIANNUALLY: + delayMs = dayjs().add(6, "month").subtract(7, "day").diff(dayjs()); + break; + case RecurringPeriodEnum.ANNUALLY: + delayMs = dayjs().add(1, "year").subtract(7, "day").diff(dayjs()); + break; + default: + delayMs = dayjs().add(1, "month").subtract(7, "day").diff(dayjs()); + } + + this.logger.log(`Calculated recurring delay: ${delayMs}ms for period: ${recurringPeriod}`); + return delayMs; + } }