id)->first(); return $am?->id; } public function stagingTables(): JsonResponse { $conn = Config::get('database.connections.gescon_import') ? 'gescon_import' : Config::get('database.default'); $driver = DB::connection($conn)->getDriverName(); $tables = []; try { if ($driver === 'mysql') { $dbName = DB::connection($conn)->getDatabaseName(); $rows = DB::connection($conn)->select( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND (table_name LIKE ? OR table_name LIKE ?) ORDER BY table_name', [$dbName, 'mdb%fornit%', 'mdb%Supplier%'] ); $tables = array_map(fn($r) => $r->table_name, $rows); } elseif ($driver === 'pgsql') { $rows = DB::connection($conn)->select( "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema') AND (tablename ILIKE 'mdb%fornit%' OR tablename ILIKE 'mdb%supplier%') ORDER BY tablename" ); $tables = array_map(fn($r) => $r->tablename, $rows); } } catch (\Throwable $e) { $tables = []; } return new JsonResponse(['tables' => $tables, 'connection' => $conn]); } public function stagingColumns(Request $request): JsonResponse { $request->validate(['table' => 'required|string']); $conn = Config::get('database.connections.gescon_import') ? 'gescon_import' : Config::get('database.default'); $table = $request->string('table')->toString(); if (!DB::connection($conn)->getSchemaBuilder()->hasTable($table)) { return new JsonResponse(['ok' => false, 'error' => 'Tabella di staging non trovata'], 404); } $driver = DB::connection($conn)->getDriverName(); $cols = []; try { if ($driver === 'mysql') { $dbName = DB::connection($conn)->getDatabaseName(); $rows = DB::connection($conn)->select('SELECT column_name FROM information_schema.columns WHERE table_schema = ? AND table_name = ? ORDER BY ordinal_position', [$dbName, $table]); $cols = array_map(fn($r) => $r->column_name, $rows); } elseif ($driver === 'pgsql') { $rows = DB::connection($conn)->select("SELECT column_name FROM information_schema.columns WHERE table_name = ? ORDER BY ordinal_position", [$table]); $cols = array_map(fn($r) => $r->column_name, $rows); } } catch (\Throwable $e) { $cols = []; } return new JsonResponse(['ok' => true, 'columns' => $cols]); } public function init(Request $request): JsonResponse { $request->validate(['staging_table' => 'required|string']); $conn = Config::get('database.connections.gescon_import') ? 'gescon_import' : Config::get('database.default'); $table = $request->string('staging_table')->toString(); if (!DB::connection($conn)->getSchemaBuilder()->hasTable($table)) { return new JsonResponse(['ok' => false, 'error' => 'Tabella di staging non trovata', 'table' => $table], 400); } $total = (int) DB::connection($conn)->table($table)->count(); $state = [ 'table' => $table, 'connection' => $conn, 'total' => $total, 'processed' => 0, 'last_id' => 0, 'status' => 'running', 'started_at' => Carbon::now()->toISOString(), ]; Cache::put($this->progressKey(), $state, Carbon::now()->addHours(2)); return new JsonResponse(['ok' => true, 'progress' => $state]); } public function progress(): JsonResponse { $state = Cache::get($this->progressKey()); if (!$state) return new JsonResponse(['ok' => false, 'error' => 'Nessun import attivo']); $pct = $state['total'] > 0 ? (int) floor(($state['processed'] / $state['total']) * 100) : 0; $state['percent'] = max(0, min(100, $pct)); return new JsonResponse(['ok' => true, 'progress' => $state]); } public function processChunk(Request $request): JsonResponse { $chunk = max(1, min(2000, (int) $request->input('chunk', 500))); $state = Cache::get($this->progressKey()); if (!$state) return new JsonResponse(['ok' => false, 'error' => 'Import non inizializzato'], 400); if (($state['status'] ?? '') === 'completed') return new JsonResponse(['ok' => true, 'progress' => $state]); $conn = $state['connection']; $table = $state['table']; $lastId = (int) ($state['last_id'] ?? 0); if (!DB::connection($conn)->getSchemaBuilder()->hasTable($table)) { return new JsonResponse(['ok' => false, 'error' => 'Tabella di staging non trovata', 'table' => $table], 400); } $rows = DB::connection($conn)->table($table) ->where('id', '>', $lastId) ->orderBy('id') ->limit($chunk) ->get(); if ($rows->isEmpty()) { $state['status'] = 'completed'; $state['completed_at'] = Carbon::now()->toISOString(); Cache::put($this->progressKey(), $state, Carbon::now()->addHours(2)); return new JsonResponse(['ok' => true, 'progress' => $state]); } $adminId = $this->currentAdminId(); $imported = 0; $mapping = GesconImportMapping::where('user_id', Auth::id())->where('legacy_code', 'fornitori')->first(); $mapCfg = (array) ($mapping?->meta['mapping'] ?? []); foreach ($rows as $r) { $data = (array) $r; $rec = $this->mapFornitoreRecord($data, $mapCfg); if (!$rec) { $lastId = $r->id; continue; } try { $this->upsertFornitore($rec, $adminId); $imported++; } catch (\Throwable $e) { Log::warning('Fornitori import: errore record', ['id' => $r->id, 'err' => $e->getMessage()]); } $lastId = $r->id; } $state['processed'] = (int) $state['processed'] + $rows->count(); $state['last_id'] = $lastId; if ($state['processed'] >= $state['total']) { $state['status'] = 'completed'; $state['completed_at'] = Carbon::now()->toISOString(); } Cache::put($this->progressKey(), $state, Carbon::now()->addHours(2)); return new JsonResponse(['ok' => true, 'progress' => $state, 'chunk_imported' => $imported]); } private function mapFornitoreRecord(array $row, array $mapCfg): ?array { // normalize keys $lower = []; foreach ($row as $k => $v) { $lower[Str::snake(strtolower((string)$k))] = is_string($v) ? trim($v) : $v; } $pick = function (?string $cfgKey, array $candidates) use ($lower, $mapCfg) { $src = $cfgKey && isset($mapCfg[$cfgKey]) ? $mapCfg[$cfgKey] : null; if ($src && array_key_exists($src, $lower)) return $lower[$src]; foreach ($candidates as $c) { if (array_key_exists($c, $lower) && $lower[$c] !== null && $lower[$c] !== '') return $lower[$c]; } return null; }; $ragsoc = $pick('fornitore.ragione_sociale', ['ragione_sociale', 'ragsoc', 'denominazione', 'nome', 'descrizione']); $piva = $pick('fornitore.partita_iva', ['partita_iva', 'piva', 'p_iva', 'p.iva']); $cf = $pick('fornitore.codice_fiscale', ['codice_fiscale', 'cf']); $indirizzo = $pick('fornitore.indirizzo', ['indirizzo', 'address', 'via']); $cap = $pick('fornitore.cap', ['cap', 'zip']); $citta = $pick('fornitore.comune', ['comune', 'citta', 'città', 'city']); $prov = $pick('fornitore.provincia', ['provincia', 'prov', 'pr']); $email = $pick('fornitore.email', ['email', 'pec']); $tel = $pick('fornitore.telefono', ['telefono', 'tel', 'phone']); if (!$ragsoc && !$piva && !$cf) return null; return [ 'ragione_sociale' => $ragsoc ?: ($cf ?: $piva), 'partita_iva' => $piva ?: null, 'codice_fiscale' => $cf ?: null, 'indirizzo' => $indirizzo ?: null, 'cap' => $cap ?: null, 'citta' => $citta ?: null, 'provincia' => $prov ? (strlen($prov) > 2 ? substr($prov, 0, 2) : strtoupper($prov)) : null, 'email' => $email ?: null, 'telefono' => $tel ?: null, ]; } private function upsertFornitore(array $rec, ?int $amministratoreId): void { $base = [ 'ragione_sociale' => $rec['ragione_sociale'], 'indirizzo' => $rec['indirizzo'], 'cap' => $rec['cap'], 'citta' => $rec['citta'], 'provincia' => $rec['provincia'], 'email' => $rec['email'], 'telefono' => $rec['telefono'], 'updated_at' => Carbon::now(), ]; if ($amministratoreId) $base['amministratore_id'] = $amministratoreId; // Prefer P.IVA then CF then ragione_sociale unique-ish if (!empty($rec['partita_iva'])) { DB::table('fornitori')->updateOrInsert( ['partita_iva' => $rec['partita_iva']], array_merge($base, [ 'codice_fiscale' => $rec['codice_fiscale'], 'created_at' => DB::raw('COALESCE(created_at, NOW())'), ]) ); return; } if (!empty($rec['codice_fiscale'])) { $existing = DB::table('fornitori')->where('codice_fiscale', $rec['codice_fiscale'])->first(); if ($existing) { DB::table('fornitori')->where('id', $existing->id)->update($base); } else { DB::table('fornitori')->insert(array_merge($base, [ 'codice_fiscale' => $rec['codice_fiscale'], 'created_at' => Carbon::now(), ])); } return; } // Fallback by ragione_sociale if (!empty($rec['ragione_sociale'])) { $existing = DB::table('fornitori')->where('ragione_sociale', $rec['ragione_sociale'])->first(); if ($existing) { DB::table('fornitori')->where('id', $existing->id)->update($base); } else { DB::table('fornitori')->insert(array_merge($base, ['created_at' => Carbon::now()])); } } } }