Commit Graph

67 Commits

Author SHA1 Message Date
Víctor Falcón 22043ced29
fix(budgets): remove Custom period type to fix duplicate-key crash (#355)
## Why merge this

Production bug
**[PHP-LARAVEL-1B](https://whisper-money.sentry.io/issues/PHP-LARAVEL-1B)**
— `/budgets/{budget}` throws a 500 (`UniqueConstraintViolationException`
on `budget_periods_budget_id_start_date_unique`) for the affected user.
Page is unusable for them until fixed.

## Root cause

The Custom period type let users create budgets with arbitrary
`period_duration`. Combined with `calculatePeriodDates()`'s day-of-month
snap (`startDate->day(period_start_day)`), short durations produced
periods where `end_date == start_date == period_start_day`. On the next
visit after that day, regeneration computed a new period whose start
snapped backward to the same day → unique key collision.

Concrete trace from the Sentry event:

- Budget: `Seguro de Salud`, `period_type=custom, period_duration=1,
period_start_day=1`, created `2026-05-05`.
- Initial period created at budget creation: `start=2026-05-01,
end=2026-05-01` (already 4 days stale because Custom snapped
`now()=05-05` back to day 1, then `addDays(1).subDay()` produced same
date).
- Today `05-05` → `getCurrentPeriod()` returns null → `generatePeriod()`
snaps back to `05-01` again → `INSERT (budget_id, 05-01)` → 1062.

Verified in prod DB: this is the **only** budget in the entire database
with `period_type='custom'` and the only period row with `end_date <=
start_date`.

## Fix

Custom is the only period type that exposes this snap-back collision
(Monthly/Weekly/Biweekly all use a self-consistent advance). It also has
no defensible UX — every legitimate use case is covered by
Monthly/Weekly/Biweekly. So we remove it end-to-end:

- `BudgetPeriodType::Custom` enum case dropped.
- `BudgetPeriodService`: Custom branches removed from
`calculatePeriodDates()` and `calculateNextPeriodStartDate()`.
- `Budget` model: `period_duration` removed from `$fillable` and
`casts()`.
- `StoreBudgetRequest` / `UpdateBudgetRequest`: `period_duration` rule
removed.
- `BudgetController`: `period_duration` no longer passed in
store/update.
- Create + edit budget dialogs: Custom option and `period_duration`
input removed.
- `ResetDemoAccountCommand`: Custom switch case removed.
- `BudgetFactory`: `custom()` state and `period_duration` default
removed.
- `types/budget.ts`: `'custom'` removed from `BUDGET_PERIOD_TYPES` and
`Budget.period_duration` field dropped.
- Migration `2026_05_05_132023_convert_custom_budgets_to_monthly`:
rewrites any existing `custom` rows to `monthly` with
`period_duration=null, period_start_day=1` so the dropped enum case
can't crash on hydration.

Period generation logic for Monthly/Weekly/Biweekly is **unchanged**.

## Manual prod cleanup (separate, after merge)

The single buggy budget in prod will be deleted manually:

```sql
DELETE FROM budget_periods WHERE id = '019df7fd-6073-731b-9c08-f3723515292b';
DELETE FROM budgets WHERE id = '019df7fd-6071-7219-b190-ece22ccdb63f';
```

## Tests

`tests/Feature/BudgetPeriodServiceTest.php` covers Monthly + Weekly
advance and Monthly first-period generation. Existing
`BudgetPeriodDateTest` unaffected.

## Risk

- `period_duration` column kept in DB (nullable) to avoid data loss; no
migration needed.
- Migration ensures any environment with `period_type='custom'` rows is
converted before the enum case is removed, preventing hydration errors.
- No customers actively rely on Custom: prod query confirmed only the
single broken budget uses it, and that one is being deleted.

Fixes PHP-LARAVEL-1B
2026-05-05 16:07:16 +02:00
Víctor Falcón 3500eaa469
refactor(real-estate): remove Pennant gating (#308)
## Summary
- remove the `real-estate` Pennant flag definition, request gating,
local auto-activation middleware, and frontend shared flag plumbing
- make real-estate account creation and onboarding availability the
default path for all users
- update feature and browser tests to cover default availability instead
of flag enable/disable behavior

## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Feature/RealEstateTest.php
- php artisan test --compact
tests/Feature/RealEstateAvailabilityTest.php
- php artisan test --compact tests/Browser/RealEstateAccountTest.php
- php artisan test --compact tests/Browser/OnboardingFlowTest.php
- php artisan test --compact --filter=\"can create a real estate account
linked to an existing loan\" tests/Browser/BankAccountsTest.php

## Notes
- `npm run build` completed the client build, but the Sentry sourcemap
upload step failed with an SSL/TLS error from the configured plugin
- deploy follow-up: run `php artisan pennant:purge real-estate` to
remove stale stored flag values
2026-04-20 13:31:49 +01:00
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 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 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 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 6e976354ba feat(accounts): merge real estate accounts with linked mortgages in UI (#248)
Combine real estate and linked loan accounts into a unified card on the
accounts list, showing equity as the primary balance with a dual-line
sparkline (solid for market value, dashed for mortgage owed). Hide linked
loans from the loan group and display mortgage bank info in the subtitle.

On the detail page, render LoanDetailsCard for real estate accounts with
linked loans and add header actions for editing loan details and updating
owed amounts via dialogs.
2026-03-26 20:54:12 +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 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 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 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 ac1476eeff
feat(pricing): dynamic Stripe pricing with locale-aware formatting (#204)
## Summary

- **Dynamic Stripe price resolution**: Replaces hardcoded
`stripe_price_id` env vars with lookup-key-based resolution
(`stripe_lookup_key`). A new `php artisan stripe:sync-prices` command
creates/updates Stripe prices from `config/subscriptions.php`
automatically.
- **Locale-aware currency formatting**: Replaces all
`getCurrencySymbol() + toFixed(2)` patterns with `formatCurrency()`
(backed by `Intl.NumberFormat`) across `welcome.tsx`, `paywall.tsx`,
`billing.tsx`, and `step-create-account.tsx`, so symbol position and
separators are correct for the user's locale (e.g. `3,90 €` in Spanish).
- **EUR defaults and updated plan prices**: Cashier currency defaulted
to EUR, plan prices updated to €7.80/month and €46.80/year, and
`pricing.currency` is now shared as an Inertia prop.
- **Promo/discount cleanup**: Removed all FOUNDER discount mentions and
Discord community links from the paywall, landing pricing section, and
invitation email.
2026-03-05 11:41:59 +00:00
Víctor Falcón 186ae57c2d
refactor(accounts): unify account type icons via accountIconByType helper (#180)
## Why

### Problem
Account type icons were defined in two separate places —
`step-account-types.tsx` (onboarding) and `account-type-icon.tsx`
(dashboard) — using different icon choices for the same account types.
For example, `checking` showed `Building2` on the dashboard but `Wallet`
in onboarding, and `retirement` showed `Umbrella` vs `TrendingUp`. This
created a visually inconsistent experience.

## What

### Changes
- Added `accountIconByType(type: AccountType): LucideIcon` helper to
`resources/js/types/account.ts` as the single source of truth for
account type icons
- Updated `AccountTypeIcon` component to delegate to the new helper
instead of maintaining its own map
- Updated `StepAccountTypes` to derive icons from the helper instead of
storing them inline per entry
- Dashboard account cards now use the same icon set as the onboarding
screen (Wallet, PiggyBank, CreditCard, LineChart, TrendingUp, Building2)

## Verification

### Tests
No behaviour change — purely a refactor consolidating icon mapping into
one place. Existing tests remain unaffected.
2026-03-02 08:47:44 +00:00
Víctor Falcón 7260525890
fix(i18n): localise missing strings in budget dialogs to Spanish (#177)
## Summary

- Wrap all hardcoded strings in `create-budget-dialog.tsx` and
`edit-budget-dialog.tsx` with `__()` so they pass through the i18n
system (submit button label, start day labels, helper text for
carry-over/reset, and the selection-required validation message)
- Update `getBudgetPeriodTypeLabel` and `getRolloverTypeLabel` in
`budget.ts` to use `__()` so period type options (Monthly, Weekly,
Bi-weekly, Custom) and rollover types (Carry Over, Reset/Pool) are
translated
- Add all missing Spanish translations to `lang/es.json`: period type
labels, rollover type labels, start-day helpers, carry-over/reset
descriptions, and validation messages
2026-03-01 17:56:02 +00:00
Víctor Falcón 717bf34103
fix(i18n): localize mobile bottom navigation labels into Spanish (#173)
## Why

### Problem
The mobile bottom tab bar navigation labels (Dashboard, Cashflow,
Accounts, Transactions, Budgets) were being rendered as raw English
strings regardless of the user's locale. Spanish-speaking users would
see English labels in the mobile navigation despite the rest of the app
being translated.

## What

### Changes
- Import the `__()` translation utility in `app-sidebar.tsx`
- Wrap `{item.title}` with `{__(item.title)}` in the mobile bottom tab
bar so labels go through the same translation lookup as the desktop
sidebar

All relevant Spanish translations (`"Dashboard"`, `"Cashflow"`,
`"Accounts"`, `"Transactions"`, `"Budgets"`) were already present in
`lang/es.json`.

## Verification
<img width="343" height="118" alt="image"
src="https://github.com/user-attachments/assets/c58acff7-fdbd-4dda-b2fc-4201865118c1"
/>
2026-03-01 12:04:44 +00:00
Víctor Falcón 0c5ba916bf
Add chart color scheme setting (#101)
## Summary

- Add a new **Chart color scheme** dropdown in Settings > Appearance
allowing users to choose between 4 color palettes: **Colorful** (new
default), **Neutral** (previous zinc grayscale), **Blue**, and **Pink**
- Persist the setting in a new `user_settings` table with instant
client-side feedback via localStorage + cookie
- All charts across dashboard, categories, budgets, and cashflow update
instantly when switching schemes
- Reduced colorful palette intensity (shifted from 500/600 to 300/400
range) for lower contrast
2026-02-28 12:58:21 +01:00
Víctor Falcón 0a9ca5b606
feat: enable invested amount tracking for savings accounts (#142)
## Summary

- Adds `Savings` to the list of account types that support invested
amount tracking, alongside `Investment` and `Retirement`
- Decouples `supportsInvestedAmount()` from
`NON_TRANSACTIONAL_ACCOUNT_TYPES` in TypeScript, since savings accounts
are transactional but now also support invested amount tracking
- Updates unit tests to reflect savings accounts supporting invested
amounts

This enables remunerated savings accounts to track invested money vs.
current balance to visualize gains/losses — the same way investment and
pension accounts already do.
2026-02-23 17:19:33 +01:00
Víctor Falcón 299b8a56d8
feat: investment benefits — show gains/losses on investment accounts (#140)
## Why

Investment and retirement accounts show balance over time, but there's
no way to see how much money was actually put in versus how much is
current value. Users can't tell at a glance whether their investments
are up or down.

## What

Adds an "invested amount" tracking system across the full stack:

**Backend**
- New `invested_amount` column on `account_balances` (nullable
bigInteger, cents, per-date)
- Auto-sync from providers: Indexa Capital (instruments_cost +
cash_amount), Bitpanda (fiat deposit/withdrawal history), Binance
(90-day windowed deposit/withdrawal with crypto→fiat conversion)
- Manual input support via Update Balance dialog
- Historical invested amount data in all balance evolution APIs (net
worth, account detail)

**Frontend**
- Dashed line on sparkline charts (dashboard + accounts page) showing
per-point historical invested amount alongside balance
- Dashed line on account detail charts (daily AreaChart + monthly
ComposedChart)
- Tooltips with labeled rows: Balance, Invested, Gain/loss (color-coded)
- Invested amount column in balances history modal
- Invested amount field in balance import wizard (CSV mapping)
- Demo account seeder updated with invested amount data

## Screenshots
<img width="1301" height="750" alt="image"
src="https://github.com/user-attachments/assets/0f05ecd0-8b98-47b4-9fa4-027f0311e3bb"
/>
<img width="744" height="374" alt="image"
src="https://github.com/user-attachments/assets/c4daa816-dee0-4f94-957f-317a13bc80d5"
/>
<img width="1267" height="738" alt="image"
src="https://github.com/user-attachments/assets/21df350c-6954-4ff5-8b3c-b858df3a8b3a"
/>
<img width="1301" height="828" alt="image"
src="https://github.com/user-attachments/assets/16f5f021-a926-4e8e-a999-c4ca32d1ea3d"
/>
<img width="1274" height="845" alt="image"
src="https://github.com/user-attachments/assets/62f2dfc0-04f0-4bdb-b072-cf7cd1be77d3"
/>
2026-02-23 13:59:10 +01:00
Víctor Falcón d1efc03fc5
Move transaction filtering from client-side to server-side (#126)
## Summary

- **Server-side filtering/sorting/search**: With encryption removed, all
transaction filtering, sorting, and full-text search (LIKE on
description + notes) now happens via SQL queries instead of client-side
IndexedDB with decryption
- **Cursor pagination**: Transactions are returned with cursor-based
pagination (default 50 per page, "Load More" pattern) instead of loading
all into IndexedDB
- **Removed IndexedDB dependency**: The main transactions page no longer
depends on Dexie/IndexedDB for data or sync — other pages (categorize,
accounts/show, budgets/show) are unaffected and will be migrated
separately

## Changes

| File | Change |
|------|--------|
| `app/Models/Transaction.php` | Added `scopeApplyFilters` query scope |
| `app/Http/Requests/IndexTransactionRequest.php` | New form request for
filter validation |
| `app/Http/Controllers/TransactionController.php` | Server-filtered
cursor-paginated index, refactored bulkUpdate to reuse scope |
| `resources/js/pages/transactions/index.tsx` | Major rewrite —
server-driven filtering, sorting, pagination via `router.visit()` |
| `resources/js/components/transactions/transaction-filters.tsx` |
Search always enabled (removed encryption key dependency) |
| `resources/js/types/transaction.ts` | Added `ServerTransaction` type |
| `resources/js/contexts/sync-context.tsx` | Simplified — removed
transaction sync service |
| `tests/Feature/TransactionFilterTest.php` | 20 new Pest tests for all
filter scenarios |

## Test plan

- [x] Run `php artisan test` — all 523+ tests pass
- [x] Navigate to `/transactions` — paginated results load from server
- [x] Apply each filter type (date, amount, category, account, label,
search) and verify server round-trip
- [x] Test "uncategorized" category filter
- [x] Test combined filters
- [x] Test sorting by date, amount, description (ascending/descending)
- [x] Test "Load More" button for cursor pagination
- [x] Test bulk operations (select some → update, select all filtered →
update)
- [x] Verify URL persistence (refresh page with filters in URL)
- [x] Test search on both description and notes fields
2026-02-16 12:49:04 +01:00
Víctor Falcón 6abec95d0e
feat: Decrypt encrypted transactions on key unlock (#123)
## Summary

- Add `GET /api/transactions?encrypted=true` endpoint for paginated
listing of encrypted transactions
- Add `PATCH /api/transactions/bulk` endpoint for batch-updating
decrypted transaction data (max 50 per request, bypasses model events)
- Add `useDecryptTransactions` hook that mirrors the existing account
name decryption flow: fetches encrypted transactions page-by-page,
decrypts `description`/`notes` client-side via Web Crypto API, sends
plaintext back and clears IVs
- Wire the hook into `AppSidebarLayout` so decryption runs automatically
when the user unlocks their encryption key
- Once all transactions (and accounts) are decrypted, the encryption key
button in the header disappears automatically

## Test plan

- [x] 11 Pest tests covering encrypted/plaintext filtering, pagination,
user scoping, bulk update, authorization, validation, nullable notes,
guest access, and no-model-events behavior
- [x] Manual: create encrypted transactions, unlock encryption key,
verify transactions get decrypted and the key button disappears
2026-02-16 10:37:43 +01:00
Víctor Falcón 6b05de173a
Remove plaintext-transactions feature flag & E2E references (#116)
## Summary

- Removes the `plaintext-transactions` Pennant feature flag — plaintext
is now the default for all users
- Removes encryption guards and `isPlaintext` conditionals from
transaction create/edit/import flows, keeping only the plaintext code
paths
- Makes `EncryptionKeyButton` conditional — only shown when user has
legacy encrypted accounts or transactions
- Removes encryption onboarding steps (`step-encryption-explained`,
`step-encryption-setup`) and related state/props
- Updates landing page to replace E2E encryption marketing with
privacy-first messaging ("Your Data, Your Rules" section)
- Updates privacy policy to replace E2E encryption claims with accurate
security language (encryption at rest, TLS in transit, no third-party
sharing)
- Cleans up tests to remove feature flag assertions and
`Feature::activate()` calls

**22 files changed, 145 insertions, 730 deletions**

## Test plan

- [x] Create a transaction without encryption key unlocked — should
succeed
- [x] EncryptionKeyButton should not appear in header for users with no
encrypted data
- [x] EncryptionKeyButton should appear for users with legacy encrypted
transactions/accounts
- [x] Landing page has no E2E encryption references, shows new privacy
section
- [x] Onboarding flow has no encryption setup steps
- [x] Privacy policy reflects accurate security language
- [x] Frontend builds successfully (`bun run build`)
- [x] All linting passes (`bun run lint`, `vendor/bin/pint --dirty`)
2026-02-13 11:10:21 +01:00
Víctor Falcón c7f3f1a978
fix: Delete pending connection and show toast on cancelled bank authorization (#111)
## Summary
- When a user cancels the bank authorization flow (e.g. clicks cancel on
the bank's page), the pending `BankingConnection` is now soft-deleted so
it doesn't remain stuck in `pending` status
- Flash messages (`success`/`error`) are now shared with the frontend
via Inertia shared data
- The connections settings page shows a toast notification with the
error message on redirect

## Test plan
- [ ] Start a bank connection flow, cancel on the bank's authorization
page, and verify:
  - A toast error appears on the connections page
  - The pending connection is removed from the list
- [x] Complete a successful bank connection and verify the success toast
appears
- [x] Run `php artisan test
tests/Feature/OpenBanking/AuthorizationControllerTest.php`
2026-02-12 11:10:15 +01:00
Víctor Falcón d1d1be7586
Remove budgets feature flag (#108)
## Summary
- Remove the `budgets` Pennant feature flag — budgets is now enabled for
all users
- Delete `EnsureBudgetsFeature` middleware and its route guard
- Remove `budgets` from shared Inertia features and the `Features`
TypeScript interface
- Remove all `Feature::for($user)->activate('budgets')` calls from tests
- Delete `BudgetFeatureFlagTest` and feature-disabled test cases from
`BudgetsFeatureNavigationTest`
2026-02-12 09:58:01 +01:00
Víctor Falcón db7b6e4da7
feat: Integrate EnableBanking as open banking provider (#106)
## Summary

- Adds **EnableBanking** as the first open banking provider, allowing
users to connect real bank accounts and automatically sync transactions
and balances
- Uses a **`BankingProviderInterface`** contract so future providers
(Plaid, GoCardless, etc.) can be added by implementing the same
interface
- Feature-flagged behind the **`open-banking`** Pennant flag (default:
off)
- Connected accounts are **unencrypted** and transactions have `source =
'enablebanking'`

### What's included

**Backend:**
- `BankingProviderInterface` contract + `EnableBankingProvider`
implementation (JWT RS256 auth)
- `BankingConnection` model with full lifecycle (pending →
awaiting_mapping → active → expired/revoked/error)
- `TransactionSyncService` — pagination, deduplication by
`external_transaction_id`, amount/date mapping
- `BalanceSyncService` — preferred balance type selection (CLBD → ITAV
fallback)
- Authorization flow: start auth → bank redirect → callback → session
creation → account mapping → sync
- `SyncBankingConnectionJob` (unique per connection, 3 retries) +
scheduled every 6 hours
- `banking:sync` artisan command
- 5 migrations: `banking_connections` table, account fields, transaction
`external_transaction_id`, `pending_accounts_data`, `linked_at`

**Frontend:**
- Manual vs Connected account choice in the create account dialog
- Multi-step bank connection dialog (country → bank selection →
confirmation → redirect)
- Account mapping page — map discovered bank accounts to existing
accounts, create new ones, or skip
- Settings/Connections page with status badges, sync/disconnect actions
- "Connected" badge on linked accounts in settings

**Tests:**
- 49 tests covering feature flags, controllers, account mapping,
transaction sync, balance sync, deduplication, and pagination

### Feature Flags

This PR introduces **two Pennant feature flags**:

1. **`open-banking`** — Gates the entire open banking feature
(institutions endpoint, authorization flow, connections page). When
disabled, all open banking routes return 404.

2. **`account-mapping`** — Controls whether users see an intermediate
account mapping step after connecting a bank. When **enabled**, users
are redirected to a mapping page where they can choose to create new
accounts, link to existing ones, or skip each discovered bank account.
When **disabled**, all discovered accounts are automatically created
(original behavior). Linked accounts only sync transactions from their
last transaction date and only update the current balance from the
provider (no historical balance calculation or daily balance tracking).

Enable per-user:
```bash
php artisan feature:enable open-banking user@example.com
php artisan feature:enable account-mapping user@example.com
```

Enable for all users:
```bash
php artisan feature:enable open-banking all
php artisan feature:enable account-mapping all
```

## Test plan

- [x] Enable feature flag: `php artisan feature:enable open-banking`
- [x] Verify "Connected" option appears in create account dialog
- [x] Start authorization flow and verify redirect to bank
- [x] With `account-mapping` **disabled**: verify callback creates
accounts directly and dispatches sync
- [x] With `account-mapping` **enabled**: verify callback redirects to
mapping page
- [x] Test mapping page: create new, link to existing, and skip actions
- [x] Verify linked accounts sync only from last transaction date and
only update current balance
- [x] Verify connections page shows "Setup Required" badge for
awaiting_mapping status
- [x] Run `php artisan banking:sync` and verify transactions sync
- [x] Verify connections page shows status, sync, and disconnect actions
- [x] Run full test suite: `php artisan test --compact`
2026-02-12 09:09:28 +01:00
Víctor Falcón e35f7125b3
feat: Plaintext transactions behind feature flag (#105)
## Summary

- Adds `plaintext-transactions` Pennant feature flag (defaults to
`false` / encryption ON)
- When active, new transactions are stored as plaintext (no client-side
encryption)
- Existing encrypted transactions continue to work — detection is based
on `description_iv` being NULL (plaintext) vs present (encrypted)
- Migration makes `description_iv` nullable on the transactions table

## Changes

**Backend:**
- Feature flag definition in `AppServiceProvider`, shared via
`HandleInertiaRequests`
- `StoreTransactionRequest` / `UpdateTransactionRequest` conditionally
require `description_iv`
- `TransactionFactory` gains a `plaintext()` state

**Frontend:**
- Edit dialog and import drawer skip encryption when flag is active
- `EncryptedTransactionDescription` renders plaintext directly when IV
is null
- All decryption loops handle both encrypted and plaintext transactions
- Rule re-evaluation stores notes as plaintext when flag is active

## Test plan

- [x] Run `php artisan migrate` to apply the migration
- [x] Activate flag: `php artisan feature:enable plaintext-transactions
all`
- [ ] Create a transaction — verify description is stored as plaintext
in DB (`description_iv` is NULL)
- [ ] Deactivate flag: `php artisan feature:disable
plaintext-transactions all`
- [ ] Create a transaction — verify encryption is still required (422
without IV)
- [x] Verify old encrypted transactions still display correctly
- [ ] Run `php artisan test --compact
tests/Feature/PlaintextTransactionsTest.php`
2026-02-10 14:31:24 +01:00
Víctor Falcón 2dc8cb554c
Remove encryption from bank account names (#104)
## Summary

- Store account names in plaintext instead of encrypting them
client-side. Encryption remains only for transaction descriptions and
notes.
- Existing encrypted account names are silently migrated when the user
unlocks their encryption key, via a new `useDecryptAccountNames` hook
that calls API endpoints to update each account.
- New `AccountName` component handles rendering both encrypted (legacy)
and plaintext accounts across the app.

## Test plan

- [x] Create a new account — name should be stored in plaintext
(`encrypted = false`, `name_iv = null`)
- [x] Unlock encryption key with existing encrypted accounts — they
should auto-migrate silently
- [x] After migration, verify accounts show `encrypted = false` in DB
and names are readable plaintext
- [x] Edit a migrated account — should work without encryption
- [x] Verify all account name displays work across dashboard, account
details, transactions, settings
2026-02-09 14:15:26 +01:00
Víctor Falcón 70b603e901
feat: Spanish localization (#74)
## Pending
- [x] Translate landing page
- [x] Dashboard
- [x] Accounts list page
- [x] Account page
- [x] Cashflow
- [x] Budgets
- [x] Transactions
- [x] Settings

## Screenshots
<img width="1210" height="969" alt="image"
src="https://github.com/user-attachments/assets/c7935e5c-488d-4941-8f19-8834e5668257"
/>
<img width="1211" height="972" alt="image"
src="https://github.com/user-attachments/assets/e94e1daf-233a-4a49-aa65-5678c772d178"
/>
2026-02-08 11:58:08 +01:00
Víctor Falcón fee7ad36ab
feat: Load transactions history on budget created (#72) 2026-01-22 11:10:15 +01:00
Víctor Falcón 9b6c30775f
Add Budgeting Feature to Track and Manage Spending (#36)
## Overview

We're excited to introduce budgeting capabilities to Whisper Money! This
feature helps you take control of your finances by setting spending
limits and tracking your progress over time.

## Screenshots
<img width="1316" height="793" alt="image"
src="https://github.com/user-attachments/assets/ac394d36-cded-4ea4-9883-120785e260f1"
/>
<img width="1315" height="907" alt="image"
src="https://github.com/user-attachments/assets/7c682474-5aa7-4388-b626-29b56f5ebbef"
/>
<img width="1315" height="992" alt="image"
src="https://github.com/user-attachments/assets/21eace45-23c6-472d-9aa0-0feb6db3fba4"
/>

## What's New

### Create Flexible Budgets
- Set budgets for specific categories or labels
- Choose from monthly, weekly, bi-weekly, or custom periods
- Set your own budget start date for better alignment with your pay
schedule

### Track Your Spending
- Visual spending charts show how much you've spent vs. your budget
- See at a glance which budgets are on track and which need attention
- View all transactions that count toward each budget

### Smart Budget Management
- **Carry Over**: Unused budget amounts automatically roll into the next
period
- **Reset**: Unused amounts return to your available money pool
- Edit or delete budgets anytime as your needs change

### Easy Access
- New Budgets section in the main navigation
- Quick overview cards showing budget status
- Detailed budget pages with spending history and transaction lists

## How It Works

1. Create a budget by selecting a category or label and setting your
spending limit
2. Your transactions are automatically matched to relevant budgets
3. Track your progress with visual charts and spending summaries
4. Adjust your budgets as needed to stay on track with your financial
goals

This feature is now available behind a feature flag and can be enabled
for users who want to start budgeting their expenses.
2026-01-21 15:25:50 +01:00
Víctor Falcón f5d09eb247
feat: Add PostHog (#70) 2026-01-20 10:47:59 +01:00
Víctor Falcón 9bd1fcea37
Demo Account Experience (#51)
## Summary

<img width="1220" height="1001" alt="whispermoney test_"
src="https://github.com/user-attachments/assets/c35751d5-385b-449c-81d6-14b5b6577ff2"
/>

Introduces a fully-functional demo account that lets prospective users
explore Whisper Money without creating an account. Users can click
"Check Demo" on the welcome page to instantly access a pre-populated
account with realistic financial data spanning 12 months.

## What's New

**Try Before You Sign Up**
- New "Check Demo" button on the welcome page for instant access
- Pre-configured demo account with real-world financial scenarios
- 12 months of sample transactions across multiple account types
(checking, savings, credit cards, investments)
- Pre-built automation rules, labels, and categories to showcase the
full app experience

**Demo Account Limitations**
- Demo accounts are read-only for sensitive operations (can't change
password, email, or payment settings)
- Clear messaging throughout the UI when demo restrictions apply
- Settings pages show helpful notices about demo limitations
- Automatic daily reset to maintain fresh demo experience

**Developer Experience**
- `php artisan demo:reset` command for manual resets
- Configurable via environment variables (DEMO_EMAIL, DEMO_PASSWORD,
DEMO_ENCRYPTION_KEY)
- Comprehensive test coverage for demo restrictions and data generation

## User Impact

This feature removes the friction of signing up before understanding the
product's value. Users can:
- Explore all features with realistic data
- Test automation rules and see them in action
- View charts and insights based on a year of financial activity
- Experience the full privacy-first encryption workflow
- Understand the product before committing to create an account

Perfect for demos, screenshots, documentation, and helping users make
informed decisions about whether Whisper Money fits their needs.
2026-01-07 10:58:14 +01:00
Víctor Falcón 1cec4a6421
Add Cashflow Analytics Feature (#49)
Summary
- Add new Cashflow page with income/expense analytics, Sankey chart
visualization, and trend analysis
- Add Cashflow summary widget to the dashboard
- Implement feature flag system using Laravel Pennant to control feature
visibility per user
- Add artisan commands to enable/disable features: `php artisan
feature:enable/disable [feature] [email|all]`

## New Features
- **Cashflow Page**: Period-based income/expense breakdown with category
analysis
- **Sankey Chart**: Visual flow diagram showing money movement between
categories
- **Trend Chart**: 12-month historical cashflow visualization
- **Dashboard Widget**: Quick cashflow summary card
- **Feature Flags**: Cashflow feature hidden behind feature flag,
controllable per-user

## How to enable it
This feature it's under a feature flag until we are sure it's working
fine. If you want to test it, [send us a message on the general channel
in our discord](https://discord.gg/9UQWZECDDv). If you're self hosting
just run `php artisan feature:enable CashflowFeature {your_email}`.

## New Cashflow Page
<img width="1278" height="1185" alt="image"
src="https://github.com/user-attachments/assets/83469f24-9f96-4e5f-bef9-86c076d31243"
/>
<img width="1274" height="1035" alt="image"
src="https://github.com/user-attachments/assets/8949ff9d-c370-4fc4-81c6-268307c587a5"
/>

## New Dashboard Widget
<img width="1180" height="289" alt="image"
src="https://github.com/user-attachments/assets/ad984a66-9ffd-4079-85ce-0b2d28278d76"
/>
<img width="1179" height="286" alt="image"
src="https://github.com/user-attachments/assets/bff60286-8ea2-4e9c-8fa6-22755566648f"
/>
2026-01-05 13:06:50 +01:00
Víctor Falcón 700f9325a3 Add currency selection to user profile settings (#41) 2025-12-30 07:22:19 +01:00
Víctor Falcón db81c9b888 feat: add version tracking with git tags and changelog
- Add version field to package.json (0.1.0)
- Install release-it for automated releases with conventional changelog
- Create .release-it.json configuration
- Create initial CHANGELOG.md
- Share version via Inertia (reads from package.json)
- Display version in user dropdown menu
2025-12-30 07:22:19 +01:00
Víctor Falcón 4b5d65ba03
feat: add transaction labels feature (#24)
## Summary

Add a labeling system for transactions that differs from categories in
that labels are ephemeral and created on-the-fly when assigned (no
pre-creation required). A transaction can have multiple labels
(many-to-many relationship), and labels can be used for filtering in the
transaction table and as actions in automation rules.

<img width="1372" height="484" alt="image"
src="https://github.com/user-attachments/assets/fd342b11-dafb-44ed-b818-578e1ea856e6"
/>
<img width="1373" height="613" alt="image"
src="https://github.com/user-attachments/assets/aa5cfb8b-50b7-4101-a872-54904924f234"
/>
<img width="1035" height="594" alt="image"
src="https://github.com/user-attachments/assets/ffa946b2-b01f-496b-8cb8-ab55ab5b79ed"
/>


## Changes

### Backend (Laravel)

- Add `labels` table with user_id, name, color, and soft deletes
- Add `label_transaction` pivot table for transaction-label many-to-many
- Add `automation_rule_labels` pivot table for automation rule-label
many-to-many
- Create Label model with relationships to User, Transaction, and
AutomationRule
- Add LabelController for CRUD operations (returns JSON for on-the-fly
creation)
- Add LabelSyncController for frontend sync
- Add LabelPolicy for authorization
- Update TransactionController to handle bulk label updates
- Update AutomationRuleController to handle label actions
- Add validation for label_ids in transactions and automation rules

### Frontend (React/TypeScript)

- Add Label type and color utilities
- Add labels table to IndexedDB (Dexie version 6)
- Create label-sync service with findOrCreate functionality
- Create LabelCombobox component with multi-select and create-on-type
- Add labels column to transactions table
- Add labels filter to transaction filters
- Add bulk label assignment in bulk actions bar
- Add labels action in create automation rule dialog
- Update rule engine to include labels in evaluation results

## Testing

- 11 feature tests for label CRUD
- 3 tests for label sync
- All 241 feature tests pass

## Screenshots

N/A - UI changes can be reviewed in browser
2025-12-13 13:02:19 +01:00
Víctor Falcón ec6b6d04cf
Add transaction selection to import preview (#25)
Allow users to select individual transactions for import instead of
automatically importing all non-duplicate transactions. Adds checkboxes
to the preview table with select-all functionality and updates the
import logic to only process selected transactions.

<img width="1076" height="592" alt="image"
src="https://github.com/user-attachments/assets/06dab7d0-87f1-4c69-97a4-fa98b33062d7"
/>
2025-12-13 12:41:18 +01:00
Víctor Falcón 58b934333f
feat: centralize pricing config with multiple plans support (#20)
## Summary

Centralizes all pricing/subscription plan configuration in a single PHP
config file, making it easy to modify prices, features, and promotional
codes from one place.

## Changes

### Config (`config/subscriptions.php`)
- Added `plans` array with support for multiple billing cycles (monthly,
yearly, lifetime)
- Each plan includes: name, price, original_price (for discounts),
stripe_price_id, billing_period, and features
- Added `default_plan` setting to control which plan is highlighted
- Added `promo` config with enabled flag, code, description, and badge

### Frontend
- Created TypeScript types for `Plan`, `PromoConfig`, and
`PricingConfig`
- Updated `welcome.tsx` to display all plans in a responsive grid layout
- Updated `paywall.tsx` to show all plans with "Most Popular" and "Best
Value" badges
- Promo code section is now conditionally rendered based on
`promo.enabled`

### Backend
- Updated `HandleInertiaRequests.php` to share pricing config globally
- Removed redundant props from routes

### Tests
- Added test to verify pricing config structure is passed correctly

## How to Use

To modify pricing, edit `config/subscriptions.php`:
- Change `default_plan` to highlight a different plan
- Set `promo.enabled` to `false` (or `PROMO_ENABLED=false` in `.env`) to
hide promo codes
- Add/remove/modify plans as needed
2025-12-09 10:03:35 +01:00
Víctor Falcón 374ce0546b
Subscriptions (#15)
## Subscribe paywall
<img width="1119" height="764" alt="image"
src="https://github.com/user-attachments/assets/55367c91-f2b9-43f5-b450-75faf867bde0"
/>

## Subscribe success page
<img width="1112" height="681" alt="image"
src="https://github.com/user-attachments/assets/a923752a-d506-410e-ac66-e02b96acca20"
/>

## Manage subscription page
<img width="1221" height="705" alt="image"
src="https://github.com/user-attachments/assets/1eba1fec-7fb7-4500-8549-d8def809ddae"
/>
2025-12-06 19:09:56 +01:00
Víctor Falcón 517d758a9d
Import balances assistant (#14)
<img width="1220" height="803" alt="image"
src="https://github.com/user-attachments/assets/ae0be642-6648-456a-8c43-cca69fd2e750"
/>
2025-12-05 16:39:56 +01:00
Víctor Falcón d179c952c7
Accounts: Allow users to create custom banks with his own logo, and name (#12)
**When a bank is not found**
<img width="1220" height="619" alt="image"
src="https://github.com/user-attachments/assets/f46cdedf-8c12-4d9b-a531-8ab17abba9c2"
/>

**You can create a new one**
<img width="1218" height="695" alt="image"
src="https://github.com/user-attachments/assets/115e6b44-d423-4edf-a9aa-e45da81f9598"
/>
2025-12-04 15:24:04 +01:00
Víctor Falcón baa77a248b
Add category type field support (#2)
* Add category type field support

- Remove default value from type column in migration
- Add type validation to StoreCategoryRequest and UpdateCategoryRequest
- Include type field in CategoryController index response
- Assign proper types to all default categories (income/expense/transfer)
- Update CategoryFactory to include type field
- Add type field to TypeScript Category interface
- Add type select field to create and edit category dialogs
- Update all category tests to include type field
- Add new tests for type field validation

All tests passing (27 category-related tests)

* Add CategoryColor enum and update validation rules

* Remove unused AccountType enum class

* Add category type column to settings page

* Remove obsolete import screenshots, add new category edit screenshot

* feat(ui): add transfer type description alerts

* feat(tests): Add .gitignore and update CategoriesTest.php for Browser tests

* fix: update transfer category UI and test

* chore: update .gitignore to include Screenshots directory

* Change category type from income to expense
2025-12-01 20:19:47 +01:00
Víctor Falcón 2fefb64710 Fix CI: Update bun.lock and fix linting errors 2025-12-01 15:16:06 +01:00
Víctor Falcón c339105875 feat: Add new category icons and colors 2025-12-01 14:55:09 +01:00
Víctor Falcón a4ed4e73c1 Add multi-column description mapping support
- Allow description field to map to multiple columns
- Update ColumnMapping type to accept string or string[] for description
- Join multiple description columns with newlines
- Add validation for multi-column descriptions
- Add test asset for multi-description CSV files
2025-12-01 10:30:11 +01:00
Víctor Falcón 8bee848fe3 Save transaction source (manually_created, or imported)
- Depending on the source, we allow or not to update the transaction
description.
2025-11-28 12:36:48 +01:00
Víctor Falcón 3cc225deb0 Y3:0 2025-11-26 12:01:49 +01:00
Víctor Falcón 669646aacb SEO landing page 2025-11-25 11:33:11 +01:00
Víctor Falcón 9d57804dc5 Migrate ID's to UUIDv7 2025-11-15 21:25:33 +01:00