Files
shop-api/src/modules/seller/seller.service.ts
T
2025-09-21 16:16:38 +03:30

960 lines
34 KiB
TypeScript

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<ILegalSeller> = {
$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<IRealSeller> = {
$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 };