From 1c45abcede4ea2d63ff3cb6225ae16636ce7526d Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 17 Jan 2026 13:16:43 +0330 Subject: [PATCH] order --- .../order/controllers/orders.controller.ts | 13 +-- src/modules/order/dto/create-order.dto.ts | 29 ++++++ .../order/entities/order-item.entity.ts | 7 +- src/modules/order/entities/order.entity.ts | 19 ++-- .../order/interface/order.interface.ts | 2 +- src/modules/order/providers/orders.service.ts | 89 +++++++++++++++++-- .../repositories/order-item.repository.ts | 11 +++ src/modules/ticket/entities/ticket.entity.ts | 16 ++-- .../ticket/providers/tickets.service.ts | 4 +- src/modules/ticket/tickets.controller.ts | 6 +- src/modules/ticket/tickets.module.ts | 8 +- 11 files changed, 161 insertions(+), 43 deletions(-) create mode 100644 src/modules/order/dto/create-order.dto.ts create mode 100644 src/modules/order/repositories/order-item.repository.ts diff --git a/src/modules/order/controllers/orders.controller.ts b/src/modules/order/controllers/orders.controller.ts index ad90358..c062a68 100644 --- a/src/modules/order/controllers/orders.controller.ts +++ b/src/modules/order/controllers/orders.controller.ts @@ -9,6 +9,7 @@ import { OrderStatus } from '../interface/order.interface'; import { UpdateOrderStatusDto } from '../dto/update-order-status.dto'; import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permission } from 'src/common/enums/permission.enum'; +import { CreateOrderDto } from '../dto/create-order.dto'; @ApiTags('orders') @ApiBearerAuth() @@ -16,13 +17,13 @@ import { Permission } from 'src/common/enums/permission.enum'; export class OrdersController { constructor(private readonly ordersService: OrdersService) { } + @Post('public/checkout') // @UseGuards(AuthGuard) - // @Post('public/checkout') - - // @ApiOperation({ summary: 'Checkout : create order and payment record' }) - // checkout(@UserId() userId: string,) { - // return this.ordersService.checkout(userId); - // } + @ApiOperation({ summary: 'Checkout : create order ' }) + @ApiBody({ type: CreateOrderDto }) + checkout(@UserId() userId: string, @Body() body: CreateOrderDto) { + return this.ordersService.createOrder(userId, body); + } // @UseGuards(AuthGuard) // @Get('public/orders') diff --git a/src/modules/order/dto/create-order.dto.ts b/src/modules/order/dto/create-order.dto.ts new file mode 100644 index 0000000..f19b74b --- /dev/null +++ b/src/modules/order/dto/create-order.dto.ts @@ -0,0 +1,29 @@ +import { IsString, IsOptional, IsBoolean, IsInt, Min, IsArray, IsNumber } from 'class-validator'; +import { ApiPropertyOptional, ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; + +export class CreateOrderDto { + @IsArray() + @ApiProperty({ isArray: true }) + items: CreateOrderItemDto[]; + + @IsString() + content: string + + @IsArray() + @IsString({ each: true }) + attachments: string[] +} + +export class CreateOrderItemDto { + + @IsInt() + @ApiProperty() + productId: bigint; + + @ApiProperty() + quantity: number + + @IsArray() + attributesValues: string[] +} \ No newline at end of file diff --git a/src/modules/order/entities/order-item.entity.ts b/src/modules/order/entities/order-item.entity.ts index e0230b8..a19f412 100644 --- a/src/modules/order/entities/order-item.entity.ts +++ b/src/modules/order/entities/order-item.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Enum, Index, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; +import { Entity, Enum, Index, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Order } from './order.entity'; import { Product } from 'src/modules/product/entities/product.entity'; @@ -10,13 +10,16 @@ import { OrderItemStatus } from '../interface/order.interface'; export class OrderItem extends BaseEntity { @PrimaryKey({ type: 'bigint', autoincrement: true }) id: bigint - + @ManyToOne(() => Order) order!: Order; @ManyToOne(() => Product) product!: Product; + @Property({ type: 'json' }) + attributesValues: string[] + @Property({ type: 'int' }) quantity!: number; diff --git a/src/modules/order/entities/order.entity.ts b/src/modules/order/entities/order.entity.ts index ca64e7d..d0b13db 100644 --- a/src/modules/order/entities/order.entity.ts +++ b/src/modules/order/entities/order.entity.ts @@ -20,9 +20,9 @@ import { ulid } from 'ulid'; @Index({ properties: ['user', 'status'] }) @Index({ properties: ['status'] }) export class Order extends BaseEntity { - @PrimaryKey({type:'string',columnType:'char(26)'}) - id:string=ulid() - + @PrimaryKey({ type: 'string', columnType: 'char(26)' }) + id: string = ulid() + @ManyToOne(() => User) user!: User; @@ -38,8 +38,8 @@ export class Order extends BaseEntity { }) items = new Collection(this); - @Property({ nullable: true }) - userAddress?: string | null; + // @Property({ nullable: true }) + // userAddress?: string | null; @Property({ type: 'bigint', autoincrement: true }) orderNumber: number; @@ -52,8 +52,8 @@ export class Order extends BaseEntity { subTotal!: number; - @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) - deliveryFee: number = 0; + // @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) + // deliveryFee: number = 0; @Property({ type: 'decimal', precision: 10, scale: 0 }) total!: number; @@ -64,12 +64,11 @@ export class Order extends BaseEntity { @Property({ type: 'decimal', precision: 10, scale: 0 }) balance!: number; - @Property({ type: 'text', nullable: true }) - description?: string; + // @Property({ type: 'text', nullable: true }) + // description?: string; @Enum(() => OrderStatus) status!: OrderStatus; - } diff --git a/src/modules/order/interface/order.interface.ts b/src/modules/order/interface/order.interface.ts index d43391a..58767a5 100644 --- a/src/modules/order/interface/order.interface.ts +++ b/src/modules/order/interface/order.interface.ts @@ -9,5 +9,5 @@ export enum OrderStatus { export enum OrderItemStatus { PENDING = 'pending', - VERIFIED = 'verified', + CONFIRMED = 'confirmed', } diff --git a/src/modules/order/providers/orders.service.ts b/src/modules/order/providers/orders.service.ts index c276182..4b7d0a5 100644 --- a/src/modules/order/providers/orders.service.ts +++ b/src/modules/order/providers/orders.service.ts @@ -3,35 +3,106 @@ import { EntityManager } from '@mikro-orm/postgresql'; import { Order } from '../entities/order.entity'; import { OrderItem } from '../entities/order-item.entity'; import { User } from '../../user/entities/user.entity'; - + import { PaymentMethodEnum, PaymentStatusEnum } from '../../payment/interface/payment'; - + import { PaymentsService } from '../../payment/services/payments.service'; - + import { OrderRepository } from '../repositories/order.repository'; import { FindOrdersDto } from '../dto/find-orders.dto'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { Payment } from 'src/modules/payment/entities/payment.entity'; - - + + import { EventEmitter2 } from '@nestjs/event-emitter'; import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events'; import { OrderMessage } from 'src/common/enums/message.enum'; - +import { CreateOrderDto } from '../dto/create-order.dto'; +import { UserService } from 'src/modules/user/providers/user.service'; +import { OrderItemStatus, OrderStatus } 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'; +import { TicketService } from 'src/modules/ticket/providers/tickets.service'; +import { TicketRepository } from 'src/modules/ticket/repositories/tickets.repository'; +import { TicketStatus } from 'src/modules/ticket/enums/ticket-status.enum'; + @Injectable() export class OrdersService { private readonly logger = new Logger(OrdersService.name); - constructor( private readonly em: EntityManager, - private readonly orderRepository: OrderRepository, + private readonly orderItemRepository: OrderItemRepository, private readonly paymentsService: PaymentsService, - + private readonly userService: UserService, + private readonly productService: ProductService, + private readonly productRepository: ProductRepository, + private readonly ticketRepository: TicketRepository, + private readonly ticketService: TicketService, private readonly eventEmitter: EventEmitter2, ) { } + async createOrder(userId: string, dto: CreateOrderDto) { + const { attachments, content, items } = dto + this.em.transactional(async (em) => { + const user = await this.userService.findById(userId) + if (!user) { + throw new BadRequestException("user not found") + } + const order = this.orderRepository.create({ + user, + discount: 0, + subTotal: 0, + orderNumber: 1, + total: 0, + paidAmount: 0, + balance: 0, + status: OrderStatus.NEW + }) + + em.persist(order) + + const productIds = items.map(item => item.productId) + const products = await this.productRepository.find({ + id: { $in: productIds } + }) + if (productIds.length !== products.length) { + throw new BadRequestException("some products not found") + } + items.forEach(item => { + const product = products.find(p => p.id == item.productId) + if (!product) { + throw new BadRequestException(`product ${product} not found`) + } + const orderItem = this.orderItemRepository.create({ + order, + attributesValues: item.attributesValues, + discount: 0, + product, + quantity: item.quantity, + status: OrderItemStatus.PENDING, + unitPrice: 0, + totalPrice: 0, + }) + em.persist(orderItem) + }); + + const ticket = this.ticketRepository.create({ + content, + user, + attachments, + status: TicketStatus.PENDING + }) + em.persist(ticket) + + em.flush() + + }) + + } + } diff --git a/src/modules/order/repositories/order-item.repository.ts b/src/modules/order/repositories/order-item.repository.ts new file mode 100644 index 0000000..f2b64e3 --- /dev/null +++ b/src/modules/order/repositories/order-item.repository.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; +import { OrderItem } from '../entities/order-item.entity'; + + +@Injectable() +export class OrderItemRepository extends EntityRepository { + constructor(readonly em: EntityManager) { + super(em, OrderItem); + } +} diff --git a/src/modules/ticket/entities/ticket.entity.ts b/src/modules/ticket/entities/ticket.entity.ts index ae82d80..62b6f74 100755 --- a/src/modules/ticket/entities/ticket.entity.ts +++ b/src/modules/ticket/entities/ticket.entity.ts @@ -7,24 +7,25 @@ import { Collection, Cascade, Enum, + PrimaryKey, } from '@mikro-orm/core'; import { BaseEntity } from "../../../common/entities/base.entity"; import { User } from "../../user/entities/user.entity"; import { TicketStatus } from "../enums/ticket-status.enum"; -import { Product } from 'src/modules/product/entities/product.entity'; +// import { Product } from 'src/modules/product/entities/product.entity'; import { Admin } from 'src/modules/admin/entities/admin.entity'; @Entity() export class Ticket extends BaseEntity { - @Property({ type: "bigint", primary: true }) - id: number; + @PrimaryKey({ type: "bigint", autoincrement: true }) + id: bigint; @Enum(() => TicketStatus) status: TicketStatus; - // TODO : create relation - @Property({ type: 'json' }) - products: Product | null; + + @Property({ type: 'text' }) + content: string; @ManyToOne(() => Admin) admin: Admin | null; @@ -35,6 +36,9 @@ export class Ticket extends BaseEntity { @ManyToOne(() => Ticket) parent: Ticket | null; + @OneToMany(()=>Ticket,(ticket)=>ticket.id) + children=new Collection(this) + @Property({ type: 'json' }) attachments: string[] } diff --git a/src/modules/ticket/providers/tickets.service.ts b/src/modules/ticket/providers/tickets.service.ts index 5e87c86..6526420 100755 --- a/src/modules/ticket/providers/tickets.service.ts +++ b/src/modules/ticket/providers/tickets.service.ts @@ -35,8 +35,8 @@ import dayjs from "dayjs"; // import { TicketsRepository } from "../repositories/tickets.repository"; @Injectable() -export class TicketsService { - private readonly logger = new Logger(TicketsService.name); +export class TicketService { + private readonly logger = new Logger(TicketService.name); // constructor( // @InjectQueue(TICKET.QUEUE_NAME) private readonly ticketQueue: Queue, diff --git a/src/modules/ticket/tickets.controller.ts b/src/modules/ticket/tickets.controller.ts index 1311385..b44f3fa 100755 --- a/src/modules/ticket/tickets.controller.ts +++ b/src/modules/ticket/tickets.controller.ts @@ -4,9 +4,9 @@ import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { CreateTicketCategoryDto } from "./DTO/create-ticket-category.dto"; import { CreateTicketMessageDto } from "./DTO/create-ticket-message.dto"; import { CreateTicketByAdminDto, CreateTicketDto } from "./DTO/create-ticket.dto"; - import { SearchTicketCategoryDto } from "./DTO/search-ticket-category.dto"; +import { SearchTicketCategoryDto } from "./DTO/search-ticket-category.dto"; import { SearchTicketQueryDto } from "./DTO/search-ticket-query.dto"; - import { TicketsService } from "./providers/tickets.service"; +import { TicketService } from "./providers/tickets.service"; import { ParamDto } from "../../common/DTO/param.dto"; @@ -15,7 +15,7 @@ import { ParamDto } from "../../common/DTO/param.dto"; @ApiTags("Tickets") export class TicketsController { - constructor(private readonly ticketsService: TicketsService) {} + constructor(private readonly ticketsService: TicketService) { } //----------------- ticket categories ------------------- diff --git a/src/modules/ticket/tickets.module.ts b/src/modules/ticket/tickets.module.ts index 68b7ad3..9d0abb7 100755 --- a/src/modules/ticket/tickets.module.ts +++ b/src/modules/ticket/tickets.module.ts @@ -4,9 +4,9 @@ import { Module } from "@nestjs/common"; import { TICKET } from "./constant"; import { Ticket } from "./entities/ticket.entity"; -import { TicketsService } from "./providers/tickets.service"; +import { TicketService } from "./providers/tickets.service"; - import { TicketRepository } from "./repositories/tickets.repository"; +import { TicketRepository } from "./repositories/tickets.repository"; import { TicketsController } from "./tickets.controller"; import { MikroOrmModule } from "@mikro-orm/nestjs"; @@ -17,8 +17,8 @@ import { MikroOrmModule } from "@mikro-orm/nestjs"; MikroOrmModule.forFeature([Ticket]), BullModule.registerQueue({ name: TICKET.QUEUE_NAME }), ], - providers: [TicketsService, TicketRepository], + providers: [TicketService, TicketRepository], controllers: [TicketsController], - exports: [TicketsService], + exports: [TicketService], }) export class TicketsModule { }