resolveAdmin((string) $this->argument('amministratore')); if (! $admin instanceof Amministratore) { $this->error('Amministratore non trovato.'); return self::FAILURE; } $fornitore = $this->resolveFornitore($admin, (string) $this->option('fornitore-id'), (string) $this->option('fornitore-term')); if (! $fornitore instanceof Fornitore) { $this->error('Fornitore non trovato per questo amministratore.'); return self::FAILURE; } $dryRun = (bool) $this->option('dry-run'); $candidates = AssistenzaTecnorepairScheda::query() ->where('amministratore_id', (int) $admin->id) ->where('fornitore_id', (int) $fornitore->id) ->orderByDesc('date_received') ->orderByDesc('id') ->get(); $customers = $this->collectUniqueCustomers($candidates); $stats = [ 'legacy_rows' => $candidates->count(), 'clienti_distinti' => $customers->count(), 'rubrica_create' => 0, 'rubrica_match' => 0, 'rubrica_update' => 0, 'fornitore_rows' => 0, ]; $runner = function () use ($customers, $fornitore, $admin, &$stats): void { foreach ($customers as $customer) { $rubrica = $this->resolveOrCreateRubrica($admin, $customer, $stats); FornitoreCliente::query()->updateOrCreate( [ 'fornitore_id' => (int) $fornitore->id, 'legacy_cliente_id' => $customer['legacy_cliente_id'], ], [ 'amministratore_id' => (int) $admin->id, 'rubrica_id' => $rubrica?->id, 'display_name' => $customer['display_name'], 'phone' => $customer['phone'], 'phone_alt' => $customer['phone_alt'], 'email' => $customer['email'], 'indirizzo' => $customer['indirizzo'], 'cap' => $customer['cap'], 'citta' => $customer['citta'], 'provincia' => $customer['provincia'], 'partita_iva' => $customer['partita_iva'], 'codice_fiscale' => $customer['codice_fiscale'], 'note' => $customer['note'], 'source' => 'tecnorepair_tclienti', 'imported_from_path' => $customer['imported_from_path'], 'imported_at' => now(), 'metadata' => $customer['metadata'], ] ); $stats['fornitore_rows']++; } }; if ($dryRun) { foreach ($customers as $customer) { $this->resolveOrCreateRubrica($admin, $customer, $stats, true); $stats['fornitore_rows']++; } } else { DB::transaction($runner); } $this->info('Import rubrica clienti completato.'); $this->table(['Campo', 'Valore'], [ ['Amministratore', (string) ($admin->denominazione_studio ?: trim((string) ($admin->nome ?? '') . ' ' . (string) ($admin->cognome ?? '')))], ['Fornitore', (string) ($fornitore->ragione_sociale ?: ('Fornitore #' . $fornitore->id))], ['Dry run', $dryRun ? 'si' : 'no'], ['Schede legacy lette', (string) $stats['legacy_rows']], ['Clienti distinti', (string) $stats['clienti_distinti']], ['Rubrica nuova', (string) $stats['rubrica_create']], ['Rubrica match', (string) $stats['rubrica_match']], ['Rubrica aggiornata', (string) $stats['rubrica_update']], ['Righe fornitore_clienti', (string) $stats['fornitore_rows']], ]); 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 . '%'); }) ->orderByDesc('is_tecnorepair_primary') ->orderBy('ragione_sociale') ->first(); } /** * @param Collection $rows * @return Collection> */ private function collectUniqueCustomers(Collection $rows): Collection { $customers = []; foreach ($rows as $scheda) { $cliente = is_array($scheda->metadata['cliente'] ?? null) ? $scheda->metadata['cliente'] : []; $legacyClienteId = $this->toInt($cliente['ID'] ?? $scheda->legacy_cliente_id); $displayName = $this->cleanString($cliente['NomeCognome'] ?? $scheda->customer_name); $phone = $this->cleanString($cliente['NumeroTelefono'] ?? $scheda->customer_phone); $phoneAlt = $this->cleanString($cliente['TelFisso'] ?? $scheda->customer_phone_alt); $email = $this->cleanString($cliente['Email'] ?? $scheda->customer_email); $indirizzo = $this->cleanString($cliente['Indirizzo'] ?? null); $cap = $this->cleanString($cliente['Cap'] ?? null); $citta = $this->cleanString($cliente['Citta'] ?? null); $provincia = $this->cleanString($cliente['Prov'] ?? null); $partitaIva = $this->cleanString($cliente['PIVA'] ?? null); $codiceFiscale = $this->cleanString($cliente['CodFis'] ?? null); $note = $this->cleanString($cliente['Annotazioni'] ?? null); $identity = $legacyClienteId !== null ? 'legacy-' . $legacyClienteId : $this->buildIdentityKey($displayName, $email, $phone, $phoneAlt); if ($identity === '' || ($displayName === null && $phone === null && $email === null)) { continue; } if (! isset($customers[$identity])) { $customers[$identity] = [ 'legacy_cliente_id' => $legacyClienteId, 'display_name' => $displayName, 'phone' => $phone, 'phone_alt' => $phoneAlt, 'email' => $email, 'indirizzo' => $indirizzo, 'cap' => $cap, 'citta' => $citta, 'provincia' => $provincia, 'partita_iva' => $partitaIva, 'codice_fiscale' => $codiceFiscale, 'note' => $note, 'imported_from_path'=> $this->cleanString($scheda->imported_from_path), 'metadata' => [ 'cliente' => $cliente, 'ultima_scheda_id' => (int) $scheda->id, 'ultima_scheda_num' => (string) ($scheda->legacy_numero_scheda ?? ''), ], ]; continue; } foreach (['display_name', 'phone', 'phone_alt', 'email', 'indirizzo', 'cap', 'citta', 'provincia', 'partita_iva', 'codice_fiscale', 'note', 'imported_from_path'] as $field) { if (($customers[$identity][$field] ?? null) === null && ${Str::camel($field)} !== null) { $customers[$identity][$field] = ${Str::camel($field)}; } } } return collect(array_values($customers)); } /** * @param array $customer * @param array $stats */ private function resolveOrCreateRubrica(Amministratore $admin, array $customer, array &$stats, bool $dryRun = false): ?RubricaUniversale { $query = RubricaUniversale::query()->where('amministratore_id', (int) $admin->id); $email = $this->cleanString($customer['email'] ?? null); $phone = $this->normalizePhone($customer['phone'] ?? null); $phoneAlt = $this->normalizePhone($customer['phone_alt'] ?? null); $phoneForRubrica = $this->normalizePhoneField($customer['phone'] ?? null); $phoneAltForRubrica = $this->normalizePhoneField($customer['phone_alt'] ?? null); $cf = $this->cleanString($customer['codice_fiscale'] ?? null); $piva = $this->cleanString($customer['partita_iva'] ?? null); $name = $this->cleanString($customer['display_name'] ?? null); $provincia = $this->normalizeProvince($customer['provincia'] ?? null); $rubrica = null; if ($cf !== null) { $rubrica = (clone $query)->where('codice_fiscale', $cf)->first(); } if (! $rubrica && $piva !== null) { $rubrica = (clone $query)->where('partita_iva', $piva)->first(); } if (! $rubrica && $email !== null) { $rubrica = (clone $query)->where('email', $email)->first(); } if (! $rubrica && $phone !== null) { $rubrica = (clone $query)->where(function ($phoneQuery) use ($phone): void { $phoneQuery->whereRaw("REGEXP_REPLACE(COALESCE(telefono_cellulare, ''), '[^0-9]', '') = ?", [$phone]) ->orWhereRaw("REGEXP_REPLACE(COALESCE(telefono_ufficio, ''), '[^0-9]', '') = ?", [$phone]) ->orWhereRaw("REGEXP_REPLACE(COALESCE(telefono_casa, ''), '[^0-9]', '') = ?", [$phone]); })->first(); } if (! $rubrica && $phoneAlt !== null) { $rubrica = (clone $query)->where(function ($phoneQuery) use ($phoneAlt): void { $phoneQuery->whereRaw("REGEXP_REPLACE(COALESCE(telefono_cellulare, ''), '[^0-9]', '') = ?", [$phoneAlt]) ->orWhereRaw("REGEXP_REPLACE(COALESCE(telefono_ufficio, ''), '[^0-9]', '') = ?", [$phoneAlt]) ->orWhereRaw("REGEXP_REPLACE(COALESCE(telefono_casa, ''), '[^0-9]', '') = ?", [$phoneAlt]); })->first(); } if (! $rubrica && $name !== null) { $rubrica = (clone $query)->where(function ($nameQuery) use ($name): void { $nameQuery->whereRaw("LOWER(TRIM(CONCAT(COALESCE(nome, ''), ' ', COALESCE(cognome, '')))) = ?", [Str::lower($name)]) ->orWhereRaw("LOWER(TRIM(COALESCE(ragione_sociale, ''))) = ?", [Str::lower($name)]); })->first(); } if ($rubrica instanceof RubricaUniversale) { $stats['rubrica_match']++; if (! $dryRun) { $dirty = false; foreach ([ 'email' => $email, 'telefono_cellulare' => $phoneForRubrica, 'telefono_ufficio' => $phoneAltForRubrica, 'indirizzo' => $this->cleanString($customer['indirizzo'] ?? null), 'cap' => $this->cleanString($customer['cap'] ?? null), 'citta' => $this->cleanString($customer['citta'] ?? null), 'provincia' => $provincia, 'partita_iva' => $piva, 'codice_fiscale' => $cf, ] as $field => $value) { if ($value !== null && ! filled($rubrica->{$field})) { $rubrica->{$field} = $value; $dirty = true; } } if ($dirty) { $rubrica->save(); $stats['rubrica_update']++; } } return $rubrica; } $stats['rubrica_create']++; if ($dryRun) { return null; } [$nome, $cognome] = $this->splitDisplayName($name); return RubricaUniversale::query()->create([ 'amministratore_id' => (int) $admin->id, 'nome' => $nome, 'cognome' => $cognome, 'tipo_contatto' => 'persona_fisica', 'codice_fiscale' => $cf, 'partita_iva' => $piva, 'indirizzo' => $this->cleanString($customer['indirizzo'] ?? null), 'cap' => $this->cleanString($customer['cap'] ?? null), 'citta' => $this->cleanString($customer['citta'] ?? null), 'provincia' => $provincia, 'telefono_cellulare' => $phoneForRubrica, 'telefono_ufficio' => $phoneAltForRubrica, 'email' => $email, 'note' => $this->buildRubricaNote($customer), 'categoria' => 'cliente', 'stato' => 'attivo', 'data_inserimento' => now(), 'data_ultima_modifica' => now(), 'creato_da' => auth()->id(), 'modificato_da' => auth()->id(), ]); } private function splitDisplayName(?string $name): array { $name = trim((string) $name); if ($name === '') { return ['Cliente', 'TecnoRepair']; } $parts = preg_split('/\s+/', $name) ?: []; if (count($parts) <= 1) { return [$name, '']; } return [trim((string) array_shift($parts)), trim(implode(' ', $parts))]; } private function buildRubricaNote(array $customer): string { $lines = array_filter([ 'Import TecnoRepair TClienti per rubrica fornitore.', isset($customer['legacy_cliente_id']) && $customer['legacy_cliente_id'] !== null ? 'Legacy cliente ID: ' . $customer['legacy_cliente_id'] : null, $this->cleanString($customer['note'] ?? null), ]); return implode("\n", $lines); } private function buildIdentityKey(?string $displayName, ?string $email, ?string $phone, ?string $phoneAlt): string { if ($email !== null) { return 'email-' . Str::lower($email); } if ($phone !== null) { return 'phone-' . preg_replace('/\D+/', '', $phone); } if ($phoneAlt !== null) { return 'phone-' . preg_replace('/\D+/', '', $phoneAlt); } if ($displayName !== null) { return 'name-' . Str::slug(Str::lower($displayName)); } return ''; } private function normalizePhone(mixed $value): ?string { $value = preg_replace('/\D+/', '', (string) $value) ?? ''; return $value !== '' ? $value : null; } private function normalizePhoneField(mixed $value): ?string { $value = trim((string) $value); if ($value === '') { return null; } if (preg_match('/\+?[0-9][0-9\s().\/-]{4,}/', $value, $matches) !== 1) { return null; } $normalized = preg_replace('/[^0-9+]/', '', (string) $matches[0]) ?? ''; $normalized = ltrim($normalized, '+') === '' ? '' : $normalized; return $normalized !== '' ? Str::limit($normalized, 30, '') : null; } private function normalizeProvince(mixed $value): ?string { $value = Str::upper(trim((string) $value)); if ($value === '') { return null; } $value = preg_replace('/[^A-Z]/', '', $value) ?? ''; if ($value === '') { return null; } return Str::limit($value, 2, ''); } private function cleanString(mixed $value): ?string { $value = trim((string) $value); return $value !== '' ? $value : null; } private function toInt(mixed $value): ?int { if ($value === null || $value === '' || ! is_numeric($value)) { return null; } return (int) $value; } }