Commit Graph

60 Commits

Author SHA1 Message Date
admin 393268dd04 Vendor profile email: add where-to-send-invoices block
Below the Submit vendor profile button, instruct vendors to email bills to
ap@avriacam.com or mail them to the association's mailing address, c/o Avria
Community Management, LLC. The address is resolved from the vendor's
association (association_id, falling back to association_ids[0]).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:20:12 -04:00
admin 7f5d21c398 Accounting: guard import-mode register rows against GL double-counting
post_transaction_gl now skips posting a register row whose exact bank
movement (company + bank account + date + amount + direction) is already
in buildium_gl, so categorizing an ad-hoc-materialized register row can no
longer duplicate the Buildium GL pull. Also freezes (exclude_from_gl) any
remaining unguarded buildium-overlapping register rows across all
import-mode companies. Genuinely-missing manual items (no GL match) still
post normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:03:40 -04:00
admin 2c50e96fbd Email: default automated sender to notifications@avriamail.com
The retired no-reply@avriamail.com had no email_senders row, so the
queue dispatcher 500'd on every run. Point DEFAULT_AUTOMATED_FROM at
notifications@avriamail.com (the sole remaining sender). The
AUTOMATED_EMAIL_FROM secret was set to match and the function deployed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:03:18 -04:00
admin 9aa1f94eb4 Accounting: categorize register transactions from Buildium GL lines
partymap mode now also resolves each Buildium transaction's dominant
income/expense account (from its Journal lines) to a local category, staged
alongside the party. Used to backfill accounting.transactions.category on the
materialized bank register. Applied to Village Woods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:16:00 -04:00
admin 0faee9994d Accounting: partymap mode + register payee backfill
Adds "partymap" mode to buildium-payee-backfill: resolves each Buildium GL
transaction to a local vendor (by name) / customer (by unit_number) and stages
it in accounting.buildium_party_map. The materialized bank register
(accounting.transactions) is then backfilled in SQL by bridging each register
row to its journal-entry bank line (bijective row-number pairing within
date/amount/side groups, so same-amount/same-day payments each get a distinct
owner) and pulling the resolved vendor/customer + name from staging.

