# Story 4.1: Settings — Monthly Income

Status: review

## Story

As a user,
I want to record my monthly take-home income,
so that the app can calculate budget allocations as a percentage of what I earn.

## Acceptance Criteria

1. `GET /settings/income` renders a form pre-filled with the current `monthly_income` (or blank if not yet set).
2. Valid POST saves the value into `Settings` (upsert id=1), PRG redirect to `/settings/income`, flash `'success'` "Monthly income updated."
3. Non-numeric input re-renders the form with a field-level error.
4. Negative value rejected with error "Income must be zero or greater."
5. Zero is accepted (disables percentage-based budget helpers).
6. Settings index page (`GET /settings/`) shows the current income value and links to `/settings/income`.
7. `pytest tests/` passes — all 254 prior tests still green.

## Tasks / Subtasks

- [x] **Task 1: Write `tests/test_blueprints/test_settings_income.py`** (TDD RED)
- [x] **Task 2: Add `IncomeForm` to `app/blueprints/settings/forms.py`**
- [x] **Task 3: Add `GET+POST /settings/income` route**
- [x] **Task 4: Create `settings/income.html` template**
- [x] **Task 5: Update `settings/index.html`** to show income and link to income page
- [x] **Task 6: Run full suite GREEN**

## Dev Notes

### Upsert pattern

```python
from app.models.settings import Settings

settings = Settings.query.first()
if settings is None:
    settings = Settings(id=1, monthly_income=amount)
    db.session.add(settings)
else:
    settings.monthly_income = amount
db.session.commit()
```

### Form

```python
class IncomeForm(FlaskForm):
    monthly_income = StringField('Monthly Take-Home Income ($)',
                                 validators=[DataRequired()])
```

Validate in route: `Decimal(form.monthly_income.data)`, reject if `< 0`.

### File List

| File | Status |
|------|--------|
| `tests/test_blueprints/test_settings_income.py` | NEW |
| `app/blueprints/settings/forms.py` | MODIFY |
| `app/blueprints/settings/routes.py` | MODIFY |
| `app/blueprints/settings/templates/settings/income.html` | NEW |
| `app/blueprints/settings/templates/settings/index.html` | MODIFY |

---

## Dev Agent Record

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

### Debug Log References
None

### Completion Notes List
- 20 new tests, all green first run — zero regressions against 254 prior tests (274 passed, 1 skipped)
- `Settings` model and table already existed from the initial schema migration — no new migration needed
- Upsert pattern: `Settings.query.first()`, create with `id=1` if None, else update in place
- Negative values rejected in route (not form validator) so the error message is domain-specific
- Settings index page now shows all three settings sections as a card list; income displays current value or "Not set"

### File List
- `tests/test_blueprints/test_settings_income.py` — NEW (20 tests)
- `app/blueprints/settings/forms.py` — MODIFIED (added IncomeForm)
- `app/blueprints/settings/routes.py` — MODIFIED (added income route, updated index to pass settings)
- `app/blueprints/settings/templates/settings/income.html` — NEW
- `app/blueprints/settings/templates/settings/index.html` — MODIFIED (card layout with income, accounts, categories)

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