chore: add order to the service category

This commit is contained in:
mahyargdz
2025-03-09 16:56:43 +03:30
parent cdeca5375e
commit cb8ecd770c
4 changed files with 100 additions and 23 deletions
+4
View File
@@ -134,6 +134,10 @@ export const enum CategoryMessage {
IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت دسته بندی باید یکی از مقادیر ۰ و ۱ باشد", IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت دسته بندی باید یکی از مقادیر ۰ و ۱ باشد",
IS_SUGGEST_SHOULD_BE_1_0 = "وضعیت پیشنهادی بودن دسته بندی باید یکی از مقادیر ۰ و ۱ باشد", IS_SUGGEST_SHOULD_BE_1_0 = "وضعیت پیشنهادی بودن دسته بندی باید یکی از مقادیر ۰ و ۱ باشد",
CATEGORY_NOT_EXIST_OR_HAS_NO_SERVICE = "دسته بندی مورد نظر یافت نشد یا هیچ سرویسی در این دسته بندی وجود ندارد", CATEGORY_NOT_EXIST_OR_HAS_NO_SERVICE = "دسته بندی مورد نظر یافت نشد یا هیچ سرویسی در این دسته بندی وجود ندارد",
ORDER_REQUIRED = "ترتیب دسته بندی مورد نیاز است",
ORDER_SHOULD_BE_INT = "ترتیب دسته بندی باید یک عدد صحیح باشد",
CREATION_FAILED = "ایجاد دسته بندی موفیقیت آمیز نبود",
UPDATE_FAILED = "به روز رسانی دسته بندی موفیقیت آمیز نبود",
} }
export const enum ServiceMessage { export const enum ServiceMessage {
@@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length } from "class-validator"; import { IsBoolean, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length } from "class-validator";
import { CategoryMessage } from "../../../common/enums/message.enum"; import { CategoryMessage } from "../../../common/enums/message.enum";
@@ -25,4 +25,9 @@ export class CreateCategoryDto {
@IsUUID("4", { message: CategoryMessage.PARENT_ID_SHOULD_BE_UUID }) @IsUUID("4", { message: CategoryMessage.PARENT_ID_SHOULD_BE_UUID })
@ApiPropertyOptional({ description: "Parent category id", example: "8b1e8b1e-8b1e-8b1e-8b1e-8b1e8b1e8b1e" }) @ApiPropertyOptional({ description: "Parent category id", example: "8b1e8b1e-8b1e-8b1e-8b1e-8b1e8b1e8b1e" })
parentId?: string; parentId?: string;
@IsNotEmpty({ message: CategoryMessage.ORDER_REQUIRED })
@IsInt({ message: CategoryMessage.ORDER_SHOULD_BE_INT })
@ApiProperty({ description: "Category order", example: 1 })
order: number;
} }
@@ -14,6 +14,9 @@ export class DanakServiceCategory extends BaseEntity {
@Column({ type: "boolean", default: true }) @Column({ type: "boolean", default: true })
isActive: boolean; isActive: boolean;
@Column({ type: "int", default: 1 })
order: number;
@ManyToOne(() => DanakServiceCategory, (category) => category.children) @ManyToOne(() => DanakServiceCategory, (category) => category.children)
@JoinColumn({ name: "parentId" }) @JoinColumn({ name: "parentId" })
parent: DanakServiceCategory; parent: DanakServiceCategory;
@@ -1,5 +1,5 @@
import { BadRequestException, Injectable } from "@nestjs/common"; import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { FindOptionsWhere, In, IsNull } from "typeorm"; import { DataSource, FindOptionsWhere, In, IsNull, Not, QueryRunner } from "typeorm";
import { ParamDto } from "../../../common/DTO/param.dto"; import { ParamDto } from "../../../common/DTO/param.dto";
import { CategoryMessage, CommonMessage, ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum"; import { CategoryMessage, CommonMessage, ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
@@ -24,24 +24,67 @@ import { DanakServicesRepository } from "../repositories/danak-services.reposito
@Injectable() @Injectable()
export class DanakServicesService { export class DanakServicesService {
// private readonly logger = new Logger(DanakServicesService.name); private readonly logger = new Logger(DanakServicesService.name);
constructor( constructor(
private readonly danakServicesRepository: DanakServicesRepository, private readonly danakServicesRepository: DanakServicesRepository,
private readonly danakServicesCategoryRepository: DanakServicesCategoryRepository, private readonly danakServicesCategoryRepository: DanakServicesCategoryRepository,
private readonly danakServiceReviewRepository: DanakServiceReviewRepository, private readonly danakServiceReviewRepository: DanakServiceReviewRepository,
private readonly danakServicesImageRepository: DanakServicesImageRepository, private readonly danakServicesImageRepository: DanakServicesImageRepository,
private readonly dataSource: DataSource,
) {} ) {}
/******************************************** */ /******************************************** */
async createCategory(createDto: CreateCategoryDto) { async createCategory(createDto: CreateCategoryDto) {
const existCategory = await this.danakServicesCategoryRepository.findOneByTitle(createDto.title); const queryRunner = this.dataSource.createQueryRunner();
if (existCategory) throw new BadRequestException(CategoryMessage.TITLE_EXIST); await queryRunner.connect();
const category = this.danakServicesCategoryRepository.create(createDto); await queryRunner.startTransaction();
await this.danakServicesCategoryRepository.save(category);
try {
const existCategory = await queryRunner.manager.findOne(this.danakServicesCategoryRepository.target, {
where: { title: createDto.title },
});
if (existCategory) {
throw new BadRequestException(CategoryMessage.TITLE_EXIST);
}
const category = queryRunner.manager.create(this.danakServicesCategoryRepository.target, createDto);
await queryRunner.manager.save(this.danakServicesCategoryRepository.target, category);
await this.handleOrderCollision(queryRunner, category);
await queryRunner.commitTransaction();
return { return {
message: CommonMessage.CREATED, message: CommonMessage.CREATED,
category, category,
}; };
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error("Error in create category", error);
throw new BadRequestException(CategoryMessage.CREATION_FAILED);
} finally {
await queryRunner.release();
}
}
//*********************************** */
private async handleOrderCollision(queryRunner: QueryRunner, newCategory: DanakServiceCategory): Promise<void> {
const conflictingCategory = await queryRunner.manager.findOne(this.danakServicesCategoryRepository.target, {
where: {
order: newCategory.order,
id: Not(newCategory.id),
deletedAt: IsNull(),
},
});
if (conflictingCategory) {
const highestOrderCategory = await queryRunner.manager.findOne(this.danakServicesCategoryRepository.target, {
order: { order: "DESC" },
where: { deletedAt: IsNull() },
});
const newOrder = highestOrderCategory ? highestOrderCategory.order + 1 : newCategory.order + 1;
await queryRunner.manager.update(this.danakServicesCategoryRepository.target, conflictingCategory.id, { order: newOrder });
}
} }
/******************************************** */ /******************************************** */
@@ -56,19 +99,41 @@ export class DanakServicesService {
/******************************************** */ /******************************************** */
async updateCategory(categoryId: string, updateDto: UpdateCategoryDto) { async updateCategory(categoryId: string, updateDto: UpdateCategoryDto) {
const category = await this.danakServicesCategoryRepository.findOneById(categoryId); const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const category = await queryRunner.manager.findOne(DanakServiceCategory, { where: { id: categoryId } });
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST); if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
if (updateDto.title) { if (updateDto.title) {
const existCategory = await this.danakServicesCategoryRepository.findOneByTitle(updateDto.title, categoryId); const existCategory = await queryRunner.manager.findOne(DanakServiceCategory, {
where: { title: updateDto.title, id: Not(categoryId) },
});
if (existCategory) throw new BadRequestException(CategoryMessage.TITLE_EXIST); if (existCategory) throw new BadRequestException(CategoryMessage.TITLE_EXIST);
} }
await this.danakServicesCategoryRepository.save({ ...category, ...updateDto }); await queryRunner.manager.update(DanakServiceCategory, categoryId, updateDto);
if (updateDto.order !== undefined && updateDto.order !== category.order) {
await this.handleOrderCollision(queryRunner, { ...category, ...updateDto });
}
await queryRunner.commitTransaction();
return { return {
message: CommonMessage.UPDATE_SUCCESS, message: CommonMessage.UPDATE_SUCCESS,
}; };
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error("Error in update category", error);
throw new BadRequestException(CategoryMessage.UPDATE_FAILED);
} finally {
await queryRunner.release();
} }
}
/******************************************** */
async deleteCategoryById(categoryId: string) { async deleteCategoryById(categoryId: string) {
const category = await this.danakServicesCategoryRepository.findOneById(categoryId); const category = await this.danakServicesCategoryRepository.findOneById(categoryId);
@@ -131,7 +196,7 @@ export class DanakServicesService {
async getCategoriesUserSide() { async getCategoriesUserSide() {
const categories = await this.danakServicesCategoryRepository.find({ const categories = await this.danakServicesCategoryRepository.find({
where: { isActive: true, deletedAt: IsNull() }, where: { isActive: true, deletedAt: IsNull() },
order: { createdAt: "DESC" }, order: { order: "DESC" },
}); });
return { return {
categories, categories,