Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,15 @@
using PowderCoating.Core.Entities;
namespace PowderCoating.Web.ViewModels;
public class OnlinePaymentsViewModel
{
public DateTime From { get; set; }
public DateTime To { get; set; }
public decimal TotalCollected { get; set; }
public decimal TotalRefunded { get; set; }
public decimal SurchargesCollected { get; set; }
public decimal NetRevenue => TotalCollected - TotalRefunded;
public List<Invoice> Invoices { get; set; } = new();
public List<Refund> Refunds { get; set; } = new();
}
@@ -0,0 +1,55 @@
namespace PowderCoating.Web.ViewModels;
public class QuoteApprovalViewModel
{
public string Token { get; set; } = string.Empty;
public string QuoteNumber { get; set; } = string.Empty;
public string CompanyName { get; set; } = string.Empty;
public string? CompanyPhone { get; set; }
public string? CompanyEmail { get; set; }
public bool CompanyHasLogo { get; set; }
public string CustomerName { get; set; } = string.Empty;
public string? CustomerEmail { get; set; }
public DateTime? ExpirationDate { get; set; }
public DateTime? ApprovalTokenExpiresAt { get; set; }
public decimal SubTotal { get; set; }
public decimal DiscountAmount { get; set; }
public bool HideDiscountFromCustomer { get; set; }
public decimal RushFee { get; set; }
public decimal TaxAmount { get; set; }
public decimal Total { get; set; }
public string? Terms { get; set; }
public string? Description { get; set; }
public string? SpecialInstructions { get; set; }
public List<QuoteApprovalItemViewModel> Items { get; set; } = new();
public string? DeclineError { get; set; }
// For AlreadyActed view
public string? CurrentStatus { get; set; }
public string? DeclineReason { get; set; }
// Deposit payment link (shown on Confirmation page after approval)
public bool RequiresDeposit { get; set; }
public decimal DepositPercent { get; set; }
public decimal DepositAmount { get; set; } // pre-calculated: Total * DepositPercent/100
public string? DepositPaymentLinkToken { get; set; }
public decimal DepositAmountPaid { get; set; }
// Prospect details collection
public bool IsProspect { get; set; }
public string? ProspectContactName { get; set; }
public string? ProspectEmail { get; set; }
public string? ProspectPhone { get; set; }
public string? ProspectCompanyName { get; set; }
public string? ProspectAddress { get; set; }
public string? ProspectCity { get; set; }
public string? ProspectState { get; set; }
public string? ProspectZipCode { get; set; }
}
public class QuoteApprovalItemViewModel
{
public string Description { get; set; } = string.Empty;
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
}
@@ -0,0 +1,13 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class CustomerOverviewViewModel : ReportViewModelBase
{
public List<string> MonthLabels { get; set; } = new();
public List<int> NewCustomersPerMonth { get; set; } = new();
public decimal CustomerRetentionRate { get; set; }
public int ActiveCustomersCount { get; set; }
public List<CustomerLifetimeValueItem> CustomerLifetimeValue { get; set; } = new();
public Dictionary<string, int> QuotesByStatus { get; set; } = new();
public QuoteConversionFunnel QuoteFunnel { get; set; } = new();
public Dictionary<string, int> CatalogByCategory { get; set; } = new();
}
@@ -0,0 +1,11 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class CustomerRetentionViewModel : ReportViewModelBase
{
public List<CustomerRetentionItem> Items { get; set; } = new();
public int ActiveCount { get; set; }
public int AtRiskCount { get; set; }
public int LapsingCount { get; set; }
public int ChurnedCount { get; set; }
public int NeverOrderedCount { get; set; }
}
@@ -0,0 +1,18 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class ExpensesApViewModel : ReportViewModelBase
{
public decimal TotalBilled { get; set; }
public decimal TotalBillsPaid { get; set; }
public decimal TotalApOutstanding { get; set; }
public decimal TotalDirectExpenses { get; set; }
public List<string> MonthLabels { get; set; } = new();
public List<AgingBucketItem> ApAgingBuckets { get; set; } = new();
public List<VendorSpendItem> VendorSpend { get; set; } = new();
public List<ExpenseByAccountItem> ExpensesByAccount { get; set; } = new();
public List<decimal> MonthlyBillsPaid { get; set; } = new();
public List<decimal> MonthlyDirectExpenses { get; set; } = new();
public List<decimal> PlRevenue { get; set; } = new();
public List<decimal> PlExpenses { get; set; } = new();
public List<decimal> PlNetIncome { get; set; } = new();
}
@@ -0,0 +1,19 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class FinancialSummaryViewModel : ReportViewModelBase
{
public decimal TotalInvoiced { get; set; }
public decimal TotalCollected { get; set; }
public decimal TotalOutstanding { get; set; }
public decimal TotalOverdue { get; set; }
public int InvoicesOverdueCount { get; set; }
public int InvoicesDraftCount { get; set; }
public int InvoicesPaidCount { get; set; }
public double AvgDaysToPayment { get; set; }
public List<string> MonthLabels { get; set; } = new();
public List<decimal> MonthlyInvoiced { get; set; } = new();
public List<decimal> MonthlyCollected { get; set; } = new();
public List<AgingBucketItem> AgingBuckets { get; set; } = new();
public List<RecentPaymentItem> RecentPayments { get; set; } = new();
public List<OutstandingCustomerItem> TopOutstandingCustomers { get; set; } = new();
}
@@ -0,0 +1,6 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class InventoryTurnoverViewModel : ReportViewModelBase
{
public List<InventoryTurnoverItem> Items { get; set; } = new();
}
@@ -0,0 +1,7 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class InvoiceAgingDetailViewModel : ReportViewModelBase
{
public List<InvoiceAgingDetailItem> Items { get; set; } = new();
public decimal TotalBalance { get; set; }
}
@@ -0,0 +1,7 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class JobCycleTimeViewModel : ReportViewModelBase
{
public List<JobCycleTimeItem> Items { get; set; } = new();
public double OverallAvgCycleDays { get; set; }
}
@@ -0,0 +1,6 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class JobStatusAgingViewModel : ReportViewModelBase
{
public List<JobStatusAgingItem> Items { get; set; } = new();
}
@@ -0,0 +1,24 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class KpiDashboardViewModel : ReportViewModelBase
{
public decimal TotalRevenue { get; set; }
public int ActiveJobsCount { get; set; }
public int ActiveCustomersCount { get; set; }
public decimal QuoteWinRate { get; set; }
public int TotalQuotes { get; set; }
public int CompletedJobsThisMonth { get; set; }
public decimal AverageJobValue { get; set; }
public int AppointmentsThisMonth { get; set; }
public double AverageJobDuration { get; set; }
public decimal MonthOverMonthGrowth { get; set; }
public decimal CustomerRetentionRate { get; set; }
public decimal AppointmentCompletionRate { get; set; }
public int LowStockCount { get; set; }
public List<string> MonthLabels { get; set; } = new();
public List<decimal> MonthlyRevenue { get; set; } = new();
public Dictionary<string, int> JobsByStatus { get; set; } = new();
public List<TopCustomerItem> TopCustomers { get; set; } = new();
public Dictionary<string, int> EquipmentByStatus { get; set; } = new();
}
@@ -0,0 +1,16 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class OperationsReportViewModel : ReportViewModelBase
{
public Dictionary<string, int> JobsByStatus { get; set; } = new();
public Dictionary<string, int> ActiveJobsByPriority { get; set; } = new();
public Dictionary<string, int> AppointmentsByType { get; set; } = new();
public Dictionary<string, int> AppointmentsByStatus { get; set; } = new();
public decimal AppointmentCompletionRate { get; set; }
public Dictionary<string, int> AppointmentsByDayOfWeek { get; set; } = new();
public List<WorkerStatsItem> WorkerStats { get; set; } = new();
public Dictionary<string, int> EquipmentByStatus { get; set; } = new();
public List<LowStockItem> LowStockItems { get; set; } = new();
public int ActiveJobsCount { get; set; }
public int TotalAppointments { get; set; }
}
@@ -0,0 +1,8 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class PowderConsumptionViewModel : ReportViewModelBase
{
public List<PowderConsumptionItem> Items { get; set; } = new();
public decimal TotalPurchasedLbs { get; set; }
public decimal TotalConsumedLbs { get; set; }
}
@@ -0,0 +1,13 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class PowderUsageViewModel : ReportViewModelBase
{
public decimal TotalPowderUsedLbs { get; set; }
public decimal TotalPowderCost { get; set; }
public int TotalJobsWithPowderUsage { get; set; }
public int ColorsUsedCount { get; set; }
public List<string> MonthLabels { get; set; } = new();
public List<decimal> MonthlyPowderUsageLbs { get; set; } = new();
public List<decimal> MonthlyPowderUsageCost { get; set; } = new();
public List<PowderUsageByColorItem> TopColorsByUsage { get; set; } = new();
}
@@ -0,0 +1,205 @@
namespace PowderCoating.Web.ViewModels.Reports;
// ── Shared item types used across analytics reports ─────────────────────────
public class TopCustomerItem
{
public int Id { get; set; }
public string? Name { get; set; }
public decimal Revenue { get; set; }
public int JobCount { get; set; }
}
public class LowStockItem
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? ColorName { get; set; }
public decimal QuantityOnHand { get; set; }
public decimal ReorderPoint { get; set; }
public string UnitOfMeasure { get; set; } = string.Empty;
}
public class WorkerStatsItem
{
public string Name { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public int JobsAssigned { get; set; }
public int JobsCompleted { get; set; }
public int AppointmentsAssigned { get; set; }
public decimal CompletionRate => JobsAssigned > 0 ? Math.Round((decimal)JobsCompleted / JobsAssigned * 100, 1) : 0;
}
public class CustomerLifetimeValueItem
{
public string? CustomerName { get; set; }
public decimal TotalRevenue { get; set; }
public int JobCount { get; set; }
public decimal AvgOrderValue { get; set; }
public DateTime FirstJobDate { get; set; }
public DateTime LastJobDate { get; set; }
}
public class QuoteConversionFunnel
{
public int Draft { get; set; }
public int Sent { get; set; }
public int Approved { get; set; }
public int Converted { get; set; }
public int Rejected { get; set; }
public int Expired { get; set; }
public int Total => Draft + Sent + Approved + Converted + Rejected + Expired;
public decimal ConversionRate => Total > 0 ? Math.Round((decimal)(Approved + Converted) / Total * 100, 1) : 0;
}
public class AgingBucketItem
{
public string Label { get; set; } = string.Empty;
public decimal Amount { get; set; }
public int Count { get; set; }
}
public class RecentPaymentItem
{
public string InvoiceNumber { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
public decimal Amount { get; set; }
public string PaymentMethod { get; set; } = string.Empty;
public DateTime PaymentDate { get; set; }
}
public class OutstandingCustomerItem
{
public string CustomerName { get; set; } = string.Empty;
public decimal OutstandingBalance { get; set; }
public int OpenInvoiceCount { get; set; }
}
public class VendorSpendItem
{
public string VendorName { get; set; } = string.Empty;
public decimal TotalBilled { get; set; }
public decimal TotalPaid { get; set; }
public int BillCount { get; set; }
public decimal BalanceDue => TotalBilled - TotalPaid;
}
public class ExpenseByAccountItem
{
public string AccountName { get; set; } = string.Empty;
public decimal Amount { get; set; }
public int Count { get; set; }
}
public class PowderUsageByColorItem
{
public int InventoryItemId { get; set; }
public string ColorName { get; set; } = string.Empty;
public string? ColorCode { get; set; }
public string? SKU { get; set; }
public string? Manufacturer { get; set; }
public decimal TotalLbsUsed { get; set; }
public decimal TotalCost { get; set; }
public int JobCount { get; set; }
public string DisplayLabel => string.IsNullOrWhiteSpace(ColorCode)
? ColorName
: $"{ColorName} ({ColorCode})";
}
public class SalesByCustomerItem
{
public int CustomerId { get; set; }
public string CustomerName { get; set; } = string.Empty;
public bool IsCommercial { get; set; }
public int InvoiceCount { get; set; }
public decimal TotalInvoiced { get; set; }
public decimal TotalPaid { get; set; }
public decimal BalanceDue => TotalInvoiced - TotalPaid;
public decimal AvgInvoiceValue => InvoiceCount > 0 ? Math.Round(TotalInvoiced / InvoiceCount, 2) : 0;
public DateTime? LastInvoiceDate { get; set; }
}
public class CustomerRetentionItem
{
public int CustomerId { get; set; }
public string CustomerName { get; set; } = string.Empty;
public string? Email { get; set; }
public string? Phone { get; set; }
public int TotalJobs { get; set; }
public decimal LifetimeRevenue { get; set; }
public DateTime? LastJobDate { get; set; }
public int DaysSinceLastJob { get; set; }
public string RetentionStatus { get; set; } = string.Empty;
}
public class JobCycleTimeItem
{
public string StatusCode { get; set; } = string.Empty;
public string StatusName { get; set; } = string.Empty;
public int DisplayOrder { get; set; }
public double AvgDays { get; set; }
public double MinDays { get; set; }
public double MaxDays { get; set; }
public int JobCount { get; set; }
}
public class JobStatusAgingItem
{
public int JobId { get; set; }
public string JobNumber { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
public string StatusName { get; set; } = string.Empty;
public string StatusCode { get; set; } = string.Empty;
public string PriorityName { get; set; } = string.Empty;
public string PriorityCode { get; set; } = string.Empty;
public int DaysInCurrentStatus { get; set; }
public DateTime? DueDate { get; set; }
public bool IsOverdue { get; set; }
}
public class InvoiceAgingDetailItem
{
public int InvoiceId { get; set; }
public string InvoiceNumber { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
public string? CustomerEmail { get; set; }
public string? CustomerPhone { get; set; }
public DateTime InvoiceDate { get; set; }
public DateTime? DueDate { get; set; }
public decimal Total { get; set; }
public decimal AmountPaid { get; set; }
public decimal BalanceDue { get; set; }
public int DaysOverdue { get; set; }
public string AgingBucket { get; set; } = string.Empty;
public string StatusDisplay { get; set; } = string.Empty;
}
public class PowderConsumptionItem
{
public int InventoryItemId { get; set; }
public string ItemName { get; set; } = string.Empty;
public string? SKU { get; set; }
public string? ColorName { get; set; }
public string? ColorCode { get; set; }
public string? Manufacturer { get; set; }
public decimal TotalPurchasedLbs { get; set; }
public decimal TotalConsumedLbs { get; set; }
public decimal VarianceLbs => TotalPurchasedLbs - TotalConsumedLbs;
public int PurchaseCount { get; set; }
public int UsageJobCount { get; set; }
}
public class InventoryTurnoverItem
{
public int InventoryItemId { get; set; }
public string ItemName { get; set; } = string.Empty;
public string? SKU { get; set; }
public string? ColorName { get; set; }
public decimal CurrentStockLbs { get; set; }
public decimal TotalConsumedLbs { get; set; }
public decimal TotalPurchasedLbs { get; set; }
public decimal DailyConsumptionLbs { get; set; }
public double DaysToStockout { get; set; }
public double TurnoverRate { get; set; }
public string StockStatus { get; set; } = string.Empty;
}
@@ -0,0 +1,8 @@
namespace PowderCoating.Web.ViewModels.Reports;
public abstract class ReportViewModelBase
{
public int SelectedMonths { get; set; } = 6;
public string ReportTitle { get; set; } = string.Empty;
public string ReportDescription { get; set; } = string.Empty;
}
@@ -0,0 +1,15 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class RevenueTrendsViewModel : ReportViewModelBase
{
public List<string> MonthLabels { get; set; } = new();
public List<decimal> MonthlyRevenue { get; set; } = new();
public List<int> MonthlyJobCount { get; set; } = new();
public List<decimal> AverageOrderValueTrend { get; set; } = new();
public Dictionary<string, decimal> RevenueByCustomerType { get; set; } = new();
public Dictionary<string, decimal> RevenueByPriority { get; set; } = new();
public List<TopCustomerItem> TopCustomers { get; set; } = new();
public decimal TotalRevenue { get; set; }
public int TotalCompletedJobs { get; set; }
public decimal AverageJobValue { get; set; }
}
@@ -0,0 +1,8 @@
namespace PowderCoating.Web.ViewModels.Reports;
public class SalesByCustomerViewModel : ReportViewModelBase
{
public List<SalesByCustomerItem> Items { get; set; } = new();
public decimal TotalInvoiced { get; set; }
public decimal TotalPaid { get; set; }
}