Files
shop-api/src/modules/cancel/cancel.service.ts
T
morteza-mortezai 8685f78aaf fix : cancell order
2025-11-06 11:39:36 +03:30

385 lines
15 KiB
TypeScript

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 };