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>
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>