whisper-money/resources/js/components/transactions
Víctor Falcón a873582191
feat(transactions): allow editing all fields of manual transactions (#683)
## What & why

Until now only the **category, notes, labels and (for manual
transactions) the description** could be changed after a transaction was
created — the **amount and date were immutable and the account was
create-only**. Users creating transactions by hand had no way to fix a
wrong amount or date.

This lets **manually created transactions edit every field at any time
after creation**: account, date, description and amount, on top of
category/labels/notes. **Imported / bank-synced transactions keep
amount, date, account, currency and description locked** to their source
data.

## Changes

**Backend**
- `UpdateTransactionRequest` now validates `amount`, `transaction_date`,
`account_id` and `currency_code` **only when the transaction's `source`
is `manually_created`**. For imported transactions those keys are not
validated, so `validated()` drops them and they can't be changed even
via a crafted request.
- `TransactionController::update()` moves the manual account balance to
match an edited amount/date/account, **opt-in via the same
`update_balance` flag used by create/delete**. It snapshots the pre-edit
state, and only rebalances when one of amount/date/account actually
changed. Connected accounts are skipped inside the adjuster.
- `ManualBalanceAdjuster` was refactored to a shared private `adjust()`
primitive (removing duplication between the existing create/delete
paths) and gains `reverseCreatedTransaction()` so an edit can reverse
the old contribution and apply the new one.

**Frontend**
- `edit-transaction-dialog.tsx` unifies the create/edit editability
decision behind a single `canEditAllFields` flag and renders the
account, date and amount inputs (plus the "update account balance"
checkbox) when editing a manual transaction.
- `transaction-sync.ts` `update()` gains an `{ updateBalance }` option,
mirroring `create()`.

## Known limitation (by design)

Like create/delete, the balance update trusts the opt-in flag and nudges
a **single dated snapshot** — it doesn't cascade to later snapshots and
keeps no record of whether creation adjusted the balance. So it's exact
for the common case (recent transaction, flag used consistently) and can
drift otherwise. This matches the app's existing snapshot-based balance
model; a transaction-derived balance would be a separate, larger change.
Documented with a `ponytail:` comment at the call site.

## Tests

- **Feature (`tests/Feature/TransactionTest.php`)**: manual transaction
edits amount/date/account/currency; imported transaction cannot; balance
moves by the delta on amount change; no change when not requested;
moving between accounts reverses the old and credits the new;
currency-only edit doesn't rebalance; connected accounts never change.
All green locally (55 passed).
- **Component (`edit-transaction-dialog.test.tsx`)**: manual transaction
shows editable amount/date/description; imported keeps them read-only.

## QA

Real browser QA (Playwright) against the running app with the
`demo@whisper.money` data:
- Opened a manual transaction → edited date, description and amount,
kept "update account balance" checked, saved. Verified in the DB:
`amount -4200 → -7550`, `date 2026-07-10 → 2026-07-12`, description
updated, and the account balance snapshots moved accordingly.
- Opened an imported transaction → amount and description render
read-only (locked).

## Demo

https://github.com/user-attachments/assets/1c8790e8-31f5-4283-b260-353650ad007c
2026-07-16 08:59:10 +02:00
..
ai-upsell-sample.test.ts AI auto-categorization: open to pro + consent, nudge free users (#561) 2026-06-19 14:08:40 +00:00
ai-upsell-sample.ts AI auto-categorization: open to pro + consent, nudge free users (#561) 2026-06-19 14:08:40 +00:00
bulk-actions-bar.tsx Fix label creation dropdown refresh (#363) 2026-05-07 10:26:43 +01:00
bulk-category-select.tsx feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
bulk-label-select.tsx Fix label creation dropdown refresh (#363) 2026-05-07 10:26:43 +01:00
categorizer-card.tsx feat(categorize): show debtor and creditor names (#454) 2026-05-30 12:27:59 +02:00
categorizer-command.tsx feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
category-cell.tsx feat(ai): learn from category corrections so the AI stops repeating the same mistake (#608) 2026-06-29 19:12:15 +02:00
category-select.tsx feat(shared): Add CategoryCombobox component 2025-11-14 16:05:53 +01:00
edit-transaction-dialog.test.tsx feat(transactions): allow editing all fields of manual transactions (#683) 2026-07-16 08:59:10 +02:00
edit-transaction-dialog.tsx feat(transactions): allow editing all fields of manual transactions (#683) 2026-07-16 08:59:10 +02:00
import-step-account.test.tsx Fix transaction importer account preselect (#396) 2026-05-14 12:38:44 +02:00
import-step-account.tsx Fix transaction importer account preselect (#396) 2026-05-14 12:38:44 +02:00
import-step-mapping.tsx fix(balances): allow saving a zero balance (#664) 2026-07-10 15:02:38 +02:00
import-step-preview.tsx feat(transactions): serve import dedup and account ledger from the backend (#631) 2026-07-03 16:49:59 +02:00
import-step-upload.test.tsx Fix transaction importer account preselect (#396) 2026-05-14 12:38:44 +02:00
import-step-upload.tsx Fix transaction importer account preselect (#396) 2026-05-14 12:38:44 +02:00
import-transactions-button.tsx refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
import-transactions-drawer.tsx feat(transactions): serve import dedup and account ledger from the backend (#631) 2026-07-03 16:49:59 +02:00
saved-filters.tsx fix(transactions): keep saved-filter delete button visible on touch and confirm before deleting (#648) 2026-07-06 09:37:38 +00:00
transaction-actions-menu.test.tsx feat(transactions): release transaction analysis to all users (#579) 2026-06-22 16:09:09 +02:00
transaction-actions-menu.tsx feat(transactions): release transaction analysis to all users (#579) 2026-06-22 16:09:09 +02:00
transaction-analysis-drawer.test.tsx feat(analysis): shared bar-list breakdowns in transaction drawer (#517) 2026-06-10 12:36:20 +02:00
transaction-analysis-drawer.tsx feat(analysis): shared bar-list breakdowns in transaction drawer (#517) 2026-06-10 12:36:20 +02:00
transaction-columns.test.tsx fix(transactions): pad Category column when Date column is hidden (#584) 2026-06-22 16:51:50 +00:00
transaction-columns.tsx fix(transactions): let date column size to its content (#610) 2026-06-29 15:51:51 +00:00
transaction-description.test.tsx refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
transaction-description.tsx refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
transaction-filters.test.tsx feat(transactions): reorder filters and switch accounts to a logo dropdown (#598) 2026-06-26 20:09:02 +02:00
transaction-filters.tsx feat(transactions): reorder filters and switch accounts to a logo dropdown (#598) 2026-06-26 20:09:02 +02:00
transaction-list.test.tsx fix(accounts): remove double-skeleton flash on account show (#634) 2026-07-03 15:56:49 +00:00
transaction-list.tsx fix(accounts): remove double-skeleton flash on account show (#634) 2026-07-03 15:56:49 +00:00