- 4 nuove tabelle: remission_practice, remission_invoice, remission_ula_employee, remission_document
con cascade delete e FK
- 13 endpoint /api/remission-practices/*:
GET /mine (lista pratiche user + applications CONTRACT_SIGNED ready_to_start)
POST /start (avvia pratica da application_id, richiede schema PUBLISHED)
GET /{id}, PUT /{id} (regime IVA + note)
POST/DELETE /{id}/invoices
POST/DELETE /{id}/ula-employees
PUT/DELETE /{id}/documents/{doc_code}
GET /{id}/gate-check (valida gate rules contro pratica, ritorna totali + checks)
POST /{id}/submit (gate-check obbligatorio, status DRAFT -> SUBMITTED)
- 1 endpoint debug /api/debug/impersonate (sandbox-only, genera JWT per utente
- necessario perche' /v1/user/login del BE Spring esclude ROLE_BENEFICIARY)
- Gate check calcola: totali per categoria, grand_total, max_remission = min(cap_pct*erogato, cap_abs),
remission_due = min(grand_total, max_remission), applica iva_ordinario_imponibile_only
174 lines
4.0 KiB
Python
174 lines
4.0 KiB
Python
"""
|
|
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, ...}
|
|
|
|
|
|
# ====================== Wrapper ======================
|
|
|
|
class ApiResponse(BaseModel):
|
|
status: str = "SUCCESS"
|
|
message: Optional[str] = None
|
|
data: Optional[Any] = None
|