update: invoice processor and fix bug
This commit is contained in:
@@ -9,9 +9,10 @@ export function bullMqConfig(): SharedBullAsyncConfiguration {
|
|||||||
url: configService.getOrThrow<string>("REDIS_URI"),
|
url: configService.getOrThrow<string>("REDIS_URI"),
|
||||||
},
|
},
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
removeOnComplete: 1000,
|
removeOnComplete: 2000, // Consider lower values in high-volume production
|
||||||
removeOnFail: 5000,
|
removeOnFail: 5000, // Consider lower values to reduce Redis memory usage
|
||||||
attempts: 3,
|
attempts: 3, // Reasonable default, adjust based on job criticality
|
||||||
|
backoff: { type: "exponential", delay: 2000 }, // Consider adding for better retry behavior
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ export const INVOICE = Object.freeze({
|
|||||||
FINE_PERCENTAGE: 0.01, // 1% of the total price
|
FINE_PERCENTAGE: 0.01, // 1% of the total price
|
||||||
MAX_DAYS_AFTER_OVERDUE: 10,
|
MAX_DAYS_AFTER_OVERDUE: 10,
|
||||||
DUEDATE: 7,
|
DUEDATE: 7,
|
||||||
DAYS_BEFORE_OVERDUE: 6,
|
DAYS_BEFORE_OVERDUE: 3,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ export class InvoicesService {
|
|||||||
const totalPrice = invoiceItems.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0));
|
const totalPrice = invoiceItems.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0));
|
||||||
const tax = totalPrice.mul(0.1);
|
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, {
|
const invoice = queryRunner.manager.create(Invoice, {
|
||||||
user: { id: createDto.userId },
|
user: { id: createDto.userId },
|
||||||
@@ -90,13 +91,12 @@ export class InvoicesService {
|
|||||||
},
|
},
|
||||||
queryRunner,
|
queryRunner,
|
||||||
);
|
);
|
||||||
//TODO:remove the test delay
|
|
||||||
await this.invoiceQueue.add(
|
await this.invoiceQueue.add(
|
||||||
INVOICE.INVOICE_REMINDER_JOB_NAME,
|
INVOICE.INVOICE_REMINDER_JOB_NAME,
|
||||||
{ invoiceId: invoice.id },
|
{ invoiceId: invoice.id },
|
||||||
{
|
{
|
||||||
// delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()),
|
delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()),
|
||||||
delay: 900000, // 15 minutes in milliseconds
|
// delay: 1.5 * 60 * 1000, // 5 minutes in milliseconds
|
||||||
attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS,
|
attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS,
|
||||||
backoff: INVOICE.INVOICE_REMINDER_JOB_BACKOFF,
|
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.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED);
|
||||||
if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID);
|
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;
|
invoice.status = InvoiceStatus.EXPIRED;
|
||||||
await this.invoiceRepository.save(invoice);
|
await this.invoiceRepository.save(invoice);
|
||||||
throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE);
|
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.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED);
|
||||||
if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID);
|
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);
|
if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_APPROVED);
|
||||||
|
|
||||||
invoice.status = InvoiceStatus.WAIT_PAYMENT;
|
invoice.status = InvoiceStatus.WAIT_PAYMENT;
|
||||||
@@ -256,8 +262,7 @@ export class InvoicesService {
|
|||||||
INVOICE.INVOICE_REMINDER_JOB_NAME,
|
INVOICE.INVOICE_REMINDER_JOB_NAME,
|
||||||
{ invoiceId: invoice.id },
|
{ invoiceId: invoice.id },
|
||||||
{
|
{
|
||||||
// delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()),
|
delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()),
|
||||||
delay: 900000, // 15 minutes in milliseconds
|
|
||||||
attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS,
|
attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS,
|
||||||
backoff: INVOICE.INVOICE_REMINDER_JOB_BACKOFF,
|
backoff: INVOICE.INVOICE_REMINDER_JOB_BACKOFF,
|
||||||
repeat: { every: 300000 },
|
repeat: { every: 300000 },
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { DataSource, QueryRunner } from "typeorm";
|
|||||||
|
|
||||||
import { WorkerProcessor } from "../../../common/queues/worker.processor";
|
import { WorkerProcessor } from "../../../common/queues/worker.processor";
|
||||||
import { NotificationsService } from "../../notifications/providers/notifications.service";
|
import { NotificationsService } from "../../notifications/providers/notifications.service";
|
||||||
|
import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum";
|
||||||
import { INVOICE } from "../constants";
|
import { INVOICE } from "../constants";
|
||||||
import { Invoice } from "../entities/invoice.entity";
|
import { Invoice } from "../entities/invoice.entity";
|
||||||
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
||||||
@@ -77,7 +78,7 @@ export class InvoiceProcessor extends WorkerProcessor {
|
|||||||
private async fetchInvoice(invoiceId: string, queryRunner: QueryRunner) {
|
private async fetchInvoice(invoiceId: string, queryRunner: QueryRunner) {
|
||||||
const invoice = await queryRunner.manager.findOne(Invoice, {
|
const invoice = await queryRunner.manager.findOne(Invoice, {
|
||||||
where: { id: invoiceId },
|
where: { id: invoiceId },
|
||||||
relations: { user: true },
|
relations: { user: true, items: { subscriptionPlan: true } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!invoice) throw new Error(`Invoice not found: ${invoiceId}`);
|
if (!invoice) throw new Error(`Invoice not found: ${invoiceId}`);
|
||||||
@@ -86,15 +87,34 @@ export class InvoiceProcessor extends WorkerProcessor {
|
|||||||
//********************************** */
|
//********************************** */
|
||||||
|
|
||||||
private isInvoiceOverdueForCancellation(invoice: Invoice): boolean {
|
private isInvoiceOverdueForCancellation(invoice: Invoice): boolean {
|
||||||
const fiveDaysAfterDueDate = dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day");
|
const tenDaysAfterDueDate = dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day");
|
||||||
return dayjs().isAfter(fiveDaysAfterDueDate);
|
return dayjs().isAfter(tenDaysAfterDueDate);
|
||||||
}
|
}
|
||||||
//********************************** */
|
//********************************** */
|
||||||
|
|
||||||
private async cancelOverdueInvoice(invoice: Invoice, queryRunner: QueryRunner) {
|
private async cancelOverdueInvoice(invoice: Invoice, queryRunner: QueryRunner) {
|
||||||
invoice.status = InvoiceStatus.CANCELLED;
|
invoice.status = InvoiceStatus.CANCELLED;
|
||||||
await queryRunner.manager.save(Invoice, invoice);
|
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.`);
|
||||||
}
|
}
|
||||||
//********************************** */
|
//********************************** */
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export enum SubscriptionStatus {
|
export enum SubscriptionStatus {
|
||||||
ACTIVE = "ACTIVE",
|
ACTIVE = "ACTIVE",
|
||||||
INACTIVE = "INACTIVE",
|
INACTIVE = "INACTIVE",
|
||||||
// PENDING = "PENDING",
|
// CANCELED = "CANCELED",
|
||||||
CANCELED = "CANCELED",
|
// EXPIRED = "EXPIRED",
|
||||||
EXPIRED = "EXPIRED",
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user