This commit is contained in:
2025-12-06 22:05:56 +03:30
parent e342c00e25
commit f36125764d
5 changed files with 140 additions and 6 deletions
+94 -3
View File
@@ -11,6 +11,7 @@ import { SetAddressDto } from '../dto/set-address.dto';
import { SetPaymentMethodDto } from '../dto/set-payment-method.dto';
import { SetDeliveryMethodDto } from '../dto/set-delivery-method.dto';
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 { Cart, CartItem } from '../interfaces/cart.interface';
@@ -366,20 +367,35 @@ export class CartService {
const orderAmount = cart.subTotal - cart.itemsDiscount;
const { valid, coupon } = await this.couponService.validateCoupon(
// check if coupon is valid and belong to the restaurant
const { valid, coupon, message } = await this.couponService.validateCoupon(
applyCouponDto.code,
restaurantId,
Number(orderAmount),
);
if (!valid) {
throw new BadRequestException('Invalid coupon code');
throw new BadRequestException(message || 'Invalid coupon code');
}
if (!coupon) {
throw new BadRequestException('Coupon not found');
}
// Validate user phone if coupon is restricted to a specific user
if (coupon.userPhone) {
const user = await this.em.findOne(User, { id: userId });
if (!user) {
throw new NotFoundException('User not found');
}
if (user.phone !== coupon.userPhone) {
throw new BadRequestException(
'This coupon is only available for a specific user and cannot be applied to your account.',
);
}
}
// Check maxUsesPerUser limit by counting how many orders this user has with this coupon
if (coupon.maxUsesPerUser) {
const result = await this.em.getConnection().execute<{ count: string }>(
@@ -401,6 +417,40 @@ export class CartService {
}
}
// Validate that cart has items matching coupon criteria (if restrictions exist)
const hasCategoryRestriction = coupon.foodCategories && coupon.foodCategories.length > 0;
const hasFoodRestriction = coupon.foods && coupon.foods.length > 0;
if (hasCategoryRestriction || hasFoodRestriction) {
// Fetch all foods in cart with their categories
const foodIds = cart.items.map(item => item.foodId);
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] });
const foodMap = new Map(foods.map(f => [f.id, f]));
let hasMatchingItem = false;
for (const item of cart.items) {
const food = foodMap.get(item.foodId);
if (!food) continue;
// Check if item matches coupon criteria
const matchesCategory =
hasCategoryRestriction && food.category && coupon.foodCategories!.includes(food.category.id);
const matchesFood = hasFoodRestriction && coupon.foods!.includes(food.id);
// Item is eligible if it matches category OR food criteria
if (matchesCategory || matchesFood) {
hasMatchingItem = true;
break;
}
}
if (!hasMatchingItem) {
throw new BadRequestException(
'This coupon cannot be applied to the items in your cart. Please add items from the specified categories or foods.',
);
}
}
const couponDetail: OrderCouponDetail = {
couponId: coupon.id,
couponName: coupon.name,
@@ -562,7 +612,48 @@ export class CartService {
let couponDiscount = 0;
if (cart.coupon) {
const priceAfterItemDiscount = subTotal - itemsDiscount;
// Get the full coupon entity to check foodCategories and foods
const coupon = await this.couponService.findById(cart.coupon.couponId);
// Determine which items are eligible for the coupon
let eligibleItemsTotal = 0;
let eligibleItemsDiscount = 0;
// If coupon has foodCategories or foods restrictions, filter items
const hasCategoryRestriction = coupon.foodCategories && coupon.foodCategories.length > 0;
const hasFoodRestriction = coupon.foods && coupon.foods.length > 0;
if (hasCategoryRestriction || hasFoodRestriction) {
// Fetch all foods in cart with their categories
const foodIds = cart.items.map(item => item.foodId);
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] });
const foodMap = new Map(foods.map(f => [f.id, f]));
for (const item of cart.items) {
const food = foodMap.get(item.foodId);
if (!food) continue;
// Check if item matches coupon criteria
const matchesCategory =
hasCategoryRestriction && food.category && coupon.foodCategories!.includes(food.category.id);
const matchesFood = hasFoodRestriction && coupon.foods!.includes(food.id);
// Item is eligible if it matches category OR food criteria
if (matchesCategory || matchesFood) {
const itemPrice = item.price * item.quantity;
const itemDiscount = item.discount * item.quantity;
eligibleItemsTotal += itemPrice;
eligibleItemsDiscount += itemDiscount;
}
}
} else {
// No restrictions, apply to all items
eligibleItemsTotal = subTotal;
eligibleItemsDiscount = itemsDiscount;
}
const priceAfterItemDiscount = eligibleItemsTotal - eligibleItemsDiscount;
if (cart.coupon.type === CouponType.PERCENTAGE) {
let discount = (priceAfterItemDiscount * cart.coupon.value) / 100;
// Apply maxDiscount limit if set