update: commit for the new file
This commit is contained in:
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
@@ -518,6 +518,9 @@ export const enum DiscountMessage {
|
|||||||
VALUE_REQUIRED = "مقدار تخفیف الزامی است",
|
VALUE_REQUIRED = "مقدار تخفیف الزامی است",
|
||||||
IS_ACTIVE_REQUIRED = "وضعیت فعال بودن الزامی است",
|
IS_ACTIVE_REQUIRED = "وضعیت فعال بودن الزامی است",
|
||||||
CODE_GENERATION_FAILED = "ایجاد کد تخفیف با خطا مواجه شد",
|
CODE_GENERATION_FAILED = "ایجاد کد تخفیف با خطا مواجه شد",
|
||||||
|
START_DATE_MUST_BE_BEFORE_END_DATE = "تاریخ شروع باید قبل از تاریخ پایان باشد",
|
||||||
|
START_DATE_MUST_BE_AFTER_NOW = "تاریخ شروع باید بعد از تاریخ فعلی باشد",
|
||||||
|
END_DATE_MUST_BE_AFTER_NOW = "تاریخ پایان باید بعد از تاریخ فعلی باشد",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum EmailMessage {
|
export const enum EmailMessage {
|
||||||
|
|||||||
Regular → Executable
@@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions {
|
|||||||
username: configService.getOrThrow<string>("DB_USER"),
|
username: configService.getOrThrow<string>("DB_USER"),
|
||||||
password: configService.getOrThrow<string>("DB_PASS"),
|
password: configService.getOrThrow<string>("DB_PASS"),
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : false,
|
||||||
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
||||||
migrationsTableName: "typeorm_migrations",
|
migrationsTableName: "typeorm_migrations",
|
||||||
migrationsRun: false,
|
migrationsRun: false,
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+5
-1
@@ -12,7 +12,11 @@ export class CreateDiscountDto {
|
|||||||
|
|
||||||
@IsNotEmpty({ message: DiscountMessage.DISCOUNT_TYPE_REQUIRED })
|
@IsNotEmpty({ message: DiscountMessage.DISCOUNT_TYPE_REQUIRED })
|
||||||
@IsEnum(DiscountType, { message: DiscountMessage.INVALID_DISCOUNT_TYPE })
|
@IsEnum(DiscountType, { message: DiscountMessage.INVALID_DISCOUNT_TYPE })
|
||||||
@ApiProperty({ description: "The type of discount", example: DiscountType.PERCENTAGE, enum: DiscountType })
|
@ApiProperty({
|
||||||
|
description: "The type of discount",
|
||||||
|
example: `${DiscountType.PERCENTAGE} ==> type 0 means percentage, type 1 means fixed amount`,
|
||||||
|
enum: DiscountType,
|
||||||
|
})
|
||||||
type: DiscountType;
|
type: DiscountType;
|
||||||
|
|
||||||
@IsNotEmpty({ message: DiscountMessage.DIRECT_REQUIRED })
|
@IsNotEmpty({ message: DiscountMessage.DIRECT_REQUIRED })
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+4
-3
@@ -7,13 +7,14 @@ import { DirectDiscount } from "./entities/direct-discount.entity";
|
|||||||
import { Discount } from "./entities/discount.entity";
|
import { Discount } from "./entities/discount.entity";
|
||||||
import { UsageDiscount } from "./entities/usage-discount.entity";
|
import { UsageDiscount } from "./entities/usage-discount.entity";
|
||||||
import { DiscountsService } from "./providers/discounts.service";
|
import { DiscountsService } from "./providers/discounts.service";
|
||||||
|
import { DirectDiscountRepository } from "./repositories/direct-discounts.repository";
|
||||||
import { DiscountRepository } from "./repositories/discounts.repository";
|
import { DiscountRepository } from "./repositories/discounts.repository";
|
||||||
import { UsageDiscountRepository } from "./repositories/usage-discounts.repository";
|
import { UsageDiscountRepository } from "./repositories/usage-discounts.repository";
|
||||||
|
import { SubscriptionsModule } from "../subscriptions/subscriptions.module";
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Discount, UsageDiscount, DirectDiscount]), UsersModule],
|
imports: [TypeOrmModule.forFeature([Discount, UsageDiscount, DirectDiscount]), UsersModule, SubscriptionsModule],
|
||||||
controllers: [DiscountsController],
|
controllers: [DiscountsController],
|
||||||
providers: [DiscountsService, DiscountRepository, UsageDiscountRepository],
|
providers: [DiscountsService, DiscountRepository, DirectDiscountRepository, UsageDiscountRepository],
|
||||||
exports: [DiscountsService],
|
exports: [DiscountsService],
|
||||||
})
|
})
|
||||||
export class DiscountsModule {}
|
export class DiscountsModule {}
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+42
-5
@@ -1,22 +1,28 @@
|
|||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
|
|
||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import { DataSource, QueryRunner } from "typeorm";
|
import { DataSource, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
import { DiscountMessage } from "../../../common/enums/message.enum";
|
import { DiscountMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service";
|
||||||
import { CreateDiscountDto } from "../DTO/create-discount.dto";
|
import { CreateDiscountDto } from "../DTO/create-discount.dto";
|
||||||
import { DiscountType } from "../enums/discount-type.enum";
|
import { DiscountType } from "../enums/discount-type.enum";
|
||||||
|
import { DirectDiscountRepository } from "../repositories/direct-discounts.repository";
|
||||||
import { DiscountRepository } from "../repositories/discounts.repository";
|
import { DiscountRepository } from "../repositories/discounts.repository";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DiscountsService {
|
export class DiscountsService {
|
||||||
private MAX_ATTEMPTS = 10;
|
private MAX_ATTEMPTS = 10;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly discountRepository: DiscountRepository,
|
private readonly discountRepository: DiscountRepository,
|
||||||
|
private readonly directDiscountRepository: DirectDiscountRepository,
|
||||||
|
private readonly subscriptionService: SubscriptionsService,
|
||||||
private readonly dataSource: DataSource,
|
private readonly dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
//***************************************** */
|
||||||
|
|
||||||
async create(createDiscountDto: CreateDiscountDto) {
|
async create(createDiscountDto: CreateDiscountDto) {
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
|
||||||
@@ -24,6 +30,8 @@ export class DiscountsService {
|
|||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
|
await this.validateDates(createDiscountDto.startDate, createDiscountDto.endDate);
|
||||||
|
|
||||||
if (!createDiscountDto.direct) {
|
if (!createDiscountDto.direct) {
|
||||||
await this.createCodeBasedDiscount(createDiscountDto, queryRunner);
|
await this.createCodeBasedDiscount(createDiscountDto, queryRunner);
|
||||||
} else {
|
} else {
|
||||||
@@ -42,7 +50,7 @@ export class DiscountsService {
|
|||||||
await queryRunner.release();
|
await queryRunner.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//***************************************** */
|
||||||
private async createCodeBasedDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
|
private async createCodeBasedDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
|
||||||
const discount = queryRunner.manager.create(this.discountRepository.target, createDiscountDto);
|
const discount = queryRunner.manager.create(this.discountRepository.target, createDiscountDto);
|
||||||
|
|
||||||
@@ -63,12 +71,21 @@ export class DiscountsService {
|
|||||||
await queryRunner.manager.save(discount);
|
await queryRunner.manager.save(discount);
|
||||||
return discount;
|
return discount;
|
||||||
}
|
}
|
||||||
|
//***************************************** */
|
||||||
//**************************** */
|
|
||||||
private async createDirectBasedDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
|
private async createDirectBasedDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
|
||||||
const discount = queryRunner.manager.create(this.discountRepository.target, createDiscountDto);
|
// Create the discount entity
|
||||||
|
const discount = queryRunner.manager.create(this.directDiscountRepository.target, createDiscountDto);
|
||||||
discount.value = this.calculateDiscountValue(createDiscountDto);
|
discount.value = this.calculateDiscountValue(createDiscountDto);
|
||||||
|
|
||||||
await queryRunner.manager.save(discount);
|
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;
|
return discount;
|
||||||
}
|
}
|
||||||
//**************************** */
|
//**************************** */
|
||||||
@@ -91,4 +108,24 @@ export class DiscountsService {
|
|||||||
|
|
||||||
throw new BadRequestException(DiscountMessage.INVALID_DISCOUNT_TYPE);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayjs(startDate).isBefore(dayjs().subtract(1, "day"))) {
|
||||||
|
throw new BadRequestException(DiscountMessage.START_DATE_MUST_BE_AFTER_NOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayjs(startDate).isSame(dayjs(endDate))) {
|
||||||
|
throw new BadRequestException(DiscountMessage.START_DATE_MUST_BE_BEFORE_END_DATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayjs(endDate).isBefore(dayjs())) {
|
||||||
|
throw new BadRequestException(DiscountMessage.END_DATE_MUST_BE_AFTER_NOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { DirectDiscount } from "../entities/direct-discount.entity";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DirectDiscountRepository extends Repository<DirectDiscount> {
|
||||||
|
constructor(@InjectRepository(DirectDiscount) repository: Repository<DirectDiscount>) {
|
||||||
|
super(repository.target, repository.manager, repository.queryRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
@@ -10,7 +10,6 @@ import { InvoicesService } from "./providers/invoices.service";
|
|||||||
import { InvoiceProcessor } from "./queue/invoice.processor";
|
import { InvoiceProcessor } from "./queue/invoice.processor";
|
||||||
import { InvoiceItemsRepository } from "./repositories/invoice-items.repository";
|
import { InvoiceItemsRepository } from "./repositories/invoice-items.repository";
|
||||||
import { InvoicesRepository } from "./repositories/invoices.repository";
|
import { InvoicesRepository } from "./repositories/invoices.repository";
|
||||||
import { DiscountsModule } from "../discounts/discounts.module";
|
|
||||||
import { Discount } from "../discounts/entities/discount.entity";
|
import { Discount } from "../discounts/entities/discount.entity";
|
||||||
import { UsageDiscount } from "../discounts/entities/usage-discount.entity";
|
import { UsageDiscount } from "../discounts/entities/usage-discount.entity";
|
||||||
import { DiscountRepository } from "../discounts/repositories/discounts.repository";
|
import { DiscountRepository } from "../discounts/repositories/discounts.repository";
|
||||||
@@ -26,7 +25,6 @@ import { WalletsModule } from "../wallets/wallets.module";
|
|||||||
LoggerModule,
|
LoggerModule,
|
||||||
BullModule.registerQueue({ name: INVOICE.INVOICE_QUEUE_NAME }),
|
BullModule.registerQueue({ name: INVOICE.INVOICE_QUEUE_NAME }),
|
||||||
TypeOrmModule.forFeature([Invoice, InvoiceItem, Discount, UsageDiscount]),
|
TypeOrmModule.forFeature([Invoice, InvoiceItem, Discount, UsageDiscount]),
|
||||||
DiscountsModule,
|
|
||||||
UsersModule,
|
UsersModule,
|
||||||
WalletsModule,
|
WalletsModule,
|
||||||
UtilsModule,
|
UtilsModule,
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
@@ -1,6 +1,6 @@
|
|||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import Decimal from "decimal.js";
|
import Decimal from "decimal.js";
|
||||||
import { Column, Entity, Index, ManyToOne, OneToMany } from "typeorm";
|
import { Column, DeleteDateColumn, Entity, Index, ManyToOne, OneToMany } from "typeorm";
|
||||||
|
|
||||||
import { UserSubscription } from "./user-subscription.entity";
|
import { UserSubscription } from "./user-subscription.entity";
|
||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
@@ -31,4 +31,7 @@ export class SubscriptionPlan extends BaseEntity {
|
|||||||
|
|
||||||
@ManyToOne(() => DirectDiscount, (directDiscount) => directDiscount.subscriptionPlans, { nullable: true })
|
@ManyToOne(() => DirectDiscount, (directDiscount) => directDiscount.subscriptionPlans, { nullable: true })
|
||||||
directDiscount?: DirectDiscount;
|
directDiscount?: DirectDiscount;
|
||||||
|
|
||||||
|
@DeleteDateColumn({ nullable: true })
|
||||||
|
deletedAt?: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
Regular → Executable
@@ -144,7 +144,7 @@ export class SubscriptionsService {
|
|||||||
}
|
}
|
||||||
//************************************ */
|
//************************************ */
|
||||||
|
|
||||||
async getServiceSubscriptions(serviceId: string, queryDto: ServiceSubsQueryDto) {
|
async All(serviceId: string, queryDto: ServiceSubsQueryDto) {
|
||||||
const service = await this.danakServices.findServiceById(serviceId);
|
const service = await this.danakServices.findServiceById(serviceId);
|
||||||
|
|
||||||
const { limit, skip } = PaginationUtils(queryDto);
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
@@ -237,6 +237,45 @@ export class SubscriptionsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//************************************ */
|
//************************************ */
|
||||||
|
async getServiceSubscriptions(serviceId: string, queryDto: ServiceSubsQueryDto) {
|
||||||
|
const service = await this.danakServices.findServiceById(serviceId);
|
||||||
|
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const queryBuilder = this.subscriptionsPlanRepository
|
||||||
|
.createQueryBuilder("subscription")
|
||||||
|
.leftJoin("subscription.service", "service")
|
||||||
|
.addSelect(["service.id"])
|
||||||
|
// .leftJoin("subscription.discounts", "discount")
|
||||||
|
// .addSelect(["discount.calculationType", "discount.amount", "discount.startDate", "discount.endDate", "discount.isActive"])
|
||||||
|
.where("service.id = :serviceId", { serviceId });
|
||||||
|
|
||||||
|
if (queryDto.q) {
|
||||||
|
queryBuilder.andWhere("subscription.name ILIKE :query", { query: `%${queryDto.q}%` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDto.isActive !== undefined) {
|
||||||
|
queryBuilder.andWhere("subscription.isActive = :isActive", { isActive: queryDto.isActive === 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
||||||
|
|
||||||
|
return {
|
||||||
|
service,
|
||||||
|
subscriptions,
|
||||||
|
count,
|
||||||
|
pagination: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//************************************ */
|
||||||
|
|
||||||
|
async getAllServiceSubscriptions(serviceIds: string[]) {
|
||||||
|
const subscriptions = await this.subscriptionsPlanRepository.find({
|
||||||
|
where: { service: { id: In(serviceIds) }, isActive: true },
|
||||||
|
});
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
|
//************************************ */
|
||||||
|
|
||||||
async countUserSubscriptions(userId: string) {
|
async countUserSubscriptions(userId: string) {
|
||||||
const subscriptionCount = await this.userSubscriptionsRepository.count({
|
const subscriptionCount = await this.userSubscriptionsRepository.count({
|
||||||
@@ -335,8 +374,6 @@ export class SubscriptionsService {
|
|||||||
const startOfWeek = dayjs().startOf("week").toDate();
|
const startOfWeek = dayjs().startOf("week").toDate();
|
||||||
const endOfWeek = dayjs().endOf("week").toDate();
|
const endOfWeek = dayjs().endOf("week").toDate();
|
||||||
|
|
||||||
console.log(startOfWeek, endOfWeek);
|
|
||||||
|
|
||||||
const sales = await this.userSubscriptionsRepository
|
const sales = await this.userSubscriptionsRepository
|
||||||
.createQueryBuilder("userSubscription")
|
.createQueryBuilder("userSubscription")
|
||||||
.select("DATE(userSubscription.startDate)", "date")
|
.select("DATE(userSubscription.startDate)", "date")
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
@@ -31,6 +31,6 @@ import { UserQuickAccessRepository } from "./repositories/users-quick-access.rep
|
|||||||
UserQuickAccessService,
|
UserQuickAccessService,
|
||||||
],
|
],
|
||||||
controllers: [SubscriptionsController],
|
controllers: [SubscriptionsController],
|
||||||
exports: [SubscriptionsService, TypeOrmModule, SubscriptionsPlanRepository, UserSubscriptionsRepository],
|
exports: [SubscriptionsService, SubscriptionsPlanRepository, UserSubscriptionsRepository],
|
||||||
})
|
})
|
||||||
export class SubscriptionsModule {}
|
export class SubscriptionsModule {}
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user