chore: apply discount on plan

This commit is contained in:
Matin
2025-02-18 10:10:23 +03:30
parent 6014da7e73
commit ff96fd6c32
4 changed files with 66 additions and 25 deletions
+1
View File
@@ -358,6 +358,7 @@ export const enum LearningMessage {
} }
export const enum DiscountMessage { export const enum DiscountMessage {
CAN_NOT_APPLICABLE = "این کد تخفیف برای این فاکتور قابل اعمال نیست",
TYPE_REQUIRED = "وارد کردن نوع تخفیف الزامی است", TYPE_REQUIRED = "وارد کردن نوع تخفیف الزامی است",
TYPE_INVALID = "نوع تخفیف نامعتبر است", TYPE_INVALID = "نوع تخفیف نامعتبر است",
CALCULATION_TYPE_REQUIRED = "وارد کردن نوع محاسبه تخفیف الزامی است", CALCULATION_TYPE_REQUIRED = "وارد کردن نوع محاسبه تخفیف الزامی است",
@@ -59,9 +59,4 @@ export class InvoicesController {
async applyDiscount(@Param() paramDto: ParamDto, @Body() applyDiscountDto: ApplyDiscountDto) { async applyDiscount(@Param() paramDto: ParamDto, @Body() applyDiscountDto: ApplyDiscountDto) {
return this.invoiceService.applyDiscount(paramDto.id, applyDiscountDto); return this.invoiceService.applyDiscount(paramDto.id, applyDiscountDto);
} }
@Get(":id/final-price")
async getFinalPrice(@Param() paramDto: ParamDto) {
return this.invoiceService.calculateFinalPrice(paramDto.id);
}
} }
@@ -4,7 +4,7 @@ import dayjs from "dayjs";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
import { QueryRunner } from "typeorm"; import { QueryRunner } from "typeorm";
import { InvoiceMessage } from "../../../common/enums/message.enum"; import { DiscountMessage, InvoiceMessage } from "../../../common/enums/message.enum";
import { DiscountRepository } from "../../discounts/repositories/discount.repository"; import { DiscountRepository } from "../../discounts/repositories/discount.repository";
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { PaginationUtils } from "../../utils/providers/pagination.utils";
@@ -207,7 +207,7 @@ export class InvoicesService {
relations: ["subscriptionPlans", "users"], relations: ["subscriptionPlans", "users"],
}); });
if (!discount) { if (!discount) {
throw new BadRequestException(`Discount with code ${applyDiscountDto.discountCode} not found or inactive`); throw new BadRequestException(DiscountMessage.NOT_FOUND);
} }
if (discount.subscriptionPlans && discount.subscriptionPlans.length > 0) { if (discount.subscriptionPlans && discount.subscriptionPlans.length > 0) {
@@ -217,7 +217,7 @@ export class InvoicesService {
return discount.subscriptionPlans.some((plan) => plan.id === subscriptionPlan.id); return discount.subscriptionPlans.some((plan) => plan.id === subscriptionPlan.id);
}); });
if (!isApplicable) { if (!isApplicable) {
throw new BadRequestException("This discount is not applicable to any subscription plan in the invoice."); throw new BadRequestException(DiscountMessage.CAN_NOT_APPLICABLE);
} }
} }
@@ -233,18 +233,18 @@ export class InvoicesService {
return { invoice }; return { invoice };
} }
async calculateFinalPrice(invoiceId: string): Promise<Decimal> { // async calculateFinalPrice(invoiceId: string): Promise<Decimal> {
const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId }); // const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId });
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); // if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
let discountValue = new Decimal(0); // let discountValue = new Decimal(0);
if (invoice.discount) { // if (invoice.discount) {
if (invoice.discount.calculationType === "PERCENTAGE") { // if (invoice.discount.calculationType === "PERCENTAGE") {
discountValue = invoice.totalPrice.mul(invoice.discount.amount).div(100); // discountValue = invoice.totalPrice.mul(invoice.discount.amount).div(100);
} else if (invoice.discount.calculationType === "FIXED") { // } else if (invoice.discount.calculationType === "FIXED") {
discountValue = new Decimal(invoice.discount.amount); // discountValue = new Decimal(invoice.discount.amount);
} // }
} // }
return invoice.totalPrice.minus(discountValue); // return invoice.totalPrice.minus(discountValue);
} // }
} }
@@ -5,6 +5,7 @@ import { DataSource, In } from "typeorm";
import { ServiceMessage, SubscriptionMessage, WalletMessage } from "../../../common/enums/message.enum"; import { ServiceMessage, SubscriptionMessage, WalletMessage } from "../../../common/enums/message.enum";
import { DanakServicesService } from "../../danak-services/providers/danak-services.service"; import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
import { DiscountCalculationType } from "../../discounts/enums/discount-type.enum";
import { InvoicesService } from "../../invoices/providers/invoices.service"; import { InvoicesService } from "../../invoices/providers/invoices.service";
import { UsersService } from "../../users/providers/users.service"; import { UsersService } from "../../users/providers/users.service";
import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { PaginationUtils } from "../../utils/providers/pagination.utils";
@@ -146,6 +147,8 @@ export class SubscriptionsService {
.createQueryBuilder("subscription") .createQueryBuilder("subscription")
.leftJoin("subscription.service", "service") .leftJoin("subscription.service", "service")
.addSelect(["service.id"]) .addSelect(["service.id"])
.leftJoin("subscription.discounts", "discount")
.addSelect(["discount.calculationType", "discount.amount", "discount.startDate", "discount.endDate", "discount.isActive"])
.where("service.id = :serviceId", { serviceId }); .where("service.id = :serviceId", { serviceId });
if (queryDto.q) { if (queryDto.q) {
@@ -158,9 +161,31 @@ export class SubscriptionsService {
const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
const transformedSubscriptions = subscriptions.map((subscription) => {
let price = new Decimal(subscription.price);
if (subscription.discounts && subscription.discounts.length > 0) {
subscription.discounts.forEach((discount) => {
if (discount.isActive && new Date() >= discount.startDate && new Date() <= discount.endDate) {
if (discount.calculationType === DiscountCalculationType.PERCENTAGE) {
const discountAmount = price.mul(discount.amount).div(100);
price = price.sub(discountAmount);
} else if (discount.calculationType === DiscountCalculationType.FIXED) {
price = price.sub(discount.amount);
}
}
});
}
return {
...subscription,
finalPrice: price.toNumber(),
};
});
return { return {
service, service,
subscriptions, subscriptions: transformedSubscriptions,
count, count,
pagination: true, pagination: true,
}; };
@@ -193,6 +218,7 @@ export class SubscriptionsService {
where: { id: subscribeDto.planId, service: { id: serviceId } }, where: { id: subscribeDto.planId, service: { id: serviceId } },
relations: { relations: {
service: true, service: true,
discounts: true,
}, },
}); });
@@ -208,15 +234,34 @@ export class SubscriptionsService {
const userWallet = await this.walletsService.getWalletByUserId(user.id, queryRunner); const userWallet = await this.walletsService.getWalletByUserId(user.id, queryRunner);
if (userWallet.balance < plan.price) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE); let finalPrice = new Decimal(plan.price);
if (plan.discounts && plan.discounts.length > 0) {
plan.discounts.forEach((discount) => {
if (discount.isActive && new Date() >= discount.startDate && new Date() <= discount.endDate) {
if (discount.calculationType === DiscountCalculationType.PERCENTAGE) {
const discountAmount = finalPrice.mul(discount.amount).div(100);
finalPrice = finalPrice.sub(discountAmount);
} else if (discount.calculationType === DiscountCalculationType.FIXED) {
finalPrice = finalPrice.sub(discount.amount);
}
}
});
}
plan.price = finalPrice;
if (new Decimal(userWallet.balance).lessThan(finalPrice)) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE);
// if (userWallet.balance < plan.price) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE);
// //
userWallet.balance = new Decimal(userWallet.balance).sub(plan.price); userWallet.balance = new Decimal(userWallet.balance).sub(finalPrice);
await queryRunner.manager.save(Wallet, userWallet); await queryRunner.manager.save(Wallet, userWallet);
// //
await this.walletsService.createSubscriptionTransaction(plan.price, userWallet.id, queryRunner); await this.walletsService.createSubscriptionTransaction(finalPrice, userWallet.id, queryRunner);
//TODO: need queue handling for notification //TODO: need queue handling for notification
const invoiceDueDate = new Date(userSubscription.endDate); const invoiceDueDate = new Date(userSubscription.endDate);
invoiceDueDate.setDate(userSubscription.endDate.getDate() - 3); invoiceDueDate.setDate(userSubscription.endDate.getDate() - 3);