chore: complete the discount service and test the logic to create
This commit is contained in:
@@ -7,22 +7,22 @@ import { DataSource, QueryRunner } from "typeorm";
|
||||
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 { Discount } from "../entities/discount.entity";
|
||||
import { DiscountApplicationType } from "../enums/discount-application-type.enum";
|
||||
import { DiscountType } from "../enums/discount-type.enum";
|
||||
import { DirectDiscountRepository } from "../repositories/direct-discounts.repository";
|
||||
import { DiscountRepository } from "../repositories/discounts.repository";
|
||||
|
||||
@Injectable()
|
||||
export class DiscountsService {
|
||||
private MAX_ATTEMPTS = 10;
|
||||
|
||||
constructor(
|
||||
private readonly discountRepository: DiscountRepository,
|
||||
private readonly directDiscountRepository: DirectDiscountRepository,
|
||||
private readonly subscriptionService: SubscriptionsService,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
//***************************************** */
|
||||
|
||||
async create(createDiscountDto: CreateDiscountDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
@@ -32,16 +32,13 @@ export class DiscountsService {
|
||||
|
||||
await this.validateDates(createDiscountDto.startDate, createDiscountDto.endDate);
|
||||
|
||||
if (!createDiscountDto.direct) {
|
||||
await this.createCodeBasedDiscount(createDiscountDto, queryRunner);
|
||||
} else {
|
||||
await this.createDirectBasedDiscount(createDiscountDto, queryRunner);
|
||||
}
|
||||
const discount = await this.createDiscount(createDiscountDto, queryRunner);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
message: DiscountMessage.CREATED,
|
||||
discount,
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
@@ -50,10 +47,125 @@ export class DiscountsService {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
//***************************************** */
|
||||
private async createCodeBasedDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
|
||||
const discount = queryRunner.manager.create(this.discountRepository.target, createDiscountDto);
|
||||
|
||||
async getDiscountsForAdmin(queryDto: SearchDiscountQueryDto) {
|
||||
const [discounts, count] = await this.discountRepository.findDiscountForAdmin(queryDto);
|
||||
|
||||
return {
|
||||
discounts,
|
||||
count,
|
||||
pagination: true,
|
||||
};
|
||||
}
|
||||
|
||||
async updateDiscount(id: string, updateDiscountDto: CreateDiscountDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
const existingDiscount = await this.discountRepository.findOne({
|
||||
where: { id },
|
||||
relations: ["subscriptionPlans"],
|
||||
withDeleted: false,
|
||||
});
|
||||
|
||||
if (!existingDiscount) {
|
||||
throw new BadRequestException(DiscountMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.validateDates(updateDiscountDto.startDate, updateDiscountDto.endDate);
|
||||
|
||||
// Update basic discount properties
|
||||
Object.assign(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);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
message: DiscountMessage.UPDATED,
|
||||
discount: existingDiscount,
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
async softDeleteDiscount(id: string): Promise<void> {
|
||||
await this.discountRepository.softDeleteDiscount(id);
|
||||
}
|
||||
|
||||
private async createDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
|
||||
const discount = queryRunner.manager.create(this.discountRepository.target, {
|
||||
...createDiscountDto,
|
||||
applicationType: createDiscountDto.direct ? DiscountApplicationType.DIRECT : DiscountApplicationType.CODE_BASED,
|
||||
});
|
||||
|
||||
if (!createDiscountDto.direct) {
|
||||
await this.generateDiscountCode(discount, queryRunner);
|
||||
}
|
||||
|
||||
discount.value = this.calculateDiscountValue(createDiscountDto);
|
||||
await queryRunner.manager.save(discount);
|
||||
|
||||
if (createDiscountDto.direct) {
|
||||
await this.applyDirectDiscount(createDiscountDto, discount, queryRunner);
|
||||
}
|
||||
|
||||
return discount;
|
||||
}
|
||||
|
||||
private async applyDirectDiscount(createDiscountDto: CreateDiscountDto, discount: Discount, queryRunner: QueryRunner) {
|
||||
const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions(
|
||||
createDiscountDto.targetServices ?? [],
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const plansWithExistingDiscount = subscriptionPlans.filter((plan) => plan.directDiscount);
|
||||
if (plansWithExistingDiscount.length > 0) {
|
||||
throw new BadRequestException(
|
||||
DiscountMessage.DIRECT_DISCOUNT_ALREADY_EXISTS.replace("[serviceName]", plansWithExistingDiscount[0].service.name),
|
||||
);
|
||||
}
|
||||
|
||||
for (const subscriptionPlan of subscriptionPlans) {
|
||||
subscriptionPlan.directDiscount = discount;
|
||||
await queryRunner.manager.save(subscriptionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
private async generateDiscountCode(discount: Discount, queryRunner: QueryRunner) {
|
||||
let attempts = 0;
|
||||
let code: string;
|
||||
let existCode;
|
||||
@@ -61,46 +173,24 @@ export class DiscountsService {
|
||||
do {
|
||||
if (attempts >= this.MAX_ATTEMPTS) throw new BadRequestException(DiscountMessage.CODE_GENERATION_FAILED);
|
||||
|
||||
code = await this.generateDiscountCode();
|
||||
code = await this.generateRandomCode();
|
||||
existCode = await this.findDiscountByCode(code, queryRunner);
|
||||
attempts++;
|
||||
} while (existCode && attempts < this.MAX_ATTEMPTS);
|
||||
|
||||
discount.code = code;
|
||||
discount.value = this.calculateDiscountValue(createDiscountDto);
|
||||
await queryRunner.manager.save(discount);
|
||||
return discount;
|
||||
}
|
||||
//***************************************** */
|
||||
private async createDirectBasedDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
|
||||
// Create the discount entity
|
||||
const discount = queryRunner.manager.create(this.directDiscountRepository.target, createDiscountDto);
|
||||
discount.value = this.calculateDiscountValue(createDiscountDto);
|
||||
|
||||
await queryRunner.manager.save(discount);
|
||||
|
||||
const subscriptionPlans = await this.subscriptionService.getAllServiceSubscriptions(createDiscountDto.targetServices ?? []);
|
||||
|
||||
for (const subscriptionPlan of subscriptionPlans) {
|
||||
subscriptionPlan.directDiscount = discount;
|
||||
await queryRunner.manager.save(subscriptionPlan);
|
||||
}
|
||||
|
||||
return discount;
|
||||
}
|
||||
//**************************** */
|
||||
|
||||
private async findDiscountByCode(code: string, queryRunner: QueryRunner) {
|
||||
const discount = await queryRunner.manager.findOne(this.discountRepository.target, { where: { code } });
|
||||
return discount;
|
||||
return queryRunner.manager.findOne(this.discountRepository.target, {
|
||||
where: { code },
|
||||
withDeleted: false,
|
||||
});
|
||||
}
|
||||
//**************************** */
|
||||
|
||||
private async generateDiscountCode(length: number = 8) {
|
||||
const code = randomBytes(length).toString("hex");
|
||||
return code;
|
||||
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;
|
||||
@@ -109,8 +199,6 @@ 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);
|
||||
@@ -128,4 +216,39 @@ export class DiscountsService {
|
||||
throw new BadRequestException(DiscountMessage.END_DATE_MUST_BE_AFTER_NOW);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateDirectDiscount(updateDiscountDto: CreateDiscountDto, 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,
|
||||
);
|
||||
|
||||
for (const plan of currentPlans) {
|
||||
if (!subscriptionPlans.some((newPlan) => newPlan.id === plan.id)) {
|
||||
plan.directDiscount = null;
|
||||
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) {
|
||||
throw new BadRequestException(
|
||||
DiscountMessage.DIRECT_DISCOUNT_ALREADY_EXISTS.replace("[serviceName]", plansWithExistingDiscount[0].service.name),
|
||||
);
|
||||
}
|
||||
|
||||
// Apply discount to new target plans
|
||||
for (const subscriptionPlan of subscriptionPlans) {
|
||||
subscriptionPlan.directDiscount = discount;
|
||||
await queryRunner.manager.save(subscriptionPlan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user