chore: add update logic for discount

This commit is contained in:
mahyargdz
2025-04-19 11:05:06 +03:30
parent c42f43ee95
commit f9e44fc29a
4 changed files with 56 additions and 51 deletions
+1
View File
@@ -524,6 +524,7 @@ export const enum DiscountMessage {
END_DATE_MUST_BE_AFTER_NOW = "تاریخ پایان باید بعد از تاریخ فعلی باشد", END_DATE_MUST_BE_AFTER_NOW = "تاریخ پایان باید بعد از تاریخ فعلی باشد",
EACH_TARGET_PRODUCT_MUST_BE_A_UUID = "هر محصول باید یک UUID معتبر باشد", EACH_TARGET_PRODUCT_MUST_BE_A_UUID = "هر محصول باید یک UUID معتبر باشد",
DIRECT_DISCOUNT_ALREADY_EXISTS = "تخفیف مستقیم قبلا برای سرویس [serviceName] ثبت شده است", DIRECT_DISCOUNT_ALREADY_EXISTS = "تخفیف مستقیم قبلا برای سرویس [serviceName] ثبت شده است",
UPDATED = "تخفیف با موفقیت بروزرسانی شد",
} }
export const enum EmailMessage { export const enum EmailMessage {
@@ -1,5 +1,17 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsArray, IsBoolean, IsDateString, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, ValidateIf } from "class-validator"; import {
IsArray,
IsBoolean,
IsDateString,
IsEnum,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUUID,
Min,
ValidateIf,
} from "class-validator";
import { DiscountMessage } from "../../../common/enums/message.enum"; import { DiscountMessage } from "../../../common/enums/message.enum";
import { DiscountType } from "../enums/discount-type.enum"; import { DiscountType } from "../enums/discount-type.enum";
@@ -29,6 +41,7 @@ export class CreateDiscountDto {
@IsNotEmpty({ message: DiscountMessage.VALUE_REQUIRED }) @IsNotEmpty({ message: DiscountMessage.VALUE_REQUIRED })
@IsNumber({}, { message: DiscountMessage.VALUE_MUST_BE_A_NUMBER }) @IsNumber({}, { message: DiscountMessage.VALUE_MUST_BE_A_NUMBER })
@Min(0)
@ApiProperty({ description: "The value of the discount", example: 20 }) @ApiProperty({ description: "The value of the discount", example: 20 })
value: number; value: number;
@@ -58,4 +71,9 @@ export class CreateDiscountDto {
type: [String], type: [String],
}) })
targetServices?: string[]; targetServices?: string[];
// @IsString()
// @IsOptional()
// @ApiPropertyOptional({ description: "The code of the discount", example: "SUMMER2023" })
// code?: string;
} }
@@ -8,6 +8,7 @@ import { DiscountMessage } from "../../../common/enums/message.enum";
import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service";
import { CreateDiscountDto } from "../DTO/create-discount.dto"; import { CreateDiscountDto } from "../DTO/create-discount.dto";
import { SearchDiscountQueryDto } from "../DTO/search-discount-query.dto"; import { SearchDiscountQueryDto } from "../DTO/search-discount-query.dto";
import { UpdateDiscountDto } from "../DTO/update-discount.dto";
import { Discount } from "../entities/discount.entity"; import { Discount } from "../entities/discount.entity";
import { DiscountApplicationType } from "../enums/discount-application-type.enum"; import { DiscountApplicationType } from "../enums/discount-application-type.enum";
import { DiscountType } from "../enums/discount-type.enum"; import { DiscountType } from "../enums/discount-type.enum";
@@ -47,7 +48,7 @@ export class DiscountsService {
await queryRunner.release(); await queryRunner.release();
} }
} }
//************************************ */
async getDiscountsForAdmin(queryDto: SearchDiscountQueryDto) { async getDiscountsForAdmin(queryDto: SearchDiscountQueryDto) {
const [discounts, count] = await this.discountRepository.findDiscountForAdmin(queryDto); const [discounts, count] = await this.discountRepository.findDiscountForAdmin(queryDto);
@@ -57,53 +58,33 @@ export class DiscountsService {
pagination: true, pagination: true,
}; };
} }
//************************************ */
async updateDiscount(id: string, updateDiscountDto: CreateDiscountDto) { async getDiscountById(id: string) {
return this.discountRepository.findById(id);
}
//************************************ */
async updateDiscount(id: string, updateDiscountDto: UpdateDiscountDto) {
const queryRunner = this.dataSource.createQueryRunner(); const queryRunner = this.dataSource.createQueryRunner();
try { try {
await queryRunner.connect(); await queryRunner.connect();
await queryRunner.startTransaction(); await queryRunner.startTransaction();
const existingDiscount = await this.discountRepository.findOne({ const existingDiscount = await this.discountRepository.findById(id);
where: { id },
relations: ["subscriptionPlans"],
withDeleted: false,
});
if (!existingDiscount) { if (!existingDiscount) throw new BadRequestException(DiscountMessage.NOT_FOUND);
throw new BadRequestException(DiscountMessage.NOT_FOUND);
}
await this.validateDates(updateDiscountDto.startDate, updateDiscountDto.endDate); if (updateDiscountDto.startDate && updateDiscountDto.endDate)
await this.validateDates(updateDiscountDto.startDate, updateDiscountDto.endDate);
// Update basic discount properties if (updateDiscountDto.value) existingDiscount.value = updateDiscountDto.value;
Object.assign(existingDiscount, {
await queryRunner.manager.save({
...existingDiscount,
...updateDiscountDto, ...updateDiscountDto,
applicationType: updateDiscountDto.direct ? DiscountApplicationType.DIRECT : DiscountApplicationType.CODE_BASED, applicationType: updateDiscountDto.direct ? DiscountApplicationType.DIRECT : DiscountApplicationType.CODE_BASED,
}); });
// Handle code-based discount updates
if (!updateDiscountDto.direct) {
if (updateDiscountDto.code && updateDiscountDto.code !== existingDiscount.code) {
const existingCode = await this.findDiscountByCode(updateDiscountDto.code, queryRunner);
if (existingCode) {
throw new BadRequestException(DiscountMessage.CODE_ALREADY_EXISTS);
}
existingDiscount.code = updateDiscountDto.code;
}
} else {
// Clear code for direct discounts
existingDiscount.code = null;
}
// Calculate and update value
existingDiscount.value = this.calculateDiscountValue(updateDiscountDto);
// Save the updated discount
await queryRunner.manager.save(existingDiscount);
// Handle direct discount updates
if (updateDiscountDto.direct) { if (updateDiscountDto.direct) {
await this.updateDirectDiscount(updateDiscountDto, existingDiscount, queryRunner); await this.updateDirectDiscount(updateDiscountDto, existingDiscount, queryRunner);
} }
@@ -121,11 +102,11 @@ export class DiscountsService {
await queryRunner.release(); await queryRunner.release();
} }
} }
//************************************ */
async softDeleteDiscount(id: string): Promise<void> { async softDeleteDiscount(id: string): Promise<void> {
await this.discountRepository.softDeleteDiscount(id); await this.discountRepository.softDeleteDiscount(id);
} }
//************************************ */
private async createDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) { private async createDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
const discount = queryRunner.manager.create(this.discountRepository.target, { const discount = queryRunner.manager.create(this.discountRepository.target, {
...createDiscountDto, ...createDiscountDto,
@@ -145,7 +126,7 @@ export class DiscountsService {
return discount; return discount;
} }
//************************************ */
private async applyDirectDiscount(createDiscountDto: CreateDiscountDto, discount: Discount, queryRunner: QueryRunner) { private async applyDirectDiscount(createDiscountDto: CreateDiscountDto, discount: Discount, queryRunner: QueryRunner) {
const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions( const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions(
createDiscountDto.targetServices ?? [], createDiscountDto.targetServices ?? [],
@@ -163,7 +144,10 @@ export class DiscountsService {
subscriptionPlan.directDiscount = discount; subscriptionPlan.directDiscount = discount;
await queryRunner.manager.save(subscriptionPlan); await queryRunner.manager.save(subscriptionPlan);
} }
await queryRunner.manager.save(discount);
} }
//************************************ */
private async generateDiscountCode(discount: Discount, queryRunner: QueryRunner) { private async generateDiscountCode(discount: Discount, queryRunner: QueryRunner) {
let attempts = 0; let attempts = 0;
@@ -180,6 +164,7 @@ export class DiscountsService {
discount.code = code; discount.code = code;
} }
//************************************ */
private async findDiscountByCode(code: string, queryRunner: QueryRunner) { private async findDiscountByCode(code: string, queryRunner: QueryRunner) {
return queryRunner.manager.findOne(this.discountRepository.target, { return queryRunner.manager.findOne(this.discountRepository.target, {
@@ -187,10 +172,12 @@ export class DiscountsService {
withDeleted: false, withDeleted: false,
}); });
} }
//************************************ */
private async generateRandomCode(length: number = 4) { private async generateRandomCode(length: number = 4) {
return randomBytes(length).toString("hex").toUpperCase(); return randomBytes(length).toString("hex").toUpperCase();
} }
//************************************ */
private calculateDiscountValue(createDiscountDto: CreateDiscountDto): number { private calculateDiscountValue(createDiscountDto: CreateDiscountDto): number {
if (createDiscountDto.type === DiscountType.PERCENTAGE) return createDiscountDto.value; if (createDiscountDto.type === DiscountType.PERCENTAGE) return createDiscountDto.value;
@@ -198,7 +185,7 @@ export class DiscountsService {
throw new BadRequestException(DiscountMessage.INVALID_DISCOUNT_TYPE); throw new BadRequestException(DiscountMessage.INVALID_DISCOUNT_TYPE);
} }
//************************************ */
private async validateDates(startDate: string, endDate: string) { private async validateDates(startDate: string, endDate: string) {
if (dayjs(startDate).isAfter(dayjs(endDate))) { if (dayjs(startDate).isAfter(dayjs(endDate))) {
throw new BadRequestException(DiscountMessage.START_DATE_MUST_BE_BEFORE_END_DATE); throw new BadRequestException(DiscountMessage.START_DATE_MUST_BE_BEFORE_END_DATE);
@@ -216,14 +203,13 @@ export class DiscountsService {
throw new BadRequestException(DiscountMessage.END_DATE_MUST_BE_AFTER_NOW); throw new BadRequestException(DiscountMessage.END_DATE_MUST_BE_AFTER_NOW);
} }
} }
//************************************ */
private async updateDirectDiscount(updateDiscountDto: CreateDiscountDto, discount: Discount, queryRunner: QueryRunner) { private async updateDirectDiscount(updateDiscountDto: UpdateDiscountDto, discount: Discount, queryRunner: QueryRunner) {
const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions( const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions(
updateDiscountDto.targetServices ?? [], updateDiscountDto.targetServices ?? [],
queryRunner, queryRunner,
); );
// Remove discount from plans that are no longer in the target services
const currentPlans = await this.subscriptionService.getAllServiceSubscriptions( const currentPlans = await this.subscriptionService.getAllServiceSubscriptions(
discount.subscriptionPlans.map((plan) => plan.service.id), discount.subscriptionPlans.map((plan) => plan.service.id),
queryRunner, queryRunner,
@@ -231,12 +217,11 @@ export class DiscountsService {
for (const plan of currentPlans) { for (const plan of currentPlans) {
if (!subscriptionPlans.some((newPlan) => newPlan.id === plan.id)) { if (!subscriptionPlans.some((newPlan) => newPlan.id === plan.id)) {
plan.directDiscount = null; plan.directDiscount = undefined;
await queryRunner.manager.save(plan); await queryRunner.manager.save(plan);
} }
} }
// Check for existing discounts in new target plans
const plansWithExistingDiscount = subscriptionPlans.filter((plan) => plan.directDiscount && plan.directDiscount.id !== discount.id); const plansWithExistingDiscount = subscriptionPlans.filter((plan) => plan.directDiscount && plan.directDiscount.id !== discount.id);
if (plansWithExistingDiscount.length > 0) { if (plansWithExistingDiscount.length > 0) {
@@ -245,10 +230,11 @@ export class DiscountsService {
); );
} }
// Apply discount to new target plans
for (const subscriptionPlan of subscriptionPlans) { for (const subscriptionPlan of subscriptionPlans) {
subscriptionPlan.directDiscount = discount; subscriptionPlan.directDiscount = discount;
await queryRunner.manager.save(subscriptionPlan); await queryRunner.manager.save(subscriptionPlan);
} }
await queryRunner.manager.save(discount);
} }
} }
@@ -12,21 +12,21 @@ export class DiscountRepository extends Repository<Discount> {
super(repository.target, repository.manager, repository.queryRunner); super(repository.target, repository.manager, repository.queryRunner);
} }
async findById(id: string): Promise<Discount | null> { async findById(id: string) {
return this.findOne({ return this.findOne({
where: { id }, where: { id, isActive: true },
withDeleted: false, withDeleted: false,
}); });
} }
async findByCode(code: string): Promise<Discount | null> { async findByCode(code: string) {
return this.findOne({ return this.findOne({
where: { code }, where: { code, isActive: true },
withDeleted: false, withDeleted: false,
}); });
} }
async findActiveByCode(code: string): Promise<Discount | null> { async findActiveByCode(code: string) {
return this.findOne({ return this.findOne({
where: { where: {
code, code,