netgescon-day0/resources/js/admin/gescon-import-mapping.js

1222 lines
72 KiB
JavaScript

/* global window, document, CSS, fetch */
// NetGescon — GESCON Mapping JS (extracted from Blade)
(function () {
'use strict';
const CFG = window.GESCON_MAPPING || {};
const R = (k) => (CFG.routes || {})[k] || '';
const LEGACY = CFG.legacyCode || '';
const CSRF = CFG.csrf || '';
// Utilities
function escapeHtml(s) {
return String(s)
.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
.replaceAll('"', '&quot;').replaceAll("'", '&#39;');
}
function formatEuro(value) {
if (value === null || value === undefined || value === '') {
return '';
}
const num = Number(value);
if (!Number.isFinite(num)) {
return String(value);
}
try {
return new Intl.NumberFormat('it-IT', { style: 'currency', currency: 'EUR' }).format(num);
} catch (e) {
return num.toFixed(2);
}
}
function toggleInlineCreate(trigger, show = true) {
const row = trigger && typeof trigger.closest === 'function'
? trigger.closest('.mapping-tab-row, .mapping-voce-row')
: null;
if (!row) return;
const ui = row.querySelector('.inline-create-ui');
if (!ui) return;
if (show) {
ui.classList.remove('hidden');
const focusTarget = ui.querySelector('.new-col-name');
if (focusTarget) {
setTimeout(() => focusTarget.focus(), 0);
}
} else {
ui.classList.add('hidden');
}
}
// ===== Mapping filtering & UI =====
let __mappingVisibilityMode = 'all';
let __searchTerm = '';
function filterLegacyList(term) {
const t = (term || '').toLowerCase();
document.querySelectorAll('#legacy-list > li').forEach(li => {
const key = (li.getAttribute('data-key') || '').toLowerCase();
const txt = (li.innerText || '').toLowerCase();
li.style.display = (!t || key.includes(t) || txt.includes(t)) ? '' : 'none';
});
}
function applyMappingVisibility() {
document.querySelectorAll('#mapping-rows .mapping-row').forEach(row => {
const sel = row.querySelector('select.mapping-select');
const mapped = !!(sel && sel.value);
let show = true;
if (__mappingVisibilityMode === 'mapped') show = mapped;
else if (__mappingVisibilityMode === 'unmapped') show = !mapped;
if (show && __searchTerm) {
const term = __searchTerm.toLowerCase();
const key = (row.getAttribute('data-key') || '').toLowerCase();
const nameEl = row.querySelector('label small');
const nameTxt = ((nameEl && nameEl.textContent) || '').toLowerCase();
let selectedLabel = '', selectedValue = '';
if (sel && sel.selectedIndex >= 0) {
const opt = sel.options[sel.selectedIndex];
selectedLabel = (opt.text || '').toLowerCase();
selectedValue = (opt.value || '').toLowerCase();
}
if (!(key.includes(term) || nameTxt.includes(term) || selectedLabel.includes(term) || selectedValue.includes(term))) {
show = false;
}
}
row.style.display = show ? '' : 'none';
});
}
function setMappingFilter(mode) {
__mappingVisibilityMode = mode || 'all';
document.querySelectorAll('#mapping-visibility-buttons button').forEach(btn => {
if (btn.getAttribute('data-mode') === __mappingVisibilityMode) btn.classList.add('active'); else btn.classList.remove('active');
});
applyMappingVisibility();
}
function syncMappingSearch(term) {
__searchTerm = term || '';
try {
const lf = document.getElementById('legacy-filter-input');
if (lf && lf.value !== term) lf.value = term;
const tf = document.getElementById('target-filter-input');
if (tf && tf.value !== term) tf.value = term;
} catch (e) { }
filterLegacyList(term);
applyMappingVisibility();
}
function buildTargetSelect(name) {
let html = '<select class="netgescon-input mapping-select" name="' + escapeHtml(name) + '" style="padding:.35rem .5rem;">';
html += '<option value="">— Seleziona campo target —</option>';
(window.__targetFields || []).forEach(function (tf) {
html += '<option value="' + escapeHtml(tf.value) + '">' + escapeHtml(tf.label) + '</option>';
});
html += '</select>';
return html;
}
function promptAddField() {
const key = prompt('Chiave campo legacy (es. telefono_amministratore):');
if (!key) return;
const name = prompt('Etichetta leggibile (opzionale):', key.replace(/[_-]/g, ' '));
addLegacyField(key, name || key);
}
function addLegacyField(key, label) {
if (document.querySelector('#mapping-rows .mapping-row[data-key="' + key + '"]')) return;
const li = document.createElement('li');
li.className = 'flex justify-between items-center';
li.style.borderBottom = '1px solid #eee';
li.style.padding = '.5rem';
li.setAttribute('data-key', key);
li.innerHTML = '<div><div class="font-semibold">' + escapeHtml(label) + '</div><small class="text-gray-500">' + escapeHtml(key) + '</small></div><span class="netgescon-badge netgescon-badge-secondary">Sorgente</span>';
const ul = document.getElementById('legacy-list'); if (ul) ul.appendChild(li);
const row = document.createElement('div');
row.className = 'grid md:grid-cols-12 gap-3 items-center mb-2 mapping-row';
row.setAttribute('data-key', key);
row.innerHTML = '<div class="md:col-span-5 col-span-12"><label class="mb-0"><small>' + escapeHtml(label) + '</small></label><div class="text-gray-500 text-sm">' + escapeHtml(key) + '</div></div>' +
'<div class="md:col-span-7 col-span-12">' + buildTargetSelect('mapping[' + key + ']') + '</div>';
const container = document.getElementById('mapping-rows'); if (container) container.appendChild(row);
}
function updateMappingPreviews() {
document.querySelectorAll('#mapping-form .mapping-row').forEach(row => {
const key = row.getAttribute('data-key');
const valEl = document.querySelector('.legacy-value[data-field-value="' + CSS.escape(key) + '"]');
const preview = row.querySelector('.import-preview');
if (preview) preview.textContent = valEl ? (valEl.textContent || '—') : '—';
});
}
// ===== Manual Values =====
function getManualContainer() {
return document.getElementById('manual-values-container');
}
function refreshManualEmptyState() {
const container = getManualContainer();
if (!container) return;
const rows = container.querySelectorAll('.manual-value-row');
const placeholder = container.querySelector('.manual-value-empty');
if (rows.length === 0) {
if (placeholder) {
placeholder.classList.remove('hidden');
} else {
const info = document.createElement('div');
info.className = 'manual-value-empty text-xs text-gray-500 border rounded p-2 bg-slate-50';
info.textContent = 'Nessun valore manuale configurato.';
container.appendChild(info);
}
} else if (placeholder) {
placeholder.classList.add('hidden');
}
}
function syncManualValueRow(selectEl) {
if (!selectEl) return;
const row = selectEl.closest('.manual-value-row');
if (!row) return;
const input = row.querySelector('.manual-value-input');
if (!input) return;
const target = (selectEl.value || '').trim();
if (target) {
input.name = 'manual_target_values[' + target + ']';
row.dataset.target = target;
} else {
input.name = '';
row.dataset.target = '';
}
}
function addManualValueRow(target = '', value = '') {
const container = getManualContainer();
if (!container) return;
const tmpl = document.getElementById('manual-value-row-template');
if (!tmpl) return;
const cloneRoot = (() => {
if (tmpl.content && tmpl.content.firstElementChild) {
return tmpl.content.firstElementChild.cloneNode(true);
}
const wrapper = document.createElement('div');
wrapper.innerHTML = (tmpl.innerHTML || '').trim();
return wrapper.firstElementChild ? wrapper.firstElementChild.cloneNode(true) : null;
})();
if (!cloneRoot) return;
container.appendChild(cloneRoot);
const select = cloneRoot.querySelector('.manual-value-target');
const input = cloneRoot.querySelector('.manual-value-input');
if (select) {
select.value = target || '';
syncManualValueRow(select);
}
if (input) input.value = value || '';
refreshManualEmptyState();
if (select) {
setTimeout(() => select.focus(), 0);
} else if (input) {
setTimeout(() => input.focus(), 0);
}
}
function removeManualValueRow(triggerEl) {
const row = triggerEl ? triggerEl.closest('.manual-value-row') : null;
if (row) row.remove();
refreshManualEmptyState();
}
function initializeManualValueRows() {
const container = getManualContainer();
if (!container) return;
container.querySelectorAll('.manual-value-row .manual-value-target').forEach(selectEl => {
syncManualValueRow(selectEl);
});
const defaults = (CFG.manualTargetValues && typeof CFG.manualTargetValues === 'object') ? CFG.manualTargetValues : null;
if (container.querySelectorAll('.manual-value-row').length === 0 && defaults) {
Object.keys(defaults).forEach(key => {
addManualValueRow(key, defaults[key]);
});
}
refreshManualEmptyState();
}
// ===== Samples & Presets =====
let __sampleIndex = 0;
async function loadSample(idx) {
const box = document.getElementById('legacy-sample');
if (box) box.innerHTML = '<div class="text-gray-500 text-sm">Caricamento…</div>';
try {
const params = new URLSearchParams({ index: String(idx) });
if (LEGACY) params.set('legacy_code', LEGACY);
const res = await fetch(R('sample') + '?' + params.toString());
const j = await res.json();
if (!j.ok) { if (box) box.innerHTML = '<div class="text-danger small">' + escapeHtml(j.error || 'Errore caricamento') + '</div>'; return; }
__sampleIndex = j.index || 0;
const data = j.data || {};
document.querySelectorAll('.legacy-value[data-field-value]').forEach(el => {
const key = el.getAttribute('data-field-value');
el.textContent = (key in data) ? String(data[key] ?? '') : '—';
});
try {
const legacyBankBadge = document.getElementById('bank-count-legacy');
if (legacyBankBadge) {
const keys = Object.keys(data || {});
const ibanKeys = keys.filter(k => /iban/.test(String(k).toLowerCase()));
const count = ibanKeys.filter(k => String(data[k] ?? '').trim().length > 0).length;
legacyBankBadge.textContent = 'Banche presenti: ' + count;
legacyBankBadge.style.display = '';
}
} catch (e) { }
updateMappingPreviews();
try { populateGestioneRatesFromData(data); } catch (e) { }
if (box) box.innerHTML = '';
} catch (e) { if (box) box.innerHTML = '<div class="text-danger small">Errore</div>'; }
}
function browseSample(delta) { __sampleIndex = Math.max(0, __sampleIndex + (delta || 0)); return loadSample(__sampleIndex); }
function syncLegacyPreview() { return loadSample(__sampleIndex || 0); }
function applyPreset() {
const sel = document.getElementById('preset-select');
const key = (sel?.value || '').trim();
const preset = (CFG.mappingPresets || {})[key];
document.getElementById('preset_key').value = key;
if (!preset) { updatePresetMeta(); return; }
const mapping = preset.mapping || {};
document.querySelectorAll('#mapping-form .mapping-row').forEach(row => {
const legacyKey = row.getAttribute('data-key');
const sel2 = row.querySelector('select.mapping-select');
if (!legacyKey || !sel2) return;
const target = mapping[legacyKey];
if (target) { sel2.value = target; }
});
const meta = Object.assign({}, preset.meta || {});
document.getElementById('preset_meta').value = JSON.stringify(meta);
}
function updatePresetMeta() {
const sel = document.getElementById('preset-select');
const key = (sel?.value || '').trim();
const preset = (CFG.mappingPresets || {})[key];
const base = preset?.meta || {};
const meta = Object.assign({}, base);
document.getElementById('preset_meta').value = JSON.stringify(meta);
document.getElementById('preset_key').value = key;
}
// ===== Segments =====
function switchSegment(seg) {
const segments = ['stabili', 'gestioni', 'finanziari', 'unita', 'tabelle', 'voci'];
segments.forEach(s => {
const el = document.getElementById('segment-' + s);
if (el) { if (s === seg) el.classList.remove('d-none'); else el.classList.add('d-none'); }
document.querySelectorAll('.mapping-steps [data-segment="' + s + '"]').forEach(li => {
if (s === seg) li.classList.add('active'); else li.classList.remove('active');
});
});
if (LEGACY) {
if (seg === 'unita') { refreshUnitaPreview(); }
if (seg === 'gestioni') {
if (!Array.isArray(__gestioniYears) || !__gestioniYears.length) {
// try to fetch years then load data
(async () => {
try {
const res = await fetch(R('years') + '?legacy_code=' + encodeURIComponent(LEGACY));
if (res.ok) { const js = await res.json(); __gestioniYears = Array.isArray(js.years) ? js.years : []; __gestioniYearIndex = Math.max(0, __gestioniYears.length - 1); updateGestioneYearUI(); }
} catch (e) { }
try { await loadGestioneYearData(); } catch (e) { }
})();
} else { loadGestioneYearData(); }
}
if (seg === 'finanziari') { loadLegacyCasse(); }
if (seg === 'tabelle' && typeof window.reloadTabelleColumns === 'function') { try { window.reloadTabelleColumns(); } catch (_) { } }
if (seg === 'voci' && typeof window.reloadVociColumns === 'function') { try { window.reloadVociColumns(); } catch (_) { } }
}
}
// ===== Gestione anni (generale_stabile) =====
function ensureRow(id) {
const tr = document.getElementById(id);
if (!tr) return null;
if (tr.children.length < 13) {
for (let i = tr.children.length; i < 13; i++) {
const td = document.createElement('td'); td.className = 'text-center text-xs'; td.dataset.mese = i; td.textContent = ''; tr.appendChild(td);
}
}
return tr;
}
function populateGestioneRatesFromData(data) {
if (!data) return;
const rowOrd = ensureRow('row-ord');
const rowRis = ensureRow('row-ris');
for (let i = 1; i <= 12; i++) {
const ko = 'ord_rata_' + i; const kr = 'ris_rata_' + i;
if (rowOrd) { const td = rowOrd.children[i]; if (td) td.textContent = (ko in data && String(data[ko] || '0') !== '0') ? '✓' : ''; }
if (rowRis) { const td = rowRis.children[i]; if (td) td.textContent = (kr in data && String(data[kr] || '0') !== '0') ? '✓' : ''; }
}
const annoO = data['anno_o'] || data['anno_or'] || '';
const annoR = data['anno_r'] || '';
const goAnno = document.getElementById('go-anno'); if (goAnno) goAnno.textContent = annoO ? '(' + annoO + ')' : '';
const grAnno = document.getElementById('gr-anno'); if (grAnno) grAnno.textContent = annoR ? '(' + annoR + ')' : '';
const cellAnnoO = document.getElementById('gg-anno-o'); if (cellAnnoO) cellAnnoO.textContent = annoO || '—';
const cellAnnoR = document.getElementById('gg-anno-r'); if (cellAnnoR) cellAnnoR.textContent = annoR || '—';
const statoO = data['stato_o'] || (annoO ? 'aperta' : '—');
const statoR = data['stato_r'] || (annoR ? 'aperta' : '—');
const cellStatoO = document.getElementById('gg-stato-o'); if (cellStatoO) cellStatoO.textContent = statoO;
const cellStatoR = document.getElementById('gg-stato-r'); if (cellStatoR) cellStatoR.textContent = statoR;
}
let __gestioniYears = Array.isArray(CFG.availableYears) ? CFG.availableYears : [];
let __gestioniYearIndex = 0;
function browseGestioneYear(delta) {
if (!__gestioniYears || __gestioniYears.length === 0) return;
__gestioniYearIndex = Math.min(Math.max(0, __gestioniYearIndex + delta), __gestioniYears.length - 1);
updateGestioneYearUI();
loadGestioneYearData();
}
function updateGestioneYearUI() {
const lbl = document.getElementById('gestione-year-label');
if (lbl) { lbl.textContent = (__gestioniYears && __gestioniYears.length) ? __gestioniYears[__gestioniYearIndex] : '—'; }
}
async function loadGestioneYearData() {
const year = __gestioniYears[__gestioniYearIndex];
if (!year || !LEGACY) return;
try {
const url = R('gestioniYear') + '?legacy_code=' + encodeURIComponent(LEGACY) + '&anno=' + encodeURIComponent(year);
const res = await fetch(url);
if (!res.ok) throw new Error('Errore caricamento gestioni');
const js = await res.json();
// Wire Staging Loader Unità link if possible
try {
const btn = document.getElementById('btn-unita-loader');
const base = R('archiveUi') || '';
if (btn && base) {
const p = (js.meta && js.meta.path_singolo_anno) ? String(js.meta.path_singolo_anno) : '';
const q = new URLSearchParams();
if (p) q.set('mdb_path', p);
// Pre-fill doesn't support table param explicitly, but UI lets user pick tables; this opens with mdb preselected
btn.href = base + (q.toString() ? ('?' + q.toString()) : '');
}
} catch (_) { }
const data = {};
if (js.ordinaria && js.ordinaria.rate) { Object.keys(js.ordinaria.rate).forEach(k => { data['ord_rata_' + k] = 1; }); data.anno_o = js.ordinaria.anno; data.stato_o = js.ordinaria.stato; }
if (js.riscaldamento && js.riscaldamento.rate) { Object.keys(js.riscaldamento.rate).forEach(k => { data['ris_rata_' + k] = 1; }); data.anno_r = js.riscaldamento.anno; data.stato_r = js.riscaldamento.stato; }
populateGestioneRatesFromData(data, year);
const tbody = document.querySelector('#straordinarie-table tbody');
if (tbody) {
const list = js.straordinarie || [];
if (list.length === 0) { tbody.innerHTML = `<tr><td colspan="6" class="text-xs text-gray-500">(Nessuna straordinaria per ${year})</td></tr>`; }
else { tbody.innerHTML = list.map(s => `<tr><td class='text-xs'>${s.id || '—'}</td><td class='text-xs'>${escapeHtml(s.descrizione || '—')}</td><td class='text-xs'>${s.data_inizio || '—'}</td><td class='text-xs'>${s.data_fine || '—'}</td><td class='text-xs'>${(s.rate_emesse || 0)}/${(s.rate_totali || 0)}</td><td class='text-xs'>${s.periodicita || ''}</td></tr>`).join(''); }
}
const annoS = document.getElementById('gg-anno-s'); if (annoS) annoS.textContent = year;
} catch (e) { console.warn(e); }
}
// ===== Finanziari (casse/banche) =====
let __legacyCasse = [];
async function loadLegacyCasse() {
const tb = document.querySelector('#legacy-casse-table tbody');
if (!tb) return;
tb.innerHTML = '<tr><td colspan="8" class="text-xs text-gray-500">Caricamento...</td></tr>';
try {
const url = R('casse') + '?legacy_code=' + encodeURIComponent(LEGACY);
const res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status);
const js = await res.json();
__legacyCasse = (js.casse || []).map(c => ({
codice: c.codice,
descrizione: c.descrizione,
abi: c.abi,
cab: c.cab,
cin: c.cin || c.CIN || null,
cuc: c.cuc || c.CUC || c.codice_cuc || null,
iban: c.iban,
swift: c.swift || c.SWIFT || c.bic || c.BIC || null,
conto: c.numero_conto || c.conto || null,
intestatario: c.intestatario || c.intestazione || null,
filiale: c.filiale || c.filiale_descr || null,
indirizzo: c.indirizzo || c.indirizzo_filiale || null,
cap: c.cap || c.cap_filiale || null,
citta: c.citta || c.comune || null,
provincia: c.provincia || c.prov || null,
telefono: c.telefono || c.telefono_ufficio || null,
email: c.email || null,
pec: c.pec || c.email_pec || null,
valuta: c.valuta || c.currency || null,
saldo: c.saldo_iniziale_euro ?? c.saldo_iniziale ?? c.saldo ?? null,
saldo_euro: c.saldo_iniziale_euro ?? null,
data_saldo: c.data_saldo || c.data_saldo_iniziale || null,
note: c.note || null
}));
if (!__legacyCasse.find(c => c.codice === 'CON')) { __legacyCasse.unshift({ codice: 'CON', descrizione: 'CASSA CONTANTI', abi: null, cab: null, cin: null, cuc: null, iban: null, swift: null, conto: 'CON', intestatario: null, valuta: 'EUR', saldo: null, saldo_euro: null, data_saldo: null, note: null }); }
renderLegacyCasse();
ensureBancheMappingRows();
ensureMandatoryConMapping();
} catch (e) { tb.innerHTML = '<tr><td colspan="8" class="text-xs text-danger">Errore caricamento</td></tr>'; }
}
function renderLegacyCasse(term) {
const tbody = document.querySelector('#legacy-casse-table tbody'); if (!tbody) return;
const t = (term || '').toLowerCase();
const rows = __legacyCasse.filter(c => !t || c.codice.toLowerCase().includes(t) || (c.descrizione || '').toLowerCase().includes(t));
if (rows.length === 0) { tbody.innerHTML = '<tr><td colspan="8" class="text-xs text-gray-500">Nessun conto</td></tr>'; return; }
tbody.innerHTML = rows.map(c => {
const saldoFormatted = formatEuro(c.saldo_euro ?? c.saldo);
return `<tr data-cod="${c.codice}">
<td>${escapeHtml(c.codice || '')}</td>
<td>${escapeHtml(c.descrizione || '')}</td>
<td>${escapeHtml(c.abi || '')}</td>
<td>${escapeHtml(c.cab || '')}</td>
<td>${escapeHtml(c.cuc || '')}</td>
<td class="text-xs font-mono">${escapeHtml(c.iban || '')}</td>
<td class="text-xs">${escapeHtml(saldoFormatted || '')}</td>
<td><button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-primary" onclick="addBancaMapping('${c.codice}')">Mappa</button></td>
</tr>`;
}).join('');
try {
const badge = document.getElementById('bank-count-legacy');
if (badge) {
const exclCon = __legacyCasse.filter(c => c.codice !== 'CON').length;
const hasCon = __legacyCasse.some(c => c.codice === 'CON');
badge.style.display = 'inline-block';
badge.textContent = 'Legacy conti: ' + String(exclCon) + (hasCon ? ' (+CON)' : '');
}
} catch (e) { }
}
function filterLegacyCasse(term) { renderLegacyCasse(term); }
function ensureBancheMappingRows() {
const container = document.getElementById('banche-mapping-rows'); if (!container) return;
const saved = Array.isArray(CFG.savedBancheMapping) ? CFG.savedBancheMapping : [];
if (!container.querySelector('.banca-mapping-row')) {
if (saved.length) {
container.innerHTML = '';
saved.forEach(bm => {
if (!bm || !bm.codice_legacy) return;
addBancaMapping(String(bm.codice_legacy), bm);
});
syncBancheJson();
} else {
renderBanchePlaceholder(container);
}
}
}
function renderBanchePlaceholder(container) {
if (!container) return;
container.innerHTML = '<div class="placeholder-banche text-xs text-slate-500 border border-dashed border-slate-300 rounded px-4 py-4 text-center bg-slate-50">Seleziona una cassa legacy a sinistra per iniziare la configurazione.</div>';
}
function addBancaMapping(codice, data) {
if (!codice) return null;
const container = document.getElementById('banche-mapping-rows'); if (!container) return null;
const existing = container.querySelector(`[data-mapping-cod='${CSS.escape(codice)}']`);
if (existing) {
if (data) populateBancaMappingRow(existing, data);
return existing;
}
if (container.querySelector('.placeholder-banche')) {
container.innerHTML = '';
}
const legacy = (__legacyCasse.find(x => x.codice === codice) || {});
const idx = container.querySelectorAll('.banca-mapping-row').length;
const legacyDescr = (data?.descrizione_legacy) || (data?.legacy?.descrizione) || legacy.descrizione || '';
const legacyIban = data?.legacy?.iban || data?.iban || legacy.iban || '';
const legacyAbi = data?.legacy?.abi || legacy.abi || '';
const legacyCab = data?.legacy?.cab || legacy.cab || '';
const legacyCuc = data?.legacy?.cuc || data?.cuc || legacy.cuc || '';
const legacyCin = data?.legacy?.cin || legacy.cin || '';
const legacySwift = data?.legacy?.swift || legacy.swift || '';
const legacyConto = data?.legacy?.conto || legacy.conto || '';
const legacyIntestatario = data?.legacy?.intestatario || legacy.intestatario || '';
const legacyFiliale = data?.legacy?.filiale || data?.filiale || legacy.filiale || '';
const legacyIndirizzo = data?.legacy?.indirizzo || data?.indirizzo || legacy.indirizzo || '';
const legacyCap = data?.legacy?.cap || data?.cap || legacy.cap || '';
const legacyCitta = data?.legacy?.citta || data?.citta || legacy.citta || '';
const legacyProvincia = data?.legacy?.provincia || data?.provincia || legacy.provincia || '';
const legacyTelefono = data?.legacy?.telefono || data?.telefono || legacy.telefono || '';
const legacyEmail = data?.legacy?.email || data?.email || legacy.email || '';
const legacyPec = data?.legacy?.pec || data?.pec || legacy.pec || '';
const legacySaldo = (legacy.saldo_euro ?? legacy.saldo ?? '');
const hasLegacySaldo = legacySaldo !== '' && legacySaldo !== null && legacySaldo !== undefined;
const legacyBadges = [
legacyIban ? `<span class="inline-flex items-center gap-1 rounded border border-slate-200 bg-white px-2 py-0.5 font-mono uppercase">IBAN: ${escapeHtml(legacyIban)}</span>` : '',
(legacyAbi || legacyCab) ? `<span class="inline-flex items-center gap-1 rounded border border-slate-200 bg-white px-2 py-0.5">ABI/CAB: ${escapeHtml([legacyAbi, legacyCab].filter(Boolean).join(' • '))}</span>` : '',
legacyCuc ? `<span class="inline-flex items-center gap-1 rounded border border-slate-200 bg-white px-2 py-0.5">CUC: ${escapeHtml(legacyCuc)}</span>` : '',
legacySwift ? `<span class="inline-flex items-center gap-1 rounded border border-slate-200 bg-white px-2 py-0.5">SWIFT: ${escapeHtml(legacySwift)}</span>` : '',
legacyConto ? `<span class="inline-flex items-center gap-1 rounded border border-slate-200 bg-white px-2 py-0.5">Conto: ${escapeHtml(legacyConto)}</span>` : '',
hasLegacySaldo ? `<span class="inline-flex items-center gap-1 rounded border border-slate-200 bg-white px-2 py-0.5">Saldo: ${escapeHtml(formatEuro(legacySaldo) || '')}</span>` : '',
legacyFiliale ? `<span class="inline-flex items-center gap-1 rounded border border-slate-200 bg-white px-2 py-0.5">Filiale: ${escapeHtml(legacyFiliale)}</span>` : ''
].filter(Boolean).join('<span class="text-slate-300">•</span>');
const row = document.createElement('div');
row.className = 'banca-mapping-row border border-slate-200 rounded-lg p-4 bg-white shadow-sm hover:shadow transition-shadow';
row.setAttribute('data-mapping-cod', codice);
row.setAttribute('data-descrizione-legacy', legacyDescr || '');
row.innerHTML = `
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800">${escapeHtml(codice)}${escapeHtml(legacyDescr || 'Conto senza descrizione')}</div>
<div class="legacy-tags flex flex-wrap gap-2 mt-2 text-[11px] text-slate-500">${legacyBadges || '<span>Dati legacy non disponibili</span>'}</div>
</div>
<div class="flex items-center gap-3 text-xs">
<label class="flex items-center gap-2 text-slate-600">
<input type="checkbox" class="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500" name="banche_mapping[${idx}][target][is_nostro_conto]" value="1" onchange="syncBancheJson()">
Conto principale
</label>
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-danger" onclick="removeBancaMappingRow(this)">Rimuovi</button>
</div>
</div>
<div class="grid gap-3 mt-4 text-xs md:grid-cols-2 xl:grid-cols-3">
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Tipo conto</label>
<select name="banche_mapping[${idx}][tipo_conto]" class="netgescon-input" onchange="syncBancheJson()" style="padding:.35rem .5rem;">
<option value="bancario">Bancario</option>
<option value="contanti">Cassa contanti</option>
<option value="postale">Postale</option>
<option value="risparmio">Risparmio</option>
<option value="altro">Altro</option>
</select>
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Denominazione banca</label>
<input type="text" name="banche_mapping[${idx}][target][denominazione_banca]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Intestazione conto</label>
<input type="text" name="banche_mapping[${idx}][target][intestazione_conto]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Numero conto</label>
<input type="text" name="banche_mapping[${idx}][target][numero_conto]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">IBAN</label>
<input type="text" name="banche_mapping[${idx}][target][iban]" value="${escapeHtml(legacyIban)}" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">BIC / SWIFT</label>
<input type="text" name="banche_mapping[${idx}][target][bic_swift]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">ABI</label>
<input type="text" name="banche_mapping[${idx}][target][abi]" value="${escapeHtml(legacyAbi)}" class="netgescon-input" maxlength="5" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">CAB</label>
<input type="text" name="banche_mapping[${idx}][target][cab]" value="${escapeHtml(legacyCab)}" class="netgescon-input" maxlength="5" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">CUC</label>
<input type="text" name="banche_mapping[${idx}][target][cuc]" value="${escapeHtml(legacyCuc)}" class="netgescon-input" maxlength="10" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">CIN</label>
<input type="text" name="banche_mapping[${idx}][target][cin]" value="${escapeHtml(legacyCin)}" class="netgescon-input" maxlength="1" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Valuta</label>
<input type="text" name="banche_mapping[${idx}][target][valuta]" value="EUR" class="netgescon-input" maxlength="3" oninput="syncBancheJson()" style="padding:.35rem .5rem; text-transform:uppercase;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Saldo iniziale</label>
<input type="number" step="0.01" name="banche_mapping[${idx}][target][saldo_iniziale]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Data saldo</label>
<input type="date" name="banche_mapping[${idx}][target][data_saldo_iniziale]" class="netgescon-input" onchange="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Stato</label>
<select name="banche_mapping[${idx}][target][stato_conto]" class="netgescon-input" onchange="syncBancheJson()" style="padding:.35rem .5rem;">
<option value="attivo">Attivo</option>
<option value="sospeso">Sospeso</option>
<option value="chiuso">Chiuso</option>
</select>
</div>
<div class="flex flex-col gap-1 md:col-span-2 xl:col-span-3">
<label class="text-slate-600 font-medium">Note</label>
<textarea name="banche_mapping[${idx}][target][note]" rows="2" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;"></textarea>
</div>
<div class="md:col-span-2 xl:col-span-3 border-t border-dashed border-slate-200 pt-3 mt-2">
<div class="text-[11px] uppercase tracking-wide text-slate-500 font-semibold">Anagrafica contatto / Filiale</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Filiale</label>
<input type="text" name="banche_mapping[${idx}][target][filiale]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Indirizzo</label>
<input type="text" name="banche_mapping[${idx}][target][indirizzo]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">CAP</label>
<input type="text" name="banche_mapping[${idx}][target][cap]" maxlength="5" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Città</label>
<input type="text" name="banche_mapping[${idx}][target][citta]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Provincia</label>
<input type="text" name="banche_mapping[${idx}][target][provincia]" maxlength="2" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem; text-transform:uppercase;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Telefono ufficio</label>
<input type="text" name="banche_mapping[${idx}][target][telefono_ufficio]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">Email</label>
<input type="email" name="banche_mapping[${idx}][target][email]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
<div class="flex flex-col gap-1">
<label class="text-slate-600 font-medium">PEC</label>
<input type="email" name="banche_mapping[${idx}][target][pec]" class="netgescon-input" oninput="syncBancheJson()" style="padding:.35rem .5rem;">
</div>
</div>
<input type="hidden" name="banche_mapping[${idx}][codice_legacy]" value="${escapeHtml(codice)}">
<input type="hidden" name="banche_mapping[${idx}][descrizione_legacy]" value="${escapeHtml(legacyDescr)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][descrizione]" value="${escapeHtml(legacyDescr)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][iban]" value="${escapeHtml(legacyIban)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][abi]" value="${escapeHtml(legacyAbi)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][cab]" value="${escapeHtml(legacyCab)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][cin]" value="${escapeHtml(legacyCin)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][conto]" value="${escapeHtml(legacyConto)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][intestatario]" value="${escapeHtml(legacyIntestatario)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][swift]" value="${escapeHtml(legacySwift)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][cuc]" value="${escapeHtml(legacyCuc)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][filiale]" value="${escapeHtml(legacyFiliale)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][indirizzo]" value="${escapeHtml(legacyIndirizzo)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][cap]" value="${escapeHtml(legacyCap)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][citta]" value="${escapeHtml(legacyCitta)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][provincia]" value="${escapeHtml(legacyProvincia)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][telefono]" value="${escapeHtml(legacyTelefono)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][email]" value="${escapeHtml(legacyEmail)}">
<input type="hidden" name="banche_mapping[${idx}][legacy][pec]" value="${escapeHtml(legacyPec)}">
`;
container.appendChild(row);
if (data) {
populateBancaMappingRow(row, data, legacy);
}
enforceDefaultForRow(row, legacy, codice);
syncBancheJson();
return row;
}
function enforceDefaultForRow(row, legacy, codice) {
if (!row) return;
const tipo = row.querySelector('select[name*="[tipo_conto]"]');
if (tipo && !tipo.value) {
tipo.value = (codice === 'CON') ? 'contanti' : 'bancario';
}
const denominazione = row.querySelector('input[name*="[denominazione_banca]"]');
if (denominazione && !denominazione.value) {
denominazione.value = legacy?.descrizione || (codice === 'CON' ? 'Cassa contanti' : '');
}
const intestazione = row.querySelector('input[name*="[intestazione_conto]"]');
if (intestazione && !intestazione.value) {
intestazione.value = legacy?.intestatario || '';
}
const numero = row.querySelector('input[name*="[numero_conto]"]');
if (numero && !numero.value) {
numero.value = legacy?.conto || codice;
}
const cucInput = row.querySelector('input[name*="[target][cuc]"]');
if (cucInput && !cucInput.value && legacy?.cuc) {
cucInput.value = legacy.cuc;
}
const stato = row.querySelector('select[name*="[stato_conto]"]');
if (stato) {
stato.value = 'attivo';
}
const valuta = row.querySelector('input[name*="[valuta]"]');
if (valuta && !valuta.value) {
valuta.value = 'EUR';
}
const saldo = row.querySelector('input[name*="[saldo_iniziale]"]');
const saldoLegacy = (legacy?.saldo_euro ?? legacy?.saldo);
if (saldo && !saldo.value && saldoLegacy !== undefined && saldoLegacy !== null && saldoLegacy !== '') {
saldo.value = saldoLegacy;
}
const dataSaldo = row.querySelector('input[name*="[data_saldo_iniziale]"]');
if (dataSaldo && !dataSaldo.value && legacy?.data_saldo) {
try {
const d = new Date(legacy.data_saldo);
if (!Number.isNaN(d.getTime())) {
dataSaldo.value = d.toISOString().substring(0, 10);
}
} catch (e) {
dataSaldo.value = legacy.data_saldo;
}
}
const principale = row.querySelector('input[name*="[is_nostro_conto]"]');
if (principale && codice === 'CON') {
principale.checked = true;
}
const filiale = row.querySelector('input[name*="[target][filiale]"]');
if (filiale && !filiale.value && legacy?.filiale) {
filiale.value = legacy.filiale;
}
const indirizzo = row.querySelector('input[name*="[target][indirizzo]"]');
if (indirizzo && !indirizzo.value && legacy?.indirizzo) {
indirizzo.value = legacy.indirizzo;
}
const cap = row.querySelector('input[name*="[target][cap]"]');
if (cap && !cap.value && legacy?.cap) {
cap.value = legacy.cap;
}
const citta = row.querySelector('input[name*="[target][citta]"]');
if (citta && !citta.value && legacy?.citta) {
citta.value = legacy.citta;
}
const provincia = row.querySelector('input[name*="[target][provincia]"]');
if (provincia && !provincia.value && legacy?.provincia) {
provincia.value = legacy.provincia;
}
const telefono = row.querySelector('input[name*="[target][telefono_ufficio]"]');
if (telefono && !telefono.value && legacy?.telefono) {
telefono.value = legacy.telefono;
}
const email = row.querySelector('input[name*="[target][email]"]');
if (email && !email.value && legacy?.email) {
email.value = legacy.email;
}
const pec = row.querySelector('input[name*="[target][pec]"]');
if (pec && !pec.value && legacy?.pec) {
pec.value = legacy.pec;
}
}
function populateBancaMappingRow(row, data, legacyDefaults) {
if (!row || !data) return;
const target = data.target || {};
const tipoSelect = row.querySelector('select[name*="[tipo_conto]"]');
const tipoValue = target.tipo_conto || data.tipo_conto || (row.getAttribute('data-mapping-cod') === 'CON' ? 'contanti' : 'bancario');
if (tipoSelect && tipoValue) tipoSelect.value = tipoValue;
const mapping = {
'[denominazione_banca]': target.denominazione_banca || data.denominazione_banca || data.descrizione_legacy || legacyDefaults?.descrizione || '',
'[intestazione_conto]': target.intestazione_conto || data.intestazione_conto || legacyDefaults?.intestatario || '',
'[numero_conto]': target.numero_conto || data.numero_conto || data.target_cod_cassa || legacyDefaults?.conto || data.codice_legacy || row.getAttribute('data-mapping-cod'),
'[iban]': target.iban || data.iban || data.legacy?.iban || legacyDefaults?.iban || '',
'[bic_swift]': target.bic_swift || data.swift || data.bic_swift || legacyDefaults?.swift || '',
'[abi]': target.abi || data.abi || data.legacy?.abi || legacyDefaults?.abi || '',
'[cab]': target.cab || data.cab || data.legacy?.cab || legacyDefaults?.cab || '',
'[target][cuc]': target.cuc || data.cuc || data.legacy?.cuc || legacyDefaults?.cuc || '',
'[cin]': target.cin || data.cin || data.legacy?.cin || legacyDefaults?.cin || '',
'[valuta]': target.valuta || data.valuta || 'EUR',
'[saldo_iniziale]': target.saldo_iniziale ?? data.saldo_iniziale ?? '',
'[data_saldo_iniziale]': target.data_saldo_iniziale || data.data_saldo_iniziale || '',
'[note]': target.note || data.note || '',
'[target][filiale]': target.filiale || data.filiale || data.target_filiale || legacyDefaults?.filiale || '',
'[target][indirizzo]': target.indirizzo || data.indirizzo || data.target_indirizzo || legacyDefaults?.indirizzo || '',
'[target][cap]': target.cap || data.cap || data.target_cap || legacyDefaults?.cap || '',
'[target][citta]': target.citta || data.citta || data.target_citta || legacyDefaults?.citta || '',
'[target][provincia]': target.provincia || data.provincia || data.target_provincia || legacyDefaults?.provincia || '',
'[target][telefono_ufficio]': target.telefono_ufficio || data.telefono_ufficio || data.telefono || legacyDefaults?.telefono || '',
'[target][email]': target.email || data.email || data.target_email || legacyDefaults?.email || '',
'[target][pec]': target.pec || data.pec || data.target_pec || legacyDefaults?.pec || ''
};
Object.entries(mapping).forEach(([suffix, value]) => {
const selector = suffix === '[note]'
? `textarea[name*="${suffix}"]`
: `input[name*="${suffix}"]`;
const el = row.querySelector(selector);
if (el !== null && value !== undefined && value !== null) {
if (el.type === 'date' && typeof value === 'string' && value.includes('/')) {
const [d, m, y] = value.split(/[\/\-]/);
if (d && m && y) {
const iso = `${y.length === 2 ? '20' + y : y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
el.value = iso;
} else {
el.value = value;
}
} else {
el.value = String(value);
}
}
});
const stato = row.querySelector('select[name*="[stato_conto]"]');
if (stato) {
stato.value = target.stato_conto || data.stato_conto || 'attivo';
}
const principale = row.querySelector('input[name*="[is_nostro_conto]"]');
if (principale) {
const checked = target.is_nostro_conto ?? data.is_nostro_conto ?? data.target_is_nostro_conto ?? (row.getAttribute('data-mapping-cod') === 'CON');
principale.checked = Boolean(checked);
}
}
function removeBancaMappingRow(trigger) {
const row = trigger?.closest('.banca-mapping-row');
const container = row?.parentElement;
if (row) row.remove();
if (container && !container.querySelector('.banca-mapping-row')) {
renderBanchePlaceholder(container);
}
syncBancheJson();
}
function ensureMandatoryConMapping() {
if (__legacyCasse.find(c => c.codice === 'CON')) {
const container = document.getElementById('banche-mapping-rows');
if (container && !container.querySelector('[data-mapping-cod="CON"]')) {
addBancaMapping('CON');
}
const conRow = container?.querySelector('[data-mapping-cod="CON"]');
if (conRow) {
const tipo = conRow.querySelector('select[name*="[tipo_conto]"]');
if (tipo) tipo.value = 'contanti';
const principale = conRow.querySelector('input[name*="[is_nostro_conto]"]');
if (principale) principale.checked = true;
}
syncBancheJson();
}
}
function collectValue(row, selector) {
const el = row.querySelector(selector);
if (!el) return null;
const value = el.value;
if (typeof value === 'string') {
const trimmed = value.trim();
return trimmed === '' ? null : trimmed;
}
return value ?? null;
}
function syncBancheJson() {
const container = document.getElementById('banche-mapping-rows');
const rows = [...(container?.querySelectorAll('.banca-mapping-row') || [])];
const normalized = rows.map(row => {
const legacy = {
descrizione: collectValue(row, 'input[name*="[legacy][descrizione]"]'),
iban: collectValue(row, 'input[name*="[legacy][iban]"]'),
abi: collectValue(row, 'input[name*="[legacy][abi]"]'),
cab: collectValue(row, 'input[name*="[legacy][cab]"]'),
cin: collectValue(row, 'input[name*="[legacy][cin]"]'),
cuc: collectValue(row, 'input[name*="[legacy][cuc]"]'),
conto: collectValue(row, 'input[name*="[legacy][conto]"]'),
intestatario: collectValue(row, 'input[name*="[legacy][intestatario]"]'),
swift: collectValue(row, 'input[name*="[legacy][swift]"]')
};
Object.keys(legacy).forEach(key => { if (legacy[key] === null) delete legacy[key]; });
const target = {
tipo_conto: collectValue(row, 'select[name*="[tipo_conto]"]'),
denominazione_banca: collectValue(row, 'input[name*="[denominazione_banca]"]'),
intestazione_conto: collectValue(row, 'input[name*="[intestazione_conto]"]'),
numero_conto: collectValue(row, 'input[name*="[numero_conto]"]'),
iban: collectValue(row, 'input[name*="[target][iban]"]'),
bic_swift: collectValue(row, 'input[name*="[bic_swift]"]'),
abi: collectValue(row, 'input[name*="[abi]"]'),
cab: collectValue(row, 'input[name*="[cab]"]'),
cuc: collectValue(row, 'input[name*="[target][cuc]"]'),
cin: collectValue(row, 'input[name*="[cin]"]'),
valuta: collectValue(row, 'input[name*="[valuta]"]'),
saldo_iniziale: collectValue(row, 'input[name*="[saldo_iniziale]"]'),
data_saldo_iniziale: collectValue(row, 'input[name*="[data_saldo_iniziale]"]'),
note: collectValue(row, 'textarea[name*="[note]"]'),
stato_conto: collectValue(row, 'select[name*="[stato_conto]"]'),
filiale: collectValue(row, 'input[name*="[target][filiale]"]'),
indirizzo: collectValue(row, 'input[name*="[target][indirizzo]"]'),
cap: collectValue(row, 'input[name*="[target][cap]"]'),
citta: collectValue(row, 'input[name*="[target][citta]"]'),
provincia: collectValue(row, 'input[name*="[target][provincia]"]'),
telefono_ufficio: collectValue(row, 'input[name*="[target][telefono_ufficio]"]'),
email: collectValue(row, 'input[name*="[target][email]"]'),
pec: collectValue(row, 'input[name*="[target][pec]"]'),
is_nostro_conto: row.querySelector('input[name*="[is_nostro_conto]"]')?.checked || false
};
Object.keys(target).forEach(key => {
const value = target[key];
if (key === 'is_nostro_conto') {
if (!value) delete target[key];
return;
}
if (value === null || value === undefined || value === '') {
delete target[key];
}
});
const entry = {
codice_legacy: row.getAttribute('data-mapping-cod') || null,
descrizione_legacy: row.getAttribute('data-descrizione-legacy') || null,
tipo_conto: target.tipo_conto || 'bancario',
legacy: Object.keys(legacy).length ? legacy : null,
target
};
entry.iban = target.iban || legacy.iban || null;
entry.swift = target.bic_swift || legacy.swift || null;
entry.cuc = target.cuc || legacy.cuc || null;
entry.numero_conto = target.numero_conto || null;
entry.is_nostro_conto = Boolean(target.is_nostro_conto);
return entry;
});
const hidden = document.getElementById('banche_mapping_json'); if (hidden) hidden.value = JSON.stringify(normalized);
}
async function saveBancheMapping(e) {
e.preventDefault();
syncBancheJson();
const form = e.target;
const fd = new FormData(form);
try {
const res = await fetch(R('mappingSave'), { method: 'POST', headers: { 'X-CSRF-TOKEN': CSRF }, body: fd });
if (res.redirected) { window.location.href = res.url; return false; }
alert(res.ok ? 'Mapping banche salvato' : 'Errore salvataggio banche');
} catch (err) { alert('Errore rete'); }
return false;
}
// ===== Unità (singolo_anno.mdb / condomin) =====
let __legacyYears = [];
let __currentUnitaYearIdx = 0;
async function loadLegacyYears() {
const nav = document.getElementById('unita-year-nav');
const label = document.getElementById('unita-year-label');
const badges = document.getElementById('years-badges');
if (!nav || !label) return;
try {
const url = R('years') + '?legacy_code=' + encodeURIComponent(LEGACY);
const res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status);
const js = await res.json();
__legacyYears = Array.isArray(js.years) ? js.years.map(y => String(y)) : [];
if (__legacyYears.length) {
nav.style.display = 'flex';
__currentUnitaYearIdx = __legacyYears.length - 1;
label.textContent = __legacyYears[__currentUnitaYearIdx];
const l2 = document.getElementById('unita-anno-label-2'); if (l2) l2.textContent = label.textContent;
// Render years badges if container exists
if (badges) {
badges.innerHTML = '<span>Anni:</span>' + __legacyYears.map(y => '<span class="netgescon-badge netgescon-badge-light">' + escapeHtml(y) + '</span>').join('');
}
// Pre-set Staging Loader base URL (without specific file)
try { const btn = document.getElementById('btn-unita-loader'); if (btn && R('archiveUi')) { btn.href = R('archiveUi'); } } catch (_) { }
try { await refreshUnitaPreview(); } catch (e) { }
} else {
// Nessun anno rilevato: mostra la nav con placeholder e prova fallback su staging (senza anno)
nav.style.display = 'flex';
label.textContent = '—';
const l2 = document.getElementById('unita-anno-label-2'); if (l2) l2.textContent = '—';
if (badges) { badges.innerHTML = ''; }
try { const btn = document.getElementById('btn-unita-loader'); if (btn && R('archiveUi')) { btn.href = R('archiveUi'); } } catch (_) { }
try { await refreshUnitaPreview(); } catch (e) { }
}
} catch (e) {
// In caso di errore nel recupero anni, visualizza comunque la nav e tenta preview fallback
nav.style.display = 'flex';
label.textContent = '—';
const l2 = document.getElementById('unita-anno-label-2'); if (l2) l2.textContent = '—';
if (badges) { badges.innerHTML = ''; }
try { const btn = document.getElementById('btn-unita-loader'); if (btn && R('archiveUi')) { btn.href = R('archiveUi'); } } catch (_) { }
try { await refreshUnitaPreview(); } catch (_) { }
}
}
function browseUnitaYear(delta) {
if (!__legacyYears.length) return;
__currentUnitaYearIdx = Math.min(__legacyYears.length - 1, Math.max(0, __currentUnitaYearIdx + (delta || 0)));
const label = document.getElementById('unita-year-label'); if (label) label.textContent = __legacyYears[__currentUnitaYearIdx] || '—';
const l2 = document.getElementById('unita-anno-label-2'); if (l2) l2.textContent = label?.textContent || '—';
}
async function syncUnitaCurrentYear() {
const year = __legacyYears[__currentUnitaYearIdx];
if (!year) { alert('Nessun anno disponibile.'); return; }
if (!confirm('Sincronizzare le unità per l\'anno ' + year + '? L\'operazione è idempotente.')) return;
try {
const payload = { legacy_code: LEGACY, steps: ['unita', 'soggetti', 'diritti'], anno: Number(year), dry_run: false };
const res = await fetch(R('importUnita'), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF }, body: JSON.stringify(payload) });
const js = await res.json().catch(() => ({}));
if (!res.ok || js.ok === false) {
const failing = (js.results || []).filter(r => r && r.ok === false).map(r => r.step).join(', ');
alert('Sync Unità fallita: ' + (js.error || failing || res.status));
return;
}
alert('Sync Unità eseguita su ' + year + ': ' + (js.results || []).map(r => r.step + ':' + (r.ok ? 'OK' : 'FAIL')).join(' | '));
} catch (e) { alert('Errore rete sync unità'); }
}
async function refreshUnitaPreview() {
const years = __legacyYears || [];
const idx = __currentUnitaYearIdx || 0;
const year = years[idx];
const lbl2 = document.getElementById('unita-anno-label-2'); if (lbl2) lbl2.textContent = year || '—';
const head = document.getElementById('unita-legacy-head'); const body = document.getElementById('unita-legacy-body');
if (!LEGACY || !head || !body) { if (body) body.innerHTML = '<tr><td class="text-xs text-gray-500">Dati non disponibili.</td></tr>'; return; }
body.innerHTML = '<tr><td class="text-xs text-gray-500">Caricamento…</td></tr>';
try {
let url = R('unitaYear') + '?legacy_code=' + encodeURIComponent(LEGACY) + '&limit=50';
if (year) { url += '&anno=' + encodeURIComponent(year); }
const res = await fetch(url);
const js = await res.json().catch(() => ({}));
if (!res.ok || js.ok === false) { head.innerHTML = '<th>—</th>'; body.innerHTML = '<tr><td class="text-xs text-danger">Errore o nessun dato</td></tr>'; return; }
renderUnitaPreview(js.headers || [], js.rows || []);
} catch (e) { head.innerHTML = '<th>—</th>'; body.innerHTML = '<tr><td class="text-xs text-danger">Errore rete</td></tr>'; }
}
function renderUnitaPreview(headers, rows) {
const head = document.getElementById('unita-legacy-head'); const body = document.getElementById('unita-legacy-body');
if (!head || !body) { return; }
const shown = (headers || []).filter(h => !/^memo$/i.test(h)).slice(0, 12);
if (!shown.length) { head.innerHTML = '<th>—</th>'; body.innerHTML = '<tr><td class="text-xs text-gray-500">Nessun dato.</td></tr>'; return; }
head.innerHTML = shown.map(h => '<th>' + escapeHtml(h) + '</th>').join('');
if (!rows || !rows.length) { body.innerHTML = '<tr><td colspan="' + shown.length + '" class="text-xs text-gray-500">Nessuna riga.</td></tr>'; return; }
body.innerHTML = rows.map(r => '<tr>' + shown.map(h => '<td class="text-xs legacy-value" data-field-value="' + escapeHtml(h) + '">' + escapeHtml(String(r[h] ?? '')) + '</td>').join('') + '</tr>').join('');
}
// ===== Import & actions =====
function handleImportConfirm(form, blocked, legacyCode) {
if (blocked) { return false; }
if (!legacyCode) { return confirm('Importare lo STABILE (solo anagrafica)?'); }
return confirm('Importare lo STABILE (solo anagrafica) per il legacy ' + legacyCode + '?');
}
function saveMapping() { return true; }
function resetMapping() { document.querySelectorAll('#mapping-form select').forEach(s => s.value = ''); }
async function giCreaGestione() {
try {
const anno = parseInt(document.getElementById('giAnno').value || (new Date()).getFullYear());
const tipo = document.getElementById('giTipo').value;
const legacy = document.getElementById('giLegacy').value || '';
const mesi = {};
document.querySelectorAll('.giMese').forEach(c => { if (c.checked) { mesi[c.dataset.mese] = 'on'; } });
const url = CFG.routes.stabiliGestioneStore || '#';
if (!url || url === '#') throw new Error('Nessuno stabile selezionato');
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF }, body: JSON.stringify({ anno_gestione: anno, tipo_gestione: tipo, codice_archivio_legacy: legacy, mesi }) });
if (!res.ok) throw new Error('Errore durante la creazione');
window.location.href = (CFG.routes.stabileShow || '') + '#tab-gestioni';
} catch (e) { alert(e.message || 'Errore'); }
}
async function applyMappingImport(legacyCode, stabileId) {
if (!legacyCode || !stabileId) return;
if (!confirm('Applicare il mapping (conti + gestioni) allo stabile selezionato?')) return;
try {
const payload = { legacy_code: legacyCode, stabile_id: stabileId };
const res = await fetch(R('applyMapping'), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF }, body: JSON.stringify(payload) });
const js = await res.json().catch(() => ({}));
if (!res.ok || !js.ok) { alert('Errore applicazione mapping: ' + (js.error || res.status)); return; }
alert('Mapping applicato. Conti creati: ' + js.conti.created + ' (upd ' + js.conti.updated + '). Gestioni create: ' + js.gestioni.created + ' (upd ' + js.gestioni.updated + ').');
setTimeout(() => { if (CFG.routes.stabileShow) { window.location.href = CFG.routes.stabileShow + '#tab-gestioni'; } }, 600);
} catch (e) { alert('Errore rete / runtime'); }
}
async function quickSaveAdminCf() {
try {
const url = CFG.routes.stabileAdminQuick || '';
if (!url) { alert('Stabile non selezionato'); return; }
const cf = (document.getElementById('quickAdminCf')?.value || '').trim();
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF }, body: JSON.stringify({ cod_fisc_amministratore: cf }) });
const js = await res.json().catch(() => ({}));
if (!res.ok || js.ok === false) { alert('Errore salvataggio CF amministratore'); return; }
alert('CF amministratore aggiornato');
} catch (e) { alert('Errore rete'); }
}
async function quickImportStabile(legacy) {
if (!legacy) return; if (!confirm('Eseguire import rapido per lo stabile ' + legacy + '?')) return;
try {
const res = await fetch(R('quickStabile'), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF }, body: JSON.stringify({ legacy_code: legacy }) });
const js = await res.json().catch(() => ({}));
if (!res.ok || !js.ok) { alert('Errore import rapido: ' + (js.error || res.status)); return; }
alert('Import rapido OK: ' + (js.created ? 'Creato' : 'Aggiornato') + ' ID ' + js.stabile_id);
setTimeout(() => window.location.reload(), 600);
} catch (e) { alert('Errore rete import rapido'); }
}
async function microImportStabile(legacy) {
if (!legacy) return; if (!confirm('Eseguire MICRO import per lo stabile ' + legacy + '?')) return;
try {
const res = await fetch(R('microImportStabile'), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF }, body: JSON.stringify({ legacy_code: legacy }) });
const js = await res.json().catch(() => ({}));
if (!res.ok || !js.ok) { alert('Errore micro import: ' + (js.error || res.status)); return; }
alert('Micro import OK: ' + (js.created ? 'Creato' : 'Aggiornato') + ' ID ' + js.stabile_id + '\nFonte: ' + (js.source || '-'));
setTimeout(() => window.location.reload(), 600);
} catch (e) { alert('Errore rete micro import'); }
}
// Expose minimal API for inline handlers in markup
window.switchSegment = switchSegment;
window.syncMappingSearch = syncMappingSearch;
window.setMappingFilter = setMappingFilter;
window.applyPreset = applyPreset;
window.updatePresetMeta = updatePresetMeta;
window.updateMappingPreviews = updateMappingPreviews;
window.promptAddField = promptAddField;
window.browseSample = function (delta) { return browseSample(delta); };
window.loadSample = function (idx) { return loadSample(idx); };
window.syncLegacyPreview = syncLegacyPreview;
window.__toggleInlineCreate = function (trigger, show) { toggleInlineCreate(trigger, show !== false); };
window.handleImportConfirm = handleImportConfirm;
window.saveMapping = saveMapping;
window.resetMapping = resetMapping;
window.populateGestioneRatesFromData = populateGestioneRatesFromData;
window.browseGestioneYear = browseGestioneYear;
window.loadGestioneYearData = loadGestioneYearData;
window.loadLegacyCasse = loadLegacyCasse;
window.renderLegacyCasse = renderLegacyCasse;
window.filterLegacyCasse = filterLegacyCasse;
window.addBancaMapping = addBancaMapping;
window.removeBancaMappingRow = removeBancaMappingRow;
window.syncBancheJson = syncBancheJson;
window.saveBancheMapping = saveBancheMapping;
window.loadLegacyYears = loadLegacyYears;
window.browseUnitaYear = browseUnitaYear;
window.syncUnitaCurrentYear = syncUnitaCurrentYear;
window.refreshUnitaPreview = refreshUnitaPreview;
window.giCreaGestione = giCreaGestione;
window.applyMappingImport = applyMappingImport;
window.quickSaveAdminCf = quickSaveAdminCf;
window.quickImportStabile = quickImportStabile;
window.microImportStabile = microImportStabile;
window.addManualValueRow = addManualValueRow;
window.syncManualValueRow = syncManualValueRow;
window.removeManualValueRow = removeManualValueRow;
window.escapeHtml = escapeHtml;
// Init on DOM ready
document.addEventListener('DOMContentLoaded', function () {
// Export target fields
window.__targetFields = Array.isArray(CFG.targetFields) ? CFG.targetFields : [];
initializeManualValueRows();
// Presets
try {
const sel = document.getElementById('preset-select');
if (sel) {
sel.value = CFG.savedPresetKey || (CFG.defaultPreset || 'stabili_full');
document.getElementById('preset_key').value = sel.value || '';
updatePresetMeta();
if (!CFG.savedPresetKey) applyPreset();
}
} catch (e) { }
// Initial sample
browseSample(0);
// Init filter state
setMappingFilter('all');
// Init years and casse if legacy selected
if (LEGACY) {
loadLegacyYears();
loadLegacyCasse();
(async () => {
try {
const hasYears = Array.isArray(CFG.availableYears) && CFG.availableYears.length;
if (!hasYears) {
const res = await fetch(R('years') + '?legacy_code=' + encodeURIComponent(LEGACY));
if (res.ok) {
const js = await res.json();
const yrs = Array.isArray(js.years) ? js.years : [];
if (yrs.length) {
__gestioniYears = yrs;
__gestioniYearIndex = Math.max(0, __gestioniYears.length - 1);
updateGestioneYearUI();
try { await loadGestioneYearData(); } catch (e) { }
}
}
} else {
try { await loadGestioneYearData(); } catch (e) { }
}
} catch (e) { }
})();
}
// Progress bar feedback on import submits
document.querySelectorAll('.js-import-form').forEach(f => {
f.addEventListener('submit', () => {
const box = document.getElementById('import-progress');
if (box) box.classList.remove('d-none');
let p = 15; const bar = document.getElementById('import-progress-bar');
const timer = setInterval(() => { p = Math.min(90, p + Math.random() * 7); if (bar) bar.style.width = p + '%'; }, 400);
f.querySelectorAll('button[type="submit"]').forEach(b => b.setAttribute('disabled', 'disabled'));
}, { once: true });
});
// Year UI update
(function () { const lbl = document.getElementById('gestione-year-label'); if (lbl) { updateGestioneYearUI(); } })();
});
// Diagnostic: capture JS errors to help debug
window.addEventListener('error', function (ev) {
try {
const boxId = 'gescon-mapping-diagnostics';
let box = document.getElementById(boxId);
if (!box) {
box = document.createElement('div');
box.id = boxId;
box.style.cssText = 'position:fixed;bottom:6px;right:6px;z-index:9999;background:#fee;border:1px solid #f99;padding:6px 10px;font:12px monospace;max-width:420px;max-height:180px;overflow:auto;';
box.innerHTML = '<strong>JS Error</strong><br>';
document.body.appendChild(box);
}
box.innerHTML += (ev.message + ' @' + (ev.filename || '') + ':' + ev.lineno + '<br>');
} catch (e) { }
});
})();