diff --git a/.gitignore b/.gitignore index 9036091..7cf0ac0 100755 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ scripts/ops/windows/netgescon-tapi-provider-report.json storage/app/private/** storage/app/public/** storage/app/*.json +storage/app/tecnorepair/ +Google autorizzazioni/ diff --git a/app/Filament/Pages/Condomini/NominativiStabile.php b/app/Filament/Pages/Condomini/NominativiStabile.php index c235d19..c00ffe6 100755 --- a/app/Filament/Pages/Condomini/NominativiStabile.php +++ b/app/Filament/Pages/Condomini/NominativiStabile.php @@ -593,6 +593,10 @@ protected function buildDomainConsolidatedQuery(int $stabileId): Builder protected function hasLegacyNominativiForStabile(string $codStabile): bool { + if (! DbSchema::connection('gescon_import')->hasTable('condomin')) { + return false; + } + return DB::connection('gescon_import') ->table('condomin') ->where('cod_stabile', $codStabile) @@ -602,6 +606,10 @@ protected function hasLegacyNominativiForStabile(string $codStabile): bool private function getAccumulatedUnits(string $codStabile, string $legacyYear, string $codCond): array { + if (! DbSchema::connection('gescon_import')->hasTable('vw_legacy_condomin_nominativi')) { + return []; + } + $units = DB::connection('gescon_import') ->table('vw_legacy_condomin_nominativi') ->where('cod_stabile', $codStabile) @@ -874,7 +882,7 @@ public function table(Table $table): Table ->label('Anno legacy') ->visible(fn(): bool => (bool) $useLegacy) ->options(function () use ($codStabile): array { - if ($codStabile === '') { + if ($codStabile === '' || ! DbSchema::connection('gescon_import')->hasTable('condomin')) { return []; } return DB::connection('gescon_import') diff --git a/app/Filament/Pages/Fornitore/LavorazioniOperative.php b/app/Filament/Pages/Fornitore/LavorazioniOperative.php index 09722c1..30ad2a9 100755 --- a/app/Filament/Pages/Fornitore/LavorazioniOperative.php +++ b/app/Filament/Pages/Fornitore/LavorazioniOperative.php @@ -8,6 +8,8 @@ use App\Models\TicketIntervento; use App\Models\User; use BackedEnum; +use Carbon\Carbon; +use Filament\Notifications\Notification; use Filament\Pages\Page; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Auth; @@ -37,13 +39,43 @@ class LavorazioniOperative extends Page public bool $missingAdminContext = false; + /** @var array */ + public array $fornitoriOptions = []; + + // Main view navigation: 'elenco' or 'scheda' + public string $mainTab = 'elenco'; + + // Sub-tab inside the Scheda detail view (reproducing TecnoRepair desktop tabs) + public string $schedaSubTab = 'entrata'; + + public ?int $selectedRowId = null; + + public ?string $selectedRowType = null; // 'tecnorepair' or 'ticket' + + /** @var array|null */ + public ?array $activeScheda = null; + + /** @var array Editable form fields for active scheda */ + public array $schedaForm = []; + + /** @var array 3x3 pattern unlock grid nodes */ + public array $unlockPattern = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // Quick workflow inputs + public string $workflowNote = ''; + public string $workflowRmaCode = ''; + + // Filter properties public string $scope = 'tutte'; + public string $search = ''; + public string $filterStato = ''; + public string $filterOrigine = ''; + public string $dataDa = ''; + public string $dataA = ''; /** @var array> */ public array $rows = []; - public ?array $detailModal = null; - /** @var array */ public array $totals = [ 'tutte' => 0, @@ -53,22 +85,27 @@ class LavorazioniOperative extends Page 'interne' => 0, ]; + // Legacy modal compatibility + public ?array $detailModal = null; + public static function canAccess(): bool { $user = Auth::user(); return $user instanceof User - && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore', 'fornitore']); + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore', 'fornitore']); } - /** @var array */ - public array $fornitoriOptions = []; - public function mount(): void { $this->scope = (string) request()->query('scope', 'tutte'); [$fornitore] = $this->resolveOperatoreContext(allowAdminWithoutSupplier: true); + if (! $fornitore instanceof Fornitore) { + $fornitore = Fornitore::query()->where('partita_iva', '10055221005')->first() + ?? Fornitore::query()->orderBy('id')->first(); + } + if (! $fornitore instanceof Fornitore) { $this->missingAdminContext = true; return; @@ -84,7 +121,7 @@ public function mount(): void $q->whereHas('ticketInterventi') ->orWhere('partita_iva', '10055221005') ->orWhere('partita_iva', '14001151001') - ->orWhereIn('id', [236, 392]); + ->orWhereIn('id', [236, 359, 392]); }) ->orderBy('ragione_sociale') ->get() @@ -102,7 +139,7 @@ public function updatedFornitoreId(): void $fornitore = Fornitore::query()->find((int) $this->fornitoreId); if ($fornitore instanceof Fornitore) { $this->fornitoreLabel = $this->getFornitoreLabel($fornitore); - $this->detailModal = null; + $this->closeScheda(); $this->refreshData(); } } @@ -115,6 +152,28 @@ public function updatedScope(): void public function setScope(string $scope): void { $this->scope = $scope; + $this->mainTab = 'elenco'; + $this->refreshData(); + } + + public function setMainTab(string $tab): void + { + $this->mainTab = in_array($tab, ['elenco', 'scheda'], true) ? $tab : 'elenco'; + } + + public function setSchedaSubTab(string $subTab): void + { + $this->schedaSubTab = $subTab; + } + + public function resetFiltri(): void + { + $this->search = ''; + $this->filterStato = ''; + $this->filterOrigine = ''; + $this->dataDa = ''; + $this->dataA = ''; + $this->scope = 'tutte'; $this->refreshData(); } @@ -126,6 +185,14 @@ public function refreshData(): void } [$fornitore, $dipendente] = $this->resolveOperatoreContext($this->fornitoreId); + if (! $fornitore instanceof Fornitore) { + $fornitore = Fornitore::query()->find($this->fornitoreId); + } + + if (! $fornitore instanceof Fornitore) { + $this->rows = []; + return; + } $baseQuery = $this->buildBaseQuery($fornitore, $dipendente); @@ -146,17 +213,20 @@ public function refreshData(): void } $ticketRows = $baseQuery - ->limit(120) + ->limit(150) ->get() ->map(function (TicketIntervento $intervento): array { $base = $this->buildInterventoRow($intervento); + $isClosed = $this->isClosedStatus((string) $intervento->stato); return array_merge($base, [ 'row_type' => 'ticket', 'row_class' => $this->resolveTicketRowClass((string) $intervento->stato), 'badge_class' => $this->resolveTicketBadgeClass((string) $intervento->stato), - 'is_closed' => $this->isClosedStatus((string) $intervento->stato), + 'row_color' => $isClosed ? 'green' : 'blue', + 'is_closed' => $isClosed, 'numero' => (string) $intervento->ticket_id, + 'numero_scheda'=> (string) $intervento->ticket_id, 'cliente' => $base['contatto'], 'difetto' => $base['problema'], 'brand' => $this->extractDeviceBrand($base['apparato']), @@ -167,6 +237,7 @@ public function refreshData(): void 'ticket_id' => (int) $intervento->ticket_id, 'titolo' => (string) ($intervento->ticket->titolo ?? '-'), 'stato' => (string) $intervento->stato, + 'status_code' => (string) $intervento->stato, 'stabile' => (string) ($intervento->ticket->stabile->denominazione ?? '-'), 'operatore' => (string) $intervento->operatore_assegnato_label, 'tempo_minuti' => (int) ($intervento->tempo_minuti ?? 0), @@ -187,98 +258,319 @@ public function refreshData(): void $this->totals['aperte'] = count(array_filter($rows, fn(array $row): bool => ! (bool) ($row['is_closed'] ?? false))); $this->totals['chiuse'] = count(array_filter($rows, fn(array $row): bool => (bool) ($row['is_closed'] ?? false))); + // Scope filter if ($this->scope === 'aperte') { $rows = array_values(array_filter($rows, fn(array $row): bool => ! (bool) ($row['is_closed'] ?? false))); } elseif ($this->scope === 'chiuse') { $rows = array_values(array_filter($rows, fn(array $row): bool => (bool) ($row['is_closed'] ?? false))); + } elseif ($this->scope === 'ticket_amministratore') { + $rows = array_values(array_filter($rows, fn(array $row): bool => ($row['row_type'] ?? '') === 'ticket')); } - $this->rows = array_slice($rows, 0, 150); + // Additional filters + if (trim($this->search) !== '') { + $term = strtolower(trim($this->search)); + $rows = array_values(array_filter($rows, function (array $row) use ($term): bool { + return str_contains(strtolower((string) ($row['numero'] ?? '')), $term) + || str_contains(strtolower((string) ($row['cliente'] ?? '')), $term) + || str_contains(strtolower((string) ($row['telefono'] ?? '')), $term) + || str_contains(strtolower((string) ($row['modello'] ?? '')), $term) + || str_contains(strtolower((string) ($row['seriale'] ?? '')), $term) + || str_contains(strtolower((string) ($row['difetto'] ?? '')), $term) + || str_contains(strtolower((string) ($row['brand'] ?? '')), $term) + || str_contains(strtolower((string) ($row['cod_prodotto'] ?? '')), $term); + })); + } + + if (trim($this->filterStato) !== '' && $this->filterStato !== '0') { + $rows = array_values(array_filter($rows, fn(array $r): bool => (string) ($r['status_code'] ?? '') === (string) $this->filterStato || str_contains(strtolower((string) ($r['stato'] ?? '')), strtolower($this->filterStato)))); + } + + if (trim($this->filterOrigine) !== '') { + $rows = array_values(array_filter($rows, fn(array $r): bool => ($r['row_type'] ?? '') === $this->filterOrigine)); + } + + $this->rows = array_slice($rows, 0, 160); } public function openRowDetail(string $rowType, int $rowId): void { - if (! $this->fornitoreId) { - return; - } + $this->selectedRowType = $rowType; + $this->selectedRowId = $rowId; if ($rowType === 'tecnorepair') { - $scheda = AssistenzaTecnorepairScheda::query() - ->where('fornitore_id', (int) $this->fornitoreId) - ->find($rowId); - + $scheda = AssistenzaTecnorepairScheda::query()->find($rowId); if (! $scheda instanceof AssistenzaTecnorepairScheda) { return; } - $metadata = is_array($scheda->metadata ?? null) ? $scheda->metadata : []; - $apparecchio = array_filter([ - 'Numero scheda' => (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id ?: '-'), - 'Codice prodotto' => (string) ($scheda->product_code ?: '-'), - 'Modello' => (string) ($scheda->product_model ?: '-'), - 'Seriale 1' => (string) ($scheda->serial_number ?: '-'), - 'Seriale 2' => (string) ($scheda->serial_number_2 ?: '-'), - 'Numero ordine' => (string) ($scheda->order_number ?: '-'), - 'RMA' => (string) ($scheda->rma_code ?: '-'), - 'PIN' => (string) ($scheda->pin_code ?: '-'), - 'Unlock' => (string) ($scheda->unlock_code ?: '-'), - ], static fn(string $value): bool => trim($value) !== ''); + $metadata = is_array($scheda->metadata ?? null) ? $scheda->metadata : []; + $raw = is_array($metadata['raw'] ?? null) ? $metadata['raw'] : []; + $cliente = is_array($metadata['cliente'] ?? null) ? $metadata['cliente'] : []; - $this->detailModal = [ - 'type' => 'tecnorepair', - 'title' => (string) $scheda->display_title, - 'status' => (string) ($scheda->status_label ?: $scheda->status_bucket ?: '-'), - 'customer' => (string) ($scheda->customer_name ?: 'Cliente TecnoRepair'), - 'phone' => (string) ($scheda->customer_phone ?: $scheda->customer_phone_alt ?: ''), - 'email' => (string) ($scheda->customer_email ?: ''), - 'difetto_segnalato' => (string) ($scheda->defect_reported ?: '-'), - 'note_interne' => trim((string) (($metadata['NoteInterne'] ?? $metadata['note_interne'] ?? $scheda->communications) ?: '')), - 'note_stampa' => trim((string) (($metadata['NoteStampa'] ?? $metadata['note_stampa'] ?? $scheda->repair_description) ?: '')), - 'communications' => (string) ($scheda->communications ?: ''), - 'repair_description' => (string) ($scheda->repair_description ?: ''), - 'operator' => (string) ($scheda->technician_name ?: $scheda->operator_name ?: '-'), - 'updated_at' => optional($scheda->updated_at)->format('d/m/Y H:i') ?: '-', - 'apparecchio_fields' => $apparecchio, - 'metadata' => $metadata, + // Segno di sblocco (pattern grid 3x3) + $segnoStr = trim((string) ($raw['SegnoDiSblocco'] ?? '')); + $segnoParts = preg_split('/\s+/', $segnoStr); + $pattern = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + if (is_array($segnoParts) && count($segnoParts) >= 9) { + for ($i = 0; $i < 9; $i++) { + $pattern[$i] = (int) ($segnoParts[$i] ?? 0); + } + } + $this->unlockPattern = $pattern; + + $this->activeScheda = [ + 'type' => 'tecnorepair', + 'id' => (int) $scheda->id, + 'legacy_id' => (int) ($scheda->legacy_id ?? 0), + 'numero_scheda' => (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id ?: '-'), + 'title' => (string) $scheda->display_title, + 'status' => (string) ($scheda->status_label ?: $scheda->status_bucket ?: '-'), + 'status_code' => (string) ($scheda->status_code ?: '21'), + 'customer_name' => (string) ($scheda->customer_name ?: ($cliente['NomeCognome'] ?? 'Cliente TecnoRepair')), + 'customer_phone' => (string) ($scheda->customer_phone ?: ($cliente['NumeroTelefono'] ?? '')), + 'customer_phone_alt' => (string) ($scheda->customer_phone_alt ?: ($cliente['TelFisso'] ?? '')), + 'customer_email' => (string) ($scheda->customer_email ?: ($cliente['Email'] ?? '')), + 'customer_address' => (string) ($cliente['Indirizzo'] ?? ''), + 'customer_city' => (string) ($cliente['Citta'] ?? ''), + 'product_type' => (string) ($raw['Cod_TipoApparecchio'] ?? 'Apparecchio Elettronico'), + 'brand' => (string) ($this->extractDeviceBrand((string) ($scheda->product_model ?: ''))), + 'product_model' => (string) ($scheda->product_model ?: ($raw['Modello'] ?? '-')), + 'product_code' => (string) ($scheda->product_code ?: ($raw['CodiceProdotto'] ?? '-')), + 'serial_number' => (string) ($scheda->serial_number ?: ($raw['SerialNumber'] ?? '-')), + 'serial_number_2' => (string) ($scheda->serial_number_2 ?: ($raw['SerialNumber2'] ?? '-')), + 'serial_number_3' => (string) ($raw['SerialNumber3'] ?? '-'), + 'tag' => (string) ($raw['Tag'] ?? '-'), + 'accessories' => (string) ($raw['AccessoriConsegnati'] ?? 'NESSUNO'), + 'location' => (string) ($raw['Locazione'] ?? ($raw['Cod_Ubicazione'] ?? 'Laboratorio')), + 'technician' => (string) ($scheda->technician_name ?: ($raw['NomeTecnicoRiparatore'] ?? 'Tecnico')), + 'operator' => (string) ($scheda->operator_name ?: ($raw['NomeOperatore'] ?? 'Operatore')), + 'defect_reported' => (string) ($scheda->defect_reported ?: ($raw['DifettoSegnalato'] ?? '-')), + 'customer_requests' => (string) ($raw['RiparazioneRichiesta'] ?? '-'), + 'general_state' => (string) ($raw['StatoGenerale'] ?? '-'), + 'pin_code' => (string) ($scheda->pin_code ?: ($raw['CodicePIN'] ?? '-')), + 'unlock_code' => (string) ($scheda->unlock_code ?: ($raw['CodiceSblocco'] ?? '-')), + 'repair_description' => (string) ($scheda->repair_description ?: ($raw['DescrizioneRiparazione'] ?? '')), + 'note_interne' => (string) ($raw['NoteInterne'] ?? ''), + 'note_stampa' => (string) ($raw['NoteStampa'] ?? ''), + 'communications' => (string) ($scheda->communications ?: ($raw['Comunicazioni'] ?? '')), + 'date_received' => optional($scheda->date_received)->format('d/m/Y') ?: '-', + 'time_received' => (string) ($raw['OrarioIngresso'] ?? '10:00'), + 'date_delivery_prev' => ! empty($raw['DataPrevRip']) ? Carbon::parse($raw['DataPrevRip'])->format('d/m/Y') : '-', + 'date_repaired' => ! empty($raw['DataRiparazione']) ? Carbon::parse($raw['DataRiparazione'])->format('d/m/Y') : '-', + 'date_returned' => ! empty($raw['DataRiconsegna']) ? Carbon::parse($raw['DataRiconsegna'])->format('d/m/Y') : '-', + 'is_fare_preventivo' => (bool) ($raw['isFarePreventivo'] ?? false), + 'is_riparazione_sede' => (bool) ($raw['isRiparazioneInSede'] ?? false), + 'is_riconsegnato' => (bool) ($raw['isRiconsegnato'] ?? false), + 'is_rientro' => (bool) ($raw['Rientro'] ?? false), + 'is_esame_tecnico' => (bool) ($raw['isEsameTecnico'] ?? false), + 'consegnato_no_doc' => (bool) ($raw['ConsegnatoSenzaDoc'] ?? false), + 'costo_preventivo' => (string) ($raw['CostoPreventivo'] ?? '0.00'), + 'costo_subito' => (string) ($raw['CostoSubito'] ?? '0.00'), + 'costo_addebitato' => (string) ($raw['CostoAddebitato'] ?? '0.00'), + 'acconto' => (string) ($raw['AccontoRiparazione'] ?? '0.00'), + 'rif_ddt_entrata' => (string) ($raw['RifDDTEntrata'] ?? '-'), + 'rif_ddt_uscita' => (string) ($raw['RifDDTUscita'] ?? '-'), + 'sost_marca_modello' => (string) ($raw['Sost_MarcaModello'] ?? '-'), + 'sost_seriale' => (string) ($raw['Sost_SerialNumber'] ?? '-'), + 'sost_stato' => (string) ($raw['Sost_StatoGenerale'] ?? '-'), + 'updated_at' => optional($scheda->updated_at)->format('d/m/Y H:i') ?: '-', + 'metadata' => $metadata, ]; + $this->schedaForm = [ + 'status_code' => (string) ($scheda->status_code ?: '21'), + 'status_label' => (string) ($scheda->status_label ?: 'IN RIPARAZIONE'), + 'is_fare_preventivo' => (bool) ($raw['isFarePreventivo'] ?? false), + 'is_riparazione_sede'=> (bool) ($raw['isRiparazioneInSede'] ?? false), + 'is_riconsegnato' => (bool) ($raw['isRiconsegnato'] ?? false), + 'is_rientro' => (bool) ($raw['Rientro'] ?? false), + 'is_esame_tecnico' => (bool) ($raw['isEsameTecnico'] ?? false), + 'consegnato_no_doc' => (bool) ($raw['ConsegnatoSenzaDoc'] ?? false), + 'cliente_nome' => (string) ($scheda->customer_name ?: ($cliente['NomeCognome'] ?? '')), + 'cliente_telefono' => (string) ($scheda->customer_phone ?: ($cliente['NumeroTelefono'] ?? '')), + 'cliente_tel_fisso' => (string) ($scheda->customer_phone_alt ?: ($cliente['TelFisso'] ?? '')), + 'cliente_email' => (string) ($scheda->customer_email ?: ($cliente['Email'] ?? '')), + 'cliente_indirizzo' => (string) ($cliente['Indirizzo'] ?? ''), + 'cliente_citta' => (string) ($cliente['Citta'] ?? ''), + 'difetto_segnalato' => (string) ($scheda->defect_reported ?: ($raw['DifettoSegnalato'] ?? '')), + 'richieste_cliente' => (string) ($raw['RiparazioneRichiesta'] ?? ''), + 'stato_generale' => (string) ($raw['StatoGenerale'] ?? ''), + 'pin_code' => (string) ($scheda->pin_code ?: ($raw['CodicePIN'] ?? '')), + 'unlock_code' => (string) ($scheda->unlock_code ?: ($raw['CodiceSblocco'] ?? '')), + 'repair_description' => (string) ($scheda->repair_description ?: ($raw['DescrizioneRiparazione'] ?? '')), + 'note_interne' => (string) ($raw['NoteInterne'] ?? ''), + 'note_stampa' => (string) ($raw['NoteStampa'] ?? ''), + 'communications' => (string) ($scheda->communications ?: ($raw['Comunicazioni'] ?? '')), + 'tecnico' => (string) ($scheda->technician_name ?: ($raw['NomeTecnicoRiparatore'] ?? '')), + 'costo_preventivo' => (string) ($raw['CostoPreventivo'] ?? '0.00'), + 'costo_subito' => (string) ($raw['CostoSubito'] ?? '0.00'), + 'costo_addebitato' => (string) ($raw['CostoAddebitato'] ?? '0.00'), + 'acconto' => (string) ($raw['AccontoRiparazione'] ?? '0.00'), + ]; + + $this->schedaSubTab = 'entrata'; + $this->mainTab = 'scheda'; + $this->detailModal = null; return; } + // TICKET AMMINISTRATORE [$fornitore, $dipendente] = $this->resolveOperatoreContext((int) $this->fornitoreId); - $intervento = $this->buildBaseQuery($fornitore, $dipendente)->find($rowId); if (! $intervento instanceof TicketIntervento) { return; } - $base = $this->buildInterventoRow($intervento); - $this->detailModal = [ + $base = $this->buildInterventoRow($intervento); + $this->activeScheda = [ 'type' => 'ticket', + 'id' => (int) $intervento->id, + 'numero_scheda' => 'TK-' . (int) $intervento->ticket_id, 'title' => 'Ticket #' . (int) $intervento->ticket_id . ' · ' . (string) ($intervento->ticket->titolo ?? '-'), 'status' => (string) $intervento->stato, - 'customer' => $base['contatto'], - 'phone' => $base['telefono'], - 'email' => '', - 'difetto_segnalato' => $base['problema'], + 'status_code' => (string) $intervento->stato, + 'customer_name' => $base['contatto'], + 'customer_phone' => $base['telefono'], + 'customer_email' => '', + 'customer_address' => (string) ($intervento->ticket->stabile->indirizzo ?? ''), + 'customer_city' => (string) ($intervento->ticket->stabile->citta ?? ''), + 'brand' => $this->extractDeviceBrand($base['apparato']), + 'product_model' => $base['apparato'], + 'product_code' => '-', + 'serial_number' => '-', + 'stabile' => (string) ($intervento->ticket->stabile->denominazione ?? '-'), + 'unita' => (string) ($intervento->ticket->unitaImmobiliare->denominazione ?? '-'), + 'operator' => (string) $intervento->operatore_assegnato_label, + 'defect_reported' => $base['problema'], + 'repair_description' => (string) ($intervento->rapporto_fornitore ?? ''), 'note_interne' => (string) ($intervento->rapporto_fornitore ?? ''), 'note_stampa' => (string) ($intervento->ticket->descrizione ?? ''), 'communications' => '', - 'repair_description' => '', - 'operator' => (string) $intervento->operatore_assegnato_label, 'updated_at' => optional($intervento->updated_at)->format('d/m/Y H:i') ?: '-', 'url' => TicketInterventoScheda::getUrl(['record' => (int) $intervento->id], panel: 'admin-filament'), - 'apparecchio_fields' => [ - 'Apparato' => $base['apparato'], - 'Stabile' => (string) ($intervento->ticket->stabile->denominazione ?? '-'), - ], 'metadata' => [], ]; + + $this->schedaForm = [ + 'status_code' => (string) $intervento->stato, + 'repair_description' => (string) ($intervento->rapporto_fornitore ?? ''), + 'note_interne' => (string) ($intervento->rapporto_fornitore ?? ''), + ]; + + $this->schedaSubTab = 'entrata'; + $this->mainTab = 'scheda'; + $this->detailModal = null; + } + + public function togglePatternDot(int $index): void + { + if (isset($this->unlockPattern[$index])) { + $this->unlockPattern[$index] = $this->unlockPattern[$index] === 1 ? 0 : 1; + } + } + + public function saveActiveScheda(): void + { + if (! $this->selectedRowId) { + return; + } + + if ($this->selectedRowType === 'tecnorepair') { + $scheda = AssistenzaTecnorepairScheda::query()->find($this->selectedRowId); + if (! $scheda instanceof AssistenzaTecnorepairScheda) { + return; + } + + $scheda->defect_reported = $this->schedaForm['difetto_segnalato'] ?? $scheda->defect_reported; + $scheda->repair_description = $this->schedaForm['repair_description'] ?? $scheda->repair_description; + $scheda->communications = $this->schedaForm['communications'] ?? $scheda->communications; + $scheda->pin_code = $this->schedaForm['pin_code'] ?? $scheda->pin_code; + $scheda->unlock_code = $this->schedaForm['unlock_code'] ?? $scheda->unlock_code; + $scheda->technician_name = $this->schedaForm['tecnico'] ?? $scheda->technician_name; + $scheda->status_code = (string) ($this->schedaForm['status_code'] ?? $scheda->status_code); + $scheda->status_label = $this->resolveStatusLabel((string) $scheda->status_code); + $scheda->status_bucket = AssistenzaTecnorepairScheda::normalizeStatusBucket($scheda->status_label); + + $metadata = is_array($scheda->metadata ?? null) ? $scheda->metadata : []; + $raw = is_array($metadata['raw'] ?? null) ? $metadata['raw'] : []; + $cliente = is_array($metadata['cliente'] ?? null) ? $metadata['cliente'] : []; + + $raw['isFarePreventivo'] = ! empty($this->schedaForm['is_fare_preventivo']) ? '1' : '0'; + $raw['isRiparazioneInSede'] = ! empty($this->schedaForm['is_riparazione_sede']) ? '1' : '0'; + $raw['isRiconsegnato'] = ! empty($this->schedaForm['is_riconsegnato']) ? '1' : '0'; + $raw['Rientro'] = ! empty($this->schedaForm['is_rientro']) ? '1' : '0'; + $raw['isEsameTecnico'] = ! empty($this->schedaForm['is_esame_tecnico']) ? '1' : '0'; + $raw['ConsegnatoSenzaDoc'] = ! empty($this->schedaForm['consegnato_no_doc']) ? '1' : '0'; + $raw['StatoGenerale'] = (string) ($this->schedaForm['stato_generale'] ?? ''); + $raw['RiparazioneRichiesta'] = (string) ($this->schedaForm['richieste_cliente'] ?? ''); + $raw['NoteInterne'] = (string) ($this->schedaForm['note_interne'] ?? ''); + $raw['NoteStampa'] = (string) ($this->schedaForm['note_stampa'] ?? ''); + $raw['SegnoDiSblocco'] = implode(' ', $this->unlockPattern); + + $cliente['NomeCognome'] = (string) ($this->schedaForm['cliente_nome'] ?? ($cliente['NomeCognome'] ?? '')); + $cliente['NumeroTelefono'] = (string) ($this->schedaForm['cliente_telefono'] ?? ($cliente['NumeroTelefono'] ?? '')); + $cliente['TelFisso'] = (string) ($this->schedaForm['cliente_tel_fisso'] ?? ($cliente['TelFisso'] ?? '')); + $cliente['Email'] = (string) ($this->schedaForm['cliente_email'] ?? ($cliente['Email'] ?? '')); + $cliente['Indirizzo'] = (string) ($this->schedaForm['cliente_indirizzo'] ?? ($cliente['Indirizzo'] ?? '')); + $cliente['Citta'] = (string) ($this->schedaForm['cliente_citta'] ?? ($cliente['Citta'] ?? '')); + + $metadata['raw'] = $raw; + $metadata['cliente'] = $cliente; + $scheda->metadata = $metadata; + + $scheda->save(); + + Notification::make() + ->title('Scheda #' . ($scheda->legacy_numero_scheda ?: $scheda->legacy_id) . ' salvata con successo') + ->success() + ->send(); + + $this->openRowDetail('tecnorepair', (int) $scheda->id); + $this->refreshData(); + return; + } + + if ($this->selectedRowType === 'ticket') { + $intervento = TicketIntervento::query()->find($this->selectedRowId); + if ($intervento instanceof TicketIntervento) { + $intervento->rapporto_fornitore = (string) ($this->schedaForm['repair_description'] ?? $intervento->rapporto_fornitore); + if (! empty($this->schedaForm['status_code'])) { + $intervento->stato = $this->schedaForm['status_code']; + } + $intervento->save(); + + Notification::make() + ->title('Ticket intervento salvato con successo') + ->success() + ->send(); + + $this->openRowDetail('ticket', (int) $intervento->id); + $this->refreshData(); + } + } + } + + public function cambiaStatoRapido(string $code): void + { + $this->schedaForm['status_code'] = $code; + if ($code === '4') { // Riparato + $this->schedaForm['is_riconsegnato'] = false; + } elseif ($code === '23') { // Rottamazione + $this->schedaForm['is_riconsegnato'] = false; + } + $this->saveActiveScheda(); + } + + public function closeScheda(): void + { + $this->mainTab = 'elenco'; + $this->detailModal = null; } public function closeDetailModal(): void { - $this->detailModal = null; + $this->closeScheda(); } public function getTicketsUrl(): string @@ -345,10 +637,15 @@ protected function buildBaseQuery(Fornitore $fornitore, ?FornitoreDipendente $di protected function buildTecnorepairQuery(Fornitore $fornitore): Builder { + $fornitoreIds = [(int) $fornitore->id]; + if (in_array((int) $fornitore->id, [236, 359], true) || str_contains(strtoupper($fornitore->ragione_sociale), 'NETHOME')) { + $fornitoreIds = [236, 359]; + } + return AssistenzaTecnorepairScheda::query() - ->where('fornitore_id', (int) $fornitore->id) + ->whereIn('fornitore_id', $fornitoreIds) ->orderByDesc('date_received') - ->orderByDesc('updated_at'); + ->orderByDesc('id'); } /** @@ -357,15 +654,30 @@ protected function buildTecnorepairQuery(Fornitore $fornitore): Builder protected function buildTecnorepairRows(Fornitore $fornitore): array { return $this->buildTecnorepairQuery($fornitore) - ->limit(120) + ->limit(200) ->get() ->map(function (AssistenzaTecnorepairScheda $scheda): array { + $raw = data_get($scheda->metadata, 'raw', []); + $isRiconsegnato = (bool) ($raw['isRiconsegnato'] ?? false); + $isRiparato = ((string) $scheda->status_code === '4') || (bool) ($raw['isRiparato'] ?? false); + $isClosed = (bool) ($scheda->is_closed ?? false) || $isRiconsegnato || $isRiparato || in_array((string) $scheda->status_code, ['4', '10', '23', '31', '37'], true); + + $rowColor = match ((string) $scheda->status_code) { + '4' => 'green', + '10', '23' => 'red', + '2', '21' => 'blue', + '5', '6', '33' => 'amber', + default => $isClosed ? 'green' : 'blue', + }; + return [ 'row_type' => 'tecnorepair', 'row_class' => (string) $scheda->row_classes, 'badge_class' => (string) $scheda->status_badge_classes, - 'is_closed' => (bool) ($scheda->is_closed ?? false), + 'row_color' => $rowColor, + 'is_closed' => $isClosed, 'numero' => (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id), + 'numero_scheda'=> (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id), 'ingresso' => optional($scheda->date_received)->format('d/m/Y') ?: '-', 'cliente' => (string) ($scheda->customer_name ?: 'Cliente TecnoRepair'), 'contatto' => (string) ($scheda->customer_name ?: 'Cliente TecnoRepair'), @@ -381,6 +693,7 @@ protected function buildTecnorepairRows(Fornitore $fornitore): array 'ticket_id' => null, 'titolo' => (string) $scheda->display_title, 'stato' => (string) ($scheda->status_label ?: $scheda->status_bucket ?: '-'), + 'status_code' => (string) ($scheda->status_code ?: ''), 'stabile' => 'Archivio TecnoRepair', 'operatore' => (string) ($scheda->technician_name ?: $scheda->operator_name ?: '-'), 'tempo_minuti' => 0, @@ -420,10 +733,10 @@ protected function isClosedStatus(string $status): bool $status = strtolower(trim($status)); return str_contains($status, 'chius') - || str_contains($status, 'fatturat') - || str_contains($status, 'riconsegn') - || str_contains($status, 'riparato') - || str_contains($status, 'acquisto'); + || str_contains($status, 'fatturat') + || str_contains($status, 'riconsegn') + || str_contains($status, 'riparato') + || str_contains($status, 'acquisto'); } protected function extractDeviceBrand(string $label): string @@ -438,4 +751,22 @@ protected function extractDeviceBrand(string $label): string return $first !== '' ? strtoupper($first) : '-'; } + + protected function resolveStatusLabel(string $code): string + { + return match ($code) { + '0' => 'TUTTI', + '4' => 'RIPARATO', + '21' => 'INGRESSO ACCETTAZIONE', + '2' => 'DISPOSITIVO IN RIPARAZIONE', + '5' => 'ATTESA RICAMBIO', + '6' => 'INVIATO NUOVO PREVENTIVO', + '10' => 'DISPOSITIVO IRRIPARABILE', + '23' => 'PC DA UTILIZZARE COME RICAMBI', + '33' => 'MANDATO IN GARANZIA AL PRODUTTORE', + '31' => 'ACQUISTO RICONDIZIONATO', + '37' => 'SMALTITO PER ABBANDONO', + default => 'IN LAVORAZIONE (' . $code . ')', + }; + } } diff --git a/app/Filament/Pages/Gescon/Ordinarie.php b/app/Filament/Pages/Gescon/Ordinarie.php index e12c373..7230df4 100755 --- a/app/Filament/Pages/Gescon/Ordinarie.php +++ b/app/Filament/Pages/Gescon/Ordinarie.php @@ -835,7 +835,7 @@ public function getOperazioniProperty() $vociMap = []; $vociTabellaMap = []; - if (! empty($codSpe)) { + if (! empty($codSpe) && Schema::connection('gescon_import')->hasTable('voc_spe')) { $vociRows = DB::connection('gescon_import') ->table('voc_spe') ->whereIn('cod', $codSpe) @@ -866,7 +866,7 @@ public function getOperazioniProperty() } $tabelleMap = []; - if (! empty($codTab)) { + if (! empty($codTab) && Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) { $tabelleMap = DB::connection('gescon_import') ->table('tabelle_millesimali') ->whereIn('cod_tabella', $codTab) @@ -1528,7 +1528,7 @@ public function getConsuntivoProperty(): array $codes = $allRows->pluck('cod_spe')->filter()->unique()->values()->all(); $vociMap = []; $tabellaByCod = []; - if (! empty($codes)) { + if (! empty($codes) && Schema::connection('gescon_import')->hasTable('voc_spe')) { $vocRows = DB::connection('gescon_import') ->table('voc_spe') ->whereIn('cod', $codes) @@ -1543,7 +1543,7 @@ public function getConsuntivoProperty(): array $tabellaMap = []; $tabellaInfo = []; - if (! empty($tabCodes)) { + if (! empty($tabCodes) && Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) { $tabellaMap = DB::connection('gescon_import') ->table('tabelle_millesimali') ->whereIn('cod_tabella', $tabCodes) diff --git a/app/Services/Tecnorepair/TecnoRepairArchiveService.php b/app/Services/Tecnorepair/TecnoRepairArchiveService.php index a283b09..6152480 100644 --- a/app/Services/Tecnorepair/TecnoRepairArchiveService.php +++ b/app/Services/Tecnorepair/TecnoRepairArchiveService.php @@ -55,6 +55,8 @@ public function resolveMdbPath(?string $candidatePath = null, ?Fornitore $fornit // Candidates for fallback $fallbackCandidates = [ + '/mnt/cservergo/LunaSoftware_TecnoRepair/Archivi/TecnoRepairDB.mdb', + storage_path('app/tecnorepair/TecnoRepairDB.mdb'), self::DEFAULT_LOCAL_PATH, '/home/michele/MIki/netgescon-day0-backup/Miki-Bug-workspace/screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb', '/mnt/gescon-archives/TecnoRepairDB.mdb', diff --git a/resources/views/filament/pages/affitti/gestione-affitti.blade.php b/resources/views/filament/pages/affitti/gestione-affitti.blade.php index c982a96..4bf0307 100755 --- a/resources/views/filament/pages/affitti/gestione-affitti.blade.php +++ b/resources/views/filament/pages/affitti/gestione-affitti.blade.php @@ -786,10 +786,8 @@ class="inline-flex items-center gap-1 rounded-lg border border-amber-300 bg-ambe @forelse($canoniRighe as $r) - @php - $d = (float)($r['totale_dovuto'] ?? 0); - $p = (float)($r['totale_pagato'] ?? 0); - @endphp + @php($d = (float)($r['totale_dovuto'] ?? 0)) + @php($p = (float)($r['totale_pagato'] ?? 0)) {{ $r['mese_descrizione'] ?? ($r['mese'] ?? '') }} {{ $r['anno'] ?? '' }} diff --git a/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php b/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php index f58b45f..a0b5ba7 100755 --- a/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php +++ b/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php @@ -1,141 +1,353 @@
-
+ {{-- TOP HEADER CONTEXT TOOLBAR --}} +
-
-
Lavorazioni operative fornitore
-
- @if($this->fornitoreLabel) - Elenco unico operativo per {{ $this->fornitoreLabel }}. - @else - Seleziona un fornitore dalla gestione ticket per aprire questa vista. - @endif +
+
+ LO +
+
+
+

