feat(istruttoria): verifica riga-per-riga con dual declared/verified
Replica il workflow del foglio Excel originale (REMISSIONE_DEL_DEBITO_5888.xlsm).
Istruttore ora verifica ogni fattura, ogni dipendente ULA, ogni documento singolarmente
invece di accettare/respingere la pratica intera.
Modello dati - nuove colonne su 3 tabelle:
- remission_invoice: taxable_verified, vat_verified, total_verified,
verification_status (PENDING/AMMESSA/PARZIALE/RESPINTA), verification_notes,
date_checks (JSONB con invoice_in_period/payment_in_period), verified_by, verified_at
- remission_ula_employee: fte_pct_verified, verification_status, verification_notes,
verified_by, verified_at
- remission_document: verification_status (PENDING/VALIDO/NON_VALIDO/SCADUTO),
verification_notes, verified_by, verified_at
- remission_practice: instructor_final_notes, instructor_checklist (JSONB 3 gate SI/NO),
verbale_date
Nuovi endpoint:
- PUT /instructor/{id}/invoices/{inv_id}/verify (status + rettifica importi + note)
- PUT /instructor/{id}/ula-employees/{emp_id}/verify (rettifica FTE + note)
- PUT /instructor/{id}/documents/{doc_code}/verify (VALIDO/NON_VALIDO/SCADUTO + note)
- PUT /instructor/{id}/final-notes (note sintetiche + checklist)
Ricalcolo gate_check dual track:
- grand_total_declared: sempre (importo richiesto dal beneficiario)
- grand_total_verified: somma solo fatture AMMESSA/PARZIALE (se PARZIALE usa verified)
- remission_due: usa verified se any_verified=True, altrimenti declared (backward compat)
- residuo_da_restituire: amount_erogato - remission_due
- flag any_verified e all_verified per gating decisione finale
_auto_check_dates: fattura in periodo? pagamento in periodo?
Legge period_start e period_end da schema.gate_rules (superadmin editor).
Template: aggiunto period_start/period_end_date come campi 'editable_by superadmin'
nella sezione general static_fields.
Schema editor FE (BandoRendicontazioneSchemaEdit): aggiunto Calendar period_start
accanto a period_end in section gate rules. period_start_rule dropdown per logica
(erogato_date|fixed) resta; period_start data fissa usata dal check.
This commit is contained in:
@@ -67,6 +67,9 @@ class RemissionPractice(Base):
|
||||
reviewed_by = Column(Integer, nullable=True)
|
||||
rejection_reason = Column(Text, nullable=True)
|
||||
approved_remission = Column(Numeric(14, 2), nullable=True)
|
||||
instructor_final_notes = Column(Text, nullable=True)
|
||||
instructor_checklist = Column(JSONB, nullable=True, default=dict)
|
||||
verbale_date = Column(Date, nullable=True)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
@@ -101,6 +104,17 @@ class RemissionInvoice(Base):
|
||||
total = Column(Numeric(14, 2), nullable=False)
|
||||
pdf_filename = Column(String(512), nullable=True) # per ora solo nome, upload vero dopo
|
||||
|
||||
# Campi istruttoria (dual declared/verified)
|
||||
taxable_verified = Column(Numeric(14, 2), nullable=True)
|
||||
vat_verified = Column(Numeric(14, 2), nullable=True)
|
||||
total_verified = Column(Numeric(14, 2), nullable=True)
|
||||
verification_status = Column(String(16), nullable=False, default="PENDING")
|
||||
# PENDING | AMMESSA | PARZIALE | RESPINTA
|
||||
verification_notes = Column(Text, nullable=True)
|
||||
date_checks = Column(JSONB, nullable=True, default=dict)
|
||||
verified_by = Column(Integer, nullable=True)
|
||||
verified_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
practice = relationship("RemissionPractice", back_populates="invoices")
|
||||
@@ -127,6 +141,13 @@ class RemissionUlaEmployee(Base):
|
||||
supporting_doc_type = Column(String(64), nullable=True)
|
||||
supporting_doc_filename = Column(String(512), nullable=True)
|
||||
|
||||
# Campi istruttoria
|
||||
fte_pct_verified = Column(Numeric(5, 4), nullable=True)
|
||||
verification_status = Column(String(16), nullable=False, default="PENDING")
|
||||
verification_notes = Column(Text, nullable=True)
|
||||
verified_by = Column(Integer, nullable=True)
|
||||
verified_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
practice = relationship("RemissionPractice", back_populates="ula_employees")
|
||||
@@ -148,6 +169,13 @@ class RemissionDocument(Base):
|
||||
expires_at = Column(Date, nullable=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
|
||||
# Campi istruttoria
|
||||
verification_status = Column(String(16), nullable=False, default="PENDING")
|
||||
# PENDING | VALIDO | NON_VALIDO | SCADUTO
|
||||
verification_notes = Column(Text, nullable=True)
|
||||
verified_by = Column(Integer, nullable=True)
|
||||
verified_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
practice = relationship("RemissionPractice", back_populates="documents")
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,13 @@ from ..models import RemissionPractice, RemissionAmendmentRequest
|
||||
from ..schemas import (
|
||||
AmendmentRequestCreate, AmendmentRequestOut, AmendmentResponseSubmit,
|
||||
ReviewApproveBody, ReviewRejectBody,
|
||||
InstructorQueueItem, PracticeOut, ApiResponse
|
||||
InstructorQueueItem, PracticeOut, ApiResponse,
|
||||
InvoiceVerifyBody, UlaVerifyBody, DocumentVerifyBody,
|
||||
InstructorFinalNotesBody,
|
||||
InvoiceOut, UlaEmployeeOut, DocumentOut
|
||||
)
|
||||
from ..models import RemissionInvoice, RemissionUlaEmployee, RemissionDocument
|
||||
from datetime import date
|
||||
from .practices import _compute_gate_check
|
||||
|
||||
router = APIRouter(prefix="/api/remission-practices/instructor", tags=["instructor"])
|
||||
@@ -274,3 +279,153 @@ def respond_amendment_beneficiary(practice_id: UUID, amendment_id: UUID,
|
||||
db.refresh(ar)
|
||||
return ApiResponse(message="Risposta registrata. L'istruttore verrà notificato.",
|
||||
data=AmendmentRequestOut.model_validate(ar).model_dump(mode="json"))
|
||||
|
||||
|
||||
|
||||
# ========== VERIFICA SINGOLA RIGA ==========
|
||||
|
||||
def _auto_check_dates(inv, practice) -> dict:
|
||||
"""Verifica automatica: fattura emessa e pagata dentro il periodo ammissibilita.
|
||||
Legge period_start/period_end dal gate_rules dello schema_snapshot."""
|
||||
rules = practice.schema_snapshot.get("gate_rules", {}) or {}
|
||||
|
||||
pstart = rules.get("period_start")
|
||||
pend = rules.get("period_end")
|
||||
try:
|
||||
from datetime import date as _date
|
||||
pstart_d = _date.fromisoformat(pstart) if pstart else None
|
||||
pend_d = _date.fromisoformat(pend) if pend else None
|
||||
except Exception:
|
||||
pstart_d = pend_d = None
|
||||
|
||||
def _in_range(d):
|
||||
if not d: return None
|
||||
ok = True
|
||||
if pstart_d: ok = ok and d >= pstart_d
|
||||
if pend_d: ok = ok and d <= pend_d
|
||||
return ok
|
||||
|
||||
return {
|
||||
"period_start": pstart,
|
||||
"period_end": pend,
|
||||
"invoice_in_period": _in_range(inv.invoice_date),
|
||||
"payment_in_period": _in_range(inv.payment_date)
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{practice_id}/invoices/{invoice_id}/verify", response_model=ApiResponse)
|
||||
def verify_invoice(practice_id: UUID, invoice_id: UUID, body: InvoiceVerifyBody,
|
||||
db: Session = Depends(get_db), user: AuthUser = Depends(_require_instructor)):
|
||||
"""Rettifica/verifica una singola fattura."""
|
||||
p = _get_practice_or_404(db, practice_id)
|
||||
if p.status not in ("UNDER_REVIEW", "AWAITING_AMENDMENT"):
|
||||
raise HTTPException(status_code=409, detail=f"Pratica in stato {p.status}")
|
||||
if body.verification_status not in ("AMMESSA", "PARZIALE", "RESPINTA", "PENDING"):
|
||||
raise HTTPException(status_code=422, detail="verification_status non valido")
|
||||
|
||||
inv = db.query(RemissionInvoice).filter(
|
||||
RemissionInvoice.id == invoice_id,
|
||||
RemissionInvoice.practice_id == practice_id
|
||||
).first()
|
||||
if not inv:
|
||||
raise HTTPException(status_code=404, detail="Fattura non trovata")
|
||||
|
||||
inv.verification_status = body.verification_status
|
||||
inv.verification_notes = body.verification_notes
|
||||
if body.verification_status in ("AMMESSA", "PARZIALE"):
|
||||
# Se AMMESSA e nessuna rettifica: copio i dichiarati come verificati
|
||||
if body.verification_status == "AMMESSA" and body.taxable_verified is None and body.total_verified is None:
|
||||
inv.taxable_verified = inv.taxable
|
||||
inv.vat_verified = inv.vat
|
||||
inv.total_verified = inv.total
|
||||
else:
|
||||
if body.taxable_verified is not None: inv.taxable_verified = body.taxable_verified
|
||||
if body.vat_verified is not None: inv.vat_verified = body.vat_verified
|
||||
if body.total_verified is not None: inv.total_verified = body.total_verified
|
||||
else: # RESPINTA | PENDING
|
||||
inv.taxable_verified = inv.vat_verified = inv.total_verified = None
|
||||
|
||||
inv.date_checks = _auto_check_dates(inv, p)
|
||||
inv.verified_by = user.user_id
|
||||
inv.verified_at = datetime.now(timezone.utc)
|
||||
|
||||
db.commit()
|
||||
db.refresh(inv)
|
||||
return ApiResponse(message=f"Fattura {body.verification_status}",
|
||||
data=InvoiceOut.model_validate(inv).model_dump(mode="json"))
|
||||
|
||||
|
||||
@router.put("/{practice_id}/ula-employees/{employee_id}/verify", response_model=ApiResponse)
|
||||
def verify_ula_employee(practice_id: UUID, employee_id: UUID, body: UlaVerifyBody,
|
||||
db: Session = Depends(get_db), user: AuthUser = Depends(_require_instructor)):
|
||||
p = _get_practice_or_404(db, practice_id)
|
||||
if p.status not in ("UNDER_REVIEW", "AWAITING_AMENDMENT"):
|
||||
raise HTTPException(status_code=409, detail=f"Pratica in stato {p.status}")
|
||||
if body.verification_status not in ("AMMESSA", "PARZIALE", "RESPINTA", "PENDING"):
|
||||
raise HTTPException(status_code=422, detail="verification_status non valido")
|
||||
|
||||
e = db.query(RemissionUlaEmployee).filter(
|
||||
RemissionUlaEmployee.id == employee_id,
|
||||
RemissionUlaEmployee.practice_id == practice_id
|
||||
).first()
|
||||
if not e:
|
||||
raise HTTPException(status_code=404, detail="Dipendente non trovato")
|
||||
|
||||
e.verification_status = body.verification_status
|
||||
e.verification_notes = body.verification_notes
|
||||
if body.verification_status in ("AMMESSA", "PARZIALE"):
|
||||
if body.verification_status == "AMMESSA" and body.fte_pct_verified is None:
|
||||
e.fte_pct_verified = e.fte_pct
|
||||
elif body.fte_pct_verified is not None:
|
||||
e.fte_pct_verified = body.fte_pct_verified
|
||||
else:
|
||||
e.fte_pct_verified = None
|
||||
|
||||
e.verified_by = user.user_id
|
||||
e.verified_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
db.refresh(e)
|
||||
return ApiResponse(message=f"Dipendente {body.verification_status}",
|
||||
data=UlaEmployeeOut.model_validate(e).model_dump(mode="json"))
|
||||
|
||||
|
||||
@router.put("/{practice_id}/documents/{doc_code}/verify", response_model=ApiResponse)
|
||||
def verify_document(practice_id: UUID, doc_code: str, body: DocumentVerifyBody,
|
||||
db: Session = Depends(get_db), user: AuthUser = Depends(_require_instructor)):
|
||||
p = _get_practice_or_404(db, practice_id)
|
||||
if p.status not in ("UNDER_REVIEW", "AWAITING_AMENDMENT"):
|
||||
raise HTTPException(status_code=409, detail=f"Pratica in stato {p.status}")
|
||||
if body.verification_status not in ("VALIDO", "NON_VALIDO", "SCADUTO", "PENDING"):
|
||||
raise HTTPException(status_code=422, detail="verification_status non valido")
|
||||
|
||||
d = db.query(RemissionDocument).filter(
|
||||
RemissionDocument.practice_id == practice_id,
|
||||
RemissionDocument.doc_code == doc_code
|
||||
).first()
|
||||
if not d:
|
||||
raise HTTPException(status_code=404, detail="Documento non trovato")
|
||||
|
||||
d.verification_status = body.verification_status
|
||||
d.verification_notes = body.verification_notes
|
||||
d.verified_by = user.user_id
|
||||
d.verified_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
db.refresh(d)
|
||||
return ApiResponse(message=f"Documento {body.verification_status}",
|
||||
data=DocumentOut.model_validate(d).model_dump(mode="json"))
|
||||
|
||||
|
||||
@router.put("/{practice_id}/final-notes", response_model=ApiResponse)
|
||||
def set_instructor_final_notes(practice_id: UUID, body: InstructorFinalNotesBody,
|
||||
db: Session = Depends(get_db), user: AuthUser = Depends(_require_instructor)):
|
||||
p = _get_practice_or_404(db, practice_id)
|
||||
if p.status not in ("UNDER_REVIEW", "AWAITING_AMENDMENT"):
|
||||
raise HTTPException(status_code=409, detail=f"Pratica in stato {p.status}")
|
||||
if body.instructor_final_notes is not None:
|
||||
p.instructor_final_notes = body.instructor_final_notes
|
||||
if body.instructor_checklist is not None:
|
||||
p.instructor_checklist = body.instructor_checklist
|
||||
db.commit()
|
||||
db.refresh(p)
|
||||
return ApiResponse(message="Verbale aggiornato",
|
||||
data=PracticeOut.model_validate(p).model_dump(mode="json"))
|
||||
|
||||
@@ -52,20 +52,49 @@ def _ensure_editable(practice: RemissionPractice):
|
||||
|
||||
|
||||
def _compute_gate_check(practice: RemissionPractice) -> GateCheckResult:
|
||||
"""Valuta le gate_rules dello schema snapshot contro il contenuto della pratica."""
|
||||
"""Valuta le gate_rules dello schema snapshot contro il contenuto della pratica.
|
||||
Calcola:
|
||||
- per_category_declared: totali dichiarati dal beneficiario (sempre)
|
||||
- per_category_verified: totali verificati dall istruttore (solo AMMESSA/PARZIALE)
|
||||
- grand_total: totale dichiarato (rif. beneficiario)
|
||||
- grand_total_verified: totale verificato (rif. remissione finale)
|
||||
"""
|
||||
rules = practice.schema_snapshot.get("gate_rules", {}) or {}
|
||||
sections = practice.schema_snapshot.get("sections", []) or []
|
||||
|
||||
# totali per categoria
|
||||
per_category = {}
|
||||
grand_total = Decimal("0")
|
||||
iva_ordinario_only_taxable = rules.get("iva_ordinario_imponibile_only", True)
|
||||
use_taxable_only = (practice.iva_regime == "ORDINARIO" and iva_ordinario_only_taxable)
|
||||
|
||||
per_category_declared = {}
|
||||
per_category_verified = {}
|
||||
grand_total = Decimal("0")
|
||||
grand_total_verified = Decimal("0")
|
||||
|
||||
any_verified = False
|
||||
all_verified = len(practice.invoices) > 0
|
||||
|
||||
for inv in practice.invoices:
|
||||
amt = inv.taxable if use_taxable_only else inv.total
|
||||
per_category[inv.category_code] = per_category.get(inv.category_code, Decimal("0")) + amt
|
||||
grand_total += amt
|
||||
# Dichiarato (sempre)
|
||||
amt_decl = inv.taxable if use_taxable_only else inv.total
|
||||
per_category_declared[inv.category_code] = per_category_declared.get(inv.category_code, Decimal("0")) + amt_decl
|
||||
grand_total += amt_decl
|
||||
|
||||
# Verificato (solo se stato AMMESSA o PARZIALE)
|
||||
if inv.verification_status in ("AMMESSA", "PARZIALE"):
|
||||
any_verified = True
|
||||
# se PARZIALE usa i valori verified; se AMMESSA ma verified sono null, usa dichiarato
|
||||
if inv.verification_status == "PARZIALE":
|
||||
tax_v = inv.taxable_verified if inv.taxable_verified is not None else inv.taxable
|
||||
tot_v = inv.total_verified if inv.total_verified is not None else inv.total
|
||||
else: # AMMESSA
|
||||
tax_v = inv.taxable_verified if inv.taxable_verified is not None else inv.taxable
|
||||
tot_v = inv.total_verified if inv.total_verified is not None else inv.total
|
||||
amt_ver = tax_v if use_taxable_only else tot_v
|
||||
per_category_verified[inv.category_code] = per_category_verified.get(inv.category_code, Decimal("0")) + amt_ver
|
||||
grand_total_verified += amt_ver
|
||||
elif inv.verification_status == "PENDING":
|
||||
all_verified = False
|
||||
# RESPINTA: non contribuisce ai verified
|
||||
|
||||
# cap remissione
|
||||
amt_erogato = practice.amount_erogato
|
||||
@@ -73,6 +102,14 @@ def _compute_gate_check(practice: RemissionPractice) -> GateCheckResult:
|
||||
cap_abs = Decimal(str(rules.get("cap_absolute", 12500)))
|
||||
max_remission = min(cap_pct * amt_erogato, cap_abs)
|
||||
|
||||
# Se almeno 1 verifica fatta -> uso grand_total_verified per remission_due
|
||||
# altrimenti uso grand_total (dichiarato) per preview pre-istruttoria
|
||||
effective_total = grand_total_verified if any_verified else grand_total
|
||||
remission_due = min(effective_total, max_remission)
|
||||
|
||||
# Per compatibilità: per_category e grand_total restano "dichiarato"
|
||||
per_category = per_category_declared
|
||||
|
||||
checks = []
|
||||
|
||||
# Check 1: regime IVA scelto
|
||||
@@ -140,17 +177,22 @@ def _compute_gate_check(practice: RemissionPractice) -> GateCheckResult:
|
||||
"detail": f"Erogato: {amt_erogato} EUR"
|
||||
})
|
||||
|
||||
remission_due = min(grand_total, max_remission)
|
||||
|
||||
return GateCheckResult(
|
||||
passed=all(c["passed"] for c in checks),
|
||||
checks=checks,
|
||||
totals={
|
||||
"per_category": {k: float(v) for k, v in per_category.items()},
|
||||
"per_category_declared": {k: float(v) for k, v in per_category_declared.items()},
|
||||
"per_category_verified": {k: float(v) for k, v in per_category_verified.items()},
|
||||
"grand_total": float(grand_total),
|
||||
"grand_total_declared": float(grand_total),
|
||||
"grand_total_verified": float(grand_total_verified),
|
||||
"max_remission": float(max_remission),
|
||||
"remission_due": float(remission_due),
|
||||
"amount_erogato": float(amt_erogato)
|
||||
"amount_erogato": float(amt_erogato),
|
||||
"any_verified": any_verified,
|
||||
"all_verified": all_verified,
|
||||
"residuo_da_restituire": float(max(amt_erogato - Decimal(str(remission_due)), Decimal("0")))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -68,6 +68,15 @@ class InvoiceOut(InvoiceCreate):
|
||||
id: UUID
|
||||
practice_id: UUID
|
||||
created_at: datetime
|
||||
# istruttoria
|
||||
taxable_verified: Optional[Decimal] = None
|
||||
vat_verified: Optional[Decimal] = None
|
||||
total_verified: Optional[Decimal] = None
|
||||
verification_status: str = "PENDING"
|
||||
verification_notes: Optional[str] = None
|
||||
date_checks: Optional[dict] = None
|
||||
verified_by: Optional[int] = None
|
||||
verified_at: Optional[datetime] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -89,6 +98,12 @@ class UlaEmployeeOut(UlaEmployeeCreate):
|
||||
id: UUID
|
||||
practice_id: UUID
|
||||
created_at: datetime
|
||||
# istruttoria
|
||||
fte_pct_verified: Optional[Decimal] = None
|
||||
verification_status: str = "PENDING"
|
||||
verification_notes: Optional[str] = None
|
||||
verified_by: Optional[int] = None
|
||||
verified_at: Optional[datetime] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -110,6 +125,11 @@ class DocumentOut(BaseModel):
|
||||
uploaded_at: Optional[datetime] = None
|
||||
expires_at: Optional[date] = None
|
||||
notes: Optional[str] = None
|
||||
# istruttoria
|
||||
verification_status: str = "PENDING"
|
||||
verification_notes: Optional[str] = None
|
||||
verified_by: Optional[int] = None
|
||||
verified_at: Optional[datetime] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -130,6 +150,16 @@ class PracticeOut(BaseModel):
|
||||
updated_at: datetime
|
||||
submitted_at: Optional[datetime] = None
|
||||
|
||||
# istruttoria
|
||||
assigned_instructor_id: Optional[int] = None
|
||||
reviewed_at: Optional[datetime] = None
|
||||
reviewed_by: Optional[int] = None
|
||||
rejection_reason: Optional[str] = None
|
||||
approved_remission: Optional[Decimal] = None
|
||||
instructor_final_notes: Optional[str] = None
|
||||
instructor_checklist: Optional[dict] = None
|
||||
verbale_date: Optional[date] = None
|
||||
|
||||
invoices: List[InvoiceOut] = []
|
||||
ula_employees: List[UlaEmployeeOut] = []
|
||||
documents: List[DocumentOut] = []
|
||||
@@ -223,6 +253,35 @@ class InstructorQueueItem(BaseModel):
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
|
||||
# Verifica singola fattura
|
||||
class InvoiceVerifyBody(BaseModel):
|
||||
verification_status: str # AMMESSA | PARZIALE | RESPINTA
|
||||
taxable_verified: Optional[Decimal] = None
|
||||
vat_verified: Optional[Decimal] = None
|
||||
total_verified: Optional[Decimal] = None
|
||||
verification_notes: Optional[str] = None
|
||||
|
||||
|
||||
# Verifica singolo dipendente ULA
|
||||
class UlaVerifyBody(BaseModel):
|
||||
verification_status: str # AMMESSA | PARZIALE | RESPINTA
|
||||
fte_pct_verified: Optional[Decimal] = None
|
||||
verification_notes: Optional[str] = None
|
||||
|
||||
|
||||
# Verifica singolo documento
|
||||
class DocumentVerifyBody(BaseModel):
|
||||
verification_status: str # VALIDO | NON_VALIDO | SCADUTO
|
||||
verification_notes: Optional[str] = None
|
||||
|
||||
|
||||
# Note finali istruttore + checklist
|
||||
class InstructorFinalNotesBody(BaseModel):
|
||||
instructor_final_notes: Optional[str] = None
|
||||
instructor_checklist: Optional[dict] = None
|
||||
|
||||
|
||||
# ====================== Wrapper ======================
|
||||
|
||||
class ApiResponse(BaseModel):
|
||||
|
||||
@@ -12,8 +12,24 @@ RESTART_TEMPLATE = {
|
||||
"type": "static_fields",
|
||||
"id": "general",
|
||||
"label": "Dati generali",
|
||||
"description": "Regime IVA e dati base del beneficiario. ATECO e importo erogato sono pre-compilati dalla domanda approvata.",
|
||||
"description": "Regime IVA, dati base del beneficiario, periodo di ammissibilità delle spese.",
|
||||
"fields": [
|
||||
{
|
||||
"id": "period_start_date",
|
||||
"type": "date",
|
||||
"label": "Periodo ammissibilità — Data inizio",
|
||||
"description": "Data minima di emissione/pagamento fatture ammissibili. Di norma: data erogazione del finanziamento.",
|
||||
"editable_by": "superadmin",
|
||||
"required": True
|
||||
},
|
||||
{
|
||||
"id": "period_end_date",
|
||||
"type": "date",
|
||||
"label": "Periodo ammissibilità — Data fine",
|
||||
"description": "Data massima di emissione/pagamento fatture ammissibili.",
|
||||
"editable_by": "superadmin",
|
||||
"required": True
|
||||
},
|
||||
{
|
||||
"id": "iva_regime",
|
||||
"type": "select",
|
||||
|
||||
Reference in New Issue
Block a user