""" Router scadenzario e tracking deadlines (Feature 4). Endpoint: GET /deadlines – messaggi con scadenze imminenti POST /messages/{id}/deadline – imposta/modifica/rimuove scadenza """ import uuid from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Query from pydantic import BaseModel from sqlalchemy import and_, select from app.dependencies import CurrentUser, DB from app.models.message import Message from app.schemas.message import MessageResponse from app.core.exceptions import NotFoundError router = APIRouter(tags=["Deadlines"]) class DeadlineSetRequest(BaseModel): deadline_at: datetime | None = None """Imposta a null per rimuovere la scadenza.""" deadline_note: str | None = None class DeadlineMessageResponse(BaseModel): model_config = {"from_attributes": True} id: uuid.UUID subject: str | None from_address: str | None to_addresses: list[str] | None = None direction: str pec_type: str state: str mailbox_id: uuid.UUID deadline_at: datetime | None = None deadline_note: str | None = None is_overdue: bool = False received_at: datetime | None = None sent_at: datetime | None = None created_at: datetime @router.get("/deadlines", response_model=list[DeadlineMessageResponse]) async def list_deadlines( 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 scadenze gia' passate"), ) -> list[DeadlineMessageResponse]: """ Restituisce i messaggi con scadenze nel range specificato. Ordinati per: scaduti prima, poi per deadline_at ASC. """ now = datetime.now(timezone.utc) future_limit = now + timedelta(days=days_ahead) conditions = [ Message.tenant_id == current_user.tenant_id, Message.deadline_at.is_not(None), Message.is_trashed == False, # noqa: E712 ] if include_overdue: # Include scaduti e futuri fino al limite conditions.append(Message.deadline_at <= future_limit) else: # Solo scadenze future conditions.append(and_(Message.deadline_at > now, Message.deadline_at <= future_limit)) result = await db.execute( select(Message) .where(and_(*conditions)) .order_by(Message.deadline_at) .limit(200) ) messages = list(result.scalars().all()) items = [] for msg in messages: is_overdue = msg.deadline_at < now if msg.deadline_at else False items.append(DeadlineMessageResponse( 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, deadline_at=msg.deadline_at, deadline_note=msg.deadline_note, is_overdue=is_overdue, received_at=msg.received_at, sent_at=msg.sent_at, created_at=msg.created_at, )) return items @router.post("/messages/{message_id}/deadline", response_model=DeadlineMessageResponse) async def set_deadline( message_id: uuid.UUID, data: DeadlineSetRequest, current_user: CurrentUser, db: DB, ) -> DeadlineMessageResponse: """ Imposta, modifica o rimuove la scadenza di un messaggio. Passa deadline_at=null per rimuovere la scadenza. """ result = await db.execute( select(Message).where( Message.id == message_id, Message.tenant_id == current_user.tenant_id, ) ) msg = result.scalar_one_or_none() if not msg: raise NotFoundError(f"Messaggio {message_id} non trovato") msg.deadline_at = data.deadline_at msg.deadline_note = data.deadline_note await db.commit() await db.refresh(msg) now = datetime.now(timezone.utc) is_overdue = msg.deadline_at < now if msg.deadline_at else False return DeadlineMessageResponse( 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, deadline_at=msg.deadline_at, deadline_note=msg.deadline_note, is_overdue=is_overdue, received_at=msg.received_at, sent_at=msg.sent_at, created_at=msg.created_at, )