260 lines
11 KiB
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()]));
|
|
}
|
|
}
|
|
}
|
|
}
|