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> {
|
async addOrIncrementItem(cart: Cart, foodId: string, restaurantId: string, quantityToAdd: number): Promise<void> {
|
||||||
const food = await this.validationService.validateAndGetFood(foodId, restaurantId, quantityToAdd);
|
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);
|
const index = this.getItemIndex(cart, food.id);
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Order } from '../../orders/entities/order.entity';
|
|||||||
import { OrderStatus } from '../../orders/interface/order.interface';
|
import { OrderStatus } from '../../orders/interface/order.interface';
|
||||||
import { Cart } from '../interfaces/cart.interface';
|
import { Cart } from '../interfaces/cart.interface';
|
||||||
import { GeographicUtils } from '../utils/geographic.utils';
|
import { GeographicUtils } from '../utils/geographic.utils';
|
||||||
|
import { MealType } from '../../foods/interface/food.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CartValidationService {
|
export class CartValidationService {
|
||||||
@@ -290,5 +291,126 @@ export class CartValidationService {
|
|||||||
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] });
|
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] });
|
||||||
return new Map(foods.map(f => [f.id, f]));
|
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;
|
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
|
// Final recalculation + save and return cart
|
||||||
return this.recalculateAndSaveCart(cart);
|
return this.recalculateAndSaveCart(cart);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user