refactor: the invoice service

This commit is contained in:
mahyargdz
2025-04-19 16:28:39 +03:30
parent 1567d61aca
commit db9fff066b
2 changed files with 84 additions and 45 deletions
+12 -2
View File
@@ -1,10 +1,20 @@
export const DISCOUNT = Object.freeze({ export const DISCOUNT = Object.freeze({
// Queue names
DISCOUNT_QUEUE_NAME: "discount", DISCOUNT_QUEUE_NAME: "discount",
// Job names
DISCOUNT_DEACTIVATE_JOB_NAME: "deactivateDiscount", DISCOUNT_DEACTIVATE_JOB_NAME: "deactivateDiscount",
//
// Job configurations
DISCOUNT_DEACTIVATE_JOB_PRIORITY: 1, // high priority DISCOUNT_DEACTIVATE_JOB_PRIORITY: 1, // high priority
DISCOUNT_DEACTIVATE_JOB_ATTEMPTS: 3, // 3 times DISCOUNT_DEACTIVATE_JOB_ATTEMPTS: 3, // 3 times
DISCOUNT_DEACTIVATE_JOB_BACKOFF: 5 * 1000, // ms DISCOUNT_DEACTIVATE_JOB_BACKOFF: 5 * 1000, // ms
DISCOUNT_DEACTIVATE_JOB_TIMEOUT: 10000, // timeout after 10 seconds DISCOUNT_DEACTIVATE_JOB_TIMEOUT: 10000, // timeout after 10 seconds
//
// Discount deactivation messages
DISCOUNT_DEACTIVATION_SUCCESS: "Discount successfully deactivated",
DISCOUNT_DEACTIVATION_ERROR: "Failed to deactivate discount",
// Discount application types
DISCOUNT_APPLICATION_DIRECT: "DIRECT",
}); });
@@ -114,7 +114,6 @@ export class InvoicesService {
//********************************** */ //********************************** */
//TODO: fix this
async updateInvoiceAdmin(invoiceId: string, updateDto: UpdateInvoiceDto) { async updateInvoiceAdmin(invoiceId: string, updateDto: UpdateInvoiceDto) {
const queryRunner = this.dataSource.createQueryRunner(); const queryRunner = this.dataSource.createQueryRunner();
@@ -158,6 +157,16 @@ export class InvoicesService {
const user = await this.usersService.findOneByIdWithQueryRunner(updateDto.userId, queryRunner); const user = await this.usersService.findOneByIdWithQueryRunner(updateDto.userId, queryRunner);
invoice.user = user; invoice.user = user;
} }
// Save the updated invoice
await queryRunner.manager.save(this.invoiceRepository.target, invoice);
await queryRunner.commitTransaction();
return {
message: InvoiceMessage.INVOICE_UPDATED,
invoice,
};
} catch (error) { } catch (error) {
await queryRunner.rollbackTransaction(); await queryRunner.rollbackTransaction();
throw error; throw error;
@@ -168,39 +177,48 @@ export class InvoicesService {
//********************************** */ //********************************** */
async approveInvoiceRequest(invoiceId: string, userId: string) { async approveInvoiceRequest(invoiceId: string, userId: string) {
const { user } = await this.usersService.findOneById(userId); const queryRunner = this.dataSource.createQueryRunner();
const invoice = await this.invoiceRepository.findOne({ where: { id: invoiceId, user: { id: userId } }, relations: { items: true } }); try {
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); await queryRunner.connect();
await queryRunner.startTransaction();
if (invoice.status === InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID);
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); const invoice = await this.validateInvoiceForApproval(invoiceId, userId, queryRunner);
const existCode = await this.otpService.checkExistOtp(user.phone, "INVOICE_VERIFY");
if (existCode) {
return {
message: AuthMessage.OTP_ALREADY_SENT,
ttlSecond: existCode,
};
}
// Generate and send OTP
const otpCode = await this.otpService.generateAndSetInCache(user.phone, "INVOICE_VERIFY");
const items = invoice.items.map((item) => item.name).join(", ");
await this.smsService.sendInvoiceVerifyCode(
user.phone,
otpCode,
invoice.numericId,
new Decimal(invoice.totalPrice).toNumber(),
items,
);
await queryRunner.commitTransaction();
const existCode = await this.otpService.checkExistOtp(user.phone, "INVOICE_VERIFY");
if (existCode) {
return { return {
message: AuthMessage.OTP_ALREADY_SENT, message: AuthMessage.OTP_SENT,
ttlSecond: existCode, otpCode,
}; };
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
} }
const otpCode = await this.otpService.generateAndSetInCache(user.phone, "INVOICE_VERIFY");
const items = invoice.items.map((item) => item.name).join(", ");
//
await this.smsService.sendInvoiceVerifyCode(user.phone, otpCode, invoice.numericId, new Decimal(invoice.totalPrice).toNumber(), items);
this.logger.debug(`OTP sent to ${user.phone}: ${otpCode}`);
return {
message: AuthMessage.OTP_SENT,
otpCode,
};
} }
//********************************** */ //********************************** */
@@ -213,27 +231,12 @@ export class InvoicesService {
await queryRunner.startTransaction(); await queryRunner.startTransaction();
const { code } = verifyOtpDto; const { code } = verifyOtpDto;
const invoice = await queryRunner.manager.findOne(Invoice, {
where: { id: invoiceId, user: { id: userId } },
relations: { items: true },
});
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner); const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
const isValid = await this.otpService.verifyOtp(user.phone, code, "INVOICE_VERIFY"); const isValid = await this.otpService.verifyOtp(user.phone, code, "INVOICE_VERIFY");
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
if (invoice.status === InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); const invoice = await this.validateInvoiceForApproval(invoiceId, userId, queryRunner);
if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID);
//
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; invoice.status = InvoiceStatus.WAIT_PAYMENT;
await queryRunner.manager.save(Invoice, invoice); await queryRunner.manager.save(Invoice, invoice);
@@ -267,7 +270,33 @@ export class InvoicesService {
await queryRunner.release(); await queryRunner.release();
} }
} }
//********************************** */
private async validateInvoiceForApproval(invoiceId: string, userId: string, queryRunner: QueryRunner): Promise<Invoice> {
const invoice = await queryRunner.manager.findOne(Invoice, {
where: { id: invoiceId, user: { id: userId } },
relations: { items: true },
});
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
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(dayjs(invoice.dueDate).add(INVOICE.MAX_DAYS_AFTER_OVERDUE, "day"))) {
invoice.status = InvoiceStatus.EXPIRED;
await queryRunner.manager.save(Invoice, invoice);
throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE);
}
if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_APPROVED);
return invoice;
}
///********************************** */ ///********************************** */
async createInvoiceForSubscription(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner) { async createInvoiceForSubscription(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner) {
const discount = plan.directDiscount; const discount = plan.directDiscount;
const originalPrice = plan.originalPrice || plan.price; const originalPrice = plan.originalPrice || plan.price;