From 61cdfbd06bb0916df26160bce56359d2a805ae30 Mon Sep 17 00:00:00 2001 From: BFLOWS Sandbox Date: Sat, 18 Apr 2026 11:03:15 +0200 Subject: [PATCH] feat(istruttoria UI): verifica riga-per-riga con thumbs up/down/occhio/download/rettifica MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replica il workflow del foglio Excel originale dell'istruttoria Gepafin. Pattern preso da DomandaEditPreInstructor/components/ListOfFiles. Pagina IstruttoriaPratica riscritta (858 righe): RIEPILOGO FINANZIARIO esteso: - Totale dichiarato (dal beneficiario) - Totale verificato (somma AMMESSA + PARZIALE istruttore) - Cap remissione (min(50% erogato, 12500)) - Remissione da riconoscere (da verificato) - Residuo da restituire (erogato - remissione) VERIFICA FATTURE per categoria con appPageSection__list: - Ogni fattura come row con numero, fornitore, date, descrizione, Tag stato - Tag rosso 'Date fuori periodo' se invoice_in_period=false O payment_in_period=false - Riga dichiarato + riga verificato (se presente) - Note rettifica evidenziate con barra arancione - 5 pulsanti icona: eye (anteprima PDF) + download + pencil (rettifica con dialog) + thumbs-up AMMESSA + thumbs-down RESPINTA - Thumbs up/down = ammissione/rifiuto rapido senza rettifica - Pencil = dialog con dichiarato readonly + verificato editabile + note obbligatorie -> PARZIALE VERIFICA ULA: - Stesso pattern: eye/download/pencil/up/down - Rettifica FTE (0-1) con note VERIFICA DOCUMENTI: - eye/download/thumbs-up VALIDO - clock SCADUTO (apre dialog con motivazione) - thumbs-down NON_VALIDO (apre dialog con motivazione) VERBALE ISTRUTTORIA finale (visibile in UNDER_REVIEW/AWAITING_AMENDMENT): - 3 checkbox: documentazione completa, ULA>1, erogato in range - Textarea note sintetiche con save onBlur Approva disabilitato finché tutte le righe hanno status != PENDING. Anteprima PDF: dialog con placeholder sandbox (file reale sarà in prod). Download: toast stub (in prod scarica dal storage). --- .../pages/BandoRendicontazioneSchemaEdit.js | 12 +- .../pages/IstruttoriaPratica.js | 673 ++++++++++++++---- .../service/rendicontazioneService.js | 35 + 3 files changed, 569 insertions(+), 151 deletions(-) diff --git a/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js b/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js index 3fd8546..19df8ed 100644 --- a/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js +++ b/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js @@ -61,6 +61,7 @@ const schemaJsonToForm = (j) => { return { amount_min: gate.amount_range?.min ?? 5000, amount_max: gate.amount_range?.max ?? 25000, + period_start: gate.period_start ? new Date(gate.period_start) : null, period_end: gate.period_end ? new Date(gate.period_end) : null, period_start_rule: gate.period_start_rule ?? 'erogato_date', iva_regimes_allowed: ivaAllowed, @@ -125,6 +126,7 @@ const formToSchemaJson = (f, base = null) => { cap_absolute: f.cap_absolute, iva_ordinario_imponibile_only: f.iva_ordinario_imponibile_only, period_start_rule: f.period_start_rule, + period_start: fmtDate(f.period_start), period_end: fmtDate(f.period_end), require_at_least_one_invoice_per_nonzero_category: f.require_invoice_per_category, require_ula_above_threshold: f.require_ula_above_threshold, @@ -333,13 +335,19 @@ const BandoRendicontazioneSchemaEdit = () => {
- + update({period_start_rule: e.value})} options={PERIOD_START_RULES} disabled={readOnly} />
- + + update({period_start: e.value})} + dateFormat="dd/mm/yy" showIcon disabled={readOnly} /> + {__("Usata dalla verifica date fatture. Compila se la regola non è 'data erogazione'.",'gepafin')} +
+
+ update({period_end: e.value})} dateFormat="dd/mm/yy" showIcon disabled={readOnly} />
diff --git a/src/modules/rendicontazione/pages/IstruttoriaPratica.js b/src/modules/rendicontazione/pages/IstruttoriaPratica.js index 648aab8..e9914c6 100644 --- a/src/modules/rendicontazione/pages/IstruttoriaPratica.js +++ b/src/modules/rendicontazione/pages/IstruttoriaPratica.js @@ -11,9 +11,9 @@ import { InputText } from 'primereact/inputtext'; import { InputNumber } from 'primereact/inputnumber'; import { InputTextarea } from 'primereact/inputtextarea'; import { Calendar } from 'primereact/calendar'; -import { DataTable } from 'primereact/datatable'; -import { Column } from 'primereact/column'; +import { Checkbox } from 'primereact/checkbox'; import { ConfirmPopup, confirmPopup } from 'primereact/confirmpopup'; +import { isNil } from 'ramda'; import RendicontazioneService from '../service/rendicontazioneService'; @@ -25,13 +25,27 @@ const CONTRACT_TYPES = { const PRACTICE_STATUS = { DRAFT: { severity: 'warning', label: 'Bozza beneficiario' }, - SUBMITTED: { severity: 'info', label: 'Inviata' }, - UNDER_REVIEW: { severity: 'warning', label: 'In valutazione' }, + SUBMITTED: { severity: 'info', label: 'Inviata — da prendere in carico' }, + UNDER_REVIEW: { severity: 'warning', label: 'In lavorazione' }, AWAITING_AMENDMENT: { severity: 'warning', label: 'Soccorso aperto' }, APPROVED: { severity: 'success', label: 'Approvata' }, REJECTED: { severity: 'danger', label: 'Respinta' } }; +const VERIFICATION_INVOICE_TAG = { + PENDING: { severity: 'secondary', label: 'Da verificare' }, + AMMESSA: { severity: 'success', label: 'Ammessa' }, + PARZIALE: { severity: 'warning', label: 'Parziale' }, + RESPINTA: { severity: 'danger', label: 'Respinta' } +}; + +const VERIFICATION_DOC_TAG = { + PENDING: { severity: 'secondary', label: 'Da verificare' }, + VALIDO: { severity: 'success', label: 'Valido' }, + NON_VALIDO: { severity: 'danger', label: 'Non valido' }, + SCADUTO: { severity: 'warning', label: 'Scaduto' } +}; + const AMENDMENT_STATUS = { AWAITING: { severity: 'warning', label: 'Attesa risposta' }, RESPONSE_RECEIVED: { severity: 'info', label: 'Risposta ricevuta' }, @@ -39,7 +53,7 @@ const AMENDMENT_STATUS = { EXPIRED: { severity: 'danger', label: 'Scaduta' } }; -const euro = (v) => '€ ' + Number(v || 0).toLocaleString('it-IT', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +const euro = (v) => v == null ? '—' : '€ ' + Number(v || 0).toLocaleString('it-IT', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const formatDate = (d) => d ? new Date(d).toLocaleDateString('it-IT') : '—'; const formatDateTime = (d) => d ? new Date(d).toLocaleString('it-IT') : '—'; @@ -50,7 +64,13 @@ const IstruttoriaPratica = () => { const toast = useRef(null); const [loading, setLoading] = useState(true); - const [bundle, setBundle] = useState(null); // {practice, gate_check, amendments} + const [bundle, setBundle] = useState(null); + + // dialoghi + const [previewDialog, setPreviewDialog] = useState({ visible: false, filename: null, title: null }); + const [invRectDialog, setInvRectDialog] = useState({ visible: false, invoice: null }); + const [ulaRectDialog, setUlaRectDialog] = useState({ visible: false, employee: null }); + const [docNoteDialog, setDocNoteDialog] = useState({ visible: false, doc: null, status: null }); const [approveDialog, setApproveDialog] = useState({ visible: false, amount: null }); const [rejectDialog, setRejectDialog] = useState({ visible: false, reason: '' }); @@ -86,12 +106,12 @@ const IstruttoriaPratica = () => { }, [sections]); const openAmendments = amendments.filter(a => a.status === 'AWAITING' || a.status === 'RESPONSE_RECEIVED'); - const isReviewable = practice && ['SUBMITTED', 'UNDER_REVIEW', 'AWAITING_AMENDMENT'].includes(practice.status); const isDecidable = practice && ['UNDER_REVIEW', 'AWAITING_AMENDMENT'].includes(practice.status); + const isVerifiable = practice && ['UNDER_REVIEW', 'AWAITING_AMENDMENT'].includes(practice.status); // ---------- actions ---------- - const afterOk = (msg) => () => { + const afterOk = (msg) => (resp) => { toast.current?.show({ severity: 'success', summary: msg }); load(); }; @@ -100,63 +120,144 @@ const IstruttoriaPratica = () => { detail: typeof err?.detail === 'object' ? JSON.stringify(err.detail) : err?.detail }); }; - const handleClaim = (e) => { + const openPreview = (filename, title) => setPreviewDialog({ visible: true, filename, title }); + const downloadStub = (filename) => { + toast.current?.show({ severity: 'info', summary: __('Sandbox', 'gepafin'), + detail: __(`Download di ${filename} — in produzione scarica il file reale dallo storage.`, 'gepafin') }); + }; + + // Quick verify (thumbs up/down) senza rettifica + const quickVerifyInvoice = (invoice, newStatus) => { + RendicontazioneService.verifyInvoice(practiceId, invoice.id, + { verification_status: newStatus, verification_notes: null }, + afterOk(__(`Fattura ${newStatus.toLowerCase()}`, 'gepafin')), onErr); + }; + const quickVerifyUla = (employee, newStatus) => { + RendicontazioneService.verifyUlaEmployee(practiceId, employee.id, + { verification_status: newStatus, verification_notes: null }, + afterOk(__(`Dipendente ${newStatus.toLowerCase()}`, 'gepafin')), onErr); + }; + const quickVerifyDoc = (doc, newStatus) => { + RendicontazioneService.verifyDocument(practiceId, doc.doc_code, + { verification_status: newStatus, verification_notes: null }, + afterOk(__(`Documento ${newStatus.toLowerCase()}`, 'gepafin')), onErr); + }; + + const saveInvoiceRect = () => { + const i = invRectDialog.invoice; + RendicontazioneService.verifyInvoice(practiceId, i.id, { + verification_status: 'PARZIALE', + taxable_verified: i.taxable_verified, + vat_verified: i.vat_verified, + total_verified: i.total_verified, + verification_notes: i.verification_notes + }, + (resp) => { setInvRectDialog({ visible: false, invoice: null }); afterOk(__('Rettifica salvata', 'gepafin'))(resp); }, + onErr); + }; + + const saveUlaRect = () => { + const e = ulaRectDialog.employee; + RendicontazioneService.verifyUlaEmployee(practiceId, e.id, { + verification_status: 'PARZIALE', + fte_pct_verified: e.fte_pct_verified, + verification_notes: e.verification_notes + }, + (resp) => { setUlaRectDialog({ visible: false, employee: null }); afterOk(__('Rettifica ULA salvata', 'gepafin'))(resp); }, + onErr); + }; + + const saveDocNote = () => { + const d = docNoteDialog.doc; + const status = docNoteDialog.status; + RendicontazioneService.verifyDocument(practiceId, d.doc_code, + { verification_status: status, verification_notes: d.verification_notes }, + (resp) => { setDocNoteDialog({ visible: false, doc: null, status: null }); afterOk(__('Documento aggiornato', 'gepafin'))(resp); }, + onErr); + }; + + // Final notes + checklist (debounced inline save) + const saveFinalNotes = (patch) => { + RendicontazioneService.setInstructorFinalNotes(practiceId, patch, afterOk(__('Verbale aggiornato', 'gepafin')), onErr); + }; + + // Claim / approve / reject / amendment / close amendment — come prima + const handleClaim = (ev) => { confirmPopup({ - target: e.currentTarget, - message: __('Prendere in carico la pratica? Lo stato passerà a In valutazione.', 'gepafin'), + target: ev.currentTarget, + message: __('Prendere in carico la pratica? Lo stato passerà a "In lavorazione".', 'gepafin'), icon: 'pi pi-info-circle', acceptLabel: __('Prendi in carico', 'gepafin'), rejectLabel: __('Annulla', 'gepafin'), accept: () => RendicontazioneService.claimPractice(practiceId, afterOk(__('Pratica presa in carico', 'gepafin')), onErr) }); }; - const doApprove = () => { const body = approveDialog.amount != null ? { approved_remission: approveDialog.amount } : {}; RendicontazioneService.approvePractice(practiceId, body, - (resp) => { setApproveDialog({ visible: false, amount: null }); afterOk(__('Pratica approvata', 'gepafin'))(resp); }, - onErr); + (resp) => { setApproveDialog({ visible: false, amount: null }); afterOk(__('Pratica approvata', 'gepafin'))(resp); }, onErr); }; - const doReject = () => { if (!rejectDialog.reason || rejectDialog.reason.trim().length < 10) { - toast.current?.show({ severity: 'warn', summary: __('Motivazione troppo corta', 'gepafin'), detail: __('Minimo 10 caratteri', 'gepafin') }); + toast.current?.show({ severity: 'warn', summary: __('Motivazione troppo corta (min 10 caratteri)', 'gepafin') }); return; } RendicontazioneService.rejectPractice(practiceId, rejectDialog.reason, - (resp) => { setRejectDialog({ visible: false, reason: '' }); afterOk(__('Pratica respinta', 'gepafin'))(resp); }, - onErr); + (resp) => { setRejectDialog({ visible: false, reason: '' }); afterOk(__('Pratica respinta', 'gepafin'))(resp); }, onErr); }; - const doAmend = () => { if (!amendDialog.text || amendDialog.text.trim().length < 10) { - toast.current?.show({ severity: 'warn', summary: __('Testo troppo corto', 'gepafin') }); - return; + toast.current?.show({ severity: 'warn', summary: __('Testo troppo corto', 'gepafin') }); return; } if (!amendDialog.deadline) { - toast.current?.show({ severity: 'warn', summary: __('Deadline obbligatoria', 'gepafin') }); - return; + toast.current?.show({ severity: 'warn', summary: __('Deadline obbligatoria', 'gepafin') }); return; } - const body = { - request_text: amendDialog.text, - deadline: typeof amendDialog.deadline === 'string' ? amendDialog.deadline : amendDialog.deadline.toISOString().slice(0, 10) - }; + const body = { request_text: amendDialog.text, + deadline: typeof amendDialog.deadline === 'string' ? amendDialog.deadline : amendDialog.deadline.toISOString().slice(0, 10) }; RendicontazioneService.createAmendment(practiceId, body, - (resp) => { setAmendDialog({ visible: false, text: '', deadline: null }); afterOk(__('Soccorso avviato', 'gepafin'))(resp); }, - onErr); + (resp) => { setAmendDialog({ visible: false, text: '', deadline: null }); afterOk(__('Soccorso avviato', 'gepafin'))(resp); }, onErr); }; - - const closeAmendment = (e, amendment) => { + const closeAmendment = (ev, a) => { confirmPopup({ - target: e.currentTarget, - message: __('Chiudi questa richiesta di soccorso? La pratica torna in valutazione.', 'gepafin'), + target: ev.currentTarget, + message: __('Chiudi questa richiesta di soccorso? La pratica torna in lavorazione.', 'gepafin'), icon: 'pi pi-info-circle', acceptLabel: __('Chiudi', 'gepafin'), rejectLabel: __('Annulla', 'gepafin'), - accept: () => RendicontazioneService.closeAmendment(practiceId, amendment.id, - afterOk(__('Soccorso chiuso', 'gepafin')), onErr) + accept: () => RendicontazioneService.closeAmendment(practiceId, a.id, afterOk(__('Soccorso chiuso', 'gepafin')), onErr) }); }; + // ---------- render helpers ---------- + const renderThumbsRow = (currentStatus, onUp, onDown, onPreview, onDownload, filename, extraButtons) => { + const isUp = currentStatus === 'AMMESSA' || currentStatus === 'VALIDO'; + const isDown = currentStatus === 'RESPINTA' || currentStatus === 'NON_VALIDO' || currentStatus === 'SCADUTO'; + return ( +
+
+ ); + }; + // ---------- render ---------- if (loading) { return
; @@ -167,20 +268,26 @@ const IstruttoriaPratica = () => { const statusCfg = PRACTICE_STATUS[practice.status] || { severity: 'secondary', label: practice.status }; const totals = gate?.totals || {}; - const perCat = totals.per_category || {}; + const perDecl = totals.per_category_declared || {}; + const perVerif = totals.per_category_verified || {}; const invoicesOfCat = (code) => practice.invoices.filter(i => i.category_code === code); + const allItemsVerified = practice.invoices.every(i => i.verification_status !== 'PENDING') && + practice.ula_employees.every(e => e.verification_status !== 'PENDING') && + practice.documents.every(d => d.verification_status !== 'PENDING'); + const checklist = practice.instructor_checklist || {}; return (
+ {/* HEADER */}
-

{__('Istruttoria pratica', 'gepafin')}

+

{__('Istruttoria pratica rendicontazione', 'gepafin')}

- {practice.schema_snapshot?.template_label || `Call #${practice.call_id}`} · {__('Pratica', 'gepafin')} #{practice.application_id} + {practice.schema_snapshot?.template_label || `Bando #${practice.call_id}`} · {__('Pratica', 'gepafin')} #{practice.application_id} @@ -204,6 +311,9 @@ const IstruttoriaPratica = () => { {isDecidable && (<>

+ + ); + })} + + )} +
+ ); + })}
- {/* ULA */} - {ulaSection.enabled && (<> + {/* ULA DIPENDENTI */} + {ulaSection.enabled && practice.ula_employees.length > 0 && (<>
-

{__('Dipendenti ULA', 'gepafin')}

- {practice.ula_employees.length > 0 ? ( - - - - CONTRACT_TYPES[r.contract_type] || r.contract_type} /> - - Number(r.fte_pct).toFixed(2)} /> - `${formatDate(r.period_start_date)} → ${formatDate(r.period_end_date)}`} /> - r.supporting_doc_filename - ? {r.supporting_doc_filename} - : } /> - - ) :

{__('Nessun dipendente caricato', 'gepafin')}

} +

