diff --git a/src/modules/cart/controllers/cart.controller.ts b/src/modules/cart/controllers/cart.controller.ts index 9ad04ee..f78a8d4 100644 --- a/src/modules/cart/controllers/cart.controller.ts +++ b/src/modules/cart/controllers/cart.controller.ts @@ -12,6 +12,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiParam, ApiHeader } fr import { AuthGuard } from '../../auth/guards/auth.guard'; import { UserId } from 'src/common/decorators/user-id.decorator'; import { RestId } from 'src/common/decorators/rest-id.decorator'; +import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; const API_HEADER_SLUG = { name: 'X-Slug', @@ -161,4 +162,15 @@ export class CartController { ) { return this.cartService.setCarDelivery(userId, restaurantId, setCarDeliveryDto); } + @Patch('all') + @ApiOperation({ summary: 'Set car delivery for cart' }) + @ApiHeader(API_HEADER_SLUG) + @ApiBody({ type: SetAllCartParmsDto }) + setAllCartParams( + @UserId() userId: string, + @RestId() restaurantId: string, + @Body() setCarDeliveryDto: SetCarDeliveryDto, + ) { + return this.cartService.setCarDelivery(userId, restaurantId, setCarDeliveryDto); + } } diff --git a/src/modules/cart/dto/set-all-cart-params.dto.ts b/src/modules/cart/dto/set-all-cart-params.dto.ts new file mode 100644 index 0000000..d5c691d --- /dev/null +++ b/src/modules/cart/dto/set-all-cart-params.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { SetCarDeliveryDto } from './set-car-delivery.dto'; + + +export class SetAllCartParmsDto { + @ApiProperty({ + description: 'Cart description or notes', + required: false, + example: 'Please deliver to the back door', + }) + @IsOptional() + @IsString() + description?: string; + + @ApiProperty({ description: 'Delivery method ID', example: '01ARZ3NDEKTSV4RRFFQ69G5FAV' }) + @IsNotEmpty() + @IsString() + deliveryMethodId!: string; + + @ApiProperty({ description: 'User address ID', example: '01ARZ3NDEKTSV4RRFFQ69G5FAV' }) + @IsOptional() + @IsString() + addressId?: string; + + @ApiProperty({ description: 'Payment method ID', example: '01ARZ3NDEKTSV4RRFFQ69G5FAV' }) + @IsNotEmpty() + @IsString() + paymentMethodId!: string; + + + @ApiProperty() + @IsOptional() + @IsString() + carAddress?: SetCarDeliveryDto; +} diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index 40f539f..24a0612 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -22,6 +22,7 @@ import { OrderCouponDetail } from 'src/modules/orders/interface/order.interface' import { CouponType } from 'src/modules/coupons/interface/coupon'; import { UserWallet } from 'src/modules/users/entities/user-wallet.entity'; import { PaymentMethodEnum } from 'src/modules/payments/interface/payment'; +import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; @Injectable() export class CartService { @@ -34,6 +35,92 @@ export class CartService { private readonly couponService: CouponService, ) { } + async setAllCartParams(userId: string, restaurantId: string, params: SetAllCartParmsDto): Promise { + const { deliveryMethodId, paymentMethodId, addressId, carAddress, description } = 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.getEnabledDeliveryMethodOrFail(restaurantId, deliveryMethodId); + cart.deliveryMethodId = deliveryMethodId; + // clear fields incompatible with the chosen delivery method + this.clearFieldsIncompatibleWithDeliveryMethod(cart, deliveryMethod.method); + } + + // 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.getDeliveryMethodForRestaurantOrFail(restaurantId, effectiveDeliveryMethodId); + this.assertDeliveryMethod(deliveryMethod.method, DeliveryMethodEnum.DeliveryCourier, 'Delivery method must be DeliveryCourier'); + + const address = await this.getUserAddressOrFail(addressId); + if (address.user.id !== userId) { + throw new BadRequestException('Address does not belong to the current user'); + } + + // 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 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.getDeliveryMethodForRestaurantOrFail(restaurantId, effectiveDeliveryMethodId); + this.assertDeliveryMethod(deliveryMethod.method, DeliveryMethodEnum.DeliveryCar, 'Delivery method must be DeliveryCar'); + + const user = await this.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.getEnabledPaymentMethodOrFail(restaurantId, paymentMethodId); + + // Recalculate totals first so wallet check uses up-to-date total (delivery method may have changed above). + await this.recalculateCartTotals(cart); + + if (paymentMethod.method === PaymentMethodEnum.Wallet) { + await this.assertWalletHasEnoughBalance(userId, restaurantId, cart.total); + } + + cart.paymentMethodId = paymentMethodId; + } + + // Description (if provided) + if (description !== undefined) { + cart.description = description; + } + + // Final recalculation + save and return cart + return this.recalculateAndSaveCart(cart); + } + /** * Get or create cart for user and restaurant */ diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index 29391e3..e9ac5a4 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -240,9 +240,6 @@ export class PaymentsService { order.status = OrderStatus.PAID; } - - - private async getOrCreateLatestPendingPayment( orderId: string, params: {