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

201 lines
9.0 KiB
PHP
Executable File

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class GesconQaEmissione extends Command
{
protected $signature = 'gescon:qa-emissione
{--stabile= : Codice stabile (es. 0021) oppure mnemonico (es. 148) oppure ID}
{--emissione= : Data emissione (YYYY-MM-DD) es. 2025-07-02}
{--scadenza= : Data scadenza (YYYY-MM-DD) es. 2025-07-12}
{--descr= : Filtro contains su descrizione (es. LUG-AGO-SET)}
{--limit=20 : Quante ricevute mostrare in output}';
protected $description = 'QA su una specifica emissione/rata: totali, numero avvisi (ricevute) e differenze domain vs gescon_import (se disponibile).';
public function handle(): int
{
$input = trim((string) ($this->option('stabile') ?? ''));
if ($input === '') {
$this->error('Manca --stabile= (es. --stabile=0021 oppure --stabile=148)');
return self::INVALID;
}
$dataEmissione = trim((string) ($this->option('emissione') ?? ''));
$dataScadenza = trim((string) ($this->option('scadenza') ?? ''));
$descr = trim((string) ($this->option('descr') ?? ''));
$limitRaw = (int) ($this->option('limit') ?? 20);
$limit = max(1, min(200, $limitRaw));
$stabile = DB::table('stabili')->where('codice_stabile', $input)->first();
if (! $stabile) {
$stabile = DB::table('stabili')->where('cod_stabile', $input)->first();
}
if (! $stabile && ctype_digit($input) && ! str_starts_with($input, '0')) {
$stabile = DB::table('stabili')->where('id', (int) $input)->first();
}
if (! $stabile) {
$this->error('Stabile non trovato per: ' . $input);
return self::FAILURE;
}
$stabileId = (int) $stabile->id;
$codiceStabile = (string) ($stabile->codice_stabile ?? '');
$codStabile = (string) ($stabile->cod_stabile ?? '');
$this->line('Stabile: #' . $stabileId . ' codice=' . $codiceStabile . ' mnemo=' . $codStabile . ' denom=' . (string) ($stabile->denominazione ?? ''));
$this->line('Filtri: emissione=' . ($dataEmissione !== '' ? $dataEmissione : '—') . ' scadenza=' . ($dataScadenza !== '' ? $dataScadenza : '—') . ' descr contains=' . ($descr !== '' ? $descr : '—'));
if (! Schema::hasTable('rate_emesse')) {
$this->error('Tabella rate_emesse non presente nel DB domain.');
return self::FAILURE;
}
$domainBase = DB::table('rate_emesse as r')
->join('unita_immobiliari as u', 'u.id', '=', 'r.unita_immobiliare_id')
->where('u.stabile_id', $stabileId);
if ($dataEmissione !== '') {
$domainBase->whereDate('r.data_emissione', $dataEmissione);
}
if ($dataScadenza !== '') {
$domainBase->whereDate('r.data_scadenza', $dataScadenza);
}
if ($descr !== '') {
$domainBase->where('r.descrizione', 'like', '%' . $descr . '%');
}
$domainAgg = (clone $domainBase)
->selectRaw('COUNT(*) as righe')
->selectRaw('COUNT(DISTINCT r.numero_rata_progressivo) as avvisi')
->selectRaw("COUNT(DISTINCT CONCAT(r.numero_rata_progressivo, '|', r.soggetto_responsabile_id)) as avvisi_x_soggetto")
->selectRaw('COALESCE(SUM(r.importo_addebitato_soggetto), 0) as totale_addebitato')
->selectRaw('COALESCE(SUM(r.importo_pagato), 0) as totale_pagato')
->first();
$this->newLine();
$this->info('DOMAIN (rate_emesse)');
$this->line(str_pad('righe', 22) . ': ' . (int) ($domainAgg->righe ?? 0));
$this->line(str_pad('avvisi (dist ricevuta)', 22) . ': ' . (int) ($domainAgg->avvisi ?? 0));
$this->line(str_pad('avvisi x soggetto', 22) . ': ' . (int) ($domainAgg->avvisi_x_soggetto ?? 0));
$this->line(str_pad('totale addebitato', 22) . ': ' . (string) ($domainAgg->totale_addebitato ?? 0));
$this->line(str_pad('totale pagato', 22) . ': ' . (string) ($domainAgg->totale_pagato ?? 0));
$topRicevute = (clone $domainBase)
->selectRaw('r.numero_rata_progressivo as ricevuta')
->selectRaw('COUNT(*) as righe')
->selectRaw('COUNT(DISTINCT r.unita_immobiliare_id) as n_unita')
->selectRaw('COUNT(DISTINCT r.soggetto_responsabile_id) as n_soggetti')
->selectRaw('COALESCE(SUM(r.importo_addebitato_soggetto), 0) as totale')
->groupBy('r.numero_rata_progressivo')
->orderByDesc('totale')
->limit($limit)
->get();
$this->newLine();
$this->info('TOP RICEVUTE (domain)');
foreach ($topRicevute as $row) {
$this->line(
'Ricevuta ' . str_pad((string) ($row->ricevuta ?? ''), 6)
. ' | totale=' . (string) ($row->totale ?? 0)
. ' | righe=' . (int) ($row->righe ?? 0)
. ' | unita=' . (int) ($row->n_unita ?? 0)
. ' | soggetti=' . (int) ($row->n_soggetti ?? 0)
);
}
// Staging gescon_import: se esiste, confronta emes_det.
$this->newLine();
$this->info('GESCON_IMPORT (se disponibile)');
try {
// In gescon_import, cod_stabile di solito contiene il codice cartella (es. 0021).
// Il mnemonico (es. 148) può vivere altrove: qui preferiamo codice_stabile.
$codForImport = $codiceStabile !== '' ? $codiceStabile : $codStabile;
$table = null;
if (Schema::connection('gescon_import')->hasTable('rate_emissioni_dettaglio')) {
$table = 'rate_emissioni_dettaglio';
} elseif (Schema::connection('gescon_import')->hasTable('emes_det')) {
$table = 'emes_det';
}
if (! $table) {
$this->line('rate_emissioni_dettaglio/emes_det: (tabella non presente)');
return self::SUCCESS;
}
$stg = DB::connection('gescon_import')->table($table . ' as d')
->where('d.cod_stabile', $codForImport)
->whereNotNull('d.numero_ricevuta');
if ($dataEmissione !== '' && Schema::connection('gescon_import')->hasColumn($table, 'data_emissione')) {
$stg->whereDate('d.data_emissione', $dataEmissione);
}
if ($dataScadenza !== '' && Schema::connection('gescon_import')->hasColumn($table, 'data_scadenza')) {
$stg->whereDate('d.data_scadenza', $dataScadenza);
}
if ($descr !== '' && Schema::connection('gescon_import')->hasColumn($table, 'descrizione')) {
$stg->where('d.descrizione', 'like', '%' . $descr . '%');
}
$stgAgg = (clone $stg)
->selectRaw('COUNT(*) as righe')
->selectRaw('COUNT(DISTINCT d.numero_ricevuta) as avvisi')
->selectRaw('COALESCE(SUM(COALESCE(d.importo_dovuto_euro, 0)), 0) as totale_dovuto_euro')
->first();
$this->line('tabella : ' . $table);
$this->line(str_pad('righe', 22) . ': ' . (int) ($stgAgg->righe ?? 0));
$this->line(str_pad('avvisi (dist ricevuta)', 22) . ': ' . (int) ($stgAgg->avvisi ?? 0));
$this->line(str_pad('totale dovuto euro', 22) . ': ' . (string) ($stgAgg->totale_dovuto_euro ?? 0));
// Differenze avvisi: staging (numero_ricevuta) vs domain (numero_rata_progressivo)
$stgRicevute = (clone $stg)
->select('d.numero_ricevuta')
->distinct()
->orderBy('d.numero_ricevuta')
->pluck('numero_ricevuta')
->map(fn($v) => (int) $v)
->filter(fn(int $v) => $v > 0)
->values()
->all();
$domRicevute = (clone $domainBase)
->select('r.numero_rata_progressivo')
->distinct()
->orderBy('r.numero_rata_progressivo')
->pluck('numero_rata_progressivo')
->map(fn($v) => (int) $v)
->filter(fn(int $v) => $v > 0)
->values()
->all();
$missing = array_values(array_diff($stgRicevute, $domRicevute));
sort($missing);
$this->newLine();
$this->info('AVVISI MANCANTI IN DOMAIN');
$this->line(str_pad('missing_count', 22) . ': ' . count($missing));
if (! empty($missing)) {
$missingSum = (string) (clone $stg)
->whereIn('d.numero_ricevuta', $missing)
->sum('d.importo_dovuto_euro');
$this->line(str_pad('missing_totale_euro', 22) . ': ' . $missingSum);
$preview = array_slice($missing, 0, 50);
$this->line('missing_ricevute (prime 50): ' . implode(', ', $preview));
}
} catch (\Throwable $e) {
$this->warn('Impossibile leggere gescon_import: ' . $e->getMessage());
}
return self::SUCCESS;
}
}