{__('Verifica dipendenti ULA', 'gepafin')}

+
    + {practice.ula_employees.map(e => { + const cfg = VERIFICATION_INVOICE_TAG[e.verification_status] || VERIFICATION_INVOICE_TAG.PENDING; + return ( +
  1. +
    +
    +
    + {e.full_name} + ({e.codice_fiscale}) + +
    +
    + {CONTRACT_TYPES[e.contract_type] || e.contract_type} + {e.role_description && ` · ${e.role_description}`} + {' · '}{formatDate(e.period_start_date)} → {formatDate(e.period_end_date)} +
    +
    + {__('FTE dichiarato:', 'gepafin')} {Number(e.fte_pct).toFixed(2)} + {e.fte_pct_verified != null && ( + + {__('FTE verificato:', 'gepafin')} {Number(e.fte_pct_verified).toFixed(2)} + + )} +
    + {e.verification_notes && ( +
    + {e.verification_notes} +
    + )} +
    + {renderThumbsRow( + e.verification_status, + () => quickVerifyUla(e, 'AMMESSA'), + () => quickVerifyUla(e, 'RESPINTA'), + () => openPreview(e.supporting_doc_filename, `${__('Documento ULA', 'gepafin')} — ${e.full_name}`), + () => downloadStub(e.supporting_doc_filename), + e.supporting_doc_filename, +
    +
  2. + ); + })} +
)} -
- {/* DOCUMENTI */} +
-