Applied to all five Buildium-imported associations (Village Woods, Bent Oak,
Village Grove, Bridgewater, Casuarina).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 19:44:42 -04:00
admin 12e551f578 Accounting: add account/txn detail modes to buildium-payee-backfill
account mode lists a single account's ledger entries; txn mode reconstructs
one transaction's full double-entry across accounts. Used to trace the VW
5098 discrepancy to a missing 2025-12-31 reserve-funded reclass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:27:00 -04:00
admin e999890ee5 Accounting: add report mode to buildium-payee-backfill
report mode pulls /v1/generalledger signed balances per account for the
window so imported books can be diffed against Buildium's authoritative GL
to verify completeness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:18:34 -04:00
admin 3a7e08fb78 Accounting: buildium-payee-backfill edge function
Re-pulls Buildium GL transactions and backfills payor/payee names onto
imported journal_entries.description (matched by external_id). Vendor for
Bill/Check/EFT via PaymentDetail.Payee; owner for Charge/Payment/ApplyDeposit
resolved from UnitId via /v1/associations/owners. Idempotent; modes
sample/dry/apply. Used to backfill Village Woods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:12:00 -04:00
admin 10cd24e738 Bank feeds: replace Plaid with Stripe Financial Connections
Swap the bank-linking + transaction-download integration from Plaid to
Stripe Financial Connections (you're already on Stripe for payments).

- accounting.stripe_bank_connections table (mirrors plaid_connections)
- stripe-financial-connections edge function: create_session / save_account
  / sync / disconnect, using per-association keys from stripe_account_mappings
  (handles connected accounts via Stripe-Account + stripeAccount on the client)
- lib/stripeBank.ts: client wrappers + the Stripe.js collectFinancialConnections
  Accounts flow
- AccountingBankingPage: Connect/Sync/Disconnect now drive Stripe FC; removed
  react-plaid-link usage and the PlaidLinkButton
- Integrations page wording updated
- Imported transactions land uncategorised in the bank feed (credit/debit by
  amount sign) for matching, same as before

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:27:05 -04:00
admin 266a99d4b2 Accounting: recurring bills & journal entries
Add accounting.recurring_templates (bill|journal) with a schedule
(frequency/interval/day-of-month/start/end). New generate_due_recurring()
materialises real bills (-> A/P) and journal entries on cadence — catching
up any missed periods — posting through existing triggers. Runs nightly via
pg_cron ('accounting-recurring-daily') and on demand from a new Recurring
page ('Generate due now'). Page lists templates with pause/resume/edit/
delete; create dialog handles both kinds (vendor + line items for bills,
signed balanced lines for journals). Wired into routes + accounting nav.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 23:28:43 -04:00
admin 3ab016fc57 Accounting: post locally-entered bank transactions to the GL (import mode)
Import-mode companies pull their GL from Buildium, so transactions entered
directly in Banking/Reconciliation never reached the GL — they reconciled but
were missing from GL-based reports (P&L etc.). This is why Bent Oak's May P&L
was missing Bank Interest and understated expenses.

- post_transaction_gl no longer gates on gl_managed; it posts any transaction
  that isn't voided, Buildium-sourced (journal_entry_line_id set), frozen
  (exclude_from_gl), a transfer/deposit leg, or missing an account/counter
- New accounting.transactions.exclude_from_gl flag freezes pre-existing manual /
  duplicate register rows on already-tied books so they can't double-post
- One-time data ops: linked all Buildium-sourced register rows to their GL bank
  line; froze remaining pre-existing manual rows for Bridgewater/Casuarina/
  Village Grove (ambiguous, would risk double-count); posted Bent Oak's 4
  verified-missing operating items. Bent Oak GL stays balanced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:15:59 -04:00
admin 6bf9da5482 Accounting: prior-period reconcile items, void txns, unified report filters, accrual-only
1. Reconciliation now shows ALL outstanding (unreconciled) items on/before the
   statement date, including ones from prior periods — removed the prior-recon
   date floor. Finalized items still drop off (they carry a reconciliation_id).

2. Void transactions in Banking and Reconciliation. New accounting.transactions
   .voided flag (+ voided_at/by); voided rows stay visible (strikethrough + VOID
   badge) but are excluded from the running balance, register totals, cached
   account balance, and reconciliation. post_transaction_gl reverses the GL for
   gl_managed companies; un-void supported from Banking.

3. Unified report filters: the single Period bar on the Reports page now drives
   every report. General Ledger, Trial Balance, AR Aging (Property), Pre-Paid
   Homeowners, Cash Disbursement, and Reserve Fund no longer have their own date
   pickers — they consume the shared from/to (range) or to (as-of).

4. Accrual only: removed the cash-basis toggle from Trial Balance and General
   Ledger (the data was always accrual GL anyway; the cash label was misleading).
   All income/expense reports recognize on billed/issue date.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:51:03 -04:00
admin 5aef967b74 Buildium GL sync: keep import-mode bank registers current
Root cause of 'syncs stop at 5/31': the nightly buildium-gl-sync writes journal
entries (so reports stay current) but never created the matching bank-register
transactions for import-mode companies (gl_auto_post=false). Those registers were
materialized once at import, so the reconciliation/register views froze ~5/31
while the GL kept advancing.

- Add accounting.transactions.journal_entry_line_id (FK + unique index) to link
  register rows to their source GL line, making materialization idempotent
- buildium-gl-sync now materializes a register transaction for each bank line it
  inserts, for import-mode companies only (bank debit -> deposit/credit,
  bank credit -> withdrawal/debit; category/coa from the single offset account),
  upserting on journal_entry_line_id so re-runs never double-insert
- One-time backfill (run against prod) filled the existing gap: 231 register
  rows across Bridgewater/Casuarina/Village Grove/Bent Oak, skipping 3 near-miss
  rows that share a day with an existing register row (incl. a Casuarina transfer)
  for manual review

gl_managed companies are unaffected — their register drives the GL via
post_transaction_gl, not the other way around.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:10:25 -04:00
admin df8623ff9f buildium-gl-sync: include inactive bank-account GL ids from /v1/bankaccounts
/v1/glaccounts omits some inactive accounts (e.g. prior-management bank
accounts) whose ledgers still hold one side of historical transactions,
making checks/deposits come back unbalanced on backfills (found during the
Casuarina Club GL import).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 19:52:21 -04:00
admin 28c3c7bd0a Bills import: degrade gracefully when Buildium API key lacks Vendors permission
GET /v1/vendors 403s on keys without the Vendors scope; import bills without
vendor links instead of failing the pull, and surface it in results.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:58:13 -04:00
admin cc5f70bc5b Bills import: A/P cutover guard — only post JEs dated after the GL watermark
Anything on/before buildium_gl.last_synced_date is already in the books as
buildium_gl entries; ap_cutover_date freezes that boundary so direct
buildium_bill/billpay postings never double-count history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:46:24 -04:00
admin 25064d8418 Direct Buildium A/P import: bills, payments, one-off checks via GL Account Map
- public.bills.line_items + line-aware accounting mirror (one bill_item per
  Buildium line with its mapped platform account)
- buildium-sync bills: strict per-line resolution through
  buildium_gl_account_links (unmapped -> bill held + flagged); pulls
  /bills/{id}/payments (check#, bank, date) and /bankaccounts/{id}/checks
  (one-off checks become paid bill+payment pairs)
- import-mode companies get direct JEs (buildium_bill Dr expense/Cr AP,
  buildium_billpay Dr AP/Cr mapped bank) + cleared register rows; sets
  buildium_gl.exclude_ap so the nightly GL pull skips Bill/Bill Payment/Check
- buildium-gl-sync: exclude_ap transaction-type filter; preserve buildium_gl
  config keys when advancing the watermark
- Settings: Pull Bills & Expenses card with held/unmapped reporting

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:43:58 -04:00
admin c1fad194f7 ARC imports: record Buildium decision votes + richer decision notes; PDF spacing
- Buildium API exposes no ARC comment threads or member votes (verified live);
  surface the final decision instead: vote row dated to the actual decision,
  decision_notes 'Approved by X on date (via Buildium)'
- record_buildium_arc_vote RPC bypasses the finalized-ARC write lock that was
  silently swallowing import votes; 69 existing imports backfilled
- Application Record PDF: paragraph break between comments and decision notes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:26:48 -04:00
admin ff65c8a656 Buildium GL account map: strict account links, separate pull/push charges
- buildium_gl_account_links + buildium_unmapped_gl_accounts tables (strict, hold-and-flag)
- buildium-sync: pull charges syncType, account-first push resolution, no fuzzy matching, push dryRun
- buildium-gl-sync: links-only resolution, watermark held while unmapped accounts exist
- GL Account Map settings tab + Pull Charges card + unmapped results panel

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:07:18 -04:00
admin abd46bcb2b Hostinger Reach integration UI + ARC Buildium matching, drop Mailchimp
- HostingerReachPage (replaces MailchimpPage): connect Reach via
  reach-connection, per-association segment sync via reach-sync
- ARC Applications: Buildium import review/matching updates
- buildium-import-stage/apply: latest staging + apply changes (already
  deployed to Supabase)
- migrations: hostinger_reach_integration + arc_finalized_lock service
  role (already applied to live DB)
- CI: note that deployment is VPS-side polling (auto-deploy.sh cron)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 23:07:30 -04:00
admin f5f6285bbd Accounting: fix empty JE/GL pages + nightly Buildium GL pull sync
- Add missing indexes on journal_entry_lines (journal_entry_id, account_id)
  and journal_entries (company_id, date): Bridgewater's ledger query took
  7.5s, blew the 8s statement timeout, and rendered the JE/GL pages empty
- Paginate JE/GL page fetches past the 1000-row PostgREST cap (shared
  fetchJournalEntries helper) and surface query errors instead of
  swallowing them into an empty list
- New buildium-gl-sync edge function (scheduled nightly via pg_cron):
  incrementally pulls new Buildium GL activity into accounting journal
  entries via GET /v1/generalledger, reconstructing double-entry JEs by
  grouping per-account entries by transaction id; watermark + 14-day
  overlap window, dedupe on (company_id, external_source, external_id),
  account mapping by external_id/code/name with auto-create for new
  Buildium accounts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 22:12:38 -04:00
admin 4f0ac97e83 Income Statement: Buildium-style category subgroups
Group the multi-period Income Statement by account category (Operating Income,
Administration, Utilities, Reserves Budget, …) with "Total for <category>"
subtotals, matching the Buildium layout, in the on-screen table, PDF, and CSV.

- New accounting.accounts.category column (nullable; null = ungrouped), seeded
  from the local chart_of_accounts parent hierarchy.
- Editable in Chart of Accounts: single-edit (with datalist autocomplete) and
  bulk-edit (blank = no change, __clear__ to unset).
- buildium-account-categories edge function pulls each account's parent-GL
  category from Buildium (matched by code, fallback name) and backfills
  accounting.accounts.category; idempotent and re-runnable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 22:34:58 -04:00
admin 6fe1e3943c Budget Workbook: editable unit count for per-unit assessment rate
The per-unit assessment (annual expenses / 12 / units) used the live association
unit count only. Add an override so the rate can use weighted/excluded units,
persisted on accounting.budget_workbooks.unit_override (null = live count). New
"# Units" control with a reset link; summary card and CSV use the effective count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:43:45 -04:00
admin 215ecb3153 Budget Workbook: replace Budget Management with a YTD-actuals workbook
/dashboard/budget-management now renders a Budget Workbook that pulls YTD
actuals (from the accounting GL, through a chosen month), derives a monthly
average (YTD/N), takes a per-line inflation %, and projects an annual budget
(avg x 12 x (1+infl)). Footer rolls up annual budget / 12 / # units = per-unit
monthly assessment. Income + expense sections; all imported fields editable.

- Standalone saved worksheet (accounting.budget_workbooks/_lines, RLS like accounts)
- "Push to Budget" writes projected/12 into accounting.budgets + budget_entries
- Uses accounting.accounts (synced with the Accounting dashboard COA) and paginates
  the GL fetch (1000-row cap). Nav relabeled Budget Management -> Budget Workbook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:29:25 -04:00
admin f3b81eaeeb Revert vendor->A/P posting rule (it collapsed the P&L)
The blanket rule stripped expense from direct-expense checks that have no bill,
leaving the P&L showing only a couple of accounts. Restore original
post_transaction_gl precedence (coded account before vendor). Re-posted affected
transactions to restore expense recognition. Double-count needs a bill-linkage fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 17:38:15 -04:00
admin fdb9174c45 Accounting GL: vendor checks relieve A/P; Village Grove payments direct-to-bank
- post_transaction_gl: a vendor money-out (check) now posts Dr A/P / Cr Bank
  instead of re-debiting the coded expense account. Fixes double-counted expense
  and checks showing as P&L debits (bill already recognized the expense).
- post_payment_gl: Village Grove owner payments post straight to HOLII COGENT
  Checking instead of Undeposited Funds (scoped; others unchanged).

Both applied to the live DB; Ashley Manor's affected transactions re-posted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 14:45:04 -04:00
admin 69f643a51e RV/Boat Lots: sync lots to public rental_calendar amenities
Add a "Sync to Public Page" button that creates/updates one rental_calendar
amenity per lot (name, size · rate · availability in the description, rate in
booking_config, shown on the public page). Idempotent via amenities.source_rv_lot_id;
removes synced amenities for deleted lots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:06:21 -04:00
admin f549f21c21 RV/Boat Lots: waitlist size + more vehicle types
- Waitlist now captures a free-form Size (requested_size column) in place of
  the type field in the internal form/table.
- Lot type selector (add + bulk edit) expanded to RV, Boat, Travel Trailer,
  Fifth Wheel, Camper, Car, Truck, Trailer, Other.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:01:18 -04:00
admin 0c0300efce RV/Boat Lots: internal lot map with directory-unit links
Add a Map tab that reuses the reservation-map pin picker. Staff drop/label
pins per lot and optionally link a directory unit (pin.linked_amenity_id =
unit id). Config persists per association in rv_boat_lot_maps.

GoogleMapPicker generalized with optional linkLabel + allowLinkAnyStatus
(defaults preserve the amenity reservation-map behavior).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:40:52 -04:00
admin 308af20aa1 RV/Boat Lots: request renter insurance (vendor-style flow)
Phase 4. Mirror the vendor insurance request flow for RV/boat renters:
- Migration: insurance fields on rv_boat_lot_rentals + rv_renter_insurance_requests
  table + token-scoped lookup/submit SECURITY DEFINER RPCs (granted to anon).
- Edge fn send-rv-renter-insurance-request emails the renter a secure link
  (reuses the vendor-insurance-request email template).
- Public page /rv-insurance/:token to submit carrier/policy/expiration + COI upload.
- "Request Insurance" button on each active rental + insurance status display.

DB RPCs verified end-to-end (rolled-back txn): submit matches token, updates the
rental, marks the request submitted. Edge function deployed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:16:31 -04:00
admin 6f68619b9c Bill approvals: backfill imported bills' approvers + auto-create on import
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>
2026-06-07 13:52:19 -04:00
admin fd7107290a Bill approvals: admins-only mark-paid + DB guard
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>
2026-06-07 12:28:03 -04:00
admin ea8cf6efb6 Merge remote-tracking branch 'origin/main' into accounting-sales-receipts-coa-sync-expenses
# Conflicts:
#	src/integrations/supabase/types.ts
#	src/pages/BillDetailPage.tsx
2026-06-06 23:24:45 -04:00
admin af67e2fb9a Accounting: rename bill_approvals.vendor_name→approver_name + COA import dialog
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>
2026-06-06 23:04:29 -04:00
admin 096cac6b0b Merge pull request #6 from renee-png/accounting-sales-receipts-coa-sync-expenses
Accounting: sales receipts, COA dashboard sync, accrual A/P, manual deposits, per-association COA, bill-approval sync fix
2026-06-05 20:52:43 -04:00
admin cc34ae9418 Accounting: two-way bill creation + mirror pending bills
- 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>
2026-06-04 20:54:09 -04:00
admin 756ebce121 buildium-sync: robustly resolve bill line GL account into expense_account_id
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>
2026-06-04 20:17:07 -04:00
admin 84c8483169 Accounting: unify vendor roster + COA across bill-approvals and accounting bills
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>
2026-06-04 18:57:32 -04:00
admin 84541a6813 Accounting: back-sync bill paid status to bill_approvals on INSERT
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>
2026-06-04 18:17:39 -04:00
admin 6376d5cc7e send-bill-approval-invites: forward Authorization on inner invoke
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>
2026-06-04 17:37:11 -04:00
admin 2c723410a4 Bill approvals: surface approvers, fix email path, schema cleanup
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>
2026-06-04 17:17:05 -04:00
admin 6634907799 Per-association chart of accounts
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>
2026-06-04 13:38:50 -04:00
admin 8f1cbcd3af Accounting: selectable-source / multi-line manual deposits
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>
2026-06-04 13:00:41 -04:00
admin 7464d55b6c Accounting: enforce accrual A/P on bill payments (match rule + guard + cleanup)
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>
2026-06-04 12:30:44 -04:00
admin d82466f826 Accounting: Sales Receipts, COA sync to dashboard, vendor-expense recognition
- 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>
2026-06-04 10:01:18 -04:00
admin bd5caf5415 Merge pull request #3 from renee-png/email-cutover-and-accrual
Migrate email pipeline off Lovable + branded auth emails
2026-06-03 02:40:35 -04:00
admin f7dc5d8177 Buildium -> Accounting: auto-provision companies + opening-balance migration
- 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>
2026-06-03 02:31:21 -04:00
admin b1486a0b2a Migrate email pipeline off Lovable + branded auth emails
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>
2026-06-02 23:07:26 -04:00
admin e302fb91f0 Accounting platform: remove Zoho, unify reports, board access, vendor sharing
- 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>
2026-06-02 18:29:31 -04:00
admin 96de47496a Reconcile imported-GL companies: Bridgewater opening equity + scope R7/R8
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>
2026-06-02 02:14:25 -04:00