cart refactor

This commit is contained in:
2026-02-10 12:40:44 +03:30
parent 5fb1c63c4c
commit 30be5402f3
5 changed files with 49 additions and 43 deletions
+10 -14
View File
@@ -23,26 +23,23 @@ export class CartController {
return this.cartService.getOrCreateCart(userId, shopId);
}
@Post('product/:productId/increment')
@Post('variant/:variantId/increment')
@ApiOperation({ summary: 'Increment item quantity in cart by 1' })
@ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'productId', description: 'Product ID to increment in cart' })
async incrementItem(@UserId() userId: string, @ShopId() shopId: string, @Param('productId') productId: string) {
return this.cartService.incrementItem(userId, shopId, productId, 1);
async incrementItem(@UserId() userId: string, @ShopId() shopId: string, @Param('variantId') variantId: string) {
return this.cartService.incrementItem(userId, shopId, variantId, 1);
}
@Post('product/:productId/decrement')
@Post('variant/:variantId/decrement')
@ApiOperation({ summary: 'Decrement item quantity in cart by 1 (removes item if quantity reaches 0)' })
@ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'productId', description: 'Product ID to decrement in cart' })
async decrementItem(@UserId() userId: string, @ShopId() shopId: string, @Param('productId') productId: string) {
return this.cartService.decrementItem(userId, shopId, productId);
async decrementItem(@UserId() userId: string, @ShopId() shopId: string, @Param('variantId') variantId: string) {
return this.cartService.decrementItem(userId, shopId, variantId);
}
@Post('product/bulk')
@Post('variant/bulk')
@ApiOperation({ summary: 'Bulk add items to cart (increments quantity if items exist)' })
@ApiHeader(API_HEADER_SLUG)
@ApiBody({ type: BulkAddItemsToCartDto })
bulkAddItems(
@UserId() userId: string,
@ShopId() shopId: string,
@@ -51,12 +48,11 @@ export class CartController {
return this.cartService.bulkAddItems(userId, shopId, bulkAddItemsDto);
}
@Delete('product/:productId')
@Delete('variant/:variantId')
@ApiOperation({ summary: 'Remove item from cart' })
@ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'productId', description: 'Product ID in the cart' })
async removeItem(@UserId() userId: string, @ShopId() shopId: string, @Param('productId') productId: string) {
return this.cartService.removeItem(userId, shopId, productId);
async removeItem(@UserId() userId: string, @ShopId() shopId: string, @Param('variantId') variantId: string) {
return this.cartService.removeItem(userId, shopId, variantId);
}
@Delete()
+20 -19
View File
@@ -5,6 +5,7 @@ import { CartValidationService } from './cart-validation.service';
import { CartCalculationService } from './cart-calculation.service';
import { CartMessage } from 'src/common/enums/message.enum';
import { ProductService } from 'src/modules/products/providers/product.service';
import { Variant } from 'src/modules/products/entities/variant.entity';
@Injectable()
export class CartItemService {
@@ -17,13 +18,13 @@ export class CartItemService {
/**
* Create a new cart item from product
*/
createCartItem(product: Product, quantity: number): CartItem {
const itemPrice = product.price || 0;
const itemDiscount = product.discount || 0;
createCartItem(variant: Variant, quantity: number): CartItem {
const itemPrice = variant.price || 0;
const itemDiscount = variant.product.discount || 0;
return {
variantId: product.id,
productTitle: product.title,
variantId: variant.id,
productTitle: variant.product.title,
quantity,
price: itemPrice,
discount: itemDiscount,
@@ -34,13 +35,13 @@ export class CartItemService {
/**
* Build cart item from product (updating existing item if provided)
*/
buildCartItemFromFood(product: Product, quantity: number, previous?: CartItem): CartItem {
const itemPrice = product.price || 0;
const itemDiscount = product.discount || 0;
buildCartItemFromFood(variant: Variant, quantity: number, previous?: CartItem): CartItem {
const itemPrice = variant.price || 0;
const itemDiscount = variant.product.discount || 0;
return {
...(previous ?? ({} as CartItem)),
variantId: product.id,
productTitle: product.title,
variantId: variant.id,
productTitle: variant.product.title,
quantity,
price: itemPrice,
discount: itemDiscount,
@@ -58,20 +59,20 @@ export class CartItemService {
/**
* Add or increment item in cart
*/
async addOrIncrementItem(cart: Cart, productId: string, shopId: string, quantityToAdd: number): Promise<void> {
const product = await this.validationService.validateAndGetFood(productId, shopId, quantityToAdd);
async addOrIncrementItem(cart: Cart, variantId: string, shopId: string, quantityToAdd: number): Promise<void> {
const variant = await this.validationService.validateAndGetVariant(variantId, shopId, quantityToAdd);
const index = this.getItemIndex(cart, product.id);
const index = this.getItemIndex(cart, variant.id);
if (index < 0) {
cart.items.push(this.createCartItem(product, quantityToAdd));
cart.items.push(this.createCartItem(variant, quantityToAdd));
return;
}
const existingItem = cart.items[index];
const newQuantity = existingItem.quantity + quantityToAdd;
cart.items[index] = this.buildCartItemFromFood(product, newQuantity, existingItem);
cart.items[index] = this.buildCartItemFromFood(variant, newQuantity, existingItem);
}
/**
@@ -88,8 +89,8 @@ export class CartItemService {
/**
* Decrement item quantity or remove if quantity reaches 0
*/
async decrementOrRemoveItem(cart: Cart, productId: string): Promise<void> {
const itemIndex = this.getItemIndex(cart, productId);
async decrementOrRemoveItem(cart: Cart, variantId: string): Promise<void> {
const itemIndex = this.getItemIndex(cart, variantId);
if (itemIndex < 0) {
throw new NotFoundException(CartMessage.ITEM_NOT_FOUND);
}
@@ -102,8 +103,8 @@ export class CartItemService {
return;
}
const product = await this.productService.findOrFail(productId);
cart.items[itemIndex] = this.buildCartItemFromFood(product, newQuantity, existingItem);
const variant = await this.productService.findOrFailVariant(variantId);
cart.items[itemIndex] = this.buildCartItemFromFood(variant, newQuantity, existingItem);
}
}
@@ -15,6 +15,7 @@ import { ProductService } from 'src/modules/products/providers/product.service';
import { ShopService } from 'src/modules/shops/providers/shops.service';
import { DeliveryService } from 'src/modules/delivery/providers/delivery.service';
import { PaymentMethodService } from 'src/modules/payments/services/payment-method.service';
import { Variant } from 'src/modules/products/entities/variant.entity';
@Injectable()
export class CartValidationService {
@@ -30,14 +31,14 @@ export class CartValidationService {
/**
* Validate product exists and belongs to shop
*/
async validateAndGetFood(productId: string, shopId: string, quantity: number): Promise<Product> {
const product = await this.productService.findOrFail(productId)
async validateAndGetVariant(variantId: string, shopId: string, quantity: number): Promise<Variant> {
const variant = await this.productService.findOrFailVariant(variantId)
if (product.shop.id !== shopId) {
if (variant.product.shop.id !== shopId) {
throw new BadRequestException(CartMessage.PRODUCT_NOT_BELONGS_TO_SHOP);
}
return product;
return variant;
}
+6 -6
View File
@@ -156,18 +156,18 @@ export class CartService {
/**
* Increment item quantity in cart
*/
async incrementItem(userId: string, shopId: string, foodId: string, quantity: number): Promise<Cart> {
async incrementItem(userId: string, shopId: string, variantId: string, quantity: number): Promise<Cart> {
const cart = await this.getOrCreateCart(userId, shopId);
await this.itemService.addOrIncrementItem(cart, foodId, shopId, quantity);
await this.itemService.addOrIncrementItem(cart, variantId, shopId, quantity);
return this.recalculateAndSaveCart(cart);
}
/**
* Decrement item quantity in cart by 1 (removes item if quantity reaches 0)
*/
async decrementItem(userId: string, shopId: string, foodId: string): Promise<Cart> {
async decrementItem(userId: string, shopId: string, variantId: string): Promise<Cart> {
const cart = await this.findOneOrFail(userId, shopId);
await this.itemService.decrementOrRemoveItem(cart, foodId);
await this.itemService.decrementOrRemoveItem(cart, variantId);
return this.recalculateAndSaveCart(cart);
}
@@ -187,9 +187,9 @@ export class CartService {
/**
* Remove item from cart
*/
async removeItem(userId: string, shopId: string, foodId: string): Promise<Cart> {
async removeItem(userId: string, shopId: string, variantId: string): Promise<Cart> {
const cart = await this.findOneOrFail(userId, shopId);
this.itemService.removeItemOrFail(cart, foodId);
this.itemService.removeItemOrFail(cart, variantId);
return this.recalculateAndSaveCart(cart);
}
@@ -222,4 +222,12 @@ export class ProductService {
return product
}
async findOrFailVariant(variantId: string) {
const variant = await this.em.findOne(Variant, { id: variantId } ,{ populate: ['product','product.shop',] });
if (!variant) throw new NotFoundException('Variant not found');
return variant
}
}