product verity

This commit is contained in:
morteza-mortezai
2025-11-29 08:52:05 +03:30
parent e54fc58b37
commit 37ecf9f449
35 changed files with 741 additions and 529 deletions
+4 -20
View File
@@ -3,9 +3,7 @@ import { IsNotEmpty, IsNumber, IsOptional, Min } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { ColorModel } from "../../category/models/color.model";
import { MeterageModel } from "../../category/models/meterage.model";
import { SizeModel } from "../../category/models/size.model";
import { ThemeValueModel } from "../../category/models/themeValue.model";
import { WarrantyModel } from "../../warranty/models/warranty.model";
// import { ShipmentModel } from "../../../models/shipment.model";
@@ -19,23 +17,9 @@ export class AddVariantDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsValidId(ColorModel)
@ApiProperty({ type: "number", description: "color id ==> one of color,size or meterage should send", example: 1 })
colorId?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsValidId(SizeModel)
@ApiProperty({ type: "number", description: "size id ==> one of color,size or meterage should send", example: 5 })
sizeId?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsValidId(MeterageModel)
@ApiProperty({ type: "number", description: "meterage id ==> one of color,size or meterage should send", example: 5 })
meterageId?: number;
@IsValidId(ThemeValueModel)
@ApiProperty({ type: "string", description: "theme value id" })
themeValueId?: string;
@Expose()
@IsNotEmpty()
@@ -1,14 +1,10 @@
import { Types } from "mongoose";
import { CategoryThemeEnum } from "../../../../common/enums/category.enum";
import { ProductDiscountType, ProductMarketStatus } from "../../../../common/enums/product.enum";
// Dynamic type for category theme fields
type CategoryThemeFields = {
[K in Lowercase<Exclude<CategoryThemeEnum, CategoryThemeEnum.No_color_No_sized>>]?: number;
};
export interface IProductVariant extends CategoryThemeFields {
export interface IProductVariant {
_id: Types.ObjectId;
product: number;
statistics: IStatistics;
@@ -25,6 +21,7 @@ export interface IProductVariant extends CategoryThemeFields {
saleFormat: ISaleFormat;
dimensions: IDimensions;
sellerSpecialCode: string;
themeValue: Types.ObjectId;
}
export interface IPrice {
@@ -1,7 +1,6 @@
import { Schema, model } from "mongoose";
import { IDimensions, IPrice, IProductVariant, ISaleFormat, IStatistics } from "./Abstraction/IProductVariant";
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
import { ProductMarketStatus } from "../../../common/enums/product.enum";
const productStatisticsSchema = new Schema<IStatistics>(
@@ -45,24 +44,6 @@ const productPriceSchema = new Schema<IPrice>(
{ _id: false },
);
// Utility function to generate dynamic category theme fields
const generateCategoryThemeFields = () => {
const fields: Record<string, { type: NumberConstructor; ref: string; required: boolean }> = {};
Object.values(CategoryThemeEnum).forEach((theme) => {
if (theme !== CategoryThemeEnum.No_color_No_sized) {
const fieldName = theme.toLowerCase();
fields[fieldName] = {
type: Number,
ref: theme,
required: false,
};
}
});
return fields;
};
const productVariantSchema = new Schema<IProductVariant>(
{
product: { type: Number, ref: "Product", required: true },
@@ -78,7 +59,7 @@ const productVariantSchema = new Schema<IProductVariant>(
saleFormat: { type: productSaleFormat },
dimensions: { type: productDimensionsSchema, required: true },
sellerSpecialCode: { type: String },
...generateCategoryThemeFields(),
themeValue: { type: Schema.Types.ObjectId, ref: "ThemeValue", required: true },
},
{ timestamps: true, toObject: { virtuals: true, versionKey: false }, toJSON: { virtuals: true, versionKey: false }, id: false },
);
@@ -1,9 +1,8 @@
import { inject, injectable } from "inversify";
import { ClientSession, FilterQuery, isValidObjectId, startSession } from "mongoose";
import { ClientSession, Types, isValidObjectId, startSession } from "mongoose";
import slugify from "slugify";
import { PaginationDTO } from "../../../common/dto/pagination.dto";
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
import {
AdMessage,
AttributeMessage,
@@ -29,7 +28,7 @@ import { paginationUtils } from "../../../utils/pagination.utils";
import { TimeService } from "../../../utils/time.service";
import { AddIncredibleOffersParamDto } from "../../admin/DTO/product-param.dto";
import { BrandDTO } from "../../brand/DTO/brand.dto";
import { AttributeValueRepo, CategoryAttributeRepo, CategoryRepository } from "../../category/category.repository";
import { AttributeValueRepo, CategoryAttributeRepo, CategoryRepository, ThemeValueRepository } from "../../category/category.repository";
import { CategoryTreeDTO } from "../../category/DTO/category.dto";
import { NotificationService } from "../../notification/notification.service";
import { PricingRepository } from "../../pricing/pricing.repository";
@@ -96,6 +95,7 @@ class ProductService {
@inject(IOCTYPES.ShopRepo) shopeRepo: ShopRepo;
@inject(IOCTYPES.IncredibleOffersRepo) incredibleOffersRepo: IncredibleOffersRepo;
@inject(IOCTYPES.NotificationService) notificationService: NotificationService;
@inject(IOCTYPES.ThemeValueRepository) themeValueRepo: ThemeValueRepository;
//###############################################################
async searchProducts(q: string) {
@@ -557,41 +557,30 @@ class ProductService {
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
this.ensureProductIsNotInDraft(product);
const category = await this.ensureCategoryTheme(product.category.toString(), addVariantDto);
await this.ensureCategoryTheme(product.category.toString(), addVariantDto);
await this.checkVariantCount(product, category.theme);
// await this.checkVariantCount(product, category.theme);
// Check if a variant with the same colorId, meterageId, or sizeId already exists
// Check if a variant with the same colorId, meterageId, or sizeId already exists
const query: FilterQuery<IProductVariant> = { product: product._id };
// Check if a variant with the same themeValue already exists
// const query: FilterQuery<IProductVariant> = { product: product._id };
if (addVariantDto.colorId) {
query.color = addVariantDto.colorId;
}
if (addVariantDto.meterageId) {
query.meterage = addVariantDto.meterageId;
}
if (addVariantDto.sizeId) {
query.size = addVariantDto.sizeId;
}
// if (addVariantDto.themeValueId) {
// const existingVariant = await this.productVariantRepo.model.findOne({
// product: product._id,
// themeValue: addVariantDto.themeValueId,
// });
if (category.toString() !== CategoryThemeEnum.No_color_No_sized) {
const existingVariant = await this.productVariantRepo.model.findOne(query);
if (existingVariant) throw new BadRequestError(ProductMessage.VariantAlreadyExists);
}
// if (existingVariant) throw new BadRequestError(ProductMessage.VariantAlreadyExists);
// }
const productVariant = await this.createProductVariant(addVariantDto, product._id, shop._id.toString(), session);
await this.productRepo.model.findByIdAndUpdate(product._id, { $push: { variants: productVariant } }, { session });
// product.variants.push(productVariant[0]._id);
// await product.save({ session });
const { colorId, sizeId, meterageId } = addVariantDto;
PriceChangeEvent.emitPriceChange({
colorId,
sizeId,
meterageId,
themeValueId: addVariantDto.themeValueId,
product: productId,
variantId: productVariant[0]._id.toString(),
shop: shop._id.toString(),
@@ -654,9 +643,6 @@ class ProductService {
);
PriceChangeEvent.emitPriceChange({
colorId: updatedVariant?.color,
sizeId: updatedVariant?.size,
meterageId: updatedVariant?.meterage,
product: productId,
variantId: updatedVariant?._id.toString() as string,
shop: shop._id.toString(),
@@ -1447,33 +1433,10 @@ class ProductService {
throw new BadRequestError(CategoryMessage.NotValidId);
}
// Check if the category theme is color, size, or meterage
if (category.theme === CategoryThemeEnum.Colored) {
if (addVariantDto.sizeId || addVariantDto.meterageId) {
throw new BadRequestError(AttributeMessage.SizeNotAllowedForColoredTheme);
}
if (!addVariantDto.colorId) {
throw new BadRequestError(AttributeMessage.ColorRequiredForColoredTheme);
}
} else if (category.theme === CategoryThemeEnum.Sized) {
if (addVariantDto.colorId || addVariantDto.meterageId) {
throw new BadRequestError(AttributeMessage.ColorNotAllowedForSizedTheme);
}
if (!addVariantDto.sizeId) {
throw new BadRequestError(AttributeMessage.SizeRequiredForSizedTheme);
}
} else if (category.theme === CategoryThemeEnum.Meterage) {
if (addVariantDto.colorId || addVariantDto.sizeId) {
throw new BadRequestError(AttributeMessage.ColorOrSizeNotAllowedForMeterageTheme);
}
if (!addVariantDto.meterageId) {
throw new BadRequestError(AttributeMessage.MeterageRequiredForMeterageTheme);
}
} else {
// For other themes that are neither color, size, nor meterage
if (addVariantDto.sizeId || addVariantDto.colorId || addVariantDto.meterageId) {
throw new BadRequestError(AttributeMessage.ThemeIsNoColorNoSizeNoMeterage);
}
if (addVariantDto.themeValueId) {
const themeValue = await this.themeValueRepo.findById(addVariantDto.themeValueId);
if (!themeValue) throw new BadRequestError(CommonMessage.NotFoundById);
category.variants = [themeValue._id];
}
return category;
@@ -1549,9 +1512,7 @@ class ProductService {
shop: shopId,
warranty: addVariantDto.warrantyId,
// shipmentMethod: addVariantDto.shipmentsId,
color: addVariantDto.colorId,
size: addVariantDto.sizeId,
meterage: addVariantDto.meterageId,
themeValue: new Types.ObjectId(addVariantDto.themeValueId),
dimensions,
market_status: ProductMarketStatus.Marketable,
price: product_price,
@@ -1611,16 +1572,6 @@ class ProductService {
return totalCost;
}
private async checkVariantCount(product: IProduct, categoryTheme: CategoryThemeEnum) {
if (categoryTheme !== CategoryThemeEnum.No_color_No_sized) return true;
const variantCount = product.variants.length;
console.log("variantCount", variantCount);
if (!variantCount) return true;
console.log("pro", product, categoryTheme);
throw new BadRequestError(ProductMessage.ProductWithNoVariantCategoryTheme);
}
//###############################################################
//###################### popular product ############################
@@ -1744,6 +1695,16 @@ class ProductService {
// return searchCost;
// }
// private async checkVariantCount(product: IProduct, categoryTheme: Types.ObjectId | undefined) {
// if (categoryTheme) return true;
// const variantCount = product.variants.length;
// console.log("variantCount", variantCount);
// if (!variantCount) return true;
// console.log("pro", product, categoryTheme);
// throw new BadRequestError(ProductMessage.ProductWithNoVariantCategoryTheme);
// }
}
export { ProductService };