From ef53604d91c24cec111c39226e70c7e6862dcfa4 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Mon, 9 Feb 2026 12:17:11 +0330 Subject: [PATCH] order --- src/common/entities/base.entity.ts | 2 +- src/modules/orders/entities/order.entity.ts | 7 +-- .../products/dto/create-category.dto.ts | 7 ++- .../products/entities/category.entity.ts | 8 +++- .../products/providers/category.service.ts | 44 ++++++++++++++----- 5 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/common/entities/base.entity.ts b/src/common/entities/base.entity.ts index 17681c0..e9d5845 100755 --- a/src/common/entities/base.entity.ts +++ b/src/common/entities/base.entity.ts @@ -5,7 +5,7 @@ import { ulid } from 'ulid'; @Index({ properties: ['deletedAt'] }) @Index({ properties: ['createdAt'] }) export abstract class BaseEntity { - [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'deletedAt'|'orderNumber'; + [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'deletedAt'|'orderNumber'|'children'; @PrimaryKey({ type: 'string', columnType: 'char(26)' }) id: string = ulid(); diff --git a/src/modules/orders/entities/order.entity.ts b/src/modules/orders/entities/order.entity.ts index 0df4769..8daa424 100644 --- a/src/modules/orders/entities/order.entity.ts +++ b/src/modules/orders/entities/order.entity.ts @@ -20,11 +20,6 @@ import { OrderUserAddress, OrderCarAddress } from '../interface/order.interface' import { Payment } from 'src/modules/payments/entities/payment.entity'; @Entity({ tableName: 'orders' }) -@Unique({ properties: ['shop', 'orderNumber'] }) -@Index({ properties: ['shop', 'status'] }) -@Index({ properties: ['user', 'status'] }) -@Index({ properties: ['shop', 'orderNumber'] }) -@Index({ properties: ['status'] }) export class Order extends BaseEntity { @ManyToOne(() => User) user!: User; @@ -56,7 +51,7 @@ export class Order extends BaseEntity { @ManyToOne(() => PaymentMethod) paymentMethod: PaymentMethod; - @Property({ type: 'int', defaultRaw: `nextval('order_number_seq')` }) + @Property({ type: 'int', autoincrement: true }) orderNumber!: number; @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) diff --git a/src/modules/products/dto/create-category.dto.ts b/src/modules/products/dto/create-category.dto.ts index 8646850..54f6046 100644 --- a/src/modules/products/dto/create-category.dto.ts +++ b/src/modules/products/dto/create-category.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsOptional, IsBoolean, IsInt, Min } from 'class-validator'; +import { IsString, IsOptional, IsBoolean, IsInt, Min, IsUUID } from 'class-validator'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; @@ -24,4 +24,9 @@ export class CreateCategoryDto { @Type(() => Number) @ApiPropertyOptional({ example: 1 }) order?: number; + + @IsOptional() + @IsUUID() + @ApiPropertyOptional({ example: '123e4567-e89b-12d3-a456-426614174000' }) + parentId?: string; } diff --git a/src/modules/products/entities/category.entity.ts b/src/modules/products/entities/category.entity.ts index 8b93768..f9b5fac 100644 --- a/src/modules/products/entities/category.entity.ts +++ b/src/modules/products/entities/category.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Index, Property, Collection, OneToMany, ManyToOne } from '@mikro-orm/core'; +import { Entity, Index, Property, Collection, OneToMany, ManyToOne, Cascade } from '@mikro-orm/core'; import { Product } from './product.entity'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Shop } from '../../shops/entities/shop.entity'; @@ -10,6 +10,12 @@ export class Category extends BaseEntity { @ManyToOne(() => Category, { nullable: true }) parent?: Category + @OneToMany(() => Category, (cat) => cat.parent, { + cascade: [Cascade.ALL], + orphanRemoval: true + }) + children: Category[] + @Property() title!: string; diff --git a/src/modules/products/providers/category.service.ts b/src/modules/products/providers/category.service.ts index b018e73..756fa93 100644 --- a/src/modules/products/providers/category.service.ts +++ b/src/modules/products/providers/category.service.ts @@ -20,11 +20,23 @@ export class CategoryService { if (!shop) { throw new NotFoundException(RestMessage.NOT_FOUND); } + + let parent: Category | undefined; + if (dto.parentId) { + const foundParent = await this.categoryRepository.findOne({ id: dto.parentId, shop: { id: shopId } }); + if (!foundParent) { + throw new NotFoundException(CategoryMessage.NOT_FOUND); + } + parent = foundParent; + } + const data: RequiredEntityData = { title: dto.title, isActive: dto.isActive ?? true, shop: shop, avatarUrl: dto.avatarUrl, + order: dto.order, + parent: parent, }; const category = this.categoryRepository.create(data); @@ -32,23 +44,20 @@ export class CategoryService { return category; } - async findAllByShop(slug: string): Promise { - const shop = await this.em.findOne(Shop, { slug }); - if (!shop || !shop.id) { - throw new NotFoundException(RestMessage.NOT_FOUND); - } + async findAllByShop(shopSlug: string): Promise { return this.categoryRepository.find( - { shop: shop, isActive: true }, { orderBy: { order: 'ASC' } }); + { shop: { slug: shopSlug }, isActive: true, parent: null }, + { orderBy: { order: 'ASC' }, populate: ['parent'] }); } async findAllByShopId(shopId: string): Promise { return this.categoryRepository.find( { shop: { id: shopId } }, - { orderBy: { order: 'asc' } }); + { orderBy: { order: 'asc' }, populate: ['parent'] }); } async findOne(shopId: string, id: string): Promise { - const cat = await this.categoryRepository.findOne({ id, shop: { id: shopId } }, { populate: ['products'] }); + const cat = await this.categoryRepository.findOne({ id, shop: { id: shopId } }, { populate: ['products', 'parent'] }); if (!cat) throw new NotFoundException(CategoryMessage.NOT_FOUND); return { @@ -57,17 +66,32 @@ export class CategoryService { isActive: cat.isActive, shop: cat.shop, avatarUrl: cat.avatarUrl, + order: cat.order, + parent: cat.parent, createdAt: cat.createdAt, updatedAt: cat.updatedAt, products: cat.products.getItems().map(f => ({ id: f.id, title: f.title })), } as unknown as Category; } - async update(shopId: string, id: string, dto: UpdateCategoryDto): Promise { const cat = await this.categoryRepository.findOne({ id, shop: { id: shopId } }); if (!cat) throw new NotFoundException(CategoryMessage.NOT_FOUND); - this.em.assign(cat, dto); + + // Handle parent relationship update + if (dto.parentId !== undefined) { + if (dto.parentId) { + const parent = await this.categoryRepository.findOne({ id: dto.parentId, shop: { id: shopId } }); + if (!parent) { + throw new NotFoundException(CategoryMessage.NOT_FOUND); + } + cat.parent = parent; + } else { + cat.parent = undefined; + } + } + + this.em.assign(cat, { ...dto, parentId: undefined }); // Exclude parentId from direct assignment await this.em.persistAndFlush(cat); return cat; }