Skip to content

Conversation

@marcolagos
Copy link

Personal Finance Tracking for Cash Accounts

Hello! I've been using Wealthfolio and love the "local-first, no cloud" philosophy. I've extended my fork with some additional personal finance features for tracking cash account transactions (checking, savings, credit cards).

The Problem

As someone tracking my full financial picture, I wanted to:

  • Track expenses and income from bank/credit card accounts in the same app as my investments
  • Categorize transactions (groceries, utilities, travel, etc.) with auto-categorization rules
  • See spending breakdowns and trends over time
  • Set budgets and track spending against targets
  • Import CSV statements from banks without manual entry

Currently, Wealthfolio excels at investment tracking (which is solid) but doesn't have expense/budget tracking for cash accounts.

What I Built

I've implemented these features in my fork:

Core Infrastructure

Categories - Hierarchical expense/income categories with subcategories (seeded with common types like Food & Dining, Transportation, Shopping, etc.)

Activity Rules - Pattern-based auto-categorization (e.g., "AMAZON" → Shopping, "UBER" → Transportation)

Events - Group transactions by trips, holidays, or occasions for better expense tracking

image image image

Cash Activities

  • Dedicated Cashflow page for cash account transactions (separate from investment activities)
  • Bulk CSV import wizard with column mapping and validation
  • Category assignment with rule auto-matching during import
  • Support for Deposits, Withdrawals, and Transfers between accounts
image image

Spending & Income Analytics

Spending Page - Category breakdowns, monthly trends, time period comparisons, top subcategories

Income Integration - Cash income appears alongside investment income on the income page

image image

Reports & Analysis

Monthly Analysis - Day-by-day spending trends, category breakdowns, transaction metrics, notable changes vs previous month

Budget vs Actual - Set monthly spending/income targets, allocate to categories, track progress

image image

Goals Enhancement

  • Manual goal contributions with tracking
  • Goals Allocation tab in reports showing contribution sources
  • Cumulative contributions chart with target line
image

Technical Approach

  • All features are additive - existing investment tracking is unchanged
  • Cash accounts (Savings, Credit, Checking) are handled separately from investment accounts
  • New database tables: categories, activity_rules, event_types, events, budget_config, budget_allocations, goal_contributions
  • Extended activities table with optional category_id, sub_category_id, event_id, name, recurrence
  • Follows existing codebase patterns (Rust/Diesel backend, React/TanStack Query frontend)

Stats: 198 files changed, ~27,600 insertions

Feature Summary

Feature Description
Cash Activities CRUD for checking/savings/credit card transactions
CSV Import Multi-step wizard with column mapping and validation
Categories Hierarchical expense/income categories with colors
Activity Rules Pattern matching for auto-categorization
Events Group transactions by occasion (trips, holidays)
Spending Analysis Category breakdowns, trends, comparisons
Budget Tracking Monthly targets with category allocations
Reports Monthly analysis with spending trends
Goal Contributions Manual tracking with progress charts

Known Issues

I haven't ironed out all the wrinkles from my changes, so there may be some issues:

  • Desktop application may have some rough edges
  • Income page styling could be improved
  • Some edge cases in the import wizard

Conclusion

This is a fat PR so just consider this a shot from left field. Merge or not, all is well.

@marcolagos marcolagos force-pushed the ml-enhance-spending-tracking branch 3 times, most recently from 1225dc6 to 2393340 Compare December 19, 2025 02:22
Add core infrastructure for cash activity tracking:
- Categories: expense/income categorization with hierarchical subcategories
- Category rules: auto-categorization based on transaction name patterns
- Events: group transactions for trips, holidays, special occasions
- Event types: categorize events (travel, holiday, business, etc.)

Database changes:
- Add categories table with seed data for common expense/income types
- Add category_rules table for pattern-based auto-categorization
- Add event_types and events tables
- Extend activities table with name, category_id, sub_category_id, event_id

