resolveAdmin((string) $this->argument('amministratore')); if (! $admin instanceof Amministratore) { $this->error('Amministratore non trovato.'); return self::FAILURE; } $mdbPath = realpath((string) $this->option('mdb')) ?: (string) $this->option('mdb'); if ($mdbPath === '' || ! is_file($mdbPath)) { $this->error('Archivio MDB non trovato: ' . $mdbPath); return self::FAILURE; } $fornitore = $this->resolveFornitore($admin, (string) $this->option('fornitore-id'), (string) $this->option('fornitore-term')); $limit = max(0, (int) $this->option('limit')); $dryRun = (bool) $this->option('dry-run'); $forcePrimary = (bool) $this->option('force-primary'); try { $tables = $reader->listTables($mdbPath); } catch (\Throwable $e) { $this->error($e->getMessage()); return self::FAILURE; } foreach (['TClienti', 'TApparecchi', 'TAllegati'] as $requiredTable) { if (! in_array($requiredTable, $tables, true)) { $this->error('Tabella MDB mancante: ' . $requiredTable); return self::FAILURE; } } try { $clientiRows = $reader->exportTable($mdbPath, 'TClienti'); $schedeRows = $reader->exportTable($mdbPath, 'TApparecchi'); $allegatiRows = $reader->exportTable($mdbPath, 'TAllegati'); } catch (\Throwable $e) { $this->error($e->getMessage()); return self::FAILURE; } if ($limit > 0) { $schedeRows = array_slice($schedeRows, 0, $limit); } $clienti = []; foreach ($clientiRows as $clienteRow) { $clienteId = $this->toInt($clienteRow['ID'] ?? null); if ($clienteId !== null) { $clienti[$clienteId] = $clienteRow; } } $allegatiByScheda = []; foreach ($allegatiRows as $allegatoRow) { $schedaId = $this->toInt($allegatoRow['ID_Scheda'] ?? null); if ($schedaId === null) { continue; } $allegatiByScheda[$schedaId][] = $allegatoRow; } $stats = [ 'schede_lette' => count($schedeRows), 'schede_create' => 0, 'schede_aggiornate' => 0, 'schede_saltate' => 0, 'allegati_importati' => 0, 'seriali_allineati' => 0, ]; $runner = function () use ($admin, $mdbPath, $fornitore, $dryRun, $forcePrimary, $schedeRows, $clienti, $allegatiByScheda, $catalogService, &$stats): void { if ($fornitore instanceof Fornitore && $forcePrimary && ! $dryRun) { $fornitore->forceFill(['is_tecnorepair_primary' => true])->save(); } foreach ($schedeRows as $row) { $legacyId = $this->toInt($row['ID'] ?? null); if ($legacyId === null) { $stats['schede_saltate']++; continue; } $legacyClienteId = $this->toInt($row['ID_Cliente'] ?? null); $cliente = $legacyClienteId !== null ? ($clienti[$legacyClienteId] ?? []) : []; $statusLabel = $this->clean($row['StatoRiparazione'] ?? null) ?: $this->clean($row['ID_StatoRip'] ?? null); $payload = [ 'amministratore_id' => (int) $admin->id, 'fornitore_id' => $fornitore?->id, 'legacy_id' => $legacyId, 'legacy_cliente_id' => $legacyClienteId, 'legacy_centro_ass_id' => $this->toInt($row['ID_CentroAss'] ?? null), 'legacy_committente_codice' => $this->clean($row['Cod_Committente'] ?? null), 'legacy_numero_scheda' => $this->clean($row['NumeroScheda'] ?? null), 'customer_name' => $this->clean($cliente['NomeCognome'] ?? null), 'customer_phone' => $this->clean($cliente['NumeroTelefono'] ?? null), 'customer_phone_alt' => $this->clean($cliente['TelFisso'] ?? null), 'customer_email' => $this->clean($cliente['Email'] ?? null), 'product_model' => $this->clean($row['Modello'] ?? null), 'product_code' => $this->clean($row['CodiceProdotto'] ?? null), 'serial_number' => $this->clean($row['SerialNumber'] ?? null), 'serial_number_2' => $this->clean($row['SerialNumber2'] ?? null), 'status_code' => $this->clean($row['ID_StatoRip'] ?? null), 'status_label' => $statusLabel, 'status_bucket' => AssistenzaTecnorepairScheda::normalizeStatusBucket($statusLabel), 'defect_reported' => $this->clean($row['DifettoSegnalato'] ?? null), 'repair_description' => $this->clean($row['DescrizioneRiparazione'] ?? null), 'communications' => $this->clean($row['Comunicazioni'] ?? null), 'operator_name' => $this->clean($row['NomeOperatore'] ?? null), 'technician_name' => $this->clean($row['NomeTecnicoRiparatore'] ?? null), 'date_received' => $this->normalizeDate($row['DataIngresso'] ?? null), 'ordered_at' => $this->normalizeDate($row['DataOrdine'] ?? null), 'order_number' => $this->clean($row['NumOrdine'] ?? null), 'rma_code' => $this->clean($row['CodiceRMA'] ?? null), 'pin_code' => $this->clean($row['CodicePIN'] ?? null), 'unlock_code' => $this->clean($row['CodiceSblocco'] ?? null), 'legacy_attachment_path' => $this->clean($row['FileAllegato1'] ?? null), 'imported_from_path' => $mdbPath, 'imported_at' => now(), 'metadata' => [ 'cliente' => $cliente, 'raw' => $row, ], ]; $existing = AssistenzaTecnorepairScheda::query() ->where('imported_from_path', $mdbPath) ->where('legacy_id', $legacyId) ->first(); if ($dryRun) { if ($existing) { $stats['schede_aggiornate']++; } else { $stats['schede_create']++; } continue; } $scheda = AssistenzaTecnorepairScheda::query()->updateOrCreate( [ 'imported_from_path' => $mdbPath, 'legacy_id' => $legacyId, ], $payload, ); if ($existing) { $stats['schede_aggiornate']++; } else { $stats['schede_create']++; } $serial = ProductSerial::query()->updateOrCreate( ['legacy_scheda_id' => (int) $scheda->id], [ 'fornitore_id' => $fornitore?->id, 'customer_name' => $scheda->customer_name, 'product_model' => $scheda->product_model, 'product_code' => $scheda->product_code, 'serial_number' => $scheda->serial_number, 'serial_number_2' => $scheda->serial_number_2, 'date_received' => $scheda->date_received, 'internal_notes' => $scheda->communications, 'source' => 'tecnorepair_mdb', 'source_reference' => (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id), ] ); $stats['seriali_allineati']++; if ($fornitore instanceof Fornitore) { $catalogService->syncTecnorepairProduct($fornitore, $scheda, $serial); } AssistenzaTecnorepairAllegato::query()->where('scheda_id', (int) $scheda->id)->delete(); foreach ($allegatiByScheda[$legacyId] ?? [] as $allegatoRow) { AssistenzaTecnorepairAllegato::query()->create([ 'scheda_id' => (int) $scheda->id, 'legacy_id' => $this->toInt($allegatoRow['ID'] ?? null), 'legacy_scheda_id' => $legacyId, 'legacy_attachment_number' => $this->toInt($allegatoRow['NumAllegato'] ?? null), 'file_name' => basename((string) ($allegatoRow['FileAllegato'] ?? 'allegato')), 'file_path' => $this->clean($allegatoRow['FileAllegato'] ?? null), 'imported_from_path' => $mdbPath, 'metadata' => ['raw' => $allegatoRow], ]); $stats['allegati_importati']++; } } }; if ($dryRun) { $runner(); } else { DB::transaction($runner); } $this->info('Import TecnoRepair completato.'); $this->table( ['Campo', 'Valore'], [ ['Amministratore', (string) ($admin->denominazione_studio ?: ($admin->nome . ' ' . $admin->cognome))], ['Archivio MDB', $mdbPath], ['Fornitore collegato', $fornitore?->ragione_sociale ?: 'nessuno'], ['Dry run', $dryRun ? 'si' : 'no'], ['Schede lette', (string) $stats['schede_lette']], ['Schede create', (string) $stats['schede_create']], ['Schede aggiornate', (string) $stats['schede_aggiornate']], ['Schede saltate', (string) $stats['schede_saltate']], ['Allegati importati', (string) $stats['allegati_importati']], ['Seriali allineati', (string) $stats['seriali_allineati']], ] ); return self::SUCCESS; } private function resolveAdmin(string $value): ?Amministratore { $value = trim($value); if ($value === '') { return null; } return Amministratore::query() ->when(is_numeric($value), fn($query) => $query->orWhere('id', (int) $value)) ->orWhere('codice_amministratore', $value) ->orWhere('codice_univoco', $value) ->first(); } private function resolveFornitore(Amministratore $admin, string $fornitoreIdOption, string $fornitoreTermOption): ?Fornitore { $fornitoreId = (int) $fornitoreIdOption; if ($fornitoreId > 0) { return Fornitore::query() ->where('amministratore_id', (int) $admin->id) ->whereKey($fornitoreId) ->first(); } $term = trim($fornitoreTermOption); if ($term === '') { return null; } return Fornitore::query() ->where('amministratore_id', (int) $admin->id) ->where(function ($query) use ($term): void { $query->where('ragione_sociale', 'like', '%' . $term . '%') ->orWhere('nome', 'like', '%' . $term . '%') ->orWhere('cognome', 'like', '%' . $term . '%') ->orWhere('tags', 'like', '%' . $term . '%'); }) ->orderByDesc('is_tecnorepair_primary') ->orderBy('ragione_sociale') ->first(); } private function clean(mixed $value): ?string { if (! is_string($value) && ! is_numeric($value)) { return null; } $value = trim((string) $value); return $value !== '' ? $value : null; } private function toInt(mixed $value): ?int { if ($value === null || $value === '') { return null; } if (! is_numeric($value)) { return null; } return (int) $value; } private function normalizeDate(mixed $value): ?string { $value = $this->clean($value); if ($value === null) { return null; } try { return Carbon::parse($value)->toDateTimeString(); } catch (\Throwable) { return null; } } }