From fdf06b5d88f51284211631a05694aa50ffff767b Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Thu, 2 Jul 2026 19:49:15 +0330 Subject: [PATCH] update order print --- .../migrations/Migration20260626160000.ts | 13 --- src/modules/invoice/invoice.controller.ts | 8 ++ src/modules/invoice/invoice.service.ts | 12 +++ .../order/controllers/order.controller.ts | 4 +- .../order/dto/create-order-print.dto.ts | 25 +++--- src/modules/order/dto/find-orders.dto.ts | 15 ++++ .../order/dto/order-print-value.dto.ts | 13 +++ .../entities/order-print-value.entity.ts | 19 +++++ .../order/entities/order-print.entity.ts | 15 ++-- .../order/interface/order.interface.ts | 12 +++ src/modules/order/order.module.ts | 6 +- src/modules/order/providers/order.service.ts | 84 +++++++++++++++---- .../order-print-value.repository.ts | 10 +++ .../order/repositories/order.repository.ts | 16 ++-- 14 files changed, 189 insertions(+), 63 deletions(-) delete mode 100644 database/migrations/Migration20260626160000.ts create mode 100644 src/modules/order/dto/order-print-value.dto.ts create mode 100644 src/modules/order/entities/order-print-value.entity.ts create mode 100644 src/modules/order/repositories/order-print-value.repository.ts diff --git a/database/migrations/Migration20260626160000.ts b/database/migrations/Migration20260626160000.ts deleted file mode 100644 index 80621a9..0000000 --- a/database/migrations/Migration20260626160000.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260626160000 extends Migration { - - override async up(): Promise { - this.addSql(`alter table "request_items" drop column "quantity";`); - } - - override async down(): Promise { - this.addSql(`alter table "request_items" add column "quantity" int not null default 1;`); - } - -} diff --git a/src/modules/invoice/invoice.controller.ts b/src/modules/invoice/invoice.controller.ts index 953f344..2ddcee3 100644 --- a/src/modules/invoice/invoice.controller.ts +++ b/src/modules/invoice/invoice.controller.ts @@ -55,6 +55,14 @@ export class InvoiceController { return this.invoiceService.findAllAsAdmin(dto); } + @Get('admin/invoice/item/:id') + @UseGuards(AdminAuthGuard) + @Permissions(PermissionEnum.VIEW_INVOICES) + @ApiOperation({ summary: 'Get one invoice item by id (admin)' }) + findInvoiceItemAsAdmin(@Param('id') id: string) { + return this.invoiceService.findInvoiceItemAsAdmin(id); + } + @Get('admin/invoice/:id') @UseGuards(AdminAuthGuard) @Permissions(PermissionEnum.VIEW_INVOICES) diff --git a/src/modules/invoice/invoice.service.ts b/src/modules/invoice/invoice.service.ts index 13e3665..36123f0 100644 --- a/src/modules/invoice/invoice.service.ts +++ b/src/modules/invoice/invoice.service.ts @@ -203,6 +203,18 @@ export class InvoiceService { return invoice; } + async findInvoiceItemAsAdmin(id: string) { + const item = await this.em.findOne( + InvoiceItem, + { id }, + { populate: ['product', 'invoice', 'invoice.user'] }, + ); + if (!item) { + throw new NotFoundException('Invoice item not found'); + } + return item; + } + async confirmInvoiceItem(id: string, userId: string) { const item = await this.em.findOne( diff --git a/src/modules/order/controllers/order.controller.ts b/src/modules/order/controllers/order.controller.ts index 5a4d7f3..ee274ab 100644 --- a/src/modules/order/controllers/order.controller.ts +++ b/src/modules/order/controllers/order.controller.ts @@ -1,5 +1,5 @@ import { Controller, Get, Post, Param, UseGuards, Patch, Query, Body, Delete } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiOkResponse } from '@nestjs/swagger'; import { OrderService } from '../providers/order.service'; import { AuthGuard } from '../../auth/guards/auth.guard'; import { UserId } from '../../../common/decorators/user-id.decorator'; @@ -108,6 +108,7 @@ export class OrderController { @UseGuards(AdminAuthGuard) @Permissions(PermissionEnum.CREATE_PRINT) @ApiOperation({ summary: 'Create order print' }) + @ApiOkResponse({ description: 'Order print created or updated' }) createPrint(@Param('orderId') orderId: string, @Body() body: CreateOrderPrintDto) { return this.orderService.createPrint(orderId, body); } @@ -116,6 +117,7 @@ export class OrderController { @UseGuards(AdminAuthGuard) @Permissions(PermissionEnum.VIEW_PRINT) @ApiOperation({ summary: 'Get order print' }) + @ApiOkResponse({ description: 'Order print details' }) getPrint(@Param('orderId') orderId: string) { return this.orderService.getPrint(orderId); } diff --git a/src/modules/order/dto/create-order-print.dto.ts b/src/modules/order/dto/create-order-print.dto.ts index 7861f97..fdea558 100644 --- a/src/modules/order/dto/create-order-print.dto.ts +++ b/src/modules/order/dto/create-order-print.dto.ts @@ -1,19 +1,16 @@ -import { - IsArray, -} from 'class-validator'; +import { IsArray, ValidateNested } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { IField } from 'src/modules/request/interface/request.interface'; - +import { Type } from 'class-transformer'; +import { OrderPrintValueDto } from './order-print-value.dto'; export class CreateOrderPrintDto { - - @ApiProperty({ - example: [ - { fieldId: '', value: '' } - ] - }) - @IsArray() - fields: IField[]; - + @ApiProperty({ + type: [OrderPrintValueDto], + example: [{ fieldId: '', value: '' }], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => OrderPrintValueDto) + fields: OrderPrintValueDto[]; } diff --git a/src/modules/order/dto/find-orders.dto.ts b/src/modules/order/dto/find-orders.dto.ts index 59846eb..d7a02a8 100644 --- a/src/modules/order/dto/find-orders.dto.ts +++ b/src/modules/order/dto/find-orders.dto.ts @@ -59,6 +59,21 @@ export class FindOrdersDto { @IsDateString() to?: string; + @ApiPropertyOptional() + @IsOptional() + @IsString() + categoryId?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + userId?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + designerId?: string; + @ApiPropertyOptional({ default: 'createdAt' }) @IsOptional() @IsString() diff --git a/src/modules/order/dto/order-print-value.dto.ts b/src/modules/order/dto/order-print-value.dto.ts new file mode 100644 index 0000000..487cf03 --- /dev/null +++ b/src/modules/order/dto/order-print-value.dto.ts @@ -0,0 +1,13 @@ +import { IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class OrderPrintValueDto { + @ApiProperty({ example: '01HXXXXXXXXXXXXXXXXXXXXX' }) + @IsString() + @IsNotEmpty() + fieldId: string; + + @ApiProperty({ example: 'some value' }) + @IsString() + value: string; +} diff --git a/src/modules/order/entities/order-print-value.entity.ts b/src/modules/order/entities/order-print-value.entity.ts new file mode 100644 index 0000000..0639acd --- /dev/null +++ b/src/modules/order/entities/order-print-value.entity.ts @@ -0,0 +1,19 @@ +import { Entity, Index, ManyToOne, OptionalProps, Property } from '@mikro-orm/core'; +import { BaseEntity } from 'src/common/entities/base.entity'; +import { Field } from 'src/modules/form-builder/entities/field.entity'; +import { OrderPrint } from './order-print.entity'; + +@Entity({ tableName: 'order_print_values' }) +@Index({ properties: ['orderPrint'] }) +export class OrderPrintValue extends BaseEntity { + [OptionalProps]?: 'createdAt' | 'deletedAt'; + + @ManyToOne(() => OrderPrint) + orderPrint!: OrderPrint; + + @ManyToOne(() => Field) + field!: Field; + + @Property({ type: 'text' }) + value!: string; +} diff --git a/src/modules/order/entities/order-print.entity.ts b/src/modules/order/entities/order-print.entity.ts index 9724734..9364374 100644 --- a/src/modules/order/entities/order-print.entity.ts +++ b/src/modules/order/entities/order-print.entity.ts @@ -1,18 +1,15 @@ -import { Entity, Property, OptionalProps, OneToOne } from '@mikro-orm/core'; +import { Collection, Entity, OneToMany, OptionalProps, OneToOne } from '@mikro-orm/core'; import { BaseEntity } from 'src/common/entities/base.entity'; -import { IField } from 'src/modules/request/interface/request.interface'; import { Order } from './order.entity'; - +import { OrderPrintValue } from './order-print-value.entity'; @Entity({ tableName: 'order_prints' }) export class OrderPrint extends BaseEntity { - [OptionalProps]?: 'createdAt' | 'deletedAt' - - @Property({ type: 'json', nullable: true }) - fileds?: IField[] + [OptionalProps]?: 'createdAt' | 'deletedAt'; + @OneToMany(() => OrderPrintValue, (value) => value.orderPrint, { orphanRemoval: true }) + values = new Collection(this); @OneToOne(() => Order, (order) => order.print, { mappedBy: 'print' }) - order!: Order - + order!: Order; } diff --git a/src/modules/order/interface/order.interface.ts b/src/modules/order/interface/order.interface.ts index 52b63ac..7fc3ff1 100644 --- a/src/modules/order/interface/order.interface.ts +++ b/src/modules/order/interface/order.interface.ts @@ -29,6 +29,18 @@ export enum OrderStatusEnum { export interface IAttachment { type: string, url: string } +export interface IOrderPrintValue { + fieldId: string; + value: string; +} + +export interface IOrderPrintResponse { + id: string; + createdAt: Date; + deletedAt?: Date; + fileds: IOrderPrintValue[]; +} + export interface CreateOrder { userId: string diff --git a/src/modules/order/order.module.ts b/src/modules/order/order.module.ts index a1768d4..1ed8ea5 100644 --- a/src/modules/order/order.module.ts +++ b/src/modules/order/order.module.ts @@ -4,6 +4,7 @@ import { OrderService } from './providers/order.service'; import { OrderController } from './controllers/order.controller'; import { Order } from './entities/order.entity'; import { OrderPrint } from './entities/order-print.entity'; +import { OrderPrintValue } from './entities/order-print-value.entity'; import { AuthModule } from '../auth/auth.module'; import { PaymentModule } from '../payment/payments.module'; import { JwtModule } from '@nestjs/jwt'; @@ -17,10 +18,11 @@ import { UtilsModule } from '../util/utils.module'; import { ProductModule } from '../product/product.module'; import { ChatModule } from '../chat/chat.module'; import { OrderPrintRepository } from './repositories/order-print.repository'; +import { OrderPrintValueRepository } from './repositories/order-print-value.repository'; @Module({ imports: [ - MikroOrmModule.forFeature([Order, OrderPrint]), + MikroOrmModule.forFeature([Order, OrderPrint, OrderPrintValue]), AuthModule, forwardRef(() => PaymentModule), JwtModule, @@ -33,7 +35,7 @@ import { OrderPrintRepository } from './repositories/order-print.repository'; ], controllers: [OrderController], providers: [OrderService, OrderRepository, OrderListeners, - OrdersCrone, OrderPrintRepository], + OrdersCrone, OrderPrintRepository, OrderPrintValueRepository], exports: [OrderRepository, OrderService], }) export class OrderModule { } diff --git a/src/modules/order/providers/order.service.ts b/src/modules/order/providers/order.service.ts index 35bdd58..010c03b 100644 --- a/src/modules/order/providers/order.service.ts +++ b/src/modules/order/providers/order.service.ts @@ -7,7 +7,7 @@ import { CreateOrderAsAdminDto, } from '../dto/create-order.dto'; import { UserService } from 'src/modules/user/providers/user.service'; -import { OrderStatusEnum } from '../interface/order.interface'; +import { OrderStatusEnum, IOrderPrintResponse } from '../interface/order.interface'; import { Order } from '../entities/order.entity'; import { AdminService } from 'src/modules/admin/providers/admin.service'; import { UpdateOrderAsAdminDto } from '../dto/update-order.dto'; @@ -19,8 +19,11 @@ import { InvoiceItem } from 'src/modules/invoice/entities/invoice-item.entity'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { UpdateStatusDto } from '../dto/update-status.dto'; import { CreateOrderPrintDto } from '../dto/create-order-print.dto'; +import { OrderPrintValueDto } from '../dto/order-print-value.dto'; import { OrderPrint } from '../entities/order-print.entity'; +import { OrderPrintValue } from '../entities/order-print-value.entity'; import { OrderPrintRepository } from '../repositories/order-print.repository'; +import { Field } from 'src/modules/form-builder/entities/field.entity'; import { Request } from 'src/modules/request/entities/request.entity'; import { Invoice } from 'src/modules/invoice/entities/invoice.entity'; import { OrderCreatedEvent } from '../events/order.events'; @@ -44,7 +47,7 @@ export class OrderService { let user: User | null = null if (dto.invoiceItemId) { - invoiceItem = await this.em.findOne(InvoiceItem, { id: dto.invoiceItemId }, { populate: ['invoice', 'invoice.user'] }); + invoiceItem = await this.em.findOne(InvoiceItem, { id: dto.invoiceItemId }, { populate: ['invoice', 'invoice.user', 'product'] }); if (!invoiceItem) { throw new NotFoundException(`Invoice item with ID ${dto.invoiceItemId} not found.`); } @@ -86,6 +89,8 @@ export class OrderService { if (!product) { throw new NotFoundException(`Product with ID ${dto.productId} not found.`); } + } else if (invoiceItem?.product) { + product = invoiceItem.product; } let designer = null; @@ -233,29 +238,74 @@ export class OrderService { return order } - async createPrint(orderId: string, dto: CreateOrderPrintDto): Promise { - const order = await this.findOrderOrFail(orderId); - - if (order.print) { - order.print.fileds = dto.fields; - await this.em.flush(); - return order.print; + async createPrint(orderId: string, dto: CreateOrderPrintDto): Promise { + const order = await this.em.findOne(Order, { id: orderId }, { populate: ['print'] }); + if (!order) { + throw new NotFoundException(`Order with ID ${orderId} not found.`); } - const print = this.em.create(OrderPrint, { - order, - fileds: dto.fields, - }); + const fieldMap = await this.resolvePrintFields(dto.fields); + + let print = order.print; + if (print) { + await this.em.populate(print, ['values']); + print.values.removeAll(); + } else { + print = this.em.create(OrderPrint, { order }); + } + + this.assignPrintValues(print, dto.fields, fieldMap); await this.em.persistAndFlush(print); - return print; + await this.em.populate(print, ['values', 'values.field']); + return this.serializeOrderPrint(print); } - async getPrint(orderId: string) { - const orderPrint = await this.orderPrintRepository.findOne({ order: { id: orderId } }); + async getPrint(orderId: string): Promise { + const orderPrint = await this.orderPrintRepository.findOne( + { order: { id: orderId } }, + { populate: ['values', 'values.field'] }, + ); if (!orderPrint) { throw new NotFoundException(`Print with ID ${orderId} not found.`); } - return orderPrint; + return this.serializeOrderPrint(orderPrint); + } + + private serializeOrderPrint(print: OrderPrint): IOrderPrintResponse { + return { + id: print.id, + createdAt: print.createdAt, + deletedAt: print.deletedAt, + fileds: print.values.getItems().map((item) => ({ + fieldId: item.field.id, + value: item.value, + })), + }; + } + + private async resolvePrintFields(dtoFields: OrderPrintValueDto[]): Promise> { + const fieldIds = [...new Set(dtoFields.map((field) => field.fieldId))]; + const fields = await this.em.find(Field, { id: { $in: fieldIds } }); + if (fields.length !== fieldIds.length) { + throw new NotFoundException('One or more fields not found.'); + } + return new Map(fields.map((field) => [field.id, field])); + } + + private assignPrintValues( + print: OrderPrint, + dtoFields: OrderPrintValueDto[], + fieldMap: Map, + ): void { + for (const dtoField of dtoFields) { + print.values.add( + this.em.create(OrderPrintValue, { + orderPrint: print, + field: fieldMap.get(dtoField.fieldId)!, + value: dtoField.value, + }), + ); + } } // -----------Hrlper Methods ----------- diff --git a/src/modules/order/repositories/order-print-value.repository.ts b/src/modules/order/repositories/order-print-value.repository.ts new file mode 100644 index 0000000..c054795 --- /dev/null +++ b/src/modules/order/repositories/order-print-value.repository.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; +import { OrderPrintValue } from '../entities/order-print-value.entity'; + +@Injectable() +export class OrderPrintValueRepository extends EntityRepository { + constructor(readonly em: EntityManager) { + super(em, OrderPrintValue); + } +} diff --git a/src/modules/order/repositories/order.repository.ts b/src/modules/order/repositories/order.repository.ts index aaf9389..4740cec 100644 --- a/src/modules/order/repositories/order.repository.ts +++ b/src/modules/order/repositories/order.repository.ts @@ -7,11 +7,9 @@ import { OrderStatusEnum } from '../interface/order.interface'; import { FindOrdersDto } from '../dto/find-orders.dto'; -class FindOrdersOpts extends FindOrdersDto { - userId?: string; - adminId?: string; - designerId?: string -}; +// class FindOrdersOpts extends FindOrdersDto { +// adminId?: string; +// }; @Injectable() export class OrderRepository extends EntityRepository { @@ -19,7 +17,7 @@ export class OrderRepository extends EntityRepository { super(em, Order); } - async findAllPaginated(opts: FindOrdersOpts): Promise> { + async findAllPaginated(opts: FindOrdersDto): Promise> { const { page = 1, limit = 10, @@ -30,8 +28,8 @@ export class OrderRepository extends EntityRepository { userId, from, to, + categoryId, designerId, - adminId } = opts; const offset = (page - 1) * limit; @@ -63,6 +61,10 @@ export class OrderRepository extends EntityRepository { where.designer = { id: designerId }; } + if (categoryId) { + where.type = { id: categoryId }; + } + // Filter by date range if (from || to) { where.createdAt = {};