first
This commit is contained in:
@@ -0,0 +1,960 @@
|
||||
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);
|
||||
console.log({ document });
|
||||
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 };
|
||||
Reference in New Issue
Block a user