cart
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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'] });
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user