Commit Graph

811 Commits

Author SHA1 Message Date
Sure Admin (bot)
0954200ad4 fix(auth): surface exact OIDC issuer mismatches (#1666)
* fix(auth): surface exact OIDC issuer mismatches

* fix(auth): align issuer mismatch hint with tests

---------

Co-authored-by: SureBot <sure-bot@we-promise.com>
2026-05-05 00:47:45 +02:00
ghost
a108e6501e feat(exports): include holding snapshots (#1643)
* feat(exports): include holding snapshots

* fix(exports): resolve holding securities without mic

* fix(exports): harden holding snapshot imports

* fix(exports): harden holding snapshot upserts

* fix(exports): keep holding upserts database-driven
2026-05-05 00:44:29 +02:00
HugoleDino
ddaf42c96c Add assurance vie to investment subtypes (#1665)
* add assurance vie in investment subtype

* add unit test for assurance vie subtype
2026-05-04 16:04:44 +02:00
ghost
98df770547 feat(exports): preserve recurring transactions (#1638)
* feat(exports): preserve recurring transactions

* fix(exports): harden recurring import records
2026-05-04 01:04:06 +02:00
ghost
911aa34ba9 feat(auth): add WebAuthn MFA credentials (#1628)
* feat(auth): add WebAuthn MFA credentials

* fix(auth): harden WebAuthn MFA review paths

* fix(auth): polish WebAuthn error handling

* fix(auth): handle duplicate WebAuthn credential races

* fix(auth): permit WebAuthn credential params

* fix(auth): trim WebAuthn registration controller cleanup

* fix(auth): tighten WebAuthn MFA handling

* fix(auth): pin WebAuthn relying party config
2026-05-03 22:13:28 +02:00
Michal Tajchert
ccd6a53071 fix(chat): eager pending AssistantMessage to fix Turbo subscribe race (#1657) (#1658)
* fix(chat): persist eager pending assistant message to fix subscribe race

When the LLM replies in ~1-2s the assistant message broadcast could
fire before the client's Turbo stream subscription was established,
leaving the UI stuck on the thinking indicator while the response was
already persisted.

Create the AssistantMessage as `pending` synchronously in
`Chat#ask_assistant_later`, so it is rendered server-side on the chat
show page with a "Thinking ..." inline placeholder. The worker then
finds and updates the existing row via `append_text!`, which flips the
status to `complete` and broadcasts updates against a DOM id that is
already in the page — no race possible. On error, the placeholder is
destroyed if no content streamed, otherwise demoted to `failed`.

Replaces the standalone thinking indicator partial and the
`Assistant::Broadcastable` thinking helpers, both now redundant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): bind each assistant job to its specific pending placeholder

Addressing review feedback on #1658:

1. The pending placeholder lookup based on `last pending` was racy —
   back-to-back user messages would let one job fill another job's
   placeholder. Pass the placeholder through the job arguments
   (`AssistantResponseJob.perform_later(user_message, pending)`) so
   each turn is bound to its own row.

2. In `Assistant::External#respond_to`, the configured/authorized
   guards raise before the local was bound, leaving rescue cleanup
   with `nil` and the placeholder visible forever. Bind the parameter
   first so cleanup can destroy it on the misconfigured path.

The kwarg defaults to nil so the API#retry path
(`AssistantResponseJob.perform_later(new_message)`) and the model-level
test calls continue to work — they fall back to an in-memory new
message, restoring the original test count assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): i18n the pending assistant placeholder string

Move the hardcoded "Thinking ..." indicator into the locale file per
CLAUDE.md i18n guidelines. With i18n.fallbacks enabled, non-en locales
fall back to English until translated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add thinking label translations

* Fix chat pending assistant expectations

* Fix external assistant pending test lookup

* Scope chat stream targets per chat

* Update message broadcast target tests

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:33:29 +02:00
ghost
6c84fc760e fix(mercury): support named multiple API connections (#1627)
* fix(mercury): support named multiple connections

* fix(mercury): address multi-connection review feedback

* fix(mercury): localize connection labels

* fix(mercury): strip API tokens before provider calls

* test(mercury): localize provider config assertions

* fix(mercury): address multi-connection review

* refactor(mercury): simplify connection selection failure
2026-05-03 10:56:31 +02:00
Sure Admin (bot)
e677d382c2 fix: send first-time SnapTrade users to connect flow (#1613)
* fix: route unregistered SnapTrade users to connect flow

* test: fix snaptrade controller sign-out helper

* fix: prefer active registered snaptrade items

* test: avoid Current.family outside request cycle

* fix: preserve snaptrade resume flow

* fix: read snaptrade resume session with indifferent keys

---------

Co-authored-by: SureBot <sure-bot@we-promise.com>
2026-05-03 10:28:31 +02:00
wps260
d74e9caf7b Optimize and Fix provider price fetches for sold securities and batch queries (#1580)
* Performance and bug fixes in provider price fetches

Three distinct bugs caused the price provider API to be called unnecessarily
on every investment account sync.

1. Sold securities triggered a provider call on every sync forever

   import_security_prices passed end_date: Date.current for every security
   ever traded. Security::Price::Importer short-circuits via all_prices_exist?
   only when persisted_count == expected_count, where:

     expected_count = (clamped_start_date..Date.current).count

   This range increases daily, so a security closed two years ago would have
   all historical prices in the DB unnecessarily.  This also causes any closed
   securities to fetch prices daily, forever.

   Fix: separate currently-held securities (end_date: Date.current) from
   historical-only securities (end_date: last holding date for that security).
   Once a closed position's price range is complete through its last holding
   date, all_prices_exist? becomes permanently stable and no further provider
   calls occur for that security.

   "Currently held" is defined as appearing in account.current_holdings, which
   returns the most recent holding per security with qty != 0. On the first
   sync after a sell, the pre-sale holding is still the most recent, so the
   security correctly receives end_date: Date.current for one final sync before
   the new qty=0 holding is materialised.

2. Offline securities were not filtered

   account.trades.map(&:security) returned all securities regardless of the
   offline flag. This results in fetching of securities even if the provider
   cannot serve them, or if the user don't want them served for some reason
   (eg when there are symbol collisions that causes the wrong prices to be
   returned) The global MarketDataImporter correctly uses Security.online;
   the account-scoped importer did not.

   Fix: Security.online.where(id: all_security_ids) matches the established
   contract. Offline IDs still pass through the pluck step but resolve to nil
   in the securities hash and are skipped by the existing `next unless security`
   guard.

3. N+1 queries for security loading and per-security start dates

   - account.trades.map(&:security): triggered one SQL query per trade to load
     the security association (N+1).
   - first_required_price_date(security): issued 2 DB queries per security -
     one MIN(entries.date) and one EXISTS - so S securities = 2S queries.

   Fix: replace with batch queries totalling 4 regardless of security count:
   - account.current_holdings.pluck(:security_id) - current security IDs
   - account.trades.pluck(:security_id).uniq - traded security IDs
   - Security.online.where(id: ...) - load all security records at once
   - batch_first_required_price_dates: one GROUP BY security_id MIN(entries.date)
     over trades, one pluck for provider-holding security IDs, one GROUP BY
     security_id MAX(date) over holdings for historical end dates

* fix(market-data-importer): fetch prices through today for reopened positions

Account::Syncer runs import_market_data before materialize_balances, so
current_holdings reflects the last materialized snapshot rather than the
current trade state. If a security was previously sold (stale holdings show
qty=0) and then repurchased in the same sync cycle, it landed in
historical_ids and had its end_date capped at the old last_holding_date.
This caused all_prices_exist? to short-circuit, skipping the price fetch
through today, and leaving the forthcoming holding materialization without
a price for the repurchase period.

Fix: compare the latest trade date against the last holding date for each
historical security. If the trade is newer, the position was reopened before
holdings were rematerialized; treat end_date as Date.current for that sync.
The cap still applies on subsequent syncs once materialize_balances has
updated the holdings table.

Adds a regression test covering the repurchase scenario.

* hoist account.start_date out of per-security loop

Account#start_date issues SELECT MIN(date) FROM entries on every call.
Inside batch_first_required_price_dates it was called up to twice per
security (holding_date assignment + fallback), producing up to 2N extra
queries for an account with N provider-held securities.

Cache the result in account_start_date before the loop.

* assert offline securities are skipped

Adds a regression test verifying that Account::MarketDataImporter never
calls fetch_security_prices for a security with offline: true, covering
the Security.online filter on line 54 of the importer.
2026-05-01 23:40:33 +02:00
ghost
c4414c4fbb feat(api): expose import status details (#1599)
* feat(api): expose import status details

* fix(api): reuse import status validation counts

* fix(api): cache Sure import status reads

* fix(imports): invalidate cached Sure import blobs

* docs(api): split import status schemas

* fix(api): refine import status detail contract
2026-05-01 22:59:32 +02:00
ghost
b710b55124 feat(api): add recurring transaction endpoints (#1600)
* feat(api): add recurring transaction endpoints

* fix(api): return validation errors for recurring writes

* fix(api): harden recurring transaction request handling

* fix(api): require writable recurring account access

* fix(api): default null recurring manual flag

* fix(api): tighten recurring transaction contracts

* test(api): align recurring transaction fixtures

* docs(api): regenerate recurring transaction OpenAPI
2026-05-01 21:21:34 +02:00
Will Wilson
2cff2065eb fix: use ProviderLoader for AuthConfig.sso_providers when DB providers enabled\n\nAuthConfig.sso_providers only read from YAML config, so self-hosted\nsetups with DB-configured SSO providers (e.g. Authentik via admin UI)\nhad no SSO button on the login page.\n\nWire it to ProviderLoader.load_providers when FeatureFlags.db_sso_providers?\nis true, falling back to YAML config otherwise. (#1614) 2026-05-01 13:31:32 +02:00
ghost
fbdcfdcab7 fix(imports): preserve Sure opening balance history (#1595) 2026-05-01 12:24:41 +02:00
ghost
072f92c715 fix(imports): preserve account status from backups (#1603) 2026-04-30 23:53:55 +02:00
wps260
c91b730122 Performance improvements in balance sync cache (#1581)
* Performance improvements in balance sync cache

Balance::SyncCache#converted_holdings called account.holdings.map { |h| h.dup }
which duplicated every holding record into a new ActiveRecord object, converted
its currency, and stored the full object in a holdings_by_date array hash.
For an investment account with years of history this allocates 100,000+
AR objects on every sync - one per holding row - creating proportional GC
pressure that scaled with account age.

The only consumer of get_holdings(date) was BaseCalculator#holdings_value_for_date,
which immediately discarded the objects after calling .sum(&:amount). The
individual holding objects were never accessed for any other attribute.

Replace the dup-and-group approach with a single aggregation pass that stores
only the per-date sum:

  holdings_value_by_date: account.holdings.each_with_object(Hash.new(0)) do |h, totals|
    converted = Money.new(h.amount, h.currency).exchange_to(account.currency, date: h.date).amount
    totals[h.date] += converted
  end

Interface change: get_holdings(date) -> get_holdings_value(date) returns a
Numeric directly rather than an Array. BaseCalculator#holdings_value_for_date
is updated accordingly, and its own per-date memoization layer is removed
since holdings_value_by_date is already fully memoized at the SyncCache level.

* fall back to 1:1 rate in SyncCache when holding exchange rate is missing; update tests to use investment class
2026-04-29 21:47:01 +02:00
maverick
ee352dada4 Added ability to bulk-edit transaction names for multiple selected transactions (#1553)
* Added ability to bulk-edit transaction names for multiple selected transactions.

* Added ability to bulk-edit transaction names for multiple selected transactions.

* Added ability to bulk-edit transaction names for multiple selected transactions.

* Lint, minimize changes

---------

Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-04-29 18:35:00 +02:00
Juan José Mata
d49250826b Improve error handling with user-friendly messages and classification (#1591)
* Improve chat LLM error messages

* Fix chat visibility regression in tests

* Harden chat error handling for review feedback

* Fix rubocop private method indentation

* Fix nil presentable_error_message, i18n strings, bare rescue

- Guard `presentable_error_message` with `return nil if error.blank?` so
  chats with no error return nil instead of the fallback string; this
  prevents the API serialisers from emitting a spurious error message and
  stops the mobile polling guard from firing on every successful chat
- Move all hardcoded user-facing error strings into
  config/locales/models/chat/en.yml and reference them via I18n.t()
- Replace bare `rescue` in `error_message_for` with `rescue StandardError`
  to avoid swallowing system-level exceptions
- Update tests to reference I18n keys instead of raw strings, and add
  tests for the nil-error case and the unrecognized-error fallback

https://claude.ai/code/session_01YFMjEds5WVyKPL42xBqMCX

---------

Co-authored-by: SureBot <sure-bot@we-promise.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-29 17:51:06 +02:00
GermanDZ
7c14c80444 Fix SimpleFIN inverting Loan account balances (#1574)
* Fix SimpleFIN inverting Loan account balances

SimplefinAccount::Processor#process_account! routes every liability
through OverpaymentAnalyzer + normalize_liability_balance. That path
is built around credit-like liabilities, where transaction history
distinguishes debt vs. credit. For a Loan account with only the
opening anchor (no payment history), the analyzer returns :unknown
and the fallback negates the observed value:

    def normalize_liability_balance(observed, bal, avail)
      ...
      -observed
    end

That's wrong for loans: the bank reports the principal outstanding
as a positive number from its own books. Negating it stores the loan
balance as negative, so BalanceSheet#net_worth = assets - liabilities
ends up _adding_ the loan instead of subtracting it (off by 2× the
loan amount). Example with a hypothetical mortgage:

  raw_balance       = 100000.00  (positive — bank's own report)
  Sure stored       = -100000.00 (negated by the fallback)
  Net worth shown   = inflated by 2 × 100000

Short-circuit Loan accountables straight to observed.abs and skip the
analyzer/fallback entirely. Loans don't have credit-vs-debt
ambiguity — if the loan is paid off the balance is 0, not negative.
Credit cards still go through the existing heuristic.

* Add observability for the SimpleFIN loan sign branch

Mirrors the logging + Sentry breadcrumb the credit-card branches emit
when the OverpaymentAnalyzer classifies as :credit / :debt, so the
loan short-circuit shows up in production traces too. Per CodeRabbit
review on #1574.

* Test that positive bank-reported loan balances are preserved

The existing "inverts negative balance for loan liabilities" test only
covers a bank that reports the loan as negative — both the old (buggy)
fallback and the new short-circuit produce the same +50000 there, which
is why the inversion bug went undetected. Add a sibling test where the
bank reports +50000 (the common mortgage convention); under the old
code that became -50000 and inflated net worth.

* Redact monetary amounts from SimpleFIN liability info logs

Move raw observed/stored amounts and metric totals from `Rails.logger.info`
and `Sentry.add_breadcrumb` payloads to a `Rails.logger.debug` line.
The info-level message and breadcrumb data now carry only identifiers
(`sfa_id`) plus the classification (`loan` / `credit` / `debt` /
`unknown`) and `tx_count`, so log aggregators and Sentry no longer
receive raw monetary values for any of the four liability branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:00:38 +02:00
GermanDZ
c5503320af Fix IndexaCapital sync, account setup, and balance/type bugs (#1562)
* Add missing IndexaCapitalItem::SyncCompleteEvent

Syncable#sync_broadcaster instantiates self.class::SyncCompleteEvent,
which is implemented for every other provider (Plaid, Lunchflow,
Mercury, etc.) but was missing for IndexaCapitalItem. The error was
swallowed by Sync#perform_post_sync's rescue, so syncs appeared to
succeed but post-sync UI broadcasts never fired:

  Error performing post-sync for IndexaCapitalItem (...):
  uninitialized constant IndexaCapitalItem::SyncCompleteEvent

This adds the class, modeled on LunchflowItem::SyncCompleteEvent,
restoring per-account and per-item Turbo broadcasts after Indexa
Capital syncs.

* Fix IndexaCapital account setup never creating accounts

complete_account_setup read params[:accounts], but the form in
setup_accounts.html.erb submits account_ids[] (array) and
sync_start_dates[<id>] (hash). The hash was always empty, so every
submit hit the empty-config branch and bounced back with
"No accounts to set up." — accounts were never created.

The controller also branched on config[:account_type] / config[:subtype]
even though the form has no account-type picker (Indexa Capital is an
investment-only broker). Rewrote complete_account_setup to consume the
form's actual params and infer the accountable type as Investment from
indexa_capital_account.account_type.

* Fix IndexaCapital balance double-count and account type

Two more issues in the IndexaCapital flow that surfaced once accounts
could actually be created (see prior commit):

1. Accountable type was inferred from indexa_capital_account.account_type
   ("mutual" / "pension"), but infer_accountable_type doesn't recognize
   those values and falls through to "Depository". The result: every
   imported Indexa account showed up as a Cash depository account
   instead of an Investment account, hiding holdings/trades surfaces.
   Indexa Capital is investment-only, so hard-code the accountable
   type to Investment.

2. Account::Processor#calculate_total_balance summed every row in
   raw_holdings_payload. Indexa returns a time series — one row per
   security per date — so the naive sum double-counts (observed:
   reported €91,633 became stored balance €180,039). Trust the API's
   current_balance when present, and if we have to fall back to a
   computed total, dedupe by instrument and take the latest-dated
   amount per security.

* Fix IndexaCapital holdings reflecting oldest snapshot per security

HoldingsProcessor#process iterated every row in raw_holdings_payload.
Indexa returns a time series (many rows per security across dates),
and each iteration upserts the same (account, security, today) holding
row, so the LAST row processed wins. The payload is ordered with
newer dates first, so the last row processed is the OLDEST snapshot —
the holdings shown in the UI reflected tiny early positions instead
of the current ones (e.g. 3.8 shares of US 500 stored vs 62.34 actual).

Reduce the payload to one row per security (latest date) before
processing. The cost-basis update is now also driven by the latest
snapshot for the same reason.

* Fix IndexaCapital holdings using per-lot detail instead of totals

Importer#normalize_holdings_response read data[:fiscal_results], which
the Indexa API returns as per-tax-lot detail — many rows per security
covering each subscription_date, plus virtual sell/buy rows generated
by rebalances. Iterating it produced wildly wrong stored holdings:
e.g. 9.61 shares stored for Vanguard US 500 vs 62.34 actual; total
weights summed to ~10% instead of 100%.

The same response also includes data[:total_fiscal_results] — one
aggregated row per security with current titles/amount/cost matching
the Indexa UI and the user-downloadable positions CSV. Prefer it,
falling back to the per-lot field only when the totals are absent.

* Address CodeRabbit review on IndexaCapital fixes

Four review items, all fixed:

* Share instrument-key extraction
  HoldingsProcessor#extract_ticker and Processor#calculate_holdings_value
  used different fallback orders (one looked at :isin, the other at
  :isin_code), so they could disagree on which rows referred to the same
  security. Moved a single extract_instrument_key helper into
  IndexaCapitalAccount::DataHelpers and routed both callers through it.

* Simplify Processor#calculate_holdings_value
  The date-based dedupe was a workaround for the bug already fixed in
  the importer (which now stores total_fiscal_results — one row per
  security). Replaced the date comparison with a per-security map
  populated via the shared key extractor. Same end result, fewer
  moving parts, no fragile string-date comparison.

* Drop dead config key passed to create_account_from_indexa_capital
  create_account_from_indexa_capital only reads :subtype and :balance
  from its config arg. Passing :sync_start_date there was inert.

* Don't mark created accounts as skipped on post-create errors
  In complete_account_setup, ensure_account_provider! and
  update!(sync_start_date:) ran inside the same begin/rescue as the
  Account.create!. If either raised after the Account row was already
  persisted, control jumped to the rescue with created_count not yet
  incremented and the account was wrongly counted as skipped. Now:
  parse the form-supplied sync_start_date up front (a malformed value
  is silently dropped instead of bubbling out of the loop), bump
  created_count immediately after persisted?, and isolate the post-
  create steps in their own rescue so failures there are logged but
  don't desync the success counter.

* Fall back to /portfolio so pension plans get holdings imported

Indexa's /accounts/{id}/fiscal-results endpoint returns
{fiscal_results: [], total_fiscal_results: []} for pension plan
accounts (e.g. type "pension"). The same positions are exposed via
/accounts/{id}/portfolio in instrument_accounts[].positions[] for
both mutual funds and pensions, so use it as a fallback when
fiscal-results is empty.

The portfolio response uses the same field names HoldingsProcessor
already understands (instrument, titles, price, amount, cost_amount)
plus a derived cost_price (cost_amount / titles) added during
adaptation. No HoldingsProcessor changes needed.

Verified against the user-downloadable "Posiciones" CSV for an
SH71ZPMY pension account: two positions (N5138 Acciones, N5137
Bonos) and balance €8,273.56 match exactly.

* Fix CI: update tests for new IndexaCapital flow + rubocop blank line

* Lint: drop trailing blank line before `end` in
  IndexaCapitalAccount::Processor (Layout/EmptyLinesAroundClassBody).

* Controller test: complete_account_setup#creates was posting
  params: { accounts: { id => { account_type:, subtype: } } } against
  the old controller schema. The new endpoint reads
  params[:account_ids] and infers Investment for Indexa Capital, so
  switch the test to that shape (and update the matching skip-already-
  linked / no-selected-accounts cases).

* Processor test: "updates account balance from holdings value" set
  current_balance: 38905.21 alongside holdings summing to 27093.01
  and asserted the latter wins. After the fix
  (calculate_total_balance prefers the API-reported current_balance
  when present), the API value is the right answer. Renamed to
  "trusts API current_balance over holdings sum when present" and
  added a sibling test that nils current_balance to exercise the
  holdings-sum fallback path explicitly (still asserts 27093.01).

* Wrap account creation+linking in a transaction to avoid orphans

complete_account_setup created the Account row first, incremented
created_count, and only then called ensure_account_provider! / the
sync_start_date update inside an inner rescue. If the link or the
sync_start_date update raised after the Account was already persisted,
control fell into the inner rescue: the orphaned Account row stayed
in the database, the failure was silently logged, and the success
counter was inflated.

Wrap creation, ensure_account_provider!, and the optional
sync_start_date update in a single ActiveRecord::Base.transaction.
Increment created_count only after the transaction commits; on any
exception the outer rescue rolls the whole step into skipped_count
with a clear log line tagged with the indexa_capital_account id.
2026-04-27 18:33:22 +02:00
Copilot
6f195c6c9c Hide nested budget categories in the Budget spent donut (#1544)
* Initial plan

* Hide nested budget categories in spent donut

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/aea0de69-f123-4417-ba31-d08300fb852d

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Harden budget donut segment test

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-04-23 21:28:38 +02:00
Roger Saner
b3c88e09f3 Feature: remember value of chart period selector (#1528)
* feat: remember chart period by last selection not user preferences

* feat: schema update

* fix: revert unnecessary parts of schema.rb update

* fix: check period key is valid before setting it

* revert: no database changes and keep the UI setting

* refactor: don't store the default period in the session, just use the user

* fix: migration

The migration uses the User model directly, which loads all current enums
including ui_layout which doesn't exist yet at that point in migration history.
Fix it with raw SQL.

* revert: not relevant to this PR
2026-04-21 19:02:41 +02:00
Sophtron Rocky
b32e9dbc45 Add Sophtron Provider (#596)
* Add Sophtron Provider

* fix syncer test issue

* fix schema  wrong merge

* sync #588

* sync code for #588

* fixed a view issue

* modified by comment

* modified

* modifed

* modified

* modified

* fixed a schema issue

* use global subtypes

* add some locales

* fix a safe_return_to_path

* fix exposing raw exception messages issue

* fix a merged issue

* update schema.rb

* fix a schema issue

* fix some issue

* Update bank sync controller to reflect beta status

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Rename settings section title to 'Sophtron (alpha)'

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Consistency in alpha/beta for Sophtron

* Good PR suggestions from CodeRabbit

---------

Signed-off-by: soky srm <sokysrm@gmail.com>
Signed-off-by: Sophtron Rocky <rocky@sophtron.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: soky srm <sokysrm@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-04-19 11:16:04 +02:00
Roger Saner
5965604359 Feature: AI sidebar hidden by default for members and guests if AI is disabled (#1510)
* feat: new guest and member has a hidden AI sidebar if AI is disabled

* test: show_ai_sidebar state when adding new users

* test: covers guests
2026-04-19 08:25:49 +02:00
LPW
0a96bf199d SimpleFIN: setup UX + same-provider relink + card-replacement detection (#1493)
* SimpleFIN: setup UX + same-provider relink + card-replacement detection

Fixes three bugs and adds auto-detection for credit-card fraud replacement.

Bugs:
- Importer: per-institution auth errors no longer flip the whole item to
  requires_update. Partial errors stay on sync_stats so other institutions
  keep syncing.
- Setup page: new activity badges (recent / dormant / empty / likely-closed)
  via SimplefinAccount::ActivitySummary. Likely-closed (dormant + near-zero
  balance + prior history) defaults to "skip" in the type picker.
- Relink: link_existing_account allows SimpleFIN to SimpleFIN swaps by
  atomically detaching the old AccountProvider inside a transaction. Adds
  "Change SimpleFIN account" menu item on linked-account dropdowns.

Feature (credit-card scope only):
- SimplefinItem::ReplacementDetector runs post-sync. Pairs a linked dormant
  zero-balance sfa with an unlinked active sfa at the same institution and
  account type. Persists suggestions on Sync#sync_stats.
- Inline banner on the SimpleFIN item card prompts relink via CustomConfirm.
  Per-pair dismiss button scoped to the current sync (resurfaces on next
  sync if still applicable). Auto-suppresses once the relink has landed.

Dev tooling:
- bin/rails simplefin:seed_fraud_scenario[email] creates a realistic broken
  pair for manual QA; cleanup_fraud_scenario reverses it.

* Address review feedback on #1493

- ReplacementDetector: symmetric one-to-one matching. Two dormant cards
  pointing at the same active card are now both skipped — previously the
  detector could emit two suggestions that would clobber each other if
  the user accepted both.
- ReplacementDetector: require non-blank institution names on both sides
  before matching. Blank-vs-blank was accidentally treated as equal,
  risking cross-provider false matches when SimpleFIN omitted org_data.
- ActivitySummary: fall back to "posted" when "transacted_at" is 0
  (SimpleFIN's "unknown" sentinel). Integer 0 is truthy in Ruby, so the
  previous `|| fallback` short-circuited and ignored posted.
- Controller: dismiss key is now the (dormant, active) pair so dismissing
  one candidate for a dormant card doesn't suppress others.
- Helper test: freeze time around "6.hours.ago" and "5.days.ago"
  assertions so they don't flake when the suite runs before 06:00.

* Address second review pass on #1493

- ReplacementDetector: canonicalize account_type in one place so filtering
  (supported_type?) and matching (type_matches?) agree on "credit card"
  vs "credit_card" variants.
- ReplacementDetector: skip candidates with nil current_balance. nil is
  "unknown," not "zero" — previously fell back to 0 and passed the near-
  zero gate, allowing suggestions without balance evidence.
2026-04-18 09:50:34 +02:00
Sure Admin (bot)
ae37c2495f Fix loan account subtype not persisting on create (#1491)
* Fix loan account subtype not persisting on create

The LoansController was missing :subtype in permitted_accountable_attributes,
causing form submissions with account[subtype] to be silently ignored.

This is the same bug that was fixed for Investment accounts in PR #1039
and Crypto accounts in PR #1022.

Fixes: loan account subtype not saving (v0.7.0-alpha.4)

* Validate loan subtype values

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/54bc6874-2cc0-43aa-ac44-9acd50316be3

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

---------

Co-authored-by: SureBot <sure-bot@we-promise.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>
2026-04-18 00:06:24 +02:00
Daniel Tschinder
d415672247 EnableBanking: use remittance for CARD-* names and merchants (#1478)
* EnableBanking: skip CARD-* counterparty in name

# Conflicts:
#	test/models/enable_banking_entry/processor_test.rb

# Conflicts:
#	test/models/enable_banking_entry/processor_test.rb

* Fix whitespace in remittance_information array

Whitespace added before 'ACME SHOP' in remittance_information.

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Fix merchant creation for Wise and prefer remittance for Entry name if counterparty is CARD-XXX

* Fix review

* Handle scalars

* Handle empty strings

* Fix review

* Make truncate not use ellipsis at the end

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: quentinreytinas <quentin@reytinas.fr>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-04-16 17:44:42 +02:00
Alessio Cappa
3eedf5137d fix(Enable Banking): Restore legacy fallback for credit card balance calculation (#1477)
* fix: Restore legacy fallback for credit card balance calculation in Enable Banking

* test: update test following new behavior

* test: keep old test

* fix: use absolute value for balance computation
2026-04-15 23:25:41 +02:00
Alessio Cappa
156694494d feat: Import pending transactions from Enable Banking only if option is enabled (#1476)
* feat: Import pending transactions from Enable Banking only if option is enabled in settings

* feat: Move include_pending checks outside of if statement

* chore: code clean-up
2026-04-15 20:49:48 +02:00
xinmotlanthua
92e1b64d03 fix: preserve Generic investment subtypes in account creation form (#1465)
* Addressable RegExp Denial of Service

* fix: preserve Generic investment subtypes in account creation form

The .compact call in Investment.subtypes_grouped_for_select removed
all nil values from the region order array, which inadvertently
excluded Generic subtypes (region: nil) from the dropdown for all
users regardless of currency setting.

Replace .compact with conditional logic that preserves nil in the
region order while still handling the user_region placement.

Closes #1446

* Breakage on `main` reverted

* style: fix SpaceInsideArrayLiteralBrackets lint offense

Add spaces inside array literal brackets to match project Rubocop rules.

---------

Co-authored-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: khanhkhanhlele <namkhanh2172@gmail.com>
2026-04-15 20:10:22 +02:00
Juan José Mata
7b2b1dd367 Rebase PR #784 and fix OpenAI model/chat regressions (#1384)
* Wire conversation history through OpenAI responses API

* Fix RuboCop hash brace spacing in assistant tests

* Pipelock ignores

* Batch fixes

---------

Co-authored-by: sokiee <sokysrm@gmail.com>
2026-04-15 18:45:24 +02:00
Juan José Mata
69827dada8 Fix transaction search account scope bypass (#1460)
Ensure accessible_account_ids filtering is applied whenever account scope is provided, including empty arrays, so users with no shared accounts cannot see family-wide transactions.

Also make totals robust when scoped queries return no rows and add regression tests for both visibility and totals behavior with empty accessible account lists.
2026-04-13 21:23:59 +02:00
Tao Chen
aacbb5ef3b Budget page refactor: split into(All - Over Budget - On Track) (#1195)
* Optimize UI in budget

* update locales

* Optimize UI

* optimize suggested_daily_spending

* try over_budget and on_track

* update locale

* optimize

* add budgets_helper.rb

* fix

* hide no buget and no expense sub-catogory

* Optimize

* Optimize button on phone

* Fix Pipelock CI noise

* using section to render both overbudget and onTrack

* hide last ruler

* fix

* update test

---------

Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-04-13 20:03:55 +02:00
Ang Wei Feng (Ted)
60929cdee0 feat: add currency management for families with enabled currencies (#1419)
* feat: add currency management for families with enabled currencies

* feat: update currency selection logic and improve accessibility

* feat: update currency preferences to use group moniker in titles

---------

Signed-off-by: Ang Wei Feng (Ted) <hello@tedawf.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-04-13 19:53:04 +02:00
Alex Kreidler
0761e8c237 Fix transactions page crash when no accounts exist (#1453)
Skip the accessible_account_ids filter when the array is empty, preventing
Rails from creating a "none" relation that causes .take to return nil.
An empty [] is truthy in Ruby, so `if accessible_account_ids` was applying
a WHERE account_id IN () clause that matched nothing.

Fixes #1452

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 13:31:00 +02:00
soky srm
e40811b1ee Add improvements from security providers to FX providers also (#1445)
* FIX prefer provider rate always

- add debugging also

* Move logic from securities over

* FIXes

* Review fixes

* Update provided.rb

---------

Signed-off-by: soky srm <sokysrm@gmail.com>
2026-04-13 00:51:23 +02:00
soky srm
c57c08c78a FIX networth chart dashboard (#1449) 2026-04-12 18:09:03 +02:00
Romain Brucker
16a0fa08f8 Add DeFi via Coinstats (#1417)
* feat: handle defi account with coinstats provider

* chore: refactor to follow project conventions

* fix: fixing codex/coderabbit findings

* fix: fixing coderabbit findings

* fix: fixing coderabbit findings

* fix: fixing coderabbit findings

* fix: fixing coderabbit findings

* fix: fixing coderabbit findings
2026-04-11 21:37:07 +02:00
Louis
7427b753e5 fix(enable-banking): refactor error handling and add missing GIN index (#1432)
* fix(enable-banking): refactor error handling and add missing GIN index

* fix(enable-banking): handle wrapped network errors and fix concurrent index migration

* fix(enable-banking): extract network errors to frozen constant

* fix(enable-banking): consolidate error handling and enforce strict localization

* fix(enable-banking): improve sync error handling and fix invalid test status

* test(enable_banking): use OpenStruct instead of mock for provider
2026-04-11 21:32:20 +02:00
soky srm
97eacc515c Investments currency fix (#1436)
* Investments currency fix

* FIX Money multiplication
2026-04-11 15:09:59 +02:00
Louis
e96fb0c23f feat(enable-banking): enhance transaction import, metadata handling, and UI (#1406)
* feat(enable-banking): enhance transaction import, metadata handling, and UI

* fix(enable-banking): address security, sync edge cases and PR feedback

* fix(enable-banking): resolve silent failures, auth overrides, and sync logic bugs

* fix(enable-banking): resolve sync logic bugs, trailing whitespaces, and apply safe_psu_headers

* test(enable-banking): mock set_current_balance to return success result

* fix(budget): properly filter pending transactions and classify synced loan payments

* style: fix trailing whitespace detected by rubocop

* refactor: address code review feedback for Enable Banking sync and reporting

---------

Signed-off-by: Louis <contact@boul2gom.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-04-10 23:19:48 +02:00
soky srm
dcebda05de Move back to brandfetch (#1427)
* Move back to brandfetch

* Update security.rb

* Update security.rb
2026-04-10 17:42:16 +02:00
soky srm
0aca297e9c Add binance security provider for crypto (#1424)
* Binance as securities provider

* Disable twelve data crypto results

* Add logo support and new currency pairs

* FIX importer fallback

* Add price clamping and optiimize retrieval

* Review

* Update adding-a-securities-provider.md

* day gap miss fix

* New fixes

* Brandfetch doesn't support crypto. add new CDN

* Update _investment_performance.html.erb
2026-04-10 15:43:22 +02:00
Louis
6551aaee0f fix(binance): fix hmac signature by using same parameter order in request and sign (#1425) 2026-04-10 15:40:13 +02:00
soky srm
7908f7d8a4 Expand financial providers (#1407)
* Initial implementation

* Tiingo fixes

* Adds 2 providers, remove 2

* Add  extra checks

* FIX a big hotwire race condition

// Fix hotwire_combobox race condition: when typing quickly, a slow response for
// an early query (e.g. "A") can overwrite the correct results for the final query
// (e.g. "AAPL"). We abort the previous in-flight request whenever a new one fires,
// so stale Turbo Stream responses never reach the DOM.

* pipelock

* Update price_test.rb

* Reviews

* i8n

* fixes

* fixes

* Update tiingo.rb

* fixes

* Improvements

* Big revamp

* optimisations

* Update 20260408151837_add_offline_reason_to_securities.rb

* Add missing tests, fixes

* small rank tests

* FIX tests

* Update show.html.erb

* Update resolver.rb

* Update usd_converter.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update _yahoo_finance_settings.html.erb
2026-04-09 18:33:59 +02:00
Juan José Mata
a30e9b75a7 Fix Binance USD converter custom rate keyword (#1414) 2026-04-08 22:23:15 +02:00
Pedro J. Aramburu
f699660479 Add exchange rate feature with multi-currency transactions and transfers support (#1099)
Co-authored-by: Pedro J. Aramburu <pedro@joakin.dev>
2026-04-08 21:05:58 +02:00
soky srm
1d7c4158d4 Merge pull request #925 from grrtt49/feature/future-budget
feat: Allow creating budgets up to 2 years ahead
2026-04-08 08:35:47 +02:00
soky srm
be42988adf Add throttling and cross-rate for twelve data (#1396)
* Add throttling and cross-rate for twelve data

* FIX yahoo precision also

* FIXES

* Update importer.rb

* Fixes

* Revert job

* Fixes
2026-04-07 20:46:05 +02:00
Copilot
ec1562782b Make parent budgets auto-aggregate from subcategory edits (#1312)
* Initial plan

* Auto-sum parent budgets from subcategory edits

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/f1c1b9ef-0e5d-4300-8f1b-e40876abfdcd

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Finalize subcategory budget parent aggregation

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/f1c1b9ef-0e5d-4300-8f1b-e40876abfdcd

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Address follow-up review on budget aggregation

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/b773decd-69a2-4da9-81ed-3be7d24cbb52

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>
2026-04-07 16:41:45 +02:00
Louis
d3469a91f2 Refactor: Use Encryptable concern in CoinbaseItem (#1339)
Remove the duplicated encryption_ready? method in favor of the Encryptable concern which provides the exact same functionality.
2026-04-07 16:28:01 +02:00