netgescon-day0/app/Http/Controllers/Admin/FornitoriImportController.php

260 lines
11 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Amministratore;
use App\Models\GesconImportMapping;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Carbon\Carbon;
class FornitoriImportController extends Controller
{
private function progressKey(): string
{
return 'fornitori_import:' . (Auth::id() ?? 'guest');
}
private function currentAdminId(): ?int
{
$user = Auth::user();
if (!$user) return null;
$am = Amministratore::where('user_id', $user->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()]));
}
}
}
}