error('Tabella incassi_estratto_conto non trovata.'); return self::FAILURE; } $tenant = (string) $this->argument('tenant'); $mdb = trim((string) ($this->option('mdb') ?: '')); $table = trim((string) ($this->option('table') ?: 'Inc_da_ec')); $anno = $this->option('anno') ? (int) $this->option('anno') : null; $limit = max(1, (int) ($this->option('limit') ?: 20)); $preview = (bool) $this->option('preview'); if ($mdb === '' || ! is_file($mdb)) { $this->error('File MDB non trovato. Passa --mdb=/path/file.mdb'); return self::FAILURE; } $stabileId = $this->resolveStabileId(); $stabileCode = $this->resolveStabileCode(); if (! $stabileId || $stabileCode === null) { $this->error('Stabile non risolto: passa --stabile-id e/o --stabile-code.'); return self::FAILURE; } $gestioneId = $this->resolveGestioneId($tenant, $stabileId, $anno); $bin = trim((string) @shell_exec('command -v mdb-export')); if ($bin === '') { $this->error('mdb-export non trovato (installa mdbtools).'); return self::FAILURE; } $csv = $this->runShell(sprintf( "%s -D %s -d %s -q %s -R \"\\n\" %s %s", escapeshellarg($bin), escapeshellarg('%Y-%m-%d %H:%M:%S'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($mdb), escapeshellarg($table) )); if ($csv === null || trim($csv) === '') { $this->error('Nessun dato esportato: tabella non trovata o MDB non leggibile.'); return self::FAILURE; } $rows = $this->parseDelimited($csv, '|'); if ($rows === []) { $this->warn('Nessuna riga da importare.'); return self::SUCCESS; } if ($preview) { foreach (array_slice($rows, 0, $limit) as $row) { $mapped = $this->mapRow($row, $stabileCode, (string) $this->option('pdf-base')); $this->line(json_encode($mapped, JSON_UNESCAPED_UNICODE)); } $this->info('Preview completata. Righe sorgente: ' . count($rows)); return self::SUCCESS; } $toProcess = $limit > 0 ? array_slice($rows, 0, $limit) : $rows; $imported = 0; $matched = 0; foreach ($toProcess as $row) { $mapped = $this->mapRow($row, $stabileCode, (string) $this->option('pdf-base')); $numIncasso = $mapped['num_incasso']; $incassoId = null; if ($numIncasso !== null && Schema::hasTable('incassi') && Schema::hasColumn('incassi', 'n_riferimento')) { $incassoQuery = DB::table('incassi') ->where('n_riferimento', (string) $numIncasso) ->when($stabileId && Schema::hasColumn('incassi', 'condominio_id'), fn ($q) => $q->where('condominio_id', (int) $stabileId)); $incassoId = $incassoQuery->orderByDesc('id')->value('id'); } if ($incassoId) { $matched++; } $hash = hash('sha256', implode('|', [ $tenant, $gestioneId ?? 0, $mapped['num_incasso'] ?? '', $mapped['data_operazione'] ?? '', $mapped['importo'] ?? 0, ])); $payload = [ 'tenant_id' => $tenant, 'gestione_id' => $gestioneId, 'id_gescon' => $mapped['num_incasso'], 'data_operazione' => $mapped['data_operazione'], 'data_valuta' => $mapped['data_valuta'], 'importo' => $mapped['importo'], 'descrizione' => $mapped['descrizione'], 'tipo_movimento' => $mapped['importo'] < 0 ? 'uscita' : 'entrata', 'riferimento_bancario' => $mapped['num_incasso'] !== null ? (string) $mapped['num_incasso'] : null, 'incasso_id' => $incassoId, 'stato' => $incassoId ? 'riconciliato' : 'da_riconciliare', 'confidenza_match' => $incassoId ? 100 : 0, 'tipo_riconciliazione' => $incassoId ? 'automatica' : null, 'file_origine' => $mapped['nome_file_pdf'], 'formato_file' => 'mdb', 'hash_movimento' => $hash, 'metadati_gescon' => json_encode([ 'source_table' => $table, 'num_incasso' => $mapped['num_incasso'], 'nome_file_pdf' => $mapped['nome_file_pdf'], 'pdf_path' => $mapped['pdf_path'], 'raw' => $row, ], JSON_UNESCAPED_UNICODE), 'updated_at' => now(), ]; $existing = DB::table('incassi_estratto_conto') ->where('tenant_id', $tenant) ->where('hash_movimento', $hash) ->first(['id']); if ($existing) { DB::table('incassi_estratto_conto') ->where('id', (int) $existing->id) ->update($payload); } else { $payload['created_at'] = now(); DB::table('incassi_estratto_conto')->insert($payload); } $imported++; } $this->info("Import completato: {$imported} righe, match incassi={$matched}"); return self::SUCCESS; } private function mapRow(array $row, string $stabileCode, string $pdfBase): array { $get = function (array $keys) use ($row) { foreach ($keys as $key) { $lk = strtolower($key); if (array_key_exists($lk, $row)) { $value = $row[$lk]; return is_string($value) ? trim($value) : $value; } } return null; }; $numIncasso = $this->toInt($get(['Num_incasso', 'num_incasso', 'id_incasso', 'id'])); $nomeFilePdf = $this->nullIfEmpty($get(['Nome_file_pdf', 'nome_file_pdf', 'file_pdf', 'pdf'])); $pdfPath = null; if ($nomeFilePdf) { $pdfPath = rtrim($pdfBase, '/') . '/' . $stabileCode . '/INC_EC/' . ltrim($nomeFilePdf, '/'); } return [ 'num_incasso' => $numIncasso, 'data_operazione' => $this->toDate($get(['data_oper', 'data_operazione', 'data'])), 'data_valuta' => $this->toDate($get(['data_valuta', 'valuta', 'data_val'])), 'importo' => $this->money($get(['importo_euro', 'importo'])), 'descrizione' => $this->nullIfEmpty($get(['descrizione', 'causale', 'note'])), 'nome_file_pdf' => $nomeFilePdf, 'pdf_path' => $pdfPath, ]; } private function parseDelimited(string $text, string $sep): array { $lines = preg_split("/\r?\n/", trim($text)); if (! $lines || count($lines) === 0) { return []; } $headers = str_getcsv(array_shift($lines), $sep); $headers = array_map(fn ($h) => strtolower(trim((string) $h)), $headers); $rows = []; foreach ($lines as $line) { if ($line === '') { continue; } $cols = str_getcsv($line, $sep); if (count($cols) < count($headers)) { $cols = array_pad($cols, count($headers), null); } if (count($cols) > count($headers)) { $cols = array_slice($cols, 0, count($headers)); } $row = @array_combine($headers, $cols); if (! $row) { continue; } $rows[] = $row; } return $rows; } private function resolveStabileId(): ?int { $fromOption = $this->option('stabile-id'); if (is_numeric($fromOption)) { return (int) $fromOption; } $code = trim((string) ($this->option('stabile-code') ?: '')); if ($code !== '' && Schema::hasTable('stabili')) { $id = DB::table('stabili')->where('codice_stabile', $code)->value('id'); return is_numeric($id) ? (int) $id : null; } return null; } private function resolveStabileCode(): ?string { $fromOption = trim((string) ($this->option('stabile-code') ?: '')); if ($fromOption !== '') { return str_pad($fromOption, 4, '0', STR_PAD_LEFT); } $stabileId = $this->resolveStabileId(); if ($stabileId && Schema::hasTable('stabili')) { $code = DB::table('stabili')->where('id', (int) $stabileId)->value('codice_stabile'); if (is_string($code) && trim($code) !== '') { return str_pad(trim($code), 4, '0', STR_PAD_LEFT); } } return null; } private function resolveGestioneId(string $tenant, int $stabileId, ?int $anno): ?int { if (! Schema::hasTable('gestioni_contabili')) { return null; } $query = DB::table('gestioni_contabili') ->where('tenant_id', $tenant) ->where('stabile_id', $stabileId) ->where('tipo_gestione', 'ordinaria'); if ($anno) { $query->where('anno_gestione', $anno); } return $query->orderByDesc('anno_gestione')->value('id'); } private function runShell(string $cmd): ?string { $out = shell_exec($cmd . ' 2>/dev/null'); if ($out === null || $out === '') { return null; } return $out; } private function nullIfEmpty($value): ?string { if ($value === null) { return null; } $trim = trim((string) $value); return $trim === '' ? null : $trim; } private function toInt($value): ?int { if ($value === null) { return null; } $trim = trim((string) $value); return $trim !== '' && is_numeric($trim) ? (int) $trim : null; } private function toDate($value): ?string { if ($value === null) { return null; } $raw = trim((string) $value); if ($raw === '') { return null; } if (preg_match('/^\d{4}-\d{2}-\d{2}/', $raw)) { return substr($raw, 0, 10); } if (preg_match('/^\d{2}\/\d{2}\/\d{2} /', $raw)) { $dt = \DateTime::createFromFormat('m/d/y H:i:s', $raw); if ($dt) { $yy = (int) $dt->format('y'); $curYY = (int) now()->format('y'); $year = ($yy > $curYY) ? (1900 + $yy) : (2000 + $yy); return sprintf('%04d-%02d-%02d', $year, (int) $dt->format('m'), (int) $dt->format('d')); } } if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $raw)) { $dt = \DateTime::createFromFormat('d/m/Y', $raw); return $dt?->format('Y-m-d'); } $ts = @strtotime($raw); return $ts ? date('Y-m-d', $ts) : null; } private function money($value): float { if ($value === null || $value === '') { return 0.0; } $raw = str_replace(["\r", "\n", "\t", ' '], '', (string) $value); if (str_contains($raw, ',') && str_contains($raw, '.')) { $raw = str_replace('.', '', $raw); $raw = str_replace(',', '.', $raw); } elseif (str_contains($raw, ',')) { $raw = str_replace(',', '.', $raw); } return is_numeric($raw) ? (float) $raw : 0.0; } }