first ruggedy draft

This commit is contained in:
2026-06-28 13:48:15 +02:00
parent c916f64fcb
commit 1053f6a2a3
78 changed files with 20905 additions and 2 deletions

View File

@@ -0,0 +1,188 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from .. import models
from ..db import get_session
from ..schemas import (
CategoryCreate,
CategoryRead,
CategoryUpdate,
TagCreate,
TagRead,
TagUpdate,
)
router = APIRouter(tags=["taxonomy"])
def _require_household(session: Session, household_id: str) -> None:
if not session.get(models.Household, household_id):
raise HTTPException(status_code=404, detail="Household not found")
def _category_path(
session: Session,
payload: CategoryCreate | CategoryUpdate,
category: models.Category | None = None,
) -> str:
if payload.path:
return payload.path
name = payload.name if payload.name is not None else category.name if category else ""
parent_id = payload.parent_id if payload.parent_id is not None else category.parent_id if category else None
if parent_id:
parent = session.get(models.Category, parent_id)
if not parent:
raise HTTPException(status_code=404, detail="Parent category not found")
return f"{parent.path}/{name}"
return name
@router.post("/categories", response_model=CategoryRead)
def create_category(
payload: CategoryCreate,
session: Session = Depends(get_session),
) -> models.Category:
_require_household(session, payload.household_id)
if payload.parent_id:
parent = session.get(models.Category, payload.parent_id)
if not parent or parent.household_id != payload.household_id:
raise HTTPException(status_code=404, detail="Parent category not found")
category = models.Category(
household_id=payload.household_id,
parent_id=payload.parent_id,
name=payload.name,
path=_category_path(session, payload),
is_active=payload.is_active,
)
session.add(category)
session.commit()
session.refresh(category)
return category
@router.get("/categories", response_model=list[CategoryRead])
def list_categories(
household_id: str,
include_inactive: bool = False,
session: Session = Depends(get_session),
) -> list[models.Category]:
query = (
select(models.Category)
.where(models.Category.household_id == household_id)
.order_by(models.Category.path)
)
if not include_inactive:
query = query.where(models.Category.is_active.is_(True))
return list(session.scalars(query).all())
@router.patch("/categories/{category_id}", response_model=CategoryRead)
def update_category(
category_id: str,
payload: CategoryUpdate,
session: Session = Depends(get_session),
) -> models.Category:
category = session.get(models.Category, category_id)
if not category:
raise HTTPException(status_code=404, detail="Category not found")
if payload.parent_id:
parent = session.get(models.Category, payload.parent_id)
if not parent or parent.household_id != category.household_id:
raise HTTPException(status_code=404, detail="Parent category not found")
for field, value in payload.model_dump(exclude_unset=True, exclude={"path"}).items():
setattr(category, field, value)
if {"name", "parent_id", "path"} & payload.model_fields_set:
category.path = _category_path(session, payload, category)
session.add(category)
session.commit()
session.refresh(category)
return category
@router.delete("/categories/{category_id}")
def delete_category(
category_id: str,
session: Session = Depends(get_session),
) -> dict[str, bool]:
category = session.get(models.Category, category_id)
if not category:
raise HTTPException(status_code=404, detail="Category not found")
for event in session.scalars(
select(models.FinancialEvent).where(models.FinancialEvent.category_id == category_id)
):
event.category_id = None
for commitment in session.scalars(
select(models.RecurringCommitment).where(models.RecurringCommitment.category_id == category_id)
):
commitment.category_id = None
for budget_line in session.scalars(
select(models.ProjectBudgetLine).where(models.ProjectBudgetLine.category_id == category_id)
):
budget_line.category_id = None
for child in session.scalars(select(models.Category).where(models.Category.parent_id == category_id)):
child.parent_id = None
child.path = child.name
session.delete(category)
session.commit()
return {"deleted": True}
@router.post("/tags", response_model=TagRead)
def create_tag(
payload: TagCreate,
session: Session = Depends(get_session),
) -> models.Tag:
_require_household(session, payload.household_id)
tag = models.Tag(household_id=payload.household_id, name=payload.name)
session.add(tag)
session.commit()
session.refresh(tag)
return tag
@router.get("/tags", response_model=list[TagRead])
def list_tags(
household_id: str,
session: Session = Depends(get_session),
) -> list[models.Tag]:
return list(
session.scalars(
select(models.Tag).where(models.Tag.household_id == household_id).order_by(models.Tag.name)
).all()
)
@router.patch("/tags/{tag_id}", response_model=TagRead)
def update_tag(
tag_id: str,
payload: TagUpdate,
session: Session = Depends(get_session),
) -> models.Tag:
tag = session.get(models.Tag, tag_id)
if not tag:
raise HTTPException(status_code=404, detail="Tag not found")
for field, value in payload.model_dump(exclude_unset=True).items():
setattr(tag, field, value)
session.add(tag)
session.commit()
session.refresh(tag)
return tag
@router.delete("/tags/{tag_id}")
def delete_tag(
tag_id: str,
session: Session = Depends(get_session),
) -> dict[str, bool]:
tag = session.get(models.Tag, tag_id)
if not tag:
raise HTTPException(status_code=404, detail="Tag not found")
session.query(models.EventTag).filter(models.EventTag.tag_id == tag_id).delete(
synchronize_session=False
)
session.delete(tag)
session.commit()
return {"deleted": True}