Commit Graph

19 Commits

Author SHA1 Message Date
Mikael Møller
5cb474d61c Quick Categorize Wizard — follow-up fixes (#1393)
* Extract Entry.uncategorized_transactions scope, remove Family#uncategorized_transaction_count

Adds a single Entry.uncategorized_transactions scope containing the
shared conditions (transactions join, active accounts, category nil,
not transfer kinds, not excluded). All callers now use this scope:

- Entry.uncategorized_matching builds on it
- Transaction::Grouper::ByMerchantOrName#uncategorized_entries uses it
- categorizes_controller#uncategorized_entries_for uses it (also fixes
  missing status/excluded filters that were silently absent before)
- Both controllers replace Current.family.uncategorized_transaction_count
  with Current.accessible_entries.uncategorized_transactions.count so
  the button count and wizard count both respect account sharing

Family#uncategorized_transaction_count removed as it is now unused and
was family-scoped rather than user-scoped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Scope assign_entry write to Current.accessible_entries

Replaces unscoped Entry.where(id:) with Current.accessible_entries.where(id:)
so the write path is consistent with the find above it. Not exploitable
given the find would 404 first, but removes the pattern inconsistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add privacy-sensitive class to amounts in categorize wizard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Extract uncategorized_count helper in CategorizesController

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix comment on uncategorized_transactions scope to mention draft accounts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Use uncategorized_count helper in assign_entry action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 13:17:37 +02:00
Mikael Møller
0870ebb56b Add Quick Categorize Wizard (#1386)
* Add Quick Categorize Wizard (iteration 1)

Adds a step-by-step wizard for bulk-categorizing uncategorized transactions
and optionally creating auto-categorization rules, reducing friction after
connecting a new bank account.

New files:
- Transaction::Grouper abstraction + ByMerchantOrName strategy (groups by
  merchant name when present, falls back to entry name; sorted by count desc)
- Transactions::CategorizesController (GET show / POST create)
- Wizard view at app/views/transactions/categorizes/show.html.erb
- Stimulus categorize_controller.js (Enter-key-to-select-first)
- Tests for grouper and controller

Modified files:
- routes.rb: resource :categorize inside namespace :transactions
- transactions_controller.rb: expose @uncategorized_count to index
- transactions/index.html.erb: Categorize (N) button in header
- family.rb: uncategorized_transaction_count query
- rules_controller.rb: return_to param support for wizard → rule editor flow
- rules/_form.html.erb, rules/new.html.erb: pass return_to through form
- i18n: categorizes show/create keys + rules.create.success

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Quick Categorize Wizard — iteration 2 polish

Six improvements from live testing:

- Breadcrumb: Home > Transactions > Categorize
- Layout: category picker + confirmation dialog above transaction list
- Inline confirmation dialog: clicking a category pill shows a <dialog>
  summarising what will happen (N transactions → category, rule if checked)
  with Confirm and Cancel buttons — no redirect to rule editor
- Direct rule creation: rule created with active: true in the controller
  instead of redirecting to the rule editor; revert return_to plumbing from
  RulesController, rules/_form, rules/new, rules/en.yml
- Individual row assignment: per-row category <select> submits via
  PATCH /transactions/categorize/assign_entry and removes the row via
  Turbo Stream (assign_entry action + route)
- Enter key guard: selectFirst only fires when exactly 1 pill is visible
  after filtering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Quick Categorize Wizard — iteration 3 reliability fixes and UX polish

- Fix Stimulus controller not loading: remove invalid `@hotwired/turbo` named
  import (not in importmap); use global `Turbo.renderStreamMessage` instead
- Fix Enter key submitting form with wrong category when search field is
  unfocused: move keydown listener to document so it fires regardless of focus
- Prevent Enter from submitting when multiple categories are visible
- Clear search filter after bulk category assignment (pill click or Enter),
  but not after individual row dropdown assignment
- Update group transaction count and total amount live as entries are assigned
  via row dropdown or partial bulk assignment
- Add turbo frames for remaining count and group summary so they update
  without a full page reload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Quick categorization polish

* refactoring

* Remove unused GROUPS_PER_BATCH constant, fix ERB self-closing tags

Wizard only ever uses one group at a time so limit: 1 is correct and
more honest than fetching 20 and discarding 19. ERB linter fixes are
whitespace/void-element corrections with no functional change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Move Categorize button into ... menu on transactions index

Reduces header clutter by putting it in the overflow menu at the bottom,
where it only appears when there are uncategorized transactions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Scope categorize wizard to accessible entries only

Fixes a security issue where users with restricted account access via
account sharing could view and categorize transactions from accounts
they cannot access through normal transaction flows.

- Pass Current.accessible_entries to Transaction::Grouper so the wizard
  only displays groups from accounts the user can see
- Use Current.accessible_entries on all write paths in create and
  assign_entry, matching the pattern in TransactionCategoriesController
- Refactor Grouper to accept an entries scope instead of a family object,
  keeping authorization concerns in the controller
- Add tests verifying inaccessible entries are hidden from the wizard
  and cannot be categorized via forged POST/PATCH params

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Clamp position param to >= 0 to guard against negative offset

Prevents ArgumentError from Array#drop when a negative position is
passed via a tampered query string or form value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Surface rule creation failure and add accessible names to entry row

- Capture Rule.create_from_grouping! return value; set flash[:alert] when
  nil so users who checked "Create Rule" know it wasn't created (e.g. a
  duplicate already exists); stream the notification for partial updates
