update: the invoice update by admin logic

This commit is contained in:
mahyargdz
2025-05-31 11:17:30 +03:30
parent aa695cc17c
commit cfaac752d4
@@ -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) { async createInvoiceAdmin(createDto: CreateInvoiceDto) {
const queryRunner = this.dataSource.createQueryRunner(); 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 (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_UPDATE);
if (updateDto.items) { if (updateDto.items) {
this.validateInvoiceItems(updateDto.items);
const invoiceItemsData = updateDto.items.map((item) => ({ const invoiceItemsData = updateDto.items.map((item) => ({
name: item.name, name: item.name,
count: item.count, count: item.count,
@@ -176,6 +154,9 @@ export class InvoicesService {
})); }));
// calculate total price and tax // calculate total price and tax
const totalPrice = invoiceItemsData.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0)); 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); const tax = totalPrice.mul(0.1);
invoice.totalPrice = totalPrice.add(tax); invoice.totalPrice = totalPrice.add(tax);
invoice.tax = tax; invoice.tax = tax;
@@ -194,6 +175,15 @@ export class InvoicesService {
invoice.user = user; 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 // Save the updated invoice
await queryRunner.manager.save(this.invoiceRepository.target, invoice); await queryRunner.manager.save(this.invoiceRepository.target, invoice);
@@ -678,34 +668,6 @@ export class InvoicesService {
return invoiceCount; 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) { async applyDiscount(invoiceId: string, discountCode: string, userId: string) {
const queryRunner = this.dataSource.createQueryRunner(); 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 // Add to queue for billing reminder
await this.invoiceQueue.add( await this.invoiceQueue.add(
INVOICE.REMINDER_JOB_NAME, 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}`); 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) { private async addNotifyAdminForSubscriptionPaymentToQueue(invoice: Invoice) {
await this.invoiceQueue.add( await this.invoiceQueue.add(
@@ -859,4 +840,79 @@ export class InvoicesService {
userEmail: user.email, 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;
}
} }