Accounting reports: AR Aging (Property), Pre-Paid Homeowners, Cash Disbursement

Buildium-style reports built on the owner ledger and GL:
- AR Aging (Property): FIFO-aged buckets (0-30/over 30/60/90) per unit with
  charge-type breakdown, collection status, summary + distribution bar
- Pre-Paid Homeowners: units with net credit balances as of a date
- Cash Disbursement: bank-credit GL entries grouped by bank account with
  check#/vendor/invoice enrichment from the banking register and GL line detail
All with branded PDF/CSV exports; shared owner-ledger helpers in lib/ownerLedger.ts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 17:26:30 -04:00
parent 4c7fe7840b
commit e510a76dfc
5 changed files with 1092 additions and 4 deletions
+20 -4
View File
@@ -31,6 +31,9 @@ import { Lock } from "lucide-react";
import { TrialBalanceReport } from "./components/TrialBalanceReport";
import { GeneralLedgerReport } from "./components/GeneralLedgerReport";
import { ReserveFundReport } from "./components/ReserveFundReport";
import { ARAgingPropertyReport } from "./components/ARAgingPropertyReport";
import { PrepaidHomeownersReport } from "./components/PrepaidHomeownersReport";
import { CashDisbursementReport } from "./components/CashDisbursementReport";
import { ReportSheet } from "./components/ReportSheet";
import { loadBrandedLogo, drawBrandedHeader, drawBrandedFooter, type BrandedLogo } from "./lib/reportHeader";
import { generateBudgetVsActualPdf } from "@/lib/budgetVsActualPdf";
@@ -38,8 +41,8 @@ import { generateBudgetVsActualPdf } from "@/lib/budgetVsActualPdf";
type ReportId =
| "pnl" | "income-statement" | "balance-sheet" | "cash-flow" | "movement-of-equity" | "budget-vs-actuals"
| "trial-balance" | "general-ledger"
| "invoice-summary" | "customer-balances" | "ar-aging" | "homeowner-summary" | "delinquency"
| "expense-summary" | "vendor-balances" | "ap-aging" | "reconciliation"
| "invoice-summary" | "customer-balances" | "ar-aging" | "ar-aging-property" | "prepaid-homeowners" | "homeowner-summary" | "delinquency"
| "expense-summary" | "vendor-balances" | "ap-aging" | "cash-disbursement" | "reconciliation"
| "reserve-fund";
const APP_NAME = "Cozy Books";
@@ -57,6 +60,8 @@ const GROUPS = [
{ id: "budget-vs-actuals" as ReportId, name: "Budget vs Actuals" },
]},
{ name: "Receivables", reports: [
{ id: "ar-aging-property" as ReportId, name: "AR Aging (Property)" },
{ id: "prepaid-homeowners" as ReportId, name: "Pre-Paid Homeowners" },
{ id: "ar-aging" as ReportId, name: "AR Aging Details" },
{ id: "homeowner-summary" as ReportId, name: "Homeowner Balance Summary" },
{ id: "customer-balances" as ReportId, name: "Invoice Summary by Customer" },
@@ -64,6 +69,7 @@ const GROUPS = [
{ id: "delinquency" as ReportId, name: "Delinquency Report" },
]},
{ name: "Payables", reports: [
{ id: "cash-disbursement" as ReportId, name: "Cash Disbursement" },
{ id: "ap-aging" as ReportId, name: "AP Aging Details" },
{ id: "expense-summary" as ReportId, name: "Expense Summary" },
{ id: "vendor-balances" as ReportId, name: "Vendor Balance Summary" },
@@ -396,7 +402,8 @@ export default function AccountingReportsPage({ association }: { association?: {
}, [active, arOpen, data, flat, structured, cur, activeMeta.name]);
// Reports whose export is handled internally (own PDF/CSV buttons inside the component)
const hasOwnExport = active === "trial-balance" || active === "general-ledger" || active === "budget-vs-actuals" || active === "income-statement";
const hasOwnExport = active === "trial-balance" || active === "general-ledger" || active === "budget-vs-actuals" || active === "income-statement"
|| active === "ar-aging-property" || active === "prepaid-homeowners" || active === "cash-disbursement";
const anyExportable = !!(structured || flat || exportFlat);
const doExportPDF = async () => {
@@ -588,6 +595,15 @@ export default function AccountingReportsPage({ association }: { association?: {
{active === "reserve-fund" && (
<ReserveFundReport companyId={cid} companyName={associationName ?? ""} fiscalYearStart={fiscalYearStart} logoUrl={logoUrl} />
)}
{active === "ar-aging-property" && (
<ARAgingPropertyReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} />
)}
{active === "prepaid-homeowners" && (
<PrepaidHomeownersReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} />
)}
{active === "cash-disbursement" && (
<CashDisbursementReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} />
)}
{active === "reconciliation" && (
<ReportSheet title="Reconciliation Checks" companyName={associationName ?? "Company"} period={rangeLabel} logoUrl={logoUrl}>
<ReconciliationReport d={data} currency={cur} />
@@ -604,7 +620,7 @@ export default function AccountingReportsPage({ association }: { association?: {
<Card><CardContent className="p-6"><div className="py-12 text-center text-sm text-muted-foreground">No data for this report in the selected range.</div></CardContent></Card>
)
)}
{!isFinancial && active !== "budget-vs-actuals" && active !== "income-statement" && active !== "trial-balance" && active !== "general-ledger" && active !== "reserve-fund" && active !== "reconciliation" && (
{!isFinancial && active !== "budget-vs-actuals" && active !== "income-statement" && active !== "trial-balance" && active !== "general-ledger" && active !== "reserve-fund" && active !== "reconciliation" && active !== "ar-aging-property" && active !== "prepaid-homeowners" && active !== "cash-disbursement" && (
<ReportSheet title={activeMeta.name} companyName={associationName ?? "Company"} period={rangeLabel} logoUrl={logoUrl}>
{!data ? (
<div className="text-sm text-muted-foreground">Loading</div>