448 lines
16 KiB
PHP
448 lines
16 KiB
PHP
<?php
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class SyncGesconFattureToContabilitaCommand extends Command
|
|
{
|
|
protected $signature = 'gescon:sync-fatture-fornitori
|
|
{tenant : Tenant ID}
|
|
{--stabile-id= : ID stabile locale}
|
|
{--stabile-code= : Codice stabile legacy (es. 0019)}
|
|
{--anno= : Anno gestione}
|
|
{--limit= : Limite righe}
|
|
{--dry-run : Simula senza scrivere}';
|
|
|
|
protected $description = 'Sincronizza fatture legacy GESCON (staging o gescon_import.fatture) in contabilita_fatture_fornitori';
|
|
|
|
public function handle(): int
|
|
{
|
|
if (! Schema::hasTable('contabilita_fatture_fornitori')) {
|
|
$this->error('Tabella contabilita_fatture_fornitori non trovata.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$tenant = (string) $this->argument('tenant');
|
|
$stabileId = $this->resolveStabileId();
|
|
$stabileCode = trim((string) ($this->option('stabile-code') ?: ''));
|
|
$anno = $this->option('anno') ? (int) $this->option('anno') : null;
|
|
$limit = $this->option('limit') ? max(1, (int) $this->option('limit')) : null;
|
|
$dryRun = (bool) $this->option('dry-run');
|
|
|
|
if (! $stabileId) {
|
|
$this->error('Stabile non risolto: passa --stabile-id oppure --stabile-code.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
[$sourceType, $query] = $this->buildSourceQuery($tenant, $stabileCode, $anno);
|
|
if (! $query) {
|
|
$this->error('Nessuna sorgente disponibile: né stg_fatture_gescon né gescon_import.fatture.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
if ($limit) {
|
|
$query->limit($limit);
|
|
}
|
|
|
|
$rows = $query->get();
|
|
if ($rows->isEmpty()) {
|
|
$this->warn('Nessuna fattura da sincronizzare.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$codes = $rows->pluck('cod_fornitore')
|
|
->filter(fn($v) => trim((string) $v) !== '')
|
|
->map(fn($v) => trim((string) $v))
|
|
->flatMap(function (string $code) {
|
|
$trim = trim($code);
|
|
$noZero = ltrim($trim, '0');
|
|
|
|
$out = [$trim];
|
|
if ($noZero !== '' && $noZero !== $trim) {
|
|
$out[] = $noZero;
|
|
}
|
|
|
|
return $out;
|
|
})
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
$fornitoreMap = [];
|
|
if (! empty($codes) && Schema::hasTable('fornitori')) {
|
|
$fornitori = DB::table('fornitori')
|
|
->whereIn('old_id', $codes)
|
|
->get(['id', 'old_id']);
|
|
|
|
foreach ($fornitori as $fornitore) {
|
|
$raw = trim((string) $fornitore->old_id);
|
|
$noZero = ltrim($raw, '0');
|
|
|
|
if ($raw !== '') {
|
|
$fornitoreMap[$raw] = (int) $fornitore->id;
|
|
}
|
|
if ($noZero !== '') {
|
|
$fornitoreMap[$noZero] = (int) $fornitore->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
$created = 0;
|
|
$updated = 0;
|
|
$skipped = 0;
|
|
|
|
foreach ($rows as $row) {
|
|
$codForn = trim((string) ($row->cod_fornitore ?? ''));
|
|
$codFornNoZero = ltrim($codForn, '0');
|
|
$fornitoreId = $fornitoreMap[$codForn] ?? ($codFornNoZero !== '' ? ($fornitoreMap[$codFornNoZero] ?? null) : null);
|
|
|
|
if (! $fornitoreId) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$gestioneTipoRaw = strtoupper(trim((string) ($row->reg_gestione ?? 'O')));
|
|
$tipoGestione = match ($gestioneTipoRaw) {
|
|
'R' => 'riscaldamento',
|
|
'S' => 'straordinaria',
|
|
default => 'ordinaria',
|
|
};
|
|
|
|
$numeroStra = $tipoGestione === 'straordinaria' && is_numeric($row->reg_nstra ?? null)
|
|
? (int) $row->reg_nstra
|
|
: null;
|
|
|
|
$gestioneId = null;
|
|
if (Schema::hasTable('gestioni_contabili')) {
|
|
$gestioneQuery = DB::table('gestioni_contabili')
|
|
->where('tenant_id', $tenant)
|
|
->where('tipo_gestione', $tipoGestione)
|
|
->when($stabileId, fn($q) => $q->where('stabile_id', (int) $stabileId));
|
|
|
|
if ($anno) {
|
|
$gestioneQuery->where('anno_gestione', $anno);
|
|
}
|
|
|
|
if ($numeroStra !== null) {
|
|
$gestioneQuery->where('numero_straordinaria', $numeroStra);
|
|
}
|
|
|
|
$gestioneId = $gestioneQuery->value('id');
|
|
}
|
|
|
|
$dataDocumento = $this->toDate($row->data_fattura ?? null);
|
|
$numeroDocumento = $this->nullIfEmpty($row->numero_fattura ?? null);
|
|
$totale = $this->money($row->totale_fattura ?? $row->importo_totale ?? $row->importo_euro ?? 0);
|
|
|
|
if (! $dataDocumento || ! $numeroDocumento) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$existing = DB::table('contabilita_fatture_fornitori')
|
|
->where('stabile_id', (int) $stabileId)
|
|
->where('fornitore_id', (int) $fornitoreId)
|
|
->where('numero_documento', $numeroDocumento)
|
|
->whereDate('data_documento', $dataDocumento)
|
|
->first(['id']);
|
|
|
|
$payload = [
|
|
'stabile_id' => $stabileId,
|
|
'gestione_id' => $gestioneId,
|
|
'fornitore_id' => $fornitoreId,
|
|
'data_registrazione' => $dataDocumento,
|
|
'data_documento' => $dataDocumento,
|
|
'numero_documento' => $numeroDocumento,
|
|
'causale_contabile' => 'FATT_FORN',
|
|
'descrizione' => $this->nullIfEmpty($row->descrizione_sintetica ?? $row->descrizione ?? $row->descrizione_corpo ?? null),
|
|
'imponibile' => $this->money($row->imponibile ?? $row->importo_totale ?? 0),
|
|
'iva' => $this->money($row->importo_iva ?? $row->iva ?? 0),
|
|
'totale' => $totale,
|
|
'data_scadenza' => $this->toDate($row->data_scadenza ?? null),
|
|
'data_pagamento' => $this->toDate($row->data_pagamento ?? null),
|
|
'ritenuta_importo' => $this->money($row->importo_rda ?? $row->ritenuta ?? 0),
|
|
'netto_da_pagare' => $this->money($row->importo_netto ?? $totale),
|
|
'stato' => ! empty($row->data_pagamento) ? 'pagato' : 'inserito',
|
|
'note' => $this->buildNote($sourceType, $row),
|
|
'dati_fornitura' => json_encode([
|
|
'source_table' => $sourceType,
|
|
'legacy_id' => $row->legacy_id_fatture ?? null,
|
|
'legacy_cod_fornitore' => $codForn !== '' ? $codForn : null,
|
|
'legacy_riferimento' => $row->riferimento ?? null,
|
|
], JSON_UNESCAPED_UNICODE),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if (Schema::hasColumn('contabilita_fatture_fornitori', 'fattura_elettronica_id') && Schema::hasTable('fatture_elettroniche')) {
|
|
$feId = $this->resolveFatturaElettronicaId((int) $stabileId, (int) $fornitoreId, $numeroDocumento, $dataDocumento);
|
|
if ($feId) {
|
|
$payload['fattura_elettronica_id'] = $feId;
|
|
}
|
|
}
|
|
|
|
if ($dryRun) {
|
|
if ($existing) {
|
|
$updated++;
|
|
} else {
|
|
$created++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ($existing) {
|
|
DB::table('contabilita_fatture_fornitori')
|
|
->where('id', (int) $existing->id)
|
|
->update($payload);
|
|
$updated++;
|
|
} else {
|
|
$payload['created_at'] = now();
|
|
DB::table('contabilita_fatture_fornitori')->insert($payload);
|
|
$created++;
|
|
}
|
|
}
|
|
|
|
$mode = $dryRun ? 'SIMULAZIONE' : 'SYNC';
|
|
$this->info("{$mode} completata: create={$created}, aggiornate={$updated}, saltate={$skipped}");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function buildSourceQuery(string $tenant, string $stabileCode, ?int $anno): array
|
|
{
|
|
$legacyStabileIds = $this->resolveLegacyStabileIdCandidates($stabileCode);
|
|
$legacyCodiciStabile = $this->resolveLegacyCodiceCandidates($stabileCode);
|
|
|
|
if (Schema::hasTable('stg_fatture_gescon')) {
|
|
$query = DB::table('stg_fatture_gescon')
|
|
->where('tenant_id', $tenant);
|
|
|
|
if ($anno && Schema::hasColumn('stg_fatture_gescon', 'data_fattura')) {
|
|
$query->whereYear('data_fattura', $anno);
|
|
}
|
|
|
|
if (! empty($legacyStabileIds) && Schema::hasColumn('stg_fatture_gescon', 'legacy_id_stabile')) {
|
|
$query->whereIn('legacy_id_stabile', $legacyStabileIds);
|
|
}
|
|
|
|
if ((clone $query)->exists()) {
|
|
return ['stg_fatture_gescon', $query];
|
|
}
|
|
}
|
|
|
|
if (Schema::connection('gescon_import')->hasTable('fatture')) {
|
|
$query = DB::connection('gescon_import')->table('fatture');
|
|
|
|
if (! empty($legacyCodiciStabile) && Schema::connection('gescon_import')->hasColumn('fatture', 'cod_stabile')) {
|
|
$query->whereIn('cod_stabile', $legacyCodiciStabile);
|
|
}
|
|
|
|
if ($anno && Schema::connection('gescon_import')->hasColumn('fatture', 'data_fattura')) {
|
|
$query->whereYear('data_fattura', $anno);
|
|
}
|
|
|
|
return ['fatture', $query];
|
|
}
|
|
|
|
return [null, null];
|
|
}
|
|
|
|
private function resolveLegacyStabileIdCandidates(string $stabileCode): array
|
|
{
|
|
$ids = [];
|
|
|
|
$trim = trim($stabileCode);
|
|
if ($trim !== '' && is_numeric($trim)) {
|
|
$ids[] = (int) ltrim($trim, '0');
|
|
$ids[] = (int) $trim;
|
|
}
|
|
|
|
if ($trim !== '' && Schema::hasTable('stabili')) {
|
|
$row = DB::table('stabili')
|
|
->where(function ($q) use ($trim) {
|
|
$q->where('codice_stabile', $trim)
|
|
->orWhere('cod_stabile', $trim);
|
|
})
|
|
->first(['old_id', 'cod_stabile', 'codice_stabile']);
|
|
|
|
foreach ([(string) ($row->old_id ?? ''), (string) ($row->cod_stabile ?? ''), (string) ($row->codice_stabile ?? '')] as $value) {
|
|
$v = trim($value);
|
|
if ($v !== '' && is_numeric($v)) {
|
|
$ids[] = (int) ltrim($v, '0');
|
|
$ids[] = (int) $v;
|
|
}
|
|
}
|
|
}
|
|
|
|
$ids = array_values(array_unique(array_filter($ids, fn($v) => is_int($v) && $v > 0)));
|
|
return $ids;
|
|
}
|
|
|
|
private function resolveLegacyCodiceCandidates(string $stabileCode): array
|
|
{
|
|
$codes = [];
|
|
|
|
$trim = trim($stabileCode);
|
|
if ($trim !== '') {
|
|
$codes[] = $trim;
|
|
$noZero = ltrim($trim, '0');
|
|
if ($noZero !== '') {
|
|
$codes[] = $noZero;
|
|
if (is_numeric($noZero)) {
|
|
$codes[] = str_pad((string) ((int) $noZero), 4, '0', STR_PAD_LEFT);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($trim !== '' && Schema::hasTable('stabili')) {
|
|
$row = DB::table('stabili')
|
|
->where(function ($q) use ($trim) {
|
|
$q->where('codice_stabile', $trim)
|
|
->orWhere('cod_stabile', $trim);
|
|
})
|
|
->first(['old_id', 'cod_stabile', 'codice_stabile']);
|
|
|
|
foreach ([(string) ($row->old_id ?? ''), (string) ($row->cod_stabile ?? ''), (string) ($row->codice_stabile ?? '')] as $value) {
|
|
$v = trim($value);
|
|
if ($v === '') {
|
|
continue;
|
|
}
|
|
|
|
$codes[] = $v;
|
|
$noZero = ltrim($v, '0');
|
|
if ($noZero !== '') {
|
|
$codes[] = $noZero;
|
|
if (is_numeric($noZero)) {
|
|
$codes[] = str_pad((string) ((int) $noZero), 4, '0', STR_PAD_LEFT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$codes = array_values(array_unique(array_filter($codes, fn($v) => is_string($v) && $v !== '')));
|
|
return $codes;
|
|
}
|
|
|
|
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 nullIfEmpty($value): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
$trim = trim((string) $value);
|
|
return $trim === '' ? null : $trim;
|
|
}
|
|
|
|
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{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;
|
|
}
|
|
|
|
private function buildNote(string $sourceType, object $row): ?string
|
|
{
|
|
$parts = [
|
|
'Legacy ' . $sourceType,
|
|
];
|
|
|
|
if (! empty($row->legacy_id_fatture)) {
|
|
$parts[] = 'ID ' . $row->legacy_id_fatture;
|
|
}
|
|
|
|
if (! empty($row->riferimento)) {
|
|
$parts[] = 'Rif ' . $row->riferimento;
|
|
}
|
|
|
|
return implode(' · ', $parts);
|
|
}
|
|
|
|
private function resolveFatturaElettronicaId(int $stabileId, int $fornitoreId, string $numeroDocumento, string $dataDocumento): ?int
|
|
{
|
|
$numeroNorm = strtolower(trim((string) $numeroDocumento));
|
|
if ($stabileId <= 0 || $fornitoreId <= 0 || $numeroNorm === '' || $dataDocumento === '') {
|
|
return null;
|
|
}
|
|
|
|
$base = DB::table('fatture_elettroniche')
|
|
->where('stabile_id', $stabileId)
|
|
->whereDate('data_fattura', $dataDocumento)
|
|
->whereRaw('LOWER(TRIM(numero_fattura)) = ?', [$numeroNorm]);
|
|
|
|
$strict = (clone $base)
|
|
->where('fornitore_id', $fornitoreId)
|
|
->orderByDesc('id')
|
|
->value('id');
|
|
|
|
if (is_numeric($strict) && (int) $strict > 0) {
|
|
return (int) $strict;
|
|
}
|
|
|
|
$rows = (clone $base)
|
|
->orderByDesc('id')
|
|
->limit(2)
|
|
->pluck('id')
|
|
->filter(fn($v) => is_numeric($v))
|
|
->map(fn($v) => (int) $v)
|
|
->values()
|
|
->all();
|
|
|
|
if (count($rows) === 1) {
|
|
return $rows[0];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|