netgescon-day0/app/Console/Commands/TecnoRepairImportLegacyArchiveCommand.php

343 lines
14 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Amministratore;
use App\Models\AssistenzaTecnorepairAllegato;
use App\Models\AssistenzaTecnorepairScheda;
use App\Models\Fornitore;
use App\Models\ProductSerial;
use App\Services\Catalog\FornitoreProductCatalogService;
use App\Support\TecnoRepairMdbReader;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class TecnoRepairImportLegacyArchiveCommand extends Command
{
protected $signature = 'tecnorepair:import-legacy
{amministratore : ID o codice amministratore}
{--mdb=/home/michele/netgescon/netgescon-day0/Miki-Bug-workspace/screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb : Percorso archivio MDB TecnoRepair}
{--fornitore-id= : ID fornitore da collegare alle schede}
{--fornitore-term=NETHOME : Ragione sociale o frammento per trovare il fornitore di riferimento}
{--limit=0 : Limita le schede importate}
{--dry-run : Esegue solo validazione e riepilogo senza scrivere}
{--force-primary : Marca il fornitore collegato come centro TecnoRepair principale}';
protected $description = 'Importa in staging l\'archivio TecnoRepair MDB e lo collega al fornitore operativo, di default NETHOME.';
public function handle(TecnoRepairMdbReader $reader, FornitoreProductCatalogService $catalogService): int
{
$admin = $this->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_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;
}
}
}