feat(docs): link documenti dal repository company + gate submit su EXPIRED

Implementa il riutilizzo dei documenti caricati in fase domanda (gepafin_schema.company_document):
il benef puo selezionarli dal picker repository invece di caricarli dal PC, ereditando
filename/expires_at/storage_path. Tracciato via source_company_document_id per lookup
live dello stato (VALID/DUE/EXPIRED).

Modifiche:
- migrations.py: ALTER TABLE remission_document ADD source_company_document_id + index partial
- models.py: aggiunto campo source_company_document_id su RemissionDocument
- schemas.py: esposto source_company_document_id in DocumentUpsert + DocumentOut
- routers/files.py: nuovo POST /document/{id}/link-from-repository — verifica ownership
  company, pulisce file PC precedente, copia metadati dal sorgente, ritorna source_status
- routers/practices.py: nuovo check documents_not_expired in _compute_gate_check —
  JOIN live su gepafin_schema.company_document.status per doc linkati, controllo expires_at
  per upload diretti. Gate hard: documento EXPIRED blocca submit (422).

Test E2E verificati via curl/JWT offline:
- link VALID → metadati copiati, gate passed
- link EXPIRED → gate overall FAIL con detail 'Scaduti: DURC'
- re-link VALID → gate torna passed
- submit bloccato solo su check non-doc (fatture/altri doc mancanti), docs_not_expired OK

Seed sandbox: 4 document_category + 5 company_document su NAPOLI SAS (3 VALID / 1 DUE / 1 EXPIRED).
This commit is contained in:
BFLOWS
2026-04-20 18:47:03 +02:00
parent a3f863ecdb
commit 7c8de6aec8
5 changed files with 138 additions and 0 deletions

View File

@@ -216,6 +216,30 @@ def _compute_gate_check(db: Session, practice: RemissionPractice) -> GateCheckRe
"detail": f"Mancanti: {', '.join(missing_docs)}" if missing_docs else "Tutti presenti"
})
# Check 5b: documenti non scaduti (gate hard su EXPIRED)
# 2026-04-20: documento EXPIRED blocca la submit. Status letto live via JOIN sul
# BE Gepafin per doc collegati dal repository; per upload diretto PC controlla expires_at.
from datetime import date as _date_today_cls
today = _date_today_cls.today()
expired_docs = []
for doc in practice.documents:
if doc.source_company_document_id:
cd_status = db.execute(text("""
SELECT status FROM gepafin_schema.company_document
WHERE id = :cid AND is_deleted = false
"""), {"cid": doc.source_company_document_id}).scalar()
if cd_status == 'EXPIRED':
expired_docs.append(doc.doc_code)
elif doc.expires_at is not None and doc.expires_at < today:
expired_docs.append(doc.doc_code)
checks.append({
"id": "documents_not_expired",
"label": "Nessun documento scaduto",
"passed": len(expired_docs) == 0,
"detail": f"Scaduti: {', '.join(expired_docs)}" if expired_docs else "Tutti validi"
})
# Check 6: importo range (cap erogato)
amt_range = rules.get("amount_range", {})
min_e = Decimal(str(amt_range.get("min", 0)))