fix(lavorazioni): solve 500 blade error, mount live tecnorepair mdb (1876), refactor lavorazioni with in-page tab scheda and modern design
This commit is contained in:
parent
ce33fe4f08
commit
067be6513a
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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<int, string> */
|
||||
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<string, mixed>|null */
|
||||
public ?array $activeScheda = null;
|
||||
|
||||
/** @var array<string, mixed> Editable form fields for active scheda */
|
||||
public array $schedaForm = [];
|
||||
|
||||
/** @var array<int, int> 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<int, array<string, mixed>> */
|
||||
public array $rows = [];
|
||||
|
||||
public ?array $detailModal = null;
|
||||
|
||||
/** @var array<string, int> */
|
||||
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<int, string> */
|
||||
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 . ')',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -786,10 +786,8 @@ class="inline-flex items-center gap-1 rounded-lg border border-amber-300 bg-ambe
|
|||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
@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))
|
||||
<tr class="hover:bg-slate-50/70 transition">
|
||||
<td class="px-3 py-2 font-medium text-slate-800">{{ $r['mese_descrizione'] ?? ($r['mese'] ?? '') }} {{ $r['anno'] ?? '' }}</td>
|
||||
<td class="px-3 py-2 text-slate-600">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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 `/(?<!@)@php(.*?)@endphp/s`. La regex inglobava 19.000 caratteri da una precedente direttiva `@php(...)`, lasciando orfano `@empty` e mandando il contatore `forElseCounter` a `-1`, scatenando `$__empty_-1 = true;` in compilazioni successive.
|
||||
- Soluzione: convertite le righe a direttive inline `@php($d = ...)` e `@php($p = ...)`.
|
||||
- Verifica: `php artisan view:clear && php artisan view:cache` -> **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.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Filament\Pages\Fornitore\LavorazioniOperative;
|
||||
use App\Filament\Pages\Fornitore\PraticheTecnorepair;
|
||||
use App\Filament\Pages\Fornitore\ProdottiCatalogo;
|
||||
use App\Filament\Pages\Fornitore\SerialiCatalogo;
|
||||
|
|
@ -237,3 +238,50 @@
|
|||
expect($prodottiPage->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');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user