462 lines
19 KiB
PHP
462 lines
19 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_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;
|
|
}
|
|
}
|
|
}
|