Commit Graph
100 Commits
Author SHA1 Message Date
Herculino Trotta 3468b10e94 feat(dca): limit create-transaction accounts to the strategy currencies 2026-11-10 21:57:39 -03:00
Herculino Trotta 750b86394e feat(dca): optional amounts, currency-checked links and entities on entries
- Amounts default to the linked transaction's amount when left empty
- Linked transactions must be in the strategy's payment/target currency; pickers only search those
- Allow setting entities on transactions created from an entry
- Fix crash when only the expense transaction was linked
2026-11-10 21:55:29 -03:00
Herculino Trotta 706f8fea2b feat(transactions): allow filtering the transaction picker search
TransactionSelect takes a filters dict sent as query params; the search endpoint applies allowlisted ones (currency, account).
2026-11-10 21:55:29 -03:00
Herculino Trotta 0093cabfc9 fix(investments): solve short-period return over the holding period
Holdings under a year annualized their rate before turning it back into
the period return, so a few days with a modest gain (+1.3% in one day is
~10,600% a year) fell outside xirr's search range and showed no percentage.
Solve the rate over the period held instead.
2026-11-10 20:35:03 -03:00
Herculino Trotta 0854805630 feat(transactions): show combined transfers as two legs on a timeline
- Shared header (date, description, badges); each side gets its own row:
  account with its category, tags and entities, amount + exchanged amount
- Timeline rail from the transfer icon branching into the out/in icons
- Amounts drop below on small screens and sit bottom-right on lg, like
  regular items; spacing matches regular item metadata rows
- Notes move below the legs on transfers so they don't sit in the rail
2026-11-09 23:35:56 -03:00
Herculino Trotta 4826c4932a fix(transactions): show exchanged amount for both sides of combined transfers 2026-11-09 23:16:47 -03:00
Herculino Trotta d0b6be14e7 feat(investments): calmer list and detail pages, group holdings by asset class
- One hero number with a single return line; secondary totals behind a Details toggle
- Holdings in one list card with quiet group subheaders, value and %/yr per row
- Group holdings by account group or asset class, remembered in the session
- Detail page: value and chart up top, Your position and home-currency sidebar
- Replace tooltips with visible text; keep amounts from wrapping on phones
2026-11-08 21:48:40 -03:00
Herculino Trotta 55f064ee7c feat(distribution): bracket projected + current segments with their sum
Remove the unused exchanged branch from calculate_percentage_distribution; nothing read it and it mixed up income_current with expense_projected.
2026-11-08 20:16:13 -03:00
Herculino Trotta fda4bade62 fix(investments): count income categories on unit-priced accounts and transfers at their own amount
- Unit-priced accounts now honour their income categories, tags and entities:
  a matching transaction is a gain or loss, and any other unlinked one is money
  in or out at that day's market price. Before, every unlinked outflow was a
  loss and yields were booked as new money in.
- A transfer out of an account tracked by value counts its own amount. It used
  the other side converted at the day's rate, so a coin bought from a savings
  account moved the coin's price change onto the savings account.
2026-11-08 18:05:17 -03:00
Herculino Trotta 448e9c8f5c fix(api): save income categories, tags and entities on account create and update
AccountSerializer assigned the many-to-many fields with setattr, which Django rejects, so any write that included them returned a 500. The default ModelSerializer create/update already handles them.
2026-11-08 17:57:56 -03:00
Herculino Trotta 78cb61b0b6 feat(demo): add investment examples to the demo data
Investments landed without demo data. The ETF, Bitcoin and savings accounts become investments, and new accounts cover the rest of the tracker: Apple shares (dividends paid into checking, a partial sale, gifted shares with an estimated cost), a retirement fund tracked by value (quarterly gains and a loss, a yearly fee) and gold. Asset classes have targets that add up to 100%, plus one without a target to show how rebalancing leaves it out.

