Transaction Types
Overview
Finapp supports three transaction types and one special category:
| Type | TrnType | Description |
|---|---|---|
| Expense | 0 | Money spent |
| Income | 1 | Money received |
| Transfer | 2 | Money moved between wallets |
| Adjustment | 0 or 1 | Balance correction (Expense or Income with categoryId: 'adjustment') |
Expense
A regular expense transaction. Decreases wallet balance.
- Requires: wallet, category, amount
- Shown in expense statistics
- Amount displayed with minus sign
Income
A regular income transaction. Increases wallet balance.
- Requires: wallet, category, amount
- Shown in income statistics
- Amount displayed with plus sign
Transfer
Money moved between two wallets. Does not affect income/expense statistics.
- Requires: expense wallet, income wallet, amounts for each
- Supports different currencies (e.g., USD wallet -> EUR wallet)
- Uses system category
transfer(categoryId: 'transfer') - Available only when 2+ wallets exist
Adjustment
Balance correction for a wallet. Affects wallet balance but is excluded from income/expense statistics.
How it works
Adjustment is not a separate transaction type. It is a regular Expense or Income transaction with the system category adjustment (categoryId === 'adjustment').
- Expense + adjustment - decreases wallet balance (e.g., "lost money", correction down)
- Income + adjustment - increases wallet balance (e.g., "found money", correction up)
Detection
Adjustment is identified by categoryId, not by type:
const isAdjustment = trn.categoryId === 'adjustment'
Behavior
- Excluded from income/expense statistics in
getTotal() - Excluded from recent categories in
recentCategoriesIds - Excluded from last created transaction in
lastCreatedTrnId - Included in wallet balance calculation (as a separate
adjustmentfield in totals) - Not shown in the category tree (system category)
- Available in the category selector modal (Command Palette)
System Categories
Two system categories are always present in the store and cannot be edited or deleted:
| ID | Name | Purpose |
|---|---|---|
transfer | Transfer | Used by Transfer transactions |
adjustment | Adjustment | Used for balance corrections |
These categories are:
- Auto-added in
useCategoriesStore.setCategories() - Excluded from
categoriesRootIds(not shown in category tree) - Protected from editing/deletion in
saveCategory()anddeleteCategory()
Type Change Behavior
When a transaction's type changes, normalization happens client-side in trnToRow (services/powersync/transforms.ts) at write time - there is no server backend doing it:
- → Transfer:
trnToRowsetscategoryIdto'transfer'and writesexpenseWalletId/incomeWalletId/expenseAmount/incomeAmount; non-transfer fields (walletId,amount) are written asnull - → Expense/Income:
trnToRowwriteswalletId/amount; transfer-specific fields (expenseWalletId,incomeWalletId,expenseAmount,incomeAmount) are written asnull - → Adjustment: no extra clearing - adjustment uses regular Expense/Income fields with
categoryId: 'adjustment'
This ensures only the fields relevant to the current type carry non-null values in the local SQLite row and on the server.
Statistics
getTotal() returns:
| Field | Includes |
|---|---|
income | Income transactions (excluding transfers and adjustments) |
expense | Expense transactions (excluding transfers and adjustments) |
adjustment | Adjustment transactions (affects wallet balance) |
incomeTransfers | Transfer income side |
expenseTransfers | Transfer expense side |
sum | income - expense |
sumTransfers | incomeTransfers - expenseTransfers |
Next Steps
- Sync - how transactions sync to the server
- Technical Decisions - rationale behind key design choices