254 lines
8.7 KiB
Python
254 lines
8.7 KiB
Python
"""
|
||
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,
|
||
FascicoloDeadlineResponse,
|
||
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.get("/fascicoli/scadenzario", response_model=list[FascicoloDeadlineResponse])
|
||
async def list_fascicoli_scadenzario(
|
||
current_user: CurrentUser,
|
||
db: DB,
|
||
days_ahead: int = Query(30, ge=1, le=365, description="Giorni da considerare in avanti"),
|
||
include_overdue: bool = Query(True, description="Includi fascicoli gia' scaduti"),
|
||
) -> list[FascicoloDeadlineResponse]:
|
||
"""
|
||
Scadenzario pratiche: fascicoli con scadenza imminente o scaduta.
|
||
|
||
Ordinati per scadenza ASC (scaduti prima, poi futuri).
|
||
"""
|
||
svc = FascicoloService(db)
|
||
rows = await svc.list_fascicoli_scadenzario(
|
||
current_user.tenant_id,
|
||
days_ahead=days_ahead,
|
||
include_overdue=include_overdue,
|
||
)
|
||
items = []
|
||
for fascicolo, cnt, is_overdue in rows:
|
||
resp = FascicoloDeadlineResponse.model_validate(fascicolo)
|
||
resp.message_count = cnt
|
||
resp.is_overdue = is_overdue
|
||
items.append(resp)
|
||
return items
|
||
|
||
|
||
@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]
|