@using PowderCoating.Web.Controllers @using PowderCoating.Web.ViewModels.Reports @model AnalyticsDashboardViewModel @{ ViewData["Title"] = "Analytics & Reports"; ViewData["PageIcon"] = "bi-graph-up"; var allowAccounting = Context.Items["AllowAccounting"] as bool? ?? false; }
Total Revenue
Active Jobs
Quote Win Rate
Active Customers
Avg Job Value
Appointments This Month
Avg Job Duration
Low Stock Items
No data
}No customer data yet
}No equipment data yet
}No data
}No data
}| Customer | Jobs | Revenue |
|---|---|---|
| @customer.Name | @customer.JobCount | @customer.Revenue.ToString("C0") |
No customer revenue data yet
No active jobs
}No appointments
}No data
}| Worker | Role | Jobs Assigned | Jobs Completed | Appointments | Completion Rate |
|---|---|---|---|---|---|
| @worker.Name | @worker.Role | @worker.JobsAssigned | @worker.JobsCompleted | @worker.AppointmentsAssigned |
|
No worker data yet
| Item | On Hand | Reorder Point | Status |
|---|---|---|---|
|
@item.Name
@if (!string.IsNullOrEmpty(item.ColorName))
{
@item.ColorName } |
@item.QuantityOnHand.ToString("F1") @item.UnitOfMeasure | @item.ReorderPoint.ToString("F1") @item.UnitOfMeasure | @(item.QuantityOnHand == 0 ? "Out of Stock" : "Low Stock") |
All items sufficiently stocked
No quote data yet
}| Customer | Total Jobs | Avg Order Value | Lifetime Revenue | First Job | Last Job |
|---|---|---|---|---|---|
| @customer.CustomerName | @customer.JobCount | @customer.AvgOrderValue.ToString("C0") | @customer.TotalRevenue.ToString("C0") | @customer.FirstJobDate.ToString("MMM yyyy") | @customer.LastJobDate.ToString("MMM yyyy") |
No customer lifetime value data yet
Total Invoiced
Collected
Outstanding
Overdue
Avg. Days to Payment
| Age | Invoices | Amount |
|---|---|---|
| @bucket.Label | @bucket.Count | @bucket.Amount.ToString("C0") |
| Total Outstanding | @Model.AgingBuckets.Sum(b => b.Count) | @Model.AgingBuckets.Sum(b => b.Amount).ToString("C0") |
No outstanding invoices
| Customer | Invoices | Balance | |
|---|---|---|---|
| @oc.CustomerName | @oc.OpenInvoiceCount | @oc.OutstandingBalance.ToString("C0") |
No outstanding customer balances
| Date | Invoice # | Customer | Method | Amount |
|---|---|---|---|---|
| @pmt.PaymentDate.ToString("MM/dd/yyyy") | @pmt.InvoiceNumber | @pmt.CustomerName | @pmt.PaymentMethod | @pmt.Amount.ToString("C") |
No payments recorded yet
Total Bills (All Time)
@Model.TotalBilled.ToString("C0")
Total Bills Paid
@Model.TotalBillsPaid.ToString("C0")
AP Outstanding
@Model.TotalApOutstanding.ToString("C0")
Direct Expenses (All Time)
@Model.TotalDirectExpenses.ToString("C0")
No expense data yet.
}| Bucket | Count | Balance |
|---|---|---|
| @bucket.Label | @bucket.Count | @bucket.Amount.ToString("C") |
| Total | @Model.ApAgingBuckets.Sum(b => b.Count) | @Model.ApAgingBuckets.Sum(b => b.Amount).ToString("C") |
| Vendor | Bills | Billed | Paid | Balance |
|---|---|---|---|---|
| @v.VendorName | @v.BillCount | @v.TotalBilled.ToString("C") | @v.TotalPaid.ToString("C") | @v.BalanceDue.ToString("C") |
No vendor bills recorded yet.
}| Account | Transactions | Total |
|---|---|---|
| @acct.AccountName | @acct.Count | @acct.Amount.ToString("C") |
| Total | @Model.ExpensesByAccount.Sum(e => e.Count) | @Model.ExpensesByAccount.Sum(e => e.Amount).ToString("C") |
No powder usage recorded yet
Usage is tracked when jobs are completed| Color / SKU | Manufacturer | Lbs Used | Est. Cost | Jobs | Avg / Job |
|---|---|---|---|---|---|
|
@color.DisplayLabel
@color.SKU
|
@(color.Manufacturer ?? "—") |
@color.TotalLbsUsed.ToString("N1") lbs
|
@color.TotalCost.ToString("C") | @color.JobCount | @avgPerJob.ToString("N1") lbs |
| Total | @Model.TotalPowderUsedLbs.ToString("N1") lbs | @Model.TotalPowderCost.ToString("C") | @Model.TotalJobsWithPowderUsage | ||
Total Invoiced
Total Collected
Active Customers
| Customer | Type | Invoices | Total Invoiced | Total Paid | Balance Due | Avg Invoice | Last Invoice | Share |
|---|---|---|---|---|---|---|---|---|
| @c.CustomerName | @if (c.IsCommercial) { Commercial } else { Personal } | @c.InvoiceCount | @c.TotalInvoiced.ToString("C") | @c.TotalPaid.ToString("C") | @c.BalanceDue.ToString("C") | @c.AvgInvoiceValue.ToString("C") | @(c.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—") |
|
| Total | @Model.SalesByCustomerTotalInvoiced.ToString("C") | @Model.SalesByCustomerTotalPaid.ToString("C") | @((Model.SalesByCustomerTotalInvoiced - Model.SalesByCustomerTotalPaid).ToString("C")) | |||||
No invoice data available for the selected period.
| Customer | Status | Last Job | Days Since | Total Jobs | Lifetime Revenue |
|---|---|---|---|---|---|
|
@r.CustomerName
@if (!string.IsNullOrEmpty(r.Email))
{
@r.Email
}
|
@r.RetentionStatus | @(r.LastJobDate.HasValue ? r.LastJobDate.Value.ToString("MMM d, yyyy") : "—") | @(r.DaysSinceLastJob >= 0 ? r.DaysSinceLastJob + "d" : "—") | @r.TotalJobs | @r.LifetimeRevenue.ToString("C0") |
No customer data available.
Overall Avg Cycle Time
Statuses Tracked
Longest Avg Stage
@if (Model.JobCycleTime.Any()) { var longest = Model.JobCycleTime.OrderByDescending(x => x.AvgDays).First();| Stage | Avg Days | Min Days | Max Days | Jobs Sampled |
|---|---|---|---|---|
| @s.StatusName | @s.AvgDays.ToString("N1") | @s.MinDays.ToString("N1") | @s.MaxDays.ToString("N1") | @s.JobCount |
Not enough job history data to calculate cycle times yet.
Active Jobs
Overdue
Avg Days in Status
| Job # | Customer | Current Status | Priority | Days in Status | Due Date |
|---|---|---|---|---|---|
| @j.JobNumber | @j.CustomerName | @j.StatusName | @j.PriorityName | @j.DaysInCurrentStatus d | @if (j.DueDate.HasValue) { @j.DueDate.Value.ToString("MMM d, yyyy") @if (j.IsOverdue) { } } else { — } |
No active jobs to display.
| Invoice # | Customer | Invoice Date | Due Date | Total | Paid | Balance Due | Days Overdue | Bucket |
|---|---|---|---|---|---|---|---|---|
| @inv.InvoiceNumber |
@inv.CustomerName
@if (!string.IsNullOrEmpty(inv.CustomerEmail))
{
@inv.CustomerEmail
}
|
@inv.InvoiceDate.ToString("MMM d, yyyy") | @(inv.DueDate.HasValue ? inv.DueDate.Value.ToString("MMM d, yyyy") : "—") | @inv.Total.ToString("C") | @inv.AmountPaid.ToString("C") | @inv.BalanceDue.ToString("C") | @if (inv.DaysOverdue > 0) { @inv.DaysOverdue d } else { current } | @inv.AgingBucket |
No outstanding invoices.
Total Purchased
Total Consumed
Utilization Rate
| Item / Color | Manufacturer | Purchased (lbs) | Consumed (lbs) | Utilization | Jobs Used |
|---|---|---|---|---|---|
|
@(p.ColorName ?? p.ItemName)
@if (!string.IsNullOrEmpty(p.ColorCode))
{
@p.ColorCode @(!string.IsNullOrEmpty(p.SKU) ? $"· {p.SKU}" : "")
}
|
@(p.Manufacturer ?? "—") | @p.TotalPurchasedLbs.ToString("N1") | @p.TotalConsumedLbs.ToString("N1") | = 60 ? "bg-warning text-dark" : "bg-danger")"> @util% | @p.UsageJobCount |
No inventory transaction data available.
| Item / Color | On Hand (lbs) | Daily Use (lbs) | Days to Stockout | Turnover Rate | Total Consumed | Status |
|---|---|---|---|---|---|---|
|
@(it.ColorName ?? it.ItemName)
@if (!string.IsNullOrEmpty(it.SKU))
{
@it.SKU
}
|
@it.CurrentStockLbs.ToString("N1") | @it.DailyConsumptionLbs.ToString("N3") | @(it.DaysToStockout >= 9999 ? "∞" : it.DaysToStockout.ToString("N0") + " d") | @it.TurnoverRate.ToString("N2")x | @it.TotalConsumedLbs.ToString("N1") lbs | @it.StockStatus |
No inventory data available.