argument('amministratore_id'); $stabileId = $this->option('stabile_id') !== null ? (int) $this->option('stabile_id') : null; $cartella = $this->option('cartella') ? trim((string) $this->option('cartella')) : null; $path = $this->option('path') ? (string) $this->option('path') : null; $dir = (string) ($this->option('dir') ?: ''); $dry = (bool) $this->option('dry-run'); $limit = max(0, (int) $this->option('limit')); if ($amministratoreId <= 0) { $this->error('amministratore_id non valido'); return self::FAILURE; } if ($stabileId !== null && $stabileId > 0) { $exists = Stabile::query()->whereKey($stabileId)->where('amministratore_id', $amministratoreId)->exists(); if (!$exists) { $this->error("stabile_id={$stabileId} non appartiene ad amministratore_id={$amministratoreId}"); return self::FAILURE; } } $files = []; if ($path) { if (!is_file($path)) { $this->error("CSV non trovato: {$path}"); return self::FAILURE; } $files = [$path]; } else { if ($dir === '' || !is_dir($dir)) { $this->error("Directory non valida: {$dir}"); return self::FAILURE; } $it = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)); foreach ($it as $file) { if (!$file instanceof \SplFileInfo) { continue; } if (!$file->isFile()) { continue; } $name = $file->getFilename(); if (!Str::endsWith(Str::lower($name), '.csv')) { continue; } $files[] = $file->getPathname(); } sort($files); } if (empty($files)) { $this->warn('Nessun CSV trovato'); return self::SUCCESS; } $stats = [ 'files' => count($files), 'rows' => 0, 'created' => 0, 'updated' => 0, 'skipped' => 0, ]; $runner = function () use (&$stats, $files, $amministratoreId, $stabileId, $cartella, $dry, $limit): void { foreach ($files as $filePath) { [$rows, $headers] = $this->readCsvSemicolon($filePath); if (empty($rows)) { continue; } if ($limit > 0) { $rows = array_slice($rows, 0, $limit); } foreach ($rows as $row) { $stats['rows']++; $sdi = $this->clean($row['sdi/file'] ?? null); if ($sdi === null) { $stats['skipped']++; continue; } $payload = [ 'amministratore_id' => $amministratoreId, 'stabile_id' => $stabileId, 'cartella_legacy' => $cartella, 'tipo_documento' => $this->clean($row['tipo documento'] ?? null), 'numero_documento' => $this->clean($row['numero fattura / documento'] ?? null), 'data_emissione' => $this->parseDateIt($row['data emissione fattura'] ?? null), 'identificativo_fornitore' => $this->clean($row['identificativo fornitore'] ?? null), 'denominazione_fornitore' => $this->clean($row['denominazione fornitore'] ?? null), 'imponibile' => $this->parseDecimalIt($row['imponibile / importo'] ?? null), 'imposta' => $this->parseDecimalIt($row['imposta (totale in euro)'] ?? null), 'sdi_file' => $sdi, 'stato_visualizzazione' => $this->clean($row['fatture visualizzate'] ?? null), 'source_file' => $filePath, 'raw' => $row, ]; if ($dry) { continue; } $existing = StgFatturaAde::query() ->where('amministratore_id', $amministratoreId) ->where('stabile_id', $stabileId) ->where('sdi_file', $sdi) ->first(); if ($existing) { $existing->fill($payload); if ($existing->isDirty()) { $existing->save(); $stats['updated']++; } } else { StgFatturaAde::create($payload); $stats['created']++; } } } }; if ($dry) { $runner(); } else { DB::transaction($runner); } $this->info('✅ Import AdE CSV completato' . ($dry ? ' (dry-run)' : '')); $this->line(json_encode($stats, JSON_UNESCAPED_UNICODE)); return self::SUCCESS; } /** @return array{0: array>, 1: array} */ private function readCsvSemicolon(string $filePath): array { $content = @file_get_contents($filePath); if (!is_string($content) || trim($content) === '') { return [[], []]; } // Normalize line endings $content = str_replace(["\r\n", "\r"], "\n", $content); $fh = fopen('php://temp', 'r+'); fwrite($fh, $content); rewind($fh); $headers = fgetcsv($fh, separator: ';'); if (!$headers) { return [[], []]; } $headers = array_map(fn($h) => mb_strtolower(trim((string) $h)), $headers); $rows = []; while (($cols = fgetcsv($fh, separator: ';')) !== false) { if (empty($cols)) { continue; } if (count($cols) < count($headers)) { $cols = array_pad($cols, count($headers), null); } if (count($cols) > count($headers)) { $cols = array_slice($cols, 0, count($headers)); } $assoc = @array_combine($headers, $cols); if (!$assoc) { continue; } $rows[] = $assoc; } return [$rows, $headers]; } private function parseDateIt($v): ?string { $s = $this->clean($v); if ($s === null) { return null; } // Expected dd/mm/yyyy $parts = explode('/', $s); if (count($parts) !== 3) { return null; } [$d, $m, $y] = $parts; $d = (int) $d; $m = (int) $m; $y = (int) $y; if ($d <= 0 || $m <= 0 || $y <= 0) { return null; } return sprintf('%04d-%02d-%02d', $y, $m, $d); } private function parseDecimalIt($v): float { $s = $this->clean($v); if ($s === null) { return 0.0; } // Remove thousand separators and convert comma decimal $s = str_replace('.', '', $s); $s = str_replace(',', '.', $s); $s = preg_replace('/[^0-9\.-]/', '', $s) ?? $s; if ($s === '' || !is_numeric($s)) { return 0.0; } return (float) $s; } private function clean($v): ?string { if ($v === null) { return null; } $s = trim((string) $v); return $s === '' ? null : $s; } }