Frontend:
- Add Settings pages for Categories, Category Rules, and Events
- Add navigation links and routing
Update the existing activity page to focus on investment accounts only:
- Filter activities to exclude CASH account type
- Page now displays only SECURITIES and CRYPTOCURRENCY accounts
- Prepares for separate cash activities page
Add dedicated page for cash account transactions (Savings, Checking, Credit):
- Cash activities page with datagrid and filtering
- Cash activity form with category and event selection
- Cash transfer form between accounts
- Category selection component with search
- Category rule matching for auto-categorization
- Hooks for data fetching and mutations

Backend:
- Add activity model fields for category, subcategory, event, name
- Update activity repository and service for new fields
- Add API endpoints for cash activities
Add multi-step CSV import wizard for cash transactions:
- Step 1: Account selection and file upload
- Step 2: Column mapping with header detection
- Step 3: Transaction editing and validation
- Step 4: Preview before import
- Step 5: Import results summary

Features:
- CSV validation and error highlighting
- Category rule auto-matching during import
- Transaction preview table with edit capabilities
- Help tooltips for mapping guidance
Add spending analysis page for expense visualization:
- Spending overview with category breakdowns
- Spending history chart with time period filters
- Category-based spending summaries

Backend:
- Add spending module with service and models
- Spending data aggregation by category and time period
Integrate cash activities with income categories into the income summary:
- Add CashIncomeData and CapitalGainsData types to income module
- Add SourceTypeBreakdown for income source visualization
- Update IncomeSummary with investment_income, cash_income, capital_gains
- Add methods for cash income and capital gains aggregation
- Update income service to process cash activities with income categories
- Update income page to display cash income alongside investment income
- Add manual filter refresh to prevent rows from disappearing when
  categorizing while filtering (rows stay visible until Refresh clicked)
- Add sticky table header for visibility while scrolling
- Replace individual create buttons with dropdown menu
- Add manage dialogs for categories, rules, and events without
  navigating away from the import page
- Fix spending page query to include uncategorized withdrawals
Convert arrow function components to function declarations for
consistency and better stack traces.
- Fix cash income not showing in Income Sources breakdown by using
  [$CASH] prefix that frontend expects
- Display category/subcategory for cash income in Top 10 instead of
  account name (e.g., "Employment > Salary" instead of "360 Savings")
- Remove hardcoded "Dividend" label from investment income items
- Add missing TypeScript types (investmentIncome, cashIncome,
  capitalGains, bySourceType) to match backend IncomeSummary
- Use backend-provided bySourceType for Income Sources card instead
  of manual frontend calculation
- Combine investment income and capital gains into single
  "Investment Income" category in Income Sources breakdown
Add Tauri command modules that were missing from the desktop app,
causing features to fail with "undefined" errors when invoked.

New command modules:
- category.rs: CRUD operations for expense/income categories
- category_rule.rs: Category rule management with pattern matching
- event.rs: Event CRUD and transaction date validation
- event_type.rs: Event type management

Changes:
- Create input structs that match frontend payload format
- Fill in timestamps server-side instead of expecting from frontend
- Register all new commands in the Tauri invoke handler
- Add services to ServiceContext for dependency injection
- Export CategoryWithChildren from core categories module
DEPOSIT transactions were incorrectly counted in spending when
categorized with an expense category like "Gifts & Donations".
Since deposits represent incoming money, they should never be
counted as spending regardless of category assignment.
- Remove transferAccountId field from database schema, models, and frontend
- Replace separate deposit/withdrawal/transfer forms with unified activity form
- Add radio button activity type selector (Deposit, Withdrawal, Transfer In, Transfer Out)
- Consolidate dropdown menu to "Import from CSV" and "Add Transaction"
- Delete CashTransferForm component (functionality merged into CashActivityForm)
Add confirmation dialogs when users try to navigate away with unsaved
changes in Edit Mode (activities and cash activities datagrids) or
during bulk import processes. This prevents accidental data loss.

