using Microsoft.AspNetCore.Mvc; using Meezi.API.Services; using Meezi.Core.Interfaces; using Meezi.Shared; namespace Meezi.API.Controllers; [Route("api/cafes/{cafeId}/inventory")] public class InventoryController : CafeApiControllerBase { private readonly IInventoryService _inventory; public InventoryController(IInventoryService inventory) => _inventory = inventory; [HttpGet("ingredients")] public async Task List(string cafeId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _inventory.ListAsync(cafeId, ct); return Ok(new ApiResponse(true, data)); } [HttpGet("low-stock")] public async Task LowStock(string cafeId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _inventory.LowStockAsync(cafeId, ct); return Ok(new ApiResponse(true, data)); } [HttpPost("ingredients")] public async Task Create( string cafeId, [FromBody] CreateIngredientRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (string.IsNullOrWhiteSpace(request.Name)) return BadRequest(new ApiResponse(false, null, new ApiError("VALIDATION_ERROR", "Name is required."))); if (request.QuantityOnHand > 0 && request.TotalPaidToman > 0 && string.IsNullOrWhiteSpace(request.BranchId)) return BadRequest(new ApiResponse(false, null, new ApiError("BRANCH_ID_REQUIRED", "Branch is required when recording purchase cost."))); var created = await _inventory.CreateAsync(cafeId, request, ct); return Ok(new ApiResponse(true, created)); } [HttpPatch("ingredients/{ingredientId}")] public async Task Update( string cafeId, string ingredientId, [FromBody] UpdateIngredientRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var updated = await _inventory.UpdateAsync(cafeId, ingredientId, request, ct); if (updated is null) return NotFoundError(); return Ok(new ApiResponse(true, updated)); } [HttpDelete("ingredients/{ingredientId}")] public async Task Delete( string cafeId, string ingredientId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var deleted = await _inventory.DeleteAsync(cafeId, ingredientId, ct); if (!deleted) return NotFoundError(); return Ok(new ApiResponse(true, new { id = ingredientId })); } [HttpPost("ingredients/{ingredientId}/adjust")] public async Task Adjust( string cafeId, string ingredientId, [FromBody] AdjustStockRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; try { var updated = await _inventory.AdjustAsync(cafeId, ingredientId, request, tenant.UserId, ct); if (updated is null) return NotFoundError(); return Ok(new ApiResponse(true, updated)); } catch (InvalidOperationException ex) when (ex.Message is "TOTAL_PAID_REQUIRED" or "BRANCH_ID_REQUIRED") { return BadRequest(new ApiResponse(false, null, new ApiError(ex.Message, ex.Message switch { "TOTAL_PAID_REQUIRED" => "Enter total paid for stock received.", _ => "Branch is required for purchase cost." }))); } } [HttpGet("purchases")] public async Task PurchasesSummary( string cafeId, [FromQuery] string branchId, [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (string.IsNullOrWhiteSpace(branchId)) return BadRequest(new ApiResponse(false, null, new ApiError("BRANCH_ID_REQUIRED", "branchId is required."))); var today = DateOnly.FromDateTime(DateTime.UtcNow); var summary = await _inventory.GetPurchasesSummaryAsync( cafeId, branchId, from ?? today.AddDays(-30), to ?? today, ct); return Ok(new ApiResponse(true, summary)); } [HttpGet("menu-items/{menuItemId}/recipe")] public async Task GetRecipe( string cafeId, string menuItemId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var recipe = await _inventory.GetRecipeAsync(cafeId, menuItemId, ct); if (recipe is null) return NotFoundError("Menu item not found."); return Ok(new ApiResponse(true, recipe)); } [HttpPut("menu-items/{menuItemId}/recipe")] public async Task SetRecipe( string cafeId, string menuItemId, [FromBody] SetMenuItemRecipeRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var recipe = await _inventory.SetRecipeAsync(cafeId, menuItemId, request, ct); if (recipe is null) return NotFoundError("Menu item not found."); return Ok(new ApiResponse(true, recipe)); } }