Files
PecHub/backend/app/api/v1/fascicoli.py
T

226 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Router Fascicoli — fascicolazione pratiche.
Endpoint:
- GET /fascicoli lista fascicoli (con filtri)
- POST /fascicoli crea un fascicolo
- GET /fascicoli/{id} dettaglio fascicolo
- PATCH /fascicoli/{id} modifica fascicolo
- DELETE /fascicoli/{id} elimina fascicolo (solo admin)
- GET /fascicoli/{id}/messages messaggi del fascicolo
- POST /fascicoli/{id}/messages aggiungi messaggi al fascicolo
- DELETE /fascicoli/{id}/messages rimuovi messaggi dal fascicolo
- GET /messages/{message_id}/fascicoli fascicoli di un messaggio
Permessi:
- Tutti gli utenti autenticati possono creare fascicoli.
- PATCH: creatore o admin.
- DELETE fascicolo: solo admin.
- Operazioni su messaggi: utente con accesso al tenant.
"""
import uuid
from typing import Optional
from fastapi import APIRouter, Query, status
from sqlalchemy import select
from app.core.exceptions import ForbiddenError, NotFoundError
from app.dependencies import AdminUser, CurrentUser, DB
from app.models.message import Message
from app.schemas.fascicolo import (
FascicoloAddMessagesRequest,
FascicoloCreate,
FascicoloMessageItem,
FascicoloRemoveMessagesRequest,
FascicoloResponse,
FascicoloUpdate,
MessageFascicoloSummary,
)
from app.services.fascicolo_service import FascicoloService
router = APIRouter(tags=["Fascicoli"])
# ─── Helpers ──────────────────────────────────────────────────────────────────
def _to_response(fascicolo, message_count: int) -> FascicoloResponse:
resp = FascicoloResponse.model_validate(fascicolo)
resp.message_count = int(message_count)
return resp
# ─── CRUD Fascicolo ───────────────────────────────────────────────────────────
@router.get("/fascicoli", response_model=list[FascicoloResponse])
async def list_fascicoli(
current_user: CurrentUser,
db: DB,
stato: Optional[str] = Query(None, pattern=r"^(aperto|chiuso|archiviato)$"),
responsabile_id: Optional[uuid.UUID] = Query(None),
search: Optional[str] = Query(None, max_length=200),
) -> list[FascicoloResponse]:
"""Elenca i fascicoli del tenant con filtri opzionali."""
svc = FascicoloService(db)
rows = await svc.list_fascicoli(
current_user.tenant_id,
stato=stato,
responsabile_id=responsabile_id,
search=search,
)
return [_to_response(f, cnt) for f, cnt in rows]
@router.post("/fascicoli", response_model=FascicoloResponse, status_code=status.HTTP_201_CREATED)
async def create_fascicolo(
data: FascicoloCreate,
current_user: CurrentUser,
db: DB,
) -> FascicoloResponse:
"""Crea un nuovo fascicolo."""
svc = FascicoloService(db)
fascicolo, cnt = await svc.create_fascicolo(
current_user.tenant_id, data, created_by=current_user.id
)
return _to_response(fascicolo, cnt)
@router.get("/fascicoli/{fascicolo_id}", response_model=FascicoloResponse)
async def get_fascicolo(
fascicolo_id: uuid.UUID,
current_user: CurrentUser,
db: DB,
) -> FascicoloResponse:
"""Restituisce il dettaglio di un fascicolo."""
svc = FascicoloService(db)
fascicolo, cnt = await svc.get_fascicolo(current_user.tenant_id, fascicolo_id)
return _to_response(fascicolo, cnt)
@router.patch("/fascicoli/{fascicolo_id}", response_model=FascicoloResponse)
async def update_fascicolo(
fascicolo_id: uuid.UUID,
data: FascicoloUpdate,
current_user: CurrentUser,
db: DB,
) -> FascicoloResponse:
"""
Modifica un fascicolo.
Solo il creatore o un amministratore possono modificarlo.
"""
svc = FascicoloService(db)
fascicolo, cnt = await svc.update_fascicolo(
current_user.tenant_id,
fascicolo_id,
data,
current_user_id=current_user.id,
is_admin=current_user.is_admin,
)
return _to_response(fascicolo, cnt)
@router.delete("/fascicoli/{fascicolo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_fascicolo(
fascicolo_id: uuid.UUID,
current_user: AdminUser,
db: DB,
) -> None:
"""Elimina un fascicolo (solo admin). I messaggi non vengono eliminati."""
svc = FascicoloService(db)
await svc.delete_fascicolo(current_user.tenant_id, fascicolo_id)
# ─── Messaggi del fascicolo ───────────────────────────────────────────────────
@router.get("/fascicoli/{fascicolo_id}/messages", response_model=list[FascicoloMessageItem])
async def get_fascicolo_messages(
fascicolo_id: uuid.UUID,
current_user: CurrentUser,
db: DB,
) -> list[FascicoloMessageItem]:
"""Restituisce i messaggi collegati al fascicolo."""
svc = FascicoloService(db)
rows = await svc.get_fascicolo_messages(current_user.tenant_id, fascicolo_id)
items = []
for msg, added_at in rows:
item = FascicoloMessageItem(
id=msg.id,
subject=msg.subject,
from_address=msg.from_address,
to_addresses=msg.to_addresses,
direction=msg.direction,
pec_type=msg.pec_type,
state=msg.state,
mailbox_id=msg.mailbox_id,
received_at=msg.received_at,
sent_at=msg.sent_at,
created_at=msg.created_at,
added_at=added_at,
)
items.append(item)
return items
@router.post("/fascicoli/{fascicolo_id}/messages", response_model=dict)
async def add_messages_to_fascicolo(
fascicolo_id: uuid.UUID,
data: FascicoloAddMessagesRequest,
current_user: CurrentUser,
db: DB,
) -> dict:
"""
Aggiunge messaggi al fascicolo.
Ignora i messaggi non del tenant o gia' presenti.
"""
svc = FascicoloService(db)
added = await svc.add_messages(
current_user.tenant_id,
fascicolo_id,
data.message_ids,
added_by=current_user.id,
)
return {"added": added}
@router.delete("/fascicoli/{fascicolo_id}/messages", response_model=dict)
async def remove_messages_from_fascicolo(
fascicolo_id: uuid.UUID,
data: FascicoloRemoveMessagesRequest,
current_user: CurrentUser,
db: DB,
) -> dict:
"""Rimuove messaggi dal fascicolo (non li elimina dalla posta)."""
svc = FascicoloService(db)
removed = await svc.remove_messages(
current_user.tenant_id,
fascicolo_id,
data.message_ids,
)
return {"removed": removed}
# ─── Fascicoli di un messaggio ────────────────────────────────────────────────
@router.get("/messages/{message_id}/fascicoli", response_model=list[MessageFascicoloSummary])
async def get_message_fascicoli(
message_id: uuid.UUID,
current_user: CurrentUser,
db: DB,
) -> list[MessageFascicoloSummary]:
"""Restituisce i fascicoli a cui appartiene un messaggio."""
# Verifica che il messaggio esista nel tenant
result = await db.execute(
select(Message).where(
Message.id == message_id,
Message.tenant_id == current_user.tenant_id,
)
)
if not result.scalar_one_or_none():
raise NotFoundError(f"Messaggio {message_id} non trovato")
svc = FascicoloService(db)
fascicoli = await svc.get_message_fascicoli(current_user.tenant_id, message_id)
return [MessageFascicoloSummary.model_validate(f) for f in fascicoli]