From b45bc93799b6bf5e114cc3899f4fb71dc3e31069 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Mon, 26 Jan 2026 09:41:07 +0330 Subject: [PATCH] order item --- .../order/controllers/order.controller.ts | 4 +- src/modules/order/dto/create-order.dto.ts | 26 +++- .../order/dto/update-order-item.dto.ts | 4 +- src/modules/order/dto/update-order.dto.ts | 2 +- .../order/interface/order.interface.ts | 13 +- src/modules/order/providers/order.service.ts | 147 ++++++------------ 6 files changed, 79 insertions(+), 117 deletions(-) diff --git a/src/modules/order/controllers/order.controller.ts b/src/modules/order/controllers/order.controller.ts index 4239ffb..34cea1f 100644 --- a/src/modules/order/controllers/order.controller.ts +++ b/src/modules/order/controllers/order.controller.ts @@ -4,7 +4,7 @@ import { OrderService } from '../providers/order.service'; import { AuthGuard } from '../../auth/guards/auth.guard'; import { UserId } from '../../../common/decorators/user-id.decorator'; import { FindOrdersDto } from '../dto/find-orders.dto'; -import { CreateOrderDto, CreateOrderItemDto } from '../dto/create-order.dto'; +import { CreateOrderDto, CreateOrderItemAsUserDto } from '../dto/create-order.dto'; import { AdminId } from 'src/common/decorators/admin-id.decorator'; import { CreateOrderAsAdminDto } from '../dto/create-order-as-admin.dto'; import { AssignDesignerDto } from '../dto/assign-designer.dto'; @@ -40,7 +40,7 @@ export class OrderController { @Patch('public/order/:id/item') @UseGuards(AuthGuard) @ApiOperation({ summary: 'add item to order as user' }) - addOrderItem(@Param('id') orderId: string, @Body() body: CreateOrderItemDto) { + addOrderItem(@Param('id') orderId: string, @Body() body: CreateOrderItemAsUserDto) { return this.orderService.addOrderItemAsUser(orderId, body); } diff --git a/src/modules/order/dto/create-order.dto.ts b/src/modules/order/dto/create-order.dto.ts index 4acd523..c85015c 100644 --- a/src/modules/order/dto/create-order.dto.ts +++ b/src/modules/order/dto/create-order.dto.ts @@ -1,13 +1,13 @@ import { - IsString, IsOptional, IsBoolean, - IsInt, Min, IsArray, IsNumber, + IsString, + IsInt, IsArray, IsNumber, IsNotEmpty, ArrayMinSize } from 'class-validator'; -import { ApiPropertyOptional, ApiProperty } from '@nestjs/swagger'; +import { ApiPropertyOptional, ApiProperty, OmitType } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -export class CreateOrderItemDto { +export class CreateOrderItemDtoAsAdmin { @IsInt() @ApiProperty() @@ -28,12 +28,24 @@ export class CreateOrderItemDto { @ApiPropertyOptional({ example: [] }) @IsArray() attachments: { url: string, type: string }[] + + @ApiProperty() + @IsNumber() + unitPrice: number + + @ApiProperty() + @IsNumber() + discount: number + } +export class CreateOrderItemAsUserDto extends OmitType(CreateOrderItemDtoAsAdmin, ['unitPrice', 'discount']) { } + + export class CreateOrderDto { @IsArray() @ApiProperty({ - isArray: true, type: [CreateOrderItemDto], example: [ + isArray: true, type: [CreateOrderItemAsUserDto], example: [ { productId: 1, quantity: 100, @@ -47,7 +59,7 @@ export class CreateOrderDto { }) @IsNotEmpty() @ArrayMinSize(1, { message: 'At least one item is required' }) - @Type(() => CreateOrderItemDto) - items: CreateOrderItemDto[]; + @Type(() => CreateOrderItemAsUserDto) + items: CreateOrderItemAsUserDto[]; } diff --git a/src/modules/order/dto/update-order-item.dto.ts b/src/modules/order/dto/update-order-item.dto.ts index f151533..5b4fc79 100644 --- a/src/modules/order/dto/update-order-item.dto.ts +++ b/src/modules/order/dto/update-order-item.dto.ts @@ -1,6 +1,6 @@ import { PartialType } from '@nestjs/swagger'; -import { CreateOrderItemDto } from './create-order.dto'; +import { CreateOrderItemAsUserDto } from './create-order.dto'; -export class UpdateOrderItemDto extends PartialType(CreateOrderItemDto) { } +export class UpdateOrderItemDto extends PartialType(CreateOrderItemAsUserDto) { } diff --git a/src/modules/order/dto/update-order.dto.ts b/src/modules/order/dto/update-order.dto.ts index 01213fd..f141262 100644 --- a/src/modules/order/dto/update-order.dto.ts +++ b/src/modules/order/dto/update-order.dto.ts @@ -50,7 +50,7 @@ export class NewItemDto { } -export class UpdateNewOrderDto { +export class UpdateOrderDto { @IsArray() @ApiProperty({ isArray: true, type: [ItemDto], example: [ diff --git a/src/modules/order/interface/order.interface.ts b/src/modules/order/interface/order.interface.ts index 8be865e..6ffe67e 100644 --- a/src/modules/order/interface/order.interface.ts +++ b/src/modules/order/interface/order.interface.ts @@ -26,7 +26,12 @@ export enum OrderStatusEnum { } -// export enum OrderItemStatus { -// PENDING = 'pending', -// CONFIRMED = 'confirmed', -// } +export interface AddOrderItem { + productId: bigint; + quantity: number + attributesValues: string[] + description: string + attachments: { url: string, type: string }[] + unitPrice?: number + discount?: number +} \ No newline at end of file diff --git a/src/modules/order/providers/order.service.ts b/src/modules/order/providers/order.service.ts index e9e736d..575a771 100644 --- a/src/modules/order/providers/order.service.ts +++ b/src/modules/order/providers/order.service.ts @@ -4,9 +4,9 @@ import { OrderItem } from '../entities/order-item.entity'; import { OrderRepository } from '../repositories/order.repository'; import { FindOrdersDto } from '../dto/find-orders.dto'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { CreateOrderDto, CreateOrderItemDto } from '../dto/create-order.dto'; +import { CreateOrderDto, CreateOrderItemAsUserDto, CreateOrderItemDtoAsAdmin } from '../dto/create-order.dto'; import { UserService } from 'src/modules/user/providers/user.service'; -import { OrderStatusEnum } from '../interface/order.interface'; +import { AddOrderItem, OrderStatusEnum } from '../interface/order.interface'; import { OrderItemRepository } from '../repositories/order-item.repository'; import { ProductService } from 'src/modules/product/providers/product.service'; import { ProductRepository } from 'src/modules/product/repositories/product.repository'; @@ -17,6 +17,7 @@ import { CreateOrderAsAdminDto } from '../dto/create-order-as-admin.dto'; import { Order } from '../entities/order.entity'; import { PaymentRepository } from 'src/modules/payment/repositories/payment.repository'; import { UpdateOrderItemDto } from '../dto/update-order-item.dto'; +import { UpdateOrderDto } from '../dto/update-order.dto'; @Injectable() @@ -96,19 +97,39 @@ export class OrderService { return order } - async addOrderItemAsUser(orderId: string, dto: CreateOrderItemDto) { - const { attributesValues, description, productId, quantity, attachments } = dto + async addOrderItemAsUser(orderId: string, dto: CreateOrderItemAsUserDto) { const order = await this.findOneOrFail(orderId) - if (!order) { - throw new BadRequestException(`Order not found`) - } - if (order.status !== OrderStatusEnum.CREATED) { throw new BadRequestException(`You can not update when status is ${order.status}`) } + const orderItem = this.persistOrderItem(order, dto) + + await this.em.flush() + + return orderItem + } + + async addOrderItemAsAdmin(orderId: string, dto: CreateOrderItemDtoAsAdmin) { + + const order = await this.findOneOrFail(orderId) + + const orderItem = this.persistOrderItem(order, dto) + + await this.em.flush() + + await this.calculateOrder(undefined, orderId) + + await this.em.flush() + + return orderItem + } + + private async persistOrderItem(order: Order, dto: AddOrderItem) { + const { attributesValues, description, productId, quantity, attachments, discount, unitPrice } = dto + const found = order.items.find(it => it.product.id == productId) if (found) { throw new BadRequestException(`Product already exists`) @@ -125,13 +146,13 @@ export class OrderService { quantity, attachments, subTotal: 0, - discount: 0, + discount: discount ?? 0, total: 0, - unitPrice: 0, + unitPrice: unitPrice ?? 0, product }) - this.em.persistAndFlush(orderItem) + this.em.persist(orderItem) return orderItem } @@ -274,7 +295,7 @@ export class OrderService { // TODO : calculation must be done after create order - await this.calculateOrder(order, em) + await this.calculateOrder(order) await em.flush() @@ -299,8 +320,17 @@ export class OrderService { return order } - async calculateOrder(order: Order, em: EntityManager) { - + async calculateOrder(inputOrder?: Order, orderId?: string) { + let order: undefined | Order = undefined + if (orderId) { + order = await this.findOneOrFail(orderId) + } + if (order) { + order = inputOrder + } + if (!order) { + throw new BadRequestException("Order not found") + } // calculate order financials let subTotal = 0 let totalDiscount = 0 @@ -330,91 +360,6 @@ export class OrderService { return order } - // async updateOrderAsAdmin(orderId: string, dto: UpdateNewOrderDto) { - // const { items, enableTax, attachments, newItems } = dto - - // const order = await this.findOneOrFail(orderId) - - // if (order.status !== OrderStatusEnum.CREATED) { - // throw new BadRequestException(`You can not update when status is ${order.status}`) - // } - - // const updatedOrder = await this.em.transactional(async (em) => { - - // for (let orderItem of order.items) { - - // const found = items.find(it => it.orderItemId == Number(orderItem.id)) - // if (!found) { - // throw new BadRequestException(`order item ${orderItem} not found`) - // } - // // TODO use Deciaml js to calculation - // orderItem.unitPrice = found.unitPrice - - // subTotal += orderItem.subTotal - - // em.persist(orderItem) - - // } - - // newItems.forEach(async item => { - // const found = updatedOrder.items.find((it => Number(it.product.id) === item.productId)) - - // if (found) { - // throw new BadRequestException("Product already exist!") - // } - // const product = await this.productRepository.findOne({ id: item.productId }) - // if (!product) { - // throw new BadRequestException("Product not found!") - // } - // const newOrderItem = this.orderItemRepository.create({ - // discount: item.discount, - // product, - // unitPrice: item.unitPrice, - // total: (item.unitPrice - item.discount) * item.quantity, - // subTotal: item.unitPrice * item.quantity, - // quantity: item.quantity, - // confirmedAt: new Date(), - // attributesValues: item.attributesValues, - // order: updatedOrder - // }) - - // subTotal += item.unitPrice - - // this.em.persist(newOrderItem) - // }) - - // // calculate order financials - // order.subTotal = subTotal - // order.discount = totalDiscount - - // const total = subTotal - totalDiscount - // let tax = 0 - - // if (enableTax) { - // tax = 0.1 * total - // } - - // order.taxAmount = tax - // order.total = total + tax - // order.balance = total + tax - - // if (attachments.length) { - // updatedOrder.attachments = attachments - // } - - // // change status - // order.status = OrderStatusEnum.INVOICED - // order.invoicedAt = new Date() - - // em.persist(order) - - - // return order - // }) - - // return updatedOrder - // } - async confirmOrderItem(userId: string, orderId: string, orderItemId: number) { const orderItem = await this.orderItemRepository.findOne({ id: orderItemId, order: { id: orderId } }, @@ -478,7 +423,7 @@ export class OrderService { } - async removeOrderAsUser(orderId:string){ + async removeOrderAsUser(orderId: string) { const order = await this.orderRepository.findOne({ id: orderId }) if (!order) { throw new BadRequestException("Order not found") @@ -488,7 +433,7 @@ export class OrderService { throw new BadRequestException("Order can not be deleted") } - return this.hardDeleteOrder(orderId) + return this.hardDeleteOrder(orderId) } async updateStatus(orderId: string, newStatus: OrderStatusEnum) {