Changes:
- Add UnsavedChangesProvider context for global unsaved changes state
- Add useUnsavedChanges hook for component-level integration
- Protect Edit Mode in /activities and /cash/activities pages
- Protect bulk import flows in /import and /cash/activities/import
- Show confirmation when switching from Edit to View mode
- Handle browser back/forward, page refresh, and internal navigation
- Add subcategory spending aggregation to backend (Rust)
- Display top 10 spending subcategories panel on /spending page
- Add client-side amount filter (min/max) to:
  - Investment activities page (/activities)
  - Cash activities page (/cash/activities)
  - Import preview tables for both activity types
- Extract DEFAULT_CHART_COLORS constant in spending page
- Add comprehensive filters to cash import edit step (account, type, category, subcategory, event, status, amount)
- Reorganize filter layouts to two-row structure across all activity pages
- Move search bar to top row with mode toggle/actions
- Move filters to second row for independent expansion
- Increase search bar width (250px/350px) for better usability
- Remove unnecessary code comments to improve readability
- Update sidebar navigation items from "Investment Activity" to "Trades"
  and "Cashflow Activity" to "Cashflow"
- Change URL paths from /activities to /trades and /cash/activities to /cashflow
- Update all page titles and navigation links accordingly
- Clean up unnecessary comments
Add the ability to bulk assign activity types (Deposit, Withdrawal,
Transfer In, Transfer Out) when multi-selecting transactions in the
cashflow import edit step. The selector uses a 2x2 grid UI similar
to manual transaction creation for quick type selection.

Also improves the multi-select action bar layout by condensing button
labels and grouping clear actions into a dropdown menu.
- Fix import link on trades page pointing to /import instead of /trades/import
- Remove Uncategorized category from migration seed (NULL category_id represents uncategorized)
- Clean up unnecessary comments following frontend-rules.mdc
…support

Renames the Category Rules feature to Activity Rules and adds the ability
to assign activity types (deposit, withdrawal, interest, etc.) via rules.
Rules can now set both a category and/or an activity type when matching.

Changes:
- Database migration to rename category_rules table to activity_rules
- Add activity_type column to store assigned activity type
- Rename all Rust modules, models, services from category_rules to activity_rules
- Update API endpoints from /category-rules to /activity-rules
- Update frontend types, commands, and query keys
- Rename settings page from Category Rules to Activity Rules
- Update rule form to allow selecting activity type
- Update bulk import and manual activity forms to use new ActivityRule types
Changes:
- Remove +/- indicator from all category dropdowns
- CategorySelect component now accepts categoryType prop to filter
- Add EXPENSE_CATEGORIES and INCOME_CATEGORIES query keys
- Update cash-activity-datagrid to filter categories per row
- Update cash-import-edit-step to filter categories per row
- Remove isIncome prop from DataTableFacetedFilter
- Remove isIncome prop from SelectCell
- Update rule-form to show color dot instead of +/- indicator
Move client-side filters to server-side for proper pagination behavior.
Previously, filters were applied after pagination, causing transactions
to not appear until more pages were loaded.

Added server-side support for:
- is_categorized_filter: filter by category presence
- has_event_filter: filter by event presence
- amount_min_filter: minimum absolute amount
- amount_max_filter: maximum absolute amount
…tegories/events

Add tooltip indicators showing transaction counts next to categories and events.
Implement deletion safeguards that prevent deleting categories, subcategories,
events, and event types that have associated transactions, with toast notifications
explaining why deletion is blocked.
The duplicate function was missing required fields (assetId, name,
categoryId, subCategoryId, eventId), causing API validation to fail.
Users can now click on categories, subcategories, and events from the spending, income, and settings pages to navigate directly to the cashflow page with appropriate filters pre-applied.

Changes:
- Add cashflow-navigation utility with URL builder and period-to-date conversion
- Add date range filter support (startDate, endDate) in backend activity search
- Sync URL params with filter state in cashflow page
- Fix subcategory filtering to only send subcategory IDs when selected
- Add View Transactions buttons to category items and events pages
Add eye icon toggles to income and spending pages that filter the bar
chart data without changing the displayed percentages. Users can now:

