# Story 4.2: Monthly Budget Entry Per Category

Status: review

## Story

As a user,
I want to set a monthly budget amount for each category,
so that I can define my spending plan and track how much I have left in each area.

## Acceptance Criteria

1. `GET /budgets/` renders all active categories each with a pre-filled amount input (current month's budget or blank if none set).
2. Valid POST saves/updates the Budget record (category_id, month, year, amount); PRG redirect to `/budgets/`, flash `'success'` "Budget saved."
3. Budgets are month-specific — a May 2026 budget does not affect June 2026.
4. Categories with no budget set display "No budget set — displays $0.00 budgeted".
5. Non-numeric input re-renders the form with a field-level error via `form_row.html`.
6. Negative value rejected with error "Amount must be zero or greater."
7. Invalid category_id returns HTTP 404.
8. `pytest tests/` passes — all 295 prior + new tests green (21 new tests, 295 total).

## Tasks / Subtasks

- [x] **Task 1: Write `tests/test_blueprints/test_budgets_entry.py`** (TDD RED — 21 tests)
- [x] **Task 2: Add `BudgetEntryForm` to `app/blueprints/budgets/forms.py`**
- [x] **Task 3: Implement `GET+POST /budgets/` route in `routes.py`**
- [x] **Task 4: Add `template_folder='templates'` to budgets Blueprint**
- [x] **Task 5: Create `budgets/index.html` template**
- [x] **Task 6: Run full suite GREEN**

## Dev Notes

### Per-category form pattern

Each category row has its own `<form method="POST">` posting to `/budgets/` with:
- `{{ form.hidden_tag() }}` for CSRF
- `<input type="hidden" name="category_id" value="{{ cat.id }}">`
- Amount field — uses `render_field(form.amount)` when `error_cat_id == cat.id`, otherwise a plain input pre-filled with current budget

### Upsert pattern

```python
existing = {b.category_id: b for b in Budget.query.filter_by(month=m, year=y).all()}
budget = existing.get(cat.id)
if budget is None:
    budget = Budget(category_id=cat.id, month=m, year=y, amount=amount)
    db.session.add(budget)
else:
    budget.amount = amount
db.session.commit()
```

### File List

| File | Status |
|------|--------|
| `tests/test_blueprints/test_budgets_entry.py` | NEW |
| `app/blueprints/budgets/forms.py` | MODIFIED |
| `app/blueprints/budgets/routes.py` | MODIFIED |
| `app/blueprints/budgets/__init__.py` | MODIFIED (added template_folder) |
| `app/blueprints/budgets/templates/budgets/index.html` | NEW |

---

## Dev Agent Record

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

### Debug Log References
- Fixed `TemplateNotFound: budgets/index.html` — budgets Blueprint was missing `template_folder='templates'`

### Completion Notes List
- 21 new tests, all green first run after template_folder fix — zero regressions against 274 prior tests (295 passed, 1 skipped)
- Per-category inline forms pattern: each category row has its own `<form>` posting to `/budgets/` with hidden `category_id` field
- `error_cat_id` context variable determines which row renders `form_row.html` (with errors) vs. a plain pre-filled input
- Amount validation done in route (Decimal conversion + negative check) to produce domain-specific error messages
- Zero budget accepted (no budget set = $0.00 budgeted per AC)

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