diff --git a/src/configs/bullmq.config.ts b/src/configs/bullmq.config.ts index fb6ef96..ed0021f 100755 --- a/src/configs/bullmq.config.ts +++ b/src/configs/bullmq.config.ts @@ -9,9 +9,10 @@ export function bullMqConfig(): SharedBullAsyncConfiguration { url: configService.getOrThrow("REDIS_URI"), }, defaultJobOptions: { - removeOnComplete: 1000, - removeOnFail: 5000, - attempts: 3, + removeOnComplete: 2000, // Consider lower values in high-volume production + removeOnFail: 5000, // Consider lower values to reduce Redis memory usage + attempts: 3, // Reasonable default, adjust based on job criticality + backoff: { type: "exponential", delay: 2000 }, // Consider adding for better retry behavior }, }), }; diff --git a/src/modules/invoices/constants/index.ts b/src/modules/invoices/constants/index.ts index d804aa8..426be20 100644 --- a/src/modules/invoices/constants/index.ts +++ b/src/modules/invoices/constants/index.ts @@ -11,5 +11,5 @@ export const INVOICE = Object.freeze({ FINE_PERCENTAGE: 0.01, // 1% of the total price MAX_DAYS_AFTER_OVERDUE: 10, DUEDATE: 7, - DAYS_BEFORE_OVERDUE: 6, + DAYS_BEFORE_OVERDUE: 3, }); diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index 215baab..9ac8029 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -62,7 +62,8 @@ export class InvoicesService { const totalPrice = invoiceItems.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0)); const tax = totalPrice.mul(0.1); - const dueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate(); + // const dueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate(); + const dueDate = dayjs().add(1, "minute").toDate(); const invoice = queryRunner.manager.create(Invoice, { user: { id: createDto.userId }, @@ -90,13 +91,12 @@ export class InvoicesService { }, queryRunner, ); - //TODO:remove the test delay await this.invoiceQueue.add( INVOICE.INVOICE_REMINDER_JOB_NAME, { invoiceId: invoice.id }, { - // delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()), - delay: 900000, // 15 minutes in milliseconds + delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()), + // delay: 1.5 * 60 * 1000, // 5 minutes in milliseconds attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS, backoff: INVOICE.INVOICE_REMINDER_JOB_BACKOFF, }, @@ -126,7 +126,7 @@ export class InvoicesService { 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(invoice.dueDate)) { + 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); @@ -177,7 +177,13 @@ export class InvoicesService { 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(invoice.dueDate)) throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); + // + 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); invoice.status = InvoiceStatus.WAIT_PAYMENT; @@ -256,8 +262,7 @@ export class InvoicesService { INVOICE.INVOICE_REMINDER_JOB_NAME, { invoiceId: invoice.id }, { - // delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()), - delay: 900000, // 15 minutes in milliseconds + delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()), attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS, backoff: INVOICE.INVOICE_REMINDER_JOB_BACKOFF, repeat: { every: 300000 }, diff --git a/src/modules/invoices/queue/invoice.processor.ts b/src/modules/invoices/queue/invoice.processor.ts index 8b4d9df..68ab03c 100644 --- a/src/modules/invoices/queue/invoice.processor.ts +++ b/src/modules/invoices/queue/invoice.processor.ts @@ -8,6 +8,7 @@ import { DataSource, QueryRunner } from "typeorm"; import { WorkerProcessor } from "../../../common/queues/worker.processor"; import { NotificationsService } from "../../notifications/providers/notifications.service"; +import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum"; import { INVOICE } from "../constants"; import { Invoice } from "../entities/invoice.entity"; import { InvoiceStatus } from "../enums/invoice-status.enum"; @@ -77,7 +78,7 @@ export class InvoiceProcessor extends WorkerProcessor { private async fetchInvoice(invoiceId: string, queryRunner: QueryRunner) { const invoice = await queryRunner.manager.findOne(Invoice, { where: { id: invoiceId }, - relations: { user: true }, + relations: { user: true, items: { subscriptionPlan: true } }, }); if (!invoice) throw new Error(`Invoice not found: ${invoiceId}`); @@ -86,15 +87,34 @@ export class InvoiceProcessor extends WorkerProcessor { //********************************** */ private isInvoiceOverdueForCancellation(invoice: Invoice): boolean { - const fiveDaysAfterDueDate = dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day"); - return dayjs().isAfter(fiveDaysAfterDueDate); + const tenDaysAfterDueDate = dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day"); + return dayjs().isAfter(tenDaysAfterDueDate); } //********************************** */ private async cancelOverdueInvoice(invoice: Invoice, queryRunner: QueryRunner) { invoice.status = InvoiceStatus.CANCELLED; await queryRunner.manager.save(Invoice, invoice); - this.logger.log(`Invoice ${invoice.id} has been cancelled as it is more than 7 days overdue.`); + if (invoice.items[0]?.subscriptionPlan) { + this.logger.log( + `Invoice ${invoice.id} has been cancelled as it is more than 10 days overdue. Subscription Plan ${invoice.items[0].subscriptionPlan.id} has been cancelled.`, + ); + const userSubscription = invoice.items[0].subscriptionPlan; + userSubscription.status = SubscriptionStatus.INACTIVE; + + await queryRunner.manager.save(userSubscription); + + // await this.notificationService.createSubscriptionPlanCancelledNotification( + // invoice.user.id, + // { + // userPhone: invoice.user.phone, + // userEmail: invoice.user.email, + // subscriptionPlanId: invoice.items[0].subscriptionPlan.id, + // }, + // queryRunner, + // ); + } + this.logger.log(`Invoice ${invoice.id} has been cancelled as it is more than 10 days overdue.`); } //********************************** */ diff --git a/src/modules/subscriptions/enums/subscription-status.enum.ts b/src/modules/subscriptions/enums/subscription-status.enum.ts index 12f269e..6d86647 100755 --- a/src/modules/subscriptions/enums/subscription-status.enum.ts +++ b/src/modules/subscriptions/enums/subscription-status.enum.ts @@ -1,7 +1,6 @@ export enum SubscriptionStatus { ACTIVE = "ACTIVE", INACTIVE = "INACTIVE", - // PENDING = "PENDING", - CANCELED = "CANCELED", - EXPIRED = "EXPIRED", + // CANCELED = "CANCELED", + // EXPIRED = "EXPIRED", }