From 5febf51c1f01324ee80db53d04320c7e6ae38fa5 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 20 Dec 2025 14:52:58 +0330 Subject: [PATCH] crete inventory with food creation and update --- src/modules/foods/dto/create-food.dto.ts | 6 ++ src/modules/foods/providers/food.service.ts | 111 +++++++++++++------- 2 files changed, 77 insertions(+), 40 deletions(-) diff --git a/src/modules/foods/dto/create-food.dto.ts b/src/modules/foods/dto/create-food.dto.ts index e91825f..d19cb39 100644 --- a/src/modules/foods/dto/create-food.dto.ts +++ b/src/modules/foods/dto/create-food.dto.ts @@ -99,6 +99,12 @@ export class CreateFoodDto { @ApiPropertyOptional({ example: 0 }) discount?: number; + @IsOptional() + @IsNumber() + @Min(0) + @Type(() => Number) + @ApiProperty({ example: 50 }) + dailyStock: number; @IsOptional() @IsBoolean() diff --git a/src/modules/foods/providers/food.service.ts b/src/modules/foods/providers/food.service.ts index 5b2ec27..fc36254 100644 --- a/src/modules/foods/providers/food.service.ts +++ b/src/modules/foods/providers/food.service.ts @@ -11,6 +11,7 @@ import { RestRepository } from 'src/modules/restaurants/repositories/rest.reposi 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() export class FoodService { @@ -25,8 +26,8 @@ export class FoodService { private readonly cacheService: CacheService, ) {} - async create(restId: string, createFoodDto: CreateFoodDto): Promise { - const { categoryId, ...rest } = createFoodDto; + async create(restId: string, createFoodDto: CreateFoodDto) { + const { categoryId, dailyStock = 0, ...rest } = createFoodDto; const restaurant = await this.restRepository.findOne({ id: restId }); if (!restaurant) { throw new NotFoundException(RestMessage.NOT_FOUND); @@ -36,39 +37,47 @@ export class FoodService { throw new NotFoundException(CategoryMessage.NOT_FOUND); } - // prepare data with defaults for required fields so repository.create typing is satisfied - 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, - // map single-title/content DTO to localized fields - title: rest.title, - 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, - }; + const { food, inventory } = await this.em.transactional(async em => { + // prepare data with defaults for required fields so repository.create typing is satisfied + 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, + // map single-title/content DTO to localized fields + title: rest.title, + 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, + }; - const food = this.foodRepository.create(data); - if (!food) { - throw new Error('Failed to create food entity'); - } + const food = em.create(Food, data); + const newInventoryRecord = em.create(Inventory, { + food: food, + availableStock: dailyStock, + totalStock: dailyStock, + }); - await this.em.persistAndFlush(food); + await em.flush(); + return { food, inventory: newInventoryRecord }; + }); - // Invalidate cache for the restaurant - // await this.invalidateRestaurantFoodsCache(restaurant.slug); + // Re-load created entities with the primary EM to ensure they're attached + const savedFood = food?.id + ? await this.foodRepository.findOne({ id: food.id }, { populate: ['category', 'restaurant'] }) + : null; + const savedInventory = inventory?.id ? await this.em.findOne(Inventory, { id: inventory.id }) : inventory; - return food; + return { food: savedFood ?? food, inventory: savedInventory ?? inventory }; } findAll(restId: string, dto: FindFoodsDto) { @@ -157,13 +166,15 @@ export class FoodService { } 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'] }); if (!food) { throw new NotFoundException(FoodMessage.NOT_FOUND); } + // attach new categories if provided (adds, does not attempt to preserve/remove existing ones) - if (dto.categoryId) { - const category = await this.categoryRepository.findOne({ id: dto.categoryId, restaurant: { id: restId } }); + if (categoryId) { + const category = await this.categoryRepository.findOne({ id: categoryId, restaurant: { id: restId } }); if (!category) { throw new NotFoundException(CategoryMessage.NOT_FOUND); @@ -172,17 +183,37 @@ export class FoodService { this.em.assign(food, { category: category }); } - // assign other fields from DTO - const { categoryId, ...rest } = dto; - void categoryId; + // assign other fields from DTO (excluding dailyStock handled below) this.em.assign(food, rest); - await this.em.persistAndFlush(food); + // Persist food and update/create related inventory atomically + await this.em.transactional(async em => { + await em.persistAndFlush(food); - // Invalidate cache for the restaurant - // await this.invalidateRestaurantFoodsCache(food.restaurant.slug); + if (typeof dailyStock !== 'undefined') { + const inventoryRecord = await em.findOne(Inventory, { food: food }); + if (inventoryRecord) { + inventoryRecord.totalStock = dailyStock; + inventoryRecord.availableStock = dailyStock; + } else { + em.create(Inventory, { + food: food, + availableStock: dailyStock, + totalStock: dailyStock, + }); + } + } - return food; + await em.flush(); + }); + + // Re-load the food to ensure populated relations and stable instance + const savedFood = await this.foodRepository.findOne({ id: food.id }, { populate: ['category', 'restaurant'] }); + + // Invalidate cache for the restaurant if present (optional) + // await this.invalidateRestaurantFoodsCache(savedFood?.restaurant?.slug); + + return savedFood ?? food; } async remove(restId: string, id: string): Promise {