chore: add 10 percent tax to the invoice

This commit is contained in:
mahyargdz
2025-03-04 16:57:59 +03:30
parent 1b1c532488
commit 0af4719969
2 changed files with 148 additions and 158 deletions
+12 -13
View File
@@ -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);
// }
}
+136 -145
View File
@@ -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<boolean> {
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<boolean> {
// const usage = await this.usageDiscountRepository.findOne({
// where: {
// users: {
// id: userId,
// },
// discount: {
// id: discountId,
// },
// },
// });
// return !!usage;
// }
}