- Toggle income source types and top 10 income sources to filter the
  income history chart
- Toggle spending categories and subcategories to filter the spending
  history chart

Backend changes:
- Add byMonthBySymbol to IncomeSummary for per-symbol monthly breakdown
- Add byMonthBySubcategory to SpendingSummary for per-subcategory
  monthly breakdown

Frontend changes:
- Update chart components to accept hidden items and filter accordingly
- Calculate filtered totals for summary cards while preserving original
  percentages
Remove standalone /performance route that duplicated content in /insights.
Performance is now accessed via tabs within the Insights page. Also fix
unused variable warnings from TypeScript build.
Consolidate page structure by combining Spending/Income into Cashflow tabs
and Trades/Transactions into Activity tabs. Updates Insights page to use
consistent Page/PageHeader/PageContent pattern with URL state management.
Reorders sidebar navigation to place Cashflow before Activity.
…s tab

Update buildCashflowUrl to navigate to /activity?tab=transactions instead
of /cashflow, fixing category/subcategory click navigation from spending
and income pages.
- Add Reports page with Month Analysis view for spending insights
- Add spending trends chart with day-by-day cumulative spending
- Add category breakdown panel with pie chart and notable changes
- Add transaction metrics panel with top expenses
- Add MonthYearPicker and MonthSwitcher UI components
- Add ViewTransactionsButton reusable component
- Add get_spending_trends and get_month_metrics backend endpoints
- Change event filtering from exclude to include semantics (events excluded by default)
- Move Reports above Cashflow in navigation
Add ability to bulk assign type, category, and event to multiple
selected transactions in the Activity Transactions tab edit mode.
Includes bulk clear options for categories and events.
- Show all categories for all activity types (not just income/expense)
- Add "No category" option to category dropdown
- Add "No subcategory" option to subcategory dropdown
- Add "No event" option to event dropdown
Categories with subcategories now show a chevron indicator and can be
expanded to reveal nested subcategory spending with progress bars and
percentages.
Add ability to filter spending data by events in the Spending History chart.
By default, transactions associated with events are excluded from spending
calculations. Users can now include all events or select specific events
to include in the spending summary.

Changes:
- Backend: Add include_event_ids and include_all_events params to spending queries
- API: Change /spending/summary from GET to POST to support request body
- Frontend: Add Events filter dropdown to SpendingHistoryChart component
Add optional recurrence column to activities with values: 'fixed', 'variable', 'periodic'

Backend changes:
- Add recurrence column to activities and activity_rules tables
- Extend activity search with recurrence filtering
- Support recurrence in activity rules matching

Frontend changes:
- Add recurrence field to transaction forms and edit mode
- Add recurrence filter options in transaction list view
- Support recurrence mapping in CSV import flow
- Apply recurrence from activity rules during import
- Add 4-square button UI for bulk type/recurrence assignment modals
Add a recurrence breakdown section to the Transaction Metrics panel showing
fixed, variable, periodic, and non-recurring spending percentages with
dollar amounts. Also fixes the ViewTransactionsButton date parsing issue
by using parseISO instead of new Date() to prevent timezone-related
off-by-one month errors.
Add a new "Goals Allocation" tab to the reports page that displays:
- Goal selector in header (single-click toggle, defaults to largest goal)
- Summary cards showing total contributed, target amount, and progress
- Donut chart showing contribution sources by account
- Contributions table showing recent contributions with dates
- Area chart showing cumulative contributions over time with target line
- Add goal_contributions table with migration
- Implement Rust backend for creating, deleting, and fetching contributions
- Add goal contributions UI in settings with form dialog
- Update dashboard goals display to show amount/target on same row
- Replace goal allocations with manual contributions system
- Apply code formatting fixes across codebase
Change from hsl(var(--success)) to var(--success) to match
the pattern used in other charts across the app.
- Add budget configuration with monthly spending/income targets
- Add category-level budget allocations for expenses and income
- Add Budget vs Actual comparison in monthly analysis reports
- Create budget settings page with target cards and allocation management
- Support for flexible/unallocated budget amounts
- Add database migrations for budget tables
- Integrate budget service with spending data for actuals comparison
@marcolagos marcolagos force-pushed the ml-enhance-spending-tracking branch from 99368b9 to 5133df7 Compare December 19, 2025 02:52
- Monthly Analysis: show "Import transactions" link when no data exists
- Spending tab: show "Import transactions" link when no spending data
- Income tab: show "Import transactions" link when no income data
- Events tab: show "Create an event" link to settings/events
- Fix unused imports in budget components
- Add deposits from SECURITIES and CRYPTO accounts to income summary
- Display investment deposits by account name and account type
- Fix negative zero display in formatAmount and formatPercent
- Filter cash activities to only show CASH account transactions
- Rename activity tabs from Trades/Transactions to Asset Accounts/Cash Accounts
- Update tab URL params from trades/transactions to assets/cash
@afadil
Copy link
Owner

