385 lines
15 KiB
TypeScript
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 };
|