84 lines
3.4 KiB
C#
84 lines
3.4 KiB
C#
namespace Distribution.Features.DashboardReport;
|
|
|
|
public class DashboardReportService(IDashboardReportRepository repo)
|
|
{
|
|
public const int MaxRangeDays = 366;
|
|
private const int DailyGranularityMaxDays = 62;
|
|
|
|
public static DashboardFilter BuildFilter(DateTime? from, DateTime? to, Guid? regionId, Guid? categoryId, Guid? warehouseId)
|
|
{
|
|
var end = (to ?? DateTime.Now).Date;
|
|
var start = (from ?? end.AddDays(-29)).Date;
|
|
return new DashboardFilter(start, end, regionId, categoryId, warehouseId);
|
|
}
|
|
|
|
public static string? Validate(DashboardFilter f)
|
|
{
|
|
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.";
|
|
return null;
|
|
}
|
|
|
|
private static bool Daily(DashboardFilter f) => (f.To - f.From).TotalDays <= DailyGranularityMaxDays;
|
|
|
|
private static string Bucket(DateTime d, bool daily) => daily ? d.ToString("yyyy-MM-dd") : d.ToString("yyyy-MM");
|
|
|
|
private static SalesKpis ToKpis(SalesTotals t)
|
|
{
|
|
var net = t.GrossSales - t.ReturnValue;
|
|
var gp = t.CostedSales - t.CostOfSales;
|
|
return new SalesKpis(
|
|
t.GrossSales, t.ReturnValue, net, t.Orders,
|
|
t.Orders > 0 ? Math.Round(t.GrossSales / t.Orders, 2) : 0m,
|
|
gp, t.CostedSales > 0 ? Math.Round(gp / t.CostedSales * 100, 2) : null);
|
|
}
|
|
|
|
public async Task<SalesSummaryResponse> GetSalesSummaryAsync(DashboardFilter f)
|
|
{
|
|
var days = (f.To - f.From).Days + 1;
|
|
var prev = f with { From = f.From.AddDays(-days), To = f.From.AddDays(-1) };
|
|
var daily = Daily(f);
|
|
|
|
var current = await repo.GetSalesTotalsAsync(f);
|
|
var previous = await repo.GetSalesTotalsAsync(prev);
|
|
var sales = await repo.GetDailySalesAsync(f);
|
|
var returns = await repo.GetDailyReturnsAsync(f);
|
|
|
|
var trend = sales.Select(s => (s.Date, Sales: s.Amount, Returns: 0m))
|
|
.Concat(returns.Select(r => (r.Date, Sales: 0m, Returns: r.Amount)))
|
|
.GroupBy(x => Bucket(x.Date, daily))
|
|
.OrderBy(g => g.Key)
|
|
.Select(g => new TrendPoint(g.Key, g.Sum(x => x.Sales), g.Sum(x => x.Returns)))
|
|
.ToList();
|
|
|
|
static NamedAmount Map(GroupAggregate g) => new(g.Key, g.Name, g.Amount);
|
|
return new SalesSummaryResponse(
|
|
f.From, f.To, daily ? "day" : "month",
|
|
new SalesKpiComparison(ToKpis(current), ToKpis(previous), prev.From, prev.To),
|
|
trend,
|
|
(await repo.GetSalesByCategoryAsync(f)).Select(Map).ToList(),
|
|
(await repo.GetSalesByRegionAsync(f)).Select(Map).ToList(),
|
|
(await repo.GetSalesByRepAsync(f)).Select(Map).ToList());
|
|
}
|
|
|
|
public async Task<InventorySummaryResponse> GetInventorySummaryAsync(DashboardFilter f)
|
|
{
|
|
var daily = Daily(f);
|
|
var movements = (await repo.GetDailyMovementsAsync(f))
|
|
.GroupBy(m => Bucket(m.Date, daily))
|
|
.OrderBy(g => g.Key)
|
|
.Select(g =>
|
|
{
|
|
var rec = g.Sum(x => x.Received);
|
|
var iss = g.Sum(x => x.Issued);
|
|
return new MovementPoint(g.Key, rec, iss, rec - iss);
|
|
}).ToList();
|
|
|
|
return new InventorySummaryResponse(
|
|
f.From, f.To, daily ? "day" : "month",
|
|
await repo.GetStockValueByCategoryAsync(f),
|
|
await repo.GetStockByWarehouseAsync(f),
|
|
movements);
|
|
}
|
|
}
|