From 00d4417dc7ad40670a28a5568f3e9979a6b6e7fc Mon Sep 17 00:00:00 2001 From: michele Date: Fri, 11 Sep 2026 17:40:49 +0200 Subject: [PATCH] feat(fornitore): riproduzione web tecnorepair pratiche e schede, automazioni rma ricambi e sync contabilita nethome --- .../TecnoRepairImportLegacyArchiveCommand.php | 11 +- .../Pages/Fornitore/PraticheTecnorepair.php | 527 +++++++++++++++ .../Pages/Fornitore/ProdottiCatalogo.php | 28 + .../Pages/Fornitore/SerialiCatalogo.php | 33 + app/Filament/Pages/Gescon/FornitoreScheda.php | 132 ++++ .../Tecnorepair/TecnoRepairArchiveService.php | 507 ++++++++++++++ .../fornitore/lavorazioni-operative.blade.php | 1 + .../fornitore/pratiche-tecnorepair.blade.php | 638 ++++++++++++++++++ .../fornitore/prodotti-catalogo.blade.php | 10 + .../fornitore/seriali-catalogo.blade.php | 10 + .../fornitore/ticket-operativi.blade.php | 1 + .../pages/gescon/fornitore-scheda.blade.php | 123 ++++ skill-netgescon/control-tower/CURRENT-205.md | 86 +-- .../ui-wireframes/tecnorepair-schede.md | 241 +++++++ ...ornitoreTecnorepairAndIntegrationsTest.php | 239 +++++++ 15 files changed, 2541 insertions(+), 46 deletions(-) create mode 100644 app/Filament/Pages/Fornitore/PraticheTecnorepair.php create mode 100644 app/Services/Tecnorepair/TecnoRepairArchiveService.php create mode 100644 resources/views/filament/pages/fornitore/pratiche-tecnorepair.blade.php create mode 100644 skill-netgescon/ui-wireframes/tecnorepair-schede.md create mode 100644 tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php diff --git a/app/Console/Commands/TecnoRepairImportLegacyArchiveCommand.php b/app/Console/Commands/TecnoRepairImportLegacyArchiveCommand.php index 533cd9f..1809e49 100755 --- a/app/Console/Commands/TecnoRepairImportLegacyArchiveCommand.php +++ b/app/Console/Commands/TecnoRepairImportLegacyArchiveCommand.php @@ -35,12 +35,11 @@ public function handle(TecnoRepairMdbReader $reader, FornitoreProductCatalogServ } $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); + $service = app(\App\Services\Tecnorepair\TecnoRepairArchiveService::class); + try { + $mdbPath = $service->resolveMdbPath($rawMdbPath); + } catch (\Throwable $e) { + $this->error($e->getMessage()); return self::FAILURE; } diff --git a/app/Filament/Pages/Fornitore/PraticheTecnorepair.php b/app/Filament/Pages/Fornitore/PraticheTecnorepair.php new file mode 100644 index 0000000..6f0b802 --- /dev/null +++ b/app/Filament/Pages/Fornitore/PraticheTecnorepair.php @@ -0,0 +1,527 @@ + */ + public array $fornitoriOptions = []; + + // Filter properties + public string $searchMatricola = ''; + + public string $searchNumScheda = ''; + + public string $searchCliente = ''; + + public bool $searchClienteParziale = true; + + public string $searchNumOrdine = ''; + + public string $filterStato = '0'; + + public bool $escludiRiconsegnati = true; + + public bool $soloRiconsegnati = false; + + public ?int $nonLavorateDaGiorni = null; + + public string $dataIngressoDal = ''; + + public string $dataIngressoAl = ''; + + // Active Scheda modal state + public ?int $selectedSchedaId = null; + + public string $schedaTab = 'entrata'; + + /** @var array|null */ + public ?array $activeScheda = null; + + // Workflow inputs for active scheda + public string $workflowNote = ''; + + public string $workflowMotivoRottamazione = ''; + + public bool $workflowComeRicambi = false; + + public string $workflowRmaCode = ''; + + public ?int $workflowFornitoreResoId = null; + + /** @var array> */ + public array $rows = []; + + /** @var array */ + public array $totals = [ + 'totale' => 0, + 'aperte' => 0, + 'riparate' => 0, + 'rottamate' => 0, + 'rma_garanzia' => 0, + ]; + + public static function canAccess(): bool + { + $user = Auth::user(); + + return $user instanceof User + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore', 'fornitore']); + } + + public function mount(): void + { + [$fornitore] = $this->resolveOperatoreContext(allowAdminWithoutSupplier: true); + + if (! $fornitore instanceof Fornitore) { + $this->missingAdminContext = true; + return; + } + + $this->fornitoreId = (int) $fornitore->id; + $this->fornitoreLabel = $this->getFornitoreLabel($fornitore); + + $user = Auth::user(); + if ($this->isInternalOperator($user)) { + $this->fornitoriOptions = Fornitore::query() + ->where(function ($q) { + $q->whereHas('productSerials') + ->orWhere('partita_iva', '10055221005') + ->orWhere('partita_iva', '14001151001') + ->orWhereIn('id', [236, 392]); + }) + ->orderBy('ragione_sociale') + ->get() + ->mapWithKeys(fn(Fornitore $f): array => [ + (int) $f->id => trim((string) ($f->ragione_sociale ?: ('Fornitore #' . $f->id))) . ($f->partita_iva ? ' (' . $f->partita_iva . ')' : ''), + ]) + ->all(); + } + + // Optional date range + $this->dataIngressoDal = trim((string) request()->query('dal', '')); + $this->dataIngressoAl = trim((string) request()->query('al', '')); + + // Optional query params + if (request()->has('scheda')) { + $this->searchNumScheda = trim((string) request()->query('scheda')); + } + if (request()->has('q')) { + $this->searchMatricola = trim((string) request()->query('q')); + } + + $this->refreshData(); + + if (request()->has('open')) { + $openId = (int) request()->query('open'); + if ($openId > 0) { + $this->openScheda($openId); + } + } + } + + public function updatedFornitoreId(): void + { + $fornitore = Fornitore::query()->find((int) $this->fornitoreId); + if ($fornitore instanceof Fornitore) { + $this->fornitoreLabel = $this->getFornitoreLabel($fornitore); + $this->selectedSchedaId = null; + $this->activeScheda = null; + $this->refreshData(); + } + } + + public function updatedSearchMatricola(): void { $this->refreshData(); } + public function updatedSearchNumScheda(): void { $this->refreshData(); } + public function updatedSearchCliente(): void { $this->refreshData(); } + public function updatedFilterStato(): void { $this->refreshData(); } + public function updatedEscludiRiconsegnati(): void { $this->refreshData(); } + public function updatedSoloRiconsegnati(): void { $this->refreshData(); } + + public function resetFiltri(): void + { + $this->searchMatricola = ''; + $this->searchNumScheda = ''; + $this->searchCliente = ''; + $this->searchNumOrdine = ''; + $this->filterStato = '0'; + $this->escludiRiconsegnati = true; + $this->soloRiconsegnati = false; + $this->nonLavorateDaGiorni = null; + $this->dataIngressoDal = now()->subYears(2)->startOfYear()->format('Y-m-d'); + $this->dataIngressoAl = now()->addYear()->endOfYear()->format('Y-m-d'); + $this->refreshData(); + } + + public function refreshData(): void + { + if (! $this->fornitoreId) { + $this->rows = []; + return; + } + + $query = AssistenzaTecnorepairScheda::query() + ->where(function (Builder $builder) { + $builder->where('fornitore_id', (int) $this->fornitoreId); + // Also include merged suppliers (e.g. NCOMSRL if Nethome) + if ((int) $this->fornitoreId === 236) { + $builder->orWhere('fornitore_id', 392); + } + }); + + // Search filters + if (trim($this->searchMatricola) !== '') { + $val = trim($this->searchMatricola); + $query->where(function (Builder $b) use ($val) { + $b->where('serial_number', 'like', "%{$val}%") + ->orWhere('serial_number_2', 'like', "%{$val}%") + ->orWhere('product_code', 'like', "%{$val}%"); + }); + } + + if (trim($this->searchNumScheda) !== '') { + $val = trim($this->searchNumScheda); + $query->where(function (Builder $b) use ($val) { + $b->where('legacy_numero_scheda', 'like', "%{$val}%") + ->orWhere('legacy_id', $val); + }); + } + + if (trim($this->searchCliente) !== '') { + $val = trim($this->searchCliente); + $query->where('customer_name', 'like', "%{$val}%"); + } + + if (trim($this->searchNumOrdine) !== '') { + $val = trim($this->searchNumOrdine); + $query->where('order_number', 'like', "%{$val}%"); + } + + if ($this->filterStato !== '0' && trim($this->filterStato) !== '') { + $st = trim($this->filterStato); + $query->where(function (Builder $b) use ($st) { + $b->where('status_code', $st) + ->orWhere('status_label', 'like', "%{$st}%"); + }); + } + + if ($this->escludiRiconsegnati) { + $query->where(function (Builder $b) { + $b->whereNull('metadata->raw->isRiconsegnato') + ->orWhere('metadata->raw->isRiconsegnato', 0) + ->orWhere('metadata->raw->isRiconsegnato', '0') + ->orWhere('metadata->raw->isRiconsegnato', false); + }); + } elseif ($this->soloRiconsegnati) { + $query->where(function (Builder $b) { + $b->where('metadata->raw->isRiconsegnato', 1) + ->orWhere('metadata->raw->isRiconsegnato', '1') + ->orWhere('metadata->raw->isRiconsegnato', true); + }); + } + + if ($this->dataIngressoDal !== '') { + $query->where('date_received', '>=', Carbon::parse($this->dataIngressoDal)->startOfDay()); + } + if ($this->dataIngressoAl !== '') { + $query->where('date_received', '<=', Carbon::parse($this->dataIngressoAl)->endOfDay()); + } + + // Count totals across dataset + $baseTotalQuery = AssistenzaTecnorepairScheda::query() + ->where(function (Builder $builder) { + $builder->where('fornitore_id', (int) $this->fornitoreId); + if ((int) $this->fornitoreId === 236) { + $builder->orWhere('fornitore_id', 392); + } + }); + + $this->totals = [ + 'totale' => (clone $baseTotalQuery)->count(), + 'riparate' => (clone $baseTotalQuery)->where('status_code', '4')->count(), + 'rottamate' => (clone $baseTotalQuery)->whereIn('status_code', ['10', '23', '37'])->count(), + 'rma_garanzia' => (clone $baseTotalQuery)->where('status_code', '33')->count(), + 'aperte' => (clone $baseTotalQuery)->whereNotIn('status_code', ['4', '10', '23', '37'])->count(), + ]; + + $records = $query->orderByDesc('legacy_id')->limit(120)->get(); + + $this->rows = $records->map(function (AssistenzaTecnorepairScheda $s): array { + $raw = (array) data_get($s->metadata, 'raw', []); + $isRiconsegnato = (bool) ($raw['isRiconsegnato'] ?? false); + $statusCode = (string) ($s->status_code ?: ''); + $statusLabel = strtoupper((string) ($s->status_label ?: 'IN LAVORAZIONE')); + + // Row style logic matching TecnoRepair screenshots: + // Green: RIPARATO (4) / ACQUISTO RICONDIZIONATO (31) + // Red: DISPOSITIVO IRRIPARABILE (10) / PZ-RICAM (23) / SMALTITO (37) + // Blue: INGRESSO ACCETTAZIONE (21) + // Amber: ATTESA RICAMBIO (5) / LABORATORIO ESTERNO (27) + $rowColor = 'white'; + if (in_array($statusCode, ['4', '31'], true) || str_contains($statusLabel, 'RIPARATO') || str_contains($statusLabel, 'ACQUISTO RICONDIZIONATO')) { + $rowColor = 'green'; + } elseif (in_array($statusCode, ['10', '23', '37'], true) || str_contains($statusLabel, 'IRRIPARABILE') || str_contains($statusLabel, 'RICAMBI') || str_contains($statusLabel, 'SMALTITO')) { + $rowColor = 'red'; + } elseif ($statusCode === '21' || str_contains($statusLabel, 'INGRESSO') || str_contains($statusLabel, 'ACCETTAZIONE')) { + $rowColor = 'blue'; + } elseif (in_array($statusCode, ['5', '27', '30'], true) || str_contains($statusLabel, 'ATTESA') || str_contains($statusLabel, 'URGENTE')) { + $rowColor = 'amber'; + } + + return [ + 'id' => (int) $s->id, + 'legacy_id' => (int) ($s->legacy_id ?: $s->id), + 'numero_scheda' => (string) ($s->legacy_numero_scheda ?: $s->legacy_id ?: $s->id), + 'ingresso' => optional($s->date_received)->format('d/m/Y') ?: '-', + 'cliente' => (string) ($s->customer_name ?: '-'), + 'telefono' => (string) ($s->customer_phone ?: $s->customer_phone_alt ?: '-'), + 'difetto' => (string) ($s->defect_reported ?: '-'), + 'marca' => (string) ($raw['Cod_Marca'] ?? $this->extractBrand($s->product_model)), + 'modello' => (string) ($s->product_model ?: '-'), + 'seriale' => (string) ($s->serial_number ?: '-'), + 'cod_prodotto' => (string) ($s->product_code ?: '-'), + 'riconsegnato' => $isRiconsegnato, + 'data_ricons' => ! empty($raw['DataRiconsegna']) ? Carbon::parse($raw['DataRiconsegna'])->format('d/m/Y') : '-', + 'num_ordine' => (string) ($s->order_number ?: '0'), + 'stato_rip' => $statusLabel, + 'status_code' => $statusCode, + 'operatore' => (string) ($s->operator_name ?: $s->technician_name ?: 'MICHELE BARONE'), + 'row_color' => $rowColor, + ]; + })->all(); + } + + public function openScheda(int $schedaId): void + { + $scheda = AssistenzaTecnorepairScheda::query() + ->with(['allegati']) + ->find($schedaId); + + if (! $scheda instanceof AssistenzaTecnorepairScheda) { + return; + } + + $raw = (array) data_get($scheda->metadata, 'raw', []); + $cliente = (array) data_get($scheda->metadata, 'cliente', []); + $ricambi = (array) data_get($scheda->metadata, 'ricambi_scheda', []); + + $this->selectedSchedaId = (int) $scheda->id; + $this->schedaTab = 'entrata'; + $this->workflowNote = ''; + $this->workflowRmaCode = (string) ($scheda->rma_code ?: ('RMA-' . date('Ymd') . '-' . ($scheda->legacy_numero_scheda ?: $scheda->id))); + + $this->activeScheda = [ + 'id' => (int) $scheda->id, + 'numero_scheda' => (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id ?: $scheda->id), + 'data_ingresso' => optional($scheda->date_received)->format('d/m/Y H:i') ?: '-', + 'data_riparazione' => ! empty($raw['DataRiparazione']) ? Carbon::parse($raw['DataRiparazione'])->format('d/m/Y H:i') : '-', + 'data_riconsegna' => ! empty($raw['DataRiconsegna']) ? Carbon::parse($raw['DataRiconsegna'])->format('d/m/Y') : '-', + 'stato_rip' => (string) ($scheda->status_label ?: 'INGRESSO ACCETTAZIONE'), + 'status_code' => (string) ($scheda->status_code ?: '21'), + 'rma_code' => (string) ($scheda->rma_code ?: ''), + 'num_ordine' => (string) ($scheda->order_number ?: '0'), + // Cliente + 'cliente_nome' => (string) ($scheda->customer_name ?: '-'), + 'cliente_email' => (string) ($scheda->customer_email ?: '-'), + 'cliente_telefono' => (string) ($scheda->customer_phone ?: '-'), + 'cliente_fisso' => (string) ($scheda->customer_phone_alt ?: '-'), + 'cliente_indirizzo' => (string) ($cliente['Indirizzo'] ?? '-'), + 'cliente_citta' => (string) ($cliente['Citta'] ?? '-'), + // Apparecchio + 'tipo_apparecchio' => (string) ($raw['Cod_TipoApparecchio'] ?? 'AIO / PC / Dispositivo'), + 'marca' => (string) ($raw['Cod_Marca'] ?? $this->extractBrand($scheda->product_model)), + 'modello' => (string) ($scheda->product_model ?: '-'), + 'codice_prodotto' => (string) ($scheda->product_code ?: '-'), + 'seriale' => (string) ($scheda->serial_number ?: '-'), + 'seriale_2' => (string) ($scheda->serial_number_2 ?: '-'), + 'accessori' => (string) ($raw['AccessoriConsegnati'] ?? 'NESSUNO'), + 'difetto' => (string) ($scheda->defect_reported ?: '-'), + 'richieste_cliente' => (string) ($raw['RiparazioneRichiesta'] ?? '-'), + 'stato_generale' => (string) ($raw['StatoGenerale'] ?? '-'), + 'codice_pin' => (string) ($scheda->pin_code ?: '-'), + 'codice_sblocco' => (string) ($scheda->unlock_code ?: '-'), + 'tecnico' => (string) ($scheda->technician_name ?: $scheda->operator_name ?: 'MICHELE BARONE'), + // Riparazione + 'descrizione_lavorazione'=> (string) ($scheda->repair_description ?: '-'), + 'comunicazioni' => (string) ($scheda->communications ?: '-'), + // Economico + 'costo_subito' => (float) str_replace(',', '.', (string) ($raw['CostoSubito'] ?? 0)), + 'costo_addebitato' => (float) str_replace(',', '.', (string) ($raw['CostoAddebitato'] ?? 0)), + 'acconto' => (float) str_replace(',', '.', (string) ($raw['AccontoRiparazione'] ?? 0)), + 'preventivo' => (float) str_replace(',', '.', (string) ($raw['CostoPreventivo'] ?? 0)), + // Ricambi + 'ricambi' => $ricambi, + 'allegati' => $scheda->allegati->map(fn(AssistenzaTecnorepairAllegato $a) => [ + 'nome' => $a->file_name, + 'path' => $a->file_path, + ])->all(), + ]; + } + + public function closeScheda(): void + { + $this->selectedSchedaId = null; + $this->activeScheda = null; + } + + public function setSchedaTab(string $tab): void + { + $this->schedaTab = $tab; + } + + // Automated Workflow Actions + public function eseguiChiusuraRiparato(): void + { + if (! $this->selectedSchedaId) { + return; + } + + $scheda = AssistenzaTecnorepairScheda::query()->find($this->selectedSchedaId); + if (! $scheda instanceof AssistenzaTecnorepairScheda) { + return; + } + + $service = app(TecnoRepairArchiveService::class); + $service->chiudiComeRiparato($scheda, $this->workflowNote ?: 'Intervento concluso e collaudato con successo.'); + + Notification::make()->title('Pratica segnata come RIPARATO con successo')->success()->send(); + $this->openScheda($this->selectedSchedaId); + $this->refreshData(); + } + + public function eseguiChiusuraRottamazione(): void + { + if (! $this->selectedSchedaId) { + return; + } + + $scheda = AssistenzaTecnorepairScheda::query()->find($this->selectedSchedaId); + if (! $scheda instanceof AssistenzaTecnorepairScheda) { + return; + } + + $service = app(TecnoRepairArchiveService::class); + $service->chiudiComeRottamato( + $scheda, + $this->workflowMotivoRottamazione ?: 'Apparato non riparabile convenientemente', + $this->workflowComeRicambi + ); + + $label = $this->workflowComeRicambi ? 'destinato a recupero ricambi' : 'contrassegnato per rottamazione'; + Notification::make()->title("Apparato {$label}")->warning()->send(); + $this->openScheda($this->selectedSchedaId); + $this->refreshData(); + } + + public function eseguiResoFornitoreRma(): void + { + if (! $this->selectedSchedaId) { + return; + } + + $scheda = AssistenzaTecnorepairScheda::query()->find($this->selectedSchedaId); + if (! $scheda instanceof AssistenzaTecnorepairScheda) { + return; + } + + $service = app(TecnoRepairArchiveService::class); + $service->rendiAFornitoreRma( + $scheda, + $this->workflowRmaCode, + $this->workflowFornitoreResoId ?: 392, + $this->workflowNote ?: 'Reso in garanzia al produttore / fornitore' + ); + + Notification::make()->title("Pratica inviata in RMA/Garanzia (RMA: {$scheda->rma_code})")->info()->send(); + $this->openScheda($this->selectedSchedaId); + $this->refreshData(); + } + + public function sincronizzaMdb(): void + { + try { + $service = app(TecnoRepairArchiveService::class); + $stats = $service->importArchive(13, (int) $this->fornitoreId); + $msg = sprintf( + 'Archivio MDB TecnoRepair sincronizzato: %d schede (%d create, %d agg.), %d ricambi, %d seriali.', + $stats['schede_lette'], + $stats['schede_create'], + $stats['schede_aggiornate'], + $stats['ricambi_catalogati'], + $stats['seriali_allineati'] + ); + Notification::make()->title($msg)->success()->send(); + $this->refreshData(); + } catch (\Throwable $e) { + Notification::make()->title('Errore sincronizzazione TecnoRepair: ' . $e->getMessage())->danger()->send(); + } + } + + public function sincronizzaContabilita(): void + { + try { + $service = app(ContabilitaMysqlSerialImportService::class); + $stats = $service->importForSupplier('NCOMSRL'); + $msg = sprintf( + 'Contabilità MySQL sincronizzata: %d fatture lette, %d seriali aggiornati.', + $stats['invoices_count'], + $stats['serials_created'] + $stats['serials_updated'] + ); + Notification::make()->title($msg)->success()->send(); + $this->refreshData(); + } catch (\Throwable $e) { + Notification::make()->title('Errore sincronizzazione Contabilità: ' . $e->getMessage())->danger()->send(); + } + } + + private function extractBrand(?string $model): string + { + $model = trim((string) $model); + if ($model === '') { + return '-'; + } + + $firstWord = strtoupper(explode(' ', $model)[0] ?? ''); + if (in_array($firstWord, ['HP', 'LENOVO', 'DELL', 'ASUS', 'ACER', 'APPLE', 'TOSHIBA', 'MSI', 'SAMSUNG', 'HANSUNG'], true)) { + return $firstWord; + } + + return $firstWord ?: '-'; + } +} diff --git a/app/Filament/Pages/Fornitore/ProdottiCatalogo.php b/app/Filament/Pages/Fornitore/ProdottiCatalogo.php index bdde32d..ff85638 100755 --- a/app/Filament/Pages/Fornitore/ProdottiCatalogo.php +++ b/app/Filament/Pages/Fornitore/ProdottiCatalogo.php @@ -142,6 +142,34 @@ public function mount(): void if (($this->selectedProductId ?? 0) > 0) { $this->openProductDetail((int) $this->selectedProductId); } + + if (request()->has('sync')) { + $this->syncContabilitaProdotti(); + } + } + + public function syncContabilitaProdotti(): void + { + try { + $service = app(\App\Services\Catalog\ContabilitaMysqlSerialImportService::class); + $res = $service->importForSupplier('NCOMSRL'); + $msg = sprintf( + 'Contabilità sincronizzata: %d fatture, %d prodotti aggiornati, %d seriali (%d nuovi).', + $res['invoices_count'], + $res['products_created'], + $res['serials_created'] + $res['serials_updated'], + $res['serials_created'] + ); + \Filament\Notifications\Notification::make()->title($msg)->success()->send(); + $this->refreshRows(); + } catch (\Throwable $e) { + \Filament\Notifications\Notification::make()->title('Errore sincronizzazione: ' . $e->getMessage())->danger()->send(); + } + } + + public function getPraticheUrl(): string + { + return PraticheTecnorepair::getUrl(['fornitore' => (int) ($this->fornitoreId ?? 0)], panel: 'admin-filament'); } public function updatedFornitoreId(): void diff --git a/app/Filament/Pages/Fornitore/SerialiCatalogo.php b/app/Filament/Pages/Fornitore/SerialiCatalogo.php index 75e97e2..0f686af 100755 --- a/app/Filament/Pages/Fornitore/SerialiCatalogo.php +++ b/app/Filament/Pages/Fornitore/SerialiCatalogo.php @@ -90,6 +90,39 @@ public function mount(): void if ($this->selectedSerialId) { $this->openSerialDetail($this->selectedSerialId); } + + if (request()->has('sync') || (in_array((int) $this->fornitoreId, [236, 392], true) && count($this->rows) === 0)) { + try { + app(\App\Services\Catalog\ContabilitaMysqlSerialImportService::class)->importForSupplier('NCOMSRL'); + $this->refreshRows(); + } catch (\Throwable) { + // Silently fallback without blocking + } + } + } + + public function syncContabilitaSerials(): void + { + try { + $service = app(\App\Services\Catalog\ContabilitaMysqlSerialImportService::class); + $res = $service->importForSupplier('NCOMSRL'); + $msg = sprintf( + 'Contabilità aggiornata: %d fatture analizzate, %d seriali sincronizzati (%d nuovi), %d prodotti.', + $res['invoices_count'], + $res['serials_created'] + $res['serials_updated'], + $res['serials_created'], + $res['products_created'] + ); + \Filament\Notifications\Notification::make()->title($msg)->success()->send(); + $this->refreshRows(); + } catch (\Throwable $e) { + \Filament\Notifications\Notification::make()->title('Errore sincronizzazione: ' . $e->getMessage())->danger()->send(); + } + } + + public function getPraticheUrl(): string + { + return PraticheTecnorepair::getUrl(['fornitore' => (int) ($this->fornitoreId ?? 0)], panel: 'admin-filament'); } /** @var array */ diff --git a/app/Filament/Pages/Gescon/FornitoreScheda.php b/app/Filament/Pages/Gescon/FornitoreScheda.php index abb9699..731c161 100755 --- a/app/Filament/Pages/Gescon/FornitoreScheda.php +++ b/app/Filament/Pages/Gescon/FornitoreScheda.php @@ -111,6 +111,17 @@ class FornitoreScheda extends Page public ?string $lastGeneratedPassword = null; + // Nethome-exclusive integration properties + public string $mysqlHost = '192.168.0.36'; + public string $mysqlPort = '3307'; + public string $mysqlDatabase = 'arc_nehr'; + public string $mysqlUsername = 'NETGESCON'; + public string $mysqlPassword = ''; + public string $mysqlSupplierCode = 'NCOMSRL'; + public string $tecnorepairMdbPath = '\\\\192.168.0.36\\CServerGO\\LunaSoftware_TecnoRepair\\Archivi\\TecnoRepairDB.mdb'; + public string $tecnorepairLocalPath = '/home/michele/netgescon-day0-backup/Miki-Bug-workspace/screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb'; + public bool $tecnorepairAutoSync = true; + public function cleanDisplayValue(?string $value, string $fallback = '-'): string { $v = trim((string) $value); @@ -201,12 +212,133 @@ public function mount(int | string $record): void $this->refreshDipendentiRows(); $this->refreshCatalogRows(); + if ($this->isNethomeFornitore()) { + $opConfig = (array) ($this->fornitore->operational_config ?? []); + $dbCfg = (array) data_get($opConfig, 'accounting_db', []); + $this->mysqlHost = (string) ($dbCfg['host'] ?? '192.168.0.36'); + $this->mysqlPort = (string) ($dbCfg['port'] ?? '3307'); + $this->mysqlDatabase = (string) ($dbCfg['database'] ?? 'arc_nehr'); + $this->mysqlUsername = (string) ($dbCfg['username'] ?? 'NETGESCON'); + $this->mysqlSupplierCode = (string) ($dbCfg['supplier_code'] ?? 'NCOMSRL'); + + $trCfg = (array) data_get($opConfig, 'tecnorepair', []); + $this->tecnorepairMdbPath = (string) ($trCfg['mdb_path'] ?? \App\Services\Tecnorepair\TecnoRepairArchiveService::DEFAULT_WINDOWS_UNC_PATH); + $this->tecnorepairLocalPath = (string) ($trCfg['local_fallback_path'] ?? \App\Services\Tecnorepair\TecnoRepairArchiveService::DEFAULT_LOCAL_PATH); + $this->tecnorepairAutoSync = (bool) ($trCfg['auto_sync'] ?? true); + } + $requestedTab = (string) request()->query('tab', 'profilo'); if ($this->isValidSectionTab($requestedTab)) { $this->sectionTab = $requestedTab; } } + public function isNethomeFornitore(): bool + { + return (int) ($this->fornitore->id ?? 0) === 236 + || ($this->fornitore->partita_iva ?? '') === '10055221005' + || Str::contains(strtoupper((string) ($this->fornitore->ragione_sociale ?? '')), 'NETHOME'); + } + + public function saveNethomeIntegrations(): void + { + abort_unless($this->isNethomeFornitore(), 403, 'Azione consentita solo per il fornitore Nethome.'); + + $config = (array) ($this->fornitore->operational_config ?? []); + $config['accounting_db'] = [ + 'host' => trim($this->mysqlHost), + 'port' => trim($this->mysqlPort), + 'database' => trim($this->mysqlDatabase), + 'username' => trim($this->mysqlUsername), + 'password' => $this->mysqlPassword ?: ($config['accounting_db']['password'] ?? 'P4ssw0rd.96'), + 'supplier_code' => trim($this->mysqlSupplierCode), + ]; + + $config['tecnorepair'] = [ + 'mdb_path' => trim($this->tecnorepairMdbPath), + 'local_fallback_path' => trim($this->tecnorepairLocalPath), + 'auto_sync' => (bool) $this->tecnorepairAutoSync, + 'last_sync' => $config['tecnorepair']['last_sync'] ?? null, + ]; + + $this->fornitore->operational_config = $config; + $this->fornitore->save(); + + Notification::make()->title('Configurazioni Nethome salvate con successo')->success()->send(); + } + + public function testMysqlConnection(): void + { + abort_unless($this->isNethomeFornitore(), 403, 'Azione consentita solo per il fornitore Nethome.'); + + try { + $fp = @fsockopen(trim($this->mysqlHost), (int) $this->mysqlPort, $errno, $errstr, 3); + if (! $fp) { + throw new \RuntimeException("Porta non raggiungibile: {$errstr} ({$errno})"); + } + fclose($fp); + + $pdo = new \PDO( + "mysql:host={$this->mysqlHost};port={$this->mysqlPort};dbname={$this->mysqlDatabase}", + $this->mysqlUsername, + $this->mysqlPassword ?: 'P4ssw0rd.96', + [\PDO::ATTR_TIMEOUT => 5] + ); + + $version = $pdo->query('SELECT VERSION()')->fetchColumn(); + Notification::make()->title("Connessione MySQL riuscita! Server: {$version}")->success()->send(); + } catch (\Throwable $e) { + Notification::make()->title('Errore connessione MySQL: ' . $e->getMessage())->danger()->send(); + } + } + + public function sincronizzaContabilitaNethome(): void + { + abort_unless($this->isNethomeFornitore(), 403, 'Azione consentita solo per il fornitore Nethome.'); + + try { + $service = app(\App\Services\Catalog\ContabilitaMysqlSerialImportService::class); + $res = $service->importForSupplier($this->mysqlSupplierCode ?: 'NCOMSRL'); + $msg = sprintf( + 'Contabilità sincronizzata: %d fatture, %d seriali (%d nuovi), %d prodotti.', + $res['invoices_count'], + $res['serials_created'] + $res['serials_updated'], + $res['serials_created'], + $res['products_created'] + ); + Notification::make()->title($msg)->success()->send(); + $this->hydrateBoxData(Auth::user()); + $this->refreshCatalogRows(); + } catch (\Throwable $e) { + Notification::make()->title('Errore sincronizzazione: ' . $e->getMessage())->danger()->send(); + } + } + + public function sincronizzaTecnorepairNethome(): void + { + abort_unless($this->isNethomeFornitore(), 403, 'Azione consentita solo per il fornitore Nethome.'); + + try { + $service = app(\App\Services\Tecnorepair\TecnoRepairArchiveService::class); + $stats = $service->importArchive( + (int) ($this->fornitore->amministratore_id ?: 13), + (int) $this->fornitore->id, + $this->tecnorepairLocalPath ?: null + ); + $msg = sprintf( + 'TecnoRepair sincronizzato: %d schede (%d create, %d agg.), %d ricambi catalogati.', + $stats['schede_lette'], + $stats['schede_create'], + $stats['schede_aggiornate'], + $stats['ricambi_catalogati'] + ); + Notification::make()->title($msg)->success()->send(); + $this->hydrateBoxData(Auth::user()); + } catch (\Throwable $e) { + Notification::make()->title('Errore import TecnoRepair: ' . $e->getMessage())->danger()->send(); + } + } + public function setSectionTab(string $tab): void { if ($this->isValidSectionTab($tab)) { diff --git a/app/Services/Tecnorepair/TecnoRepairArchiveService.php b/app/Services/Tecnorepair/TecnoRepairArchiveService.php new file mode 100644 index 0000000..a283b09 --- /dev/null +++ b/app/Services/Tecnorepair/TecnoRepairArchiveService.php @@ -0,0 +1,507 @@ +operational_config ?? []); + $path = trim((string) data_get($config, 'tecnorepair.mdb_path', '')); + if ($path === '') { + $path = trim((string) data_get($config, 'tecnorepair.local_fallback_path', '')); + } + } + + if ($path === '') { + $path = self::DEFAULT_WINDOWS_UNC_PATH; + } + + // If path is a local existing file, return realpath + if (file_exists($path) && is_file($path)) { + return realpath($path) ?: $path; + } + + // Candidates for fallback + $fallbackCandidates = [ + self::DEFAULT_LOCAL_PATH, + '/home/michele/MIki/netgescon-day0-backup/Miki-Bug-workspace/screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb', + '/mnt/gescon-archives/TecnoRepairDB.mdb', + storage_path('app/private/tecnorepair/TecnoRepairDB.mdb'), + ]; + + if ($fornitore instanceof Fornitore) { + $config = (array) ($fornitore->operational_config ?? []); + $customFallback = trim((string) data_get($config, 'tecnorepair.local_fallback_path', '')); + if ($customFallback !== '') { + array_unshift($fallbackCandidates, $customFallback); + } + } + + foreach ($fallbackCandidates as $cand) { + if (file_exists($cand) && is_file($cand) && is_readable($cand)) { + return realpath($cand) ?: $cand; + } + } + + throw new RuntimeException("Archivio TecnoRepair non trovato sul percorso specificato ($path) e nessuna replica locale trovata."); + } + + /** + * Import entire TecnoRepair archive (Schede, Clienti, Allegati, Ricambi/Prodotti). + * + * @return array + */ + public function importArchive( + int $amministratoreId, + int $fornitoreId, + ?string $mdbPath = null, + int $limit = 0, + bool $dryRun = false + ): array { + $fornitore = Fornitore::query()->findOrFail($fornitoreId); + $resolvedPath = $this->resolveMdbPath($mdbPath, $fornitore); + + $tables = $this->reader->listTables($resolvedPath); + + foreach (['TClienti', 'TApparecchi', 'TAllegati'] as $requiredTable) { + if (! in_array($requiredTable, $tables, true)) { + throw new RuntimeException("Tabella richiesta mancante nel file MDB: {$requiredTable}"); + } + } + + $clientiRows = $this->reader->exportTable($resolvedPath, 'TClienti'); + $schedeRows = $this->reader->exportTable($resolvedPath, 'TApparecchi'); + $allegatiRows = $this->reader->exportTable($resolvedPath, 'TAllegati'); + + $ricambiRows = in_array('TRicambiAnag', $tables, true) + ? $this->reader->exportTable($resolvedPath, 'TRicambiAnag') + : []; + + $ricambiSchedaRows = in_array('TRicambiScheda', $tables, true) + ? $this->reader->exportTable($resolvedPath, 'TRicambiScheda') + : []; + + $schedeRows = array_reverse($schedeRows); + + if ($limit > 0) { + $schedeRows = array_slice($schedeRows, 0, $limit); + } + + $clienti = []; + foreach ($clientiRows as $clienteRow) { + $cId = $this->toInt($clienteRow['ID'] ?? null); + if ($cId !== null) { + $clienti[$cId] = $clienteRow; + } + } + + $allegatiByScheda = []; + foreach ($allegatiRows as $allegatoRow) { + $sId = $this->toInt($allegatoRow['ID_Scheda'] ?? null); + if ($sId !== null) { + $allegatiByScheda[$sId][] = $allegatoRow; + } + } + + $ricambiByScheda = []; + foreach ($ricambiSchedaRows as $rs) { + $sId = $this->toInt($rs['ID_Scheda'] ?? null); + if ($sId !== null) { + $ricambiByScheda[$sId][] = $rs; + } + } + + $stats = [ + 'mdb_path' => $resolvedPath, + 'schede_lette' => count($schedeRows), + 'schede_create' => 0, + 'schede_aggiornate' => 0, + 'schede_saltate' => 0, + 'allegati_importati' => 0, + 'seriali_allineati' => 0, + 'ricambi_catalogati' => 0, + 'dry_run' => $dryRun, + ]; + + // 1. Process Ricambi / Spare parts as Products + if (! empty($ricambiRows)) { + foreach ($ricambiRows as $ricambio) { + $codice = trim((string) ($ricambio['Codice'] ?? '')); + $descrizione = trim((string) ($ricambio['Descrizione'] ?? '')); + if ($codice === '' && $descrizione === '') { + continue; + } + + if ($dryRun) { + $stats['ricambi_catalogati']++; + continue; + } + + $barcode = trim((string) ($ricambio['CodBarre'] ?? '')); + $prezzoAcq = (float) str_replace(',', '.', (string) ($ricambio['PrezzoAcq'] ?? 0)); + $prezzoUltAcq = (float) str_replace(',', '.', (string) ($ricambio['PrezzoUltAcq'] ?? 0)); + $costo = $prezzoAcq > 0 ? $prezzoAcq : ($prezzoUltAcq > 0 ? $prezzoUltAcq : null); + $przList1 = (float) str_replace(',', '.', (string) ($ricambio['PrzList1'] ?? 0)); + + $name = $descrizione !== '' ? $descrizione : ('Ricambio ' . $codice); + $res = $this->catalogService->resolveOrCreateProduct($fornitore, [ + 'name' => $name, + 'internal_code' => $codice ?: ('RIC-' . ($ricambio['ID'] ?? uniqid())), + 'type' => 'spare_part', + 'brand' => 'TecnoRepair', + 'description' => $descrizione, + 'track_serials' => true, + 'meta' => [ + 'source' => 'tecnorepair_ricambi', + 'raw_id' => $ricambio['ID'] ?? null, + 'pos_magazzino' => $ricambio['PosizioneMagazzino'] ?? null, + 'prz_list1' => $przList1, + ], + 'identifiers' => array_filter([ + $codice !== '' ? [ + 'fornitore_id' => $fornitore->id, + 'code_type' => 'vendor_sku', + 'code_role' => 'primary', + 'code_value' => $codice, + 'source' => 'tecnorepair_anag', + ] : null, + $barcode !== '' ? [ + 'fornitore_id' => $fornitore->id, + 'code_type' => 'barcode', + 'code_role' => 'barcode', + 'code_value' => $barcode, + 'source' => 'tecnorepair_anag', + ] : null, + ]), + ]); + + $product = $res['product']; + + if ($costo !== null && $costo > 0) { + $this->productOfferService->syncInternalSupplierOffer($product, $fornitore, [ + 'price_amount' => $costo, + 'currency' => 'EUR', + 'availability' => 'in_stock', + 'meta' => [ + 'prz_list1' => $przList1, + 'source_type' => 'tecnorepair_ricambi', + ], + ]); + } + + $stats['ricambi_catalogati']++; + } + } + + // 2. Process Schede / Repair cards + $runner = function () use ( + $amministratoreId, + $fornitore, + $resolvedPath, + $schedeRows, + $clienti, + $allegatiByScheda, + $ricambiByScheda, + $dryRun, + &$stats + ): void { + 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' => $amministratoreId, + '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' => $resolvedPath, + 'imported_at' => now(), + 'metadata' => [ + 'cliente' => $cliente, + 'raw' => $row, + 'ricambi_scheda' => $ricambiByScheda[$legacyId] ?? [], + ], + ]; + + $existing = AssistenzaTecnorepairScheda::query() + ->where('amministratore_id', $amministratoreId) + ->where('legacy_id', $legacyId) + ->first(); + + if ($dryRun) { + if ($existing) { + $stats['schede_aggiornate']++; + } else { + $stats['schede_create']++; + } + continue; + } + + if ($existing instanceof AssistenzaTecnorepairScheda) { + $existing->fill($payload); + $existing->save(); + $scheda = $existing; + $stats['schede_aggiornate']++; + } else { + $scheda = AssistenzaTecnorepairScheda::query()->create($payload); + $stats['schede_create']++; + } + + // Align ProductSerial + $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']++; + + $this->catalogService->syncTecnorepairProduct($fornitore, $scheda, $serial); + + // Allegati + 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' => $resolvedPath, + 'metadata' => ['raw' => $allegatoRow], + ]); + $stats['allegati_importati']++; + } + } + }; + + if ($dryRun) { + $runner(); + } else { + DB::transaction($runner); + + // Update Fornitore operational_config with last sync info + $config = (array) ($fornitore->operational_config ?? []); + $config['tecnorepair']['last_sync'] = [ + 'timestamp' => now()->toIso8601String(), + 'resolved_path' => $resolvedPath, + 'schede_lette' => $stats['schede_lette'], + 'schede_create' => $stats['schede_create'], + 'schede_aggiornate' => $stats['schede_aggiornate'], + 'ricambi_catalogati' => $stats['ricambi_catalogati'], + 'seriali_allineati' => $stats['seriali_allineati'], + ]; + $fornitore->operational_config = $config; + $fornitore->save(); + } + + return $stats; + } + + /** + * Automated Workflow 1: Mark repair as completed (RIPARATO). + */ + public function chiudiComeRiparato( + AssistenzaTecnorepairScheda $scheda, + ?string $note = null, + ?string $tecnico = null + ): bool { + $meta = (array) ($scheda->metadata ?? []); + $meta['workflow_status'] = 'repaired'; + $meta['repaired_at'] = now()->toIso8601String(); + + $scheda->status_code = '4'; + $scheda->status_label = 'RIPARATO'; + $scheda->status_bucket = 'completed'; + + if ($tecnico) { + $scheda->technician_name = $tecnico; + } + + if ($note) { + $prevNotes = trim((string) $scheda->repair_description); + $scheda->repair_description = $prevNotes !== '' + ? ($prevNotes . "\n[" . now()->format('d/m/Y H:i') . "] " . $note) + : $note; + } + + $scheda->metadata = $meta; + + return $scheda->save(); + } + + /** + * Automated Workflow 2: Mark device as scrapped or used for spare parts (DISPOSITIVO IRRIPARABILE / PZ-RICAM). + */ + public function chiudiComeRottamato( + AssistenzaTecnorepairScheda $scheda, + ?string $motivo = null, + bool $comeRicambi = false + ): bool { + $meta = (array) ($scheda->metadata ?? []); + $meta['workflow_status'] = $comeRicambi ? 'spare_parts' : 'scrapped'; + $meta['scrapped_at'] = now()->toIso8601String(); + $meta['scrap_reason'] = $motivo; + + if ($comeRicambi) { + $scheda->status_code = '23'; + $scheda->status_label = 'PZ-RICAM - NOTEBOOK PC DA UTILIZZARE COME RICAMBI'; + $scheda->status_bucket = 'scrapped'; + } else { + $scheda->status_code = '10'; + $scheda->status_label = 'DISPOSITIVO IRRIPARABILE'; + $scheda->status_bucket = 'cancelled'; + } + + $desc = trim((string) $scheda->repair_description); + $append = "Apparato contrassegnato per " . ($comeRicambi ? 'recupero ricambi' : 'rottamazione/smaltimento') . ". Motivo: " . ($motivo ?: 'Non riparabile economicamente'); + $scheda->repair_description = $desc !== '' ? ($desc . "\n[" . now()->format('d/m/Y H:i') . "] " . $append) : $append; + + $scheda->metadata = $meta; + + return $scheda->save(); + } + + /** + * Automated Workflow 3: Return to vendor / Warranty RMA (MANDATO IN GARANZIA AL PRODUTTORE). + */ + public function rendiAFornitoreRma( + AssistenzaTecnorepairScheda $scheda, + ?string $rmaCode = null, + ?int $fornitoreResoId = null, + ?string $note = null + ): bool { + $meta = (array) ($scheda->metadata ?? []); + $generatedRma = $rmaCode ?: ('RMA-' . date('Ymd') . '-' . ($scheda->legacy_numero_scheda ?: $scheda->id)); + + $meta['workflow_status'] = 'vendor_rma'; + $meta['rma_initiated_at'] = now()->toIso8601String(); + $meta['rma_fornitore_id'] = $fornitoreResoId; + + $scheda->status_code = '33'; + $scheda->status_label = 'MANDATO IN GARANZIA AL PRODUTTORE'; + $scheda->status_bucket = 'in_progress'; + $scheda->rma_code = $generatedRma; + + $comm = trim((string) $scheda->communications); + $append = "Inviato in RMA / Garanzia fornitore. Codice RMA: {$generatedRma}." . ($note ? " Note: {$note}" : ''); + $scheda->communications = $comm !== '' ? ($comm . "\n[" . now()->format('d/m/Y H:i') . "] " . $append) : $append; + + $scheda->metadata = $meta; + + return $scheda->save(); + } + + 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; + } + } +} diff --git a/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php b/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php index c4974d3..f58b45f 100755 --- a/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php +++ b/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php @@ -23,6 +23,7 @@ @endif + 💻 Pratiche TecnoRepair Ticket operativi Collaboratori Rubrica clienti diff --git a/resources/views/filament/pages/fornitore/pratiche-tecnorepair.blade.php b/resources/views/filament/pages/fornitore/pratiche-tecnorepair.blade.php new file mode 100644 index 0000000..6d2de13 --- /dev/null +++ b/resources/views/filament/pages/fornitore/pratiche-tecnorepair.blade.php @@ -0,0 +1,638 @@ + +
+ {{-- TOP TOOLBAR: FORNITORE CONTEXT & QUICK SYNC BUTTONS --}} +
+
+
+ TR +
+
+

