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
+65
View File
@@ -0,0 +1,65 @@
import { Expose, Type, plainToInstance } from "class-transformer";
import { FineRuleListDTO } from "./fineRule.dto";
import { ProductListDTO } from "../../product/DTO/product.dto";
export class FineDto {
@Expose()
product: string;
@Expose()
seller: string;
@Expose()
orderItem: string;
@Expose()
reason: string;
@Expose()
fine_rule: string;
@Expose()
fine_amount: number;
}
export class FineListDTO {
@Expose()
_id: string;
@Expose()
orderItemRef: string;
@Expose()
reasonRef: string;
@Expose()
fine_amount: number;
@Expose()
createdAt: Date;
@Expose()
fineRuleDetails: FineRuleListDTO;
@Expose()
reasonDetails: any;
@Expose()
orderItemsDetails: any;
@Expose()
shipmentItem: any;
@Expose()
@Type(() => ProductListDTO)
product: ProductListDTO;
public static transformFineList(data: any): FineListDTO {
const fineListDTO = plainToInstance(FineListDTO, data, {
excludeExtraneousValues: true,
enableImplicitConversion: true,
});
return fineListDTO;
}
}
+29
View File
@@ -0,0 +1,29 @@
import { Expose } from "class-transformer";
import { IsNotEmpty, IsNumber, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
export class FineRuleDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the title of fine", example: "تاخیر در تحویل" })
title: string;
@Expose()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", description: "the fine percentage of fine", example: "20" })
fine_percentage: number;
}
export class FineRuleListDTO {
@Expose()
_id: string;
@Expose()
title: string;
@Expose()
fine_percentage: number;
}
+18
View File
@@ -0,0 +1,18 @@
import { Expose } from "class-transformer";
import { IsNumber, IsOptional, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
export class UpdateFineRuleDTO {
@Expose()
@IsOptional()
@IsString()
@ApiProperty({ type: "string", description: "the title of fine", example: "تاخیر در تحویل" })
title: string;
@Expose()
@IsOptional()
@IsNumber()
@ApiProperty({ type: "number", description: "the fine percentage of fine", example: "20" })
fine_percentage: number;
}
+46
View File
@@ -0,0 +1,46 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpGet, queryParam, request } from "inversify-express-utils";
import { FineService } from "./fine.service";
import { HttpStatus } from "../../common";
import { BaseController } from "../../common/base/controller";
import { ApiAuth, ApiOperation, ApiQuery, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
import { Guard } from "../../core/middlewares/guard.middleware";
import { IOCTYPES } from "../../IOC/ioc.types";
import { ISeller } from "../seller/models/Abstraction/ISeller";
@controller("/fine")
@ApiTags("Fine")
class FineController extends BaseController {
@inject(IOCTYPES.FineService) fineService: FineService;
//return all fine rules
@ApiOperation("get list of fine rules")
@ApiResponse("successful", HttpStatus.Ok)
@httpGet("/rules")
public async getFineRules() {
const data = await this.fineService.getFineRules();
return this.response(data);
}
@ApiOperation("get all fine ==> login as seller")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of fines data")
@ApiQuery("page", "the page want to get")
@ApiAuth()
@httpGet("/fines", Guard.authSeller())
public async getAllFinesBySeller(@request() req: Request, @queryParam("limit") limit: string, @queryParam("page") page: string) {
const seller = req.user as ISeller;
const queries = {
limit: parseInt(limit),
page: parseInt(page),
};
const data = await this.fineService.getAllFinesBySeller(seller?._id.toString(), queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, fineList: data.fineList });
}
}
export { FineController };
+59
View File
@@ -0,0 +1,59 @@
import { inject, injectable } from "inversify";
import { FineListDTO } from "./DTO/fine.dto";
import { FineRuleDTO } from "./DTO/fineRule.dto";
import { UpdateFineRuleDTO } from "./DTO/role-update.dto";
import { FineRepo } from "./repository/fine.repository";
import { FineRuleRepo } from "./repository/fineRule.repository";
import { AdminFinesQueries, SellerFinesQueries } from "../../common/types/query.type";
import { IOCTYPES } from "../../IOC/ioc.types";
@injectable()
class FineService {
@inject(IOCTYPES.FineRuleRepo) private fineRuleRepo: FineRuleRepo;
@inject(IOCTYPES.FineRepo) private fineRepo: FineRepo;
async createFineRule(fineRuleDto: FineRuleDTO) {
const fineRule = await this.fineRuleRepo.model.create(fineRuleDto);
return { fineRule };
}
async getFineRules() {
const fineRules = await this.fineRuleRepo.model.find({ deleted: false });
return { fineRules };
}
async updateFineRule(ruleId: string, updateDto: UpdateFineRuleDTO) {
const fineRule = await this.fineRuleRepo.model.findOneAndUpdate({ _id: ruleId }, updateDto, { new: true });
return { fineRule };
}
async getRuleWithId(ruleId: string) {
const fineRule = await this.fineRuleRepo.findById(ruleId);
return { fineRule };
}
async deleteRule(ruleId: string) {
const fineRule = await this.fineRuleRepo.model.findOneAndUpdate({ _id: ruleId }, { deleted: true }, { new: true });
return { fineRule };
}
async getAllFines(queries: AdminFinesQueries) {
const { docs, count } = await this.fineRepo.findAllForAdmin(queries);
const fineList = docs.map((doc: any) => FineListDTO.transformFineList(doc));
return { fineList, count };
}
async getAllFinesBySeller(sellerId: string, queries: SellerFinesQueries) {
const { docs, count } = await this.fineRepo.findAllBySeller(sellerId, queries);
const fineList = docs.map((doc: any) => FineListDTO.transformFineList(doc));
console.log(fineList);
return {
fineList,
count,
};
}
}
export { FineService };
@@ -0,0 +1,14 @@
import { Types } from "mongoose";
import { OrderItemRefEnum, ReasonRefEnum } from "../fine.model";
export interface IFine {
product: Types.ObjectId;
seller: Types.ObjectId;
orderItem: Types.ObjectId;
orderItemRef: OrderItemRefEnum;
fine_rule: Types.ObjectId;
reason: Types.ObjectId;
reasonRef: ReasonRefEnum;
fine_amount: number;
}
@@ -0,0 +1,5 @@
export interface IFineRule {
title: string;
fine_percentage: number;
deleted: boolean;
}
+30
View File
@@ -0,0 +1,30 @@
import { Schema, model } from "mongoose";
import { IFine } from "./Abstraction/IFine";
export enum ReasonRefEnum {
Cancel = "Cancel",
Return = "Return",
}
export enum OrderItemRefEnum {
CancelOrderItem = "CancelOrderItem",
ReturnOrderItem = "ReturnOrderItem",
}
const FineSchema = new Schema<IFine>(
{
seller: { type: Schema.Types.ObjectId, ref: "Seller", required: true },
orderItem: { type: Schema.Types.ObjectId, ref: "orderItemRef", required: true },
orderItemRef: { type: String, enum: OrderItemRefEnum, required: true },
fine_rule: { type: Schema.Types.ObjectId, ref: "FineRule", required: true },
reason: { type: Schema.Types.ObjectId, refPath: "reasonRef", required: true },
reasonRef: { type: String, enum: ReasonRefEnum, required: true },
fine_amount: { type: Number, required: true },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, id: false },
);
const FineModel = model<IFine>("Fine", FineSchema);
export { FineModel };
+16
View File
@@ -0,0 +1,16 @@
import { Schema, model } from "mongoose";
import { IFineRule } from "./Abstraction/IFineRule";
const FineRuleSchema = new Schema<IFineRule>(
{
title: { type: String, required: true },
fine_percentage: { type: Number, required: true },
deleted: { type: Boolean, default: false },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, id: false },
);
const FineRuleModel = model<IFineRule>("FineRule", FineRuleSchema);
export { FineRuleModel };
@@ -0,0 +1,335 @@
import { Types } from "mongoose";
import { BaseRepository } from "../../../common/base/repository";
import { SellerFinesQueries } from "../../../common/types/query.type";
import { IFine } from "../models/Abstraction/IFine";
import { FineModel } from "../models/fine.model";
export class FineRepo extends BaseRepository<IFine> {
constructor() {
super(FineModel);
}
async findAllBySeller(sellerId: string, queries: SellerFinesQueries) {
const page = queries.page || 1;
const limit = queries.limit || 10;
const skip = (page - 1) * limit;
const docs = await this.model.aggregate([
{
$match: {
seller: new Types.ObjectId(sellerId),
},
},
{
$lookup: {
from: "finerules",
localField: "fine_rule",
foreignField: "_id",
as: "fineRuleDetails",
},
},
{
$unwind: "$fineRuleDetails",
},
{
$addFields: {
sellerDetails: { $arrayElemAt: ["$sellerDetails", 0] },
},
},
{
$lookup: {
from: "cancelreasons",
localField: "reason",
foreignField: "_id",
as: "cancelReasonDetails",
},
},
{
$addFields: {
cancelReasonDetails: { $ifNull: ["$cancelReasonDetails", []] },
},
},
{
$lookup: {
from: "returnreasons",
localField: "reason",
foreignField: "_id",
as: "returnReasonDetails",
},
},
{
$addFields: {
returnReasonDetails: { $ifNull: ["$returnReasonDetails", []] },
},
},
{
$addFields: {
reasonDetails: {
$cond: {
if: { $eq: ["$reasonRef", "Cancel"] },
then: { $arrayElemAt: ["$cancelReasonDetails", 0] },
else: { $arrayElemAt: ["$returnReasonDetails", 0] },
},
},
},
},
{
$unset: ["cancelReasonDetails", "returnReasonDetails"],
},
{
$lookup: {
from: "cancelorderitems",
localField: "orderItem",
foreignField: "_id",
as: "cancelOrderItemDetails",
},
},
{
$addFields: {
cancelOrderItemDetails: { $ifNull: ["$cancelOrderItemDetails", []] },
},
},
{
$lookup: {
from: "returnorderitems",
localField: "orderItem",
foreignField: "_id",
as: "returnOrderItemDetails",
},
},
{
$addFields: {
returnOrderItemDetails: { $ifNull: ["$returnOrderItemDetails", []] },
},
},
{
$addFields: {
orderItemsDetails: {
$cond: {
if: { $eq: ["$orderItemRef", "CancelOrderItem"] },
then: { $arrayElemAt: ["$cancelOrderItemDetails", 0] },
else: { $arrayElemAt: ["$returnOrderItemDetails", 0] },
},
},
},
},
{
$unset: ["cancelOrderItemDetails", "returnOrderItemDetails"],
},
{
$lookup: {
from: "orderitems",
localField: "orderItemsDetails.orderItem",
foreignField: "_id",
as: "orderItemsDetails.orderItem",
},
},
{
$unwind: "$orderItemsDetails.orderItem",
},
{
$addFields: {
shipmentItem: {
$arrayElemAt: ["$orderItemsDetails.orderItem.shipmentItems", 0],
},
},
},
{
$lookup: {
from: "products",
localField: "shipmentItem.product",
foreignField: "_id",
as: "product",
},
},
{
$addFields: {
product: { $arrayElemAt: ["$product", 0] },
},
},
{
$facet: {
data: [{ $skip: skip }, { $limit: limit }],
totalCount: [{ $count: "count" }],
},
},
{
$addFields: {
totalCount: { $arrayElemAt: ["$totalCount.count", 0] },
},
},
{
$sort: {
createdAt: -1,
},
},
]);
console.dir(docs[0], { depth: null });
return {
count: (docs[0]?.totalCount as number) || 0,
docs: docs[0]?.data || [],
};
}
async findAllForAdmin(queries: SellerFinesQueries) {
const page = queries.page || 1;
const limit = queries.limit || 10;
const skip = (page - 1) * limit;
const docs = await this.model.aggregate([
{
$lookup: {
from: "finerules",
localField: "fine_rule",
foreignField: "_id",
as: "fineRuleDetails",
},
},
{
$unwind: "$fineRuleDetails",
},
{
$addFields: {
sellerDetails: { $arrayElemAt: ["$sellerDetails", 0] },
},
},
{
$lookup: {
from: "cancelreasons",
localField: "reason",
foreignField: "_id",
as: "cancelReasonDetails",
},
},
{
$addFields: {
cancelReasonDetails: { $ifNull: ["$cancelReasonDetails", []] },
},
},
{
$lookup: {
from: "returnreasons",
localField: "reason",
foreignField: "_id",
as: "returnReasonDetails",
},
},
{
$addFields: {
returnReasonDetails: { $ifNull: ["$returnReasonDetails", []] },
},
},
{
$addFields: {
reasonDetails: {
$cond: {
if: { $eq: ["$reasonRef", "Cancel"] },
then: { $arrayElemAt: ["$cancelReasonDetails", 0] },
else: { $arrayElemAt: ["$returnReasonDetails", 0] },
},
},
},
},
{
$unset: ["cancelReasonDetails", "returnReasonDetails"],
},
{
$lookup: {
from: "cancelorderitems",
localField: "orderItem",
foreignField: "_id",
as: "cancelOrderItemDetails",
},
},
{
$addFields: {
cancelOrderItemDetails: { $ifNull: ["$cancelOrderItemDetails", []] },
},
},
{
$lookup: {
from: "returnorderitems",
localField: "orderItem",
foreignField: "_id",
as: "returnOrderItemDetails",
},
},
{
$addFields: {
returnOrderItemDetails: { $ifNull: ["$returnOrderItemDetails", []] },
},
},
{
$addFields: {
orderItemsDetails: {
$cond: {
if: { $eq: ["$orderItemRef", "CancelOrderItem"] },
then: { $arrayElemAt: ["$cancelOrderItemDetails", 0] },
else: { $arrayElemAt: ["$returnOrderItemDetails", 0] },
},
},
},
},
{
$unset: ["cancelOrderItemDetails", "returnOrderItemDetails"],
},
{
$lookup: {
from: "orderitems",
localField: "orderItemsDetails.orderItem",
foreignField: "_id",
as: "orderItemsDetails.orderItem",
},
},
{
$unwind: "$orderItemsDetails.orderItem",
},
{
$addFields: {
shipmentItem: {
$arrayElemAt: ["$orderItemsDetails.orderItem.shipmentItems", 0],
},
},
},
{
$lookup: {
from: "products",
localField: "shipmentItem.product",
foreignField: "_id",
as: "product",
},
},
{
$addFields: {
product: { $arrayElemAt: ["$product", 0] },
},
},
{
$facet: {
data: [{ $skip: skip }, { $limit: limit }],
totalCount: [{ $count: "count" }],
},
},
{
$addFields: {
totalCount: { $arrayElemAt: ["$totalCount.count", 0] },
},
},
{
$sort: {
createdAt: -1,
},
},
]);
console.dir(docs[0], { depth: null });
return {
count: (docs[0]?.totalCount as number) || 0,
docs: docs[0]?.data || [],
};
}
}
export function createFineRepo(): FineRepo {
return new FineRepo();
}
@@ -0,0 +1,17 @@
import { BaseRepository } from "../../../common/base/repository";
import { IFineRule } from "../models/Abstraction/IFineRule";
import { FineRuleModel } from "../models/fineRule.model";
export class FineRuleRepo extends BaseRepository<IFineRule> {
constructor() {
super(FineRuleModel);
}
async findFineRuleByCancelReasonId(cancelReasonId: string) {
return this.model.findOne({ cancel_reason: cancelReasonId }).populate("cancel_reason");
}
}
export function createFineRuleRepo(): FineRuleRepo {
return new FineRuleRepo();
}