Commit Graph

258 Commits

Author SHA1 Message Date
Juan José Mata
3adc011df0 Require admin role for API family reset (#1189)
Prevent non-admin users with read_write API access from triggering family-wide reset jobs via /api/v1/users/reset.
2026-03-13 08:07:30 +01:00
soky srm
e1ff6d46ee Make categories global (#1160)
* Make categories global

This solves us A LOT of cash flow and budgeting problems.

* Update schema.rb

* Update auto_categorizer.rb

* Update income_statement.rb

* FIX budget sub-categories

* FIX sub-categories and tests

* Add 2 step migration
2026-03-11 15:54:01 +01:00
Juan José Mata
7ae9077935 Add default family selection for invite-only onboarding mode (#1174)
* Add default family selection for invite-only onboarding mode

When onboarding is set to invite-only, admins can now choose a default
family that new users without an invitation are automatically placed into
as members, instead of creating a new family for each signup.

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Restrict invite codes and onboarding settings to super_admin only

The Invite Codes section on /settings/hosting was visible to any
authenticated user via the show action, leaking all family names/IDs
through the default-family dropdown. This tightens access:

- Hide the entire Invite Codes section in the view behind super_admin?
- Add before_action :ensure_super_admin to InviteCodesController for
  all actions (index, create, destroy), replacing the inline admin? check
- Add ensure_super_admin_for_onboarding filter on hostings#update that
  blocks non-super_admin users from changing onboarding_state or
  invite_only_default_family_id

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Fix tests for super_admin-only invite codes and onboarding settings

- Hostings controller test: sign in as sure_support_staff (super_admin)
  for the onboarding_state update test, since ensure_super_admin_for_onboarding
  now requires super_admin role
- Invite codes tests: use super_admin fixture for the success case and
  verify that a regular admin gets redirected instead of raising StandardError

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Fix system test to use super_admin for self-hosting settings

The invite codes section is now only visible to super_admin users,
so the system test needs to sign in as sure_support_staff to find
the onboarding_state select element.

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Skip invite code requirement when a default family is configured

When onboarding is invite-only but a default family is set, the
claim_invite_code before_action was blocking registration before
the create action could assign the user to the default family.
Now invite_code_required? returns false when
invite_only_default_family_id is present, allowing codeless
signups to land in the configured default family.

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 18:12:53 +01:00
Juan José Mata
c09362b880 Check for pending invitations before creating new Family during SSO log in/sign up (#1171)
* Check for pending invitations before creating new Family during SSO account creation

When a user signs in via Google SSO and doesn't have an account yet, the
system now checks for pending invitations before creating a new Family.
If an invitation exists, the user joins the invited family instead.

- OidcAccountsController: check Invitation.pending in link/create_user
- API AuthController: check pending invitations in sso_create_account
- SessionsController: pass has_pending_invitation to mobile SSO callback
- Web view: show "Accept Invitation" button when invitation exists
- Flutter: show "Accept Invitation" tab/button when invitation pending

https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU

* Fix external assistant tests: clear Settings cache to prevent test pollution

The tests relied solely on with_env_overrides to clear configuration, but
rails-settings-cached may retain stale Setting values across tests when
the cache isn't explicitly invalidated. Ensure both ENV vars AND Setting
values are cleared with Setting.clear_cache before assertions.

https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 13:38:42 +01:00
Juan José Mata
f6e7234ead Enable Google SSO account creation in Flutter app (#1164)
* Add Google SSO onboarding flow for Flutter mobile app

Previously, mobile users attempting Google SSO without a linked OIDC
identity received an error telling them to link from the web app first.
This adds the same account linking/creation flow that exists on the PWA.

Backend changes:
- sessions_controller: Cache pending OIDC auth with a linking code and
  redirect back to the app instead of returning an error
- api/v1/auth_controller: Add sso_link endpoint to link Google identity
  to an existing account via email/password, and sso_create_account
  endpoint to create a new SSO-only account (respects JIT config)
- routes: Add POST auth/sso_link and auth/sso_create_account

Flutter changes:
- auth_service: Detect account_not_linked callback status, add ssoLink
  and ssoCreateAccount API methods
- auth_provider: Track SSO onboarding state, expose linking/creation
  methods and cancelSsoOnboarding
- sso_onboarding_screen: New screen with tabs to link existing account
  or create new account, pre-filled with Google profile data
- main.dart: Show SsoOnboardingScreen when ssoOnboardingPending is true

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Fix broken SSO tests: use MemoryStore cache and correct redirect param

- Sessions test: check `status` param instead of `error` since
  handle_mobile_sso_onboarding sends linking info with status key
- API auth tests: swap null_store for MemoryStore so cache-based
  linking code validation works in test environment

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Delay linking-code consumption until SSO link/create succeeds

Split validate_and_consume_linking_code into validate_linking_code
(read-only) and consume_linking_code! (delete). The code is now only
consumed after password verification (sso_link) or successful user
save (sso_create_account), so recoverable errors no longer burn the
one-time code and force a full Google SSO roundtrip.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Make linking-code consumption atomic to prevent race conditions

Move consume_linking_code! (backed by Rails.cache.delete) to after
recoverable checks (bad password, policy rejection) but before
side-effecting operations (identity/user creation). Only the first
caller to delete the cache key gets true, so concurrent requests
with the same code cannot both succeed.

- sso_link: consume after password auth, before OidcIdentity creation
- sso_create_account: consume after allow_account_creation check,
  before User creation
- Bad password still preserves the code for retry
- Add single-use regression tests for both endpoints

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Add missing sso_create_account test coverage for blank code and validation failure

- Test blank linking_code returns 400 (bad_request) with proper error
- Test duplicate email triggers user.save failure → 422 with validation errors

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Verify cache payload in mobile SSO onboarding test with MemoryStore

The test environment uses :null_store which silently discards cache
writes, so handle_mobile_sso_onboarding's Rails.cache.write was never
verified. Swap in a MemoryStore for this test and assert the full
cached payload (provider, uid, email, name, device_info,
allow_account_creation) at the linking_code key from the redirect URL.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Add rswag/OpenAPI specs for sso_link and sso_create_account endpoints

POST /api/v1/auth/sso_link: documents linking_code + email/password
params, 200 (tokens), 400 (missing code), 401 (invalid creds/expired).

POST /api/v1/auth/sso_create_account: documents linking_code +
optional first_name/last_name params, 200 (tokens), 400 (missing code),
401 (expired code), 403 (creation disabled), 422 (validation errors).

Note: RAILS_ENV=test bundle exec rake rswag:specs:swaggerize should be
run to regenerate docs/api/openapi.yaml once the runtime environment
matches the Gemfile Ruby version.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Preserve OIDC issuer through mobile SSO onboarding flow

handle_mobile_sso_onboarding now caches the issuer from
auth.extra.raw_info.iss so it survives the linking-code round trip.
build_omniauth_hash populates extra.raw_info.iss from the cached
issuer so OidcIdentity.create_from_omniauth stores it correctly.

Previously the issuer was always nil for mobile SSO-created identities
because build_omniauth_hash passed an empty raw_info OpenStruct.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Block MFA users from bypassing second factor via sso_link

sso_link authenticated with email/password but never checked
user.otp_required?, allowing MFA users to obtain tokens without
a second factor. The mobile SSO callback already rejects MFA users
with "mfa_not_supported"; apply the same guard in sso_link before
consuming the linking code or creating an identity.

Returns 401 with mfa_required: true, consistent with the login
action's MFA response shape.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Fix NoMethodError in SSO link MFA test

Replace non-existent User.generate_otp_secret class method with
ROTP::Base32.random(32), matching the pattern used in User#setup_mfa!.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Assert linking code survives rejected SSO create account

Add cache persistence assertion to "should reject SSO create account
when not allowed" test, verifying the linking code is not consumed on
the 403 path. This mirrors the pattern used in the invalid-password
sso_link test.

The other rejection tests (expired/missing linking code) don't have a
valid cached code to check, so no assertion is needed there.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-09 16:47:32 +01:00
Juan José Mata
7fce804c89 Group users by family in /admin/users (#1139)
* Display user admins grouped

* Start family/groups collapsed

* Sort by number of transactions

* Display subscription status

* Fix tests

* Use Stimulus
2026-03-06 23:38:25 +01:00
Serge L
a92fd3b3e8 feat: Enhance holding detail drawer with live price sync and enriched overview (#1086)
* Feat: Implement manual sync prices functionality and enhance holdings display

* Feat: Enhance sync prices functionality with error handling and update UI components

* Feat: Update sync prices error handling and enhance Spanish locale messages

* Fix: Address CodeRabbit review feedback

- Set fallback @provider_error when prices_updated == 0 so turbo stream
  never fails silently without a visible error message
- Move attr_reader :provider_error to class header in Price::Importer
  for conventional placement alongside other attribute declarations
- Precompute @last_price_updated in controller (show + sync_prices)
  instead of running a DB query directly inside ERB templates

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

* Fix: Replace bare rescue with explicit exception handling in turbo stream view

Bare `rescue` silently swallows all exceptions, making debugging impossible.
Match the pattern already used in show.html.erb: rescue ActiveRecord::RecordInvalid
explicitly, then catch StandardError with logging (message + backtrace) before
falling back to the unknown label.

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

* Fix: Update test assertion to expect actual provider error message

The stub returns "Yahoo Finance rate limit exceeded" as the provider error.
After the @provider_error fallback fix, the controller now correctly surfaces
the real provider error when present (using .presence || fallback), so the
flash[:alert] is the actual error string, not the generic fallback.

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

* Fix: Assert scoped security_ids in sync_prices materializer test

Replace loose stub with constructor expectation to verify that
Balance::Materializer is instantiated with the single-security scope.

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

* Fix: Assert holding remap in remap_security test

Add assertion that @holding.security_id is updated to the target
security after remap, covering the core command outcome.

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

* Fix: CI test failure - Update disconnect external assistant test to use env overrides

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 10:05:52 +01:00
LPW
84bfe5b7ab Add external AI assistant with Pipelock security proxy (#1069)
* feat(helm): add Pipelock ConfigMap, scanning config, and consolidate compose

- Add ConfigMap template rendering DLP, response scanning, MCP input/tool
  scanning, and forward proxy settings from values
- Mount ConfigMap as /etc/pipelock/pipelock.yaml volume in deployment
- Add checksum/config annotation for automatic pod restart on config change
- Gate HTTPS_PROXY/HTTP_PROXY env injection on forwardProxy.enabled (skip
  in MCP-only mode)
- Use hasKey for all boolean values to prevent Helm default swallowing false
- Single source of truth for ports (forwardProxy.port/mcpProxy.port)
- Pipelock-specific imagePullSecrets with fallback to app secrets
- Merge standalone compose.example.pipelock.yml into compose.example.ai.yml
- Add pipelock.example.yaml for Docker Compose users
- Add exclude-paths to CI workflow for locale file false positives

* Add external assistant support (OpenAI-compatible SSE proxy)

Allow self-hosted instances to delegate chat to an external AI agent
via an OpenAI-compatible streaming endpoint. Configurable per-family
through Settings UI or ASSISTANT_TYPE env override.

- Assistant::External::Client: SSE streaming HTTP client (no new gems)
- Settings UI with type selector, env lock indicator, config status
- Helm chart and Docker Compose env var support
- 45 tests covering client, config, routing, controller, integration

* Add session key routing, email allowlist, and config plumbing

Route to the actual OpenClaw session via x-openclaw-session-key header
instead of creating isolated sessions. Gate external assistant access
behind an email allowlist (EXTERNAL_ASSISTANT_ALLOWED_EMAILS env var).
Plumb session_key and allowedEmails through Helm chart, compose, and
env template.

* Add HTTPS_PROXY support to External::Client for Pipelock integration

Net::HTTP does not auto-read HTTPS_PROXY/HTTP_PROXY env vars (unlike
Faraday). Explicitly resolve proxy from environment in build_http so
outbound traffic to the external assistant routes through Pipelock's
forward proxy when enabled. Respects NO_PROXY for internal hosts.

* Add UI fields for external assistant config (Setting-backed with env fallback)

Follow the same pattern as OpenAI settings: database-backed Setting
fields with env var defaults. Self-hosters can now configure the
external assistant URL, token, and agent ID from the browser
(Settings > Self-Hosting > AI Assistant) instead of requiring env vars.
Fields disable when the corresponding env var is set.

* Improve external assistant UI labels and add help text

Change placeholder to generic OpenAI-compatible URL pattern. Add help
text under each field explaining where the values come from: URL from
agent provider, token for authentication, agent ID for multi-agent
routing.

* Add external assistant docs and fix URL help text

Add External AI Assistant section to docs/hosting/ai.md covering setup
(UI and env vars), how it works, Pipelock security scanning, access
control, and Docker Compose example. Drop "chat completions" jargon
from URL help text.

* Harden external assistant: retry logic, disconnect UI, error handling, and test coverage

- Add retry with backoff for transient network errors (no retry after streaming starts)
- Add disconnect button with confirmation modal in self-hosting settings
- Narrow rescue scope with fallback logging for unexpected errors
- Safe cleanup of partial responses on stream interruption
- Gate ai_available? on family assistant_type instead of OR-ing all providers
- Truncate conversation history to last 20 messages
- Proxy-aware HTTP client with NO_PROXY support
- Sanitize protocol to use generic headers (X-Agent-Id, X-Session-Key)
- Full test coverage for streaming, retries, proxy routing, config, and disconnect

* Exclude external assistant client from Pipelock scan-diff

False positive: `@token` instance variable flagged as "Credential in URL".
Temporary workaround until Pipelock supports inline suppression.

* Address review feedback: NO_PROXY boundary fix, SSE done flag, design tokens

- Fix NO_PROXY matching to require domain boundary (exact match or .suffix),
  case-insensitive. Prevents badexample.com matching example.com.
- Add done flag to SSE streaming so read_body stops after [DONE]
- Move MAX_CONVERSATION_MESSAGES to class level
- Use bg-success/bg-destructive design tokens for status indicators
- Add rationale comment for pipelock scan exclusion
- Update docs last-updated date

* Address second round of review feedback

- Allowlist email comparison is now case-insensitive and nil-safe
- Cap SSE buffer at 1 MB to prevent memory blowup from malformed streams
- Don't expose upstream HTTP response body in user-facing errors (log it instead)
- Fix frozen string warning on buffer initialization
- Fix "builtin" typo in docs (should be "built-in")

* Protect completed responses from cleanup, sanitize error messages

- Don't destroy a fully streamed assistant message if post-stream
  metadata update fails (only cleanup partial responses)
- Log raw connection/HTTP errors internally, show generic messages
  to users to avoid leaking network/proxy details
- Update test assertions for new error message wording

* Fix SSE content guard and NO_PROXY test correctness

Use nil check instead of present? for SSE delta content to preserve
whitespace-only chunks (newlines, spaces) that can occur in code output.

Fix NO_PROXY test to use HTTP_PROXY matching the http:// client URL so
the proxy resolution and NO_PROXY bypass logic are actually exercised.

* Forward proxy credentials to Net::HTTP

Pass proxy_uri.user and proxy_uri.password to Net::HTTP.new so
authenticated proxies (http://user:pass@host:port) work correctly.
Without this, credentials parsed from the proxy URL were silently
dropped. Nil values are safe as positional args when no creds exist.

* Update pipelock integration to v0.3.1 with full scanning config

Bump Helm image tag from 0.2.7 to 0.3.1. Add missing security
sections to both the Helm ConfigMap and compose example config:
mcp_tool_policy, mcp_session_binding, and tool_chain_detection.
These protect the /mcp endpoint against tool injection, session
hijacking, and multi-step exfiltration chains.

Add version and mode fields to config files. Enable include_defaults
for DLP and response scanning to merge user patterns with the 35
built-in patterns. Remove redundant --mode CLI flag from the Helm
deployment template since mode is now in the config file.
2026-03-03 15:47:51 +01:00
Ang Wei Feng (Ted)
4db5737c9c fix: maintain activity tab during pagination from holdings tab (#1096) 2026-03-02 22:44:24 +01:00
Dream
91b79053fd Fix export polling missing template error (#796) (#721)
The polling controller was requesting turbo_stream format but only an
HTML template exists. Fix the Accept header to request text/html and
handle both formats in the controller.
2026-02-25 17:09:51 -05:00
LPW
17e9bb8fbf Add MCP server endpoint for external AI assistants (#1051)
* Add MCP server endpoint for external AI assistants

Expose Sure's Assistant::Function tools via JSON-RPC 2.0 at POST /mcp,
enabling external AI clients (Claude, GPT, etc.) to query financial data
through the Model Context Protocol.

- Bearer token auth via MCP_API_TOKEN / MCP_USER_EMAIL env vars
- JSON-RPC 2.0 with proper id threading, notification handling (204)
- Transient session (sessions.build) to prevent impersonation leaks
- Centralize function_classes in Assistant module
- Docker Compose example with Pipelock forward proxy
- 18 integration tests with scoped env (ClimateControl)

* Update compose for full Pipelock MCP reverse proxy integration

Use Pipelock's --mcp-listen/--mcp-upstream flags (PR #127) to run
bidirectional MCP scanning in the same container as the forward proxy.
External AI clients connect to port 8889, Pipelock scans requests
(DLP, injection, tool policy) and responses (injection, tool poisoning)
before forwarding to Sure's /mcp endpoint.

This supersedes the standalone compose in PR #1050.

* Fix compose --preset→--mode, add port 3000 trust comment, notification test

Review fixes:
- pipelock run uses --mode not --preset (would prevent stack startup)
- Document port 3000 exposes /mcp directly (auth still required)
- Add version requirement note for Pipelock MCP listener support
- Add test: tools/call sent as notification does not execute
2026-02-23 09:13:15 -05:00
Juan José Mata
ad3087f1dd Improvements to Flutter client (#1042)
* Chat improvements

* Delete/reset account via API for Flutter app

* Fix tests.

* Add "contact us" to settings

* Update mobile/lib/screens/chat_conversation_screen.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Improve LLM special token detection

* Deactivated user shouldn't have API working

* Fix tests

* API-Key usage

* Flutter app launch failure on no network

* Handle deletion/reset delays

* Local cached data may become stale

* Use X-Api-Key correctly!

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-22 21:22:32 -05:00
dataCenter430
cfadff641f Fix crypto subtype for trades api (#1022)
* fix: crypto subtype not persisted by permitting :subtype in CryptosController

* Backfill crypto subtype for existig accounts so Trades API works

* fix: backfill only unlinked cryptos; use raw SQL in migration; deterministic redirect in test

* Update schema.rb for BackfillcryptoSubtypeForTrades migration

---------

Signed-off-by: dataCenter430 <161712630+dataCenter430@users.noreply.github.com>
2026-02-19 19:51:42 +01:00
Juan José Mata
76bc12cf7b Improve tests 2026-02-16 19:17:58 +00:00
Juan José Mata
06fedb34f3 Add new columns and sorting to admin users list (#1004)
* Add trial end date to admin users list

* Add new columns

* Regression
2026-02-16 20:10:14 +01:00
Juan José Mata
b48cec3a2e fix: Transfers were not syncing between accounts (#987)
* fix: Include investment_contribution in transfer? check and protect transfer entries from sync

Transfer transactions with kind "investment_contribution" were not recognized
as transfers by the UI, causing missing +/- indicators, "Transfer" labels,
and showing regular transaction forms instead of transfer details.

Also adds user_modified: true to entries created via TransferMatchesController
and SetAsTransferOrPayment rule action to protect them from provider sync
overwrites, matching the existing behavior in Transfer::Creator.

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

* fix: Centralize transfer/budget kind constants for consistent investment_contribution handling

Define TRANSFER_KINDS and BUDGET_EXCLUDED_KINDS on Transaction to eliminate
hard-coded kind lists scattered across filters, rules, and analytics code.

investment_contribution is now consistently treated as a transfer in search
filters, rule conditions, and UI display (via TRANSFER_KINDS), while budget
analytics correctly continue treating it as an expense (via BUDGET_EXCLUDED_KINDS).

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

* fix: Update tests for consistent investment_contribution as transfer kind

- search_test: loan_payment is now in TRANSFER_KINDS, so uncategorized
  filter correctly excludes it (same as funds_movement/cc_payment)
- condition_test: investment_contribution is now a transfer kind, so it
  matches the transfer filter rather than expense filter

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

* fix: Eliminate SQL injection warnings in Transaction::Search

Replace string-interpolated SQL with parameterized queries:
- totals: use sanitize_sql_array with ? placeholders
- apply_category_filter: pass TRANSFER_KINDS as bind parameter
- apply_type_filter: use where(kind:)/where.not(kind:) and
  parameterized IN (?) for compound OR conditions
- Remove unused transfer_kinds_sql helper

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 13:50:06 +01:00
Mark Hendriksen
f5469ea774 Replace text-tertiary with text-subdued in views (#999)
* Replace text-tertiary with text-subdued in views

Replace usages of the text-tertiary utility with text-subdued across several view partials to standardize subdued text styling because text-tertiary does not exist in the design system (reports, doorkeeper auth, simplefin items). Also adjust the net worth empty-liabilities markup to use a grid layout for consistent spacing, and update the related controller test selector to match the new CSS class.

* Standardize empty net worth message markup

Replace inconsistent markup and classes for empty asset/liability sections in the net worth partial. Swap text-secondary/p-2/text-center for text-subdued with unified padding (py-3 px-4 lg:px-6), and simplify the liabilities block from a grid/div to a single paragraph for consistent styling and spacing.
2026-02-15 23:33:41 +01:00
Juan José Mata
8264943f23 Show disabled import options when no accounts exist (#977)
* Show disabled import options before accounts exist

Keep account-dependent import choices visible on /imports/new and render them as disabled with guidance when no accounts are available.

* Refactor disabled import options: extract partial, fix accessibility (#986)

- Extract _import_option partial to eliminate duplicated enabled/disabled
  markup across TransactionImport, TradeImport, and MintImport (also
  used by AccountImport, CategoryImport, RuleImport for consistency)
- Replace misleading chevron-right with lock icon in disabled state
- Add aria-disabled="true" for screen reader accessibility
- Remove redundant default: parameter from t() call
- Fix locale key ordering (requires_account after import_* keys)
- Fix extra blank line in test file
- Add assertion for aria-disabled attribute in test

https://claude.ai/code/session_016j9tDYEBfWX9Dzd99rAYjX

Co-authored-by: Claude <noreply@anthropic.com>

* Tailwind fixes

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-14 01:10:25 +01:00
Juan José Mata
bf0be85859 Expose ui_layout and ai_enabled to mobile clients and add enable_ai endpoint (#983)
* Wire ui layout and AI flags into mobile auth

Include ui_layout and ai_enabled in mobile login/signup/SSO payloads,
add an authenticated endpoint to enable AI from Flutter, and gate
mobile navigation based on intro layout and AI consent flow.

* Linter

* Ensure write scope on enable_ai

* Make sure AI is available before enabling it

* Test improvements

* PR comment

* Fix review issues: test assertion bug, missing coverage, and Dart defaults (#985)

- Fix login test to use ai_enabled? (method) instead of ai_enabled (column)
  to match what mobile_user_payload actually serializes
- Add test for enable_ai when ai_available? returns false (403 path)
- Default aiEnabled to false when user is null in AuthProvider to avoid
  showing AI as available before authentication completes
- Remove extra blank lines in auth_provider.dart and auth_service.dart

https://claude.ai/code/session_01LEYYmtsDBoqizyihFtkye4

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-14 00:39:03 +01:00
Pluto
e99e38a91c fix: Handle empty compound conditions on rules index (#965)
* fix: Handle empty compound conditions on rules index

* fix: avoid contradictory rule condition summary on /rules

* refactor: move rules condition display logic from view to model

* fix: localize rule title fallback and preload conditions in rules index
2026-02-13 19:53:24 +01:00
Juan José Mata
868a0ae4d8 Add family moniker selection and dynamic UI labels (#981)
* Add family moniker selection and dynamic UI labels

Introduce a Family moniker persisted in the database with allowed values Family/Group, add required onboarding selection for it, and thread moniker-aware copy through key user-facing views and locales. Also add helper methods and tests for onboarding form presence and family moniker behavior.

* Small copy edits/change moniker question order

* Conditional Group/Family onboarding flow fixes

* Fix label

* Grouping of fields

* Profile Info page Group/Family changes

* Only admins can change Group/Family moniker

* Repetitive defaults

* Moniker in Account model

* Moniker in User model

* Auth fix

* Sure product is also a moniker

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
2026-02-13 19:30:29 +01:00
Juan José Mata
d3c4986bfd Fix flaky expectations in import-related tests (#963) 2026-02-11 19:42:26 +01:00
Juan José Mata
0eedd533bb Generalize from PDF import to just files 2026-02-11 17:56:49 +00:00
Juan José Mata
4b0986220f Remove Flipper and replace with ENV-driven FeatureFlags (#957)
* Presence of valid DEFAULT_UI_LAYOUT is sufficient

* Linter
2026-02-10 23:30:45 +01:00
Juan José Mata
6a95c91d73 Forgot to make test Flipper-conditional 2026-02-09 12:52:52 +00:00
Juan José Mata
705b5a8b26 First cut of a simplified "intro" UI layout (#265)
* First cut of a simplified "intro" UI layout

* Linter

* Add guest role and intro-only access

* Fix guest role UI defaults (#940)

Use enum predicate to avoid missing role helper.

* Remove legacy user role mapping (#941)

Drop the unused user role references in role normalization
and SSO role mapping forms to avoid implying a role that
never existed.

Refs: #0

* Remove role normalization (#942)

Remove role normalization

Roles are now stored directly without legacy mappings.

* Revert role mapping logic

* Remove `normalize_role_settings`

* Remove unnecessary migration

* Make `member` the default

* Broken `.erb`

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
2026-02-09 11:09:25 +01:00
David Gil
ba442d5f26 Implement Indexa Capital provider with real API integration (#933)
* Add Indexa Capital provider scaffold

Generate Indexa Capital provider scaffolding and align credential fields with the API authentication requirements.

* Fix PR 926 lint and schema CI failures

* Implement Indexa Capital provider with real API integration

- Rewrite all broken view templates (were meta-ERB from code generator)
- Create missing select_accounts.html.erb template
- Implement real API calls: list_accounts via /users/me, get_holdings
  via /accounts/{number}/fiscal-results, get_account_balance via
  /accounts/{number}/performance
- Add API token auth support (stored token > env token > credentials)
- Add api_token column with encryption support
- Redesign settings panel: API token prominent, credentials collapsible
- Fix account balances display using performance endpoint portfolios
- Fix accounts index empty-state guard missing indexa_capital_items
- Simplify activities fetch job (no activities API endpoint exists)
- Fix i18n interpolation (%%{ -> %{) throughout locale file

* Add tests for Indexa Capital provider integration

- IndexaCapitalItemTest: validations, credentials, scopes, sync status
- IndexaCapitalAccountTest: upsert, holdings, account provider linking
- Provider::IndexaCapitalTest: auth modes, API stubs, error handling
- IndexaCapitalItemsControllerTest: CRUD, setup, linking, authorization
- Fixtures for items (token + credentials) and accounts (mutual + pension)

52 tests, 98 assertions, 0 failures

* Address code review feedback from PR #933

- Fix zero balance bug: use `nil?` instead of `present?` so 0 is stored
- Fix has_indexa_capital_credentials? to check api_token (was ignored)
- Fix build_provider to delegate to Provided concern (was ignoring token)
- Fix IndexaCapital section outside encryption_error guard in settings
- Add account_number sanitization to prevent path traversal in API URLs
- Replace all skipped processor tests with real working tests
- Add zero-balance and path-traversal test coverage

61 tests, 107 assertions, 0 failures

* Address code review round 2: credentials validation, RuboCop, test quality

- Fix RuboCop SpaceInsideArrayLiteralBrackets in credentials check
- Chain where.not calls so all three username/document/password must be present
- Require all three credentials (||) instead of any one (&&) in validate_configuration!
- Move attr_reader to private to avoid exposing credentials publicly
- Parse dates with Date.parse in extract_balance for robustness
- Remove stale TODO and Crypto from supported_account_types
- Order build_provider query deterministically by created_at
- Replace no-op holdings assertion with meaningful assert_difference

* Address code review round 3: JSON parse safety and test precision

- Rescue JSON::ParserError on 2xx responses for clearer error messages
- Fix weak balance assertion: set balance to 0 before processing, assert
  expected value (27093.01 = sum of holdings amounts)

* Include Indexa Capital in automatic family sync

Add indexa_capital_items to Family::Syncer#child_syncables so balances
and holdings refresh on daily auto-sync and login sync, not only on
manual sync button clicks.

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-02-08 18:19:37 +01:00
LPW
24981ffd52 Add "Link to existing" option in SnapTrade Setup Accounts modal (#935)
* Add account linking functionality for SnapTrade items

- Introduced UI to link existing accounts when setting up SnapTrade items, preventing duplicate account creation.
- Updated controller to fetch linkable accounts.
- Added tests to verify proper filtering of accounts and linking behavior.

* Add `snaptrade_item_id` to account linking flow for SnapTrade items

- Updated controller to allow specifying `snaptrade_item_id` when linking accounts.
- Adjusted form and views to include `snaptrade_item_id` as a hidden field.
- Enhanced tests to validate behavior with the new parameter.
2026-02-08 10:30:46 +01:00
LPW
01c2209492 Add tests for SnapTrade error handling and refine unlink behavior (#931)
- Introduced new tests to cover SnapTrade decryption and connection errors in `SnaptradeItemsControllerTest`.
- Updated error messages for improved user clarity.
- Modified `unlink` functionality to preserve `SnaptradeAccount` records while ensuring proper detachment of associated holdings.
2026-02-07 23:15:18 +01:00
LPW
36661bdc9b Auto-categorize investment contributions across all transfer paths (#924)
* Ensure investment contributions are auto-categorized with proper kind and category creation.

* Retrigger CI
2026-02-07 16:41:31 +01:00
Dream
6779951194 Fix mobile login "Record not found" for unseeded instances (#916)
* Auto-create mobile OAuth application when missing (#912)

Self-hosted users who set up their instance without running `db:seed`
(or reset their database) got "Record not found" on mobile login because
`MobileDevice.shared_oauth_application` used `find_by!` which raises
when the "Sure Mobile" Doorkeeper application does not exist.

Switch to `find_or_create_by!` so the record is created transparently
on first use, matching the attributes from the seed file.

* Nice Claude Code suggestion

---------

Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-02-06 18:04:29 +01:00
MkDev11
87117445fe Fix OIDC household invitation (issue #900) (#904)
* Fix OIDC household invitation (issue #900)

- Auto-add existing user when inviting by email (no invite email sent)
- Accept page: choose 'Create account' or 'Sign in' (supports OIDC)
- Store invitation token in session on sign-in; accept after login (password,
  OIDC, OIDC link, OIDC JIT, MFA)
- Invitation#accept_for!(user): add user to household and mark accepted
- Defensive guards: nil/blank user, token normalization, accept_for! return check

* Address PR review: rename accept_for! to accept_for, i18n OIDC notice, test fixes, stub Rails.application.config

* Fix flaky system test: assert only configure step, not flash message

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: mkdev11 <jaysmth689+github@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 16:14:42 +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
Dream
ca3abd5d8b Add Google Sign-In (SSO) support to Flutter mobile app (#860)
* Add mobile SSO support to sessions controller

Add /auth/mobile/:provider route and mobile_sso_start action that
captures device params in session and renders an auto-submitting POST
form to OmniAuth (required by omniauth-rails_csrf_protection).

Modify openid_connect callback to detect mobile_sso session, issue
Doorkeeper tokens via MobileDevice, and redirect to sureapp://oauth/callback
with tokens. Handles MFA users and unlinked accounts with error redirects.

Validates provider name against configured SSO providers and device info
before proceeding.

* Add SSO auth flow to Flutter service and provider

Add buildSsoUrl() and handleSsoCallback() to AuthService for
constructing the mobile SSO URL and parsing tokens from the deep
link callback.

Add startSsoLogin() and handleSsoCallback() to AuthProvider for
launching browser-based SSO and processing the redirect.

* Register deep link listener for SSO callback

Listen for sureapp://oauth/* deep links via app_links package,
handling both cold start (getInitialLink) and warm (uriLinkStream)
scenarios. Routes callbacks to AuthProvider.handleSsoCallback().

* Add Google Sign-In button to Flutter login screen

Add "or" divider and outlined Google Sign-In button that triggers
browser-based SSO via startSsoLogin('google_oauth2').

Add app_links and url_launcher dependencies to pubspec.yaml.

* Fix mobile SSO failure handling to redirect back to app

When OmniAuth fails during mobile SSO flow, redirect to
sureapp://oauth/callback with the error instead of the web login page.
Cleans up mobile_sso session data on failure.

* Address PR review feedback for mobile SSO flow

- Use strong params for device info in mobile_sso_start
- Guard against nil session data in handle_mobile_sso_callback
- Add error handling for AppLinks initialization and stream
- Handle launchUrl false return value in SSO login
- Use user-friendly error messages instead of exposing exceptions
- Reject empty token strings in SSO callback validation

* Consolidate mobile device token logic into MobileDevice model

Extract duplicated device upsert and token issuance code from
AuthController and SessionsController into MobileDevice. Add
CALLBACK_URL constant and URL builder helpers to eliminate repeated
deep-link strings. Add mobile SSO integration tests covering the
full flow, MFA rejection, unlinked accounts, and failure handling.

* Fix CI: resolve Brakeman redirect warnings and rubocop empty line

Move mobile SSO redirect into a private controller method with an
inline string literal so Brakeman can statically verify the target.
Remove unused URL builder helpers from MobileDevice. Fix extra empty
line at end of AuthController class body.

* Use authorization code exchange for mobile SSO and add signup error handling

Replace passing plaintext tokens in mobile SSO redirect URLs with a
one-time authorization code pattern. Tokens are now stored server-side
in Rails.cache (5min TTL) and exchanged via a secure POST to
/api/v1/auth/sso_exchange. Also wraps device/token creation in the
signup action with error handling and sanitizes device error messages.

* Add error handling for login device registration and blank SSO code guard

* Address PR #860 review: fix SSO race condition, add OpenAPI spec, and cleanup

- Fix race condition in sso_exchange by checking Rails.cache.delete return
  value to ensure only one request can consume an authorization code
- Use strong parameters (params.require) for sso_exchange code param
- Move inline HTML from mobile_sso_start to a proper view template
- Clear stale session[:mobile_sso] flag on web login paths to prevent
  abandoned mobile flows from hijacking subsequent web SSO logins
- Add OpenAPI/rswag spec for all auth API endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix mobile SSO test to match authorization code exchange pattern

The test was asserting tokens directly in the callback URL, but the code
uses an authorization code exchange pattern. Updated to exchange the code
via the sso_exchange API endpoint. Also swaps in a MemoryStore for this
test since the test environment uses null_store which discards writes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Refactor mobile OAuth to use single shared application

Replace per-device Doorkeeper::Application creation with a shared
"Sure Mobile" OAuth app. Device tracking uses mobile_device_id on
access tokens instead of oauth_application_id on mobile_devices.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 00:45:11 +01:00
David Gil
b08285a10f API v1: add amount_cents + signed_amount_cents to transactions (#899)
* feat(api): add amount_cents + signed_amount_cents to transactions

* fix: use currency.minor_unit_conversion for amount_cents

- Replace hardcoded *100 with currency.minor_unit_conversion
- Handles JPY (0 decimals), KWD/BHD (3 decimals), etc. correctly
- Add assert_amount_cents_fields helper to validate sign/scale invariants
2026-02-04 22:55:50 +01:00
MkDev11
0afdb1d0fd Feature/pdf import transaction rows (#846)
* Add import row generation from PDF extracted data

- Add generate_rows_from_extracted_data method to PdfImport
- Add import! method to create transactions from PDF rows
- Update ProcessPdfJob to generate rows after extraction
- Update configured?, cleaned?, publishable? for PDF workflow
- Add column_keys, required_column_keys, mapping_steps
- Set bank statements to pending status for user review
- Add tests for new functionality

Closes #844

* Add tests for BankStatementExtractor

- Test transaction extraction from PDF content
- Test deduplication across chunk boundaries
- Test amount normalization for various formats
- Test graceful handling of malformed JSON responses
- Test error handling for empty/nil PDF content

* Fix supports_pdf_processing? to validate effective model

The validation was always checking @default_model, but process_pdf
allows overriding the model via parameter. This could cause a
vision-capable override model to be rejected, or a non-vision-capable
override to pass validation only to fail during processing.

Changes:
- supports_pdf_processing? now accepts optional model parameter
- process_pdf passes effective model to validation
- Raise Provider::Openai::Error inside with_provider_response for
  consistent error handling

Addresses review feedback from PR#808

* Fix insert_all! bug: explicitly set import_id

Rails insert_all! on associations does NOT auto-set the foreign key.
Added import_id explicitly and use Import::Row.insert_all! directly.
Also reload rows before counting to ensure accurate count.

* Fix pending status showing as processing for bank statements with rows

When bank statement PDF imports have extracted rows, show a 'Ready for Review'
screen with a link to the confirm path instead of the 'Processing' spinner.

This addresses the PR feedback that users couldn't reach the review flow even
though rows were created.

* Gate publishable? on account.present? to prevent import failure

PDF imports are created without an account, and import! raises if account
is missing. This prevents users from hitting publish and having the job fail.

* Wrap generate_rows_from_extracted_data in transaction for atomicity

- Clear rows and reset count even when no transactions extracted
- Use transaction block to prevent partial updates on failure
- Use mapped_rows.size instead of reload for count

* Localize transactions count string with i18n helper

* Add AccountMapping step for PDF imports when account is nil

PDF imports need account selection before publishing. This adds
Import::AccountMapping to mapping_steps when account is nil,
matching the behavior of TransactionImport and TradeImport.

Addresses PR#846 feedback about account selection for PDF imports.

* Only include CategoryMapping when rows have non-empty categories

PDF extraction doesn't extract categories from bank statements,
so the CategoryMapping step would show empty. Now we only include
CategoryMapping if rows actually have non-empty category values.

This prevents showing an empty mapping step for PDF imports.

* Fix PDF import UI flow and account selection

- Add direct account selection in PDF import UI instead of AccountMapping
- AccountMapping designed for CSV imports with multiple account values
- PDF imports need single account for all transactions
- Add update action and route for imports controller
- Fix controller to handle pdf_import param format from form_with
- Show Publish button when import is publishable (account set)
- Fix stepper nav: Upload/Configure/Clean non-clickable for PDF imports
- Redirect PDF imports from configuration step (auto-configured)
- Improve AI prompt to recognize M-PESA/mobile money as bank statements
- Fix migration ordering for import_rows table columns

* Add guard for invalid account_id in imports#update

Prevents silently clearing account when invalid ID is passed.
Returns error message instead of confusing 'Account saved' notice.

* Localize step names in import nav and add account guard

- Use t() helper for all step names (Upload, Configure, Clean, Map, Confirm)
- Add guard for invalid account_id in imports#update
- Prevents silently clearing account when invalid ID is passed

* Make category column migrations idempotent

Check if columns exist before adding to prevent duplicate column
errors when migrations are re-run with new timestamps.

* Add match_path for PDF import step highlighting

Fixes step detection when path is nil by using separate match_path
for current step highlighting while keeping links disabled.

* Rename category migrations and update to Rails 7.2

- Rename class to EnsureCategoryFieldsOnImportRows to avoid conflicts
- Rename class to EnsureCategoryIconOnImportRows
- Update migration version from 7.1 to 7.2 per guidelines
- Rename files to match class names
- Add match_path for PDF import step highlighting

* Use primary (black) style for Create Account and Save buttons

* Remove match_path from auto-completed PDF steps

Only step 4 (Confirm) needs match_path for active-step detection.
Steps 1-3 are purely informational and always complete.

* Add fallback for document type translation

Handles nil or unexpected document_type values gracefully.
Also removes match_path from auto-completed PDF steps.

* Use index-based step number for mobile indicator

Fixes 'Step 5 of 4' issue when Map step is dynamically removed.

* Fix hostings_controller_test: use blank? instead of nil

Setting returns empty string not nil for unset values.

* Localize step progress label and use design token

* Fix button styling: use design system Tailwind classes

btn--primary and btn--secondary CSS classes don't exist.
Use actual design system classes from DS::Buttonish.

* Fix CRLF line endings in tags_controller_test.rb

---------

Co-authored-by: mkdev11 <jaysmth689+github@users.noreply.github.com>
2026-02-02 16:27:02 +01:00
Juan José Mata
4f60aef6a4 chore: normalize line endings in tags_controller_test.rb (#848)
Convert CRLF to LF line endings for consistency.

https://claude.ai/code/session_012VFw2enx8PaDGqoHYqZ6jP

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-31 10:27:24 +01:00
MkDev11
6f8858b1a6 feat/Add AI-Powered Bank Statement Import (step 1, PDF import & analysis) (#808)
* feat: Add PDF import with AI-powered document analysis

This enhances the import functionality to support PDF files with AI-powered
document analysis. When a PDF is uploaded, it is processed by AI to:
- Identify the document type (bank statement, credit card statement, etc.)
- Generate a summary of the document contents
- Extract key metadata (institution, dates, balances, transaction count)

After processing, an email is sent to the user asking for next steps.

Key changes:
- Add PdfImport model for handling PDF document imports
- Add Provider::Openai::PdfProcessor for AI document analysis
- Add ProcessPdfJob for async PDF processing
- Add PdfImportMailer for user notification emails
- Update imports controller to detect and handle PDF uploads
- Add PDF import option to the new import page
- Add i18n translations for all new strings
- Add comprehensive tests for the new functionality

* Add bank statement import with AI extraction

- Create ImportBankStatement assistant function for MCP
- Add BankStatementExtractor with chunked processing for small context windows
- Register function in assistant configurable
- Make PdfImport#pdf_file_content public for extractor access
- Increase OpenAI request timeout to 600s for slow local models
- Increase DB connection pool to 20 for concurrent operations

Tested with M-Pesa bank statement via remote Ollama (qwen3:8b):
- Successfully extracted 18 transactions
- Generated CSV and created TransactionImport
- Works with 3000 char chunks for small context windows

* Add pdf-reader gem dependency

The BankStatementExtractor uses PDF::Reader to parse bank statement
PDFs, but the gem was not properly declared in the Gemfile. This would
cause NameError in production when processing bank statements.

Added pdf-reader ~> 2.12 to Gemfile dependencies.

* Fix transaction deduplication to preserve legitimate duplicates

The previous deduplication logic removed ALL duplicate transactions based
on [date, amount, name], which would drop legitimate same-day duplicates
like multiple ATM withdrawals or card authorizations.

Changed to only deduplicate transactions that appear in consecutive chunks
(chunking artifacts) while preserving all legitimate duplicates within the
same chunk or non-adjacent chunks.

* Refactor bank statement extraction to use public provider method

Address code review feedback:
- Add public extract_bank_statement method to Provider::Openai
- Remove direct access to private client via send(:client)
- Update ImportBankStatement to use new public method
- Add require 'set' to BankStatementExtractor
- Remove PII-sensitive content from error logs
- Add defensive check for nil response.error
- Handle oversized PDF pages in chunking logic
- Remove unused process_native and process_generic methods
- Update email copy to reflect feature availability
- Add guard for nil document_type in email template
- Document pdf-reader gem rationale in Gemfile

Tested with both OpenAI (gpt-4o) and Ollama (qwen3:8b):
- OpenAI: 49 transactions extracted in 30s
- Ollama: 40 transactions extracted in 368s
- All encapsulation and error handling working correctly

* Update schema.rb with ai_summary and document_type columns

* Address PR #808 review comments

- Rename :csv_file to :import_file across controllers/views/tests
- Add PDF test fixture (sample_bank_statement.pdf)
- Add supports_pdf_processing? method for graceful degradation
- Revert unrelated database.yml pool change (600->3)
- Remove month_start_day schema bleed from other PR
- Fix PdfProcessor: use .strip instead of .strip_heredoc
- Add server-side PDF magic byte validation
- Conditionally show PDF import option when AI provider available
- Fix ProcessPdfJob: sanitize errors, handle update failure
- Move pdf_file attachment from Import to PdfImport
- Document deduplication logic limitations
- Fix ImportBankStatement: catch specific exceptions only
- Remove unnecessary require 'set'
- Remove dead json_schema method from PdfProcessor
- Reduce default OpenAI timeout from 600s to 60s
- Fix nil guard in text mailer template
- Add require 'csv' to ImportBankStatement
- Remove Gemfile pdf-reader comment

* Fix RuboCop indentation in ProcessPdfJob

* Refactor PDF import check to use model predicate method

Replace is_a?(PdfImport) type check with requires_csv_workflow? predicate
that leverages STI inheritance for cleaner controller logic.

* Fix missing 'unknown' locale key and schema version mismatch

- Add 'unknown: Unknown Document' to document_types locale
- Fix schema version to match latest migration (2026_01_24_180211)

* Document OPENAI_REQUEST_TIMEOUT env variable

Added to .env.local.example and docs/hosting/ai.md

* Rename ALLOWED_MIME_TYPES to ALLOWED_CSV_MIME_TYPES for clarity

* Add comment explaining requires_csv_workflow? predicate

* Remove redundant required_column_keys from PdfImport

Base class already returns [] by default

* Add ENV toggle to disable PDF processing for non-vision endpoints

OPENAI_SUPPORTS_PDF_PROCESSING=false can be used for OpenAI-compatible
endpoints (e.g., Ollama) that don't support vision/PDF processing.

* Wire up transaction extraction for PDF bank statements

- Add extracted_data JSONB column to imports
- Add extract_transactions method to PdfImport
- Call extraction in ProcessPdfJob for bank statements
- Store transactions in extracted_data for later review

* Fix ProcessPdfJob retry logic, sanitize and localize errors

- Allow retries after partial success (classification ok, extraction failed)
- Log sanitized error message instead of raw message to avoid data leakage
- Use i18n for user-facing error messages

* Add vision-capable model validation for PDF processing

* Fix drag-and-drop test to use correct field name csv_file

* Schema bleedover from another branch

* Fix drag-drop import form field name to match controller

* Add vision capability guard to process_pdf method

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: mkdev11 <jaysmth689+github@users.noreply.github.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-01-30 20:44:25 +01:00
Pere Montpeó
9f5fdd4d13 feat: add valuations API endpoints for managing account reconciliations (#745)
* feat: add valuations API endpoints for managing account reconciliations

* refactor: formatting

* fix: make account extraction clearer

* feat: validation and error handling improvements

* feat: transaction

* feat: error handling

* Add API documentation LLM context

* Make it easier for people

* feat: transaction in creation

* feat: add OpenAPI spec for Valuations API

* fix: update notes validation to check for key presence

* Prevent double render

* All other docs use `apiKeyAuth`

* More `apiKeyAuth`

* Remove testing assertions from API doc specs

* fix: correct valuation entry references

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-30 18:54:15 +01:00
Juan José Mata
c7ab25b866 Use browser Accept-Language for login and onboarding locale (#768)
* Use Accept-Language for unauthenticated locale

* Add per-user locale overrides

* Fix test

* Use more than the top `accept-language` entry

* Localization of string
2026-01-24 22:00:41 +01:00
LPW
6197419f6c Add protection indicator to entries and unlock functionality (#765)
* feat: add protection indicator to entries and unlock functionality

- Introduced protection indicator component rendering on hover and in detail views.
- Added support to unlock entries, clearing protection flags (`user_modified`, `import_locked`, and locked attributes).
- Updated routes, controllers, and models to enable unlock functionality for trades and transactions.
- Refactored views and localized content to support the new feature.
- Added relevant tests for unlocking functionality and attribute handling.

* feat: improve sync protection and turbo stream updates for entries

- Added tests for turbo stream updates reflecting protection indicators.
- Ensured user-modified entries lock specific attributes to prevent overwrites.
- Updated controllers to mark entries as user-modified and reload for accurate rendering.
- Enhanced protection indicator rendering using turbo frames.
- Applied consistent lock state handling across trades and transactions.

* Address PR review comments for protection indicator

---------

Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
2026-01-24 16:03:23 +01:00
Juan José Mata
e0fb585bda Hide contribution payments from demo user(s) (#738)
* Hide payment contribution options from demo and manually created users

Demo data users and manually created users don't have stripe_customer_id
set on their family, so they should not see payment/contribution options.

Changes:
- Add can_manage_subscription? method to Family::Subscribeable that checks
  for presence of stripe_customer_id
- Guard Settings::PaymentsController to return 403 for users without
  stripe_customer_id
- Guard SubscriptionsController#show action (Stripe portal redirect) for
  users without stripe_customer_id
- Update settings navigation to hide the payment link when
  stripe_customer_id is not present
- Add tests for the new behavior

* Fix broken test

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-23 12:35:49 +01:00
Jose
69d9f51d57 # test(api): Add request specs for merchants and tags endpoints (#645)
* Add files via upload

Signed-off-by: Jose <39016041+jospaquim@users.noreply.github.com>

* Add merchants and tags resources to routes

Signed-off-by: Jose <39016041+jospaquim@users.noreply.github.com>

* update

* update spaces

* fix: Apply CodeRabbit suggestions and add YARD documentation

* docs: Add API documentation for merchants and tags endpoints

* fix: Address CodeRabbit feedback on documentation

* fix: Use authorize_scope! instead of ensure_read_scope

* test(api): Add request specs for merchants and tags endpoints

* test(api): Add request specs for merchants and tags endpoints

* test(api): Convert specs to Minitest format in test/

* fix: Correct indentation for private methods

* fix: merchant and tag test

* Enhance tag tests for family scope and access

Added tests to ensure tags from other families are not returned and that attempts to access them return 404.

Signed-off-by: Jose <39016041+jospaquim@users.noreply.github.com>

* Enhance merchants controller tests for family scope

Added tests to ensure that merchants from other families are not returned in the index action and that accessing a merchant from another family returns a 404 error.

Signed-off-by: Jose <39016041+jospaquim@users.noreply.github.com>

* Fix test/implementation

* Remove old token test code

* Improve test

---------

Signed-off-by: Jose <39016041+jospaquim@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-23 10:18:22 +01:00
soky srm
87b77bb7df Merge pull request #744 from alessiocappa/FT-UpdateReportPageUI
feat: Improve report page UI
2026-01-23 09:19:17 +01:00
Alessio Cappa
96dfad1cfb fix: fix tests 2026-01-23 00:49:47 +01:00
Juan José Mata
bf76d6b88d Let /settings/providers work in demo site 2026-01-22 20:01:18 +00:00
Juan José Mata
7842b4a044 Fixes to contributions payment copy 2026-01-22 19:22:55 +00:00
LPW
dd991fa339 Add Coinbase exchange integration with CDP API support (#704)
* **Add Coinbase integration with item and account management**
- Creates migrations for `coinbase_items` and `coinbase_accounts`.
- Adds models, controllers, views, and background tasks to support account linking, syncing, and transaction handling.
- Implements Coinbase API client and adapter for seamless integration.
- Supports ActiveRecord encryption for secure credential storage.
- Adds UI components for provider setup, account management, and synchronization.

* Localize Coinbase-related UI strings, refine account linking for security, and add timeouts to Coinbase API requests.

* Localize Coinbase account handling to support native currencies (USD, EUR, GBP, etc.) across balances, trades, holdings, and transactions.

* Improve Coinbase processing with timezone-safe parsing, native currency support, and immediate holdings updates.

* Improve trend percentage formatting and enhance race condition handling for Coinbase account linking.

* Fix log message wording for orphan cleanup

* Ensure `selected_accounts` parameter is sanitized by rejecting blank entries.

* Add tests for Coinbase integration: account, item, and controller coverage

- Adds unit tests for `CoinbaseAccount` and `CoinbaseItem` models.
- Adds integration tests for `CoinbaseItemsController`.
- Introduces Stimulus `select-all` controller for UI checkbox handling.
- Localizes UI strings and logging for Coinbase integration.

* Update test fixtures to use consistent placeholder API keys and secrets

* Refine `coinbase_item` tests to ensure deterministic ordering and improve scope assertions.

* Integrate `SyncStats::Collector` into Coinbase syncer to streamline statistics collection and enhance consistency.

* Localize Coinbase sync status messages and improve sync summary test coverage.

* Update `CoinbaseItem` encryption: use deterministic encryption for `api_key` and standard for `api_secret`.

* fix schema drift

* Beta labels to lower expectations

---------

Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-21 22:56:39 +01:00
Juan José Mata
8e36c8e736 Rename billing to payment throughout the codebase (#726)
* Rename billing to payment throughout the codebase

This change updates terminology from "billing" to "payment" to better
reflect that these are contributions/payments rather than bills.

Changes include:
- Rename BillingsController to PaymentsController
- Rename billing_email to payment_email
- Rename next_billing_date to next_payment_date
- Rename create_billing_portal_session_url to create_payment_portal_session_url
- Update routes from billing to payment
- Update all 12 locale files with new terminology
- Update views, helpers, and tests

* Update app/views/subscriptions/upgrade.html.erb

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-21 19:06:00 +01:00
Julien Orain
777fbdc4ca feat(settings): add pagination to imports and exports pages (#598)
* feat(settings): split imports and exports

* feat(security): sanitize pagination params to prevent abuse

* fix(settings): fix syntax in settings nav

* feat(settings): internationalize family_exports and imports UI strings

* fix(settings): fix coderabbit review

* fix(settings): fix coderabbit review

* fix(settings): fix coderabbit review

* Change default per_page value from 20 to 10

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

* Add `/family_export` to navigation

* Consistency with old defaults

* Align `safe_per_page` even if not DRY

---------

Signed-off-by: Julien Orain <julien.orain@gmail.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: JulienOrain <your-github-email@example.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-20 00:11:22 +01:00