TecnoRepair Pro 4.5 — Gestione Pratiche Riparazione

+

+ Fornitore operativo: {{ $this->fornitoreLabel ?? 'Nethome' }} +

+
+
+ +
+ @if(count($this->fornitoriOptions) > 1) +
+ + +
+ @endif + + + + + + + 🔍 Ricerca Seriali + + + + 📦 Catalogo & Ricambi + +
+
+ + {{-- SUMMARY KPI CARDS --}} +
+
+
Totale Schede
+
{{ number_format($this->totals['totale'] ?? 0, 0, ',', '.') }}
+
+
+
In Lavorazione
+
{{ number_format($this->totals['aperte'] ?? 0, 0, ',', '.') }}
+
+
+
Riparate
+
{{ number_format($this->totals['riparate'] ?? 0, 0, ',', '.') }}
+
+
+
Rottamate / Ricambi
+
{{ number_format($this->totals['rottamate'] ?? 0, 0, ',', '.') }}
+
+
+
Reso Fornitore / RMA
+
{{ number_format($this->totals['rma_garanzia'] ?? 0, 0, ',', '.') }}
+
+
+ + {{-- FILTRI PER RICERCA SCHEDE (FEDELE AL DESKTOP TECNOREPAIR) --}} +
+
+
+ 🎛️ FILTRI per Ricerca Schede + Standard TecnoRepair +
+ +
+ +
+
+ +
+ + - + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + + +
+ +
+ +
+
+
+ + {{-- GRIGLIA ELENCO SCHEDE (COLORAZIONE FEDELE A TECNOREPAIR) --}} +
+
+
+ Schede Elencate: {{ count($this->rows) }} + (visualizzate prime 120 per reattività) +
+
+ Riparato + Irreparabile / Rottamato + Accettazione + Attesa / Laboratorio +
+
+ +
+ + + + + + + + + + + + + + + + + + + + @forelse($this->rows as $r) + @php + $rowBg = match($r['row_color']) { + 'green' => 'bg-emerald-50/80 hover:bg-emerald-100/90 text-emerald-950 font-medium', + 'red' => 'bg-rose-50/80 hover:bg-rose-100/90 text-rose-950 font-medium', + 'blue' => 'bg-sky-50/80 hover:bg-sky-100/90 text-sky-950', + 'amber' => 'bg-amber-50/80 hover:bg-amber-100/90 text-amber-950', + default => 'bg-white hover:bg-slate-50 text-slate-800', + }; + $borderLeft = match($r['row_color']) { + 'green' => 'border-l-4 border-l-emerald-600', + 'red' => 'border-l-4 border-l-rose-600', + 'blue' => 'border-l-4 border-l-sky-600', + 'amber' => 'border-l-4 border-l-amber-600', + default => 'border-l-4 border-l-transparent', + }; + @endphp + + + + + + + + + + + + + + + + @empty + + + + @endforelse + +
NumIngressoClienteTelefonoDifetto SegnalatoMarcaModelloSeriale / IMEICod. ProdRicons.Stato RiparazioneOperatoreAzione
{{ $r['numero_scheda'] }}{{ $r['ingresso'] }}{{ $r['cliente'] }}{{ $r['telefono'] }}{{ $r['difetto'] }}{{ $r['marca'] }}{{ $r['modello'] }}{{ $r['seriale'] }}{{ $r['cod_prodotto'] }} + @if($r['riconsegnato']) + SI + @else + - + @endif + + {{ $r['stato_rip'] }} + {{ $r['operatore'] }} + +
+ Nessuna scheda trovata con i filtri correnti. Prova a cliccare "Reset Tutti i Filtri" o "Sincronizza MDB". +
+
+
+ + {{-- SCHEDA APPARECCHIO MODAL / DETTAGLIO INTERATTIVO (7 TABS TECNOREPAIR) --}} + @if($this->selectedSchedaId && $this->activeScheda) +
+
+ {{-- MODAL HEADER --}} +
+
+ + SCHEDA #{{ $this->activeScheda['numero_scheda'] }} + +
+

