feat: endpoint pratiche rendicontazione (lato beneficiario)
- 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
This commit is contained in:
139
app/schemas.py
139
app/schemas.py
@@ -1,14 +1,17 @@
|
||||
"""
|
||||
Pydantic schemas per API.
|
||||
"""
|
||||
from typing import Optional, Any
|
||||
from datetime import datetime
|
||||
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 = Field(..., description="JSON dello schema di rendicontazione")
|
||||
schema_json: dict
|
||||
|
||||
|
||||
class RemissionSchemaCreate(RemissionSchemaBase):
|
||||
@@ -34,6 +37,136 @@ class RemissionSchemaOut(BaseModel):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user