- Dockerfile: dipendenze sistema libpango/libgdk-pixbuf/libcairo/shared-mime-info + fonts-dejavu per rendering WeasyPrint su debian slim - requirements: weasyprint==61.2 + pydyf==0.10.0 (vincolo compatibilita, weasyprint 62.x ha bug con pydyf 0.11 su stream.transform) + jinja2==3.1.3 - templates_jinja/verbale_istruttoria.html: layout A4 professionale con intestazione Gepafin, dati pratica, tabelle fatture raggruppate per categoria (dichiarato vs ammesso con motivazione rettifica), ULA, documenti, soccorsi istruttori, totali, checklist finale, note istruttore, blocco firma - routers/verbale: endpoint /verbale.html (debug preview) e /verbale.pdf (weasyprint on-the-fly) — solo ruoli istruttore/superadmin - main: include router verbale, version bump 0.3.0 Testato E2E: PDF 27KB generato su pratica UNDER_REVIEW, magic bytes PDF-1.7 OK.
483 lines
18 KiB
HTML
483 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Verbale di istruttoria — Pratica {{ practice.application_id }}</title>
|
|
<style>
|
|
@page {
|
|
size: A4;
|
|
margin: 22mm 18mm 20mm 18mm;
|
|
@top-left {
|
|
content: "GEPAFIN S.p.A.";
|
|
font-family: "DejaVu Sans", Helvetica, sans-serif;
|
|
font-size: 8pt;
|
|
color: #4a5568;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
@top-right {
|
|
content: "Verbale di istruttoria — Pratica {{ practice.application_id }}";
|
|
font-family: "DejaVu Sans", Helvetica, sans-serif;
|
|
font-size: 8pt;
|
|
color: #4a5568;
|
|
}
|
|
@bottom-center {
|
|
content: "Pagina " counter(page) " di " counter(pages);
|
|
font-family: "DejaVu Sans", Helvetica, sans-serif;
|
|
font-size: 8pt;
|
|
color: #718096;
|
|
}
|
|
@bottom-right {
|
|
content: "Generato: {{ generated_at }}";
|
|
font-family: "DejaVu Sans", Helvetica, sans-serif;
|
|
font-size: 7pt;
|
|
color: #a0aec0;
|
|
}
|
|
}
|
|
|
|
html { font-family: "DejaVu Sans", Helvetica, sans-serif; font-size: 10pt; color: #1a202c; }
|
|
body { margin: 0; }
|
|
|
|
h1 { font-size: 18pt; margin: 0 0 4pt 0; color: #1a365d; letter-spacing: -0.3px; }
|
|
h2 { font-size: 12pt; margin: 18pt 0 6pt 0; padding: 4pt 0 4pt 8pt;
|
|
border-left: 3pt solid #2b6cb0; color: #1a365d; page-break-after: avoid; }
|
|
h3 { font-size: 10pt; margin: 12pt 0 4pt 0; color: #2d3748; page-break-after: avoid; }
|
|
p { margin: 4pt 0; line-height: 1.4; }
|
|
small { color: #4a5568; }
|
|
|
|
/* Intestazione GEPAFIN */
|
|
.hdr {
|
|
border-bottom: 2pt solid #1a365d;
|
|
padding-bottom: 10pt;
|
|
margin-bottom: 14pt;
|
|
}
|
|
.hdr__logo {
|
|
font-size: 22pt; font-weight: 900; color: #1a365d; letter-spacing: 1pt;
|
|
display: inline-block;
|
|
}
|
|
.hdr__subtitle {
|
|
font-size: 9pt; color: #4a5568; margin-top: 2pt; letter-spacing: 0.5px;
|
|
}
|
|
.hdr__right {
|
|
float: right; text-align: right; font-size: 9pt; color: #4a5568;
|
|
}
|
|
|
|
/* Badge esito */
|
|
.badge {
|
|
display: inline-block; padding: 3pt 8pt; border-radius: 4pt;
|
|
font-size: 10pt; font-weight: 700; letter-spacing: 0.4pt;
|
|
}
|
|
.badge--approved { background: #c6f6d5; color: #22543d; border: 0.5pt solid #68d391; }
|
|
.badge--rejected { background: #fed7d7; color: #742a2a; border: 0.5pt solid #fc8181; }
|
|
.badge--review { background: #fefcbf; color: #744210; border: 0.5pt solid #ecc94b; }
|
|
.badge--amendment { background: #feebc8; color: #7b341e; border: 0.5pt solid #f6ad55; }
|
|
|
|
/* Grid dati pratica */
|
|
.meta-grid {
|
|
display: table; width: 100%; border-collapse: collapse;
|
|
margin: 8pt 0 4pt 0; background: #f7fafc; border: 0.5pt solid #e2e8f0;
|
|
}
|
|
.meta-grid .row { display: table-row; }
|
|
.meta-grid .cell { display: table-cell; padding: 5pt 8pt;
|
|
border-bottom: 0.3pt solid #edf2f7; vertical-align: top; }
|
|
.meta-grid .cell.label { width: 32%; font-weight: 700; color: #4a5568; font-size: 9pt; background: #edf2f7; }
|
|
.meta-grid .cell.val { font-size: 10pt; }
|
|
|
|
/* Tabelle fatture / ULA / doc */
|
|
table.data {
|
|
width: 100%; border-collapse: collapse; margin: 4pt 0 8pt 0;
|
|
font-size: 8.5pt; border: 0.5pt solid #cbd5e0;
|
|
}
|
|
table.data th {
|
|
background: #2d3748; color: white; padding: 4pt 6pt;
|
|
font-weight: 600; text-align: left; border-right: 0.3pt solid #4a5568;
|
|
}
|
|
table.data td {
|
|
padding: 4pt 6pt; border-bottom: 0.3pt solid #e2e8f0;
|
|
border-right: 0.3pt solid #edf2f7; vertical-align: top;
|
|
}
|
|
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
table.data tr.subheader td {
|
|
background: #ebf4ff; color: #2a4365; font-weight: 700;
|
|
border-top: 0.5pt solid #4299e1; border-bottom: 0.5pt solid #4299e1;
|
|
padding: 5pt 6pt; font-size: 9pt;
|
|
}
|
|
table.data tr.totals td {
|
|
background: #f7fafc; font-weight: 700; border-top: 0.8pt solid #2d3748;
|
|
}
|
|
table.data tr.rejected td {
|
|
background: #fff5f5; color: #742a2a;
|
|
}
|
|
table.data tr.partial td {
|
|
background: #fffaf0;
|
|
}
|
|
|
|
/* Stato tag inline */
|
|
.status-inline {
|
|
display: inline-block; padding: 1pt 5pt; border-radius: 3pt;
|
|
font-size: 7.5pt; font-weight: 700; letter-spacing: 0.3pt;
|
|
}
|
|
.status-AMMESSA, .status-VALIDO { background: #c6f6d5; color: #22543d; }
|
|
.status-PARZIALE { background: #feebc8; color: #7b341e; }
|
|
.status-RESPINTA, .status-NON_VALIDO, .status-SCADUTO { background: #fed7d7; color: #742a2a; }
|
|
.status-PENDING { background: #edf2f7; color: #4a5568; }
|
|
|
|
.note-box {
|
|
margin: 4pt 0 6pt 0;
|
|
padding: 6pt 8pt;
|
|
background: #fffaf0;
|
|
border-left: 2pt solid #ed8936;
|
|
font-size: 9pt; font-style: italic;
|
|
}
|
|
|
|
.totals-summary {
|
|
display: table; width: 100%; margin: 10pt 0;
|
|
background: #f7fafc; border: 0.5pt solid #cbd5e0;
|
|
}
|
|
.totals-summary .row { display: table-row; }
|
|
.totals-summary .cell {
|
|
display: table-cell; padding: 8pt 10pt; width: 25%;
|
|
border-right: 0.3pt solid #e2e8f0; vertical-align: middle;
|
|
}
|
|
.totals-summary .cell:last-child { border-right: none; }
|
|
.totals-summary .lbl { font-size: 8pt; color: #4a5568; text-transform: uppercase; letter-spacing: 0.3pt; }
|
|
.totals-summary .val { font-size: 14pt; font-weight: 700; color: #1a365d; margin-top: 2pt; }
|
|
.totals-summary .val.final { color: #2b6cb0; }
|
|
.totals-summary .val.residuo { color: #c53030; }
|
|
|
|
.amend-box {
|
|
border: 0.5pt solid #f6ad55; border-left: 2pt solid #ed8936;
|
|
background: #fffaf0; padding: 6pt 10pt; margin: 4pt 0 6pt 0;
|
|
}
|
|
.amend-box .head { font-size: 9pt; color: #744210; margin-bottom: 4pt; }
|
|
.amend-box .req, .amend-box .resp { font-size: 9pt; margin: 3pt 0; white-space: pre-wrap; }
|
|
.amend-box .resp { padding: 4pt 6pt; background: white; border-radius: 2pt; }
|
|
|
|
/* Firma */
|
|
.sign-block {
|
|
margin-top: 18pt;
|
|
display: table; width: 100%;
|
|
}
|
|
.sign-block .col { display: table-cell; width: 50%; padding: 6pt 10pt; vertical-align: top; }
|
|
.sign-block .lbl { font-size: 9pt; color: #4a5568; font-weight: 700; letter-spacing: 0.3pt; }
|
|
.sign-block .val { font-size: 10pt; margin-top: 2pt; }
|
|
.sign-block .sig-line {
|
|
margin-top: 30pt;
|
|
border-top: 0.5pt solid #2d3748;
|
|
font-size: 8pt;
|
|
color: #4a5568;
|
|
padding-top: 2pt;
|
|
}
|
|
|
|
.ok { color: #22543d; font-weight: 700; }
|
|
.ko { color: #c53030; font-weight: 700; }
|
|
.text-secondary { color: #718096; }
|
|
|
|
/* Clearfix per header float */
|
|
.clearfix::after { content: ""; display: table; clear: both; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="hdr clearfix">
|
|
<div class="hdr__right">
|
|
<div><strong>Verbale di istruttoria</strong></div>
|
|
<div>Rendicontazione bando</div>
|
|
<div>Pratica n. {{ practice.application_id }}</div>
|
|
</div>
|
|
<div>
|
|
<span class="hdr__logo">GEPAFIN</span>
|
|
<div class="hdr__subtitle">Finanziaria regionale dell'Umbria</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h1>Verbale di istruttoria — Rendicontazione</h1>
|
|
<p>
|
|
{% if practice.status == 'APPROVED' %}
|
|
<span class="badge badge--approved">ESITO: APPROVATA</span>
|
|
{% elif practice.status == 'REJECTED' %}
|
|
<span class="badge badge--rejected">ESITO: RESPINTA</span>
|
|
{% elif practice.status == 'AWAITING_AMENDMENT' %}
|
|
<span class="badge badge--amendment">SOCCORSO ISTRUTTORIO IN CORSO</span>
|
|
{% else %}
|
|
<span class="badge badge--review">IN ISTRUTTORIA</span>
|
|
{% endif %}
|
|
</p>
|
|
|
|
<h2>Dati pratica</h2>
|
|
<div class="meta-grid">
|
|
<div class="row">
|
|
<div class="cell label">Bando</div>
|
|
<div class="cell val">{{ practice.schema_snapshot.template_label or ('Bando #' ~ practice.call_id) }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="cell label">Numero applicazione</div>
|
|
<div class="cell val">#{{ practice.application_id }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="cell label">Beneficiario</div>
|
|
<div class="cell val">
|
|
{{ company.company_name or '(non disponibile)' }}
|
|
{% if company.vat_number %} · P.IVA {{ company.vat_number }}{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="cell label">Regime IVA</div>
|
|
<div class="cell val">{{ practice.iva_regime or '—' }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="cell label">Importo erogato</div>
|
|
<div class="cell val">{{ practice.amount_erogato|euro }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="cell label">Periodo rendicontazione</div>
|
|
<div class="cell val">
|
|
{% set period = practice.schema_snapshot.get('period') or {} %}
|
|
{% if period.get('start_date') and period.get('end_date') %}
|
|
{{ period.start_date|datefmt }} — {{ period.end_date|datefmt }}
|
|
{% else %}—{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="cell label">Data presentazione</div>
|
|
<div class="cell val">{{ practice.submitted_at|datetimefmt if practice.submitted_at else '—' }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="cell label">Data istruttoria</div>
|
|
<div class="cell val">{{ practice.reviewed_at|datetimefmt if practice.reviewed_at else generated_at }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# ============ FATTURE ============ #}
|
|
<h2>Verifica fatture</h2>
|
|
{% if practice.invoices %}
|
|
{% set use_taxable = totals.use_taxable_only is not sameas false %}
|
|
<table class="data">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:9%">N°</th>
|
|
<th style="width:9%">Data</th>
|
|
<th style="width:24%">Fornitore / Descrizione</th>
|
|
<th style="width:12%" class="num">{{ 'Imponibile' if use_taxable else 'Totale' }} dichiarato</th>
|
|
<th style="width:12%" class="num">{{ 'Imponibile' if use_taxable else 'Totale' }} ammesso</th>
|
|
<th style="width:10%">Stato</th>
|
|
<th style="width:24%">Motivazione istruttore</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for cat_code, items in invoices_by_cat.items() %}
|
|
{% set cat_label = categories_map.get(cat_code, cat_code) %}
|
|
{% set cat_decl = per_cat_declared.get(cat_code, 0) %}
|
|
{% set cat_verif = per_cat_verified.get(cat_code, 0) %}
|
|
<tr class="subheader">
|
|
<td colspan="3"><strong>{{ cat_code }}</strong> — {{ cat_label }}</td>
|
|
<td class="num">{{ cat_decl|euro }}</td>
|
|
<td class="num">{{ cat_verif|euro }}</td>
|
|
<td colspan="2"><small>{{ items|length }} fatture</small></td>
|
|
</tr>
|
|
{% for inv in items %}
|
|
{% set cls = 'rejected' if inv.verification_status == 'RESPINTA' else ('partial' if inv.verification_status == 'PARZIALE' else '') %}
|
|
{% set declared = inv.taxable if use_taxable else inv.total %}
|
|
{% if inv.verification_status == 'PENDING' %}
|
|
{% set verified_val = None %}
|
|
{% elif inv.verification_status == 'RESPINTA' %}
|
|
{% set verified_val = 0 %}
|
|
{% elif use_taxable %}
|
|
{% set verified_val = inv.taxable_verified if inv.taxable_verified is not none else declared %}
|
|
{% else %}
|
|
{% set verified_val = inv.total_verified if inv.total_verified is not none else declared %}
|
|
{% endif %}
|
|
<tr class="{{ cls }}">
|
|
<td>{{ inv.invoice_number }}</td>
|
|
<td>{{ inv.invoice_date|datefmt }}</td>
|
|
<td>
|
|
<strong>{{ inv.supplier_name }}</strong><br>
|
|
<small>{{ inv.description|truncate(80) }}</small>
|
|
</td>
|
|
<td class="num">{{ declared|euro }}</td>
|
|
<td class="num">{{ verified_val|euro if verified_val is not none else '—' }}</td>
|
|
<td><span class="status-inline status-{{ inv.verification_status }}">{{ inv.verification_status }}</span></td>
|
|
<td>{{ inv.verification_notes or '—' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
<tr class="totals">
|
|
<td colspan="3"><strong>Totale complessivo</strong></td>
|
|
<td class="num">{{ totals.grand_total_declared|euro }}</td>
|
|
<td class="num">{{ totals.grand_total_verified|euro }}</td>
|
|
<td colspan="2"></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p class="text-secondary">Nessuna fattura rendicontata.</p>
|
|
{% endif %}
|
|
|
|
{# ============ ULA ============ #}
|
|
{% if ula_section_enabled and practice.ula_employees %}
|
|
<h2>Verifica dipendenti ULA</h2>
|
|
<p><small>Soglia incremento richiesta: <strong>≥ {{ '%.2f'|format(ula_threshold) }}</strong> · FTE dichiarato: <strong>{{ '%.2f'|format(ula_fte_decl) }}</strong> · FTE ammesso: <strong class="{{ 'ok' if ula_ok else 'ko' }}">{{ '%.2f'|format(ula_fte_verif) }}</strong></small></p>
|
|
<table class="data">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:16%">CF</th>
|
|
<th style="width:18%">Dipendente</th>
|
|
<th style="width:14%">Contratto</th>
|
|
<th style="width:16%">Periodo</th>
|
|
<th style="width:8%" class="num">FTE dich.</th>
|
|
<th style="width:8%" class="num">FTE amm.</th>
|
|
<th style="width:8%">Stato</th>
|
|
<th style="width:12%">Note</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for emp in practice.ula_employees %}
|
|
{% set cls = 'rejected' if emp.verification_status == 'RESPINTA' else ('partial' if emp.verification_status == 'PARZIALE' else '') %}
|
|
{% set fte_verif = emp.fte_pct_verified if emp.fte_pct_verified is not none else emp.fte_pct %}
|
|
<tr class="{{ cls }}">
|
|
<td>{{ emp.codice_fiscale }}</td>
|
|
<td>{{ emp.full_name }}</td>
|
|
<td>{{ contract_labels.get(emp.contract_type, emp.contract_type) }}</td>
|
|
<td>{{ emp.period_start_date|datefmt }}<br><small>→ {{ emp.period_end_date|datefmt }}</small></td>
|
|
<td class="num">{{ '%.2f'|format(emp.fte_pct|float) }}</td>
|
|
<td class="num">
|
|
{% if emp.verification_status == 'PENDING' %}—
|
|
{% elif emp.verification_status == 'RESPINTA' %}0.00
|
|
{% else %}{{ '%.2f'|format(fte_verif|float) }}
|
|
{% endif %}
|
|
</td>
|
|
<td><span class="status-inline status-{{ emp.verification_status }}">{{ emp.verification_status }}</span></td>
|
|
<td>{{ emp.verification_notes|default('—') }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endif %}
|
|
|
|
{# ============ DOCUMENTI ============ #}
|
|
<h2>Verifica documenti</h2>
|
|
{% if docs_required %}
|
|
<table class="data">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:25%">Documento</th>
|
|
<th style="width:20%">File allegato</th>
|
|
<th style="width:12%">Esito</th>
|
|
<th style="width:43%">Note istruttore</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for dr in docs_required %}
|
|
{% set doc = docs_by_code.get(dr.code, {}) %}
|
|
{% set stat = doc.verification_status or 'PENDING' %}
|
|
{% set cls = 'rejected' if stat in ('NON_VALIDO', 'SCADUTO') else '' %}
|
|
<tr class="{{ cls }}">
|
|
<td><strong>{{ dr.label }}</strong><br><small>{{ dr.code }}</small></td>
|
|
<td>
|
|
{% if doc.filename %}<i>{{ doc.filename }}</i>
|
|
{% else %}<span class="ko">non caricato</span>
|
|
{% endif %}
|
|
</td>
|
|
<td><span class="status-inline status-{{ stat }}">{{ stat }}</span></td>
|
|
<td>{{ doc.verification_notes|default('—') }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p class="text-secondary">Nessun documento richiesto dallo schema del bando.</p>
|
|
{% endif %}
|
|
|
|
{# ============ SOCCORSI ============ #}
|
|
{% if amendments %}
|
|
<h2>Soccorso istruttorio</h2>
|
|
{% for a in amendments %}
|
|
<div class="amend-box">
|
|
<div class="head">
|
|
<strong>{{ a.status|amendstatus }}</strong>
|
|
· deadline {{ a.deadline|datefmt }}
|
|
· aperto il {{ a.created_at|datetimefmt }}
|
|
{% if a.closed_at %} · chiuso il {{ a.closed_at|datetimefmt }}{% endif %}
|
|
</div>
|
|
<div class="req"><strong>Richiesta istruttore:</strong><br>{{ a.request_text }}</div>
|
|
{% if a.response_text %}
|
|
<div class="resp"><strong>Risposta beneficiario</strong> ({{ a.response_at|datetimefmt }}):<br>{{ a.response_text }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
{# ============ TOTALI ============ #}
|
|
<h2>Riepilogo finanziario</h2>
|
|
<div class="totals-summary">
|
|
<div class="row">
|
|
<div class="cell">
|
|
<div class="lbl">Totale dichiarato</div>
|
|
<div class="val">{{ totals.grand_total_declared|euro }}</div>
|
|
</div>
|
|
<div class="cell">
|
|
<div class="lbl">Totale ammesso</div>
|
|
<div class="val">{{ totals.grand_total_verified|euro }}</div>
|
|
</div>
|
|
<div class="cell">
|
|
<div class="lbl">Cap remissione</div>
|
|
<div class="val">{{ totals.max_remission|euro }}</div>
|
|
</div>
|
|
<div class="cell">
|
|
<div class="lbl">Remissione spettante</div>
|
|
<div class="val final">{{ totals.remission_due|euro }}</div>
|
|
</div>
|
|
</div>
|
|
{% if practice.status == 'APPROVED' %}
|
|
<div class="row">
|
|
<div class="cell" style="background: #f0fff4;">
|
|
<div class="lbl">Remissione approvata</div>
|
|
<div class="val" style="color: #22543d;">{{ practice.approved_remission|euro }}</div>
|
|
</div>
|
|
<div class="cell" style="background: #fff5f5;">
|
|
<div class="lbl">Residuo da restituire</div>
|
|
<div class="val residuo">{{ (practice.amount_erogato - (practice.approved_remission or 0))|euro }}</div>
|
|
</div>
|
|
<div class="cell" colspan="2"></div>
|
|
<div class="cell"></div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# ============ CHECKLIST + NOTE ============ #}
|
|
{% set checklist = practice.instructor_checklist or {} %}
|
|
{% if checklist %}
|
|
<h3>Checklist finale istruttore</h3>
|
|
<ul style="font-size:9pt; margin: 4pt 0 6pt 14pt;">
|
|
<li>Documentazione completa e coerente: {{ 'SÌ' if checklist.get('domanda_completa') else 'NO' }}</li>
|
|
<li>Incremento ULA > 1 verificato: {{ 'SÌ' if checklist.get('ula_ok') else 'NO' }}</li>
|
|
<li>Importo erogato entro il range bando: {{ 'SÌ' if checklist.get('erogato_in_range') else 'NO' }}</li>
|
|
</ul>
|
|
{% endif %}
|
|
|
|
{% if practice.instructor_final_notes %}
|
|
<h3>Note sintetiche di istruttoria</h3>
|
|
<div class="note-box">{{ practice.instructor_final_notes }}</div>
|
|
{% endif %}
|
|
|
|
{% if practice.rejection_reason %}
|
|
<h3>Motivazione del rigetto</h3>
|
|
<div class="note-box" style="background:#fff5f5; border-left-color:#fc8181;">{{ practice.rejection_reason }}</div>
|
|
{% endif %}
|
|
|
|
{# ============ FIRMA ============ #}
|
|
<div class="sign-block">
|
|
<div class="col">
|
|
<div class="lbl">LUOGO E DATA</div>
|
|
<div class="val">Perugia, {{ generated_at }}</div>
|
|
<div class="sig-line"> </div>
|
|
</div>
|
|
<div class="col">
|
|
<div class="lbl">ISTRUTTORE</div>
|
|
<div class="val">{{ instructor_name or '(firma)' }}</div>
|
|
<div class="sig-line">Firma</div>
|
|
</div>
|
|
</div>
|
|
|
|
</body>
|
|
</html>
|