""" 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]