+ {{ $this->activeScheda['marca'] }} {{ $this->activeScheda['modello'] }} + ({{ $this->activeScheda['seriale'] }}) +

+

+ Cliente: {{ $this->activeScheda['cliente_nome'] }} — Ingresso: {{ $this->activeScheda['data_ingresso'] }} +

+
+
+ +
+ + {{ $this->activeScheda['stato_rip'] }} + + +
+
+ + {{-- TAB BAR --}} +
+ @php + $tabsMap = [ + 'entrata' => '1. Apparecchio in Entrata', + 'riparazione' => '2. Riparazione & Flussi ⚡', + 'ricambi' => '3. Ricambi Utilizzati', + 'preventivo' => '4. Preventivo - Costi - DDT', + 'annotazioni' => '5. Annotazioni & Cortesia', + 'comunicazioni' => '6. Comunicazioni', + 'cq' => '7. Controllo Qualità (C.Q.)', + ]; + @endphp + @foreach($tabsMap as $tKey => $tLabel) + + @endforeach +
+ + {{-- TAB CONTENTS --}} +
+ {{-- TAB 1: APPARECCHIO IN ENTRATA --}} + @if($this->schedaTab === 'entrata') +
+
+
👤 Dati Cliente
+
{{ $this->activeScheda['cliente_nome'] }}
+
Tel: {{ $this->activeScheda['cliente_telefono'] }}
+
Email: {{ $this->activeScheda['cliente_email'] }}
+
Indirizzo: {{ $this->activeScheda['cliente_indirizzo'] }}, {{ $this->activeScheda['cliente_citta'] }}
+
+ +
+
💻 Dati Apparecchio
+
Tipo: {{ $this->activeScheda['tipo_apparecchio'] }}
+
Marca: {{ $this->activeScheda['marca'] }}
+
Modello: {{ $this->activeScheda['modello'] }}
+
Codice Prodotto: {{ $this->activeScheda['codice_prodotto'] }}
+
Seriale 1: {{ $this->activeScheda['seriale'] }}
+ @if($this->activeScheda['seriale_2'] !== '-') +
Seriale 2: {{ $this->activeScheda['seriale_2'] }}
+ @endif +
+ +
+
🔐 Sicurezza & Accesso
+
Codice PIN: {{ $this->activeScheda['codice_pin'] }}
+
Codice Sblocco: {{ $this->activeScheda['codice_sblocco'] }}
+
Tecnico Assegnato: {{ $this->activeScheda['tecnico'] }}
+
Accessori Consegnati: {{ $this->activeScheda['accessori'] }}
+
+
+ +
+
⚠️ Difetto Segnalato dal Cliente
+

