""" Pydantic schemas per API. """ from typing import Optional, Any, List from datetime import datetime, date from decimal import Decimal from uuid import UUID from pydantic import BaseModel, Field # ====================== Schema di rendicontazione (bando) ====================== class RemissionSchemaBase(BaseModel): schema_json: dict class RemissionSchemaCreate(RemissionSchemaBase): pass class RemissionSchemaUpdate(BaseModel): schema_json: Optional[dict] = None class RemissionSchemaOut(BaseModel): id: UUID call_id: int schema_version: int status: str schema_json: dict created_by: int created_at: datetime updated_at: datetime published_at: Optional[datetime] = None published_by: Optional[int] = None model_config = {"from_attributes": True} # ====================== Pratica di rendicontazione (beneficiario) ====================== class PracticeStartRequest(BaseModel): """Input minimo per avviare una pratica: solo application_id. Il resto viene dal DB.""" application_id: int class PracticeUpdate(BaseModel): iva_regime: Optional[str] = None notes_beneficiario: Optional[str] = None # Fattura class InvoiceCreate(BaseModel): category_code: str invoice_number: str invoice_date: date payment_date: date supplier_name: str supplier_vat: str description: str taxable: Decimal vat: Decimal = Decimal("0") total: Decimal pdf_filename: Optional[str] = None class InvoiceOut(InvoiceCreate): id: UUID practice_id: UUID created_at: datetime model_config = {"from_attributes": True} # ULA Employee class UlaEmployeeCreate(BaseModel): codice_fiscale: str full_name: str contract_type: str role_description: Optional[str] = None fte_pct: Decimal = Decimal("1") period_start_date: date period_end_date: date supporting_doc_type: Optional[str] = None supporting_doc_filename: Optional[str] = None class UlaEmployeeOut(UlaEmployeeCreate): id: UUID practice_id: UUID created_at: datetime model_config = {"from_attributes": True} # Document class DocumentUpsert(BaseModel): doc_code: str filename: Optional[str] = None uploaded_at: Optional[datetime] = None expires_at: Optional[date] = None notes: Optional[str] = None class DocumentOut(BaseModel): id: UUID practice_id: UUID doc_code: str filename: Optional[str] = None uploaded_at: Optional[datetime] = None expires_at: Optional[date] = None notes: Optional[str] = None model_config = {"from_attributes": True} # Pratica dettagliata class PracticeOut(BaseModel): id: UUID call_id: int application_id: int company_id: int user_id: int status: str schema_snapshot: dict iva_regime: Optional[str] = None amount_erogato: Decimal notes_beneficiario: Optional[str] = None created_at: datetime updated_at: datetime submitted_at: Optional[datetime] = None invoices: List[InvoiceOut] = [] ula_employees: List[UlaEmployeeOut] = [] documents: List[DocumentOut] = [] model_config = {"from_attributes": True} class PracticeListItem(BaseModel): """Riga leggera per liste.""" id: UUID call_id: int application_id: int company_id: int status: str amount_erogato: Decimal created_at: datetime submitted_at: Optional[datetime] = None # campi denormalizzati aggiunti a runtime call_name: Optional[str] = None company_name: Optional[str] = None invoice_count: int = 0 ula_count: int = 0 document_count: int = 0 model_config = {"from_attributes": True} # Gate check class GateCheckResult(BaseModel): passed: bool checks: List[dict] # [{id, label, passed, detail}] totals: dict # {per_category: {B1: 1234.56, ...}, grand_total, max_remission_due, ...} # ====================== Istruttoria ====================== class AmendmentRequestCreate(BaseModel): request_text: str deadline: date scope: Optional[dict] = None class AmendmentResponseSubmit(BaseModel): response_text: str class AmendmentRequestOut(BaseModel): id: UUID practice_id: UUID requested_by: int request_text: str scope: Optional[dict] = None deadline: date status: str response_text: Optional[str] = None response_at: Optional[datetime] = None closed_at: Optional[datetime] = None closed_by: Optional[int] = None created_at: datetime model_config = {"from_attributes": True} class ReviewRejectBody(BaseModel): rejection_reason: str class ReviewApproveBody(BaseModel): approved_remission: Optional[Decimal] = None notes: Optional[str] = None class InstructorQueueItem(BaseModel): id: UUID call_id: int application_id: int company_id: int status: str amount_erogato: Decimal submitted_at: Optional[datetime] = None assigned_instructor_id: Optional[int] = None call_name: Optional[str] = None company_name: Optional[str] = None invoice_count: int = 0 ula_count: int = 0 document_count: int = 0 open_amendments: int = 0 remission_due: Optional[float] = None model_config = {"from_attributes": True} # ====================== Wrapper ====================== class ApiResponse(BaseModel): status: str = "SUCCESS" message: Optional[str] = None data: Optional[Any] = None