change entity names

This commit is contained in:
2026-02-08 09:18:20 +03:30
parent 6be6a66079
commit 9a7010dcea
180 changed files with 2751 additions and 2513 deletions
@@ -0,0 +1,214 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateFoodDto } from '../dto/create-product.dto';
import { FindFoodsDto } from ../../..products.dto';
import { FoodRepository } from '../repositories/product.repository';
import { CategoryRepository } from '../repositories/category.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData, FilterQuery } from '@mikro-orm/core';
import { Product } from '../entities/product.entity';
import { CategoryMessage, FoodMessage, RestMessage } from 'src/common/enums/message.enum';
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { CacheService } from '../../../utils/cache.service';
import { Favorite } from '../entities/favorite.entity';
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
@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) {
const { categoryId, dailyStock = 0, ...rest } = createFoodDto;
const shop = await this.restRepository.findOne({ id: restId });
if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND);
}
const category = await this.categoryRepository.findOne({ id: categoryId, shop: { id: restId } });
if (!category) {
throw new NotFoundException(CategoryMessage.NOT_FOUND);
}
const { product, inventory } = await this.em.transactional(async em => {
// prepare data with defaults for required fields so repository.create typing is satisfied
const data: RequiredEntityData<Product> = {
desc: rest.desc,
isActive: rest.isActive ?? true,
discount: rest.discount ?? 0,
isSpecialOffer: rest.isSpecialOffer ?? false,
order: rest.order ?? null,
// map single-title/content DTO to localized fields
title: rest.title,
content: rest.content,
// numeric/array fields
price: rest.price ?? 0,
images: rest.images ?? undefined,
shop: shop,
category: category,
};
const product = em.create(Product, data);
const newInventoryRecord = em.create(Inventory, {
product: product,
availableStock: dailyStock,
totalStock: dailyStock,
});
await em.flush();
return { product, inventory: newInventoryRecord };
});
// Re-load created entities with the primary EM to ensure they're attached
const savedFood = product?.id
? await this.foodRepository.findOne({ id: product.id }, { populate: ['category', 'shop'] })
: null;
const savedInventory = inventory?.id ? await this.em.findOne(Inventory, { id: inventory.id }) : inventory;
return { product: savedFood ?? product, inventory: savedInventory ?? inventory };
}
findAll(restId: string, dto: FindFoodsDto) {
return this.foodRepository.findAllPaginated(restId, dto);
}
/**
* Public product detail (only active products are visible).
*/
async findPublicById(foodId: string, userId?: string): Promise<any> {
const product = await this.foodRepository.findOne({ id: foodId, isActive: true }, { populate: ['category', 'inventory'] });
if (!product) throw new NotFoundException(FoodMessage.NOT_FOUND);
let isFavorite = false;
if (userId) {
isFavorite = (await this.em.count(Favorite, { user: { id: userId }, product: { id: foodId } })) > 0;
}
return {
...product,
isFavorite,
};
}
/**
* Admin product detail (scoped to the authenticated shop).
*/
async findAdminById(restId: string, id: string): Promise<Product> {
const product = await this.foodRepository.findOne({ id, shop: { id: restId } }, { populate: ['category', 'inventory'] });
if (!product) throw new NotFoundException(FoodMessage.NOT_FOUND);
return product;
}
/**
* Find active products for a shop.
* @param slug - Shop slug identifier
* @returns Array of active products
*/
async findByWeekDateAndMealType(slug: string): Promise<Product[]> {
const shop = await this.restRepository.findOne({ slug });
if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND);
}
const queryFilter: FilterQuery<Product> = {
shop: { slug },
isActive: true,
} as unknown as FilterQuery<Product>;
return this.foodRepository.find(queryFilter, {
populate: ['category'],
orderBy: { order: 'asc' }
});
}
async update(restId: string, id: string, dto: Partial<CreateFoodDto>): Promise<Product> {
const { categoryId, dailyStock, ...rest } = dto;
const product = await this.foodRepository.findOne({ id, shop: { id: restId } }, { populate: ['shop'] });
if (!product) {
throw new NotFoundException(FoodMessage.NOT_FOUND);
}
// attach new categories if provided (adds, does not attempt to preserve/remove existing ones)
if (categoryId) {
const category = await this.categoryRepository.findOne({ id: categoryId, shop: { id: restId } });
if (!category) {
throw new NotFoundException(CategoryMessage.NOT_FOUND);
}
this.em.assign(product, { category: category });
}
// assign other fields from DTO (excluding dailyStock handled below)
this.em.assign(product, rest);
// Persist product and update/create related inventory atomically
await this.em.transactional(async em => {
await em.persistAndFlush(product);
if (typeof dailyStock !== 'undefined') {
const inventoryRecord = await em.findOne(Inventory, { product: product });
if (inventoryRecord) {
inventoryRecord.totalStock = dailyStock;
} else {
em.create(Inventory, {
product: product,
availableStock: dailyStock,
totalStock: dailyStock,
});
}
}
await em.flush();
});
// Re-load the product to ensure populated relations and stable instance
const savedFood = await this.foodRepository.findOne({ id: product.id }, { populate: ['category', 'shop'] });
// Invalidate cache for the shop if present (optional)
// await this.invalidateRestaurantFoodsCache(savedFood?.shop?.slug);
return savedFood ?? product;
}
async remove(restId: string, id: string): Promise<void> {
const product = await this.foodRepository.findOne({ id, shop: { id: restId } }, { populate: ['shop'] });
if (!product) {
throw new NotFoundException(FoodMessage.NOT_FOUND);
}
// const restaurantSlug = product.shop.slug;
product.deletedAt = new Date();
await this.em.persistAndFlush(product);
// Invalidate cache for the shop
// await this.invalidateRestaurantFoodsCache(restaurantSlug);
}
async toggleFavorite(userId: string, foodId: string) {
const favorite = await this.em.findOne(Favorite, { user: userId, product: foodId });
if (favorite) {
return this.em.removeAndFlush(favorite);
}
const newFavorite = this.em.create(Favorite, {
user: userId,
product: foodId,
});
return this.em.persistAndFlush(newFavorite);
}
async getMyFavorites(userId: string, restId: string) {
return this.em.find(Favorite, { user: userId, product: { shop: { id: restId } } }, { populate: ['product'] });
}
/**
* Invalidate cache for shop products
*/
// private async invalidateRestaurantFoodsCache(slug: string): Promise<void> {
// const cacheKey = `${this.FOODS_BY_RESTAURANT_CACHE_KEY_PREFIX}${slug}`;
// await this.cacheService.del(cacheKey);
// }
}