changed name from Apollinare to Zentral
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
using Zentral.API.Modules.Warehouse.Services;
|
||||
using Zentral.Domain.Entities.Warehouse;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Zentral.API.Modules.Warehouse.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller per la gestione delle giacenze e valorizzazione
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/warehouse/stock")]
|
||||
public class StockLevelsController : ControllerBase
|
||||
{
|
||||
private readonly IWarehouseService _warehouseService;
|
||||
private readonly ILogger<StockLevelsController> _logger;
|
||||
|
||||
public StockLevelsController(
|
||||
IWarehouseService warehouseService,
|
||||
ILogger<StockLevelsController> logger)
|
||||
{
|
||||
_warehouseService = warehouseService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene le giacenze con filtri opzionali
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<List<StockLevelDto>>> GetStockLevels([FromQuery] StockLevelFilterDto? filter)
|
||||
{
|
||||
var stockFilter = filter != null ? new StockLevelFilter
|
||||
{
|
||||
ArticleId = filter.ArticleId,
|
||||
WarehouseId = filter.WarehouseId,
|
||||
BatchId = filter.BatchId,
|
||||
CategoryId = filter.CategoryId,
|
||||
OnlyWithStock = filter.OnlyWithStock,
|
||||
OnlyLowStock = filter.OnlyLowStock,
|
||||
Skip = filter.Skip,
|
||||
Take = filter.Take
|
||||
} : null;
|
||||
|
||||
var stockLevels = await _warehouseService.GetStockLevelsAsync(stockFilter);
|
||||
return Ok(stockLevels.Select(MapToDto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene la giacenza per articolo/magazzino/batch
|
||||
/// </summary>
|
||||
[HttpGet("{articleId}/{warehouseId}")]
|
||||
public async Task<ActionResult<StockLevelDto>> GetStockLevel(int articleId, int warehouseId, [FromQuery] int? batchId = null)
|
||||
{
|
||||
var stockLevel = await _warehouseService.GetStockLevelAsync(articleId, warehouseId, batchId);
|
||||
if (stockLevel == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(MapToDto(stockLevel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene gli articoli sotto scorta
|
||||
/// </summary>
|
||||
[HttpGet("low-stock")]
|
||||
public async Task<ActionResult<List<StockLevelDto>>> GetLowStockArticles()
|
||||
{
|
||||
var lowStock = await _warehouseService.GetLowStockArticlesAsync();
|
||||
return Ok(lowStock.Select(MapToDto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene il riepilogo giacenze per articolo
|
||||
/// </summary>
|
||||
[HttpGet("summary/{articleId}")]
|
||||
public async Task<ActionResult<StockSummaryDto>> GetStockSummary(int articleId)
|
||||
{
|
||||
var article = await _warehouseService.GetArticleByIdAsync(articleId);
|
||||
if (article == null)
|
||||
return NotFound();
|
||||
|
||||
var totalStock = await _warehouseService.GetTotalStockAsync(articleId);
|
||||
var availableStock = await _warehouseService.GetAvailableStockAsync(articleId);
|
||||
var stockLevels = await _warehouseService.GetStockLevelsAsync(new StockLevelFilter { ArticleId = articleId });
|
||||
|
||||
return Ok(new StockSummaryDto(
|
||||
articleId,
|
||||
article.Code,
|
||||
article.Description,
|
||||
article.UnitOfMeasure,
|
||||
totalStock,
|
||||
availableStock,
|
||||
article.MinimumStock,
|
||||
article.MaximumStock,
|
||||
article.ReorderPoint,
|
||||
article.MinimumStock.HasValue && totalStock <= article.MinimumStock.Value,
|
||||
stockLevels.Sum(s => s.StockValue ?? 0),
|
||||
stockLevels.Count,
|
||||
stockLevels.Select(MapToDto).ToList()
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcola la valorizzazione di un articolo
|
||||
/// </summary>
|
||||
[HttpGet("valuation/{articleId}")]
|
||||
public async Task<ActionResult<ArticleValuationDto>> GetArticleValuation(int articleId, [FromQuery] ValuationMethod? method = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var article = await _warehouseService.GetArticleByIdAsync(articleId);
|
||||
if (article == null)
|
||||
return NotFound();
|
||||
|
||||
var effectiveMethod = method ?? article.ValuationMethod ?? ValuationMethod.WeightedAverage;
|
||||
var totalValue = await _warehouseService.CalculateArticleValueAsync(articleId, effectiveMethod);
|
||||
var totalStock = await _warehouseService.GetTotalStockAsync(articleId);
|
||||
var avgCost = await _warehouseService.GetWeightedAverageCostAsync(articleId);
|
||||
|
||||
return Ok(new ArticleValuationDto(
|
||||
articleId,
|
||||
article.Code,
|
||||
article.Description,
|
||||
effectiveMethod,
|
||||
totalStock,
|
||||
article.UnitOfMeasure,
|
||||
avgCost,
|
||||
article.StandardCost,
|
||||
article.LastPurchaseCost,
|
||||
totalValue
|
||||
));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return NotFound(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcola la valorizzazione di periodo
|
||||
/// </summary>
|
||||
[HttpGet("valuation/period/{period}")]
|
||||
public async Task<ActionResult<List<PeriodValuationDto>>> GetPeriodValuation(int period, [FromQuery] int? warehouseId = null)
|
||||
{
|
||||
var valuations = await _warehouseService.GetValuationsAsync(period, warehouseId);
|
||||
return Ok(valuations.Select(v => new PeriodValuationDto(
|
||||
v.Id,
|
||||
v.Period,
|
||||
v.ValuationDate,
|
||||
v.ArticleId,
|
||||
v.Article?.Code ?? "",
|
||||
v.Article?.Description ?? "",
|
||||
v.WarehouseId,
|
||||
v.Warehouse?.Code,
|
||||
v.Warehouse?.Name,
|
||||
v.Quantity,
|
||||
v.Method,
|
||||
v.UnitCost,
|
||||
v.TotalValue,
|
||||
v.InboundQuantity,
|
||||
v.InboundValue,
|
||||
v.OutboundQuantity,
|
||||
v.OutboundValue,
|
||||
v.IsClosed
|
||||
)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Genera la valorizzazione per un articolo e periodo
|
||||
/// </summary>
|
||||
[HttpPost("valuation/calculate")]
|
||||
public async Task<ActionResult<PeriodValuationDto>> CalculatePeriodValuation([FromBody] CalculateValuationDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var valuation = await _warehouseService.CalculatePeriodValuationAsync(dto.ArticleId, dto.Period, dto.WarehouseId);
|
||||
return Ok(new PeriodValuationDto(
|
||||
valuation.Id,
|
||||
valuation.Period,
|
||||
valuation.ValuationDate,
|
||||
valuation.ArticleId,
|
||||
valuation.Article?.Code ?? "",
|
||||
valuation.Article?.Description ?? "",
|
||||
valuation.WarehouseId,
|
||||
valuation.Warehouse?.Code,
|
||||
valuation.Warehouse?.Name,
|
||||
valuation.Quantity,
|
||||
valuation.Method,
|
||||
valuation.UnitCost,
|
||||
valuation.TotalValue,
|
||||
valuation.InboundQuantity,
|
||||
valuation.InboundValue,
|
||||
valuation.OutboundQuantity,
|
||||
valuation.OutboundValue,
|
||||
valuation.IsClosed
|
||||
));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return NotFound(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chiude un periodo (blocca modifiche)
|
||||
/// </summary>
|
||||
[HttpPost("valuation/close-period/{period}")]
|
||||
public async Task<ActionResult> ClosePeriod(int period)
|
||||
{
|
||||
await _warehouseService.ClosePeriodAsync(period);
|
||||
return Ok(new { message = $"Periodo {period} chiuso correttamente" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ricalcola il costo medio ponderato di un articolo
|
||||
/// </summary>
|
||||
[HttpPost("recalculate-average/{articleId}")]
|
||||
public async Task<ActionResult> RecalculateAverageCost(int articleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _warehouseService.UpdateWeightedAverageCostAsync(articleId);
|
||||
var avgCost = await _warehouseService.GetWeightedAverageCostAsync(articleId);
|
||||
return Ok(new { articleId, weightedAverageCost = avgCost });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
#region DTOs
|
||||
|
||||
public record StockLevelFilterDto(
|
||||
int? ArticleId,
|
||||
int? WarehouseId,
|
||||
int? BatchId,
|
||||
int? CategoryId,
|
||||
bool? OnlyWithStock,
|
||||
bool? OnlyLowStock,
|
||||
int Skip = 0,
|
||||
int Take = 100
|
||||
);
|
||||
|
||||
public record StockLevelDto(
|
||||
int Id,
|
||||
int ArticleId,
|
||||
string ArticleCode,
|
||||
string ArticleDescription,
|
||||
string? CategoryName,
|
||||
int WarehouseId,
|
||||
string WarehouseCode,
|
||||
string WarehouseName,
|
||||
int? BatchId,
|
||||
string? BatchNumber,
|
||||
DateTime? BatchExpiryDate,
|
||||
decimal Quantity,
|
||||
decimal ReservedQuantity,
|
||||
decimal AvailableQuantity,
|
||||
decimal OnOrderQuantity,
|
||||
decimal? UnitCost,
|
||||
decimal? StockValue,
|
||||
string? LocationCode,
|
||||
DateTime? LastMovementDate,
|
||||
DateTime? LastInventoryDate,
|
||||
decimal? MinimumStock,
|
||||
bool IsLowStock
|
||||
);
|
||||
|
||||
public record StockSummaryDto(
|
||||
int ArticleId,
|
||||
string ArticleCode,
|
||||
string ArticleDescription,
|
||||
string UnitOfMeasure,
|
||||
decimal TotalStock,
|
||||
decimal AvailableStock,
|
||||
decimal? MinimumStock,
|
||||
decimal? MaximumStock,
|
||||
decimal? ReorderPoint,
|
||||
bool IsLowStock,
|
||||
decimal TotalValue,
|
||||
int WarehouseCount,
|
||||
List<StockLevelDto> StockByWarehouse
|
||||
);
|
||||
|
||||
public record ArticleValuationDto(
|
||||
int ArticleId,
|
||||
string ArticleCode,
|
||||
string ArticleDescription,
|
||||
ValuationMethod Method,
|
||||
decimal TotalQuantity,
|
||||
string UnitOfMeasure,
|
||||
decimal WeightedAverageCost,
|
||||
decimal? StandardCost,
|
||||
decimal? LastPurchaseCost,
|
||||
decimal TotalValue
|
||||
);
|
||||
|
||||
public record PeriodValuationDto(
|
||||
int Id,
|
||||
int Period,
|
||||
DateTime ValuationDate,
|
||||
int ArticleId,
|
||||
string ArticleCode,
|
||||
string ArticleDescription,
|
||||
int? WarehouseId,
|
||||
string? WarehouseCode,
|
||||
string? WarehouseName,
|
||||
decimal Quantity,
|
||||
ValuationMethod Method,
|
||||
decimal UnitCost,
|
||||
decimal TotalValue,
|
||||
decimal InboundQuantity,
|
||||
decimal InboundValue,
|
||||
decimal OutboundQuantity,
|
||||
decimal OutboundValue,
|
||||
bool IsClosed
|
||||
);
|
||||
|
||||
public record CalculateValuationDto(
|
||||
int ArticleId,
|
||||
int Period,
|
||||
int? WarehouseId
|
||||
);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mapping
|
||||
|
||||
private static StockLevelDto MapToDto(StockLevel stock)
|
||||
{
|
||||
var isLowStock = stock.Article?.MinimumStock.HasValue == true &&
|
||||
stock.Quantity <= stock.Article.MinimumStock.Value;
|
||||
|
||||
return new StockLevelDto(
|
||||
stock.Id,
|
||||
stock.ArticleId,
|
||||
stock.Article?.Code ?? "",
|
||||
stock.Article?.Description ?? "",
|
||||
stock.Article?.Category?.Name,
|
||||
stock.WarehouseId,
|
||||
stock.Warehouse?.Code ?? "",
|
||||
stock.Warehouse?.Name ?? "",
|
||||
stock.BatchId,
|
||||
stock.Batch?.BatchNumber,
|
||||
stock.Batch?.ExpiryDate,
|
||||
stock.Quantity,
|
||||
stock.ReservedQuantity,
|
||||
stock.AvailableQuantity,
|
||||
stock.OnOrderQuantity,
|
||||
stock.UnitCost,
|
||||
stock.StockValue,
|
||||
stock.LocationCode,
|
||||
stock.LastMovementDate,
|
||||
stock.LastInventoryDate,
|
||||
stock.Article?.MinimumStock,
|
||||
isLowStock
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user