From 0af4719969096a63cd93945f08780adb616e564f Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Tue, 4 Mar 2025 16:57:59 +0330 Subject: [PATCH] chore: add 10 percent tax to the invoice --- src/modules/invoices/invoices.controller.ts | 25 +- .../invoices/providers/invoices.service.ts | 281 +++++++++--------- 2 files changed, 148 insertions(+), 158 deletions(-) diff --git a/src/modules/invoices/invoices.controller.ts b/src/modules/invoices/invoices.controller.ts index 84e277e..bccb7dd 100755 --- a/src/modules/invoices/invoices.controller.ts +++ b/src/modules/invoices/invoices.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Get, Param, Patch, Post, Query } from "@nestjs/common"; import { ApiOperation } from "@nestjs/swagger"; -import { ApplyDiscountDto } from "./DTO/apply-discount-invoice.dto"; import { CreateInvoiceDto } from "./DTO/create-invoice.dto"; import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "./DTO/invoices-search-query.dto"; import { InvoicesService } from "./providers/invoices.service"; @@ -58,21 +57,21 @@ export class InvoicesController { return this.invoiceService.approveInvoiceByUser(paramDto.id, userId, verifyOtpDto); } - @ApiOperation({ summary: "apply discount on invoice by user" }) - @Post(":id/apply-discount") - async applyDiscount(@Param() paramDto: ParamDto, @Body() applyDiscountDto: ApplyDiscountDto, @UserDec("id") userId: string) { - return this.invoiceService.applyDiscount(paramDto.id, applyDiscountDto, userId); - } - - @ApiOperation({ summary: "cancel discount by user" }) - @Post(":id/cancel-discount") - cancelDiscount(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { - return this.invoiceService.cancelDiscount(paramDto.id, userId); - } - @ApiOperation({ summary: "pay invoice by user" }) @Post(":id/pay") async payInvoice(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { return this.invoiceService.payInvoice(paramDto.id, userId); } + + // @ApiOperation({ summary: "apply discount on invoice by user" }) + // @Post(":id/apply-discount") + // async applyDiscount(@Param() paramDto: ParamDto, @Body() applyDiscountDto: ApplyDiscountDto, @UserDec("id") userId: string) { + // return this.invoiceService.applyDiscount(paramDto.id, applyDiscountDto, userId); + // } + + // @ApiOperation({ summary: "cancel discount by user" }) + // @Post(":id/cancel-discount") + // cancelDiscount(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { + // return this.invoiceService.cancelDiscount(paramDto.id, userId); + // } } diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index a60a08e..46dfa25 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -5,10 +5,8 @@ import dayjs from "dayjs"; import Decimal from "decimal.js"; import { Between, DataSource, QueryRunner } from "typeorm"; -import { AuthMessage, DiscountMessage, InvoiceMessage, WalletMessage } from "../../../common/enums/message.enum"; +import { AuthMessage, InvoiceMessage, WalletMessage } from "../../../common/enums/message.enum"; import { VerifyOtpWithUserId } from "../../auth/DTO/verify-otp.dto"; -import { DiscountRepository } from "../../discounts/repositories/discount.repository"; -import { UsageDiscountRepository } from "../../discounts/repositories/usage-discount.repository"; import { NotificationsService } from "../../notifications/providers/notifications.service"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; import { UserSubscription } from "../../subscriptions/entities/user-subscription.entity"; @@ -20,7 +18,6 @@ import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { SmsService } from "../../utils/providers/sms.service"; import { Wallet } from "../../wallets/entities/wallet.entity"; import { WalletsService } from "../../wallets/providers/wallets.service"; -import { ApplyDiscountDto } from "../DTO/apply-discount-invoice.dto"; import { CreateInvoiceDto } from "../DTO/create-invoice.dto"; import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "../DTO/invoices-search-query.dto"; import { Invoice } from "../entities/invoice.entity"; @@ -34,8 +31,6 @@ export class InvoicesService { constructor( private readonly notificationsService: NotificationsService, private readonly invoiceRepository: InvoicesRepository, - private readonly discountRepository: DiscountRepository, - private readonly usageDiscountRepository: UsageDiscountRepository, private readonly usersService: UsersService, private readonly walletsService: WalletsService, private readonly otpService: OTPService, @@ -68,7 +63,7 @@ export class InvoicesService { const invoice = queryRunner.manager.create(Invoice, { user: { id: createDto.userId }, - totalPrice: totalPrice.toNumber(), + totalPrice: totalPrice.add(tax).toNumber(), items: invoiceItems, tax: tax.toNumber(), dueDate, @@ -173,33 +168,31 @@ export class InvoicesService { }; } ///********************************** */ - - async createInvoiceForSubscription( - user: User, - plan: SubscriptionPlan, - userSub: UserSubscription, - dueDate: Date, - queryRunner: QueryRunner, - ) { - const invoiceItems = [ - { - name: plan.service.name, - count: 1, - unitPrice: plan.price, - discount: 0, - subscriptionPlan: userSub, - totalPrice: plan.price, - }, - ]; - const invoice = queryRunner.manager.create(Invoice, { + async createInvoiceForSubscription(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner) { + const invoiceItem = { + name: plan.service.name, + count: 1, + unitPrice: plan.price, + discount: 0, + subscriptionPlan: userSub, totalPrice: plan.price, + }; + + const basePrice = plan.price; + const taxAmount = new Decimal(basePrice).mul(0.1); + const totalPrice = new Decimal(basePrice).add(taxAmount); + + const invoice = qryRnr.manager.create(Invoice, { user, + totalPrice: totalPrice, + tax: taxAmount.toNumber(), status: InvoiceStatus.WAIT_PAYMENT, paidAt: new Date(), dueDate, - items: invoiceItems, + items: [invoiceItem], }); - await queryRunner.manager.save(Invoice, invoice); + + await qryRnr.manager.save(Invoice, invoice); await this.notificationsService.createInvoiceCreationNotification( user.id, @@ -207,14 +200,14 @@ export class InvoicesService { invoiceId: invoice.numericId.toString(), dueDate: invoice.dueDate, createDate: invoice.createdAt, - price: new Decimal(invoice.totalPrice).toNumber(), + price: totalPrice.toNumber(), userPhone: user.phone, userEmail: user.email, - items: invoiceItems.map((item) => item.name).join(", "), + items: plan.service.name, }, - queryRunner, + qryRnr, ); - // + return invoice; } @@ -363,106 +356,6 @@ export class InvoicesService { //*********************************** */ - async applyDiscount(invoiceId: string, applyDiscountDto: ApplyDiscountDto, userId: string) { - const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId, status: InvoiceStatus.PENDING }); - if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); - - const discount = await this.discountRepository.findOne({ - where: { code: applyDiscountDto.discountCode, isActive: true }, - relations: ["subscriptionPlans", "users"], - }); - if (!discount) { - throw new BadRequestException(DiscountMessage.NOT_FOUND); - } - - const hasUsedDiscount = await this.hasUserUsedDiscount(userId, discount.id); - if (hasUsedDiscount) throw new BadRequestException(DiscountMessage.ALREADY_USED); - - if (discount.subscriptionPlans && discount.subscriptionPlans.length > 0) { - const isApplicable = invoice.items.some((item) => { - const subscriptionPlan = item.subscriptionPlan; - if (!subscriptionPlan) return false; - return discount.subscriptionPlans.some((plan) => plan.id === subscriptionPlan.id); - }); - if (!isApplicable) { - throw new BadRequestException(DiscountMessage.CAN_NOT_APPLICABLE); - } - } - - if (discount.users && discount.users.length > 0) { - if (!discount.users.some((user) => user.id === userId)) { - throw new BadRequestException(DiscountMessage.CAN_NOT_APPLICABLE); - } - } - - const discountAmount = - discount.calculationType === "PERCENTAGE" - ? new Decimal(invoice.totalPrice).mul(discount.amount).div(100) - : new Decimal(discount.amount); - - invoice.totalPrice = new Decimal(invoice.totalPrice).minus(discountAmount); - await this.invoiceRepository.save(invoice); - - const usageDiscount = this.usageDiscountRepository.create({ - discount, - users: [{ id: userId }], - usageDate: new Date(), - }); - - await this.usageDiscountRepository.save(usageDiscount); - - return { - message: InvoiceMessage.DISCOUNT_APPLIED, - invoice, - }; - } - - //*********************************** */ - - async cancelDiscount(invoiceId: string, userId: string) { - // Find the invoice by ID and ensure it is in a PENDING status - const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId, status: InvoiceStatus.PENDING }); - if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); - - // Find the usage discount associated with the user and invoice - const usage = await this.usageDiscountRepository.findOne({ - where: { - users: { id: userId }, - discount: { invoice: { id: invoice.id } }, - }, - relations: { - discount: true, - users: true, - }, - }); - console.log(usage); - if (!usage) throw new BadRequestException(DiscountMessage.NOT_FOUND); - - // Find the discount associated with the usage - const discount = await this.discountRepository.findOneBy({ id: usage.discount.id }); - if (!discount) throw new BadRequestException(DiscountMessage.NOT_FOUND); - - // Calculate the discount amount to be removed from the invoice - const discountAmount = - discount.calculationType === "PERCENTAGE" - ? new Decimal(invoice.totalPrice).mul(discount.amount).div(100) - : new Decimal(discount.amount); - - // Update the invoice's total price by adding the discount amount (effectively removing the discount) - invoice.totalPrice = new Decimal(invoice.totalPrice).add(discountAmount); - await this.invoiceRepository.save(invoice); - - // Delete the usage discount record - await this.usageDiscountRepository.delete(usage.id); - - return { - message: InvoiceMessage.DISCOUNT_CANCELLED, - invoice, - }; - } - - //*********************************** */ - async getInvoicesCount() { const count = await this.invoiceRepository.count({ where: { @@ -485,17 +378,115 @@ export class InvoicesService { //*********************************** */ - private async hasUserUsedDiscount(userId: string, discountId: string): Promise { - const usage = await this.usageDiscountRepository.findOne({ - where: { - users: { - id: userId, - }, - discount: { - id: discountId, - }, - }, - }); - return !!usage; - } + // async applyDiscount(invoiceId: string, applyDiscountDto: ApplyDiscountDto, userId: string) { + // const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId, status: InvoiceStatus.PENDING }); + // if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); + + // const discount = await this.discountRepository.findOne({ + // where: { code: applyDiscountDto.discountCode, isActive: true }, + // relations: ["subscriptionPlans", "users"], + // }); + // if (!discount) { + // throw new BadRequestException(DiscountMessage.NOT_FOUND); + // } + + // const hasUsedDiscount = await this.hasUserUsedDiscount(userId, discount.id); + // if (hasUsedDiscount) throw new BadRequestException(DiscountMessage.ALREADY_USED); + + // if (discount.subscriptionPlans && discount.subscriptionPlans.length > 0) { + // const isApplicable = invoice.items.some((item) => { + // const subscriptionPlan = item.subscriptionPlan; + // if (!subscriptionPlan) return false; + // return discount.subscriptionPlans.some((plan) => plan.id === subscriptionPlan.id); + // }); + // if (!isApplicable) { + // throw new BadRequestException(DiscountMessage.CAN_NOT_APPLICABLE); + // } + // } + + // if (discount.users && discount.users.length > 0) { + // if (!discount.users.some((user) => user.id === userId)) { + // throw new BadRequestException(DiscountMessage.CAN_NOT_APPLICABLE); + // } + // } + + // const discountAmount = + // discount.calculationType === "PERCENTAGE" + // ? new Decimal(invoice.totalPrice).mul(discount.amount).div(100) + // : new Decimal(discount.amount); + + // invoice.totalPrice = new Decimal(invoice.totalPrice).minus(discountAmount); + // await this.invoiceRepository.save(invoice); + + // const usageDiscount = this.usageDiscountRepository.create({ + // discount, + // users: [{ id: userId }], + // usageDate: new Date(), + // }); + + // await this.usageDiscountRepository.save(usageDiscount); + + // return { + // message: InvoiceMessage.DISCOUNT_APPLIED, + // invoice, + // }; + // } + + //*********************************** */ + + // async cancelDiscount(invoiceId: string, userId: string) { + // // Find the invoice by ID and ensure it is in a PENDING status + // const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId, status: InvoiceStatus.PENDING }); + // if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); + + // // Find the usage discount associated with the user and invoice + // const usage = await this.usageDiscountRepository.findOne({ + // where: { + // users: { id: userId }, + // discount: { invoice: { id: invoice.id } }, + // }, + // relations: { + // discount: true, + // users: true, + // }, + // }); + // console.log(usage); + // if (!usage) throw new BadRequestException(DiscountMessage.NOT_FOUND); + + // // Find the discount associated with the usage + // const discount = await this.discountRepository.findOneBy({ id: usage.discount.id }); + // if (!discount) throw new BadRequestException(DiscountMessage.NOT_FOUND); + + // // Calculate the discount amount to be removed from the invoice + // const discountAmount = + // discount.calculationType === "PERCENTAGE" + // ? new Decimal(invoice.totalPrice).mul(discount.amount).div(100) + // : new Decimal(discount.amount); + + // // Update the invoice's total price by adding the discount amount (effectively removing the discount) + // invoice.totalPrice = new Decimal(invoice.totalPrice).add(discountAmount); + // await this.invoiceRepository.save(invoice); + + // // Delete the usage discount record + // await this.usageDiscountRepository.delete(usage.id); + + // return { + // message: InvoiceMessage.DISCOUNT_CANCELLED, + // invoice, + // }; + // } + + // private async hasUserUsedDiscount(userId: string, discountId: string): Promise { + // const usage = await this.usageDiscountRepository.findOne({ + // where: { + // users: { + // id: userId, + // }, + // discount: { + // id: discountId, + // }, + // }, + // }); + // return !!usage; + // } }