resolveAdmin((string) $this->argument('amministratore')); if (! $admin instanceof Amministratore) { $this->error('Amministratore non trovato.'); return self::FAILURE; } $rawMdbPath = trim((string) $this->option('mdb')); $mdbPath = $rawMdbPath !== '' && file_exists($rawMdbPath) ? (realpath($rawMdbPath) ?: $rawMdbPath) : $rawMdbPath; if ($mdbPath === '' || ! is_file($mdbPath)) { $this->error('Archivio MDB non trovato: ' . $mdbPath); return self::FAILURE; } if (! is_readable($mdbPath)) { $this->error('Archivio MDB non leggibile: ' . $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_consolidate' => 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(); } $this->consolidateLegacyDuplicates( amministratoreId: (int) $admin->id, fornitoreId: $fornitore?->id ? (int) $fornitore->id : null, dryRun: $dryRun, stats: $stats, ); 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, ], ]; $matchingSchede = $this->buildExistingSchedaQuery( amministratoreId: (int) $admin->id, legacyId: $legacyId, fornitoreId: $fornitore?->id ? (int) $fornitore->id : null, ) ->orderByDesc('fornitore_id') ->orderBy('id') ->get(); $existing = $matchingSchede->first(); $duplicateSchede = $matchingSchede->slice(1); $duplicateSchedaIds = $duplicateSchede ->pluck('id') ->filter(fn($value): bool => is_numeric($value) && (int) $value > 0) ->map(fn($value): int => (int) $value) ->all(); if ($dryRun) { if ($existing) { $stats['schede_aggiornate']++; } else { $stats['schede_create']++; } if ($duplicateSchedaIds !== []) { $stats['schede_consolidate'] += count($duplicateSchedaIds); } continue; } if ($duplicateSchedaIds !== []) { ProductSerial::query()->whereIn('legacy_scheda_id', $duplicateSchedaIds)->delete(); AssistenzaTecnorepairScheda::query()->whereKey($duplicateSchedaIds)->delete(); $stats['schede_consolidate'] += count($duplicateSchedaIds); } if ($existing instanceof AssistenzaTecnorepairScheda) { $existing->fill($payload); $existing->save(); $scheda = $existing; } else { $scheda = AssistenzaTecnorepairScheda::query()->create($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 consolidate', (string) $stats['schede_consolidate']], ['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 buildExistingSchedaQuery(int $amministratoreId, int $legacyId, ?int $fornitoreId = null) { return AssistenzaTecnorepairScheda::query() ->where('amministratore_id', $amministratoreId) ->where('legacy_id', $legacyId) ->when( $fornitoreId !== null, fn($query) => $query->where(function ($inner) use ($fornitoreId): void { $inner->where('fornitore_id', $fornitoreId) ->orWhereNull('fornitore_id'); }) ); } private function consolidateLegacyDuplicates(int $amministratoreId, ?int $fornitoreId, bool $dryRun, array &$stats): void { $duplicateLegacyIds = AssistenzaTecnorepairScheda::query() ->where('amministratore_id', $amministratoreId) ->when( $fornitoreId !== null, fn($query) => $query->where(function ($inner) use ($fornitoreId): void { $inner->where('fornitore_id', $fornitoreId) ->orWhereNull('fornitore_id'); }) ) ->whereNotNull('legacy_id') ->select('legacy_id') ->groupBy('legacy_id') ->havingRaw('COUNT(*) > 1') ->pluck('legacy_id') ->filter(fn($value): bool => is_numeric($value) && (int) $value > 0) ->map(fn($value): int => (int) $value) ->all(); foreach ($duplicateLegacyIds as $legacyId) { $matchingSchede = $this->buildExistingSchedaQuery( amministratoreId: $amministratoreId, legacyId: $legacyId, fornitoreId: $fornitoreId, ) ->orderByDesc('fornitore_id') ->orderBy('id') ->get(); if ($matchingSchede->count() <= 1) { continue; } /** @var AssistenzaTecnorepairScheda $canonical */ $canonical = $matchingSchede->first(); $duplicateSchede = $matchingSchede->slice(1); foreach ($duplicateSchede as $duplicateScheda) { $duplicateId = (int) $duplicateScheda->id; if ($duplicateId <= 0) { continue; } if ($dryRun) { $stats['schede_consolidate']++; continue; } AssistenzaTecnorepairAllegato::query() ->where('scheda_id', $duplicateId) ->update(['scheda_id' => (int) $canonical->id]); $serials = ProductSerial::query() ->where('legacy_scheda_id', $duplicateId) ->orderBy('id') ->get(); foreach ($serials as $index => $serial) { if ($index === 0 && ! ProductSerial::query()->where('legacy_scheda_id', (int) $canonical->id)->exists()) { $serial->forceFill(['legacy_scheda_id' => (int) $canonical->id])->save(); continue; } $serial->delete(); } $duplicateScheda->delete(); $stats['schede_consolidate']++; } } } 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; } } }