This commit is contained in:
2026-02-10 12:00:07 +03:30
parent 280815dce3
commit 5fb1c63c4c
10 changed files with 31 additions and 25 deletions
+3 -3
View File
@@ -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()
+1 -1
View File
@@ -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()
@@ -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;
@@ -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<Map<string, Product>> {
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]));
}
@@ -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);
}
/**
@@ -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<Map<string, Product>> {
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'] });
+1 -1
View File
@@ -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);
@@ -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) {
@@ -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: [{
@@ -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<Product> = {
@@ -62,8 +66,6 @@ export class ProductService {
return product;
});
return product;
}
@@ -96,15 +98,13 @@ export class ProductService {
async findByShop(slug: string): Promise<Product[]> {
const shop = await this.shopService.findOrFailBySlug(slug);
const queryFilter: FilterQuery<Product> = {
shop: { slug },
isActive: true,
} as unknown as FilterQuery<Product>;
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,