diff --git a/src/modules/cart/providers/cart-item.service.ts b/src/modules/cart/providers/cart-item.service.ts index d513016..26c2ed1 100644 --- a/src/modules/cart/providers/cart-item.service.ts +++ b/src/modules/cart/providers/cart-item.service.ts @@ -59,12 +59,6 @@ export class CartItemService { async addOrIncrementItem(cart: Cart, foodId: string, restaurantId: string, quantityToAdd: number): Promise { 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) { diff --git a/src/modules/cart/providers/cart-validation.service.ts b/src/modules/cart/providers/cart-validation.service.ts index 6af1065..33bb42f 100644 --- a/src/modules/cart/providers/cart-validation.service.ts +++ b/src/modules/cart/providers/cart-validation.service.ts @@ -12,7 +12,6 @@ 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'; import { CartMessage } from 'src/common/enums/message.enum'; import { WalletTransactionRepository } from 'src/modules/users/repositories/wallet-transaction.repository'; @@ -284,139 +283,5 @@ export class CartValidationService { 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 { - 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( - CartMessage.FOODS_MUST_HAVE_PICKUP_SERVE_FOR_COURIER.replace('[foodTitles]', 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) { - const foodTitle = food.title || food.id; - throw new BadRequestException( - CartMessage.FOOD_ONLY_AVAILABLE_DURING_MEAL_TIMES.replace('[foodTitle]', foodTitle), - ); - } - - // Check if current meal type is in food's allowed meal types - if (!food.mealTypes.includes(mealType)) { - const foodTitle = food.title || food.id; - const mealTypeMap: Record = { - [MealType.BREAKFAST]: 'صبحانه', - [MealType.LUNCH]: 'ناهار', - [MealType.SNACK]: 'عصرانه', - [MealType.DINNER]: 'شام', - }; - const allowedMealTypes = food.mealTypes.map(mt => mealTypeMap[mt]).join(', '); - const currentMealType = mealType ? mealTypeMap[mealType] : mealType; - throw new BadRequestException( - CartMessage.FOOD_ONLY_AVAILABLE_FOR_MEAL_TYPES.replace('[foodTitle]', foodTitle) - .replace('[allowedMealTypes]', allowedMealTypes) - .replace('[mealType]', currentMealType), - ); - } - } - - /** - * 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 = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه']; - const currentDayName = dayNames[iranWeekDay]; - const allowedDays = food.weekDays.map(day => dayNames[day]).join(', '); - const foodTitle = food.title || food.id; - throw new BadRequestException( - CartMessage.FOOD_ONLY_AVAILABLE_ON_DAYS.replace('[foodTitle]', foodTitle) - .replace('[allowedDays]', allowedDays) - .replace('[currentDay]', currentDayName), - ); - } - } } diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index e791c62..f292ffd 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -120,9 +120,6 @@ export class CartService { cart.description = description; } - // Final validation: if effective delivery method is courier, ensure all foods have pickupServe - await this.validationService.assertAllFoodsHavePickupServeForCourier(cart, deliveryMethod.method); - // Final recalculation + save and return cart return this.recalculateAndSaveCart(cart); } diff --git a/src/modules/foods/dto/create-food.dto.ts b/src/modules/foods/dto/create-food.dto.ts index 988ee06..cd814db 100644 --- a/src/modules/foods/dto/create-food.dto.ts +++ b/src/modules/foods/dto/create-food.dto.ts @@ -13,7 +13,6 @@ import { Min, } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { MealType } from '../interface/food.interface'; export class CreateFoodDto { @IsNotEmpty() @@ -37,23 +36,6 @@ export class CreateFoodDto { @ApiPropertyOptional({ type: [String] }) content?: string[]; - @IsOptional() - @IsArray() - @ArrayUnique() - @IsInt({ each: true }) - @Min(0, { each: true }) - @Max(6, { each: true }) - @Type(() => Number) - @ApiPropertyOptional({ type: [Number], example: [0, 1, 2, 3, 4, 5, 6] }) - weekDays?: number[]; - - @IsOptional() - @IsArray() - @ArrayUnique() - @IsEnum(MealType, { each: true }) - @ApiPropertyOptional({ enum: MealType, isArray: true }) - mealTypes?: MealType[]; - @IsOptional() @IsNumber() @Min(0) @@ -61,13 +43,6 @@ export class CreateFoodDto { @ApiPropertyOptional({ example: 120000 }) price?: number; - @IsOptional() - @IsInt() - @Min(0) - @Type(() => Number) - @ApiPropertyOptional({ example: 15 }) - prepareTime?: number; - @IsOptional() @IsBoolean() @ApiPropertyOptional({ example: true }) @@ -80,18 +55,6 @@ export class CreateFoodDto { @ApiPropertyOptional({ type: [String] }) images?: string[]; - @IsOptional() - @IsBoolean() - @ApiPropertyOptional({ example: false }) - @Type(() => Boolean) - inPlaceServe?: boolean; - - @IsOptional() - @IsBoolean() - @ApiPropertyOptional({ example: false }) - @Type(() => Boolean) - pickupServe?: boolean; - @IsOptional() @IsNumber() @Min(0) diff --git a/src/modules/foods/entities/food.entity.ts b/src/modules/foods/entities/food.entity.ts index 14443ae..579fe9b 100644 --- a/src/modules/foods/entities/food.entity.ts +++ b/src/modules/foods/entities/food.entity.ts @@ -4,7 +4,6 @@ import { BaseEntity } from '../../../common/entities/base.entity'; import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity'; import { Review } from 'src/modules/review/entities/review.entity'; import { Inventory } from 'src/modules/inventory/entities/inventory.entity'; -import { MealType } from '../interface/food.interface'; import { Favorite } from './favorite.entity'; @Entity({ tableName: 'foods' }) @@ -45,27 +44,12 @@ export class Food extends BaseEntity { @Property({ type: 'int', nullable: true }) order?: number; - @Property({ type: 'int', nullable: true }) - prepareTime?: number; // in minutes - - @Property({ type: 'jsonb', default: [] }) - weekDays: number[] = [0, 1, 2, 3, 4, 5, 6]; - - @Property({ type: 'jsonb', default: [] }) - mealTypes: MealType[] = []; - @Property({ type: 'boolean', default: true }) isActive: boolean = true; @Property({ type: 'json', nullable: true }) images?: string[]; - @Property({ type: 'boolean', default: false }) - inPlaceServe: boolean = false; - - @Property({ type: 'boolean', default: false }) - pickupServe: boolean = false; - @Property({ type: 'float', default: null }) score?: number | null = null; diff --git a/src/modules/foods/providers/food.service.ts b/src/modules/foods/providers/food.service.ts index 2972052..b1bda09 100644 --- a/src/modules/foods/providers/food.service.ts +++ b/src/modules/foods/providers/food.service.ts @@ -10,7 +10,6 @@ import { CategoryMessage, FoodMessage, RestMessage } from 'src/common/enums/mess import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository'; import { CacheService } from '../../utils/cache.service'; import { Favorite } from '../entities/favorite.entity'; -import { MealType } from '../interface/food.interface'; import { Inventory } from 'src/modules/inventory/entities/inventory.entity'; @Injectable() @@ -38,8 +37,6 @@ export class FoodService { const data: RequiredEntityData = { desc: rest.desc, isActive: rest.isActive ?? true, - inPlaceServe: rest.inPlaceServe ?? false, - pickupServe: rest.pickupServe ?? false, discount: rest.discount ?? 0, isSpecialOffer: rest.isSpecialOffer ?? false, order: rest.order ?? null, @@ -48,11 +45,7 @@ export class FoodService { content: rest.content, // numeric/array fields price: rest.price ?? 0, - prepareTime: rest.prepareTime ?? 0, images: rest.images ?? undefined, - // new fields - weekDays: rest.weekDays ?? [0, 1, 2, 3, 4, 5, 6], - mealTypes: rest.mealTypes ?? [], restaurant: restaurant, category: category, }; @@ -108,9 +101,9 @@ export class FoodService { } /** - * Find active foods for a restaurant based on current week day and meal type in Iran timezone. + * Find active foods for a restaurant. * @param slug - Restaurant slug identifier - * @returns Array of active foods matching current day and meal time + * @returns Array of active foods */ async findByWeekDateAndMealType(slug: string): Promise { const restaurant = await this.restRepository.findOne({ slug }); @@ -118,13 +111,9 @@ export class FoodService { throw new NotFoundException(RestMessage.NOT_FOUND); } - const { iranWeekDay, mealType } = this.getCurrentIranTimeContext(); - const queryFilter: FilterQuery = { restaurant: { slug }, isActive: true, - weekDays: { $contains: iranWeekDay }, - ...(mealType ? { mealTypes: { $contains: mealType } } : {}), } as unknown as FilterQuery; return this.foodRepository.find(queryFilter, { @@ -133,54 +122,6 @@ export class FoodService { }); } - /** - * Get current Iran timezone context (weekday and meal type). - * @returns Object containing Iran weekday (0-6, where 0=Saturday) and current 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; - } - async update(restId: string, id: string, dto: Partial): Promise { const { categoryId, dailyStock, ...rest } = dto; const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] }); diff --git a/src/seeders/data/foods.data.ts b/src/seeders/data/foods.data.ts index 7172bfe..e297acb 100644 --- a/src/seeders/data/foods.data.ts +++ b/src/seeders/data/foods.data.ts @@ -1,5 +1,3 @@ -import { MealType } from '../../modules/foods/interface/food.interface'; - export interface FoodData { title: string; desc: string; @@ -11,12 +9,8 @@ export interface FoodData { stockDefault: number; images: string[]; content?: string[]; - weekDays?: number[]; - mealTypes?: MealType[]; discount?: number; score?: number; - inPlaceServe?: boolean; - pickupServe?: boolean; isSpecialOffer?: boolean; } diff --git a/src/seeders/foods.seeder.ts b/src/seeders/foods.seeder.ts index c2a652a..19e87eb 100644 --- a/src/seeders/foods.seeder.ts +++ b/src/seeders/foods.seeder.ts @@ -2,7 +2,6 @@ import type { EntityManager } from '@mikro-orm/core'; import { Food } from '../modules/foods/entities/food.entity'; import type { Restaurant } from '../modules/restaurants/entities/restaurant.entity'; import type { Category } from '../modules/foods/entities/category.entity'; -import { MealType } from '../modules/foods/interface/food.interface'; import { foodsData } from './data/foods.data'; export class FoodsSeeder { @@ -53,13 +52,8 @@ export class FoodsSeeder { restaurant, category, isActive: foodData.isActive, - // new fields on Food entity - weekDays: foodData.weekDays ?? [0, 1, 2, 3, 4, 5, 6], - mealTypes: foodData.mealTypes ?? [MealType.BREAKFAST, MealType.LUNCH, MealType.DINNER, MealType.SNACK], discount: foodData.discount ?? 0, score: foodData.score ?? 0, - inPlaceServe: true, - pickupServe: true, images: foodData.images, isSpecialOffer: foodData.isSpecialOffer ?? false, });