update cart
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { Controller, Get, Post, Body, Patch, Delete, UseGuards, Param } from '@nestjs/common';
|
import { Controller, Get, Post, Body, Patch, Delete, UseGuards, Param } from '@nestjs/common';
|
||||||
import { CartService } from '../providers/cart.service';
|
import { CartService } from '../providers/cart.service';
|
||||||
import { AddItemToCartDto } from '../dto/add-item.dto';
|
|
||||||
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
|
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
|
||||||
import { UpdateItemQuantityDto } from '../dto/update-item.dto';
|
import { UpdateItemQuantityDto } from '../dto/update-item.dto';
|
||||||
import { ApplyCouponDto } from '../dto/apply-coupon.dto';
|
import { ApplyCouponDto } from '../dto/apply-coupon.dto';
|
||||||
@@ -26,14 +25,23 @@ export class CartController {
|
|||||||
return this.cartService.findOne(userId, restaurantId);
|
return this.cartService.findOne(userId, restaurantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('items')
|
@Post('items/:foodId/increment')
|
||||||
@ApiOperation({ summary: 'Add item to cart (increments quantity if item exists)' })
|
@ApiOperation({ summary: 'Increment item quantity in cart by 1' })
|
||||||
@ApiBody({ type: AddItemToCartDto })
|
@ApiParam({ name: 'foodId', description: 'Food ID to increment in cart' })
|
||||||
@ApiResponse({ status: 201, description: 'Item added to cart successfully' })
|
@ApiResponse({ status: 201, description: 'Item incremented in cart successfully' })
|
||||||
@ApiResponse({ status: 400, description: 'Bad request (e.g., insufficient stock)' })
|
@ApiResponse({ status: 400, description: 'Bad request (e.g., insufficient stock)' })
|
||||||
@ApiResponse({ status: 404, description: 'Food not found' })
|
@ApiResponse({ status: 404, description: 'Food not found' })
|
||||||
async addItem(@UserId() userId: string, @RestId() restaurantId: string, @Body() addItemDto: AddItemToCartDto) {
|
async incrementItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) {
|
||||||
return this.cartService.addItem(userId, restaurantId, addItemDto);
|
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')
|
@Post('items/bulk')
|
||||||
@@ -50,9 +58,9 @@ export class CartController {
|
|||||||
return this.cartService.bulkAddItems(userId, restaurantId, bulkAddItemsDto);
|
return this.cartService.bulkAddItems(userId, restaurantId, bulkAddItemsDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch('items/:itemId')
|
@Patch('items/:foodId')
|
||||||
@ApiOperation({ summary: 'Update item quantity in cart' })
|
@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 })
|
@ApiBody({ type: UpdateItemQuantityDto })
|
||||||
@ApiResponse({ status: 200, description: 'Item quantity updated successfully' })
|
@ApiResponse({ status: 200, description: 'Item quantity updated successfully' })
|
||||||
@ApiResponse({ status: 400, description: 'Bad request (e.g., insufficient stock)' })
|
@ApiResponse({ status: 400, description: 'Bad request (e.g., insufficient stock)' })
|
||||||
@@ -60,19 +68,19 @@ export class CartController {
|
|||||||
async updateItemQuantity(
|
async updateItemQuantity(
|
||||||
@UserId() userId: string,
|
@UserId() userId: string,
|
||||||
@RestId() restaurantId: string,
|
@RestId() restaurantId: string,
|
||||||
@Param('itemId') itemId: string,
|
@Param('foodId') foodId: string,
|
||||||
@Body() updateItemDto: UpdateItemQuantityDto,
|
@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' })
|
@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: 200, description: 'Item removed from cart successfully' })
|
||||||
@ApiResponse({ status: 404, description: 'Item not found in cart' })
|
@ApiResponse({ status: 404, description: 'Item not found in cart' })
|
||||||
async removeItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('itemId') itemId: string) {
|
async removeItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) {
|
||||||
return this.cartService.removeItem(userId, restaurantId, itemId);
|
return this.cartService.removeItem(userId, restaurantId, foodId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('apply-coupon')
|
@Post('apply-coupon')
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsNotEmpty, IsString, IsNumber, Min } from 'class-validator';
|
import { IsNotEmpty, IsNumber, IsString, Min } from 'class-validator';
|
||||||
|
|
||||||
export class AddItemToCartDto {
|
export class AddItemToCartDto {
|
||||||
@ApiProperty({ description: 'Food ID' })
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
foodId!: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'Quantity of the food item', example: 1, minimum: 1 })
|
@ApiProperty({ description: 'Quantity of the food item', example: 1, minimum: 1 })
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
quantity!: number;
|
quantity!: number;
|
||||||
}
|
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Food ID' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
foodId!: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,4 +19,3 @@ export class BulkAddItemsToCartDto {
|
|||||||
@Type(() => AddItemToCartDto)
|
@Type(() => AddItemToCartDto)
|
||||||
items!: AddItemToCartDto[];
|
items!: AddItemToCartDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export interface CartItem {
|
export interface CartItem {
|
||||||
itemId: string;
|
|
||||||
foodId: string;
|
foodId: string;
|
||||||
foodTitle?: string;
|
foodTitle?: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Food } from '../../foods/entities/food.entity';
|
|||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
import { EntityManager } from '@mikro-orm/postgresql';
|
||||||
import { CacheService } from '../../utils/cache.service';
|
import { CacheService } from '../../utils/cache.service';
|
||||||
import { ulid } from 'ulid';
|
|
||||||
import { AddItemToCartDto } from '../dto/add-item.dto';
|
import { AddItemToCartDto } from '../dto/add-item.dto';
|
||||||
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
|
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
|
||||||
import { UpdateItemQuantityDto } from '../dto/update-item.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)
|
* Add item to cart (increment quantity if item exists, otherwise create new)
|
||||||
*/
|
*/
|
||||||
async addItem(userId: string, restaurantId: string, addItemDto: AddItemToCartDto): Promise<Cart> {
|
async addItem(userId: string, restaurantId: string, foodId: string, addItemDto: AddItemToCartDto): Promise<Cart> {
|
||||||
const cart = await this.getOrCreateCart(userId, restaurantId);
|
const cart = await this.getOrCreateCart(userId, restaurantId);
|
||||||
|
|
||||||
// Find food
|
// 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) {
|
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
|
// Check if food belongs to the restaurant
|
||||||
@@ -96,7 +95,7 @@ export class CartService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if item already exists in cart
|
// 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) {
|
if (existingItemIndex >= 0) {
|
||||||
// Update existing item quantity
|
// Update existing item quantity
|
||||||
@@ -128,7 +127,6 @@ export class CartService {
|
|||||||
const itemFinalPrice = itemTotalPrice - itemTotalDiscount;
|
const itemFinalPrice = itemTotalPrice - itemTotalDiscount;
|
||||||
|
|
||||||
const cartItem: CartItem = {
|
const cartItem: CartItem = {
|
||||||
itemId: ulid(),
|
|
||||||
foodId: food.id,
|
foodId: food.id,
|
||||||
foodTitle: food.title,
|
foodTitle: food.title,
|
||||||
quantity: addItemDto.quantity,
|
quantity: addItemDto.quantity,
|
||||||
@@ -147,6 +145,57 @@ export class CartService {
|
|||||||
return cart;
|
return cart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment item quantity in cart by 1
|
||||||
|
*/
|
||||||
|
async incrementItem(userId: string, restaurantId: string, foodId: string): Promise<Cart> {
|
||||||
|
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<Cart> {
|
||||||
|
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)
|
* Bulk add items to cart (increments quantity if items exist)
|
||||||
*/
|
*/
|
||||||
@@ -204,7 +253,6 @@ export class CartService {
|
|||||||
const itemFinalPrice = itemTotalPrice - itemTotalDiscount;
|
const itemFinalPrice = itemTotalPrice - itemTotalDiscount;
|
||||||
|
|
||||||
const cartItem: CartItem = {
|
const cartItem: CartItem = {
|
||||||
itemId: ulid(),
|
|
||||||
foodId: food.id,
|
foodId: food.id,
|
||||||
foodTitle: food.title,
|
foodTitle: food.title,
|
||||||
quantity: addItemDto.quantity,
|
quantity: addItemDto.quantity,
|
||||||
@@ -230,14 +278,14 @@ export class CartService {
|
|||||||
async updateItemQuantity(
|
async updateItemQuantity(
|
||||||
userId: string,
|
userId: string,
|
||||||
restaurantId: string,
|
restaurantId: string,
|
||||||
itemId: string,
|
foodId: string,
|
||||||
updateItemDto: UpdateItemQuantityDto,
|
updateItemDto: UpdateItemQuantityDto,
|
||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
const cart = await this.findOne(userId, restaurantId);
|
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) {
|
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];
|
const item = cart.items[itemIndex];
|
||||||
@@ -276,12 +324,12 @@ export class CartService {
|
|||||||
/**
|
/**
|
||||||
* Remove item from cart
|
* Remove item from cart
|
||||||
*/
|
*/
|
||||||
async removeItem(userId: string, restaurantId: string, itemId: string): Promise<Cart> {
|
async removeItem(userId: string, restaurantId: string, foodId: string): Promise<Cart> {
|
||||||
const cart = await this.findOne(userId, restaurantId);
|
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) {
|
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
|
// Remove item
|
||||||
|
|||||||
Reference in New Issue
Block a user