From 5fb1c63c4c44a2fa94bf5c03a5eaaea39b9b70e6 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 10 Feb 2026 12:00:07 +0330 Subject: [PATCH] cart --- src/modules/cart/dto/bulk-add-items.dto.ts | 6 +++--- src/modules/cart/dto/create-cart.dto.ts | 2 +- src/modules/cart/interfaces/cart.interface.ts | 2 +- .../cart/providers/cart-calculation.service.ts | 8 ++++---- src/modules/cart/providers/cart-item.service.ts | 8 ++++---- .../cart/providers/cart-validation.service.ts | 4 ++-- src/modules/cart/providers/cart.service.ts | 2 +- src/modules/orders/providers/orders.service.ts | 2 +- src/modules/products/dto/create-product.dto.ts | 8 +++++++- src/modules/products/providers/product.service.ts | 14 +++++++------- 10 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/modules/cart/dto/bulk-add-items.dto.ts b/src/modules/cart/dto/bulk-add-items.dto.ts index 14c92eb..7b8f434 100644 --- a/src/modules/cart/dto/bulk-add-items.dto.ts +++ b/src/modules/cart/dto/bulk-add-items.dto.ts @@ -13,7 +13,7 @@ class AddItemToCartDto { @ApiProperty({ description: 'Product ID' }) @IsNotEmpty() @IsString() - productId!: string; + variantId!: string; } export class BulkAddItemsToCartDto { @@ -21,8 +21,8 @@ export class BulkAddItemsToCartDto { description: 'Array of items to add to cart', type: [AddItemToCartDto], example: [ - { productId: 'product-123', quantity: 2 }, - { productId: 'product-456', quantity: 1 }, + { variantId: 'product-123', quantity: 2 }, + { productId: 'product-456', quantity: 1 }, ], }) @IsNotEmpty() diff --git a/src/modules/cart/dto/create-cart.dto.ts b/src/modules/cart/dto/create-cart.dto.ts index e263c9d..e00b66b 100644 --- a/src/modules/cart/dto/create-cart.dto.ts +++ b/src/modules/cart/dto/create-cart.dto.ts @@ -6,7 +6,7 @@ export class CartItemDto { @ApiProperty({ description: 'Product ID' }) @IsNotEmpty() @IsString() - foodId!: string; + variantId!: string; @ApiProperty({ description: 'Quantity of the product item', example: 2, minimum: 1 }) @IsNotEmpty() diff --git a/src/modules/cart/interfaces/cart.interface.ts b/src/modules/cart/interfaces/cart.interface.ts index 9d2771f..200a199 100644 --- a/src/modules/cart/interfaces/cart.interface.ts +++ b/src/modules/cart/interfaces/cart.interface.ts @@ -1,7 +1,7 @@ import type { OrderCouponDetail, OrderUserAddress } from 'src/modules/orders/interface/order.interface'; export interface CartItem { - productId: string; + variantId: string; productTitle?: string; quantity: number; price: number; diff --git a/src/modules/cart/providers/cart-calculation.service.ts b/src/modules/cart/providers/cart-calculation.service.ts index c20f6af..b211d5a 100644 --- a/src/modules/cart/providers/cart-calculation.service.ts +++ b/src/modules/cart/providers/cart-calculation.service.ts @@ -71,7 +71,7 @@ export class CartCalculationService { const foodMap = await this.getFoodsInCartWithCategories(cart); for (const item of cart.items) { - const product = foodMap.get(item.productId); + const product = foodMap.get(item.variantId); if (!product) continue; const matchesCategory = @@ -227,10 +227,10 @@ export class CartCalculationService { * Get products in cart with categories */ private async getFoodsInCartWithCategories(cart: Cart): Promise> { - const foodIds = cart.items.map(item => item.productId); - if (foodIds.length === 0) return new Map(); + const variantIds = cart.items.map(item => item.variantId); + if (variantIds.length === 0) return new Map(); - const products = await this.em.find(Product, { id: { $in: foodIds } }, { populate: ['category'] }); + const products = await this.em.find(Product, { id: { $in: variantIds } }, { populate: ['category'] }); return new Map(products.map(f => [f.id, f])); } diff --git a/src/modules/cart/providers/cart-item.service.ts b/src/modules/cart/providers/cart-item.service.ts index e976719..62d2d59 100644 --- a/src/modules/cart/providers/cart-item.service.ts +++ b/src/modules/cart/providers/cart-item.service.ts @@ -22,7 +22,7 @@ export class CartItemService { const itemDiscount = product.discount || 0; return { - productId: product.id, + variantId: product.id, productTitle: product.title, quantity, price: itemPrice, @@ -39,7 +39,7 @@ export class CartItemService { const itemDiscount = product.discount || 0; return { ...(previous ?? ({} as CartItem)), - productId: product.id, + variantId: product.id, productTitle: product.title, quantity, price: itemPrice, @@ -51,8 +51,8 @@ export class CartItemService { /** * Get item index in cart */ - getItemIndex(cart: Cart, productId: string): number { - return cart.items.findIndex(item => item.productId === productId); + getItemIndex(cart: Cart, variantId: string): number { + return cart.items.findIndex(item => item.variantId === variantId); } /** diff --git a/src/modules/cart/providers/cart-validation.service.ts b/src/modules/cart/providers/cart-validation.service.ts index 6e79b66..c522ec9 100644 --- a/src/modules/cart/providers/cart-validation.service.ts +++ b/src/modules/cart/providers/cart-validation.service.ts @@ -175,7 +175,7 @@ export class CartValidationService { const foodMap = await this.getFoodsInCartWithCategories(cart); for (const item of cart.items) { - const product = foodMap.get(item.productId); + const product = foodMap.get(item.variantId); if (!product) continue; const matchesCategory = hasCategoryRestriction && product.category && categoryRestriction.includes(product.category.id); @@ -190,7 +190,7 @@ export class CartValidationService { * Get products in cart with categories */ async getFoodsInCartWithCategories(cart: Cart): Promise> { - const productIds = cart.items.map(item => item.productId); + const productIds = cart.items.map(item => item.variantId); if (productIds.length === 0) return new Map(); const products = await this.em.find(Product, { id: { $in: productIds } }, { populate: ['category'] }); diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index 6900421..623da25 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -178,7 +178,7 @@ export class CartService { const cart = await this.getOrCreateCart(userId, shopId); for (const addItemDto of bulkAddItemsDto.items) { - await this.itemService.addOrIncrementItem(cart, addItemDto.productId, shopId, addItemDto.quantity); + await this.itemService.addOrIncrementItem(cart, addItemDto.variantId, shopId, addItemDto.quantity); } return this.recalculateAndSaveCart(cart); diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index e34f3de..2ddd991 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -358,7 +358,7 @@ export class OrdersService { const orderItemsData: OrderItemData[] = []; for (const cartItem of cart.items) { - const variant = await this.em.findOne(Variant, { id: cartItem.productId }, { populate: ['product', 'product.shop'] }); + const variant = await this.em.findOne(Variant, { id: cartItem.variantId }, { populate: ['product', 'product.shop'] }); if (!variant) throw new NotFoundException(OrderMessage.PRODUCT_NOT_FOUND); if (variant.product.shop.id !== shopId) { diff --git a/src/modules/products/dto/create-product.dto.ts b/src/modules/products/dto/create-product.dto.ts index bdf8507..126e886 100644 --- a/src/modules/products/dto/create-product.dto.ts +++ b/src/modules/products/dto/create-product.dto.ts @@ -8,8 +8,8 @@ import { IsNumber, IsOptional, IsString, - Max, Min, + Length, } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; @@ -21,9 +21,14 @@ export class CreateVariantDto { @IsString() @ApiProperty() + @IsOptional() + @IsNotEmpty() value: string @IsNumber() + @IsInt() + @Min(1000) + @IsNotEmpty() @ApiProperty() price: number @@ -46,6 +51,7 @@ export class CreateProductDto { attribute?: string; @IsOptional() + @Length(1, 100) @ApiPropertyOptional({ type: CreateVariantDto, example: [{ diff --git a/src/modules/products/providers/product.service.ts b/src/modules/products/providers/product.service.ts index b357aa4..92ff00f 100644 --- a/src/modules/products/providers/product.service.ts +++ b/src/modules/products/providers/product.service.ts @@ -22,13 +22,17 @@ export class ProductService { private readonly shopService: ShopService, private readonly em: EntityManager, ) { } - + // TODO : Messages must be in persian async create(shopId: string, dto: CreateProductDto) { const { categoryId, variants, ...rest } = dto; const shop = await this.shopService.findOrFail(shopId); const category = await this.categoryService.findOrFail(categoryId) + if (variants.length > 1 && !dto.attribute) { + throw new BadRequestException('Attribute is required for multiple variants'); + } + const product = await this.em.transactional(async em => { // prepare data with defaults for required fields so repository.create typing is satisfied const data: RequiredEntityData = { @@ -62,8 +66,6 @@ export class ProductService { return product; }); - - return product; } @@ -96,15 +98,13 @@ export class ProductService { async findByShop(slug: string): Promise { - const shop = await this.shopService.findOrFailBySlug(slug); - const queryFilter: FilterQuery = { shop: { slug }, isActive: true, } as unknown as FilterQuery; return this.productRepository.find(queryFilter, { - populate: ['category'], + populate: ['category', 'variants'], orderBy: { order: 'asc' } }); } @@ -199,7 +199,7 @@ export class ProductService { } const userRef = this.em.getReference(User, userId); const productRef = this.em.getReference(Product, productId); - + const newFavorite = this.em.create(Favorite, { user: userRef, product: productRef,