Phase H: Add Cash Flow Statement (direct / cash-basis method)

- CashFlowStatementDto (Operating, Investing, Financing sections; BeginningCash/EndingCash)
- CashFlowLineDto for Investing/Financing line items
- GetCashFlowStatementAsync on IFinancialReportService + implementation in FinancialReportService
- GenerateCashFlowStatementPdfAsync on IPdfService + QuestPDF implementation in PdfService
- ReportsController.CashFlowStatement GET + CashFlowStatementPdf GET with inline/download mode
- CashFlowStatement.cshtml view with date filter, 3-section cards, summary sidebar, methodology note
- Reports Landing page: Cash Flow Statement card added to Accounting section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 11:14:47 -04:00
parent 42eff3357e
commit 14026818e2
8 changed files with 546 additions and 0 deletions
@@ -1255,6 +1255,42 @@ public class ReportsController : Controller
: File(pdfBytes, "application/pdf", $"TrialBalance-{asOfDate:yyyyMMdd}.pdf");
}
/// <summary>
/// Cash Flow Statement — shows cash receipts from customers, cash payments to vendors and
/// for direct expenses, and a summary of beginning/ending cash position. Uses the direct
/// (cash-basis) method for operating activities so the numbers reflect actual cash movement
/// regardless of the company's accrual vs cash accounting preference.
/// Gated behind <see cref="AllowAccounting"/>.
/// </summary>
// GET: /Reports/CashFlowStatement
public async Task<IActionResult> CashFlowStatement(DateTime? from, DateTime? to)
{
if (!AllowAccounting()) return RedirectToAction(nameof(Landing));
var fromDate = (from ?? new DateTime(DateTime.Today.Year, 1, 1)).Date;
var toDate = (to ?? DateTime.Today).Date;
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var cid) ? cid : 0;
var dto = await _financialReports.GetCashFlowStatementAsync(companyId, fromDate, toDate);
return View(dto);
}
/// <summary>
/// PDF export of the Cash Flow Statement. Same inline/attachment pattern as other PDF actions.
/// Gated behind <see cref="AllowAccounting"/>.
/// </summary>
// GET: /Reports/CashFlowStatementPdf
public async Task<IActionResult> CashFlowStatementPdf(DateTime? from, DateTime? to, bool inline = false)
{
if (!AllowAccounting()) return RedirectToAction(nameof(Landing));
var fromDate = (from ?? new DateTime(DateTime.Today.Year, 1, 1)).Date;
var toDate = (to ?? DateTime.Today).Date;
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var cid) ? cid : 0;
var dto = await _financialReports.GetCashFlowStatementAsync(companyId, fromDate, toDate);
var pdfBytes = await _pdfService.GenerateCashFlowStatementPdfAsync(dto);
return inline
? File(pdfBytes, "application/pdf")
: File(pdfBytes, "application/pdf", $"CashFlow-{fromDate:yyyyMMdd}-{toDate:yyyyMMdd}.pdf");
}
// ── INDIVIDUAL REPORT PAGES ──────────────────────────────────────────────
/// <summary>