Files
mousehold/apps/api/mousehold_api/routers/projects.py
2026-06-28 13:48:15 +02:00

151 lines
4.9 KiB
Python

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 (
PaymentLineCreate,
PaymentLineRead,
ProjectBudgetLineCreate,
ProjectBudgetLineRead,
ProjectCreate,
ProjectParticipantCreate,
ProjectParticipantRead,
ProjectPlannedItemCreate,
ProjectPlannedItemRead,
ProjectRead,
ProjectSummary,
SettlementSummary,
)
from ..services.projects import calculate_project_summary
from ..services.settlement import calculate_settlement
router = APIRouter(tags=["projects"])
def _get_project(session: Session, project_id: str) -> models.Project:
project = session.get(models.Project, project_id)
if not project:
raise HTTPException(status_code=404, detail="Project not found")
return project
@router.post("/projects", response_model=ProjectRead)
def create_project(
payload: ProjectCreate, session: Session = Depends(get_session)
) -> models.Project:
project = models.Project(**payload.model_dump(exclude={"participant_user_ids"}))
session.add(project)
session.flush()
for user_id in payload.participant_user_ids:
session.add(models.ProjectParticipant(project_id=project.id, user_id=user_id))
session.commit()
session.refresh(project)
return project
@router.get("/projects", response_model=list[ProjectRead])
def list_projects(
household_id: str, session: Session = Depends(get_session)
) -> list[models.Project]:
return list(
session.scalars(
select(models.Project)
.where(models.Project.household_id == household_id)
.order_by(models.Project.created_at.desc())
).all()
)
@router.get("/projects/{project_id}", response_model=ProjectRead)
def get_project(project_id: str, session: Session = Depends(get_session)) -> models.Project:
return _get_project(session, project_id)
@router.post("/projects/{project_id}/participants", response_model=ProjectParticipantRead)
def add_project_participant(
project_id: str,
payload: ProjectParticipantCreate,
session: Session = Depends(get_session),
) -> models.ProjectParticipant:
_get_project(session, project_id)
participant = models.ProjectParticipant(project_id=project_id, **payload.model_dump())
session.add(participant)
session.commit()
session.refresh(participant)
return participant
@router.post("/projects/{project_id}/budget-lines", response_model=ProjectBudgetLineRead)
def add_project_budget_line(
project_id: str,
payload: ProjectBudgetLineCreate,
session: Session = Depends(get_session),
) -> models.ProjectBudgetLine:
_get_project(session, project_id)
budget_line = models.ProjectBudgetLine(project_id=project_id, **payload.model_dump())
session.add(budget_line)
session.commit()
session.refresh(budget_line)
return budget_line
@router.post("/projects/{project_id}/planned-items", response_model=ProjectPlannedItemRead)
def add_project_planned_item(
project_id: str,
payload: ProjectPlannedItemCreate,
session: Session = Depends(get_session),
) -> models.ProjectPlannedItem:
_get_project(session, project_id)
planned_item = models.ProjectPlannedItem(project_id=project_id, **payload.model_dump())
session.add(planned_item)
session.commit()
session.refresh(planned_item)
return planned_item
@router.post("/projects/{project_id}/payment-lines", response_model=PaymentLineRead)
def add_project_payment_line(
project_id: str,
payload: PaymentLineCreate,
session: Session = Depends(get_session),
) -> models.PaymentLine:
_get_project(session, project_id)
if payload.planned_item_id and not session.get(
models.ProjectPlannedItem, payload.planned_item_id
):
raise HTTPException(status_code=404, detail="Planned item not found")
payment_line = models.PaymentLine(**payload.model_dump())
session.add(payment_line)
session.commit()
session.refresh(payment_line)
return payment_line
@router.get("/projects/{project_id}/summary", response_model=ProjectSummary)
def get_project_summary(project_id: str, session: Session = Depends(get_session)) -> ProjectSummary:
try:
return calculate_project_summary(session, project_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.get("/projects/{project_id}/settlement", response_model=SettlementSummary)
def get_project_settlement(
project_id: str,
currency: str = "EUR",
include_announced: bool = False,
session: Session = Depends(get_session),
) -> SettlementSummary:
project = _get_project(session, project_id)
return calculate_settlement(
session,
household_id=project.household_id,
project_id=project_id,
currency=currency,
include_announced=include_announced,
)