""" 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 # ========================================================================= # BLANK_TEMPLATE — scheletro minimo v2, solo sezioni vuote da popolare # ========================================================================= BLANK_TEMPLATE = { "version": "2.0", "schema_version": 2, "template_id": "BLANK_V2", "template_label": "Nuovo schema vuoto", "sections": [ { "type": "static_fields", "id": "general", "label": "Dati generali", "description": "Regime IVA, dati base del beneficiario, periodo di ammissibilita delle spese.", "fields": [ { "id": "period_start_date", "type": "date", "label": "Periodo ammissibilita — Data inizio", "description": "Data minima di emissione/pagamento fatture ammissibili.", "editable_by": "superadmin", "required": True, }, { "id": "period_end_date", "type": "date", "label": "Periodo ammissibilita — 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 e rendicontabile.", }, ], }, { "type": "invoice_table", "id": "invoices", "label": "Fatture ammissibili", "description": "Categorie di spesa da configurare. Aggiungi almeno una categoria prima di pubblicare.", "categories": [], }, { "type": "ula_block", "id": "ula", "label": "Incremento occupazione (ULA)", "description": "Dipendenti su cui calcolare l'incremento ULA. Disattiva la sezione se il bando non lo richiede.", "enabled": False, "threshold": 1.0, "fields": [], }, { "type": "documents_required", "id": "docs", "label": "Documenti richiesti", "description": "Documenti che il beneficiario deve allegare alla rendicontazione. Aggiungi almeno i documenti obbligatori.", "items": [], }, ], "custom_checks": [], "gate_rules": { "invoices_min_count": 1, "amount_range": {"min": 0, "max": 100000}, "cap_pct_erogato": 0.5, "cap_absolute": 100000, "amount_basis": "imponibile_only_ordinario", "period_start_rule": "erogato_date", "period_end": None, "require_at_least_one_invoice_per_nonzero_category": True, "require_ula_above_threshold": False, "require_all_documents_resolved": True, "max_tranches": 1, }, } # ========================================================================= # TEMPLATES registry — esteso in futuro con nuovi bandi # ========================================================================= TEMPLATES = { "blank": { "template_id": "blank", "label": "Nuovo schema (da zero)", "description": "Scheletro minimo: sezioni vuote (categorie, documenti, controlli) da popolare. Usa questo quando il bando e nuovo e non somiglia a bandi precedenti.", "schema": BLANK_TEMPLATE, }, "restart": { "template_id": "restart", "label": "RE-START (fondo prestiti con remissione del debito)", "description": "Template del bando RE-START: 3 categorie B1/B2/B3 (tecnologie, ULA, formazione), sezione ULA attiva con soglia 1.0, 4 documenti standard, max 2 tranches.", "schema": RESTART_TEMPLATE, }, } def list_templates(): """Restituisce i template disponibili (senza lo schema completo, solo metadati).""" return [ { "template_id": t["template_id"], "label": t["label"], "description": t["description"], } for t in TEMPLATES.values() ] def get_template(template_id: str): """Restituisce uno schema template pronto per l'uso (deep copy).""" import copy t = TEMPLATES.get(template_id) if not t: return None return copy.deepcopy(t["schema"])