Add stock report features r
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
using Distribution.Features.Stock;
|
||||
|
||||
namespace Distribution.Features.StockReport;
|
||||
|
||||
public class StockReportService(IStockReportRepository repo)
|
||||
{
|
||||
public const int MaxRangeDays = 366;
|
||||
public const int MaxExpiryDays = 3650;
|
||||
|
||||
public static string? Validate(StockReportFilter f, int page, int pageSize)
|
||||
{
|
||||
if (page < 1 || pageSize < 1 || pageSize > 200) return "page must be >= 1 and pageSize between 1 and 200.";
|
||||
if (f.To < f.From) return "'to' must not be before 'from'.";
|
||||
if ((f.To - f.From).TotalDays > MaxRangeDays) return $"Date range cannot exceed {MaxRangeDays} days.";
|
||||
if (f.Status is not (null or "low" or "out")) return "status must be 'low' or 'out'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? ValidateExpiry(int days, int page, int pageSize)
|
||||
{
|
||||
if (page < 1 || pageSize < 1 || pageSize > 200) return "page must be >= 1 and pageSize between 1 and 200.";
|
||||
if (days < 0 || days > MaxExpiryDays) return $"days must be between 0 and {MaxExpiryDays}.";
|
||||
return null;
|
||||
}
|
||||
|
||||
// Low-stock is judged on warehouse quantity, so it only applies when the scope includes warehouses.
|
||||
private async Task<bool> IncludesWarehouses(Guid? locationId) =>
|
||||
!locationId.HasValue || await repo.IsWarehouseAsync(locationId.Value);
|
||||
|
||||
public async Task<StockOverview> GetOverviewAsync(Guid? locationId, int expiryDays)
|
||||
{
|
||||
var agg = await repo.GetOverviewAsync(locationId, await IncludesWarehouses(locationId), DateTime.Now.Date, expiryDays);
|
||||
return new StockOverview(agg.InStockItems, agg.TotalQty, agg.TotalValue, agg.LowStockItems,
|
||||
agg.ActiveItems - agg.InStockItems, agg.ExpiringSoonBatches, agg.ExpiredBatches);
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<StockLedgerRow>> GetLedgerAsync(StockReportFilter f, int page, int pageSize)
|
||||
{
|
||||
var includeLow = await IncludesWarehouses(f.LocationId);
|
||||
var (rows, total) = await repo.GetLedgerAsync(f, includeLow, page, pageSize);
|
||||
var mapped = rows.Select(r =>
|
||||
{
|
||||
var status = r.Current <= 0 ? StockStatus.Out
|
||||
: includeLow && r.ReorderLevel != null && r.WarehouseQty <= r.ReorderLevel ? StockStatus.Low
|
||||
: StockStatus.Ok;
|
||||
return new StockLedgerRow(r.ItemId, r.Sku, r.Name, r.Brand, r.Category, r.Uom,
|
||||
r.Opening, r.In, r.Out, r.Opening + r.In - r.Out, r.Current,
|
||||
r.CostPrice, r.Current * (r.CostPrice ?? 0m), r.ReorderLevel, status);
|
||||
}).ToList();
|
||||
return new PagedResponse<StockLedgerRow>(page, pageSize, total, mapped);
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<StockExpiryRow>> GetExpiryAsync(
|
||||
Guid? locationId, string? search, int days, int page, int pageSize)
|
||||
{
|
||||
var today = DateTime.Now.Date;
|
||||
var (rows, total) = await repo.GetExpiryAsync(locationId, search, today.AddDays(days + 1), page, pageSize);
|
||||
var mapped = rows.Select(r => new StockExpiryRow(
|
||||
r.ItemId, r.Sku, r.Name, r.BatchId, r.BatchNumber, r.ExpiryDate,
|
||||
(int)(r.ExpiryDate.Date - today).TotalDays,
|
||||
r.Quantity, r.CostPrice, r.Quantity * (r.CostPrice ?? 0m),
|
||||
r.LocationId, r.LocationType, r.LocationName)).ToList();
|
||||
return new PagedResponse<StockExpiryRow>(page, pageSize, total, mapped);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user