update: the invoice update by admin logic
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user