This commit is contained in:
mahyargdz
2025-09-14 15:15:03 +03:30
commit be82059172
534 changed files with 51310 additions and 0 deletions
+444
View File
@@ -0,0 +1,444 @@
import { inject, injectable } from "inversify";
import { ClientSession, startSession } from "mongoose";
import { InsertTrackCodeDTO, InsertTrackCodeGroupDTO } from "./DTO/insertTrackCode.dto";
import { ReceivedOrderItemDTO } from "./DTO/received.dto";
import { SellerOrdersDTO } from "./DTO/sellerOrder.dto";
import { UserOrderDTO } from "./DTO/userOrder.dto";
import { IShipmentAddress } from "./models/Abstraction/IOrder";
import { IOrderItem } from "./models/Abstraction/IOrderItem";
import { OrderItemRepo, OrderRepository } from "./order.repository";
import { AddressMessage, OrderMessage, ShopMessage, UserMessage } from "../../common/enums/message.enum";
import { OrderItemsStatus, OrdersStatus } from "../../common/enums/order.enum";
import { OrderStatusQuery, SellerOrdersQueries } from "../../common/types/query.type";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
import { OrderQueue } from "../../queues/order/OrderQueue";
import { EmailService } from "../../utils/email.service";
import { AddressService } from "../address/address.service";
import { ICity } from "../address/models/city.model";
import { IProvince } from "../address/models/province.model";
import { ICartShipmentItem } from "../cart/models/Abstraction/ICartShipmentItem";
import { ICartPayment } from "../payment/models/Abstraction/IPayments";
import { OwnerRef } from "../shop/models/Abstraction/IShop";
import { ShopRepo } from "../shop/shop.repository";
import { IUser } from "../user/models/Abstraction/IUser";
import { WalletService } from "../wallet/wallet.service";
@injectable()
class OrderService {
@inject(IOCTYPES.OrderRepository) private orderRepo: OrderRepository;
@inject(IOCTYPES.OrderItemRepo) private orderItemRepo: OrderItemRepo;
@inject(IOCTYPES.ShopRepo) private shopRepo: ShopRepo;
@inject(IOCTYPES.AddressService) private addressService: AddressService;
@inject(IOCTYPES.WalletService) private walletService: WalletService;
//#######################################################
//#######################################################
async updateOrderStatus(orderId: number, status: OrdersStatus, session: ClientSession) {
return await this.orderRepo.model.findByIdAndUpdate(orderId, { orderStatus: status }, { session });
}
//#######################################################
//#######################################################
async getOrderItemByOrderId(orderId: number, session: ClientSession) {
return await this.orderItemRepo.model.find({ order: orderId }, null, { session });
}
//#######################################################
//#######################################################
async getOrderByPaymentId(paymentId: string, session: ClientSession) {
return await this.orderRepo.model.findOne({ payment: paymentId }).session(session);
}
//#######################################################
//#######################################################
async createOrder(user: IUser, payment: ICartPayment, cartShipmentItems: ICartShipmentItem[], session: ClientSession) {
if (!user.address) throw new BadRequestError([AddressMessage.UserShouldHaveAddress, AddressMessage.NotFound]);
const userAddress = await this.addressService.getUserAddress(user.address.toString());
const city = userAddress.city as unknown as ICity;
const province = userAddress.province as unknown as IProvince;
const shipmentAddress: IShipmentAddress = {
address: userAddress.address,
city: city.name,
province: province.name,
phone: user.phoneNumber,
plaque: userAddress.plaque,
postalCode: userAddress.postalCode,
};
const order = await this.orderRepo.model.create(
[
{
user: user._id.toString(),
payment: payment._id,
shipmentAddress,
},
],
{ session },
);
const orderItemsData: IOrderItem[] = cartShipmentItems.map((item) => ({
order: order[0]._id,
shop: item.shop,
shipmentItems: item.shipmentItems,
shipper: item.shipper,
totalSellingPrice: item.totalSellingPrice,
totalPaymentPrice: item.totalPaymentPrice,
totalRetailPrice: item.totalRetailPrice,
totalCouponDiscount: item.totalCouponDiscount,
totalShipmentCost: item.totalShipmentCost,
itemsCount: item.itemsCount,
status: OrderItemsStatus.Processing,
}));
const orderItems = await this.orderItemRepo.model.create(orderItemsData, { session });
// Add the order to the queue with a 5-minute delay
await OrderQueue.addOrderToQueue(order[0]._id, 5 * 60 * 1000);
return {
order: order[0],
orderItems,
};
}
//#######################################################
//#######################################################
async getAdminOrders(sellerId: string, queries: SellerOrdersQueries) {
const shop = await this.shopRepo.model.findOne({ owner: sellerId });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const priceRange = await this.orderItemRepo.getOrderPriceRange(shop._id.toString());
const { docs, count } = await this.orderItemRepo.getOrdersForSellerPanel(shop._id.toString(), queries);
const orders = docs.map((doc: any) => SellerOrdersDTO.transformOrder(doc));
return { orders, count, priceRange };
}
//#######################################################
//#######################################################
async getSellerOrders(ownerRef: OwnerRef, queries: SellerOrdersQueries) {
const shops = await this.shopRepo.model.find({ ownerRef }).select("_id");
const shopIds = shops.map((shop) => shop._id);
const priceRange = await this.orderItemRepo.getSellerOrderPriceRange(shopIds);
const { docs, count } = await this.orderItemRepo.getSellerOrdersForAdminPanel(shopIds, queries);
const orders = docs.map((doc: any) => SellerOrdersDTO.transformOrder(doc));
return { orders, count, priceRange };
}
//#######################################################
//#######################################################
async getOrders(sellerId: string, queries: SellerOrdersQueries, orderIds?: string[] | string) {
const shop = await this.shopRepo.model.findOne({ owner: sellerId });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const priceRange = await this.orderItemRepo.getOrderPriceRange(shop._id.toString());
const { docs, count } = await this.orderItemRepo.getOrdersForSellerPanel(shop._id.toString(), queries, orderIds);
const orders = docs.map((doc: any) => SellerOrdersDTO.transformOrder(doc));
return { orders, count, priceRange };
}
//#######################################################
//#######################################################
async insertTrackCode(orderId: number, sellerId: string, insertDto: InsertTrackCodeDTO) {
const { orderItem } = await this.validateSellerOrder(orderId, sellerId);
orderItem.trackCode = insertDto.trackCode;
orderItem.postingDate = insertDto.postingDate;
orderItem.postingReceipt = insertDto.postingReceipt;
orderItem.status = OrderItemsStatus.Shipped;
await orderItem.save();
return {
message: OrderMessage.TrackingDetailInserted,
orderItem,
};
}
//#######################################################
async insertTrackCodeGroup(sellerId: string, insertDto: InsertTrackCodeGroupDTO) {
const session = await startSession();
session.startTransaction();
try {
const orderIds = insertDto.trackCodes.map((tc) => tc.orderId);
const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const orderItems = await this.orderItemRepo.model.find({ order: { $in: orderIds }, shop: shop._id }).session(session);
if (orderItems.length !== orderIds.length) throw new BadRequestError(OrderMessage.OrderItemsNotFound);
// Prepare bulk operations
const bulkOps = insertDto.trackCodes.map((trackCodeDto) => ({
updateOne: {
filter: {
order: trackCodeDto.orderId,
shop: shop._id,
},
update: {
$set: {
trackCode: trackCodeDto.trackCode,
postingDate: trackCodeDto.postingDate,
postingReceipt: trackCodeDto.postingReceipt,
status: OrderItemsStatus.Shipped,
},
},
},
}));
// Execute bulk operation
await this.orderItemRepo.model.bulkWrite(bulkOps, { session });
await session.commitTransaction();
return {
message: OrderMessage.TrackingDetailInserted,
};
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
await session.endSession();
}
}
//#######################################################
//#######################################################
async insertNativeShopTrackCode(orderId: number, adminId: string, insertDto: InsertTrackCodeDTO) {
const { orderItem } = await this.validateSellerOrder(orderId, adminId);
orderItem.trackCode = insertDto.trackCode;
orderItem.postingDate = insertDto.postingDate;
orderItem.postingReceipt = insertDto.postingReceipt;
orderItem.status = OrderItemsStatus.Shipped;
await orderItem.save();
return {
message: OrderMessage.TrackingDetailInserted,
orderItem,
};
}
//#######################################################
//#######################################################
async trackOrder(orderId: number, userId: string, userEmail: string) {
if (isNaN(orderId)) throw new BadRequestError(OrderMessage.NotFound);
if (!userEmail) throw new BadRequestError(UserMessage.EmailNotExist);
const order = await this.orderRepo.model.findOne({ _id: orderId, user: userId });
if (!order) throw new BadRequestError(OrderMessage.NotFound);
const trackingDetails = await this.orderItemRepo.getOrderTrackingDetails(orderId);
if (!trackingDetails.length) throw new BadRequestError(OrderMessage.TrackingCodeNotFound);
const trackingInfo = trackingDetails.map((item: any) => ({
trackCode: item.trackCode,
status: item.status,
shipper: item.shipper.name,
postingDate: item.postingDate,
}));
await EmailService.sendOrderTrackingEmail(userEmail, { orderId, trackingInfo });
return {
message: OrderMessage.TrackingEmailSent,
};
}
//#######################################################
async setOrderItemReceived(orderId: number, userId: string, receivedOrderItemDto: ReceivedOrderItemDTO) {
const session = await startSession();
session.startTransaction();
try {
await this.validateUserOrder(orderId, userId);
const orderItem = await this.orderItemRepo.model.findById(receivedOrderItemDto.orderItemId).session(session);
if (!orderItem) throw new BadRequestError(`OrderItem with ID ${receivedOrderItemDto.orderItemId} not found.`);
if (orderItem.status === OrderItemsStatus.Delivered) throw new BadRequestError(OrderMessage.ItemAlreadyReceived);
if (orderItem.status !== OrderItemsStatus.Shipped) throw new BadRequestError(OrderMessage.ItemNotShipped);
orderItem.status = OrderItemsStatus.Delivered;
const shop = await this.shopRepo.model.findById(orderItem.shop.toString()).session(session);
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFoundById);
if (shop.ownerRef === OwnerRef.SELLER) {
await this.walletService.creditSellerWalletForOrder(shop.owner.toString(), orderId, orderItem.totalPaymentPrice, session);
}
await orderItem.save({ session });
await session.commitTransaction();
await session.endSession();
return {
message: OrderMessage.ItemReceived,
};
} catch (error) {
await session.abortTransaction();
await session.endSession();
throw error;
}
}
//#######################################################
//#######################################################
async getSellerOrderDetail(orderId: number, sellerId: string) {
const { shop } = await this.validateSellerOrder(orderId, sellerId);
const doc = await this.orderItemRepo.getSellerOrderDetail(orderId, shop._id.toString());
const order = SellerOrdersDTO.transformOrder(doc[0]);
return { order };
}
//#######################################################
//#######################################################
async getUserOrderDetail(orderId: number, userId: string) {
await this.validateUserOrder(orderId, userId);
const doc = await this.orderRepo.getUserOrderDetail(orderId, userId);
const order = UserOrderDTO.transformOrder(doc[0]);
return { order };
}
async getUserOrderItemsDetail(orderId: number, orderItemId: string, userId: string) {
await this.validateUserOrder(orderId, userId);
const doc = await this.orderItemRepo.getUserOrderItemsDetail(orderId, orderItemId);
const orderItems = UserOrderDTO.transformOrder(doc[0]);
return { orderItems };
}
//#######################################################
//#######################################################
async getSellerSalesStats(sellerId: string, queries: { start: string; end: string }) {
const shop = await this.shopRepo.model.findOne({ owner: sellerId });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const saleStats = await this.orderItemRepo.getSellerSalesStats(shop._id.toString(), queries);
return { saleStats };
}
//#######################################################
//#######################################################
async getUserOrders(userId: string, statusQuery: OrderStatusQuery) {
// const orders = await this.orderRepo.model.find({ user: userId });
const docs = await this.orderRepo.getUserOrders(userId, statusQuery);
const orders = docs.map((doc) => UserOrderDTO.transformOrder(doc));
const mappedOrders = orders.map((order) => this.mapUserOrderItems(order));
return { mappedOrders, orders };
}
//helper methods
//#######################################################
//#######################################################
private async validateSellerOrder(orderId: number, sellerId: string) {
const order = await this.orderRepo.findById(orderId);
if (!order) throw new BadRequestError(OrderMessage.NotFound);
const shop = await this.shopRepo.model.findOne({ owner: sellerId });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const orderItem = await this.orderItemRepo.model.findOne({ shop: shop._id.toString(), order: order._id });
if (!orderItem) throw new BadRequestError(OrderMessage.OrderItemsNotFound);
return { order, orderItem, shop };
}
//#######################################################
//#######################################################
private async validateUserOrder(orderId: number, userId: string) {
const order = await this.orderRepo.model.findOne({ _id: orderId, user: userId });
if (!order) throw new BadRequestError(OrderMessage.NotFound);
const orderItems = await this.orderItemRepo.model.find({ order: order._id });
if (!orderItems || orderItems.length === 0) throw new BadRequestError(OrderMessage.OrderItemsNotFound);
return { order, orderItems };
}
//#######################################
//#######################################
private mapUserOrderItems(userOrder: UserOrderDTO) {
const shipmentItems = userOrder.orderItems.map((orderItem) => {
return orderItem.shipmentItems;
});
return {
orderId: userOrder._id,
user: userOrder.user,
orderItems: shipmentItems.flat(Infinity),
payment: userOrder.payment,
shipmentAddress: userOrder.shipmentAddress,
orderStatus: userOrder.orderStatus,
createdAt: userOrder.createdAt,
};
}
//#######################################
//#######################################
async getDailySalesReport() {
const sales = await this.orderItemRepo.getDailySalesReport();
return sales;
}
async getWeeklySalesReport() {
const sales = await this.orderItemRepo.getWeeklySalesReport();
return sales;
}
async getMonthlySalesReport() {
const sales = await this.orderItemRepo.getMonthlySalesReport();
return sales;
}
async getAnnualSalesReport() {
const sales = await this.orderItemRepo.getAnnualSalesReport();
return sales;
}
async getDailyShipmentSalesReport() {
const sales = await this.orderItemRepo.getDailyShipmentSalesReport();
return sales;
}
async getWeeklyShipmentSalesReport() {
const sales = await this.orderItemRepo.getWeeklyShipmentSalesReport();
return sales;
}
async getMonthlyShipmentSalesReport() {
const sales = await this.orderItemRepo.getMonthlyShipmentSalesReport();
return sales;
}
async getAnnualShipmentSalesReport() {
const sales = await this.orderItemRepo.getAnnualShipmentSalesReport();
return sales;
}
async getFiveDaysAgoSalesReport() {
const sales = await this.orderItemRepo.getFiveDaysAgoSalesReport();
return sales;
}
async getTopSellingProductsS() {
const sales = await this.orderItemRepo.getTopSellingProducts();
return sales;
}
async getAllProcessingOrders() {
const sales = await this.orderItemRepo.getAllProcessingOrders();
return sales;
}
}
export { OrderService };