afadil commented Dec 20, 2025

Thank you for this nice feature. Would you be okay if I reworked it a bit? I have a design in mind that includes Investing and Spending modules. I need to group some pages.

@marcolagos
Copy link
Author

marcolagos commented Dec 22, 2025

Thank you for this nice feature. Would you be okay if I reworked it a bit? I have a design in mind that includes Investing and Spending modules. I need to group some pages.

@afadil completely fine with this - please do!

- Fix category mutations to invalidate EXPENSE_CATEGORIES and
  INCOME_CATEGORIES query keys so dropdowns update when new
  categories/subcategories are created during import
- Add ability to reset transactions to their original imported state
- Add per-row reset button (undo icon) that appears on modified rows
- Add bulk reset button in selection toolbar for resetting multiple
  transactions at once
- Skip rows where all values are empty/whitespace during CSV parsing
- Show only distinct representative rows in mapping table (matches securities import)
…ring

- Add interactive category breakdown panel with dual pie charts (categories + subcategories)
- Display top 5 transactions per selected category with recurrence badges
- Add page-wide event filtering to monthly analysis metrics
- Add regex match type for activity rules pattern matching
- Improve spending calculation: deposits with expense categories subtract from spending
- Fix recurrence field not persisting during cash transaction import
- Fix duplicate transaction not preserving original date
- Fix category transactions not updating when selecting different categories
- Add "None" options to activity rule form selects for clearing values
- Add tooltip support to pie charts with proper spacing
- Add BudgetProgressRing and BudgetGaugeCard components with 4-tier
  color scheme: green (under budget), blue (on track), yellow (over
  tolerance), red (120%+)
- Add budget_variance_tolerance setting (5%, 10%, 15%) to Rust backend
  and frontend Settings
- Add Budget Tolerance selector in budget settings page
- Add QuickBudgetModal for adding budgets from unbudgeted categories
- Sort category spending by budget amount (highest to lowest)
- Increase spending history chart height on desktop (550px)
- Add event filtering to income summary queries (backend + frontend)
  matching the existing spending summary pattern
- Add monthly average display for categories, subcategories, and
  income sources in spending and income pages
- Add centralized invalidateActivityQueries helper to reduce
  duplicate query invalidation logic
- Move event filter to header actions in spending and reports pages
… page

- Remove contribution validation that blocked over-allocation (backend)
- Create shared ContributionForm and ContributionList components
- Add contribution management UI to /reports?tab=allocation page
- Allow contributions even when freeCash would be negative
- Remove at-risk display from all UI components
- Refactor goal-contributions.tsx to use shared components

Users can now add/remove contributions from both the settings page
and the allocation reports page. Negative freeCash values are
displayed to indicate over-allocated accounts.
For cash accounts, the account metrics card now shows:
- Balance (editable)
- Amount allocated to savings goals
- Available balance (actual - contributions)