{__('Documenti', 'gepafin')}

-
+

{__('Verifica documenti', 'gepafin')}

+
    {docsRequired.map(dr => { - const existing = practice.documents.find(d => d.doc_code === dr.code); + const doc = practice.documents.find(d => d.doc_code === dr.code) || { doc_code: dr.code }; + const cfg = VERIFICATION_DOC_TAG[doc.verification_status || 'PENDING']; return ( -
    - -
    - {dr.label} -
    {dr.code}
    +
  1. +
    +
    +
    + {dr.label} + {dr.code} + {doc.filename ? : } +
    + {doc.filename && ( +
    + {doc.filename} · {__('caricato il', 'gepafin')} {formatDateTime(doc.uploaded_at)} +
    + )} + {doc.verification_notes && ( +
    + {doc.verification_notes} +
    + )} +
    + {renderThumbsRow( + doc.verification_status, + () => quickVerifyDoc(doc, 'VALIDO'), + () => setDocNoteDialog({ visible: true, doc: { ...doc, verification_notes: doc.verification_notes || '' }, status: 'NON_VALIDO' }), + () => openPreview(doc.filename, `${dr.label} — ${doc.filename || ''}`), + () => downloadStub(doc.filename), + doc.filename, +
    -
    - {existing?.filename - ? {existing.filename} — {__('caricato il', 'gepafin')} {formatDateTime(existing.uploaded_at)} - : {__('Non caricato', 'gepafin')}} -
    -
  2. + ); })} -
    +
- {/* ---------- DIALOG APPROVA ---------- */} + {/* VERBALE ISTRUTTORIA */} + {isVerifiable && (<> +
+
+

{__('Verbale istruttoria', 'gepafin')}

+ +
+

{__('Checklist finale', 'gepafin')}

+
+ {[ + { id: 'domanda_completa', label: __('Documentazione completa e coerente', 'gepafin') }, + { id: 'ula_ok', label: __('Incremento ULA > 1 verificato', 'gepafin') }, + { id: 'erogato_in_range', label: __('Importo erogato entro il range bando', 'gepafin') } + ].map(item => ( +
+ saveFinalNotes({ + instructor_checklist: { ...checklist, [item.id]: e.checked } + })} /> + +
+ ))} +
+
+ +
+
+ + { + if (e.target.value !== (practice.instructor_final_notes || '')) { + saveFinalNotes({ instructor_final_notes: e.target.value }); + } + }} + placeholder={__('Note di sintesi che saranno incluse nel verbale finale...', 'gepafin')} /> + {__('Le note si salvano quando esci dal campo.', 'gepafin')} +
+
+
+ )} + + {/* ============ DIALOGS ============ */} + + {/* Preview PDF */} + setPreviewDialog({ visible: false, filename: null, title: null })}> +
+ +

