Refactor dashboard queries to push filtering and aggregation into the database
DashboardReadService no longer loads full entity lists and filters in memory. All job panels (today/overdue/in-progress) now execute targeted COUNT + capped SELECT queries in SQL. AR aging buckets, powder order lines, bill totals, and active-customer counts are all aggregated at the DB level. The SuperAdmin action previously loaded every company row to compute plan distribution and alert lists; it now delegates to a new GetSuperAdminDashboardDataAsync() that uses SQL GROUP BY and projections instead. DashboardIndexData record updated to carry pre-sliced counts and capped lists so the controller only does lightweight DTO projection. DashboardPowderOrderLineData replaces the deep Job→JobItem→Coat Include chains with a single flat coat query projected in SQL. OnlineUserMiddleware switches its per-user throttle from a static ConcurrentDictionary (grows forever) to IMemoryCache with a 60-second sliding expiry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,30 +1,127 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Enums;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Result record carrying all pre-fetched entity lists and aggregates needed to render the operator
|
||||
/// dashboard index view. Raw entities are returned so the controller can apply in-memory
|
||||
/// filtering, grouping, and DTO projection without additional round-trips.
|
||||
/// Result record carrying the pre-sliced entity lists and aggregates needed to render the
|
||||
/// operator dashboard index view. The read service does the heavy SQL filtering so the
|
||||
/// controller can focus on lightweight DTO projection and view assembly.
|
||||
/// </summary>
|
||||
public record DashboardIndexData(
|
||||
List<Job> ActiveJobs,
|
||||
decimal MonthlyRevenue,
|
||||
int ActiveJobsCount,
|
||||
int TodaysJobsCount,
|
||||
List<Job> TodaysJobs,
|
||||
int OverdueJobsCount,
|
||||
List<Job> OverdueJobs,
|
||||
List<Job> InProgressJobs,
|
||||
int TodaysAppointmentsCount,
|
||||
List<Appointment> TodaysAppointments,
|
||||
int LowStockCount,
|
||||
List<InventoryItem> LowStockItems,
|
||||
int PendingMaintenanceCount,
|
||||
List<MaintenanceRecord> UpcomingMaintenance,
|
||||
int PendingQuotesCount,
|
||||
decimal PendingQuoteValue,
|
||||
List<Quote> PendingQuotes,
|
||||
List<Invoice> OpenInvoices,
|
||||
List<Quote> ExpiringQuotes,
|
||||
int ActiveCustomersCount,
|
||||
decimal MonthlyRevenue,
|
||||
decimal OutstandingAr,
|
||||
decimal InvoicedThisMonth,
|
||||
decimal CollectedThisMonth,
|
||||
int OverdueInvoicesCount,
|
||||
decimal OverdueInvoicesAmount,
|
||||
DashboardArAgingData ArAging,
|
||||
List<Invoice> OverdueInvoices,
|
||||
List<Payment> RecentPayments,
|
||||
List<Quote> RecentQuotes,
|
||||
List<Job> RecentJobs,
|
||||
List<Job> JobsNeedingPowder,
|
||||
List<Job> JobsWithOrderedPowder,
|
||||
List<Equipment> EquipmentAlerts,
|
||||
List<DashboardPowderOrderLineData> PowderOrdersNeeded,
|
||||
List<DashboardPowderOrderLineData> PowderOrdersPlaced,
|
||||
int BillsDueCount,
|
||||
decimal BillsDueAmount,
|
||||
List<Bill> BillsDue,
|
||||
string? TipOfTheDay
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// AR aging bucket totals used by the dashboard receivables summary.
|
||||
/// </summary>
|
||||
public record DashboardArAgingData(
|
||||
decimal Current,
|
||||
decimal Days1To30,
|
||||
decimal Days31To60,
|
||||
decimal Days61To90,
|
||||
decimal DaysOver90
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Flattened powder-order line data so the controller does not need to materialize full job/item/coat graphs.
|
||||
/// </summary>
|
||||
public record DashboardPowderOrderLineData(
|
||||
int CoatId,
|
||||
int JobId,
|
||||
string JobNumber,
|
||||
string CustomerName,
|
||||
string CoatName,
|
||||
string? ColorName,
|
||||
string? ColorCode,
|
||||
string? Finish,
|
||||
string? SKU,
|
||||
decimal LbsToOrder,
|
||||
decimal? CostPerLb,
|
||||
DateTime? OrderedAt,
|
||||
bool HasInventoryItem,
|
||||
int? VendorId,
|
||||
string? VendorName,
|
||||
string? VendorPhone,
|
||||
string? VendorEmail
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Aggregated data for the SuperAdmin dashboard.
|
||||
/// </summary>
|
||||
public record SuperAdminDashboardData(
|
||||
int TotalCompanies,
|
||||
int ActiveCompanies,
|
||||
int InactiveCompanies,
|
||||
int TotalUsers,
|
||||
int ActiveSubscriptions,
|
||||
int GracePeriodCount,
|
||||
int ExpiredCount,
|
||||
Dictionary<int, DashboardPlanDistributionData> PlanDistribution,
|
||||
List<SuperAdminCompanyAlertData> CompanyAlerts,
|
||||
List<SuperAdminRecentCompanyData> RecentCompanies
|
||||
);
|
||||
|
||||
public record DashboardPlanDistributionData(
|
||||
string DisplayName,
|
||||
int Count
|
||||
);
|
||||
|
||||
public record SuperAdminCompanyAlertData(
|
||||
int Id,
|
||||
string CompanyName,
|
||||
int Plan,
|
||||
string PlanDisplayName,
|
||||
SubscriptionStatus Status,
|
||||
DateTime? SubscriptionEndDate,
|
||||
int DaysOverdue,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record SuperAdminRecentCompanyData(
|
||||
int Id,
|
||||
string CompanyName,
|
||||
int Plan,
|
||||
string PlanDisplayName,
|
||||
SubscriptionStatus Status,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Read-only service for the dashboard. All methods execute complex queries that require
|
||||
/// ThenInclude chains or navigation-property predicates beyond what the generic
|
||||
@@ -37,6 +134,9 @@ public interface IDashboardReadService
|
||||
/// <param name="today">The local date used for date-range predicates (today, start-of-month, etc.).</param>
|
||||
Task<DashboardIndexData> GetIndexDataAsync(DateTime today);
|
||||
|
||||
/// <summary>Fetches all data needed to render the SuperAdmin dashboard.</summary>
|
||||
Task<SuperAdminDashboardData> GetSuperAdminDashboardDataAsync(DateTime today);
|
||||
|
||||
/// <summary>Returns the total count of tenant users (CompanyId > 0) for the SuperAdmin dashboard.</summary>
|
||||
Task<int> GetTotalUserCountAsync();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user