79f597cc90
category variant
960 lines
34 KiB
TypeScript
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 };
|