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

463 lines
24 KiB
PHP

@php
$fornitoriFieldGroups = $fornitoriFieldGroups ?? \App\Support\FornitoriImportTargets::groups();
$fornitoriMapping = $fornitoriMapping ?? [];
$fornitoriManualValues = $fornitoriManualValues ?? [];
$fornitoriTargetAdminId = $fornitoriTargetAdminId ?? ($selectedAmministratoreId ?? ($amministratore->id ?? null));
$fornitoriReferenceData = $fornitoriReferenceData ?? [
'regimi_fiscali' => collect(),
'tipologie_cassa' => collect(),
'aliquote_iva' => collect(),
];
$amministratoriDisponibili = $amministratoriDisponibili ?? collect();
if (!($amministratoriDisponibili instanceof \Illuminate\Support\Collection)) {
$amministratoriDisponibili = collect($amministratoriDisponibili);
}
@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-store me-2"></i> Fornitori</h3>
<div class="flex gap-2 flex-wrap">
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-primary" onclick="openStagingPanel_fornitori()">
<i class="mdi mdi-database-import"></i> Staging MDB
</button>
<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-danger" onclick="fornitoriResetProgress()">
<i class="mdi mdi-backspace"></i> Reset stato
</button>
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-success" onclick="window.location.href='/admin/gescon-import/export/fornitori'">
<i class="mdi mdi-file-excel"></i> Esporta
</button>
</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>Ragione Sociale</th>
<th>CF / P.IVA</th>
<th>Città</th>
<th>Contatti</th>
<th>Creato</th>
</tr>
</thead>
<tbody>
@forelse(($fornitori ?? []) as $f)
<tr>
<td><strong>{{ $f->ragione_sociale }}</strong></td>
<td>
@if($f->partita_iva)
<span class="netgescon-badge">P.IVA: {{ $f->partita_iva }}</span>
@elseif($f->codice_fiscale)
<span class="netgescon-badge">CF: {{ $f->codice_fiscale }}</span>
@else
<span class="text-gray-500"></span>
@endif
</td>
<td>
{{ $f->citta ?? '—' }}
@if($f->provincia) ({{ $f->provincia }}) @endif
</td>
<td>
@if($f->telefono || $f->email)
@if($f->telefono)
<div><i class="mdi mdi-phone"></i> {{ $f->telefono }}</div>
@endif
@if($f->email)
<div><i class="mdi mdi-email"></i> {{ $f->email }}</div>
@endif
@else
<span class="text-gray-500">N/D</span>
@endif
</td>
<td><small>{{ optional($f->created_at)->format('d/m/Y H:i') }}</small></td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center text-gray-500 py-3">Nessun fornitore</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@if(isset($fornitori) && method_exists($fornitori, 'hasPages') && $fornitori->hasPages())
<div class="netgescon-card-footer flex justify-end">
{{ $fornitori->links() }}
</div>
@endif
</div>
@include('admin.gescon-import.partials.staging-panel', [
'context' => 'fornitori',
'title' => 'Staging MDB Fornitori → DB (gescon_import)'
])
<div class="netgescon-card mt-3">
<div class="netgescon-card-header flex justify-between items-center">
<strong><i class="mdi mdi-tune"></i> Valori di default e lookup fiscali</strong>
<small class="text-gray-500">Salvati insieme al mapping</small>
</div>
<div class="netgescon-card-body">
<div class="grid md:grid-cols-12 gap-3">
<div class="col-span-12 md:col-span-4">
<label class="form-label small">Regime fiscale predefinito</label>
<select class="netgescon-input fornitori-manual" data-manual-key="lookup.regime_fiscale">
<option value=""> Non impostare </option>
@foreach($fornitoriReferenceData['regimi_fiscali'] as $regime)
<option value="{{ $regime->codice }}" @selected(($fornitoriManualValues['lookup.regime_fiscale'] ?? null) === $regime->codice)>
{{ $regime->codice }} {{ $regime->descrizione }}
</option>
@endforeach
</select>
</div>
<div class="col-span-12 md:col-span-4">
<label class="form-label small">Tipologia cassa predefinita</label>
<select class="netgescon-input fornitori-manual" data-manual-key="lookup.tipologia_cassa">
<option value=""> Non impostare </option>
@foreach($fornitoriReferenceData['tipologie_cassa'] as $cassa)
<option value="{{ $cassa->codice }}" @selected(($fornitoriManualValues['lookup.tipologia_cassa'] ?? null) === $cassa->codice)>
{{ $cassa->codice }} {{ $cassa->descrizione }}
</option>
@endforeach
</select>
</div>
<div class="col-span-12 md:col-span-4">
<label class="form-label small">Aliquota IVA predefinita</label>
<select class="netgescon-input fornitori-manual" data-manual-key="lookup.aliquota_iva">
<option value=""> Non impostare </option>
@foreach($fornitoriReferenceData['aliquote_iva'] as $iva)
<option value="{{ $iva->codice }}" @selected(($fornitoriManualValues['lookup.aliquota_iva'] ?? null) === $iva->codice)>
{{ $iva->codice }} {{ number_format($iva->valore, 2) }}%
@if($iva->descrizione)
({{ $iva->descrizione }})
@endif
</option>
@endforeach
</select>
</div>
</div>
</div>
</div>
<div class="netgescon-card mt-3">
<div class="netgescon-card-header flex justify-between items-center">
<strong><i class="mdi mdi-source-merge me-2"></i> Mapping Fornitori (SX: Legacy/Staging DX: NetGescon)</strong>
<small class="text-gray-500">1) Seleziona tabella di staging 2) Mappa colonne 3) Salva mapping 4) Importa con progress</small>
</div>
<div class="netgescon-card-body">
<div class="grid md:grid-cols-12 gap-3 items-end">
<div class="col-span-12 md:col-span-5">
<label class="form-label small">Tabella di staging</label>
<div class="flex gap-2">
<select id="fornitori-staging-table" class="netgescon-input">
<option value=""> Carico… </option>
</select>
<button type="button" class="netgescon-btn netgescon-btn-outline-secondary" id="btn-fornitori-refresh">Aggiorna</button>
<button type="button" class="netgescon-btn netgescon-btn-outline-secondary" id="btn-fornitori-load-cols">Colonne</button>
</div>
<div class="text-sm text-gray-500 mt-1">Colonne rilevate: <span id="fornitori-cols-count">0</span></div>
</div>
<div class="col-span-6 md:col-span-3">
<label class="form-label small">Chunk size</label>
<input type="number" id="fornitori-chunk" class="netgescon-input" value="500" min="50" max="2000" step="50"/>
</div>
<div class="col-span-6 md:col-span-4">
<label class="form-label small">Amministratore destinatario</label>
@if($amministratoriDisponibili->count())
<select id="fornitori-target-admin" class="netgescon-input">
<option value=""> Usa amministratore corrente </option>
@foreach($amministratoriDisponibili as $amm)
@php
$label = trim($amm->denominazione_studio ?? '') ?: trim(($amm->nome ?? '') . ' ' . ($amm->cognome ?? ''));
if ($label === '') {
$label = 'Amministratore #' . $amm->id;
}
@endphp
<option value="{{ $amm->id }}" @selected(($fornitoriTargetAdminId ?? null) == $amm->id)>{{ $label }} (ID {{ $amm->id }})</option>
@endforeach
</select>
@else
<input type="hidden" id="fornitori-target-admin" value="{{ $fornitoriTargetAdminId }}">
<div class="text-sm text-gray-500">Import associato a {{ $amministratore->denominazione_studio ?? $amministratore->nome ?? 'amministratore corrente' }}</div>
@endif
</div>
<div class="col-span-12 md:col-span-4 text-right">
<div class="flex gap-2 justify-end">
<button type="button" class="netgescon-btn netgescon-btn-primary" id="btn-fornitori-start">Avvia Import</button>
<button type="button" class="netgescon-btn netgescon-btn-outline-secondary" id="btn-fornitori-pause" disabled>Pausa</button>
</div>
</div>
</div>
<div class="grid md:grid-cols-12 gap-3 mt-3">
<div class="col-span-12 lg:col-span-6">
<div class="netgescon-card h-100">
<div class="netgescon-card-header"><strong>Sorgenti (SX) Colonne Staging</strong></div>
<div class="netgescon-card-body">
<ul class="list-group" id="fornitori-cols-list">
<li class="list-group-item text-gray-500">Nessuna colonna caricata</li>
</ul>
</div>
</div>
</div>
<div class="col-span-12 lg:col-span-6">
<div class="netgescon-card h-100">
<div class="netgescon-card-header flex justify-between items-center">
<strong>Destinazione (DX) Campi NetGescon</strong>
<div class="flex gap-2">
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-secondary" id="btn-fornitori-clear-mapping">Svuota campi</button>
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-success" id="btn-fornitori-save-mapping">Salva Mapping</button>
</div>
</div>
<div class="netgescon-card-body space-y-4">
@foreach($fornitoriFieldGroups as $group)
<div>
<div class="text-xs uppercase text-gray-500 font-semibold mb-2">{{ $group['label'] }}</div>
<div class="grid md:grid-cols-12 gap-2">
@foreach($group['fields'] as $field)
<div class="col-span-12 md:col-span-4">
<label class="form-label small">{{ $field['label'] }}</label>
<select class="netgescon-input fornitori-map" data-key="{{ $field['key'] }}" data-saved-value="{{ $fornitoriMapping[$field['key']] ?? '' }}">
<option value=""> Seleziona </option>
</select>
</div>
@endforeach
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
<div class="mt-3">
<div class="text-sm text-gray-500 mb-1">Avanzamento</div>
<div class="progress" style="height: 18px;">
<div id="fornitori-progress-bar" class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
</div>
<div class="flex justify-between text-sm mt-2">
<div>Processati: <span id="fornitori-processed">0</span> / <span id="fornitori-total">0</span></div>
<div>Stato: <span id="fornitori-status" class="netgescon-badge netgescon-badge-secondary">idle</span></div>
</div>
<div class="flex justify-between text-xs text-gray-500 mt-1">
<div>Ultimo chunk importato: <span id="fornitori-last-chunk">0</span></div>
<div>Aggiornato: <span id="fornitori-last-at"></span></div>
</div>
</div>
</div>
</div>
<script>
const fornitoriCsrf = @json(csrf_token());
const fornitoriSavedMapping = @json($fornitoriMapping ?? []);
const fornitoriManualValues = @json($fornitoriManualValues ?? []);
let fornitoriTargetAdmin = @json($fornitoriTargetAdminId ?? null);
let fornitoriTimer = null;
let fornitoriRunning = false;
let fornitoriColumns = [];
async function fornitoriLoadStagingTables(){
try {
const res = await fetch(@json(route('admin.gescon-import.fornitori-import.staging-tables')));
const j = await res.json();
const sel = document.getElementById('fornitori-staging-table');
sel.innerHTML = '';
const tables = j.tables||[];
if (!tables.length){ const o=document.createElement('option'); o.value=''; o.textContent='— Nessuna tabella mdb%fornit% —'; sel.appendChild(o); return; }
tables.forEach(t=>{ const o=document.createElement('option'); o.value=t; o.textContent=t; sel.appendChild(o); });
} catch(e){ console.error(e); alert('Errore caricamento tabelle'); }
}
async function fornitoriLoadColumns(){
const table = document.getElementById('fornitori-staging-table').value;
if (!table){ alert('Seleziona una tabella'); return; }
const url = new URL(@json(route('admin.gescon-import.fornitori-import.staging-columns')), window.location.origin);
url.searchParams.set('table', table);
const res = await fetch(url.toString());
const j = await res.json();
if (!j.ok){ alert('Errore lettura colonne'); return; }
fornitoriColumns = j.columns||[];
document.getElementById('fornitori-cols-count').textContent = String(fornitoriColumns.length);
const list = document.getElementById('fornitori-cols-list');
list.innerHTML = '';
fornitoriColumns.forEach(c=>{ const li=document.createElement('li'); li.className='list-group-item small'; li.textContent=c; list.appendChild(li); });
document.querySelectorAll('.fornitori-map').forEach(sel=>{
const saved = sel.dataset.savedValue || sel.value || '';
sel.innerHTML = '<option value="">— Seleziona —</option>' + fornitoriColumns.map(c=>`<option value="${c}">${c}</option>`).join('');
sel.value = saved;
});
fornitoriApplySavedMapping();
}
function fornitoriApplySavedMapping(){
document.querySelectorAll('.fornitori-map').forEach(sel=>{
const key = sel.getAttribute('data-key');
if (!key) return;
const saved = sel.dataset.savedValue || fornitoriSavedMapping[key] || '';
if (saved && !Array.from(sel.options).some(opt => opt.value === saved)) {
const opt = document.createElement('option');
opt.value = saved;
opt.textContent = `${saved} (legacy)`;
sel.appendChild(opt);
}
sel.value = saved || '';
});
}
function fornitoriApplyManualDefaults(){
document.querySelectorAll('.fornitori-manual').forEach(input => {
const key = input.dataset.manualKey;
if (!key) return;
const saved = fornitoriManualValues[key];
if (typeof saved !== 'undefined') {
input.value = saved;
}
});
const targetAdminSelect = document.getElementById('fornitori-target-admin');
if (targetAdminSelect && fornitoriTargetAdmin) {
targetAdminSelect.value = fornitoriTargetAdmin;
}
}
function fornitoriClearMapping(){
document.querySelectorAll('.fornitori-map').forEach(sel=>{
sel.value = '';
sel.dataset.savedValue = '';
const key = sel.getAttribute('data-key');
if (key) delete fornitoriSavedMapping[key];
});
document.querySelectorAll('.fornitori-manual').forEach(input=>{
input.value = '';
const key = input.dataset.manualKey;
if (key) delete fornitoriManualValues[key];
});
}
async function fornitoriSaveMapping(){
const selects = Array.from(document.querySelectorAll('.fornitori-map'));
const mapping = {};
selects.forEach(s=>{ const k = s.getAttribute('data-key'); const v = (s.value || '').trim(); if (k && v) mapping[k] = v; });
const manualInputs = Array.from(document.querySelectorAll('.fornitori-manual'));
const fd = new FormData();
fd.set('_mapping_only', '1');
fd.set('_legacy_code', 'fornitori');
for (const [k,v] of Object.entries(mapping)) {
fd.append(`mapping[${k}]`, v);
}
manualInputs.forEach(input => {
const key = input.dataset.manualKey;
const value = (input.value || '').trim();
if (key && value !== '') {
fd.append(`manual_target_values[${key}]`, value);
}
});
const targetAdminSelect = document.getElementById('fornitori-target-admin');
if (targetAdminSelect) {
fd.set('target_admin_id', targetAdminSelect.value || '');
}
const res = await fetch(@json(route('admin.gescon-import.mapping.save')), { method:'POST', headers:{'X-CSRF-TOKEN': fornitoriCsrf}, body: fd });
selects.forEach(sel => {
const key = sel.getAttribute('data-key');
const value = sel.value || '';
sel.dataset.savedValue = value;
if (key) {
if (value) {
fornitoriSavedMapping[key] = value;
} else {
delete fornitoriSavedMapping[key];
}
}
});
manualInputs.forEach(input => {
const key = input.dataset.manualKey;
if (!key) return;
const value = (input.value || '').trim();
if (value) {
fornitoriManualValues[key] = value;
} else {
delete fornitoriManualValues[key];
}
});
if (targetAdminSelect) {
fornitoriTargetAdmin = targetAdminSelect.value || null;
}
if (res.redirected){ window.location.href = res.url; return; }
try { const j = await res.json(); if (j.success) alert('Mapping salvato'); } catch(_e){ /* ignore */ }
}
async function fornitoriInit(){
const table = document.getElementById('fornitori-staging-table').value;
if (!table){ alert('Seleziona una tabella di staging'); return false; }
const fd = new FormData(); fd.set('staging_table', table);
const res = await fetch(@json(route('admin.gescon-import.fornitori-import.init')), { method:'POST', headers:{'X-CSRF-TOKEN': fornitoriCsrf}, body: fd });
const j = await res.json(); if (!j.ok){ alert(j.error||'Errore init'); return false; }
fornitoriUpdateUI(j.progress); return true;
}
async function fornitoriProcessTick(){
if (!fornitoriRunning) return;
const chunk = parseInt(document.getElementById('fornitori-chunk').value||'500',10);
const fd = new FormData(); fd.set('chunk', String(chunk));
try{
const res = await fetch(@json(route('admin.gescon-import.fornitori-import.process-chunk')), { method:'POST', headers:{'X-CSRF-TOKEN': fornitoriCsrf}, body: fd });
const j = await res.json(); if (j.ok){ fornitoriUpdateUI(j.progress); if (j.progress.status==='completed') fornitoriStop(); }
}catch(e){ console.error(e); fornitoriStop(); }
}
function fornitoriStart(){ if (fornitoriRunning) return; fornitoriRunning=true; document.getElementById('btn-fornitori-start').disabled=true; document.getElementById('btn-fornitori-pause').disabled=false; fornitoriProcessTick(); fornitoriTimer = setInterval(fornitoriProcessTick, 800); }
function fornitoriPause(){ fornitoriRunning=false; document.getElementById('btn-fornitori-start').disabled=false; document.getElementById('btn-fornitori-pause').disabled=true; if (fornitoriTimer){ clearInterval(fornitoriTimer); fornitoriTimer=null; } }
function fornitoriStop(){ fornitoriPause(); fornitoriPollProgress(); }
async function fornitoriPollProgress(){ try{ const res = await fetch(@json(route('admin.gescon-import.fornitori-import.progress'))); const j = await res.json(); if (j.ok) fornitoriUpdateUI(j.progress);}catch(e){console.error(e);} }
function fornitoriUpdateUI(p){
if (!p) return;
const pct = Math.max(0, Math.min(100, parseInt(p.percent||((p.processed||0)/(p.total||1)*100),10)));
const bar = document.getElementById('fornitori-progress-bar');
bar.style.width = pct+'%'; bar.setAttribute('aria-valuenow', String(pct)); bar.textContent = pct+'%';
document.getElementById('fornitori-processed').textContent = String(p.processed||0);
document.getElementById('fornitori-total').textContent = String(p.total||0);
const st = document.getElementById('fornitori-status');
const status = p.status||'idle';
st.textContent = status;
st.className='netgescon-badge ' + (status==='completed'?'netgescon-badge-success':(status==='running'?'netgescon-badge-primary':'netgescon-badge-secondary'));
document.getElementById('fornitori-last-chunk').textContent = String(p.last_chunk_imported ?? 0);
const lastAtEl = document.getElementById('fornitori-last-at');
if (p.last_chunk_at) {
const d = new Date(p.last_chunk_at);
lastAtEl.textContent = isNaN(d.getTime()) ? p.last_chunk_at : d.toLocaleString();
} else {
lastAtEl.textContent = '—';
}
}
async function fornitoriResetProgress(){
if (!confirm('Azzerare stato e progress dell\'import fornitori?')) return;
try {
const res = await fetch(@json(route('admin.gescon-import.fornitori-import.reset')), { method:'POST', headers:{'X-CSRF-TOKEN': fornitoriCsrf} });
if (res.ok) {
fornitoriUpdateUI({ processed: 0, total: 0, status: 'idle', percent: 0, last_chunk_imported: 0 });
}
} catch (e) {
console.error(e);
}
}
document.addEventListener('DOMContentLoaded', ()=>{
fornitoriLoadStagingTables();
fornitoriApplySavedMapping();
fornitoriApplyManualDefaults();
fornitoriPollProgress();
document.getElementById('btn-fornitori-refresh').addEventListener('click', fornitoriLoadStagingTables);
document.getElementById('btn-fornitori-load-cols').addEventListener('click', fornitoriLoadColumns);
document.getElementById('btn-fornitori-save-mapping').addEventListener('click', fornitoriSaveMapping);
document.getElementById('btn-fornitori-clear-mapping').addEventListener('click', fornitoriClearMapping);
document.getElementById('btn-fornitori-start').addEventListener('click', async ()=>{ const ok = await fornitoriInit(); if (ok) fornitoriStart(); });
document.getElementById('btn-fornitori-pause').addEventListener('click', fornitoriPause);
});
</script>