427 lines
17 KiB
PHP
427 lines
17 KiB
PHP
<?php
|
|
namespace App\Services\Fornitore;
|
|
|
|
use App\Models\AssistenzaTecnorepairScheda;
|
|
use App\Models\Fornitore;
|
|
use App\Models\FornitoreCliente;
|
|
use App\Models\RubricaUniversale;
|
|
use App\Models\TicketIntervento;
|
|
use App\Services\Tecnorepair\TecnoRepairArchiveService;
|
|
use App\Support\PhoneNumber;
|
|
use App\Support\TecnoRepairMdbReader;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
|
|
class FornitoreRubricaSyncService
|
|
{
|
|
public function __construct(
|
|
private readonly TecnoRepairMdbReader $mdbReader,
|
|
private readonly TecnoRepairArchiveService $archiveService,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Synchronize contacts from all three sources:
|
|
* 1. TecnoRepair MDB (TClienti)
|
|
* 2. Contabilità MySQL arc_nehr (cli + ind)
|
|
* 3. NetGescon Tickets / Stabili
|
|
*
|
|
* @return array{tecnorepair: int, contabilita: int, tickets: int, total: int}
|
|
*/
|
|
public function syncAll(int $amministratoreId, int $fornitoreId): array
|
|
{
|
|
$primaryFornitoreId = in_array($fornitoreId, [236, 359], true) ? 236 : $fornitoreId;
|
|
|
|
$stats = [
|
|
'tecnorepair' => 0,
|
|
'contabilita' => 0,
|
|
'tickets' => 0,
|
|
'total' => 0,
|
|
];
|
|
|
|
// 1. Sync TecnoRepair TClienti
|
|
try {
|
|
$stats['tecnorepair'] = $this->syncTecnoRepair($amministratoreId, $primaryFornitoreId);
|
|
} catch (\Throwable $e) {
|
|
Log::warning('FornitoreRubricaSyncService: errore sync TecnoRepair: ' . $e->getMessage());
|
|
}
|
|
|
|
// 2. Sync Contabilità MySQL
|
|
try {
|
|
$stats['contabilita'] = $this->syncContabilita($amministratoreId, $primaryFornitoreId);
|
|
} catch (\Throwable $e) {
|
|
Log::warning('FornitoreRubricaSyncService: errore sync Contabilità: ' . $e->getMessage());
|
|
}
|
|
|
|
// 3. Sync NetGescon Tickets
|
|
try {
|
|
$stats['tickets'] = $this->syncNetGesconTickets($amministratoreId, $primaryFornitoreId);
|
|
} catch (\Throwable $e) {
|
|
Log::warning('FornitoreRubricaSyncService: errore sync Tickets: ' . $e->getMessage());
|
|
}
|
|
|
|
$stats['total'] = FornitoreCliente::query()
|
|
->where('fornitore_id', $primaryFornitoreId)
|
|
->count();
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Import contacts from TecnoRepairDB.mdb TClienti table
|
|
*/
|
|
public function syncTecnoRepair(int $amministratoreId, int $fornitoreId): int
|
|
{
|
|
$fornitore = Fornitore::query()->find($fornitoreId);
|
|
$mdbPath = $this->archiveService->resolveMdbPath(null, $fornitore);
|
|
|
|
if (! file_exists($mdbPath) || ! is_readable($mdbPath)) {
|
|
return 0;
|
|
}
|
|
|
|
$tables = $this->mdbReader->listTables($mdbPath);
|
|
if (! in_array('TClienti', $tables, true)) {
|
|
return 0;
|
|
}
|
|
|
|
$clientiRows = $this->mdbReader->exportTable($mdbPath, 'TClienti');
|
|
$count = 0;
|
|
|
|
foreach ($clientiRows as $row) {
|
|
$legacyId = isset($row['ID']) && is_numeric($row['ID']) ? (int) $row['ID'] : null;
|
|
$name = trim((string) ($row['NomeCognome'] ?? ''));
|
|
|
|
if (! $legacyId && $name === '') {
|
|
continue;
|
|
}
|
|
|
|
$phone = trim((string) ($row['NumeroTelefono'] ?? ''));
|
|
$phoneAlt = trim((string) ($row['TelFisso'] ?? ''));
|
|
$email = trim((string) ($row['Email'] ?? ''));
|
|
$cf = trim((string) ($row['CodFis'] ?? ''));
|
|
$piva = trim((string) ($row['PIVA'] ?? ''));
|
|
|
|
// Link to rubrica_universale if found
|
|
$rubricaId = $this->findRubricaMatch($amministratoreId, $cf, $piva, $phone, $phoneAlt, $email, $name);
|
|
|
|
FornitoreCliente::query()->updateOrCreate(
|
|
[
|
|
'fornitore_id' => $fornitoreId,
|
|
'legacy_cliente_id' => $legacyId,
|
|
],
|
|
[
|
|
'amministratore_id' => $amministratoreId,
|
|
'rubrica_id' => $rubricaId,
|
|
'display_name' => $name ?: ('Cliente TecnoRepair #' . $legacyId),
|
|
'phone' => $phone,
|
|
'phone_alt' => $phoneAlt,
|
|
'email' => $email,
|
|
'indirizzo' => trim((string) ($row['Indirizzo'] ?? '')),
|
|
'cap' => trim((string) ($row['Cap'] ?? '')),
|
|
'citta' => trim((string) ($row['Citta'] ?? '')),
|
|
'provincia' => trim((string) ($row['Prov'] ?? '')),
|
|
'partita_iva' => $piva,
|
|
'codice_fiscale' => $cf,
|
|
'note' => trim((string) ($row['Annotazioni'] ?? '')),
|
|
'source' => 'tecnorepair_tclienti',
|
|
'imported_from_path' => $mdbPath,
|
|
'imported_at' => now(),
|
|
'metadata' => [
|
|
'raw' => $row,
|
|
],
|
|
]
|
|
);
|
|
|
|
$count++;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Import contacts from Contabilità MySQL (arc_nehr: cli + ind)
|
|
*/
|
|
public function syncContabilita(int $amministratoreId, int $fornitoreId): int
|
|
{
|
|
$conn = DB::connection('contabilita_mysql');
|
|
|
|
// Check if connection works
|
|
try {
|
|
$conn->getPdo();
|
|
} catch (\Throwable $e) {
|
|
Log::info('Contabilità MySQL non disponibile per sync rubrica: ' . $e->getMessage());
|
|
return 0;
|
|
}
|
|
|
|
// Read destinations/addresses with phone numbers from `ind`
|
|
$indRows = $conn->table('ind')
|
|
->where(function ($q) {
|
|
$q->where('TELEFONO', '!=', '')
|
|
->orWhere('CELLULARE', '!=', '')
|
|
->orWhere('e_mail', '!=', '');
|
|
})
|
|
->get();
|
|
|
|
$indByCli = [];
|
|
foreach ($indRows as $ind) {
|
|
$cliCod = trim((string) $ind->CLI_CODICE);
|
|
if ($cliCod !== '' && ! isset($indByCli[$cliCod])) {
|
|
$indByCli[$cliCod] = $ind;
|
|
}
|
|
}
|
|
|
|
// Read all active customers from `cli`
|
|
$cliList = $conn->table('cli')
|
|
->where('OBSOLETO', '!=', 'si')
|
|
->get([
|
|
'ID', 'CODICE', 'DESCRIZIONE1', 'DESCRIZIONE2', 'VIA', 'CITTA',
|
|
'PARTITA_IVA', 'CODICE_FISCALE', 'NOTE'
|
|
]);
|
|
|
|
$count = 0;
|
|
foreach ($cliList as $cli) {
|
|
$desc1 = trim((string) $cli->DESCRIZIONE1);
|
|
$desc2 = trim((string) $cli->DESCRIZIONE2);
|
|
$name = trim($desc1 . ' ' . $desc2);
|
|
if ($name === '') {
|
|
continue;
|
|
}
|
|
|
|
$codice = trim((string) $cli->CODICE);
|
|
$ind = $indByCli[$codice] ?? null;
|
|
|
|
$phone = trim((string) ($ind?->CELLULARE ?: $ind?->TELEFONO ?: ''));
|
|
$phoneAlt = trim((string) ($ind?->TELEFONO_01 ?: $ind?->FAX ?: ''));
|
|
$email = trim((string) ($ind?->e_mail ?: ''));
|
|
$piva = trim((string) ($cli->PARTITA_IVA ?: $ind?->partita_iva ?: ''));
|
|
$cf = trim((string) ($cli->CODICE_FISCALE ?: $ind?->codice_fiscale ?: ''));
|
|
|
|
// Check if already present from TecnoRepair
|
|
$existing = FornitoreCliente::query()
|
|
->where('fornitore_id', $fornitoreId)
|
|
->where(function ($q) use ($name, $cf, $piva, $codice) {
|
|
if ($cf !== '') {
|
|
$q->orWhere('codice_fiscale', $cf);
|
|
}
|
|
if ($piva !== '') {
|
|
$q->orWhere('partita_iva', $piva);
|
|
}
|
|
$q->orWhere('metadata->cli_codice', $codice);
|
|
})
|
|
->first();
|
|
|
|
if ($existing) {
|
|
// Enrich existing record with contabilità metadata
|
|
$meta = is_array($existing->metadata) ? $existing->metadata : [];
|
|
$meta['cli_codice'] = $codice;
|
|
$meta['contabilita_sync'] = true;
|
|
$existing->metadata = $meta;
|
|
if ($existing->phone === '' && $phone !== '') {
|
|
$existing->phone = $phone;
|
|
}
|
|
if ($existing->email === '' && $email !== '') {
|
|
$existing->email = $email;
|
|
}
|
|
$existing->save();
|
|
$count++;
|
|
continue;
|
|
}
|
|
|
|
$rubricaId = $this->findRubricaMatch($amministratoreId, $cf, $piva, $phone, $phoneAlt, $email, $name);
|
|
|
|
FornitoreCliente::query()->create([
|
|
'amministratore_id' => $amministratoreId,
|
|
'fornitore_id' => $fornitoreId,
|
|
'rubrica_id' => $rubricaId,
|
|
'legacy_cliente_id' => null,
|
|
'display_name' => $name,
|
|
'phone' => $phone,
|
|
'phone_alt' => $phoneAlt,
|
|
'email' => $email,
|
|
'indirizzo' => trim((string) ($cli->VIA ?: $ind?->VIA ?: '')),
|
|
'cap' => trim((string) ($ind?->CAP ?: '')),
|
|
'citta' => trim((string) ($cli->CITTA ?: $ind?->CITTA ?: '')),
|
|
'provincia' => trim((string) ($ind?->provincia ?: '')),
|
|
'partita_iva' => $piva,
|
|
'codice_fiscale' => $cf,
|
|
'note' => trim((string) ($cli->NOTE ?: $ind?->NOTE ?: '')),
|
|
'source' => 'contabilita_mysql',
|
|
'imported_from_path' => 'arc_nehr.cli',
|
|
'imported_at' => now(),
|
|
'metadata' => [
|
|
'cli_codice' => $codice,
|
|
'ind_id' => $ind?->ID ?? null,
|
|
],
|
|
]);
|
|
|
|
$count++;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Sync contacts from NetGescon tickets assigned to supplier
|
|
*/
|
|
public function syncNetGesconTickets(int $amministratoreId, int $fornitoreId): int
|
|
{
|
|
$supplierIds = in_array($fornitoreId, [236, 359], true) ? [236, 359, 392] : [$fornitoreId];
|
|
|
|
$interventi = TicketIntervento::query()
|
|
->with(['ticket.stabile', 'ticket.unitaImmobiliare', 'ticket.soggettoRichiedente'])
|
|
->whereIn('fornitore_id', $supplierIds)
|
|
->latest('created_at')
|
|
->limit(200)
|
|
->get();
|
|
|
|
$count = 0;
|
|
foreach ($interventi as $intervento) {
|
|
$ticket = $intervento->ticket;
|
|
if (! $ticket) {
|
|
continue;
|
|
}
|
|
|
|
$soggetto = $ticket->soggettoRichiedente;
|
|
$name = '';
|
|
$phone = '';
|
|
$email = '';
|
|
$cf = '';
|
|
|
|
if ($soggetto) {
|
|
$name = trim((string) ($soggetto->ragione_sociale ?: trim(($soggetto->nome ?? '') . ' ' . ($soggetto->cognome ?? ''))));
|
|
$phone = trim((string) ($soggetto->telefono ?: ''));
|
|
$email = trim((string) ($soggetto->email ?: ''));
|
|
$cf = trim((string) ($soggetto->codice_fiscale ?: ''));
|
|
}
|
|
|
|
if ($name === '' && $ticket->descrizione) {
|
|
// Extract from description lines
|
|
if (preg_match('/(?:Chiamante selezionato|Contatto associato):\s*(.+)/i', $ticket->descrizione, $m)) {
|
|
$name = trim($m[1]);
|
|
}
|
|
if (preg_match('/(?:Telefono|Telefono richiamabile):\s*([0-9+\s().\/-]+)/i', $ticket->descrizione, $m)) {
|
|
$phone = trim($m[1]);
|
|
}
|
|
}
|
|
|
|
if ($name === '' && $phone === '') {
|
|
continue;
|
|
}
|
|
|
|
$name = $name ?: ('Richiedente Ticket #' . $ticket->id);
|
|
|
|
// Check if already present
|
|
$existing = FornitoreCliente::query()
|
|
->where('fornitore_id', $fornitoreId)
|
|
->where(function ($q) use ($name, $phone, $cf) {
|
|
if ($cf !== '') {
|
|
$q->where('codice_fiscale', $cf);
|
|
} elseif ($phone !== '') {
|
|
$norm = PhoneNumber::normalizeForMatch($phone);
|
|
$q->whereRaw("REGEXP_REPLACE(COALESCE(phone, ''), '[^0-9]', '') = ?", [$norm]);
|
|
} else {
|
|
$q->where('display_name', $name);
|
|
}
|
|
})
|
|
->first();
|
|
|
|
if ($existing) {
|
|
continue;
|
|
}
|
|
|
|
$rubricaId = $this->findRubricaMatch($amministratoreId, $cf, null, $phone, null, $email, $name);
|
|
|
|
FornitoreCliente::query()->create([
|
|
'amministratore_id' => $amministratoreId,
|
|
'fornitore_id' => $fornitoreId,
|
|
'rubrica_id' => $rubricaId,
|
|
'legacy_cliente_id' => null,
|
|
'display_name' => $name,
|
|
'phone' => $phone,
|
|
'phone_alt' => '',
|
|
'email' => $email,
|
|
'indirizzo' => (string) ($ticket->unitaImmobiliare?->scala_piano_interno ?? ''),
|
|
'cap' => '',
|
|
'citta' => (string) ($ticket->stabile?->citta ?? ''),
|
|
'provincia' => (string) ($ticket->stabile?->provincia ?? ''),
|
|
'partita_iva' => '',
|
|
'codice_fiscale' => $cf,
|
|
'note' => 'Ticket #' . $ticket->id . ': ' . ($ticket->titolo ?: '-'),
|
|
'source' => 'netgescon_ticket',
|
|
'imported_from_path' => 'tickets.id#' . $ticket->id,
|
|
'imported_at' => now(),
|
|
'metadata' => [
|
|
'ticket_id' => (int) $ticket->id,
|
|
'stabile_nome' => (string) ($ticket->stabile?->denominazione ?? ''),
|
|
'stabile_codice' => (string) ($ticket->stabile?->cod_stabile ?? ''),
|
|
],
|
|
]);
|
|
|
|
$count++;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
private function findRubricaMatch(
|
|
int $amministratoreId,
|
|
?string $cf = null,
|
|
?string $piva = null,
|
|
?string $phone = null,
|
|
?string $phoneAlt = null,
|
|
?string $email = null,
|
|
?string $name = null
|
|
): ?int {
|
|
$query = RubricaUniversale::query()->where('amministratore_id', $amministratoreId);
|
|
|
|
if ($cf !== null && $cf !== '') {
|
|
$m = (clone $query)->where('codice_fiscale', strtoupper(trim($cf)))->value('id');
|
|
if ($m) return (int) $m;
|
|
}
|
|
|
|
if ($piva !== null && $piva !== '') {
|
|
$m = (clone $query)->where('partita_iva', trim($piva))->value('id');
|
|
if ($m) return (int) $m;
|
|
}
|
|
|
|
if ($phone !== null && trim($phone) !== '') {
|
|
$digits = PhoneNumber::normalizeForMatch($phone);
|
|
if ($digits !== '') {
|
|
$m = (clone $query)->where(function ($bq) use ($digits) {
|
|
$bq->whereRaw("REGEXP_REPLACE(COALESCE(telefono_cellulare, ''), '[^0-9]', '') = ?", [$digits])
|
|
->orWhereRaw("REGEXP_REPLACE(COALESCE(telefono_ufficio, ''), '[^0-9]', '') = ?", [$digits])
|
|
->orWhereRaw("REGEXP_REPLACE(COALESCE(telefono_casa, ''), '[^0-9]', '') = ?", [$digits]);
|
|
})->value('id');
|
|
if ($m) return (int) $m;
|
|
}
|
|
}
|
|
|
|
if ($phoneAlt !== null && trim($phoneAlt) !== '') {
|
|
$digits = PhoneNumber::normalizeForMatch($phoneAlt);
|
|
if ($digits !== '') {
|
|
$m = (clone $query)->where(function ($bq) use ($digits) {
|
|
$bq->whereRaw("REGEXP_REPLACE(COALESCE(telefono_cellulare, ''), '[^0-9]', '') = ?", [$digits])
|
|
->orWhereRaw("REGEXP_REPLACE(COALESCE(telefono_ufficio, ''), '[^0-9]', '') = ?", [$digits]);
|
|
})->value('id');
|
|
if ($m) return (int) $m;
|
|
}
|
|
}
|
|
|
|
if ($email !== null && trim($email) !== '') {
|
|
$m = (clone $query)->whereRaw('LOWER(email) = ?', [mb_strtolower(trim($email))])->value('id');
|
|
if ($m) return (int) $m;
|
|
}
|
|
|
|
if ($name !== null && trim($name) !== '') {
|
|
$m = (clone $query)->where(function ($nq) use ($name) {
|
|
$nq->whereRaw("LOWER(TRIM(CONCAT(COALESCE(nome, ''), ' ', COALESCE(cognome, '')))) = ?", [mb_strtolower(trim($name))])
|
|
->orWhereRaw("LOWER(TRIM(COALESCE(ragione_sociale, ''))) = ?", [mb_strtolower(trim($name))]);
|
|
})->value('id');
|
|
if ($m) return (int) $m;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|