Files
bflows-bandi-fe/src/modules/rendicontazione/service/rendicontazioneService.js
BFLOWS Sandbox 61cdfbd06b feat(istruttoria UI): verifica riga-per-riga con thumbs up/down/occhio/download/rettifica
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).
2026-04-18 11:03:15 +02:00

276 lines
12 KiB
JavaScript

/**
* Client HTTP per rendicontazione-api (microservizio BFLOWS).
* Usa fetch nativa come NetworkService. Il microservizio valida lo stesso JWT di GEPAFIN-BE.
*
* Env var: REACT_APP_RENDICONTAZIONE_API_URL (es. http://78.46.41.91:18090)
*/
import { storeGet } from '../../../store';
const BASE_URL = process.env.REACT_APP_RENDICONTAZIONE_API_URL || '';
const buildHeaders = () => {
const token = storeGet('getToken');
const h = { 'Content-Type': 'application/json' };
if (token) {
h['Authorization'] = `Bearer ${token}`;
}
return h;
};
const handleResponse = async (response, onSuccess, onError) => {
let body = null;
try {
body = await response.json();
} catch (e) {
body = { detail: response.statusText };
}
if (response.status >= 200 && response.status < 300) {
if (onSuccess) onSuccess(body);
} else {
if (onError) onError({ status: response.status, ...body });
}
};
const handleError = (err, onError) => {
if (onError) onError({ status: 0, detail: err.message });
};
const RendicontazioneService = {
getSchemaByCallId(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
initializeRestartTemplate(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}/initialize-restart`, {
method: 'POST', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
updateSchema(callId, schemaJson, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}`, {
method: 'PUT', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify({ schema_json: schemaJson })
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
publishSchema(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}/publish`, {
method: 'POST', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
deleteSchema(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}`, {
method: 'DELETE', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
getRestartTemplatePreview(onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/templates/restart`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
}
};
export default RendicontazioneService;
// ====================== PRATICHE BENEFICIARIO ======================
const extendPractice = {
listMine(onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/mine`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
startPractice(applicationId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/start`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify({ application_id: applicationId })
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
getPractice(practiceId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
updatePractice(practiceId, patch, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}`, {
method: 'PUT', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify(patch)
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
addInvoice(practiceId, invoice, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/invoices`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify(invoice)
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
deleteInvoice(practiceId, invoiceId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/invoices/${invoiceId}`, {
method: 'DELETE', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
addUlaEmployee(practiceId, emp, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/ula-employees`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify(emp)
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
deleteUlaEmployee(practiceId, empId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/ula-employees/${empId}`, {
method: 'DELETE', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
upsertDocument(practiceId, docCode, payload, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/documents/${docCode}`, {
method: 'PUT', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify({ doc_code: docCode, ...payload })
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
clearDocument(practiceId, docCode, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/documents/${docCode}`, {
method: 'DELETE', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
gateCheck(practiceId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/gate-check`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
submitPractice(practiceId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/${practiceId}/submit`, {
method: 'POST', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
// dev-only: impersonation per test beneficiary
impersonate(email, onSuccess, onError) {
fetch(`${BASE_URL}/api/debug/impersonate`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify({ email })
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
}
};
// Attach to main export
Object.assign(RendicontazioneService, extendPractice);
// ====================== ISTRUTTORE ======================
const extendInstructor = {
instructorQueue(onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/queue`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
instructorViewPractice(practiceId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
claimPractice(practiceId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/claim`, {
method: 'POST', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
approvePractice(practiceId, body, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/approve`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify(body || {})
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
rejectPractice(practiceId, reason, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/reject`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify({ rejection_reason: reason })
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
createAmendment(practiceId, body, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/amendment`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify(body)
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
closeAmendment(practiceId, amendmentId, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/amendment/${amendmentId}/close`, {
method: 'POST', mode: 'cors', headers: buildHeaders()
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
},
respondAmendmentBeneficiary(practiceId, amendmentId, responseText, onSuccess, onError) {
fetch(`${BASE_URL}/api/remission-practices/instructor/${practiceId}/amendment/${amendmentId}/respond-beneficiary`, {
method: 'POST', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify({ response_text: responseText })
}).then(r => handleResponse(r, onSuccess, onError)).catch(e => handleError(e, onError));
}
};
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);