Files
dmenu-api/src/modules/foods/providers/food.service.ts
T
2025-11-25 11:26:33 +03:30

118 lines
4.0 KiB
TypeScript

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateFoodDto } from '../dto/create-food.dto';
import { FindFoodsDto } from '../dto/find-foods.dto';
import { FoodRepository } from '../repositories/food.repository';
import { CategoryRepository } from '../repositories/category.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Food } from '../entities/food.entity';
import { CategoryMessage, FoodMessage, RestMessage } from 'src/common/enums/message.enum';
import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository';
@Injectable()
export class FoodService {
constructor(
private readonly foodRepository: FoodRepository,
private readonly categoryRepository: CategoryRepository,
private readonly restRepository: RestRepository,
private readonly em: EntityManager,
) {}
async create(restId: string, createFoodDto: CreateFoodDto): Promise<Food> {
const { categoryId, ...rest } = createFoodDto;
const restaurant = await this.restRepository.findOne({ id: restId });
if (!restaurant) {
throw new NotFoundException(RestMessage.NOT_FOUND);
}
const category = await this.categoryRepository.findOne({ id: categoryId });
if (!category) {
throw new NotFoundException(CategoryMessage.NOT_FOUND);
}
// prepare data with defaults for required fields so repository.create typing is satisfied
const data: RequiredEntityData<Food> = {
// boolean/day flags
sat: rest.sat ?? false,
sun: rest.sun ?? false,
mon: rest.mon ?? false,
breakfast: rest.breakfast ?? false,
noon: rest.noon ?? false,
dinner: rest.dinner ?? false,
// pickup/stock
stock: rest.stock ?? 0,
stockDefault: rest.stockDefault ?? 0,
isActive: rest.isActive ?? true,
inPlaceServe: rest.inPlaceServe ?? false,
pickupServe: rest.pickupServe ?? false,
discount: rest.discount ?? 0,
// map single-title/content DTO to localized fields
title: rest.title,
content: rest.content,
// numeric/array fields
price: rest.price ?? 0,
points: rest.points ?? 0,
prepareTime: rest.prepareTime ?? 0,
images: rest.images ?? undefined,
restaurant: restaurant,
category: category
};
const food = this.foodRepository.create(data);
if (!food) {
throw new Error('Failed to create food entity');
}
await this.em.persistAndFlush(food);
return food;
}
findAll(restId: string, dto: FindFoodsDto) {
return this.foodRepository.findAllPaginated(restId, dto);
}
async findById(id: string): Promise<Food> {
const food = await this.foodRepository.findOne({ id }, { populate: ['category'] });
if (!food) {
throw new NotFoundException(FoodMessage.NOT_FOUND);
}
return food;
}
async findAllByRestaurant(slug: string): Promise<Food[]> {
const restaurant = await this.restRepository.findOne({ slug });
if (!restaurant) {
throw new NotFoundException(RestMessage.NOT_FOUND);
}
return this.foodRepository.find({ restaurant: { slug }, isActive: true }, { populate: ['category'] });
}
async update(id: string, dto: Partial<CreateFoodDto>): Promise<Food> {
const food = await this.foodRepository.findOne({ id });
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 });
if (!category) {
throw new NotFoundException(CategoryMessage.NOT_FOUND);
}
this.em.assign(food, { category: category });
}
await this.em.persistAndFlush(food);
return food;
}
async remove(id: string) {
const food = await this.foodRepository.findOne({ id });
if (!food) {
throw new NotFoundException(FoodMessage.NOT_FOUND);
}
food.deletedAt = new Date();
await this.em.persistAndFlush(food);
}
}