The investment examples use their own random seed, so the rest of the demo is unchanged. The demo test now renders the investment pages and checks each kind of holding has figures.
2026-11-08 17:17:16 -03:00
Herculino Trotta 52eae5180f fix(transactions): let transfer account names use the free row space
Account names on a combined transfer were cut at a fixed 14rem even when the row had room. On large screens the row now stays on one line and the account side can grow up to half of it, so names only truncate past that.
2026-11-08 17:17:05 -03:00
Herculino Trotta 122f0b7a28 fix(transfers): entities, paid state and inactive items in the transfer form
The transfer form had no entities field and always created paid transfers. Each side now has its own entities, shown as from -> to in the list like category and tags, and a Projected/Paid toggle applies to both sides.

Editing a transfer also offered only active categories, tags and entities and non-archived accounts, so saving silently dropped a deactivated tag or failed on an archived account. The transfer's own items stay selectable, as in the transaction form.
2026-11-08 17:16:49 -03:00
Herculino Trotta 1369c3edbf Merge remote-tracking branch 'origin/main' into feat/bank-sync-2
# Conflicts:
#	app/apps/export_app/views.py
#	app/templates/includes/sidebar.html
2026-11-08 16:17:06 -03:00
Herculino Trotta cc4c58b676 feat(bank-sync): add Pluggy provider
Brazilian banks through Open Finance. Users give their Pluggy client
credentials and the ids of their connections (items), from Meu Pluggy.

- Uses the cursor-paginated /v2/transactions; /transactions is removed
  after 2026-12-31. Our own filters are sent again with each cursor.
- The direction comes from `type`, not the amount's sign: credit cards
  keep the card's sign, purchases are positive. Checked against a real
  Meu Pluggy card and checking account; the sandbox data doesn't match
  production here.
- Pending transactions keep their id once they post (a card's open bill
  stays pending until it closes), so they're imported unpaid and settled
  later. Future-dated ones (installments, scheduled payments) wait for
  their date. Installments already come dated in their own month.
- Foreign purchases use the account's amount, with the original amount
  in the notes.
- Connections that need the user to log in again show as an error.
2026-11-08 16:10:38 -03:00
Herculino Trotta 2a4202118b feat(bank-sync): import pending transactions and settle them when they post
Providers can now return pending transactions, flagged as such, when
their ids survive posting. They're imported unpaid right away and, once
the bank posts them, marked paid with their final amount and date.

- Settling happens once, so later edits stick, and one marked paid by
  hand is left alone. It goes through save() so a transfer's other side
  follows; no rules run again. reference_date moves only if it still
  followed the date.
- Each sync looks back to the oldest unpaid synced transaction, since a
  card purchase can post weeks later when the bill closes. Pending ones
  older than 60 days are taken as cancelled and no longer followed.
2026-11-08 16:09:23 -03:00
Herculino Trotta 9238c48bb5 feat(bank-sync): add SimpleFIN provider
Users paste a one-time setup token. On save it's claimed and the access
URL it returns is stored; credentials go in a header, never the URL, so
errors don't leak them.

- Providers can validate or transform their settings (clean_settings),
  run by the service form once every other field is valid, so a token
  isn't spent on a form that fails anyway.
- Providers can declare a minimum sync interval. SimpleFIN Bridge
  disables tokens that go much past ~24 requests a day, so it's 6 hours.
- Tokens and access URLs are user input pointing the server at a host:
  only SimpleFIN Bridge is allowed unless SIMPLEFIN_ALLOWED_HOSTS says
  otherwise. Checked again on every request, for restored settings.
- Transactions are fetched per account in 90-day windows, Bridge's
  limit per request. Pending ones are skipped.
2026-11-08 16:08:37 -03:00
Herculino Trotta 8ed2c98074 fix(currencies): renumber retention migration after main's 0026 2026-11-08 15:06:14 -03:00
Herculino Trotta 76090ceb4a Merge remote-tracking branch 'origin/main' into feat/investments 2026-11-08 15:01:22 -03:00
Herculino Trotta 6ed0ba4efa feat(api): asset class endpoints
- /api/investment-classes/ to list, create, edit and delete asset classes
- Accounts return their asset class and take investment_class_id, filterable
  by investment_class
