Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Reports/ApAging.cshtml
T
spouliot 7e1676cfd7 Add Phase A accounting features: AP Aging, Trial Balance, Cash vs Accrual
- AP Aging report (GetApAgingAsync, controller actions, view, PDF export)
  mirrors AR Aging — groups open bills by vendor, buckets by days past due date
- Trial Balance report (GetTrialBalanceAsync, view, PDF export)
  uses Account.CurrentBalance, groups by AccountType, validates debits == credits
- Cash vs Accrual accounting method setting on Company entity
  switchable at any time — report-time only, no GL re-posting on change
  P&L cash: revenue = payments received; expenses = bills/expenses paid in period
  Balance Sheet cash: omits AR and AP lines (no receivables/payables concept)
  AccountingMethod badge shown on P&L and Balance Sheet views
- Migration A (AddAccountingMethod) applied, default = Accrual for all existing companies
- AP Aging and Trial Balance added to Reports Landing page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:34:54 -04:00

243 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@model PowderCoating.Application.DTOs.Accounting.ApAgingReportDto
@{
ViewData["Title"] = "AP Aging";
ViewData["PageIcon"] = "bi-hourglass-split";
var today = DateTime.Today;
}
<style>
@@media print {
.no-print { display: none !important; }
.card { border: 1px solid #dee2e6 !important; box-shadow: none !important; }
body { font-size: 11px; }
.table { font-size: 11px; }
}
.aging-current { color: #198754; }
.aging-1-30 { color: #fd7e14; }
.aging-31-60 { color: #dc6c02; }
.aging-61-90 { color: #dc3545; }
.aging-over90 { color: #842029; font-weight: 700; }
.vendor-row { background: #f8f9fa; font-weight: 600; }
</style>
<!-- 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">As of @Model.AsOf.ToString("MMMM d, yyyy") · @Model.Vendors.Sum(v => v.Bills.Count) open bills</p>
<div class="ms-auto d-flex gap-2">
<a href="@Url.Action("ApAgingPdf", new { asOf = Model.AsOf.ToString("yyyy-MM-dd") })"
class="btn btn-sm btn-outline-danger no-print" target="_blank">
<i class="bi bi-file-pdf me-1"></i>Download PDF
</a>
<a href="@Url.Action("ApAgingPdf", new { asOf = Model.AsOf.ToString("yyyy-MM-dd"), inline = true })"
class="btn btn-sm btn-outline-secondary no-print" target="_blank">
<i class="bi bi-printer me-1"></i>Print
</a>
</div>
</div>
<!-- Date filter -->
<div class="card shadow-sm mb-4 no-print">
<div class="card-body py-3">
<form method="get" class="row g-2 align-items-end">
<div class="col-auto">
<label class="form-label form-label-sm mb-1">As of Date</label>
<input type="date" name="asOf" class="form-control form-control-sm" value="@Model.AsOf.ToString("yyyy-MM-dd")" />
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-funnel me-1"></i>Run Report</button>
</div>
<div class="col-auto ms-2">
<div class="btn-group btn-group-sm">
<a href="@Url.Action("ApAging", new { asOf = today.ToString("yyyy-MM-dd") })" class="btn btn-outline-secondary">Today</a>
<a href="@Url.Action("ApAging", new { asOf = new DateTime(today.Year, today.Month, 1).AddDays(-1).ToString("yyyy-MM-dd") })" class="btn btn-outline-secondary">End of Last Month</a>
</div>
</div>
</form>
</div>
</div>
<!-- Print header -->
<div class="text-center mb-4 d-none d-print-block">
<h4 class="fw-bold">@Model.CompanyName</h4>
<h5>Accounts Payable Aging</h5>
<p class="text-muted">As of @Model.AsOf.ToString("MMMM d, yyyy")</p>
</div>
<!-- Aging summary cards -->
<div class="row g-3 mb-4">
<div class="col-6 col-lg">
<div class="card shadow-sm text-center h-100">
<div class="card-body py-3">
<div class="h6 text-success mb-1">@Model.TotalCurrent.ToString("C0")</div>
<div class="text-muted small">Current</div>
</div>
</div>
</div>
<div class="col-6 col-lg">
<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">130 Days</div>
</div>
</div>
</div>
<div class="col-6 col-lg">
<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">3160 Days</div>
</div>
</div>
</div>
<div class="col-6 col-lg">
<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">6190 Days</div>
</div>
</div>
</div>
<div class="col-6 col-lg">
<div class="card shadow-sm text-center h-100">
<div class="card-body py-3">
<div class="h6 aging-over90 mb-1">@Model.TotalOver90.ToString("C0")</div>
<div class="text-muted small">Over 90 Days</div>
</div>
</div>
</div>
<div class="col-6 col-lg">
<div class="card shadow-sm text-center h-100 border-danger border-opacity-25">
<div class="card-body py-3">
<div class="h6 text-danger fw-bold mb-1">@Model.TotalOutstanding.ToString("C0")</div>
<div class="text-muted small">Total Owed</div>
</div>
</div>
</div>
</div>
@if (!Model.Vendors.Any())
{
<div class="card shadow-sm">
<div class="card-body text-center py-5 text-muted">
<i class="bi bi-check-circle text-success fs-1 d-block mb-2"></i>
<p class="mb-0 fw-semibold">All bills are paid!</p>
<p class="small mb-0">No outstanding balances as of @Model.AsOf.ToString("MMMM d, yyyy").</p>
</div>
</div>
}
else
{
<!-- Summary table -->
<div class="card shadow-sm mb-4">
<div class="card-header fw-semibold">
<i class="bi bi-table me-1"></i>Aging Summary by Vendor
</div>
<div class="table-responsive">
<table class="table table-hover table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th>Vendor</th>
<th class="text-end">Current</th>
<th class="text-end">130 Days</th>
<th class="text-end">3160 Days</th>
<th class="text-end">6190 Days</th>
<th class="text-end">Over 90</th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>
@foreach (var vend in Model.Vendors)
{
<tr>
<td>
<a asp-controller="Vendors" asp-action="Details" asp-route-id="@vend.VendorId" class="text-decoration-none fw-medium">
@vend.VendorName
</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 fw-semibold">@vend.TotalBalance.ToString("C")</td>
</tr>
}
</tbody>
<tfoot class="table-light fw-bold">
<tr>
<td>Total</td>
<td class="text-end aging-current">@Model.TotalCurrent.ToString("C")</td>
<td class="text-end aging-1-30">@Model.Total1to30.ToString("C")</td>
<td class="text-end aging-31-60">@Model.Total31to60.ToString("C")</td>
<td class="text-end aging-61-90">@Model.Total61to90.ToString("C")</td>
<td class="text-end aging-over90">@Model.TotalOver90.ToString("C")</td>
<td class="text-end">@Model.TotalOutstanding.ToString("C")</td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- Detail by vendor -->
<div class="card shadow-sm">
<div class="card-header fw-semibold">
<i class="bi bi-list-ul me-1"></i>Bill Detail
</div>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th>Bill #</th>
<th>Bill Date</th>
<th>Due Date</th>
<th class="text-end">Balance Due</th>
<th class="text-end">Age</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var vend in Model.Vendors)
{
<tr class="vendor-row">
<td colspan="6" class="py-2">@vend.VendorName</td>
</tr>
@foreach (var bill in vend.Bills.OrderBy(b => b.DaysOverdue))
{
string ageBadge = bill.DaysOverdue <= 0 ? "bg-success-subtle text-success"
: bill.DaysOverdue <= 30 ? "bg-warning-subtle text-warning"
: bill.DaysOverdue <= 60 ? "bg-orange-subtle text-warning"
: bill.DaysOverdue <= 90 ? "bg-danger-subtle text-danger"
: "bg-danger text-white";
string ageLabel = bill.DaysOverdue <= 0 ? "Current" : $"{bill.DaysOverdue}d overdue";
<tr>
<td class="ps-4">
<a asp-controller="Bills" asp-action="Details" asp-route-id="@bill.BillId" class="text-decoration-none fw-medium">
@bill.BillNumber
</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-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>
</tr>
}
<tr class="table-light">
<td colspan="3" class="ps-4 fw-semibold text-end small">@vend.VendorName subtotal</td>
<td class="text-end fw-semibold">@vend.TotalBalance.ToString("C")</td>
<td colspan="2"></td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<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") · Includes all open bills (excluding Draft and Voided). Age calculated from due date.
</div>