This commit is contained in:
mahyargdz
2025-09-14 15:15:03 +03:30
commit be82059172
534 changed files with 51310 additions and 0 deletions
@@ -0,0 +1,94 @@
import { Expose } from "class-transformer";
import { IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsString, Length, MinLength } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidPersianDate } from "../../../common/decorator/validation.decorator";
import { AuthMessage, CommonMessage, SellerMessage } from "../../../common/enums/message.enum";
import { BusinessTypeEnum, CompanyTypeEnum } from "../enum/seller.enum";
export class CompleteRegistrationDTO {
@Expose()
@IsString()
@MinLength(6)
@IsNotEmpty({ message: CommonMessage.NameNotEmpty })
@ApiProperty({ type: "string", description: "fullname of seller", example: "مهیار گودرزی" })
fullName: string;
@Expose()
@IsString()
@IsNotEmpty()
@IsValidPersianDate()
@ApiProperty({ type: "string", description: "data of birth", example: "1402/02/25" })
dateOfBirth: string;
@Expose()
@IsNotEmpty({ message: AuthMessage.EmailNotEmpty })
@IsEmail(undefined, { message: AuthMessage.IncorrectEmail })
@ApiProperty({ type: "string", description: "email of seller", example: "shop@email.com" })
email: string;
@Expose()
@IsNotEmpty()
@IsEnum(BusinessTypeEnum)
@ApiProperty({ type: "string", description: "the business type of seller ", example: BusinessTypeEnum.LEGAL })
business_type: BusinessTypeEnum;
@Expose()
@IsNotEmpty()
@IsString()
@MinLength(3)
@ApiProperty({ type: "string", description: "the name of company", example: "لورم ایپسوم" })
companyName: string;
}
export class CompleteRespirationRealDTO extends CompleteRegistrationDTO {
@Expose()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: CommonMessage.NationalCodeIncorrect })
@ApiProperty({ type: "string", description: "iranian format (10 char)", example: "4569852169" })
@Length(10, 10, { message: CommonMessage.NationalCodeIncorrect })
nationalCode: string;
@Expose()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: SellerMessage.CardNumberIncorrect })
@Length(16, 16, { message: SellerMessage.CardNumberIncorrect })
@ApiProperty({ type: "string", description: "the card number of seller", example: "5896541236547896" })
cardNumber: string;
@Expose()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: CommonMessage.ShebaIncorrect })
@Length(24, 24, { message: CommonMessage.ShebaIncorrect })
@ApiProperty({ type: "string", description: "the sheba number without IR (24 char)", example: "456985478562145879652145" })
shebaNumber: string;
}
export class CompleteRespirationLegalDTO extends CompleteRegistrationDTO {
@Expose()
@IsNotEmpty()
@IsEnum(CompanyTypeEnum)
@ApiProperty({ type: "string", description: "the company type", example: CompanyTypeEnum.COOPERATIVE })
companyType: CompanyTypeEnum;
@Expose()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: SellerMessage.ShopIdIncorrect })
@ApiProperty({ type: "string", description: "the shop national id code (11 char)==> شناسه شرکت", example: "19354687915" })
@Length(11, 11, { message: SellerMessage.ShopIdIncorrect })
companyNationalId: string;
@Expose()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: SellerMessage.ShopIdIncorrect })
@ApiProperty({ type: "string", description: "the shop national id code (11 char)==> شناسه شرکت", example: "19354687915" })
@Length(11, 11, { message: SellerMessage.ShopIdIncorrect })
companyEconomicNumber?: string;
@Expose()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: CommonMessage.ShebaIncorrect })
@Length(24, 24, { message: CommonMessage.ShebaIncorrect })
@ApiProperty({ type: "string", description: "the sheba number without IR (24 char)", example: "456985478562145879652145" })
IBan: string;
}
@@ -0,0 +1,26 @@
import { Expose } from "class-transformer";
import { IsBoolean, IsNotEmpty, IsString, MinLength } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
export class CreateDocumentTypeDTO {
@Expose()
@IsNotEmpty()
@IsString()
@MinLength(3)
@ApiProperty({ type: "string", description: "Document title", example: "عکس پشت کارت ملی" })
title: string;
@Expose()
@IsNotEmpty()
@IsString()
@MinLength(3)
@ApiProperty({ type: "string", description: "Document description", example: "لطفا به صورت واضح آپلود کنید" })
description: string;
@Expose()
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ type: "boolean", description: "specify if Document is required", example: true })
required: boolean;
}
@@ -0,0 +1,27 @@
import { Expose } from "class-transformer";
import { IsOptional } from "class-validator";
import { IsValidPersianDate } from "../../../common/decorator/validation.decorator";
import { PaginationDTO } from "../../../common/dto/pagination.dto";
import { ProductStatus } from "../../../common/enums/product.enum";
// import { TimeService } from "../../../utils/time.service";
export class SellerProductQueryDTO extends PaginationDTO {
@Expose()
@IsOptional()
categoryId: string;
@Expose()
@IsOptional()
// @IsEnum(ProductStatus)
status: ProductStatus;
@Expose()
@IsOptional()
q: string;
@Expose()
@IsOptional()
@IsValidPersianDate()
since: string;
}
@@ -0,0 +1,89 @@
import { Expose } from "class-transformer";
import { IsEnum, IsNotEmpty, IsNumberString, IsOptional, IsString, Length, MinLength } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidPersianDate } from "../../../common/decorator/validation.decorator";
import { CommonMessage, SellerMessage } from "../../../common/enums/message.enum";
import { CompanyTypeEnum } from "../enum/seller.enum";
export class SellerUpdateDto {
@Expose()
@IsOptional()
@IsNotEmpty({ message: CommonMessage.NameNotEmpty })
@ApiProperty({ type: "string", description: "fullname of seller", example: "مهیار گودرزی" })
fullName?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsValidPersianDate()
@ApiProperty({ type: "string", description: "data of birth", example: "1402/02/25" })
dateOfBirth?: string;
}
export class RealSellerUpdateDto extends SellerUpdateDto {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: CommonMessage.NationalCodeIncorrect })
@ApiProperty({ type: "string", description: "iranian format (10 char)", example: "4569852169" })
@Length(10, 10, { message: CommonMessage.NationalCodeIncorrect })
nationalCode?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: SellerMessage.CardNumberIncorrect })
@Length(16, 16, { message: SellerMessage.CardNumberIncorrect })
@ApiProperty({ type: "string", description: "the card number of seller", example: "5896541236547896" })
cardNumber?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: CommonMessage.ShebaIncorrect })
@Length(24, 24, { message: CommonMessage.ShebaIncorrect })
@ApiProperty({ type: "string", description: "the sheba number without IR (24 char)", example: "456985478562145879652145" })
shebaNumber?: string;
}
export class LegalSellerUpdateDto extends SellerUpdateDto {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@MinLength(3)
@ApiProperty({ type: "string", description: "the name of company", example: "لورم ایپسوم" })
companyName?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsEnum(CompanyTypeEnum)
@ApiProperty({ type: "string", description: "the company type", example: CompanyTypeEnum.COOPERATIVE })
companyType?: CompanyTypeEnum;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: SellerMessage.ShopIdIncorrect })
@ApiProperty({ type: "string", description: "the shop national id code (11 char)==> شناسه شرکت", example: "19354687915" })
@Length(11, 11, { message: SellerMessage.ShopIdIncorrect })
companyNationalId?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: SellerMessage.ShopIdIncorrect })
@ApiProperty({ type: "string", description: "the shop national id code (11 char)==> شناسه شرکت", example: "19354687915" })
@Length(11, 11, { message: SellerMessage.ShopIdIncorrect })
companyEconomicNumber?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: CommonMessage.ShebaIncorrect })
@Length(24, 24, { message: CommonMessage.ShebaIncorrect })
@ApiProperty({ type: "string", description: "the sheba number without IR (24 char)", example: "456985478562145879652145" })
IBan?: string;
}
@@ -0,0 +1,28 @@
import { Expose } from "class-transformer";
import { IsNotEmpty, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { DocumentTypeModel } from "../models/documentType.mode";
// import { SellerDocumentModel } from "../models/sellerDocument.model";
export class UploadSellerDocumentDTO {
// @Expose()
// @IsOptional()
// @IsNotEmpty()
// @IsValidId(SellerDocumentModel)
// @ApiProperty({ type: "string", description: "id of the seller docs (optional)", example: "60f4b3b3b3f3b4f4b3b3b3f3" })
// docsId?: string;
@Expose()
@IsNotEmpty()
@IsValidId(DocumentTypeModel)
@ApiProperty({ type: "string", description: "id of the document type", example: "60f4b3b3b3f3b4f4b3b3b3f3" })
documentTypeId: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "address of the uploaded docs", example: "https://example.com/default.png" })
docsUrl: string;
}
@@ -0,0 +1,15 @@
import { Expose } from "class-transformer";
import { IsInt, IsNotEmpty } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { ShipmentModel } from "../../shipment/models/shipment.model";
export class UpdateShopShipmentDTO {
@Expose()
@IsNotEmpty()
@IsInt()
@IsValidId(ShipmentModel)
@ApiProperty({ type: "number", description: "the id of shipper", example: 123 })
shipmentId: number;
}
+22
View File
@@ -0,0 +1,22 @@
export enum BusinessTypeEnum {
REAL = "real",
LEGAL = "legal",
}
export enum CompanyTypeEnum {
PUBLIC = "public",
PRIVATE = "private",
LIMITED_LIABILITY = "limited_liability",
COOPERATIVE = "cooperative",
PARTNERSHIP = "partnership",
INSTITUTE = "institute",
OTHER = "other",
}
// سهامی عام
// سهامی خاص
// مسئولیت محدود
// تعاونی
// تضامنی
// موسسه
// سایر
@@ -0,0 +1,5 @@
export interface IDocumentType {
title: string;
description: string;
required: boolean;
}
@@ -0,0 +1,12 @@
import { Types } from "mongoose";
import { CompanyTypeEnum } from "../../enum/seller.enum";
export class ILegalSeller {
seller: Types.ObjectId;
companyName: string;
companyType: CompanyTypeEnum;
companyNationalId: string;
companyEconomicNumber: string;
IBan: string;
}
@@ -0,0 +1,8 @@
import { Types } from "mongoose";
export interface IRealSeller {
seller: Types.ObjectId;
nationalCode: string;
cardNumber: string;
shebaNumber: string;
}
@@ -0,0 +1,9 @@
import { Types } from "mongoose";
export class ISellerContract {
content: string;
seller: Types.ObjectId;
contractNumber: string;
signed: boolean;
singedAt: string;
}
@@ -0,0 +1,11 @@
import { Types } from "mongoose";
import { StatusEnum } from "../../../../common/enums/status.enum";
export class ISellerDocument {
seller: Types.ObjectId;
type: Types.ObjectId;
status: StatusEnum;
docsUrl: string;
comment: string;
}
@@ -0,0 +1,5 @@
import { Types } from "mongoose";
export class ISellerLearning {
seller: Types.ObjectId;
}
@@ -0,0 +1,9 @@
import { Types } from "mongoose";
export interface ISellerStatus {
seller: Types.ObjectId;
document: boolean;
contract: boolean;
learning: boolean;
register: boolean;
}
@@ -0,0 +1,16 @@
import { Types } from "mongoose";
import { StatusEnum } from "../../../../common/enums/status.enum";
import { BusinessTypeEnum } from "../../enum/seller.enum";
export interface ISeller {
_id: Types.ObjectId;
fullName: string;
dateOfBirth: string;
phoneNumber: string;
email: string;
accountStatus: StatusEnum;
isRegisterCompleted: boolean;
businessType: BusinessTypeEnum;
isWholesaler: boolean;
}
@@ -0,0 +1,9 @@
import { Types } from "mongoose";
import { StatusEnum } from "../../../../common/enums/status.enum";
export interface IWholesaleRequest {
seller: Types.ObjectId;
shop: Types.ObjectId;
status: StatusEnum;
}
@@ -0,0 +1,16 @@
import { Schema, model } from "mongoose";
import { IDocumentType } from "./Abstraction/IDocumentType";
const DocumentTypeSchema = new Schema<IDocumentType>(
{
title: { type: String, required: true },
description: { type: String, required: true },
required: { type: Boolean, required: true },
},
{ timestamps: true, toJSON: { versionKey: false }, id: false },
);
const DocumentTypeModel = model<IDocumentType>("DocumentType", DocumentTypeSchema);
export { DocumentTypeModel };
@@ -0,0 +1,22 @@
import { Schema, model } from "mongoose";
import { ILegalSeller } from "./Abstraction/ILegal-seller";
const LegalSellerSchema = new Schema<ILegalSeller>(
{
seller: { type: Schema.Types.ObjectId, ref: "Seller", required: true },
companyName: { type: String, required: true, unique: true },
companyType: { type: String, required: true },
companyNationalId: { type: String, required: true, unique: true },
companyEconomicNumber: { type: String, required: true, unique: true },
IBan: { type: String, required: true, unique: true },
},
{
timestamps: true,
toJSON: { virtuals: true, versionKey: false },
id: false,
},
);
const LegalSellerModel = model<ILegalSeller>("LegalSeller", LegalSellerSchema);
export { LegalSellerModel };
@@ -0,0 +1,17 @@
import { Schema, model } from "mongoose";
import { IRealSeller } from "./Abstraction/IReal-seller";
const RealSellerSchema = new Schema<IRealSeller>(
{
seller: { type: Schema.Types.ObjectId, ref: "Seller", required: true },
nationalCode: { type: String, required: true, unique: true },
cardNumber: { type: String, required: true, unique: true },
shebaNumber: { type: String, required: true, unique: true },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
const RealSellerModel = model<IRealSeller>("RealSeller", RealSellerSchema);
export { RealSellerModel };
+25
View File
@@ -0,0 +1,25 @@
import { Schema, model } from "mongoose";
import { ISeller } from "./Abstraction/ISeller";
// import { SellerType } from "../../../common/enums/seller.enum";
import { StatusEnum } from "../../../common/enums/status.enum";
import { BusinessTypeEnum } from "../enum/seller.enum";
const sellerSchema = new Schema<ISeller>(
{
fullName: { type: String, required: true, trim: true },
dateOfBirth: { type: String, default: null },
phoneNumber: { type: String, unique: true, sparse: true },
email: { type: String, unique: true, sparse: true, trim: true },
accountStatus: { type: String, enum: StatusEnum, default: StatusEnum.Pending },
// type: { type: String, enum: SellerType, default: SellerType.RETAILER },
isRegisterCompleted: { type: Boolean, default: false },
businessType: { type: String, enum: BusinessTypeEnum, default: null },
isWholesaler: { type: Boolean, default: false },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
const SellerModel = model<ISeller>("Seller", sellerSchema);
export { SellerModel };
@@ -0,0 +1,17 @@
import { Schema, model } from "mongoose";
import { ISellerContract } from "./Abstraction/ISeller-contract";
const SellerContractSchema = new Schema<ISellerContract>(
{
content: { type: String, required: true },
seller: { type: Schema.Types.ObjectId, ref: "Seller", unique: true, required: true },
contractNumber: { type: String, required: true, unique: true },
signed: { type: Boolean, default: false },
singedAt: { type: String, default: null },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
const SellerContractModel = model<ISellerContract>("SellerContract", SellerContractSchema);
export { SellerContractModel };
@@ -0,0 +1,18 @@
import { Schema, model } from "mongoose";
import { ISellerDocument } from "./Abstraction/ISeller-document";
import { StatusEnum } from "../../../common/enums/status.enum";
const SellerDocumentSchema = new Schema<ISellerDocument>(
{
seller: { type: Schema.Types.ObjectId, ref: "Seller", required: true },
type: { type: Schema.Types.ObjectId, ref: "DocumentType", required: true },
status: { type: String, enum: StatusEnum, default: StatusEnum.Pending },
docsUrl: { type: String, required: true },
comment: { type: String, default: null },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
const SellerDocumentModel = model<ISellerDocument>("SellerDocument", SellerDocumentSchema);
export { SellerDocumentModel };
@@ -0,0 +1,17 @@
import { Schema, model } from "mongoose";
import { ISellerStatus } from "./Abstraction/ISeller-status";
const SellerStatusSchema = new Schema<ISellerStatus>(
{
seller: { type: Schema.Types.ObjectId, ref: "Seller", required: true },
contract: { type: Boolean, default: false },
document: { type: Boolean, default: false },
learning: { type: Boolean, default: false },
register: { type: Boolean, default: false },
},
{ timestamps: true, toJSON: { versionKey: false }, id: false },
);
const SellerStatusModel = model<ISellerStatus>("SellerStatus", SellerStatusSchema);
export { SellerStatusModel };
@@ -0,0 +1,17 @@
import { Schema, model } from "mongoose";
import { IWholesaleRequest } from "./Abstraction/IWholesaleRequest";
import { StatusEnum } from "../../../common/enums/status.enum";
const WholesaleRequestSchema = new Schema<IWholesaleRequest>(
{
seller: { type: Schema.Types.ObjectId, ref: "Seller", required: true },
shop: { type: Schema.Types.ObjectId, ref: "Shop", required: true },
status: { type: String, enum: StatusEnum, default: StatusEnum.Pending },
},
{ timestamps: true, toJSON: { versionKey: false }, id: false },
);
WholesaleRequestSchema.index({ seller: 1, shop: 1 }, { unique: true });
const WholesaleRequestModel = model<IWholesaleRequest>("WholesaleRequest", WholesaleRequestSchema);
export { WholesaleRequestModel };
@@ -0,0 +1,13 @@
import { BaseRepository } from "../../../common/base/repository";
import { IDocumentType } from "../models/Abstraction/IDocumentType";
import { DocumentTypeModel } from "../models/documentType.mode";
export class DocumentTypeRepo extends BaseRepository<IDocumentType> {
constructor() {
super(DocumentTypeModel);
}
}
export function createDocumentTypeRepo(): DocumentTypeRepo {
return new DocumentTypeRepo();
}
@@ -0,0 +1,12 @@
import { BaseRepository } from "../../../common/base/repository";
import { ILegalSeller } from "../models/Abstraction/ILegal-seller";
import { LegalSellerModel } from "../models/legalSeller.model";
export class LegalSellerRepo extends BaseRepository<ILegalSeller> {
constructor() {
super(LegalSellerModel);
}
}
export function createLegalSellerRepo(): LegalSellerRepo {
return new LegalSellerRepo();
}
@@ -0,0 +1,13 @@
import { BaseRepository } from "../../../common/base/repository";
import { IRealSeller } from "../models/Abstraction/IReal-seller";
import { RealSellerModel } from "../models/realSeller.model";
export class RealSellerRepo extends BaseRepository<IRealSeller> {
constructor() {
super(RealSellerModel);
}
}
export function createRealSellerRepo(): RealSellerRepo {
return new RealSellerRepo();
}
@@ -0,0 +1,13 @@
import { BaseRepository } from "../../../common/base/repository";
import { ISellerContract } from "../models/Abstraction/ISeller-contract";
import { SellerContractModel } from "../models/sellerContract.model";
export class SellerContractRepo extends BaseRepository<ISellerContract> {
constructor() {
super(SellerContractModel);
}
}
export function createSellerContractRepo(): SellerContractRepo {
return new SellerContractRepo();
}
@@ -0,0 +1,13 @@
import { BaseRepository } from "../../../common/base/repository";
import { ISellerDocument } from "../models/Abstraction/ISeller-document";
import { SellerDocumentModel } from "../models/sellerDocument.model";
export class SellerDocumentRepo extends BaseRepository<ISellerDocument> {
constructor() {
super(SellerDocumentModel);
}
}
export function createSellerDocumentRepo(): SellerDocumentRepo {
return new SellerDocumentRepo();
}
@@ -0,0 +1,13 @@
import { BaseRepository } from "../../../common/base/repository";
import { ISellerStatus } from "../models/Abstraction/ISeller-status";
import { SellerStatusModel } from "../models/sellerStatus.model";
export class SellerStatusRepo extends BaseRepository<ISellerStatus> {
constructor() {
super(SellerStatusModel);
}
}
export function createSellerStatusRepo(): SellerStatusRepo {
return new SellerStatusRepo();
}
@@ -0,0 +1,17 @@
import { BaseRepository } from "../../../common/base/repository";
import { IWholesaleRequest } from "../models/Abstraction/IWholesaleRequest";
import { WholesaleRequestModel } from "../models/wholesaleRequest.model";
export class WholesaleRequestRepo extends BaseRepository<IWholesaleRequest> {
constructor() {
super(WholesaleRequestModel);
}
async getWholesaleRequestCount() {
return this.model.countDocuments();
}
}
export function createWholesaleRequestRepo(): WholesaleRequestRepo {
return new WholesaleRequestRepo();
}
+439
View File
@@ -0,0 +1,439 @@
import { Request } from "express";
// eslint-disable-next-line import/no-named-as-default
import rateLimit from "express-rate-limit";
import { inject } from "inversify";
import { controller, httpGet, httpPatch, httpPost, queryParam, request, requestBody, requestParam } from "inversify-express-utils";
import { LegalSellerUpdateDto, RealSellerUpdateDto } from "./DTO/sellerUpdate.dto";
import { HttpStatus } from "../../common";
import { CompleteRespirationLegalDTO, CompleteRespirationRealDTO } from "./DTO/complete-register.dto";
import { SellerProductQueryDTO } from "./DTO/sellerProductQuery.dto";
import { UploadSellerDocumentDTO } from "./DTO/update-sellerDocument.dto";
import { ISeller } from "./models/Abstraction/ISeller";
import { SellerService } from "./seller.service";
import { BaseController } from "../../common/base/controller";
import {
ApiAuth,
ApiBody,
ApiFile,
ApiModel,
ApiOperation,
ApiParam,
ApiQuery,
ApiResponse,
ApiTags,
} from "../../common/decorator/swggerDocs";
import { AuthMessage } from "../../common/enums/message.enum";
import { appConfig } from "../../core/config/app.config";
import { Guard } from "../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../IOC/ioc.types";
import { UploadService } from "../../utils/upload.service";
import { AuthService } from "../auth/auth.service";
import { AuthDTO } from "../auth/DTO/Auth.dto";
import { AuthCheckOtpDTO } from "../auth/DTO/AuthCheckOtp.dto";
import { TokenDto } from "../auth/DTO/Token.dto";
import { OrderService } from "../order/order.service";
import { ProductRequestService } from "../product/providers/product-request.service";
import { ProductService } from "../product/providers/product.service";
import { ShopUpdateDTO } from "../shop/DTO/shop-update.dto";
import { OwnerRef } from "../shop/models/Abstraction/IShop";
import { ShopService } from "../shop/shop.service";
import { UpdateShopShipmentDTO } from "./DTO/update-shop-shipment.dto";
import { BusinessTypeEnum } from "./enum/seller.enum";
@controller("/seller")
@ApiTags("Seller")
class SellerController extends BaseController {
@inject(IOCTYPES.SellerService) sellerService: SellerService;
@inject(IOCTYPES.AuthService) authService: AuthService;
@inject(IOCTYPES.ProductService) productService: ProductService;
@inject(IOCTYPES.OrderService) orderService: OrderService;
@inject(IOCTYPES.ShopService) shopService: ShopService;
@inject(IOCTYPES.ProductRequestService) productRequestService: ProductRequestService;
// @ApiOperation("get all sellers data")
// @httpGet("")
// public async getSellers() {
// const data = await this.sellerService.getSellers();
// return this.response({ data });
// }
@ApiOperation("get all sellers data")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/panel", Guard.authSeller())
public async sellerPanel(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.getSellerPanelInfo(seller._id.toString());
return this.response(data);
}
//get orders for seller panel
@ApiOperation("get all orders of current session seller")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("shipperId", "shipper id")
@ApiQuery("status", "status of payment ==> value = Completed | Cancelled | Pending ")
@ApiQuery(
"since",
"time of order created ==> value should be standard timestamp since that time user want like == > Date.now() - (7 * 24 * 60 * 60 * 1000) ==> this mean from a week ago till now ",
)
@ApiQuery("maxPrice", "search based on payment priceRange")
@ApiQuery("minPrice", "search based on payment priceRange")
@ApiQuery("orderIds", "Array of order IDs to fetch", false, "array")
@ApiAuth()
@httpGet("/panel/orders", Guard.authSeller())
public async getOrders(
@request() req: Request,
@queryParam("limit") limit: string,
@queryParam("page") page: string,
@queryParam("shipperId") shipperId: string,
@queryParam("status") status: string,
@queryParam("since") since: string,
@queryParam("maxPrice") maxPrice: string,
@queryParam("minPrice") minPrice: string,
@queryParam("orderIds") orderIds: string[],
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
shipperId,
status,
since: +since,
maxPrice: +maxPrice,
minPrice: +minPrice,
};
const seller = req.user as ISeller;
const { priceRange, ...data } = await this.orderService.getOrders(seller._id.toString(), queries, orderIds);
const { pager } = this.paginate(data.count);
return this.response({ pager, orders: data.orders, priceRange });
}
//###############################################################
//###############################################################
//get product for seller panel
@ApiOperation("get all products of current session seller")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("categoryId", "category id")
@ApiQuery("status", "status of product ==> value = Approved | Rejected | Pending ")
@ApiQuery("q", "q is the product variant id or special code of variant that seller specify")
@ApiQuery("since", "time of product created ==> value should be standard shamsi date since that time user want like == > 1400-01-01")
@ApiAuth()
@httpGet("/panel/products", Guard.authSeller(), ValidationMiddleware.validateQuery(SellerProductQueryDTO))
public async getSellerProduct(@request() req: Request, @queryParam() queryDto: SellerProductQueryDTO) {
const seller = req.user as ISeller;
const data = await this.productService.findSellerProduct(seller._id.toString(), queryDto);
const { pager } = this.paginate(data.count);
return this.response({ pager, products: data.products, brands: data.brands, categories: data.categories });
}
@ApiOperation("get seller statuses")
@ApiAuth()
@httpGet("/panel/status", Guard.authSeller())
public async getSellerStatus(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.getSellerStatus(seller._id.toString());
return this.response(data);
}
//###############################################################
//###############################################################
//get product of seller that is in the draft status
@ApiOperation("get product requests of seller")
@ApiResponse("successfully")
@ApiAuth()
@httpGet("/panel/products/request", Guard.authSeller())
public async getSellerProductRequests(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.productRequestService.getProductRequests(seller._id.toString());
return this.response(data);
}
@ApiOperation("get single product request details")
@ApiResponse("successfully")
@ApiParam("id", "the product id", true)
@ApiAuth()
@httpGet("/panel/products/request/:id", Guard.authSeller())
public async getSellerProductRequestDetails(@request() req: Request, @requestParam("id") requestId: string) {
const seller = req.user as ISeller;
const data = await this.productRequestService.getProductRequestDetails(seller._id.toString(), requestId);
return this.response(data);
}
@ApiOperation("get single product details")
@ApiResponse("successfully")
@ApiParam("id", "the product id", true)
@ApiAuth()
@httpGet("/panel/products/:id", Guard.authSeller())
public async getSingleProduct(@request() req: Request, @requestParam("id") productId: string) {
const seller = req.user as ISeller;
const data = await this.productService.getProductDetailsForSellerPanel(seller._id.toString(), +productId);
return this.response(data);
}
//#################################################################
//#################################################################
@ApiOperation("get sale-stats of current session seller")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiQuery("start", "the start date of stats range")
@ApiQuery("end", "the end data of stats range")
@ApiAuth()
@httpGet("/panel/sales-stats", Guard.authSeller())
public async getSellerSalesStats(@request() req: Request, @queryParam("start") start: string, @queryParam("end") end: string) {
const queries = {
start,
end,
};
const seller = req.user as ISeller;
const data = await this.orderService.getSellerSalesStats(seller._id.toString(), queries);
return this.response(data);
}
//###############################################################
//##############################################################
@ApiOperation("Authenticate to send OTP code")
@ApiResponse("Successful", 200, { message: AuthMessage.OtpSentToNo, phone: "09122569856" })
@ApiResponse("BadRequest", 400, { details: ["فرمت موبایل اشتباه است"] })
@ApiModel(AuthDTO)
@ApiBody("Authenticate with phone or email")
@httpPost("/authenticate", rateLimit(appConfig.rate), ValidationMiddleware.validateInput(AuthDTO))
async authSeller(@requestBody() authDto: AuthDTO) {
const data = await this.authService.authenticate(authDto, "seller");
return this.response({ data }, HttpStatus.Ok);
}
@ApiOperation("login seller -> check sent OTP and generate the token")
@ApiResponse("Successful login", 200)
@ApiModel(AuthCheckOtpDTO)
@ApiBody("login seller with otp code")
@httpPost("/login/otp", rateLimit(appConfig.rate), ValidationMiddleware.validateInput(AuthCheckOtpDTO))
async loginOtp(@requestBody() loginOtpDto: AuthCheckOtpDTO) {
const data = await this.authService.loginOtpS(loginOtpDto, "seller");
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("complete seller registration step 3")
@ApiResponse("Successful", 200)
@ApiModel(CompleteRespirationLegalDTO)
@ApiAuth()
@httpPost("/complete-register/legal", Guard.authSeller(), ValidationMiddleware.validateInput(CompleteRespirationLegalDTO))
async completeRegistrationLegal(@request() req: Request, @requestBody() completeDto: CompleteRespirationLegalDTO) {
const seller = req.user as ISeller;
const data = await this.sellerService.completeRegistrationLegal(seller._id.toString(), completeDto);
return this.response(data);
}
@ApiOperation("complete seller registration step 3")
@ApiResponse("Successful", 200)
@ApiModel(CompleteRespirationRealDTO)
@ApiAuth()
@httpPost("/complete-register/real", Guard.authSeller(), ValidationMiddleware.validateInput(CompleteRespirationRealDTO))
async completeRegistrationReal(@request() req: Request, @requestBody() completeDto: CompleteRespirationRealDTO) {
const seller = req.user as ISeller;
const data = await this.sellerService.completeRegistrationReal(seller._id.toString(), completeDto);
return this.response(data);
}
@ApiOperation("check the refresh token and generate access token base on that")
@ApiResponse("Successful", 200)
@ApiResponse("unauthorized", 401)
@ApiResponse("notFound", 404)
@ApiModel(TokenDto)
@httpPost("/token", rateLimit(appConfig.rate), ValidationMiddleware.validateInput(TokenDto))
public async refreshTokens(@requestBody() refreshToken: TokenDto) {
const data = await this.authService.refreshTokensS(refreshToken);
//return new tokens
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("logout the seller based on the sent authorization token")
@ApiResponse("Successful", 200, { message: AuthMessage.LoggedOut })
@ApiAuth()
@httpPost("/logout", rateLimit(appConfig.rate), Guard.authSeller())
public async logout(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.authService.logoutS(seller._id.toString());
return this.response({ data });
}
//##############################################################
@ApiOperation("activate the shop shipment method")
@ApiModel(UpdateShopShipmentDTO)
@ApiAuth()
@httpPatch("/shipment/activate", Guard.authSeller(), ValidationMiddleware.validateInput(UpdateShopShipmentDTO))
public async activateShopShipper(@request() req: Request, @requestBody() updateDto: UpdateShopShipmentDTO) {
const seller = req.user as ISeller;
const data = await this.sellerService.updateShopShipper(seller._id.toString(), updateDto, "activate");
return this.response(data);
}
@ApiOperation("deactivate the shop shipment method")
@ApiModel(UpdateShopShipmentDTO)
@ApiAuth()
@httpPatch("/shipment/deactivate", Guard.authSeller(), ValidationMiddleware.validateInput(UpdateShopShipmentDTO))
public async deactivateShopShipper(@request() req: Request, @requestBody() updateDto: UpdateShopShipmentDTO) {
const seller = req.user as ISeller;
const data = await this.sellerService.updateShopShipper(seller._id.toString(), updateDto, "deactivate");
return this.response(data);
}
@ApiOperation("change status of shop chat")
@ApiAuth()
@httpPatch("/chat/status", Guard.authSeller())
public async changeShopChatStatus(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.changeShopChatStatus(seller._id.toString());
return this.response(data);
}
@ApiOperation("get all items count of the seller like notification count or ...")
@ApiAuth()
@httpGet("/items/count", Guard.authSeller())
public async getItemsCount(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.getItemsCount(seller._id.toString());
return this.response(data);
}
//##############################################################
@ApiOperation("get current logged in seller info")
@ApiResponse("successful", 200)
@ApiAuth()
@httpGet("/profile/self", Guard.authSeller())
public async getProfileInfo(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.getSellerProfileInfo(seller._id.toString());
return this.response(data);
}
@ApiOperation("get current logged in seller shop info")
@ApiResponse("successful", 200)
@ApiAuth()
@httpGet("/profile/shop", Guard.authSeller())
public async getShopInfo(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.shopService.getShopInfo(seller._id.toString(), OwnerRef.SELLER);
return this.response(data);
}
@ApiOperation("update fields of the real seller info")
@ApiModel(RealSellerUpdateDto)
@ApiAuth()
@httpPatch("/profile/self/real", Guard.authSeller(), ValidationMiddleware.validateInput(RealSellerUpdateDto))
public async updateRealSellerProfileInfo(@requestBody() updateDto: RealSellerUpdateDto, @request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.updateSellerInfo(BusinessTypeEnum.REAL, updateDto, seller._id.toString());
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update fields of the legal seller info")
@ApiModel(LegalSellerUpdateDto)
@ApiAuth()
@httpPatch("/profile/self/legal", Guard.authSeller(), ValidationMiddleware.validateInput(LegalSellerUpdateDto))
public async updateLegalSellerProfileInfo(@requestBody() updateDto: LegalSellerUpdateDto, @request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.updateSellerInfo(BusinessTypeEnum.LEGAL, updateDto, seller._id.toString());
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update fields of the seller shop")
@ApiModel(ShopUpdateDTO)
@ApiAuth()
@httpPatch("/profile/shop", Guard.authSeller(), ValidationMiddleware.validateInput(ShopUpdateDTO))
public async updateSellerShopInfo(@requestBody() updateDto: ShopUpdateDTO, @request() req: Request) {
const seller = req.user as ISeller;
const data = await this.shopService.updateSellerShopInfo(seller._id.toString(), updateDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("get contract details")
@ApiAuth()
@httpGet("/contract", Guard.authSeller())
public async getContract(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.getContract(seller);
return this.response(data);
}
@ApiOperation("sign contract")
@ApiParam("id", "the id of contract", true)
@ApiAuth()
@httpPost("/contract/sign/:id", Guard.authSeller())
public async signContract(@request() req: Request, @requestParam("id") contractId: string) {
const seller = req.user as ISeller;
const data = await this.sellerService.signContract(seller._id.toString(), contractId);
return this.response(data);
}
@ApiOperation("upload seller document")
@ApiModel(UploadSellerDocumentDTO)
@ApiAuth()
@httpPatch("/document", Guard.authSeller(), ValidationMiddleware.validateInput(UploadSellerDocumentDTO))
public async sellerDocument(@requestBody() uploadDto: UploadSellerDocumentDTO, @request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.uploadSellerDocument(seller._id.toString(), uploadDto);
return this.response(data);
}
@ApiOperation("get seller documents")
@ApiAuth()
@httpGet("/document", Guard.authSeller())
public async getSellerDocument(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.getSellerDocument(seller._id.toString());
return this.response(data);
}
@ApiOperation("get single seller document")
@ApiParam("id", "the document id", true)
@ApiAuth()
@httpGet("/document/:id", Guard.authSeller())
public async getSingleSellerDocument(@request() req: Request, @requestParam("id") documentId: string) {
const seller = req.user as ISeller;
const data = await this.sellerService.getSingleSellerDocument(seller._id.toString(), documentId);
return this.response(data);
}
@ApiOperation("get document types")
@httpGet("/document-types")
public async getDocumentTypes() {
const data = await this.sellerService.getDocumentTypes();
return this.response(data);
}
@ApiOperation("request to be a wholesale seller")
@ApiAuth()
@httpPost("/wholesale/request", Guard.authSeller())
public async requestWholesale(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.sellerService.requestWholesale(seller._id.toString());
return this.response(data);
}
//uploader
@ApiOperation("Upload a seller info image")
@ApiResponse("File uploaded successfully", HttpStatus.Accepted)
@ApiFile("image")
@ApiAuth()
@httpPost("/image/upload", Guard.authSeller(), UploadService.single("image", "seller-profile"))
public async uploadImage(@request() req: Request) {
// eslint-disable-next-line no-undef
const file = req.file as Express.MulterS3.File;
const data = {
message: "file uploaded!",
url: {
size: file?.size,
url: file?.location,
type: file?.mimetype,
},
};
return this.response({ data }, HttpStatus.Accepted);
}
}
export { SellerController };
+208
View File
@@ -0,0 +1,208 @@
import { Types } from "mongoose";
import { ISeller } from "./models/Abstraction/ISeller";
import { SellerModel } from "./models/seller.model";
import { BaseRepository } from "../../common/base/repository";
class SellerRepository extends BaseRepository<ISeller> {
constructor() {
super(SellerModel);
}
async findByPhone(phoneNumber: string) {
return this.model.findOne({ phoneNumber });
}
async findByEmail(email: string) {
return this.model.findOne({ email });
}
async getPanelInfo(sellerId: string) {
return this.model.aggregate([
{
$match: {
_id: new Types.ObjectId(sellerId),
},
},
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "sellerId",
as: "orders",
},
},
{
$lookup: {
from: "products",
localField: "_id",
foreignField: "seller",
as: "products",
},
},
{
$addFields: {
orderCount: { $size: "$orders" },
productCount: { $size: "$products" },
completedOrdersCount: {
$size: {
$filter: {
input: "$orders",
as: "order",
cond: { $eq: ["$$order.status", "completed"] },
},
},
},
totalRevenue: {
$sum: "$orders.totalAmount",
},
weeklyRevenue: {
$sum: {
$map: {
input: {
$filter: {
input: "$orders",
as: "order",
cond: {
$gte: ["$$order.date", new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)],
},
},
},
as: "order",
in: "$$order.totalAmount",
},
},
},
// Order Management Fields
ordersThisWeek: {
$size: {
$filter: {
input: "$orders",
as: "order",
cond: {
$gte: ["$$order.date", new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)],
},
},
},
},
ordersToday: {
$size: {
$filter: {
input: "$orders",
as: "order",
cond: {
$gte: ["$$order.date", new Date(new Date().setHours(0, 0, 0, 0))],
},
},
},
},
unapprovedOrders: {
$size: {
$filter: {
input: "$orders",
as: "order",
cond: { $eq: ["$$order.status", "unapproved"] },
},
},
},
shippedOrders: {
$size: {
$filter: {
input: "$orders",
as: "order",
cond: { $eq: ["$$order.status", "shipped"] },
},
},
},
delayedOrders: {
$size: {
$filter: {
input: "$orders",
as: "order",
cond: { $eq: ["$$order.status", "delayed"] },
},
},
},
// Inventory Management Fields
availableProducts: {
$size: {
$filter: {
input: "$products",
as: "product",
cond: { $gt: ["$$product.stock", 0] }, // Fix here: provide two arguments to $gt
},
},
},
outOfStockProducts: {
$size: {
$filter: {
input: "$products",
as: "product",
cond: { $eq: ["$$product.stock", 0] },
},
},
},
restockedLastWeek: {
$size: {
$filter: {
input: "$products",
as: "product",
cond: {
$gte: ["$$product.restockDate", new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)],
},
},
},
},
restockingProducts: {
$size: {
$filter: {
input: "$products",
as: "product",
cond: { $eq: ["$$product.restocking", true] },
},
},
},
rivalProducts: {
$size: {
$filter: {
input: "$products",
as: "product",
cond: { $eq: ["$$product.hasRival", true] },
},
},
},
},
},
{
$project: {
dateOfBirth: 0,
nationalCode: 0,
gender: 0,
shopNationalId: 0,
province: 0,
city: 0,
shebaNo: 0,
phoneNumber: 0,
workNumber: 0,
shopPostalCode: 0,
address: 0,
nationalCardImage: 0,
products: 0,
__v: 0,
updatedAt: 0,
},
},
]);
}
async getAllSellerForReport() {
return this.model.countDocuments();
}
}
function createSellerRepository(): SellerRepository {
return new SellerRepository();
}
export { SellerRepository, createSellerRepository };
+960
View File
@@ -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 };
@@ -0,0 +1,11 @@
export type ContractType = {
date: string;
seller_name: string;
id_number: string;
national_code: string;
address: string;
postal_code: string;
phone_number: string;
email: string;
content: string;
};