This commit is contained in:
2026-01-25 15:05:01 +03:30
parent bca1932ae3
commit 55856a40f1
6 changed files with 62 additions and 46 deletions
@@ -53,7 +53,7 @@ export class OrderController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiOperation({ summary: 'create order as admin' }) @ApiOperation({ summary: 'create order as admin' })
createOrderAsAdmin(@AdminId() adminId: string, @Body() body: CreateOrderAsAdminDto) { createOrderAsAdmin(@AdminId() adminId: string, @Body() body: CreateOrderAsAdminDto) {
return this.orderService.createOrdeAsAdmin(adminId, body); return this.orderService.createOrderAsAdmin(adminId, body);
} }
@Delete('admin/orders/:orderId') @Delete('admin/orders/:orderId')
@@ -26,9 +26,13 @@ export class CreateOrderItemDto {
@IsNumber() @IsNumber()
unitPrice: number unitPrice: number
@ApiProperty()
@IsNumber()
discount: number
@ApiPropertyOptional({ example: 'توضیحات' }) @ApiPropertyOptional({ example: 'توضیحات' })
@IsString() @IsString()
content: string description: string
@ApiPropertyOptional({ example: [] }) @ApiPropertyOptional({ example: [] })
@IsArray() @IsArray()
+6 -2
View File
@@ -37,12 +37,16 @@ export class CreateOrderDto {
{ {
productId: 1, productId: 1,
quantity: 100, quantity: 100,
attributesValues: [] attributesValues: [],
attachments: [
{ url: '', type: '' }
],
description: ''
} }
] ]
}) })
@IsNotEmpty() @IsNotEmpty()
@ArrayMinSize(1, { message: 'At least one product is required' }) @ArrayMinSize(1, { message: 'At least one item is required' })
@Type(() => CreateOrderItemDto) @Type(() => CreateOrderItemDto)
items: CreateOrderItemDto[]; items: CreateOrderItemDto[];
} }
@@ -1,4 +1,4 @@
import { Entity, Enum, Index, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { Entity, Index, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
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';
@@ -6,7 +6,6 @@ import { Product } from 'src/modules/product/entities/product.entity';
@Entity({ tableName: 'order_items' }) @Entity({ tableName: 'order_items' })
@Index({ properties: ['order'] }) @Index({ properties: ['order'] })
@Index({ properties: ['product'] })
export class OrderItem extends BaseEntity { export class OrderItem extends BaseEntity {
@PrimaryKey({ type: 'bigint', autoincrement: true }) @PrimaryKey({ type: 'bigint', autoincrement: true })
id: bigint id: bigint
@@ -37,17 +36,15 @@ export class OrderItem extends BaseEntity {
return Number(this.subTotal) - Number(this.discount || 0) return Number(this.subTotal) - Number(this.discount || 0)
} }
@Property({ nullable: true }) @Property({type:'text', nullable: true })
description?: string; description?: string;
@Property({ nullable: true }) @Property({type:'text', nullable: true })
adminDescription?: string; adminDescription?: string;
@Property({ type: 'json', nullable: true }) @Property({ type: 'json', nullable: true })
attachments?: { type: string, url: string }[] // user attachments like voice and photos attachments?: { type: string, url: string }[] // user attachments like voice and photos
// @Enum(() => OrderItemStatus)
// status!: OrderItemStatus;
@Property({ nullable: true, columnType: 'timestamptz' }) @Property({ nullable: true, columnType: 'timestamptz' })
confirmedAt?: Date; confirmedAt?: Date;
} }
+32 -35
View File
@@ -15,6 +15,8 @@ import { TicketRepository } from 'src/modules/ticket/repositories/tickets.reposi
import { CreateInvoiceDto } from '../dto/create-invoice.dto'; import { CreateInvoiceDto } from '../dto/create-invoice.dto';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository'; import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { CreateOrderAsAdminDto } from '../dto/create-order-as-admin.dto'; import { CreateOrderAsAdminDto } from '../dto/create-order-as-admin.dto';
import { Order } from '../entities/order.entity';
import { PaymentRepository } from 'src/modules/payment/repositories/payment.repository';
@Injectable() @Injectable()
@@ -33,6 +35,7 @@ export class OrderService {
private readonly ticketService: TicketService, private readonly ticketService: TicketService,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly adminRepository: AdminRepository, private readonly adminRepository: AdminRepository,
private readonly paymentRepository: PaymentRepository,
) { } ) { }
async createNewOrder(userId: string, dto: CreateOrderDto) { async createNewOrder(userId: string, dto: CreateOrderDto) {
@@ -94,7 +97,7 @@ export class OrderService {
return order return order
} }
async createOrdeAsAdmin(adminId: string, dto: CreateOrderAsAdminDto) { async createOrderAsAdmin(adminId: string, dto: CreateOrderAsAdminDto) {
const { items, userPhone } = dto const { items, userPhone } = dto
const user = await this.userService.findByPhone(userPhone) const user = await this.userService.findByPhone(userPhone)
@@ -131,7 +134,6 @@ export class OrderService {
throw new BadRequestException("some products not found") throw new BadRequestException("some products not found")
} }
items.forEach(item => { items.forEach(item => {
const product = products.find(p => p.id == item.productId) const product = products.find(p => p.id == item.productId)
@@ -146,8 +148,10 @@ export class OrderService {
quantity: item.quantity, quantity: item.quantity,
unitPrice: item.unitPrice, unitPrice: item.unitPrice,
total: item.unitPrice * item.quantity, total: item.unitPrice * item.quantity,
discount: 0, discount: item.discount,
subTotal: 0 subTotal: item.unitPrice * item.quantity - item.discount,
attachments: item.attachments,
description: item.description
}) })
em.persist(orderItem) em.persist(orderItem)
@@ -155,6 +159,8 @@ export class OrderService {
// TODO : calculation must be done after create order // TODO : calculation must be done after create order
await this.calculateOrder(order, em)
await em.flush() await em.flush()
return order return order
@@ -178,42 +184,33 @@ export class OrderService {
return order return order
} }
async calculateOrder(orderId: string, em: EntityManager) { async calculateOrder(order: Order, em: EntityManager) {
const targetOrder = await this.findOneOrFail(orderId) // calculate order financials
let subTotal = 0
let totalDiscount = 0
const order = await this.em.transactional(async (em) => { // TODO : use reduce
for (let orderItem of order.items) {
subTotal += orderItem.total
totalDiscount += orderItem.discount
}
// calculate order financials const totalBeforeTax = subTotal - totalDiscount
let subTotal = 0 let tax = 0
let totalDiscount = 0
// TODO : use reduce if (order.enableTax) {
tax = 0.1 * totalBeforeTax
}
for (let orderItem of targetOrder.items) { const total = totalBeforeTax + tax
subTotal += orderItem.total const paidAmount = await this.paymentRepository.getActualPaidAmount(order.id)
totalDiscount += orderItem.discount // Update Order financial values
} order.subTotal = subTotal
order.discount = totalDiscount
const totalBeforeTax = subTotal - totalDiscount order.taxAmount = tax
let tax = 0 order.total = total
order.balance = total - paidAmount
if (targetOrder.enableTax) {
tax = 0.1 * totalBeforeTax
}
const total = totalBeforeTax + tax
// Update Order financial values
targetOrder.subTotal = subTotal
targetOrder.discount = totalDiscount
targetOrder.taxAmount = tax
targetOrder.total = total
em.persist(targetOrder)
return targetOrder
})
return order return order
} }
@@ -106,4 +106,18 @@ export class PaymentRepository extends EntityRepository<Payment> {
}, },
}; };
} }
async getActualPaidAmount(orderId: string): Promise<number> {
const raw = await this.em.createQueryBuilder(Payment, 'p')
.select('COALESCE(SUM(p.amount), 0) as total')
.where({
order: { id: orderId },
status: PaymentStatusEnum.Paid
})
.execute('get')
const row = raw as unknown as { total: string } | undefined;
return Number(row?.total ?? 0);
}
} }