Add SQLite passive income dashboard with Excel import and deployment tools
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user