whisper-money/resources/js/types
Víctor Falcón 0351dbfb38
feat(budgets): add per-budget email notifications (#731)
## What

Adds opt-in **email notifications per budget** for three events, plus a
dedicated **Notifications** settings page.

Each budget can independently enable:
- **New transaction** — a transaction was assigned to the budget
- **Close to limit** — spending crossed 90% of the limit
- **Over limit** — spending reached/passed the limit

A **Default (new budgets)** row lets you set the toggles that newly
created budgets inherit.

The existing *bank-transactions-synced* email toggle was **moved** from
the account page into this new Notifications page, so all email
preferences live in one place.

## UI

`Settings → Notifications`: one "Email notifications" section containing
the bank-sync toggle and a "Budgets" table (rows = Default + each
budget, columns = the three events).

<img width="1265" height="638" alt="budget-notifications-settings"
src="https://github.com/user-attachments/assets/b8386410-98dd-4a5e-9dee-6e5bf7effa31"
/>

## How it works

- Notifications fire from the **live transaction-assignment path**
(`BudgetTransactionService::assignTransaction`, already a queued
listener), never from historical backfill when a budget is created.
- Only the budget's **current period** is considered (the emails
describe the live "available before the limit" state).
- **New transaction** fires for a genuinely newly-assigned transaction;
the email includes the transaction.
- **Close/over** use two atomic per-period flags
(`close_to_limit_notified` / `over_limit_notified`) flipped with a
compare-and-set `UPDATE`, so exactly one email is sent per crossing even
under concurrent queue workers. The flags reset once spending drops back
below the close threshold, so a later crossing can notify again.
- Budgets with no limit (`allocated_amount <= 0`) never send close/over
emails; the "new transaction" email for such a budget omits the
limit/available rows.
- Every email shows the budget's current status (period, spent, limit,
available or over-by). Subcopy links back to the Notifications page.
- New budgets inherit the user's default toggles at creation time.

## Data model

- `budgets`: `notify_on_new_transaction`, `notify_on_close_to_limit`,
`notify_on_over_limit` (default `false`)
- `user_settings`: `budget_notify_on_*` defaults for new budgets
(default `false`)
- `budget_periods`: `close_to_limit_notified`, `over_limit_notified`
(dedup flags)

## Deliberate decisions

- **Threshold is a fixed 90%** for "close to limit" (not configurable) —
kept simple; easy to make per-budget later if requested.
- **"New transaction" sends one email per assigned transaction.** It is
opt-in and defaults to off. A bank sync importing many matching
transactions into an opted-in budget will therefore send several emails;
if this proves noisy we can batch per sync run (mirroring the daily
bank-sync digest). Flagged here rather than pre-building batching.
- **Limit basis is `allocated_amount`** (matches the budget
cards/spending chart), carry-over excluded.

## Tests

- Preference endpoints: page renders budgets + defaults, per-budget
toggle update, cross-user update forbidden, user-settings defaults
update, new budget inherits defaults.
- Sending: new-transaction opt-in/out, over-limit send, close-limit at
90%, no-resend dedup, re-notify after dropping below and crossing again,
no-limit budget skips close/over, historical backfill sends nothing,
notify after enabling the preference while already over. Plus a render
test covering all three email variants.

## Notes

- CI-enforced `es.json` keys for the new page and emails are included.
- All new PHP follows existing mail/queue conventions (`ShouldQueue`
mailable, `emails` queue, `RateLimited` middleware).
2026-07-24 12:52:03 +02:00
..
account.ts feat(settings): centralize currency options and split profile/account support (#256) 2026-04-02 19:23:10 +02:00
automation-rule.ts feat(ai): learn from category corrections so the AI stops repeating the same mistake (#608) 2026-06-29 19:12:15 +02:00
balance-import.ts feat: investment benefits — show gains/losses on investment accounts (#140) 2026-02-23 13:59:10 +01:00
banking.ts feat(connections): manage which accounts a bank connection syncs (#558) 2026-06-18 16:22:49 +02:00
budget.ts feat(budgets): add per-budget email notifications (#731) 2026-07-24 12:52:03 +02:00
categorization.ts feat(ai): manage AI consent outside onboarding with live backfill (#591) 2026-06-25 10:50:35 +02:00
category.test.ts fix(categories): fall back to gray when category color is unknown (#675) 2026-07-14 22:24:52 +02:00
category.ts fix(categories): fall back to gray when category color is unknown (#675) 2026-07-14 22:24:52 +02:00
import.ts feat(importer): support YYYYMMDD date format (#470) 2026-06-01 16:44:46 +02:00
index.d.ts feat(mcp): read-only MCP server for Pro accounts (#689) 2026-07-17 16:54:15 +02:00
label.ts feat: add transaction labels feature (#24) 2025-12-13 13:02:19 +01:00
language.ts feat(i18n): add French translation support (#532) 2026-06-15 19:15:43 +02:00
pricing.ts feat(pricing): dynamic Stripe pricing with locale-aware formatting (#204) 2026-03-05 11:41:59 +00:00
transaction.ts refactor: consolidate duplicated financial calculations (#643) 2026-07-04 22:26:44 +02:00
uuid.ts Fix CI: Update bun.lock and fix linting errors 2025-12-01 15:16:06 +01:00
vite-env.d.ts Init browser Sentry from env DSN (#370) 2026-05-09 12:07:11 +02:00