- Add aria-label to the per-row checkbox and category select in
  _entry_row so screen readers can identify which transaction each
  control belongs to

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Localize breadcrumb labels in categorizes controller

Follows the pattern used by FamilyExportsController and ImportsController.
Adds 'transactions' and 'categorize' keys to the breadcrumbs locale file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add error handling to categorize controller fetch calls

Check response.ok before parsing the body and add .catch handlers
so network failures and non-2xx responses are logged rather than
silently swallowed. On assignment failure the per-row select is
reset to empty so the user can retry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Scope preview_rule to accessible entries only

Entry.uncategorized_matching now accepts an entries scope instead of a
family object, matching the same pattern used for Transaction::Grouper.
The preview_rule action passes Current.accessible_entries so rule
previews respect account sharing permissions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Scope remaining count to accessible entries

Adds Entry.uncategorized_count(entries) following the same pattern as
uncategorized_matching. Replaces all three uses of
Current.family.uncategorized_transaction_count in the categorize
controller so the remaining-count badge reflects only the transactions
the current user can actually access and categorize.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Comments got separated from their function

* Remove quick-categorize-wizard dev notes

This was a planning document used during development, not intended
for the final branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Recompute remaining entries from server state after writes

Adds uncategorized_entries_for helper that reloads remaining entries
from the DB with a category_id IS NULL filter after each write, so
the partial-update Turbo Stream reflects server-side state rather than
trusting the client-provided remaining_ids. This handles the case where
a concurrent request has categorized one of the remaining entries
between page render and form submit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Rename create_from_grouping! to create_from_grouping

The method rescues RecordInvalid and returns nil, which contradicts
the bang convention. Dropping the ! correctly signals that callers
should check the return value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Clamp offset in grouper to guard against negative values

