# Story 3.1: Account Setup

Status: review

## Story

As a user,
I want to add and manage my bank and payment accounts (checking, savings, credit),
so that I can assign transactions to the correct account and the app knows which accounts I use.

## Acceptance Criteria

1. Navigating to `/settings/accounts` shows all existing accounts and an "Add Account" form.
2. Submitting the form with valid data creates an Account (name required, type from dropdown: checking/savings/credit/loan, optional institution name, is_active=True). PRG redirect to accounts list, flash `'success'` "Account added."
3. Invalid form submission re-renders with inline errors via `form_row.html` — no redirect.
4. Each account row shows name, type, and institution name.
5. Edit at `GET/POST /settings/accounts/<id>/edit`; PRG redirect on valid submit, flash `'success'` "Account updated."
6. Delete via `POST /settings/accounts/<id>/delete` with confirm_modal; on confirm: hard-delete, PRG redirect, flash `'success'` "Account deleted."
7. Attempting to delete an account that has transactions: PRG redirect, flash `'error'` "Account in use — cannot be deleted."
8. A default "Cash" account (type: checking) is seeded by a new migration so the app always has at least one account.
9. `GET /settings/` renders a settings landing page (active_page='settings') with a link to account management.
10. `pytest tests/` passes with all prior 84 tests still green.

## Tasks / Subtasks

- [ ] **Task 1: Write `tests/test_blueprints/test_accounts.py`** (TDD RED)
- [ ] **Task 2: New migration** — seed default "Cash" account
- [ ] **Task 3: `app/blueprints/settings/forms.py`** — AccountForm
- [ ] **Task 4: `app/blueprints/settings/routes.py`** — all account routes + `/settings/` landing
- [ ] **Task 5: Templates** — `settings/index.html`, `settings/accounts.html`, `settings/account_edit.html`
- [ ] **Task 6: Run full suite GREEN**

## Dev Notes

### AccountForm

```python
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField
from wtforms.validators import DataRequired, Length, Optional

class AccountForm(FlaskForm):
    name = StringField('Account Name', validators=[DataRequired(), Length(max=200)])
    type = SelectField('Type', choices=[
        ('checking', 'Checking'),
        ('savings', 'Savings'),
        ('credit', 'Credit'),
        ('loan', 'Loan'),
    ], validators=[DataRequired()])
    institution_name = StringField('Institution (optional)', validators=[Optional(), Length(max=200)])
```

### Routes

Settings blueprint is mounted at `/settings`. Routes:

- `GET /settings/` → render `settings/index.html` (active_page='settings')
- `GET /settings/accounts` → list + form (active_page='settings')
- `POST /settings/accounts` → create; redirect to accounts list
- `GET /settings/accounts/<id>/edit` → edit form
- `POST /settings/accounts/<id>/edit` → update; redirect to accounts list
- `POST /settings/accounts/<id>/delete` → delete or error; redirect to accounts list

### Delete guard

```python
from sqlalchemy import select
has_txns = db.session.execute(
    select(Transaction.id).where(Transaction.account_id == account.id).limit(1)
).first() is not None
if has_txns:
    flash('Account in use — cannot be deleted.', 'error')
    return redirect(url_for('settings.accounts'))
db.session.delete(account)
db.session.commit()
flash('Account deleted.', 'success')
```

### Cash account migration

Create a new migration (revision) that inserts the Cash account in `upgrade()` and deletes it in `downgrade()`. Use `op.execute()` with raw SQL to avoid importing ORM models in migrations.

```python
def upgrade():
    op.execute(
        "INSERT INTO accounts (name, type, is_active, created_at) "
        "VALUES ('Cash', 'checking', 1, CURRENT_TIMESTAMP)"
    )

def downgrade():
    op.execute("DELETE FROM accounts WHERE name='Cash' AND type='checking'")
```

### Template structure

- `settings/index.html` — extends base.html, active_page='settings', link to /settings/accounts
- `settings/accounts.html` — page_header, account list table, Add Account form using form_row.html, confirm_modal for each delete
- `settings/account_edit.html` — page_header, edit form using form_row.html

### File List

| File | Status |
|------|--------|
| `tests/test_blueprints/test_accounts.py` | NEW |
| `app/blueprints/settings/forms.py` | MODIFY |
| `app/blueprints/settings/routes.py` | MODIFY |
| `app/blueprints/settings/templates/settings/index.html` | NEW |
| `app/blueprints/settings/templates/settings/accounts.html` | NEW |
| `app/blueprints/settings/templates/settings/account_edit.html` | NEW |
| `migrations/versions/<rev>_seed_cash_account.py` | NEW |

---

## Dev Agent Record

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

### Debug Log References
None

### Completion Notes List
- Added `template_folder='templates'` to settings blueprint `__init__.py` (was missing unlike dashboard blueprint)
- Form_row macro name is `render_field` not `render_form_row` — corrected in templates
- `institution_name` can be `None` from WTForms Optional() validator; used `(data or '').strip() or None` pattern in both create and edit routes
- 105 passed, 1 skipped — zero regressions

### File List
- `tests/test_blueprints/test_accounts.py` — NEW (21 tests)
- `app/blueprints/settings/__init__.py` — MODIFIED (added template_folder)
- `app/blueprints/settings/forms.py` — REPLACED stub with AccountForm
- `app/blueprints/settings/routes.py` — REPLACED stub with full routes
- `app/blueprints/settings/templates/settings/index.html` — NEW
- `app/blueprints/settings/templates/settings/accounts.html` — NEW
- `app/blueprints/settings/templates/settings/account_edit.html` — NEW
- `migrations/versions/472c5233138e_seed_cash_default_account.py` — NEW

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