3820 lines
169 KiB
PHP
3820 lines
169 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Amministratore;
|
||
use App\Models\Stabile;
|
||
use App\Models\Soggetto;
|
||
use App\Models\Fornitore;
|
||
use App\Models\UnitaImmobiliare;
|
||
use App\Models\DocumentoCollegato;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Artisan;
|
||
use Illuminate\Support\Facades\Storage;
|
||
use App\Models\UserSetting;
|
||
use App\Models\GesconImportMapping;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use App\Models\MigrationDataset;
|
||
use App\Models\MigrationRecord;
|
||
use App\Models\MigrationSource;
|
||
use Illuminate\Support\Facades\Response;
|
||
use function response;
|
||
use App\Services\AmministratoreArchiveSyncService;
|
||
use App\Services\GesconImport\LegacyArchiveService;
|
||
use App\Services\GesconImport\ImportProgressLogger;
|
||
// (no extra facades required; originals already imported above)
|
||
|
||
class GesconImportController extends Controller
|
||
{
|
||
protected LegacyArchiveService $legacy;
|
||
protected AmministratoreArchiveSyncService $archiveSync;
|
||
|
||
public function __construct(LegacyArchiveService $legacy, AmministratoreArchiveSyncService $archiveSync)
|
||
{
|
||
$this->middleware('auth');
|
||
// Accesso ristretto: solo super-admin e amministratore possono usare l'area Import GESCON
|
||
$this->middleware('role:super-admin|amministratore');
|
||
// Visibilità area import solo se l'utente ha il permesso esplicito
|
||
$this->middleware('permission:gescon-import.view');
|
||
// Esecuzione task di import richiede permesso dedicato
|
||
$this->middleware('permission:gescon-import.execute')->only(['executeImport']);
|
||
// Service per accesso archivi legacy/staging
|
||
$this->legacy = $legacy;
|
||
$this->archiveSync = $archiveSync;
|
||
}
|
||
|
||
/**
|
||
* Dashboard principale per visualizzare i dati importati da GESCON
|
||
*/
|
||
public function index(\Illuminate\Http\Request $request)
|
||
{
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
if (!$amministratore) {
|
||
return redirect()->route('admin.dashboard')
|
||
->with('error', 'Nessun amministratore associato all\'utente corrente.');
|
||
}
|
||
|
||
$canSwitchAmministratore = $this->canSwitchAmministratore();
|
||
$amministratoriDisponibili = $canSwitchAmministratore
|
||
? Amministratore::orderBy('denominazione_studio')
|
||
->orderBy('nome')
|
||
->orderBy('cognome')
|
||
->get(['id', 'nome', 'cognome', 'denominazione_studio', 'codice_amministratore'])
|
||
: Amministratore::where('id', $amministratore->id)
|
||
->get(['id', 'nome', 'cognome', 'denominazione_studio', 'codice_amministratore']);
|
||
$selectedAmministratoreId = $amministratore->id;
|
||
|
||
// Statistiche generali
|
||
$stats = $this->getStatisticheImportate($amministratore->id);
|
||
|
||
// Dati recenti per le cards
|
||
$datiRecenti = $this->getDatiRecenti($amministratore->id);
|
||
|
||
// Dati per tab Stabili (mostra tutti dello stesso amministratore; il tag GESCON è informativo, non filtrante)
|
||
$stabiliQuery = Stabile::where('amministratore_id', $amministratore->id);
|
||
$stabili = (clone $stabiliQuery)
|
||
->orderBy('denominazione')
|
||
->paginate(15, ['*'], 'stabili_page');
|
||
$province = (clone $stabiliQuery)
|
||
->whereNotNull('provincia')
|
||
->distinct()
|
||
->pluck('provincia')
|
||
->filter()
|
||
->sort()
|
||
->values()
|
||
->toArray();
|
||
|
||
// Dati per tab Soggetti (solo soggetti collegati a stabili dell'amministratore)
|
||
$soggetti = Soggetto::with(['unitaImmobiliari.stabile'])
|
||
->whereHas('unitaImmobiliari.stabile', function ($q) use ($amministratore) {
|
||
$q->where('amministratore_id', $amministratore->id);
|
||
})
|
||
->orderBy('cognome')
|
||
->orderBy('nome')
|
||
->paginate(20, ['*'], 'soggetti_page');
|
||
$stabiliList = Stabile::where('amministratore_id', $amministratore->id)
|
||
->orderBy('denominazione')
|
||
->get(['id', 'denominazione']);
|
||
// Dati per tab Unità e Fornitori (solo quelli dello stesso amministratore)
|
||
$unita = UnitaImmobiliare::with('stabile')
|
||
->whereHas('stabile', function ($q) use ($amministratore) {
|
||
$q->where('amministratore_id', $amministratore->id);
|
||
})
|
||
->latest()
|
||
->paginate(20, ['*'], 'unita_page');
|
||
$fornitori = Fornitore::where('amministratore_id', $amministratore->id)
|
||
->orderBy('ragione_sociale')
|
||
->paginate(20, ['*'], 'fornitori_page');
|
||
|
||
// Anteprima legacy (Archivio migrazioni -> MDB -> directory)
|
||
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '';
|
||
// Base degli archivi montati (directory per-stabile e sottocartelle per anno)
|
||
$legacyBasePath = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
// MDB generali nella sottocartella "dbc"
|
||
$mdbPath = UserSetting::get('gescon.mdb_stabili', '/mnt/gescon-archives/gescon/dbc/Stabili.mdb');
|
||
$mdbIndexPath = UserSetting::get('gescon.mdb_index', '/mnt/gescon-archives/gescon/cartellestabili_e_anni.mdb');
|
||
$dataset = MigrationDataset::where('key', 'stabili')->orderByDesc('updated_at')->first();
|
||
$cacheKey = 'gescon_legacy_preview:' . md5(($legacyBasePath ?: '') . '|' . ($mdbPath ?: '') . '|' . ($dataset?->id ?? 0) . '|' . ($dataset?->records_count ?? 0));
|
||
if ($request->boolean('refresh')) {
|
||
Cache::forget($cacheKey);
|
||
}
|
||
$legacyPreview = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($dataset, $mdbPath, $hasMdbTools, $legacyBasePath, $mdbIndexPath) {
|
||
try {
|
||
$preview = [];
|
||
// 1) Archivio migrazioni (se presente)
|
||
if ($dataset && $dataset->records_count > 0) {
|
||
$records = MigrationRecord::where('dataset_id', $dataset->id)->limit(5000)->get();
|
||
$map = [];
|
||
foreach ($records as $rec) {
|
||
$data = (array)($rec->data ?? []);
|
||
$legacy = $rec->legacy_key
|
||
?: ($data['nome_directory'] ?? ($data['internet_cod_stab'] ?? ($data['cod_stabile'] ?? null)));
|
||
if (!$legacy) continue;
|
||
$denom = $data['denominazione'] ?? ($data['fe_denominazione'] ?? null);
|
||
$map[(string)$legacy] = [
|
||
'legacy_code' => trim((string)$legacy),
|
||
'denominazione' => $denom ? trim((string)$denom) : null,
|
||
];
|
||
}
|
||
$preview = array_values($map);
|
||
}
|
||
// 2) Indice master cartellestabili_e_anni.mdb (preferibile per anni/cartelle)
|
||
if (empty($preview) && $hasMdbTools && $mdbIndexPath && is_file($mdbIndexPath) && is_readable($mdbIndexPath)) {
|
||
$preview = $this->legacy->scanStabiliFromMasterIndex($mdbIndexPath, $legacyBasePath);
|
||
}
|
||
// 3) MDB Stabili diretto
|
||
if (empty($preview) && $mdbPath && is_file($mdbPath) && $hasMdbTools) {
|
||
$preview = $this->legacy->scanStabiliFromMdb($mdbPath);
|
||
}
|
||
// 4) Fallback directory
|
||
if (empty($preview) && is_dir($legacyBasePath)) {
|
||
$preview = $this->legacy->scanLegacyCodesFromDir($legacyBasePath);
|
||
}
|
||
if (!empty($preview)) {
|
||
$preview = $this->legacy->enrichLegacyWithArchives($preview, $legacyBasePath);
|
||
}
|
||
return $preview;
|
||
} catch (\Throwable $e) {
|
||
return [];
|
||
}
|
||
});
|
||
|
||
// Mapping/sync attivi per suddividere legacy in "collegati" vs "da collegare"
|
||
$userId = auth()->id();
|
||
$mappedLegacyCodes = \App\Models\GesconImportMapping::where('user_id', $userId)
|
||
->where('legacy_code', '!=', '*')
|
||
->pluck('legacy_code')
|
||
->filter()
|
||
->values()
|
||
->toArray();
|
||
$syncByLegacy = \App\Models\GesconImportMapping::where('user_id', $userId)
|
||
->where('legacy_code', '!=', '*')
|
||
->get(['legacy_code', 'meta'])
|
||
->mapWithKeys(function ($m) {
|
||
$meta = is_array($m->meta) ? $m->meta : [];
|
||
return [$m->legacy_code => (bool)($meta['sync_enabled'] ?? false)];
|
||
})
|
||
->toArray();
|
||
|
||
// Nuovi amministratori: se non ci sono dati importati, invia alla Config solo se ha il permesso specifico.
|
||
$onboarding = ($stats['stabili_totali'] ?? 0) == 0 && ($stats['stabili_gescon'] ?? 0) == 0;
|
||
if ($onboarding && !$canSwitchAmministratore) {
|
||
if (auth()->user() && auth()->user()->can('gescon-import.config')) {
|
||
return redirect()->route('admin.gescon-import.config');
|
||
}
|
||
// Altrimenti mostra una card guida senza link attivo alla Config
|
||
return view('admin.gescon-import.index', compact(
|
||
'stats',
|
||
'datiRecenti',
|
||
'amministratore',
|
||
'stabili',
|
||
'province',
|
||
'soggetti',
|
||
'stabiliList',
|
||
'unita',
|
||
'fornitori',
|
||
'legacyPreview',
|
||
'hasMdbTools',
|
||
'legacyBasePath',
|
||
'mdbPath',
|
||
'mappedLegacyCodes',
|
||
'syncByLegacy',
|
||
'legacyCode',
|
||
'legacyUnita',
|
||
'unitaFieldMapping',
|
||
'canSwitchAmministratore',
|
||
'amministratoriDisponibili',
|
||
'selectedAmministratoreId',
|
||
'onboarding'
|
||
));
|
||
}
|
||
|
||
// Optional: legacy Unità preview (for mapping tab) when a legacy code is selected
|
||
$legacyCode = $request->query('legacy_code');
|
||
$legacyUnita = [];
|
||
try {
|
||
if ($legacyCode && Schema::connection('gescon_import')->hasTable('condomin')) {
|
||
$legacyUnita = DB::connection('gescon_import')
|
||
->table('condomin')
|
||
->where('cod_stabile', $legacyCode)
|
||
->select(['cod_stabile', 'scala', 'interno', 'cognome', 'nome', 'cod_cond'])
|
||
->limit(500)
|
||
->get();
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$legacyUnita = [];
|
||
}
|
||
|
||
// Recupera mapping Unità esistente (field mapping) per il legacy selezionato
|
||
$unitaFieldMapping = [];
|
||
if ($legacyCode) {
|
||
try {
|
||
$mapRec = GesconImportMapping::where('user_id', auth()->id())
|
||
->where('legacy_code', $legacyCode)
|
||
->first();
|
||
if ($mapRec) {
|
||
$unitaFieldMapping = (array) data_get($mapRec, 'meta.unita_field_mapping', []);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$unitaFieldMapping = [];
|
||
}
|
||
}
|
||
|
||
return view('admin.gescon-import.index', compact(
|
||
'stats',
|
||
'datiRecenti',
|
||
'amministratore',
|
||
'stabili',
|
||
'province',
|
||
'soggetti',
|
||
'stabiliList',
|
||
'unita',
|
||
'fornitori',
|
||
'legacyPreview',
|
||
'hasMdbTools',
|
||
'legacyBasePath',
|
||
'mdbPath',
|
||
'mappedLegacyCodes',
|
||
'syncByLegacy',
|
||
'canSwitchAmministratore',
|
||
'amministratoriDisponibili',
|
||
'selectedAmministratoreId',
|
||
'onboarding'
|
||
));
|
||
}
|
||
|
||
/**
|
||
* Visualizza lista stabili importati da GESCON
|
||
*/
|
||
public function stabili(Request $request)
|
||
{
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
|
||
$query = Stabile::where('amministratore_id', $amministratore->id)
|
||
->where('note', 'like', '%GESCON%');
|
||
|
||
// Filtri
|
||
if ($request->filled('search')) {
|
||
$search = $request->search;
|
||
$query->where(function ($q) use ($search) {
|
||
$q->where('denominazione', 'like', "%{$search}%")
|
||
->orWhere('codice_stabile', 'like', "%{$search}%")
|
||
->orWhere('indirizzo', 'like', "%{$search}%");
|
||
});
|
||
}
|
||
|
||
if ($request->filled('citta')) {
|
||
$query->where('citta', 'like', "%{$request->citta}%");
|
||
}
|
||
|
||
if ($request->filled('provincia')) {
|
||
$query->where('provincia', $request->provincia);
|
||
}
|
||
|
||
$stabili = $query->orderBy('denominazione')->paginate(20);
|
||
|
||
// Statistiche per questa vista
|
||
$stats = [
|
||
'totale' => $query->count(),
|
||
'attivi' => $query->where('attivo', true)->count(),
|
||
'province' => $query->distinct('provincia')->count('provincia'),
|
||
];
|
||
}
|
||
|
||
private function resolveMappingCodiceCassa(array $mapping): ?string
|
||
{
|
||
$target = isset($mapping['target']) && is_array($mapping['target']) ? $mapping['target'] : [];
|
||
$legacy = isset($mapping['legacy']) && is_array($mapping['legacy']) ? $mapping['legacy'] : [];
|
||
$candidates = [
|
||
$target['codice_cassa'] ?? null,
|
||
$mapping['target_cod_cassa'] ?? null,
|
||
$mapping['target_cod_cassa_select'] ?? null,
|
||
$mapping['codice_legacy'] ?? null,
|
||
$legacy['cod_cassa'] ?? null,
|
||
$mapping['legacy_cod_cassa'] ?? null,
|
||
$mapping['codice_cassa'] ?? null,
|
||
];
|
||
foreach ($candidates as $candidate) {
|
||
if (is_string($candidate) && trim($candidate) !== '') {
|
||
return strtoupper(trim($candidate));
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private function resolveMappingLegacyField(array $mapping, string $field): ?string
|
||
{
|
||
$legacy = isset($mapping['legacy']) && is_array($mapping['legacy']) ? $mapping['legacy'] : [];
|
||
$candidates = [
|
||
$legacy[$field] ?? null,
|
||
$mapping['legacy_' . $field] ?? null,
|
||
];
|
||
foreach ($candidates as $candidate) {
|
||
if (is_string($candidate) && trim($candidate) !== '') {
|
||
return trim($candidate);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Dettaglio singolo stabile importato
|
||
*/
|
||
public function stabileShow($id)
|
||
{
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
|
||
$stabile = Stabile::where('amministratore_id', $amministratore->id)
|
||
->where('id', $id)
|
||
->firstOrFail();
|
||
|
||
// Condomini collegati a questo stabile (tramite campo note o altra logica)
|
||
$condomini = Soggetto::where('note', 'like', "%{$stabile->codice_stabile}%")
|
||
->orWhere('note', 'like', "%GESCON%")
|
||
->paginate(50);
|
||
|
||
// Fornitori per questo amministratore
|
||
$fornitori = Fornitore::where('amministratore_id', $amministratore->id)
|
||
->latest()
|
||
->take(10)
|
||
->get();
|
||
|
||
return view('admin.gescon-import.stabile-show', compact('stabile', 'condomini', 'fornitori', 'amministratore'));
|
||
}
|
||
|
||
/**
|
||
* Lista condomini (soggetti) importati da GESCON
|
||
*/
|
||
public function condomini(Request $request)
|
||
{
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
|
||
$query = Soggetto::query();
|
||
|
||
// Filtri
|
||
if ($request->filled('search')) {
|
||
$search = $request->search;
|
||
$query->where(function ($q) use ($search) {
|
||
$q->where('nome', 'like', "%{$search}%")
|
||
->orWhere('cognome', 'like', "%{$search}%")
|
||
->orWhere('ragione_sociale', 'like', "%{$search}%")
|
||
->orWhere('codice_fiscale', 'like', "%{$search}%");
|
||
});
|
||
}
|
||
|
||
if ($request->filled('tipo')) {
|
||
$query->where('tipo', $request->tipo);
|
||
}
|
||
|
||
if ($request->filled('citta')) {
|
||
$query->where('citta', 'like', "%{$request->citta}%");
|
||
}
|
||
|
||
$condomini = $query->orderBy('cognome')
|
||
->orderBy('nome')
|
||
->paginate(50);
|
||
|
||
// Statistiche
|
||
$stats = [
|
||
'totale' => $query->count(),
|
||
'proprietari' => Soggetto::where('tipo', 'proprietario')->count(),
|
||
'inquilini' => Soggetto::where('tipo', 'inquilino')->count(),
|
||
];
|
||
|
||
return view('admin.gescon-import.condomini', compact('condomini', 'stats', 'amministratore'));
|
||
}
|
||
|
||
/**
|
||
* Lista fornitori importati da GESCON
|
||
*/
|
||
public function fornitori(Request $request)
|
||
{
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
|
||
$query = Fornitore::where('amministratore_id', $amministratore->id);
|
||
|
||
// Filtri
|
||
if ($request->filled('search')) {
|
||
$search = $request->search;
|
||
$query->where(function ($q) use ($search) {
|
||
$q->where('ragione_sociale', 'like', "%{$search}%")
|
||
->orWhere('codice_fiscale', 'like', "%{$search}%")
|
||
->orWhere('partita_iva', 'like', "%{$search}%");
|
||
});
|
||
}
|
||
|
||
if ($request->filled('citta')) {
|
||
$query->where('citta', 'like', "%{$request->citta}%");
|
||
}
|
||
|
||
if ($request->filled('provincia')) {
|
||
$query->where('provincia', $request->provincia);
|
||
}
|
||
|
||
$fornitori = $query->orderBy('ragione_sociale')->paginate(50);
|
||
|
||
// Statistiche
|
||
$stats = [
|
||
'totale' => $query->count(),
|
||
'con_piva' => $query->whereNotNull('partita_iva')->count(),
|
||
'province' => $query->distinct('provincia')->count('provincia'),
|
||
];
|
||
|
||
return view('admin.gescon-import.fornitori', compact('fornitori', 'stats', 'amministratore'));
|
||
}
|
||
|
||
/**
|
||
* Reports e statistiche GESCON
|
||
*/
|
||
public function reports()
|
||
{
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
|
||
// Report dettagliato per amministratore
|
||
$report = [
|
||
'stabili' => [
|
||
'totale' => Stabile::where('amministratore_id', $amministratore->id)
|
||
->where('note', 'like', '%GESCON%')->count(),
|
||
'per_provincia' => Stabile::where('amministratore_id', $amministratore->id)
|
||
->where('note', 'like', '%GESCON%')
|
||
->select('provincia', DB::raw('count(*) as total'))
|
||
->groupBy('provincia')
|
||
->get(),
|
||
],
|
||
'fornitori' => [
|
||
'totale' => Fornitore::where('amministratore_id', $amministratore->id)->count(),
|
||
'con_piva' => Fornitore::where('amministratore_id', $amministratore->id)
|
||
->whereNotNull('partita_iva')->count(),
|
||
],
|
||
'condomini' => [
|
||
'totale' => Soggetto::count(),
|
||
'per_tipo' => Soggetto::select('tipo', DB::raw('count(*) as total'))
|
||
->groupBy('tipo')
|
||
->get(),
|
||
]
|
||
];
|
||
|
||
return view('admin.gescon-import.reports', compact('report', 'amministratore'));
|
||
}
|
||
|
||
/**
|
||
* Atomic page: Unità mapping UI (standalone), with "da associare" option and optional column creation panel.
|
||
*/
|
||
public function unitaMappingPage(Request $request)
|
||
{
|
||
$legacyCode = (string) $request->query('legacy_code', '');
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
// Existing mapping
|
||
$unitaFieldMapping = [];
|
||
$unitaLastFilters = [];
|
||
$palazzineConfig = [];
|
||
$unitaPresetKey = null;
|
||
try {
|
||
if ($legacyCode !== '') {
|
||
$mapRec = GesconImportMapping::where('user_id', auth()->id())
|
||
->where('legacy_code', $legacyCode)
|
||
->first();
|
||
if ($mapRec) {
|
||
$unitaFieldMapping = (array) data_get($mapRec, 'meta.unita_field_mapping', []);
|
||
$unitaLastFilters = (array) data_get($mapRec, 'meta.unita_last_filters', []);
|
||
$palazzineConfig = (array) data_get($mapRec, 'meta.palazzine_config', []);
|
||
$unitaPresetKey = data_get($mapRec, 'meta.unita_preset_key');
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$unitaFieldMapping = [];
|
||
}
|
||
|
||
// Legacy sample rows from staging (condomin)
|
||
$legacyUnita = [];
|
||
$legacyHeaders = [];
|
||
try {
|
||
if ($legacyCode !== '' && Schema::connection('gescon_import')->hasTable('condomin')) {
|
||
$legacyUnita = DB::connection('gescon_import')
|
||
->table('condomin')
|
||
->where('cod_stabile', $legacyCode)
|
||
->select(['cod_stabile', 'cod_cond', 'scala', 'interno', 'cognome', 'nome', 'codice_fiscale', 'millesimi_generali', 'millesimi_risc'])
|
||
->limit(20)
|
||
->get()
|
||
->map(fn($r) => (array)$r)
|
||
->toArray();
|
||
// headers
|
||
try {
|
||
$legacyHeaders = Schema::connection('gescon_import')->getColumnListing('condomin');
|
||
} catch (\Throwable $e) {
|
||
$legacyHeaders = [];
|
||
}
|
||
} else {
|
||
// fallback headers minimal
|
||
$legacyHeaders = ['cod_stabile', 'cod_cond', 'scala', 'interno', 'cognome', 'nome', 'codice_fiscale', 'millesimi_generali', 'millesimi_risc'];
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$legacyHeaders = ['cod_stabile', 'cod_cond', 'scala', 'interno'];
|
||
}
|
||
|
||
// Distinct scales from preview (best-effort for UI chips)
|
||
$scaleList = collect($legacyUnita)->pluck('scala')->filter()->unique()->values()->all();
|
||
|
||
// Target groups similar to Stabili mapping UX
|
||
$targetGroups = [
|
||
'soggetto' => [
|
||
'cognome',
|
||
'nome',
|
||
'codice_fiscale',
|
||
'partita_iva',
|
||
'email',
|
||
'telefono',
|
||
'indirizzo',
|
||
'civico',
|
||
'cap',
|
||
'citta',
|
||
'provincia'
|
||
],
|
||
'unita' => [
|
||
'codice_unita',
|
||
'scala',
|
||
'piano',
|
||
'interno',
|
||
'categoria_catastale',
|
||
'classe_catastale',
|
||
'consistenza',
|
||
'rendita',
|
||
'millesimi_generali',
|
||
'millesimi_riscaldamento',
|
||
'superficie',
|
||
'note',
|
||
'sezione',
|
||
'foglio',
|
||
'particella',
|
||
'subalterno',
|
||
'indirizzo',
|
||
'civico',
|
||
'mq_riscaldati',
|
||
'mq_non_riscaldati',
|
||
'tipo_unita',
|
||
'pertinenza_di'
|
||
],
|
||
'relazioni' => [
|
||
'conduttore.cf',
|
||
'conduttore.nome',
|
||
'conduttore.cognome',
|
||
'inquilino.cf',
|
||
'inquilino.nome',
|
||
'inquilino.cognome'
|
||
],
|
||
];
|
||
// Flat suggestion list preserved for compatibility if needed
|
||
$targetFields = array_values(array_unique(array_merge(
|
||
$targetGroups['unita'],
|
||
$targetGroups['soggetto'],
|
||
['codice_fiscale_proprietario', 'codice_fiscale_inquilino']
|
||
)));
|
||
|
||
// Global mapping sources (segment-level tables/columns) for filtering legacy fields
|
||
$mappingSources = \App\Models\UserSetting::get('gescon.mapping_sources', []);
|
||
// Derive per-stabile first available year (from gestioni or gestione_years table if exists) – best effort
|
||
$firstYear = null;
|
||
$availableYears = [];
|
||
try {
|
||
if ($legacyCode && Schema::connection('gescon_import')->hasTable('gestioni')) {
|
||
$yrs = DB::connection('gescon_import')->table('gestioni')
|
||
->where('cod_stabile', $legacyCode)
|
||
->whereNotNull('anno')
|
||
->orderBy('anno')
|
||
->pluck('anno')->filter()->unique()->values()->all();
|
||
$availableYears = $yrs;
|
||
if (!empty($yrs)) {
|
||
$firstYear = (int) min($yrs);
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$firstYear = null;
|
||
}
|
||
if ($firstYear === null && $legacyCode) {
|
||
// fallback: try to guess from unita/piano data if any numeric year col exists
|
||
$firstYear = (int) date('Y');
|
||
}
|
||
|
||
// Prefill field mapping suggestion: build from existing mapping record or heuristic using mappingSources columns
|
||
$prefillMapping = [];
|
||
if ($legacyCode) {
|
||
try {
|
||
$mapRec = GesconImportMapping::where('user_id', auth()->id())
|
||
->where('legacy_code', $legacyCode)
|
||
->first();
|
||
if ($mapRec) {
|
||
$prefillMapping = (array) data_get($mapRec, 'meta.unita_field_mapping', []);
|
||
}
|
||
if (empty($prefillMapping) && is_array($mappingSources)) {
|
||
// Use segment 'unita' if present to propose legacy→target quick suggestions
|
||
$segUnita = collect($mappingSources)->firstWhere('segment', 'unita');
|
||
$colList = (array) ($segUnita['columns'] ?? []);
|
||
$pairs = [
|
||
'cognome' => 'cognome',
|
||
'nome' => 'nome',
|
||
'codice_fiscale' => 'codice_fiscale',
|
||
'scala' => 'scala',
|
||
'interno' => 'interno',
|
||
'millesimi_generali' => 'millesimi_generali',
|
||
'millesimi_risc' => 'millesimi_riscaldamento'
|
||
];
|
||
foreach ($pairs as $legacy => $target) {
|
||
if (in_array($legacy, $colList, true)) {
|
||
$prefillMapping[] = ['legacy' => $legacy, 'target' => $target];
|
||
}
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$prefillMapping = [];
|
||
}
|
||
}
|
||
|
||
// Provide filtered mappingSources only for selected stable & unita segment in front-end context if needed
|
||
$filteredSources = [];
|
||
if ($legacyCode && is_array($mappingSources)) {
|
||
foreach ($mappingSources as $ms) {
|
||
if (!is_array($ms)) continue;
|
||
// Keep only global segments relevant to unita/stabile to reduce noise
|
||
if (in_array($ms['segment'] ?? '', ['stabile', 'stabili', 'unita', 'relazioni'], true)) {
|
||
$filteredSources[] = $ms;
|
||
}
|
||
}
|
||
}
|
||
|
||
return view('admin.gescon-import.unita-mapping-page', compact(
|
||
'amministratore',
|
||
'legacyCode',
|
||
'legacyUnita',
|
||
'legacyHeaders',
|
||
'targetFields',
|
||
'unitaFieldMapping',
|
||
'targetGroups',
|
||
'unitaLastFilters',
|
||
'palazzineConfig',
|
||
'scaleList',
|
||
'unitaPresetKey',
|
||
'mappingSources',
|
||
'firstYear',
|
||
'availableYears',
|
||
'prefillMapping',
|
||
'filteredSources'
|
||
));
|
||
}
|
||
|
||
/**
|
||
* Dedicated Relations Manager page (Config-like), to manage relation rules per segment.
|
||
*/
|
||
public function relationsPage(Request $request)
|
||
{
|
||
$segment = (string) $request->query('segment', 'unita');
|
||
$legacyCode = (string) $request->query('legacy_code', '');
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
|
||
// Load existing relation rules
|
||
$existingRelations = [];
|
||
try {
|
||
if ($legacyCode !== '') {
|
||
$mapRec = GesconImportMapping::where('user_id', auth()->id())
|
||
->where('legacy_code', $legacyCode)
|
||
->first();
|
||
$existingRelations = (array) data_get($mapRec, 'meta.relation_mapping', []);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$existingRelations = [];
|
||
}
|
||
|
||
// Build legacy fields list per segment
|
||
$legacyFields = [];
|
||
try {
|
||
if ($segment === 'unita') {
|
||
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
||
$legacyFields = Schema::connection('gescon_import')->getColumnListing('condomin');
|
||
}
|
||
} elseif ($segment === 'stabili') {
|
||
// Try to read headers from Stabili.mdb; fallback to dataset
|
||
$headers = [];
|
||
$hasMdb = trim((string)@shell_exec('command -v mdb-export')) !== '';
|
||
$mdbPath = \App\Models\UserSetting::get('gescon.mdb_stabili');
|
||
if ($hasMdb && $mdbPath && @is_file($mdbPath)) {
|
||
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
|
||
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
|
||
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($mdbPath))) ?: '';
|
||
$tableGuess = 'Stabili';
|
||
if ($tables) {
|
||
foreach (preg_split('/\r?\n/', trim($tables)) as $t) {
|
||
if ($t === '') continue;
|
||
if (preg_match('/stabil/i', $t)) {
|
||
$tableGuess = trim($t);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
|
||
$cmd = sprintf(
|
||
'%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
|
||
escapeshellarg($binExport),
|
||
escapeshellarg('%Y-%m-%d'),
|
||
escapeshellarg('|'),
|
||
escapeshellarg('"'),
|
||
escapeshellarg($mdbPath),
|
||
escapeshellarg($tableGuess),
|
||
escapeshellarg($tmp)
|
||
);
|
||
@shell_exec($cmd);
|
||
if (@filesize($tmp) > 0) {
|
||
$fh = fopen($tmp, 'r');
|
||
$h = fgetcsv($fh, 0, '|');
|
||
fclose($fh);
|
||
@unlink($tmp);
|
||
$headers = $h ? array_map(fn($x) => strtolower(trim((string)$x)), $h) : [];
|
||
} else {
|
||
@unlink($tmp);
|
||
}
|
||
}
|
||
if (empty($headers)) {
|
||
$ds = \App\Models\MigrationDataset::where('key', 'stabili')->orderByDesc('updated_at')->first();
|
||
if ($ds) {
|
||
$rec = \App\Models\MigrationRecord::where('dataset_id', $ds->id)->orderBy('id')->first();
|
||
$headers = array_keys((array)($rec?->data ?? []));
|
||
} else {
|
||
$headers = ['denominazione', 'indirizzo', 'comune', 'provincia', 'cap'];
|
||
}
|
||
}
|
||
$legacyFields = $headers;
|
||
} else {
|
||
// tabelle / voci: try to guess from staging
|
||
$conn = \App\Models\UserSetting::get('gescon.import_conn', 'gescon_import');
|
||
$candidates = $segment === 'tabelle' ? ['milles', 'millesimi', 'tab_millesimi'] : ['piano_conti', 'pianoconti', 'voci', 'voci_spesa'];
|
||
$chosen = null;
|
||
$all = [];
|
||
try {
|
||
$all = Schema::connection($conn)->getAllTables();
|
||
} catch (\Throwable $e) {
|
||
$all = [];
|
||
}
|
||
$names = array_values(array_filter(array_map(function ($r) {
|
||
$n = $r['name'] ?? ($r['TABLE_NAME'] ?? null);
|
||
return $n ? (string)$n : null;
|
||
}, $all)));
|
||
foreach ($candidates as $pat) {
|
||
foreach ($names as $n) {
|
||
if (str_contains(strtolower($n), $pat)) {
|
||
$chosen = $n;
|
||
break 2;
|
||
}
|
||
}
|
||
}
|
||
if ($chosen && Schema::connection($conn)->hasTable($chosen)) {
|
||
$legacyFields = Schema::connection($conn)->getColumnListing($chosen);
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$legacyFields = $legacyFields ?: [];
|
||
}
|
||
|
||
return view('admin.gescon-import.relations', compact('amministratore', 'segment', 'legacyCode', 'legacyFields', 'existingRelations'));
|
||
}
|
||
|
||
/**
|
||
* Configurazione import GESCON
|
||
*/
|
||
public function config(Request $request)
|
||
{
|
||
if (!(auth()->user() && auth()->user()->can('gescon-import.config'))) {
|
||
return redirect()->route('admin.gescon-import.index')->with('error', 'Non hai i permessi per accedere alla configurazione.');
|
||
}
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
// Impostazioni utente predefinite per percorso sorgente e DSN MDB
|
||
$settings = [
|
||
'gescon.source_path' => UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon'),
|
||
'gescon.mdb_stabili' => UserSetting::get('gescon.mdb_stabili', '/mnt/gescon-archives/gescon/dbc/Stabili.mdb'),
|
||
'gescon.mdb_index' => UserSetting::get('gescon.mdb_index', '/mnt/gescon-archives/gescon/cartellestabili_e_anni.mdb'),
|
||
'gescon.import_conn' => UserSetting::get('gescon.import_conn', 'gescon_import'),
|
||
];
|
||
|
||
// Nuova scansione archivi MDB disponibili (1 livello) per UI selezione archivi
|
||
$mdbArchives = [];
|
||
$scanBase = $settings['gescon.source_path'];
|
||
try {
|
||
if (is_dir($scanBase)) {
|
||
$rii = scandir($scanBase);
|
||
foreach ($rii as $f) {
|
||
if ($f === '.' || $f === '..') continue;
|
||
$full = rtrim($scanBase, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $f;
|
||
if (is_file($full) && preg_match('/\.mdb$/i', $f)) {
|
||
$mdbArchives[] = ['name' => $f, 'path' => $full, 'size' => filesize($full)];
|
||
}
|
||
if (is_dir($full)) {
|
||
// cerca file .mdb in cartella (solo primo livello per performance)
|
||
foreach (glob($full . DIRECTORY_SEPARATOR . '*.mdb') as $m) {
|
||
$mdbArchives[] = ['name' => basename($m), 'path' => $m, 'dir' => $f, 'size' => filesize($m)];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$mdbArchives = $mdbArchives ?: [];
|
||
}
|
||
// Valori precedenti selezionati
|
||
$selectedArchives = UserSetting::get('gescon.selected_archives', []);
|
||
|
||
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '';
|
||
$legacyBasePath = $settings['gescon.source_path'] ?? '';
|
||
$mdbPath = $settings['gescon.mdb_stabili'] ?? '';
|
||
$mdbIndexPath = $settings['gescon.mdb_index'] ?? '';
|
||
$dataset = MigrationDataset::where('key', 'stabili')->orderByDesc('updated_at')->first();
|
||
$cacheKey = 'gescon_legacy_preview:' . md5(($legacyBasePath ?: '') . '|' . ($mdbPath ?: '') . '|' . ($dataset?->id ?? 0) . '|' . ($dataset?->records_count ?? 0));
|
||
if ($request->boolean('refresh')) {
|
||
Cache::forget($cacheKey);
|
||
}
|
||
$preview = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($dataset, $mdbPath, $hasMdbTools, $legacyBasePath, $mdbIndexPath) {
|
||
try {
|
||
$preview = [];
|
||
if ($dataset && $dataset->records_count > 0) {
|
||
$records = MigrationRecord::where('dataset_id', $dataset->id)->limit(5000)->get();
|
||
$map = [];
|
||
foreach ($records as $rec) {
|
||
$data = (array)($rec->data ?? []);
|
||
$legacy = $rec->legacy_key
|
||
?: ($data['nome_directory'] ?? ($data['internet_cod_stab'] ?? ($data['cod_stabile'] ?? null)));
|
||
if (!$legacy) continue;
|
||
$denom = $data['denominazione'] ?? ($data['fe_denominazione'] ?? null);
|
||
$map[(string)$legacy] = [
|
||
'legacy_code' => trim((string)$legacy),
|
||
'denominazione' => $denom ? trim((string)$denom) : null,
|
||
];
|
||
}
|
||
$preview = array_values($map);
|
||
}
|
||
if (empty($preview) && $hasMdbTools && $mdbIndexPath && is_file($mdbIndexPath) && is_readable($mdbIndexPath)) {
|
||
$preview = $this->legacy->scanStabiliFromMasterIndex($mdbIndexPath, $legacyBasePath);
|
||
}
|
||
if (empty($preview) && $mdbPath && is_file($mdbPath) && $hasMdbTools) {
|
||
$preview = $this->legacy->scanStabiliFromMdb($mdbPath);
|
||
}
|
||
if (empty($preview) && is_dir($legacyBasePath)) {
|
||
$preview = $this->legacy->scanLegacyCodesFromDir($legacyBasePath);
|
||
}
|
||
if (!empty($preview)) {
|
||
$preview = $this->legacy->enrichLegacyWithArchives($preview, $legacyBasePath);
|
||
}
|
||
return $preview;
|
||
} catch (\Throwable $e) {
|
||
return [];
|
||
}
|
||
});
|
||
|
||
$archive = [
|
||
'source_registered' => (bool) MigrationSource::where('name', 'GESCON Stabili')->exists(),
|
||
'dataset_records' => (int) ($dataset->records_count ?? 0),
|
||
];
|
||
|
||
$mappingSources = UserSetting::get('gescon.mapping_sources', []);
|
||
return view('admin.gescon-import.config', compact('amministratore', 'settings', 'preview', 'hasMdbTools', 'legacyBasePath', 'archive', 'mdbArchives', 'selectedArchives', 'mappingSources'));
|
||
}
|
||
|
||
/**
|
||
* API: elenca archivi MDB (cache semplice in memoria richiesta) – simile alla logica inline in config().
|
||
*/
|
||
public function mdbScan(Request $request)
|
||
{
|
||
if (!(auth()->user() && auth()->user()->can('gescon-import.config'))) {
|
||
return response()->json(['ok' => false, 'error' => 'Permesso negato'], 403);
|
||
}
|
||
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
$out = [];
|
||
try {
|
||
if (is_dir($base)) {
|
||
foreach (scandir($base) as $f) {
|
||
if ($f === '.' || $f === '..') continue;
|
||
$full = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $f;
|
||
if (is_file($full) && preg_match('/\.mdb$/i', $f)) {
|
||
$out[] = ['name' => $f, 'path' => $full, 'size' => @filesize($full)];
|
||
}
|
||
if (is_dir($full)) {
|
||
foreach (glob($full . DIRECTORY_SEPARATOR . '*.mdb') as $m) {
|
||
$out[] = ['name' => basename($m), 'path' => $m, 'dir' => $f, 'size' => @filesize($m)];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage(), 'archives' => $out]);
|
||
}
|
||
return response()->json(['ok' => true, 'base' => $base, 'archives' => $out]);
|
||
}
|
||
|
||
/**
|
||
* API: lista tabelle in un archivio MDB (usa mdb-tables). Param: path (validato deve stare sotto source_path).
|
||
*/
|
||
public function mdbTables(Request $request)
|
||
{
|
||
if (!(auth()->user() && auth()->user()->can('gescon-import.config'))) {
|
||
return response()->json(['ok' => false, 'error' => 'Permesso negato'], 403);
|
||
}
|
||
$path = (string)$request->query('path');
|
||
$base = realpath(UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon')) ?: null;
|
||
$real = realpath($path) ?: null;
|
||
if (!$real || !$base || !str_starts_with($real, $base)) {
|
||
return response()->json(['ok' => false, 'error' => 'Percorso non consentito']);
|
||
}
|
||
$tables = [];
|
||
try {
|
||
$bin = trim((string)@shell_exec('command -v mdb-tables')) ?: 'mdb-tables';
|
||
$out = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($bin), escapeshellarg($real)));
|
||
if ($out) {
|
||
foreach (preg_split('/\r?\n/', trim($out)) as $t) {
|
||
if ($t !== '') $tables[] = $t;
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()]);
|
||
}
|
||
return response()->json(['ok' => true, 'tables' => $tables]);
|
||
}
|
||
|
||
/**
|
||
* API: colonne di una tabella MDB (legge prima riga CSV via mdb-export).
|
||
*/
|
||
public function mdbColumns(Request $request)
|
||
{
|
||
if (!(auth()->user() && auth()->user()->can('gescon-import.config'))) {
|
||
return response()->json(['ok' => false, 'error' => 'Permesso negato'], 403);
|
||
}
|
||
$path = (string)$request->query('path');
|
||
$table = (string)$request->query('table');
|
||
if (!$path || !$table) return response()->json(['ok' => false, 'error' => 'path e table richiesti'], 422);
|
||
$base = realpath(UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon')) ?: null;
|
||
$real = realpath($path) ?: null;
|
||
if (!$real || !$base || !str_starts_with($real, $base)) {
|
||
return response()->json(['ok' => false, 'error' => 'Percorso non consentito']);
|
||
}
|
||
try {
|
||
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: 'mdb-export';
|
||
$tmp = tempnam(sys_get_temp_dir(), 'mdbcols_');
|
||
$cmd = sprintf(
|
||
'%s -D %s -d %s -q %s -R "\n" %s %s > %s 2>/dev/null',
|
||
escapeshellarg($binExport),
|
||
escapeshellarg('%Y-%m-%d'),
|
||
escapeshellarg('|'),
|
||
escapeshellarg('"'),
|
||
escapeshellarg($real),
|
||
escapeshellarg($table),
|
||
escapeshellarg($tmp)
|
||
);
|
||
@shell_exec($cmd);
|
||
$cols = [];
|
||
if (@filesize($tmp) > 0) {
|
||
$fh = fopen($tmp, 'r');
|
||
$header = fgetcsv($fh, 0, '|');
|
||
if ($header) {
|
||
$cols = array_map(fn($x) => trim((string)$x), $header);
|
||
}
|
||
fclose($fh);
|
||
}
|
||
@unlink($tmp);
|
||
return response()->json(['ok' => true, 'columns' => $cols]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Esegue l'import GESCON via web interface
|
||
*/
|
||
public function executeImport(Request $request)
|
||
{
|
||
// Sicurezza aggiuntiva: blocca se l'utente non ha il permesso di eseguire
|
||
if (!auth()->user()->can('gescon-import.execute')) {
|
||
return redirect()->back()->with('error', 'Non hai i permessi per eseguire l\'import.');
|
||
}
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
|
||
$request->validate([
|
||
'tipo_import' => 'required|in:stabili,fornitori,condomini,all',
|
||
'test_mode' => 'boolean',
|
||
'legacy_code' => 'sometimes|nullable|string',
|
||
'archive_source_path' => 'sometimes|nullable|string',
|
||
'refresh_archive' => 'sometimes|boolean',
|
||
]);
|
||
|
||
// Guard RBAC: per default gli amministratori non possono creare nuovi stabili
|
||
// Il super-admin può abilitare singolarmente: amministratori.abilita_import_stabili = 1
|
||
$isSuperAdmin = auth()->user()->hasRole('super-admin') || auth()->user()->can('gescon-import.config');
|
||
|
||
try {
|
||
$logger = new ImportProgressLogger();
|
||
// Esegue pipeline reale o dry-run per uno specifico stabile o per tutti (stabili)
|
||
$legacyCode = $request->input('legacy_code'); // es. 0024, 0019
|
||
// Percorsi sorgente: usa default sicuri se non configurati
|
||
$path = $request->input('path') ?: UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
$mdbOverride = $request->input('mdb_path');
|
||
$mdbCondomin = $request->input('mdb_condomin_path'); // singolo_anno.mdb (tabella condomin)
|
||
$splitMode = $request->input('split_mode'); // none|palazzine|stabili-per-scala
|
||
$anno = $request->input('anno');
|
||
$importAll = $request->boolean('import_all');
|
||
// Mappa tipo_import in step singolo quando opportuno (la pagina Stabili invia tipo_import=stabili)
|
||
$solo = $request->input('solo'); // priorità a parametro esplicito
|
||
if (!$solo) {
|
||
$tipo = $request->input('tipo_import');
|
||
$map = [
|
||
'stabili' => 'stabili',
|
||
'condomini' => 'soggetti', // corrisponde allo step soggetti
|
||
'fornitori' => null, // non gestito in questa pipeline
|
||
'all' => null,
|
||
];
|
||
if (array_key_exists($tipo, $map)) {
|
||
$solo = $map[$tipo];
|
||
}
|
||
}
|
||
$dry = $request->boolean('test_mode');
|
||
$archiveSourceOverride = $request->input('archive_source_path');
|
||
$refreshArchive = $request->boolean('refresh_archive');
|
||
|
||
// Applica guardie prima di avviare l'Artisan
|
||
if (!$isSuperAdmin) {
|
||
// Blocco import STABILI per non-super-admin
|
||
$tipo = (string)$request->input('tipo_import');
|
||
if ($tipo === 'stabili' || $solo === 'stabili') {
|
||
// Eccezione: consenti se il super admin ha abilitato l'import per questo amministratore
|
||
if (!$amministratore->abilita_import_stabili) {
|
||
return redirect()->back()->with('error', 'Import STABILI disabilitato per il tuo profilo. Chiedi al Super Admin l\'abilitazione.');
|
||
}
|
||
}
|
||
// Per altri import, richiedi che lo stabile esista già se è stato indicato un legacy_code
|
||
if ($legacyCode) {
|
||
$exists = Stabile::where('amministratore_id', $amministratore->id)
|
||
->where(function ($q) use ($legacyCode) {
|
||
$q->where('codice_stabile', $legacyCode)
|
||
->orWhere('codice_interno', $legacyCode);
|
||
})
|
||
->exists();
|
||
if (!$exists) {
|
||
return redirect()->back()->with('error', 'Lo stabile selezionato non è stato ancora creato. Contatta il Super Admin per l\'inizializzazione.');
|
||
}
|
||
}
|
||
}
|
||
|
||
$params = [];
|
||
if ($legacyCode) $params['--stabile'] = $legacyCode;
|
||
if ($path) $params['--path'] = rtrim($path, '/');
|
||
if ($solo) $params['--solo'] = $solo;
|
||
if ($dry) $params['--dry-run'] = true;
|
||
if ($anno) $params['--anno'] = $anno;
|
||
// Super-admin: instrada verso un amministratore specifico se fornito
|
||
if ($isSuperAdmin && $request->filled('amministratore_id')) {
|
||
$params['--amministratore-id'] = (int) $request->input('amministratore_id');
|
||
} else {
|
||
// Fallback: onora l'amministratore target salvato nel mapping (se presente)
|
||
if ($legacyCode) {
|
||
try {
|
||
$mapRec = GesconImportMapping::where('user_id', auth()->id())
|
||
->where('legacy_code', $legacyCode)
|
||
->first();
|
||
$targetAdminId = (int) data_get($mapRec, 'meta.target_admin_id');
|
||
if ($targetAdminId) {
|
||
// Consenti se il target coincide con l'amministratore corrente o se l'utente ha permesso config
|
||
$currentAdmin = $amministratore?->id ?? null;
|
||
if ($isSuperAdmin || ($currentAdmin && $currentAdmin === $targetAdminId)) {
|
||
$params['--amministratore-id'] = $targetAdminId;
|
||
}
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!isset($params['--amministratore-id']) && !$isSuperAdmin && $amministratore) {
|
||
$params['--amministratore-id'] = $amministratore->id;
|
||
}
|
||
|
||
// Gestione percorso MDB di Stabili: usa override, altrimenti impostazione utente, e passalo al comando
|
||
if ($mdbOverride && is_string($mdbOverride)) {
|
||
UserSetting::set('gescon.mdb_stabili', $mdbOverride);
|
||
}
|
||
// MDB Stabili: preferisci override, altrimenti impostazione utente con default; se non presente ma esiste $path/Stabili.mdb, usa quello
|
||
$mdbPath = $mdbOverride ?: UserSetting::get('gescon.mdb_stabili', '/mnt/gescon-archives/gescon/dbc/Stabili.mdb');
|
||
// Se il percorso non è leggibile, prova una copia server-friendly in storage
|
||
if ((!$mdbOverride) && (!is_string($mdbPath) || $mdbPath === '' || !is_readable($mdbPath))) {
|
||
$storageFallback = '/var/www/netgescon/storage/gescon/Stabili.mdb';
|
||
if (is_file($storageFallback) && is_readable($storageFallback)) {
|
||
$mdbPath = $storageFallback;
|
||
}
|
||
}
|
||
if (!$mdbOverride && (!is_string($mdbPath) || $mdbPath === '' || !is_file($mdbPath))) {
|
||
$guess = rtrim((string)$path, '/') . '/Stabili.mdb';
|
||
if (is_file($guess)) {
|
||
$mdbPath = $guess;
|
||
}
|
||
}
|
||
if ($mdbPath && is_string($mdbPath)) {
|
||
$params['--mdb'] = $mdbPath;
|
||
}
|
||
// MDB specifico per tabella condomin (singolo_anno.mdb)
|
||
if ($mdbCondomin && is_string($mdbCondomin)) {
|
||
$params['--mdb-condomin'] = $mdbCondomin;
|
||
}
|
||
|
||
if (is_string($splitMode) && in_array($splitMode, ['none', 'palazzine', 'stabili-per-scala'], true)) {
|
||
$params['--split-mode'] = $splitMode;
|
||
}
|
||
|
||
// In dry-run evita ricreare viste QA e limita i record per reattività
|
||
if ($dry) {
|
||
$params['--no-views'] = true;
|
||
if (!$request->filled('limit')) {
|
||
$params['--limit'] = 200; // default veloce
|
||
}
|
||
}
|
||
|
||
// Opzione opzionale per forzare l'override dei campi mappati (es. CF)
|
||
if ($request->boolean('force_mapping')) {
|
||
$params['--force-mapping'] = true;
|
||
}
|
||
// Preflight minimo: per import STABILI richiede almeno una sorgente valida (MDB o staging)
|
||
if (($solo === 'stabili' || $request->input('tipo_import') === 'stabili') && !$importAll) {
|
||
$hasMdb = $mdbPath && is_file($mdbPath);
|
||
$hasStaging = false;
|
||
try {
|
||
$hasStaging = (
|
||
(Schema::connection('gescon_import')->hasTable('stabili')) ||
|
||
(Schema::connection('gescon_import')->hasTable('condomin'))
|
||
);
|
||
} catch (\Throwable $e) {
|
||
$hasStaging = false;
|
||
}
|
||
if (!$hasMdb && !$hasStaging) {
|
||
$hint = 'Sorgente non configurata: imposta il percorso di Stabili.mdb in Impostazioni oppure carica la tabella di staging (condomin/stabili).';
|
||
if ($mdbPath) {
|
||
$hint .= ' Percorso risolto: ' . $mdbPath;
|
||
if (is_string($mdbPath) && str_starts_with($mdbPath, '/home/')) {
|
||
$hint .= ' (attenzione ai permessi: i file sotto /home potrebbero non essere leggibili dal server web)';
|
||
}
|
||
}
|
||
return redirect()->back()->with('error', $hint);
|
||
}
|
||
}
|
||
|
||
// Inizio logging
|
||
$taskKey = $logger->begin('import-full', [
|
||
'legacy_code' => $legacyCode,
|
||
'solo' => $solo,
|
||
'dry' => $dry,
|
||
'anno' => $anno,
|
||
'path' => $path,
|
||
]);
|
||
$logger->checkpoint('preflight', ['params' => $params]);
|
||
|
||
// Caso: import massivo di tutti gli stabili dalla UI
|
||
if ($importAll && ($solo === 'stabili' || $request->input('tipo_import') === 'stabili')) {
|
||
if (!$isSuperAdmin) {
|
||
return redirect()->back()->with('error', 'Solo il Super Admin può eseguire l\'import massivo degli stabili.');
|
||
}
|
||
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '';
|
||
$done = 0;
|
||
$errors = 0;
|
||
$codes = [];
|
||
$totCreated = 0;
|
||
$totUpdated = 0;
|
||
$mdbPath = $mdbOverride ?: UserSetting::get('gescon.mdb_stabili', '/mnt/gescon-archives/gescon/dbc/Stabili.mdb');
|
||
if (!is_file($mdbPath)) {
|
||
$guess = rtrim((string)$path, '/') . '/Stabili.mdb';
|
||
if (is_file($guess)) {
|
||
$mdbPath = $guess;
|
||
}
|
||
}
|
||
if ($hasMdbTools && $mdbPath && is_file($mdbPath)) {
|
||
$items = $this->scanStabiliFromMdb($mdbPath);
|
||
$codes = array_values(array_unique(array_map(fn($it) => (string)($it['legacy_code'] ?? ''), $items)));
|
||
}
|
||
if (empty($codes)) {
|
||
// Fallback a sorgente directory
|
||
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
if ($base && is_dir($base)) {
|
||
$items = $this->scanLegacyCodesFromDir($base);
|
||
$codes = array_values(array_unique(array_map(fn($it) => (string)($it['legacy_code'] ?? ''), $items)));
|
||
}
|
||
}
|
||
foreach ($codes as $code) {
|
||
if ($code === '') continue;
|
||
try {
|
||
$pp = $params;
|
||
$pp['--stabile'] = $code;
|
||
Artisan::call('gescon:import-full', $pp);
|
||
$out = (string) Artisan::output();
|
||
$logger->checkpoint('import.stabile.done', ['code' => $code]);
|
||
$logger->stdout("\n===== IMPORT {$code} =====\n" . $out . "\n");
|
||
$counts = $this->parseStabiliCountsFromOutput($out);
|
||
$totCreated += $counts['created'] ?? 0;
|
||
$totUpdated += $counts['updated'] ?? 0;
|
||
$done++;
|
||
} catch (\Throwable $e) {
|
||
$errors++;
|
||
$logger->error('import.stabile.error', ['code' => $code, 'error' => $e->getMessage()]);
|
||
// Continua con gli altri
|
||
}
|
||
}
|
||
$logger->finish(['mode' => 'bulk', 'done' => $done, 'errors' => $errors, 'created' => $totCreated, 'updated' => $totUpdated]);
|
||
return redirect()->back()
|
||
->with('success', "Import stabili completato: {$done} eseguiti" . ($errors ? ", errori: {$errors}" : ''))
|
||
->with('import_summary', [
|
||
'mode' => 'bulk',
|
||
'done' => $done,
|
||
'errors' => $errors,
|
||
'created' => $totCreated,
|
||
'updated' => $totUpdated,
|
||
]);
|
||
}
|
||
|
||
Artisan::call('gescon:import-full', $params);
|
||
$output = (string) Artisan::output();
|
||
$logger->stdout($output);
|
||
$counts = $this->parseStabiliCountsFromOutput($output);
|
||
|
||
$archiveMeta = null;
|
||
if (!$dry && $legacyCode && $amministratore) {
|
||
$archiveBase = is_string($archiveSourceOverride) ? trim($archiveSourceOverride) : null;
|
||
if ($archiveBase === '') {
|
||
$archiveBase = null;
|
||
}
|
||
if (!$archiveBase && $path) {
|
||
$archiveBase = $path;
|
||
}
|
||
try {
|
||
$archiveMeta = $this->archiveSync->sync(
|
||
$amministratore,
|
||
$legacyCode,
|
||
$archiveBase,
|
||
$refreshArchive
|
||
);
|
||
$logger->checkpoint('post.archive', [
|
||
'legacy_code' => $legacyCode,
|
||
'source_base' => $archiveBase,
|
||
'target' => $archiveMeta['target'] ?? null,
|
||
]);
|
||
} catch (\Throwable $e) {
|
||
$archiveMeta = [
|
||
'legacy_code' => $legacyCode,
|
||
'error' => $e->getMessage(),
|
||
];
|
||
$logger->error('post.archive.error', ['error' => $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
// Post-import: crea/aggiorna gestioni per anno a partire dagli archivi legacy (generale_stabile/anni)
|
||
try {
|
||
if (!empty($legacyCode) && !$dry) {
|
||
$gestOpt = [
|
||
'--stabile' => $legacyCode,
|
||
'--root' => rtrim((string)($path ?: '/mnt/gescon-archives/gescon'), '/'),
|
||
];
|
||
if ($request->boolean('force_update_gestioni')) {
|
||
$gestOpt['--force-update-months'] = true;
|
||
}
|
||
Artisan::call('gescon:import-gestioni', $gestOpt);
|
||
$logger->checkpoint('post.gestioni');
|
||
// Non bloccare in caso di errori: esegui in best-effort
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$logger->error('post.gestioni.error', ['error' => $e->getMessage()]);
|
||
// Log silenzioso o aggiungere a summary in futuro
|
||
}
|
||
|
||
// Post-import: se abbiamo importato solo gli STABILI per uno specifico legacy, esegui anche lo step BANCHE
|
||
try {
|
||
if (!$dry && !empty($legacyCode)) {
|
||
$didOnlyStabili = ($solo === 'stabili') || ($request->input('tipo_import') === 'stabili');
|
||
if ($didOnlyStabili) {
|
||
$pp = [];
|
||
if ($legacyCode) $pp['--stabile'] = $legacyCode;
|
||
if ($path) $pp['--path'] = rtrim($path, '/');
|
||
$pp['--solo'] = 'banche';
|
||
Artisan::call('gescon:import-full', $pp);
|
||
$logger->checkpoint('post.banche');
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$logger->error('post.banche.error', ['error' => $e->getMessage()]);
|
||
// Ignora in caso di failure; verrà ripetuto in import completi
|
||
}
|
||
|
||
// Aggiorna mapping record
|
||
if ($legacyCode) {
|
||
$mapping = GesconImportMapping::firstOrCreate(
|
||
['user_id' => auth()->id(), 'legacy_code' => $legacyCode],
|
||
['source_path' => $path, 'status' => 'pending']
|
||
);
|
||
$mapping->status = $dry ? 'dry-run' : 'synced';
|
||
$mapping->legacy_denominazione = $request->input('legacy_denominazione');
|
||
$mapping->last_dry_run_at = $dry ? now() : $mapping->last_dry_run_at;
|
||
$mapping->last_sync_at = $dry ? $mapping->last_sync_at : now();
|
||
$meta = is_array($mapping->meta) ? $mapping->meta : [];
|
||
$meta['source_path'] = $path;
|
||
if ($archiveMeta !== null) {
|
||
$meta['archive'] = array_merge(
|
||
array_filter([
|
||
'source_base' => $archiveBase,
|
||
'refresh_requested' => $refreshArchive,
|
||
], fn($value) => $value !== null && $value !== ''),
|
||
$archiveMeta
|
||
);
|
||
}
|
||
if ($amministratore) {
|
||
$meta['target_admin_id'] = $amministratore->id;
|
||
}
|
||
if (!empty($legacyCode)) {
|
||
$meta['legacy_code'] = $legacyCode;
|
||
}
|
||
$mapping->meta = $meta;
|
||
$mapping->save();
|
||
}
|
||
|
||
$logger->finish(['mode' => 'single', 'legacy_code' => $legacyCode, 'counts' => $counts]);
|
||
return redirect()->back()
|
||
->with('success', 'Import GESCON avviato con successo.')
|
||
->with('import_summary', [
|
||
'mode' => 'single',
|
||
'legacy_code' => $legacyCode,
|
||
'created' => $counts['created'] ?? null,
|
||
'updated' => $counts['updated'] ?? null,
|
||
]);
|
||
} catch (\Exception $e) {
|
||
if (isset($logger)) {
|
||
$logger->error('fatal', ['error' => $e->getMessage()]);
|
||
}
|
||
return redirect()->back()
|
||
->with('error', 'Errore durante l\'import: ' . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Estrae i conteggi dallo stdout del comando artisan gescon:import-full
|
||
*/
|
||
private function parseStabiliCountsFromOutput(string $out): array
|
||
{
|
||
$created = null;
|
||
$updated = null;
|
||
// Formato tipico: "stabili creati: 0, aggiornati: 2"
|
||
if (preg_match('/stabili[^\n]*creati\s*:\s*(\d+)[^\n]*aggiornati\s*:\s*(\d+)/i', $out, $m)) {
|
||
$created = (int)($m[1] ?? 0);
|
||
$updated = (int)($m[2] ?? 0);
|
||
} elseif (preg_match('/creati\s*:\s*(\d+).*aggiornati\s*:\s*(\d+)/i', $out, $m)) {
|
||
$created = (int)($m[1] ?? 0);
|
||
$updated = (int)($m[2] ?? 0);
|
||
}
|
||
return ['created' => $created ?? 0, 'updated' => $updated ?? 0];
|
||
}
|
||
|
||
/**
|
||
* Restituisce un record di esempio dall'archivio legacy per anteprima mapping.
|
||
*/
|
||
public function sampleLegacy(Request $request)
|
||
{
|
||
$legacy = $request->query('legacy_code');
|
||
$index = (int)$request->query('index', 0);
|
||
$index = max(0, $index);
|
||
try {
|
||
// Preferisci direttamente Stabili.mdb per il mapping (richiesta utente); fallback a dataset se assente
|
||
$hasMdb = trim((string)@shell_exec('command -v mdb-export')) !== '';
|
||
$mdbPath = \App\Models\UserSetting::get('gescon.mdb_stabili');
|
||
if (!$mdbPath || !@is_file($mdbPath) || !@is_readable($mdbPath)) {
|
||
$storageFallback = '/var/www/netgescon/storage/gescon/Stabili.mdb';
|
||
if (@is_file($storageFallback) && @is_readable($storageFallback)) {
|
||
$mdbPath = $storageFallback;
|
||
}
|
||
}
|
||
if ($hasMdb && $mdbPath && is_file($mdbPath)) {
|
||
$row = $this->sampleStabileFromMdb($mdbPath, $legacy, $index);
|
||
if (!empty($row)) {
|
||
$legacyKey = $row['legacy_key'] ?? null;
|
||
unset($row['legacy_key']);
|
||
return response()->json(['ok' => true, 'index' => $index, 'data' => $row, 'legacy_key' => $legacyKey]);
|
||
}
|
||
}
|
||
|
||
// Fallback: archivio dataset (se presente)
|
||
$ds = MigrationDataset::where('key', 'stabili')->orderByDesc('updated_at')->first();
|
||
if ($ds) {
|
||
$rec = MigrationRecord::where('dataset_id', $ds->id)
|
||
->when($legacy, function ($q) use ($legacy) {
|
||
$q->where('legacy_key', $legacy);
|
||
})
|
||
->orderBy('id')
|
||
->skip($index)
|
||
->take(1)
|
||
->first();
|
||
if ($rec) {
|
||
return response()->json(['ok' => true, 'index' => $index, 'data' => (array)($rec->data ?? []), 'legacy_key' => $rec->legacy_key]);
|
||
}
|
||
}
|
||
|
||
return response()->json(['ok' => false, 'error' => 'Anteprima non disponibile: nessun dataset e MDB non accessibile', 'index' => $index]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => 'Errore: ' . $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Sample Unità Immobiliari (preview mapping) – placeholder: ritorna record mock o da staging condomin.
|
||
*/
|
||
public function sampleUnita(Request $request)
|
||
{
|
||
$legacy = $request->query('legacy_code');
|
||
$index = max(0, (int)$request->query('index', 0));
|
||
try {
|
||
$rows = [];
|
||
if ($legacy && \Schema::connection('gescon_import')->hasTable('condomin')) {
|
||
$rows = \DB::connection('gescon_import')
|
||
->table('condomin')
|
||
->when($legacy, function ($q) use ($legacy) {
|
||
$q->where('cod_stabile', $legacy);
|
||
})
|
||
->select(['cod_stabile', 'cod_cond', 'scala', 'interno', 'cognome', 'nome', 'codice_fiscale', 'millesimi_generali', 'millesimi_risc'])
|
||
->orderBy('cod_cond')
|
||
->skip($index)
|
||
->take(1)
|
||
->get()->map(fn($r) => (array)$r)->toArray();
|
||
}
|
||
if (empty($rows)) {
|
||
$rows[] = [
|
||
'cod_stabile' => $legacy,
|
||
'cod_cond' => 'U' . str_pad((string)$index, 4, '0', STR_PAD_LEFT),
|
||
'scala' => 'A',
|
||
'interno' => '1',
|
||
'cognome' => 'Rossi',
|
||
'nome' => 'Mario',
|
||
'codice_fiscale' => 'RSSMRA80A01H501Z',
|
||
'millesimi_generali' => 45.67,
|
||
'millesimi_risc' => 12.34,
|
||
];
|
||
}
|
||
return response()->json(['ok' => true, 'index' => $index, 'data' => $rows[0]]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Sample Tabelle millesimali (preview) – per ora mock.
|
||
*/
|
||
public function sampleTabelle(Request $request)
|
||
{
|
||
$legacy = $request->query('legacy_code');
|
||
$index = max(0, (int)$request->query('index', 0));
|
||
try {
|
||
// TODO: estrarre da eventuale staging o MDB futuro
|
||
$mock = [
|
||
'cod_stabile' => $legacy,
|
||
'tabella' => 'Generali',
|
||
'voce' => 'Scala A (app1)',
|
||
'millesimi' => 45.6700,
|
||
'nord_x10' => null,
|
||
];
|
||
return response()->json(['ok' => true, 'index' => $index, 'data' => $mock]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Sample Voci (Piano dei Conti) – mock finché non importiamo reale.
|
||
*/
|
||
public function sampleVoci(Request $request)
|
||
{
|
||
$legacy = $request->query('legacy_code');
|
||
$index = max(0, (int)$request->query('index', 0));
|
||
try {
|
||
$mock = [
|
||
'cod_stabile' => $legacy,
|
||
'cod_voce' => 'A' . str_pad((string)$index, 3, '0', STR_PAD_LEFT),
|
||
'descrizione' => 'Spese generali condominio',
|
||
'gruppo' => 'MANUT',
|
||
'tipo' => 'ordinaria',
|
||
];
|
||
return response()->json(['ok' => true, 'index' => $index, 'data' => $mock]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Restituisce lista anni disponibili (cartelle con singolo_anno.mdb) per un codice legacy.
|
||
*/
|
||
public function years(Request $request)
|
||
{
|
||
$legacy = (string) $request->query('legacy_code');
|
||
if (!$legacy) return response()->json(['ok' => false, 'error' => 'legacy_code richiesto'], 422);
|
||
try {
|
||
$years = $this->legacy->scanLegacyYears($legacy);
|
||
return response()->json(['ok' => true, 'legacy_code' => $legacy, 'years' => $years]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Ritorna i dati di una gestione per anno (ordinaria/riscaldamento + straordinarie) dal filesystem legacy.
|
||
* JSON shape attesa dal frontend:
|
||
* {
|
||
* ok: true,
|
||
* anno: 2024,
|
||
* ordinaria: { anno:2024, stato:'aperta|chiusa', rate:{1:1,4:1,...} },
|
||
* riscaldamento: { anno:2024, stato:'aperta|chiusa', rate:{...} },
|
||
* straordinarie: [ { id:'STR001', descrizione:'Facciate', data_inizio:'2024-03-10', data_fine:null, rate_totali:6, rate_emesse:3 } ]
|
||
* }
|
||
*/
|
||
public function gestioniYear(Request $request)
|
||
{
|
||
$legacy = (string)$request->query('legacy_code');
|
||
$anno = (int)$request->query('anno');
|
||
if (!$legacy || !$anno) {
|
||
return response()->json(['ok' => false, 'error' => 'legacy_code e anno richiesti']);
|
||
}
|
||
$data = $this->legacy->gestioniYear($legacy, $anno);
|
||
return response()->json($data);
|
||
}
|
||
|
||
/**
|
||
* Anteprima Unità (tabella condomin) per anno: ritorna headers e qualche riga dal singolo_anno.mdb
|
||
* GET params: legacy_code, anno, limit (default 20)
|
||
* Response: { ok, legacy_code, anno, headers:[], rows:[{col:val,...}] }
|
||
*/
|
||
public function unitaYear(Request $request)
|
||
{
|
||
$legacy = (string)$request->query('legacy_code');
|
||
$anno = (int)$request->query('anno');
|
||
$limit = (int)($request->query('limit', 20));
|
||
if (!$legacy) {
|
||
return response()->json(['ok' => false, 'error' => 'legacy_code richiesto'], 422);
|
||
}
|
||
$data = $this->legacy->unitaYear($legacy, $anno ?: null, $limit);
|
||
return response()->json($data);
|
||
}
|
||
|
||
/**
|
||
* Elenco casse/conti legacy (inclusa sempre la cassa CON) dal generale_stabile.mdb.
|
||
*/
|
||
public function casse(Request $request)
|
||
{
|
||
$legacy = (string)$request->query('legacy_code');
|
||
if (!$legacy) return \response()->json(['ok' => false, 'error' => 'legacy_code richiesto']);
|
||
$rows = $this->legacy->casse($legacy);
|
||
return response()->json(['ok' => true, 'legacy_code' => $legacy, 'casse' => $rows]);
|
||
}
|
||
|
||
/**
|
||
* Applica il mapping (banche + gestioni) persistendo in DB per uno stabile target.
|
||
*/
|
||
public function applyMappingImport(Request $request)
|
||
{
|
||
$data = $request->validate([
|
||
'legacy_code' => 'required|string',
|
||
'stabile_id' => 'required|integer|exists:stabili,id',
|
||
'anni' => 'sometimes|array',
|
||
'refresh_archive' => 'sometimes|boolean',
|
||
'archive_source_path' => 'sometimes|string|nullable',
|
||
]);
|
||
$legacy = $data['legacy_code'];
|
||
$stabileId = (int)$data['stabile_id'];
|
||
$anni = $data['anni'] ?? null;
|
||
$stabile = Stabile::with('amministratore')->findOrFail($stabileId);
|
||
$amministratoreDest = $stabile->amministratore;
|
||
$tenantId = (string) (auth()->user()->tenant_id ?? 'default');
|
||
$mapping = GesconImportMapping::where('user_id', auth()->id())->where('legacy_code', $legacy)->first();
|
||
if (!$mapping) {
|
||
return response()->json(['ok' => false, 'error' => 'Mapping non trovato per legacy_code'], 404);
|
||
}
|
||
$meta = $mapping->meta ?? [];
|
||
$archiveMeta = null;
|
||
if ($legacy && $amministratoreDest) {
|
||
try {
|
||
$archiveMeta = $this->archiveSync->sync(
|
||
$amministratoreDest,
|
||
$legacy,
|
||
$data['archive_source_path'] ?? null,
|
||
(bool) ($data['refresh_archive'] ?? false)
|
||
);
|
||
} catch (\Throwable $e) {
|
||
$archiveMeta = [
|
||
'legacy_code' => $legacy,
|
||
'error' => $e->getMessage(),
|
||
];
|
||
}
|
||
} elseif ($legacy) {
|
||
$archiveMeta = [
|
||
'legacy_code' => $legacy,
|
||
'error' => 'Amministratore associato non trovato.',
|
||
];
|
||
}
|
||
if ($archiveMeta) {
|
||
$meta['archive'] = [
|
||
'legacy_code' => $archiveMeta['legacy_code'] ?? $legacy,
|
||
'source' => $archiveMeta['source'] ?? null,
|
||
'target' => $archiveMeta['target'] ?? null,
|
||
'size_bytes' => $archiveMeta['size_bytes'] ?? null,
|
||
'files_count' => $archiveMeta['files_count'] ?? null,
|
||
'directories_count' => $archiveMeta['directories_count'] ?? null,
|
||
'latest_file_at' => $archiveMeta['latest_file_at'] ?? null,
|
||
'synced_at' => now()->toDateTimeString(),
|
||
'error' => $archiveMeta['error'] ?? null,
|
||
];
|
||
$mapping->meta = $meta;
|
||
$mapping->save();
|
||
}
|
||
$bancheMapping = $meta['banche_mapping'] ?? [];
|
||
$defaultBank = isset($meta['stabile_banca_predefinita']) ? strtoupper((string)$meta['stabile_banca_predefinita']) : null;
|
||
// Assicura che CON e CCB (se presente nell'archivio legacy) siano sempre presenti in mapping logico per DatiBancari
|
||
$legacyCasse = [];
|
||
try {
|
||
$legacyCasse = $this->legacy->casse($legacy);
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
$legacyCodici = collect($legacyCasse)->pluck('codice')->map(fn($c) => strtoupper($c))->unique();
|
||
// Forza CON se non mappato
|
||
if (!$legacyCodici->contains('CON')) {
|
||
$legacyCodici->push('CON');
|
||
}
|
||
// NON creiamo CCB se non esiste proprio; se esiste aggiungiamo
|
||
if ($legacyCodici->contains('CCB') && !collect($bancheMapping)->pluck('codice_legacy')->map('strtoupper')->contains('CCB')) {
|
||
$bancheMapping[] = [
|
||
'codice_legacy' => 'CCB',
|
||
'tipo_conto' => 'bancario',
|
||
'iban' => null,
|
||
'swift' => null,
|
||
'descrizione_legacy' => 'CONTO CORRENTE BANCARIO'
|
||
];
|
||
}
|
||
if (!collect($bancheMapping)->pluck('codice_legacy')->map('strtoupper')->contains('CON')) {
|
||
$bancheMapping[] = [
|
||
'codice_legacy' => 'CON',
|
||
'tipo_conto' => 'contanti',
|
||
'iban' => null,
|
||
'swift' => null,
|
||
'descrizione_legacy' => 'CASSA CONTANTI'
|
||
];
|
||
}
|
||
// 1) Import Conti Bancari (mapping manuale)
|
||
$logger = new ImportProgressLogger();
|
||
$logger->begin('apply-mapping', ['legacy_code' => $legacy, 'stabile_id' => $stabileId]);
|
||
if ($archiveMeta) {
|
||
$logger->checkpoint('archive.sync', [
|
||
'legacy_code' => $legacy,
|
||
'target' => $archiveMeta['target'] ?? null,
|
||
'source' => $archiveMeta['source'] ?? null,
|
||
'error' => $archiveMeta['error'] ?? null,
|
||
]);
|
||
}
|
||
$contiCreated = 0;
|
||
$contiUpdated = 0;
|
||
$contiSkipped = 0;
|
||
// Se definita banca predefinita, azzera il flag principale su tutti i conti dello stabile
|
||
if ($defaultBank) {
|
||
try {
|
||
\App\Models\DatiBancari::where('stabile_id', $stabileId)->update(['is_nostro_conto' => false]);
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
|
||
foreach ((array)$bancheMapping as $bm) {
|
||
if (!is_array($bm)) {
|
||
$contiSkipped++;
|
||
continue;
|
||
}
|
||
$cod = $this->resolveMappingCodiceCassa($bm);
|
||
if (!$cod) {
|
||
$raw = $bm['codice_legacy'] ?? null;
|
||
$cod = $raw ? strtoupper(trim((string)$raw)) : null;
|
||
}
|
||
if (!$cod) {
|
||
$contiSkipped++;
|
||
continue;
|
||
}
|
||
$targetArr = isset($bm['target']) && is_array($bm['target']) ? $bm['target'] : [];
|
||
$isMain = ($defaultBank && $cod === $defaultBank)
|
||
|| !empty($bm['is_nostro_conto'])
|
||
|| !empty($bm['target_is_nostro_conto'])
|
||
|| !empty($targetArr['is_nostro_conto'] ?? null);
|
||
$tipo = $bm['tipo_conto'] ?? 'bancario';
|
||
$iban = $bm['iban'] ?? ($bm['legacy_iban'] ?? null);
|
||
if (is_string($iban)) {
|
||
$iban = trim($iban) !== '' ? $iban : null;
|
||
}
|
||
$swift = $bm['swift'] ?? ($bm['legacy_swift'] ?? null);
|
||
$desc = $bm['descrizione_legacy'] ?? ($bm['legacy_descrizione'] ?? ($bm['legacy_nome_banca'] ?? $cod));
|
||
$existing = \App\Models\ContoBancario::where('tenant_id', $tenantId)->where('codice', $cod)->first();
|
||
if ($existing) {
|
||
$existing->update([
|
||
'descrizione' => $desc,
|
||
'tipo_conto' => $tipo,
|
||
'iban' => $iban,
|
||
'swift' => $swift,
|
||
'attivo' => true,
|
||
]);
|
||
$contiUpdated++;
|
||
} else {
|
||
\App\Models\ContoBancario::create([
|
||
'tenant_id' => $tenantId,
|
||
'gestione_id' => null,
|
||
'id_cassa_gescon' => null,
|
||
'codice' => $cod,
|
||
'descrizione' => $desc,
|
||
'saldo_iniziale' => 0,
|
||
'tipo_conto' => $tipo,
|
||
'banca' => null,
|
||
'numero_conto' => null,
|
||
'iban' => $iban,
|
||
'swift' => $swift,
|
||
'saldo_corrente' => 0,
|
||
'attivo' => true,
|
||
'metadati_gescon' => $bm,
|
||
]);
|
||
$contiCreated++;
|
||
}
|
||
$logger->checkpoint('banche.upsert', ['codice' => $cod, 'is_main' => (bool)$isMain]);
|
||
// Upsert DatiBancari collegato allo stabile (visibilità nel dettaglio stabile)
|
||
try {
|
||
$db = \App\Models\DatiBancari::where('stabile_id', $stabileId)
|
||
->where(function ($q) use ($cod) {
|
||
$q->where('legacy_cod_cassa', $cod)->orWhere('numero_conto', $cod);
|
||
})
|
||
->first();
|
||
$dbcPayload = [
|
||
'stabile_id' => $stabileId,
|
||
'tipo_conto' => $tipo === 'contanti' ? 'cassa' : 'corrente',
|
||
'denominazione_banca' => $desc,
|
||
'numero_conto' => $cod,
|
||
'iban' => $iban,
|
||
'legacy_cod_cassa' => $cod,
|
||
'abi' => null,
|
||
'cab' => null,
|
||
'bic_swift' => $swift,
|
||
'intestazione_conto' => $desc,
|
||
'stato_conto' => 'attivo',
|
||
'is_nostro_conto' => (bool) $isMain,
|
||
];
|
||
if ($db) {
|
||
$db->update($dbcPayload);
|
||
} else {
|
||
\App\Models\DatiBancari::create($dbcPayload);
|
||
}
|
||
} catch (\Throwable $e) { /* ignore individual failure */
|
||
}
|
||
}
|
||
// Seleziona comunque come principale il conto esistente con codice uguale alla banca predefinita, anche se non era nel loop
|
||
if ($defaultBank) {
|
||
try {
|
||
\App\Models\DatiBancari::where('stabile_id', $stabileId)
|
||
->where(function ($q) use ($defaultBank) {
|
||
$q->whereRaw('UPPER(COALESCE(legacy_cod_cassa, "")) = ?', [$defaultBank])
|
||
->orWhereRaw('UPPER(COALESCE(numero_conto, "")) = ?', [$defaultBank]);
|
||
})
|
||
->update(['is_nostro_conto' => true]);
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
// 2) Import Gestioni (scan anni + parse)
|
||
$yearsAvailable = $anni ?: $this->legacy->scanLegacyYears($legacy);
|
||
$gestioniCreated = 0;
|
||
$gestioniUpdated = 0;
|
||
$straOrdCreated = 0;
|
||
foreach ($yearsAvailable as $anno) {
|
||
$parsed = $this->legacy->gestioniYear($legacy, (int)$anno);
|
||
if (!$parsed['ok']) continue;
|
||
$paths = $parsed['meta'] ?? [];
|
||
// Ordinaria
|
||
if (!empty($parsed['ordinaria'])) {
|
||
$res = $this->upsertGestione($tenantId, $stabileId, $parsed['ordinaria'], 'ordinaria', $paths);
|
||
if ($res) $gestioniCreated += $res;
|
||
else $gestioniUpdated++;
|
||
$logger->checkpoint('gestioni.ordinaria', ['anno' => (int)$anno, 'created' => (bool)$res]);
|
||
}
|
||
// Riscaldamento
|
||
if (!empty($parsed['riscaldamento'])) {
|
||
$res = $this->upsertGestione($tenantId, $stabileId, $parsed['riscaldamento'], 'riscaldamento', $paths);
|
||
if ($res) $gestioniCreated += $res;
|
||
else $gestioniUpdated++;
|
||
$logger->checkpoint('gestioni.riscaldamento', ['anno' => (int)$anno, 'created' => (bool)$res]);
|
||
}
|
||
// Straordinarie → ciascuna gestione separata
|
||
$n = 1;
|
||
foreach (($parsed['straordinarie'] ?? []) as $s) {
|
||
$gestData = [
|
||
'anno' => (int)$anno,
|
||
'stato' => 'aperta',
|
||
'rate' => $s['rate_totali'] ?? [],
|
||
'numero_straordinaria' => $n,
|
||
'piano' => [
|
||
'id_legacy' => $s['id'] ?? null,
|
||
'descrizione' => $s['descrizione'] ?? null,
|
||
'rate_emesse' => $s['rate_emesse'] ?? null,
|
||
'rate_totali' => $s['rate_totali'] ?? null,
|
||
]
|
||
];
|
||
$res = $this->upsertGestione($tenantId, $stabileId, $gestData, 'straordinaria', $paths, $n);
|
||
if ($res) {
|
||
$straOrdCreated += $res;
|
||
} else {
|
||
$gestioniUpdated++;
|
||
}
|
||
$logger->checkpoint('gestioni.straordinaria', ['anno' => (int)$anno, 'numero' => $n, 'created' => (bool)$res]);
|
||
$n++;
|
||
}
|
||
}
|
||
$logger->finish([
|
||
'conti' => ['created' => $contiCreated, 'updated' => $contiUpdated, 'skipped' => $contiSkipped],
|
||
'gestioni' => ['created' => $gestioniCreated + $straOrdCreated, 'updated' => $gestioniUpdated],
|
||
'anni_processati' => $yearsAvailable,
|
||
]);
|
||
return response()->json([
|
||
'ok' => true,
|
||
'legacy_code' => $legacy,
|
||
'stabile_id' => $stabileId,
|
||
'archivio' => $archiveMeta,
|
||
'conti' => ['created' => $contiCreated, 'updated' => $contiUpdated, 'skipped' => $contiSkipped],
|
||
'gestioni' => ['created' => $gestioniCreated + $straOrdCreated, 'updated' => $gestioniUpdated],
|
||
'anni_processati' => $yearsAvailable,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Diagnostica rapida sullo staging: connessione, tabelle presenti e sample righe per un legacy_code.
|
||
*/
|
||
public function stagingDiagnostics(Request $request)
|
||
{
|
||
$legacy = (string) $request->query('legacy_code', '');
|
||
$conn = UserSetting::get('gescon.import_conn', 'gescon_import');
|
||
$out = [
|
||
'connection' => $conn,
|
||
'legacy_code' => $legacy ?: null,
|
||
'tables' => [],
|
||
'samples' => [],
|
||
];
|
||
try {
|
||
$tables = ['condomin', 'Anag_casse', 'anag_casse', 'stabili'];
|
||
foreach ($tables as $t) {
|
||
$has = Schema::connection($conn)->hasTable($t);
|
||
$out['tables'][$t] = $has;
|
||
if ($has) {
|
||
$q = DB::connection($conn)->table($t);
|
||
if ($legacy && Schema::connection($conn)->hasColumn($t, 'cod_stabile')) {
|
||
$q->where('cod_stabile', $legacy);
|
||
}
|
||
$out['samples'][$t] = $q->limit(3)->get();
|
||
}
|
||
}
|
||
return response()->json(['ok' => true, 'data' => $out]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage(), 'data' => $out], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Ritorna le colonne e alcune righe di esempio per una tabella dello staging gescon_import.
|
||
* GET: table? (opzionale, se assente usa guess), guess? (regex o frammento, es. 'mille'|'voci'), legacy_code? (filtra se presente colonna cod_stabile)
|
||
* Response: { ok, connection, table, columns:[], rows:[{..}], count }
|
||
*/
|
||
public function stagingColumns(Request $request)
|
||
{
|
||
$conn = UserSetting::get('gescon.import_conn', 'gescon_import');
|
||
$table = $request->query('table');
|
||
$guess = $request->query('guess');
|
||
$legacy = $request->query('legacy_code');
|
||
try {
|
||
$chosen = null;
|
||
if ($table) {
|
||
if (!Schema::connection($conn)->hasTable($table)) {
|
||
return response()->json(['ok' => false, 'error' => "Tabella non trovata: {$table}", 'connection' => $conn], 404);
|
||
}
|
||
$chosen = $table;
|
||
} else {
|
||
// Scansione euristica: cerca per frammento in ordine di preferenza
|
||
$tables = [];
|
||
try {
|
||
$tables = Schema::connection($conn)->getAllTables();
|
||
} catch (\Throwable $e) {
|
||
$tables = [];
|
||
}
|
||
if (empty($tables)) {
|
||
// getAllTables non sempre disponibile: fallback usando information_schema
|
||
$tables = DB::connection($conn)->select("select table_name as name from information_schema.tables where table_schema=database()");
|
||
$tables = array_map(fn($r) => (array)$r, $tables);
|
||
}
|
||
$names = array_values(array_filter(array_map(function ($r) {
|
||
$n = $r['name'] ?? ($r['TABLE_NAME'] ?? null);
|
||
return $n ? (string)$n : null;
|
||
}, $tables)));
|
||
$order = [];
|
||
if ($guess) {
|
||
$g = strtolower((string)$guess);
|
||
foreach ($names as $n) {
|
||
if (str_contains(strtolower($n), $g)) $order[] = $n;
|
||
}
|
||
}
|
||
// fallback comuni
|
||
foreach (['condomin', 'milles', 'millesimi', 'tab_millesimi', 'tabelle', 'voci', 'pianoconti', 'piano_conti', 'conti'] as $pat) {
|
||
foreach ($names as $n) {
|
||
if (str_contains(strtolower($n), $pat) && !in_array($n, $order, true)) $order[] = $n;
|
||
}
|
||
}
|
||
$chosen = $order[0] ?? null;
|
||
if (!$chosen) {
|
||
return response()->json(['ok' => false, 'error' => 'Nessuna tabella candidata trovata', 'connection' => $conn]);
|
||
}
|
||
}
|
||
$cols = Schema::connection($conn)->getColumnListing($chosen);
|
||
$q = DB::connection($conn)->table($chosen);
|
||
if ($legacy) {
|
||
if (Schema::connection($conn)->hasColumn($chosen, 'cod_stabile')) $q->where('cod_stabile', $legacy);
|
||
elseif (Schema::connection($conn)->hasColumn($chosen, 'cod_condominio')) $q->where('cod_condominio', $legacy);
|
||
}
|
||
$rows = $q->limit(5)->get();
|
||
return response()->json([
|
||
'ok' => true,
|
||
'connection' => $conn,
|
||
'table' => $chosen,
|
||
'columns' => array_values($cols),
|
||
'rows' => $rows,
|
||
'count' => $q->count(),
|
||
]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage(), 'connection' => $conn], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* List columns for an explicit staging table (advanced explorer for MDB→staging).
|
||
*/
|
||
public function stagingTableColumns(Request $request)
|
||
{
|
||
$table = trim((string) $request->query('table', ''));
|
||
$conn = (string) ($request->query('connection') ?: UserSetting::get('gescon.import_conn', 'gescon_import'));
|
||
if ($table === '') {
|
||
return response()->json(['ok' => false, 'error' => 'Missing table'], 422);
|
||
}
|
||
try {
|
||
if (!Schema::connection($conn)->hasTable($table)) {
|
||
return response()->json(['ok' => false, 'error' => 'Table not found', 'connection' => $conn, 'table' => $table], 404);
|
||
}
|
||
$cols = Schema::connection($conn)->getColumnListing($table);
|
||
return response()->json(['ok' => true, 'connection' => $conn, 'table' => $table, 'columns' => array_values($cols)]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Aggrega tutte le colonne dalle principali tabelle dello staging, per consentire mapping oltre a CONDOMIN.
|
||
* GET: connection? (default gescon_import)
|
||
* Response: { ok, connection, tables: [{name, columns:[], preview:[]}] }
|
||
*/
|
||
public function stagingAllColumns(Request $request)
|
||
{
|
||
$conn = (string) ($request->query('connection') ?: UserSetting::get('gescon.import_conn', 'gescon_import'));
|
||
try {
|
||
// Enumera tabelle disponibili
|
||
$tablesRaw = [];
|
||
try {
|
||
$tablesRaw = Schema::connection($conn)->getAllTables();
|
||
} catch (\Throwable $e) {
|
||
$tablesRaw = DB::connection($conn)->select("select table_name as name from information_schema.tables where table_schema=database()");
|
||
$tablesRaw = array_map(fn($r) => (array)$r, $tablesRaw);
|
||
}
|
||
$names = array_values(array_filter(array_map(function ($r) {
|
||
$n = $r['name'] ?? ($r['TABLE_NAME'] ?? null);
|
||
return $n ? (string)$n : null;
|
||
}, $tablesRaw)));
|
||
// Focus su tabelle probabili + tutte le altre
|
||
$preferred = ['condomin', 'anag_casse', 'Anag_casse', 'stabili', 'piano_conti', 'pianoconti', 'voci', 'tabelle', 'milles', 'millesimi', 'tab_millesimi'];
|
||
$ordered = [];
|
||
foreach ($preferred as $p) {
|
||
foreach ($names as $n) {
|
||
if (strtolower($n) === strtolower($p) && !in_array($n, $ordered, true)) $ordered[] = $n;
|
||
}
|
||
}
|
||
foreach ($names as $n) {
|
||
if (!in_array($n, $ordered, true)) $ordered[] = $n;
|
||
}
|
||
$out = [];
|
||
foreach ($ordered as $t) {
|
||
try {
|
||
$cols = Schema::connection($conn)->getColumnListing($t);
|
||
// Prendi poche righe per capire i valori
|
||
$rows = DB::connection($conn)->table($t)->limit(2)->get();
|
||
$out[] = ['name' => $t, 'columns' => array_values($cols), 'preview' => $rows];
|
||
} catch (\Throwable $e) {
|
||
$out[] = ['name' => $t, 'columns' => [], 'preview' => []];
|
||
}
|
||
}
|
||
return response()->json(['ok' => true, 'connection' => $conn, 'tables' => $out]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage(), 'connection' => $conn], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create new columns in target tables as requested by mapping UI ("crea campo nuovo").
|
||
* Input: { table: string, connection?: string, columns: [{ name, type, nullable? }] }
|
||
*/
|
||
public function createTargetColumns(Request $request)
|
||
{
|
||
$data = $request->validate([
|
||
'table' => 'required|string',
|
||
'connection' => 'nullable|string',
|
||
'columns' => 'required|array|min:1',
|
||
'columns.*.name' => 'required|string',
|
||
'columns.*.type' => 'required|string',
|
||
'columns.*.nullable' => 'nullable|boolean',
|
||
]);
|
||
$table = (string) $data['table'];
|
||
$conn = (string) ($data['connection'] ?? config('database.default', 'mysql'));
|
||
$cols = (array) $data['columns'];
|
||
try {
|
||
// Filter out existing columns to avoid errors
|
||
$existing = [];
|
||
try {
|
||
$existing = Schema::connection($conn)->getColumnListing($table);
|
||
} catch (\Throwable $e) {
|
||
$existing = [];
|
||
}
|
||
$toAdd = array_values(array_filter($cols, function ($c) use ($existing) {
|
||
$n = isset($c['name']) ? (string)$c['name'] : '';
|
||
return $n !== '' && !in_array($n, $existing, true);
|
||
}));
|
||
if (empty($toAdd)) {
|
||
return response()->json(['ok' => true, 'skipped' => 'All columns already exist']);
|
||
}
|
||
Schema::connection($conn)->table($table, function ($blueprint) use ($toAdd) {
|
||
foreach ($toAdd as $c) {
|
||
$name = (string) $c['name'];
|
||
$type = strtolower((string) $c['type']);
|
||
$nullable = !empty($c['nullable']);
|
||
if ($type === 'string' || $type === 'varchar') {
|
||
$col = $blueprint->string($name, 255);
|
||
} elseif ($type === 'text') {
|
||
$col = $blueprint->text($name);
|
||
} elseif ($type === 'integer' || $type === 'int') {
|
||
$col = $blueprint->integer($name);
|
||
} elseif ($type === 'bigint' || $type === 'biginteger') {
|
||
$col = $blueprint->bigInteger($name);
|
||
} elseif ($type === 'boolean' || $type === 'bool') {
|
||
$col = $blueprint->boolean($name);
|
||
} elseif ($type === 'decimal' || $type === 'numeric') {
|
||
$col = $blueprint->decimal($name, 15, 4);
|
||
} elseif ($type === 'datetime' || $type === 'timestamp') {
|
||
$col = $blueprint->dateTime($name);
|
||
} elseif ($type === 'date') {
|
||
$col = $blueprint->date($name);
|
||
} else {
|
||
$col = $blueprint->text($name);
|
||
}
|
||
if ($nullable && method_exists($col, 'nullable')) {
|
||
$col->nullable();
|
||
}
|
||
}
|
||
});
|
||
return response()->json(['ok' => true]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Importa Unità Immobiliari (ed eventualmente Soggetti/Diritti) per uno stabile legacy.
|
||
* Usa la pipeline artisan esistente (gescon:import-full) con step selezionabili.
|
||
* Accetta anche un percorso esplicito a singolo_anno.mdb oppure deduce dal path configurato + anno.
|
||
*/
|
||
public function importUnita(Request $request)
|
||
{
|
||
// Permesso esecuzione import richiesto (già gestito da middleware sulla rotta)
|
||
$data = $request->validate([
|
||
'legacy_code' => 'required|string',
|
||
'steps' => 'sometimes|array', // [unita,soggetti,diritti]
|
||
'anno' => 'sometimes|integer',
|
||
'mdb_condomin_path' => 'sometimes|string|nullable',
|
||
'dry_run' => 'sometimes|boolean',
|
||
'limit' => 'sometimes|integer|min:1|max:100000',
|
||
// Filtri opzionali derivati da CONDOMIN
|
||
'scala' => 'sometimes|string|nullable|max:10',
|
||
'piano_min' => 'sometimes|integer|nullable',
|
||
'piano_max' => 'sometimes|integer|nullable',
|
||
'only_pertinenze' => 'sometimes|string|nullable|in:any,cantina,box,posto_auto,soffitta,locale_tecnico'
|
||
]);
|
||
$legacy = (string) $data['legacy_code'];
|
||
$steps = (array) ($data['steps'] ?? ['unita']);
|
||
$anno = isset($data['anno']) ? (int)$data['anno'] : null;
|
||
$mdbCondomin = (string) ($data['mdb_condomin_path'] ?? '');
|
||
$dry = (bool) ($data['dry_run'] ?? true);
|
||
$limit = isset($data['limit']) ? (int)$data['limit'] : null;
|
||
$scala = isset($data['scala']) ? (string)$data['scala'] : null;
|
||
$pianoMin = $data['piano_min'] ?? null;
|
||
$pianoMax = $data['piano_max'] ?? null;
|
||
$onlyPert = isset($data['only_pertinenze']) ? (string)$data['only_pertinenze'] : null;
|
||
|
||
// Deduce path a singolo_anno.mdb se non fornito esplicitamente
|
||
if (!$mdbCondomin && $anno) {
|
||
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
$cand = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy . DIRECTORY_SEPARATOR . $anno . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
|
||
if (@is_file($cand)) {
|
||
$mdbCondomin = $cand;
|
||
}
|
||
}
|
||
|
||
// Determine amministratore target similar to executeImport fallback
|
||
$isSuperAdmin = auth()->user()->hasRole('super-admin') || auth()->user()->can('gescon-import.config');
|
||
$currentAdmin = $this->getAmministratoreCorrente();
|
||
$targetAdminId = null;
|
||
try {
|
||
$mapRec = GesconImportMapping::where('user_id', auth()->id())->where('legacy_code', $legacy)->first();
|
||
$targetAdminId = (int) data_get($mapRec, 'meta.target_admin_id');
|
||
if (!$targetAdminId) $targetAdminId = null;
|
||
} catch (\Throwable $e) {
|
||
$targetAdminId = null;
|
||
}
|
||
|
||
$logger = new ImportProgressLogger();
|
||
$logger->begin('import-unita', ['legacy_code' => $legacy, 'anno' => $anno, 'steps' => $steps, 'dry' => $dry]);
|
||
$results = [];
|
||
foreach ($steps as $step) {
|
||
try {
|
||
$params = [];
|
||
if ($legacy) $params['--stabile'] = $legacy;
|
||
if ($mdbCondomin) $params['--mdb-condomin'] = $mdbCondomin;
|
||
if ($anno) $params['--anno'] = $anno;
|
||
$params['--solo'] = $step; // unita | soggetti | diritti
|
||
if ($dry) $params['--dry-run'] = true;
|
||
if ($limit) $params['--limit'] = $limit;
|
||
if ($scala) $params['--scala'] = $scala;
|
||
if ($pianoMin !== null) $params['--piano-min'] = (string)$pianoMin;
|
||
if ($pianoMax !== null) $params['--piano-max'] = (string)$pianoMax;
|
||
if ($onlyPert) $params['--only-pertinenze'] = $onlyPert;
|
||
// Pass amministratore id if allowed
|
||
if ($targetAdminId && ($isSuperAdmin || ($currentAdmin && $currentAdmin->id === $targetAdminId))) {
|
||
$params['--amministratore-id'] = $targetAdminId;
|
||
}
|
||
\Artisan::call('gescon:import-full', $params);
|
||
$out = (string) \Illuminate\Support\Facades\Artisan::output();
|
||
$logger->checkpoint('step.done', ['step' => $step]);
|
||
$logger->stdout("\n===== STEP {$step} =====\n" . $out . "\n");
|
||
$results[] = ['step' => $step, 'ok' => true, 'output' => $out];
|
||
} catch (\Throwable $e) {
|
||
$logger->error('step.error', ['step' => $step, 'error' => $e->getMessage()]);
|
||
$results[] = ['step' => $step, 'ok' => false, 'error' => $e->getMessage()];
|
||
}
|
||
}
|
||
|
||
$logger->finish(['results' => array_map(fn($r) => ['step' => $r['step'], 'ok' => $r['ok']], $results)]);
|
||
// Persisti ultimi filtri usati nel mapping meta
|
||
try {
|
||
$mapping = GesconImportMapping::firstOrCreate(
|
||
['user_id' => auth()->id(), 'legacy_code' => $legacy],
|
||
['status' => 'pending']
|
||
);
|
||
$meta = $mapping->meta ?? [];
|
||
$meta['unita_last_filters'] = [
|
||
'anno' => $anno,
|
||
'scala' => $scala,
|
||
'piano_min' => $pianoMin,
|
||
'piano_max' => $pianoMax,
|
||
'only_pertinenze' => $onlyPert,
|
||
'dry' => $dry,
|
||
'limit' => $limit,
|
||
];
|
||
$mapping->meta = $meta;
|
||
$mapping->save();
|
||
} catch (\Throwable $e) { /* ignore persistence errors */
|
||
}
|
||
|
||
return response()->json([
|
||
'ok' => !collect($results)->contains(fn($r) => $r['ok'] === false),
|
||
'legacy_code' => $legacy,
|
||
'anno' => $anno,
|
||
'mdb_condomin_path' => $mdbCondomin,
|
||
'dry_run' => $dry,
|
||
'results' => $results,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Salva un preset della "Maschera Universale Import" lato server nell'UserSetting dell'utente.
|
||
* Input JSON: { name: string, preset: object }
|
||
* Memorizza su chiave 'gescon.universal_presets' come array di voci [{name, preset, saved_at, user_id}].
|
||
*/
|
||
public function saveUniversalPreset(Request $request)
|
||
{
|
||
if (!(auth()->user() && auth()->user()->can('gescon-import.view'))) {
|
||
return response()->json(['ok' => false, 'error' => 'Permesso negato'], 403);
|
||
}
|
||
try {
|
||
$data = $request->all();
|
||
$name = trim((string)($data['name'] ?? ''));
|
||
$preset = $data['preset'] ?? null;
|
||
if ($name === '' || !is_array($preset)) {
|
||
return response()->json(['ok' => false, 'error' => 'Parametri non validi'], 422);
|
||
}
|
||
// Carica elenco esistente, append/replace per nome
|
||
$list = UserSetting::get('gescon.universal_presets', []);
|
||
if (!is_array($list)) {
|
||
$list = [];
|
||
}
|
||
$entry = [
|
||
'name' => $name,
|
||
'preset' => $preset,
|
||
'saved_at' => now()->toIso8601String(),
|
||
'user_id' => auth()->id(),
|
||
];
|
||
// Sostituisci se esiste già con lo stesso nome (case-insensitive)
|
||
$replaced = false;
|
||
foreach ($list as $idx => $it) {
|
||
if (is_array($it) && strtolower((string)($it['name'] ?? '')) === strtolower($name)) {
|
||
$list[$idx] = $entry;
|
||
$replaced = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!$replaced) {
|
||
$list[] = $entry;
|
||
}
|
||
UserSetting::set('gescon.universal_presets', $list);
|
||
return response()->json(['ok' => true, 'replaced' => $replaced, 'count' => count($list)]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Vista stato sincronizzazione Staging: mostra per ciascun codice legacy se i dati sono presenti e aggiornati.
|
||
*/
|
||
public function stagingStatus(Request $request)
|
||
{
|
||
// Recupera elenco stabili/legacy (riusa l'anteprima già calcolata dall'index)
|
||
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
$mdbPath = UserSetting::get('gescon.mdb_stabili', '/mnt/gescon-archives/gescon/dbc/Stabili.mdb');
|
||
$mdbIndexPath = UserSetting::get('gescon.mdb_index', '/mnt/gescon-archives/gescon/cartellestabili_e_anni.mdb');
|
||
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '';
|
||
$dataset = MigrationDataset::where('key', 'stabili')->orderByDesc('updated_at')->first();
|
||
$preview = Cache::remember('gescon_legacy_preview:staging_status', now()->addMinutes(5), function () use ($dataset, $mdbPath, $hasMdbTools, $base, $mdbIndexPath) {
|
||
try {
|
||
$items = [];
|
||
if ($dataset && $dataset->records_count > 0) {
|
||
$records = MigrationRecord::where('dataset_id', $dataset->id)->limit(10000)->get();
|
||
$map = [];
|
||
foreach ($records as $rec) {
|
||
$data = (array)($rec->data ?? []);
|
||
$legacy = $rec->legacy_key
|
||
?: ($data['nome_directory'] ?? ($data['internet_cod_stab'] ?? ($data['cod_stabile'] ?? null)));
|
||
if (!$legacy) continue;
|
||
$denom = $data['denominazione'] ?? ($data['fe_denominazione'] ?? null);
|
||
$map[(string)$legacy] = [
|
||
'legacy_code' => trim((string)$legacy),
|
||
'denominazione' => $denom ? trim((string)$denom) : null,
|
||
];
|
||
}
|
||
$items = array_values($map);
|
||
}
|
||
if (empty($items) && $hasMdbTools && $mdbIndexPath && is_file($mdbIndexPath)) {
|
||
$items = $this->scanStabiliFromMasterIndex($mdbIndexPath, $base);
|
||
}
|
||
if (empty($items) && $mdbPath && is_file($mdbPath) && $hasMdbTools) {
|
||
$items = $this->scanStabiliFromMdb($mdbPath);
|
||
}
|
||
if (empty($items) && is_dir($base)) {
|
||
$items = $this->scanLegacyCodesFromDir($base);
|
||
}
|
||
return $items;
|
||
} catch (\Throwable $e) {
|
||
return [];
|
||
}
|
||
});
|
||
|
||
$overview = $this->buildStagingOverview($preview);
|
||
return view('admin.gescon-import.staging-status', [
|
||
'overview' => $overview,
|
||
'staging_connection' => $overview['connection'] ?? null,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Costruisce una panoramica dello stato di staging: connessione usata, presenza tabelle, e conteggi per legacy.
|
||
* Ritorna un array: [connection, tables:{}, items:[{legacy_code, denominazione, staging:{condomin,casse}, legacy:{has_generale, years_count}}]]
|
||
*/
|
||
private function buildStagingOverview(array $legacyItems): array
|
||
{
|
||
$conn = UserSetting::get('gescon.import_conn', 'gescon_import');
|
||
$connCandidates = array_values(array_unique([$conn, 'gescon_import_00000000']));
|
||
$activeConn = null;
|
||
$tablesPresence = [];
|
||
$itemsOut = [];
|
||
// Seleziona prima connessione che abbia almeno una tabella nota
|
||
foreach ($connCandidates as $c) {
|
||
try {
|
||
$has = Schema::connection($c)->hasTable('condomin') || Schema::connection($c)->hasTable('Anag_casse') || Schema::connection($c)->hasTable('anag_casse');
|
||
if ($has) {
|
||
$activeConn = $c;
|
||
break;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
$activeConn = $activeConn ?: $connCandidates[0];
|
||
foreach (['condomin', 'Anag_casse', 'anag_casse'] as $t) {
|
||
try {
|
||
$tablesPresence[$t] = Schema::connection($activeConn)->hasTable($t);
|
||
} catch (\Throwable $e) {
|
||
$tablesPresence[$t] = false;
|
||
}
|
||
}
|
||
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
foreach ($legacyItems as $it) {
|
||
$code = (string)($it['legacy_code'] ?? '');
|
||
if ($code === '') continue;
|
||
$den = $it['denominazione'] ?? null;
|
||
// Conteggi staging
|
||
$cntCondomin = null;
|
||
$cntCasse = null;
|
||
try {
|
||
$tblC = Schema::connection($activeConn)->hasTable('condomin') ? 'condomin' : (Schema::connection($activeConn)->hasTable('condomini') ? 'condomini' : null);
|
||
if ($tblC) {
|
||
$q = DB::connection($activeConn)->table($tblC);
|
||
if (Schema::connection($activeConn)->hasColumn($tblC, 'cod_stabile')) $q->where('cod_stabile', $code);
|
||
elseif (Schema::connection($activeConn)->hasColumn($tblC, 'cod_condominio')) $q->where('cod_condominio', $code);
|
||
$cntCondomin = (int) $q->count();
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$cntCondomin = null;
|
||
}
|
||
try {
|
||
$tblB = null;
|
||
foreach (['Anag_casse', 'anag_casse', 'casse', 'banche'] as $t) {
|
||
if (Schema::connection($activeConn)->hasTable($t)) {
|
||
$tblB = $t;
|
||
break;
|
||
}
|
||
}
|
||
if ($tblB) {
|
||
$q = DB::connection($activeConn)->table($tblB);
|
||
if (Schema::connection($activeConn)->hasColumn($tblB, 'cod_stabile')) $q->where('cod_stabile', $code);
|
||
$cntCasse = (int) $q->count();
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$cntCasse = null;
|
||
}
|
||
// Presenza archivi legacy su FS
|
||
$dir = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $code;
|
||
$hasGenerale = is_file($dir . DIRECTORY_SEPARATOR . 'generale_stabile.mdb');
|
||
$yearsCount = 0;
|
||
if (is_dir($dir)) {
|
||
try {
|
||
foreach (scandir($dir) as $f) {
|
||
if ($f === '.' || $f === '..') continue;
|
||
if (!preg_match('/^\d{4}$/', $f)) continue;
|
||
$sa = $dir . DIRECTORY_SEPARATOR . $f . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
|
||
if (is_file($sa)) $yearsCount++;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
$itemsOut[] = [
|
||
'legacy_code' => $code,
|
||
'denominazione' => $den,
|
||
'staging' => [
|
||
'condomin_rows' => $cntCondomin,
|
||
'casse_rows' => $cntCasse,
|
||
],
|
||
'legacy' => [
|
||
'has_generale' => $hasGenerale,
|
||
'years_count' => $yearsCount,
|
||
]
|
||
];
|
||
}
|
||
// Ordina per codice naturale
|
||
usort($itemsOut, fn($a, $b) => strnatcasecmp($a['legacy_code'], $b['legacy_code']));
|
||
return [
|
||
'connection' => $activeConn,
|
||
'tables' => $tablesPresence,
|
||
'items' => $itemsOut,
|
||
];
|
||
}
|
||
|
||
private function scanLegacyYears(string $legacy): array
|
||
{
|
||
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
$dir = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy;
|
||
if (!is_dir($dir)) return [];
|
||
$years = [];
|
||
foreach (scandir($dir) as $f) {
|
||
if ($f === '.' || $f === '..') continue;
|
||
if (preg_match('/^\d{4}$/', $f) && is_file($dir . DIRECTORY_SEPARATOR . $f . DIRECTORY_SEPARATOR . 'singolo_anno.mdb')) {
|
||
$years[] = $f;
|
||
}
|
||
}
|
||
sort($years, SORT_STRING);
|
||
return $years;
|
||
}
|
||
|
||
private function parseGestioneYear(string $legacy, int $anno): array
|
||
{
|
||
$req = new Request(['legacy_code' => $legacy, 'anno' => $anno]);
|
||
// Riusa logica gestioniYear senza HTTP overhead
|
||
$resp = $this->gestioniYear($req);
|
||
$data = $resp->getData(true);
|
||
return $data;
|
||
}
|
||
|
||
private function upsertGestione(string $tenantId, int $stabileId, array $src, string $tipo, array $paths, ?int $numeroStra = null): int
|
||
{
|
||
$anno = (int)($src['anno'] ?? $src['anno_gestione'] ?? 0);
|
||
if (!$anno) return 0;
|
||
$q = \App\Models\GestioneContabile::where('tenant_id', $tenantId)
|
||
->where('stabile_id', $stabileId)
|
||
->where('anno_gestione', $anno)
|
||
->where('tipo_gestione', $tipo);
|
||
if ($tipo === 'straordinaria' && $numeroStra) {
|
||
$q->where('numero_straordinaria', $numeroStra);
|
||
}
|
||
$existing = $q->first();
|
||
$mesiOrd = null;
|
||
$mesiRis = null;
|
||
$mesiStra = null;
|
||
if ($tipo === 'ordinaria' && !empty($src['rate'])) {
|
||
$mesiOrd = array_map('intval', array_keys($src['rate']));
|
||
}
|
||
if ($tipo === 'riscaldamento' && !empty($src['rate'])) {
|
||
$mesiRis = array_map('intval', array_keys($src['rate']));
|
||
}
|
||
if ($tipo === 'straordinaria' && !empty($src['piano']['rate_totali'])) {
|
||
$mesiStra = [];
|
||
}
|
||
$payload = [
|
||
'tenant_id' => $tenantId,
|
||
'stabile_id' => $stabileId,
|
||
'anno_gestione' => $anno,
|
||
'tipo_gestione' => $tipo,
|
||
'numero_straordinaria' => $tipo === 'straordinaria' ? $numeroStra : null,
|
||
'denominazione' => ucfirst($tipo) . ' ' . $anno . ($numeroStra ? ' #' . $numeroStra : ''),
|
||
'stato' => $src['stato'] ?? 'aperta',
|
||
'gestione_attiva' => true,
|
||
'mesi_rate_ordinaria' => $mesiOrd ?? [],
|
||
'mesi_rate_riscaldamento' => $mesiRis ?? [],
|
||
'mesi_rate_straordinaria' => $mesiStra ?? [],
|
||
'codice_archivio_legacy' => $paths['path_singolo_anno'] ?? null,
|
||
'piano_straordinario' => $tipo === 'straordinaria' ? ($src['piano'] ?? null) : null,
|
||
];
|
||
if ($existing) {
|
||
$existing->update($payload);
|
||
return 0; // updated
|
||
}
|
||
\App\Models\GestioneContabile::create($payload);
|
||
return 1; // created
|
||
}
|
||
|
||
/**
|
||
* Salva impostazioni base per GESCON Import (percorso e connessione)
|
||
*/
|
||
public function saveConfig(Request $request)
|
||
{
|
||
// Solo chi ha il permesso di configurazione può modificare le impostazioni globali
|
||
if (!$request->boolean('_mapping_only') && !(auth()->user() && auth()->user()->can('gescon-import.config'))) {
|
||
return redirect()->route('admin.gescon-import.index')->with('error', 'Non hai i permessi per modificare la configurazione.');
|
||
}
|
||
// Se arriva esclusivamente il mapping, salviamo su GesconImportMapping.meta
|
||
if ($request->boolean('_mapping_only')) {
|
||
$request->validate([
|
||
'_legacy_code' => 'nullable|string',
|
||
'mapping' => 'array',
|
||
'preset_key' => 'nullable|string',
|
||
'preset_meta' => 'nullable|string',
|
||
'sync_enabled' => 'nullable|in:0,1'
|
||
]);
|
||
$legacyCode = $request->input('_legacy_code');
|
||
$mapping = GesconImportMapping::firstOrCreate(
|
||
['user_id' => auth()->id(), 'legacy_code' => $legacyCode],
|
||
['status' => 'pending']
|
||
);
|
||
$meta = $mapping->meta ?? [];
|
||
if ($request->has('mapping')) {
|
||
$meta['mapping'] = $request->input('mapping', []);
|
||
}
|
||
// Salva anche le sorgenti selezionate (stabili/generale/singolo_anno)
|
||
if ($request->has('sources')) {
|
||
$meta['sources'] = array_values(array_filter((array)$request->input('sources')));
|
||
}
|
||
// Flag di sincronizzazione veloce dalla lista stabili
|
||
if ($request->has('sync_enabled')) {
|
||
$meta['sync_enabled'] = $request->input('sync_enabled') === '1';
|
||
}
|
||
// Preset selezionato e metadati (incluso NORD weighting)
|
||
$presetKey = $request->input('preset_key');
|
||
if (is_string($presetKey) && $presetKey !== '') {
|
||
$meta['preset_key'] = $presetKey;
|
||
}
|
||
$presetMetaRaw = $request->input('preset_meta');
|
||
if (is_string($presetMetaRaw) && $presetMetaRaw !== '') {
|
||
try {
|
||
$presetMeta = json_decode($presetMetaRaw, true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($presetMeta)) {
|
||
$meta['preset_meta'] = $presetMeta;
|
||
}
|
||
} catch (\Throwable $e) {
|
||
// ignora JSON invalido
|
||
}
|
||
}
|
||
$mapping->meta = $meta;
|
||
$mapping->save();
|
||
|
||
// Replica come mapping globale se richiesto
|
||
if ($request->boolean('replicate_global')) {
|
||
$global = GesconImportMapping::firstOrCreate(
|
||
['user_id' => auth()->id(), 'legacy_code' => '*'],
|
||
['status' => 'pending']
|
||
);
|
||
$gmeta = $global->meta ?? [];
|
||
$gmeta['mapping'] = $meta['mapping'] ?? [];
|
||
if (!empty($meta['sources'])) $gmeta['sources'] = $meta['sources'];
|
||
if (!empty($meta['preset_key'])) $gmeta['preset_key'] = $meta['preset_key'];
|
||
if (!empty($meta['preset_meta'])) $gmeta['preset_meta'] = $meta['preset_meta'];
|
||
$global->meta = $gmeta;
|
||
$global->save();
|
||
}
|
||
return redirect()->route('admin.gescon-import.index', ['legacy_code' => $legacyCode])
|
||
->with('success', 'Mapping campi salvato.');
|
||
}
|
||
|
||
$data = $request->validate([
|
||
'gescon_source_path' => 'required|string',
|
||
'gescon_mdb_stabili' => 'required|string',
|
||
'gescon_mdb_index' => 'nullable|string',
|
||
'gescon_import_conn' => 'required|string',
|
||
// Stabile template di riferimento per replica selezioni
|
||
'template_stabile_code' => 'sometimes|nullable|string|max:32',
|
||
// Avanzato: configurazione sorgenti per discovery mapping
|
||
'gescon_source_conn' => 'sometimes|nullable|string',
|
||
'gescon_source_tables' => 'sometimes|nullable|string',
|
||
// Nuove selezioni multi-archivio / tabelle / colonne
|
||
'selected_archives' => 'sometimes|array',
|
||
'selected_archives.*' => 'string',
|
||
'selected_tables' => 'sometimes|array',
|
||
'selected_tables.*' => 'string',
|
||
'selected_columns' => 'sometimes|array',
|
||
'selected_columns.*' => 'string',
|
||
// Tabella di collegamento segment->tables/columns persistente
|
||
'mapping_sources' => 'sometimes|array',
|
||
'mapping_sources.*.segment' => 'required_with:mapping_sources|string|max:100',
|
||
'mapping_sources.*.tables' => 'sometimes|array',
|
||
'mapping_sources.*.tables.*' => 'string',
|
||
'mapping_sources.*.columns' => 'sometimes|array',
|
||
'mapping_sources.*.columns.*' => 'string',
|
||
]);
|
||
|
||
// Normalize paths (trim hidden spaces) and ensure we persist the full absolute path if possible
|
||
$normalize = function ($p) {
|
||
$p = trim((string)$p);
|
||
$p = preg_replace('/[\x00-\x1F\x7F\x{00A0}\x{2000}-\x{200B}\x{202F}\x{FEFF}]/u', '', $p);
|
||
return $p;
|
||
};
|
||
$srcPath = $normalize($data['gescon_source_path']);
|
||
$mdbPath = $normalize($data['gescon_mdb_stabili']);
|
||
$mdbIndexPath = isset($data['gescon_mdb_index']) ? $normalize($data['gescon_mdb_index']) : null;
|
||
$srcReal = @realpath($srcPath) ?: $srcPath;
|
||
$mdbReal = @realpath($mdbPath) ?: $mdbPath;
|
||
$mdbIndexReal = $mdbIndexPath ? (@realpath($mdbIndexPath) ?: $mdbIndexPath) : null;
|
||
|
||
foreach (
|
||
[
|
||
'gescon.source_path' => $srcReal,
|
||
'gescon.mdb_stabili' => $mdbReal,
|
||
// Persisti l'indice se fornito, altrimenti mantieni quello esistente
|
||
'gescon.mdb_index' => $mdbIndexReal ?: UserSetting::get('gescon.mdb_index', '/mnt/gescon-archives/gescon/cartellestabili_e_anni.mdb'),
|
||
'gescon.import_conn' => $data['gescon_import_conn'],
|
||
] as $k => $v
|
||
) {
|
||
UserSetting::set($k, $v);
|
||
}
|
||
|
||
// Opzionale: salva connessione e tabelle sorgente avanzate
|
||
if (array_key_exists('gescon_source_conn', $data)) {
|
||
UserSetting::set('gescon.source_conn', $data['gescon_source_conn'] ?? '');
|
||
}
|
||
if (array_key_exists('gescon_source_tables', $data)) {
|
||
$raw = trim((string)($data['gescon_source_tables'] ?? ''));
|
||
$val = $raw;
|
||
if ($raw !== '') {
|
||
$decoded = null;
|
||
try {
|
||
$decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
|
||
} catch (\Throwable $e) {
|
||
$decoded = null;
|
||
}
|
||
if (is_array($decoded)) {
|
||
$val = json_encode($decoded, JSON_UNESCAPED_UNICODE);
|
||
}
|
||
}
|
||
UserSetting::set('gescon.source_tables', $val);
|
||
}
|
||
|
||
// Persisti nuove selezioni multi-livello se presenti
|
||
if ($request->has('selected_archives')) {
|
||
$archives = array_values(array_filter($request->input('selected_archives', []), fn($v) => is_string($v) && $v !== ''));
|
||
UserSetting::set('gescon.selected_archives', $archives);
|
||
}
|
||
if ($request->has('selected_tables')) {
|
||
$tables = array_values(array_filter($request->input('selected_tables', []), fn($v) => is_string($v) && $v !== ''));
|
||
UserSetting::set('gescon.selected_tables', $tables);
|
||
}
|
||
if ($request->has('selected_columns')) {
|
||
$cols = array_values(array_filter($request->input('selected_columns', []), fn($v) => is_string($v) && $v !== ''));
|
||
// Per le colonne possiamo salvare come struttura raggruppata table:col
|
||
UserSetting::set('gescon.selected_columns', $cols);
|
||
}
|
||
if ($request->has('mapping_sources')) {
|
||
$rawSources = $request->input('mapping_sources', []);
|
||
$norm = [];
|
||
foreach ($rawSources as $row) {
|
||
if (!is_array($row)) continue;
|
||
$seg = trim((string)($row['segment'] ?? ''));
|
||
if ($seg === '') continue;
|
||
$tables = array_values(array_filter(is_array($row['tables'] ?? null) ? $row['tables'] : []));
|
||
$columns = array_values(array_filter(is_array($row['columns'] ?? null) ? $row['columns'] : []));
|
||
$norm[] = ['segment' => $seg, 'tables' => $tables, 'columns' => $columns];
|
||
}
|
||
UserSetting::set('gescon.mapping_sources', $norm);
|
||
}
|
||
|
||
// Persisti codice stabile template e uno snapshot delle selezioni correnti se richiesto
|
||
if ($request->filled('template_stabile_code')) {
|
||
$tpl = trim($request->input('template_stabile_code'));
|
||
UserSetting::set('gescon.template_stabile_code', $tpl);
|
||
// Salva snapshot per rapida replica futura
|
||
$snapshot = [
|
||
'archives' => UserSetting::get('gescon.selected_archives', []),
|
||
'tables' => UserSetting::get('gescon.selected_tables', []),
|
||
'columns' => UserSetting::get('gescon.selected_columns', []),
|
||
'mapping_sources' => UserSetting::get('gescon.mapping_sources', []),
|
||
];
|
||
UserSetting::set('gescon.template_stabile_sources', $snapshot);
|
||
}
|
||
|
||
return redirect()->route('admin.gescon-import.config')
|
||
->with('success', 'Impostazioni GESCON salvate.');
|
||
}
|
||
|
||
/**
|
||
* Salvataggio mapping dedicato (senza permesso di configurazione globale)
|
||
*/
|
||
public function saveMapping(Request $request)
|
||
{
|
||
$request->validate([
|
||
'_legacy_code' => 'nullable|string',
|
||
'mapping' => 'array',
|
||
'manual_target_values' => 'sometimes|array',
|
||
'manual_target_values.*' => 'nullable|string',
|
||
'preset_key' => 'nullable|string',
|
||
'preset_meta' => 'nullable|string',
|
||
'sync_enabled' => 'nullable|in:0,1',
|
||
// Allow both JSON string and array payloads
|
||
'banche_mapping' => 'nullable',
|
||
'tabelle_mapping' => 'nullable|string',
|
||
'voci_mapping' => 'nullable|string',
|
||
'voci_field_mapping' => 'nullable|string',
|
||
'unita_mapping' => 'nullable|string',
|
||
'unita_field_mapping' => 'nullable|string',
|
||
'tabelle_field_mapping' => 'nullable|string',
|
||
'relation_mapping' => 'nullable|string',
|
||
'nord_factor' => 'nullable|integer|min:1|max:1000',
|
||
'target_admin_id' => 'sometimes|nullable|integer|exists:amministratori,id',
|
||
'stabile_banca_predefinita' => 'sometimes|nullable|string|max:50',
|
||
'palazzine_config' => 'nullable',
|
||
'unita_preset_key' => 'sometimes|nullable|string|max:100',
|
||
]);
|
||
$legacyCode = $request->input('_legacy_code');
|
||
$mapping = GesconImportMapping::firstOrCreate(
|
||
['user_id' => auth()->id(), 'legacy_code' => $legacyCode],
|
||
['status' => 'pending']
|
||
);
|
||
$meta = $mapping->meta ?? [];
|
||
if ($request->has('mapping')) {
|
||
$meta['mapping'] = $request->input('mapping', []);
|
||
}
|
||
if ($request->has('manual_target_values')) {
|
||
$rawManual = $request->input('manual_target_values');
|
||
if (is_array($rawManual)) {
|
||
$normalizedManual = [];
|
||
foreach ($rawManual as $targetKey => $value) {
|
||
if (!is_string($targetKey)) {
|
||
continue;
|
||
}
|
||
$targetKey = trim($targetKey);
|
||
if ($targetKey === '') {
|
||
continue;
|
||
}
|
||
if (is_array($value)) {
|
||
$value = $value['value'] ?? null;
|
||
}
|
||
if ($value === null) {
|
||
continue;
|
||
}
|
||
if (is_string($value)) {
|
||
$value = trim($value);
|
||
}
|
||
if ($value === '') {
|
||
continue;
|
||
}
|
||
$normalizedManual[$targetKey] = is_scalar($value) ? (string) $value : json_encode($value);
|
||
}
|
||
if (!empty($normalizedManual)) {
|
||
$meta['manual_target_values'] = $normalizedManual;
|
||
} else {
|
||
unset($meta['manual_target_values']);
|
||
}
|
||
}
|
||
} else {
|
||
unset($meta['manual_target_values']);
|
||
}
|
||
// Relation mapping (generic cross-entity rules)
|
||
if ($request->filled('relation_mapping')) {
|
||
try {
|
||
$rel = json_decode($request->string('relation_mapping'), true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($rel)) {
|
||
// keep only safe keys per rule
|
||
$norm = [];
|
||
foreach ($rel as $rule) {
|
||
if (!is_array($rule)) continue;
|
||
$segment = isset($rule['segment']) ? (string)$rule['segment'] : '';
|
||
$legacyField = isset($rule['legacy_field']) ? (string)$rule['legacy_field'] : '';
|
||
$targetTable = isset($rule['target_table']) ? (string)$rule['target_table'] : '';
|
||
$assignTo = isset($rule['assign_to']) ? (string)$rule['assign_to'] : '';
|
||
$match = is_array($rule['match'] ?? null) ? $rule['match'] : [];
|
||
if ($segment === '' || $legacyField === '' || $targetTable === '' || $assignTo === '') continue;
|
||
$by = in_array(($match['by'] ?? 'field'), ['field', 'composite'], true) ? $match['by'] : 'field';
|
||
$rules = is_array($match['rules'] ?? null) ? $match['rules'] : [];
|
||
$onMiss = in_array(($match['on_miss'] ?? 'log'), ['log', 'ignore', 'create'], true) ? $match['on_miss'] : 'log';
|
||
$norm[] = [
|
||
'segment' => $segment,
|
||
'legacy_field' => $legacyField,
|
||
'target_table' => $targetTable,
|
||
'assign_to' => $assignTo,
|
||
'match' => [
|
||
'by' => $by,
|
||
'rules' => array_values(array_map(function ($r) {
|
||
return [
|
||
'table' => isset($r['table']) ? (string)$r['table'] : null,
|
||
'field' => isset($r['field']) ? (string)$r['field'] : null,
|
||
'source' => isset($r['source']) ? (string)$r['source'] : 'legacy',
|
||
];
|
||
}, $rules)),
|
||
'on_miss' => $onMiss,
|
||
],
|
||
];
|
||
}
|
||
$meta['relation_mapping'] = $norm;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
if ($request->has('sources')) {
|
||
$meta['sources'] = array_values(array_filter((array)$request->input('sources')));
|
||
}
|
||
// Persisti amministratore target per questa mappatura
|
||
if ($request->has('target_admin_id')) {
|
||
$tid = $request->input('target_admin_id');
|
||
$meta['target_admin_id'] = $tid ? (int)$tid : null;
|
||
}
|
||
// Persisti banca/cassa predefinita selezionata nello step Stabili
|
||
if ($request->has('stabile_banca_predefinita')) {
|
||
$val = trim((string)$request->input('stabile_banca_predefinita'));
|
||
$meta['stabile_banca_predefinita'] = $val !== '' ? $val : null;
|
||
}
|
||
// Mappatura multi-banca: accetta sia JSON string che array strutturato
|
||
if ($request->has('banche_mapping')) {
|
||
$raw = $request->input('banche_mapping');
|
||
$bm = [];
|
||
if (is_string($raw)) {
|
||
try {
|
||
$decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($decoded)) {
|
||
$bm = $decoded;
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$bm = [];
|
||
}
|
||
} elseif (is_array($raw)) {
|
||
$bm = $raw;
|
||
}
|
||
if (is_array($bm)) {
|
||
$norm = [];
|
||
foreach ($bm as $item) {
|
||
if (!is_array($item)) {
|
||
continue;
|
||
}
|
||
$legacy = [];
|
||
$target = [];
|
||
if (isset($item['legacy']) && is_array($item['legacy'])) {
|
||
$legacy = $item['legacy'];
|
||
}
|
||
if (isset($item['target']) && is_array($item['target'])) {
|
||
$target = $item['target'];
|
||
}
|
||
// Supporta formato piatto precedente (legacy_nome_banca, nome_banca, ...)
|
||
foreach ([
|
||
'nome_banca',
|
||
'iban',
|
||
'swift',
|
||
'conto',
|
||
'intestatario',
|
||
'cod_cassa',
|
||
'descrizione',
|
||
] as $field) {
|
||
$legacyKey = 'legacy_' . $field;
|
||
if (!array_key_exists($field, $legacy) && array_key_exists($legacyKey, $item)) {
|
||
$legacy[$field] = $item[$legacyKey];
|
||
}
|
||
if (!array_key_exists($field, $target) && array_key_exists($field, $item)) {
|
||
$target[$field] = $item[$field];
|
||
}
|
||
}
|
||
if (!array_key_exists('codice_cassa', $target) && array_key_exists('target_cod_cassa', $item)) {
|
||
$target['codice_cassa'] = $item['target_cod_cassa'];
|
||
}
|
||
if (!array_key_exists('is_nostro_conto', $target) && array_key_exists('target_is_nostro_conto', $item)) {
|
||
$target['is_nostro_conto'] = (bool) $item['target_is_nostro_conto'];
|
||
}
|
||
// Normalizza stringhe
|
||
$legacy = array_filter(array_map(function ($value) {
|
||
if (is_string($value)) {
|
||
$value = trim($value);
|
||
}
|
||
return ($value === '' ? null : $value);
|
||
}, $legacy), fn($v) => $v !== null);
|
||
$target = array_filter(array_map(function ($value) {
|
||
if (is_string($value)) {
|
||
$value = trim($value);
|
||
}
|
||
return ($value === '' ? null : $value);
|
||
}, $target), fn($v) => $v !== null);
|
||
|
||
if (empty($legacy) && empty($target)) {
|
||
continue; // riga vuota
|
||
}
|
||
|
||
$codiceLegacy = $item['codice_legacy']
|
||
?? ($target['codice_cassa'] ?? null)
|
||
?? ($legacy['cod_cassa'] ?? null)
|
||
?? ($legacy['conto'] ?? null);
|
||
$codiceLegacy = $codiceLegacy ? strtoupper((string) $codiceLegacy) : null;
|
||
$descrLegacy = $item['descrizione_legacy']
|
||
?? ($legacy['descrizione'] ?? null)
|
||
?? ($legacy['nome_banca'] ?? null);
|
||
|
||
$entry = [
|
||
'legacy' => $legacy ?: null,
|
||
'target' => $target ?: null,
|
||
'codice_legacy' => $codiceLegacy,
|
||
'descrizione_legacy' => $descrLegacy,
|
||
'tipo_conto' => $item['tipo_conto'] ?? ($target['tipo_conto'] ?? null),
|
||
'iban' => $item['iban'] ?? ($legacy['iban'] ?? null),
|
||
'swift' => $item['swift'] ?? ($legacy['swift'] ?? null),
|
||
];
|
||
|
||
if (isset($target['is_nostro_conto'])) {
|
||
$entry['is_nostro_conto'] = (bool) $target['is_nostro_conto'];
|
||
} elseif (isset($item['is_nostro_conto'])) {
|
||
$entry['is_nostro_conto'] = (bool) $item['is_nostro_conto'];
|
||
}
|
||
|
||
// Espone campi legacy per UI (compatibilità con vecchio formato)
|
||
foreach ([
|
||
'nome_banca',
|
||
'iban',
|
||
'swift',
|
||
'conto',
|
||
'intestatario',
|
||
'cod_cassa',
|
||
'descrizione'
|
||
] as $field) {
|
||
if (isset($legacy[$field]) && !isset($entry['legacy_' . $field])) {
|
||
$entry['legacy_' . $field] = $legacy[$field];
|
||
}
|
||
}
|
||
|
||
// Salva mapping target per UI (senza sovrascrivere eventuali dati reali già forniti)
|
||
foreach ([
|
||
'nome_banca',
|
||
'iban',
|
||
'swift',
|
||
'conto',
|
||
'intestatario',
|
||
'cod_cassa'
|
||
] as $field) {
|
||
$targetValue = $target[$field] ?? null;
|
||
if ($targetValue !== null) {
|
||
$targetKey = $field === 'cod_cassa' ? 'target_' . $field . '_select' : 'target_' . $field;
|
||
$entry[$targetKey] = $targetValue;
|
||
if (!array_key_exists($field, $entry) || $entry[$field] === null) {
|
||
$entry[$field] = $targetValue;
|
||
}
|
||
}
|
||
}
|
||
if (isset($target['codice_cassa'])) {
|
||
$entry['target_cod_cassa'] = $target['codice_cassa'];
|
||
if (!array_key_exists('codice_cassa', $entry) || $entry['codice_cassa'] === null) {
|
||
$entry['codice_cassa'] = $target['codice_cassa'];
|
||
}
|
||
}
|
||
|
||
$norm[] = array_filter($entry, fn($v) => $v !== null);
|
||
}
|
||
$meta['banche_mapping'] = array_values($norm);
|
||
}
|
||
}
|
||
// Tabelle millesimali + NORD factor
|
||
if ($request->filled('tabelle_mapping')) {
|
||
try {
|
||
$tm = json_decode($request->string('tabelle_mapping'), true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($tm)) {
|
||
$meta['tabelle_mapping'] = $tm;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
if ($request->has('nord_factor')) {
|
||
$nf = (int) $request->input('nord_factor');
|
||
if ($nf > 0) $meta['nord_factor'] = $nf;
|
||
}
|
||
// Voci (piano dei conti)
|
||
if ($request->filled('voci_mapping')) {
|
||
try {
|
||
$vm = json_decode($request->string('voci_mapping'), true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($vm)) {
|
||
$meta['voci_mapping'] = $vm;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
// Mapping campi Voci (legacy field -> target field)
|
||
if ($request->filled('voci_field_mapping')) {
|
||
try {
|
||
$vfm = json_decode($request->string('voci_field_mapping'), true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($vfm)) {
|
||
$norm = [];
|
||
foreach ($vfm as $row) {
|
||
if (!is_array($row)) continue;
|
||
$legacyF = isset($row['legacy']) ? trim((string)$row['legacy']) : '';
|
||
$targetF = isset($row['target']) ? trim((string)$row['target']) : '';
|
||
if ($legacyF === '') continue;
|
||
$norm[] = ['legacy' => $legacyF, 'target' => $targetF !== '' ? $targetF : null];
|
||
}
|
||
$meta['voci_field_mapping'] = $norm;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
// Unità / Anagrafiche
|
||
if ($request->filled('unita_mapping')) {
|
||
try {
|
||
$um = json_decode($request->string('unita_mapping'), true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($um)) {
|
||
$meta['unita_mapping'] = $um;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
// Mapping campi Unità (legacy field -> target field)
|
||
if ($request->filled('unita_field_mapping')) {
|
||
try {
|
||
$ufm = json_decode($request->string('unita_field_mapping'), true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($ufm)) {
|
||
// Normalizza struttura: [{legacy:"cod_cond", target:"codice_unita"}, ...]
|
||
$norm = [];
|
||
foreach ($ufm as $row) {
|
||
if (!is_array($row)) continue;
|
||
$legacyF = isset($row['legacy']) ? trim((string)$row['legacy']) : '';
|
||
$targetF = isset($row['target']) ? trim((string)$row['target']) : '';
|
||
if ($legacyF === '') continue;
|
||
$norm[] = [
|
||
'legacy' => $legacyF,
|
||
'target' => $targetF !== '' ? $targetF : null,
|
||
];
|
||
}
|
||
$meta['unita_field_mapping'] = $norm;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
// Palazzine config (JSON string or array). Shape example: [{name:'Pal.1', scales:['A','B','C']}, ...]
|
||
if ($request->has('palazzine_config')) {
|
||
$raw = $request->input('palazzine_config');
|
||
$cfg = [];
|
||
if (is_string($raw)) {
|
||
try {
|
||
$dec = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($dec)) $cfg = $dec;
|
||
} catch (\Throwable $e) {
|
||
$cfg = [];
|
||
}
|
||
} elseif (is_array($raw)) {
|
||
$cfg = $raw;
|
||
}
|
||
if (is_array($cfg)) {
|
||
// Keep only safe keys
|
||
$norm = [];
|
||
foreach ($cfg as $item) {
|
||
if (!is_array($item)) continue;
|
||
$name = isset($item['name']) ? trim((string)$item['name']) : '';
|
||
$scales = array_values(array_filter(array_map('strval', (array)($item['scales'] ?? []))));
|
||
if ($name === '' || empty($scales)) continue;
|
||
$norm[] = ['name' => $name, 'scales' => $scales];
|
||
}
|
||
$meta['palazzine_config'] = $norm;
|
||
}
|
||
}
|
||
// Optional: persist selected preset key for unità mapping
|
||
if ($request->filled('unita_preset_key')) {
|
||
$meta['unita_preset_key'] = (string) $request->input('unita_preset_key');
|
||
}
|
||
// Mapping campi Tabelle Millesimali (legacy field -> target field)
|
||
if ($request->filled('tabelle_field_mapping')) {
|
||
try {
|
||
$tfm = json_decode($request->string('tabelle_field_mapping'), true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($tfm)) {
|
||
$norm = [];
|
||
foreach ($tfm as $row) {
|
||
if (!is_array($row)) continue;
|
||
$legacyF = isset($row['legacy']) ? trim((string)$row['legacy']) : '';
|
||
$targetF = isset($row['target']) ? trim((string)$row['target']) : '';
|
||
if ($legacyF === '') continue;
|
||
$norm[] = [
|
||
'legacy' => $legacyF,
|
||
'target' => $targetF !== '' ? $targetF : null,
|
||
];
|
||
}
|
||
$meta['tabelle_field_mapping'] = $norm;
|
||
}
|
||
} catch (\Throwable $e) { /* ignore */
|
||
}
|
||
}
|
||
if ($request->has('sync_enabled')) {
|
||
$meta['sync_enabled'] = $request->input('sync_enabled') === '1';
|
||
}
|
||
$presetKey = $request->input('preset_key');
|
||
if (is_string($presetKey) && $presetKey !== '') {
|
||
$meta['preset_key'] = $presetKey;
|
||
}
|
||
$presetMetaRaw = $request->input('preset_meta');
|
||
if (is_string($presetMetaRaw) && $presetMetaRaw !== '') {
|
||
try {
|
||
$presetMeta = json_decode($presetMetaRaw, true, 512, JSON_THROW_ON_ERROR);
|
||
if (is_array($presetMeta)) {
|
||
$meta['preset_meta'] = $presetMeta;
|
||
}
|
||
} catch (\Throwable $e) {
|
||
// ignora JSON invalido
|
||
}
|
||
}
|
||
$mapping->meta = $meta;
|
||
$mapping->save();
|
||
|
||
if ($request->boolean('replicate_global')) {
|
||
$global = GesconImportMapping::firstOrCreate(
|
||
['user_id' => auth()->id(), 'legacy_code' => '*'],
|
||
['status' => 'pending']
|
||
);
|
||
$gmeta = $global->meta ?? [];
|
||
$gmeta['mapping'] = $meta['mapping'] ?? [];
|
||
if (!empty($meta['sources'])) $gmeta['sources'] = $meta['sources'];
|
||
if (!empty($meta['preset_key'])) $gmeta['preset_key'] = $meta['preset_key'];
|
||
if (!empty($meta['preset_meta'])) $gmeta['preset_meta'] = $meta['preset_meta'];
|
||
if (array_key_exists('manual_target_values', $meta)) {
|
||
$gmeta['manual_target_values'] = $meta['manual_target_values'];
|
||
} else {
|
||
unset($gmeta['manual_target_values']);
|
||
}
|
||
$global->meta = $gmeta;
|
||
$global->save();
|
||
}
|
||
|
||
return redirect()->route('admin.gescon-import.index', ['legacy_code' => $legacyCode])
|
||
->with('success', 'Mapping campi salvato.');
|
||
}
|
||
|
||
/**
|
||
* Aggiornamento rapido del CF Amministratore sullo stabile corrente (usato nel mapping header)
|
||
*/
|
||
public function quickUpdateStabileAdminCf(Request $request, $stabileId)
|
||
{
|
||
$this->authorize('gescon-import.execute');
|
||
$cf = (string)($request->input('cod_fisc_amministratore', ''));
|
||
try {
|
||
$ok = DB::table('stabili')
|
||
->where('id', (int)$stabileId)
|
||
->update([
|
||
'cod_fisc_amministratore' => $cf,
|
||
'updated_at' => now(),
|
||
]);
|
||
return response()->json(['ok' => true, 'updated' => (bool)$ok]);
|
||
} catch (\Throwable $e) {
|
||
return response()->json(['ok' => false, 'error' => $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Import rapido "micro" per uno stabile (solo anagrafica + mapping campi stabile.*) senza pipeline estesa.
|
||
*/
|
||
public function microImportStabile(Request $request)
|
||
{
|
||
$data = $request->validate([
|
||
'legacy_code' => 'required|string',
|
||
'amministratore_id' => 'sometimes|integer|exists:amministratori,id'
|
||
]);
|
||
$legacy = $data['legacy_code'];
|
||
// Priorità: parametro esplicito > mapping target_admin_id > amministratore corrente
|
||
if (isset($data['amministratore_id'])) {
|
||
$amministratore = \App\Models\Amministratore::find($data['amministratore_id']);
|
||
} else {
|
||
$mappingRec = GesconImportMapping::where('user_id', auth()->id())
|
||
->where('legacy_code', $legacy)
|
||
->first();
|
||
$targetAdminId = (int) data_get($mappingRec, 'meta.target_admin_id');
|
||
$amministratore = $targetAdminId ? \App\Models\Amministratore::find($targetAdminId) : null;
|
||
if (!$amministratore) {
|
||
$amministratore = $this->getAmministratoreCorrente();
|
||
}
|
||
}
|
||
if (!$amministratore) return response()->json(['ok' => false, 'error' => 'Amministratore non trovato'], 404);
|
||
$mappingRec = GesconImportMapping::where('user_id', auth()->id())->where('legacy_code', $legacy)->first();
|
||
$map = (array) data_get($mappingRec, 'meta.mapping', []);
|
||
$svc = new \App\Services\GesconImport\Micro\StabiliMicroImporter();
|
||
$res = $svc->import($legacy, $amministratore, $map);
|
||
return response()->json(['ok' => true, 'result' => $res]);
|
||
}
|
||
|
||
/**
|
||
* Aggiorna rapidamente il percorso di Stabili.mdb (dal picker inline) e ricarica anteprima
|
||
*/
|
||
public function setMdbPath(Request $request)
|
||
{
|
||
$request->validate(['mdb_path' => 'required|string']);
|
||
$path = $request->string('mdb_path')->toString();
|
||
if (!is_file($path)) {
|
||
return redirect()->back()->with('error', 'File non trovato: ' . $path);
|
||
}
|
||
if (!is_readable($path)) {
|
||
$msg = 'Il file esiste ma non è leggibile dal server web: ' . $path;
|
||
if (str_starts_with($path, '/home/')) {
|
||
$msg .= ' (attenzione: percorsi in /home potrebbero non essere accessibili a www-data)';
|
||
}
|
||
return redirect()->back()->with('error', $msg);
|
||
}
|
||
UserSetting::set('gescon.mdb_stabili', $path);
|
||
return redirect()->route('admin.gescon-import.index', ['refresh' => 1, '#stabili'])->with('success', 'Percorso MDB aggiornato.');
|
||
}
|
||
|
||
/**
|
||
* Browser minimale lato server per selezionare directory/file su Linux (solo lettura).
|
||
*/
|
||
public function browse(Request $request)
|
||
{
|
||
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
|
||
$path = $request->query('path', $base);
|
||
// Hard cap: consenti solo all'interno di alcune radici note per sicurezza
|
||
$allowedRoots = [
|
||
'/home',
|
||
'/var/www',
|
||
'/mnt',
|
||
dirname($base)
|
||
];
|
||
$isAllowed = false;
|
||
foreach ($allowedRoots as $root) {
|
||
if (str_starts_with(realpath($path) ?: '', realpath($root) ?: '')) {
|
||
$isAllowed = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!$isAllowed) {
|
||
return Response::json(['error' => 'Percorso non consentito'], 403);
|
||
}
|
||
$entries = [];
|
||
try {
|
||
if (is_dir($path)) {
|
||
$dh = opendir($path);
|
||
if ($dh) {
|
||
while (($file = readdir($dh)) !== false) {
|
||
if ($file === '.' || $file === '..') continue;
|
||
$full = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
|
||
$entries[] = [
|
||
'name' => $file,
|
||
'path' => $full,
|
||
'is_dir' => is_dir($full),
|
||
];
|
||
}
|
||
closedir($dh);
|
||
}
|
||
// Ordina directory prima, poi file
|
||
usort($entries, function ($a, $b) {
|
||
if ($a['is_dir'] === $b['is_dir']) return strnatcasecmp($a['name'], $b['name']);
|
||
return $a['is_dir'] ? -1 : 1;
|
||
});
|
||
}
|
||
} catch (\Throwable $e) {
|
||
return Response::json(['error' => 'Impossibile leggere la directory'], 500);
|
||
}
|
||
return Response::json([
|
||
'path' => $path,
|
||
'entries' => $entries,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Scansiona l'MDB indice master cartellestabili_e_anni.mdb per elencare stabili e anni disponibili.
|
||
* Restituisce un array di elementi: legacy_code, denominazione, years_count (e opzionale years[]).
|
||
*/
|
||
private function scanStabiliFromMasterIndex(string $indexMdbPath, string $legacyBasePath): array
|
||
{
|
||
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
|
||
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
|
||
if (!is_file($indexMdbPath) || !is_readable($indexMdbPath)) return [];
|
||
|
||
// Individua una tabella plausibile (cartelle / stabili / anni)
|
||
$tableGuess = null;
|
||
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($indexMdbPath))) ?: '';
|
||
if ($tables) {
|
||
foreach (preg_split('/\r?\n/', trim($tables)) as $t) {
|
||
if ($t === '') continue;
|
||
if (preg_match('/(cartelle|stabil|anni)/i', $t)) {
|
||
$tableGuess = trim($t);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (!$tableGuess) {
|
||
// Fallback: usa la prima tabella
|
||
$lines = array_filter(preg_split('/\r?\n/', trim($tables)));
|
||
$tableGuess = $lines ? trim(array_values($lines)[0]) : null;
|
||
}
|
||
if (!$tableGuess) return [];
|
||
|
||
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
|
||
$cmd = sprintf(
|
||
'%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
|
||
escapeshellarg($binExport),
|
||
escapeshellarg('%Y-%m-%d'),
|
||
escapeshellarg('|'),
|
||
escapeshellarg('"'),
|
||
escapeshellarg($indexMdbPath),
|
||
escapeshellarg($tableGuess),
|
||
escapeshellarg($tmp)
|
||
);
|
||
@shell_exec($cmd);
|
||
if (!is_file($tmp) || filesize($tmp) === 0) {
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$fh = fopen($tmp, 'r');
|
||
if (!$fh) {
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$headers = fgetcsv($fh, 0, '|');
|
||
if (!$headers) {
|
||
fclose($fh);
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
|
||
$byCodeYears = [];
|
||
$byCodeName = [];
|
||
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
|
||
if ($cols === [null]) continue;
|
||
$cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols);
|
||
if (count($cols) < count($headers)) $cols = array_pad($cols, count($headers), null);
|
||
if (count($cols) > count($headers)) $cols = array_slice($cols, 0, count($headers));
|
||
$row = @array_combine($headers, $cols) ?: [];
|
||
if (!$row) continue;
|
||
// Estrapola codice, denominazione e anno da possibili colonne
|
||
$code = $row['nome_directory']
|
||
?? $row['cod_stabile']
|
||
?? $row['internet_cod_stab']
|
||
?? $row['codice']
|
||
?? null;
|
||
$denom = $row['denominazione'] ?? $row['fe_denominazione'] ?? null;
|
||
$anno = $row['anno'] ?? $row['year'] ?? null;
|
||
if (!$code) continue;
|
||
$code = (string)$code;
|
||
if (!isset($byCodeYears[$code])) $byCodeYears[$code] = [];
|
||
if ($anno && preg_match('/^\d{4}$/', (string)$anno)) {
|
||
$byCodeYears[$code][(string)$anno] = true;
|
||
}
|
||
if ($denom) $byCodeName[$code] = (string)$denom;
|
||
}
|
||
fclose($fh);
|
||
@unlink($tmp);
|
||
|
||
// Costruisci elementi
|
||
$items = [];
|
||
foreach ($byCodeYears as $code => $yearsSet) {
|
||
$den = $byCodeName[$code] ?? null;
|
||
$years = array_keys($yearsSet);
|
||
sort($years, SORT_STRING);
|
||
$items[] = [
|
||
'legacy_code' => $code,
|
||
'denominazione' => $den,
|
||
'years_count' => count($years),
|
||
'years' => $years,
|
||
];
|
||
}
|
||
// Se non sono stati letti anni, prova ad arricchire dal filesystem
|
||
if (empty($items)) {
|
||
$items = $this->scanLegacyCodesFromDir($legacyBasePath);
|
||
$items = $this->enrichLegacyWithArchives($items, $legacyBasePath);
|
||
}
|
||
// Ordina per codice naturale
|
||
usort($items, function ($a, $b) {
|
||
return strnatcasecmp((string)($a['legacy_code'] ?? ''), (string)($b['legacy_code'] ?? ''));
|
||
});
|
||
return $items;
|
||
}
|
||
|
||
/**
|
||
* Esegue una scansione dello Stabili.mdb per mostrare elenco stabili legacy (codice e denominazione)
|
||
*/
|
||
private function scanStabiliFromMdb(string $mdbPath): array
|
||
{
|
||
// Risolvi binari mdb-tools con percorso assoluto per ambienti web (www-data)
|
||
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
|
||
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
|
||
// Usa export temporaneo con delimitatore sicuro e parsing robusto
|
||
$tableGuess = 'Stabili';
|
||
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($mdbPath))) ?: '';
|
||
if ($tables) {
|
||
foreach (preg_split('/\r?\n/', trim($tables)) as $t) {
|
||
if ($t === '') continue;
|
||
if (preg_match('/stabil/i', $t)) {
|
||
$tableGuess = trim($t);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
|
||
$cmd = sprintf(
|
||
'%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
|
||
escapeshellarg($binExport),
|
||
escapeshellarg('%Y-%m-%d'),
|
||
escapeshellarg('|'),
|
||
escapeshellarg('"'),
|
||
escapeshellarg($mdbPath),
|
||
escapeshellarg($tableGuess),
|
||
escapeshellarg($tmp)
|
||
);
|
||
@shell_exec($cmd);
|
||
if (!is_file($tmp) || filesize($tmp) === 0) {
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$fh = fopen($tmp, 'r');
|
||
if (!$fh) {
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$headers = fgetcsv($fh, 0, '|');
|
||
if (!$headers) {
|
||
fclose($fh);
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
|
||
$items = [];
|
||
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
|
||
if ($cols === [null] || $cols === false) continue;
|
||
// Allinea colonne a headers
|
||
$cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols);
|
||
$countH = count($headers);
|
||
if (count($cols) < $countH) $cols = array_pad($cols, $countH, null);
|
||
if (count($cols) > $countH) $cols = array_slice($cols, 0, $countH);
|
||
$row = @array_combine($headers, $cols);
|
||
if (!$row) continue;
|
||
$legacyCode = $row['nome_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? null));
|
||
$denom = $row['denominazione'] ?? ($row['fe_denominazione'] ?? null);
|
||
if ($legacyCode) {
|
||
$items[] = [
|
||
'legacy_code' => trim((string)$legacyCode),
|
||
'denominazione' => $denom ? trim((string)$denom) : null,
|
||
];
|
||
}
|
||
}
|
||
fclose($fh);
|
||
@unlink($tmp);
|
||
|
||
// Dedup per legacy_code
|
||
$byCode = [];
|
||
foreach ($items as $it) {
|
||
if (!empty($it['legacy_code'])) $byCode[$it['legacy_code']] = $it;
|
||
}
|
||
ksort($byCode, SORT_NATURAL);
|
||
return array_values($byCode);
|
||
}
|
||
|
||
/**
|
||
* Ritorna un record di esempio da Stabili.mdb come array associativo leggibile dal mapping.
|
||
* Se $legacyCode è impostato prova a cercare la riga corrispondente; altrimenti usa l'indice $index.
|
||
*/
|
||
private function sampleStabileFromMdb(string $mdbPath, ?string $legacyCode, int $index = 0): array
|
||
{
|
||
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
|
||
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
|
||
$tableGuess = 'Stabili';
|
||
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($mdbPath))) ?: '';
|
||
if ($tables) {
|
||
foreach (preg_split('/\r?\n/', trim($tables)) as $t) {
|
||
if ($t === '') continue;
|
||
if (preg_match('/stabil/i', $t)) {
|
||
$tableGuess = trim($t);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
|
||
$cmd = sprintf(
|
||
'%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
|
||
escapeshellarg($binExport),
|
||
escapeshellarg('%Y-%m-%d'),
|
||
escapeshellarg('|'),
|
||
escapeshellarg('"'),
|
||
escapeshellarg($mdbPath),
|
||
escapeshellarg($tableGuess),
|
||
escapeshellarg($tmp)
|
||
);
|
||
@shell_exec($cmd);
|
||
if (!is_file($tmp) || filesize($tmp) === 0) {
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$fh = fopen($tmp, 'r');
|
||
if (!$fh) {
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$headers = fgetcsv($fh, 0, '|');
|
||
if (!$headers) {
|
||
fclose($fh);
|
||
@unlink($tmp);
|
||
return [];
|
||
}
|
||
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
|
||
$rowData = [];
|
||
$i = 0;
|
||
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
|
||
if ($cols === [null]) continue;
|
||
$cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols);
|
||
$countH = count($headers);
|
||
if (count($cols) < $countH) $cols = array_pad($cols, $countH, null);
|
||
if (count($cols) > $countH) $cols = array_slice($cols, 0, $countH);
|
||
$row = @array_combine($headers, $cols) ?: [];
|
||
|
||
$legacyKey = $row['nome_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? null));
|
||
if ($legacyCode) {
|
||
if ((string)$legacyKey === (string)$legacyCode) {
|
||
$rowData = $row;
|
||
break;
|
||
}
|
||
} else {
|
||
if ($i++ >= $index) {
|
||
$rowData = $row;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
fclose($fh);
|
||
@unlink($tmp);
|
||
if (empty($rowData)) return [];
|
||
// Normalizza: aggiungi legacy_key e mantieni chiavi così come appaiono per il mapping
|
||
$rowData['legacy_key'] = (string)($rowData['nome_directory'] ?? ($rowData['internet_cod_stab'] ?? ($rowData['cod_stabile'] ?? '')));
|
||
return $rowData;
|
||
}
|
||
|
||
/**
|
||
* Fallback: costruisce una lista codici legacy a partire dalle directory presenti nella sorgente.
|
||
*/
|
||
private function scanLegacyCodesFromDir(string $basePath): array
|
||
{
|
||
$items = [];
|
||
if (!is_dir($basePath)) return $items;
|
||
try {
|
||
$dh = opendir($basePath);
|
||
if ($dh) {
|
||
while (($file = readdir($dh)) !== false) {
|
||
if ($file === '.' || $file === '..') continue;
|
||
$full = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
|
||
if (!is_dir($full)) continue;
|
||
|
||
// Estrarre un codice numerico dalla cartella (inizio o in qualunque punto)
|
||
$code = null;
|
||
if (preg_match('/^(\d{2,5})/', $file, $m)) {
|
||
$code = $m[1];
|
||
} elseif (preg_match('/(\d{2,5})/', $file, $m)) {
|
||
$code = $m[1];
|
||
}
|
||
|
||
// Se non c'è un numero nel nome, verifica la presenza di file attesi
|
||
$hasGenerale = is_file($full . DIRECTORY_SEPARATOR . 'generale_stabile.mdb');
|
||
$hasYears = false;
|
||
if (is_dir($full)) {
|
||
try {
|
||
$sub = opendir($full);
|
||
if ($sub) {
|
||
while (($yf = readdir($sub)) !== false) {
|
||
if ($yf === '.' || $yf === '..') continue;
|
||
if (!preg_match('/^\d{4}$/', $yf)) continue;
|
||
$ya = $full . DIRECTORY_SEPARATOR . $yf . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
|
||
if (is_file($ya)) {
|
||
$hasYears = true;
|
||
break;
|
||
}
|
||
}
|
||
closedir($sub);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
}
|
||
}
|
||
|
||
if ($code || $hasGenerale || $hasYears) {
|
||
$items[] = [
|
||
'legacy_code' => $code ?: $file,
|
||
'denominazione' => null,
|
||
];
|
||
}
|
||
}
|
||
closedir($dh);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
// ignora
|
||
}
|
||
// Dedup per codice e ordina naturalmente
|
||
$byCode = [];
|
||
foreach ($items as $it) {
|
||
$c = (string)($it['legacy_code'] ?? '');
|
||
if ($c !== '') {
|
||
$byCode[$c] = $it;
|
||
}
|
||
}
|
||
uksort($byCode, 'strnatcasecmp');
|
||
return array_values($byCode);
|
||
}
|
||
|
||
/**
|
||
* Enrich: per ogni stabile verifica se esistono i due archivi (generale_stabile.mdb e uno o più singolo_anno.mdb)
|
||
* Restituisce gli stessi elementi con chiavi aggiuntive: has_generale(bool), years_count(int).
|
||
*/
|
||
private function enrichLegacyWithArchives(array $items, string $basePath): array
|
||
{
|
||
$enriched = [];
|
||
foreach ($items as $it) {
|
||
$code = trim((string)($it['legacy_code'] ?? ''));
|
||
if ($code === '') {
|
||
$enriched[] = $it;
|
||
continue;
|
||
}
|
||
$dir = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $code;
|
||
$hasGenerale = is_file($dir . DIRECTORY_SEPARATOR . 'generale_stabile.mdb');
|
||
$yearsCount = 0;
|
||
if (is_dir($dir)) {
|
||
// conta sottodirectory anno che contengono singolo_anno.mdb
|
||
try {
|
||
$dh = opendir($dir);
|
||
if ($dh) {
|
||
while (($f = readdir($dh)) !== false) {
|
||
if ($f === '.' || $f === '..') continue;
|
||
if (!preg_match('/^\d{4}$/', $f)) continue;
|
||
$sub = $dir . DIRECTORY_SEPARATOR . $f . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
|
||
if (is_file($sub)) $yearsCount++;
|
||
}
|
||
closedir($dh);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
}
|
||
}
|
||
$it['has_generale'] = $hasGenerale;
|
||
$it['years_count'] = $yearsCount;
|
||
$enriched[] = $it;
|
||
}
|
||
return $enriched;
|
||
}
|
||
|
||
/**
|
||
* Ottiene l'amministratore corrente dall'utente loggato
|
||
*/
|
||
protected function canSwitchAmministratore(): bool
|
||
{
|
||
$user = Auth::user();
|
||
if (!$user) {
|
||
return false;
|
||
}
|
||
|
||
return $user->hasRole('super-admin') || $user->can('gescon-import.config');
|
||
}
|
||
|
||
protected function resolveSelectedAmministratore(): ?Amministratore
|
||
{
|
||
if (!$this->canSwitchAmministratore()) {
|
||
return null;
|
||
}
|
||
|
||
$request = request();
|
||
$sessionKey = 'gescon_import.selected_admin_id';
|
||
|
||
if ($request && $request->has('amministratore_id')) {
|
||
$id = (int) $request->input('amministratore_id');
|
||
if ($id > 0) {
|
||
$model = Amministratore::find($id);
|
||
if ($model) {
|
||
session([$sessionKey => $model->id]);
|
||
return $model;
|
||
}
|
||
}
|
||
session()->forget($sessionKey);
|
||
return null;
|
||
}
|
||
|
||
$selectedId = session($sessionKey);
|
||
if ($selectedId) {
|
||
return Amministratore::find($selectedId);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
protected function ensureAmministratoreArchive(?Amministratore $amministratore): ?Amministratore
|
||
{
|
||
if ($amministratore && method_exists($amministratore, 'archiveExists') && !$amministratore->archiveExists()) {
|
||
if (method_exists($amministratore, 'createFolderStructure')) {
|
||
$amministratore->createFolderStructure();
|
||
}
|
||
}
|
||
|
||
return $amministratore;
|
||
}
|
||
|
||
protected function getAmministratoreCorrente()
|
||
{
|
||
$user = Auth::user();
|
||
if (!$user) {
|
||
return null;
|
||
}
|
||
|
||
if ($this->canSwitchAmministratore()) {
|
||
$selected = $this->resolveSelectedAmministratore();
|
||
if ($selected) {
|
||
return $this->ensureAmministratoreArchive($selected);
|
||
}
|
||
|
||
$fallback = Amministratore::orderBy('denominazione_studio')
|
||
->orderBy('nome')
|
||
->orderBy('cognome')
|
||
->first();
|
||
|
||
return $this->ensureAmministratoreArchive($fallback);
|
||
}
|
||
|
||
$amministratore = Amministratore::where('user_id', $user->id)->first();
|
||
if (!$amministratore && $user->hasRole('amministratore')) {
|
||
// Prova a separare nome e cognome dal campo name dell'utente
|
||
$parts = preg_split('/\s+/', (string)$user->name, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||
$cognome = count($parts) > 1 ? array_pop($parts) : 'N/D';
|
||
$nome = count($parts) > 0 ? implode(' ', $parts) : (string)$user->name;
|
||
$amministratore = Amministratore::create([
|
||
'user_id' => $user->id,
|
||
'nome' => $nome,
|
||
'cognome' => $cognome,
|
||
]);
|
||
}
|
||
|
||
return $this->ensureAmministratoreArchive($amministratore);
|
||
}
|
||
|
||
/**
|
||
* Calcola statistiche sui dati importati
|
||
*/
|
||
protected function getStatisticheImportate($amministratoreId)
|
||
{
|
||
// ID stabili per l'amministratore
|
||
$stabiliQuery = Stabile::where('amministratore_id', $amministratoreId);
|
||
$stabiliIds = (clone $stabiliQuery)->pluck('id');
|
||
|
||
// Conteggi
|
||
$stabiliTotali = (clone $stabiliQuery)->count();
|
||
$stabiliGescon = (clone $stabiliQuery)->where('note', 'like', '%GESCON%')->count();
|
||
$stabiliAttivi = (clone $stabiliQuery)->where('attivo', true)->count();
|
||
|
||
$unitaTotali = UnitaImmobiliare::whereIn('stabile_id', $stabiliIds)->count();
|
||
$unitaConMillesimi = UnitaImmobiliare::whereIn('stabile_id', $stabiliIds)
|
||
->where(function ($q) {
|
||
$q->whereNotNull('millesimi_generali')->where('millesimi_generali', '>', 0)
|
||
->orWhereNotNull('millesimi_riscaldamento')->where('millesimi_riscaldamento', '>', 0)
|
||
->orWhereNotNull('millesimi_ascensore')->where('millesimi_ascensore', '>', 0)
|
||
->orWhereNotNull('millesimi_scale')->where('millesimi_scale', '>', 0);
|
||
})->count();
|
||
|
||
$fornitoriTotali = Fornitore::where('amministratore_id', $amministratoreId)->count();
|
||
// Non abbiamo un campo "attivo" sui fornitori: consideriamo tutti attivi per ora
|
||
$fornitoriAttivi = $fornitoriTotali;
|
||
|
||
$condominiTotali = Soggetto::count();
|
||
$soggettiProprietari = Soggetto::where('tipo', 'proprietario')->count();
|
||
$soggettiInquilini = Soggetto::where('tipo', 'inquilino')->count();
|
||
|
||
$ultimoImport = (clone $stabiliQuery)
|
||
->where('note', 'like', '%GESCON%')
|
||
->latest('created_at')
|
||
->value('created_at');
|
||
|
||
// Struttura attesa dalla vista (nidificata)
|
||
return [
|
||
'stabili' => [
|
||
'totali' => $stabiliGescon ?: $stabiliTotali,
|
||
'attivi' => $stabiliAttivi,
|
||
],
|
||
'unita' => [
|
||
'totali' => $unitaTotali,
|
||
'con_millesimi' => $unitaConMillesimi,
|
||
],
|
||
'soggetti' => [
|
||
'totali' => $condominiTotali,
|
||
'proprietari' => $soggettiProprietari,
|
||
'inquilini' => $soggettiInquilini,
|
||
],
|
||
'fornitori' => [
|
||
'totali' => $fornitoriTotali,
|
||
'attivi' => $fornitoriAttivi,
|
||
],
|
||
// Chiavi flat per retro-compatibilità con altri template
|
||
'stabili_gescon' => $stabiliGescon,
|
||
'stabili_totali' => $stabiliTotali,
|
||
'fornitori_totali' => $fornitoriTotali,
|
||
'fornitori_attivi' => $fornitoriAttivi,
|
||
'condomini_totali' => $condominiTotali,
|
||
'soggetti_proprietari' => $soggettiProprietari,
|
||
'unita_totali' => $unitaTotali,
|
||
'unita_con_millesimi' => $unitaConMillesimi,
|
||
'ultimo_import' => $ultimoImport,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Ottiene dati recenti per le cards
|
||
*/
|
||
protected function getDatiRecenti($amministratoreId)
|
||
{
|
||
$stabiliIds = Stabile::where('amministratore_id', $amministratoreId)->pluck('id');
|
||
|
||
return [
|
||
'ultimi_stabili' => Stabile::where('amministratore_id', $amministratoreId)
|
||
->where('note', 'like', '%GESCON%')
|
||
->latest()
|
||
->take(5)
|
||
->get(),
|
||
'ultime_unita' => UnitaImmobiliare::whereIn('stabile_id', $stabiliIds)
|
||
->with('stabile')
|
||
->latest()
|
||
->take(10)
|
||
->get(),
|
||
'ultimi_fornitori' => Fornitore::where('amministratore_id', $amministratoreId)
|
||
->latest()
|
||
->take(5)
|
||
->get(),
|
||
'ultimi_condomini' => Soggetto::whereHas('unitaImmobiliari.stabile', function ($q) use ($amministratoreId) {
|
||
$q->where('amministratore_id', $amministratoreId);
|
||
})
|
||
->latest()
|
||
->take(10)
|
||
->get(),
|
||
'ultimi_documenti' => DocumentoCollegato::whereIn('stabile_id', $stabiliIds)
|
||
->with(['stabile'])
|
||
->latest()
|
||
->take(5)
|
||
->get(),
|
||
];
|
||
}
|
||
}
|