import { inject, injectable } from "inversify"; import { isValidObjectId, startSession } from "mongoose"; import { CancelOrderItemRepo, CancelOrderRepo, CancelReasonRepo } from "./cancel.repository"; import { CancelOrderDTO, CompeleteCancelOrderDTO, CreateCancelDTO } from "./DTO/cancel.dto"; import { CancelReasonDTO } from "./DTO/cancelReason.dto"; import { CommonMessage, OrderMessage } from "../../common/enums/message.enum"; import { OrderItemsStatus } from "../../common/enums/order.enum"; import { AdminCancelsQueries } from "../../common/types/query.type"; import { BadRequestError } from "../../core/app/app.errors"; import { IOCTYPES } from "../../IOC/ioc.types"; import { OrderItemRefEnum, ReasonRefEnum } from "../fine/models/fine.model"; import { FineRepo } from "../fine/repository/fine.repository"; import { OrderItemRepo } from "../order/order.repository"; import { CancellerRefEnum, CancelStatusEnum } from "./models/cancel.model"; import { StatusEnum } from "../../common/enums/status.enum"; import { FineRuleRepo } from "../fine/repository/fineRule.repository"; import { NotificationService } from "../notification/notification.service"; @injectable() class CancelService { @inject(IOCTYPES.OrderItemRepo) private orderItemRepo: OrderItemRepo; @inject(IOCTYPES.CancelOrderRepo) private cancelOrderRepo: CancelOrderRepo; @inject(IOCTYPES.CancelOrderItemRepo) private cancelOrderItemRepo: CancelOrderItemRepo; @inject(IOCTYPES.CancelReasonRepo) private cancelReasonRepo: CancelReasonRepo; @inject(IOCTYPES.FineRuleRepo) private fineRuleRepo: FineRuleRepo; @inject(IOCTYPES.FineRepo) private fineRepo: FineRepo; @inject(IOCTYPES.NotificationService) notificationService: NotificationService; //####################################################### async getCancelOrders(userId: string) { const cancelOrders = await this.cancelOrderRepo.model.find({ canceller: userId }).sort({ createdAt: -1 }); return { cancelOrders, }; } async getAllUserCancelOrders(userId: string) { const cancelOrders = await this.cancelOrderRepo.getAllCancelOrdersForUser(userId); return { cancelOrders, }; } //####################################################### async getAllCancelOrders(queries: AdminCancelsQueries) { const { docs, count } = await this.cancelOrderRepo.getAllCancelsForAdmin(queries); const cancelsOrders = docs.map((doc: any) => CancelOrderDTO.transformCancelOrder(doc)); return { cancelsOrders, count, }; } //####################################################### //####################################################### async createCancelOrderByUser(userId: string, createCancelDto: CreateCancelDTO) { const session = await startSession(); session.startTransaction(); try { // create Cancel Order let total_cancel_price = 0; const cancelOrder = await this.cancelOrderRepo.model.create( [ { cancellerRef: CancellerRefEnum.User, canceller: userId, order: createCancelDto.orderId, status: CancelStatusEnum.Pending, }, ], { session }, ); const cancelOrderDoc = cancelOrder[0]; // Create cancelOrderItems for (const item of createCancelDto.items) { const orderItem = await this.orderItemRepo.model.findById(item.orderItemId).session(session); if (!orderItem) { throw new BadRequestError(`OrderItem with ID ${item.orderItemId} not found.`); } if (orderItem.status === OrderItemsStatus.Shipped || orderItem.status === OrderItemsStatus.Delivered) { throw new BadRequestError(`OrderItem with ID ${item.orderItemId} cannot cancel in this position`); } const shipmentItem = orderItem.shipmentItems.find((shipItem) => shipItem._id?.equals(item.shipmentItemId)); if (!shipmentItem) { throw new BadRequestError(`ShipmentItem with ID ${item.shipmentItemId} not found in OrderItem ${item.orderItemId}.`); } if (item.quantity > shipmentItem.quantity - shipmentItem.cancelled_quantity! - shipmentItem.returned_quantity!) { throw new BadRequestError(`cancel quantity for ShipmentItem ${item.shipmentItemId} exceeds the available quantity.`); } total_cancel_price += item.quantity * shipmentItem.selling_price; await this.cancelOrderItemRepo.model.create( [ { cancelOrderId: cancelOrderDoc._id, orderItem: item.orderItemId, shipmentItem: item.shipmentItemId, quantity: item.quantity, comment: item.comment, reason: item.reason, status: OrderItemsStatus.cancelled_user, }, ], { session }, ); shipmentItem.cancelled_quantity = item.quantity; orderItem.status = OrderItemsStatus.cancelled_user; orderItem.save({ session }); } cancelOrderDoc.total_price = total_cancel_price; await cancelOrderDoc.save({ session }); await session.commitTransaction(); await session.endSession(); return { cancelOrder: cancelOrder[0] }; } catch (error) { await session.abortTransaction(); await session.endSession(); throw error; } } //####################################################### //####################################################### async createCancelOrderBySeller(sellerId: string, createCancelDto: CreateCancelDTO) { const session = await startSession(); session.startTransaction(); try { // create Cancel Order let total_cancel_price = 0; const cancelOrder = await this.cancelOrderRepo.model.create( [ { cancellerRef: CancellerRefEnum.Seller, canceller: sellerId, order: createCancelDto.orderId, status: OrderItemsStatus.cancelled_shop, }, ], { session }, ); const cancelOrderDoc = cancelOrder[0]; // Create cancelOrderItems for (const item of createCancelDto.items) { const orderItem = await this.orderItemRepo.model.findById(item.orderItemId).session(session); if (!orderItem) throw new BadRequestError(`آیتم سفارش با شناسه ${item.orderItemId} یافت نشد.`); if (orderItem.status === OrderItemsStatus.Shipped || orderItem.status === OrderItemsStatus.Delivered) throw new BadRequestError(`آیتم سفارش با شناسه ${item.orderItemId} در این وضعیت قابل لغو نیست.`); const shipmentItem = orderItem.shipmentItems.find((shipItem) => shipItem._id?.equals(item.shipmentItemId)); if (!shipmentItem) throw new BadRequestError(`آیتم حمل با شناسه ${item.shipmentItemId} در آیتم سفارش ${item.orderItemId} یافت نشد.`); const cancelReason = await this.cancelReasonRepo.model.findById(item.reason); if (!cancelReason) { throw new BadRequestError(CommonMessage.NotFoundById); } const cancelledQuantity = shipmentItem.cancelled_quantity || 0; const returnedQuantity = shipmentItem.returned_quantity || 0; if (item.quantity > shipmentItem.quantity - cancelledQuantity - returnedQuantity) { throw new BadRequestError(`Return quantity for ShipmentItem ${item.shipmentItemId} exceeds the available quantity.`); } total_cancel_price += item.quantity * shipmentItem.selling_price; const cancelOrderItem = await this.cancelOrderItemRepo.model.create( [ { cancelOrderId: cancelOrderDoc._id, orderItem: item.orderItemId, shipmentItem: item.shipmentItemId, quantity: item.quantity, comment: item.comment, reason: item.reason, status: OrderItemsStatus.cancelled_shop, }, ], { session }, ); const fineRule = await this.fineRuleRepo.findById(cancelReason.fineRule.toString()); if (fineRule) { const fineAmount = (fineRule.fine_percentage * total_cancel_price) / 100; await this.fineRepo.model.create( [ { seller: sellerId, orderItem: cancelOrderItem[0]._id, orderItemRef: OrderItemRefEnum.CancelOrderItem, fine_rule: fineRule._id, reason: item.reason, reasonRef: ReasonRefEnum.Cancel, fine_amount: fineAmount, }, ], { session }, ); await this.notificationService.notifyFine(sellerId, fineAmount, session); //TODO : decrease balance from seller wallet } shipmentItem.cancelled_quantity! += item.quantity; if (shipmentItem.cancelled_quantity === shipmentItem.quantity) orderItem.status = OrderItemsStatus.cancelled_shop; await orderItem.save({ session }); } cancelOrderDoc.total_price = total_cancel_price; await cancelOrderDoc.save({ session }); await session.commitTransaction(); await session.endSession(); return { cancelOrder: cancelOrder[0] }; } catch (error) { await session.abortTransaction(); await session.endSession(); throw error; } } //####################################################### //####################################################### async createCancelOrderByAdmin(adminId: string, createCancelDto: CreateCancelDTO) { const session = await startSession(); session.startTransaction(); try { // create Cancel Order let total_cancel_price = 0; const cancelOrder = await this.cancelOrderRepo.model.create( [ { cancellerRef: CancellerRefEnum.Admin, canceller: adminId, order: createCancelDto.orderId, status: OrderItemsStatus.cancelled_system, }, ], { session }, ); const cancelOrderDoc = cancelOrder[0]; // Create cancelOrderItems for (const item of createCancelDto.items) { const orderItem = await this.orderItemRepo.model.findById(item.orderItemId).session(session); if (!orderItem) throw new BadRequestError(`آیتم سفارش با شناسه ${item.orderItemId} یافت نشد.`); if (orderItem.status === OrderItemsStatus.Shipped || orderItem.status === OrderItemsStatus.Delivered) throw new BadRequestError(`آیتم سفارش با شناسه ${item.orderItemId} در این وضعیت قابل لغو نیست.`); const shipmentItem = orderItem.shipmentItems.find((shipItem) => shipItem._id?.equals(item.shipmentItemId)); if (!shipmentItem) throw new BadRequestError(`آیتم حمل با شناسه ${item.shipmentItemId} در آیتم سفارش ${item.orderItemId} یافت نشد.`); const cancelReason = await this.cancelReasonRepo.model.findById(item.reason); if (!cancelReason) { throw new BadRequestError(CommonMessage.NotFoundById); } const cancelledQuantity = shipmentItem.cancelled_quantity || 0; const returnedQuantity = shipmentItem.returned_quantity || 0; if (item.quantity > shipmentItem.quantity - cancelledQuantity - returnedQuantity) { throw new BadRequestError(`Return quantity for ShipmentItem ${item.shipmentItemId} exceeds the available quantity.`); } total_cancel_price += item.quantity * shipmentItem.selling_price; await this.cancelOrderItemRepo.model.create( [ { cancelOrderId: cancelOrderDoc._id, orderItem: item.orderItemId, shipmentItem: item.shipmentItemId, quantity: item.quantity, comment: item.comment, reason: item.reason, status: OrderItemsStatus.cancelled_shop, }, ], { session }, ); shipmentItem.cancelled_quantity! += item.quantity; if (shipmentItem.cancelled_quantity === shipmentItem.quantity) orderItem.status = OrderItemsStatus.cancelled_shop; await orderItem.save({ session }); } cancelOrderDoc.total_price = total_cancel_price; await cancelOrderDoc.save({ session }); await session.commitTransaction(); await session.endSession(); return { cancelOrder: cancelOrder[0] }; } catch (error) { await session.abortTransaction(); await session.endSession(); throw error; } } //####################################################### //####################################################### async getCancelReasons() { const reasons = await this.cancelReasonRepo.model.find({ deleted: false }).populate("fineRule"); return { reasons }; } //####################################################### //####################################################### async createCancelReason(cancelReasonDto: CancelReasonDTO) { const fineRule = await this.fineRuleRepo.model.findById(cancelReasonDto.fineRule); if (!fineRule) { throw new BadRequestError(CommonMessage.NotFoundById); } const newReason = await this.cancelReasonRepo.model.create({ title: cancelReasonDto.title, fineRule: fineRule._id, }); return { message: CommonMessage.Created, newReason, }; } async deleteCancelReason(reasonId: string) { if (!isValidObjectId(reasonId)) throw new BadRequestError(CommonMessage.NotValidId); await this.cancelReasonRepo.model.findByIdAndUpdate(reasonId, { deleted: true }, { new: true }); return { message: CommonMessage.Deleted, }; } async completeCancelOrder(cancelRequestId: string, dto: CompeleteCancelOrderDTO) { const { refundComment, refundDate, refundReceipt } = dto; const cancelOrder = await this.cancelOrderRepo.model.findByIdAndUpdate(cancelRequestId, { status: StatusEnum.Completed, refundComment, refundDate, refundReceipt, }); if (!cancelOrder) throw new BadRequestError(OrderMessage.NotFound); return { message: OrderMessage.CancelCompleted, }; } //####################################################### //####################################################### //####################################################### //####################################################### // async updateReturnReason(reasonId: string, updateReasonDto: UpdateReasonDTO) { // if (!isValidObjectId(reasonId)) throw new BadRequestError(CommonMessage.NotValidId); // const updatedReason = await this.returnReasonRep.model.findByIdAndUpdate(reasonId, updateReasonDto, { new: true }); // if (!updatedReason) throw new BadRequestError(CommonMessage.NotFoundById); // return { // message: CommonMessage.Updated, // updatedReason, // }; // } //####################################################### //####################################################### } export { CancelService };