From 4093e3863e1fc28cb95c03d2e6aaa99d82918f74 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Mon, 1 Dec 2025 11:31:09 +0330 Subject: [PATCH] update cart --- .../cart/controllers/cart.controller.ts | 38 ++++++---- src/modules/cart/dto/add-item.dto.ts | 13 ++-- src/modules/cart/dto/bulk-add-items.dto.ts | 1 - src/modules/cart/interfaces/cart.interface.ts | 1 - src/modules/cart/providers/cart.service.ts | 74 +++++++++++++++---- 5 files changed, 90 insertions(+), 37 deletions(-) diff --git a/src/modules/cart/controllers/cart.controller.ts b/src/modules/cart/controllers/cart.controller.ts index 2e815a8..d00ddbe 100644 --- a/src/modules/cart/controllers/cart.controller.ts +++ b/src/modules/cart/controllers/cart.controller.ts @@ -1,6 +1,5 @@ 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'; @@ -26,14 +25,23 @@ export class CartController { return this.cartService.findOne(userId, restaurantId); } - @Post('items') - @ApiOperation({ summary: 'Add item to cart (increments quantity if item exists)' }) - @ApiBody({ type: AddItemToCartDto }) - @ApiResponse({ status: 201, description: 'Item added to cart successfully' }) + @Post('items/:foodId/increment') + @ApiOperation({ summary: 'Increment item quantity in cart by 1' }) + @ApiParam({ name: 'foodId', description: 'Food ID to increment in cart' }) + @ApiResponse({ status: 201, description: 'Item incremented in cart successfully' }) @ApiResponse({ status: 400, description: 'Bad request (e.g., insufficient stock)' }) @ApiResponse({ status: 404, description: 'Food not found' }) - async addItem(@UserId() userId: string, @RestId() restaurantId: string, @Body() addItemDto: AddItemToCartDto) { - return this.cartService.addItem(userId, restaurantId, addItemDto); + async incrementItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) { + return this.cartService.incrementItem(userId, restaurantId, foodId); + } + + @Post('items/:foodId/decrement') + @ApiOperation({ summary: 'Decrement item quantity in cart by 1 (removes item if quantity reaches 0)' }) + @ApiParam({ name: 'foodId', description: 'Food ID to decrement in cart' }) + @ApiResponse({ status: 200, description: 'Item decremented in cart successfully' }) + @ApiResponse({ status: 404, description: 'Item not found in cart' }) + async decrementItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) { + return this.cartService.decrementItem(userId, restaurantId, foodId); } @Post('items/bulk') @@ -50,9 +58,9 @@ export class CartController { return this.cartService.bulkAddItems(userId, restaurantId, bulkAddItemsDto); } - @Patch('items/:itemId') + @Patch('items/:foodId') @ApiOperation({ summary: 'Update item quantity in cart' }) - @ApiParam({ name: 'itemId', description: 'Item ID in the cart' }) + @ApiParam({ name: 'foodId', description: 'Food ID in the cart' }) @ApiBody({ type: UpdateItemQuantityDto }) @ApiResponse({ status: 200, description: 'Item quantity updated successfully' }) @ApiResponse({ status: 400, description: 'Bad request (e.g., insufficient stock)' }) @@ -60,19 +68,19 @@ export class CartController { async updateItemQuantity( @UserId() userId: string, @RestId() restaurantId: string, - @Param('itemId') itemId: string, + @Param('foodId') foodId: string, @Body() updateItemDto: UpdateItemQuantityDto, ) { - return this.cartService.updateItemQuantity(userId, restaurantId, itemId, updateItemDto); + return this.cartService.updateItemQuantity(userId, restaurantId, foodId, updateItemDto); } - @Delete('items/:itemId') + @Delete('items/:foodId') @ApiOperation({ summary: 'Remove item from cart' }) - @ApiParam({ name: 'itemId', description: 'Item ID in the cart' }) + @ApiParam({ name: 'foodId', description: 'Food ID in the cart' }) @ApiResponse({ status: 200, description: 'Item removed from cart successfully' }) @ApiResponse({ status: 404, description: 'Item not found in cart' }) - async removeItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('itemId') itemId: string) { - return this.cartService.removeItem(userId, restaurantId, itemId); + async removeItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) { + return this.cartService.removeItem(userId, restaurantId, foodId); } @Post('apply-coupon') diff --git a/src/modules/cart/dto/add-item.dto.ts b/src/modules/cart/dto/add-item.dto.ts index ec8f99d..154845b 100644 --- a/src/modules/cart/dto/add-item.dto.ts +++ b/src/modules/cart/dto/add-item.dto.ts @@ -1,16 +1,15 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsNumber, Min } from 'class-validator'; +import { IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; export class AddItemToCartDto { - @ApiProperty({ description: 'Food ID' }) - @IsNotEmpty() - @IsString() - foodId!: string; - @ApiProperty({ description: 'Quantity of the food item', example: 1, minimum: 1 }) @IsNotEmpty() @IsNumber() @Min(1) quantity!: number; -} + @ApiProperty({ description: 'Food ID' }) + @IsNotEmpty() + @IsString() + foodId!: string; +} diff --git a/src/modules/cart/dto/bulk-add-items.dto.ts b/src/modules/cart/dto/bulk-add-items.dto.ts index 0f6bae1..783688b 100644 --- a/src/modules/cart/dto/bulk-add-items.dto.ts +++ b/src/modules/cart/dto/bulk-add-items.dto.ts @@ -19,4 +19,3 @@ export class BulkAddItemsToCartDto { @Type(() => AddItemToCartDto) items!: AddItemToCartDto[]; } - diff --git a/src/modules/cart/interfaces/cart.interface.ts b/src/modules/cart/interfaces/cart.interface.ts index 9cfa5f4..2d031b1 100644 --- a/src/modules/cart/interfaces/cart.interface.ts +++ b/src/modules/cart/interfaces/cart.interface.ts @@ -1,5 +1,4 @@ export interface CartItem { - itemId: string; foodId: string; foodTitle?: string; quantity: number; diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index 6624ddb..f6b92cb 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -3,7 +3,6 @@ import { Food } from '../../foods/entities/food.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity'; 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'; @@ -76,13 +75,13 @@ export class CartService { /** * Add item to cart (increment quantity if item exists, otherwise create new) */ - async addItem(userId: string, restaurantId: string, addItemDto: AddItemToCartDto): Promise { + async addItem(userId: string, restaurantId: string, foodId: string, addItemDto: AddItemToCartDto): Promise { const cart = await this.getOrCreateCart(userId, restaurantId); // Find food - const food = await this.em.findOne(Food, { id: addItemDto.foodId }, { populate: ['restaurant'] }); + const food = await this.em.findOne(Food, { id: foodId }, { populate: ['restaurant'] }); if (!food) { - throw new NotFoundException(`Food with ID ${addItemDto.foodId} not found`); + throw new NotFoundException(`Food with ID ${foodId} not found`); } // Check if food belongs to the restaurant @@ -96,7 +95,7 @@ export class CartService { } // Check if item already exists in cart - const existingItemIndex = cart.items.findIndex(item => item.foodId === addItemDto.foodId); + const existingItemIndex = cart.items.findIndex(item => item.foodId === foodId); if (existingItemIndex >= 0) { // Update existing item quantity @@ -128,7 +127,6 @@ export class CartService { const itemFinalPrice = itemTotalPrice - itemTotalDiscount; const cartItem: CartItem = { - itemId: ulid(), foodId: food.id, foodTitle: food.title, quantity: addItemDto.quantity, @@ -147,6 +145,57 @@ export class CartService { return cart; } + /** + * Increment item quantity in cart by 1 + */ + async incrementItem(userId: string, restaurantId: string, foodId: string): Promise { + return this.addItem(userId, restaurantId, foodId, { foodId, quantity: 1 }); + } + + /** + * Decrement item quantity in cart by 1 (removes item if quantity reaches 0) + */ + async decrementItem(userId: string, restaurantId: string, foodId: string): Promise { + const cart = await this.findOne(userId, restaurantId); + + const itemIndex = cart.items.findIndex(item => item.foodId === foodId); + if (itemIndex < 0) { + throw new NotFoundException(`Item with food ID ${foodId} not found in cart`); + } + + const existingItem = cart.items[itemIndex]; + const newQuantity = existingItem.quantity - 1; + + if (newQuantity <= 0) { + // Remove item if quantity becomes 0 or less + cart.items.splice(itemIndex, 1); + } else { + // Update item quantity and recalculate price + const food = await this.em.findOne(Food, { id: foodId }); + if (!food) { + throw new NotFoundException(`Food with ID ${foodId} not found`); + } + + const itemPrice = food.price || 0; + const itemDiscount = food.discount || 0; + const itemTotalPrice = itemPrice * newQuantity; + const itemTotalDiscount = itemDiscount * newQuantity; + const itemFinalPrice = itemTotalPrice - itemTotalDiscount; + + cart.items[itemIndex] = { + ...existingItem, + quantity: newQuantity, + totalPrice: itemFinalPrice, + }; + } + + // Recalculate cart totals + await this.recalculateCartTotals(cart); + await this.saveCart(cart); + + return cart; + } + /** * Bulk add items to cart (increments quantity if items exist) */ @@ -204,7 +253,6 @@ export class CartService { const itemFinalPrice = itemTotalPrice - itemTotalDiscount; const cartItem: CartItem = { - itemId: ulid(), foodId: food.id, foodTitle: food.title, quantity: addItemDto.quantity, @@ -230,14 +278,14 @@ export class CartService { async updateItemQuantity( userId: string, restaurantId: string, - itemId: string, + foodId: string, updateItemDto: UpdateItemQuantityDto, ): Promise { const cart = await this.findOne(userId, restaurantId); - const itemIndex = cart.items.findIndex(item => item.itemId === itemId); + const itemIndex = cart.items.findIndex(item => item.foodId === foodId); if (itemIndex < 0) { - throw new NotFoundException(`Item with ID ${itemId} not found in cart`); + throw new NotFoundException(`Item with food ID ${foodId} not found in cart`); } const item = cart.items[itemIndex]; @@ -276,12 +324,12 @@ export class CartService { /** * Remove item from cart */ - async removeItem(userId: string, restaurantId: string, itemId: string): Promise { + async removeItem(userId: string, restaurantId: string, foodId: string): Promise { const cart = await this.findOne(userId, restaurantId); - const itemIndex = cart.items.findIndex(item => item.itemId === itemId); + const itemIndex = cart.items.findIndex(item => item.foodId === foodId); if (itemIndex < 0) { - throw new NotFoundException(`Item with ID ${itemId} not found in cart`); + throw new NotFoundException(`Item with food ID ${foodId} not found in cart`); } // Remove item