crete inventory with food creation and update

This commit is contained in:
2025-12-20 14:52:58 +03:30
parent 48524542f2
commit 5febf51c1f
2 changed files with 77 additions and 40 deletions
+6
View File
@@ -99,6 +99,12 @@ export class CreateFoodDto {
@ApiPropertyOptional({ example: 0 }) @ApiPropertyOptional({ example: 0 })
discount?: number; discount?: number;
@IsOptional()
@IsNumber()
@Min(0)
@Type(() => Number)
@ApiProperty({ example: 50 })
dailyStock: number;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
+71 -40
View File
@@ -11,6 +11,7 @@ import { RestRepository } from 'src/modules/restaurants/repositories/rest.reposi
import { CacheService } from '../../utils/cache.service'; import { CacheService } from '../../utils/cache.service';
import { Favorite } from '../entities/favorite.entity'; import { Favorite } from '../entities/favorite.entity';
import { MealType } from '../interface/food.interface'; import { MealType } from '../interface/food.interface';
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
@Injectable() @Injectable()
export class FoodService { export class FoodService {
@@ -25,8 +26,8 @@ export class FoodService {
private readonly cacheService: CacheService, private readonly cacheService: CacheService,
) {} ) {}
async create(restId: string, createFoodDto: CreateFoodDto): Promise<Food> { async create(restId: string, createFoodDto: CreateFoodDto) {
const { categoryId, ...rest } = createFoodDto; const { categoryId, dailyStock = 0, ...rest } = createFoodDto;
const restaurant = await this.restRepository.findOne({ id: restId }); const restaurant = await this.restRepository.findOne({ id: restId });
if (!restaurant) { if (!restaurant) {
throw new NotFoundException(RestMessage.NOT_FOUND); throw new NotFoundException(RestMessage.NOT_FOUND);
@@ -36,39 +37,47 @@ export class FoodService {
throw new NotFoundException(CategoryMessage.NOT_FOUND); throw new NotFoundException(CategoryMessage.NOT_FOUND);
} }
// prepare data with defaults for required fields so repository.create typing is satisfied const { food, inventory } = await this.em.transactional(async em => {
const data: RequiredEntityData<Food> = { // prepare data with defaults for required fields so repository.create typing is satisfied
desc: rest.desc, const data: RequiredEntityData<Food> = {
isActive: rest.isActive ?? true, desc: rest.desc,
inPlaceServe: rest.inPlaceServe ?? false, isActive: rest.isActive ?? true,
pickupServe: rest.pickupServe ?? false, inPlaceServe: rest.inPlaceServe ?? false,
discount: rest.discount ?? 0, pickupServe: rest.pickupServe ?? false,
isSpecialOffer: rest.isSpecialOffer ?? false, discount: rest.discount ?? 0,
// map single-title/content DTO to localized fields isSpecialOffer: rest.isSpecialOffer ?? false,
title: rest.title, // map single-title/content DTO to localized fields
content: rest.content, title: rest.title,
// numeric/array fields content: rest.content,
price: rest.price ?? 0, // numeric/array fields
prepareTime: rest.prepareTime ?? 0, price: rest.price ?? 0,
images: rest.images ?? undefined, prepareTime: rest.prepareTime ?? 0,
// new fields images: rest.images ?? undefined,
weekDays: rest.weekDays ?? [0, 1, 2, 3, 4, 5, 6], // new fields
mealTypes: rest.mealTypes ?? [], weekDays: rest.weekDays ?? [0, 1, 2, 3, 4, 5, 6],
restaurant: restaurant, mealTypes: rest.mealTypes ?? [],
category: category, restaurant: restaurant,
}; category: category,
};
const food = this.foodRepository.create(data); const food = em.create(Food, data);
if (!food) { const newInventoryRecord = em.create(Inventory, {
throw new Error('Failed to create food entity'); food: food,
} availableStock: dailyStock,
totalStock: dailyStock,
});
await this.em.persistAndFlush(food); await em.flush();
return { food, inventory: newInventoryRecord };
});
// Invalidate cache for the restaurant // Re-load created entities with the primary EM to ensure they're attached
// await this.invalidateRestaurantFoodsCache(restaurant.slug); 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) { findAll(restId: string, dto: FindFoodsDto) {
@@ -157,13 +166,15 @@ export class FoodService {
} }
async update(restId: string, id: string, dto: Partial<CreateFoodDto>): Promise<Food> { async update(restId: string, id: string, dto: Partial<CreateFoodDto>): Promise<Food> {
const { categoryId, dailyStock, ...rest } = dto;
const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] }); const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
if (!food) { if (!food) {
throw new NotFoundException(FoodMessage.NOT_FOUND); throw new NotFoundException(FoodMessage.NOT_FOUND);
} }
// attach new categories if provided (adds, does not attempt to preserve/remove existing ones) // attach new categories if provided (adds, does not attempt to preserve/remove existing ones)
if (dto.categoryId) { if (categoryId) {
const category = await this.categoryRepository.findOne({ id: dto.categoryId, restaurant: { id: restId } }); const category = await this.categoryRepository.findOne({ id: categoryId, restaurant: { id: restId } });
if (!category) { if (!category) {
throw new NotFoundException(CategoryMessage.NOT_FOUND); throw new NotFoundException(CategoryMessage.NOT_FOUND);
@@ -172,17 +183,37 @@ export class FoodService {
this.em.assign(food, { category: category }); this.em.assign(food, { category: category });
} }
// assign other fields from DTO // assign other fields from DTO (excluding dailyStock handled below)
const { categoryId, ...rest } = dto;
void categoryId;
this.em.assign(food, rest); 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 if (typeof dailyStock !== 'undefined') {
// await this.invalidateRestaurantFoodsCache(food.restaurant.slug); 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<void> { async remove(restId: string, id: string): Promise<void> {