diff --git a/src/modules/order/controllers/order.controller.ts b/src/modules/order/controllers/order.controller.ts index 32d7f40..77ab9d0 100644 --- a/src/modules/order/controllers/order.controller.ts +++ b/src/modules/order/controllers/order.controller.ts @@ -53,7 +53,7 @@ export class OrderController { @UseGuards(AuthGuard) @ApiOperation({ summary: 'create order as admin' }) createOrderAsAdmin(@AdminId() adminId: string, @Body() body: CreateOrderAsAdminDto) { - return this.orderService.createOrdeAsAdmin(adminId, body); + return this.orderService.createOrderAsAdmin(adminId, body); } @Delete('admin/orders/:orderId') diff --git a/src/modules/order/dto/create-order-as-admin.dto.ts b/src/modules/order/dto/create-order-as-admin.dto.ts index 5b4ee5a..7def32d 100644 --- a/src/modules/order/dto/create-order-as-admin.dto.ts +++ b/src/modules/order/dto/create-order-as-admin.dto.ts @@ -26,15 +26,19 @@ export class CreateOrderItemDto { @IsNumber() unitPrice: number + @ApiProperty() + @IsNumber() + discount: number + @ApiPropertyOptional({ example: 'توضیحات' }) @IsString() - content: string + description: string @ApiPropertyOptional({ example: [] }) @IsArray() @IsString({ each: true }) attachments: { url: string, type: string }[] -} +} export class CreateOrderAsAdminDto { @ApiPropertyOptional({ example: 'توضیحات' }) diff --git a/src/modules/order/dto/create-order.dto.ts b/src/modules/order/dto/create-order.dto.ts index 39f79aa..2d6f7c4 100644 --- a/src/modules/order/dto/create-order.dto.ts +++ b/src/modules/order/dto/create-order.dto.ts @@ -37,12 +37,16 @@ export class CreateOrderDto { { productId: 1, quantity: 100, - attributesValues: [] + attributesValues: [], + attachments: [ + { url: '', type: '' } + ], + description: '' } ] }) @IsNotEmpty() - @ArrayMinSize(1, { message: 'At least one product is required' }) + @ArrayMinSize(1, { message: 'At least one item is required' }) @Type(() => CreateOrderItemDto) items: CreateOrderItemDto[]; } diff --git a/src/modules/order/entities/order-item.entity.ts b/src/modules/order/entities/order-item.entity.ts index 3bb1886..5d45779 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, 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 { Order } from './order.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' }) @Index({ properties: ['order'] }) -@Index({ properties: ['product'] }) export class OrderItem extends BaseEntity { @PrimaryKey({ type: 'bigint', autoincrement: true }) id: bigint @@ -37,17 +36,15 @@ export class OrderItem extends BaseEntity { return Number(this.subTotal) - Number(this.discount || 0) } - @Property({ nullable: true }) + @Property({type:'text', nullable: true }) description?: string; - @Property({ nullable: true }) + @Property({type:'text', nullable: true }) adminDescription?: string; @Property({ type: 'json', nullable: true }) attachments?: { type: string, url: string }[] // user attachments like voice and photos - // @Enum(() => OrderItemStatus) - // status!: OrderItemStatus; @Property({ nullable: true, columnType: 'timestamptz' }) confirmedAt?: Date; } diff --git a/src/modules/order/providers/order.service.ts b/src/modules/order/providers/order.service.ts index 0b10098..d6d6850 100644 --- a/src/modules/order/providers/order.service.ts +++ b/src/modules/order/providers/order.service.ts @@ -15,6 +15,8 @@ import { TicketRepository } from 'src/modules/ticket/repositories/tickets.reposi 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'; @Injectable() @@ -33,6 +35,7 @@ export class OrderService { private readonly ticketService: TicketService, private readonly eventEmitter: EventEmitter2, private readonly adminRepository: AdminRepository, + private readonly paymentRepository: PaymentRepository, ) { } async createNewOrder(userId: string, dto: CreateOrderDto) { @@ -94,7 +97,7 @@ export class OrderService { return order } - async createOrdeAsAdmin(adminId: string, dto: CreateOrderAsAdminDto) { + async createOrderAsAdmin(adminId: string, dto: CreateOrderAsAdminDto) { const { items, userPhone } = dto const user = await this.userService.findByPhone(userPhone) @@ -131,7 +134,6 @@ export class OrderService { throw new BadRequestException("some products not found") } - items.forEach(item => { const product = products.find(p => p.id == item.productId) @@ -146,8 +148,10 @@ export class OrderService { quantity: item.quantity, unitPrice: item.unitPrice, total: item.unitPrice * item.quantity, - discount: 0, - subTotal: 0 + discount: item.discount, + subTotal: item.unitPrice * item.quantity - item.discount, + attachments: item.attachments, + description: item.description }) em.persist(orderItem) @@ -155,6 +159,8 @@ export class OrderService { // TODO : calculation must be done after create order + await this.calculateOrder(order, em) + await em.flush() return order @@ -178,42 +184,33 @@ export class OrderService { 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 - let subTotal = 0 - let totalDiscount = 0 + const totalBeforeTax = subTotal - totalDiscount + let tax = 0 - // TODO : use reduce + if (order.enableTax) { + tax = 0.1 * totalBeforeTax + } - for (let orderItem of targetOrder.items) { - subTotal += orderItem.total - totalDiscount += orderItem.discount - } - - const totalBeforeTax = subTotal - totalDiscount - let tax = 0 - - 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 - }) + const total = totalBeforeTax + tax + const paidAmount = await this.paymentRepository.getActualPaidAmount(order.id) + // Update Order financial values + order.subTotal = subTotal + order.discount = totalDiscount + order.taxAmount = tax + order.total = total + order.balance = total - paidAmount return order } diff --git a/src/modules/payment/repositories/payment.repository.ts b/src/modules/payment/repositories/payment.repository.ts index 4ce8af8..06165b2 100644 --- a/src/modules/payment/repositories/payment.repository.ts +++ b/src/modules/payment/repositories/payment.repository.ts @@ -106,4 +106,18 @@ export class PaymentRepository extends EntityRepository { }, }; } + + async getActualPaidAmount(orderId: string): Promise { + + 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); + } }