- Unique names and targets up to 100% are checked in the model, shared by the
  form and the API
2026-11-08 14:55:42 -03:00
Herculino Trotta 25a2326cf6 feat(export): include bank sync services and account links in export and restore 2026-11-08 12:39:17 -03:00
Herculino Trotta 77ee56f7a3 feat(bank-sync): add sidebar entry 2026-11-08 12:39:17 -03:00
Herculino Trotta 620e7df015 feat(bank-sync): sync bank transactions from external providers
Provider registry modeled after automatic exchange rates: each provider
declares its own settings fields, rendered dynamically in the service form.
Lunch Flow is the first provider.

- Map provider accounts to WYGIWYH accounts with a start date and an
  optional currency filter
- Hourly task syncs due services; manual sync from the list
- Deduplicates through Transaction.internal_id, optionally matching
  transactions already entered by hand
- Per account errors and provider reported connection problems

Refs #498
2026-11-08 12:39:16 -03:00
Herculino Trotta 9f9146aaec feat(transactions): search transactions by date, amount, entity and account
Each word of the query must match something, and words can match
different fields: description, notes, category, tags, entities, account
and account group, a date in the user's format (the year is optional),
or an amount in the user's number format, where a whole number like 234
matches 234.00 to 234.99.

Matching through an id subquery also stops transactions with several
matching tags from showing up more than once.
2026-11-08 03:27:21 -03:00
Herculino Trotta 8482c85a76 fix(transactions): keep transaction picker results in monthly view order
Results come back as today, tomorrow, yesterday, upcoming (soonest first)
and then past (newest first); past dates used to be oldest first. The
endpoint is now capped at 100 results, as its docstring already claimed.

tom-select also re-scored and re-sorted the server's results on the
client, which scrambled the order while searching. Load-based selects now
keep the server's order, drop the previous query's results, and preload
recent transactions on focus.
2026-11-08 03:27:15 -03:00
Herculino Trotta aa3ffe9b3e feat(transactions): render the transaction picker as a mini transaction
TransactionSelect options now look like a compact version of the
transaction item: paid state, type colour, date, description, category,
tags, entities, amount and account. The search endpoint returns that data
as JSON, and pre-selected options carry it in data-data so edit forms
render the same way.
2026-11-08 03:27:01 -03:00
Herculino Trotta 48afe0b7b3 fix(dca): repair the create transaction section layout
Nesting Row(Column(Row(Column))) shrank every field to a quarter of the
width, since Column defaults to half, and rounded-3 is a Bootstrap class
that doesn't exist here. Use the same card layout as the transfer form:
account at full width with category and tags side by side.
2026-11-08 03:26:21 -03:00
Herculino Trotta 03f2b0aeb4 fix(toasts): space the close button from the text and edges
Lay the alert out as an icon | text | button grid so the close button gets
a consistent gap from the message and the same visual inset as the icon.
Long messages now wrap instead of pushing the button, and the aria-label
is quoted.
2026-11-08 03:26:11 -03:00
Herculino Trotta 34f6066187 docs(readme): give IMPORT_TEMP_ROOT a literal default and move path vars up
List the Docker path as the default like the other rows do, and move
both rows out from under the dev-only Vite settings.
2026-11-08 03:17:33 -03:00
Herculino Trotta e0c1e4d5ff fix(import): resolve symlinks when checking the upload path
The temp root is resolve()d in settings but the file path was only
abspath()ed, so a symlink inside the temp folder could point outside it
and still pass the check. Resolve both sides the same way.

Also drop the unused /temp folder from the prod image.
2026-11-08 03:09:04 -03:00
Herculino Trotta 6a335b1a63 fix(automatic-exchange-rates): skip bad AllRatesToday rows individually
A non-numeric, NaN or infinite value raised while parsing and threw away
the whole table. Each bad row is now skipped on its own.

Log a warning when a table has rows but none of a usable rate type, so a
bank with unknown types does not fail silently.
2026-11-08 02:57:07 -03:00
Herculino Trotta 43145be24d feat(automatic-exchange-rates): add provider options field
Add an Options field to exchange rate services. Providers get the raw
text, can validate it on save through clean_options, and describe it
with options_help.

