using MealPlan.Api.Data; using MealPlan.Api.DTOs.IngredientCatalog; using MealPlan.Api.Models; using Microsoft.EntityFrameworkCore; namespace MealPlan.Api.Services; public class IngredientCatalogService : IIngredientCatalogService { private readonly AppDbContext _db; public IngredientCatalogService(AppDbContext db) => _db = db; public async Task> GetCategoriesAsync(CancellationToken ct = default) { return await _db.IngredientCategories.AsNoTracking() .Where(c => c.IsActive) .OrderBy(c => c.SortOrder) .ThenBy(c => c.Name) .Select(c => new IngredientCategoryDto { Id = c.Id, Code = c.Code, Name = c.Name, ItemCount = c.Items.Count(i => i.IsActive), }) .ToListAsync(ct); } public async Task> GetItemsAsync( string? categoryCode, string? search, CancellationToken ct = default) { var query = _db.IngredientNutritionCatalog.AsNoTracking() .Include(i => i.Category) .Where(i => i.IsActive && i.Category!.IsActive); if (!string.IsNullOrWhiteSpace(categoryCode)) { var code = categoryCode.Trim().ToLowerInvariant(); query = query.Where(i => i.Category!.Code == code); } if (!string.IsNullOrWhiteSpace(search)) { var term = search.Trim().ToLowerInvariant(); query = query.Where(i => i.Name.ToLower().Contains(term) || (i.NamePl != null && i.NamePl.ToLower().Contains(term))); } var rows = await query .OrderBy(i => i.Category!.SortOrder) .ThenBy(i => i.SortOrder) .ThenBy(i => i.Name) .ToListAsync(ct); return rows.Select(MapItem).ToList(); } public async Task GetItemByIdAsync(int id, CancellationToken ct = default) { var row = await _db.IngredientNutritionCatalog.AsNoTracking() .Include(i => i.Category) .FirstOrDefaultAsync(i => i.Id == id && i.IsActive, ct); return row is null ? null : MapItem(row); } public async Task CreateItemAsync( UpsertIngredientNutritionItemDto dto, CancellationToken ct = default) { var category = await _db.IngredientCategories.AsNoTracking() .FirstOrDefaultAsync(c => c.Id == dto.CategoryId && c.IsActive, ct); if (category is null) return null; var entity = new IngredientNutritionCatalogItem { CategoryId = dto.CategoryId, Name = dto.Name.Trim(), NamePl = string.IsNullOrWhiteSpace(dto.NamePl) ? null : dto.NamePl.Trim(), CaloriesPer100G = dto.CaloriesPer100G, ProteinGPer100G = dto.ProteinGPer100G, FatGPer100G = dto.FatGPer100G, CarbsGPer100G = dto.CarbsGPer100G, FiberGPer100G = dto.FiberGPer100G, SortOrder = dto.SortOrder, IsActive = dto.IsActive, }; _db.IngredientNutritionCatalog.Add(entity); await _db.SaveChangesAsync(ct); entity.Category = category; return MapItem(entity); } public async Task UpdateItemAsync( int id, UpsertIngredientNutritionItemDto dto, CancellationToken ct = default) { var entity = await _db.IngredientNutritionCatalog .Include(i => i.Category) .FirstOrDefaultAsync(i => i.Id == id, ct); if (entity is null) return null; var category = await _db.IngredientCategories.AsNoTracking() .FirstOrDefaultAsync(c => c.Id == dto.CategoryId && c.IsActive, ct); if (category is null) return null; entity.CategoryId = dto.CategoryId; entity.Name = dto.Name.Trim(); entity.NamePl = string.IsNullOrWhiteSpace(dto.NamePl) ? null : dto.NamePl.Trim(); entity.CaloriesPer100G = dto.CaloriesPer100G; entity.ProteinGPer100G = dto.ProteinGPer100G; entity.FatGPer100G = dto.FatGPer100G; entity.CarbsGPer100G = dto.CarbsGPer100G; entity.FiberGPer100G = dto.FiberGPer100G; entity.SortOrder = dto.SortOrder; entity.IsActive = dto.IsActive; await _db.SaveChangesAsync(ct); entity.Category = category; return MapItem(entity); } public async Task DeleteItemAsync(int id, CancellationToken ct = default) { var entity = await _db.IngredientNutritionCatalog.FirstOrDefaultAsync(i => i.Id == id, ct); if (entity is null) return false; entity.IsActive = false; await _db.SaveChangesAsync(ct); return true; } private static IngredientNutritionItemDto MapItem(IngredientNutritionCatalogItem item) => new() { Id = item.Id, CategoryId = item.CategoryId, CategoryCode = item.Category?.Code ?? string.Empty, CategoryName = item.Category?.Name ?? string.Empty, Name = item.Name, NamePl = item.NamePl, CaloriesPer100G = item.CaloriesPer100G, ProteinGPer100G = item.ProteinGPer100G, FatGPer100G = item.FatGPer100G, CarbsGPer100G = item.CarbsGPer100G, FiberGPer100G = item.FiberGPer100G, }; }