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
248 B
248 B