The service form now shows API Key only for providers that require one,
and Options only for providers that read it, with that provider's help.

AllRatesToday reads its bank code from Options instead of API Key.
2026-11-08 02:57:07 -03:00
Herculino Trotta 38118c56d3 Merge remote-tracking branch 'origin/main' into fix/configurable-app-paths 2026-11-08 02:53:53 -03:00
Herculino Trotta a50bdb10ff Merge remote-tracking branch 'origin/main' into feat/allratestoday-provider 2026-11-08 02:53:53 -03:00
Herculino Trotta c17f03775c Merge remote-tracking branch 'origin/main' into feat/investments 2026-11-08 02:53:52 -03:00
Herculino Trotta a691da7523 chore(dev): run the dev stack in multiple git worktrees
Drop fixed container names so each worktree gets its own containers,
add docker/dev/worktree.sh to set up and tear down a worktree, and
document it under a new Development section in the README.
2026-11-08 02:48:59 -03:00
Herculino Trotta 494937870e fix(export): back up asset classes and investment income links
Restore asset classes before accounts and take a second pass over accounts
once categories, tags and entities exist, so their links survive a restore
into a fresh install.
2026-11-08 02:26:40 -03:00
Herculino Trotta 7038ce8e18 feat(investments): asset classes with target allocation, charts and rebalance
- Asset classes (name, optional target %) managed under Management > Investments,
  chosen per account
- Charts tab: allocation by asset class and currency, value against invested,
  return by investment; off-target classes flagged by the 5/25 rule
- Rebalance tab: what to buy or sell per class to reach the targets
- Investment page: price against average cost with trades, monthly earnings
  and income
2026-11-08 02:26:40 -03:00
Herculino Trotta 7902c4fc4e fix(forms): resolve dynamic select values from the form's queryset
A value the default manager hides, such as another user's object already set
on a shared record, was taken as a new name and created a duplicate named
after its id.
2026-11-08 02:26:40 -03:00
Herculino Trotta 26d0751df6 fix(datepicker): reset a stale overlay before reattaching it
Navigating back while a mobile picker was open reattached its overlay still
active, leaving the page covered by an overlay nothing could close. Also skip
the reattach when the global container doesn't exist yet.
2026-11-08 02:25:31 -03:00
Herculino Trotta da7a77a06d docs(readme): update demo url and space out sponsor note 2026-11-07 13:28:27 -03:00
Herculino Trotta fa120bb9f0 docs(readme): credit Simafri for demo and site hosting 2026-11-07 13:25:50 -03:00
Herculino Trotta 0d7395cee6 fix(investments): tell outside money from yield on value-tracked accounts
Unlinked transactions on value-tracked accounts counted as gains and losses,
so a salary was yield and a bill was a loss. They now count as money in and
out, unless they match the account's new income categories or tags, or its
income entities. Those three define the account's result; only entities also
match income paid into other accounts.
2026-11-07 01:30:24 -03:00
Herculino Trotta 1f9ba8eba8 feat(investments): break converted totals down by currency
Replace the note on converted totals with a collapsible per currency, and
drop the uppercase labels from the summary cards.
2026-11-07 01:30:23 -03:00
Herculino Trotta 99c6ca08a2 feat(currencies): configurable retention for automatic exchange rates
Replace the single exchange rate option with a limit of rates kept per
currency pair (oldest deleted first) and an option to keep one rate per
day. Services that kept a single rate migrate to a limit of 1.
2026-11-05 11:07:47 -03:00
Herculino Trotta 3e4e9f4220 fix(forms): show switch help text below the label 2026-11-05 11:07:47 -03:00
Herculino Trotta 359e5880cc feat(investments): add investment tracker
Investments are accounts flagged as investments; performance is derived from their transactions. Linked transfers are contributions and withdrawals (average cost, realized gains), unlinked transactions are gains or losses, and income comes from the accounts' income entities.

