import React, { useEffect, useState, useRef, useMemo } from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
// store
import { useStoreValue } from '../../../store';
// components
import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast';
import { Tag } from 'primereact/tag';
import { Skeleton } from 'primereact/skeleton';
import { ConfirmPopup, confirmPopup } from 'primereact/confirmpopup';
import { InputText } from 'primereact/inputtext';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import { MultiSelect } from 'primereact/multiselect';
import { Dropdown } from 'primereact/dropdown';
import { Calendar } from 'primereact/calendar';
import { InputTextarea } from 'primereact/inputtextarea';
import BlockingOverlay from '../../../components/BlockingOverlay';
// api
import RendicontazioneService from '../service/rendicontazioneService';
import SchemaTemplatePicker from '../components/SchemaTemplatePicker';
import BandoService from '../../../service/bando-service';
// ---------- costanti ----------
const IVA_REGIMES = [
{ value: 'ORDINARIO', label: 'Ordinario' },
{ value: 'FORFETTARIO', label: 'Forfettario' },
{ value: 'ESENTE', label: 'Esente' }
];
const AMOUNT_BASIS_OPTIONS = [
{ value: 'imponibile_always', label: 'Solo imponibile (sempre)' },
{ value: 'imponibile_only_ordinario', label: 'Imponibile in ordinario, totale in forfettario' },
{ value: 'totale_always', label: 'Totale IVA inclusa (sempre)' }
];
const PERIOD_START_RULES = [
{ value: 'erogato_date', label: 'Data di erogazione del finanziamento' },
{ value: 'contract_signed_date', label: 'Data firma contratto' },
{ value: 'custom', label: 'Data personalizzata' }
];
const ULA_DOC_TYPES = [
{ value: 'LUL', label: 'Libro Unico del Lavoro (LUL)' },
{ value: 'GESTIONALE_PAGHE', label: 'Estratto gestionale paghe' },
{ value: 'DICHIARAZIONE_CDL', label: 'Dichiarazione Consulente del Lavoro' },
{ value: 'ALTRO', label: 'Altro documento di supporto' }
];
// ---------- helpers JSON <-> form ----------
const schemaJsonToForm = (j) => {
if (!j || !j.sections) return null;
const general = j.sections.find(s => s.type === 'static_fields') || {};
const expenses = j.sections.find(s => s.type === 'category_grid') || {};
const ula = j.sections.find(s => s.type === 'ula_block') || {};
const docs = j.sections.find(s => s.type === 'document_checklist') || {};
const gate = j.gate_rules || {};
const ivaField = (general.fields || []).find(f => f.id === 'iva_regime');
const ivaAllowed = ivaField && ivaField.options
? ivaField.options.map(o => typeof o === 'string' ? o : o.value)
: ['ORDINARIO','FORFETTARIO','ESENTE'];
const parseList = (list) => (list || []).map(x =>
typeof x === 'string' ? { code: x, label: x } : { code: x.code || '', label: x.label || x.code || '' });
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,
amount_basis: gate.amount_basis || (gate.iva_ordinario_imponibile_only === false ? 'totale_always' : 'imponibile_only_ordinario'),
period_start_rule: gate.period_start_rule ?? 'erogato_date',
iva_regimes_allowed: ivaAllowed,
iva_ordinario_imponibile_only: gate.iva_ordinario_imponibile_only ?? true,
categories: (expenses.categories || []).map(c => ({
code: c.code || '', label: c.label || '',
description: c.description || '', cap_amount: c.cap_amount ?? null
})),
ula_enabled: ula.enabled ?? false,
ula_threshold: ula.threshold ?? 1.0,
ula_period_start_rule: ula.period_start_rule ?? 'erogato_date',
ula_period_end: ula.period_end ? new Date(ula.period_end) : null,
ula_supporting_doc_required: ula.supporting_doc_required ?? true,
ula_supporting_doc_types: (ula.supporting_doc_types || []).map(t => typeof t === 'string' ? t : t.code),
docs_required: parseList(docs.required_types),
cap_pct_erogato: gate.cap_pct_erogato != null ? Math.round(gate.cap_pct_erogato * 100) : 50,
cap_absolute: gate.cap_absolute ?? 12500,
require_invoice_per_category: gate.require_at_least_one_invoice_per_nonzero_category ?? true,
require_ula_above_threshold: gate.require_ula_above_threshold ?? true,
require_all_documents_resolved: gate.require_all_documents_resolved ?? true,
// v2 multi-tranche + custom_checks
max_tranches: gate.max_tranches ?? 1,
custom_checks: (j.custom_checks || []).map(cc => ({
code: cc.code || '',
label: cc.label || '',
description: cc.description || '',
requires_document: !!cc.requires_document,
required: !!cc.required,
}))
};
};
const formToSchemaJson = (f, base = null) => {
const orig = base || {};
const fmtDate = (d) => d ? (typeof d === 'string' ? d : d.toISOString().slice(0, 10)) : null;
return {
version: orig.version || '1.0',
template_id: orig.template_id || 'CUSTOM',
template_label: orig.template_label || 'Schema personalizzato',
sections: [
{
type: 'static_fields', id: 'general', label: 'Dati generali',
fields: [{
id: 'iva_regime', type: 'select', label: 'Regime IVA', required: true,
options: IVA_REGIMES.filter(o => f.iva_regimes_allowed.includes(o.value))
}]
},
{
type: 'category_grid', id: 'expenses', label: 'Spese ammissibili per categoria',
categories: f.categories,
invoice_schema: { required_fields: ['invoice_number','invoice_date','payment_date','supplier_name','supplier_vat','description','taxable','vat','total','pdf'] }
},
{
type: 'ula_block', id: 'ula', label: 'Calcolo ULA',
enabled: f.ula_enabled, threshold: f.ula_threshold,
period_start_rule: f.ula_period_start_rule,
period_end: fmtDate(f.ula_period_end),
supporting_doc_required: f.ula_supporting_doc_required,
supporting_doc_types: ULA_DOC_TYPES
.filter(t => f.ula_supporting_doc_types.includes(t.value))
.map(t => ({ code: t.value, label: t.label }))
},
{
type: 'document_checklist', id: 'docs', label: 'Documenti richiesti',
required_types: f.docs_required
}
],
gate_rules: {
amount_range: { min: f.amount_min, max: f.amount_max },
cap_pct_erogato: f.cap_pct_erogato / 100,
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),
amount_basis: f.amount_basis,
require_at_least_one_invoice_per_nonzero_category: f.require_invoice_per_category,
require_ula_above_threshold: f.require_ula_above_threshold,
require_all_documents_resolved: f.require_all_documents_resolved,
max_tranches: f.max_tranches || 1
},
custom_checks: (f.custom_checks || []).map(cc => ({
code: cc.code,
label: cc.label,
description: cc.description,
requires_document: !!cc.requires_document,
required: !!cc.required,
})),
schema_version: 2
};
};
const BandoRendicontazioneSchemaEdit = () => {
const { id } = useParams();
const navigate = useNavigate();
const isAsyncRequest = useStoreValue('isAsyncRequest');
const callId = parseInt(id);
const [bando, setBando] = useState(null);
const [bandoLoading, setBandoLoading] = useState(true);
const [schemaRecord, setSchemaRecord] = useState(null);
const [schemaLoading, setSchemaLoading] = useState(true);
const [form, setForm] = useState(null);
const [dirty, setDirty] = useState(false);
const toast = useRef(null);
// ---------- load ----------
const loadBando = () => {
setBandoLoading(true);
BandoService.getBando(callId,
(r) => { setBando(r?.data || null); setBandoLoading(false); },
() => setBandoLoading(false));
};
const loadSchema = () => {
setSchemaLoading(true);
RendicontazioneService.getSchemaByCallId(callId,
(resp) => {
const rec = resp?.data || null;
setSchemaRecord(rec);
setForm(rec ? schemaJsonToForm(rec.schema_json) : null);
setDirty(false);
setSchemaLoading(false);
},
(err) => {
if (err?.status === 404) { setSchemaRecord(null); setForm(null); }
else toast.current?.show({ severity: 'error', summary: __('Errore caricamento schema','gepafin'), detail: err?.detail });
setSchemaLoading(false);
});
};
useEffect(() => {
if (!isNaN(callId)) { loadBando(); loadSchema(); }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [callId]);
// ---------- updates ----------
const update = (patch) => { setForm(p => ({ ...p, ...patch })); setDirty(true); };
const updateCategory = (idx, patch) => {
setForm(p => ({ ...p, categories: p.categories.map((c,i) => i===idx ? {...c, ...patch} : c) }));
setDirty(true);
};
const addCategory = () => {
setForm(p => ({ ...p, categories: [...p.categories, { code:'', label:'', description:'', cap_amount:null }] }));
setDirty(true);
};
const removeCategory = (idx) => {
setForm(p => ({ ...p, categories: p.categories.filter((_,i) => i!==idx) }));
setDirty(true);
};
const updateDoc = (idx, patch) => {
setForm(p => ({ ...p, docs_required: p.docs_required.map((d,i) => i===idx ? {...d,...patch} : d) }));
setDirty(true);
};
const addDoc = () => {
setForm(p => ({ ...p, docs_required: [...p.docs_required, { code:'', label:'' }] }));
setDirty(true);
};
const removeDoc = (idx) => {
setForm(p => ({ ...p, docs_required: p.docs_required.filter((_,i) => i!==idx) }));
setDirty(true);
};
// v2 custom_checks
const updateCheck = (idx, patch) => {
setForm(p => ({ ...p, custom_checks: p.custom_checks.map((c,i) => i===idx ? {...c, ...patch} : c) }));
setDirty(true);
};
const addCheck = () => {
setForm(p => ({ ...p, custom_checks: [...(p.custom_checks || []), { code:'', label:'', description:'', requires_document:false, required:false }] }));
setDirty(true);
};
const removeCheck = (idx) => {
setForm(p => ({ ...p, custom_checks: p.custom_checks.filter((_,i) => i!==idx) }));
setDirty(true);
};
// ---------- actions ----------
const handleInitializeRestart = (e) => {
confirmPopup({
target: e.currentTarget,
message: __('Inizializzo lo schema con il template RE-START? Sarà modificabile finché non verrà pubblicato.','gepafin'),
icon: 'pi pi-info-circle',
acceptLabel: __('Inizializza','gepafin'), rejectLabel: __('Annulla','gepafin'),
accept: () => RendicontazioneService.initializeRestartTemplate(callId,
() => { toast.current?.show({severity:'success', summary: __('Schema inizializzato','gepafin')}); loadSchema(); },
(err) => toast.current?.show({severity:'error', summary:__('Inizializzazione fallita','gepafin'), detail: err?.detail}))
});
};
const handleSave = () => {
const newJson = formToSchemaJson(form, schemaRecord?.schema_json);
RendicontazioneService.updateSchema(callId, newJson,
(resp) => {
toast.current?.show({severity:'success', summary: __('Schema salvato','gepafin')});
setSchemaRecord(resp?.data);
setForm(schemaJsonToForm(resp?.data?.schema_json));
setDirty(false);
},
(err) => toast.current?.show({severity:'error', summary:__('Salvataggio fallito','gepafin'), detail: err?.detail}));
};
const handlePublish = (e) => {
confirmPopup({
target: e.currentTarget,
message: __('Dopo la pubblicazione lo schema non sarà più modificabile e diventerà visibile ai beneficiari. Confermi?','gepafin'),
icon: 'pi pi-exclamation-triangle',
acceptLabel: __('Pubblica','gepafin'), rejectLabel: __('Annulla','gepafin'),
acceptClassName: 'p-button-success',
accept: () => RendicontazioneService.publishSchema(callId,
(resp) => { toast.current?.show({severity:'success', summary:__('Schema pubblicato','gepafin')}); setSchemaRecord(resp?.data); },
(err) => toast.current?.show({severity:'error', summary:__('Pubblicazione fallita','gepafin'), detail: err?.detail}))
});
};
// ---------- render ----------
const isPublished = schemaRecord?.status === 'PUBLISHED';
const readOnly = isPublished;
const hasSchema = !!schemaRecord;
const statusTag = useMemo(() => {
if (!hasSchema) return
{bandoLoading
?