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>
This commit is contained in:
@@ -1192,6 +1192,69 @@ public class ReportsController : Controller
|
||||
return File(bytes, "text/csv", $"SalesTaxReport-{fromDate:yyyyMMdd}-{toDate:yyyyMMdd}.csv");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accounts Payable Aging report — mirrors the AR Aging but groups open bills by vendor
|
||||
/// and buckets them by days past due date. Gated behind <see cref="AllowAccounting"/>.
|
||||
/// </summary>
|
||||
// GET: /Reports/ApAging
|
||||
public async Task<IActionResult> ApAging(DateTime? asOf)
|
||||
{
|
||||
if (!AllowAccounting()) return RedirectToAction(nameof(Landing));
|
||||
var asOfDate = (asOf ?? DateTime.Today).Date;
|
||||
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var cid) ? cid : 0;
|
||||
var dto = await _financialReports.GetApAgingAsync(companyId, asOfDate);
|
||||
return View(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PDF export of the AP Aging report. Same inline/attachment pattern as other PDF actions.
|
||||
/// Gated behind <see cref="AllowAccounting"/>.
|
||||
/// </summary>
|
||||
// GET: /Reports/ApAgingPdf
|
||||
public async Task<IActionResult> ApAgingPdf(DateTime? asOf, bool inline = false)
|
||||
{
|
||||
if (!AllowAccounting()) return RedirectToAction(nameof(Landing));
|
||||
var asOfDate = (asOf ?? DateTime.Today).Date;
|
||||
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var cid) ? cid : 0;
|
||||
var dto = await _financialReports.GetApAgingAsync(companyId, asOfDate);
|
||||
var pdfBytes = await _pdfService.GenerateApAgingPdfAsync(dto);
|
||||
return inline
|
||||
? File(pdfBytes, "application/pdf")
|
||||
: File(pdfBytes, "application/pdf", $"AP-Aging-{asOfDate:yyyyMMdd}.pdf");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trial Balance report — lists all active accounts with debit and credit balances using
|
||||
/// <c>Account.CurrentBalance</c> (live, not point-in-time). Validates that debits equal
|
||||
/// credits. Gated behind <see cref="AllowAccounting"/>.
|
||||
/// </summary>
|
||||
// GET: /Reports/TrialBalance
|
||||
public async Task<IActionResult> TrialBalance(DateTime? asOf)
|
||||
{
|
||||
if (!AllowAccounting()) return RedirectToAction(nameof(Landing));
|
||||
var asOfDate = (asOf ?? DateTime.Today).Date;
|
||||
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var cid) ? cid : 0;
|
||||
var dto = await _financialReports.GetTrialBalanceAsync(companyId, asOfDate);
|
||||
return View(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PDF export of the Trial Balance report. Same inline/attachment pattern as other PDF actions.
|
||||
/// Gated behind <see cref="AllowAccounting"/>.
|
||||
/// </summary>
|
||||
// GET: /Reports/TrialBalancePdf
|
||||
public async Task<IActionResult> TrialBalancePdf(DateTime? asOf, bool inline = false)
|
||||
{
|
||||
if (!AllowAccounting()) return RedirectToAction(nameof(Landing));
|
||||
var asOfDate = (asOf ?? DateTime.Today).Date;
|
||||
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var cid) ? cid : 0;
|
||||
var dto = await _financialReports.GetTrialBalanceAsync(companyId, asOfDate);
|
||||
var pdfBytes = await _pdfService.GenerateTrialBalancePdfAsync(dto);
|
||||
return inline
|
||||
? File(pdfBytes, "application/pdf")
|
||||
: File(pdfBytes, "application/pdf", $"TrialBalance-{asOfDate:yyyyMMdd}.pdf");
|
||||
}
|
||||
|
||||
// ── INDIVIDUAL REPORT PAGES ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user