feat(akte): FILE-009 Freitext-Notizen an beliebiger Ressource
Polymorphes akte_notiz-Modell (gleiches entitaet_typ/entitaet_id-Muster wie Dokument/Historie/Mangel), CRUD-Endpunkte, NotizenPanel im Akte-Grid. Löschen nur durch Autor oder Administrator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -16,6 +16,7 @@ from app.api.v1.endpoints import (
|
||||
lager,
|
||||
lagerbewegung,
|
||||
mangel,
|
||||
notiz,
|
||||
objekte,
|
||||
permission,
|
||||
personal,
|
||||
@@ -44,6 +45,7 @@ api_router.include_router(personal.router, tags=["personal"])
|
||||
api_router.include_router(mangel.router, tags=["mangel"])
|
||||
api_router.include_router(lagerbewegung.router, tags=["lagerbewegung"])
|
||||
api_router.include_router(dokument.router, tags=["dokument"])
|
||||
api_router.include_router(notiz.router, tags=["notiz"])
|
||||
api_router.include_router(permission.router, tags=["permission"])
|
||||
api_router.include_router(akte.router, tags=["akte"])
|
||||
api_router.include_router(lager.router, tags=["lager"])
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import require_roles
|
||||
from app.db.session import get_db
|
||||
from app.models.auth import Benutzer, RolleTyp
|
||||
from app.models.notiz import AkteNotiz
|
||||
from app.schemas.dokument import EntitaetTyp
|
||||
from app.schemas.notiz import AkteNotizCreate, AkteNotizRead
|
||||
from app.services.notiz import erstelle_notiz, liste_fuer_entitaet, loesche_notiz
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
_mitarbeiter_plus = require_roles(
|
||||
RolleTyp.mitarbeiter,
|
||||
RolleTyp.materialverantwortlicher,
|
||||
RolleTyp.leitungsverantwortlicher,
|
||||
RolleTyp.administration,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/notizen", response_model=list[AkteNotizRead])
|
||||
async def liste_notizen(
|
||||
entitaet_typ: EntitaetTyp,
|
||||
entitaet_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_=Depends(_mitarbeiter_plus),
|
||||
) -> list[AkteNotiz]:
|
||||
return await liste_fuer_entitaet(db, entitaet_typ=entitaet_typ, entitaet_id=entitaet_id)
|
||||
|
||||
|
||||
@router.post("/notizen", response_model=AkteNotizRead, status_code=status.HTTP_201_CREATED)
|
||||
async def notiz_anlegen(
|
||||
payload: AkteNotizCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Benutzer = Depends(_mitarbeiter_plus),
|
||||
) -> AkteNotiz:
|
||||
return await erstelle_notiz(
|
||||
db,
|
||||
entitaet_typ=payload.entitaet_typ,
|
||||
entitaet_id=payload.entitaet_id,
|
||||
text=payload.text,
|
||||
autor_id=current_user.id,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/notizen/{notiz_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def notiz_loeschen(
|
||||
notiz_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Benutzer = Depends(_mitarbeiter_plus),
|
||||
) -> None:
|
||||
notiz = await db.get(AkteNotiz, notiz_id)
|
||||
if notiz is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Notiz nicht gefunden")
|
||||
ist_administrator = RolleTyp.administration.value in current_user.rollen_namen
|
||||
if notiz.autor_id != current_user.id and not ist_administrator:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail="Nur eigene Notizen oder als Administrator löschbar"
|
||||
)
|
||||
await loesche_notiz(db, notiz=notiz)
|
||||
@@ -12,6 +12,7 @@ from app.models.lagerbewegung import Lagerbewegung
|
||||
from app.models.mangel import Mangel, MangelPrioritaet, MangelStatus
|
||||
from app.models.mindermenge import MindermengeStatus, MindermengenGenehmigung
|
||||
from app.models.nachfuellung import Nachfuellung
|
||||
from app.models.notiz import AkteNotiz
|
||||
from app.models.objekt import Objekt, ObjektStatus
|
||||
from app.models.objektposition import Objektposition, ObjektpositionStatus
|
||||
from app.models.permission import Berechtigung, BenutzerRolleZuordnung, Rolle, RolleBerechtigung
|
||||
@@ -65,6 +66,7 @@ __all__ = [
|
||||
"MindermengeStatus",
|
||||
"MindermengenGenehmigung",
|
||||
"Nachfuellung",
|
||||
"AkteNotiz",
|
||||
"Objekt",
|
||||
"ObjektStatus",
|
||||
"Objektposition",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import ForeignKey, String
|
||||
from sqlalchemy.dialects.postgresql import TIMESTAMP, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class AkteNotiz(Base):
|
||||
"""FILE-009: formlose Freitext-Notiz an beliebiger Ressource - gleiches
|
||||
entitaet_typ/entitaet_id-Muster wie Dokument/Historie/Mangel."""
|
||||
|
||||
__tablename__ = "akte_notiz"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
entitaet_typ: Mapped[str] = mapped_column(String, nullable=False)
|
||||
entitaet_id: Mapped[str] = mapped_column(String, nullable=False)
|
||||
text: Mapped[str] = mapped_column(String, nullable=False)
|
||||
autor_id: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
|
||||
erstellt_am: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
|
||||
@@ -0,0 +1,22 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from app.schemas.dokument import EntitaetTyp
|
||||
|
||||
|
||||
class AkteNotizCreate(BaseModel):
|
||||
entitaet_typ: EntitaetTyp
|
||||
entitaet_id: str
|
||||
text: str
|
||||
|
||||
|
||||
class AkteNotizRead(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: uuid.UUID
|
||||
entitaet_typ: str
|
||||
entitaet_id: str
|
||||
text: str
|
||||
autor_id: int
|
||||
erstellt_am: datetime
|
||||
@@ -0,0 +1,38 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.notiz import AkteNotiz
|
||||
|
||||
|
||||
async def erstelle_notiz(
|
||||
db: AsyncSession, *, entitaet_typ: str, entitaet_id: str, text: str, autor_id: int
|
||||
) -> AkteNotiz:
|
||||
notiz = AkteNotiz(
|
||||
id=uuid.uuid4(),
|
||||
entitaet_typ=entitaet_typ,
|
||||
entitaet_id=entitaet_id,
|
||||
text=text,
|
||||
autor_id=autor_id,
|
||||
erstellt_am=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(notiz)
|
||||
await db.commit()
|
||||
await db.refresh(notiz)
|
||||
return notiz
|
||||
|
||||
|
||||
async def liste_fuer_entitaet(db: AsyncSession, *, entitaet_typ: str, entitaet_id: str) -> list[AkteNotiz]:
|
||||
ergebnis = await db.execute(
|
||||
select(AkteNotiz)
|
||||
.where(AkteNotiz.entitaet_typ == entitaet_typ, AkteNotiz.entitaet_id == entitaet_id)
|
||||
.order_by(AkteNotiz.erstellt_am.desc())
|
||||
)
|
||||
return list(ergebnis.scalars().all())
|
||||
|
||||
|
||||
async def loesche_notiz(db: AsyncSession, *, notiz: AkteNotiz) -> None:
|
||||
await db.delete(notiz)
|
||||
await db.commit()
|
||||
Reference in New Issue
Block a user