product verity
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEnum, IsNotEmpty, IsOptional } from "class-validator";
|
||||
import { IsNotEmpty, IsOptional } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { IsValidId } from "../../../common/decorator/validation.decorator";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
import { CategoryModel } from "../models/category.model";
|
||||
import { ThemeModel } from "../models/theme.model";
|
||||
|
||||
export class CreateCategoryDTO {
|
||||
@Expose()
|
||||
@@ -40,12 +40,11 @@ export class CreateCategoryDTO {
|
||||
parent?: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(CategoryThemeEnum)
|
||||
@IsOptional()
|
||||
@IsValidId(ThemeModel)
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "the variation theme of a category ==> should be one of Color/Size/Meterage or noColor_noSize",
|
||||
example: "Color",
|
||||
})
|
||||
theme: CategoryThemeEnum;
|
||||
themeId: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEnum, IsNotEmpty } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { InputTypeEnum } from "../models/theme.model";
|
||||
|
||||
export class CreateThemeDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the title of theme", example: "Color" })
|
||||
title: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(InputTypeEnum)
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "the input type of theme",
|
||||
example: InputTypeEnum.Color,
|
||||
})
|
||||
inputType: InputTypeEnum;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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 { ThemeModel } from "../models/theme.model";
|
||||
|
||||
export class CreateThemeValueDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@IsValidId(ThemeModel)
|
||||
@ApiProperty({ type: "string", description: "the theme id", example: "66f3bcaee566db722a044c62" })
|
||||
theme: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ type: "string", description: "the name of theme value", example: "Red" })
|
||||
name: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the value of theme value (can be string, number, or color hex)", example: "#FF0000" })
|
||||
value: string | number;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEnum, IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
|
||||
import { IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { IsValidId } from "../../../common/decorator/validation.decorator";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
import { CategoryModel } from "../models/category.model";
|
||||
import { ThemeModel } from "../models/theme.model";
|
||||
|
||||
export class UpdateCategoryDTO {
|
||||
@Expose()
|
||||
@@ -54,12 +54,11 @@ export class UpdateCategoryDTO {
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(CategoryThemeEnum)
|
||||
@IsValidId(ThemeModel)
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "the variation theme of a category ==> should be one of Color/Size/Meterage or noColor_noSize",
|
||||
example: "Color",
|
||||
})
|
||||
theme: CategoryThemeEnum;
|
||||
themeId: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEnum, IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { IsValidId } from "../../../common/decorator/validation.decorator";
|
||||
import { InputTypeEnum, ThemeModel } from "../models/theme.model";
|
||||
|
||||
export class UpdateThemeDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@Length(24, 24)
|
||||
@IsValidId(ThemeModel)
|
||||
@ApiProperty({ type: "string", description: "theme id", example: "66f3bcaee566db722a044c62" })
|
||||
themeId: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the title of theme", example: "Color" })
|
||||
title?: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(InputTypeEnum)
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "the input type of theme",
|
||||
example: InputTypeEnum.Color,
|
||||
})
|
||||
inputType?: InputTypeEnum;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { IsValidId } from "../../../common/decorator/validation.decorator";
|
||||
import { ThemeValueModel } from "../models/themeValue.model";
|
||||
|
||||
export class UpdateThemeValueDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@Length(24, 24)
|
||||
@IsValidId(ThemeValueModel)
|
||||
@ApiProperty({ type: "string", description: "theme value id", example: "66f3bcaee566db722a044c62" })
|
||||
themeValueId: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ type: "string", description: "the name of theme value", example: "Red" })
|
||||
name?: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the value of theme value (can be string, number, or color hex)", example: "#FF0000" })
|
||||
value?: string | number;
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { PickType } from "@nestjs/mapped-types";
|
||||
import { PartialType } from "@nestjs/mapped-types";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { ArrayMinSize, IsEnum, IsHexColor, IsNotEmpty, IsOptional, ValidateNested } from "class-validator";
|
||||
import { ArrayMinSize, IsHexColor, IsNotEmpty, IsOptional, ValidateNested } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
import { IsValidId } from "../../../common/decorator/validation.decorator";
|
||||
import { ThemeModel } from "../models/theme.model";
|
||||
|
||||
export class CreateColorDTO {
|
||||
@Expose()
|
||||
@@ -30,14 +31,13 @@ export class CreateMeterageDTO {
|
||||
|
||||
export class CreateCategoryThemeDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(CategoryThemeEnum)
|
||||
@IsOptional()
|
||||
@IsValidId(ThemeModel)
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "the variation theme of a category ==> should be one of Color/Size/Meterage or noColor_noSize",
|
||||
example: "Color",
|
||||
description: "theme id",
|
||||
})
|
||||
theme: CategoryThemeEnum;
|
||||
themeId?: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@@ -80,4 +80,4 @@ export class CreateCategoryThemeDTO {
|
||||
meterages?: CreateMeterageDTO[];
|
||||
}
|
||||
|
||||
export class UpdateCategoryVariantDTO extends PickType(CreateCategoryThemeDTO, ["colors", "sizes", "meterages"] as const) {}
|
||||
export class UpdateCategoryVariantDTO extends PartialType(CreateCategoryThemeDTO) {}
|
||||
|
||||
@@ -4,15 +4,13 @@ import { SellerCategorySearchDTO } from "./DTO/category-sarch.dto";
|
||||
import { IAttributeValue } from "./models/Abstraction/IAttributeValue";
|
||||
import { ICategory } from "./models/Abstraction/ICategory";
|
||||
import { ICategoryAttribute } from "./models/Abstraction/ICategoryAttributes";
|
||||
import { IColor } from "./models/Abstraction/IColor";
|
||||
import { IMeterage } from "./models/Abstraction/IMeterage";
|
||||
import { ISize } from "./models/Abstraction/ISize";
|
||||
import { ITheme } from "./models/Abstraction/ITheme";
|
||||
import { IThemeValue } from "./models/Abstraction/IThemeValue";
|
||||
import { AttributeValueModel } from "./models/attributeValue.model";
|
||||
import { CategoryModel } from "./models/category.model";
|
||||
import { CategoryAttributeModel } from "./models/CategoryAttribute.model";
|
||||
import { ColorModel } from "./models/color.model";
|
||||
import { MeterageModel } from "./models/meterage.model";
|
||||
import { SizeModel } from "./models/size.model";
|
||||
import { ThemeModel } from "./models/theme.model";
|
||||
import { ThemeValueModel } from "./models/themeValue.model";
|
||||
import { BaseRepository } from "../../common/base/repository";
|
||||
import { BadRequestError } from "../../core/app/app.errors";
|
||||
const { ObjectId } = Types;
|
||||
@@ -167,21 +165,21 @@ class CategoryRepository extends BaseRepository<ICategory> {
|
||||
const result: Record<string, any[]> = {};
|
||||
|
||||
// Step 4: Perform a single lookup per theme
|
||||
for (const [theme, variantIdsSet] of themeVariantsMap.entries()) {
|
||||
const variantIds = Array.from(variantIdsSet).map((id) => +id);
|
||||
// for (const [theme, variantIdsSet] of themeVariantsMap.entries()) {
|
||||
// const variantIds = Array.from(variantIdsSet).map((id) => +id);
|
||||
|
||||
if (variantIds.length > 0) {
|
||||
let variantsData;
|
||||
// Fetch the variants from the corresponding theme collection
|
||||
if (theme === "colors") variantsData = await ColorModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
if (theme === "sizes") variantsData = await SizeModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
if (theme === "meterages") variantsData = await MeterageModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
// throw new Error("theme not valid");
|
||||
// // if (variantIds.length > 0) {
|
||||
// // let variantsData;
|
||||
// // // Fetch the variants from the corresponding theme collection
|
||||
// // if (theme === "colors") variantsData = await ColorModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
// // if (theme === "sizes") variantsData = await SizeModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
// // if (theme === "meterages") variantsData = await MeterageModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
// // // throw new Error("theme not valid");
|
||||
|
||||
// Store the result for this theme
|
||||
result[theme] = variantsData as any;
|
||||
}
|
||||
}
|
||||
// // // Store the result for this theme
|
||||
// // result[theme] = variantsData as any;
|
||||
// // }
|
||||
// }
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -356,48 +354,38 @@ function createAttributeValueRepo(): AttributeValueRepo {
|
||||
}
|
||||
|
||||
//=====================>size model repository
|
||||
class SizeRepository extends BaseRepository<ISize> {
|
||||
|
||||
//=====================>theme model repo
|
||||
class ThemeRepository extends BaseRepository<ITheme> {
|
||||
constructor() {
|
||||
super(SizeModel);
|
||||
super(ThemeModel);
|
||||
}
|
||||
}
|
||||
function createSizeRepo(): SizeRepository {
|
||||
return new SizeRepository();
|
||||
function createThemeRepo(): ThemeRepository {
|
||||
return new ThemeRepository();
|
||||
}
|
||||
|
||||
//=====================>color model repo
|
||||
class ColorRepository extends BaseRepository<IColor> {
|
||||
//=====================>theme value model repo
|
||||
class ThemeValueRepository extends BaseRepository<IThemeValue> {
|
||||
constructor() {
|
||||
super(ColorModel);
|
||||
super(ThemeValueModel);
|
||||
}
|
||||
}
|
||||
function createColorRepo(): ColorRepository {
|
||||
return new ColorRepository();
|
||||
}
|
||||
|
||||
//=====================>meterage model repo
|
||||
class MeterageRepository extends BaseRepository<IMeterage> {
|
||||
constructor() {
|
||||
super(MeterageModel);
|
||||
}
|
||||
}
|
||||
function createMeterageRepo(): MeterageRepository {
|
||||
return new MeterageRepository();
|
||||
function createThemeValueRepo(): ThemeValueRepository {
|
||||
return new ThemeValueRepository();
|
||||
}
|
||||
|
||||
export {
|
||||
createCategoryRepo,
|
||||
createCategoryAttributeRepo,
|
||||
createSizeRepo,
|
||||
createMeterageRepo,
|
||||
createColorRepo,
|
||||
createAttributeValueRepo,
|
||||
createThemeRepo,
|
||||
createThemeValueRepo,
|
||||
AttributeValueRepo,
|
||||
CategoryRepository,
|
||||
CategoryAttributeRepo,
|
||||
SizeRepository,
|
||||
ColorRepository,
|
||||
MeterageRepository,
|
||||
ThemeRepository,
|
||||
ThemeValueRepository,
|
||||
};
|
||||
|
||||
// async getCategoryVariant(catId: string) {
|
||||
|
||||
@@ -1,42 +1,31 @@
|
||||
import { inject, injectable } from "inversify";
|
||||
import { ClientSession, FilterQuery, isValidObjectId, startSession } from "mongoose";
|
||||
import { FilterQuery, Types, isValidObjectId, startSession } from "mongoose";
|
||||
import slugify from "slugify";
|
||||
|
||||
import {
|
||||
AttributeValueRepo,
|
||||
CategoryAttributeRepo,
|
||||
CategoryRepository,
|
||||
ColorRepository,
|
||||
MeterageRepository,
|
||||
SizeRepository,
|
||||
ThemeRepository,
|
||||
ThemeValueRepository,
|
||||
} from "./category.repository";
|
||||
import { BrandRepository } from "../brand/brand.repository";
|
||||
import { CategoryQueryDto, CategorySearchQueryDTO, SellerCategorySearchDTO } from "./DTO/category-sarch.dto";
|
||||
import { CategoryAttributeDTO, CategoryTreeDTO, CategoryVariantDTO } from "./DTO/category.dto";
|
||||
import { CreateCategoryDTO } from "./DTO/CreateCategory.dto";
|
||||
import { CreateCategoryAttDTO } from "./DTO/createCategoryAtt.dto";
|
||||
import {
|
||||
CreateCategoryThemeDTO,
|
||||
CreateColorDTO,
|
||||
CreateMeterageDTO,
|
||||
CreateSizeDTO,
|
||||
UpdateCategoryVariantDTO,
|
||||
} from "./DTO/createCategoryTheme.dto";
|
||||
import { CreateCategoryThemeDTO, UpdateCategoryVariantDTO } from "./DTO/createCategoryTheme.dto";
|
||||
import { UpdateCategoryDTO } from "./DTO/UpdateCategory.dto";
|
||||
import { UpdateCategoryAttDTO } from "./DTO/updateCategoryAtt.dto";
|
||||
import { IColor } from "./models/Abstraction/IColor";
|
||||
import { IMeterage } from "./models/Abstraction/IMeterage";
|
||||
import { ISize } from "./models/Abstraction/ISize";
|
||||
import { BaseRepository } from "../../common/base/repository";
|
||||
import { CategoryThemeEnum } from "../../common/enums/category.enum";
|
||||
import { ICategory } from "./models/Abstraction/ICategory";
|
||||
import { ThemeModel } from "./models/theme.model";
|
||||
import { CategoryAttrMessage, CategoryMessage, CommonMessage } from "../../common/enums/message.enum";
|
||||
import { BadRequestError, ForbiddenError } from "../../core/app/app.errors";
|
||||
import { IOCTYPES } from "../../IOC/ioc.types";
|
||||
import { paginationUtils } from "../../utils/pagination.utils";
|
||||
import { ProductDTO } from "../product/DTO/product.dto";
|
||||
import { IProduct } from "../product/models/Abstraction/IProduct";
|
||||
import { ProductRepository } from "../product/Repository/product";
|
||||
import { ICategory } from "./models/Abstraction/ICategory";
|
||||
import { paginationUtils } from "../../utils/pagination.utils";
|
||||
|
||||
@injectable()
|
||||
class CategoryService {
|
||||
@@ -45,9 +34,8 @@ class CategoryService {
|
||||
@inject(IOCTYPES.BrandRepository) brandRepo: BrandRepository;
|
||||
@inject(IOCTYPES.CategoryAttributeRepository) categoryAttRepo: CategoryAttributeRepo;
|
||||
@inject(IOCTYPES.AttributeValueRepository) attributeValueRepo: AttributeValueRepo;
|
||||
@inject(IOCTYPES.ColorRepository) colorRepo: ColorRepository;
|
||||
@inject(IOCTYPES.SizeRepository) sizeRepo: SizeRepository;
|
||||
@inject(IOCTYPES.MeterageRepository) meterageRepo: MeterageRepository;
|
||||
@inject(IOCTYPES.ThemeRepository) themeRepo: ThemeRepository;
|
||||
@inject(IOCTYPES.ThemeValueRepository) themeValueRepo: ThemeValueRepository;
|
||||
|
||||
async getCategories() {
|
||||
return this.categoryRepository.model.find({ parent: null });
|
||||
@@ -102,8 +90,10 @@ class CategoryService {
|
||||
|
||||
if (existTitle) throw new BadRequestError(CategoryMessage.DuplicateTitle);
|
||||
//TODO: check if parent is valid for update theme
|
||||
if (updateDto.theme) {
|
||||
await this.categoryRepository.model.findOneAndUpdate({ _id: category._id.toString() }, { theme: updateDto.theme, variants: [] });
|
||||
let theme: typeof ThemeModel | null = null;
|
||||
if (updateDto.themeId) {
|
||||
theme = await this.themeRepo.model.findById(updateDto.themeId);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
}
|
||||
if (updateDto.parent) {
|
||||
// Set the parent category to leaf: false (since it now has a child)
|
||||
@@ -120,7 +110,12 @@ class CategoryService {
|
||||
updatedCategory,
|
||||
};
|
||||
}
|
||||
const updatedCategory = await this.categoryRepository.model.findByIdAndUpdate(category._id.toString(), updateDto, { new: true });
|
||||
|
||||
const updatedCategory = await this.categoryRepository.model.findByIdAndUpdate(
|
||||
category._id.toString(),
|
||||
{ ...updateDto, theme: updateDto.themeId },
|
||||
{ new: true },
|
||||
);
|
||||
return {
|
||||
message: CategoryMessage.Updated,
|
||||
updatedCategory,
|
||||
@@ -133,13 +128,16 @@ class CategoryService {
|
||||
|
||||
const existTitle = await this.categoryRepository.findByTitle(createDto.title_en);
|
||||
|
||||
const theme = await this.themeRepo.findById(createDto.themeId);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
|
||||
if (existTitle) throw new BadRequestError(CategoryMessage.DuplicateTitle);
|
||||
//if parent exist first set parent leaf false
|
||||
if (createDto.parent) {
|
||||
await this.categoryRepository.model.findByIdAndUpdate(createDto.parent, { leaf: false });
|
||||
}
|
||||
//we set the leaf to true
|
||||
const category = await this.categoryRepository.model.create(createDto);
|
||||
const category = await this.categoryRepository.model.create({ ...createDto, theme: theme._id });
|
||||
|
||||
return {
|
||||
message: CategoryMessage.Created,
|
||||
@@ -177,68 +175,44 @@ class CategoryService {
|
||||
//################################
|
||||
|
||||
async updateCategoryVariantS(updateDto: UpdateCategoryVariantDTO, id: string) {
|
||||
const session = await startSession();
|
||||
session.startTransaction();
|
||||
try {
|
||||
const { colors, sizes, meterages } = updateDto;
|
||||
const { themeId } = updateDto;
|
||||
|
||||
const category = await this.checkCategoryId(id);
|
||||
const category = await this.checkCategoryId(id);
|
||||
|
||||
// Ensure the theme matches the provided attributes and create variants
|
||||
const variantRefs = await this.createVariantsBasedOnTheme(session, category.theme, colors, sizes, meterages);
|
||||
|
||||
// Update the category with the theme and variants
|
||||
category.variants = variantRefs;
|
||||
await category.save({ session });
|
||||
|
||||
await session.commitTransaction();
|
||||
|
||||
return {
|
||||
message: CommonMessage.Updated,
|
||||
category,
|
||||
};
|
||||
} catch (error) {
|
||||
await session.abortTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await session.endSession();
|
||||
if (themeId) {
|
||||
const theme = await this.themeRepo.findById(themeId);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
category.theme = new Types.ObjectId(themeId);
|
||||
}
|
||||
|
||||
return {
|
||||
message: CommonMessage.Updated,
|
||||
category,
|
||||
};
|
||||
}
|
||||
//################################
|
||||
async createCategoryThemeS(createCategoryThemeDto: CreateCategoryThemeDTO, catId: string) {
|
||||
const session = await startSession();
|
||||
session.startTransaction();
|
||||
try {
|
||||
const { theme, colors, sizes, meterages } = createCategoryThemeDto;
|
||||
const { themeId } = createCategoryThemeDto;
|
||||
|
||||
// Validate category ID
|
||||
const category = await this.checkCategoryId(catId);
|
||||
|
||||
// Check if the theme already exists
|
||||
// if (category.theme) throw new BadRequestError(CategoryMessage.AlreadyExist);
|
||||
|
||||
// Ensure the theme matches the provided attributes and create variants
|
||||
const variantRefs = await this.createVariantsBasedOnTheme(session, theme, colors, sizes, meterages);
|
||||
|
||||
// Update the category with the theme and variants
|
||||
category.theme = theme;
|
||||
category.variants = variantRefs;
|
||||
await category.save({ session });
|
||||
|
||||
// Commit the transaction
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
|
||||
return {
|
||||
message: CategoryMessage.VariantCreated,
|
||||
variant: category,
|
||||
};
|
||||
} catch (error) {
|
||||
// Rollback any changes made in the transaction
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
throw error; // Re-throw the error to be handled by the calling errorHandler
|
||||
// Validate category ID
|
||||
const category = await this.checkCategoryId(catId);
|
||||
// Check if the theme already exists
|
||||
if (themeId) {
|
||||
const theme = await this.themeRepo.findById(themeId);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
}
|
||||
|
||||
// Update the category with the theme and variants
|
||||
if (themeId) {
|
||||
// Cast themeId to ObjectId if necessary to satisfy category.theme type
|
||||
category.theme = new Types.ObjectId(themeId);
|
||||
}
|
||||
await category.save();
|
||||
|
||||
return {
|
||||
message: CategoryMessage.VariantCreated,
|
||||
variant: category,
|
||||
};
|
||||
}
|
||||
|
||||
async createCategoryAttributeS(createAttributeDto: CreateCategoryAttDTO, categoryId: string) {
|
||||
@@ -481,61 +455,6 @@ class CategoryService {
|
||||
}
|
||||
|
||||
//Helper method to Create variants based on the provided theme
|
||||
public async createVariantsBasedOnTheme(
|
||||
session: ClientSession,
|
||||
theme: CategoryThemeEnum,
|
||||
colors?: CreateColorDTO[],
|
||||
sizes?: CreateSizeDTO[],
|
||||
meterages?: CreateMeterageDTO[],
|
||||
): Promise<number[]> {
|
||||
const variantRefs: number[] = [];
|
||||
|
||||
if (theme === CategoryThemeEnum.Colored) {
|
||||
if (colors && colors.length > 0) {
|
||||
await this.processVariants(this.colorRepo, "name", colors, session, variantRefs);
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.MissingColors);
|
||||
}
|
||||
} else if (theme === CategoryThemeEnum.Sized) {
|
||||
if (sizes && sizes.length > 0) {
|
||||
await this.processVariants(this.sizeRepo, "value", sizes, session, variantRefs);
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.MissingSizes);
|
||||
}
|
||||
} else if (theme === CategoryThemeEnum.Meterage) {
|
||||
if (meterages && meterages.length > 0) {
|
||||
await this.processVariants(this.meterageRepo, "value", meterages, session, variantRefs);
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.MissingMeterages);
|
||||
}
|
||||
} else if (theme === CategoryThemeEnum.No_color_No_sized) {
|
||||
return [];
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.InvalidTheme);
|
||||
}
|
||||
|
||||
return variantRefs;
|
||||
}
|
||||
|
||||
// Generic function to process create variants
|
||||
private async processVariants<V extends IColor | ISize | IMeterage, T>(
|
||||
repo: BaseRepository<V>,
|
||||
field: keyof T,
|
||||
items: T[],
|
||||
session: ClientSession,
|
||||
variantRefs: number[],
|
||||
) {
|
||||
for (const item of items) {
|
||||
const query = { [field]: item[field] } as FilterQuery<V>;
|
||||
const existingItem = await repo.model.exists({ ...query });
|
||||
if (existingItem) {
|
||||
variantRefs.push(existingItem._id);
|
||||
} else {
|
||||
const newItem = await repo.model.create([{ ...item }], { session });
|
||||
variantRefs.push(newItem[0]._id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { CategoryService };
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
import { CategoryThemeEnum } from "../../../../common/enums/category.enum";
|
||||
|
||||
export interface ICategory {
|
||||
_id: Types.ObjectId;
|
||||
title_fa: string;
|
||||
@@ -9,8 +7,8 @@ export interface ICategory {
|
||||
icon: string;
|
||||
imageUrl: string;
|
||||
description: string;
|
||||
theme: CategoryThemeEnum;
|
||||
variants: number[];
|
||||
theme: Types.ObjectId;
|
||||
variants: Types.ObjectId[];
|
||||
leaf: boolean;
|
||||
parent: Types.ObjectId;
|
||||
url: string;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface IColor {
|
||||
_id: number;
|
||||
name: string;
|
||||
hexColor: string;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface IMeterage {
|
||||
_id: number;
|
||||
value: string;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface ISize {
|
||||
_id: number;
|
||||
value: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export interface ITheme {
|
||||
_id: Types.ObjectId;
|
||||
title: string;
|
||||
inputType: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export interface IThemeValue {
|
||||
_id: Types.ObjectId;
|
||||
theme: Types.ObjectId;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { CallbackError, Schema, model } from "mongoose";
|
||||
|
||||
import { ICategory } from "./Abstraction/ICategory";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
|
||||
const categorySchema = new Schema<ICategory>(
|
||||
{
|
||||
@@ -10,8 +9,8 @@ const categorySchema = new Schema<ICategory>(
|
||||
icon: { type: String, default: "/images/icons/default.png" },
|
||||
imageUrl: { type: String, required: true },
|
||||
description: { type: String, required: true },
|
||||
theme: { type: String, enum: CategoryThemeEnum },
|
||||
variants: [{ type: Number, refPath: "theme" }],
|
||||
theme: { type: Schema.Types.ObjectId, ref: "Theme" },
|
||||
variants: [{ type: Schema.Types.ObjectId, ref: "ThemeValue" }],
|
||||
hierarchy: [{ type: Schema.Types.ObjectId, ref: "Category" }],
|
||||
leaf: { type: Boolean, default: true },
|
||||
parent: { type: Schema.Types.ObjectId, ref: "Category", default: null },
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { IColor } from "./Abstraction/IColor";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const ColorSchema = new Schema<IColor>(
|
||||
{
|
||||
_id: Number,
|
||||
name: { type: String, unique: true, required: true },
|
||||
hexColor: { type: String, unique: true, required: true },
|
||||
},
|
||||
{ timestamps: false, _id: false, toJSON: { versionKey: false } },
|
||||
);
|
||||
|
||||
ColorSchema.plugin(AutoIncrement, { id: "color_id", inc_field: "_id" });
|
||||
const ColorModel = model<IColor>(CategoryThemeEnum.Colored, ColorSchema);
|
||||
|
||||
export { ColorModel };
|
||||
@@ -1,20 +0,0 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { IMeterage } from "./Abstraction/IMeterage";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const MeterageSchema = new Schema<IMeterage>(
|
||||
{
|
||||
_id: Number,
|
||||
value: { type: String, unique: true, required: true },
|
||||
},
|
||||
{ timestamps: false, _id: false, toJSON: { versionKey: false } },
|
||||
);
|
||||
|
||||
MeterageSchema.plugin(AutoIncrement, { id: "meterage_id", inc_field: "_id" });
|
||||
const MeterageModel = model<IMeterage>(CategoryThemeEnum.Meterage, MeterageSchema);
|
||||
|
||||
export { MeterageModel };
|
||||
@@ -1,20 +0,0 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { ISize } from "./Abstraction/ISize";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const SizeSchema = new Schema<ISize>(
|
||||
{
|
||||
_id: Number,
|
||||
value: { type: String, unique: true, required: true },
|
||||
},
|
||||
{ timestamps: false, _id: false, toJSON: { versionKey: false } },
|
||||
);
|
||||
|
||||
SizeSchema.plugin(AutoIncrement, { id: "size_id", inc_field: "_id" });
|
||||
const SizeModel = model<ISize>(CategoryThemeEnum.Sized, SizeSchema);
|
||||
|
||||
export { SizeModel };
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Schema, model } from "mongoose";
|
||||
|
||||
import { ITheme } from "./Abstraction/ITheme";
|
||||
export enum InputTypeEnum {
|
||||
Text = "text",
|
||||
Number = "number",
|
||||
Color = "color",
|
||||
}
|
||||
const themeSchema = new Schema<ITheme>(
|
||||
{
|
||||
title: { type: String, required: true },
|
||||
inputType: { type: String, enum: InputTypeEnum, required: true },
|
||||
},
|
||||
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
|
||||
);
|
||||
|
||||
const ThemeModel = model<ITheme>("Theme", themeSchema);
|
||||
export { ThemeModel };
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Schema, model } from "mongoose";
|
||||
|
||||
import { IThemeValue } from "./Abstraction/IThemeValue";
|
||||
|
||||
const themeValueSchema = new Schema<IThemeValue>(
|
||||
{
|
||||
theme: { type: Schema.Types.ObjectId, ref: "Theme", required: true },
|
||||
name: { type: String, required: true },
|
||||
value: { type: Schema.Types.Mixed, required: true },
|
||||
},
|
||||
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
|
||||
);
|
||||
|
||||
const ThemeValueModel = model<IThemeValue>("ThemeValue", themeValueSchema);
|
||||
export { ThemeValueModel };
|
||||
@@ -0,0 +1,151 @@
|
||||
import { inject, injectable } from "inversify";
|
||||
import { isValidObjectId } from "mongoose";
|
||||
|
||||
import { ThemeRepository, ThemeValueRepository } from "./category.repository";
|
||||
import { CreateThemeDTO } from "./DTO/CreateTheme.dto";
|
||||
import { CreateThemeValueDTO } from "./DTO/CreateThemeValue.dto";
|
||||
import { UpdateThemeDTO } from "./DTO/UpdateTheme.dto";
|
||||
import { UpdateThemeValueDTO } from "./DTO/UpdateThemeValue.dto";
|
||||
import { CommonMessage } from "../../common/enums/message.enum";
|
||||
import { BadRequestError } from "../../core/app/app.errors";
|
||||
import { IOCTYPES } from "../../IOC/ioc.types";
|
||||
|
||||
@injectable()
|
||||
class ThemeService {
|
||||
@inject(IOCTYPES.ThemeRepository) private themeRepository: ThemeRepository;
|
||||
@inject(IOCTYPES.ThemeValueRepository) private themeValueRepository: ThemeValueRepository;
|
||||
|
||||
async getAllThemes() {
|
||||
return this.themeRepository.findAll();
|
||||
}
|
||||
|
||||
async getThemeById(id: string) {
|
||||
if (!isValidObjectId(id)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
const theme = await this.themeRepository.findById(id);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
return theme;
|
||||
}
|
||||
|
||||
async createTheme(createDto: CreateThemeDTO) {
|
||||
const existingTheme = await this.themeRepository.model.findOne({ title: createDto.title });
|
||||
if (existingTheme) throw new BadRequestError(CommonMessage.DuplicateName);
|
||||
|
||||
const theme = await this.themeRepository.model.create(createDto);
|
||||
return {
|
||||
message: CommonMessage.Created,
|
||||
theme,
|
||||
};
|
||||
}
|
||||
|
||||
async updateTheme(updateDto: UpdateThemeDTO) {
|
||||
if (!isValidObjectId(updateDto.themeId)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
|
||||
const existingTheme = await this.themeRepository.findById(updateDto.themeId);
|
||||
if (!existingTheme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
|
||||
const updateData: Partial<{ title: string; inputType: string }> = {};
|
||||
|
||||
if (updateDto.title) {
|
||||
const duplicateTheme = await this.themeRepository.model.findOne({
|
||||
title: updateDto.title,
|
||||
_id: { $ne: updateDto.themeId },
|
||||
});
|
||||
if (duplicateTheme) throw new BadRequestError(CommonMessage.DuplicateName);
|
||||
updateData.title = updateDto.title;
|
||||
}
|
||||
|
||||
if (updateDto.inputType) {
|
||||
updateData.inputType = updateDto.inputType;
|
||||
}
|
||||
|
||||
const updatedTheme = await this.themeRepository.model.findByIdAndUpdate(updateDto.themeId, updateData, { new: true });
|
||||
|
||||
return {
|
||||
message: CommonMessage.Updated,
|
||||
theme: updatedTheme,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteTheme(id: string) {
|
||||
if (!isValidObjectId(id)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
|
||||
const theme = await this.themeRepository.findById(id);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
|
||||
await this.themeRepository.delete(id);
|
||||
return {
|
||||
message: CommonMessage.Deleted,
|
||||
};
|
||||
}
|
||||
|
||||
//#########################################ThemeValue methods
|
||||
async getAllThemeValues() {
|
||||
return this.themeValueRepository.model.find().populate("theme");
|
||||
}
|
||||
|
||||
async getThemeValueById(id: string) {
|
||||
if (!isValidObjectId(id)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
const themeValue = await this.themeValueRepository.model.findById(id).populate("theme");
|
||||
if (!themeValue) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
return themeValue;
|
||||
}
|
||||
|
||||
async getThemeValuesByThemeId(themeId: string) {
|
||||
if (!isValidObjectId(themeId)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
const theme = await this.themeRepository.findById(themeId);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
return this.themeValueRepository.model.find({ theme: themeId }).populate("theme");
|
||||
}
|
||||
|
||||
async createThemeValue(createDto: CreateThemeValueDTO) {
|
||||
if (!isValidObjectId(createDto.theme)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
const theme = await this.themeRepository.findById(createDto.theme);
|
||||
if (!theme) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
|
||||
const themeValue = await this.themeValueRepository.model.create(createDto);
|
||||
return {
|
||||
message: CommonMessage.Created,
|
||||
themeValue,
|
||||
};
|
||||
}
|
||||
|
||||
async updateThemeValue(updateDto: UpdateThemeValueDTO) {
|
||||
if (!isValidObjectId(updateDto.themeValueId)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
|
||||
const existingThemeValue = await this.themeValueRepository.findById(updateDto.themeValueId);
|
||||
if (!existingThemeValue) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
|
||||
const updateData: Partial<{ name: string; value: string | number }> = {};
|
||||
|
||||
if (updateDto.name) {
|
||||
updateData.name = updateDto.name;
|
||||
}
|
||||
|
||||
if (updateDto.value !== undefined) {
|
||||
updateData.value = updateDto.value;
|
||||
}
|
||||
|
||||
const updatedThemeValue = await this.themeValueRepository.model
|
||||
.findByIdAndUpdate(updateDto.themeValueId, updateData, { new: true })
|
||||
.populate("theme");
|
||||
|
||||
return {
|
||||
message: CommonMessage.Updated,
|
||||
themeValue: updatedThemeValue,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteThemeValue(id: string) {
|
||||
if (!isValidObjectId(id)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
|
||||
const themeValue = await this.themeValueRepository.findById(id);
|
||||
if (!themeValue) throw new BadRequestError(CommonMessage.NotFoundById);
|
||||
|
||||
await this.themeValueRepository.delete(id);
|
||||
return {
|
||||
message: CommonMessage.Deleted,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { ThemeService };
|
||||
Reference in New Issue
Block a user