The controller already clamps position before passing it as offset,
but clamping in the grouper itself prevents ArgumentError from
Array#drop if the grouper is ever called directly with a negative offset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-04-07 11:24:50 +02:00
soky srm
9410e5b38d Providers sharing (#1273)
* third party provider scoping

* Simplify logic and allow only admins to mange providers

* Broadcast fixes

* FIX tests and build

* Fixes

* Reviews

* Scope merchants

* DRY fixes
2026-03-25 17:47:04 +01:00
soky srm
560c9fbff3 Family sharing (#1272)
* Initial account sharing changes

* Update schema.rb

* Update schema.rb

* Change sharing UI to modal

* UX fixes and sharing controls

* Scope include in finances better

* Update totals.rb

* Update totals.rb

* Scope reports to finance account scope

* Update impersonation_sessions_controller_test.rb

* Review fixes

* Update schema.rb

* Update show.html.erb

* FIX db validation

* Refine edit permissions

* Review items

* Review

* Review

* Add application level helper

* Critical review

* Address remaining review items

* Fix modals

* more scoping

* linter

* small UI fix

* Fix: Sync broadcasts push unscoped balance sheet to all users

* Update sync_complete_event.rb

 The fix removes the sidebar broadcasts (which rendered unscoped account groups using family.balance_sheet without user context)
  along with the now-unused sidebar_targets, account_group, and family_balance_sheet private methods.

  The sidebar will still update correctly — when the sync completes, Family::SyncCompleteEvent#broadcast fires family.broadcast_refresh, which triggers a
  morph-based page refresh for each user with their own authenticated session, rendering properly scoped sidebar content.
2026-03-25 10:50:23 +01:00
soky srm
ae5b23fe67 Initial split transaction support (#1230)
* Initial split transaction support

* Add support to unsplit and edit split

* Update show.html.erb

* FIX address reviews

* Improve UX

* Update show.html.erb

* Reviews

* Update edit.html.erb

* Add parent category to dialog

* Update en.yml

* Add UI indication to totals

* FIX ui update

* Add category select like rest of app

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-03-20 21:19:30 +01:00
Ang Wei Feng (Ted)
c77971ea0d fix: Preserve tags on bulk edits (take 3) (#889)
* fix: handle tags separately from entryable_attributes in bulk updates

Tags use a join table (taggings) rather than a direct column, which means
empty tag_ids clears all tags rather than meaning "no change". This caused
bulk category-only edits to accidentally clear existing tags.

This fix:
- Removes tag_ids from entryable_attributes in Entry.bulk_update!
- Adds update_tags parameter to explicitly control tag updates
- Uses params.key?(:tag_ids) in controller to detect explicit tag changes
- Preserves existing tags when tag_ids is not provided in the request

This is a cleaner architectural solution compared to tracking "touched"
state in the frontend, as it properly acknowledges the semantic difference
between column attributes and join table associations.

https://claude.ai/code/session_014CsmTwjteP4qJs6YZqCKnY

* fix: handle tags separately in API transaction updates

Apply the same pattern to the API endpoint: tags are now handled
separately from entryable_attributes to distinguish between "not
provided" (preserve existing tags) and "explicitly set to empty"
(clear all tags).

This allows API consumers to:
- Update other fields without affecting tags (omit tag_ids)
- Clear all tags (send tag_ids: [])
- Set specific tags (send tag_ids: [id1, id2])

https://claude.ai/code/session_014CsmTwjteP4qJs6YZqCKnY

* Proposed fix

* fix: improve tag handling in bulk updates for transactions

* fix: allow bulk edit to clear/preserve tags by omitting hidden multi-select field

* PR comments

* Dumb copy/paste error

* Linter

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-02-06 14:11:46 +01:00
Zach Gollwitzer
e657c40d19 Account:: namespace simplifications and cleanup (#2110)
* Flatten Holding model

* Flatten balance model

* Entries domain renames

* Fix valuations reference

* Fix trades stream

* Fix brakeman warnings

* Fix tests

* Replace existing entryable type references in DB
2025-04-14 11:40:34 -04:00
Zach Gollwitzer
bddaab0192 Account namespace updates: part 4 (transfers, singular namespacing) (#896)
* Move Transfer to Account namespace

* Fix partial resolution due to namespacing plurality

* Make category and tag controllers consistent with namespacing convention

* Update stale partial reference
2024-06-20 13:32:44 -04:00
Zach Gollwitzer
dc3147c101 Move merchants to top-level namespace (#895) 2024-06-20 08:38:59 -04:00
Zach Gollwitzer
2681dd96b1 Move categories to top-level namespace (#894) 2024-06-20 08:15:09 -04:00
Zach Gollwitzer
4ebc08e5a4 Transactions cleanup (#817)
An overhaul and cleanup of the transactions feature including:

- Simplification of transactions search and filtering
- Consolidation of account sync logic after transaction change
- Split sidebar modal and modal into "drawer" and "modal" concepts
- Refactor of transaction partials and folder organization
- Cleanup turbo frames and streams for transaction updates, including new Transactions::RowsController for inline updates
- Refactored and added several integration and systems tests
2024-05-30 20:55:18 -04:00
Jakub Kottnauer
98f3f172a9 Validate transaction filtering params (#810) 2024-05-27 10:01:08 -04:00
Jakub Kottnauer
ac27a1c87f Move category dropdown menu content into a turbo frame (#782)
* Move category dropdown menu content into a turbo frame

* Fix lint

* Review fixes

* Cleanup

* Review fixes

* Final cleanup

* Revert schema change
2024-05-22 06:31:25 -04:00
Zach Gollwitzer
45ae4a9737 CSV Transaction Imports (#708)
Introduces a basic CSV import module for bulk-importing account transactions.

Changes include:

- User can load a CSV
- User can configure the column mappings for a CSV
- Imported CSV shows invalid cells
- User can clean up their data directly in the UI
- User can see a preview of the import rows and confirm import
- Layout refactor + Import nav stepper
- System test stability improvements
2024-05-17 09:09:32 -04:00
Zach Gollwitzer
75cdddc6ca Fix Merchants controller (#704)
* Add climate_control gem and test helper

* Replace ENV mods in upgrades test

* Replace ENV mods in registrations test

* Remove ENV references in hostings controller

* Update ENV refs in mailer test

* ActiveStorage cleanup

* Consolidate queue config so appropriate adapter runs in test environment

* Make test environment more explicit

* Centralize self hosting config

* Remove flaky system test

* Fix merchants controller actions
2024-05-02 13:24:23 -04:00
Jose Farias
4c5f8263bc Implement transaction category management (#688)
* Singularize "transaction" in transaction-nested paths

* Refactor category badge partial

* Let modal content define its width

* Add contectual menu to transactions index

* Add null_category helper

* Implement category edits

* Fix inline transaction category badges

* Fix typos in system test paths

* Add missing translations

* Add decoration to color select controller

* Wire up transaction category creation

* Fix indent in color-select-controller

* Add button for clearing category from transaction

* Implement category deletions

* Fix existing modal sizes

* Use null_category in a single place

* Remove anemic method in category deletion controller

* reassign_and_destroy -> reassign_transactions_then_destroy

* Fix i18n

* Remove destroy action from CategoriesController callbacks

* transactions_merchant -> transaction_merchant

* reassign_transactions_then_destroy -> replace_and_destroy

* Add transaction category CRUD tests

* Add presence check for transaction_id

* Check replacement_category_id presence

* Test Transaction::Category#replace_and_destroy!
2024-05-02 09:24:31 -04:00
Jakub Kottnauer
9549182462 Add Transaction Merchant management (#686)
* Add basid crud for merchant management

* Tweak UI and add localization

* Fix lint

* Add filtering by merchant

* Add tests

* Add stimulus controller to update avatar in merchant form

* Add line between merchant rows

* Change default merchant color

* Cleanup
2024-04-29 15:17:28 -04:00
Zach Gollwitzer
9bda7efc3f New Settings Menu, Routes and Controllers Organization (#641)
* Add new settings routes and controllers

* Add new settings view, restructure controllers and routes

* Fix lint errors
2024-04-18 07:56:51 -04:00
Zach Gollwitzer
d29d465a3c Basic transaction categories CRUD actions (inline) (#601)
* Fix dropdown issues and add dummy transaction category modal

* Minor namings tweaks

* Add search type

* Use new menu controller

* Complete basic transaction category inline CRUD actions

* Fix lint error

---------

Co-authored-by: Jakub Kottnauer <jk@jakubkottnauer.com>
2024-04-04 17:29:50 -04:00