schema created

This commit is contained in:
2026-02-18 11:40:59 +03:30
parent 6fda27491b
commit 9a8e00bf93
6 changed files with 91 additions and 208 deletions
+7 -6
View File
@@ -10,7 +10,7 @@ import {
} from 'class-validator'; } from 'class-validator';
import { ApiPropertyOptional, ApiProperty } from '@nestjs/swagger'; import { ApiPropertyOptional, ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IAttachment, IField, OrderStatusEnum } from '../interface/order.interface'; import { IAttachment, IField, OrderItemStatusEnum } from '../interface/order.interface';
export class CreateOrderItemAsUserDto { export class CreateOrderItemAsUserDto {
@@ -22,6 +22,10 @@ export class CreateOrderItemAsUserDto {
@IsNumber() @IsNumber()
quantity: number quantity: number
@ApiProperty({ enum: OrderItemStatusEnum })
@IsEnum(() => OrderItemStatusEnum)
status: OrderItemStatusEnum
@ApiProperty() @ApiProperty()
@IsArray() @IsArray()
attributes: IField[] attributes: IField[]
@@ -113,8 +117,8 @@ export class CreateOrderAsAdminDto {
unitPrice: 150000, unitPrice: 150000,
discount: 20000, discount: 20000,
adminDescription: '', adminDescription: '',
description:'توضیحات کاربر', description: 'توضیحات کاربر',
} }
] ]
}) })
@@ -137,8 +141,5 @@ export class CreateOrderAsAdminDto {
@IsInt() @IsInt()
estimatedDays: number estimatedDays: number
@ApiProperty({ enum: OrderStatusEnum })
@IsEnum(OrderStatusEnum)
status: OrderStatusEnum
} }
+14 -12
View File
@@ -1,7 +1,7 @@
import { Entity, Formula, Index, ManyToOne, OptionalProps, Property } from '@mikro-orm/core'; import { Entity, Enum, Index, ManyToOne, OptionalProps, Property } from '@mikro-orm/core';
import { Order } from './order.entity'; import { Order } from './order.entity';
import { Product } from 'src/modules/product/entities/product.entity'; import { Product } from 'src/modules/product/entities/product.entity';
import { IAttachment, IField } from '../interface/order.interface'; import { IAttachment, IField, OrderItemStatusEnum } from '../interface/order.interface';
import { Admin } from 'src/modules/admin/entities/admin.entity'; import { Admin } from 'src/modules/admin/entities/admin.entity';
import { BaseEntity } from 'src/common/entities/base.entity'; import { BaseEntity } from 'src/common/entities/base.entity';
@@ -17,6 +17,9 @@ export class OrderItem extends BaseEntity {
@ManyToOne(() => Product) @ManyToOne(() => Product)
product!: Product; product!: Product;
@Enum(() => OrderItemStatusEnum)
status!: OrderItemStatusEnum;
@ManyToOne(() => Admin, { nullable: true }) @ManyToOne(() => Admin, { nullable: true })
designer?: Admin designer?: Admin
@@ -26,24 +29,23 @@ export class OrderItem extends BaseEntity {
@Property({ type: 'int' }) @Property({ type: 'int' })
quantity!: number; quantity!: number;
@Property({ type: 'decimal', precision: 10, scale: 0, nullable: true, }) @Property({ type: 'int', nullable: true, })
unitPrice?: number; unitPrice?: number;
// @Formula(alias => ` // @Formula(alias => `
// COALESCE(${alias}.unit_price, 0) * ${alias}.quantity // COALESCE(${alias}.unit_price, 0) * ${alias}.quantity
// `) // `)
@Property({ @Property({
type: 'decimal', precision: 10, type: 'int',
columnType: 'numeric(10,0) GENERATED ALWAYS AS (COALESCE(unit_price, 0) * quantity) STORED', columnType: 'int GENERATED ALWAYS AS (COALESCE(unit_price, 0) * quantity) STORED',
persist: false, persist: false,
scale: 0, nullable: true
nullable: true
}) })
subTotal!: number; subTotal!: number;
@Property({ @Property({
type: 'decimal', precision: 10, type: 'int',
scale: 0, nullable: true nullable: true
}) })
discount?: number; discount?: number;
@@ -52,9 +54,9 @@ export class OrderItem extends BaseEntity {
// - COALESCE(${alias}.discount, 0) // - COALESCE(${alias}.discount, 0)
// `) // `)
@Property({ @Property({
type: 'decimal', precision: 10, type: 'int',
scale: 0, nullable: true, nullable: true,
columnType: 'numeric(10,0) GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED', columnType: 'int GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED',
persist: false persist: false
}) })
total!: number; total!: number;
+20 -152
View File
@@ -8,12 +8,8 @@ import {
Cascade, Cascade,
Enum, Enum,
PrimaryKey, PrimaryKey,
BeforeCreate,
type EventArgs,
OptionalProps, OptionalProps,
Formula
} from '@mikro-orm/core'; } from '@mikro-orm/core';
import { OrderStatusEnum } from '../interface/order.interface';
import { User } from '../../user/entities/user.entity'; import { User } from '../../user/entities/user.entity';
import { OrderItem } from './order-item.entity'; import { OrderItem } from './order-item.entity';
import { Payment } from 'src/modules/payment/entities/payment.entity'; import { Payment } from 'src/modules/payment/entities/payment.entity';
@@ -21,13 +17,12 @@ import { ulid } from 'ulid';
import { Admin } from 'src/modules/admin/entities/admin.entity'; import { Admin } from 'src/modules/admin/entities/admin.entity';
import { PaymentStatusEnum } from 'src/modules/payment/interface/payment'; import { PaymentStatusEnum } from 'src/modules/payment/interface/payment';
import { BaseEntity } from 'src/common/entities/base.entity'; import { BaseEntity } from 'src/common/entities/base.entity';
import { OrderStatusEnum } from '../interface/order.interface';
@Entity({ tableName: 'orders' }) @Entity({ tableName: 'orders' })
@Index({ properties: ['user', 'status'] }) @Index({ properties: ['user'] })
@Index({ properties: ['status'] }) export class Order extends BaseEntity {
export class Order extends BaseEntity{ [OptionalProps]?: 'createdAt' | 'deletedAt' | 'orderNumber'|'paidAmount'|'balance';
[OptionalProps]?: 'createdAt' | 'deletedAt' | 'balance' | 'paidAmount'
| 'discount' | 'subTotal' | 'total' | 'taxAmount';
@PrimaryKey({ type: 'string', columnType: 'char(26)' }) @PrimaryKey({ type: 'string', columnType: 'char(26)' })
id: string = ulid() id: string = ulid()
@@ -51,140 +46,32 @@ export class Order extends BaseEntity{
@ManyToOne(() => Admin, { nullable: true }) @ManyToOne(() => Admin, { nullable: true })
creator?: Admin creator?: Admin
@Property({ type: 'int', defaultRaw: `nextval('order_number_seq')` })
orderNumber!: number;
@Property({ type: 'int', nullable: true }) @Property({ type: 'int', nullable: true })
orderNumber?: number; discount?: number;
// @Property({ type: 'decimal', precision: 10, scale: 0, default: null, nullable: true }) @Property({ type: 'int', nullable: true })
// discount?: number; subTotal?: number;
@Formula(alias => `
(
SELECT COALESCE(SUM(oi.discount), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
)
`)
discount!: number;
// @Property({ type: 'decimal', precision: 10, scale: 0, default: null, nullable: true }) @Property({ type: 'int', nullable: true })
// subTotal?: number; taxAmount?: number;
@Formula(alias => `
(
SELECT COALESCE(SUM(COALESCE(oi.unit_price, 0) * oi.quantity), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
)
`)
subTotal!: number;
// @Property({ type: 'decimal', precision: 10, scale: 0, default: null, nullable: true }) @Property({ type: 'int', nullable: true })
// taxAmount?: number; total?: number;
@Formula(alias => `
(
CASE
WHEN ${alias}.enable_tax = true THEN
(
SELECT COALESCE(SUM(COALESCE(oi.unit_price, 0) * oi.quantity), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
) * 0.1
ELSE 0
END
)
`)
taxAmount!: number;
@Property({ type: 'int', default: 0 })
// @Property({ type: 'decimal', precision: 10, scale: 0, default: null, nullable: true }) paidAmount: number = 0;
// total?: number;
@Formula(alias => `
(
(
SELECT COALESCE(SUM(COALESCE(oi.unit_price, 0) * oi.quantity), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
)
-
(
SELECT COALESCE(SUM(oi.discount), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
)
+
CASE
WHEN ${alias}.enable_tax = true THEN
(
SELECT COALESCE(SUM(COALESCE(oi.unit_price, 0) * oi.quantity), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
) * 0.1
ELSE 0
END
)
`)
total!: number;
// @Property({ type: 'decimal', precision: 10, scale: 0, default: null, nullable: true }) @Property({ type: 'int', default: 0 })
// paidAmount?: number; balance: number = 0;
// private _balance!: number;
// set balance(value) {
// this._balance = this.total - this.paidAmount
// }
@Formula(alias => `
(
SELECT COALESCE(SUM(p.amount), 0)
FROM payments p
WHERE p.order_id = ${alias}.id
AND p.status = '${PaymentStatusEnum.Paid}'
)
`)
paidAmount!: number;
@Formula(alias => `
(
(
(
SELECT COALESCE(SUM(COALESCE(oi.unit_price, 0) * oi.quantity), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
)
-
(
SELECT COALESCE(SUM(oi.discount), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
)
+
CASE
WHEN ${alias}.enable_tax = true THEN
(
SELECT COALESCE(SUM(COALESCE(oi.unit_price, 0) * oi.quantity), 0)
FROM order_items oi
WHERE oi.order_id = ${alias}.id
) * 0.1
ELSE 0
END
)
-
(
SELECT COALESCE(SUM(p.amount), 0)
FROM payments p
WHERE p.order_id = ${alias}.id
AND p.status = '${PaymentStatusEnum.Paid}'
)
)
`)
balance!: number;
@Property({ type: 'int', nullable: true }) @Property({ type: 'int', nullable: true })
@@ -192,7 +79,7 @@ export class Order extends BaseEntity{
@Enum(() => OrderStatusEnum) @Enum(() => OrderStatusEnum)
status!: OrderStatusEnum; status: OrderStatusEnum = OrderStatusEnum.REQUEST;
@Property({ type: 'string', nullable: true }) @Property({ type: 'string', nullable: true })
@@ -202,26 +89,7 @@ export class Order extends BaseEntity{
invoicedAt?: Date; invoicedAt?: Date;
@Property({ type: 'boolean', default: false }) @Property({ type: 'boolean', default: false })
enableTax?: Boolean = false enableTax?: boolean = false
@BeforeCreate()
async generateOrderNumber(args: EventArgs<Order>) {
const em = args.em;
const order = args.entity;
const maxOrder = await em.findOne(
Order,
{ id: { $ne: null } },
{
orderBy: { orderNumber: 'DESC' },
fields: ['orderNumber'],
},
);
// Set the next order number (1 if no orders exist, otherwise max + 1)
order.orderNumber = maxOrder?.orderNumber ? maxOrder.orderNumber + 1 : 1;
}
} }
+11 -1
View File
@@ -2,6 +2,15 @@ import { Admin } from "src/modules/admin/entities/admin.entity"
import { Product } from "src/modules/product/entities/product.entity" import { Product } from "src/modules/product/entities/product.entity"
export enum OrderStatusEnum { export enum OrderStatusEnum {
REQUEST = 'request',
INVOICED = 'invoiced',
ORDER = 'order',
}
export enum OrderItemStatusEnum {
CREATED = 'created', CREATED = 'created',
INVOICED = 'invoiced', INVOICED = 'invoiced',
@@ -35,6 +44,7 @@ export interface IAttachment { type: string, url: string }
export interface CreateOrderItem { export interface CreateOrderItem {
productId: string, productId: string,
quantity: number quantity: number
status: OrderItemStatusEnum,
attributes?: IField[] attributes?: IField[]
description?: string description?: string
attachments?: IAttachment[] attachments?: IAttachment[]
@@ -53,7 +63,7 @@ export type CreateOrderItemPopulated = Omit<CreateOrderItem, 'productId' | 'desi
export interface CreateOrder { export interface CreateOrder {
userId: string userId: string
status: OrderStatusEnum // status: OrderStatusEnum
enableTax?: boolean enableTax?: boolean
paymentMethod?: string paymentMethod?: string
adminId?: string adminId?: string
@@ -7,7 +7,7 @@ import { NotifEvent } from 'src/modules/notification/interfaces/notification.int
import { NotificationService } from 'src/modules/notification/services/notification.service'; import { NotificationService } from 'src/modules/notification/services/notification.service';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { OrderRepository } from '../repositories/order.repository'; import { OrderRepository } from '../repositories/order.repository';
import { OrderStatusEnum } from '../interface/order.interface'; // import { OrderStatusEnum } from '../interface/order.interface';
import { PaymentMethodEnum } from 'src/modules/payment/interface/payment'; import { PaymentMethodEnum } from 'src/modules/payment/interface/payment';
import { UserService } from 'src/modules/user/providers/user.service'; import { UserService } from 'src/modules/user/providers/user.service';
+38 -36
View File
@@ -10,11 +10,12 @@ import {
} from '../dto/create-order.dto'; } from '../dto/create-order.dto';
import { UserService } from 'src/modules/user/providers/user.service'; import { UserService } from 'src/modules/user/providers/user.service';
import { import {
OrderStatusEnum, OrderItemStatusEnum,
CreateOrderWithItems, CreateOrderWithItems,
CreateOrderItemPopulated, CreateOrderItemPopulated,
UpdateOrder, UpdateOrder,
UpdateOrderItem UpdateOrderItem,
OrderStatusEnum
} from '../interface/order.interface'; } from '../interface/order.interface';
import { OrderItemRepository } from '../repositories/order-item.repository'; import { OrderItemRepository } from '../repositories/order-item.repository';
import { ProductService } from 'src/modules/product/providers/product.service'; import { ProductService } from 'src/modules/product/providers/product.service';
@@ -65,7 +66,7 @@ export class OrderService {
} }
async createOrderAsUser(userId: string, dto: CreateOrderAsUSerDto) { async createOrderAsUser(userId: string, dto: CreateOrderAsUSerDto) {
const order = await this.createOrder({ ...dto, userId, status: OrderStatusEnum.CREATED }) const order = await this.createOrder({ ...dto, userId })
this.logger.log("Order created as user") this.logger.log("Order created as user")
return order return order
@@ -145,7 +146,7 @@ export class OrderService {
description: item.description, description: item.description,
discount: item.discount, discount: item.discount,
confirmedAt: item.confirmedAt, confirmedAt: item.confirmedAt,
status: OrderItemStatusEnum.CREATED
}) })
em.persist(newOrderItem) em.persist(newOrderItem)
@@ -167,9 +168,9 @@ export class OrderService {
const order = await this.findOrderOrFail(orderId) const order = await this.findOrderOrFail(orderId)
if (order.status !== OrderStatusEnum.CREATED) { // if (order.status !== OrderStatusEnum.CREATED) {
throw new BadRequestException(`You can not update when status is ${order.status}`) // throw new BadRequestException(`You can not update when status is ${order.status}`)
} // }
if (order.user.id !== userId) { if (order.user.id !== userId) {
throw new BadRequestException(`This order doest belongs to you!`) throw new BadRequestException(`This order doest belongs to you!`)
@@ -214,9 +215,9 @@ export class OrderService {
throw new BadRequestException(`This order doesnt belongs to you`) throw new BadRequestException(`This order doesnt belongs to you`)
} }
if (orderItem.order.status !== OrderStatusEnum.CREATED) { // if (orderItem.order.status !== OrderStatusEnum.CREATED) {
throw new BadRequestException(`You can not update when status is ${orderItem.order.status}`) // throw new BadRequestException(`You can not update when status is ${orderItem.order.status}`)
} // }
if (orderItem.confirmedAt) { if (orderItem.confirmedAt) {
throw new BadRequestException(`You can not update when item is confirmed`) throw new BadRequestException(`You can not update when item is confirmed`)
@@ -270,9 +271,9 @@ export class OrderService {
throw new BadRequestException(`this order is not belongs to you`) throw new BadRequestException(`this order is not belongs to you`)
} }
if (orderItem.order.status !== OrderStatusEnum.CREATED) { // if (orderItem.order.status !== OrderStatusEnum.CREATED) {
throw new BadRequestException(`You can not delete when order status is ${orderItem.order.status}`) // throw new BadRequestException(`You can not delete when order status is ${orderItem.order.status}`)
} // }
if (orderItem.confirmedAt) { if (orderItem.confirmedAt) {
throw new BadRequestException(`You can not delete when item is confirmed`) throw new BadRequestException(`You can not delete when item is confirmed`)
@@ -366,28 +367,28 @@ export class OrderService {
throw new BadRequestException("this order doesnt belongs to you") throw new BadRequestException("this order doesnt belongs to you")
} }
if (![OrderStatusEnum.CREATED].includes(order.status)) { // if (![OrderStatusEnum.CREATED].includes(order.status)) {
throw new BadRequestException("Order can not be deleted") // throw new BadRequestException("Order can not be deleted")
} // }
return this.hardDeleteOrder(orderId) return this.hardDeleteOrder(orderId)
} }
async updateStatus(orderId: string, newStatus: OrderStatusEnum) { // async updateStatus(orderId: string, newStatus: OrderStatusEnum) {
const order = await this.findOrderOrFail(orderId) // const order = await this.findOrderOrFail(orderId)
order.status = newStatus // // order.status = newStatus
await this.em.flush() // await this.em.flush()
return order // return order
} // }
async createInvoice(orderId: string) { // async createInvoice(orderId: string) {
const order = await this.updateStatus(orderId, OrderStatusEnum.INVOICED) // const order = await this.updateStatus(orderId, OrderStatusEnum.INVOICED)
return order // return order
} // }
async getOrderAsUser(userId: string, orderId: string) { async getOrderAsUser(userId: string, orderId: string) {
const order = await this.findOrderOrFail(orderId) const order = await this.findOrderOrFail(orderId)
@@ -401,7 +402,7 @@ export class OrderService {
private async createOrderItemEntity(order: Order, dto: CreateOrderItemPopulated, em?: EntityManager) { private async createOrderItemEntity(order: Order, dto: CreateOrderItemPopulated, em?: EntityManager) {
const { attributes, description, quantity, attachments, discount, const { attributes, description, quantity, attachments, discount,
unitPrice, product, designer, printAttributes } = dto unitPrice, product, designer, printAttributes, status } = dto
const eM = em ?? this.em const eM = em ?? this.em
return eM.create( return eM.create(
@@ -417,6 +418,7 @@ export class OrderService {
unitPrice, unitPrice,
product, product,
printAttributes: printAttributes, printAttributes: printAttributes,
status
}) })
} }
@@ -424,7 +426,7 @@ export class OrderService {
async createOrder(dto: CreateOrderWithItems) { async createOrder(dto: CreateOrderWithItems) {
const { items, userId, enableTax, const { items, userId, enableTax,
estimatedDays, paymentMethod, status, adminId } = dto estimatedDays, paymentMethod, adminId } = dto
// Validate and fetch all required entities // Validate and fetch all required entities
const user = await this.userService.findOrFail(userId) const user = await this.userService.findOrFail(userId)
@@ -447,7 +449,7 @@ export class OrderService {
enableTax, enableTax,
estimatedDays, estimatedDays,
paymentMethod, paymentMethod,
status, status: OrderStatusEnum.REQUEST,
}) })
em.persist(order) em.persist(order)
@@ -486,7 +488,7 @@ export class OrderService {
async updateOrder(order: Order, param: UpdateOrder) { async updateOrder(order: Order, param: UpdateOrder) {
const { adminId, enableTax, const { adminId, enableTax,
estimatedDays, paymentMethod, status, userId } = param estimatedDays, paymentMethod, userId } = param
if (userId) { if (userId) {
@@ -513,13 +515,13 @@ export class OrderService {
order.paymentMethod = paymentMethod order.paymentMethod = paymentMethod
} }
if (status != undefined) { // if (status != undefined) {
order.status = status // order.status = status
if (status === OrderStatusEnum.INVOICED && !order.invoicedAt) { // if (status === OrderStatusEnum.INVOICED && !order.invoicedAt) {
order.invoicedAt = new Date() // order.invoicedAt = new Date()
} // }
} // }