diff --git a/src/modules/cart/controllers/cart.controller.ts b/src/modules/cart/controllers/cart.controller.ts index 5657275..59ce4bf 100644 --- a/src/modules/cart/controllers/cart.controller.ts +++ b/src/modules/cart/controllers/cart.controller.ts @@ -2,12 +2,6 @@ import { Controller, Get, Post, Body, Patch, Delete, UseGuards, Param } from '@n import { CartService } from '../providers/cart.service'; import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto'; import { ApplyCouponDto } from '../dto/apply-coupon.dto'; -import { SetAddressDto } from '../dto/set-address.dto'; -import { SetPaymentMethodDto } from '../dto/set-payment-method.dto'; -import { SetDeliveryMethodDto } from '../dto/set-delivery-method.dto'; -import { SetDescriptionDto } from '../dto/set-description.dto'; -import { SetTableNumberDto } from '../dto/set-table-number.dto'; -import { SetCarDeliveryDto } from '../dto/set-car-delivery.dto'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiParam, ApiHeader } from '@nestjs/swagger'; import { AuthGuard } from '../../auth/guards/auth.guard'; import { UserId } from 'src/common/decorators/user-id.decorator'; diff --git a/src/modules/cart/dto/set-address.dto.ts b/src/modules/cart/dto/set-address.dto.ts deleted file mode 100644 index 4bde322..0000000 --- a/src/modules/cart/dto/set-address.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class SetAddressDto { - @ApiProperty({ description: 'User address ID', example: '01ARZ3NDEKTSV4RRFFQ69G5FAV' }) - @IsNotEmpty() - @IsString() - addressId!: string; -} diff --git a/src/modules/cart/dto/set-all-cart-params.dto.ts b/src/modules/cart/dto/set-all-cart-params.dto.ts index ada17fa..d329a51 100644 --- a/src/modules/cart/dto/set-all-cart-params.dto.ts +++ b/src/modules/cart/dto/set-all-cart-params.dto.ts @@ -28,6 +28,10 @@ export class SetAllCartParmsDto { @IsString() paymentMethodId!: string; + @ApiProperty({ description: 'Table number for dine-in orders', example: '5', required: false }) + @IsOptional() + @IsString() + tableNumber?: string; @ApiProperty() @IsOptional() diff --git a/src/modules/cart/dto/set-delivery-method.dto.ts b/src/modules/cart/dto/set-delivery-method.dto.ts deleted file mode 100644 index 0fb2bd4..0000000 --- a/src/modules/cart/dto/set-delivery-method.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class SetDeliveryMethodDto { - @ApiProperty({ description: 'Delivery method ID', example: '01ARZ3NDEKTSV4RRFFQ69G5FAV' }) - @IsNotEmpty() - @IsString() - deliveryMethodId!: string; -} diff --git a/src/modules/cart/dto/set-description.dto.ts b/src/modules/cart/dto/set-description.dto.ts deleted file mode 100644 index 67b16da..0000000 --- a/src/modules/cart/dto/set-description.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; - -export class SetDescriptionDto { - @ApiProperty({ - description: 'Cart description or notes', - required: false, - example: 'Please deliver to the back door', - }) - @IsOptional() - @IsString() - description?: string; -} diff --git a/src/modules/cart/dto/set-payment-method.dto.ts b/src/modules/cart/dto/set-payment-method.dto.ts deleted file mode 100644 index 40df256..0000000 --- a/src/modules/cart/dto/set-payment-method.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class SetPaymentMethodDto { - @ApiProperty({ description: 'Payment method ID', example: '01ARZ3NDEKTSV4RRFFQ69G5FAV' }) - @IsNotEmpty() - @IsString() - paymentMethodId!: string; -} diff --git a/src/modules/cart/dto/set-table-number.dto.ts b/src/modules/cart/dto/set-table-number.dto.ts deleted file mode 100644 index 46a9c86..0000000 --- a/src/modules/cart/dto/set-table-number.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, ValidateIf } from 'class-validator'; - -export class SetTableNumberDto { - @ApiPropertyOptional({ description: 'Table number', example: '5' }) - @ValidateIf((o, value) => value !== null && value !== undefined) - @IsString() - tableNumber?: string; -} diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index d9f8a9c..7010c36 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -5,18 +5,13 @@ import { OrderCouponDetail } from '../../orders/interface/order.interface'; import { Cart } from '../interfaces/cart.interface'; import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto'; import { ApplyCouponDto } from '../dto/apply-coupon.dto'; -import { SetAddressDto } from '../dto/set-address.dto'; -import { SetPaymentMethodDto } from '../dto/set-payment-method.dto'; -import { SetDeliveryMethodDto } from '../dto/set-delivery-method.dto'; -import { SetDescriptionDto } from '../dto/set-description.dto'; -import { SetTableNumberDto } from '../dto/set-table-number.dto'; -import { SetCarDeliveryDto } from '../dto/set-car-delivery.dto'; -import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; + import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; import { CouponService } from '../../coupons/providers/coupon.service'; import { CartRepository } from '../repositories/cart.repository'; import { CartValidationService } from './cart-validation.service'; import { CartCalculationService } from './cart-calculation.service'; import { CartItemService } from './cart-item.service'; +import { SetCarDeliveryDto } from '../dto/set-car-delivery.dto'; @Injectable() export class CartService { @@ -26,127 +21,106 @@ export class CartService { private readonly calculationService: CartCalculationService, private readonly itemService: CartItemService, private readonly couponService: CouponService, - ) {} + ) { } /** * Set all cart parameters at once */ async setAllCartParams(userId: string, restaurantId: string, params: SetAllCartParmsDto): Promise { - const { deliveryMethodId, paymentMethodId, addressId, carAddress, description } = params; + const { deliveryMethodId, paymentMethodId, addressId, carAddress, description, tableNumber } = params; // get existing cart (or throw) const cart = await this.findOneOrFail(userId, restaurantId); - // If deliveryMethodId is provided -> validate and set it first - if (deliveryMethodId) { - const deliveryMethod = await this.validationService.getEnabledDeliveryMethodOrFail( - restaurantId, - deliveryMethodId, - ); - cart.deliveryMethodId = deliveryMethodId; - // clear fields incompatible with the chosen delivery method - this.clearFieldsIncompatibleWithDeliveryMethod(cart, deliveryMethod.method); + // Validate and get delivery method + const deliveryMethod = await this.validationService.getEnabledDeliveryMethodOrFail( + restaurantId, + deliveryMethodId, + ); + cart.deliveryMethodId = deliveryMethodId; + + // Handle each delivery method with its specific requirements + switch (deliveryMethod.method) { + case DeliveryMethodEnum.DineIn: + // DineIn requires table number and clears incompatible fields + if (!tableNumber) { + throw new BadRequestException('Table number is required for dine-in orders'); + } + this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DineIn); + cart.tableNumber = tableNumber; + break; + + case DeliveryMethodEnum.CustomerPickup: + // CustomerPickup just clears incompatible fields + this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.CustomerPickup); + break; + + case DeliveryMethodEnum.DeliveryCar: + // DeliveryCar requires carAddress and clears incompatible fields + if (!carAddress) { + throw new BadRequestException('Car address is required for car delivery'); + } + this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCar); + const user = await this.validationService.getUserOrFail(userId); + cart.carAddress = { + carModel: carAddress.carModel, + carColor: carAddress.carColor, + plateNumber: carAddress.plateNumber, + phone: user.phone, + }; + break; + + case DeliveryMethodEnum.DeliveryCourier: + // DeliveryCourier requires addressId and clears incompatible fields + if (!addressId) { + throw new BadRequestException('Address is required for courier delivery'); + } + this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCourier); + const address = await this.validationService.getUserAddressOrFail(addressId); + this.validationService.validateAddressOwnership(address, userId); + + // ensure address is within restaurant service area + await this.validationService.assertAddressInsideServiceArea( + restaurantId, + address.latitude, + address.longitude, + ); + + cart.userAddress = { + address: address.address, + latitude: address.latitude, + longitude: address.longitude, + city: address.city, + province: address.province || '', + postalCode: address.postalCode || undefined, + fullName: `${address.user.firstName || ''} ${address.user.lastName || ''}`.trim(), + phone: address.user.phone, + }; + break; } - // If addressId is provided -> must have delivery method (either provided above or already on cart) - if (addressId) { - const effectiveDeliveryMethodId = deliveryMethodId ?? cart.deliveryMethodId; - if (!effectiveDeliveryMethodId) { - throw new BadRequestException('Delivery method must be set before setting address'); - } - const deliveryMethod = await this.validationService.getDeliveryMethodForRestaurantOrFail( - restaurantId, - effectiveDeliveryMethodId, - ); - this.validationService.assertDeliveryMethod( - deliveryMethod.method, - DeliveryMethodEnum.DeliveryCourier, - 'Delivery method must be DeliveryCourier', - ); + // Validate and set payment method + const paymentMethod = await this.validationService.getEnabledPaymentMethodOrFail( + restaurantId, + paymentMethodId, + ); - const address = await this.validationService.getUserAddressOrFail(addressId); - this.validationService.validateAddressOwnership(address, userId); + // Recalculate totals first so wallet check uses up-to-date total (delivery method may have changed above). + await this.calculationService.recalculateCartTotals(cart); - // ensure address is within restaurant service area - await this.validationService.assertAddressInsideServiceArea( - restaurantId, - address.latitude, - address.longitude, - ); - - // ensure cart fields are compatible and set address - this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCourier); - cart.userAddress = { - address: address.address, - latitude: address.latitude, - longitude: address.longitude, - city: address.city, - province: address.province || '', - postalCode: address.postalCode || undefined, - fullName: `${address.user.firstName || ''} ${address.user.lastName || ''}`.trim(), - phone: address.user.phone, - }; + if (paymentMethod.method === PaymentMethodEnum.Wallet) { + await this.validationService.assertWalletHasEnoughBalance(userId, restaurantId, cart.total); } - // If carAddress is provided -> must have delivery method DeliveryCar - if (carAddress) { - const effectiveDeliveryMethodId = deliveryMethodId ?? cart.deliveryMethodId; - if (!effectiveDeliveryMethodId) { - throw new BadRequestException('Delivery method must be set before setting car delivery'); - } - const deliveryMethod = await this.validationService.getDeliveryMethodForRestaurantOrFail( - restaurantId, - effectiveDeliveryMethodId, - ); - this.validationService.assertDeliveryMethod( - deliveryMethod.method, - DeliveryMethodEnum.DeliveryCar, - 'Delivery method must be DeliveryCar', - ); + cart.paymentMethodId = paymentMethodId; - const user = await this.validationService.getUserOrFail(userId); - - // make sure incompatible fields are cleared and set car address (phone comes from user) - this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCar); - cart.carAddress = { - carModel: carAddress.carModel, - carColor: carAddress.carColor, - plateNumber: carAddress.plateNumber, - phone: user.phone, - }; - } - - // If payment method is provided -> validate and (for wallet) ensure enough balance. - if (paymentMethodId) { - const paymentMethod = await this.validationService.getEnabledPaymentMethodOrFail( - restaurantId, - paymentMethodId, - ); - - // Recalculate totals first so wallet check uses up-to-date total (delivery method may have changed above). - await this.calculationService.recalculateCartTotals(cart); - - if (paymentMethod.method === PaymentMethodEnum.Wallet) { - await this.validationService.assertWalletHasEnoughBalance(userId, restaurantId, cart.total); - } - - cart.paymentMethodId = paymentMethodId; - } - - // Description (if provided) + // Set description (if provided) if (description !== undefined) { cart.description = description; } // Final validation: if effective delivery method is courier, ensure all foods have pickupServe - const effectiveDeliveryMethodId = deliveryMethodId ?? cart.deliveryMethodId; - if (effectiveDeliveryMethodId) { - const effectiveDeliveryMethod = await this.validationService.getDeliveryMethodForRestaurantOrFail( - restaurantId, - effectiveDeliveryMethodId, - ); - await this.validationService.assertAllFoodsHavePickupServeForCourier(cart, effectiveDeliveryMethod.method); - } + await this.validationService.assertAllFoodsHavePickupServeForCourier(cart, deliveryMethod.method); // Final recalculation + save and return cart return this.recalculateAndSaveCart(cart); @@ -296,7 +270,7 @@ export class CartService { /** * Set address for cart */ - async setAddress(userId: string, restaurantId: string, setAddressDto: SetAddressDto): Promise { + async setAddress(userId: string, restaurantId: string, addressId: string): Promise { const cart = await this.findOneOrFail(userId, restaurantId); const deliveryMethodId = this.validationService.requireDeliveryMethodId( @@ -314,7 +288,7 @@ export class CartService { ); // Find and validate address belongs to user - const address = await this.validationService.getUserAddressOrFail(setAddressDto.addressId); + const address = await this.validationService.getUserAddressOrFail(addressId); // Verify address belongs to the user this.validationService.validateAddressOwnership(address, userId); @@ -346,18 +320,18 @@ export class CartService { async setPaymentMethod( userId: string, restaurantId: string, - setPaymentMethodDto: SetPaymentMethodDto, + paymentMethodId: string, ): Promise { const cart = await this.findOneOrFail(userId, restaurantId); const paymentMethod = await this.validationService.getEnabledPaymentMethodOrFail( restaurantId, - setPaymentMethodDto.paymentMethodId, + paymentMethodId, ); if (paymentMethod.method === PaymentMethodEnum.Wallet) { await this.validationService.assertWalletHasEnoughBalance(userId, restaurantId, cart.total); } - cart.paymentMethodId = setPaymentMethodDto.paymentMethodId; + cart.paymentMethodId = paymentMethodId; return this.recalculateAndSaveCart(cart); } @@ -367,16 +341,16 @@ export class CartService { async setDeliveryMethod( userId: string, restaurantId: string, - setDeliveryMethodDto: SetDeliveryMethodDto, + deliveryMethodId: string, ): Promise { const cart = await this.findOneOrFail(userId, restaurantId); const deliveryMethod = await this.validationService.getEnabledDeliveryMethodOrFail( restaurantId, - setDeliveryMethodDto.deliveryMethodId, + deliveryMethodId, ); - cart.deliveryMethodId = setDeliveryMethodDto.deliveryMethodId; + cart.deliveryMethodId = deliveryMethodId; this.clearFieldsIncompatibleWithDeliveryMethod(cart, deliveryMethod.method); return this.recalculateAndSaveCart(cart); @@ -385,17 +359,17 @@ export class CartService { /** * Set description for cart */ - async setDescription(userId: string, restaurantId: string, setDescriptionDto: SetDescriptionDto): Promise { + async setDescription(userId: string, restaurantId: string, description: string): Promise { const cart = await this.findOneOrFail(userId, restaurantId); - cart.description = setDescriptionDto.description; + cart.description = description; return this.saveTouchedCart(cart); } /** * Set table number for cart */ - async setTableNumber(userId: string, restaurantId: string, setTableNumberDto: SetTableNumberDto): Promise { + async setTableNumber(userId: string, restaurantId: string, tableNumber: string): Promise { const cart = await this.findOneOrFail(userId, restaurantId); const deliveryMethodId = this.validationService.requireDeliveryMethodId( @@ -413,7 +387,7 @@ export class CartService { ); this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DineIn); - cart.tableNumber = setTableNumberDto.tableNumber; + cart.tableNumber = tableNumber; return this.saveTouchedCart(cart); }