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

209 lines
8.3 KiB
PHP
Executable File

<?php
namespace App\Console\Commands;
use App\Models\Amministratore;
use App\Models\Stabile;
use App\Services\FattureElettroniche\CassettoFiscaleDownloadService;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
class FeCassettoImportSoggettoCommand extends Command
{
protected $signature = 'fe:cassetto-import-soggetto
{amministratore_id : ID amministratore proprietario del contenitore separato}
{codice_fiscale : CF/P.IVA del soggetto da interrogare sul cassetto fiscale}
{--label= : Denominazione del contenitore separato}
{--from= : Data inizio YYYY-MM-DD}
{--to= : Data fine YYYY-MM-DD}
{--quarterly=1 : 1 per spezzare automaticamente per trimestre}
{--metadati=0 : 1 per richiedere anche i metadati}
{--user_id=0 : User ID audit}
{--force_update=0 : 1 per forzare update/import anche su periodo già eseguito}
{--create_fornitore_if_missing=1 : 1 per creare i fornitori mancanti da FE}
{--importa_righe=auto : auto|si|no}';
protected $description = 'Scarica e importa FE dal Cassetto Fiscale per un soggetto fiscale in un contenitore separato dedicato.';
public function handle(CassettoFiscaleDownloadService $service): int
{
$amministratore = Amministratore::query()->find((int) $this->argument('amministratore_id'));
if (! $amministratore instanceof Amministratore) {
$this->error('Amministratore non trovato.');
return self::FAILURE;
}
$cf = $this->normalizeTaxId((string) $this->argument('codice_fiscale'));
if ($cf === '') {
$this->error('Codice fiscale / P.IVA non valido.');
return self::FAILURE;
}
$from = $this->parseYmd((string) $this->option('from'));
$to = $this->parseYmd((string) $this->option('to'));
if (! $from || ! $to) {
$this->error('Specificare --from=YYYY-MM-DD e --to=YYYY-MM-DD');
return self::FAILURE;
}
if ($from > $to) {
$this->error('Intervallo date non valido.');
return self::FAILURE;
}
$label = trim((string) $this->option('label'));
if ($label === '') {
$label = 'Archivio FE separato ' . $cf;
}
$stabile = $this->ensureDedicatedStabile($amministratore, $cf, $label);
$periods = (string) $this->option('quarterly') === '1'
? $this->splitQuarterly($from, $to)
: [[$from, $to]];
$rows = [];
$hasErrors = false;
foreach ($periods as [$dal, $al]) {
$result = $service->downloadAndImport(
$amministratore,
$stabile,
$dal,
$al,
(bool) ((int) $this->option('metadati') === 1),
(int) $this->option('user_id'),
[
'force_update' => (int) $this->option('force_update') === 1,
'create_fornitore_if_missing' => (int) $this->option('create_fornitore_if_missing') === 1,
'importa_righe' => (string) $this->option('importa_righe'),
'no_skip' => (int) $this->option('force_update') === 1,
],
);
$rows[] = [
'periodo' => $dal->format('Y-m-d') . ' -> ' . $al->format('Y-m-d'),
'status' => (string) ($result['status'] ?? 'error'),
'imported' => (string) ((int) ($result['imported'] ?? 0)),
'duplicates' => (string) ((int) ($result['duplicates'] ?? 0)),
'errors' => (string) ((int) ($result['errors'] ?? 0)),
'files' => (string) ((int) ($result['files_total'] ?? 0)),
'message' => Str::limit((string) ($result['message'] ?? ''), 120, '...'),
];
if (($result['status'] ?? 'error') === 'error') {
$hasErrors = true;
}
}
$this->line('Contenitore separato: stabile #' . (int) $stabile->id . ' [' . (string) $stabile->codice_stabile . '] ' . (string) $stabile->denominazione);
$this->table(['Periodo', 'Status', 'Importate', 'Duplicate', 'Errori', 'File', 'Messaggio'], $rows);
return $hasErrors ? self::FAILURE : self::SUCCESS;
}
private function ensureDedicatedStabile(Amministratore $amministratore, string $cf, string $label): Stabile
{
$existing = Stabile::query()
->where('amministratore_id', (int) $amministratore->id)
->where('codice_fiscale', $cf)
->orderBy('id')
->first();
if ($existing instanceof Stabile) {
$dirty = false;
if (! filled($existing->denominazione)) {
$existing->denominazione = $label;
$dirty = true;
}
if (! filled($existing->cod_fisc_amministratore) && filled($amministratore->codice_fiscale_studio)) {
$existing->cod_fisc_amministratore = (string) $amministratore->codice_fiscale_studio;
$dirty = true;
}
if ($dirty) {
$existing->save();
}
return $existing;
}
return Stabile::query()->create([
'amministratore_id' => (int) $amministratore->id,
'codice_stabile' => $this->nextDedicatedCode($amministratore, $cf),
'denominazione' => $label,
'codice_fiscale' => $cf,
'cod_fisc_amministratore' => (string) ($amministratore->codice_fiscale_studio ?? ''),
'indirizzo' => (string) ($amministratore->indirizzo_studio ?? 'Archivio separato FE'),
'cap' => (string) ($amministratore->cap_studio ?? '00000'),
'citta' => (string) ($amministratore->citta_studio ?? 'Roma'),
'provincia' => (string) ($amministratore->provincia_studio ?? 'RM'),
'attivo' => true,
'stato' => 'attivo',
'note' => 'Contenitore separato creato per import FE soggetto fiscale ' . $cf,
'amministratore_nome' => trim((string) (($amministratore->nome ?? '') . ' ' . ($amministratore->cognome ?? ''))),
'amministratore_email' => (string) ($amministratore->email_studio ?? ''),
'configurazione_avanzata' => ['scope' => 'fe_soggetto_separato'],
]);
}
private function nextDedicatedCode(Amministratore $amministratore, string $cf): string
{
$base = 'FE' . substr(preg_replace('/\D+/', '', $cf) ?: '000000', -6);
$base = str_pad(substr($base, 0, 8), 8, '0');
$code = $base;
$suffix = 1;
while (Stabile::query()->where('codice_stabile', $code)->exists()) {
$suffixText = str_pad((string) $suffix, 2, '0', STR_PAD_LEFT);
$code = substr($base, 0, 6) . $suffixText;
$suffix++;
}
return $code;
}
/**
* @return array<int,array{0:\DateTimeImmutable,1:\DateTimeImmutable}>
*/
private function splitQuarterly(\DateTimeImmutable $from, \DateTimeImmutable $to): array
{
$periods = [];
$cursor = $from;
while ($cursor <= $to) {
$quarter = intdiv(((int) $cursor->format('n')) - 1, 3) + 1;
$quarterEndMonth = $quarter * 3;
$quarterEnd = new \DateTimeImmutable($cursor->format('Y') . '-' . str_pad((string) $quarterEndMonth, 2, '0', STR_PAD_LEFT) . '-01');
$quarterEnd = $quarterEnd->modify('last day of this month');
if ($quarterEnd > $to) {
$quarterEnd = $to;
}
$periods[] = [$cursor, $quarterEnd];
$cursor = $quarterEnd->modify('+1 day');
}
return $periods;
}
private function parseYmd(string $value): ?\DateTimeImmutable
{
$value = trim($value);
if ($value === '') {
return null;
}
$date = \DateTimeImmutable::createFromFormat('Y-m-d', $value);
return $date instanceof \DateTimeImmutable ? $date : null;
}
private function normalizeTaxId(string $value) : string
{
return strtoupper(trim(preg_replace('/\s+/', '', $value) ?? ''));
}
}