- Unit-priced (exchange currency) or value-tracked accounts
- Money-weighted return (XIRR), annualized after a year
- Totals converted along each currency's exchange chain, with currency effect
- Investments list and detail pages; buy/sell/income use the transfer and transaction forms
- GET /api/accounts/<id>/performance/
2026-11-05 11:07:32 -03:00
Herculino Trotta 1b9ea452d9 feat(transactions): prefill add and transfer forms from query parameters
transaction_add accepts account and entities; transactions_transfer accepts from_account and to_account. Applied only when present.
2026-11-05 00:14:41 -03:00
Herculino Trotta 4d494549b7 feat(dca): link entry transactions as transfers
Transactions created from a DCA entry, or picked from existing ones, are now linked as a transfer, so investment tracking sees the purchase. A data migration links the pairs of existing entries when valid.
2026-11-05 00:14:41 -03:00
Herculino Trotta 2cd9238b18 feat(transactions): link an existing expense and income as a transfer
Adds Transaction.link_transfer() and transfer_link_error(). Only the link changes: dates, amounts and rules are left alone.
2026-11-05 00:14:41 -03:00
Herculino Trotta a5c73ec436 feat(updates): queue an update check on container startup
Fresh installs no longer wait up to 12 hours for the first periodic check.
2026-11-02 17:24:28 -03:00
Herculino Trotta b10897e64b fix(updates): keep update banner across page loads and restarts
The update check shared the file-based default cache with cachalot, whose culling randomly deleted the key as pages loaded. Store only the latest release in a dedicated cache, compare it to APP_VERSION at render time, and drop the startup delete. Hide the banner when CHECK_FOR_UPDATES is off or APP_VERSION isn't a valid version.
2026-11-02 17:24:28 -03:00
Herculino Trotta 5b98779070 test(auth): add positive controls for local login and tighten basic auth check
- Add flag-off login page and admin password tests so the refusal tests can't pass for unrelated reasons
- Expect exactly 401 from refused basic auth
- Gate OIDC_DISABLE_LOCAL_LOGIN on an explicit OIDC_CONFIGURED flag
- Realign the README OIDC table, use "email" consistently, and warn about lockout with OIDC_ALLOW_SIGNUP=false
2026-11-02 16:41:52 -03:00
Herculino Trotta 3716b9b0e3 feat(transactions): link transfer sides and show them as a single item
Transfers are still two transactions, now linked to each other:
- shown as one item in lists, with each side's amount, account,
  category and tags ("Combine transfers" user setting, on by default)
- date, reference date, paid and mute stay in sync between sides
- edit opens the transfer form; delete, restore, duplicate and bulk
  actions act on both sides
- transfers made by the transfer form now trigger rules for each side

Recurring and quick transactions gain a Transfer type, with a "To
account" and optional "To amount", generating linked transfers. A
transfer can be converted into either.

Existing transfers are left as they are, since there is no reliable
way to pair them.
2026-11-02 01:14:46 -03:00
Herculino Trotta 1d22c9ff66 feat(demo): generate demo data relative to today
Replace the hard-dated demo fixture with a load_demo_data command that
builds the dataset around the current date, so the demo always has data
in the current month. It also covers more features: multi-currency
accounts with exchange rate history, an ETF and Bitcoin tracked with DCA
strategies, transfers, a credit card using reference dates, installment
plans, recurring transactions, quick transactions, rules and filter
presets.
2026-11-01 00:13:01 -03:00
Herculino Trotta 556542ac9e docs(readme): refresh screenshots 2026-11-01 00:12:33 -03:00
Herculino Trotta fcd666946f feat(accounts): reconcile balances against a date or reference month cutoff
Credit cards often have paid transactions dated after the invoice being
closed, which made reconciliation against all paid transactions wrong.

