import { randomBytes } from "crypto"; import { inject, injectable } from "inversify"; import { FilterQuery, isValidObjectId, startSession } from "mongoose"; import { CompleteRespirationLegalDTO, CompleteRespirationRealDTO } from "./DTO/complete-register.dto"; import { CreateDocumentTypeDTO } from "./DTO/createDocumentType"; import { LegalSellerUpdateDto, RealSellerUpdateDto } from "./DTO/sellerUpdate.dto"; import { BusinessTypeEnum } from "./enum/seller.enum"; import { DocumentTypeRepo } from "./repository/documentType.repository"; import { LegalSellerRepo } from "./repository/legalSeller.repository"; import { SellerContractRepo } from "./repository/sellerContract.repository"; import { SellerStatusRepo } from "./repository/sellerStatus.repository"; import { SellerRepository } from "./seller.repository"; import { PaginationDTO } from "../../common/dto/pagination.dto"; import { AddressMessage, AuthMessage, CommonMessage, ContractMessage, SellerMessage, SetShipmentMessage, ShopMessage, } from "../../common/enums/message.enum"; import { OrderItemsStatus } from "../../common/enums/order.enum"; import { BadRequestError } from "../../core/app/app.errors"; import { IOCTYPES } from "../../IOC/ioc.types"; import { contractTemplate } from "../../template/contract"; import { ChatService } from "../chat/chat.service"; import { OrderItemRepo } from "../order/order.repository"; import { CommentRepository } from "../product/Repository/comment"; import { ProductRepository } from "../product/Repository/product"; import { ProductVariantRepository } from "../product/Repository/productVarinat"; import { OwnerRef } from "../shop/models/Abstraction/IShop"; import { ShopRepo } from "../shop/shop.repository"; import { ShopService } from "../shop/shop.service"; import { UploadSellerDocumentDTO } from "./DTO/update-sellerDocument.dto"; import { UpdateShopShipmentDTO } from "./DTO/update-shop-shipment.dto"; import { ILegalSeller } from "./models/Abstraction/ILegal-seller"; import { IRealSeller } from "./models/Abstraction/IReal-seller"; import { RealSellerRepo } from "./repository/realSeller.repository"; import { SellerDocumentRepo } from "./repository/sellerDocument.repository"; import { ContractType } from "./types/contract.types"; import { ContractRepository } from "../admin/repository/contract"; import { ISeller } from "./models/Abstraction/ISeller"; import { WholesaleRequestRepo } from "./repository/wholesaleRequest.repositroy"; import { StatusEnum } from "../../common/enums/status.enum"; import { paginationUtils } from "../../utils/pagination.utils"; import { UpdateSellerDocumentDto } from "../admin/DTO/updateSellerDocument.dto"; import { NotificationService } from "../notification/notification.service"; import { TicketService } from "../ticket/ticket.service"; @injectable() class SellerService { @inject(IOCTYPES.SellerRepository) sellerRepo: SellerRepository; @inject(IOCTYPES.ProductRepository) productRepo: ProductRepository; @inject(IOCTYPES.ProductVariantRepository) productVariantRepo: ProductVariantRepository; @inject(IOCTYPES.OrderItemRepo) orderItemRepo: OrderItemRepo; @inject(IOCTYPES.ShopService) shopService: ShopService; @inject(IOCTYPES.ShopRepo) shopRepo: ShopRepo; @inject(IOCTYPES.WholesaleRequestRepo) wholesaleRequestRepo: WholesaleRequestRepo; @inject(IOCTYPES.SellerStatusRepo) sellerStatusRepo: SellerStatusRepo; @inject(IOCTYPES.RealSellerRepo) realSellerRepo: RealSellerRepo; @inject(IOCTYPES.LegalSellerRepo) legalSellerRepo: LegalSellerRepo; @inject(IOCTYPES.SellerDocumentRepo) sellerDocumentRepo: SellerDocumentRepo; @inject(IOCTYPES.SellerContractRepo) sellerContractRepo: SellerContractRepo; @inject(IOCTYPES.DocumentTypeRepo) documentTypeRepo: DocumentTypeRepo; @inject(IOCTYPES.CommentRepository) commentRepo: CommentRepository; @inject(IOCTYPES.ContractRepository) contractRepo: ContractRepository; @inject(IOCTYPES.NotificationService) notificationService: NotificationService; @inject(IOCTYPES.TicketService) ticketService: TicketService; @inject(IOCTYPES.ChatService) chatService: ChatService; //################################### // async getSellers(queryDto: PaginationDTO) { // const { limit, skip } = paginationUtils(queryDto); // const count = await this.sellerRepo.model.countDocuments(); // const sellers = await this.sellerRepo.model.find().skip(skip).limit(limit).lean(); // return { sellers, count }; // } //############# async getSellersWithDocumentsAndContracts(queryDto: PaginationDTO) { const { limit, skip } = paginationUtils(queryDto); const count = await this.sellerRepo.model.countDocuments(); const sellers = await this.sellerRepo.model.aggregate([ { $lookup: { from: "sellerdocuments", localField: "_id", foreignField: "seller", as: "documents", }, }, { $lookup: { from: "shops", localField: "_id", foreignField: "owner", as: "shop", }, }, { $unwind: "$shop", }, { $lookup: { from: "sellercontracts", localField: "_id", foreignField: "seller", as: "contracts", }, }, { $unwind: { path: "$contracts", preserveNullAndEmptyArrays: true, }, }, { $skip: skip }, { $limit: limit }, ]); return { sellers, count }; } async getSellerById(sellerId: string) { if (!isValidObjectId(sellerId)) throw new BadRequestError(CommonMessage.NotValidId); const seller = await this.sellerRepo.model.findById(sellerId).lean(); if (!seller) throw new BadRequestError(SellerMessage.SellerNotFound); return { seller, }; } //################################### async approveSellerRegistration(sellerId: string) { const session = await startSession(); session.startTransaction(); try { if (!isValidObjectId(sellerId)) throw new BadRequestError(CommonMessage.NotValidId); const seller = await this.sellerRepo.model.findById(sellerId); if (!seller) throw new BadRequestError(SellerMessage.SellerNotFound); const sellerStatus = await this.sellerStatusRepo.model.findOne({ seller: sellerId }); if (!sellerStatus) throw new BadRequestError(SellerMessage.StatusNotFound); if (sellerStatus.register && seller.accountStatus === StatusEnum.Approved) { seller.accountStatus = StatusEnum.Pending; sellerStatus.register = false; await this.notificationService.notifyDeactive(sellerId, session); } else { sellerStatus.register = true; seller.accountStatus = StatusEnum.Approved; } await sellerStatus.save({ session }); await seller.save({ session }); await session.commitTransaction(); return { message: CommonMessage.Updated, status: sellerStatus, }; } catch (error) { await session.abortTransaction(); throw error; } finally { await session.endSession(); } } //################################### async updateSellerDocumentStatus(sellerId: string, updateDto: UpdateSellerDocumentDto) { if (!isValidObjectId(sellerId)) throw new BadRequestError(CommonMessage.NotValidId); const seller = await this.sellerRepo.model.findById(sellerId); if (!seller) throw new BadRequestError(SellerMessage.SellerNotFound); const document = await this.sellerDocumentRepo.model.findOneAndUpdate( { _id: updateDto.docsId, seller: sellerId }, { status: updateDto.status }, ); if (!document) throw new BadRequestError(SellerMessage.SellerDocumentNotFound); return { message: CommonMessage.Updated, status: document.status, }; } //################################### async deleteSellerDocument(documentId: string, rejectReason: string) { const document = await this.sellerDocumentRepo.model.findByIdAndDelete(documentId); if (!document) throw new BadRequestError(SellerMessage.SellerDocumentNotFound); await this.notificationService.notifyRejectDocument(document.seller.toString(), rejectReason); return { message: CommonMessage.Deleted, }; } //################################### async approveSellerContract(sellerId: string) { const session = await startSession(); session.startTransaction(); try { if (!isValidObjectId(sellerId)) throw new BadRequestError(CommonMessage.NotValidId); const seller = await this.sellerRepo.model.findById(sellerId).session(session); if (!seller) throw new BadRequestError(SellerMessage.SellerNotFound); const sellerStatus = await this.sellerStatusRepo.model.findOne({ seller: sellerId }).session(session); if (!sellerStatus) throw new BadRequestError(SellerMessage.StatusNotFound); sellerStatus.contract = true; await sellerStatus.save({ session }); this.notificationService.notifyApproveContract(sellerId, session); await session.commitTransaction(); return { message: CommonMessage.Updated, status: sellerStatus, }; } catch (error) { await session.abortTransaction(); throw error; } finally { await session.endSession(); } } //################################### async approveSellerWholesale(requestId: string) { const session = await startSession(); session.startTransaction(); try { if (!isValidObjectId(requestId)) throw new BadRequestError(CommonMessage.NotValidId); const wholesaleRequest = await this.wholesaleRequestRepo.model.findById(requestId).session(session); if (!wholesaleRequest) throw new BadRequestError(SellerMessage.WholesaleRequestNotFound); const seller = await this.sellerRepo.model.findById(wholesaleRequest.seller).session(session); if (!seller) throw new BadRequestError(SellerMessage.SellerNotFound); wholesaleRequest.status = StatusEnum.Approved; seller.isWholesaler = true; await this.notificationService.notifyWholesaleRequestApproved(seller._id.toString(), session); await seller.save({ session }); await wholesaleRequest.save({ session }); await session.commitTransaction(); return { message: SellerMessage.WholesaleRequestApproved, }; } catch (error) { await session.abortTransaction(); throw error; } finally { session.endSession(); } } async getWholesaleRequests(queryDto: PaginationDTO) { const { limit, skip } = paginationUtils(queryDto); const count = await this.wholesaleRequestRepo.model.countDocuments(); const requests = await this.wholesaleRequestRepo.model .find() .limit(limit) .skip(skip) .populate([{ path: "seller" }, { path: "shop" }]); return { requests, count, }; } //################################### async getSellerPanelInfo(sellerId: string) { const { shop } = await this.shopService.getShopInfo(sellerId, OwnerRef.SELLER); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); return { shopInfo: shop, productCount: await this.getProductCounts(shop._id.toString()), logistic: await this.logistic(shop._id.toString()), orderStats: await this.orderStats(shop._id.toString()), salesStats: await this.salesStats(shop._id.toString()), shopRating: await this.shopRating(shop._id.toString()), }; } //#################################### async updateShopShipper(sellerId: string, updateDto: UpdateShopShipmentDTO, type: "activate" | "deactivate") { const { shipmentId } = updateDto; const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); if (type === "activate") { if (shop.shipmentMethod.includes(shipmentId)) throw new BadRequestError(SetShipmentMessage.ShipmentMethodAlreadyActive); shop.shipmentMethod.push(shipmentId); } else { if (!shop.shipmentMethod.includes(shipmentId)) throw new BadRequestError(SetShipmentMessage.ShipmentMethodNotActive); shop.shipmentMethod = shop.shipmentMethod.filter((methodId) => methodId !== shipmentId); } await shop.save(); return { message: CommonMessage.Updated, }; } async changeShopChatStatus(sellerId: string) { const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); shop.isChatActive = !shop.isChatActive; await shop.save(); return { message: CommonMessage.Updated, }; } //#################################### async getItemsCount(sellerId: string) { const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); const notificationCount = await this.notificationService.getUnreadNotificationCount(sellerId); const ticketCount = await this.ticketService.getUnreadTickerCount(sellerId); const chatCount = await this.chatService.getUnreadChatCount(sellerId); const orderCount = await this.orderItemRepo.model.countDocuments({ shop: shop._id, status: OrderItemsStatus.Processing }); return { ticketCount, notificationCount, chatCount, orderCount }; } //#################################### async createDocumentType(createDto: CreateDocumentTypeDTO) { const existDocumentType = await this.documentTypeRepo.model.exists({ title: createDto.title }); if (existDocumentType) throw new BadRequestError(SellerMessage.DocumentTypeDuplicate); const documentType = await this.documentTypeRepo.model.create(createDto); return { message: CommonMessage.Created, documentType, }; } //############################################################## async getContract(seller: ISeller) { let contract; contract = await this.sellerContractRepo.model.findOne({ seller: seller._id }); if (contract) { return { contract, }; } const baseContract = await this.contractRepo.model.findOne(); if (!baseContract) throw new BadRequestError(ContractMessage.ContractNotFound); let national_code; let id_number; if (seller.businessType === BusinessTypeEnum.REAL) { const realSeller = await this.realSellerRepo.model.findOne({ seller: seller._id }); if (!realSeller) throw new BadRequestError(SellerMessage.SellerNotFound); national_code = realSeller.nationalCode; id_number = realSeller.nationalCode; } else { const legalSeller = await this.legalSellerRepo.model.findOne({ seller: seller._id }); if (!legalSeller) throw new BadRequestError(SellerMessage.SellerNotFound); national_code = legalSeller.companyNationalId; id_number = legalSeller.companyNationalId; } const { shop } = await this.shopService.getShopInfo(seller._id.toString(), OwnerRef.SELLER); if (!shop.address) throw new BadRequestError(AddressMessage.SellerAddressNotFound); const contractContent = this.renderContractTemplate({ content: baseContract.content, seller_name: seller.fullName, national_code, address: shop.address.address, postal_code: shop.address.postalCode, date: new Date().toLocaleDateString("fa-IR"), id_number, email: seller.email, phone_number: seller.phoneNumber, }); const contractNumber = `SKC-${randomBytes(5).toString("hex")}-${Date.now().toString().slice(-5)}`; contract = await this.sellerContractRepo.model.create({ seller: seller._id.toString(), contractNumber, content: contractContent, }); return { contract, }; } //############################################################## async signContract(sellerId: string, contractId: string) { if (!isValidObjectId(contractId)) throw new BadRequestError(CommonMessage.NotValidId); const contract = await this.sellerContractRepo.model.findOne({ _id: contractId, seller: sellerId }); if (!contract) throw new BadRequestError(ContractMessage.ContractNotFound); if (contract.signed) throw new BadRequestError(ContractMessage.ContractAlreadySigned); contract.singedAt = new Date().toLocaleDateString("fa-IR"); contract.signed = true; await contract.save(); return { contract, }; } //############################################################## async getDocumentTypes() { const documentTypes = await this.documentTypeRepo.model.find(); return { documentTypes }; } //############################################################## async uploadSellerDocument(sellerId: string, uploadDto: UploadSellerDocumentDTO) { const updatedSellerDocument = await this.sellerDocumentRepo.model.findOneAndUpdate( { seller: sellerId, type: uploadDto.documentTypeId }, { seller: sellerId, type: uploadDto.documentTypeId, docsUrl: uploadDto.docsUrl }, { new: true, upsert: true }, ); return { message: CommonMessage.Updated, sellerDocument: updatedSellerDocument, }; } //############################################################## async getSellerDocument(sellerId: string) { const sellerDocument = await this.sellerDocumentRepo.model.find({ seller: sellerId }).populate("type"); return { sellerDocument }; } //############################################################## async getSingleSellerDocument(sellerId: string, documentId: string) { const sellerDocument = await this.sellerDocumentRepo.model.findOne({ seller: sellerId, _id: documentId }).populate("type"); if (!sellerDocument) throw new BadRequestError(SellerMessage.SellerDocumentNotFound); return { sellerDocument, }; } //############# async updateSellerInfo(type: BusinessTypeEnum, updateDto: RealSellerUpdateDto | LegalSellerUpdateDto, sellerId: string) { const session = await startSession(); session.startTransaction(); try { if (type === BusinessTypeEnum.REAL) { const { nationalCode, cardNumber, shebaNumber, ...sellerInfoData } = updateDto as RealSellerUpdateDto; //check for duplicate await this.checkRealSellerDuplicate(sellerId, nationalCode, cardNumber, shebaNumber, true); await this.sellerRepo.model.findByIdAndUpdate(sellerId, { ...sellerInfoData }, { new: true, session }); await this.realSellerRepo.model.findOneAndUpdate( { seller: sellerId }, { nationalCode, cardNumber, shebaNumber }, { new: true, session }, ); await session.commitTransaction(); return { message: CommonMessage.Updated, }; } else { const { IBan, companyName, companyType, companyNationalId, companyEconomicNumber, ...sellerInfo } = updateDto as LegalSellerUpdateDto; //check for duplicate await this.checkCompanyDuplicate(sellerId, companyName, companyNationalId, companyEconomicNumber, IBan, true); await this.sellerRepo.model.findByIdAndUpdate(sellerId, { ...sellerInfo }, { new: true, session }); await this.legalSellerRepo.model.findOneAndUpdate( { seller: sellerId }, { companyName, companyNationalId, companyEconomicNumber, IBan, companyType }, { new: true, session }, ); await session.commitTransaction(); return { message: CommonMessage.Updated, }; } } catch (error) { await session.abortTransaction(); throw error; } finally { await session.endSession(); } } //####################### async completeRegistrationReal(sellerId: string, completeRealDto: CompleteRespirationRealDTO) { const session = await startSession(); session.startTransaction(); try { const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); const { nationalCode, cardNumber, shebaNumber, ...sellerInfo } = completeRealDto; const existEmail = await this.sellerRepo.model.exists({ _id: { $ne: sellerId }, email: sellerInfo.email }).session(session); if (existEmail) throw new BadRequestError(AuthMessage.EmailExists); const seller = await this.sellerRepo.model.findByIdAndUpdate( sellerId, { ...sellerInfo, businessType: sellerInfo.business_type, isRegisterCompleted: true }, { new: true, session }, ); const existShopName = await this.shopRepo.model.find({ shopName: completeRealDto.companyName }); if (existShopName.length) throw new BadRequestError(ShopMessage.ShopNameExist); shop.shopName = completeRealDto.companyName; await shop.save(); await this.checkRealSellerDuplicate(sellerId, nationalCode, cardNumber, shebaNumber); const realSeller = await this.realSellerRepo.model.create([{ seller: sellerId, nationalCode, cardNumber, shebaNumber }], { session }); await this.sellerStatusRepo.model.create([{ seller: sellerId }], { session }); await session.commitTransaction(); return { message: CommonMessage.Updated, seller, realSeller: realSeller[0], }; } catch (error) { await session.abortTransaction(); throw error; } finally { await session.endSession(); } } //####################### async completeRegistrationLegal(sellerId: string, completeLegalDto: CompleteRespirationLegalDTO) { const session = await startSession(); session.startTransaction(); try { const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); const { IBan, companyName, companyType, companyNationalId, companyEconomicNumber, ...sellerInfo } = completeLegalDto; const existEmail = await this.sellerRepo.model.exists({ _id: { $ne: sellerId }, email: sellerInfo.email }); if (existEmail) throw new BadRequestError(AuthMessage.EmailExists); const seller = await this.sellerRepo.model.findByIdAndUpdate( sellerId, { ...sellerInfo, businessType: sellerInfo.business_type, isRegisterCompleted: true }, { new: true, session }, ); const existShopName = await this.shopRepo.model.find({ shopName: completeLegalDto.companyName }); if (existShopName.length) throw new BadRequestError(ShopMessage.ShopNameExist); shop.shopName = completeLegalDto.companyName; await shop.save(); await this.checkCompanyDuplicate(companyName, companyNationalId, companyEconomicNumber, IBan); const legalSeller = await this.legalSellerRepo.model.create( [{ seller: sellerId, IBan, companyName, companyType, companyNationalId, companyEconomicNumber }], { session }, ); await this.sellerStatusRepo.model.create([{ seller: sellerId }], { session }); await session.commitTransaction(); return { message: CommonMessage.Updated, seller, legal: legalSeller[0], }; } catch (error) { await session.abortTransaction(); throw error; } finally { await session.endSession(); } } //####################### async getSellerProfileInfo(sellerId: string) { const seller = await this.sellerRepo.model.findById(sellerId); if (!seller) throw new BadRequestError(SellerMessage.SellerNotFound); if (seller.businessType === BusinessTypeEnum.REAL) { const realSeller = await this.realSellerRepo.model.findOne({ seller: sellerId }); return { baseSeller: seller, realSeller, }; } const legalSeller = await this.legalSellerRepo.model.findOne({ seller: sellerId }); return { baseSeller: seller, legalSeller, }; } //####################### async requestWholesale(sellerId: string) { const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); const existRequest = await this.wholesaleRequestRepo.model.exists({ seller: sellerId, shop: shop._id }); if (existRequest) throw new BadRequestError(SellerMessage.WholesaleRequestPending); const wholesaleRequest = await this.wholesaleRequestRepo.model.create({ seller: sellerId, shop: shop._id }); return { message: SellerMessage.WholesaleRequestCreated, request: wholesaleRequest, }; } //####################### async getSellerStatus(sellerId: string) { let isSellerActive = false; const status = await this.sellerStatusRepo.model.findOne({ seller: sellerId }).lean(); // if (status?.banned) throw new BadRequestError("اکانت شما از دسترس خارج شده است. با پشتیبانی تماس بگیرید.") if (status?.contract && status?.register && status?.document && status?.learning) { isSellerActive = true; } // if (!status) throw new BadRequestError(SellerMessage.StatusNotFound); return { status, isSellerActive, }; } //####################### async getAllSellerForReport() { return await this.sellerRepo.getAllSellerForReport(); } //####################### async getWholesaleRequestCount() { return await this.wholesaleRequestRepo.getWholesaleRequestCount(); } /** helper methods */ private async getProductCounts(shopId: string) { return this.productRepo.model.countDocuments({ shop: shopId }); } private async logistic(shopId: string) { const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); const shopCategories = await this.productRepo.model.distinct("category", { shop: shopId }); const rivalProductsCount = await this.productRepo.model.countDocuments({ shop: { $ne: shopId }, category: { $in: shopCategories }, }); const returnedLastWeekCount = await this.orderItemRepo.model.aggregate([ { $match: { shop: shopId, "shipmentItems.returned_quantity": { $gt: 0 }, createdAt: { $gte: oneWeekAgo }, }, }, { $group: { _id: null, totalReturns: { $sum: "$shipmentItems.returned_quantity" } }, }, ]); const returnedLastWeek = returnedLastWeekCount[0]?.totalReturns || 0; return { availableProducts: await this.productVariantRepo.model.countDocuments({ stock: { $gte: 5 }, shop: shopId }), outOfStockProducts: await this.productVariantRepo.model.countDocuments({ stock: 0, shop: shopId }), restockingProducts: await this.productVariantRepo.model.countDocuments({ stock: { $lte: 5, $gt: 0 }, shop: shopId }), returnedLastWeek, rivalProducts: rivalProductsCount, }; } private async orderStats(shopId: string) { const orderCount = await this.orderItemRepo.model.countDocuments({ shop: shopId }); const completedOrdersCount = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.Delivered, }); const startOfWeek = new Date(); startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay()); const ordersThisWeek = await this.orderItemRepo.model.countDocuments({ shop: shopId, createdAt: { $gte: startOfWeek }, }); const startOfToday = new Date(); startOfToday.setHours(0, 0, 0, 0); const ordersToday = await this.orderItemRepo.model.countDocuments({ shop: shopId, createdAt: { $gte: startOfToday }, }); const unapprovedOrders = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.Processing, }); const shippedOrders = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.Shipped, }); const delayedOrders = await this.orderItemRepo.model.countDocuments({ shop: shopId, postingDate: { $lt: new Date() }, status: { $ne: OrderItemsStatus.Delivered }, }); return { orderCount, completedOrdersCount, ordersThisWeek, ordersToday, unapprovedOrders, shippedOrders, delayedOrders, }; } private async salesStats(shopId: string) { const startOfWeek = new Date(); startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay()); const startOfLastWeek = new Date(startOfWeek); startOfLastWeek.setDate(startOfWeek.getDate() - 7); const startOfMonth = new Date(); startOfMonth.setDate(1); const totalSale = await this.orderItemRepo.model.aggregate([ { $match: { shop: shopId, status: OrderItemsStatus.Delivered } }, { $group: { _id: null, total: { $sum: "$totalSellingPrice" } } }, ]); const totalSaleAmount = totalSale[0]?.total || 0; const weeklySale = await this.orderItemRepo.model.aggregate([ { $match: { shop: shopId, status: OrderItemsStatus.Delivered, createdAt: { $gte: startOfWeek }, }, }, { $group: { _id: null, total: { $sum: "$totalSellingPrice" } } }, ]); const weeklySaleAmount = weeklySale[0]?.total || 0; const previousWeekSale = await this.orderItemRepo.model.aggregate([ { $match: { shop: shopId, status: OrderItemsStatus.Delivered, createdAt: { $gte: startOfLastWeek, $lt: startOfWeek }, }, }, { $group: { _id: null, total: { $sum: "$totalSellingPrice" } } }, ]); const previousWeekSaleAmount = previousWeekSale[0]?.total || 0; const monthlySale = await this.orderItemRepo.model.aggregate([ { $match: { shop: shopId, status: OrderItemsStatus.Delivered, createdAt: { $gte: startOfMonth }, }, }, { $group: { _id: null, total: { $sum: "$totalSellingPrice" } } }, ]); const monthlySaleAmount = monthlySale[0]?.total || 0; const salesCountWeekly = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.Delivered, createdAt: { $gte: startOfWeek }, }); const salesCountPreviousWeek = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.Delivered, createdAt: { $gte: startOfLastWeek, $lt: startOfWeek }, }); const salesCountPreviousMonth = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.Delivered, createdAt: { $gte: new Date(new Date().setDate(0)), $lt: startOfMonth, }, }); return { totalSale: totalSaleAmount, weeklySale: weeklySaleAmount, previousWeekSale: previousWeekSaleAmount, monthlySale: monthlySaleAmount, salesCountWeekly, salesCountPreviousWeek, salesCountPreviousMonth, }; } private async shopRating(shopId: string) { const shop = await this.shopRepo.model.findById(shopId); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); const oneMonthAgo = new Date(); oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); const ratingData = await this.commentRepo.model.aggregate([ { $match: { product: { $in: await this.productRepo.model.distinct("_id", { shop: shopId }) }, rate: { $exists: true }, }, }, { $group: { _id: null, totalRate: { $sum: "$rate" }, totalCount: { $sum: 1 }, }, }, ]); const totalRate = ratingData[0]?.totalRate || 0; const totalCount = ratingData[0]?.totalCount || 0; const onTimeShipments = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.Delivered, postingDate: { $exists: true, $ne: null }, createdAt: { $gte: oneMonthAgo }, }); const returnsCount = await this.orderItemRepo.model.aggregate([ { $match: { shop: shopId, "shipmentItems.returned_quantity": { $gt: 0 }, createdAt: { $gte: oneMonthAgo }, }, }, { $group: { _id: null, totalReturns: { $sum: "$shipmentItems.returned_quantity" } }, }, ]); const totalReturns = returnsCount[0]?.totalReturns || 0; const cancellationsCount = await this.orderItemRepo.model.countDocuments({ shop: shopId, status: OrderItemsStatus.cancelled_shop, createdAt: { $gte: oneMonthAgo }, }); shop!.rate.cancel = cancellationsCount; shop!.rate.onTimeShip = onTimeShipments; shop!.rate.returns = totalReturns; shop!.rate.totalCount = totalCount; shop!.rate.totalRate = totalRate; await shop!.save(); return { totalRate, totalCount, onTimeShip: onTimeShipments, returns: totalReturns, cancel: cancellationsCount, }; } private async checkCompanyDuplicate( sellerId: string, companyName?: string, companyNationalId?: string, companyEconomicNumber?: string, IBan?: string, update: boolean = false, ) { const query: FilterQuery = { $or: [{ companyName }, { companyNationalId }, { companyEconomicNumber }, { IBan }], }; if (update) query.seller = { $ne: sellerId }; const existCompany = await this.legalSellerRepo.model.findOne(query); if (existCompany) throw new BadRequestError(SellerMessage.CompanyDuplicate); return true; } private async checkRealSellerDuplicate( sellerId: string, nationalCode?: string, cardNumber?: string, shebaNumber?: string, update: boolean = false, ) { const query: FilterQuery = { $or: [{ nationalCode }, { cardNumber }, { shebaNumber }], }; if (update) query.seller = { $ne: sellerId }; const existRealSeller = await this.realSellerRepo.model.findOne(query); if (existRealSeller) throw new BadRequestError(SellerMessage.RealSellerDuplicate); return true; } private renderContractTemplate(data: ContractType): string { return contractTemplate(data); } } export { SellerService };