feat(v2): verbale istruttoria PDF con tranche N/M + custom_checks + 5 voci Cecilia + storico
B6 routers/verbale.py:
- _build_context arricchito con custom_checks_merged (schema+values da RemissionCustomCheckValue)
- previous_tranches: elenco tranche APPROVED precedenti con cumulative progressivo
- max_tranches_snapshot letto dallo schema_snapshot.gate_rules.max_tranches
- filename include _t{sequence_number}.pdf
B6 templates_jinja/verbale_istruttoria.html:
- Header: 'Tranche N/M' + period_label dopo numero pratica
- Meta-grid: riga 'Tranche / fase' quando max_tranches > 1
- Nuova sezione 'Controlli aggiuntivi' (dopo verifica documenti):
tabella label, obbligatorio, dichiarato SI/NO, doc allegato SI/NO, validazione, note
- Sezione 'Storico tranches precedenti' (solo se sequence > 1):
tabella con cumulativo progressivo
- Box totali riscritto con **5 VOCI UFFICIALI CECILIA**:
(1) Importo massimo ammissibile (cap globale) + gia approvato tranche precedenti
(2) Richiesto pre-controllo = pre_check_admissible
(3) Ammesso post-controllo = remission_due
(4) Importo finanziamento erogato + tranches count/max
(5) Residuo da restituire = erogato - approvato_prec - ammesso
- Box 'REMISSIONE APPROVATA PER QUESTA TRANCHE' evidenziato quando APPROVED
Test E2E: verbale T1 APPROVED 29.3KB con tutte sezioni presenti.
Verbale T2 simulata con storico T1 e cap tranche 2 correttamente calcolato.
This commit is contained in:
@@ -16,7 +16,7 @@ from sqlalchemy import text
|
||||
|
||||
from ..db import get_db
|
||||
from ..auth import AuthUser, get_current_user
|
||||
from ..models import RemissionPractice
|
||||
from ..models import RemissionPractice, RemissionCustomCheckValue
|
||||
from .practices import _compute_gate_check
|
||||
|
||||
router = APIRouter(prefix="/api/remission-practices/instructor", tags=["verbale"])
|
||||
@@ -177,6 +177,50 @@ def _build_context(db: Session, practice: RemissionPractice, user: AuthUser) ->
|
||||
"""), {"uid": user.user_id}).scalar()
|
||||
instructor_name = row
|
||||
|
||||
# v2: custom_checks merged (schema_snapshot.custom_checks[] + RemissionCustomCheckValue)
|
||||
check_defs = practice.schema_snapshot.get("custom_checks") or []
|
||||
values_by_code = {v.check_code: v for v in practice.custom_checks}
|
||||
custom_checks_merged = []
|
||||
for d in check_defs:
|
||||
code = d.get("code")
|
||||
val = values_by_code.get(code)
|
||||
custom_checks_merged.append({
|
||||
"code": code,
|
||||
"label": d.get("label"),
|
||||
"description": d.get("description"),
|
||||
"requires_document": bool(d.get("requires_document")),
|
||||
"required": bool(d.get("required")),
|
||||
"beneficiary_declared": bool(val.beneficiary_declared) if val else False,
|
||||
"declared_at": val.declared_at if val else None,
|
||||
"has_document": bool(val and val.storage_path),
|
||||
"verification_status": (val.verification_status if val else "PENDING"),
|
||||
"verification_notes": (val.verification_notes if val else None),
|
||||
})
|
||||
|
||||
# v2: storico tranche precedenti APPROVED (se sequence > 1)
|
||||
previous_tranches = []
|
||||
cumulative_approved = 0.0
|
||||
if practice.sequence_number > 1:
|
||||
prevs = db.query(RemissionPractice).filter(
|
||||
RemissionPractice.application_id == practice.application_id,
|
||||
RemissionPractice.sequence_number < practice.sequence_number,
|
||||
RemissionPractice.status == "APPROVED",
|
||||
).order_by(RemissionPractice.sequence_number).all()
|
||||
for pv in prevs:
|
||||
amt = float(pv.approved_remission or 0)
|
||||
cumulative_approved += amt
|
||||
previous_tranches.append({
|
||||
"sequence_number": pv.sequence_number,
|
||||
"period_label": pv.period_label,
|
||||
"reviewed_at": pv.reviewed_at,
|
||||
"approved_remission": amt,
|
||||
"cumulative": cumulative_approved,
|
||||
})
|
||||
|
||||
# v2 max_tranches dallo schema_snapshot (o dal bando corrente, fallback 1)
|
||||
snap_rules = practice.schema_snapshot.get("gate_rules") or {}
|
||||
max_tranches_snapshot = int(snap_rules.get("max_tranches") or totals.get("tranches_max") or 1)
|
||||
|
||||
return {
|
||||
"practice": practice,
|
||||
"totals": totals,
|
||||
@@ -196,6 +240,10 @@ def _build_context(db: Session, practice: RemissionPractice, user: AuthUser) ->
|
||||
"company": company,
|
||||
"instructor_name": instructor_name,
|
||||
"generated_at": datetime.now().strftime("%d/%m/%Y"),
|
||||
# v2
|
||||
"custom_checks_merged": custom_checks_merged,
|
||||
"previous_tranches": previous_tranches,
|
||||
"max_tranches_snapshot": max_tranches_snapshot,
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +282,7 @@ def verbale_pdf(
|
||||
|
||||
practice, html = _render_html(db, practice_id, user)
|
||||
pdf_bytes = WeasyHTML(string=html).write_pdf()
|
||||
filename = f"verbale_istruttoria_pratica_{practice.application_id}.pdf"
|
||||
filename = f"verbale_istruttoria_pratica_{practice.application_id}_t{practice.sequence_number}.pdf"
|
||||
return Response(
|
||||
content=pdf_bytes,
|
||||
media_type="application/pdf",
|
||||
|
||||
Reference in New Issue
Block a user