The reconciliation panel now lets you balance against all paid
transactions (previous behavior), paid transactions up to a date, or
paid transactions up to a reference month. The adjustment transaction is
dated at the cutoff so later reconciliations with the same or a later
cutoff include it. An invalid cutoff blocks the reconciliation instead
of silently falling back.
2026-10-31 17:56:36 -03:00
Herculino Trotta 5468d17e20 ci: provide container paths and a clean TMPDIR for tests
test(transactions): enable soft delete explicitly in the hard-delete attachment test
2026-10-24 14:01:47 -03:00
Herculino Trotta b6833f2b7b ci: run Django test suite on pull requests 2026-10-24 13:57:25 -03:00
Herculino Trotta 4446db3249 test(users): stop using SocialLogin.state_from_request removed in allauth 65.20
django-allauth 65.20 removed SocialLogin.state_from_request, breaking the
OIDC email auto-connect tests after the bump in #627.
2026-10-24 13:27:10 -03:00
Herculino Trotta 1e486a608e fix(import): enforce object-level authorization on import profiles and runs
ImportProfile and ImportRun had no owner, and neither the web views nor the
REST viewsets filtered by the requesting user, so any authenticated user could
read, edit and delete every other user's import profiles and runs
(GHSA-q668-h56g-mfp4).

- ImportProfile is now a SharedObject, with share and take-ownership views
  like transaction rules; the global unique constraint on name is dropped.
- ImportRun is now an OwnedObject owned by the uploader, so runs made with a
  shared profile stay private to whoever ran them.
- Existing runs are backfilled with the owner of their imported
  transactions; existing profiles stay unowned and usable by everyone.
2026-10-24 13:26:37 -03:00
Herculino Trotta 9941c8fb90 build(deps): bump django-allauth to 65.20.1 to allow oauthlib 4.0.0
django-allauth 65.18 pinned oauthlib<4, preventing Dependabot from
resolving GHSA-hj66-6f7g-4r5v and GHSA-xpv3-w29h-x7cv.
2026-10-24 12:09:19 -03:00
Herculino Trotta 3261e5f266 fix(transactions): apply the form amount to a paid transaction converted into an installment plan 2026-10-12 02:41:14 -03:00
Herculino Trotta 35ef1dd716 fix(transactions): apply the form amount to a paid transaction converted into a recurring transaction 2026-10-12 02:41:14 -03:00
Herculino Trotta dfb5cf37b4 fix(dropdown): keep submenu open while the pointer moves into it
Moving the pointer diagonally from a submenu trigger to its flyout crosses
the items below it, which closed the flyout before it could be reached.
Delay hiding the flyout by 300ms, and show it immediately on hover or focus.
2026-10-12 02:33:14 -03:00
Herculino Trotta 174d2eb340 fix(simulator): use money mask on amount field
Match the other amount fields by using the Alpine money mask, and parse the masked value with the user's separators.
2026-10-10 18:19:07 -03:00
Herculino Trotta f58c341a84 ci: use python 3.14 in translations workflow 2026-10-10 18:05:25 -03:00
Herculino Trotta 1b9c0ed0b1 build(docker): use python 3.14 base images 2026-10-10 18:05:25 -03:00
Herculino Trotta 10b3c45014 build: require python 3.14
Python 3.11 has been security-only since 2024. Bump requires-python to >=3.14 and re-lock.
2026-10-10 18:05:25 -03:00
Herculino Trotta e925822574 fix(oauth): keep specific DCR errors without exposing exception text
Parsing helpers now return (value, error) instead of raising, so no
exception object flows into the response while clients keep the
precise messages they had before.

full_clean() failures map to fixed per-field messages; redirect URI
errors surface as NON_FIELD_ERRORS from Application.clean().