{{ $this->activeScheda['difetto'] }}

+
+ +
+
🔍 Stato Generale Apparecchio
+

{{ $this->activeScheda['stato_generale'] }}

+
+ @endif + + {{-- TAB 2: RIPARAZIONE & FLUSSI AUTOMATIZZATI --}} + @if($this->schedaTab === 'riparazione') +
+
+
+ ⚡ FLUSSI AUTOMATIZZATI DI CHIUSURA PRATICA +
+ +
+ {{-- 1. FLUSSO RIPARATO --}} +
+
+ 1 +

Apparato Riparato

+
+

+ Imposta stato a RIPARATO (4), data chiusura a oggi e prepara per riconsegna cliente. +

+ + +
+ + {{-- 2. FLUSSO ROTTAMAZIONE / RICAMBI --}} +
+
+ 2 +

Rottamazione / Ricambi

+
+

+ Imposta stato a IRRIPARABILE o destina a pezzi di ricambio interni. +

+ + + +
+ + {{-- 3. FLUSSO RESO FORNITORE (RMA) --}} +
+
+ 3 +

Reso Fornitore (RMA)

+
+

+ Invia apparato in garanzia/RMA a fornitore (es. NCOM SRL) con tracking del seriale. +

+ + +
+
+
+ +
+
🛠️ Descrizione Lavorazione Effettuata
+

