## Summary
- mark cashflow analytics JSON responses as no-store/private
- add coverage for cache-control header
## Why
Browser tests reuse the same cashflow API URLs across authenticated
users. Cached user-specific analytics can leak stale breakdown data
between sessions and hide the income category.
## Tests
- php artisan test --compact tests/Feature/CashflowAnalyticsTest.php
## 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
## 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.
## 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
## 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).
## Summary
- Replace `subMonths()` with `subMonthsNoOverflow()` across controllers,
commands, and tests to prevent date overflow on months with 31 days
- On Jan 31, `subMonths(2)` overflows: Nov 31 doesn't exist → Carbon
rolls forward to Dec 1, producing wrong date ranges and off-by-one month
counts
## Root cause
Carbon's `subMonths()` allows day overflow. When the target month has
fewer days than the source date, it rolls into the next month. For
example:
- `2026-01-31 subMonths(2)` → Nov 31 → **2025-12-01** (expected Nov 30)
- `2026-01-31 subMonths(11)` → Feb 31 → **2025-03-03** (expected Feb 28)
`subMonthsNoOverflow()` clamps to the last valid day of the target month
instead.
## Files changed
- `app/Http/Controllers/Api/CashflowAnalyticsController.php` — cashflow
trend start date calculation
- `app/Console/Commands/ResetDemoAccountCommand.php` — balance history
date generation (was causing duplicate `balance_date` entries)
- `tests/Feature/AccountControllerTest.php` — balance evolution `from`
param
- `tests/Feature/CashflowAnalyticsTest.php` — transaction date
generation in loop
- `tests/Feature/DashboardAnalyticsTest.php` — net worth evolution
`from` param
## Test plan
- [x] `account balance evolution returns data` — expects 3 months, was
getting 2
- [x] `cashflow trend returns monthly data` — expects 3 months, was
getting 2
- [x] `cashflow trend defaults to 12 months` — expects 12 months, was
getting 11
- [x] `net worth evolution returns monthly data points` — expects 3
months, was getting 2
- [x] `demo:reset creates demo user` — was throwing
UniqueConstraintViolation on duplicate balance dates
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"
/>