validate food by week day and meal type
This commit is contained in:
@@ -57,6 +57,13 @@ export class CartItemService {
|
||||
*/
|
||||
async addOrIncrementItem(cart: Cart, foodId: string, restaurantId: string, quantityToAdd: number): Promise<void> {
|
||||
const food = await this.validationService.validateAndGetFood(foodId, restaurantId, quantityToAdd);
|
||||
|
||||
// Validate meal type compatibility
|
||||
this.validationService.assertMealTypeCompatibility(food);
|
||||
|
||||
// Validate weekday compatibility
|
||||
this.validationService.assertWeekdayCompatibility(food);
|
||||
|
||||
const index = this.getItemIndex(cart, food.id);
|
||||
|
||||
if (index < 0) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Order } from '../../orders/entities/order.entity';
|
||||
import { OrderStatus } from '../../orders/interface/order.interface';
|
||||
import { Cart } from '../interfaces/cart.interface';
|
||||
import { GeographicUtils } from '../utils/geographic.utils';
|
||||
import { MealType } from '../../foods/interface/food.interface';
|
||||
|
||||
@Injectable()
|
||||
export class CartValidationService {
|
||||
@@ -290,5 +291,126 @@ export class CartValidationService {
|
||||
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] });
|
||||
return new Map(foods.map(f => [f.id, f]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert all foods in cart have pickupServe true when delivery method is courier
|
||||
*/
|
||||
async assertAllFoodsHavePickupServeForCourier(cart: Cart, deliveryMethod: DeliveryMethodEnum): Promise<void> {
|
||||
if (deliveryMethod !== DeliveryMethodEnum.DeliveryCourier) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cart.items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const foodIds = cart.items.map(item => item.foodId);
|
||||
const foods = await this.em.find(Food, { id: { $in: foodIds } });
|
||||
|
||||
const foodsWithoutPickupServe = foods.filter(food => !food.pickupServe);
|
||||
|
||||
if (foodsWithoutPickupServe.length > 0) {
|
||||
const foodTitles = foodsWithoutPickupServe.map(f => f.title || f.id).join(', ');
|
||||
throw new BadRequestException(
|
||||
`All foods must have pickupServe enabled for courier delivery. The following foods do not: ${foodTitles}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Iran time context (weekday and meal type)
|
||||
*/
|
||||
private getCurrentIranTimeContext(): { iranWeekDay: number; mealType: MealType | null } {
|
||||
const nowInIran = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Tehran' }));
|
||||
const weekDay = nowInIran.getDay(); // 0 = Sunday, 6 = Saturday
|
||||
const currentHour = nowInIran.getHours();
|
||||
|
||||
// Convert to Iran weekday: Saturday=0, Sunday=1, ..., Friday=6
|
||||
// JavaScript: Sunday=0, Monday=1, ..., Saturday=6
|
||||
// Iran week: Saturday=0, Sunday=1, ..., Friday=6
|
||||
const iranWeekDay = (weekDay + 1) % 7;
|
||||
|
||||
const mealType = this.getMealTypeByHour(currentHour);
|
||||
|
||||
return { iranWeekDay, mealType };
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine meal type based on current hour in Iran timezone.
|
||||
* @param hour - Current hour (0-23)
|
||||
* @returns Meal type or null if outside meal hours
|
||||
*/
|
||||
private getMealTypeByHour(hour: number): MealType | null {
|
||||
const MEAL_TIME_RANGES = {
|
||||
BREAKFAST: { start: 6, end: 11 },
|
||||
LUNCH: { start: 11, end: 15 },
|
||||
SNACK: { start: 15, end: 19 },
|
||||
DINNER: { start: 19, end: 24 },
|
||||
} as const;
|
||||
|
||||
if (hour >= MEAL_TIME_RANGES.BREAKFAST.start && hour < MEAL_TIME_RANGES.BREAKFAST.end) {
|
||||
return MealType.BREAKFAST;
|
||||
}
|
||||
if (hour >= MEAL_TIME_RANGES.LUNCH.start && hour < MEAL_TIME_RANGES.LUNCH.end) {
|
||||
return MealType.LUNCH;
|
||||
}
|
||||
if (hour >= MEAL_TIME_RANGES.SNACK.start && hour < MEAL_TIME_RANGES.SNACK.end) {
|
||||
return MealType.SNACK;
|
||||
}
|
||||
if (hour >= MEAL_TIME_RANGES.DINNER.start && hour < MEAL_TIME_RANGES.DINNER.end) {
|
||||
return MealType.DINNER;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert meal type compatibility - if food has mealTypes, current meal time must be in that array
|
||||
*/
|
||||
assertMealTypeCompatibility(food: Food): void {
|
||||
// If food has no meal type restrictions, allow it
|
||||
if (!food.mealTypes || food.mealTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { mealType } = this.getCurrentIranTimeContext();
|
||||
|
||||
// If current time is outside meal hours, throw error
|
||||
if (mealType === null) {
|
||||
throw new BadRequestException(
|
||||
`Food ${food.title || food.id} is only available during meal times (breakfast, lunch, snack, or dinner). Current time is outside meal hours.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if current meal type is in food's allowed meal types
|
||||
if (!food.mealTypes.includes(mealType)) {
|
||||
const allowedMealTypes = food.mealTypes.join(', ');
|
||||
throw new BadRequestException(
|
||||
`Food ${food.title || food.id} is only available for ${allowedMealTypes}. Current meal time is ${mealType}, which is incompatible.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert weekday compatibility - current weekday must be in food's weekDays array
|
||||
*/
|
||||
assertWeekdayCompatibility(food: Food): void {
|
||||
// If food has no weekday restrictions (empty array or all days), allow it
|
||||
if (!food.weekDays || food.weekDays.length === 0 || food.weekDays.length === 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { iranWeekDay } = this.getCurrentIranTimeContext();
|
||||
|
||||
// Check if current weekday is in food's allowed weekdays
|
||||
if (!food.weekDays.includes(iranWeekDay)) {
|
||||
const dayNames = ['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
|
||||
const currentDayName = dayNames[iranWeekDay];
|
||||
const allowedDays = food.weekDays.map(day => dayNames[day]).join(', ');
|
||||
throw new BadRequestException(
|
||||
`Food ${food.title || food.id} is only available on ${allowedDays}. Today is ${currentDayName}, which is not available.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,16 @@ export class CartService {
|
||||
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);
|
||||
}
|
||||
|
||||
// Final recalculation + save and return cart
|
||||
return this.recalculateAndSaveCart(cart);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user