Sweep all .cshtml files for encoding corruption; add pre-commit guard
Replace all corruption variants with HTML entities across 226 view files: - 3-char UTF-8-as-Win1252 sequences (ae-corruption) - Standalone smart/curly quotes that break C# Razor expressions - Partially re-corrupted variants where the 3rd byte was normalised to ASCII tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the script itself never contains a literal non-ASCII character; supports -DryRun .githooks/pre-commit: blocks commits containing the ae-corruption byte signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the hook is repo-committed and active for all future work on this machine. Build clean; 225 unit tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@
|
||||
<!-- Loading state -->
|
||||
<div id="loadingState" class="text-center py-5 d-none">
|
||||
<div class="spinner-border text-warning mb-3" role="status"></div>
|
||||
<p class="text-muted">Scanning bills and expense accounts for anomalies…<br><small>This usually takes 5–10 seconds.</small></p>
|
||||
<p class="text-muted">Scanning bills and expense accounts for anomalies…<br><small>This usually takes 5–10 seconds.</small></p>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div id="allClearBadge" class="d-none d-flex align-items-center gap-2 px-3 py-2 rounded" style="background:#f0fdf4;">
|
||||
<i class="bi bi-shield-check text-success fs-5"></i>
|
||||
<span class="text-success fw-semibold">All Clear — no anomalies detected</span>
|
||||
<span class="text-success fw-semibold">All Clear — no anomalies detected</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
|
||||
} catch (e) {
|
||||
document.getElementById('loadingState').classList.add('d-none');
|
||||
document.getElementById('errorMessage').textContent = 'Network error — please try again.';
|
||||
document.getElementById('errorMessage').textContent = 'Network error — please try again.';
|
||||
document.getElementById('errorState').classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<div class="card shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="h6 aging-1-30 mb-1">@Model.Total1to30.ToString("C0")</div>
|
||||
<div class="text-muted small">1–30 Days</div>
|
||||
<div class="text-muted small">1–30 Days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +86,7 @@
|
||||
<div class="card shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="h6 aging-31-60 mb-1">@Model.Total31to60.ToString("C0")</div>
|
||||
<div class="text-muted small">31–60 Days</div>
|
||||
<div class="text-muted small">31–60 Days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@
|
||||
<div class="card shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="h6 aging-61-90 mb-1">@Model.Total61to90.ToString("C0")</div>
|
||||
<div class="text-muted small">61–90 Days</div>
|
||||
<div class="text-muted small">61–90 Days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,9 +139,9 @@ else
|
||||
<tr>
|
||||
<th>Vendor</th>
|
||||
<th class="text-end">Current</th>
|
||||
<th class="text-end">1–30 Days</th>
|
||||
<th class="text-end">31–60 Days</th>
|
||||
<th class="text-end">61–90 Days</th>
|
||||
<th class="text-end">1–30 Days</th>
|
||||
<th class="text-end">31–60 Days</th>
|
||||
<th class="text-end">61–90 Days</th>
|
||||
<th class="text-end">Over 90</th>
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
@@ -156,11 +156,11 @@ else
|
||||
</a>
|
||||
<span class="badge bg-secondary ms-1">@vend.Bills.Count bill@(vend.Bills.Count == 1 ? "" : "s")</span>
|
||||
</td>
|
||||
<td class="text-end aging-current">@(vend.TotalCurrent > 0 ? vend.TotalCurrent.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-1-30">@(vend.Total1to30 > 0 ? vend.Total1to30.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-31-60">@(vend.Total31to60 > 0 ? vend.Total31to60.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-61-90">@(vend.Total61to90 > 0 ? vend.Total61to90.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-over90">@(vend.TotalOver90 > 0 ? vend.TotalOver90.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-current">@(vend.TotalCurrent > 0 ? vend.TotalCurrent.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-1-30">@(vend.Total1to30 > 0 ? vend.Total1to30.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-31-60">@(vend.Total31to60 > 0 ? vend.Total31to60.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-61-90">@(vend.Total61to90 > 0 ? vend.Total61to90.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-over90">@(vend.TotalOver90 > 0 ? vend.TotalOver90.ToString("C") : "—")</td>
|
||||
<td class="text-end fw-semibold">@vend.TotalBalance.ToString("C")</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -218,7 +218,7 @@ else
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-muted small">@bill.BillDate.ToString("MM/dd/yyyy")</td>
|
||||
<td class="text-muted small">@(bill.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||
<td class="text-muted small">@(bill.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||
<td class="text-end fw-semibold @(bill.DaysOverdue > 30 ? "text-danger" : "")">@bill.BalanceDue.ToString("C")</td>
|
||||
<td class="text-end"><span class="badge @ageBadge">@ageLabel</span></td>
|
||||
<td></td>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<div class="card shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="h6 aging-1-30 mb-1">@Model.Total1to30.ToString("C0")</div>
|
||||
<div class="text-muted small">1–30 Days</div>
|
||||
<div class="text-muted small">1–30 Days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +86,7 @@
|
||||
<div class="card shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="h6 aging-31-60 mb-1">@Model.Total31to60.ToString("C0")</div>
|
||||
<div class="text-muted small">31–60 Days</div>
|
||||
<div class="text-muted small">31–60 Days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@
|
||||
<div class="card shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="h6 aging-61-90 mb-1">@Model.Total61to90.ToString("C0")</div>
|
||||
<div class="text-muted small">61–90 Days</div>
|
||||
<div class="text-muted small">61–90 Days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,9 +139,9 @@ else
|
||||
<tr>
|
||||
<th>Customer</th>
|
||||
<th class="text-end">Current</th>
|
||||
<th class="text-end">1–30 Days</th>
|
||||
<th class="text-end">31–60 Days</th>
|
||||
<th class="text-end">61–90 Days</th>
|
||||
<th class="text-end">1–30 Days</th>
|
||||
<th class="text-end">31–60 Days</th>
|
||||
<th class="text-end">61–90 Days</th>
|
||||
<th class="text-end">Over 90</th>
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
@@ -156,11 +156,11 @@ else
|
||||
</a>
|
||||
<span class="badge bg-secondary ms-1">@cust.Invoices.Count inv.</span>
|
||||
</td>
|
||||
<td class="text-end aging-current">@(cust.TotalCurrent > 0 ? cust.TotalCurrent.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-1-30">@(cust.Total1to30 > 0 ? cust.Total1to30.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-31-60">@(cust.Total31to60 > 0 ? cust.Total31to60.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-61-90">@(cust.Total61to90 > 0 ? cust.Total61to90.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-over90">@(cust.TotalOver90 > 0 ? cust.TotalOver90.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-current">@(cust.TotalCurrent > 0 ? cust.TotalCurrent.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-1-30">@(cust.Total1to30 > 0 ? cust.Total1to30.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-31-60">@(cust.Total31to60 > 0 ? cust.Total31to60.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-61-90">@(cust.Total61to90 > 0 ? cust.Total61to90.ToString("C") : "—")</td>
|
||||
<td class="text-end aging-over90">@(cust.TotalOver90 > 0 ? cust.TotalOver90.ToString("C") : "—")</td>
|
||||
<td class="text-end fw-semibold">@cust.TotalBalance.ToString("C")</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -218,7 +218,7 @@ else
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-muted small">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
||||
<td class="text-muted small">@(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||
<td class="text-muted small">@(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||
<td class="text-end fw-semibold @(inv.DaysOverdue > 30 ? "text-danger" : "")">@inv.BalanceDue.ToString("C")</td>
|
||||
<td class="text-end"><span class="badge @ageBadge">@ageLabel</span></td>
|
||||
<td></td>
|
||||
@@ -255,7 +255,7 @@ else
|
||||
<div class="card-body d-none" id="aiRiskBody">
|
||||
<div id="aiRiskSpinner" class="text-center py-3 d-none">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
<p class="text-muted mt-2 small">Claude is analyzing payment behavior…</p>
|
||||
<p class="text-muted mt-2 small">Claude is analyzing payment behavior…</p>
|
||||
</div>
|
||||
<div id="aiRiskError" class="alert alert-danger alert-permanent d-none"></div>
|
||||
<div id="aiRiskInsights" class="text-muted small mb-3"></div>
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
@Model.TotalLiabilitiesAndEquity.ToString("C")
|
||||
@if (!Model.IsBalanced)
|
||||
{
|
||||
<i class="bi bi-exclamation-triangle ms-1" title="Sheet does not balance — check account setup"></i>
|
||||
<i class="bi bi-exclamation-triangle ms-1" title="Sheet does not balance — check account setup"></i>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -105,7 +105,7 @@ else
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white border-0 py-3">
|
||||
<h5 class="mb-0 fw-semibold">
|
||||
<i class="bi bi-bar-chart-line me-2 text-primary"></i>@budget.Name — @reportYear
|
||||
<i class="bi bi-bar-chart-line me-2 text-primary"></i>@budget.Name — @reportYear
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<!-- Loading state -->
|
||||
<div id="loadingState" class="text-center py-5 d-none">
|
||||
<div class="spinner-border text-success mb-3" role="status"></div>
|
||||
<p class="text-muted">Analysing your AR, AP, and job pipeline…<br><small>This usually takes 5–10 seconds.</small></p>
|
||||
<p class="text-muted">Analysing your AR, AP, and job pipeline…<br><small>This usually takes 5–10 seconds.</small></p>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
@@ -196,9 +196,9 @@
|
||||
// Outlook banner
|
||||
const outlookMap = {
|
||||
strong: { cls: 'alert-success', icon: '💪', title: 'Strong Cash Position', sub: 'Your projected cash flow looks healthy across all three periods.' },
|
||||
moderate: { cls: 'alert-info', icon: '👍', title: 'Moderate Cash Position', sub: 'Cash flow looks manageable — a few items to watch.' },
|
||||
tight: { cls: 'alert-warning', icon: '⚠️', title: 'Tight Cash Position', sub: 'Cash flow may be constrained — consider following up on open invoices.' },
|
||||
concerning: { cls: 'alert-danger', icon: '🚨', title: 'Concerning Cash Position', sub: 'Projected cash flow is under pressure — immediate action may be needed.' }
|
||||
moderate: { cls: 'alert-info', icon: '👍', title: 'Moderate Cash Position', sub: 'Cash flow looks manageable — a few items to watch.' },
|
||||
tight: { cls: 'alert-warning', icon: '⚠️', title: 'Tight Cash Position', sub: 'Cash flow may be constrained — consider following up on open invoices.' },
|
||||
concerning: { cls: 'alert-danger', icon: '🚨', title: 'Concerning Cash Position', sub: 'Projected cash flow is under pressure — immediate action may be needed.' }
|
||||
};
|
||||
const outlook = outlookMap[data.outlook] || outlookMap.moderate;
|
||||
const banner = document.getElementById('outlookBanner');
|
||||
@@ -225,7 +225,7 @@
|
||||
|
||||
} catch (e) {
|
||||
document.getElementById('loadingState').classList.add('d-none');
|
||||
document.getElementById('errorMessage').textContent = 'Network error — please try again.';
|
||||
document.getElementById('errorMessage').textContent = 'Network error — please try again.';
|
||||
document.getElementById('errorState').classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div>
|
||||
<h4 class="fw-bold mb-0"><i class="bi bi-water me-2 text-info"></i>Cash Flow Statement</h4>
|
||||
<p class="text-muted small mb-0">
|
||||
@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy")
|
||||
@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy")
|
||||
· Direct Method (Cash Basis)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="card-body py-2">
|
||||
<div class="fs-4 fw-bold text-warning">@Model.AtRiskCount</div>
|
||||
<div class="small text-muted">At Risk</div>
|
||||
<div class="small text-muted">(31–60 days)</div>
|
||||
<div class="small text-muted">(31–60 days)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="card-body py-2">
|
||||
<div class="fs-4 fw-bold" style="color:#fd7e14">@Model.LapsingCount</div>
|
||||
<div class="small text-muted">Lapsing</div>
|
||||
<div class="small text-muted">(61–90 days)</div>
|
||||
<div class="small text-muted">(61–90 days)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,15 +101,15 @@
|
||||
@item.CustomerName
|
||||
</a>
|
||||
</td>
|
||||
<td class="small">@(item.Email ?? "—")</td>
|
||||
<td class="small">@(item.Phone ?? "—")</td>
|
||||
<td class="small">@(item.Email ?? "—")</td>
|
||||
<td class="small">@(item.Phone ?? "—")</td>
|
||||
<td class="text-end">@item.TotalJobs</td>
|
||||
<td class="text-end">@item.LifetimeRevenue.ToString("C")</td>
|
||||
<td>@(item.LastJobDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
<td>@(item.LastJobDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
<td class="text-end">
|
||||
@if (item.DaysSinceLastJob < 0)
|
||||
{
|
||||
<span class="text-muted">—</span>
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<div class="card-body">
|
||||
<div id="answerSpinner" class="text-center py-3 d-none">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
<p class="text-muted mt-2 small">Analyzing your financials…</p>
|
||||
<p class="text-muted mt-2 small">Analyzing your financials…</p>
|
||||
</div>
|
||||
<div id="answerError" class="alert alert-danger alert-permanent d-none"></div>
|
||||
<p id="answerText" class="mb-3 fs-6 d-none"></p>
|
||||
@@ -108,7 +108,7 @@
|
||||
<li class="mb-1">Ask about specific time periods: "last month", "Q1", "this year"</li>
|
||||
<li class="mb-1">Compare periods: "compared to last quarter"</li>
|
||||
<li class="mb-1">Ask about vendors, categories, or customers</li>
|
||||
<li>Claude only uses data it was given — it won't invent figures</li>
|
||||
<li>Claude only uses data it was given — it won't invent figures</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1338,7 +1338,7 @@
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header border-0 bg-body py-3">
|
||||
<h5 class="mb-0 fw-semibold">Profit & Loss — Revenue vs Expenses</h5>
|
||||
<h5 class="mb-0 fw-semibold">Profit & Loss — Revenue vs Expenses</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="plChart" height="130"></canvas>
|
||||
@@ -1604,7 +1604,7 @@
|
||||
<div class="fw-medium">@color.DisplayLabel</div>
|
||||
<div class="text-muted small">@color.SKU</div>
|
||||
</td>
|
||||
<td class="text-muted small">@(color.Manufacturer ?? "—")</td>
|
||||
<td class="text-muted small">@(color.Manufacturer ?? "—")</td>
|
||||
<td class="text-end">
|
||||
<div>@color.TotalLbsUsed.ToString("N1") lbs</div>
|
||||
<div class="progress mt-1" style="height:4px; min-width:80px;">
|
||||
@@ -1749,7 +1749,7 @@
|
||||
@c.BalanceDue.ToString("C")
|
||||
</td>
|
||||
<td class="text-end text-muted">@c.AvgInvoiceValue.ToString("C")</td>
|
||||
<td class="text-muted small">@(c.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
<td class="text-muted small">@(c.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
<td class="pe-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="progress flex-grow-1" style="height:6px;">
|
||||
@@ -1810,7 +1810,7 @@
|
||||
<div class="card-body py-3">
|
||||
<div class="fs-2 fw-bold text-warning">@retAtRisk</div>
|
||||
<div class="small text-muted">At Risk</div>
|
||||
<div class="small text-muted">30–60 days</div>
|
||||
<div class="small text-muted">30–60 days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1819,7 +1819,7 @@
|
||||
<div class="card-body py-3">
|
||||
<div class="fs-2 fw-bold text-orange" style="color:#f97316;">@retLapsing</div>
|
||||
<div class="small text-muted">Lapsing</div>
|
||||
<div class="small text-muted">60–90 days</div>
|
||||
<div class="small text-muted">60–90 days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1896,10 +1896,10 @@
|
||||
<span class="badge @badgeClass">@r.RetentionStatus</span>
|
||||
</td>
|
||||
<td class="text-center small">
|
||||
@(r.LastJobDate.HasValue ? r.LastJobDate.Value.ToString("MMM d, yyyy") : "—")
|
||||
@(r.LastJobDate.HasValue ? r.LastJobDate.Value.ToString("MMM d, yyyy") : "—")
|
||||
</td>
|
||||
<td class="text-center small">
|
||||
@(r.DaysSinceLastJob >= 0 ? r.DaysSinceLastJob + "d" : "—")
|
||||
@(r.DaysSinceLastJob >= 0 ? r.DaysSinceLastJob + "d" : "—")
|
||||
</td>
|
||||
<td class="text-end">@r.TotalJobs</td>
|
||||
<td class="text-end pe-3 fw-semibold">@r.LifetimeRevenue.ToString("C0")</td>
|
||||
@@ -1954,7 +1954,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<h3 class="mb-0 fw-bold text-muted">—</h3>
|
||||
<h3 class="mb-0 fw-bold text-muted">—</h3>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2103,7 +2103,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">—</span>
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -2128,9 +2128,9 @@
|
||||
<div class="tab-pane fade" id="invoice-aging-detail-tab">
|
||||
@{
|
||||
var iadCurrent = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "Current").ToList();
|
||||
var iad1to30 = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "1–30 Days").ToList();
|
||||
var iad31to60 = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "31–60 Days").ToList();
|
||||
var iad61to90 = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "61–90 Days").ToList();
|
||||
var iad1to30 = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "1–30 Days").ToList();
|
||||
var iad31to60 = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "31–60 Days").ToList();
|
||||
var iad61to90 = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "61–90 Days").ToList();
|
||||
var iad90plus = Model.InvoiceAgingDetail.Where(i => i.AgingBucket == "90+ Days").ToList();
|
||||
}
|
||||
<div class="row g-3 mb-4">
|
||||
@@ -2147,7 +2147,7 @@
|
||||
<div class="card border-0 shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="fs-5 fw-bold text-warning">@iad1to30.Sum(i => i.BalanceDue).ToString("C0")</div>
|
||||
<div class="small text-muted">1–30 Days</div>
|
||||
<div class="small text-muted">1–30 Days</div>
|
||||
<div class="small text-muted">@iad1to30.Count invoices</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2156,7 +2156,7 @@
|
||||
<div class="card border-0 shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="fs-5 fw-bold" style="color:#f97316;">@iad31to60.Sum(i => i.BalanceDue).ToString("C0")</div>
|
||||
<div class="small text-muted">31–60 Days</div>
|
||||
<div class="small text-muted">31–60 Days</div>
|
||||
<div class="small text-muted">@iad31to60.Count invoices</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2165,7 +2165,7 @@
|
||||
<div class="card border-0 shadow-sm text-center h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="fs-5 fw-bold text-danger">@iad61to90.Sum(i => i.BalanceDue).ToString("C0")</div>
|
||||
<div class="small text-muted">61–90 Days</div>
|
||||
<div class="small text-muted">61–90 Days</div>
|
||||
<div class="small text-muted">@iad61to90.Count invoices</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2217,9 +2217,9 @@
|
||||
{
|
||||
var bucketBadge = inv.AgingBucket switch {
|
||||
"Current" => "bg-success",
|
||||
"1–30 Days" => "bg-warning text-dark",
|
||||
"31–60 Days" => "badge-orange",
|
||||
"61–90 Days" => "bg-danger",
|
||||
"1–30 Days" => "bg-warning text-dark",
|
||||
"31–60 Days" => "badge-orange",
|
||||
"61–90 Days" => "bg-danger",
|
||||
_ => "bg-danger"
|
||||
};
|
||||
<tr>
|
||||
@@ -2235,7 +2235,7 @@
|
||||
}
|
||||
</td>
|
||||
<td class="small">@inv.InvoiceDate.ToString("MMM d, yyyy")</td>
|
||||
<td class="small">@(inv.DueDate.HasValue ? inv.DueDate.Value.ToString("MMM d, yyyy") : "—")</td>
|
||||
<td class="small">@(inv.DueDate.HasValue ? inv.DueDate.Value.ToString("MMM d, yyyy") : "—")</td>
|
||||
<td class="text-end">@inv.Total.ToString("C")</td>
|
||||
<td class="text-end text-success">@inv.AmountPaid.ToString("C")</td>
|
||||
<td class="text-end fw-semibold @(inv.BalanceDue > 0 ? "text-danger" : "")">@inv.BalanceDue.ToString("C")</td>
|
||||
@@ -2250,7 +2250,7 @@
|
||||
}
|
||||
</td>
|
||||
<td class="pe-3">
|
||||
<span class="badge @bucketBadge" style="@(inv.AgingBucket == "31–60 Days" ? "background:#f97316;" : "")">@inv.AgingBucket</span>
|
||||
<span class="badge @bucketBadge" style="@(inv.AgingBucket == "31–60 Days" ? "background:#f97316;" : "")">@inv.AgingBucket</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -2312,7 +2312,7 @@
|
||||
{
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-body border-0 pt-3 pb-2">
|
||||
<h6 class="mb-0 fw-semibold"><i class="bi bi-bar-chart-fill me-2 text-primary"></i>Top 10 Powders — Purchased vs Consumed (lbs)</h6>
|
||||
<h6 class="mb-0 fw-semibold"><i class="bi bi-bar-chart-fill me-2 text-primary"></i>Top 10 Powders — Purchased vs Consumed (lbs)</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="powderConsumptionChart" height="100"></canvas>
|
||||
@@ -2349,7 +2349,7 @@
|
||||
<div class="small text-muted">@p.ColorCode @(!string.IsNullOrEmpty(p.SKU) ? $"· {p.SKU}" : "")</div>
|
||||
}
|
||||
</td>
|
||||
<td class="small text-muted">@(p.Manufacturer ?? "—")</td>
|
||||
<td class="small text-muted">@(p.Manufacturer ?? "—")</td>
|
||||
<td class="text-end">@p.TotalPurchasedLbs.ToString("N1")</td>
|
||||
<td class="text-end">@p.TotalConsumedLbs.ToString("N1")</td>
|
||||
<td class="text-end">
|
||||
@@ -2399,7 +2399,7 @@
|
||||
<div class="card-body py-3">
|
||||
<div class="fs-2 fw-bold text-warning">@itLow</div>
|
||||
<div class="small text-muted">Low</div>
|
||||
<div class="small text-muted">7–30 days</div>
|
||||
<div class="small text-muted">7–30 days</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3197,7 +3197,7 @@
|
||||
// ── INVOICE AGING BAR ─────────────────────────────────────────────────────
|
||||
if (document.getElementById('invoiceAgingChart')) {
|
||||
@{
|
||||
var agingLabels = new[] { "Current", "1–30 Days", "31–60 Days", "61–90 Days", "90+ Days" };
|
||||
var agingLabels = new[] { "Current", "1–30 Days", "31–60 Days", "61–90 Days", "90+ Days" };
|
||||
var agingTotals = agingLabels.Select(b =>
|
||||
Model.InvoiceAgingDetail.Where(i => i.AgingBucket == b).Sum(i => i.BalanceDue)).ToArray();
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="small text-muted">@item.SKU</div>
|
||||
}
|
||||
</td>
|
||||
<td>@(item.ColorName ?? "—")</td>
|
||||
<td>@(item.ColorName ?? "—")</td>
|
||||
<td class="text-end">@item.CurrentStockLbs.ToString("N1")</td>
|
||||
<td class="text-end">@item.TotalConsumedLbs.ToString("N1")</td>
|
||||
<td class="text-end">@item.TotalPurchasedLbs.ToString("N1")</td>
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body py-2 text-center">
|
||||
<div class="small text-muted">Overdue (1–30 days)</div>
|
||||
<div class="small text-muted">Overdue (1–30 days)</div>
|
||||
<div class="fs-5 fw-bold text-warning">
|
||||
@Model.Items.Where(i => i.AgingBucket == "1–30 Days").Sum(i => i.BalanceDue).ToString("C")
|
||||
@Model.Items.Where(i => i.AgingBucket == "1–30 Days").Sum(i => i.BalanceDue).ToString("C")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,9 +65,9 @@
|
||||
{
|
||||
var bucketClass = item.AgingBucket switch {
|
||||
"Current" => "text-success",
|
||||
"1–30 Days" => "text-warning",
|
||||
"31–60 Days" => "text-orange",
|
||||
"61–90 Days" => "text-danger",
|
||||
"1–30 Days" => "text-warning",
|
||||
"31–60 Days" => "text-orange",
|
||||
"61–90 Days" => "text-danger",
|
||||
"90+ Days" => "fw-bold text-danger",
|
||||
_ => ""
|
||||
};
|
||||
@@ -85,12 +85,12 @@
|
||||
}
|
||||
</td>
|
||||
<td>@item.InvoiceDate.ToString("MMM d, yyyy")</td>
|
||||
<td>@(item.DueDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
<td>@(item.DueDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
<td class="text-end">@item.Total.ToString("C")</td>
|
||||
<td class="text-end text-success">@item.AmountPaid.ToString("C")</td>
|
||||
<td class="text-end fw-semibold">@item.BalanceDue.ToString("C")</td>
|
||||
<td class="text-end @bucketClass">
|
||||
@(item.DaysOverdue > 0 ? item.DaysOverdue.ToString() : "—")
|
||||
@(item.DaysOverdue > 0 ? item.DaysOverdue.ToString() : "—")
|
||||
</td>
|
||||
<td><span class="badge @bucketClass bg-opacity-10 border">@item.AgingBucket</span></td>
|
||||
<td><span class="badge bg-secondary-subtle text-secondary">@item.StatusDisplay</span></td>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">—</span>
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
</div>
|
||||
<h5>KPI Dashboard</h5>
|
||||
<p>High-level KPIs — revenue, active jobs, customers, and job counts with monthly trends and equipment status.</p>
|
||||
<p>High-level KPIs — revenue, active jobs, customers, and job counts with monthly trends and equipment status.</p>
|
||||
<div class="report-arrow">View dashboard <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
<a asp-controller="Reports" asp-action="Analytics" class="report-card">
|
||||
@@ -185,7 +185,7 @@
|
||||
<i class="bi bi-clock-history"></i>
|
||||
</div>
|
||||
<h5>AR Aging</h5>
|
||||
<p>Outstanding customer balances by age — current, 30, 60, and 90+ days. Exportable to PDF.</p>
|
||||
<p>Outstanding customer balances by age — current, 30, 60, and 90+ days. Exportable to PDF.</p>
|
||||
<div class="report-arrow">Open report <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
<a asp-controller="Reports" asp-action="InvoiceAgingDetail" class="report-card">
|
||||
@@ -209,7 +209,7 @@
|
||||
<i class="bi bi-hourglass-split"></i>
|
||||
</div>
|
||||
<h5>AP Aging</h5>
|
||||
<p>Outstanding vendor bills by age — current, 30, 60, and 90+ days past due. Exportable to PDF.</p>
|
||||
<p>Outstanding vendor bills by age — current, 30, 60, and 90+ days past due. Exportable to PDF.</p>
|
||||
<div class="report-arrow">Open report <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
<a asp-controller="Reports" asp-action="TrialBalance" class="report-card">
|
||||
@@ -217,7 +217,7 @@
|
||||
<i class="bi bi-list-columns-reverse"></i>
|
||||
</div>
|
||||
<h5>Trial Balance</h5>
|
||||
<p>All active accounts with debit and credit balances — validates that your books are in balance.</p>
|
||||
<p>All active accounts with debit and credit balances — validates that your books are in balance.</p>
|
||||
<div class="report-arrow">Open report <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
<a asp-controller="Reports" asp-action="CashFlowStatement" class="report-card">
|
||||
@@ -233,7 +233,7 @@
|
||||
<i class="bi bi-file-earmark-text"></i>
|
||||
</div>
|
||||
<h5>1099-NEC Report</h5>
|
||||
<p>Payments to 1099-eligible vendors by calendar year — flags those exceeding the $600 reporting threshold.</p>
|
||||
<p>Payments to 1099-eligible vendors by calendar year — flags those exceeding the $600 reporting threshold.</p>
|
||||
<div class="report-arrow">Open report <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
<a asp-controller="Reports" asp-action="BudgetVsActual" class="report-card">
|
||||
@@ -241,7 +241,7 @@
|
||||
<i class="bi bi-bar-chart-line"></i>
|
||||
</div>
|
||||
<h5>Budget vs. Actual</h5>
|
||||
<p>Compare monthly budgeted amounts against real P&L activity — revenue, expenses, and net income variance.</p>
|
||||
<p>Compare monthly budgeted amounts against real P&L activity — revenue, expenses, and net income variance.</p>
|
||||
<div class="report-arrow">Open report <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -289,7 +289,7 @@
|
||||
<i class="bi bi-person-check"></i>
|
||||
</div>
|
||||
<h5>Customer Retention</h5>
|
||||
<p>Customers segmented by recency — active, at risk, lapsing, churned, or never ordered.</p>
|
||||
<p>Customers segmented by recency — active, at risk, lapsing, churned, or never ordered.</p>
|
||||
<div class="report-arrow">Open report <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -376,7 +376,7 @@
|
||||
<i class="bi bi-phone-vibrate"></i>
|
||||
</div>
|
||||
<h5>SMS Consent Audit</h5>
|
||||
<p>Per-customer TCPA consent status — who opted in, who opted out, and when. Export to CSV for compliance records.</p>
|
||||
<p>Per-customer TCPA consent status — who opted in, who opted out, and when. Export to CSV for compliance records.</p>
|
||||
<div class="report-arrow">Open report <i class="bi bi-arrow-right"></i></div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
{
|
||||
<tr class="@(i.QuantityOnHand == 0 ? "table-danger" : "table-warning")">
|
||||
<td>@i.Name</td>
|
||||
<td>@(i.ColorName ?? "—")</td>
|
||||
<td>@(i.ColorName ?? "—")</td>
|
||||
<td class="text-end fw-semibold text-danger">@i.QuantityOnHand.ToString("N1")</td>
|
||||
<td class="text-end">@i.ReorderPoint.ToString("N1")</td>
|
||||
<td class="text-muted small">@i.UnitOfMeasure</td>
|
||||
|
||||
@@ -60,13 +60,13 @@
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@(item.ColorName ?? "—")
|
||||
@(item.ColorName ?? "—")
|
||||
@if (!string.IsNullOrEmpty(item.ColorCode))
|
||||
{
|
||||
<span class="badge bg-secondary-subtle text-secondary ms-1">@item.ColorCode</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-muted">@(item.Manufacturer ?? "—")</td>
|
||||
<td class="text-muted">@(item.Manufacturer ?? "—")</td>
|
||||
<td class="text-end">@item.TotalPurchasedLbs.ToString("N1")</td>
|
||||
<td class="text-end">@item.TotalConsumedLbs.ToString("N1")</td>
|
||||
<td class="text-end fw-semibold @varianceClass">@item.VarianceLbs.ToString("N1")</td>
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
<td>
|
||||
@item.DisplayLabel
|
||||
</td>
|
||||
<td class="text-muted small">@(item.SKU ?? "—")</td>
|
||||
<td class="text-muted small">@(item.Manufacturer ?? "—")</td>
|
||||
<td class="text-muted small">@(item.SKU ?? "—")</td>
|
||||
<td class="text-muted small">@(item.Manufacturer ?? "—")</td>
|
||||
<td class="text-end fw-semibold">@item.TotalLbsUsed.ToString("N1")</td>
|
||||
<td class="text-end">@item.TotalCost.ToString("C")</td>
|
||||
<td class="text-end">@item.JobCount</td>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<!-- Header -->
|
||||
<div class="d-flex align-items-center gap-2 mb-3 no-print">
|
||||
<a asp-action="Index" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i></a>
|
||||
<p class="text-muted mb-0">Income Statement — @Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy")</p>
|
||||
<p class="text-muted mb-0">Income Statement — @Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy")</p>
|
||||
@if (Model.AccountingMethod == PowderCoating.Core.Enums.AccountingMethod.Cash)
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Cash Basis</span>
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="text-center mb-4 d-none d-print-block">
|
||||
<h4 class="fw-bold">@Model.CompanyName</h4>
|
||||
<h5>Profit & Loss</h5>
|
||||
<p class="text-muted">@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy")</p>
|
||||
<p class="text-muted">@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy")</p>
|
||||
</div>
|
||||
|
||||
<!-- KPI Summary -->
|
||||
@@ -123,7 +123,7 @@
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold"><i class="bi bi-file-earmark-bar-graph me-1"></i>Income Statement</span>
|
||||
<span class="text-muted small">@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy")</span>
|
||||
<span class="text-muted small">@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
@@ -148,7 +148,7 @@
|
||||
<tr>
|
||||
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
||||
<td class="text-end">@line.Amount.ToString("C")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="report-subtotal-row">
|
||||
@@ -169,18 +169,18 @@
|
||||
<tr>
|
||||
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
||||
<td class="text-end">@line.Amount.ToString("C")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="report-subtotal-row">
|
||||
<td class="ps-4 fw-semibold">Total COGS</td>
|
||||
<td class="text-end fw-semibold text-warning">(@Model.TotalCogs.ToString("C"))</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.TotalCogs / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.TotalCogs / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
<tr class="report-subtotal-row">
|
||||
<td class="ps-2 fw-semibold">Gross Profit</td>
|
||||
<td class="text-end fw-semibold @(Model.GrossProfit >= 0 ? "text-success" : "text-danger")">@Model.GrossProfit.ToString("C")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : Model.GrossMarginPercent.ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : Model.GrossMarginPercent.ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@@ -198,20 +198,20 @@
|
||||
<tr>
|
||||
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
||||
<td class="text-end">@line.Amount.ToString("C")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="report-subtotal-row">
|
||||
<td class="ps-4 fw-semibold">Total Expenses</td>
|
||||
<td class="text-end fw-semibold text-danger">(@Model.TotalExpenses.ToString("C"))</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.TotalExpenses / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.TotalExpenses / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="report-net-row @(Model.NetIncome < 0 ? "report-net-negative" : "")">
|
||||
<td class="ps-2">Net Income</td>
|
||||
<td class="text-end @(Model.NetIncome >= 0 ? "text-success" : "text-danger")">@Model.NetIncome.ToString("C")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.NetIncome / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.NetIncome / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<!-- Header -->
|
||||
<div class="d-flex align-items-center gap-2 mb-3 no-print">
|
||||
<a asp-action="Index" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i></a>
|
||||
<p class="text-muted mb-0">@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy") · @Model.InvoiceCount invoices · @Model.CustomerCount customers</p>
|
||||
<p class="text-muted mb-0">@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy") · @Model.InvoiceCount invoices · @Model.CustomerCount customers</p>
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<a href="@Url.Action("SalesAndIncomePdf", new { from = Model.From.ToString("yyyy-MM-dd"), to = Model.To.ToString("yyyy-MM-dd") })"
|
||||
class="btn btn-sm btn-outline-danger no-print" target="_blank">
|
||||
@@ -72,7 +72,7 @@
|
||||
<div class="text-center mb-4 d-none d-print-block">
|
||||
<h4 class="fw-bold">@Model.CompanyName</h4>
|
||||
<h5>Sales & Income Report</h5>
|
||||
<p class="text-muted">@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy")</p>
|
||||
<p class="text-muted">@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy")</p>
|
||||
</div>
|
||||
|
||||
<!-- KPI Cards -->
|
||||
@@ -205,7 +205,7 @@ else
|
||||
<td class="text-end fw-semibold">@c.TotalInvoiced.ToString("C")</td>
|
||||
<td class="text-end text-success">@c.TotalPaid.ToString("C")</td>
|
||||
<td class="text-end @(c.BalanceDue > 0 ? "text-warning" : "text-muted")">@c.BalanceDue.ToString("C")</td>
|
||||
<td class="text-end text-muted small no-print">@(Model.TotalInvoiced == 0 ? "—" : (c.TotalInvoiced / Model.TotalInvoiced * 100).ToString("F1") + "%")</td>
|
||||
<td class="text-end text-muted small no-print">@(Model.TotalInvoiced == 0 ? "—" : (c.TotalInvoiced / Model.TotalInvoiced * 100).ToString("F1") + "%")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@@ -263,9 +263,9 @@ else
|
||||
</td>
|
||||
<td class="text-muted small">@inv.CustomerName</td>
|
||||
<td class="text-muted small">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
||||
<td class="text-muted small">@(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||
<td class="text-muted small">@(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||
<td class="text-end">@inv.SubTotal.ToString("C")</td>
|
||||
<td class="text-end text-muted small">@(inv.TaxAmount > 0 ? inv.TaxAmount.ToString("C") : "—")</td>
|
||||
<td class="text-end text-muted small">@(inv.TaxAmount > 0 ? inv.TaxAmount.ToString("C") : "—")</td>
|
||||
<td class="text-end fw-semibold">@inv.Total.ToString("C")</td>
|
||||
<td class="text-end text-success">@inv.AmountPaid.ToString("C")</td>
|
||||
<td><span class="badge @statusBadge">@inv.Status</span></td>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
@item.BalanceDue.ToString("C")
|
||||
</td>
|
||||
<td class="text-end">@item.AvgInvoiceValue.ToString("C")</td>
|
||||
<td>@(item.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
<td>@(item.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<!-- Header -->
|
||||
<div class="d-flex align-items-center gap-2 mb-3 no-print">
|
||||
<a asp-action="Index" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i></a>
|
||||
<p class="text-muted mb-0">@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy") · @(Model.TaxableInvoiceCount + Model.NonTaxableInvoiceCount) invoices</p>
|
||||
<p class="text-muted mb-0">@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy") · @(Model.TaxableInvoiceCount + Model.NonTaxableInvoiceCount) invoices</p>
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<a href="@Url.Action("SalesTaxCsv", new { from = Model.From.ToString("yyyy-MM-dd"), to = Model.To.ToString("yyyy-MM-dd") })"
|
||||
class="btn btn-sm btn-outline-success no-print">
|
||||
@@ -77,7 +77,7 @@
|
||||
<div class="text-center mb-4 d-none d-print-block">
|
||||
<h4 class="fw-bold">@Model.CompanyName</h4>
|
||||
<h5>Sales Tax Liability Report</h5>
|
||||
<p class="text-muted">@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy") · Invoice Basis</p>
|
||||
<p class="text-muted">@Model.From.ToString("MMMM d, yyyy") – @Model.To.ToString("MMMM d, yyyy") · Invoice Basis</p>
|
||||
</div>
|
||||
|
||||
<!-- KPI Cards -->
|
||||
@@ -284,11 +284,11 @@ else
|
||||
<td class="small text-muted">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
||||
<td><span class="badge @statusBadge">@inv.Status</span></td>
|
||||
<td class="text-end">@inv.SubTotal.ToString("C")</td>
|
||||
<td class="text-end text-muted small">@(isTaxable ? inv.TaxPercent.ToString("F2") + "%" : "—")</td>
|
||||
<td class="text-end @(isTaxable ? "fw-semibold text-primary" : "text-muted")">@(isTaxable ? inv.TaxAmount.ToString("C") : "—")</td>
|
||||
<td class="text-end text-muted small">@(isTaxable ? inv.TaxPercent.ToString("F2") + "%" : "—")</td>
|
||||
<td class="text-end @(isTaxable ? "fw-semibold text-primary" : "text-muted")">@(isTaxable ? inv.TaxAmount.ToString("C") : "—")</td>
|
||||
<td class="text-end fw-semibold">@inv.Total.ToString("C")</td>
|
||||
<td class="text-end text-success no-print">@inv.AmountPaid.ToString("C")</td>
|
||||
<td class="small text-muted">@(string.IsNullOrEmpty(inv.TaxAccountName) ? "—" : inv.TaxAccountName)</td>
|
||||
<td class="small text-muted">@(string.IsNullOrEmpty(inv.TaxAccountName) ? "—" : inv.TaxAccountName)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@@ -310,7 +310,7 @@ else
|
||||
|
||||
<div class="text-muted small mt-2 no-print">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Generated @DateTime.Now.ToString("MMM d, yyyy h:mm tt") · Invoice basis — tax liability is recognized when invoiced, not when collected. Excludes Draft and Voided invoices.
|
||||
Generated @DateTime.Now.ToString("MMM d, yyyy h:mm tt") · Invoice basis — tax liability is recognized when invoiced, not when collected. Excludes Draft and Voided invoices.
|
||||
</div>
|
||||
|
||||
@if (Model.ByMonth.Count > 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using PowderCoating.Web.Controllers
|
||||
@using PowderCoating.Web.Controllers
|
||||
@model List<Vendor1099Row>
|
||||
|
||||
@{
|
||||
@@ -81,7 +81,7 @@ else
|
||||
{
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white border-0 py-3">
|
||||
<h5 class="mb-0 fw-semibold"><i class="bi bi-table me-2 text-primary"></i>1099-NEC Summary — @reportYear</h5>
|
||||
<h5 class="mb-0 fw-semibold"><i class="bi bi-table me-2 text-primary"></i>1099-NEC Summary — @reportYear</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
@@ -117,14 +117,14 @@ else
|
||||
<span class="text-danger small"><i class="bi bi-exclamation-triangle me-1"></i>Missing</span>
|
||||
}
|
||||
</td>
|
||||
<td class="small">@(row.Address ?? "—")</td>
|
||||
<td class="small">@(row.Address ?? "—")</td>
|
||||
<td class="text-end">@row.BillsPaid.ToString("C")</td>
|
||||
<td class="text-end">@row.ExpensesPaid.ToString("C")</td>
|
||||
<td class="text-end fw-bold @(row.NeedsForm ? "text-danger" : "")">@row.TotalPaid.ToString("C")</td>
|
||||
<td class="text-center">
|
||||
@if (row.NeedsForm)
|
||||
{
|
||||
<span class="badge bg-danger">Yes — File Required</span>
|
||||
<span class="badge bg-danger">Yes — File Required</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user