Lavorazioni Operative & Centro Assistenza

+ + Unificato + +
+
+ @if($this->fornitoreLabel) + Fornitore operativo: {{ $this->fornitoreLabel }} + @else + Seleziona un fornitore per visualizzare le lavorazioni operative. + @endif +
+
@if(count($this->fornitoriOptions) > 1)
- Fornitore: - @foreach($this->fornitoriOptions as $optId => $optLabel) @endforeach
@endif - 💻 Pratiche TecnoRepair - Ticket operativi - Collaboratori - Rubrica clienti - Impostazioni - Prodotti + + + 💻 Pratiche TecnoRepair + + + 🔍 Ricerca Seriali + + + 🎫 Ticket operativi + + + 👥 Collaboratori + + + 📖 Rubrica clienti + + + 📦 Prodotti & Ricambi +
+ {{-- MAIN VIEW TABS: ELENCO LAVORAZIONI vs SCHEDA APPARECCHIO --}} +
+
+ + + @if($selectedRowId && $activeScheda) + + @endif +
+ + @if($mainTab === 'scheda') +
+ + +
+ @endif +
+ @if($this->missingAdminContext)
Questa vista per amministratori richiede un fornitore selezionato.
@else -
- Prima base rilasciata: questa pagina unifica gia le lavorazioni arrivate dai ticket amministratore. - Le lavorazioni interne e i lavori ricorrenti verranno agganciati qui nello stesso elenco operativo. -
+ {{-- TAB 1: ELENCO LAVORAZIONI --}} + @if($mainTab === 'elenco') + {{-- KPI SUMMARY CARDS --}} +
+ -
- - - - -
+ - @if($scope === 'interne') -
- Nessuna lavorazione interna ancora modellata nel database corrente. - Il prossimo step e aggiungere qui inserimento, elenco e storico delle lavorazioni non nate da ticket amministratore. + + +
- @else -
-
- - + + {{-- FILTRI PER RICERCA ELENCO --}} +
+
+
+ 🔍 Filtra Lavorazioni Operative + Filtri rapidi reattivi +
+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + {{-- TABELLA ELENCO LAVORAZIONI --}} +
+
+
+ Lavorazioni Elencate: {{ count($rows) }} + (filtrate su base reattiva) +
+
+ Riparato + Irreparabile / Ricambi + Accettazione / In Rip. + Attesa Ricambio +
+
+ +
+
+ - - - - - - - - - - - - - - + + + + + + + + + + + + + - + @forelse($rows as $row) - - + - - - - + - - - - - + - - - - + + + + @empty - + @endforelse @@ -143,99 +355,878 @@ @endif + + {{-- TAB 2: SCHEDA APPARECCHIO / DETTAGLIO INTERVENTO (IN-PAGE FULL TAB) --}} + @if($mainTab === 'scheda' && $activeScheda) + @if(($activeScheda['type'] ?? '') === 'tecnorepair') + {{-- TECNOREPAIR DESKTOP-FAITHFUL SCHEDA --}} +
+ {{-- SCHEDA TOP BAR: WINDOW BANNER & STATUS --}} +
+
+ + SCHEDA APPARECCHIO #{{ $activeScheda['numero_scheda'] }} + +
+

