This commit is contained in:
2026-01-17 13:16:43 +03:30
parent 2ddf892793
commit 1c45abcede
11 changed files with 161 additions and 43 deletions
@@ -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')
+29
View File
@@ -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[]
}
@@ -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;
+9 -10
View File
@@ -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<OrderItem>(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;
}
@@ -9,5 +9,5 @@ export enum OrderStatus {
export enum OrderItemStatus {
PENDING = 'pending',
VERIFIED = 'verified',
CONFIRMED = 'confirmed',
}
+80 -9
View File
@@ -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()
})
}
}
@@ -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<OrderItem> {
constructor(readonly em: EntityManager) {
super(em, OrderItem);
}
}
+10 -6
View File
@@ -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<Ticket>(this)
@Property({ type: 'json' })
attachments: string[]
}
@@ -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,
+3 -3
View File
@@ -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 -------------------
+4 -4
View File
@@ -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 { }