Commit Graph

203 Commits

Author SHA1 Message Date
Víctor Falcón 244344e953
feat(open-banking): remove feature flag gating (#297)
## Summary
- remove the Pennant-based `open-banking` flag and middleware gating so
open banking is always available for authenticated users
- simplify landing, onboarding, settings, and subscription flows to use
always-on open-banking behavior and remove stale frontend/shared flag
plumbing
- update open-banking tests and purge stored `open-banking` Pennant rows

## Testing
- `php artisan test --compact
tests/Feature/OpenBanking/InstitutionControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/BinanceControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/IndexaCapitalControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/BitpandaControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/AuthorizationControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/ConnectionControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/AccountMappingTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/OpenBankingFeatureFlagTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/SyncRetryAndLoggingTest.php`
- `php artisan test --compact tests/Feature/SubscriptionTest.php`
- `php artisan test --compact
tests/Feature/WelcomeBanksOrderingTest.php`
- `vendor/bin/pint --dirty --format agent`

## Notes
- `php artisan pennant:purge open-banking` was run to remove stale
stored values
- `php artisan test --compact tests/Browser/OnboardingFlowTest.php`
still has one unrelated real-estate onboarding browser failure (`it
creates a real estate account during onboarding when feature is
enabled`)
2026-04-17 10:20:05 +02:00
Víctor Falcón 63e472e8a4
test(schedule): remove stale horizon snapshot (#298)
- remove random and silly test
2026-04-16 15:14:16 +01:00
Víctor Falcón fde5405777
fix(user): persist detected timezones (#296)
## Summary
- store browser-detected IANA timezones on users during registration and
share the value to the frontend
- silently backfill missing timezones for authenticated users without
overwriting existing saved values
- add focused feature coverage for registration, shared props, and the
timezone backfill endpoint

## Testing
- php artisan test --compact tests/Feature/Auth/RegistrationTest.php
tests/Feature/InertiaSharedDataTest.php
tests/Feature/Settings/TimezoneTest.php
- npm test -- resources/js/utils/currency.test.ts
2026-04-16 11:36:57 +01:00
Víctor Falcón 473ac03088
fix(open-banking): skip silent sync emails (#295)
## Summary
- add a per-connection cutoff timestamp so silent first/full sync
imports are excluded from later daily bank sync emails
- keep the existing one-email-per-day user cap while still reporting
transactions created after the silent sync cutoff
- add regression coverage for silent first sync, full sync, and
post-cutoff reporting behavior

## Testing
- php artisan test --compact
tests/Feature/OpenBanking/SyncBankingConnectionJobTest.php
- vendor/bin/pint --dirty --format agent
2026-04-16 08:28:44 +01:00
Víctor Falcón 64ec047769
chore: track Sentry releases in CI (#294)
## Summary
- create and finalize Sentry releases in CI using
`whisper-money@<git-sha>`
- bake the release into the production image so Laravel tags backend
events with the deployed version
- mark production deploys in Sentry and add a focused test covering the
release binding

## Testing
- php artisan test --compact tests/Feature/SentryConfigTest.php
2026-04-16 08:04:59 +01:00
Víctor Falcón b438a1c73b
fix(schedule): remove stale horizon snapshot (#293)
## Summary
- remove the stale `horizon:snapshot` scheduled command that still runs
in production
- add a regression test to ensure scheduled commands do not include
Horizon snapshot
- keep production aligned with the current `queue:work` worker setup
2026-04-16 07:35:33 +01:00
Víctor Falcón 552aa59aaf
fix: limit bank sync emails to one per day (#290)
## Summary
- move bank transaction sync emails from per-connection inline sends to
a unique per-user daily job
- send at most one bank sync email per user per day while still
including all unreported enable-banking transactions since last reported
mail
- keep first-ever connection sync silent and add coverage for same-day
suppression and next-day catch-up emails

## Testing
- php artisan test --compact
tests/Feature/OpenBanking/SyncBankingConnectionJobTest.php
2026-04-15 16:39:28 +02:00
Víctor Falcón 2f583c0113
Cancel Enable Banking connections for free users (#289)
## Summary
- add month-end command to revoke old Enable Banking sessions for users
without a paid plan while keeping their linked accounts as manual
accounts
- extract banking disconnect flow into a reusable action so manual
disconnects and scheduled cleanup share same revoke and detach behavior
- add feature coverage for free-user cleanup, paid-user skips,
under-6-hour skips, non-Enable Banking skips, and revoke failure
fallback

## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact
tests/Feature/CancelFreeEnableBankingConnectionsCommandTest.php
tests/Feature/OpenBanking/ConnectionControllerTest.php
2026-04-15 16:23:03 +02:00
Víctor Falcón 319ca758e1
fix(pricing): update final release prices (#288)
## Summary
- update subscription pricing to the final release amounts for monthly
and annual billing
- keep billing UI formatting aligned with the configured currency so
1.99 €/month displays correctly
- refresh subscription and Stripe sync tests to lock the new values in
place
2026-04-15 14:49:02 +01:00
Víctor Falcón 094fb1b744
feat(real-estate): auto-calculate revaluation % and generate historical balances (#253)
## Summary

- **Auto-calculates Annual Revaluation %** (CAGR) on the frontend when
purchase price, purchase date, and current market value are all provided
— pre-fills the revaluation field while still allowing manual override
- **Generates historical monthly balance records** via linear
interpolation from purchase date to today when creating a real estate
account with complete purchase data
- New `RealEstateBalanceGeneratorService` handles balance generation
with dates on the 1st of each month (matching the existing
`ApplyRealEstateRevaluationCommand` convention)

## Changes

### Backend
- **`app/Services/RealEstateBalanceGeneratorService.php`** (new) —
Linear interpolation service that builds balance dates (purchase date,
1st of each intermediate month, today) and creates `AccountBalance`
records using `updateOrCreate`
- **`app/Http/Controllers/Settings/AccountController.php`** — Calls the
service after real estate detail creation when all three inputs are
present

### Frontend
- **`resources/js/components/accounts/account-form.tsx`** — Added CAGR
auto-calculation via `useEffect` with a `useRef` flag
(`isRevaluationManuallySet`) to avoid overwriting manual user input

### Tests
- **`tests/Feature/Services/RealEstateBalanceGeneratorServiceTest.php`**
(new) — 7 unit tests covering interpolation, edge cases (same-day
purchase, flat values, single month spans)
- **`tests/Feature/RealEstateTest.php`** — 6 new feature tests covering
historical balance generation through the controller (with/without
purchase data, same-day, flat values)

All 38 tests in `RealEstateTest.php` and all 7 service tests pass.
2026-04-15 12:18:33 +00:00
Víctor Falcón 5b78509588
feat: resend verification emails to unverified leads (#287)
## Summary

- Adds `leads:resend-verification-emails` artisan command that
dispatches `VerifyUserLeadEmailNotification` to all leads where
`email_verified_at IS NULL`
- Supports `--dry-run` flag to preview the count without sending
- Follows the same pattern as `leads:retry-failed-jobs` (progress bar,
summary table)

## Test plan

- [ ] `--dry-run` shows correct count without dispatching
- [ ] Command dispatches exactly one notification per unverified lead
- [ ] Verified leads are skipped
- [ ] Early exit when no unverified leads exist
2026-04-15 09:13:27 +01:00
Víctor Falcón f408dbe4c8
feat: selective retry of failed lead email jobs (#286)
## Summary

- Adds `leads:retry-failed-jobs` artisan command to selectively retry
failed `emails`-queue jobs after a Resend rate-limit incident
- Retries jobs for verified leads and `VerifyUserLeadEmailNotification`
for unverified leads; forgets jobs for deleted leads (DDoS cleanup) or
unverified leads receiving waitlist emails
- Adds `deleteWhenMissingModels = true` to all lead mail/notification
classes so future jobs for deleted leads are silently discarded instead
of failing

## Usage

```bash
# Preview (no changes)
php artisan leads:retry-failed-jobs --dry-run

# Execute
php artisan leads:retry-failed-jobs
```

## Test plan

- [x] `leads:retry-failed-jobs` forgets jobs for deleted leads
- [x] `leads:retry-failed-jobs` retries jobs for verified leads
- [x] `leads:retry-failed-jobs` forgets waitlist jobs for unverified
leads
- [x] `leads:retry-failed-jobs` retries
`VerifyUserLeadEmailNotification` for unverified leads
- [x] `leads:retry-failed-jobs` handles mixed job types correctly
- [x] `--dry-run` does not modify `failed_jobs`
2026-04-15 08:00:29 +01:00
Víctor Falcón d0aab3d11b
feat: verify waitlist leads (#285)
## Summary
- gate waitlist signup behind email confirmation so only verified leads
receive a queue position and referral code
- add signed lead verification routes, a check-email page, and a
dedicated verification email for the waitlist flow
- update lead syncing and feature tests so only verified leads are
exported to Resend and referral movement happens after verification

## Testing
- php artisan test --compact tests/Feature/UserLeadTest.php
- php artisan test --compact
tests/Feature/ResendSyncLeadsCommandTest.php
- php artisan test --compact tests/Feature/MailSenderTest.php
- vendor/bin/pint --dirty --format agent
2026-04-14 11:26:01 +01:00
Víctor Falcón 1f9c0cf030
chore: remove hardcoded resend segment ID (#284)
## Summary
- remove the hardcoded Resend leads segment ID from tracked config
- keep `RESEND_LEADS_SEGMENT_ID` environment-driven for production use
- replace leaked test UUID references with a test-only placeholder

## Testing
- php artisan test --compact
tests/Feature/ResendSyncLeadsCommandTest.php
- vendor/bin/pint --dirty --format agent
2026-04-13 20:27:00 +01:00
Víctor Falcón dc0695c2ca
feat: sync user leads to resend (#283)
## Summary
- add a `resend:sync-leads` command that syncs all `user_leads` into the
Resend leads segment
- make lead sync idempotent by creating contacts with the segment and
falling back to adding existing contacts to the segment
- schedule the command daily at `03:00` UTC and cover the
command/fallback behavior with Pest tests

## Testing
- php artisan test --compact
tests/Feature/ResendSyncLeadsCommandTest.php
2026-04-13 19:56:08 +01:00
Víctor Falcón ea9956f21d
fix: keep iOS popovers below the notch (#282)
## Summary
- add safe-area CSS variables for the viewport top and bottom insets
- apply safe-area-aware collision padding to the shared popover
primitive so iOS overlays stay below the notch
- add a focused regression test for the popover safe-area guard

## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Feature/PopoverSafeAreaTest.php
2026-04-13 15:19:56 +01:00
Víctor Falcón 80b666836c
fix: avoid iOS PWA status bar overlap (#281)
## Summary
- switch the iOS standalone PWA status bar style from
`black-translucent` to `default`
- keep installed iOS app content below the status bar and notch area
- extend the PWA template test to guard against regressing to
translucent overlap

## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Feature/PwaTest.php
2026-04-13 14:38:48 +01:00
Víctor Falcón b505d68ef0
fix: keep iOS content below the notch (#280)
## Summary
- move the top safe-area inset from the mobile header to the shared
authenticated content shell
- keep scrolling content on iOS sidebar pages from sliding under the
notch
- add a focused regression test for the shared layout safe-area
placement

## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact
tests/Feature/AuthenticatedLayoutSafeAreaTest.php
2026-04-13 13:59:05 +01:00
Víctor Falcón db359a0572
test: expand browser coverage for account types and transactions (#279)
## Summary
- add browser coverage for manual credit card, investment, retirement,
and other account creation flows
- replace placeholder transaction search coverage with real filter
assertions and add edit/delete transaction browser tests
- keep the new coverage focused on the existing account and transaction
browser test files

## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Browser/BankAccountsTest.php
tests/Browser/TransactionsTest.php
2026-04-13 14:02:21 +02:00
Víctor Falcón a7c1bd35ef
feat: link loans to existing properties (#275)
## Summary
- add an optional linked property selector when creating loan accounts
in both the main create dialog and onboarding
- validate that only the current user's unlinked real-estate accounts
can be selected and persist the reciprocal link after loan creation
- expose linked property state in shared account props and cover the new
flow with focused loan feature tests

## Testing
- php artisan test --compact tests/Feature/LoanTest.php
- vendor/bin/pint --dirty --format agent
2026-04-13 09:51:13 +01:00
Víctor Falcón dc41c5f6e0
test: expand browser coverage for accounts and linked loans (#277)
## Summary
- add browser coverage for manual loan account creation with balance and
loan details
- add browser coverage for creating real-estate accounts linked to
existing loan accounts
- add browser coverage for updating balances and linking or unlinking
loans from account details

## Testing
- php artisan test --compact tests/Browser/BankAccountsTest.php
tests/Browser/AccountsPageTest.php
2026-04-13 09:33:18 +01:00
Víctor Falcón d91d9d3b3e
fix: default to standard onboarding option (#276)
## Summary
- default the onboarding account setup choice to the connected Standard
option when open banking is available
- change the Standard onboarding card and connected-bank step accents
from blue to emerald for consistent plan styling
- update the onboarding browser test comment to reflect the new default
selection

## Verification
- `php artisan test --compact tests/Feature/SubscriptionTest.php`
- `php artisan test --compact tests/Browser/OnboardingFlowTest.php` *(3
unrelated existing failures in browser assertions)*
2026-04-13 08:54:12 +01:00
Víctor Falcón dafc58f49f
fix: clarify account creation modal copy (#274)
## Summary
- update the create account modal copy to explicitly mention loans and
property accounts
- clarify the manual option so users understand it also covers manually
created real-estate accounts
- update the browser test assertion to match the revised dialog copy
2026-04-12 16:38:00 +01:00
Víctor Falcón 80274e03a8
fix: align onboarding account types with current asset support (#273)
## Summary
- add real estate to the onboarding account type explainer and update
investment copy to mention crypto and cold wallets
- align onboarding manual account creation with the real-estate feature
flag and real-estate payload requirements
- cover the onboarding copy and real-estate creation flow with browser
tests

## Testing
- php artisan test --compact tests/Browser/OnboardingFlowTest.php
2026-04-12 16:22:38 +01:00
Víctor Falcón 1e2036110f
fix: prioritize exact bank search matches (#267)
## Summary
- remove the bank search result cap so exact matches are no longer
pushed out of the response
- rank exact name matches first, then prefix matches, then broader
substring matches
- add feature coverage for search ranking, full result sets, and
user-specific visibility
2026-04-10 13:32:56 +01:00
Víctor Falcón fec93734c0
fix: reorder signed names in mail templates (#266)
## Summary
- update mail signatures to show Álvaro before Víctor across the Blade
mail templates
- add a focused mail test that guards the signature order so the old
ordering does not reappear
- verified with `php artisan test --compact
tests/Feature/MailSenderTest.php`
2026-04-10 13:32:32 +01:00
Víctor Falcón 7be0fe0120
fix: make transaction sync email use default sender (#265)
## Summary
- explicitly set the transaction sync mailable sender to the configured
default transactional address
- add a regression test so the transaction sync email cannot fall back
to an unintended sender

## Testing
- php artisan test --compact tests/Feature/MailSenderTest.php
2026-04-07 13:50:42 +01:00
Víctor Falcón c3ff4c684a
feat: store invested_amount in user currency instead of account currency (#262)
## Summary

- **Store `invested_amount` in user's currency** (e.g. EUR) instead of
the account's currency (e.g. BTC), since it represents "how much of my
money did I put in" — a concept tied to the user's home currency.
- **Flip `display_invested_amount`** in API responses: now converts from
user currency → account currency (previously account → user), for the
chart's account-currency toggle mode.
- **Remove redundant conversions** in `AccountMetricsService` since
invested amounts are already in user currency after sync.

## Changes

### Backend (5 files)
- **BinanceBalanceSyncService** — convert to
`$account->user->currency_code` instead of `$account->currency_code`
- **BitpandaBalanceSyncService** — same currency target change
- **IndexaCapitalBalanceSyncService** — inject
`CurrencyConversionService`, convert invested amount float to user
currency before storing as cents
- **DashboardAnalyticsController** — flip `display_invested_amount`
conversion direction (user→account) in both monthly and daily endpoints
- **AccountMetricsService** — remove 4 now-redundant invested_amount
conversion spots (values already in user currency)

### Frontend (6 files)
- **update-balance-dialog.tsx** — invested amount input uses user
currency
- **balances-modal.tsx** — invested amount display and edit uses user
currency
- **account-balance-chart.tsx** — currency toggle: user-currency mode
keeps invested_amount as-is; account-currency mode swaps to
`display_invested_amount`
- **import-balances-drawer.tsx** — passes `investedAmountCurrencyCode`
(user currency) to sub-components
- **import-balance-step-mapping.tsx** /
**import-balance-step-preview.tsx** — format invested amounts in user
currency

### Tests (2 files)
- **IndexaCapitalBalanceSyncTest** — use `app()` instead of `new`
(constructor now has DI), pin account currency to USD for deterministic
assertions
- **DashboardAnalyticsTest** — add EUR-based exchange rate, flip
assertion from `/ 0.90` to `* 0.90`
2026-04-06 12:48:13 +02:00
Víctor Falcón ce5692cb30
fix: split drip and default email senders (#263)
## Summary
- route drip mailables through `Álvaro and Víctor <hi@whisper.money>`
and send the rest from `Whisper Money <no-reply@whisper.money>`
- remove legacy per-mailable `Victor` sender overrides so non-drip mail
falls back to the default sender consistently
- add focused sender coverage for drip, non-drip, and verification mail
paths

## Testing
- `php artisan test --compact tests/Feature/MailSenderTest.php
tests/Feature/Jobs/Drip/SendWelcomeEmailJobTest.php`
2026-04-06 12:16:47 +02:00
Víctor Falcón 3990472249
Make bank selection optional when creating or updating accounts (#261)
## Summary

- Makes `bank_id` nullable for all account types in both
`StoreAccountRequest` and `UpdateAccountRequest` (previously it was only
nullable for real estate)
- Shows the bank combobox field for all account types in the
`AccountForm` component, including real estate
- Removes the `required` attribute from the hidden bank input and the
logic that cleared bank selection when switching to real estate type

## Test changes

- Updated "validates required fields" test to no longer expect `bank_id`
as a required field
- Added "can create a new account without a bank" test in `AccountTest`
- Updated `RealEstateTest` to verify non-real-estate accounts can also
be created without a bank (was previously asserting the opposite)
2026-04-04 16:33:26 +01:00
Víctor Falcón 7e958284e3
fix(loans): project monthly balances from actual entries instead of original params (#259)
## Summary

- **Bug**: The `loans:generate-balances` command called
`getBalanceAtDate()` which always recalculated the remaining balance
from the **original loan parameters** (`original_amount`,
`annual_interest_rate`, `loan_term_months`, `start_date`), completely
ignoring existing actual balance entries. When the real balance was
lower than the theoretical amortization schedule (e.g. user made extra
payments), the auto-generated balance would **jump up** instead of
continuing to decrease.
- **Example**: Account `019d0b33-d3c5-7110-9033-51155ac93219` had a real
balance of ~4,489,670 (March 2026), but the command generated 5,700,356
(April 2026) — a jump **up** of ~1.2M cents instead of the expected ~16K
decrease.
- **Fix**: `getBalanceAtDate()` now checks for the latest
`AccountBalance` entry on or before the target date and projects forward
from it, falling back to the theoretical formula only when no entries
exist. This matches the method's own documented behavior and aligns with
how `generateProjection()` already works.

## Testing

Added 4 new tests:
- Projects from existing balance entries instead of original loan params
- Returns existing balance when target date is in the same month
- Generates correct monthly balance when existing entries differ from
theoretical
- All 44 existing loan tests continue to pass
2026-04-04 15:57:29 +01:00
Víctor Falcón 3d5823728a
feat(settings): centralize currency options and split profile/account support (#256)
## Summary

- **Bug fix:** Dashboard and accounts index cards displayed the
account's original currency code (e.g. `BTC`) even though balances were
already converted to the user's currency (e.g. `EUR`). Now passes
`displayCurrencyCode` to card components so the label matches the
converted amount.
- **Feature:** Added a currency toggle on the account detail chart to
switch between the account's native currency and the user's main
currency. Extends the balance evolution APIs with `display_*` fields
when conversion applies.
- **First-account restriction:** Restricts first-account creation to
primary (fiat) currencies only, ensuring the user's base currency is
always widely supported.

## Changes

### Bug fix — currency label on cards
- `AccountBalanceCard` / `AccountListCard`: Added `displayCurrencyCode`
prop; all amount renders now use it instead of `account.currency_code`
- `dashboard.tsx`: Passes `netWorthEvolution.currency_code` as
`displayCurrencyCode`
- `Accounts/Index.tsx`: Passes `auth.user.currency_code` as
`displayCurrencyCode`

### Backend — API extension
- `DashboardAnalyticsController`: Both `accountBalanceEvolution()` and
`accountDailyBalanceEvolution()` now return `display_value`,
`display_invested_amount`, `display_mortgage_balance` per data point and
a top-level `display_currency_code` when the account currency differs
from the user's

### Frontend — currency toggle
- New `ChartCurrencyToggle` component with `ToggleGroup` showing
currency code labels (e.g. `BTC` / `EUR`)
- `ChartSettingsPopover`: Extended with optional `currencyToggle` prop
for mobile
- `AccountBalanceChart`: Full integration — all amounts, trends,
tooltips, MoM chart, and equity swap to `display_*` values when toggle
is set to user currency

### First-account currency restriction
- `StoreAccountRequest`: First account limited to primary currency codes
- `AccountForm` / `CreateAccountDialog` / `StepCreateAccount`: Pass
`usePrimaryCurrenciesOnly` when applicable

### Tests
- 5 new Pest tests for `display_*` fields in balance evolution endpoints
- 3 new Vitest tests for `deriveAccountMetrics` (mixed-currency, empty
data, invested amounts)
- 1 new Pest test for first-account BTC rejection
2026-04-02 19:23:10 +02:00
Víctor Falcón 83f7e83a13
fix(cashflow): net transfer categories in sankey (#257)
## Summary
- net transfer categories in the cashflow Sankey on their configured
side instead of showing gross signed flows
- keep the existing mixed-sign behavior for non-transfer categories so
regular income and expense categories can still appear on both sides
- add regression coverage for zero-net inflow transfers and partial
inflow/outflow transfer netting

## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Feature/CashflowAnalyticsTest.php
--filter=sankey

## Notes
- running the full `CashflowAnalyticsTest` file in this workspace still
hits two unrelated page tests because `public/build/manifest.json` is
missing locally
2026-04-01 14:48:38 +01:00
Víctor Falcón c42a48a952
chore: Remove account-mapping feature flag (#252)
## Summary

- Removes the `account-mapping` Pennant feature flag entirely, making
the account mapping flow (pending accounts data + map-accounts page) the
default and only code path
- Removes the old direct-creation branches from all banking controllers
(Authorization, Bitpanda, Binance, IndexaCapital)
- Extracts a shared `CreatesAccountsFromPending` trait for the
onboarding auto-create logic
- Updates all controller tests to reflect the always-on mapping behavior

## Changes

### Backend
- **AppServiceProvider** — removed `account-mapping` flag definition
- **AuthorizationController** — removed flag check +
`createAccountsFromSession()` dead code; always stores
`pending_accounts_data` and redirects to mapping
- **BitpandaController / BinanceController / IndexaCapitalController** —
removed flag checks, old inline account creation, and unused imports
- **HandleInertiaRequests / ActivateDevelopmentFeatures /
ResolvesFeatures** — removed `account-mapping` from flag arrays
- **New `CreatesAccountsFromPending` trait** — shared auto-create logic
for onboarding path (to be consumed next)

### Frontend
- Removed `'account-mapping'` from the TypeScript `Features` interface

### Tests
- Merged flag-specific test variants into single always-on tests
- Removed redundant tests that tested the old disabled-flag code path
- Updated assertions to check `pending_accounts_data` instead of direct
account creation
2026-04-01 12:09:22 +02:00
Víctor Falcón ca189a565b
test(budgets): stabilize budget rename browser test (#255)
## Summary
- replace the budget rename test's fixed post-submit wait with stable UI
signals tied to the save lifecycle
- wait for the edit dialog to close, the budget show page to settle, and
the updated budget name to appear in the page content and title
- verify the change with `php artisan test --compact
tests/Browser/BudgetCrudTest.php`
2026-04-01 10:30:49 +01:00
Víctor Falcón f3b5929ecc
fix(banking): retry failed sync connections and log every sync attempt (#251)
## Summary

- **Fixes the broken retry mechanism** — after the first failed attempt
set status to `Error`, subsequent retry attempts bailed out because
`isActive()` returns `false` for `Error`. Now both `Active` and `Error`
statuses are syncable.
- **Adds auto-retry across scheduled runs** —
`SyncAllBankingConnectionsJob` and `banking:sync` command now include
`Error` connections where `consecutive_sync_failures < 3` (configurable
via `MAX_SCHEDULED_RETRIES`). After 3 full dispatch cycles, manual
intervention is required.
- **Logs every sync attempt to DB** — new `banking_sync_logs` table
records status (Success/Failed/Skipped), attempt number, error details,
duration, and metadata for each sync.

## Changes

### Core logic (`SyncBankingConnectionJob`)
- `isSyncableStatus()` allows both `Active` and `Error` through the gate
- Temporary errors: status only set to `Error` on the final attempt
(attempt 3); earlier attempts re-throw without changing status
- Permanent auth errors (401/403): `$this->fail()` called immediately,
`consecutive_sync_failures` set beyond the cap
- Rate limit (429): handled silently (existing behavior preserved)
- Every attempt is logged to `banking_sync_logs`

### Query updates
- `SyncAllBankingConnectionsJob`: includes `Error` connections under
retry cap
- `SyncBankingConnections` command: same query update for
`--user`/`--connection` filtered runs

### Controller updates
- `ConnectionController::sync()` and `updateCredentials()` reset
`consecutive_sync_failures` to 0

### New files
- `BankingSyncLogStatus` enum (Success, Failed, Skipped)
- `BankingSyncLog` model
- Two migrations: `add_consecutive_sync_failures` column,
`create_banking_sync_logs` table

### Tests
- Updated 3 existing auth error tests in `SyncBankingConnectionJobTest`
(24 pass)
- Added 17 new tests in `SyncRetryAndLoggingTest` covering retry
behavior, sync logging, scheduled retry inclusion/exclusion, and manual
retry reset
- All 10 `SyncBankingConnectionsCommandTest` tests still pass
2026-03-31 11:34:35 +01:00
Víctor Falcón 755452d6ce
Fix net worth chart config UX and color scheme issues (#250)
## Summary

- **Clickable toggle rows**: The full row in the chart settings popover
now toggles the checkbox, not just the checkbox itself.
- **Chart stacking order**: When toggling "Include loans" or "Include
real estate", the chart now re-mounts so accounts appear in the correct
sorted position (biggest at bottom) instead of being appended on top.
- **Scheme-aware liability dot**: The red liability indicator dot in the
chart tooltip now respects the user's chart color scheme (neutral, blue,
pink) instead of always using the destructive/red color.
2026-03-31 11:19:35 +01:00
Víctor Falcón bb65bdc16e
feat(accounts): add loan amortization projections for loan accounts (#246)
## Summary

- Adds loan detail tracking (interest rate, term, start date, original
amount) to loan-type accounts, following the existing
`real_estate_details` pattern
- Implements `LoanAmortizationService` with standard amortization math
to automatically project month-to-month balance evolution
- Shows projected future balances as a dashed line on the account
balance chart, visually distinct from historical data
- Adds a scheduled command (`loans:generate-balances`) to auto-generate
monthly balance entries for loan accounts
- Includes 36 tests (10 unit for pure math, 26 feature for CRUD, API
projections, command, and authorization)

## Changes

### Backend
- **Migration**: `loan_details` table (UUID PK, unique `account_id` FK,
interest rate, term, start date, original amount)
- **LoanDetail model** + factory with `Account::loanDetail()` HasOne
relationship
- **LoanAmortizationService**: `calculateMonthlyPayment`,
`calculateRemainingBalance`, `generateProjection`, `projectFromBalance`,
`calculateRemainingMonths`, `getBalanceAtDate`
- **GenerateMonthlyLoanBalances** command: runs monthly on 1st, skips
existing entries
- **LoanDetailController**: PATCH endpoint for editing loan details from
show page
- **Settings\AccountController**: creates/updates `LoanDetail` on
store/update
- **DashboardAnalyticsController**: appends projected data points with
`projected: true` flag
- **Validation**: loan-specific rules in StoreAccountRequest and
UpdateAccountRequest

### Frontend
- **AccountForm**: loan fields (rate, term, start date, original amount)
shown when `type === 'loan'`
- **Create/Edit dialogs**: pass loan data fields in POST/PATCH payloads
- **Show page**: `LoanDetailsCard` component with edit/read modes,
computed monthly payment and remaining months
- **Balance chart**: new `ComposedChart` branch with dashed `Line`
overlay for projected data points
- **Types**: `LoanDetail` interface in `account.ts`
2026-03-26 15:06:09 +01:00
Víctor Falcón fa11dc78e0
feat(accounts): add market value and annual revaluation to real estate accounts (#245)
## Summary

- Allow setting the **current market value** (balance) when creating a
real estate account — the balance field existed for other account types
but was hidden for real estate
- Add an **annual revaluation percentage** field to real estate
accounts, editable from both the creation form and the property details
card on the account show page
- Create a scheduled command (`real-estate:apply-revaluation`) that runs
monthly on the 1st to automatically adjust property values by
`revaluation_percentage / 12 / 100`

## Details

### Backend
- Migration adds `decimal('revaluation_percentage', 5, 2)` nullable
column to `real_estate_details`
- Validation: nullable numeric, range -100 to +100 (negative for
depreciation)
- `ApplyRealEstateRevaluationCommand` finds all accounts with a
non-null/non-zero percentage, gets the latest balance, and upserts the
new balance for today
- Scheduled `->monthlyOn(1, '00:00')` in `routes/console.php`

### Frontend
- Added `real_estate` to `BALANCE_ACCOUNT_TYPES` so the Market Value
field appears during creation
- Added Annual Revaluation (%) input to both the creation form and the
property details card
- Read-mode displays formatted percentage with sign (e.g.,
"+3.50%/year")

### Tests
- 6 new tests in `RealEstateTest.php` covering creation with balance,
revaluation %, negative %, validation, PATCH update, and clearing to
null
- 8 tests in `ApplyRealEstateRevaluationTest.php` covering
positive/negative revaluation, skip conditions, latest balance usage,
multiple accounts, and upsert behavior
2026-03-26 11:02:20 +01:00
Víctor Falcón 1880333b1c
chore: upgrade Laravel 12 to 13 (#242)
## Summary

- Upgrade `laravel/framework` from v12 to **v13.1.1** and update all 52
dependencies to their latest versions
- Bump `laravel/tinker` from v2 to **v3.0** (required for Laravel 13
compatibility)
- Address Laravel 13 breaking change: add `serializable_classes =>
false` to `config/cache.php`
- Fix cached `Collection` in `routes/web.php` — converted to plain array
via `->toArray()` for serialization safety

## Changes

| File | What changed |
|------|-------------|
| `composer.json` | Bumped `php ^8.3`, `laravel/framework ^13.0`,
`laravel/tinker ^3.0` |
| `composer.lock` | 52 packages updated, 1 removed
(`symfony/polyfill-php83`) |
| `config/cache.php` | Added `serializable_classes => false` |
| `routes/web.php` | Cached query result uses `->toArray()`, fallback
changed from `collect()` to `[]` |

## Testing

Full test suite passing: **919 tests, 3792 assertions, 0 failures**
2026-03-25 12:56:33 +00:00
Víctor Falcón 973243277a
feat(accounts): show mortgage data and equity on real estate account page (#243)
## Summary

- Add mortgage balance as a dashed line overlay on the property value
chart when a real estate account has a linked loan
- Display equity (market value - mortgage owed) in the chart header and
as a 3-column summary strip (Market Value | Mortgage Owed | Equity) in
the property details card
- Extend balance evolution API endpoints to include `mortgage_balance`
data points for linked loan accounts
- Add 6 new tests covering mortgage/equity display in account show page
and balance evolution endpoints
2026-03-24 15:56:23 +00:00
Víctor Falcón 395c4ad2c3
feat(accounts): add real estate asset tracking (#241)
## Summary

- Adds **real estate** as a new account type (`real_estate`) for
tracking property assets within the existing Accounts page
- Properties store metadata (type, address, purchase price/date, area,
notes) in a dedicated `real_estate_details` table with a one-to-one
relationship to accounts
- Properties can be **linked to a loan account** (mortgage) to
implicitly calculate equity — property value counts as asset (+), linked
loan counts as liability (-)
- Market value is tracked as the account balance and uses "market value"
terminology throughout the UI

## What's included

### Backend
- `PropertyType` enum (Residential, Commercial, Land, Vacation, Other)
- `RealEstateDetail` model, factory, migration, policy
- `StoreRealEstateDetailRequest` and `UpdateRealEstateDetailRequest`
form requests
- `RealEstateDetailController` with `update()` for editing property
details
- Extended `Settings\AccountController@store` to create
`RealEstateDetail` when type is `real_estate`
- Extended `AccountController@show` to load real estate detail, linked
loan with bank info, and available loan accounts
- Extended `AccountController@index` SQL ordering to include
`real_estate`
- Conditional validation rules in `StoreAccountRequest` for real estate
fields

### Frontend
- New `real_estate` type in `account.ts` with `PropertyType`,
`AreaUnit`, `RealEstateDetail` interface, and helper functions
- Conditional real estate fields in `account-form.tsx` (property type,
address, purchase price, purchase date, area, linked loan, notes)
- `PropertyDetailsCard` component in `Accounts/Show.tsx` with view and
inline edit modes
- "Update market value" terminology in balance update buttons for real
estate accounts
- `real_estate` added to account type ordering and groups on the Index
page
- Wayfinder routes regenerated

### Tests
- 22 feature tests covering creation, validation, show page, index
ordering, updating details, IDOR protection, model relationships, and
soft delete behavior
- Unit test updates for `AccountType` enum (`reducesNetWorth`,
`isNonTransactional`)

### Translations
- 32 new Spanish translation strings for all real estate UI

## Design decisions
- Real estate accounts are **non-transactional** (like
investment/retirement) — balance-only tracking for now. Future iteration
will link transactions directly for rental income/expense tracking
- **Single loan per property** via direct FK from
`real_estate_details.linked_loan_account_id`
- No encryption on address/notes fields (opted out per discussion)
- No separate sidebar page — real estate is a grouped section within the
existing Accounts page
2026-03-24 10:21:32 +00:00
Víctor Falcón 7a056213cf
feat(accounts): allow setting initial balance when creating balance-tracking accounts (#239)
## Summary

- Adds an optional balance input field to the account creation modal for
**investment**, **loan**, **retirement**, and **savings** account types.
- When a balance is provided, an `AccountBalance` record is created for
today's date upon account creation.
- The balance label adapts to the account type (e.g., "Owed Amount" for
loans, "Balance" for others).

## Changes

- **`account-form.tsx`** — Shows `AmountInput` when a balance-tracking
type is selected and currency is set.
- **`create-account-dialog.tsx`** — Passes `balance` in the POST payload
when present.
- **`StoreAccountRequest.php`** — Adds optional `balance` (nullable
integer) validation.
- **`AccountController::store`** — Creates an `AccountBalance` record
when balance is provided.
- **`AccountTest.php`** — 3 new tests covering creation with balance,
without balance, and invalid balance validation.

## Screenshots
<img width="1017" height="649" alt="image"
src="https://github.com/user-attachments/assets/4bf1530e-0faf-45a4-a3d1-d0ec49d5b412"
/>
2026-03-20 10:51:42 +00:00
Víctor Falcón f140b5df7f
fix(dashboard): treat loans as debt in net worth (#238)
## Summary
- fix dashboard net worth math so loan balances reduce totals and trends
instead of showing as positive assets
- add a per-user toggle in the net worth chart settings to include or
exclude loans from the dashboard net worth card
- cover the new preference and liability handling with feature and
frontend tests

## Screenshots
<img width="1121" height="509" alt="image"
src="https://github.com/user-attachments/assets/ab6a7cde-1052-4dab-aa14-34f6d9528829"
/>
<img width="1122" height="506" alt="image"
src="https://github.com/user-attachments/assets/e48a0369-16c4-4294-ae00-4eba56480264"
/>
2026-03-20 09:55:53 +00:00
Víctor Falcón 6dda5f56ad
feat(cashflow): show tracked transfers in Sankey diagram (#237)
## Summary

Transfer categories with `cashflow_direction` set to `outflow` or
`inflow` now appear in the **Sankey diagram** instead of the 12-month
trend chart. This gives a clearer picture of money flow (e.g.
investments, savings transfers) without inflating the income/expense
trend lines. Hidden transfers remain excluded from everything.

## Changes

- **Sankey endpoint**: Updated join to include tracked transfer
categories on the matching flow side (outflow → expense side, inflow →
income side)
- **Trend endpoint**: Removed `transfer_inflow` and `transfer_outflow`
columns from the SQL query and API response
- **Trend chart UI**: Removed tracked inflow/outflow bars, tooltip rows,
legend items, and related config
- **TypeScript types**: Removed `transfer_inflow`/`transfer_outflow`
from `TrendDataPoint`
- **Category form**: Updated direction field description to reference
"Sankey chart" instead of "monthly trend"
- **Tests**: Updated 4 existing tests and added 2 new tests covering
tracked transfers in Sankey breakdown

## Design decision

Tracked transfers appear **only in the Sankey diagram** — they do not
affect summary totals, savings rate, or breakdown cards. This keeps the
high-level numbers accurate while still visualizing where money flows
internally.
2026-03-19 11:49:54 +01:00
Víctor Falcón 272dac14b8
feat(cashflow): track transfer categories in trends (#236)
## Summary
- add a transfer-only cashflow reporting setting so categories like
investments can appear in the monthly cashflow trend without counting as
income or expense
- extend the cashflow trend API and chart to show tracked transfer
inflows and outflows while keeping summary cards and sankey accounting
unchanged
- expose the new setting in category management and add feature coverage
for tracked transfer analytics and category persistence

## Screenshots
<img width="808" height="164" alt="image"
src="https://github.com/user-attachments/assets/336d27d4-eacb-4a40-ba60-ecee84d65340"
/>
<img width="1098" height="474" alt="image"
src="https://github.com/user-attachments/assets/ca98883a-525d-4f71-aa2f-e8d6083e4564"
/>
<img width="1114" height="713" alt="image"
src="https://github.com/user-attachments/assets/353461b6-e5c0-42cb-a6ed-87629d7b4575"
/>
2026-03-18 14:02:47 +00:00
Víctor Falcón debb47f6af
fix(cashflow): exclude transfer categories from sankey (#235)
## Summary
- exclude transfer-type categories from the cashflow Sankey category
breakdown
- add a regression test to ensure transfer transactions never appear on
either side of the chart
- keep cashflow totals limited to real income and expense categories for
the Sankey response
2026-03-18 11:09:45 +00:00
Víctor Falcón ec245655b8
feat(cashflow): make income/expense category rows clickable to transactions (#234)
## Summary

- Makes Income Sources and Expense Categories rows on the Cashflow page
clickable, navigating to the Transactions page pre-filtered by category
and date range
- Follows the exact same pattern as the dashboard's Top Spending
Categories card (`top-categories-card.tsx`)
- Fixes progress bar hover visibility by adding the `group` Tailwind
class to the link wrapper

## Changes

- **`resources/js/components/cashflow/breakdown-card.tsx`**: Added
optional `period` prop; each category row renders as an Inertia `<Link>`
with `category_ids`, `date_from`, and `date_to` query params when period
is provided; falls back to plain `div` otherwise
- **`resources/js/pages/cashflow/index.tsx`**: Passes `period` to both
`BreakdownCard` instances (income and expense)
- **`tests/Browser/CashflowCategoryNavigationTest.php`**: Browser tests
verifying clicking a category navigates to `/transactions` with the
correct query params
2026-03-17 10:57:40 +00:00
Víctor Falcón 9e2a9cadfe
fix(cashflow): only count sign-matching transactions in Sankey category breakdown (#232)
## Problem

When an income category contained both incoming and outgoing
transactions (e.g. an \"Income from Rents\" category that also holds
property-related expense payments), the Sankey endpoint was summing
**all** amounts together and applying `abs()` to the net result.

Real example reported:

| Date | Description | Amount |
|------|-------------|--------|
| Mar 13 | BIZUM sent — Lavadora | -€124.03 |
| Mar 10 | BIZUM received — luz febrero | +€38.04 |
| Mar 9 | Endesa energy payment | -€38.04 |
| Mar 4 | BIZUM received — agua Febrero | +€41.34 |
| Mar 2 | Comunidad de Propietarios | -€105.92 |

- Net: **-€188.61** → `abs()` → **€188.61 shown as income** 
- Correct: sum of positive flows only → **€79.38** 

## Fix

Added a sign filter to `getCategoryBreakdown()` before the `GROUP BY`
aggregation:
- Income categories: `WHERE transactions.amount > 0`
- Expense categories: `WHERE transactions.amount < 0`

This ensures the Sankey shows the **actual gross flow** on each side
rather than a misleading abs-of-net.

## Test

Added a regression test in `CashflowAnalyticsTest` that reproduces the
exact five transactions from the reported scenario and asserts the
Sankey returns `7938` cents (€79.38) instead of the buggy `18861`
(€188.61).
2026-03-17 09:29:36 +00:00
Víctor Falcón e5fcaee8f8
fix(settings): restore budgets settings redirect (#228)
## Summary
- restore the legacy `/settings/budgets` route as a redirect to
`/budgets` so old settings links no longer trigger a missing Inertia
page
- add a feature test covering the redirect to prevent the broken route
from resurfacing

## Testing
- php artisan test --compact
tests/Feature/Settings/BudgetSettingsRedirectTest.php
2026-03-16 13:04:08 +01:00