Also reject non-string token_endpoint_auth_method with a 400 instead
of crashing with a TypeError.
2026-10-10 17:48:14 -03:00
Herculino Trotta 89c7a6bd4f docs(auth): explain OIDC email trust modes 2026-10-10 17:01:56 -03:00
Herculino Trotta 59de9f2443 feat(auth): configure OIDC email trust
Allow verified-only linking with OIDC_TRUST_EMAIL=false while retaining case-insensitive matching. Keep unconditional trust enabled by default for compatibility with #518; that mode retains the documented takeover risk.
2026-10-10 17:01:52 -03:00
Herculino Trotta 6e72594ec7 test(simulator): cover baseline calculation and page 2026-10-10 15:26:59 -03:00
Herculino Trotta 916c401ce3 feat(sidebar): add simulator to tools menu 2026-10-10 15:26:59 -03:00
Herculino Trotta bdf0cf8d4c feat(simulator): add simulator tool page
Hyperscript-driven page to simulate one-time, recurring and installment expenses or incomes over the projected balance.
2026-10-10 15:26:59 -03:00
Herculino Trotta 81e572c8ab feat(simulator): add projected baseline calculation
Per-currency monthly net changes from active, tracked accounts, plus the exchange rate for currencies with an exchange currency.
2026-10-10 15:26:59 -03:00
Herculino Trotta 195da63998 feat(transactions): add Convert submenu to the transaction actions menu
Replaces the "Add as quick transaction" action.
2026-10-09 23:25:20 -03:00
Herculino Trotta 8c5c299312 feat(transactions): add views to convert a transaction into a quick transaction, recurring transaction or installment plan 2026-10-09 23:25:19 -03:00
Herculino Trotta cd6069f857 feat(transactions): let new recurring transactions and installment plans adopt an existing transaction 2026-10-09 23:25:19 -03:00
Herculino Trotta a2ee0b4ae6 feat(transactions): update cuelume; bring back improved volume user setting 2026-09-13 01:24:37 -03:00
Herculino Trotta 82211567d0 feat(insights): replace fixed overviews with a single arrangeable overview
Categories, Tags and Entities Overview are merged into one Overview page.
Levels are chips that can be dragged (or moved with the arrow keys) to set
the hierarchy and clicked to switch off, so any ordering of the three is
reachable from the same page.

Only the results reload on change; the controls stay in place. The chip
order is kept in the session and defaults to categories by tags, matching
the old Categories Overview.
2026-09-12 18:02:24 -03:00
Herculino Trotta a39ee4b5d2 build(frontend): add @alpinejs/sort for drag sorting 2026-09-12 18:02:24 -03:00
Herculino Trotta fef5c48077 fix(ui): reserve scrollbar space to prevent layout shift 2026-09-12 17:33:16 -03:00
Herculino Trotta 3a5e688a90 feat(insights): add tags and entities overview 2026-09-12 14:31:12 -03:00
Herculino Trotta 5404716a81 feat(insights: categories-overview): searching shows the imedaite parents and children to provide better context 2026-09-12 14:15:31 -03:00
Herculino Trotta 52fe81f564 feat(ui:transaction): deleting transactions is smoother and no longer requires a full list refresh 2026-09-12 13:45:41 -03:00
Herculino Trotta 0ab2a41ccc feat(ui:filter): add quick clear button to filter button 2026-09-12 12:06:55 -03:00
Herculino Trotta 721b2dcd8d fix(ui): daily spending help text not showing up 2026-09-12 09:57:30 -03:00
Herculino Trotta d500bef481 ci(translations): drop force push to protected main
Branch protection rejects force pushes to main (GH006), so the
translation commit never landed. The push is a fast-forward anyway.
2026-09-06 16:53:57 -03:00
Herculino Trotta e2f26b3629 fix(sharing): enforce object-level authorization outside the rules app
The rules endpoints were one instance of a pattern repeated across every
SharedObject-backed app. Route the rest through the same helper.

DCA entries were the worst case and are strictly wider than the reported
rules bug: DCAEntry has an unscoped default manager, so filtering by
strategy__id alone reached entries on strategies the caller could not see
at all. No public or shared strategy was needed -- only a guessable
integer. strategy_entry_add/edit/delete now resolve through the parent
strategy with via="strategy".

Every SharedObject delete view carried an inverted condition:

    if obj.owner != request.user and request.user in obj.shared_with.all():
        obj.shared_with.remove(request.user)
    else:
        obj.delete()

An object its owner had made public matched neither branch's intent and
fell through to delete(), so any authenticated user could destroy it.
Confirmed reachable for accounts, account groups, categories, tags,
entities and DCA strategies. The owner now deletes, a shared user revokes
only their own access, and anyone else gets a 403.

