519 lines
14 KiB
TypeScript
519 lines
14 KiB
TypeScript
import { Injectable, BadRequestException, Logger } from '@nestjs/common';
|
|
import { EntityManager } from '@mikro-orm/postgresql';
|
|
import { OrderItem } from '../entities/order-item.entity';
|
|
import { OrderRepository } from '../repositories/order.repository';
|
|
import { FindOrdersDto } from '../dto/find-orders.dto';
|
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
import {
|
|
CreateOrderDto, CreateOrderItemAsUserDto,
|
|
CreateOrderItemDtoAsAdmin, CreateOrderAsAdminDto
|
|
} from '../dto/create-order.dto';
|
|
import { UserService } from 'src/modules/user/providers/user.service';
|
|
import { IAddOrderItem, ICreateOrder, OrderStatusEnum, IUpdateOrderItem, IUpdateOrder } 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';
|
|
|
|
|
|
@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 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,
|
|
) { }
|
|
|
|
async createOrderAsAdmin(adminId: string, dto: CreateOrderAsAdminDto) {
|
|
const order = await this.createOrder({ ...dto, adminId })
|
|
await this.calculateOrder(order)
|
|
this.em.flush()
|
|
return order
|
|
|
|
}
|
|
|
|
async createOrderAsUser(userId: string, dto: CreateOrderDto) {
|
|
const order = await this.createOrder({ ...dto, userId, status: OrderStatusEnum.CREATED })
|
|
return order
|
|
|
|
}
|
|
|
|
async createOrder(dto: ICreateOrder) {
|
|
const { items, userId, attachments, designerId, enableTax, estimatedDays, paymentMethod, status, adminId } = dto
|
|
|
|
const user = await this.userService.findById(userId)
|
|
if (!user) {
|
|
throw new BadRequestException("User not found!")
|
|
}
|
|
|
|
let admin: null | Admin = null
|
|
if (adminId) {
|
|
admin = await this.adminRepository.findOne({ id: adminId })
|
|
if (!admin) {
|
|
throw new BadRequestException("Admin not found")
|
|
}
|
|
}
|
|
|
|
let designer: null | Admin = null
|
|
if (designerId) {
|
|
designer = await this.adminRepository.findOne({ id: designerId })
|
|
if (!designer) {
|
|
throw new BadRequestException("designer not found")
|
|
}
|
|
}
|
|
|
|
const order = await this.em.transactional(async (em) => {
|
|
|
|
const order = this.orderRepository.create({
|
|
creator: admin,
|
|
user,
|
|
attachments,
|
|
designer,
|
|
enableTax,
|
|
estimatedDays,
|
|
paymentMethod,
|
|
status,
|
|
})
|
|
|
|
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 => {
|
|
this.persistOrderItem(order, item)
|
|
});
|
|
|
|
// TODO : calculation must be done after create order
|
|
|
|
await this.calculateOrder(order)
|
|
|
|
// await em.flush()
|
|
|
|
return order
|
|
})
|
|
|
|
return order
|
|
|
|
}
|
|
|
|
async updateOrder(orderId: string, dto: IUpdateOrder) {
|
|
const { attachments, adminId, designerId, enableTax, estimatedDays, paymentMethod, status, userId } = dto
|
|
|
|
const order = await this.findOneOrFail(orderId)
|
|
|
|
if (userId) {
|
|
const user = await this.userService.findById(userId)
|
|
if (!user) {
|
|
throw new BadRequestException("User not found!")
|
|
}
|
|
order.user = user
|
|
}
|
|
|
|
|
|
if (adminId) {
|
|
const admin = await this.adminRepository.findOne({ id: adminId })
|
|
if (!admin) {
|
|
throw new BadRequestException("Admin not found")
|
|
}
|
|
order.creator = admin
|
|
}
|
|
|
|
|
|
if (designerId) {
|
|
const designer = await this.adminRepository.findOne({ id: designerId })
|
|
if (!designer) {
|
|
throw new BadRequestException("designer not found")
|
|
}
|
|
order.designer = designer
|
|
}
|
|
|
|
if (attachments) {
|
|
order.attachments = attachments
|
|
}
|
|
|
|
if (typeof enableTax !== 'undefined') {
|
|
order.enableTax = enableTax
|
|
}
|
|
|
|
if (estimatedDays) {
|
|
order.estimatedDays = estimatedDays
|
|
}
|
|
|
|
if (paymentMethod) {
|
|
order.paymentMethod = paymentMethod
|
|
}
|
|
|
|
if (status) {
|
|
order.status = status
|
|
}
|
|
|
|
this.em.persist(order)
|
|
|
|
await this.calculateOrder(order)
|
|
|
|
await this.em.flush()
|
|
|
|
return order
|
|
}
|
|
|
|
async addOrderItemAsUser(userId: string, orderId: string, dto: CreateOrderItemAsUserDto) {
|
|
|
|
const order = await this.findOneOrFail(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 orderItem = this.persistOrderItem(order, dto)
|
|
|
|
await this.em.flush()
|
|
|
|
return orderItem
|
|
}
|
|
|
|
async addOrderItemAsAdmin(orderId: string, dto: CreateOrderItemDtoAsAdmin) {
|
|
|
|
const order = await this.findOneOrFail(orderId)
|
|
|
|
const orderItem = this.persistOrderItem(order, dto)
|
|
|
|
await this.em.flush()
|
|
|
|
await this.calculateOrder(undefined, orderId)
|
|
|
|
await this.em.flush()
|
|
|
|
return orderItem
|
|
}
|
|
|
|
private async persistOrderItem(order: Order, dto: IAddOrderItem) {
|
|
const { attributes, description, productId, quantity, attachments, discount, unitPrice } = dto
|
|
|
|
const found = order.items.find(it => it.product.id == productId)
|
|
if (found) {
|
|
throw new BadRequestException(`Product already exists`)
|
|
}
|
|
|
|
const product = await this.productRepository.findOne({ id: productId })
|
|
if (!product) {
|
|
throw new BadRequestException(`Product not found`)
|
|
}
|
|
const orderItem = this.orderItemRepository.create({
|
|
order,
|
|
attributes,
|
|
description,
|
|
quantity,
|
|
attachments,
|
|
subTotal: 0,
|
|
discount: discount ?? 0,
|
|
total: 0,
|
|
unitPrice: unitPrice ?? 0,
|
|
product
|
|
})
|
|
|
|
this.em.persist(orderItem)
|
|
|
|
return orderItem
|
|
}
|
|
|
|
async updateOrderItemAsUser(userId: string, orderId: string, itemId: string, dto: UpdateOrderItemDtoAsUser) {
|
|
|
|
const order = await this.findOneOrFail(orderId)
|
|
|
|
if (order.user.id !== userId) {
|
|
throw new BadRequestException(`This order doesnt belongs to you`)
|
|
}
|
|
|
|
if (order.status !== OrderStatusEnum.CREATED) {
|
|
throw new BadRequestException(`You can not update when status is ${order.status}`)
|
|
}
|
|
|
|
const orderItem = await this.updateOrderItem(itemId, dto)
|
|
|
|
return orderItem
|
|
}
|
|
|
|
async updateOrderItemAsAdmin(orderId: string, itemId: string, dto: UpdateOrderItemDtoAsAdmin) {
|
|
|
|
const order = await this.findOneOrFail(orderId)
|
|
|
|
const orderItem = await this.updateOrderItem(itemId, dto)
|
|
|
|
await this.calculateOrder(undefined, orderId)
|
|
|
|
await this.em.flush()
|
|
|
|
return orderItem
|
|
}
|
|
|
|
async updateOrderItem(itemId: string, dto: IUpdateOrderItem) {
|
|
const { attributes, description, productId, quantity, attachments, discount, unitPrice } = dto
|
|
|
|
const orderItem = await this.orderItemRepository.findOne({
|
|
id: itemId
|
|
})
|
|
|
|
if (!orderItem) {
|
|
throw new BadRequestException(`orderItem not found`)
|
|
}
|
|
|
|
// product is changed
|
|
if (productId && orderItem.product.id !== productId) {
|
|
const product = await this.productRepository.findOne({ id: productId })
|
|
|
|
if (!product) {
|
|
throw new BadRequestException(`product not found`)
|
|
}
|
|
|
|
orderItem.product = product
|
|
}
|
|
|
|
if (attributes) {
|
|
orderItem.attributes = attributes
|
|
}
|
|
if (description) {
|
|
orderItem.description = description
|
|
}
|
|
if (quantity) {
|
|
orderItem.quantity = quantity
|
|
}
|
|
|
|
if (attachments) {
|
|
orderItem.attachments = attachments
|
|
}
|
|
if (discount) {
|
|
orderItem.discount = discount
|
|
}
|
|
|
|
if (unitPrice) {
|
|
orderItem.unitPrice = unitPrice
|
|
}
|
|
|
|
await this.em.flush()
|
|
|
|
return orderItem
|
|
}
|
|
|
|
async removeOrderItemAsAdmin(orderId: string, itemId: string) {
|
|
|
|
const order = await this.findOneOrFail(orderId)
|
|
|
|
await this.removeOrderItem(itemId)
|
|
|
|
await this.calculateOrder(undefined, orderId)
|
|
|
|
await this.em.flush()
|
|
|
|
return true
|
|
}
|
|
|
|
async removeOrderItemAsUser(userId: string, orderId: string, itemId: string) {
|
|
|
|
const order = await this.findOneOrFail(orderId)
|
|
|
|
if (order.user.id !== userId) {
|
|
throw new BadRequestException(`this order is not belongs to you`)
|
|
}
|
|
|
|
if (order.status !== OrderStatusEnum.CREATED) {
|
|
throw new BadRequestException(`You can not update when status is ${order.status}`)
|
|
}
|
|
|
|
await this.removeOrderItem(itemId)
|
|
|
|
return true
|
|
}
|
|
|
|
async removeOrderItem(itemId: string) {
|
|
|
|
const orderItem = await this.orderItemRepository.findOne({
|
|
id: itemId
|
|
})
|
|
|
|
if (!orderItem) {
|
|
throw new BadRequestException(`orderItem not found`)
|
|
}
|
|
|
|
await this.em.removeAndFlush(orderItem)
|
|
|
|
return true
|
|
}
|
|
|
|
|
|
async findUserOrders(userId: string, dto: FindOrdersDto) {
|
|
const orders = await this.orderRepository.findAllPaginated({ userId, ...dto })
|
|
return orders
|
|
}
|
|
|
|
async findOneOrFail(orderId: string) {
|
|
const order = await this.orderRepository.findOne({ id: orderId },
|
|
{ populate: ['items', 'items.product', 'payments'] })
|
|
if (!order) {
|
|
throw new BadRequestException('Order not found')
|
|
}
|
|
return order
|
|
}
|
|
|
|
async calculateOrder(inputOrder?: Order, orderId?: string) {
|
|
let order: undefined | Order = undefined
|
|
if (orderId) {
|
|
order = await this.findOneOrFail(orderId)
|
|
}
|
|
if (order) {
|
|
order = inputOrder
|
|
}
|
|
if (!order) {
|
|
throw new BadRequestException("Order not found")
|
|
}
|
|
// calculate order financials
|
|
let subTotal = 0
|
|
let totalDiscount = 0
|
|
|
|
// TODO : use reduce
|
|
for (let orderItem of order.items) {
|
|
subTotal += orderItem.total
|
|
totalDiscount += Number(orderItem.discount)
|
|
}
|
|
|
|
const totalBeforeTax = subTotal - totalDiscount
|
|
let tax = 0
|
|
|
|
if (order.enableTax) {
|
|
tax = 0.1 * totalBeforeTax
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
async confirmOrderItem(userId: string, orderId: string, orderItemId: number) {
|
|
|
|
const orderItem = await this.orderItemRepository.findOne({ id: orderItemId, order: { id: orderId } },
|
|
{ populate: ['order', 'order.user'] })
|
|
|
|
if (!orderItem) {
|
|
throw new BadRequestException("Order Item not found")
|
|
}
|
|
|
|
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, designerId: string) {
|
|
const order = await this.orderRepository.findOne({ id: orderId })
|
|
if (!order) {
|
|
throw new BadRequestException("Order not found")
|
|
}
|
|
const designer = await this.adminRepository.findOne({ id: designerId })
|
|
|
|
if (!designer) {
|
|
throw new BadRequestException("designer not found")
|
|
}
|
|
|
|
order.designer = designer
|
|
// order.status = OrderStatusEnum.IN_DESIGN
|
|
|
|
|
|
await this.em.persistAndFlush(order)
|
|
|
|
return order
|
|
|
|
}
|
|
|
|
async hardDeleteOrder(orderId: string) {
|
|
const order = await this.orderRepository.findOne({ id: orderId })
|
|
if (!order) {
|
|
throw new BadRequestException("Order not found")
|
|
}
|
|
if (![OrderStatusEnum.CREATED, OrderStatusEnum.INVOICED].includes(order.status)) {
|
|
throw new BadRequestException("Order status must be of of Drfat or Invoiced")
|
|
}
|
|
if (order.payments.length > 0) {
|
|
throw new BadRequestException("order with payments can not be deleted!")
|
|
}
|
|
await this.em.transactional(async (em) => {
|
|
await this.orderRepository.nativeDelete(order)
|
|
await this.orderItemRepository.nativeDelete({ order: { id: orderId } })
|
|
await this.ticketRepository.nativeDelete({ order: { id: orderId } })
|
|
await em.flush()
|
|
})
|
|
|
|
return { message: "Order deleted successfully" }
|
|
|
|
}
|
|
|
|
async removeOrderAsUser(orderId: string) {
|
|
const order = await this.orderRepository.findOne({ id: orderId })
|
|
if (!order) {
|
|
throw new BadRequestException("Order not found")
|
|
}
|
|
|
|
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.orderRepository.findOne({ id: orderId })
|
|
if (!order) {
|
|
throw new BadRequestException("Order not found")
|
|
}
|
|
|
|
order.status = newStatus
|
|
|
|
await this.em.flush()
|
|
|
|
return order
|
|
}
|
|
|
|
async createInvoice(orderId: string) {
|
|
const order = await this.findOneOrFail(orderId)
|
|
await this.updateStatus(orderId, OrderStatusEnum.INVOICED)
|
|
await this.calculateOrder(order)
|
|
await this.em.flush()
|
|
}
|
|
}
|