refactor: the invoice service
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user