The API viewsets had no object-level check at all. DjangoModelPermissions
gated them shut for ordinary users, who hold no model permissions, so this
was not reachable in a default install -- but the check belongs there
regardless, and a user granted change_account in the admin could write any
visible account. SharedObjectPermission adds it for the SharedObject
viewsets, leaving reads to SharedObjectManager.

account_toggle_untracked only flips the calling user's own row in the
untracked_by m2m, so it takes READ rather than EDIT.

Refs GHSA-83g9-vjqf-2j5q
2026-09-01 23:15:41 -03:00
Herculino Trotta 039ad225d3 test(rules): cover object-level authorization for transaction rules
Regression tests for GHSA-83g9-vjqf-2j5q, one per endpoint the advisory
named plus the delete and view paths found alongside them: a non-owner
gets 403 on every mutation of a public or shared rule, and the object is
asserted unchanged afterwards.

Also covers the parts that are easy to regress in the other direction:
shared users keep read access, a shared user deleting only revokes their
own access, unowned rules stay claimable, and children of invisible rules
answer 404 rather than 403.

SharedObjectPredicateParityTests asserts is_visible_to agrees with
SharedObjectManager across every owner/visibility/shared combination.
The manager builds a Q and the predicate tests an instance, so they
cannot share an implementation and can otherwise drift apart.
2026-09-01 21:43:13 -03:00
Herculino Trotta 18d4ab7d11 fix(rules): enforce object-level authorization on rule endpoints
SharedObjectManager scopes querysets to what a user may see, which
includes other people's public and shared-with-them objects. Several
mutating rule endpoints treated that visibility as permission to write.

Generalise get_owned_object_or_403 into get_shared_object_or_error, which
takes an explicit access level instead of inferring one:

- READ requires the object to be visible; denial is 404 so the response
  does not confirm that an id exists.
- EDIT requires ownership; denial is 403, but only after the visibility
  check, so 403 never leaks the existence of an invisible object. This
  matters for TransactionRuleAction, whose manager is unscoped.

The previous owner_path resolved to a User and discarded the object, so
it could not express visibility at all. via= now points at the governing
SharedObject and is resolved with a plain getattr, so an unresolvable
path raises instead of silently granting access.

is_visible_to/is_editable_by replace is_accessible_by, which was never
called and tested visibility == "shared", a value that does not exist in
Visibility.

Also fixes two further holes in the same module:

- transaction_rule_delete fell through to delete() whenever the caller
  was not in shared_with, so any user could delete a public rule. Now
  only the owner deletes; a shared user revokes their own access.
- transaction_rule_view was read-only but is now explicitly READ, so
  rules shared with a user stay viewable.

The activate/deactivate control is hidden for rules the user does not
own, instead of rendering a button that always fails.

Refs GHSA-83g9-vjqf-2j5q
2026-09-01 21:25:46 -03:00
Herculino Trotta cd48edddab feat(transactions): add htmx filter presets 2026-08-16 01:40:11 -03:00
Herculino Trotta 9e135560fe feat(transactions): add filter preset model 2026-08-16 01:40:03 -03:00
Herculino Trotta 33f0904a0f feat(dca): add chart zoom navigation 2026-08-15 15:10:10 -03:00
Herculino Trotta 8e09f3e8d8 feat(net-worth): add consolidated chart lines 2026-08-15 14:36:13 -03:00
Herculino Trotta 3bd16602a0 fix(tools:calculator): prevent close button from drifiting to the right 2026-08-15 11:41:15 -03:00
Herculino Trotta f9eab3ce17 fix(docker:dev): handle CRLF endings on vite entrypoint 2026-08-15 01:32:02 -03:00
Herculino Trotta ea3e9fd68f feat(monthly): show asset accounts/currencies on specific sumaries 2026-08-15 01:30:57 -03:00
Herculino Trotta 743951a862 feat(automatic-exchange-rates): Add Yahoo Finance as a provider via yfinance 2026-07-18 15:42:51 -03:00