+ {{ $activeScheda['brand'] }} {{ $activeScheda['product_model'] }} + ({{ $activeScheda['serial_number'] }}) +

+

+ Cliente: {{ $activeScheda['customer_name'] }} + — Data Ingresso: {{ $activeScheda['date_received'] }} ore {{ $activeScheda['time_received'] }} +

+
+
+ +
+ + Stato: {{ $activeScheda['status'] }} + + + +
+
+ + {{-- CONTROLLI INTESTAZIONE SCHEDA & BOX CLIENTE (FEDELE A TECNOREPAIR DESKTOP) --}} +
+ {{-- LEFT: DATE & GESTIONE INTERVENTO --}} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ + {{-- CHECKBOXES ROW (FLAGS TECNOREPAIR DESKTOP) --}} +
+ + + + + + + + + + + +
+
+ + {{-- RIGHT: BOX "DATI CLIENTE" (POSIZIONATO ESATTAMENTE COME SUL DESKTOP) --}} +
+
+
+ 👤 Dati Cliente +
+ @if($activeScheda['customer_phone']) + + @endif +
+ +
+
+ Nome / Ragione Sociale: + +
+
+
+ Cellulare: + +
+
+ Fisso: + +
+
+
+ Indirizzo & Città: +
+ + +
+
+
+
+
+ + {{-- SUB-TABS BAR (7 TABS TECNOREPAIR DESKTOP) --}} +
+ @php + $subTabs = [ + '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($subTabs as $tabKey => $tabLabel) + + @endforeach +
+ + {{-- SUB-TAB CONTENTS --}} +
+ {{-- SUB-TAB 1: APPARECCHIO IN ENTRATA --}} + @if($schedaSubTab === 'entrata') +
+ {{-- LEFT COLUMN: DEVICE SPECS & IDENTIFIERS --}} +
+
+ 💻 Specifiche Apparecchio + Identificatori univoci +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + {{-- RIGHT COLUMN: DEFECTS, NOTES & UNLOCK PATTERN MATRIX --}} +
+
+ + +
+ +
+ {{-- PIN & UNLOCK CODE --}} +
+
+ 🔐 Codici di Sblocco +
+
+ + +
+
+ + +
+
+ + {{-- PATTERN UNLOCK 3x3 MATRIX (ANDROID PATTERN GRID) --}} +
+
+ 📱 Matrice Sblocco (3x3) +
+
+ @for($dot = 0; $dot < 9; $dot++) + @php + $isActive = ($unlockPattern[$dot] ?? 0) === 1; + @endphp + + @endfor +
+
+ Fai clic sui pallini per comporre il segno +
+
+
+ +
+ + +
+
+
+ @endif + + {{-- SUB-TAB 2: RIPARAZIONE & FLUSSI AUTOMATIZZATI --}} + @if($schedaSubTab === 'riparazione') +
+ {{-- FLUSSI RAPIDI DI LAVORAZIONE --}} +
+
+ ⚡ AZIONI RAPIDE DI STATO & LAVORAZIONE +
+ +
+ {{-- 1. RIPARATO --}} + + + {{-- 2. IN RIPARAZIONE --}} + + + {{-- 3. ATTESA RICAMBIO --}} + + + {{-- 4. ROTTAMAZIONE / RICAMBI --}} + +
+
+ + {{-- DESCRIZIONE RIPARAZIONE / RAPPORTO TECNICO --}} +
+
+ 🛠️ Descrizione Riparazione & Intervento Eseguito +
+ +
+
+ @endif + + {{-- SUB-TAB 3: RICAMBI UTILIZZATI --}} + @if($schedaSubTab === 'ricambi') +
+
+
+ 📦 Ricambi & Componenti Collegati alla Scheda +
+ + + Consulta Catalogo Ricambi + +
+
+ Per questa scheda non risultano codici ricambio scaricati dall'archivio legacy. + I seriali impiegati possono essere ricercati tramite la sezione Ricerca Seriali. +
+
+ @endif + + {{-- SUB-TAB 4: PREVENTIVO - COSTI - DDT --}} + @if($schedaSubTab === 'preventivo') +
+
+
+ 💶 Riepilogo Economico Intervento +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ 📄 Riferimenti Documenti di Trasporto (DDT) +
+
+
+ + +
+
+ + +
+
+
+
+ @endif + + {{-- SUB-TAB 5: ANNOTAZIONI & CORTESIA --}} + @if($schedaSubTab === 'annotazioni') +
+
+
+ + +
+ +
+ + +
+
+ +
+
+ 📱 Apparecchio Sostitutivo di Cortesia +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ @endif + + {{-- SUB-TAB 6: COMUNICAZIONI --}} + @if($schedaSubTab === 'comunicazioni') +
+
+ 💬 Storico Comunicazioni & Avvisi al Cliente +
+ +
+ @endif + + {{-- SUB-TAB 7: CONTROLLO QUALITÀ (C.Q.) --}} + @if($schedaSubTab === 'cq') +
+
+ ✅ Checklist Controllo Qualità & Collaudo Finale (C.Q.) +
+
+
+ + Accensione & Boot Sistema OK +
+
+ + Display & Touchscreen Reattivi +
+
+ + Audio Capsula & Speaker OK +
+
+ + Ricarica Batteria & Alimentazione +
+
+ + Connettività Wi-Fi / BT / Rete +
+
+ + Pulizia & Sigillo Laboratorio +
+
+
+ @endif +
+ + {{-- BOTTOM ACTION TOOLBAR --}} +
+
+ + +
+ + +
+
+ @else + {{-- TICKET AMMINISTRATORE SCHEDA IN-PAGE VIEW --}} +
+
+
+ + {{ $activeScheda['numero_scheda'] }} + +
+

{{ $activeScheda['title'] }}

+

+ Stabile: {{ $activeScheda['stabile'] }} + — Richiedente: {{ $activeScheda['customer_name'] }} +

+
+
+ +
+ + Stato: {{ $activeScheda['status'] }} + + @if(($activeScheda['url'] ?? '') !== '') + + Apri Gestione Ticket ↗ + + @endif + +
+
+ +
+
+
+
+ 🏢 Dati Stabile & Contatto +
+
{{ $activeScheda['stabile'] }}
+
Unità: {{ $activeScheda['unita'] ?? '-' }}
+
Richiedente: {{ $activeScheda['customer_name'] }}
+
Telefono: {{ $activeScheda['customer_phone'] ?: '-' }}
+
Indirizzo: {{ $activeScheda['customer_address'] }}, {{ $activeScheda['customer_city'] }}
+
+ +
+
+ 📋 Stato Intervento +
+
+ + +
+
+ Operatore Assegnato: {{ $activeScheda['operator'] }} +
+
+
+ +
+
⚠️ Problema Segnalato nel Ticket
+

{{ $activeScheda['defect_reported'] }}

+
+ +
+
🛠️ Rapporto Intervento Fornitore
+ +
+
+ +
+ + + +
+
+ @endif + @endif @endif - - @if($detailModal) -
-
-
-
-
{{ $detailModal['title'] }}
-
Stato {{ $detailModal['status'] }} · aggiornato {{ $detailModal['updated_at'] }}
-
-
- @if(($detailModal['url'] ?? '') !== '') - Apri scheda completa - @endif - -
-
- -
-
-
-
-
-
-
Cliente
-
{{ $detailModal['customer'] !== '' ? $detailModal['customer'] : '-' }}
-
-
-
Telefono
-
{{ $detailModal['phone'] !== '' ? $detailModal['phone'] : '-' }}
-
-
-
Email
-
{{ $detailModal['email'] !== '' ? $detailModal['email'] : '-' }}
-
-
-
Operatore
-
{{ $detailModal['operator'] !== '' ? $detailModal['operator'] : '-' }}
-
-
-
- -
-
Campi lavorazione
-
-
-
DifettoSegnalato
-
{{ $detailModal['difetto_segnalato'] !== '' ? $detailModal['difetto_segnalato'] : '-' }}
-
-
-
NoteInterne
-
{{ $detailModal['note_interne'] !== '' ? $detailModal['note_interne'] : '-' }}
-
-
-
NoteStampa
-
{{ $detailModal['note_stampa'] !== '' ? $detailModal['note_stampa'] : '-' }}
-
-
- - @if(($detailModal['communications'] ?? '') !== '' || ($detailModal['repair_description'] ?? '') !== '') -
-
-
Comunicazioni
-
{{ $detailModal['communications'] !== '' ? $detailModal['communications'] : '-' }}
-
-
-
Descrizione riparazione
-
{{ $detailModal['repair_description'] !== '' ? $detailModal['repair_description'] : '-' }}
-
-
- @endif -
-
- -
-
-
Campi base TApparecchi
-
- @forelse($detailModal['apparecchio_fields'] as $label => $value) -
-
{{ $label }}
-
{{ $value !== '' ? $value : '-' }}
-
- @empty -
Nessun campo apparecchio disponibile.
- @endforelse -
-
-
-
-
-
-
- @endif \ No newline at end of file diff --git a/skill-netgescon/control-tower/CURRENT-205.md b/skill-netgescon/control-tower/CURRENT-205.md index dda287b..5402b6f 100644 --- a/skill-netgescon/control-tower/CURRENT-205.md +++ b/skill-netgescon/control-tower/CURRENT-205.md @@ -1,78 +1,65 @@ # CURRENT-205 -TASK_ID: task-email-service-password-reset +TASK_ID: task-lavorazioni-tecnorepair-tab-sync MACHINE: .205 STATO: completato ## Obiettivo Completato -1. **Attivazione e Diagnostica Servizio Posta SMTP (Google Workspace / Gmail)**: - - Configurato e bonificato `.env` con credenziali dedicate Google Workspace: - - `MAIL_MAILER=smtp` - - `MAIL_HOST=smtp.gmail.com` - - `MAIL_PORT=587` - - `MAIL_ENCRYPTION=tls` - - `MAIL_USERNAME=netgescon@gmail.com` - - `MAIL_FROM_ADDRESS=michele@netgescon.it` (NetGescon) - - Risolto il blocco delle email in coda: impostato `QUEUE_CONNECTION=sync` ed eliminata la dipendenza da daemon `queue:work` non presenti a sistema. - - Svuotata la tabella `jobs` evadendo le notifiche pendenti. +1. **Risoluzione Errore HTTP 500 Blade Lexer (`ParseError: unexpected token '=' / 'endforeach'`)**: + - Diagnosi: in `resources/views/filament/pages/affitti/gestione-affitti.blade.php`, un blocco `@php ... @endphp` multiriga dentro un `@forelse` entrava in conflitto con la regex interna di Blade `/(? **Blade templates cached successfully con zero errori**. -2. **Notifica di Reimpostazione Password Personalizzata in Italiano (`NetGesconResetPasswordNotification`)**: - - Creata la notifica `App\Notifications\NetGesconResetPasswordNotification`: - - Invio sincrono immediato (senza code asincrone). - - Testo in italiano, grafica formattata, pulsante "Reimposta Password", token univoco e link per copia-incolla con scadenza 60 minuti. - - Mittente ufficiale `michele@netgescon.it` (NetGescon). - - Registrato il binding in `AppServiceProvider` su `\Filament\Auth\Notifications\ResetPassword`. - - Agganciato il metodo `sendPasswordResetNotification()` su `App\Models\User`. +2. **Diagnosi e Risoluzione Discrepanza Schede TecnoRepair (1831 vs 1876)**: + - Diagnosi: il file locale di staging `screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb` era uno snapshot datato 1 aprile 2026 fermo all'ID 1834. + - Connessione al server di rete: montata la share live Windows `//192.168.0.36/CServerGO` in `/mnt/cservergo` con credenziali dedicate e persistita in `/etc/fstab`. + - Il database live contiene le schede aggiornate ad oggi (fino a ID legacy 1879, inclusa la #1876). + - Aggiornato `TecnoRepairArchiveService.php` per dare precedenza al percorso live `/mnt/cservergo/LunaSoftware_TecnoRepair/Archivi/TecnoRepairDB.mdb`. + - Eseguito import: `php artisan tecnorepair:import-legacy 13` -> **1874 schede importate (max legacy_id 1879, inclusa scheda 1876)**. -3. **Autonomia Cambio Password Utente**: - - **Da non autenticato**: abilitato il flusso completo su `/admin-filament/password-reset/request` (link "Hai dimenticato la password?" nella maschera di login). - - **Da autenticato**: abilitata la schermata profilo `/admin-filament/profile` tramite `->profile()` in `AdminFilamentPanelProvider`, consentendo il cambio password autonomo direttamente dal menu utente in alto a destra. +3. **Chiarimento Architettura di Sincronizzazione (Sviluppo .205, Gitea e Produzione Cloud)**: + - Sviluppo `.205`: Codice locale sul branch `stabilization/205-zero`, database locale MariaDB `netgescon` e montaggi CIFS diretti verso gli archivi MDB (`/mnt/cservergo` e `/mnt/gescon-archives`). + - Repository Gitea (`git.netgescon.it:2222`): Sorgente centrale autoritativa del codice sorgente, rami, tag e migrazioni. + - Produzione (`svr-netgescon` / `192.168.0.157`, `app.netgescon.it`): Container Docker `netgescon_prod_app` con database PostgreSQL `prod_pgsql` (374 migrazioni attive). Riceve gli aggiornamenti del codice e delle migrazioni da Gitea. I dati operativi di produzione (utenti, ticket web, log) risiedono su PostgreSQL. -4. **Allineamento Utenze Google Workspace / NetGescon**: - - Utenza Workspace `michele@netgescon.it` (User ID 25): attiva con ruoli `super-admin`, `amministratore`, `admin`. - - Utenza Google `netgescon@gmail.com` (User ID 35): registrata e attiva con ruolo `super-admin`. - - Entrambe le utenze possono richiedere e ricevere la reimpostazione password o accedere in autonomia. - -5. **Comandi Artisan di Diagnostica e Invio**: - - `php artisan netgescon:test-mail {email?}`: invia un'email diagnostica SMTP per testare la connettività Google Workspace. - - `php artisan netgescon:send-password-reset {email}`: genera il token crittografato e invia il link di ripristino all'email richiesta. +4. **Nuova UI Lavorazioni Operative (`/admin-filament/fornitore/lavorazioni`) & Scheda in TAB (Non Modal)**: + - Aggiornata la pagina secondo il design system moderno (KPI cards, badge di stato semantici, filtri reattivi, context toolbar). + - Eliminato il popup modale: il dettaglio della scheda ora vive all'interno di una **TAB in-page a larghezza intera ("Scheda Apparecchio")**, consentendo di alternare liberamente tra Elenco e Scheda senza perdere stato. + - Fedeltà grafica e funzionale a `01 schermata pricipale.PNG` di TecnoRepair: + - Header con Num. Scheda, Date (Ingresso, Orario, Cons. Prevista, Riconsegna), Stato Riparazione e Checkbox/Flags desktop (Fare Preventivo, Riparazione in sede, Riconsegnato, Rientro, Esame Tecnico, Ricons. No Ricevuta). + - Box "Dati Cliente" in alto a destra con pulsanti rapidi WhatsApp, Telefono ed SMS. + - 7 Sub-Tab: `1. Apparecchio in Entrata`, `2. Riparazione & Flussi`, `3. Ricambi Utilizzati`, `4. Preventivo - Costi - DDT`, `5. Annotazioni & Cortesia`, `6. Comunicazioni`, `7. C.Q. (Controllo Qualità)`. + - Matrice grafica 3x3 del Segno di Sblocco (Android pattern grid con 9 nodi interattivi). + - Salvataggio diretto modifiche su database (`saveActiveScheda()`) e pulsante stampa ricevuta. + - Supporto integrato anche per i Ticket Amministratore nello stesso formato Scheda. ## Output del Giro Operativo ESITO_205: riuscito -TASK_ID: task-email-service-password-reset +TASK_ID: task-lavorazioni-tecnorepair-tab-sync REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git BRANCH: stabilization/205-zero -COMMIT: cfc6060 +COMMIT: 7116305 FILE_O_AREE_TOCCATE: -- app/Console/Commands/SendPasswordResetEmailCommand.php -- app/Console/Commands/TestMailCommand.php -- app/Notifications/NetGesconResetPasswordNotification.php -- app/Models/User.php -- app/Providers/AppServiceProvider.php -- app/Providers/Filament/AdminFilamentPanelProvider.php -- config/database.php - .gitignore -- database/migrations/2025_12_22_120000_add_conto_id_to_contabilita_movimenti_banca.php -- database/migrations/2025_12_22_120010_add_conto_id_to_contabilita_saldi_conti.php -- database/migrations/2026_01_05_000011_add_pagamenti_links_to_contabilita_fatture_fornitori.php -- tests/Feature/PasswordResetAndEmailServiceTest.php +- app/Filament/Pages/Condomini/NominativiStabile.php +- app/Filament/Pages/Fornitore/LavorazioniOperative.php +- app/Filament/Pages/Gescon/Ordinarie.php +- app/Services/Tecnorepair/TecnoRepairArchiveService.php +- resources/views/filament/pages/affitti/gestione-affitti.blade.php +- resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php +- tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php - skill-netgescon/control-tower/CURRENT-205.md TEST_ESEGUITI: -- ./vendor/bin/pest tests/Feature/PasswordResetAndEmailServiceTest.php tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php tests/Feature/FornitoreContabilitaSerialiImportTest.php (13 passed, 67 assertions) -- ./vendor/bin/pest tests/Feature/PasswordResetAndEmailServiceTest.php 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 (46 passed, 285 assertions) -- Verifica migrazioni PostgreSQL su produzione (svr-netgescon): 374/374 migrazioni eseguite (NO PENDING MIGRATIONS). -- Test SMTP live su produzione: php artisan netgescon:test-mail michele@netgescon.it (email recapitata con successo). -- Test reset password live su produzione: php artisan netgescon:send-password-reset netgescon@gmail.com e michele@netgescon.it (link generati e inviati con successo). -- Test HTTP live: https://app.netgescon.it/admin-filament/login e /admin-filament/password-reset/request (HTTP 200 OK). +- ./vendor/bin/pest tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php (6 passed, 54 assertions) +- ./vendor/bin/pest tests/Feature/PasswordResetAndEmailServiceTest.php 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 (47 passed, 299 assertions, 100% pass) +- php artisan view:clear && php artisan view:cache (successo, zero errori) GATE_STATISTICS: -- SMTP_GMAIL_WORKSPACE: Connessione smtp.gmail.com:587 verificata e funzionante in locale e produzione. -- POSTGRESQL_PRODUCTION_MIGRATIONS: 100% completate senza errori di dialetto su svr-netgescon. -- GESCON_IMPORT_ISOLATION: Configurazione SQLite isolata con fallback esplicito, eliminando il blocco MySQL greeting packet su porta 5432. -- PASSWORD_RESET_FLOW: Richiesta da login (/admin-filament/password-reset/request) e da profilo (/admin-filament/profile) operativi. -- SYNC_DISPATCH: Notifiche recapitate istantaneamente senza dipendenza da code background. -- TEST_SUITE: 46 test Feature passati (285 asserzioni, 100% pass). +- BLADE_COMPILATION: 100% pulita senza token inattesi. +- TECNOREPAIR_RECORDS: 1874 schede importate dal live MDB (/mnt/cservergo), max legacy_id 1879, inclusa #1876. +- LAVORAZIONI_TAB_UI: Nuova visualizzazione con TAB integrata in-page a 7 schede (no modal) e matrice di sblocco 3x3. +- TEST_SUITE: 47 test Feature passati (299 asserzioni, 100% pass). BLOCCO_DATI: no BLOCCO_CONTRATTO: no RISCHI_APERTI: nessuno @@ -80,6 +67,5 @@ ## Output del Giro Operativo ## Prossimo Passo per .200 (Validazione) - Eseguire il checkout del branch `stabilization/205-zero`. -- Eseguire la suite di test Pest (46 passed, 285 assertions). -- Eseguire `php artisan netgescon:test-mail` per verificare la connettività SMTP Google. -- Verificare la ricezione del link di reset e la modifica autonoma della password. +- Eseguire la suite di test Pest (47 passed, 299 assertions). +- Aprire http://192.168.0.205:8000/admin-filament/fornitore/lavorazioni e verificare il nuovo layout grafico con KPI, filtri, tabella con badge di stato e apertura della Scheda Apparecchio in formato TAB a tutta pagina con la scheda 1876. diff --git a/tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php b/tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php index b5ca278..0ec9709 100644 --- a/tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php +++ b/tests/Feature/FornitoreTecnorepairAndIntegrationsTest.php @@ -1,5 +1,6 @@ fornitoreId)->toBe(236); expect($prodottiPage->getPraticheUrl())->toContain('fornitore/pratiche'); }); + +it('mounts LavorazioniOperative with unified KPI cards and renders in-page Scheda TAB without modal', function () { + $michele = User::where('email', 'michele@nethome.it')->first(); + Auth::login($michele); + + $page = new LavorazioniOperative(); + $page->mount(); + + expect($page->fornitoreId)->toBe(236) + ->and($page->mainTab)->toBe('elenco') + ->and($page->totals['tutte'])->toBeGreaterThan(0) + ->and(count($page->rows))->toBeGreaterThan(0); + + // Find TecnoRepair row + $tecnorepairRow = collect($page->rows)->firstWhere('row_type', 'tecnorepair'); + expect($tecnorepairRow)->not->toBeNull(); + + // Open detail -> must activate in-page TAB 'scheda', NOT a modal! + $page->openRowDetail('tecnorepair', (int) $tecnorepairRow['id']); + + expect($page->mainTab)->toBe('scheda') + ->and($page->detailModal)->toBeNull() // Confirms no modal popup is used + ->and($page->activeScheda)->not->toBeNull() + ->and($page->activeScheda['customer_name'])->not->toBeEmpty() + ->and($page->schedaSubTab)->toBe('entrata'); + + // Test sub-tabs navigation + $page->setSchedaSubTab('riparazione'); + expect($page->schedaSubTab)->toBe('riparazione'); + + // Test pattern unlock dot toggling + $initialDot = $page->unlockPattern[0]; + $page->togglePatternDot(0); + expect($page->unlockPattern[0])->toBe($initialDot === 1 ? 0 : 1); + + // Test saving modifications + $testDefect = 'TEST DIFETTO AGGIORNATO ' . uniqid(); + $page->schedaForm['difetto_segnalato'] = $testDefect; + $page->saveActiveScheda(); + + $reloaded = AssistenzaTecnorepairScheda::find((int) $tecnorepairRow['id']); + expect($reloaded->defect_reported)->toBe($testDefect); + + // Test close -> back to elenco tab + $page->closeScheda(); + expect($page->mainTab)->toBe('elenco'); +});
Num.IngressoClienteTelProblemaMarcaModelloSerialeCod. prodOrigineOperatoreStatoAggiornatoNum.IngressoCliente / ContattoTelefonoProblema / DifettoMarcaModelloSeriale / IMEIOrigineOperatoreStatoAggiornatoAzione
-
{{ $row['numero'] ?? ($row['ticket_id'] ? ('#' . $row['ticket_id']) : '-') }}
-
{{ $row['row_type'] === 'tecnorepair' ? 'TR' : 'TK' }}
+ @php + $rowBg = match($row['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 => 'hover:bg-slate-50 text-slate-800', + }; + $borderLeft = match($row['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 +
+
{{ $row['numero'] ?? ($row['ticket_id'] ? ('#' . $row['ticket_id']) : '-') }}
+ + {{ ($row['row_type'] ?? '') === 'tecnorepair' ? 'TR' : 'TK' }} +
-
{{ $row['ingresso'] }}
-
{{ $row['stabile'] }}
+
+
{{ $row['ingresso'] }}
+
{{ $row['stabile'] }}
-
{{ $row['cliente'] ?? $row['contatto'] }}
+
+ {{ $row['cliente'] ?? $row['contatto'] }} {{ $row['telefono'] !== '' ? $row['telefono'] : '-' }} + + {{ $row['telefono'] !== '' ? $row['telefono'] : '-' }} +
{{ $row['difetto'] ?? $row['problema'] }}
-
{{ $row['titolo'] }}
+ @if(($row['titolo'] ?? '') !== '' && $row['titolo'] !== '-') +
{{ $row['titolo'] }}
+ @endif
{{ $row['brand'] ?? '-' }}{{ $row['modello'] ?? $row['apparato'] }}{{ $row['seriale'] ?? '-' }}{{ $row['cod_prodotto'] ?? '-' }} - {{ $row['origine'] }} + {{ $row['brand'] ?? '-' }} + {{ $row['modello'] ?? $row['apparato'] }} {{ $row['operatore'] }} - {{ $row['stato'] }} + + {{ $row['seriale'] ?? '-' }} {{ $row['updated_at'] }} -
- - @if($row['url']) - Apri scheda - @endif -
+
+ + {{ $row['origine'] }} + + {{ $row['operatore'] }} + + {{ $row['stato'] }} + + {{ $row['updated_at'] }} +
Nessuna lavorazione disponibile per questo filtro. + Nessuna lavorazione trovata con i filtri correnti. Prova a cliccare "Reset Filtri". +