calculate delivery fee based on distance
This commit is contained in:
@@ -87,73 +87,73 @@ export class CartController {
|
||||
return this.cartService.removeCoupon(userId, restaurantId);
|
||||
}
|
||||
|
||||
@Patch('address')
|
||||
@ApiOperation({ summary: 'Set address for cart' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBody({ type: SetAddressDto })
|
||||
async setAddress(@UserId() userId: string, @RestId() restaurantId: string, @Body() setAddressDto: SetAddressDto) {
|
||||
return this.cartService.setAddress(userId, restaurantId, setAddressDto);
|
||||
}
|
||||
// @Patch('address')
|
||||
// @ApiOperation({ summary: 'Set address for cart' })
|
||||
// @ApiHeader(API_HEADER_SLUG)
|
||||
// @ApiBody({ type: SetAddressDto })
|
||||
// async setAddress(@UserId() userId: string, @RestId() restaurantId: string, @Body() setAddressDto: SetAddressDto) {
|
||||
// return this.cartService.setAddress(userId, restaurantId, setAddressDto);
|
||||
// }
|
||||
|
||||
@Patch('payment-method')
|
||||
@ApiOperation({ summary: 'Set payment method for cart' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBody({ type: SetPaymentMethodDto })
|
||||
async setPaymentMethod(
|
||||
@UserId() userId: string,
|
||||
@RestId() restaurantId: string,
|
||||
@Body() setPaymentMethodDto: SetPaymentMethodDto,
|
||||
) {
|
||||
return this.cartService.setPaymentMethod(userId, restaurantId, setPaymentMethodDto);
|
||||
}
|
||||
// @Patch('payment-method')
|
||||
// @ApiOperation({ summary: 'Set payment method for cart' })
|
||||
// @ApiHeader(API_HEADER_SLUG)
|
||||
// @ApiBody({ type: SetPaymentMethodDto })
|
||||
// async setPaymentMethod(
|
||||
// @UserId() userId: string,
|
||||
// @RestId() restaurantId: string,
|
||||
// @Body() setPaymentMethodDto: SetPaymentMethodDto,
|
||||
// ) {
|
||||
// return this.cartService.setPaymentMethod(userId, restaurantId, setPaymentMethodDto);
|
||||
// }
|
||||
|
||||
@Patch('delivery-method')
|
||||
@ApiOperation({ summary: 'Set delivery method for cart' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBody({ type: SetDeliveryMethodDto })
|
||||
setDeliveryMethod(
|
||||
@UserId() userId: string,
|
||||
@RestId() restaurantId: string,
|
||||
@Body() setDeliveryMethodDto: SetDeliveryMethodDto,
|
||||
) {
|
||||
return this.cartService.setDeliveryMethod(userId, restaurantId, setDeliveryMethodDto);
|
||||
}
|
||||
// @Patch('delivery-method')
|
||||
// @ApiOperation({ summary: 'Set delivery method for cart' })
|
||||
// @ApiHeader(API_HEADER_SLUG)
|
||||
// @ApiBody({ type: SetDeliveryMethodDto })
|
||||
// setDeliveryMethod(
|
||||
// @UserId() userId: string,
|
||||
// @RestId() restaurantId: string,
|
||||
// @Body() setDeliveryMethodDto: SetDeliveryMethodDto,
|
||||
// ) {
|
||||
// return this.cartService.setDeliveryMethod(userId, restaurantId, setDeliveryMethodDto);
|
||||
// }
|
||||
|
||||
@Patch('description')
|
||||
@ApiOperation({ summary: 'Set description for cart' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBody({ type: SetDescriptionDto })
|
||||
setDescription(
|
||||
@UserId() userId: string,
|
||||
@RestId() restaurantId: string,
|
||||
@Body() setDescriptionDto: SetDescriptionDto,
|
||||
) {
|
||||
return this.cartService.setDescription(userId, restaurantId, setDescriptionDto);
|
||||
}
|
||||
// @Patch('description')
|
||||
// @ApiOperation({ summary: 'Set description for cart' })
|
||||
// @ApiHeader(API_HEADER_SLUG)
|
||||
// @ApiBody({ type: SetDescriptionDto })
|
||||
// setDescription(
|
||||
// @UserId() userId: string,
|
||||
// @RestId() restaurantId: string,
|
||||
// @Body() setDescriptionDto: SetDescriptionDto,
|
||||
// ) {
|
||||
// return this.cartService.setDescription(userId, restaurantId, setDescriptionDto);
|
||||
// }
|
||||
|
||||
@Patch('table-number')
|
||||
@ApiOperation({ summary: 'Set table number for cart (required when delivery method is DineIn)' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBody({ type: SetTableNumberDto })
|
||||
setTableNumber(
|
||||
@UserId() userId: string,
|
||||
@RestId() restaurantId: string,
|
||||
@Body() setTableNumberDto: SetTableNumberDto,
|
||||
) {
|
||||
return this.cartService.setTableNumber(userId, restaurantId, setTableNumberDto);
|
||||
}
|
||||
// @Patch('table-number')
|
||||
// @ApiOperation({ summary: 'Set table number for cart (required when delivery method is DineIn)' })
|
||||
// @ApiHeader(API_HEADER_SLUG)
|
||||
// @ApiBody({ type: SetTableNumberDto })
|
||||
// setTableNumber(
|
||||
// @UserId() userId: string,
|
||||
// @RestId() restaurantId: string,
|
||||
// @Body() setTableNumberDto: SetTableNumberDto,
|
||||
// ) {
|
||||
// return this.cartService.setTableNumber(userId, restaurantId, setTableNumberDto);
|
||||
// }
|
||||
|
||||
@Patch('car-delivery')
|
||||
@ApiOperation({ summary: 'Set car delivery for cart' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBody({ type: SetCarDeliveryDto })
|
||||
setCarDelivery(
|
||||
@UserId() userId: string,
|
||||
@RestId() restaurantId: string,
|
||||
@Body() setCarDeliveryDto: SetCarDeliveryDto,
|
||||
) {
|
||||
return this.cartService.setCarDelivery(userId, restaurantId, setCarDeliveryDto);
|
||||
}
|
||||
// @Patch('car-delivery')
|
||||
// @ApiOperation({ summary: 'Set car delivery for cart' })
|
||||
// @ApiHeader(API_HEADER_SLUG)
|
||||
// @ApiBody({ type: SetCarDeliveryDto })
|
||||
// setCarDelivery(
|
||||
// @UserId() userId: string,
|
||||
// @RestId() restaurantId: string,
|
||||
// @Body() setCarDeliveryDto: SetCarDeliveryDto,
|
||||
// ) {
|
||||
// return this.cartService.setCarDelivery(userId, restaurantId, setCarDeliveryDto);
|
||||
// }
|
||||
@Patch('all')
|
||||
@ApiOperation({ summary: 'Set all cart params' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
|
||||
@@ -15,7 +15,7 @@ import { UserAddress } from '../../users/entities/user-address.entity';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||
import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
|
||||
import { DeliveryMethodEnum, DeliveryFeeTypeEnum } from '../../delivery/interface/delivery';
|
||||
import { Cart, CartItem } from '../interfaces/cart.interface';
|
||||
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
|
||||
import { OrderCouponDetail, OrderStatus } from 'src/modules/orders/interface/order.interface';
|
||||
@@ -24,7 +24,7 @@ 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';
|
||||
import { Order } from 'src/modules/orders/entities/order.entity';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class CartService {
|
||||
private readonly CART_TTL = 3600; // 1 hour in seconds
|
||||
@@ -489,7 +489,7 @@ export class CartService {
|
||||
cart.totalDiscount = couponDiscount + itemsDiscount;
|
||||
|
||||
cart.tax = await this.calculateTax(cart.restaurantId, Math.max(0, subTotal - cart.totalDiscount));
|
||||
cart.deliveryFee = await this.calculateDeliveryFee(cart.deliveryMethodId);
|
||||
cart.deliveryFee = await this.calculateDeliveryFee(cart);
|
||||
|
||||
// total = subtotal – totalDiscount + tax + deliveryFee
|
||||
cart.total = Math.max(0, subTotal - cart.totalDiscount) + cart.tax + cart.deliveryFee;
|
||||
@@ -988,10 +988,73 @@ export class CartService {
|
||||
return (Math.max(0, amountAfterDiscounts) * vat) / 100;
|
||||
}
|
||||
|
||||
private async calculateDeliveryFee(deliveryMethodId?: string): Promise<number> {
|
||||
private async calculateDeliveryFee(cart: Cart): Promise<number> {
|
||||
const deliveryMethodId = cart.deliveryMethodId;
|
||||
if (!deliveryMethodId) return 0;
|
||||
const deliveryMethod = await this.em.findOne(Delivery, { id: deliveryMethodId });
|
||||
|
||||
const deliveryMethod = await this.em.findOne(Delivery, { id: deliveryMethodId }, { populate: ['restaurant'] });
|
||||
if (!deliveryMethod || !deliveryMethod.enabled) return 0;
|
||||
return Number(deliveryMethod.deliveryFee) || 0;
|
||||
|
||||
// If not distance based, return fixed delivery fee
|
||||
if (deliveryMethod.deliveryFeeType !== DeliveryFeeTypeEnum.DISTANCE_BASED) {
|
||||
return Number(deliveryMethod.deliveryFee) || 0;
|
||||
}
|
||||
|
||||
// For distance based calculation we need restaurant and user coordinates
|
||||
const restaurant = (deliveryMethod as any).restaurant as Restaurant | undefined;
|
||||
const userAddr = cart.userAddress;
|
||||
|
||||
if (!restaurant || restaurant.latitude == null || restaurant.longitude == null) {
|
||||
// fallback to configured fixed fee when coordinates are missing
|
||||
return Number(deliveryMethod.deliveryFee) || 0;
|
||||
}
|
||||
|
||||
if (!userAddr || userAddr.latitude == null || userAddr.longitude == null) {
|
||||
return Number(deliveryMethod.deliveryFee) || 0;
|
||||
}
|
||||
|
||||
const restLat = Number(restaurant.latitude);
|
||||
const restLng = Number(restaurant.longitude);
|
||||
const userLat = Number(userAddr.latitude);
|
||||
const userLng = Number(userAddr.longitude);
|
||||
|
||||
const distanceKm = this.getDistanceKmRounded(restLat, restLng, userLat, userLng);
|
||||
|
||||
// Try to read either possible property names (legacy vs new)
|
||||
const perKm = Number(deliveryMethod.perKilometerFee);
|
||||
const minFee = Number(deliveryMethod.distanceBasedMinCost);
|
||||
|
||||
let fee = 0;
|
||||
if (perKm <= 0) {
|
||||
fee = Number(deliveryMethod.deliveryFee) || 0;
|
||||
} else {
|
||||
fee = distanceKm * perKm;
|
||||
}
|
||||
|
||||
if (minFee > 0 && fee < minFee) fee = minFee;
|
||||
|
||||
return Math.max(0, Number(fee));
|
||||
}
|
||||
|
||||
private toRad(value: number): number {
|
||||
return (value * Math.PI) / 180;
|
||||
}
|
||||
|
||||
private getDistanceKm(lat1: number, lng1: number, lat2: number, lng2: number): number {
|
||||
const R = 6371; // Earth radius in KM
|
||||
|
||||
const dLat = this.toRad(lat2 - lat1);
|
||||
const dLng = this.toRad(lng2 - lng1);
|
||||
|
||||
const a =
|
||||
Math.sin(dLat / 2) ** 2 + Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) * Math.sin(dLng / 2) ** 2;
|
||||
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c;
|
||||
}
|
||||
private getDistanceKmRounded(lat1: number, lng1: number, lat2: number, lng2: number, precision = 2): number {
|
||||
const distance = this.getDistanceKm(lat1, lng1, lat2, lng2);
|
||||
return Number(distance.toFixed(precision));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user