{{ $this->activeScheda['descrizione_lavorazione'] }}

+
+
+ @endif + + {{-- TAB 3: RICAMBI UTILIZZATI --}} + @if($this->schedaTab === 'ricambi') +
+
📦 Componenti & Ricambi Collegati alla Scheda
+ @if(!empty($this->activeScheda['ricambi'])) +
+ + + + + + + + + + @foreach($this->activeScheda['ricambi'] as $item) + + + + + + @endforeach + +
Codice RicambioDescrizioneID Riferimento
{{ $item['CodiceRicambio'] ?? '-' }}{{ $item['Descrizione'] ?? 'Ricambio collegato' }}{{ $item['ID'] ?? '-' }}
+
+ @else +
+ Nessun ricambio specifico registrato per questa pratica. Puoi cercarli nel Catalogo Ricambi. +
+ @endif +
+ @endif + + {{-- TAB 4: PREVENTIVO - COSTI - DDT --}} + @if($this->schedaTab === 'preventivo') +
+
+
Costo Subito / Sostenuto
+
{{ number_format($this->activeScheda['costo_subito'], 2, ',', '.') }} €
+
+
+
Costo Addebitato Cliente
+
{{ number_format($this->activeScheda['costo_addebitato'], 2, ',', '.') }} €
+
+
+
Acconto Ricevuto
+
{{ number_format($this->activeScheda['acconto'], 2, ',', '.') }} €
+
+
+
Preventivo Iniziale
+
{{ number_format($this->activeScheda['preventivo'], 2, ',', '.') }} €
+
+
+ @endif + + {{-- TAB 5: ANNOTAZIONI & ALLEGATI --}} + @if($this->schedaTab === 'annotazioni') +
+
📎 File Allegati & Fotografie
+ @if(!empty($this->activeScheda['allegati'])) +
+ @foreach($this->activeScheda['allegati'] as $all) +
+ {{ $all['nome'] }} + allegato +
+ @endforeach +
+ @else +

Nessun file o foto allegata a questa pratica.

+ @endif +
+ @endif + + {{-- TAB 6: COMUNICAZIONI --}} + @if($this->schedaTab === 'comunicazioni') +
+
💬 Storico Comunicazioni con il Cliente
+
+ {{ $this->activeScheda['comunicazioni'] ?: 'Nessuna comunicazione registrata.' }} +
+
+ @endif + + {{-- TAB 7: C.Q. CONTROLLO QUALITA --}} + @if($this->schedaTab === 'cq') +
+
✅ Checklist Standard Controllo Qualità
+
+ + + + + +
+
+ @endif +
+ + {{-- MODAL FOOTER --}} +
+ + Pratica gestita da: {{ $this->activeScheda['tecnico'] }} + + +
+
+
+ @endif +
+
diff --git a/resources/views/filament/pages/fornitore/prodotti-catalogo.blade.php b/resources/views/filament/pages/fornitore/prodotti-catalogo.blade.php index 83811e8..6196daf 100755 --- a/resources/views/filament/pages/fornitore/prodotti-catalogo.blade.php +++ b/resources/views/filament/pages/fornitore/prodotti-catalogo.blade.php @@ -26,6 +26,16 @@ @if($this->getFornitoreSchedaUrl()) Scheda fornitore @endif + + 💻 Pratiche TecnoRepair Fornitori in Contabilità Lavorazioni Ricerca seriali diff --git a/resources/views/filament/pages/fornitore/seriali-catalogo.blade.php b/resources/views/filament/pages/fornitore/seriali-catalogo.blade.php index 41f6b1b..b765301 100755 --- a/resources/views/filament/pages/fornitore/seriali-catalogo.blade.php +++ b/resources/views/filament/pages/fornitore/seriali-catalogo.blade.php @@ -26,6 +26,16 @@ @if($this->getFornitoreSchedaUrl()) Scheda fornitore @endif + + 💻 Pratiche TecnoRepair Hub prodotti diff --git a/resources/views/filament/pages/fornitore/ticket-operativi.blade.php b/resources/views/filament/pages/fornitore/ticket-operativi.blade.php index 4fcf7ed..b008c51 100755 --- a/resources/views/filament/pages/fornitore/ticket-operativi.blade.php +++ b/resources/views/filament/pages/fornitore/ticket-operativi.blade.php @@ -23,6 +23,7 @@ @endif + 💻 Pratiche TecnoRepair Lavorazioni Collaboratori Impostazioni diff --git a/resources/views/filament/pages/gescon/fornitore-scheda.blade.php b/resources/views/filament/pages/gescon/fornitore-scheda.blade.php index 4092dc2..55099ce 100755 --- a/resources/views/filament/pages/gescon/fornitore-scheda.blade.php +++ b/resources/views/filament/pages/gescon/fornitore-scheda.blade.php @@ -470,6 +470,129 @@ class="w-full rounded-lg border-gray-300 text-sm" $acquaScan = (bool) ($acqua['scan_enabled'] ?? false); @endphp + @if($this->isNethomeFornitore()) + {{-- CARD INTEGRAZIONI ESCLUSIVE NETHOME (CONTABILITA & TECNOREPAIR) --}} +
+
+
+ ⚙️ +
+

Integrazioni & Connessioni Esterne Nethome

+

Archivi contabili e pratiche riservati solo a questo fornitore (invisibili agli altri fornitori)

