0, 'staged' => 0, 'errors' => $errors]; } $handle = fopen($csvPath, 'r'); if (!$handle) { $errors[] = "Impossibile aprire il file CSV."; return ['imported' => 0, 'staged' => 0, 'errors' => $errors]; } // Detect delimiter: CSV could be comma or semicolon separated $header = fgetcsv($handle, 1000, ';'); if (count($header) <= 1) { rewind($handle); $header = fgetcsv($handle, 1000, ','); $delimiter = ','; } else { $delimiter = ';'; } if (empty($header)) { $errors[] = "File CSV vuoto o non conforme."; fclose($handle); return ['imported' => 0, 'staged' => 0, 'errors' => $errors]; } // Normalize header columns to lowercase $header = array_map(fn($col) => strtolower(trim((string)$col)), $header); // Find positions of fields $posMatricola = array_search('matricola', $header) !== false ? array_search('matricola', $header) : (array_search('#id', $header) !== false ? array_search('#id', $header) : (array_search('id', $header) !== false ? array_search('id', $header) : 0)); $posData = array_search('data_lettura', $header) !== false ? array_search('data_lettura', $header) : 1; $posValore = array_search('valore_lettura', $header) !== false ? array_search('valore_lettura', $header) : (array_search('lettura', $header) !== false ? array_search('lettura', $header) : 2); $posInterno = array_search('interno', $header) !== false ? array_search('interno', $header) : 3; $servizio = DB::table('stabile_servizi') ->where('stabile_id', $stabileId) ->where('tipo', 'acqua') ->first(); $servizioId = $servizio?->id; if (!$servizioId) { $errors[] = "Servizio acqua non configurato per questo stabile."; fclose($handle); return ['imported' => 0, 'staged' => 0, 'errors' => $errors]; } while (($row = fgetcsv($handle, 1000, $delimiter)) !== false) { if (empty($row) || count($row) < 3) { continue; } $rawMatricola = trim((string)($row[$posMatricola] ?? '')); $rawData = trim((string)($row[$posData] ?? '')); $rawValore = trim((string)($row[$posValore] ?? '')); $rawInterno = isset($row[$posInterno]) ? trim((string)$row[$posInterno]) : null; if ($rawMatricola === '' || $rawValore === '') { continue; } // Parse date $dataLettura = null; try { if (str_contains($rawData, '/')) { $parts = explode('/', $rawData); if (count($parts) === 3) { $day = (int)$parts[0]; $month = (int)$parts[1]; $year = (int)$parts[2]; if ($year < 100) { $year += ($year >= 70) ? 1900 : 2000; } $dataLettura = sprintf("%04d-%02d-%02d", $year, $month, $day); } } else { $dataLettura = date('Y-m-d', strtotime($rawData)); } } catch (Exception) { $dataLettura = now()->toDateString(); } if (!$dataLettura) { $dataLettura = now()->toDateString(); } // Parse value $valoreLettura = (float) str_replace(',', '.', $rawValore); // Find matching unit by serial number or gateway device $unit = UnitaImmobiliare::where('stabile_id', $stabileId) ->where(function ($query) use ($rawMatricola) { $query->where('acqua_contatore_seriale', $rawMatricola) ->orWhere('acqua_gateway_device_id', $rawMatricola); }) ->first(); if (!$unit && $rawInterno !== null && $rawInterno !== '') { $unit = UnitaImmobiliare::where('stabile_id', $stabileId) ->where('interno', $rawInterno) ->first(); if ($unit) { $unit->update([ 'acqua_contatore_seriale' => $rawMatricola, 'acqua_gateway_device_id' => $rawMatricola, ]); } } if ($unit) { $latestReal = StabileServizioLettura::query() ->where('unita_immobiliare_id', $unit->id) ->where('stabile_servizio_id', $servizioId) ->orderByDesc('periodo_al') ->first(); StabileServizioLettura::create([ 'stabile_id' => $stabileId, 'stabile_servizio_id' => $servizioId, 'unita_immobiliare_id' => $unit->id, 'periodo_dal' => $latestReal?->periodo_al ?? $dataLettura, 'periodo_al' => $dataLettura, 'tipologia_lettura' => 'elettronica_remota', 'canale_acquisizione' => 'csv_import', 'workflow_stato' => 'rilevata', 'lettura_precedente_valore' => $latestReal?->lettura_fine ?? 0.0, 'lettura_fine' => $valoreLettura, 'consumo_valore' => max(0, $valoreLettura - ($latestReal?->lettura_fine ?? 0.0)), 'consumo_unita' => 'mc', 'raw' => ['file_name' => $fileName, 'raw_row' => $row], 'created_by' => auth()->id() ?: 1, ]); $importedCount++; } else { DB::table('contatori_orfani_staging')->insert([ 'stabile_id' => $stabileId, 'matricola' => $rawMatricola, 'interno_originale' => $rawInterno, 'data_lettura' => $dataLettura, 'valore_lettura' => $valoreLettura, 'file_name' => $fileName, 'created_at' => now(), 'updated_at' => now(), ]); $stagedCount++; } } fclose($handle); return [ 'imported' => $importedCount, 'staged' => $stagedCount, 'errors' => $errors ]; } /** * Associate an orphan serial number to a unit, learn it, and migrate the reading(s). * * @param int $orphanId * @param int $unitId * @return void */ public function associateOrphan(int $orphanId, int $unitId): void { $orphan = DB::table('contatori_orfani_staging')->where('id', $orphanId)->first(); if (!$orphan) { return; } $unit = UnitaImmobiliare::find($unitId); if (!$unit) { return; } // 1. Auto-learn: save the serial directly to the unit $unit->update([ 'acqua_contatore_seriale' => $orphan->matricola, 'acqua_gateway_device_id' => $orphan->matricola, ]); // 2. Migrate the reading to stabile_servizio_letture $servizio = DB::table('stabile_servizi') ->where('stabile_id', $unit->stabile_id) ->where('tipo', 'acqua') ->first(); $servizioId = $servizio?->id; if ($servizioId) { $latestReal = StabileServizioLettura::query() ->where('unita_immobiliare_id', $unit->id) ->where('stabile_servizio_id', $servizioId) ->orderByDesc('periodo_al') ->first(); StabileServizioLettura::create([ 'stabile_id' => $unit->stabile_id, 'stabile_servizio_id' => $servizioId, 'unita_immobiliare_id' => $unit->id, 'periodo_dal' => $latestReal?->periodo_al ?? $orphan->data_lettura, 'periodo_al' => $orphan->data_lettura, 'tipologia_lettura' => 'elettronica_remota', 'canale_acquisizione' => 'csv_import_associated', 'workflow_stato' => 'rilevata', 'lettura_precedente_valore' => $latestReal?->lettura_fine ?? 0.0, 'lettura_fine' => $orphan->valore_lettura, 'consumo_valore' => max(0, $orphan->valore_lettura - ($latestReal?->lettura_fine ?? 0.0)), 'consumo_unita' => 'mc', 'raw' => ['orphan_id' => $orphan->id, 'matricola_originale' => $orphan->matricola], 'created_by' => auth()->id() ?: 1, ]); } // 3. Delete from staging DB::table('contatori_orfani_staging')->where('id', $orphanId)->delete(); } }