netgescon-day0/app/Console/Commands/ImportGesconMdbCommand.php

312 lines
12 KiB
PHP
Executable File

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* Import diretto da MDB → MySQL (staging) senza CSV
* Richiede mdb-tools installati (mdb-tables, mdb-export, mdb-sql) oppure ODBC
*/
class ImportGesconMdbCommand extends Command
{
protected $signature = 'import:gescon-mdb
{tenant : Tenant ID}
{--mdb= : Percorso file MDB}
{--anno= : Anno gestione}
{--tipo=O : Tipo gestione O|R|S}
{--n-stra= : Identificativo gestione straordinaria}
{--table=* : Tabelle da importare (default: auto)}
{--preview : Mostra anteprima righe}
{--limit=100 : Limite preview}';
protected $description = 'Import diretto da MDB a tabelle staging MySQL (multi-anno, multi-gestione)';
public function handle()
{
$tenant = $this->argument('tenant');
$mdb = $this->option('mdb');
$anno = (int)($this->option('anno') ?: date('Y'));
$tipo = strtoupper($this->option('tipo') ?: 'O');
$nStra = $this->option('n-stra');
if (!file_exists($mdb)) {
$this->error("File MDB non trovato: {$mdb}");
return Command::FAILURE;
}
$this->info("📥 Import MDB → staging | tenant={$tenant} anno={$anno} tipo={$tipo} n_stra={$nStra}");
$this->line("Sorgente: {$mdb}");
// Determina tabelle
$tables = $this->option('table');
if (empty($tables)) {
$tables = ['Inc_da_ec', 'Nettovers_RDA', 'EMESS_GEN', 'EMESS_DET', 'EMESS_DET_2', 'FE_caricate_aut', 'operazioni'];
}
// Crea gestione (o recupera)
$gestioneId = $this->upsertGestione($tenant, $anno, $tipo, $nStra);
$stats = [];
foreach ($tables as $table) {
$imported = $this->importTable($tenant, $mdb, $table, $gestioneId, $anno, $tipo, $nStra);
$stats[$table] = $imported;
}
$this->newLine();
$this->info('✅ Import completato');
foreach ($stats as $tbl => $num) {
$this->line(" - {$tbl}: {$num} righe importate");
}
return Command::SUCCESS;
}
private function upsertGestione(string $tenant, int $anno, string $tipo, ?string $nStra): int
{
// Allinea al modello CANONICO: anno_gestione, tipo_gestione (string), numero_straordinaria
$tipoMap = [
'O' => 'ordinaria',
'R' => 'riscaldamento',
'S' => 'straordinaria',
];
$tipoGest = $tipoMap[$tipo] ?? 'ordinaria';
$numeroStra = $tipoGest === 'straordinaria' ? (int)($nStra ?? 0) : null;
$row = DB::table('gestioni_contabili')
->where('tenant_id', $tenant)
->where('anno_gestione', $anno)
->where('tipo_gestione', $tipoGest)
->where(function ($q) use ($numeroStra) {
if ($numeroStra === null) {
$q->whereNull('numero_straordinaria');
} else {
$q->where('numero_straordinaria', $numeroStra);
}
})
->first();
if ($row) return $row->id;
// Valori di default ragionevoli
$dataInizio = sprintf('%d-01-01', $anno);
$dataFine = sprintf('%d-12-31', $anno);
$denom = 'Gestione ' . $anno . ' ' . $tipoGest . ($numeroStra ? ' n.' . $numeroStra : '');
$prefixBase = ($tipo === 'R' ? 'R' : ($tipo === 'S' ? 'S' : 'O')) . $anno;
$protocolloPrefix = $prefixBase . ($tipo === 'S' && $numeroStra ? '-' . str_pad((string)$numeroStra, 3, '0', STR_PAD_LEFT) : '');
return DB::table('gestioni_contabili')->insertGetId([
'tenant_id' => $tenant,
'anno_gestione' => $anno,
'tipo_gestione' => $tipoGest,
'numero_straordinaria' => $numeroStra,
'denominazione' => $denom,
'data_inizio' => $dataInizio,
'data_fine' => $dataFine,
'protocollo_prefix' => $protocolloPrefix,
'ultimo_protocollo' => 0,
'stato' => 'aperta',
'created_at' => now(),
'updated_at' => now(),
]);
}
private function importTable(string $tenant, string $mdb, string $table, int $gestioneId, int $anno, string $tipo, ?string $nStra): int
{
$this->line("→ Import tabella {$table}");
$csv = $this->runShell("mdb-export -D '%m/%d/%y %H:%M:%S' -d '|' -Q -R '\n' " . escapeshellarg($mdb) . ' ' . escapeshellarg($table));
if ($csv === null) {
$this->warn(" (skipped) tabella non trovata");
return 0;
}
$rows = $this->parseDelimited($csv, '|');
if (count($rows) === 0) {
$this->line(" 0 righe");
return 0;
}
$count = 0;
switch ($table) {
case 'operazioni':
foreach ($rows as $r) {
DB::table('stg_operazioni_gescon')->insert([
'tenant_id' => $tenant,
'sorgente_archivio' => $mdb,
'gestione_id' => $gestioneId,
'anno' => $anno,
'gestione_tipo' => $tipo,
'n_stra' => $nStra,
'compet' => $r['COMPET'] ?? null,
'natura2' => $r['NATURA2'] ?? null,
'conto_dare' => $r['CONTO_DARE'] ?? null,
'conto_avere' => $r['CONTO_AVERE'] ?? null,
'protocollo' => $r['NUM_PROTOCOLLO'] ?? ($r['NUMERO'] ?? null),
'data_registrazione' => $this->date($r['DATA_REG'] ?? null),
'data_competenza' => $this->date($r['DATA_COMP'] ?? null),
'importo' => $this->money($r['IMPORTO'] ?? 0),
'valuta' => 'EUR',
'fornitore_cod' => $r['COD_FORN'] ?? null,
'fornitore_nome' => $r['NOME_FORN'] ?? null,
'descrizione' => $r['DESCR'] ?? null,
'cod_cassa' => $r['COD_CASSA'] ?? null,
'n_riferimento' => $r['N_RIF'] ?? null,
'numero_fattura' => $r['NUM_FATT'] ?? null,
'data_fattura' => $this->date($r['DATA_FATT'] ?? null),
'gestione_raw' => $r['GESTIONE'] ?? null,
'raw' => json_encode($r),
'created_at' => now(),
'updated_at' => now(),
]);
$count++;
}
break;
case 'Inc_da_ec':
foreach ($rows as $r) {
DB::table('stg_incassi_da_estratto')->insert([
'tenant_id' => $tenant,
'sorgente_archivio' => $mdb,
'gestione_id' => $gestioneId,
'anno' => $anno,
'cod_cassa' => $r['COD_CASSA'] ?? null,
'riferimento_banca' => $r['RIF_EC'] ?? ($r['TRN'] ?? null),
'data_banca' => $this->date($r['DATA_EC'] ?? null),
'importo' => $this->money($r['IMPORTO'] ?? 0),
'note' => $r['NOTE'] ?? null,
'raw' => json_encode($r),
'created_at' => now(),
'updated_at' => now(),
]);
$count++;
}
break;
case 'Nettovers_RDA':
foreach ($rows as $r) {
DB::table('stg_ritenute_rda')->insert([
'tenant_id' => $tenant,
'sorgente_archivio' => $mdb,
'gestione_id' => $gestioneId,
'anno' => $anno,
'rif_rda' => $r['RIF_RDA'] ?? null,
'imponibile' => $this->money($r['IMPONIBILE'] ?? 0),
'aliquota' => $this->decimal($r['ALIQ'] ?? 0),
'ritenuta' => $this->money($r['RITENUTA'] ?? 0),
'data_sorgente' => $this->date($r['DATA_SORG'] ?? null),
'data_versamento' => $this->date($r['DATA_VERS'] ?? null),
'fornitore_cod' => $r['COD_FORN'] ?? null,
'fornitore_nome' => $r['NOME_FORN'] ?? null,
'raw' => json_encode($r),
'created_at' => now(),
'updated_at' => now(),
]);
$count++;
}
break;
case 'EMESS_GEN':
case 'EMESS_DET':
case 'EMESS_DET_2':
foreach ($rows as $r) {
DB::table('stg_rate_emesse')->insert([
'tenant_id' => $tenant,
'sorgente_archivio' => $mdb,
'gestione_id' => $gestioneId,
'anno' => $anno,
'cod_cond' => $r['COD_COND'] ?? null,
'unita' => $r['UNITA'] ?? ($r['UI'] ?? null),
'o_r_s' => $r['O_R_S'] ?? null,
'n_mese' => $r['N_MESE'] ?? ($r['MESE'] ?? null),
'importo' => $this->money($r['IMPORTO'] ?? ($r['IMPORTO_EURO'] ?? 0)),
'data_emissione' => $this->date($r['DATA_EMISS'] ?? null),
'raw' => json_encode(['table' => $table, 'row' => $r]),
'created_at' => now(),
'updated_at' => now(),
]);
$count++;
}
break;
case 'FE_caricate_aut':
foreach ($rows as $r) {
DB::table('stg_fatture_fe')->insert([
'tenant_id' => $tenant,
'sorgente_archivio' => $mdb,
'gestione_id' => $gestioneId,
'anno' => $anno,
'numero' => $r['NUMERO'] ?? null,
'data' => $this->date($r['DATA'] ?? null),
'fornitore_cod' => $r['COD_FORN'] ?? null,
'fornitore_nome' => $r['NOME_FORN'] ?? null,
'imponibile' => $this->money($r['IMPONIBILE'] ?? 0),
'iva' => $this->money($r['IVA'] ?? 0),
'totale' => $this->money($r['TOTALE'] ?? 0),
'raw' => json_encode($r),
'created_at' => now(),
'updated_at' => now(),
]);
$count++;
}
break;
default:
$this->warn(" Tabella non gestita: {$table}");
}
return $count;
}
private function runShell(string $cmd): ?string
{
$out = shell_exec($cmd . ' 2>/dev/null');
if ($out === null || $out === '') return null;
return $out;
}
private function parseDelimited(string $text, string $sep): array
{
$lines = preg_split("/\r?\n/", trim($text));
if (count($lines) === 0) return [];
$headers = str_getcsv(array_shift($lines), $sep);
$rows = [];
foreach ($lines as $line) {
if ($line === '') continue;
$cols = str_getcsv($line, $sep);
if (count($cols) !== count($headers)) {
// padding
$cols = array_pad($cols, count($headers), null);
}
$rows[] = array_combine($headers, $cols);
}
return $rows;
}
private function date($v): ?string
{
if (!$v) return null;
// support "MM/DD/YY HH:MM:SS" & "DD/MM/YYYY"
$v = trim($v);
if (preg_match('/^\d{2}\/\d{2}\/\d{2} /', $v)) {
$dt = \DateTime::createFromFormat('m/d/y H:i:s', $v);
return $dt?->format('Y-m-d');
}
if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $v)) {
$dt = \DateTime::createFromFormat('d/m/Y', $v);
return $dt?->format('Y-m-d');
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $v)) return $v;
return null;
}
private function money($v): float
{
if ($v === null || $v === '') return 0.0;
$s = str_replace(['.', ' '], ['', ''], (string)$v);
$s = str_replace(',', '.', $s);
return (float)$s;
}
private function decimal($v): float
{
return $this->money($v);
}
}