diff --git a/src/modules/order/controllers/order.controller.ts b/src/modules/order/controllers/order.controller.ts index 77ab9d0..b669544 100644 --- a/src/modules/order/controllers/order.controller.ts +++ b/src/modules/order/controllers/order.controller.ts @@ -1,17 +1,14 @@ import { Controller, Get, Post, Param, UseGuards, Patch, Query, Body, Delete } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiHeader, ApiBody, ApiQuery } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; 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 { Permissions } from 'src/common/decorators/permissions.decorator'; -import { Permission } from 'src/common/enums/permission.enum'; -import { CreateOrderDto } from '../dto/create-order.dto'; -import { CreateInvoiceDto } from '../dto/create-invoice.dto'; +import { CreateOrderDto, CreateOrderItemDto } 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'; -import { AddOrderItemDto } from '../dto/add-order-item.dto'; +import { UpdateOrderItemDto } from '../dto/update-order-item.dto'; @ApiTags('orders') @ApiBearerAuth() @@ -26,6 +23,21 @@ export class OrderController { return this.orderService.createNewOrder(userId, body); } + @Patch('public/order/:id/item/:itemId') + @UseGuards(AuthGuard) + @ApiOperation({ summary: 'Update order item as user' }) + updateOrder(@Param('id') orderId: string, @Param(':itemId') itemId: string, @Body() body: UpdateOrderItemDto) { + return this.orderService.updateOrderItemAsUser(orderId, itemId, body); + } + + @Patch('public/order/:id/item') + @UseGuards(AuthGuard) + @ApiOperation({ summary: 'add item to order as user' }) + addOrderItem(@Param('id') orderId: string, @Body() body: CreateOrderItemDto) { + return this.orderService.addOrderItemAsUser(orderId, body); + } + + @Get('public/orders') @UseGuards(AuthGuard) @ApiOperation({ summary: 'Get all orders with pagination and filters' }) @@ -46,7 +58,6 @@ export class OrderController { } - /*========================== Admin Routes =====================*/ @Post('admin/order') @@ -81,7 +92,7 @@ export class OrderController { @UseGuards(AuthGuard) @ApiOperation({ summary: 'Assign Order Designer ' }) assignDesigner(@Param('orderId') orderId: string, @Body() body: AssignDesignerDto) { - return this.orderService.asignDesigner(orderId, body.designerId); + return this.orderService.assignDesigner(orderId, body.designerId); } diff --git a/src/modules/order/dto/create-invoice.dto.ts b/src/modules/order/dto/create-invoice.dto.ts deleted file mode 100644 index 12bd328..0000000 --- a/src/modules/order/dto/create-invoice.dto.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - IsInt, IsArray, IsNumber, - IsNotEmpty, - ArrayMinSize, - IsBoolean, - IsString -} from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; - -export class InvoiceItemDto { - @IsNumber() - @ApiProperty() - orderItemId: number; - - @ApiProperty() - @IsNumber() - unitPrice: number - - @ApiProperty() - @IsNumber() - discount: number - - @ApiProperty() - @IsNumber() - quantity: number - -} - -export class InvoiceNewItemDto { - @IsNumber() - @ApiProperty() - productId: number; - - @ApiProperty() - @IsArray() - attributesValues: string[] - - @ApiProperty() - @IsNumber() - quantity: number - - @ApiProperty() - @IsNumber() - unitPrice: number - - @ApiProperty() - @IsNumber() - discount: number - -} - -export class CreateInvoiceDto { - @IsArray() - @ApiProperty({ - isArray: true, type: [InvoiceItemDto], example: [ - { - orderItemId: 1, - unitPrice: 100, - } - ] - }) - @IsNotEmpty() - @ArrayMinSize(1, { message: 'At least one product is required' }) - @Type(() => InvoiceItemDto) - items: InvoiceItemDto[]; - - @IsArray() - @ApiProperty({ - isArray: true, type: [InvoiceItemDto], example: [ - { - orderItemId: 1, - unitPrice: 100, - } - ] - }) - @Type(() => InvoiceNewItemDto) - newItems: InvoiceNewItemDto[]; - - @ApiProperty() - @IsBoolean() - enableTax: boolean - - - @ApiPropertyOptional({ example: [] }) - @IsArray() - @IsString({ each: true }) - attachments: string[] - -} - diff --git a/src/modules/order/dto/create-order.dto.ts b/src/modules/order/dto/create-order.dto.ts index 2d6f7c4..4acd523 100644 --- a/src/modules/order/dto/create-order.dto.ts +++ b/src/modules/order/dto/create-order.dto.ts @@ -16,7 +16,7 @@ export class CreateOrderItemDto { @ApiProperty() @IsNumber() quantity: number - + // TODO : find a way to save attribute and value @ApiProperty() @IsArray() attributesValues: string[] diff --git a/src/modules/order/dto/update-order-item.dto.ts b/src/modules/order/dto/update-order-item.dto.ts new file mode 100644 index 0000000..f151533 --- /dev/null +++ b/src/modules/order/dto/update-order-item.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateOrderItemDto } from './create-order.dto'; + +export class UpdateOrderItemDto extends PartialType(CreateOrderItemDto) { } + + diff --git a/src/modules/order/dto/update-order.dto.ts b/src/modules/order/dto/update-order.dto.ts index 14779bd..01213fd 100644 --- a/src/modules/order/dto/update-order.dto.ts +++ b/src/modules/order/dto/update-order.dto.ts @@ -1 +1,91 @@ -export class UpdateOrderDto {} +import { + IsArray, IsNumber, + IsNotEmpty, + ArrayMinSize, + IsBoolean, + IsString +} from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; + +export class ItemDto { + @IsNumber() + @ApiProperty() + orderItemId: number; + + @ApiProperty() + @IsNumber() + unitPrice: number + + @ApiProperty() + @IsNumber() + discount: number + + @ApiProperty() + @IsNumber() + quantity: number + +} + +export class NewItemDto { + @IsNumber() + @ApiProperty() + productId: number; + + @ApiProperty() + @IsArray() + attributesValues: string[] + + @ApiProperty() + @IsNumber() + quantity: number + + @ApiProperty() + @IsNumber() + unitPrice: number + + @ApiProperty() + @IsNumber() + discount: number + +} + +export class UpdateNewOrderDto { + @IsArray() + @ApiProperty({ + isArray: true, type: [ItemDto], example: [ + { + orderItemId: 1, + unitPrice: 100, + } + ] + }) + @IsNotEmpty() + @ArrayMinSize(1, { message: 'At least one product is required' }) + @Type(() => ItemDto) + items: ItemDto[]; + + @IsArray() + @ApiProperty({ + isArray: true, type: [ItemDto], example: [ + { + orderItemId: 1, + unitPrice: 100, + } + ] + }) + @Type(() => NewItemDto) + newItems: NewItemDto[]; + + @ApiProperty() + @IsBoolean() + enableTax: boolean + + + @ApiPropertyOptional({ example: [] }) + @IsArray() + @IsString({ each: true }) + attachments: string[] + +} + diff --git a/src/modules/order/providers/order.service.ts b/src/modules/order/providers/order.service.ts index d6d6850..a63689d 100644 --- a/src/modules/order/providers/order.service.ts +++ b/src/modules/order/providers/order.service.ts @@ -4,7 +4,7 @@ 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 } from '../dto/create-order.dto'; +import { CreateOrderDto, CreateOrderItemDto } from '../dto/create-order.dto'; import { UserService } from 'src/modules/user/providers/user.service'; import { OrderStatusEnum } from '../interface/order.interface'; import { OrderItemRepository } from '../repositories/order-item.repository'; @@ -12,11 +12,11 @@ import { ProductService } from 'src/modules/product/providers/product.service'; import { ProductRepository } from 'src/modules/product/repositories/product.repository'; import { TicketService } from 'src/modules/ticket/providers/tickets.service'; import { TicketRepository } from 'src/modules/ticket/repositories/tickets.repository'; -import { CreateInvoiceDto } from '../dto/create-invoice.dto'; import { AdminRepository } from 'src/modules/admin/repositories/admin.repository'; 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'; @Injectable() @@ -27,7 +27,6 @@ export class OrderService { private readonly em: EntityManager, private readonly orderRepository: OrderRepository, private readonly orderItemRepository: OrderItemRepository, - // private readonly paymentsService: PaymentService, private readonly userService: UserService, private readonly productService: ProductService, private readonly productRepository: ProductRepository, @@ -215,98 +214,181 @@ export class OrderService { return order } - async updateOrder(orderId: string, dto: CreateInvoiceDto) { - const { items, enableTax, attachments, newItems } = dto + async updateOrderItemAsUser(orderId: string, itemId: string, dto: UpdateOrderItemDto) { + const { attributesValues, description, productId, quantity, attachments } = dto - const targetOrder = await this.findOneOrFail(orderId) + const order = await this.findOneOrFail(orderId) - if (items.length !== targetOrder.items.length) { - throw new BadRequestException("One or more order items price is not set") + if (!order) { + throw new BadRequestException(`Order not found`) } - //TODO : calc is not correct - const order = await this.em.transactional(async (em) => { + if (order.status !== OrderStatusEnum.CREATED) { + throw new BadRequestException(`You can not update when status is ${order.status}`) + } - let subTotal = 0 - let totalDiscount = 0 - // Calculate order item finnicials - - for (let orderItem of targetOrder.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 - // orderItem.subTotal = (found.unitPrice) * orderItem.quantity - - - subTotal += orderItem.subTotal - - em.persist(orderItem) - - } - - newItems.forEach(async item => { - const found = order.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 - }) - - subTotal += item.unitPrice - - this.em.persist(newOrderItem) - }) - - // calculate order financials - targetOrder.subTotal = subTotal - targetOrder.discount = totalDiscount - - const total = subTotal - totalDiscount - let tax = 0 - - if (enableTax) { - tax = 0.1 * total - } - - targetOrder.taxAmount = tax - targetOrder.total = total + tax - targetOrder.balance = total + tax - - if (attachments.length) { - order.attachments = attachments - } - - // change status - targetOrder.status = OrderStatusEnum.INVOICED - targetOrder.invoicedAt = new Date() - - em.persist(targetOrder) - - - return targetOrder + const orderItem = await this.orderItemRepository.findOne({ + id: itemId }) - return order + if (!orderItem) { + throw new BadRequestException(`orderItem not found`) + } + + // product is changed + if (productId && orderItem.product.id !== productId) { + const product = await this.productRepository.findOne({ id: productId }) + + if (!product) { + throw new BadRequestException(`product not found`) + } + + orderItem.product = product + } + + if (attributesValues) { + orderItem.attributesValues = attributesValues + } + if (description) { + orderItem.description = description + } + if (quantity) { + orderItem.quantity = quantity + } + + if (attachments) { + orderItem.attachments = attachments + } + + + return orderItem } + async addOrderItemAsUser(orderId: string, dto: CreateOrderItemDto) { + const { attributesValues, description, productId, quantity, attachments } = dto + + 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 found = order.items.find(it => it.product.id == productId) + if (found) { + throw new BadRequestException(`Product already exists`) + } + + const product = await this.productRepository.findOne({ id: productId }) + if (!product) { + throw new BadRequestException(`Product not found`) + } + const orderItem = this.orderItemRepository.create({ + order, + attributesValues, + description, + quantity, + attachments, + subTotal: 0, + discount: 0, + total: 0, + unitPrice: 0, + product + }) + + this.em.persistAndFlush(orderItem) + + return orderItem + } + + // 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 } }, @@ -327,7 +409,7 @@ export class OrderService { return orderItem } - async asignDesigner(orderId: string, designerId: string) { + async assignDesigner(orderId: string, designerId: string) { const order = await this.orderRepository.findOne({ id: orderId }) if (!order) { throw new BadRequestException("Order not found") @@ -382,40 +464,5 @@ export class OrderService { return order } - // async addOrderItem(orderId: string, dto: AddOrderItemDto) { - // const order = await this.orderRepository.findOne({ id: orderId }) - // if (!order) { - // throw new BadRequestException("Order not Found!") - // } - // const { items } = dto - // items.forEach(async item => { - // const found = order.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, - // status: OrderItemStatus.CONFIRMED, - // attributesValues: item.attributesValues, - // order - // }) - - // this.em.persist(newOrderItem) - // }) - // // need calculate order finacial - // await this.em.flush() - - // return order - // } } diff --git a/src/modules/product/repositories/product.repository.ts b/src/modules/product/repositories/product.repository.ts index 6012223..b94176d 100644 --- a/src/modules/product/repositories/product.repository.ts +++ b/src/modules/product/repositories/product.repository.ts @@ -45,7 +45,7 @@ export class ProductRepository extends EntityRepository { limit, offset, orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, - populate: ['category'], + populate: ['attributes', 'attributes.values'], }); const totalPages = Math.ceil(total / limit); @@ -60,6 +60,5 @@ export class ProductRepository extends EntityRepository { }, }; - } }