Budget System

Budget management with envelope-based tracking, automatic transaction mapping from bank imports, vendor bill integration via Billingo, and multi-currency support.

Envelope Types

Four envelope types defined in BudgetEnvelopeType:

TypePurposeDisplay
BUDGETPeriodic spending limitsProgress bar (spent vs target)
SAVINGS_GOALOne-time savings targetsProgress bar (saved vs target)
SAVINGS_TARGETRecurring savings targetsProgress bar per period
TRACKINGBalance monitoring, no targets30-day sparkline + balance

Cross-Account Envelopes

  • account_id is nullable — NULL means cross-account
  • Cross-account envelopes accept transactions from any bank account
  • Displayed with globe icon, converted balances using cached exchange rates

Transaction Types & Sources

BudgetTransactionType = 'INCOME' | 'EXPENSE' | 'TRANSFER'
BudgetTransactionSource = 'MANUAL' | 'INVOICE' | 'TIMESHEET' | 'BANK' | 'VENDOR_BILL'
  • ext JSONB column stores metadata from external sources (bank imports)
  • budget_transaction_amount_check constraint: amount > 0

Mapping Rules Engine

Automatic transaction categorization from bank imports. Rules are applied in priority order against transaction metadata.

FieldTypePurpose
account_idsUUID[]Which bank accounts this rule applies to (array, GIN-indexed)
priorityintLower = higher priority, deterministic application order
conditionsJSONBMatch criteria against transaction ext fields
actionsJSONBEnvelope assignment, categorization, field transforms

Mutation Hooks (useBudgetMutations.ts)

18 mutation hooks covering the full budget domain:

HookPurpose
useCreateBudgetMappingRuleCreate new mapping rule
useUpdateBudgetMappingRuleEdit existing rule
useDeleteBudgetMappingRuleRemove rule
useReorderBudgetMappingRulesBatch priority update
useApplyMappingRuleRetroactivelyApply a rule to all existing transactions
useBulkImportTransactionsCSV bank import with upsert (dedup by source_id)
useReconcileBankTransactionsMatch bank imports to existing manual transactions
useCreateBudgetTransferLinked TRANSFER pair (outgoing + incoming)
useConfirmPlannedTransactionMark planned transaction as confirmed

Retroactive Rule Application

useApplyMappingRuleRetroactively fetches all transactions for the rule’s accounts, evaluates conditions against each transaction’s ext and metadata fields using evaluateConditionGroup() from lib/mapping-engine.ts, and applies matched actions (envelope assignment, field updates).

Query Hooks

  • useBudgetMappingRules(accountId?) — Fetch rules, ordered by priority
  • useAvailableExtKeys(accountId) — Extract distinct keys from ext JSONB (limited to 100 txns)

UI

  • Conditions/actions rule builder
  • Preview sidebar (MappingPreviewSidebar.tsx) with focused field highlighting
  • Full bilingual support (42 HU translation keys)

Vendor Bills (Billingo Integration)

Synced via billingo-sync-inbound edge function:

Data Flow

Billingo API → edge function → vendor_bill table → budget_transaction (for paid bills)

Field Mappings (learned the hard way)

Our fieldBillingo API field
vendor_namespending.partner?.name
total_grossspending.total_gross
spending_datespending.fulfillment_date
paid_datespending.paid_at
statusDerived from paid_at + due_date

Status Derivation

function deriveStatus(spending): 'PAID' | 'OVERDUE' | 'OUTSTANDING' {
  if (spending.paid_at) return 'PAID'
  if (spending.due_date && new Date(spending.due_date) < now) return 'OVERDUE'
  return 'OUTSTANDING'
}

Schema Notes

  • total_gross is NUMERIC (not INTEGER) - API returns decimals
  • Budget transactions filtered: (bill.total_gross ?? 0) > 0 to skip zero-amount bills
  • Frontend can call without webhook secret (fixed in eb2c444)

UI Components

ComponentFilePurpose
BudgetOverviewBudgetOverview.tsx (646 lines)Envelope cards grid with type-aware rendering
EnvelopeDetailSheetEnvelopeDetailSheet.tsxSide panel with transactions and summary
VendorBillsVendorBills.tsxVendor bills table with sync and status badges
MappingPreviewSidebarMappingPreviewSidebar.tsxRule testing preview with field highlighting
EnvelopeDialogEnvelopeDialog.tsxCreate/edit envelope form
CreateAccountDialogCreateAccountDialog.tsxBank account creation
TransferDialogTransferDialog.tsxInter-account transfer form
SparklineSparkline.tsx30-day balance sparkline chart

Status Badges

  • Color-coded: green=PAID, red=OVERDUE, yellow=OUTSTANDING
  • Record-based lookups for status to CSS class mapping (not switch statements)
  • Translated labels via STATUS_LABEL_KEY Record

Envelope Cards

  • Sorted by type group (BUDGET, SAVINGS, TRACKING), then alphabetical
  • TRACKING envelopes: 30-day sparkline (daily balance snapshots, computed client-side)
  • Cross-account envelopes: globe icon indicator
  • Bank account cards show bank icons (Erste, Revolut pattern matching) with editable IBAN/SWIFT fields

Exchange Rates

Simplified in March 2026

The dedicated budget_exchange_rate table and useExchangeRates hook were removed in commit 853729a. The CurrencyProvider (using a CDN feed) is the single source of truth for EUR/HUF rates.

  • CurrencyProvider at web/src/lib/currency.tsx provides HUF/EUR/USD context
  • Rates fetched from CDN, cached via React Query
  • market-data edge function provides commodity prices (Gold, Brent Oil, EU Gas TTF) from Yahoo Finance — this is for the dashboard display, not for currency conversion