A1 migrations.py: - remission_practice DROP uq_application + ADD sequence_number/period_label/suggested_instructor_id - UNIQUE composita (application_id, sequence_number) - partial index idx_remission_practice_unassigned su assigned_instructor_id NULL - nuova tabella remission_custom_check_value (storage_path/mime/size/sha256 allineata adapter) A2 models.py + templates.py: - RemissionPractice: UniqueConstraint composita, campi multi-tranche, relationship custom_checks - classe RemissionCustomCheckValue - RESTART_TEMPLATE schema_version=2, max_tranches=2, custom_checks esempio (antiriciclaggio required no-doc, polizza_fidejussoria optional con-doc) - upgrade_schema_to_v2 idempotente per snapshot v1 esistenti A3 _compute_gate_check(db, practice) CUMULATIVO: - max_remission_global = min(cap_pct * erogato, cap_abs) - already_approved = func.sum(approved_remission) su tranche APPROVED precedenti dello stesso application_id con sequence_number < corrente - max_remission_this_tranche = max(0, global - already_approved) - pre_check_admissible = min(grand_total_declared, this_tranche) [voce 2 Cecilia] - remission_due = min(effective_total, this_tranche) - residuo_da_restituire = erogato - already_approved - remission_due (cumulativo) - output totals esteso: sequence_number, tranches_count, tranches_max - signature (db, practice) - aggiornati 6 call site in practices/instructor/verbale Test su NAPOLI SAS: erogato 17K, cap 8500, tranche 1 approvata 467.14EUR, tranche 2 vuota -> residuo disponibile 8032.86EUR, residuo_da_restituire 16532.86EUR.
179 lines
7.5 KiB
Python
179 lines
7.5 KiB
Python
"""
|
|
Template schemi precompilati per bandi noti.
|
|
RE-START: il bando del xlsx di Cecilia, base per la prima iterazione.
|
|
|
|
v2 (2026-04-18): schema_version=2, max_tranches, custom_checks[]
|
|
"""
|
|
|
|
RESTART_TEMPLATE = {
|
|
"version": "2.0",
|
|
"schema_version": 2,
|
|
"template_id": "RESTART_V2",
|
|
"template_label": "RE-START (fondo prestiti con remissione del debito)",
|
|
"sections": [
|
|
{
|
|
"type": "static_fields",
|
|
"id": "general",
|
|
"label": "Dati generali",
|
|
"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",
|
|
"label": "Regime IVA",
|
|
"required": True,
|
|
"options": [
|
|
{"value": "ORDINARIO", "label": "Ordinario — IVA non ammissibile"},
|
|
{"value": "FORFETTARIO", "label": "Forfettario — IVA ammissibile"},
|
|
{"value": "ESENTE", "label": "Esente"},
|
|
],
|
|
"help": "Il regime IVA determina se l'IVA delle fatture è rendicontabile. In regime ordinario vale solo l'imponibile.",
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"type": "category_grid",
|
|
"id": "expenses",
|
|
"label": "Spese ammissibili per categoria",
|
|
"description": "Carica le fatture dentro la categoria appropriata. Totali parziali e complessivo calcolati in tempo reale.",
|
|
"categories": [
|
|
{
|
|
"code": "B1",
|
|
"label": "Tecnologie innovative (Industry 4.0, digitale)",
|
|
"description": "Hardware, software, soluzioni innovative destinate ad attività produttive",
|
|
"cap_amount": None,
|
|
},
|
|
{
|
|
"code": "B2",
|
|
"label": "Incremento ULA (occupazione)",
|
|
"description": "Costi del personale collegati a incremento di occupazione",
|
|
"cap_amount": None,
|
|
},
|
|
{
|
|
"code": "B3",
|
|
"label": "Formazione",
|
|
"description": "Corsi, docenze, materiali didattici per il personale",
|
|
"cap_amount": None,
|
|
},
|
|
],
|
|
"invoice_schema": {
|
|
"required_fields": [
|
|
"invoice_number",
|
|
"invoice_date",
|
|
"payment_date",
|
|
"supplier_name",
|
|
"supplier_vat",
|
|
"description",
|
|
"taxable",
|
|
"vat",
|
|
"total",
|
|
"pdf",
|
|
],
|
|
"optional_fields": ["vat_rate", "vat_exempt_reason"],
|
|
},
|
|
},
|
|
{
|
|
"type": "ula_block",
|
|
"id": "ula",
|
|
"label": "Calcolo ULA (incremento occupazione)",
|
|
"description": "Per ogni dipendente: codice fiscale, tipologia contratto, percentuale di tempo, periodo. Allegato di supporto obbligatorio (LUL, estratto gestionale, dichiarazione del consulente del lavoro).",
|
|
"enabled": True,
|
|
"threshold": 1.0,
|
|
"period_start_rule": "erogato_date",
|
|
"period_end": "2021-12-31",
|
|
"supporting_doc_required": True,
|
|
"supporting_doc_types": [
|
|
{"code": "LUL", "label": "Libro Unico del Lavoro"},
|
|
{"code": "GESTIONALE_PAGHE", "label": "Estratto gestionale paghe"},
|
|
{"code": "DICHIARAZIONE_CDL", "label": "Dichiarazione Consulente del Lavoro"},
|
|
{"code": "ALTRO", "label": "Altro documento di supporto"},
|
|
],
|
|
},
|
|
{
|
|
"type": "document_checklist",
|
|
"id": "docs",
|
|
"label": "Documenti richiesti",
|
|
"description": "I documenti già in regola nel repository della Company saranno riutilizzati (semaforo verde). Solo quelli scaduti o mancanti richiedono caricamento.",
|
|
"required_types": [
|
|
{"code": "DURC", "label": "DURC (Documento Unico di Regolarità Contributiva)"},
|
|
{"code": "VISURA_CAMERALE", "label": "Visura camerale aggiornata"},
|
|
{"code": "BILANCIO", "label": "Bilancio ultimo esercizio"},
|
|
{"code": "ANTIRICICLAGGIO", "label": "Dichiarazione antiriciclaggio"},
|
|
],
|
|
},
|
|
],
|
|
"custom_checks": [
|
|
{
|
|
"code": "antiriciclaggio",
|
|
"label": "Dichiarazione antiriciclaggio",
|
|
"description": "Dichiaro che il beneficiario rispetta la normativa antiriciclaggio (D.Lgs. 231/2007 e s.m.i.) e che i soggetti coinvolti non sono iscritti in liste sanzionatorie.",
|
|
"requires_document": False,
|
|
"required": True,
|
|
},
|
|
{
|
|
"code": "polizza_fidejussoria",
|
|
"label": "Polizza fidejussoria",
|
|
"description": "Allegare copia della polizza fidejussoria a garanzia dell'importo erogato (se richiesta da bando).",
|
|
"requires_document": True,
|
|
"required": False,
|
|
},
|
|
],
|
|
"gate_rules": {
|
|
"amount_range": {"min": 5000, "max": 25000},
|
|
"cap_pct_erogato": 0.5,
|
|
"cap_absolute": 12500,
|
|
"iva_ordinario_imponibile_only": True,
|
|
"period_start_rule": "erogato_date",
|
|
"period_end": "2021-12-31",
|
|
"require_at_least_one_invoice_per_nonzero_category": True,
|
|
"require_ula_above_threshold": True,
|
|
"require_all_documents_resolved": True,
|
|
"max_tranches": 2, # v2: superadmin configurabile, default 1
|
|
},
|
|
}
|
|
|
|
|
|
def upgrade_schema_to_v2(schema_json: dict) -> dict:
|
|
"""Upgrade in-place di schema v1 a v2.
|
|
- Aggiunge schema_version=2 se mancante
|
|
- Aggiunge gate_rules.max_tranches=1 se mancante
|
|
- Aggiunge custom_checks=[] se mancante
|
|
- Assicura ula_section.enabled presente (default True se ula_block esiste)
|
|
Idempotente: se lo schema e gia v2, no-op.
|
|
"""
|
|
if not isinstance(schema_json, dict):
|
|
return schema_json
|
|
changed = False
|
|
if schema_json.get("schema_version", 1) < 2:
|
|
schema_json["schema_version"] = 2
|
|
changed = True
|
|
gate = schema_json.setdefault("gate_rules", {})
|
|
if "max_tranches" not in gate:
|
|
gate["max_tranches"] = 1
|
|
changed = True
|
|
if "custom_checks" not in schema_json:
|
|
schema_json["custom_checks"] = []
|
|
changed = True
|
|
# ula_section.enabled esplicito
|
|
for sec in schema_json.get("sections", []):
|
|
if sec.get("type") == "ula_block" and "enabled" not in sec:
|
|
sec["enabled"] = True
|
|
changed = True
|
|
return schema_json
|