3 Commits

Author SHA1 Message Date
BFLOWS
83bb0a29ec feat(auth): autorizza ROLE_CONFIDI come proprietario pratica (parallelo BENEFICIARY)
Risoluzione 403 segnalato da Rinaldo Bonazzo su upload fattura con utente
ROLE_CONFIDI (confidi4@test.test). Pattern allineato al BE Gepafin che
in DashboardDao, CompanyDocumentDao e FaqDao raggruppa BENEFICIARY+CONFIDI
con stessi diritti operativi sulla pratica.

==RAZIONALE==
Sui bandi con call.confidi=true il confidi sottomette la application
per conto dell'azienda e diventa user_id della application. Lato
microservizio rendicontazione la pratica viene ereditata con stesso
user_id, quindi il confidi e proprietario della pratica e deve poter
fare upload/download/delete come il beneficiario.

==MODIFICHE==

app/auth.py:
- Aggiunto AuthUser.is_confidi() — controlla ROLE_CONFIDI
- Aggiunto AuthUser.is_owner_role() — True per BENEFICIARY o CONFIDI
- Aggiornato docstring header con ROLE_CONFIDI
- Manteno is_beneficiary() per backward compat (non rimosso, non chiamato)

Sostituzione is_beneficiary() -> is_owner_role() in 11 punti dove la
semantica era 'proprietario pratica':
- app/routers/files.py: 3 (_can_upload, _can_download, _can_delete)
- app/routers/instructor.py: 2 (respond-beneficiary, ack-amendment)
- app/routers/practices.py: 3 (visibilita, create, schema gating)
- app/routers/custom_checks.py: 3 (declared, gate)

==COMPORTAMENTO==

Per ROLE_CONFIDI vale ora la stessa regola di BENEFICIARY:
- upload/download/delete: solo se practice.user_id == user.user_id
  AND practice.status IN ('DRAFT','AWAITING_AMENDMENT')
- respond-beneficiary: solo se proprietario pratica
- visualizzazione: solo proprie pratiche
- creazione: solo se schema PUBLISHED

Confidi su pratica di altri o su pratica non editabile -> 403 come prima.

==TEST E2E (4 step verdi)==
/tmp/test_confidi_upload.py:
1. CONFIDI proprietario DRAFT upload Invoice_zapier2024.pdf -> 200 (era 403)
2. CONFIDI NON proprietario -> 403 (scoping)
3. CONFIDI proprietario ma SUBMITTED -> 403 (stato)
4. BENEFICIARY proprietario DRAFT (regressione) -> 200
2026-04-27 09:06:10 +02:00
BFLOWS
7c8de6aec8 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).
2026-04-20 18:47:03 +02:00
BFLOWS
9a0a401ffa feat(files): upload/preview/delete allegati su fatture, ULA, documenti
- models: colonne file inline (storage_path, mime, size_bytes, sha256, uploaded_by, uploaded_at)
  su remission_invoice, remission_ula_employee, remission_document
- migrations: ALTER idempotente al lifespan per evolvere schema in sandbox
- storage: FS adapter /var/uploads con validazione MIME/size, dedup sha256, sanitize
- routers/files: POST upload / GET download (con ?inline=1) / DELETE
  matrix autorizzazioni: beneficiary su DRAFT|AWAITING_AMENDMENT, istruttore read-only, superadmin full
- main: include router files, version bump 0.2.0

Testato E2E con admin JWT: upload 549B PDF -> DB coerente, storage 1/invoice/<uuid>/<sha12>-file.pdf,
download con magic bytes PDF corretti, delete chirurgico con cleanup FS e metadata.
2026-04-18 16:54:24 +02:00