Files
finance-dashboard/app/routes/income.py
T

92 lines
4.0 KiB
Python

from datetime import date
from decimal import Decimal
from typing import Annotated
from fastapi import APIRouter, Form, HTTPException, Query, Request
from fastapi.responses import RedirectResponse
from database import connect, ensure_asset
from models import CATEGORIES
from services.income_service import assets, available_years, get_entry, list_entries, save_entry
from views import render
router = APIRouter()
def form_page(request, data, error=None, status=200, entry_id=None):
return render(request, 'income_form.html', {'data': data, 'assets': assets(), 'error': error, 'entry_id': entry_id}, status)
@router.get('/income')
def history(request: Request, year: str | None = None,
month: str | None = None,
asset_id: str | None = None, category: str | None = None,
page: Annotated[int, Query(ge=1, le=1000000)] = 1):
def number(value, maximum):
if not value:
return None
try:
parsed = int(value)
if not 1 <= parsed <= maximum:
raise ValueError
return parsed
except ValueError:
raise HTTPException(422, 'Ungültiger Filter.') from None
year, month, asset_id = number(year, 9999), number(month, 12), number(asset_id, 9223372036854775807)
category = category or None
if category is not None and category not in CATEGORIES:
raise HTTPException(422, 'Ungültige Kategorie.')
rows = list_entries(year, month, asset_id, category, limit=101, offset=(page-1)*100)
return render(request, 'income.html', dict(entries=rows[:100], more=len(rows)>100, page=page,
assets=assets(), years=available_years(), filters=dict(year=year, month=month, asset_id=asset_id, category=category)))
@router.get('/income/new')
def new(request: Request):
return form_page(request, {'date': date.today().isoformat(), 'received': True, 'expected': False,
'asset_id': request.query_params.get('asset_id', ''), 'category': 'dividend'})
@router.get('/income/{entry_id}/edit')
def edit(request: Request, entry_id: int):
data = get_entry(entry_id)
data['amount'] = str(Decimal(data['amount']) / 100).replace('.', ',')
return form_page(request, data, entry_id=entry_id)
@router.post('/income/new')
@router.post('/income/{entry_id}/edit')
def save(request: Request, date: Annotated[str, Form()] = '', asset_id: Annotated[str, Form()] = '',
category: Annotated[str, Form()] = '', amount: Annotated[str, Form()] = '',
note: Annotated[str, Form()] = '', expected: Annotated[str, Form()] = '',
received: Annotated[str, Form()] = '', entry_id: int | None = None):
data = dict(date=date, asset_id=asset_id, category=category, amount=amount, note=note, expected=expected, received=received)
try:
save_entry(data, entry_id)
except ValueError as error:
return form_page(request, data, str(error), 422, entry_id)
return RedirectResponse('/?message=saved', status_code=303)
@router.post('/income/{entry_id}/delete')
def delete(entry_id: int):
if not 1 <= entry_id <= 9223372036854775807:
raise HTTPException(404, 'Zahlung nicht gefunden.')
with connect() as db:
if db.execute('DELETE FROM income_entries WHERE id = ?', (entry_id,)).rowcount == 0:
raise HTTPException(404, 'Zahlung nicht gefunden.')
return RedirectResponse('/?message=deleted', status_code=303)
@router.get('/assets/new')
def new_asset(request: Request):
return render(request, 'asset_form.html', {'data': {}})
@router.post('/assets/new')
def create_asset(request: Request, name: Annotated[str, Form()], asset_type: Annotated[str, Form()], ticker: Annotated[str, Form()] = ''):
try:
with connect() as db:
asset_id = ensure_asset(db, name, asset_type, ticker)
except ValueError as error:
return render(request, 'asset_form.html', {'data': dict(name=name, asset_type=asset_type, ticker=ticker), 'error': str(error)}, 422)
return RedirectResponse(f'/income/new?asset_id={asset_id}', status_code=303)