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 { Body, Controller, Get, Param, Patch, Post, Query } from "@nestjs/common";
|
||||||
import { ApiOperation } from "@nestjs/swagger";
|
import { ApiOperation } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { ApplyDiscountDto } from "./DTO/apply-discount-invoice.dto";
|
|
||||||
import { CreateInvoiceDto } from "./DTO/create-invoice.dto";
|
import { CreateInvoiceDto } from "./DTO/create-invoice.dto";
|
||||||
import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "./DTO/invoices-search-query.dto";
|
import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "./DTO/invoices-search-query.dto";
|
||||||
import { InvoicesService } from "./providers/invoices.service";
|
import { InvoicesService } from "./providers/invoices.service";
|
||||||
@@ -58,21 +57,21 @@ export class InvoicesController {
|
|||||||
return this.invoiceService.approveInvoiceByUser(paramDto.id, userId, verifyOtpDto);
|
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" })
|
@ApiOperation({ summary: "pay invoice by user" })
|
||||||
@Post(":id/pay")
|
@Post(":id/pay")
|
||||||
async payInvoice(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
async payInvoice(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||||
return this.invoiceService.payInvoice(paramDto.id, userId);
|
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 Decimal from "decimal.js";
|
||||||
import { Between, DataSource, QueryRunner } from "typeorm";
|
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 { 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 { NotificationsService } from "../../notifications/providers/notifications.service";
|
||||||
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
|
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
|
||||||
import { UserSubscription } from "../../subscriptions/entities/user-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 { SmsService } from "../../utils/providers/sms.service";
|
||||||
import { Wallet } from "../../wallets/entities/wallet.entity";
|
import { Wallet } from "../../wallets/entities/wallet.entity";
|
||||||
import { WalletsService } from "../../wallets/providers/wallets.service";
|
import { WalletsService } from "../../wallets/providers/wallets.service";
|
||||||
import { ApplyDiscountDto } from "../DTO/apply-discount-invoice.dto";
|
|
||||||
import { CreateInvoiceDto } from "../DTO/create-invoice.dto";
|
import { CreateInvoiceDto } from "../DTO/create-invoice.dto";
|
||||||
import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "../DTO/invoices-search-query.dto";
|
import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "../DTO/invoices-search-query.dto";
|
||||||
import { Invoice } from "../entities/invoice.entity";
|
import { Invoice } from "../entities/invoice.entity";
|
||||||
@@ -34,8 +31,6 @@ export class InvoicesService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly notificationsService: NotificationsService,
|
private readonly notificationsService: NotificationsService,
|
||||||
private readonly invoiceRepository: InvoicesRepository,
|
private readonly invoiceRepository: InvoicesRepository,
|
||||||
private readonly discountRepository: DiscountRepository,
|
|
||||||
private readonly usageDiscountRepository: UsageDiscountRepository,
|
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
private readonly walletsService: WalletsService,
|
private readonly walletsService: WalletsService,
|
||||||
private readonly otpService: OTPService,
|
private readonly otpService: OTPService,
|
||||||
@@ -68,7 +63,7 @@ export class InvoicesService {
|
|||||||
|
|
||||||
const invoice = queryRunner.manager.create(Invoice, {
|
const invoice = queryRunner.manager.create(Invoice, {
|
||||||
user: { id: createDto.userId },
|
user: { id: createDto.userId },
|
||||||
totalPrice: totalPrice.toNumber(),
|
totalPrice: totalPrice.add(tax).toNumber(),
|
||||||
items: invoiceItems,
|
items: invoiceItems,
|
||||||
tax: tax.toNumber(),
|
tax: tax.toNumber(),
|
||||||
dueDate,
|
dueDate,
|
||||||
@@ -173,33 +168,31 @@ export class InvoicesService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
///********************************** */
|
///********************************** */
|
||||||
|
async createInvoiceForSubscription(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner) {
|
||||||
async createInvoiceForSubscription(
|
const invoiceItem = {
|
||||||
user: User,
|
name: plan.service.name,
|
||||||
plan: SubscriptionPlan,
|
count: 1,
|
||||||
userSub: UserSubscription,
|
unitPrice: plan.price,
|
||||||
dueDate: Date,
|
discount: 0,
|
||||||
queryRunner: QueryRunner,
|
subscriptionPlan: userSub,
|
||||||
) {
|
|
||||||
const invoiceItems = [
|
|
||||||
{
|
|
||||||
name: plan.service.name,
|
|
||||||
count: 1,
|
|
||||||
unitPrice: plan.price,
|
|
||||||
discount: 0,
|
|
||||||
subscriptionPlan: userSub,
|
|
||||||
totalPrice: plan.price,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const invoice = queryRunner.manager.create(Invoice, {
|
|
||||||
totalPrice: plan.price,
|
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,
|
user,
|
||||||
|
totalPrice: totalPrice,
|
||||||
|
tax: taxAmount.toNumber(),
|
||||||
status: InvoiceStatus.WAIT_PAYMENT,
|
status: InvoiceStatus.WAIT_PAYMENT,
|
||||||
paidAt: new Date(),
|
paidAt: new Date(),
|
||||||
dueDate,
|
dueDate,
|
||||||
items: invoiceItems,
|
items: [invoiceItem],
|
||||||
});
|
});
|
||||||
await queryRunner.manager.save(Invoice, invoice);
|
|
||||||
|
await qryRnr.manager.save(Invoice, invoice);
|
||||||
|
|
||||||
await this.notificationsService.createInvoiceCreationNotification(
|
await this.notificationsService.createInvoiceCreationNotification(
|
||||||
user.id,
|
user.id,
|
||||||
@@ -207,14 +200,14 @@ export class InvoicesService {
|
|||||||
invoiceId: invoice.numericId.toString(),
|
invoiceId: invoice.numericId.toString(),
|
||||||
dueDate: invoice.dueDate,
|
dueDate: invoice.dueDate,
|
||||||
createDate: invoice.createdAt,
|
createDate: invoice.createdAt,
|
||||||
price: new Decimal(invoice.totalPrice).toNumber(),
|
price: totalPrice.toNumber(),
|
||||||
userPhone: user.phone,
|
userPhone: user.phone,
|
||||||
userEmail: user.email,
|
userEmail: user.email,
|
||||||
items: invoiceItems.map((item) => item.name).join(", "),
|
items: plan.service.name,
|
||||||
},
|
},
|
||||||
queryRunner,
|
qryRnr,
|
||||||
);
|
);
|
||||||
//
|
|
||||||
return invoice;
|
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() {
|
async getInvoicesCount() {
|
||||||
const count = await this.invoiceRepository.count({
|
const count = await this.invoiceRepository.count({
|
||||||
where: {
|
where: {
|
||||||
@@ -485,17 +378,115 @@ export class InvoicesService {
|
|||||||
|
|
||||||
//*********************************** */
|
//*********************************** */
|
||||||
|
|
||||||
private async hasUserUsedDiscount(userId: string, discountId: string): Promise<boolean> {
|
// async applyDiscount(invoiceId: string, applyDiscountDto: ApplyDiscountDto, userId: string) {
|
||||||
const usage = await this.usageDiscountRepository.findOne({
|
// const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId, status: InvoiceStatus.PENDING });
|
||||||
where: {
|
// if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
|
||||||
users: {
|
|
||||||
id: userId,
|
// const discount = await this.discountRepository.findOne({
|
||||||
},
|
// where: { code: applyDiscountDto.discountCode, isActive: true },
|
||||||
discount: {
|
// relations: ["subscriptionPlans", "users"],
|
||||||
id: discountId,
|
// });
|
||||||
},
|
// if (!discount) {
|
||||||
},
|
// throw new BadRequestException(DiscountMessage.NOT_FOUND);
|
||||||
});
|
// }
|
||||||
return !!usage;
|
|
||||||
}
|
// 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