diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 10f6cd9..5fa3379 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -524,6 +524,7 @@ export const enum DiscountMessage { END_DATE_MUST_BE_AFTER_NOW = "تاریخ پایان باید بعد از تاریخ فعلی باشد", EACH_TARGET_PRODUCT_MUST_BE_A_UUID = "هر محصول باید یک UUID معتبر باشد", DIRECT_DISCOUNT_ALREADY_EXISTS = "تخفیف مستقیم قبلا برای سرویس [serviceName] ثبت شده است", + UPDATED = "تخفیف با موفقیت بروزرسانی شد", } export const enum EmailMessage { diff --git a/src/modules/discounts/DTO/create-discount.dto.ts b/src/modules/discounts/DTO/create-discount.dto.ts index e6afc37..6b86cfa 100755 --- a/src/modules/discounts/DTO/create-discount.dto.ts +++ b/src/modules/discounts/DTO/create-discount.dto.ts @@ -1,5 +1,17 @@ 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 { DiscountType } from "../enums/discount-type.enum"; @@ -29,6 +41,7 @@ export class CreateDiscountDto { @IsNotEmpty({ message: DiscountMessage.VALUE_REQUIRED }) @IsNumber({}, { message: DiscountMessage.VALUE_MUST_BE_A_NUMBER }) + @Min(0) @ApiProperty({ description: "The value of the discount", example: 20 }) value: number; @@ -58,4 +71,9 @@ export class CreateDiscountDto { type: [String], }) targetServices?: string[]; + + // @IsString() + // @IsOptional() + // @ApiPropertyOptional({ description: "The code of the discount", example: "SUMMER2023" }) + // code?: string; } diff --git a/src/modules/discounts/providers/discounts.service.ts b/src/modules/discounts/providers/discounts.service.ts index 3fea5a7..da20bb0 100755 --- a/src/modules/discounts/providers/discounts.service.ts +++ b/src/modules/discounts/providers/discounts.service.ts @@ -8,6 +8,7 @@ import { DiscountMessage } from "../../../common/enums/message.enum"; import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; import { CreateDiscountDto } from "../DTO/create-discount.dto"; import { SearchDiscountQueryDto } from "../DTO/search-discount-query.dto"; +import { UpdateDiscountDto } from "../DTO/update-discount.dto"; import { Discount } from "../entities/discount.entity"; import { DiscountApplicationType } from "../enums/discount-application-type.enum"; import { DiscountType } from "../enums/discount-type.enum"; @@ -47,7 +48,7 @@ export class DiscountsService { await queryRunner.release(); } } - + //************************************ */ async getDiscountsForAdmin(queryDto: SearchDiscountQueryDto) { const [discounts, count] = await this.discountRepository.findDiscountForAdmin(queryDto); @@ -57,53 +58,33 @@ export class DiscountsService { 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(); try { await queryRunner.connect(); await queryRunner.startTransaction(); - const existingDiscount = await this.discountRepository.findOne({ - where: { id }, - relations: ["subscriptionPlans"], - withDeleted: false, - }); + const existingDiscount = await this.discountRepository.findById(id); - if (!existingDiscount) { - throw new BadRequestException(DiscountMessage.NOT_FOUND); - } + if (!existingDiscount) 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 - Object.assign(existingDiscount, { + if (updateDiscountDto.value) existingDiscount.value = updateDiscountDto.value; + + await queryRunner.manager.save({ + ...existingDiscount, ...updateDiscountDto, 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) { await this.updateDirectDiscount(updateDiscountDto, existingDiscount, queryRunner); } @@ -121,11 +102,11 @@ export class DiscountsService { await queryRunner.release(); } } - + //************************************ */ async softDeleteDiscount(id: string): Promise { await this.discountRepository.softDeleteDiscount(id); } - + //************************************ */ private async createDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) { const discount = queryRunner.manager.create(this.discountRepository.target, { ...createDiscountDto, @@ -145,7 +126,7 @@ export class DiscountsService { return discount; } - + //************************************ */ private async applyDirectDiscount(createDiscountDto: CreateDiscountDto, discount: Discount, queryRunner: QueryRunner) { const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions( createDiscountDto.targetServices ?? [], @@ -163,7 +144,10 @@ export class DiscountsService { subscriptionPlan.directDiscount = discount; await queryRunner.manager.save(subscriptionPlan); } + + await queryRunner.manager.save(discount); } + //************************************ */ private async generateDiscountCode(discount: Discount, queryRunner: QueryRunner) { let attempts = 0; @@ -180,6 +164,7 @@ export class DiscountsService { discount.code = code; } + //************************************ */ private async findDiscountByCode(code: string, queryRunner: QueryRunner) { return queryRunner.manager.findOne(this.discountRepository.target, { @@ -187,10 +172,12 @@ export class DiscountsService { withDeleted: false, }); } + //************************************ */ private async generateRandomCode(length: number = 4) { return randomBytes(length).toString("hex").toUpperCase(); } + //************************************ */ private calculateDiscountValue(createDiscountDto: CreateDiscountDto): number { if (createDiscountDto.type === DiscountType.PERCENTAGE) return createDiscountDto.value; @@ -198,7 +185,7 @@ export class DiscountsService { throw new BadRequestException(DiscountMessage.INVALID_DISCOUNT_TYPE); } - + //************************************ */ private async validateDates(startDate: string, endDate: string) { if (dayjs(startDate).isAfter(dayjs(endDate))) { 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); } } - - private async updateDirectDiscount(updateDiscountDto: CreateDiscountDto, discount: Discount, queryRunner: QueryRunner) { + //************************************ */ + private async updateDirectDiscount(updateDiscountDto: UpdateDiscountDto, discount: Discount, queryRunner: QueryRunner) { const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions( updateDiscountDto.targetServices ?? [], queryRunner, ); - // Remove discount from plans that are no longer in the target services const currentPlans = await this.subscriptionService.getAllServiceSubscriptions( discount.subscriptionPlans.map((plan) => plan.service.id), queryRunner, @@ -231,12 +217,11 @@ export class DiscountsService { for (const plan of currentPlans) { if (!subscriptionPlans.some((newPlan) => newPlan.id === plan.id)) { - plan.directDiscount = null; + plan.directDiscount = undefined; 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); if (plansWithExistingDiscount.length > 0) { @@ -245,10 +230,11 @@ export class DiscountsService { ); } - // Apply discount to new target plans for (const subscriptionPlan of subscriptionPlans) { subscriptionPlan.directDiscount = discount; await queryRunner.manager.save(subscriptionPlan); } + + await queryRunner.manager.save(discount); } } diff --git a/src/modules/discounts/repositories/discounts.repository.ts b/src/modules/discounts/repositories/discounts.repository.ts index 99ed1e6..2a58d1e 100755 --- a/src/modules/discounts/repositories/discounts.repository.ts +++ b/src/modules/discounts/repositories/discounts.repository.ts @@ -12,21 +12,21 @@ export class DiscountRepository extends Repository { super(repository.target, repository.manager, repository.queryRunner); } - async findById(id: string): Promise { + async findById(id: string) { return this.findOne({ - where: { id }, + where: { id, isActive: true }, withDeleted: false, }); } - async findByCode(code: string): Promise { + async findByCode(code: string) { return this.findOne({ - where: { code }, + where: { code, isActive: true }, withDeleted: false, }); } - async findActiveByCode(code: string): Promise { + async findActiveByCode(code: string) { return this.findOne({ where: { code,