Exclude transaction_type 'note' from the homeowner ledger and statements
pages so internal staff notes aren't shown to owners.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an "Add Note" action that records a memo on an owner's ledger as a
$0.00 entry (transaction_type 'note', debit/credit 0). Notes work on any
ledger including paid-up ($0.00 balance) accounts, render as styled memo
rows, and don't affect the running balance. The accounting sync treats a
zero entry as a no-op, so no phantom AR is created.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add row checkboxes (with per-category select-all) to the accounting Chart of
Accounts. A selection bar exposes Edit/Delete/Clear. Bulk edit applies any of
type, parent account, bank flag, reserve flag to all selected (each field has
a "No change" option); bulk delete removes selected accounts. Mirrors the
existing bulk-edit UX on the per-association chart_of_accounts page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Approved/paid bills that never went through in-app approval (imports, bulk
loads) had no approver row, so the Approvers column was blank. Backfill a
synthetic approver: 'Imported' for system imports (created_by null), the
creator's name (fallback 'Direct entry') for in-app entries. Adds an AFTER
INSERT trigger so future imported-as-approved/paid bills get one too.
Applied to prod: +1,140 rows, 0 approved/paid bills now missing an approver.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
canvas tried to compile native bindings on the runner and failed (no
prebuilt binary for the Node ABI, no system cairo/pango). The Vite browser
build doesn't need it, so install with --ignore-scripts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The list loaded all bills (~1,150) then fetched approvers with
.in("bill_id", [all ids]); that request URL exceeds server limits and
fails silently, so approvalsByBill was always empty. Fetch the small
bill_approvals table directly (RLS scopes per role) and group locally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-fire the auto-deploy with bill-approvals approver_name + admins-only
mark-paid changes (already merged in PRs #8 and #10).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restrict marking a bill paid to admins only, per requirement.
- BillDetailPage: gate Mark Paid / Mark Unpaid on useAuth().isAdmin
(was only hidden in board view).
- BillApprovalsPage: gate Print Checks (which sets bills to paid) on isAdmin.
- Migration: BEFORE INSERT/UPDATE trigger enforce_admin_marks_bill_paid()
rejects the transition into 'paid' for authenticated non-admins. Service-role
/ system contexts (auth.uid() null: buildium-sync, accounting triggers,
autopay) remain allowed. Verified: admin allowed, non-admin blocked (23514).
Note: the approver column showing "None" in production is a stale-deploy
issue — the DB column was renamed vendor_name->approver_name (Jun 4) but
prod still ran code querying vendor_name. Deploying current main resolves it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The webpack.yml workflow ran `npx webpack` on a Vite project (no webpack
present), failing on every push/PR. static.yml uploaded the raw repo root
to GitHub Pages (not a real build; the app deploys via Vercel/Lovable) and
also failed. Replace both with a CI workflow that installs via bun
(bun.lockb is the maintained lockfile; package-lock.json is stale) and runs
the Vite build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the bill_approvals approver column from vendor_name to approver_name
across UI, hooks, types, and buildium-sync. Add a Chart of Accounts import
dialog (XLSX upload + column mapping + type-alias normalization) and a
"denied" status color.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- bill_should_mirror now includes 'pending', so approval bills appear in
Payables immediately (still excludes draft/rejected/void/cancelled/denied).
- New reverse-creation path: a bill created natively in the Accounting module
(external_source NULL, non-auto, non-payment, non-void) now creates a matching
public.bills row. The accounting row is pre-linked to the new public id so the
forward sync adopts it (no duplicate mirror); vendor is mapped back to
public.vendors and the line's GL is carried to expense_account_id.
- Backfill: mirrored existing pending public bills and reverse-created the 8
eligible native accounting bills. Verified: 0 unlinked native, 0 duplicate
mirrors, pending bills mirrored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Buildium bill imports left expense_account_id null on all bills because the
importer only read firstLine.GLAccountId, but the bill-line payload exposes the
GL id under GLAccount.Id (nested). chart_of_accounts.account_number already
stores the Buildium GL Id, so resolve the line's GL id from either shape before
mapping it. Re-syncing backfills existing bills via the update path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Single vendor source (public.vendors) and single COA source (accounting.accounts)
across both bill flows:
- Forward sync now carries public.bills.expense_account_id into the mirrored
accounting.bill_items.account_id (when it resolves to accounting.accounts).
- Reverse trigger flows a GL change on a mirrored accounting bill line back to
public.bills.expense_account_id (loop-guarded).
- New public.ensure_accounting_vendor RPC resolves a chosen public vendor to its
accounting.vendors row; one-time backfill of mirrored line account_id.
- BillApprovalsPage GL pickers now use ChartOfAccountsDropdown (accounting.accounts).
- AccountingBillsPage vendor picker now lists public.vendors scoped to the
company's association and maps to accounting.vendors on save.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
trg_acct_bill_paid_back was AFTER UPDATE only, so bills mirrored into
accounting already-paid never triggered the back-sync that flips
public.bills + bill_approvals to 'paid'. Fire the trigger on INSERT too
and reconcile existing already-paid mirrored bills. Also backfill
invoice-track approvals that uniquely match a bill (bill_id was null).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The supabase-js .functions.invoke() call from one edge function to
another doesn't reliably attach the apikey as Authorization, so the
inner send-transactional-email call was failing verify_jwt and
returning 401. Pass the service-role bearer header explicitly.
This is what was actually preventing bill-approval-vote-invite emails
from going out — every Notify Board flow logged 401s on the per-bill
sends, with zero rows ever landing in email_send_log for that template.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UI
- Dashboard BillApprovalsCard: render approver name chips (color-coded
by vote status) per pending bill instead of leaving the approver
identity invisible.
- BillDetailPage: collapse the duplicate "Requested Approvers" card
into the existing "Approvers" table. Approve/deny handler now stamps
approved_by = auth.uid() for audit trail.
- MasterBoardDashboardPage: the "pending approvals for me" count was
filtering on a non-existent bill_approvals.approver_user_id column.
Replaced with a board_members.member_name -> bill_approvals.approver_name
join (matches the RLS policy).
- BillApprovalRequestDialog + AIInvoiceParserPage: bill_approvals inserts
now set created_by.
Database
- Rename public.bill_approvals.vendor_name -> approver_name. RLS policies
auto-rewritten by ALTER TABLE RENAME COLUMN; the column was misnamed
(it stores the approver's board-member name, never a vendor).
- Restore the bill_approval_email_tokens table + lookup_/record_
bill_approval_by_token RPCs. The original 20260520153409 migration
was never applied successfully; rewrote it to use approver_name and
to populate approved_by/created_by from board_members.user_id on
token-driven votes. Added the v2 migration that matches the live DB
state.
- accounting trigger: void on accounting.bills cascades to
public.bills.status='cancelled' (existing forward sync then drops the
accounting row per accounting.bill_should_mirror).
Edge function
- send-transactional-email: add bill-approval-request and
bill-approval-vote-invite templates (caller paths in BillApprovalsPage
+ send-bill-approval-invites referenced templates that weren't in the
registry, so every email 404'd). Restored the local copies of
election-invite, board-vote-invite, and the missing registry.ts so the
repo matches what's deployed. Deployed to send-transactional-email v35.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each association now owns an independent set of chart_of_accounts rows. Two
associations can both have a "5000" meaning different things, edited
independently — previously buildium rows were shared across associations via the
association_ids[] array, so editing one association's number edited it for all.
- Data migration: split each shared buildium row into one row per association in
its association_ids (excluding nulls and ids of deleted associations). The
original row stays as the per-association row for its own association_id;
clones are added for the others — nothing is deleted, so no FK dangles.
94 -> 370 buildium rows. References repointed by each record's association
(bills, budgets, owner_ledger_entries, vendor_coa_mappings, units, vendors,
journal_entries; budget_actuals_monthly is a view). parent_account_id remapped
same-association; orphan-parent children become top-level. Pristine backup in
public._coa_perassoc_backup. Ran in one transaction with in-line verification.
- Uniqueness: drop (account_number, accounting_system); add
UNIQUE(association_id, account_number) WHERE accounting_system <> 'platform'
(platform rows mirror accounting.accounts and carry blank/dup codes). This also
finally backs the buildium importers' existing onConflict target.
- Keep association_ids as a single-element mirror of association_id during the
transition so the admin COA page and direct array-contains callers keep working.
- App: fetchChartOfAccounts scopes buildium/zoho by association_id when an
association is given (was system-wide). Importers and sync_account_to_public_coa
were already per-association; no change needed.
Verified on live data: 370 singleton-array rows across 12 associations, zero
intra-association duplicates, zero cross-association references or parents, and a
live two-association "5000" independence test (create/rename/isolate) passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deposits no longer force the credit side through Undeposited Funds — the
structural cause of negative Undeposited balances. A deposit can now credit any
account(s): interest income, a refund, an insurance reimbursement, cash straight
to the bank, etc.
- Schema: add accounting.deposit_lines (deposit_id, company_id, account_id,
amount, memo) for the credit side, plus deposits.source_account_id as a
single-source fallback. RLS mirrors deposits (staff + company member).
- post_deposit_gl: Dr bank for the total; Cr each deposit_lines row's account
for its amount; no lines -> Cr source_account_id; neither -> Cr Undeposited
Funds (backward compatible — existing deposits stay Dr Bank / Cr Undeposited).
Remainder safety net keeps the entry balanced. New trg_acct_deposit_line_gl
re-posts when lines change (header trigger fires before lines exist).
- Make Deposit page: GL-driven submit writes the deposit header + deposit_lines
and marks selected payments deposited. Adds an "Other deposit lines" grid
(account + amount + memo) alongside the existing Undeposited selection, with a
running grand total and a soft guard against over-crediting Undeposited.
Drops the old bank/Undeposited register-transaction inserts and manual balance
pokes (never exercised in production; carried a money-in sign bug). Deposits
are GL-only, consistent with the sync-created deposits already in the DB.
Verified Dr/Cr for single-source and multi-line scenarios against the live GL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Under accrual, a vendor expense is recognized once — when the bill is entered
(Dr Expense / Cr A/P). Paying a bill must only relieve the liability
(Dr A/P / Cr Bank) and never re-hit the expense account. This hardens the
existing Pay Bills flow against re-recognition and double counting.
- Strict bill matching: extract a pure, dependency-free matcher into
lib/billMatch.ts (debitMatchesBill/matchOpenBills) — same vendor, status
open/overdue/partially_paid, amount within $0.01 of remaining (or <= remaining
for partials), debit date within ±30 days of due/issue date. Unit-tested in
billMatch.test.ts (covers the identical-recurring-charge regression).
- AccountingBankingPage.saveTx uses the strict rule (was "any open bill"), so a
thrice-paid identical charge only clears the in-window bill.
- Bank-feed categorizer (bulkSetCategory) matches open bills before assigning an
expense COA: single match clears A/P + links the bill; multi-match is skipped
with a prompt to resolve in Pay Bills; no match categorizes as a direct expense.
- DB guard: add accounting.transactions.bill_id (FK -> bills) and CHECK
chk_bill_payment_no_coa (bill_id IS NULL OR coa_account_id IS NULL) so a
bill-linked payment can never carry an expense category. Both writers set
bill_id on single-bill payments; partial payments now write partially_paid.
- One-time cleanup: clear coa_account_id on Ashley Manor's 8 double-counted bill
payments ($2,198.98) so the GL reposts them as Dr A/P / Cr Bank.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add Sales Receipts page (dashboard/accounting/sales-receipts): records a
cash sale (name, address, income account, price, qty) — deposits and books
income in one step via a transaction. New accounting.sales_receipts table.
- Sync chart of accounts to the accounting dashboard: mirror accounting.accounts
into public.chart_of_accounts for platform associations (one-way, same id) so
Bill Approvals and every COA consumer use the dashboard's accounts. Legacy
rows hidden; Bill Approvals made system-aware.
- Vendor-expense recognition: a vendor payment with no bill now books the
expense directly (Dr Expense / Cr Bank) on the payment date instead of going
to A/P; payments against open bills still clear A/P (applied FIFO). Backfill
reclassifies unbilled payments stuck in A/P. Expense Summary report made
GL-driven so it follows the same rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- buildium-sync now ensures every active association has an accounting company
after syncing associations. Once it exists, the existing DB triggers flow
units -> customers, owner ledger -> A/R + income, and bills -> A/P + expense
into Accounting automatically (closing the gap where Buildium synced only the
main dashboard, not Accounting).
- New buildium-opening-balances function: fetches an association's Buildium GL
trial balance as of a cutoff (default 2025-12-31, Accrual), maps GL accounts
to accounting accounts (flagging bank accounts), rolls prior-year P&L into
Retained Earnings, writes accounting.opening_balances, and posts the opening
GL entry. Idempotent; service-role gated.
Applied to 6 Buildium associations (opening balances + 2026 activity); all
balance. New columns/data applied to the project directly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes the "Authorized Signer" text from the payee/address block, moves the
"AUTHORIZED SIGNATURE" label to just below the signature line, and raises the
signature image cap (0.42 -> 0.65 in) so the signature renders full size in
the now-clear space above the line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bill-payment check generator (checkPdf.ts) now supports per-field
position offsets (X/Y inches) and show/hide for every element — check
number, return address, bank block, date, pay-to, amounts, payee address,
memo, signature, and MICR — layered on the existing layout (defaults render
identically). Edited in Settings → Check Setup ("Element positions").
Stored in accounting.check_settings.field_positions (jsonb). Also replaced a
"→" with ">" in the MICR placeholder to avoid the UTF-16 spacing artifact.
Migration applied: check_settings.field_positions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- P&L and Balance Sheet account rows are now clickable and open the
General Ledger filtered to that account (its transaction list for the COA).
Adds StructuredRow.accountId, threaded from the report builders, with a
clickable row in StructuredTable and an initialAccountId prop on
GeneralLedgerReport.
- Bids/Quotes: PDF upload on the New Bid/Quote dialog (bid-attachments
bucket + document_url/document_name columns), shown as a link on the
bid Details dialog.
Migration applied: bids_quotes_pdf_attachments.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bill/vendor payments:
- Bill "Pay" and bank-register vendor debits now clear Accounts Payable
(Dr A/P / Cr Bank) instead of re-debiting the expense, which had
double-counted expenses in the P&L and never cleared A/P. The expense
account is kept as a display-only category on the payment.
Checks:
- Bill-payment checks now print the return address (payer name/address)
from the company check layout (was hardcoded blank).
- Per-segment MICR gap control (check# / routing / account) in both check
generators, wired to Check Setup (bill payments) and the Check Layout
editor (Print Checks). New columns: check_settings.micr_gap_1/2 and
check_layouts/company_check_layouts.micr_gap_1/2.
Accounting Dashboard / Financial Overview:
- Date-range selector (presets + custom) drives the charts, top expenses,
and recent transactions.
- PDF title renamed to "Financial Overview" and shows the period.
- Fixed amounts rendering as "$ 5 0 0. 0 0": the U+2212 minus sign forced
jsPDF into a UTF-16 fallback; replaced with an ASCII hyphen (also in the
report PDF out-of-balance line).
DB migrations applied to the project: repost_gl_on_line_item_change,
budget_actuals_accrual_owner_income, add_micr_gap_controls.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace Lovable-bound email transport and auth webhook so the platform
sends all automated email through its own infrastructure.
- process-email-queue: drop sendLovableEmail/LOVABLE_API_KEY; send via the
Hostinger Email API (primary) with automatic SMTP fallback. Shared
transports added in _shared/hostinger-mail.ts and _shared/smtp-send.ts.
- auth-email-hook: verify Supabase's native Send Email hook signature
(Standard Webhooks via SEND_EMAIL_HOOK_SECRET) instead of Lovable's libs;
build the GoTrue verify URL; keep enqueue → process-email-queue.
- Recreate the 6 auth email templates under _shared/email-templates/ that
previously only existed in the deployed function.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes a 404 when refreshing deep routes on Hostinger: serves index.html for
any request that isn't a real file/directory so the client-side router handles
the path. vercel.json doesn't apply on Apache hosts. Lives in public/ so Vite
bundles it into dist/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a dashboard PDF generator that renders the metrics, an Income & Expenses
bar chart, a Top Expenses donut, the invoices overview, and recent transactions
as native PDF vector graphics + selectable text (not a screenshot), using the
shared branded header/footer. Wire an "Export PDF" button into the dashboard
header (association logo, ACM fallback).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove the Zoho Books integration (edge functions, sync libs, settings,
reports/overview, banking links, fees tab, import dialog); preserve fee
rules as a standalone FeesTab and the COA accounting_system classification.
- Financial Overview/Reports (staff + board) render the Accounting dashboard
and reports; board reports mirror the rich Accounting Reports.
- New Reserve Fund Schedule report + an is_reserve flag on accounts.
- Unify all report exports to a branded format (logo + centered header +
footer): shared ReportSheet (on-screen) and reportHeader (PDF). Budget vs
Actuals and Bank Reconciliation PDFs now match the reference layout.
- Render financial reports inline (no preview pop-up).
- Budget Management mirrors Accounting Budgeting (staff-accessible) with SPA
navigation; editable bills in the Accounting Bills page.
- Negative opening balances flow through to the GL and reports (allow negative
input; keep non-zero on save; signed CSV import).
- Upload a per-account trial balance via CSV on Opening Balances.
- Board members: read-only RLS access to their association's accounting ledger;
editable board-members panel on the association page; share vendor contacts
with the board (toggle + directory section).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Movement of Equity derived net income from sub-ledgers (calcNetIncome over
invoices/expenses/bills), while the P&L and Balance Sheet use the GL. With any
direct bank-categorized income/expense the two disagreed — Ashley Manor's SCE
was off from the Balance Sheet equity by 9,257.44. Rebuild Movement of Equity
from the GL (current-year earnings + GL equity balances) so all three statements
tie by construction.
Complete the §9 reconciliation matrix: R3 (P&L net income == raw-GL period net
income — guards the P&L builder) and R5 (Movement of Equity ending == Balance
Sheet total equity) are now computed by cross-checking the report builders against
the raw GL; checks render in numeric order. R6 (GL == TB/BS, satisfied by
construction) and R9 (direct vs indirect CFO, only indirect built) shown as N/A
for matrix completeness. Adds 5 reconcile unit tests (12 total).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bridgewater's GL was imported as single-sided postings missing its opening fund
balance, leaving the trial balance off by 130,348.76 with an abnormal debit
equity balance. Record the gap as an Opening Fund Balance equity credit (migration
20260602150000); R1 and the Balance Sheet now tie out exactly.
A/R-A/P sub-ledger checks (R7/R8) only apply to platform-managed companies whose
invoices/bills post to the GL. Imported-GL companies (Bent Oak, Bridgewater) keep
their own AR/AP, so scope R7/R8 to gl_managed companies (new arApApplicable flag
on reconcile + gl_auto_post surfaced in useReportData). Every company now passes
the Reconciliation report.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
renderReportPdf now matches the on-screen comparison: when a comparison period
is active it renders Amount | Comparative | Change | Change % columns (was
Previous | Amount), switches to landscape for room, and underlines each numeric
column on totals. Applies to P&L, Cash Flow, Movement of Equity, and Balance
Sheet exports.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- StructuredTable now renders Comparative + Change + Change % columns when a
comparison period is on (P&L, Cash Flow, Movement of Equity, Balance Sheet).
- Balance Sheet supports an as-of comparison period (prior as-of balances per
account + totals); comparison toggle enabled for it (buildBalanceSheet takes
prior dataset). Current Year Earnings stays independently computed.
- Budget vs Actuals: optional "Compare to" date range adds Compare + Δ-vs-Compare
columns; actuals computation factored into a shared helper for both windows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Cash Flow Statement now supports a comparison period (prior-period/prior-year/
custom): refactored into computeCashFlow() and renders the comparison column
for Net Income, CFO/CFI/CFF, beginning/ending cash. Previously it ignored the
compare toggle. (P&L and Movement of Equity already had comparison.)
- Budget vs Actuals: budget is now pro-rated to the selected actuals window
(sum each budget period weighted by overlap with the range) instead of always
using the full annual figure — honors monthly/quarterly/annual period_type.
Noted in the on-screen card and the PDF header (B4).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- post_transaction_gl counter priority fixed: customer->A/R, then coa->that
account (direct categorized expense/income), then uncategorized vendor->A/P.
Previously any vendor payment went to A/P, driving A/P negative and hiding the
real expense account. The 38 direct vendor payments now post to their expense
accounts; A/P went from -5,895.79 to +3,099.29 (real open payables).
- sync_public_bill now maps the public bill's expense_account_id to the matching
accounting expense account (by name) on the bill line item, so synced bills
stop defaulting to "Administrative". Expenses now spread across Attorneys Fees,
Electric, Lawn Service, Management Fees, Water, etc.
- Verified managed companies: R1=0, R7=0. R8 surfaces a real $1,029.17 residual
(bills marked paid whose payments were entered as direct expenses, not A/P
settlements) — surfaced in the Reconciliation report, not plugged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: accounting.transactions (deposits, transfers, bank payments) never
posted to the GL, and the GL's cash came only from synthesized legs that
hard-coded Undeposited Funds. So the balance sheet ignored transfers (#1) and
showed directly-deposited funds as Undeposited (#2), with banks going negative.
- Post the bank register to the GL: customer receipt -> Dr bank/Cr A/R, vendor
payment -> Dr A/P/Cr bank, categorized -> Dr/Cr bank+COA, transfer -> Dr dest/
Cr source, deposit -> Dr bank/Cr Undeposited; payments_received -> Undeposited.
Retire the synthesized invoice/bill cash legs (acmacc_invpay/billpay) so cash is
sourced once, from the register. Triggers on transactions/deposits keep it live.
- Fix gl_managed: make it an explicit accounting.companies.gl_auto_post flag
instead of inferring from null-source journal entries (a single manual journal
entry had silently disabled all automation for Ashley Manor).
Verified (Ashley Manor): transfer reflected (Cogent +47,127 vs prior -3,521),
deposits land in BOA (Undeposited cleared), R1=0, R7=0, no negative banks.
Imported companies (Bridgewater/Bent Oak) untouched; their residuals stay
surfaced in the Reconciliation report.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add From/To date inputs to the Budget vs Actuals report (Accounting section
Reports) so actuals can be compared to the budget over any custom range,
independent of the page period preset. Defaults to the report period; drives
the actuals queries and CSV/PDF export labels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the Accounting section Reports (AccountingReportsPage):
- Rebuild the Cash Flow Statement as an indirect-method report derived from the
GL: Net Income + working-capital/non-cash movements, classified into
CFO/CFI/CFF, with Beginning/Ending Cash. Ties to the change in Balance Sheet
cash by construction (R4); surfaces an explicit residual row if it ever doesn't.
- Add R4 to the Reconciliation Checks report.
- Extract the reconciliation matrix into a pure, tested library
(lib/reconcile.ts) and route the Reconciliation report through it.
- Add §10 synthetic-ledger vitest suite (lib/reconcile.test.ts): empty, single
balanced entry, full period, partial settlement (open vs gross §1.5), and
fault ledgers (single-sided → R1; payment-not-applied → R7). Verified R4 ties
for Ashley Manor ($35,727.08 = ΔCash).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per the Financial Reports Master Spec:
- §1.5 A/R fix: invoice settlements now post to the GL (Dr Undeposited / Cr A/R
from invoice.paid_amount); payments are the cash sub-ledger only and no longer
separately credit A/R (avoids double-count). A/R control = open balance, so
recon R7 passes for managed companies (Ashley Manor 39,248 -> 0). Bills already
settled (R8 ok). Migration applied + backfilled managed companies.
- §9/§10: add a "Reconciliation Checks" report that surfaces R1/R2/R7/R8
residuals (never plugged) so imbalances are visible — e.g. Bridgewater's
imported GL is unbalanced (R1) and its sub-ledgers don't tie (R7/R8).
Imported companies (Bridgewater/Bent Oak) left untouched per decision; their
residuals now surface in the Reconciliation report.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>