chore: add 10 percent tax to the invoice
This commit is contained in:
@@ -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);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user