This commit is contained in:
2026-02-19 09:21:53 +03:30
parent 5cc9211e49
commit 61f473ea34
34 changed files with 357 additions and 2333 deletions
@@ -1,636 +0,0 @@
import { Injectable, BadRequestException, Logger, BadGatewayException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { OrderRepository } from '../repositories/order.repository';
import { FindOrdersDto } from '../dto/find-orders.dto';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
CreateOrderAsAdminDto,
CreateOrderAsUSerDto, CreateOrderItemAsUserDto,
CreateOrderItemDtoAsAdmin
} from '../dto/create-order.dto';
import { UserService } from 'src/modules/user/providers/user.service';
import {
OrderItemStatusEnum,
CreateOrderWithItems,
CreateOrderItemPopulated,
UpdateOrder,
UpdateOrderItem,
OrderStatusEnum
} 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 { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { Order } from '../entities/order.entity';
import { PaymentRepository } from 'src/modules/payment/repositories/payment.repository';
import { UpdateOrderItemDtoAsAdmin, UpdateOrderItemDtoAsUser } from '../dto/update-order-item.dto';
import { Admin } from 'src/modules/admin/entities/admin.entity';
import { AdminService } from 'src/modules/admin/providers/admin.service';
import { OrderItem } from '../entities/order-item.entity';
import { UpdateOrderAsAdminDto } from '../dto/update-order.dto';
import { CreatePrintFormDto } from '../dto/create-print-form.dto';
import { FindOrderItemsDto } from '../dto/find-order-items.dto';
import { PermissionService } from 'src/modules/roles/providers/permissions.service';
import { PermissionEnum } from 'src/common/enums/permission.enum';
@Injectable()
export class OrderService {
private readonly logger = new Logger(OrderService.name);
constructor(
private readonly em: EntityManager,
private readonly orderRepository: OrderRepository,
private readonly orderItemRepository: OrderItemRepository,
private readonly userService: UserService,
private readonly adminService: AdminService,
private readonly productService: ProductService,
private readonly productRepository: ProductRepository,
private readonly ticketRepository: TicketRepository,
private readonly ticketService: TicketService,
private readonly eventEmitter: EventEmitter2,
private readonly adminRepository: AdminRepository,
private readonly paymentRepository: PaymentRepository,
private readonly permissionService: PermissionService,
) { }
async createOrderAsAdmin(adminId: string, dto: CreateOrderAsAdminDto) {
const order = await this.createOrder({ ...dto, adminId })
// await this.calculateOrder(order)
// this.em.flush()
this.logger.log("Order created as admin")
return order
}
async createOrderAsUser(userId: string, dto: CreateOrderAsUSerDto) {
const order = await this.createOrder({ ...dto, userId })
this.logger.log("Order created as user")
return order
}
async updateOrderAsAdmin(orderId: string, dto: UpdateOrderAsAdminDto) {
const order = await this.findOrderOrFail(orderId)
// const updateOrder = await this.updateOrder(order, dto)
// const updateOrder = await this.calculateOrder(order)
// await this.em.flush()
this.logger.log("Order updated as admin")
// return updateOrder
}
async updateOrderAsAdmin2(orderId: string, dto: UpdateOrderAsAdminDto) {
const order = await this.findOrderOrFail(orderId)
const { items, ...rest } = dto
return this.em.transactional(async (em) => {
em.assign(order, rest)
for (const item of items) {
if (item.id) { // update
const currentItem = order.items.find(i => i.id == item.id)
if (!currentItem) {
throw new BadGatewayException("Order Item not found")
}
const { designerId, productId, ...itemRest } = item
let newProduct = currentItem.product
if (productId && productId !== currentItem.product.id) {
newProduct = await this.productService.findOneOrFail(productId)
}
let newDesigner: Admin | undefined
if (designerId && designerId !== currentItem.designer?.id) {
newDesigner = await this.adminService.findOrFail(designerId)
}
em.assign(currentItem, { ...itemRest, designer: newDesigner, product: newProduct })
} else {
if (!item.productId) {
throw new BadRequestException(`Product id is required for item ${item.id}`)
}
const product = await this.productService.findOneOrFail(item.productId)
const newOrderItem = em.create(OrderItem, {
order,
product,
quantity: item.quantity,
unitPrice: item.unitPrice,
adminDescription: item.adminDescription,
attachments: item.attachments,
attributes: item.attributes,
description: item.description,
discount: item.discount,
confirmedAt: item.confirmedAt,
status: OrderItemStatusEnum.CREATED
})
em.persist(newOrderItem)
}
}
await em.flush()
this.logger.log(`order #${order.orderNumber} was updated as Admin`)
return order
})
}
async addOrderItemAsUser(userId: string, orderId: string, dto: CreateOrderItemAsUserDto) {
const order = await this.findOrderOrFail(orderId)
// if (order.status !== OrderStatusEnum.CREATED) {
// throw new BadRequestException(`You can not update when status is ${order.status}`)
// }
if (order.user.id !== userId) {
throw new BadRequestException(`This order doest belongs to you!`)
}
const product = await this.productService.findOneOrFail(dto.productId)
const orderItem = this.createOrderItemEntity(order, { ...dto, product })
await this.em.flush()
return orderItem
}
async addOrderItemAsAdmin(orderId: string, dto: CreateOrderItemDtoAsAdmin) {
const order = await this.findOrderOrFail(orderId)
const product = await this.productService.findOneOrFail(dto.productId)
let designer: Admin | undefined
if (dto.designerId) {
designer = await this.adminService.findOrFail(dto.designerId)
}
const orderItem = this.createOrderItemEntity(order, { ...dto, designer, product })
await this.em.flush()
// await this.calculateOrder(undefined, orderId)
// await this.em.flush()
return orderItem
}
async updateOrderItemAsUser(userId: string, orderId: string, itemId: string, dto: UpdateOrderItemDtoAsUser) {
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
if (orderItem.order.user.id !== userId) {
throw new BadRequestException(`This order doesnt belongs to you`)
}
// if (orderItem.order.status !== OrderStatusEnum.CREATED) {
// throw new BadRequestException(`You can not update when status is ${orderItem.order.status}`)
// }
if (orderItem.confirmedAt) {
throw new BadRequestException(`You can not update when item is confirmed`)
}
const updated = await this.updateOrderItem(orderItem, dto)
return updated
}
async updateOrderItemAsAdmin(orderId: string, itemId: string, dto: UpdateOrderItemDtoAsAdmin) {
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
const updated = await this.updateOrderItem(orderItem, dto)
// await this.calculateOrder(undefined, orderId)
// await this.em.flush()
return updated
}
async createPrintForm(orderId: string, itemId: string, dto: CreatePrintFormDto) {
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
const updated = await this.updateOrderItem(orderItem, { ...dto, printFormCreatedAt: new Date() })
return updated
}
async removeOrderItemAsAdmin(orderId: string, itemId: string) {
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
await this.removeOrderItem(orderItem)
// await this.calculateOrder(undefined, orderId)
// await this.em.flush()
return { message: 'success' }
}
async removeOrderItemAsUser(userId: string, orderId: string, itemId: string) {
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
if (orderItem.order.user.id !== userId) {
throw new BadRequestException(`this order is not belongs to you`)
}
// if (orderItem.order.status !== OrderStatusEnum.CREATED) {
// throw new BadRequestException(`You can not delete when order status is ${orderItem.order.status}`)
// }
if (orderItem.confirmedAt) {
throw new BadRequestException(`You can not delete when item is confirmed`)
}
await this.removeOrderItem(orderItem)
return { message: 'success' }
}
async findOrdersAsUser(userId: string, dto: FindOrdersDto) {
const orders = await this.orderRepository.findAllPaginated({ userId, ...dto })
return orders
}
async findOrderRequestsAsAdmin(dto: FindOrdersDto) {
const orders = await this.orderRepository.findAllPaginated({ ...dto, isInvoiced: false })
return orders
}
async findInvoicedOrdersAsAdmin(adminId: string, dto: FindOrdersDto) {
const canSeeAll = await this.adminService.hasPermission(adminId, PermissionEnum.VIEW_All_INVOICED_ORDERS)
let canSeeAsigned = false
if (!canSeeAll) {
canSeeAsigned = await this.adminService.hasPermission(adminId, PermissionEnum.VIEW_MY_ASSIGNED_INVOICED_REQUESTS)
}
if (!canSeeAsigned && !canSeeAll) {
throw new BadRequestException("You dont have Permission")
}
let designerId = canSeeAll ? undefined : adminId
const orders = await this.orderRepository.findAllPaginated({ ...dto, isInvoiced: true, designerId })
return orders
}
async findPrintOrderItemsAsAdmin(dto: FindOrderItemsDto) {
const orderItems = await this.orderItemRepository.findAllPaginated({ ...dto, isPrintFormAdded: true, })
return orderItems
}
async findPrints(dto: FindOrdersDto) {
const orders = await this.orderRepository.findAllPaginated({ ...dto, statuses: [] })
return orders
}
async confirmOrderItem(userId: string, orderId: string, orderItemId: string) {
const orderItem = await this.findOrderItemOrFail(orderId, orderItemId)
if (orderItem.order.user.id !== userId) {
throw new BadRequestException("Order Item does not belong to you")
}
orderItem.confirmedAt = new Date()
await this.em.persistAndFlush(orderItem)
return orderItem
}
async assignDesigner(orderId: string, orderItemId: string, designerId: string) {
const orderItem = await this.findOrderItemOrFail(orderId, orderItemId)
const designer = await this.adminRepository.findOne({ id: designerId })
if (!designer) {
throw new BadRequestException("designer not found")
}
orderItem.designer = designer
await this.em.persistAndFlush(orderItem)
return orderItem
}
async removeOrderAsUser(userId: string, orderId: string) {
const order = await this.findOrderOrFail(orderId)
if (order.user.id !== userId) {
throw new BadRequestException("this order doesnt belongs to you")
}
// if (![OrderStatusEnum.CREATED].includes(order.status)) {
// throw new BadRequestException("Order can not be deleted")
// }
return this.hardDeleteOrder(orderId)
}
// async updateStatus(orderId: string, newStatus: OrderStatusEnum) {
// const order = await this.findOrderOrFail(orderId)
// // order.status = newStatus
// await this.em.flush()
// return order
// }
// async createInvoice(orderId: string) {
// const order = await this.updateStatus(orderId, OrderStatusEnum.INVOICED)
// return order
// }
async getOrderAsUser(userId: string, orderId: string) {
const order = await this.findOrderOrFail(orderId)
if (order.user.id !== userId) {
throw new BadRequestException("This order is not belongs to you")
}
return order
}
// ==================== Entity Methods ====================
private async createOrderItemEntity(order: Order, dto: CreateOrderItemPopulated, em?: EntityManager) {
const { attributes, description, quantity, attachments, discount,
unitPrice, product, designer, printAttributes, status } = dto
const eM = em ?? this.em
return eM.create(
OrderItem,
{
designer,
order,
attributes,
description,
quantity,
attachments,
discount,
unitPrice,
product,
printAttributes: printAttributes,
status
})
}
// ==================== Core Logic ====================
async createOrder(dto: CreateOrderWithItems) {
const { items, userId, enableTax,
estimatedDays, paymentMethod, adminId } = dto
// Validate and fetch all required entities
const user = await this.userService.findOrFail(userId)
const admin = adminId ? await this.adminService.findOrFail(adminId) : undefined
const products = await this.productService.findProductsByIds(items.map(item => item.productId))
const desiners = await this.adminService.findByIds(items.map(item => item.designerId).filter(id => id != null))
const productMap = new Map(
products.map(p => [Number(p.id), p])
);
const designerMap = new Map(
desiners.map(d => [d.id, d])
)
return this.em.transactional(async (em) => {
const order = em.create(Order, {
creator: admin,
user,
enableTax,
estimatedDays,
paymentMethod,
status: OrderStatusEnum.REQUEST,
})
em.persist(order)
for (const item of items) {
const product = productMap.get(Number(item.productId))
if (!product) {
throw new BadRequestException("Product not found!")
}
let designer: undefined | Admin = undefined
if (item.designerId) {
designer = designerMap.get(item.designerId)
if (!designer) {
throw new BadRequestException("designer not found!")
}
}
const orderItem = await this.createOrderItemEntity(order, { ...item, designer, product }, em)
order.items.add(orderItem)
}
// TODO : calculation must be done after create order
// await this.calculateOrder(order)
await em.flush()
return order
})
}
async updateOrder(order: Order, param: UpdateOrder) {
const { adminId, enableTax,
estimatedDays, paymentMethod, userId } = param
if (userId) {
const user = await this.userService.findOrFail(userId)
order.user = user
}
if (adminId != undefined && adminId !== null) {
const admin = await this.adminService.findOrFail(adminId)
order.creator = admin
}
if (enableTax != undefined) {
order.enableTax = enableTax
}
if (estimatedDays != undefined) {
order.estimatedDays = estimatedDays
}
if (paymentMethod != undefined) {
order.paymentMethod = paymentMethod
}
// if (status != undefined) {
// order.status = status
// if (status === OrderStatusEnum.INVOICED && !order.invoicedAt) {
// order.invoicedAt = new Date()
// }
// }
await this.em.persistAndFlush(order)
return order
}
async updateOrderItem(orderItem: OrderItem, dto: UpdateOrderItem) {
const { attributes, description, product, quantity, printFormCreatedAt,
attachments, discount, unitPrice, designer, printAttributes, } = dto
if (product && orderItem.product.id !== product.id) {
orderItem.product = product
}
if (designer && orderItem.designer?.id !== designer.id) {
orderItem.designer = designer
}
if (attributes != undefined) {
orderItem.attributes = attributes
}
if (description != undefined) {
orderItem.description = description
}
if (quantity != undefined) {
orderItem.quantity = quantity
}
if (attachments != undefined) {
orderItem.attachments = attachments
}
if (discount != undefined) {
orderItem.discount = discount
}
if (unitPrice != undefined) {
orderItem.unitPrice = unitPrice
}
if (printFormCreatedAt != undefined) {
orderItem.printFormCreatedAt = printFormCreatedAt
}
if (printAttributes != undefined) {
orderItem.printAttributes = printAttributes
}
await this.em.flush()
return orderItem
}
async removeOrderItem(orderItem: OrderItem) {
await this.em.removeAndFlush(orderItem)
return true
}
async hardDeleteOrder(orderId: string) {
const order = await this.findOrderOrFail(orderId)
if (order.payments.length > 0) {
throw new BadRequestException("order with payments can not be deleted!")
}
await this.em.transactional(async (em) => {
await this.ticketRepository.nativeDelete({ orderItem: { order: { id: orderId } } })
await this.orderRepository.nativeDelete(order)
// order item will be deleted because cascade is true
// TODO : images also must be deleted
await em.flush()
})
return { message: "Order deleted successfully" }
}
// ==================== Helper Methods ====================
async findOrderOrFail(orderId: string) {
const order = await this.orderRepository.findOne({ id: orderId },
{ populate: ['items', 'items.product', 'payments', 'user', 'items.designer'] })
if (!order) {
throw new BadRequestException('Order not found')
}
return order
}
async findOrderItemOrFail(orderId: string, itemId: string) {
const orderItem = await this.orderItemRepository.findOne({ id: itemId, order: { id: orderId } },
{ populate: ['order', 'order.user'] })
if (!orderItem) {
throw new BadRequestException('Order item not found')
}
return orderItem
}
async findOrderItemOrFailByItemId(itemId: string) {
const orderItem = await this.orderItemRepository.findOne({ id: itemId },
{ populate: ['order', 'order.user'] })
if (!orderItem) {
throw new BadRequestException('Order item not found')
}
return orderItem
}
}