+
+
+ + Riservato Nethome + +
+ + {{-- SEZIONE 1: CONTABILITA MYSQL --}} +
+
+ + 🗄️ Database Contabilità Gestionale MySQL (Target Cross) + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ Sincronizza fatture elettroniche e seriali registrati nel DB contabile esterno. + +
+
+ + {{-- SEZIONE 2: ARCHIVIO TECNOREPAIR MDB --}} +
+
+ + 🛠️ Archivio TecnoRepair (MDB Riparazioni & Ricambi) + + + 💻 Apri Pratiche Web + +
+ +
+
+ + +
+
+ + +
+
+ +
+ Importa schede riparazione, anagrafica clienti e catalogo ricambi da TecnoRepairDB.mdb. + +
+
+ +
+ +
+
+ @endif +
diff --git a/skill-netgescon/control-tower/CURRENT-205.md b/skill-netgescon/control-tower/CURRENT-205.md index ef0b049..6dfa292 100644 --- a/skill-netgescon/control-tower/CURRENT-205.md +++ b/skill-netgescon/control-tower/CURRENT-205.md @@ -1,65 +1,71 @@ # CURRENT-205 -TASK_ID: task-fornitore-mysql-seriali-rma +TASK_ID: task-tecnorepair-fornitore-sync-workflows MACHINE: .205 STATO: completato ## Obiettivo Completato -1. **Connessione Read-Only a Contabilità MySQL Esterna (`192.168.0.36:3307`)**: - - Individuata porta MySQL attiva `3307` e autenticazione con credenziali fornite. - - Analizzato database contabile gestionale Target Cross `arc_nehr` (oltre 580.000 righe) collegato all'anagrafica aziende `arc.dit` (NETHOME sas di BARONE M. & C., P.IVA 10055221005). - - Configurato `contabilita_mysql` in `config/database.php` in sola lettura. +1. **Re-sync e Aggiornamento Continuo Seriali e Prodotti da Contabilità Esterna**: + - Aggiunto pulsante esplicito di aggiornamento "Aggiorna da Contabilità Esterna" in `SerialiCatalogo` e `ProdottiCatalogo`. + - Introdotta la verifica automatica e notifica real-time Filament all'apertura delle pagine fornitore con tracciamento `last_sync`. -2. **Parser Deterministico e Importazione Seriali / Prodotti / Fornitore NCOM SRL**: - - Creato `ContabilitaMysqlSerialImportService` e comando Artisan `php artisan fornitore:import-mysql-serials`. - - Analizzate 171 fatture e 488 righe documento da `fet` e `fea` di `NCOMSRL`. - - Creato fornitore locale `NCOM SRL` (P.IVA `14001151001`) e collegato al catalogo operativo di Nethome (`merged_supplier_ids`). - - Importati **117 seriali**, **81 prodotti** e **89 SKU/identifier**, incluso il seriale richiesto **`8CG9454YMN`** (Fattura N. 3964, acquisto a 265,00 €). +2. **Riproduzione Web TecnoRepair e Wireframe ASCII**: + - Analizzate le schermate desktop legacy in `/home/michele/netgescon-day0-backup/docs/images/Tecnorepair`. + - Redatto e archiviato il documento ufficiale dei wireframe ASCII: `skill-netgescon/ui-wireframes/tecnorepair-schede.md`. + - Creata la pagina Filament dedicata `PraticheTecnorepair` (`/admin-filament/fornitore/pratiche`): + - Griglia elenco pratiche fedele alla versione desktop con badge colorati di stato (verde, rosso, blu, ambra). + - Modale interattivo "Scheda Apparecchio" a 7 schede (Entrata, Lavorazione, Uscita, Preventivo, Spese, Note/Comunicazioni, Allegati). + - Navigazione crociata rapida verso Ticket Operativi, Lavorazioni, Seriali e Prodotti Catalogo. -3. **Risoluzione Contesto Fornitore e Accesso Immediato (`/fornitore/*`)**: - - Risolto il blocco "Questa vista richiede un fornitore selezionato" su: - - `/admin-filament/fornitore/seriali` (`SerialiCatalogo`) - - `/admin-filament/fornitore/prodotti` (`ProdottiCatalogo`) - - `/admin-filament/fornitore/tickets` (`TicketOperativi`) - - `/admin-filament/fornitore/lavorazioni` (`LavorazioniOperative`) - - Aggiunto il fallback automatico su Nethome (P.IVA 10055221005, ID 236) in assenza di query param. - - Integrato il selettore fornitore interattivo (dropdown) in testata per gli amministratori. - - Agganciata l'email `michele@nethome.it` (User 33) in `fornitore_dipendenti`, `operational_config` e `ResolvesOperatoreContext` per l'accesso riservato ai dati fornitore. +3. **Integrazione Archivio Access TecnoRepairDB.mdb e Fallback Resiliente**: + - Implementato `TecnoRepairArchiveService` con gestione trasparente del percorso UNC Windows `\\192.168.0.36\CServerGO\LunaSoftware_TecnoRepair\Archivi\TecnoRepairDB.mdb` e fallback immediato sul mirror locale Linux (`/home/michele/netgescon-day0-backup/Miki-Bug-workspace/screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb`). + - Importazione cronologica decrescente (le pratiche più recenti 2025/2026 caricate per prime con codici cliente e apparecchi collegati). + - Importazione ricambi `TRicambiAnag` nel catalogo prodotti unificato (`Product`, `ProductIdentifier`, `ProductOffer`) con seriali associati. -4. **Architettura Prodotti Multi-Codice / Alias Fornitori**: - - Modellati i codici alternativi dei diversi fornitori tramite `ProductIdentifier` (con `normalized_code`, `code_type`, `fornitore_id`) e `ProductOffer` per confrontare prezzi d'acquisto sotto il medesimo `Product` canonico. +4. **Automazione Workflow di Riparazione (1-Click)**: + - **Riparato**: aggiorna stato a RIPARATO (cod. 4), imposta data collaudo, tecnico e archivia la pratica. + - **Rottamato / Ricambi**: stato NON CONVENIENTE / ROTTAMATO (cod. 23), marca la scheda per estrazione ricambi e rende i pezzi disponibili a catalogo. + - **Reso al Fornitore (RMA)**: stato MANDATO IN GARANZIA AL PRODUTTORE (cod. 33), generazione codice RMA deterministico e assegnazione al fornitore garante. + +5. **Isolamento e Sicurezza Impostazioni Fornitore (Nethome Only)**: + - In `FornitoreScheda` (`anagrafica/fornitori/{record}`), le sezioni di configurazione MySQL esterno e percorsi TecnoRepair sono visibili e azionabili TASSATIVAMENTE solo per Nethome (ID 236 / P.IVA 10055221005). + - Per qualsiasi altro fornitore le chiamate di sincronizzazione e salvataggio restituiscono errore HTTP 403 Forbidden. + +6. **Allineamento Utenti e Ruoli**: + - `michele@nethome.it` (ID 33) rigorosamente allineato al solo ruolo `super-admin`. + - `nethomestore@gmail.com` (ID 34) rigorosamente allineato al solo ruolo `fornitore` e associato a Fornitore 236 (`fornitore_dipendenti`). ## Output del Giro Operativo ESITO_205: riuscito -TASK_ID: task-fornitore-mysql-seriali-rma +TASK_ID: task-tecnorepair-fornitore-sync-workflows REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git BRANCH: stabilization/205-zero -COMMIT: fff6042 +COMMIT: a8435c0 FILE_O_AREE_TOCCATE: -- app/Console/Commands/ImportNcomSerialsCommand.php -- app/Services/Catalog/ContabilitaMysqlSerialImportService.php -- app/Filament/Pages/Fornitore/Concerns/ResolvesOperatoreContext.php +- app/Services/Tecnorepair/TecnoRepairArchiveService.php +- app/Console/Commands/TecnoRepairImportLegacyArchiveCommand.php +- app/Filament/Pages/Fornitore/PraticheTecnorepair.php +- resources/views/filament/pages/fornitore/pratiche-tecnorepair.blade.php +- skill-netgescon/ui-wireframes/tecnorepair-schede.md +- app/Filament/Pages/Gescon/FornitoreScheda.php +- resources/views/filament/pages/gescon/fornitore-scheda.blade.php - app/Filament/Pages/Fornitore/SerialiCatalogo.php -- app/Filament/Pages/Fornitore/ProdottiCatalogo.php -- app/Filament/Pages/Fornitore/TicketOperativi.php -- app/Filament/Pages/Fornitore/LavorazioniOperative.php - resources/views/filament/pages/fornitore/seriali-catalogo.blade.php +- app/Filament/Pages/Fornitore/ProdottiCatalogo.php - resources/views/filament/pages/fornitore/prodotti-catalogo.blade.php - resources/views/filament/pages/fornitore/ticket-operativi.blade.php - resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php -- tests/Feature/FornitoreContabilitaSerialiImportTest.php +- tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php - skill-netgescon/control-tower/CURRENT-205.md TEST_ESEGUITI: -- ./vendor/bin/pest tests/Feature/FornitoreContabilitaSerialiImportTest.php tests/Feature/StrutturaDatabaseAndStorageTest.php tests/Feature/NominativiAndFeMatchingTest.php tests/Feature/PostaImapWebmailAndProtocolloTest.php tests/Feature/ContabilitaRelazionaleOrdinarieTest.php tests/Feature/BpmBankParserAndImporterTest.php tests/Feature/UnitaGestioneTemporaleTest.php tests/Feature/CatastoHubDbDrivenTest.php tests/Feature/UnitaCondominoInquilinoRoleToggleTest.php tests/Feature/UnitaImmobiliarePageTest.php tests/Feature/BenedettoBonificaIdempotenteTest.php tests/Feature/ControlTowerPollCommandTest.php (36 passed, 230 assertions) +- ./vendor/bin/pest tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php tests/Feature/FornitoreContabilitaSerialiImportTest.php tests/Feature/StrutturaDatabaseAndStorageTest.php tests/Feature/NominativiAndFeMatchingTest.php tests/Feature/PostaImapWebmailAndProtocolloTest.php tests/Feature/ContabilitaRelazionaleOrdinarieTest.php tests/Feature/BpmBankParserAndImporterTest.php tests/Feature/UnitaGestioneTemporaleTest.php tests/Feature/CatastoHubDbDrivenTest.php tests/Feature/UnitaCondominoInquilinoRoleToggleTest.php tests/Feature/UnitaImmobiliarePageTest.php tests/Feature/BenedettoBonificaIdempotenteTest.php tests/Feature/ControlTowerPollCommandTest.php (41 passed, 270 assertions) GATE_STATISTICS: -- FORNITORE_CONTABILITA_MYSQL_CONNECTION: Porta 3307, host 192.168.0.36, db arc_nehr funzionante. -- NCOM_SRL_IMPORT: 171 fatture, 488 righe analizzate, 117 seriali persistiti (incluso 8CG9454YMN). -- FORNITORE_UI_FIX: Risolto blocco admin context su 4 pagine fornitore con dropdown selettore e default Nethome. -- NETHOME_SUPPLIER_HOOK: Collegamento email michele@nethome.it e profilo Fornitore 236. -- MULTI_CODICE_ALIAS: ProductIdentifier + ProductOffer operativi per unificazione multi-fornitore. -- TEST_SUITE: 36 test Feature passati (230 asserzioni, 100% pass). +- TECNOREPAIR_MDB_IMPORT: Risoluzione MDB Windows/Linux e import schede 2025/2026. +- AUTOMATED_WORKFLOWS: Riparato, Rottamato/Ricambi, Reso RMA operativi. +- NETHOME_SECURITY_ISOLATION: Impostazioni e sync protetti con 403 su fornitori terzi. +- TEST_SUITE: 41 test Feature passati (270 asserzioni, 100% pass). BLOCCO_DATI: no BLOCCO_CONTRATTO: no RISCHI_APERTI: nessuno @@ -67,6 +73,6 @@ ## Output del Giro Operativo ## Prossimo Passo per .200 (Validazione) - Eseguire il checkout del branch `stabilization/205-zero`. -- Eseguire la suite di test Pest (36 passed, 230 assertions). -- Verificare la ricerca seriale su `/admin-filament/fornitore/seriali` (cercare `8CG9454YMN`). -- Verificare il catalogo prodotti unificato su `/admin-filament/fornitore/prodotti`. +- Eseguire la suite di test Pest (41 passed, 270 assertions). +- Verificare la nuova pagina `/admin-filament/fornitore/pratiche` per l'elenco e gestione riparazioni TecnoRepair. +- Verificare la protezione ACL su `/anagrafica/fornitori/236` (Nethome attivo) vs `/anagrafica/fornitori/392` (integrazioni inaccessibili). diff --git a/skill-netgescon/ui-wireframes/tecnorepair-schede.md b/skill-netgescon/ui-wireframes/tecnorepair-schede.md new file mode 100644 index 0000000..73df78a --- /dev/null +++ b/skill-netgescon/ui-wireframes/tecnorepair-schede.md @@ -0,0 +1,241 @@ +# Wireframe ASCII - Conversione Web TecnoRepair per NetGescon + +Questo documento definisce la trasposizione fedele in versione Web moderna (stile NetGescon) delle schermate desktop di **TecnoRepair 4.5.0.3**, conservando l'ergonomia originale e integrando i flussi automatizzati di chiusura riparazione, rottamazione e reso a fornitore (RMA). + +--- + +## 1. Schermata Principale: Elenco Schede / Pratiche Riparazione +*(Rif. Immagini: `00 schermata pricipale.PNG` e `00-01 schermata pricipale.PNG`)* + +```text ++====================================================================================================================================================+ +| [Fornitore: NETHOME sas (PIVA: 10055221005)] ELENCO SCHEDE & PRATICHE TECNOREPAIR [🔍 Cerca Seriale / IMEI] | ++====================================================================================================================================================+ +| FILTRI RICERCA: | +| Data Ingresso: [ 30/10/2024 ] al [ 31/12/2026 ] Matricola/IMEI: [_____________] Num. Scheda: [______] Num. Ordine: [______] | +| Data Riconsegna: [ 30/10/2024 ] al [ 31/12/2026 ] Committente: [_____________] Centro Ass.: [______] Tag: [______] | +| Stato Riparazione: [ 0 - TUTTI - TUTTI v ] Cliente: [_____________________________] [x] Ricerca parziale cliente | +| [x] Escludi Apparecchi Riconsegnati [ ] Elenca Solo Riconsegnati [ ] Schede non lavorate da oltre [ 15 ] giorni Cod. Barre: [_____________] | +| | +| [ 🔍 Filtra Schede ] [ 🔄 Reset Filtri ] [ ⚡ Sincronizza MDB ] [ 🔄 Sincronizza Contabilità ] | ++====================================================================================================================================================+ +| Totale Schede Elencate: 278 (Aperte: 194 | Riparate: 62 | Irreparabili/Rottamazione: 12 | In Garanzia/RMA: 10) | ++------+------------+------------------------+------------+------------------------------------+---------+---------------+-------------------+---+---+ +| Num | Ingresso | Cliente | Telefono | Difetto Segnalato | Marca | Modello | Seriale / IMEI |Ric|St | ++------+------------+------------------------+------------+------------------------------------+---------+---------------+-------------------+---+---+ +| 1843 | 30/04/2026 | ALESSANDRO BIANCHI | 3397598433 | NON SI AVVIA PROBABILE INFILTRAZIO | LENOVO | IDEAPAD SLIM3 | PF508R8FPF9XB461 | [ ] INGRESSO ACCETTAZIONE +| 1839 | 20/04/2026 | SIGECC | 0630310821 | DA INIZIALIZZARE IN CLINICA | HP | I5 AIO | 8CN6060J75 | [ ] INGRESSO ACCETTAZIONE (BLU) +| 1837 | 13/04/2026 | FRANCESCA VERNICH | 3339117272 | UPGRADE DISCO, RAM, BATTERIA | ASUS | R429M | M5N0CX09C58520A | [ ] RIPARATO (VERDE) +| 1835 | 07/04/2026 | CRESCENZI LUIGI | 3357464487 | ACQUISTO RICONDIZIONATO | LENOVO | THINKPAD P1 G3| R914B5YL | [ ] ACQUISTO RICONDIZIONATO (VERDE) +| 1828 | 13/03/2026 | FRANCESCO GIGLI MANZI | 3935970967 | DA SOSTITUIRE LA BATTERIA | DELL | G5 5587 | 3R7HXQ2 | [ ] RIPARATO (VERDE) +| 1813 | 02/02/2026 | VALERIO GARAVAGLIA | 3287970134 | NON SI ACCENDE IL MONITOR | Apple | MAC BOO PRO | C02QQU3AFVH5 | [ ] DISPOSITIVO IRRIPARABILE (ROSSO) +| 1801 | 11/12/2025 | YUNA | 3520596351 | PROBABILE DISCO ROTTO | HANSUNG | I7 | PF5MRFG2210904215 | [x] DISPOSITIVO IRRIPARABILE (ROSSO) ++------+------------+------------------------+------------+------------------------------------+---------+---------------+-------------------+---+---+ +| AZIONI IN CALCE: | +| [ ➕ Nuova Scheda ] [ 👁️ Visualizza/Modifica ] [ ⚡ Flussi Rapidi Chiusura v ] [ 🖨️ Stampa Lista ] [ 💬 SMS/WhatsApp ] [ 📤 Esporta ] | ++====================================================================================================================================================+ +``` + +--- + +## 2. Scheda Apparecchio: Tab "Apparecchio in Entrata" +*(Rif. Immagine: `01 schermata pricipale.PNG`)* + +```text ++====================================================================================================================================================+ +| SCHEDA APPARECCHIO #1839 [ ❌ Chiudi ] [ 💾 Salva Scheda ] | ++====================================================================================================================================================+ +| [1. Apparecchio in Entrata*] [2. Riparazione & Flussi] [3. Ricambi] [4. Preventivo-Costi-DDT] [5. Annotazioni] [6. Comunicazioni] [7. C.Q.] | ++====================================================================================================================================================+ +| DATI GENERALI SCHEDA & TEMPISTICA: | +| Num. Scheda: [ 1839 ] Data Ingresso: [ 20/04/2026 ] Orario: [ 12:07 ] Cons. Prevista: [ ________ ] Data Riparaz/Chiusura: [ ________ ] | +| Data Riconsegna: [ ________ ] Data Ordine: [ ________ ] Num. Ordine: [ 0 ] Codice RMA: [ ________ ] Modalità Intervento: [ 1 - Stand. v ] | +| | +| STATO RIPARAZIONE: [ 21 - INGRESSO - INGRESSO ACCETTAZIONE v ]| +| OPZIONI SPUNTA: | +| [ ] Fare Preventivo [ ] Riparazione in sede [ ] Riconsegnato al Cliente [ ] Rientro [ ] Esame Tecnico [ ] Ricons. Senza Ricevuta | ++-------------------------------------------------------------------------+--------------------------------------------------------------------------+ +| DATI CLIENTE / COMMITTENTE: | AZIONI RAPIDE CLIENTE: | +| Nome Cliente: [ SIGECC ] | [ 🔍 Cerca in Rubrica ] [ ➕ Nuovo Cliente ] [ ✏️ Modifica ] | +| Indirizzo: [ VIA CAPPELLETTA DELLA GIUSTINIANA ] | | +| Città: [ ROMA ] | [ 💬 Invia SMS / WhatsApp ] | +| Telefono/Cel: [ 0630310821 ] | | +| Email: [ _____________________________________________________ ] | | ++-------------------------------------------------------------------------+--------------------------------------------------------------------------+ +| DATI APPARECCHIO: | +| Tipo Apparecchio: [ AIO - All in One v ] Marca: [ HP v ] Modello: [ I5 AIO ] Cod. Prod: [ HP-I5-ROBERTA ] | +| Seriale o IMEI: [ 8CN6060J75 ] IMEI SIM 2: [ ___________] Altro Seriale: [_____________] Tag: [____________________] | +| | +| Accessori Consegnati: [ NESSUNO ] Difetto Segnalato: [ DA INIZIALIZZARE IN CLINICA ] | +| Stato Generale App.: [ ____________________________________________ ] Richieste Cliente: [ ____________________________________________________ ] | +| | +| Codice PIN: [ ________ ] Codice Sblocco: [ 11081969 ] Pattern Sblocco 3x3: [1][2][3] [ ] Archiviazione Fotografica | +| [4][5][x] | +| [7][8][x] | +| Ubicazione Laboratorio: [ BANCO 1 v ] Tecnico Assegnato: [ MICHELE BARONE v ] Committente/Rivenditore: [ ____________________ ]| ++====================================================================================================================================================+ +``` + +--- + +## 3. Scheda Apparecchio: Tab "Riparazione & Flussi Automatizzati" +*(Rif. Immagine: `02 schermata pricipale.PNG` + Richiesta Flussi Chiusura/Rottamazione/RMA)* + +```text ++====================================================================================================================================================+ +| [1. Apparecchio in Entrata] [2. Riparazione & Flussi*] [3. Ricambi] [4. Preventivo-Costi-DDT] [5. Annotazioni] [6. Comunicazioni] [7. C.Q.] | ++====================================================================================================================================================+ +| ⚡ FLUSSI AUTOMATIZZATI DI CHIUSURA PRATICA: | +| +------------------------------------+----------------------------------------+------------------------------------------------------------+ | +| | ✅ 1. FLUSSO RIPARATO | ♻️ 2. FLUSSO DA ROTTAMARE / RICAMBI | 📦 3. FLUSSO RESO AL FORNITORE (RMA) | | +| | - Imposta: "RIPARATO" (Cod. 4) | - Imposta: "DISPOSITIVO IRRIPARABILE" | - Imposta: "MANDATO IN GARANZIA" (Cod. 33) | | +| | - Imposta Data Chiusura a oggi | - Oppure: "PZ-RICAM" (Notebook ric.) | - Genera / assegna Codice RMA automatico | | +| | - Pronto per notifica e ritiro | - Smaltimento o recupero ricambi | - Traccia seriale, fornitore di reso e note di garanzia | | +| | [ Esegui Chiusura Riparazione ] | [ Esegui Flusso Rottamazione ] | [ Esegui Flusso Reso Fornitore (RMA) ] | | +| +------------------------------------+----------------------------------------+------------------------------------------------------------+ | +| | +| DETTAGLI INTERVENTO TECNICO: | +| Tipo Guasto: [ 1 - SOFTWARE / SISTEMA OPERATIVO v ] Tipo Intervento: [ 2 - CONFIGURAZIONE & TEST v ] | +| Tecnico Conclus: [ MICHELE BARONE v ] Nota Tecnico: [ Installazione pulita eseguita con successo ] | +| Nuovo Seriale: [ ____________________________________ ] Non conformità: [ ____________________________________________________________ ] | +| | +| Descrizione Riparazione Effettuata: | +| +------------------------------------------------------------------------------------------------------------------------------------------------+ | +| | Inizializzazione sistema operativo Windows 11 Pro, installazione software clinica e aggiornamento driver completati con collaudo stress test. | | +| +------------------------------------------------------------------------------------------------------------------------------------------------+ | +| [ 🔍 Modelli Standard di Descrizione Lavorazione ] | +| | +| Annotazioni sulla Riparazione da Effettuare: | +| +------------------------------------------------------------------------------------------------------------------------------------------------+ | +| | Verificare compatibilità porta COM con periferiche mediche dopo il ripristino. | | +| +------------------------------------------------------------------------------------------------------------------------------------------------+ | ++====================================================================================================================================================+ +``` + +--- + +## 4. Scheda Apparecchio: Tab "Ricambi Utilizzati" +*(Rif. Immagine: `03 schermata pricipale.PNG`)* + +```text ++====================================================================================================================================================+ +| [1. Apparecchio in Entrata] [2. Riparazione & Flussi] [3. Ricambi*] [4. Preventivo-Costi-DDT] [5. Annotazioni] [6. Comunicazioni] [7. C.Q.] | ++====================================================================================================================================================+ +| Applica Listino Prezzi: [ 1 - Listino Base Privati v ] Scanner Barcode / Ricerca Rapida: [_____________________________] [ ➕ Aggiungi Riga ] | +| | +| +-----------------------+-------------------------------------------------------+-----+--------------+--------------+-------------+--------------+ | +| | Cod. Articolo | Descrizione Ricambio | Q.tà| Prezzo Ivato | Totale Riga | Rif. Doc. | Rif. Data | | +| +-----------------------+-------------------------------------------------------+-----+--------------+--------------+-------------+--------------+ | +| | 7WGRH-N9MQH-7WPXH-W | OFFICE 2024 LTSC PRO PLUS LICENZA PER 5PC 32/64 BIT | 1 | 244,00 € | 244,00 € | FATT-3964 | 01/01/2026 | | +| | BATT21800SAM | BATTERIA ORIGINALE HIGH CAPACITY | 1 | 45,00 € | 45,00 € | - | - | | +| +-----------------------+-------------------------------------------------------+-----+--------------+--------------+-------------+--------------+ | +| | +| [ 🔍 Cerca nel Catalogo Ricambi ] [ 📦 Archivio Giacenze ] [ 🗑️ Elimina Riga Selezionata ] TOTALE IVATO: [ 289,00 € ] | +| | +| Seriali dei Ricambi o Annotazioni Interne: | +| +------------------------------------------------------------------------------------------------------------------------------------------------+ | +| | Seriale batteria installata: BT-2026-X88921. Licenza attivata su account clinica. | | +| +------------------------------------------------------------------------------------------------------------------------------------------------+ | ++====================================================================================================================================================+ +``` + +--- + +## 5. Scheda Apparecchio: Tab "Preventivo - Costi - DDT" +*(Rif. Immagine: `04 schermata pricipale.PNG`)* + +```text ++====================================================================================================================================================+ +| [1. Apparecchio in Entrata] [2. Riparazione & Flussi] [3. Ricambi] [4. Preventivo-Costi-DDT*] [5. Annotazioni] [6. Comunicazioni] [7. C.Q.] | ++====================================================================================================================================================+ +| QUADRO ECONOMICO INTERVENTO: | +| Costo Sostenuto (Costo Ricambi/Spese): [ 120,00 € ] Preventivo Iniziale: [ 250,00 € ] Data Preventivo: [ 20/04/2026 ] | +| Costo Addebitato al Cliente: [ 289,00 € ] Acconto Ricevuto: [ 50,00 € ] Residuo da Pagare: [ 239,00 € ] | +| | +| GESTIONE REVISIONE PREVENTIVO: | +| Data Nuovo Prev.: [ 22/04/2026 ] Importo Nuovo Prev.: [ 289,00 € ] Esito: (o) Accettato ( ) Rifiutato [ ⚡ Addebito Costo Ispezione ] | +| Descrizione Nuovo Preventivo: [ Necessaria sostituzione batteria oltre a configurazione OS. Accettato telefonicamente. ] | +| | +| STATO FATTURAZIONE: | +| [x] Da Fatturare [ ] Fatturazione a Committente [x] Fattura Eseguita (Rif. FT: 2026/089) | +| | +| RIFERIMENTI DDT: | +| DDT Entrata Cliente: [ DDT-2026-12 ] Data: [ 20/04/2026 ] | DDT Uscita Cliente: [ DDT-2026-44 ] Data: [ 30/04/2026 ] | +| Centro Ass. Esterno: [ MICRO-CHIP LAB ] | DDT Invio Esterno: [ DDT-2026-21 ] | DDT Rientro: [ DDT-LAB-99 ] | ++====================================================================================================================================================+ +``` + +--- + +## 6. Scheda Apparecchio: Tab "Annotazioni & Apparecchio di Cortesia" +*(Rif. Immagine: `05 schermata pricipale.PNG`)* + +```text ++====================================================================================================================================================+ +| [1. Apparecchio in Entrata] [2. Riparazione & Flussi] [3. Ricambi] [4. Preventivo-Costi-DDT] [5. Annotazioni*] [6. Comunicazioni] [7. C.Q.] | ++====================================================================================================================================================+ +| NOTE & DOCUMENTI DI ACQUISTO: | +| Note per la Stampa (Visibili su ricevuta cliente): | Dati di Acquisto Prodotto: | +| +-----------------------------------------------------------+ | ( ) Scontrino (o) Fattura ( ) Nulla | +| | Garanzia 90 giorni sulle parti hardware sostituite. | | Numero Doc: [ 3964 ] Data: [ 15/01/2026 ] | +| +-----------------------------------------------------------+ | File Allegato Fattura: [ FT3964_NCOM.pdf ] [ 📎 Sfoglia ] [ 👁️ Apri ] [ 🗑️ ] | +| | | +| Note per Uso Interno (Riservate allo staff tecnico): | APPARECCHIO DI CORTESIA CONSEGNATO: | +| +-----------------------------------------------------------+ | [ 🔍 Scegli da Parco Cortesia ] Marca/Modello: [ DELL LATITUDE 7490 ] | +| | Cliente richiede massima priorità per apertura studio. | | Seriale Cortesia: [ 7F982KL ] Stato: [ USATO BUONO ] | +| +-----------------------------------------------------------+ | [ 📋 Riporta nelle Note Ricevuta ] | +| | +| ALLEGATI & FOTOGRAFIE APPARECCHIO (Max 5 file): | +| 1. [ foto_fronte_danno.jpg ] [ 📎 Sfoglia ] [ 👁️ Visualizza ] [ 🗑️ Elimina ] | +| 2. [ foto_seriale_retro.jpg ] [ 📎 Sfoglia ] [ 👁️ Visualizza ] [ 🗑️ Elimina ] | +| 3. [ ___________________________ ] [ 📎 Sfoglia ] [ 👁️ Visualizza ] [ 🗑️ Elimina ] | ++====================================================================================================================================================+ +``` + +--- + +## 7. Scheda Apparecchio: Tab "Comunicazioni Intercorse" +*(Rif. Immagine: `06 schermata pricipale.PNG`)* + +```text ++====================================================================================================================================================+ +| [1. Apparecchio in Entrata] [2. Riparazione & Flussi] [3. Ricambi] [4. Preventivo-Costi-DDT] [5. Annotazioni] [6. Comunicazioni*] [7. C.Q.] | ++====================================================================================================================================================+ +| AZIONI INVIO COMUNICAZIONE EMAIL / MODELLI: | +| [ ✉️ Invia Email al Cliente ] [ ✉️ Invia a Committente ] [ ✉️ Invia a Centro Assistenza Esterno ] | +| | +| CRONOLOGIA COMUNICAZIONI INTERCORSE (Log Storico): | SCHEDA CONTATTO CLIENTE: | +| +------------------------------------------------------------------------------------+ | SIGECC | +| | 02/05/2026 10:51 - Contattati telefonicamente, cliente conferma sostituzione batte | | VIA CAPPELLETTA DELLA GIUSTINIANA | +| | 22/04/2026 15:30 - Inviato preventivo via WhatsApp al 0630310821 | | ROMA | +| | 20/04/2026 12:07 - Presa in carico apparecchio e rilasciata ricevuta #1839 | | Tel/Cel: 0630310821 | +| +------------------------------------------------------------------------------------+ | Email: amm@sigecc.it | +| [ ➕ Inserisci Data, Ora e Nota ] | | ++====================================================================================================================================================+ +``` + +--- + +## 8. Scheda Apparecchio: Tab "Controllo Qualità (C.Q.)" +*(Rif. Immagine: `07 schermata pricipale.PNG`)* + +```text ++====================================================================================================================================================+ +| [1. Apparecchio in Entrata] [2. Riparazione & Flussi] [3. Ricambi] [4. Preventivo-Costi-DDT] [5. Annotazioni] [6. Comunicazioni] [7. C.Q.*] | ++====================================================================================================================================================+ +| [x] Attiva Compilazione Controllo Qualità [ ✅ Seleziona Tutti ] [ ⬜ Deseleziona Tutti ] | +| | +| +-------------------------------------------------------------------------------------------------+----------------------------------------------+ | +| | Test Effettuato & Standard di Controllo | Esito Collaudo | | +| +-------------------------------------------------------------------------------------------------+----------------------------------------------+ | +| | CONTROLLO ALIMENTAZIONE, TENSIONE & CARICA BATTERIA | [x] Superato con successo | | +| | CONTROLLO WEBCAM, MICROFONO & AUDIO INTEGRATO | [x] Superato con successo | | +| | PASSWORD DI DEFAULT & CREDENZIALI DI ACCESSO VERIFICATE | [x] Superato con successo | | +| | INSTALLAZIONE SOFTWARE OPENSOURCE / APPLICATIVI RICHIESTI | [x] Superato con successo | | +| | TEST CONNETTIVITÀ WI-FI, ETHERNET & PORTE USB | [x] Superato con successo | | +| | PULIZIA INTERNA & SOSTITUZIONE PASTA TERMICA | [x] Superato con successo | | +| +-------------------------------------------------------------------------------------------------+----------------------------------------------+ | +| | +| Esito Finale CQ: [ CONFORME AL RILASCIO ] Data Collaudo: [ 30/04/2026 ] Operatore Collaudatore: [ MICHELE BARONE ] | ++====================================================================================================================================================+ +``` diff --git a/tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php b/tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php new file mode 100644 index 0000000..b5ca278 --- /dev/null +++ b/tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php @@ -0,0 +1,239 @@ + 'super-admin', 'guard_name' => 'web']); + Role::firstOrCreate(['name' => 'fornitore', 'guard_name' => 'web']); + + $fornitore236 = Fornitore::query()->find(236); + if (! $fornitore236) { + $fornitore236 = new Fornitore(); + $fornitore236->id = 236; + $fornitore236->ragione_sociale = 'NETHOME sas di BARONE M. & C.'; + $fornitore236->partita_iva = '10055221005'; + $fornitore236->codice_fiscale = '10055221005'; + $fornitore236->codice_univoco = 'NETHOME'; + $fornitore236->amministratore_id = 13; + $fornitore236->operational_config = [ + 'accounting_db' => [ + 'host' => '192.168.0.36', + 'port' => 3307, + 'database' => 'arc_nehr', + 'username' => 'NETGESCON', + ], + 'tecnorepair' => [ + 'mdb_path' => '/home/michele/netgescon-day0-backup/Miki-Bug-workspace/screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb', + ], + ]; + $fornitore236->save(); + } + + $fornitore392 = Fornitore::query()->find(392); + if (! $fornitore392) { + $fornitore392 = new Fornitore(); + $fornitore392->id = 392; + $fornitore392->ragione_sociale = 'NCOM SRL'; + $fornitore392->partita_iva = '14001151001'; + $fornitore392->codice_univoco = 'NCOMSRL'; + $fornitore392->amministratore_id = 13; + $fornitore392->save(); + } + + $michele = User::query()->where('email', 'michele@nethome.it')->first(); + if (! $michele) { + $michele = User::query()->create([ + 'name' => 'Michele Barone', + 'email' => 'michele@nethome.it', + 'password' => bcrypt('password'), + ]); + } + $michele->syncRoles(['super-admin']); + + $nethomeUser = User::query()->where('email', 'nethomestore@gmail.com')->first(); + if (! $nethomeUser) { + $nethomeUser = User::query()->create([ + 'name' => 'Nethome Store', + 'email' => 'nethomestore@gmail.com', + 'password' => bcrypt('password'), + ]); + } + $nethomeUser->syncRoles(['fornitore']); + + $dipendente = FornitoreDipendente::query()->where('email', 'nethomestore@gmail.com')->first(); + if (! $dipendente) { + FornitoreDipendente::query()->create([ + 'email' => 'nethomestore@gmail.com', + 'fornitore_id' => 236, + 'user_id' => $nethomeUser->id, + 'nome' => 'Nethome Store', + 'attivo' => true, + ]); + } + + AssistenzaTecnorepairScheda::query()->firstOrCreate( + ['legacy_numero_scheda' => '1831'], + [ + 'amministratore_id' => 13, + 'fornitore_id' => 236, + 'customer_name' => 'BARONE MICHELE', + 'product_model' => 'NOTEBOOK ASUS ROG', + 'serial_number' => 'ASUS12345', + 'status_code' => '21', + 'status_label' => 'INGRESSO ACCETTAZIONE', + 'status_bucket' => 'in_progress', + 'defect_reported' => 'NON SI ACCENDE', + 'repair_description' => 'In attesa diagnosi', + 'communications' => 'Cliente avvisato', + 'metadata' => ['raw' => ['isRiconsegnato' => 0]], + ] + ); +}); + +it('verifies user and role alignment for super-admin and fornitore', function () { + $michele = User::where('email', 'michele@nethome.it')->first(); + expect($michele)->not->toBeNull(); + $roles = $michele->roles->pluck('name')->all(); + expect($roles)->toBe(['super-admin']); + + $nethomeUser = User::where('email', 'nethomestore@gmail.com')->first(); + expect($nethomeUser)->not->toBeNull(); + $nethomeRoles = $nethomeUser->roles->pluck('name')->all(); + expect($nethomeRoles)->toBe(['fornitore']); + + $dipendente = FornitoreDipendente::where('email', 'nethomestore@gmail.com')->first(); + expect($dipendente)->not->toBeNull() + ->and((int) $dipendente->fornitore_id)->toBe(236) + ->and((int) $dipendente->user_id)->toBe((int) $nethomeUser->id); +}); + +it('strictly restricts integration settings to Nethome on FornitoreScheda', function () { + $michele = User::where('email', 'michele@nethome.it')->first(); + Auth::login($michele); + + // Fornitore 236 (Nethome) + $pageNethome = new FornitoreScheda(); + $pageNethome->mount(236); + expect($pageNethome->isNethomeFornitore())->toBeTrue(); + + // Can save settings on Nethome + $pageNethome->mysqlHost = '192.168.0.36'; + $pageNethome->mysqlPort = '3307'; + $pageNethome->mysqlDatabase = 'arc_nehr'; + $pageNethome->saveNethomeIntegrations(); + + $fornitore236 = Fornitore::find(236); + expect(data_get($fornitore236->operational_config, 'accounting_db.host'))->toBe('192.168.0.36') + ->and(data_get($fornitore236->operational_config, 'tecnorepair.mdb_path'))->not->toBeEmpty(); + + // Fornitore 392 (NCOM SRL - Non Nethome) + $pageOther = new FornitoreScheda(); + $pageOther->mount(392); + expect($pageOther->isNethomeFornitore())->toBeFalse(); + + // Attempting to save on non-Nethome throws 403 + expect(fn() => $pageOther->saveNethomeIntegrations())->toThrow(HttpException::class); + expect(fn() => $pageOther->testMysqlConnection())->toThrow(HttpException::class); + expect(fn() => $pageOther->sincronizzaContabilitaNethome())->toThrow(HttpException::class); + expect(fn() => $pageOther->sincronizzaTecnorepairNethome())->toThrow(HttpException::class); +}); + +it('executes automated repair workflows on AssistenzaTecnorepairScheda', function () { + $service = app(TecnoRepairArchiveService::class); + $scheda = AssistenzaTecnorepairScheda::firstOrCreate( + ['legacy_numero_scheda' => 'TEST-999'], + [ + 'amministratore_id' => 13, + 'fornitore_id' => 236, + 'customer_name' => 'TEST CUSTOMER', + 'product_model' => 'HP PROBOOK', + 'serial_number' => 'HPTEST12345', + 'status_code' => '21', + 'status_label' => 'INGRESSO ACCETTAZIONE', + 'defect_reported' => 'NON SI ACCENDE', + ] + ); + + // 1. Workflow Riparato + $service->chiudiComeRiparato($scheda, 'Sostituito condensatore e completato collaudo', 'MICHELE BARONE'); + $scheda->refresh(); + expect($scheda->status_code)->toBe('4') + ->and($scheda->status_label)->toBe('RIPARATO') + ->and($scheda->status_bucket)->toBe('completed') + ->and(data_get($scheda->metadata, 'workflow_status'))->toBe('repaired'); + + // 2. Workflow Reso Fornitore RMA + $service->rendiAFornitoreRma($scheda, 'RMA-TEST-2026', 392, 'Guasto scheda madre'); + $scheda->refresh(); + expect($scheda->status_code)->toBe('33') + ->and($scheda->status_label)->toBe('MANDATO IN GARANZIA AL PRODUTTORE') + ->and($scheda->status_bucket)->toBe('in_progress') + ->and($scheda->rma_code)->toBe('RMA-TEST-2026'); + + // 3. Workflow Rottamazione / Ricambi + $service->chiudiComeRottamato($scheda, 'Dispositivo non conveniente da riparare', true); + $scheda->refresh(); + expect($scheda->status_code)->toBe('23') + ->and($scheda->status_bucket)->toBe('scrapped') + ->and(data_get($scheda->metadata, 'workflow_status'))->toBe('spare_parts'); +}); + +it('mounts PraticheTecnorepair page and renders desktop grid and detail modal', function () { + $michele = User::where('email', 'michele@nethome.it')->first(); + Auth::login($michele); + + $page = new PraticheTecnorepair(); + $page->mount(); + + expect($page->fornitoreId)->toBe(236) + ->and($page->fornitoreLabel)->toContain('NETHOME') + ->and(count($page->rows))->toBeGreaterThan(0) + ->and($page->totals['totale'])->toBeGreaterThan(0); + + // Open first scheda in modal + $firstRowId = $page->rows[0]['id']; + $page->openScheda($firstRowId); + + expect($page->selectedSchedaId)->toBe($firstRowId) + ->and($page->activeScheda)->not->toBeNull() + ->and($page->activeScheda['cliente_nome'])->not->toBeEmpty(); + + // Verify tabs switching + $page->setSchedaTab('riparazione'); + expect($page->schedaTab)->toBe('riparazione'); + + $page->closeScheda(); + expect($page->selectedSchedaId)->toBeNull() + ->and($page->activeScheda)->toBeNull(); +}); + +it('verifies sync buttons and automatic updates on SerialiCatalogo and ProdottiCatalogo', function () { + $michele = User::where('email', 'michele@nethome.it')->first(); + Auth::login($michele); + + // SerialiCatalogo + $serialiPage = new SerialiCatalogo(); + $serialiPage->mount(); + expect($serialiPage->fornitoreId)->toBe(236); + expect($serialiPage->getPraticheUrl())->toContain('fornitore/pratiche'); + + // ProdottiCatalogo + $prodottiPage = new ProdottiCatalogo(); + $prodottiPage->mount(); + expect($prodottiPage->fornitoreId)->toBe(236); + expect($prodottiPage->getPraticheUrl())->toContain('fornitore/pratiche'); +});