netgescon-day0/resources/views/admin/gescon-import/tabs/unita.blade.php

523 lines
33 KiB
PHP

@php
$importState = is_array($unitaImportState ?? null) ? $unitaImportState : [];
$lastFilters = is_array($importState['last_filters'] ?? null) ? $importState['last_filters'] : [];
$selectedTargetStabileId = $importState['target_stabile_id'] ?? null;
$selectedTargetStabileCode = $importState['target_stabile_code'] ?? null;
$selectedTargetAdminId = $importState['target_admin_id'] ?? ($selectedAmministratoreId ?? null);
$admins = collect($amministratoriDisponibili ?? []);
$splitModeDefault = $lastFilters['split_mode'] ?? 'palazzine';
@endphp
<div class="netgescon-card">
<div class="netgescon-card-header flex justify-between items-center">
<h3 class="card-title mb-0"><i class="mdi mdi-home-variant me-2"></i> Unità Immobiliari</h3>
<div class="flex gap-2">
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-secondary" onclick="location.reload()">
<i class="mdi mdi-refresh"></i> Aggiorna
</button>
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-success" onclick="window.location.href='/admin/gescon-import/export/unita'">
<i class="mdi mdi-file-excel"></i> Esporta
</button>
<a class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-success" href="{{ route('admin.gescon-import.unita-mapping', request()->query()) }}">
<i class="mdi mdi-table-link"></i> Mapping Unità
</a>
<a class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-primary" href="{{ route('admin.gescon-import.relations', array_merge(request()->query(), ['segment'=>'unita'])) }}">
<i class="mdi mdi-vector-link"></i> Relations
</a>
@can('gescon-import.execute')
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-primary" onclick="window.showImportUnitaModal && window.showImportUnitaModal()">
<i class="mdi mdi-database-arrow-down"></i> Importa Unità
</button>
@endcan
</div>
</div>
<div class="netgescon-card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<thead class="table-light">
<tr>
<th>Codice</th>
<th>Stabile</th>
<th>Scala/Piano/Interno</th>
<th>Millesimi legacy</th>
<th>Tabelle millesimali collegate</th>
<th>Creato</th>
</tr>
</thead>
<tbody>
@forelse(($unita ?? []) as $u)
<tr>
<td><span class="netgescon-badge netgescon-badge-secondary">{{ $u->codice_unita ?? 'N/D' }}</span></td>
<td>{{ $u->stabile->denominazione ?? '—' }}</td>
<td>
@if($u->scala) <span class="me-2">Scala {{ $u->scala }}</span> @endif
@if($u->piano) <span class="me-2">Piano {{ $u->piano }}</span> @endif
@if($u->interno) <span>Int. {{ $u->interno }}</span> @endif
</td>
<td>
@php
$mm = ($u->millesimi_generali ?? 0) + ($u->millesimi_riscaldamento ?? 0);
@endphp
@if($mm > 0)
<span class="netgescon-badge netgescon-badge-primary">{{ number_format($mm, 0) }}</span>
@else
<span class="netgescon-badge netgescon-badge-warning">N/D</span>
@endif
</td>
<td>
@php
$dettagliMillesimi = collect($u->dettagliMillesimi ?? []);
$limitMillesimi = 3;
$extraMillesimi = max($dettagliMillesimi->count() - $limitMillesimi, 0);
@endphp
@if($dettagliMillesimi->isNotEmpty())
<div class="space-y-2">
@foreach($dettagliMillesimi->take($limitMillesimi) as $dett)
@php
$tabella = $dett->tabellaMillesimale;
$code = $tabella->codice_tabella ?? $tabella->nome_tabella ?? 'TAB';
$label = $tabella->denominazione ?? $tabella->nome_tabella_millesimale ?? 'Tabella millesimale';
$percentuale = method_exists($dett, 'getPercentualeAttribute') ? $dett->percentuale : null;
@endphp
<div class="border border-slate-200 rounded-md p-2 bg-slate-50">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-700">{{ $code }}</div>
<div class="text-xs text-slate-500">{{
\Illuminate\Support\Str::limit($label, 40)
}}</div>
</div>
<div class="text-right">
<span class="netgescon-badge netgescon-badge-primary">{{ number_format($dett->millesimi ?? 0, 3) }}</span>
@if(!is_null($percentuale))
<div class="text-[11px] text-slate-500">{{ number_format($percentuale, 2) }}%</div>
@endif
</div>
</div>
@if($tabella)
<div class="mt-2 flex items-center justify-between text-[11px] text-slate-500">
<span>Totale: {{ number_format($tabella->totale_millesimi ?? 0, 0) }}</span>
<a href="{{ route('admin.tabelle-millesimali.show', $tabella) }}" class="text-primary-600 hover:underline" target="_blank" rel="noreferrer">Apri tabella</a>
</div>
@endif
</div>
@endforeach
</div>
@if($extraMillesimi > 0)
<div class="text-xs text-slate-500 mt-2">+{{ $extraMillesimi }} altr{{ $extraMillesimi === 1 ? 'a tabella' : 'e tabelle' }} collegate</div>
@endif
@else
<div class="text-xs text-slate-500">
Nessuna tabella collegata.
@if($u->stabile)
<a href="{{ route('admin.stabili.show', $u->stabile) }}#tabelle-millesimali" class="text-primary-600 hover:underline">Gestisci tabelle</a>
@endif
</div>
@endif
</td>
<td><small>{{ optional($u->created_at)->format('d/m/Y H:i') }}</small></td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-gray-500 py-3">Nessuna unità importata</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@if(isset($unita) && method_exists($unita, 'hasPages') && $unita->hasPages())
<div class="netgescon-card-footer flex justify-end">
{{ $unita->links() }}
</div>
@endif
</div>
@can('gescon-import.execute')
<!-- Modal Import Unità -->
<div id="import-unita-modal" class="fixed inset-0 bg-black/40 items-center justify-center p-4 hidden" style="z-index: 60;">
<div class="bg-white w-full max-w-2xl rounded-lg shadow-lg">
<div class="px-4 py-3 border-b flex items-center justify-between">
<h4 class="text-lg font-semibold">Importa Unità da GESCON (condomin)</h4>
<button class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-secondary" onclick="window.hideImportUnitaModal && window.hideImportUnitaModal()">
<i class="mdi mdi-close"></i>
</button>
</div>
<div class="p-4">
<form id="form-import-unita" onsubmit="return false;">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Codice Legacy Stabile</label>
<input type="text" name="legacy_code" id="import-legacy-code" class="netgescon-input w-full" placeholder="es. 99.00" list="legacy-codes-list" required value="{{ $legacyCode ?? '' }}">
<datalist id="legacy-codes-list">
@foreach(($legacyPreview ?? []) as $legacy)
<option value="{{ $legacy['legacy_code'] ?? '' }}">{{ ($legacy['legacy_code'] ?? '') }} {{ $legacy['denominazione'] ? ('- ' . $legacy['denominazione']) : '' }}</option>
@endforeach
</datalist>
<small class="text-slate-500">Scegli il codice della cartella stabile (cartella/0021, ecc.).</small>
</div>
<div>
<label class="block text-sm font-medium mb-1">Anno (opzionale)</label>
<input type="number" name="anno" id="import-anno" class="netgescon-input w-full" placeholder="es. 2024" list="import-anno-list" value="{{ $lastFilters['anno'] ?? '' }}">
<datalist id="import-anno-list"></datalist>
<small class="text-slate-500">Se impostato, viene usato il file <code>singolo_anno.mdb</code> di quell'anno.</small>
</div>
<div>
<label class="block text-sm font-medium mb-1">Amministratore destinatario</label>
<select id="import-admin" class="netgescon-input w-full">
<option value="">Usa amministratore corrente</option>
@foreach($admins as $admin)
@php
$adminId = is_object($admin) ? ($admin->id ?? null) : ($admin['id'] ?? null);
$adminNome = trim(($admin->nome ?? $admin['nome'] ?? '') . ' ' . ($admin->cognome ?? $admin['cognome'] ?? ''));
$adminStudio = $admin->denominazione_studio ?? $admin['denominazione_studio'] ?? null;
$adminLabel = trim($adminNome !== '' ? $adminNome : ($adminStudio ?? ('Amministratore #' . $adminId)));
if ($adminStudio && !str_contains($adminLabel, $adminStudio)) {
$adminLabel .= ' · ' . $adminStudio;
}
@endphp
@if($adminId)
<option value="{{ $adminId }}" data-name="{{ $adminLabel }}" @selected((int)($selectedTargetAdminId ?? 0) === (int)$adminId)>{{ $adminLabel }}</option>
@endif
@endforeach
</select>
<small class="text-slate-500">Gli step Soggetti e Diritti assoceranno i dati all'amministratore selezionato.</small>
</div>
<div>
<label class="block text-sm font-medium mb-1">Stabile di destinazione</label>
<select id="import-target-stabile" class="netgescon-input w-full">
<option value="" data-name="creazione automatica">Crea/aggiorna stabile in base al codice legacy</option>
@foreach(($stabiliList ?? collect()) as $stab)
@php
$labelDen = $stab->denominazione ?? ('Stabile #' . $stab->id);
$labelCode = $stab->codice_stabile ?? '—';
@endphp
<option value="{{ $stab->id }}" data-code="{{ $labelCode }}" data-name="{{ $labelDen }}" @selected((int)($selectedTargetStabileId ?? 0) === (int)($stab->id ?? 0))>
{{ $labelDen }} ({{ $labelCode }})
</option>
@endforeach
</select>
<small class="text-slate-500">Seleziona uno stabile esistente oppure lascia vuoto per crearne uno nuovo.</small>
</div>
<div>
<label class="block text-sm font-medium mb-1">Codice stabile dominio (override)</label>
<input type="text" id="import-target-stabile-code" class="netgescon-input w-full" placeholder="es. GES00148" value="{{ $selectedTargetStabileCode ?? '' }}">
<small class="text-slate-500">Override per generare <code>codice_unita</code> coerente in NetGescon.</small>
</div>
<div>
<label class="block text-sm font-medium mb-1">Divisione palazzine (split-mode)</label>
<select id="import-split-mode" class="netgescon-input w-full">
<option value="palazzine" @selected($splitModeDefault === 'palazzine')>Scala Palazzine (default)</option>
<option value="none" @selected($splitModeDefault === 'none')>Nessuna divisione (unico stabile)</option>
<option value="stabili-per-scala" @selected($splitModeDefault === 'stabili-per-scala')>Crea stabili separati per scala</option>
</select>
<small class="text-slate-500">Controlla come suddividere le unità: palazzine per scala, unico stabile oppure nuovo stabile per scala.</small>
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium mb-1">Step pipeline</label>
<div class="flex flex-wrap gap-3">
<label class="inline-flex items-center gap-2"><input type="checkbox" name="steps[]" value="unita" checked> <span>Unità</span></label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="steps[]" value="soggetti" @checked(in_array('soggetti', $lastFilters['steps'] ?? []))> <span>Soggetti (Anagrafica Unica)</span></label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="steps[]" value="diritti" @checked(in_array('diritti', $lastFilters['steps'] ?? []))> <span>Diritti (quote proprietario/inquilino)</span></label>
</div>
</div>
<div>
<label class="block text-sm font-medium mb-1">Limite record (opzionale)</label>
<input type="number" name="limit" id="import-limit" class="netgescon-input w-full" placeholder="ad es. 200" value="{{ $lastFilters['limit'] ?? '' }}">
</div>
<div>
<label class="block text-sm font-medium mb-1">Scala (filtro opzionale)</label>
<input type="text" name="scala" id="import-scala" class="netgescon-input w-full" placeholder="es. A" value="{{ $lastFilters['scala'] ?? '' }}">
<small class="text-slate-500">Importa solo le unità della scala indicata.</small>
</div>
<div>
<label class="block text-sm font-medium mb-1">Piano minimo / massimo</label>
<div class="flex gap-2">
<input type="number" name="piano_min" id="import-piano-min" class="netgescon-input w-full" placeholder="min" value="{{ $lastFilters['piano_min'] ?? '' }}">
<input type="number" name="piano_max" id="import-piano-max" class="netgescon-input w-full" placeholder="max" value="{{ $lastFilters['piano_max'] ?? '' }}">
</div>
<small class="text-slate-500">Lascia vuoto per includere tutti i piani.</small>
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium mb-1">Pertinenze</label>
<select id="import-only-pertinenze" class="netgescon-input w-full">
@php
$onlyPertValue = $lastFilters['only_pertinenze'] ?? '';
@endphp
<option value="" @selected($onlyPertValue === '' || $onlyPertValue === null)>Qualsiasi (incluso appartamenti)</option>
<option value="any" @selected($onlyPertValue === 'any')>Solo pertinenze (qualsiasi)</option>
<option value="cantina" @selected($onlyPertValue === 'cantina')>Solo cantine</option>
<option value="box" @selected($onlyPertValue === 'box')>Solo box</option>
<option value="posto_auto" @selected($onlyPertValue === 'posto_auto')>Solo posti auto</option>
<option value="soffitta" @selected($onlyPertValue === 'soffitta')>Solo soffitte</option>
<option value="locale_tecnico" @selected($onlyPertValue === 'locale_tecnico')>Solo locali tecnici</option>
</select>
<small class="text-slate-500">Limita l'import alle tipologie indicate.</small>
</div>
<div class="md:col-span-2">
<div class="bg-slate-50 border border-slate-200 rounded p-3">
<div class="flex items-center justify-between gap-2">
<strong class="text-sm text-slate-700">Flusso dati stabile <span id="data-flow-legacy">—</span></strong>
<span class="text-xs text-slate-500">Modalità palazzine: <span id="data-flow-split-mode">—</span></span>
</div>
<div class="mt-2 text-xs text-slate-600 space-y-2">
<div>1. Le unità vengono create/aggiornate in <code>unita_immobiliari</code> per <span id="data-flow-target-name">—</span> (<span id="data-flow-target-code">—</span>).</div>
<div>2. I soggetti sono normalizzati in Anagrafica Unica (<code>soggetti</code>) per l'amministratore <span id="data-flow-admin">Amministratore corrente</span>.</div>
<div>3. Proprietari e inquilini vengono collegati alle unità tramite <code>proprieta</code> con quote di possesso provenienti dal foglio condomin.</div>
</div>
</div>
</div>
</div>
<div class="mt-6 space-y-3">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
<div class="text-sm text-slate-500" id="data-flow-summary-text">
Seleziona codice legacy, amministratore e stabile di destinazione per preparare la prova d'importazione.
</div>
<div class="flex flex-wrap gap-2 justify-end">
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-secondary" id="btn-open-mapping">
<i class="mdi mdi-table-link"></i> Apri mappatura
</button>
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-primary" id="btn-run-dryrun">
<i class="mdi mdi-play-circle-outline"></i> Dry-run di verifica
</button>
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-success" id="btn-run-import">
<i class="mdi mdi-play-circle"></i> Importa e associa
</button>
</div>
</div>
<div class="text-xs text-slate-500">Il percorso MDB viene dedotto automaticamente dall'anno selezionato; puoi sovrascriverlo dalle impostazioni avanzate se necessario.</div>
</div>
</form>
<div id="import-output" class="mt-4 hidden">
<div class="text-sm text-slate-600 mb-2">Anno selezionato: <span id="import-anno-shown"></span></div>
<pre id="import-log" class="bg-gray-900 text-green-200 text-xs p-3 rounded max-h-64 overflow-auto"></pre>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function(){
const modal = document.getElementById('import-unita-modal');
const form = document.getElementById('form-import-unita');
const btnDry = document.getElementById('btn-run-dryrun');
const btnImport = document.getElementById('btn-run-import');
const btnMapping = document.getElementById('btn-open-mapping');
const outBox = document.getElementById('import-output');
const outLog = document.getElementById('import-log');
const targetSelect = document.getElementById('import-target-stabile');
const targetCodeInput = document.getElementById('import-target-stabile-code');
const adminSelect = document.getElementById('import-admin');
const splitModeSelect = document.getElementById('import-split-mode');
const legacyInput = document.getElementById('import-legacy-code');
const csrf = document.querySelector('meta[name="csrf-token"]')?.content || '';
const routeUrl = "{{ route('admin.gescon-import.import-unita') }}";
const yearsUrlBase = "{{ route('admin.gescon-import.years') }}";
const mappingBaseUrl = "{{ route('admin.gescon-import.unita-mapping') }}";
const dataFlowLegacy = document.getElementById('data-flow-legacy');
const dataFlowTargetName = document.getElementById('data-flow-target-name');
const dataFlowTargetCode = document.getElementById('data-flow-target-code');
const dataFlowAdmin = document.getElementById('data-flow-admin');
const dataFlowSplit = document.getElementById('data-flow-split-mode');
const dataFlowSummary = document.getElementById('data-flow-summary-text');
const buttonsToToggle = [btnDry, btnImport];
window.showImportUnitaModal = function(){ modal.classList.remove('hidden'); modal.classList.add('flex'); };
window.hideImportUnitaModal = function(){ modal.classList.remove('flex'); modal.classList.add('hidden'); };
function syncTargetCodeFromSelect(){
if (!targetSelect || !targetCodeInput) return;
const option = targetSelect.selectedOptions && targetSelect.selectedOptions[0];
if (!option) return;
const code = option.getAttribute('data-code') || '';
if (code && (targetCodeInput.dataset.autofill !== '0' || !targetCodeInput.value)) {
targetCodeInput.value = code;
targetCodeInput.dataset.autofill = '1';
}
}
if (targetSelect && targetCodeInput) {
targetSelect.addEventListener('change', () => {
syncTargetCodeFromSelect();
updateDataFlowDisplay();
});
if (!targetCodeInput.value) {
syncTargetCodeFromSelect();
}
targetCodeInput.addEventListener('input', function(){
targetCodeInput.dataset.autofill = '0';
updateDataFlowDisplay();
});
}
// Carica dinamicamente gli anni quando cambia il legacy code
legacyInput && legacyInput.addEventListener('change', async function(){
const legacy = (legacyInput.value || '').trim();
const dl = document.getElementById('import-anno-list');
if (!legacy || !dl) return;
dl.innerHTML = '';
try{
const url = yearsUrlBase + '?legacy_code=' + encodeURIComponent(legacy);
const res = await fetch(url);
const js = await res.json();
if (js && js.ok && Array.isArray(js.years)) {
js.years.forEach(y => {
const opt = document.createElement('option');
opt.value = String(y);
dl.appendChild(opt);
});
}
}catch(e){ /* ignore */ }
updateDataFlowDisplay();
});
function updateDataFlowDisplay(){
const legacy = (legacyInput?.value || '').trim();
if (dataFlowLegacy) dataFlowLegacy.textContent = legacy !== '' ? legacy : '—';
const targetOption = targetSelect && targetSelect.selectedOptions ? targetSelect.selectedOptions[0] : null;
const targetName = targetOption ? (targetOption.getAttribute('data-name') || targetOption.textContent.trim()) : 'Creazione automatica';
const targetCode = targetOption ? (targetOption.getAttribute('data-code') || targetCodeInput?.value || '—') : (targetCodeInput?.value || 'auto');
if (dataFlowTargetName) dataFlowTargetName.textContent = targetName || '—';
if (dataFlowTargetCode) dataFlowTargetCode.textContent = targetCode && targetCode !== '' ? targetCode : '—';
const adminOption = adminSelect && adminSelect.selectedOptions ? adminSelect.selectedOptions[0] : null;
const adminName = adminOption ? (adminOption.getAttribute('data-name') || adminOption.textContent.trim()) : 'Amministratore corrente';
if (dataFlowAdmin) dataFlowAdmin.textContent = adminName !== '' ? adminName : 'Amministratore corrente';
const splitOption = splitModeSelect && splitModeSelect.selectedOptions ? splitModeSelect.selectedOptions[0] : null;
const splitLabel = splitOption ? splitOption.textContent.trim() : '—';
if (dataFlowSplit) dataFlowSplit.textContent = splitLabel;
if (dataFlowSummary) {
const stableInfo = targetName ? targetName : 'lo stabile di destinazione';
dataFlowSummary.textContent = `Pipeline pronta per ${legacy || 'il codice legacy selezionato'} → ${stableInfo}. Esegui prima il dry-run per verificare le palazzine e le quote.`;
}
}
[targetSelect, adminSelect, splitModeSelect].forEach(el => {
el && el.addEventListener('change', updateDataFlowDisplay);
});
updateDataFlowDisplay();
async function executeImport(dryRun){
const legacy = (legacyInput?.value || '').trim();
if (!legacy) {
alert('Inserisci un codice legacy stabile prima di procedere.');
return;
}
const steps = Array.from(form.querySelectorAll('input[name="steps[]"]:checked')).map(i => i.value);
if (!steps.length) {
alert('Seleziona almeno uno step della pipeline.');
return;
}
const anno = (document.getElementById('import-anno')?.value || '').trim();
const limit = (document.getElementById('import-limit')?.value || '').trim();
const scala = (document.getElementById('import-scala')?.value || '').trim();
const pianoMin = (document.getElementById('import-piano-min')?.value || '').trim();
const pianoMax = (document.getElementById('import-piano-max')?.value || '').trim();
const onlyPert = (document.getElementById('import-only-pertinenze')?.value || '').trim();
const targetStabileId = targetSelect && targetSelect.value ? Number(targetSelect.value) : undefined;
const targetStabileCode = targetCodeInput && targetCodeInput.value ? targetCodeInput.value.trim() : undefined;
const adminId = adminSelect && adminSelect.value ? Number(adminSelect.value) : undefined;
const splitMode = splitModeSelect && splitModeSelect.value ? splitModeSelect.value : undefined;
buttonsToToggle.forEach(btn => btn && (btn.disabled = true));
if (btnMapping) btnMapping.disabled = true;
outBox.classList.remove('hidden');
document.getElementById('import-anno-shown').textContent = anno || '—';
outLog.textContent = (dryRun ? 'Dry-run' : 'Importazione') + ' avviato…\n';
try {
const payload = {
legacy_code: legacy,
steps: steps,
dry_run: dryRun,
anno: anno ? Number(anno) : undefined,
limit: limit ? Number(limit) : undefined,
scala: scala || undefined,
piano_min: pianoMin ? Number(pianoMin) : undefined,
piano_max: pianoMax ? Number(pianoMax) : undefined,
only_pertinenze: onlyPert || undefined,
target_stabile_id: targetStabileId || undefined,
target_stabile_code: targetStabileCode || undefined,
split_mode: splitMode || undefined,
target_admin_id: adminId || undefined,
};
const resp = await fetch(routeUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf, 'Accept': 'application/json' },
body: JSON.stringify(payload)
});
let js = {};
try { js = await resp.json(); } catch(_) { js = {}; }
const results = Array.isArray(js.results) ? js.results : [];
if (typeof js.anno !== 'undefined') {
document.getElementById('import-anno-shown').textContent = String(js.anno || '—');
}
if (typeof js.target_stabile_id !== 'undefined' || typeof js.target_stabile_code !== 'undefined') {
const infoId = js.target_stabile_id ? `#${js.target_stabile_id}` : 'creato automaticamente';
const infoCode = js.target_stabile_code || '—';
outLog.textContent += `Destinazione stabile: ${infoId} (${infoCode})\n`;
}
if (typeof js.target_admin_id !== 'undefined') {
outLog.textContent += `Amministratore applicato: ${js.target_admin_id}\n`;
}
if (splitMode) {
outLog.textContent += `Split-mode: ${splitMode}\n`;
}
if (results.length) {
results.forEach(r => {
outLog.textContent += `\n[${r.step}] ${r.ok ? 'OK' : 'FAIL'}`;
if (r.output) outLog.textContent += `\n${r.output}\n`;
if (r.error) outLog.textContent += `\nErrore: ${r.error}\n`;
});
}
if (!resp.ok || js.ok === false) {
const failing = results.filter(r => r && r.ok === false).map(r => r.step).join(', ');
const baseErr = js.error || (failing ? `Step falliti: ${failing}` : 'Risposta non valida');
outLog.textContent += `\n[ERRORE] ${baseErr} (${resp.status})`;
}
} catch(err) {
outLog.textContent += '\n[ERRORE] ' + (err?.message || String(err));
} finally {
buttonsToToggle.forEach(btn => btn && (btn.disabled = false));
if (btnMapping) btnMapping.disabled = false;
}
}
btnDry && btnDry.addEventListener('click', () => executeImport(true));
btnImport && btnImport.addEventListener('click', () => executeImport(false));
form && form.addEventListener('submit', function(e){ e.preventDefault(); executeImport(true); });
btnMapping && btnMapping.addEventListener('click', () => {
const legacy = (legacyInput?.value || '').trim();
if (!legacy) {
alert('Seleziona un codice legacy per aprire la mappatura.');
return;
}
const params = new URLSearchParams({ legacy_code: legacy });
if (targetSelect && targetSelect.value) {
params.set('target_stabile_id', targetSelect.value);
}
if (targetCodeInput && targetCodeInput.value) {
params.set('target_stabile_code', targetCodeInput.value.trim());
}
window.open(mappingBaseUrl + '?' + params.toString(), '_blank');
});
});
</script>
@endcan