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