using Fitness_core_BE.DTOs.Inventory; using Fitness_core_BE.Helpers; using Fitness_core_BE.Interfaces.Services; using Fitness_core_BE.Models.IOs; using Microsoft.AspNetCore.Mvc; using static Fitness_core_BE.Helpers.JwtMiddleware; using static Fitness_core_BE.Helpers.ApiResponseHelper; namespace Fitness_core_BE.Controllers { // Simple inventory: Products, Suppliers, GRN (stock in), Sales (stock out) and Returns // (stock write-offs). All gated on the "inventory" nav permission. [ApiController] [Route("api/client/inventory")] public class InventoryController(IInventoryService inventoryService) : ControllerBase { private readonly IInventoryService _inventoryService = inventoryService; [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("products")] public async Task>> GetProducts([FromQuery] bool includeInactive = false) => Response(await _inventoryService.GetProducts(new { includeInactive }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("products/{id:guid}")] public async Task>> GetProductById(Guid id) => Response(await _inventoryService.GetProductById(new { productId = id }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpPost("products")] public async Task>> CreateProduct([FromBody] CreateProductRequest request) => Response(await _inventoryService.CreateProduct(new { name = request.Name, unitOfMeasure = request.UnitOfMeasure, sellingPrice = request.SellingPrice }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpPut("products/{id:guid}")] public async Task>> UpdateProduct(Guid id, [FromBody] UpdateProductRequest request) { var payload = new Dictionary { ["productId"] = id }; if (request.Name != null) payload["name"] = request.Name; if (request.UnitOfMeasure != null) payload["unitOfMeasure"] = request.UnitOfMeasure; if (request.SellingPrice.HasValue) payload["sellingPrice"] = request.SellingPrice.Value; if (request.IsActive.HasValue) payload["isActive"] = request.IsActive.Value; return Response(await _inventoryService.UpdateProduct(payload, HttpContext)); } [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpPost("products/{id:guid}/image")] [RequestSizeLimit(6_000_000)] [Consumes("multipart/form-data")] public async Task>> UploadProductImage(Guid id, [FromForm] IFormFile image) => Response(await _inventoryService.UploadProductImage(id, image, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("suppliers")] public async Task>> GetSuppliers([FromQuery] bool includeInactive = false) => Response(await _inventoryService.GetSuppliers(new { includeInactive }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpPost("suppliers")] public async Task>> CreateSupplier([FromBody] CreateSupplierRequest request) => Response(await _inventoryService.CreateSupplier(new { name = request.Name, phone = request.Phone, email = request.Email, address = request.Address }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("grn")] public async Task>> GetGrns() => Response(await _inventoryService.GetGrns(new { }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("grn/{id:guid}")] public async Task>> GetGrnById(Guid id) => Response(await _inventoryService.GetGrnById(new { grnId = id }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpPost("grn")] public async Task>> CreateGrn([FromBody] CreateGrnRequest request) => Response(await _inventoryService.CreateGrn(new { supplierId = request.SupplierId, receivedAt = request.ReceivedAt?.ToString("o"), notes = request.Notes, items = request.Items.Select(i => new { productId = i.ProductId, quantity = i.Quantity, unitCost = i.UnitCost }) }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("sales")] public async Task>> GetSales() => Response(await _inventoryService.GetSales(new { }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("sales/{id:guid}")] public async Task>> GetSaleById(Guid id) => Response(await _inventoryService.GetSaleById(new { saleId = id }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpPost("sales")] public async Task>> CreateSale([FromBody] CreateSaleRequest request) => Response(await _inventoryService.CreateSale(new { soldAt = request.SoldAt?.ToString("o"), paymentMethod = request.PaymentMethod, customerId = request.CustomerId, customerName = request.CustomerName, items = request.Items.Select(i => new { productId = i.ProductId, quantity = i.Quantity, unitPrice = i.UnitPrice }) }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("returns")] public async Task>> GetStockReturns() => Response(await _inventoryService.GetStockReturns(new { }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("returns/{id:guid}")] public async Task>> GetStockReturnById(Guid id) => Response(await _inventoryService.GetStockReturnById(new { stockReturnId = id }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpPost("returns")] public async Task>> CreateStockReturn([FromBody] CreateStockReturnRequest request) => Response(await _inventoryService.CreateStockReturn(new { productId = request.ProductId, quantity = request.Quantity, category = request.Category, saleId = request.SaleId, grnId = request.GrnId, notes = request.Notes, returnedAt = request.ReturnedAt?.ToString("o") }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("inventory")] [HttpGet("stock-movements")] public async Task>> GetStockMovements() => Response(await _inventoryService.GetStockMovements(new { }, HttpContext)); } }