# Story 3.4: Transaction Edit & Delete

Status: review

## Story

As a user,
I want to edit or delete any transaction I've entered,
so that I can correct mistakes without losing all my data.

## Acceptance Criteria

1. `GET /transactions/<id>/edit` renders the form pre-filled with the existing transaction data.
2. Valid POST updates all fields, recomputes `dedup_hash`, PRG redirect to `/transactions/`, flash `'success'` "Transaction updated."
3. Editing to create a duplicate shows the same duplicate warning as Story 3.3 (force_save bypass works identically).
4. `POST /transactions/<id>/delete` shows confirm_modal (modal in template); on confirm → hard-delete → PRG redirect to `/transactions/`, flash `'success'` "Transaction deleted."
5. GET or POST to a non-existent transaction ID returns HTTP 404.
6. `pytest tests/` passes — all 165 prior tests still green.

## Tasks / Subtasks

- [ ] **Task 1: Write `tests/test_blueprints/test_transaction_edit_delete.py`** (TDD RED)
- [ ] **Task 2: Add edit and delete routes to `app/blueprints/transactions/routes.py`**
- [ ] **Task 3: Create `transactions/edit.html` template**
- [ ] **Task 4: Run full suite GREEN**

## Dev Notes

### Routes to add

- `GET  /transactions/<id>/edit` → render pre-filled TransactionForm
- `POST /transactions/<id>/edit` → update, redirect
- `POST /transactions/<id>/delete` → hard-delete, redirect

### Edit route pattern

Pre-fill form using `obj=txn` then override `date` manually since Transaction stores
date as ISO text but DateField expects a `datetime.date` object:

```python
from datetime import date as date_type
form = TransactionForm(obj=txn)
_load_form_choices(form)
if request.method == 'GET':
    form.date.data = date_type.fromisoformat(txn.date)
    form.amount.data = str(txn.amount)
    form.merchant.data = txn.merchant_normalized
    form.category_id.data = txn.category_id or 0
    form.account_id.data = txn.account_id
```

### Duplicate check on edit

Exclude the transaction being edited from the recent_hashes window query so it
doesn't flag itself as a duplicate:

```python
recent_hashes = [
    t.dedup_hash for t in
    Transaction.query
        .filter(Transaction.id != txn.id,
                Transaction.date >= window_start,
                Transaction.date <= window_end,
                Transaction.dedup_hash.isnot(None))
        .all()
]
```

### File List

| File | Status |
|------|--------|
| `tests/test_blueprints/test_transaction_edit_delete.py` | NEW |
| `app/blueprints/transactions/routes.py` | MODIFY |
| `app/blueprints/transactions/templates/transactions/edit.html` | NEW |

---

## Dev Agent Record

### Agent Model Used
claude-sonnet-4-6

### Debug Log References
None

### Completion Notes List
- 17 new tests, all green first run — zero regressions against 165 prior tests
- Edit route excludes the transaction being edited from the dedup window query (`Transaction.id != txn.id`) so re-saving unchanged data never triggers a false duplicate warning
- GET pre-fill: `date_type.fromisoformat(txn.date)` converts stored ISO text back to `datetime.date` for DateField; amount pre-filled as string for StringField display
- Delete uses the shared confirm_modal component from the edit page — no separate delete route render needed
- 182 passed, 1 skipped total

### File List
- `tests/test_blueprints/test_transaction_edit_delete.py` — NEW (17 tests)
- `app/blueprints/transactions/routes.py` — MODIFIED (added edit and delete routes)
- `app/blueprints/transactions/templates/transactions/edit.html` — NEW

### Change Log
- 2026-05-28: Story implemented and moved to review status
