fron build failure fix
This commit is contained in:
@@ -23,7 +23,12 @@ public sealed record DashboardStatsDto(
|
|||||||
int PendingCounts,
|
int PendingCounts,
|
||||||
int OpenRfqs,
|
int OpenRfqs,
|
||||||
int PendingLeaveRequests,
|
int PendingLeaveRequests,
|
||||||
int OverdueSalesInvoices);
|
int OverdueSalesInvoices,
|
||||||
|
IReadOnlyList<MonthlyPurchasesVsSalesDto> MonthlyPurchasesVsSales);
|
||||||
|
|
||||||
/// <summary>One bar in the Stock Valuation chart — total FIFO layer value for a warehouse.</summary>
|
/// <summary>One bar in the Stock Valuation chart — total FIFO layer value for a warehouse.</summary>
|
||||||
public sealed record WarehouseValuationDto(int WarehouseId, decimal Total);
|
public sealed record WarehouseValuationDto(int WarehouseId, decimal Total);
|
||||||
|
|
||||||
|
/// <summary>One month in the Monthly Purchases chart (Purchase Invoices page) — GrandTotal
|
||||||
|
/// of Posted purchase invoices vs Posted sales invoices. <c>Month</c> is "yyyy-MM".</summary>
|
||||||
|
public sealed record MonthlyPurchasesVsSalesDto(string Month, decimal Purchases, decimal Sales);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public sealed class DashboardService : IDashboardService
|
|||||||
private readonly IRepository<Rfq> _rfqs;
|
private readonly IRepository<Rfq> _rfqs;
|
||||||
private readonly IRepository<LeaveRequest> _leaveRequests;
|
private readonly IRepository<LeaveRequest> _leaveRequests;
|
||||||
private readonly IRepository<SalesInvoice> _salesInvoices;
|
private readonly IRepository<SalesInvoice> _salesInvoices;
|
||||||
|
private readonly IRepository<PurchaseInvoice> _purchaseInvoices;
|
||||||
private readonly IReorderService _reorder;
|
private readonly IReorderService _reorder;
|
||||||
|
|
||||||
// Neither SalesInvoice nor PurchaseInvoice carries a DueDate field (Phase 1 schema
|
// Neither SalesInvoice nor PurchaseInvoice carries a DueDate field (Phase 1 schema
|
||||||
@@ -33,11 +34,14 @@ public sealed class DashboardService : IDashboardService
|
|||||||
// balance older than this many days from its invoice date.
|
// balance older than this many days from its invoice date.
|
||||||
private const int OverdueAgeDays = 30;
|
private const int OverdueAgeDays = 30;
|
||||||
|
|
||||||
|
// Monthly Purchases chart window: the current month plus the previous 11.
|
||||||
|
private const int ChartMonths = 12;
|
||||||
|
|
||||||
public DashboardService(
|
public DashboardService(
|
||||||
IRepository<StockLayer> layers, IRepository<Grn> grns, IRepository<PurchaseOrder> pos,
|
IRepository<StockLayer> layers, IRepository<Grn> grns, IRepository<PurchaseOrder> pos,
|
||||||
IRepository<Requisition> requisitions, IRepository<StockCount> counts, IRepository<Rfq> rfqs,
|
IRepository<Requisition> requisitions, IRepository<StockCount> counts, IRepository<Rfq> rfqs,
|
||||||
IRepository<LeaveRequest> leaveRequests, IRepository<SalesInvoice> salesInvoices,
|
IRepository<LeaveRequest> leaveRequests, IRepository<SalesInvoice> salesInvoices,
|
||||||
IReorderService reorder)
|
IRepository<PurchaseInvoice> purchaseInvoices, IReorderService reorder)
|
||||||
{
|
{
|
||||||
_layers = layers;
|
_layers = layers;
|
||||||
_grns = grns;
|
_grns = grns;
|
||||||
@@ -47,6 +51,7 @@ public sealed class DashboardService : IDashboardService
|
|||||||
_rfqs = rfqs;
|
_rfqs = rfqs;
|
||||||
_leaveRequests = leaveRequests;
|
_leaveRequests = leaveRequests;
|
||||||
_salesInvoices = salesInvoices;
|
_salesInvoices = salesInvoices;
|
||||||
|
_purchaseInvoices = purchaseInvoices;
|
||||||
_reorder = reorder;
|
_reorder = reorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +108,50 @@ public sealed class DashboardService : IDashboardService
|
|||||||
var lowStockAlerts = (await _reorder.GetAlertsAsync(null, branchId, new PageQuery { Page = 1, PageSize = 1 }, ct))
|
var lowStockAlerts = (await _reorder.GetAlertsAsync(null, branchId, new PageQuery { Page = 1, PageSize = 1 }, ct))
|
||||||
.Pagination.TotalItems;
|
.Pagination.TotalItems;
|
||||||
|
|
||||||
|
var monthlyPurchasesVsSales = await GetMonthlyPurchasesVsSalesAsync(branchId, salesInvoices, ct);
|
||||||
|
|
||||||
return new DashboardStatsDto(
|
return new DashboardStatsDto(
|
||||||
lowStockAlerts, onHandTotal, onHandWarehouses, stockValuationTotal, stockValuationByWarehouse,
|
lowStockAlerts, onHandTotal, onHandWarehouses, stockValuationTotal, stockValuationByWarehouse,
|
||||||
pendingApprovalPOs, pendingGrns, openRequisitions, pendingCounts, openRfqs,
|
pendingApprovalPOs, pendingGrns, openRequisitions, pendingCounts, openRfqs,
|
||||||
pendingLeaveRequests, overdueSalesInvoices);
|
pendingLeaveRequests, overdueSalesInvoices, monthlyPurchasesVsSales);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Posted purchase vs sales invoice GrandTotals per calendar month (UTC) over the last
|
||||||
|
/// <see cref="ChartMonths"/> months, oldest first. Months with neither are omitted, so an
|
||||||
|
/// empty list means "no posted invoices yet". Purchases are dated by VendorInvoiceDate,
|
||||||
|
/// sales by InvoiceDate.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<IReadOnlyList<MonthlyPurchasesVsSalesDto>> GetMonthlyPurchasesVsSalesAsync(
|
||||||
|
int? branchId, IQueryable<SalesInvoice> salesInvoices, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var since = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc).AddMonths(-(ChartMonths - 1));
|
||||||
|
|
||||||
|
var purchaseInvoices = _purchaseInvoices.Query().AsNoTracking();
|
||||||
|
if (branchId is not null) purchaseInvoices = purchaseInvoices.Where(i => i.BranchId == branchId);
|
||||||
|
var purchases = await purchaseInvoices
|
||||||
|
.Where(i => i.Status == PurchaseInvoiceStatus.Posted && i.VendorInvoiceDate >= since)
|
||||||
|
.GroupBy(i => new { i.VendorInvoiceDate.Year, i.VendorInvoiceDate.Month })
|
||||||
|
.Select(g => new { g.Key.Year, g.Key.Month, Total = g.Sum(i => i.GrandTotal) })
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
var sales = await salesInvoices
|
||||||
|
.Where(i => i.Status == SalesInvoiceStatus.Posted && i.InvoiceDate >= since)
|
||||||
|
.GroupBy(i => new { i.InvoiceDate.Year, i.InvoiceDate.Month })
|
||||||
|
.Select(g => new { g.Key.Year, g.Key.Month, Total = g.Sum(i => i.GrandTotal) })
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
var purchasesByMonth = purchases.ToDictionary(p => (p.Year, p.Month), p => p.Total);
|
||||||
|
var salesByMonth = sales.ToDictionary(s => (s.Year, s.Month), s => s.Total);
|
||||||
|
|
||||||
|
return Enumerable.Range(0, ChartMonths)
|
||||||
|
.Select(offset => since.AddMonths(offset))
|
||||||
|
.Select(m => new MonthlyPurchasesVsSalesDto(
|
||||||
|
$"{m.Year:D4}-{m.Month:D2}",
|
||||||
|
purchasesByMonth.GetValueOrDefault((m.Year, m.Month)),
|
||||||
|
salesByMonth.GetValueOrDefault((m.Year, m.Month))))
|
||||||
|
.Where(m => m.Purchases != 0m || m.Sales != 0m)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,13 @@ export interface DashboardStats {
|
|||||||
openRfqs: number
|
openRfqs: number
|
||||||
pendingLeaveRequests: number
|
pendingLeaveRequests: number
|
||||||
overdueSalesInvoices: number
|
overdueSalesInvoices: number
|
||||||
|
monthlyPurchasesVsSales: MonthlyPurchasesVsSales[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// One month of Posted purchase vs sales invoice GrandTotals; `month` is "yyyy-MM".
|
||||||
|
// Only months with activity are returned (last 12, oldest first).
|
||||||
|
export interface MonthlyPurchasesVsSales {
|
||||||
|
month: string
|
||||||
|
purchases: number
|
||||||
|
sales: number
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user