Commit Graph

9 Commits

Author SHA1 Message Date
Víctor Falcón d470ee9646 feat(spaces): scope reads by active space and add the space switcher
Flip every user-facing read (dashboard, cashflow, transactions, budgets,
accounts, settings, analytics, sync, shared props) from user-scoped to
active-space-scoped via explicit $space->… relations, and re-key the category
tree, spending, and budget-assignment services by space_id. Authorization moves
from creator-ownership to space membership.

Add the visible spaces layer behind a Pennant 'spaces' flag: a sidebar space
switcher, a settings page to create/rename/delete/switch spaces, per-space
offline-cache flushing, and the currentSpace/spaces shared props. Data creation
is unchanged — the BelongsToSpace trait already stamps space_id from the active
space. Isolation tests prove no cross-space leakage.
2026-07-09 15:19:03 +02:00
Víctor Falcón e62f1d6aac
refactor(api): standardize serialization via model $hidden (#492)
## What

Standardizes how models are serialized across web, API, and Sync
responses by relying on Eloquent `$hidden` + accessors on the models
themselves, instead of ad-hoc `select` scopes and per-controller field
picking.

Touches 9 models (`Account`, `Bank`, `Budget`, `Category`, `Label`,
`LoanDetail`, `RealEstateDetail`, `Transaction`, plus pivot hiding) and
the controllers/middleware that consumed the old ad-hoc shapes.

## Why

- Single source of truth for response shape lives on the model, aligned
with Wayfinder model typegen.
- Removes duplicated field-selection logic scattered across controllers.
- Continues the duplication-removal PR series (#475–#483).

## How

- Hide internal columns and pivots via `$hidden`; expose computed fields
via accessors.
- Controllers return full models / load full relations rather than
hand-picked columns.
- `HandleInertiaRequests` slimmed down to match.

## Notes for reviewers

- Per-commit breakdown: each model standardized in its own commit for
easy review.
- Tests added/updated for each model to assert the serialized shape
(hidden columns absent, relations present).
- Full suite: 1382 passed / 1 skipped / 0 failed. `pint` clean.
2026-06-05 13:57:34 +02:00
Víctor Falcón 1cc10566a3
feat: parent/child category tree (#474)
## Summary

Adds nested categories (parent → child, up to **3 levels**) across the
app. Children inherit their parent's type and cashflow direction, and
every category selector now renders the hierarchy as an indented tree.
Gated behind the `CategoryTree` Pennant flag (off by default).

## Backend

- **Migration**: nullable self-referencing `parent_id`; uniqueness
scoped per-parent via a `parent_unique_marker` virtual column (root
names stay unique). New composite unique created before dropping the old
one so the `user_id` FK keeps a supporting index.
- **`CategoryTree` service**: descendant/ancestor resolution, depth &
cycle checks, type cascade, subtree deletion.
- **Validation**: depth limit, cycle prevention, inherited + locked
child type.
- **Delete strategies**: reparent (default), promote to root, or cascade
(uncategorizes affected transactions).
- **Transaction filter** expands a selected parent to its descendants.
- **Cashflow Sankey & breakdown** roll up to top-level parents with
click-to-drill (children + a parent "direct" node).
- **Budgets** tracking a parent also count their children's
transactions.
- **Unified** the frontend category query behind
`Category::forDisplay()` / `FRONTEND_COLUMNS` (8 call sites) so every
selector receives the full Category shape, including `parent_id`.

## Frontend

- New `category-tree.ts` helpers (build/flatten/descendants/path,
tree-aware selection toggle + tri-state).
- **Settings page**: indented tree, sortable by name/color/type
(siblings sorted, hierarchy preserved), parent picker, delete-strategy
dialog.
- **Combobox** (transaction table cell + edit/create modal + parent
picker): indented tree, search keeps matches with their ancestors.
- **Transaction filter**: indented tree, tri-state cascading selection
(parent ↔ children), themed checkboxes, selected branches float to top
on open, ancestor-aware search.
- **Categorizer palette** and **budget multi-select**: same indented
tree + ancestor-aware search.
- **Sankey**: click a parent node to drill into its children, with
breadcrumb.
- Pennant `CategoryTree` flag gates the parent UI.

## Tests

- Pest: model/validation, delete strategies, filter expansion, cashflow
rollup/drill, budget child inclusion.
- Vitest: tree-aware selection logic.
- All green; Pint / ESLint / Prettier clean.

## Rollout

Flag is off by default — enable per user with:
\`\`\`
php artisan feature:enable "App\\Features\\CategoryTree" you@example.com
\`\`\`
2026-06-03 19:30:12 +02:00
Víctor Falcón 7784f0d4fb
refactor(banks): add Bank::availableForUser scope (#480)
## What

The "public banks + user's own banks" query was repeated in **5
controllers** (Transaction ×2, Settings/Bank, Budget, Cashflow,
Onboarding).

New `Bank::availableForUser($user)` scope; all 5 sites use it.

Bonus: the Onboarding variant had no `where()` grouping around
`whereNull/orWhere` — harmless today (no other constraints) but a latent
precedence bug. The scope always groups.

## Stats

- **-31 / +24 lines**, 5 call sites → 1 definition

## Checks

- `php artisan test
--filter="Bank|Transaction|Budget|Cashflow|Onboarding"` — 612 passed
(2707 assertions), 1 skipped
- `vendor/bin/pint --dirty` — pass

Part of duplication-removal series (#475–#479).
2026-06-03 17:59:24 +02:00
Víctor Falcón ed737db7b2
feat(cashflow): add savings and period views (#424)
## Summary
- add savings and investment category types and migrate default EN/ES
categories
- show saved and invested amounts on cashflow summary
- add month, quarter, and year cashflow period views

## Validation
- vendor/bin/pint --dirty --format agent
- php -l changed PHP files
- php artisan test --compact tests/Feature/Settings/CategoryTest.php
--filter='migration updates existing default saving and investment
categories'
- npm run test -- --run resources/js/lib/chart-calculations.test.ts

## Notes
<img width="1245" height="464" alt="image"
src="https://github.com/user-attachments/assets/7456e018-fb16-4387-8989-aa0b104a42d0"
/>

- Broadened migration after read-only prod check found legacy default
categories still using expense/hidden.
2026-05-25 16:41:00 +02:00
Víctor Falcón 22952c4e75
fix(cashflow): read period from server props instead of window (#302)
## Summary
- Fixes
[PHP-LARAVEL-9](https://whisper-money.sentry.io/issues/PHP-LARAVEL-9):
`ReferenceError: window is not defined` during Inertia SSR of
`CashflowPage`.
- `CashflowController` now reads + validates the `period` query param
(`YYYY-MM`) and passes it as an Inertia prop.
- Client `useState` initializer reads from `usePage().props.period`
instead of `window.location.search`, making SSR safe and avoiding
hydration mismatch.
- `useEffect` URL sync now compares against the prop, not
`window.location.search`.

## Why (c) not a `typeof window` guard
Server-provided params avoid hydration mismatch and keep URL as single
source of truth.

## Tests
New `tests/Feature/CashflowPageTest.php`:
- guest redirect
- no query param → `period` prop `null`
- valid `?period=2025-03` → prop `'2025-03'`
- invalid value → `null`
- malformed format (`2025-3`) → `null`

All 5 pass (50 assertions).

## Scope
Cashflow only. Other pages with similar `window.location.search` in
`useState` initializers (`onboarding/index.tsx`, `welcome.tsx`,
`auth/login.tsx`) left for follow-up.

Fixes PHP-LARAVEL-9
2026-04-17 10:27:49 +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 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 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