Negative available balances are highlighted in red when
contributions exceed the actual cash balance.
Cash accounts don't have holdings, so the holdings section
(including the empty state) is now hidden for CASH account types.
- Ensure non-deposit/withdrawal cash activities default to zero amount
- Pass recurrence field through cash activity mutations
- Fix spending color indicators to reflect inflow vs outflow
- Resolve Recharts Pie activeIndex typing issue
- Improve minor UI copy consistency
@GiovanniSchiavo
Copy link

Nice work on the implementation. I have some thoughts on it I want to share:

Have you considered adding tags? This would allow users to group expenses that cut across different categories or events without being restricted by the main hierarchy.

Also, regarding the logic for Cash vs. Investment accounts, it is worth noting that many users have a single bank account for both investing and daily spending. Ideally, the architecture would accommodate this mixed usage rather than enforcing a rigid separation.

In any case, really looking forward to seeing this get merged!

@marcolagos
Copy link
Author

@GiovanniSchiavo Appreciate the feedback — glad this is heading in a good direction.

On tags: could you share an example of a use case where a tag would be useful independent of both category and event? My original intention was for events to be the cross-cutting attribute. Conceptually, I was thinking that any transaction that is “special” or notable would belong to an event, and that event could then span categories and accounts. That said, I agree the naming may be doing some work against that mental model — renaming or re-scoping events might make this clearer.

Regarding cash vs. investment accounts: for users with a single bank account used for both investing and daily spending, are the CSVs typically exported together or separately? My initial thought was that even if it’s technically a single bank account, modeling it as two logical accounts in the application might still be the cleanest abstraction — but I’m open to revisiting this if it causes friction with real-world data.

Looking forward to your thoughts.

@Jonjon-prog
Copy link

Hello @marcolagos , thanks a lot for this module and the worked you have done on that it's really a great feature !

I tested it a little bit and have just one feedback that I got in mind but not sure right now if you are still on this or Fadil took the lead.
In the "Reports" page , monthly analysis tab, I found strange that we don't have access to the details of the total income. It's like I wanted to have the possibility to click on this section and it will show me the lines of the income. Cause right now we need to switch to the "Cashflow" page , Income tab so see all the incomes, but it's not really on the month we are currently as it's tracking all-time incomes.

So kinda a little summary of the Month current incomes in the Monthly analysis tab would be great to have it quickly.

After the rest is looking fine.
I just needed to do manual intervention on my bank csv (French one) to add some tabs because we needed them for the import.

Looking forward to your thought on that.
Have a great day

@GiovanniSchiavo
Copy link

@marcolagos Sorry for the delay, I've been super busy lately.
I'm currently using Actual Budget, so that's where I'm drawing my mental model from.

A strong use case for a tag independent of an event is #reimbursable.
If I am on a work trip, a transaction might look like this:

  • Category: Food
  • Event: Conference 2026 (Time-bound grouping)
  • Tag: #reimbursable (Status/Attribute)

I need to track #reimbursable items across all years and all events. If I were forced to use an Event for this, I'd have to choose between grouping the transaction under "Conference 2026" or "Reimbursable", whereas I often need both data points. Tags allow me to group a subset of activities from different categories without a time boundary (I use this to track #coffee spending across different contexts, for example).

This pairs very well with the "split transaction" feature in Actual Budget, where you can split a single purchase/transaction into its items with various categories and/or tags. It’s harder to explain than to see in action, but it offers a lot of flexibility.

On Cash vs. Investment Accounts: Regarding CSVs, in my experience, it varies a lot bank by bank, my current bank export them separately (both in terrible ways too).
The main issue I foresee with splitting them into two logical accounts is handling liquidity. Since the cash pool is shared, if you split them, you risk duplicating transactions or having to manage complex transfers for every buy/sell order (e.g. a "withdraw" from the cash account and a "invest" to the investment account). It might be cleaner to keep them as one account where asset purchases are just treated as transfers.
This setup would also match the CSV exports from bank account better, since (at least in my experience) the expenses exports do not include liquidity changes from investments (but they include taxes, dividends and coupons, so it is kinda a mess).

I know this is a lot, but I hope it may be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants