Commit Graph

7 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
34c4a47a1c feat(amendment): ROUND 2 — scheduler + upload documenti
Seconda parte della replica soccorso istruttorio speculare al BE Gepafin.
Completata: scheduler cron (expire + reminder), upload documenti istruttore
e benef, fix duplicati config.

==SCHEDULER (app/scheduler.py NUOVO)==
APScheduler BackgroundScheduler integrato nel lifespan FastAPI.
Due cron attivi (timezone Europe/Rome):

  expire_amendments() - cron 01:05 ogni notte
    Speculare a ApplicationAmendmentScheduler.processAmendmentExpirationScheduler.
    Trova amendment AWAITING con deadline < today, passa a EXPIRED.
    Rimette pratica a UNDER_REVIEW se non ha altri amendment aperti.
    Ritorna dict stats per logging/test.

  queue_reminders() - cron 09:00 ogni mattina
    Speculare a ExpirationScheduler.processAmendmentExpiration (data-driven).
    Legge remission_expiration_config (type='AMENDMENT', interval_days=N),
    per ogni riga trova amendment con deadline esattamente today+N e setta
    pec_retry_after (marker che il BE vede via /internal pending-reminder).
    Multipli row = multipli reminder (seed: 7gg + 2gg).

Il microservizio aggiorna solo stato DB. L invio effettivo di email
reminder lo fa il BE Gepafin tramite polling, tenant-aware.

==UPLOAD DOCUMENTI==
3 nuovi endpoint nel router istruttoria:

  POST   /instructor/{pid}/amendment/{aid}/upload-document
    - Istruttore allega PDF al soccorso (motivazione, scheda tecnica).
    - Consentito in DRAFT o AWAITING. Sostituisce precedente se esiste.
    - Popola amendment_document_path + amendment_document_type.

  DELETE /instructor/{pid}/amendment/{aid}/upload-document
    - Rimuove allegato (solo in DRAFT).

  POST   /instructor/{pid}/amendment/{aid}/upload-response-document
    - Benef allega PDF come supporto alla risposta.
    - Consentito in AWAITING/RESPONSE_RECEIVED, solo proprietario.
    - Popola response_document_path + response_document_type.

Riusa save_upload() esistente con entity_type dedicati.

==FIX storage.py==
Whitelist entity_type estesa con 'amendment-instructor-doc' +
'amendment-response-doc' (prima accettava solo invoice/ula/document,
bloccando l'upload con StorageError).

==FIX migration dedup==
Scoperto in test: migration 8 faceva INSERT ON CONFLICT DO NOTHING su
remission_expiration_config ma senza UNIQUE constraint. Ogni restart
inseriva duplicati (16 righe in DB invece di 2). Fix in migration 9:
DELETE duplicati + ADD UNIQUE (type, interval_days) + re-seed pulito.

==REQUIREMENTS==
APScheduler==3.10.4

==TEST E2E==
/tmp/test_amendment_r2_fixed.py passa su tutto:
  [A] upload amendment_document istruttore + response_document benef + respond
  [B] amendment scaduto artificiale -> expire_amendments lo marca EXPIRED,
      pratica torna UNDER_REVIEW
  [C] amendment a +2gg e +7gg -> queue_reminders accoda 2 reminder,
      /internal pending-reminder li espone entrambi
2026-04-20 22:35:01 +02:00
BFLOWS
da13ca7478 feat(amendment): soccorso istruttorio v3 — base dati + endpoint CRUD + internal BE
ROUND 1 della replica soccorso istruttorio speculare al BE Gepafin
bflows-bandi-be. Pacchetto base pronto, mancano scheduler/upload/email/FE
che vengono in round successivi.

==ARCHITETTURA DECISA CON CARLO==
- multi-tenancy lato BE: microservizio resta tenant-agnostic
- BE (bflows-bandi-be) fa polling sul nostro /internal e invia PEC/protocollo
  tenant-aware (hub=1 Gepafin PEC_SERVICE, hub=2 SviluppUmbria MAILGUN_SERVICE)
- microservizio NON fa PEC ne protocollo, NON conosce hub_id
- endpoint interni autenticati via shared secret X-Internal-Secret

==MIGRATION DB (2)==
mig 7: ALTER TABLE remission_amendment_request ADD
  response_days, extended_days, extension_date, internal_note,
  amendment_document_path/type, amendment_initial_document_path,
  response_document_path/type, protocol_id, email_log_id, user_action_id,
  pec_sent_at, pec_failed_reason, pec_retry_after
  + 2 index partial (status pec-pending, deadline scadenti)

mig 8: nuova tabella remission_expiration_config (type, interval_days,
  is_deleted) per reminder data-driven speculare a expiration_config BE.
  Seeded con (AMENDMENT, 7) e (AMENDMENT, 2).

==MODELLI==
- RemissionAmendmentRequest esteso con 13 colonne nuove
- RemissionExpirationConfig nuovo

==SCHEMAS==
- AmendmentStatus enum (DRAFT, AWAITING, RESPONSE_RECEIVED, EXPIRED, CLOSED)
- AmendmentRequestCreate esteso (response_days, internal_note)
- AmendmentRequestUpdate nuovo (solo DRAFT)
- AmendmentExtend nuovo (proroga)
- AmendmentPendingPecOut, AmendmentPecDetail (per BE polling)
- MarkPecSent, MarkPecFailed (callback BE)

==ENDPOINT ISTRUTTORE (estesi o nuovi)==
- POST /{pid}/amendment              crea DRAFT (modifica: non piu AWAITING diretto)
- PUT  /{pid}/amendment/{id}         modifica solo DRAFT [NUOVO]
- DELETE /{pid}/amendment/{id}       elimina solo DRAFT [NUOVO]
- POST /{pid}/amendment/{id}/send    DRAFT -> AWAITING [NUOVO]
- POST /{pid}/amendment/{id}/extend  proroga deadline [NUOVO]
- POST /{pid}/amendment/{id}/reminder reminder manuale (flag pec_retry_after) [NUOVO]
- POST /{pid}/amendment/{id}/close   chiude (AmendmentStatus enum al posto di stringhe)
- POST /{pid}/amendment/{id}/respond-beneficiary  benef risponde

==ENDPOINT INTERNI /internal/remission-amendments (nuovi)==
- GET     ?status=pending-pec|pending-reminder&since=
- GET     /{id}                        detail per composizione PEC
- POST    /{id}/mark-pec-sent          callback BE success
- POST    /{id}/mark-pec-failed        callback BE failure
Auth: X-Internal-Secret header, 401 altrimenti.

==CONFIG==
RENDIC_INTERNAL_SECRET env var (default sandbox hard-coded).

==TEST E2E==
/tmp/test_amendment_v3.py - 10 step tutti verdi:
  A reset T2 UNDER_REVIEW
  B create DRAFT (response_days=15 default)
  C update DRAFT (response_days=20, internal_note)
  D send DRAFT->AWAITING, pratica AWAITING_AMENDMENT
  E BE poll pending-pec vede amendment
  F BE detail+mark-pec-sent salva protocol_id/email_log_id/user_action_id
  G dopo mark-pec-sent scompare da pending-pec
  H benef respond -> RESPONSE_RECEIVED
  I istruttore close -> CLOSED, pratica torna UNDER_REVIEW
  AUTH internal senza secret -> 401

==NEXT (non in questo commit)==
- scheduler APScheduler cron 01:00 EXPIRED + cron 09:00 reminder
- upload amendment_document (istruttore) + response_document (benef) via files router
- template email locali non-PEC (reminder istruttore, notifica chiusura)
- UI istruttore: lista amendment + form crea/invia + proroga + reminder manuale
- UI benef: vista amendment + risposta con upload
2026-04-20 22:22:37 +02:00
BFLOWS
8950633481 fix(instructor): pratiche SUBMITTED pre-assegnate ora compaiono in coda
Il filtro coda istruttore escludeva le pratiche in stato SUBMITTED
gia assegnate all'istruttore stesso. Risultato: una pratica appena
inviata dal benef a un istruttore preferenziale (suggested_instructor
letto da gepafin_schema.assigned_applications) cadeva nel limbo:
- non 'pool unassigned' perche ha assigned_instructor_id != NULL
- non 'UNDER_REVIEW assegnata a me' perche era ancora SUBMITTED

Aggiunta clausola: SUBMITTED AND assigned_instructor_id == user.user_id
nell'elenco degli stati visibili.

Segnalazione Carlo: istruttore loggato vede 'Nessuna pratica in coda'
anche se T2 e stata assegnata a lui.
2026-04-18 19:27:01 +02:00
BFLOWS
25215f388b feat(v2): multi-tranche DB schema + gate cumulativo 5 voci Cecilia
A1 migrations.py:
- remission_practice DROP uq_application + ADD sequence_number/period_label/suggested_instructor_id
- UNIQUE composita (application_id, sequence_number)
- partial index idx_remission_practice_unassigned su assigned_instructor_id NULL
- nuova tabella remission_custom_check_value (storage_path/mime/size/sha256 allineata adapter)

A2 models.py + templates.py:
- RemissionPractice: UniqueConstraint composita, campi multi-tranche, relationship custom_checks
- classe RemissionCustomCheckValue
- RESTART_TEMPLATE schema_version=2, max_tranches=2, custom_checks esempio
  (antiriciclaggio required no-doc, polizza_fidejussoria optional con-doc)
- upgrade_schema_to_v2 idempotente per snapshot v1 esistenti

A3 _compute_gate_check(db, practice) CUMULATIVO:
- max_remission_global = min(cap_pct * erogato, cap_abs)
- already_approved = func.sum(approved_remission) su tranche APPROVED precedenti
  dello stesso application_id con sequence_number < corrente
- max_remission_this_tranche = max(0, global - already_approved)
- pre_check_admissible = min(grand_total_declared, this_tranche)  [voce 2 Cecilia]
- remission_due = min(effective_total, this_tranche)
- residuo_da_restituire = erogato - already_approved - remission_due (cumulativo)
- output totals esteso: sequence_number, tranches_count, tranches_max
- signature (db, practice) - aggiornati 6 call site in practices/instructor/verbale

Test su NAPOLI SAS: erogato 17K, cap 8500, tranche 1 approvata 467.14EUR,
tranche 2 vuota -> residuo disponibile 8032.86EUR, residuo_da_restituire 16532.86EUR.
2026-04-18 17:35:56 +02:00
BFLOWS
f9f543b008 feat(istruttoria): verifica riga-per-riga con dual declared/verified
Replica il workflow del foglio Excel originale (REMISSIONE_DEL_DEBITO_5888.xlsm).
Istruttore ora verifica ogni fattura, ogni dipendente ULA, ogni documento singolarmente
invece di accettare/respingere la pratica intera.

Modello dati - nuove colonne su 3 tabelle:
- remission_invoice: taxable_verified, vat_verified, total_verified,
  verification_status (PENDING/AMMESSA/PARZIALE/RESPINTA), verification_notes,
  date_checks (JSONB con invoice_in_period/payment_in_period), verified_by, verified_at
- remission_ula_employee: fte_pct_verified, verification_status, verification_notes,
  verified_by, verified_at
- remission_document: verification_status (PENDING/VALIDO/NON_VALIDO/SCADUTO),
  verification_notes, verified_by, verified_at
- remission_practice: instructor_final_notes, instructor_checklist (JSONB 3 gate SI/NO),
  verbale_date

Nuovi endpoint:
- PUT /instructor/{id}/invoices/{inv_id}/verify (status + rettifica importi + note)
- PUT /instructor/{id}/ula-employees/{emp_id}/verify (rettifica FTE + note)
- PUT /instructor/{id}/documents/{doc_code}/verify (VALIDO/NON_VALIDO/SCADUTO + note)
- PUT /instructor/{id}/final-notes (note sintetiche + checklist)

Ricalcolo gate_check dual track:
- grand_total_declared: sempre (importo richiesto dal beneficiario)
- grand_total_verified: somma solo fatture AMMESSA/PARZIALE (se PARZIALE usa verified)
- remission_due: usa verified se any_verified=True, altrimenti declared (backward compat)
- residuo_da_restituire: amount_erogato - remission_due
- flag any_verified e all_verified per gating decisione finale

_auto_check_dates: fattura in periodo? pagamento in periodo?
Legge period_start e period_end da schema.gate_rules (superadmin editor).

Template: aggiunto period_start/period_end_date come campi 'editable_by superadmin'
nella sezione general static_fields.

Schema editor FE (BandoRendicontazioneSchemaEdit): aggiunto Calendar period_start
accanto a period_end in section gate rules. period_start_rule dropdown per logica
(erogato_date|fixed) resta; period_start data fissa usata dal check.
2026-04-18 11:03:15 +02:00
BFLOWS
26fbc03871 feat: endpoint istruttore (queue + review + soccorso istruttorio)
- 4 nuove colonne su remission_practice: assigned_instructor_id, reviewed_at,
  reviewed_by, rejection_reason, approved_remission
- Nuova tabella remission_amendment_request con cascade delete, scope JSONB,
  stati AWAITING -> RESPONSE_RECEIVED -> CLOSED / EXPIRED / REJECTED
- Router instructor.py (287 righe) con 8 endpoint:
  /queue, /{id}, /{id}/claim, /{id}/approve, /{id}/reject,
  /{id}/amendment, /{id}/amendment/{aid}/close,
  /{id}/amendment/{aid}/respond-beneficiary
- GET /{id} (router practices) ora include amendments nel payload
- Manager manager_view flag per ROLE_INSTRUCTOR_MANAGER + SUPER_ADMIN
  (vede tutto il pool vs solo le proprie assegnazioni)
- Logica status transitions verificata:
  SUBMITTED -> UNDER_REVIEW (claim)
  UNDER_REVIEW <-> AWAITING_AMENDMENT (amendment open/close)
  UNDER_REVIEW | AWAITING_AMENDMENT -> APPROVED | REJECTED
- _compute_gate_check riusato anche dal router istruttore per calcolo
  remission_due in coda e nel dettaglio

Test end-to-end verde: ciclo completo benef -> istruttore -> soccorso ->
risposta -> chiusura -> approvazione funzionante su NAPOLI SAS.
2026-04-18 10:15:32 +02:00