diff --git a/src/modules/cart/controllers/cart.controller.ts b/src/modules/cart/controllers/cart.controller.ts index 884b77c..2e815a8 100644 --- a/src/modules/cart/controllers/cart.controller.ts +++ b/src/modules/cart/controllers/cart.controller.ts @@ -1,6 +1,7 @@ import { Controller, Get, Post, Body, Patch, Delete, UseGuards, Param } from '@nestjs/common'; import { CartService } from '../providers/cart.service'; import { AddItemToCartDto } from '../dto/add-item.dto'; +import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto'; import { UpdateItemQuantityDto } from '../dto/update-item.dto'; import { ApplyCouponDto } from '../dto/apply-coupon.dto'; import { SetAddressDto } from '../dto/set-address.dto'; @@ -35,6 +36,20 @@ export class CartController { return this.cartService.addItem(userId, restaurantId, addItemDto); } + @Post('items/bulk') + @ApiOperation({ summary: 'Bulk add items to cart (increments quantity if items exist)' }) + @ApiBody({ type: BulkAddItemsToCartDto }) + @ApiResponse({ status: 201, description: 'Items added to cart successfully' }) + @ApiResponse({ status: 400, description: 'Bad request (e.g., insufficient stock, invalid items)' }) + @ApiResponse({ status: 404, description: 'Food not found' }) + bulkAddItems( + @UserId() userId: string, + @RestId() restaurantId: string, + @Body() bulkAddItemsDto: BulkAddItemsToCartDto, + ) { + return this.cartService.bulkAddItems(userId, restaurantId, bulkAddItemsDto); + } + @Patch('items/:itemId') @ApiOperation({ summary: 'Update item quantity in cart' }) @ApiParam({ name: 'itemId', description: 'Item ID in the cart' }) diff --git a/src/modules/cart/dto/bulk-add-items.dto.ts b/src/modules/cart/dto/bulk-add-items.dto.ts new file mode 100644 index 0000000..0f6bae1 --- /dev/null +++ b/src/modules/cart/dto/bulk-add-items.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsArray, ValidateNested, ArrayMinSize } from 'class-validator'; +import { Type } from 'class-transformer'; +import { AddItemToCartDto } from './add-item.dto'; + +export class BulkAddItemsToCartDto { + @ApiProperty({ + description: 'Array of items to add to cart', + type: [AddItemToCartDto], + example: [ + { foodId: 'food-123', quantity: 2 }, + { foodId: 'food-456', quantity: 1 }, + ], + }) + @IsNotEmpty() + @IsArray() + @ArrayMinSize(1, { message: 'At least one item is required' }) + @ValidateNested({ each: true }) + @Type(() => AddItemToCartDto) + items!: AddItemToCartDto[]; +} + diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index 028fd6b..8a09055 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -5,6 +5,7 @@ import { EntityManager } from '@mikro-orm/postgresql'; import { CacheService } from '../../utils/cache.service'; import { ulid } from 'ulid'; import { AddItemToCartDto } from '../dto/add-item.dto'; +import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto'; import { UpdateItemQuantityDto } from '../dto/update-item.dto'; import { ApplyCouponDto } from '../dto/apply-coupon.dto'; import { SetAddressDto } from '../dto/set-address.dto'; @@ -145,6 +146,83 @@ export class CartService { return cart; } + /** + * Bulk add items to cart (increments quantity if items exist) + */ + async bulkAddItems(userId: string, restaurantId: string, bulkAddItemsDto: BulkAddItemsToCartDto): Promise { + const cart = await this.getOrCreateCart(userId, restaurantId); + + // Process each item + for (const addItemDto of bulkAddItemsDto.items) { + // Find food + const food = await this.em.findOne(Food, { id: addItemDto.foodId }, { populate: ['restaurant'] }); + if (!food) { + throw new NotFoundException(`Food with ID ${addItemDto.foodId} not found`); + } + + // Check if food belongs to the restaurant + if (food.restaurant.id !== restaurantId) { + throw new BadRequestException(`Food ${food.title || food.id} does not belong to restaurant ${restaurantId}`); + } + + // Check stock availability + if (food.stock < addItemDto.quantity) { + throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${food.stock}`); + } + + // Check if item already exists in cart + const existingItemIndex = cart.items.findIndex(item => item.foodId === addItemDto.foodId); + + if (existingItemIndex >= 0) { + // Update existing item quantity + const existingItem = cart.items[existingItemIndex]; + const newQuantity = existingItem.quantity + addItemDto.quantity; + + // Check stock for new total quantity + if (food.stock < newQuantity) { + throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${food.stock}`); + } + + const itemPrice = food.price || 0; + const itemDiscount = food.discount || 0; + const itemTotalPrice = itemPrice * newQuantity; + const itemTotalDiscount = itemDiscount * newQuantity; + const itemFinalPrice = itemTotalPrice - itemTotalDiscount; + + cart.items[existingItemIndex] = { + ...existingItem, + quantity: newQuantity, + totalPrice: itemFinalPrice, + }; + } else { + // Create new item + const itemPrice = food.price || 0; + const itemDiscount = food.discount || 0; + const itemTotalPrice = itemPrice * addItemDto.quantity; + const itemTotalDiscount = itemDiscount * addItemDto.quantity; + const itemFinalPrice = itemTotalPrice - itemTotalDiscount; + + const cartItem: CartItem = { + itemId: ulid(), + foodId: food.id, + foodTitle: food.title, + quantity: addItemDto.quantity, + price: itemPrice, + discount: itemDiscount, + totalPrice: itemFinalPrice, + }; + + cart.items.push(cartItem); + } + } + + // Recalculate cart totals once after all items are added + await this.recalculateCartTotals(cart); + await this.saveCart(cart); + + return cart; + } + /** * Update item quantity in cart */