feat(rendicontazione): lato beneficiario - lista pratiche + compilazione + submit

- Nuova pagina RendicontazioniMie: dashboard beneficiario con pratiche esistenti
  + applications CONTRACT_SIGNED ready_to_start in tabella unificata
- Nuova pagina PraticaRendicontazioneEdit: form compilazione completo
  + riepilogo finanziario live (erogato, totale, cap, remissione spettante)
  + requisiti per invio con semafori live (gate check refresh on mount)
  + sezione regime IVA con update inline
  + fatture per categoria con dialog add + tabella + delete (per B1/B2/B3)
  + dipendenti ULA con dialog add (CF, contratto, FTE, periodo, allegato)
  + documenti richiesti con upload simulato (prompt nome file)
  + submit con confermazione, disabilitato finche' gate non passa
- Nuova pagina DevSwitchUser: impersonate sandbox-only per superadmin
- Voce sidebar "Le mie rendicontazioni" per ROLE_BENEFICIARY
- Voce sidebar "Dev: cambia utente" per ROLE_SUPER_ADMIN
- Service esteso con 12 metodi pratiche + impersonate
This commit is contained in:
BFLOWS Sandbox
2026-04-18 09:50:53 +02:00
parent 8888e0326d
commit 9c483ade34
6 changed files with 983 additions and 0 deletions

View File

@@ -87,3 +87,95 @@ const RendicontazioneService = {
};
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);