using Zentral.API.Modules.Warehouse.Services; using Zentral.Domain.Entities.Warehouse; using Microsoft.AspNetCore.Mvc; namespace Zentral.API.Modules.Warehouse.Controllers; /// /// Controller per la gestione delle partite/lotti /// [ApiController] [Route("api/warehouse/batches")] public class BatchesController : ControllerBase { private readonly IWarehouseService _warehouseService; private readonly ILogger _logger; public BatchesController( IWarehouseService warehouseService, ILogger logger) { _warehouseService = warehouseService; _logger = logger; } /// /// Ottiene la lista delle partite con filtri opzionali /// [HttpGet] public async Task>> GetBatches( [FromQuery] int? articleId = null, [FromQuery] BatchStatus? status = null) { var batches = await _warehouseService.GetBatchesAsync(articleId, status); return Ok(batches.Select(MapToDto)); } /// /// Ottiene una partita per ID /// [HttpGet("{id}")] public async Task> GetBatch(int id) { var batch = await _warehouseService.GetBatchByIdAsync(id); if (batch == null) return NotFound(); return Ok(MapToDto(batch)); } /// /// Ottiene una partita per articolo e numero lotto /// [HttpGet("by-number/{articleId}/{batchNumber}")] public async Task> GetBatchByNumber(int articleId, string batchNumber) { var batch = await _warehouseService.GetBatchByNumberAsync(articleId, batchNumber); if (batch == null) return NotFound(); return Ok(MapToDto(batch)); } /// /// Crea una nuova partita /// [HttpPost] public async Task> CreateBatch([FromBody] CreateBatchDto dto) { try { var batch = new ArticleBatch { ArticleId = dto.ArticleId, BatchNumber = dto.BatchNumber, ProductionDate = dto.ProductionDate, ExpiryDate = dto.ExpiryDate, SupplierBatch = dto.SupplierBatch, SupplierId = dto.SupplierId, UnitCost = dto.UnitCost, InitialQuantity = dto.InitialQuantity, CurrentQuantity = dto.InitialQuantity, Status = BatchStatus.Available, Certifications = dto.Certifications, Notes = dto.Notes }; var created = await _warehouseService.CreateBatchAsync(batch); return CreatedAtAction(nameof(GetBatch), new { id = created.Id }, MapToDto(created)); } catch (ArgumentException ex) { return NotFound(new { error = ex.Message }); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } } /// /// Aggiorna una partita esistente /// [HttpPut("{id}")] public async Task> UpdateBatch(int id, [FromBody] UpdateBatchDto dto) { try { var existing = await _warehouseService.GetBatchByIdAsync(id); if (existing == null) return NotFound(); if (dto.ProductionDate.HasValue) existing.ProductionDate = dto.ProductionDate; if (dto.ExpiryDate.HasValue) existing.ExpiryDate = dto.ExpiryDate; if (dto.SupplierBatch != null) existing.SupplierBatch = dto.SupplierBatch; if (dto.UnitCost.HasValue) existing.UnitCost = dto.UnitCost; if (dto.Certifications != null) existing.Certifications = dto.Certifications; if (dto.Notes != null) existing.Notes = dto.Notes; var updated = await _warehouseService.UpdateBatchAsync(existing); return Ok(MapToDto(updated)); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } } /// /// Aggiorna lo stato di una partita /// [HttpPut("{id}/status")] public async Task UpdateBatchStatus(int id, [FromBody] UpdateBatchStatusDto dto) { try { await _warehouseService.UpdateBatchStatusAsync(id, dto.Status); return Ok(); } catch (ArgumentException ex) { return NotFound(new { error = ex.Message }); } } /// /// Ottiene le partite in scadenza /// [HttpGet("expiring")] public async Task>> GetExpiringBatches([FromQuery] int daysThreshold = 30) { var batches = await _warehouseService.GetExpiringBatchesAsync(daysThreshold); return Ok(batches.Select(MapToDto)); } /// /// Registra un controllo qualità sulla partita /// [HttpPost("{id}/quality-check")] public async Task> RecordQualityCheck(int id, [FromBody] QualityCheckDto dto) { try { var batch = await _warehouseService.GetBatchByIdAsync(id); if (batch == null) return NotFound(); batch.QualityStatus = dto.QualityStatus; batch.LastQualityCheckDate = DateTime.UtcNow; // Aggiorna lo stato del lotto in base al risultato if (dto.QualityStatus == QualityStatus.Rejected) { batch.Status = BatchStatus.Blocked; } else if (dto.QualityStatus == QualityStatus.Approved && batch.Status == BatchStatus.Quarantine) { batch.Status = BatchStatus.Available; } var updated = await _warehouseService.UpdateBatchAsync(batch); return Ok(MapToDto(updated)); } catch (ArgumentException ex) { return NotFound(new { error = ex.Message }); } } #region DTOs public record BatchDto( int Id, int ArticleId, string? ArticleCode, string? ArticleDescription, string BatchNumber, DateTime? ProductionDate, DateTime? ExpiryDate, string? SupplierBatch, int? SupplierId, decimal? UnitCost, decimal InitialQuantity, decimal CurrentQuantity, decimal ReservedQuantity, decimal AvailableQuantity, BatchStatus Status, QualityStatus? QualityStatus, DateTime? LastQualityCheckDate, string? Certifications, string? Notes, bool IsExpired, int? DaysToExpiry, DateTime? CreatedAt, DateTime? UpdatedAt ); public record CreateBatchDto( int ArticleId, string BatchNumber, DateTime? ProductionDate, DateTime? ExpiryDate, string? SupplierBatch, int? SupplierId, decimal? UnitCost, decimal InitialQuantity, string? Certifications, string? Notes ); public record UpdateBatchDto( DateTime? ProductionDate, DateTime? ExpiryDate, string? SupplierBatch, decimal? UnitCost, string? Certifications, string? Notes ); public record UpdateBatchStatusDto(BatchStatus Status); public record QualityCheckDto(QualityStatus QualityStatus, string? Notes); #endregion #region Mapping private static BatchDto MapToDto(ArticleBatch batch) { var isExpired = batch.ExpiryDate.HasValue && batch.ExpiryDate.Value < DateTime.UtcNow; var daysToExpiry = batch.ExpiryDate.HasValue ? (int?)Math.Max(0, (batch.ExpiryDate.Value - DateTime.UtcNow).Days) : null; return new BatchDto( batch.Id, batch.ArticleId, batch.Article?.Code, batch.Article?.Description, batch.BatchNumber, batch.ProductionDate, batch.ExpiryDate, batch.SupplierBatch, batch.SupplierId, batch.UnitCost, batch.InitialQuantity, batch.CurrentQuantity, batch.ReservedQuantity, batch.CurrentQuantity - batch.ReservedQuantity, batch.Status, batch.QualityStatus, batch.LastQualityCheckDate, batch.Certifications, batch.Notes, isExpired, daysToExpiry, batch.CreatedAt, batch.UpdatedAt ); } #endregion }