first
This commit is contained in:
@@ -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 };
|
||||
Reference in New Issue
Block a user