{previewDialog.filename || '—'}

+

+ {__('Anteprima PDF — in sandbox il file reale non è caricato. In produzione qui viene visualizzato il PDF originale della fattura/documento.', 'gepafin')} +

+
+
+
+
+ + {/* Rettifica fattura */} + setInvRectDialog({ visible: false, invoice: null })}> + {invRectDialog.invoice && ( +
{ e.preventDefault(); saveInvoiceRect(); }}> +
+ {invRectDialog.invoice.invoice_number} — {invRectDialog.invoice.supplier_name}
+ {invRectDialog.invoice.description} +
+

{__('Dichiarato dal beneficiario', 'gepafin')}

+
+
+ + +
+
+ + +
+
+ + +
+
+

{__('Valori verificati (rettifica)', 'gepafin')}

+
+
+ + setInvRectDialog(d => ({ ...d, invoice: { ...d.invoice, taxable_verified: e.value } }))} /> +
+
+ + setInvRectDialog(d => ({ ...d, invoice: { ...d.invoice, vat_verified: e.value } }))} /> +
+
+ + setInvRectDialog(d => ({ ...d, invoice: { ...d.invoice, total_verified: e.value } }))} /> +
+
+
+ + setInvRectDialog(d => ({ ...d, invoice: { ...d.invoice, verification_notes: e.target.value } }))} + placeholder={__('Esempio: decurtata quota di euro X per voce non ammissibile (assicurazione)...', 'gepafin')} /> +
+
+
+
+ )} +
+ + {/* Rettifica ULA */} + setUlaRectDialog({ visible: false, employee: null })}> + {ulaRectDialog.employee && ( +
{ e.preventDefault(); saveUlaRect(); }}> +
+ {ulaRectDialog.employee.full_name} ({ulaRectDialog.employee.codice_fiscale})
+ FTE dichiarato: {Number(ulaRectDialog.employee.fte_pct).toFixed(2)} +
+
+ + setUlaRectDialog(d => ({ ...d, employee: { ...d.employee, fte_pct_verified: e.value } }))} /> +
+
+ + setUlaRectDialog(d => ({ ...d, employee: { ...d.employee, verification_notes: e.target.value } }))} + placeholder={__('Esempio: dipendente in part-time verificato al 60% non 100%...', 'gepafin')} /> +
+
+
+
+ )} +
+ + {/* Note documento (scaduto/non valido) */} + setDocNoteDialog({ visible: false, doc: null, status: null })}> + {docNoteDialog.doc && ( +
{ e.preventDefault(); saveDocNote(); }}> +
+ + setDocNoteDialog(d => ({ ...d, doc: { ...d.doc, verification_notes: e.target.value } }))} + placeholder={docNoteDialog.status === 'SCADUTO' + ? __('Esempio: DURC scaduto il 15/10/2021, non valido al momento della rendicontazione...', 'gepafin') + : __('Esempio: visura camerale non corrisponde alla ragione sociale del beneficiario...', 'gepafin')} /> +
+
+
+
+ )} +
+ + {/* DIALOG APPROVA */} setApproveDialog({ visible: false, amount: null })}> @@ -425,7 +801,7 @@ const IstruttoriaPratica = () => { setApproveDialog(d => ({ ...d, amount: e.value }))} /> - {__('Valore calcolato:', 'gepafin')} {euro(totals.remission_due || 0)}. {__('Puoi modificarlo se necessario.', 'gepafin')} + {__('Valore calcolato da verificati:', 'gepafin')} {euro(totals.remission_due)}
@@ -435,7 +811,7 @@ const IstruttoriaPratica = () => { - {/* ---------- DIALOG RESPINGI ---------- */} + {/* DIALOG RESPINGI */} setRejectDialog({ visible: false, reason: '' })}> @@ -453,7 +829,7 @@ const IstruttoriaPratica = () => { - {/* ---------- DIALOG SOCCORSO ---------- */} + {/* DIALOG SOCCORSO */} setAmendDialog({ visible: false, text: '', deadline: null })}> @@ -463,7 +839,6 @@ const IstruttoriaPratica = () => { setAmendDialog(d => ({ ...d, text: e.target.value }))} placeholder={__('Descrivi le integrazioni richieste...', 'gepafin')} /> - {__('Sarà visibile al beneficiario, che potrà rispondere integrando la documentazione.', 'gepafin')}
diff --git a/src/modules/rendicontazione/service/rendicontazioneService.js b/src/modules/rendicontazione/service/rendicontazioneService.js index 7eefc96..23865cc 100644 --- a/src/modules/rendicontazione/service/rendicontazioneService.js +++ b/src/modules/rendicontazione/service/rendicontazioneService.js @@ -238,3 +238,38 @@ const extendInstructor = { }; Object.assign(RendicontazioneService, extendInstructor); + + +// ====================== VERIFICA SINGOLA RIGA ISTRUTTORE ====================== + +const extendVerify = { + verifyInvoice(practiceId, invoiceId, body, onSuccess, onError) { + fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/invoices/${invoiceId}/verify`, { + method: 'PUT', mode: 'cors', headers: buildHeaders(), + body: JSON.stringify(body) + }).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError)); + }, + + verifyUlaEmployee(practiceId, empId, body, onSuccess, onError) { + fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/ula-employees/${empId}/verify`, { + method: 'PUT', mode: 'cors', headers: buildHeaders(), + body: JSON.stringify(body) + }).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError)); + }, + + verifyDocument(practiceId, docCode, body, onSuccess, onError) { + fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/documents/${docCode}/verify`, { + method: 'PUT', mode: 'cors', headers: buildHeaders(), + body: JSON.stringify(body) + }).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError)); + }, + + setInstructorFinalNotes(practiceId, body, onSuccess, onError) { + fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/final-notes`, { + method: 'PUT', mode: 'cors', headers: buildHeaders(), + body: JSON.stringify(body) + }).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError)); + } +}; + +Object.assign(RendicontazioneService, extendVerify);