362 lines
12 KiB
PHP
362 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class ImportGesconIncDaEcCommand extends Command
|
|
{
|
|
protected $signature = 'import:gescon-inc-da-ec
|
|
{tenant : Tenant ID}
|
|
{--mdb= : Percorso file MDB contenente Inc_da_ec}
|
|
{--table=Inc_da_ec : Nome tabella Access}
|
|
{--stabile-id= : ID stabile locale}
|
|
{--stabile-code= : Codice stabile legacy (es. 0019)}
|
|
{--anno= : Anno gestione}
|
|
{--pdf-base=/mnt/gescon-archives/gescon : Root archivio PDF}
|
|
{--preview : Mostra preview senza scrivere}
|
|
{--limit=20 : Limite righe in preview o import}';
|
|
|
|
protected $description = 'Importa Inc_da_ec e mappa incassi_estratto_conto collegando incassi.n_riferimento = Num_incasso + Nome_file_pdf';
|
|
|
|
public function handle(): int
|
|
{
|
|
if (! Schema::hasTable('incassi_estratto_conto')) {
|
|
$this->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;
|
|
}
|
|
}
|