2446 lines
99 KiB
PHP
Executable File
2446 lines
99 KiB
PHP
Executable File
<?php
|
|
namespace App\Filament\Pages\Gescon;
|
|
|
|
use App\Models\Amministratore;
|
|
use App\Models\DatiBancari;
|
|
use App\Models\GestioneContabile;
|
|
use App\Models\Stabile;
|
|
use App\Models\User;
|
|
use App\Services\GesconImport\EssentialImportService;
|
|
use App\Services\GesconImport\StabileEnrichmentService;
|
|
use App\Support\ModuleVisibility;
|
|
use App\Support\StabileContext;
|
|
use BackedEnum;
|
|
use Filament\Forms\Components\Checkbox;
|
|
use Filament\Forms\Components\FileUpload;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Concerns\InteractsWithForms;
|
|
use Filament\Forms\Contracts\HasForms;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Schemas\Components\Utilities\Set;
|
|
use Filament\Schemas\Schema;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use UnitEnum;
|
|
|
|
class ImportazioneArchivi extends Page implements HasForms
|
|
{
|
|
use InteractsWithForms;
|
|
|
|
protected static ?string $navigationLabel = 'Importazione archivi';
|
|
|
|
protected static ?string $title = 'Importazione archivi (GESCON)';
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-arrow-down-tray';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'GESCON';
|
|
|
|
protected static ?int $navigationSort = 5;
|
|
|
|
protected static ?string $slug = 'gescon/importazione-archivi';
|
|
|
|
protected string $view = 'filament.pages.gescon.importazione-archivi';
|
|
|
|
public ?array $data = [];
|
|
|
|
/** @var array<string, string> */
|
|
public array $stabiliOptions = [];
|
|
|
|
/** @var array<string, string> */
|
|
public array $anniOptions = [];
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
if (! config('netgescon.legacy_import_enabled', true)) {
|
|
return false;
|
|
}
|
|
|
|
$user = Auth::user();
|
|
|
|
return ModuleVisibility::canAccessGesconInternal($user, ['super-admin', 'admin', 'amministratore'])
|
|
&& $user instanceof User
|
|
&& $user->can('gescon-import.execute');
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$user = Auth::user();
|
|
|
|
$defaultPathRoot = '/mnt/gescon-archives/gescon';
|
|
|
|
$defaultStabileCode = null;
|
|
$defaultAnno = null;
|
|
$activeStabile = $user instanceof User ? StabileContext::getActiveStabile($user) : null;
|
|
if ($activeStabile) {
|
|
$rawCode = $activeStabile->codice_stabile ?? null;
|
|
if (is_string($rawCode) && $rawCode !== '') {
|
|
$defaultStabileCode = str_pad(trim($rawCode), 4, '0', STR_PAD_LEFT);
|
|
$label = $defaultStabileCode . ' — ' . trim((string) ($activeStabile->denominazione ?? ''));
|
|
$this->stabiliOptions = [$defaultStabileCode => trim($label, ' —')];
|
|
|
|
try {
|
|
$defaultAnno = DB::connection('gescon_import')
|
|
->table('anni_gestione')
|
|
->where('cod_stabile', $defaultStabileCode)
|
|
->max('anno');
|
|
} catch (\Throwable) {
|
|
$defaultAnno = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
$defaultAmministratoreId = null;
|
|
if ($user instanceof User && $user->amministratore) {
|
|
$defaultAmministratoreId = (int) $user->amministratore->id;
|
|
} elseif ($user instanceof User && $user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$defaultAmministratoreId = (int) (Amministratore::query()->orderBy('id')->value('id') ?? 0);
|
|
}
|
|
|
|
$this->getSchema('form')?->fill([
|
|
'source_mode' => 'linux_server',
|
|
'path_root' => $defaultPathRoot,
|
|
'stabili_mdb' => rtrim($defaultPathRoot, '/') . '/dbc/Stabili.mdb',
|
|
'fornitori_mdb' => rtrim($defaultPathRoot, '/') . '/dbc/Fornitori.mdb',
|
|
'amministratore_id' => $defaultAmministratoreId ?: null,
|
|
'stabile_code' => $defaultStabileCode,
|
|
'anno' => $defaultAnno,
|
|
'align_years_selected' => ['2024', '2025'],
|
|
'align_water_years_selected' => ['2024'],
|
|
'align_open_years_selected' => ['2024', '2025'],
|
|
'align_mark_obsolete_years' => false,
|
|
'align_with_f24' => true,
|
|
'with_unita' => true,
|
|
'with_soggetti' => true,
|
|
'with_diritti' => true,
|
|
'with_millesimi' => true,
|
|
'with_voci' => true,
|
|
'with_banche' => true,
|
|
'with_gestioni' => false,
|
|
'with_assemblee' => false,
|
|
'with_operazioni' => false,
|
|
'with_rate' => false,
|
|
'with_incassi' => false,
|
|
'with_giroconti' => false,
|
|
'with_straord' => false,
|
|
'with_addebiti' => false,
|
|
'with_detrazioni' => false,
|
|
'sync_staging' => true,
|
|
'dry_run' => false,
|
|
'stabile_denominazione' => '',
|
|
'stabile_indirizzo' => '',
|
|
'stabile_cap' => '',
|
|
'stabile_citta' => '',
|
|
'stabile_provincia' => '',
|
|
'stabile_codice_fiscale' => '',
|
|
'stabile_banca_principale' => '',
|
|
'stabile_iban_principale' => '',
|
|
'gestione_setup_enabled' => true,
|
|
'gestione_anno_iniziale' => $defaultAnno ?: (int) date('Y'),
|
|
'gestione_mostra_riscaldamento' => false,
|
|
'gestione_mesi_ordinaria' => [],
|
|
'gestione_mesi_riscaldamento' => [],
|
|
'gestione_replica_tutti_anni' => true,
|
|
]);
|
|
|
|
if (is_string($defaultStabileCode) && $defaultStabileCode !== '') {
|
|
$this->refreshAnniOptionsForStabile($defaultStabileCode, $defaultPathRoot);
|
|
$this->hydrateImportSetupState($defaultStabileCode, $defaultPathRoot, $activeStabile);
|
|
}
|
|
}
|
|
|
|
private function extractGesconZipToTemp(string $zipPath): ?string
|
|
{
|
|
if (! is_file($zipPath) || ! is_readable($zipPath)) {
|
|
return null;
|
|
}
|
|
|
|
if (! class_exists(\ZipArchive::class)) {
|
|
throw new \RuntimeException('Estrazione ZIP non disponibile: estensione PHP zip mancante.');
|
|
}
|
|
|
|
$base = storage_path('app/temp/gescon-ui-extracted');
|
|
if (! is_dir($base)) {
|
|
@mkdir($base, 0775, true);
|
|
}
|
|
|
|
$target = $base . '/' . date('Ymd_His') . '_' . bin2hex(random_bytes(4));
|
|
@mkdir($target, 0775, true);
|
|
|
|
$zip = new \ZipArchive();
|
|
if ($zip->open($zipPath) !== true) {
|
|
return null;
|
|
}
|
|
$zip->extractTo($target);
|
|
$zip->close();
|
|
|
|
// Se lo zip contiene una sola cartella root, entra automaticamente.
|
|
$entries = array_values(array_filter(scandir($target) ?: [], fn($n) => ! in_array($n, ['.', '..'], true)));
|
|
if (count($entries) === 1) {
|
|
$only = $target . '/' . $entries[0];
|
|
if (is_dir($only)) {
|
|
return $only;
|
|
}
|
|
}
|
|
|
|
return $target;
|
|
}
|
|
|
|
public function form(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->statePath('data')
|
|
->components([
|
|
Select::make('source_mode')
|
|
->label('Modalità caricamento')
|
|
->options([
|
|
'linux_server' => 'Linux / Server (path su disco)',
|
|
'windows_upload' => 'Windows / PC (carica file e cartelle)',
|
|
])
|
|
->required()
|
|
->dehydrated(),
|
|
|
|
TextInput::make('path_root')
|
|
->label('Percorso archivio GESCON (root)')
|
|
->helperText('Esempio: /mnt/gescon-archives/gescon. Dentro: <cod_stabile>/<anno>/singolo_anno.mdb e dbc/Stabili.mdb')
|
|
->required()
|
|
->visible(fn($get): bool => (string) $get('source_mode') !== 'windows_upload'),
|
|
|
|
FileUpload::make('stabili_mdb_upload')
|
|
->label('Seleziona Stabili.mdb (da PC)')
|
|
->helperText('Opzionale: carica il file da Windows/Linux. Verrà salvato sul server e usato per l\'import.')
|
|
->disk('local')
|
|
->directory('temp/gescon-ui')
|
|
->preserveFilenames()
|
|
->acceptedFileTypes([
|
|
'.mdb',
|
|
'application/octet-stream',
|
|
])
|
|
->visible(fn($get): bool => (string) $get('source_mode') === 'windows_upload')
|
|
->afterStateUpdated(function (Set $set, ?string $state): void {
|
|
if (! is_string($state) || $state === '') {
|
|
return;
|
|
}
|
|
$set('stabili_mdb', storage_path('app/' . $state));
|
|
// In modalità Windows il root può essere uno zip estratto; non forzare qui.
|
|
// In modalità Linux il root viene gestito dal path manuale/amministratore.
|
|
}),
|
|
|
|
TextInput::make('stabili_mdb')
|
|
->label('Percorso file Stabili.mdb')
|
|
->helperText('Esempio: /mnt/gescon-archives/gescon/dbc/Stabili.mdb')
|
|
->required()
|
|
->visible(fn($get): bool => (string) $get('source_mode') !== 'windows_upload')
|
|
->afterStateUpdated(function (Set $set, ?string $state): void {
|
|
if (! is_string($state) || trim($state) === '') {
|
|
return;
|
|
}
|
|
$path = trim($state);
|
|
$normalized = str_replace('\\', '/', $path);
|
|
if (str_ends_with($normalized, '/dbc/Stabili.mdb')) {
|
|
$set('path_root', dirname(dirname($path)));
|
|
} elseif (str_ends_with($normalized, '/Stabili.mdb')) {
|
|
$set('path_root', dirname($path));
|
|
}
|
|
}),
|
|
|
|
FileUpload::make('fornitori_mdb_upload')
|
|
->label('Seleziona Fornitori.mdb (da PC)')
|
|
->helperText('Opzionale: carica Fornitori.mdb; se non caricato verrà cercato in /dbc/Fornitori.mdb dentro il root.')
|
|
->disk('local')
|
|
->directory('temp/gescon-ui')
|
|
->preserveFilenames()
|
|
->acceptedFileTypes([
|
|
'.mdb',
|
|
'application/octet-stream',
|
|
])
|
|
->visible(fn($get): bool => (string) $get('source_mode') === 'windows_upload')
|
|
->afterStateUpdated(function (Set $set, ?string $state): void {
|
|
if (! is_string($state) || $state === '') {
|
|
return;
|
|
}
|
|
$set('fornitori_mdb', storage_path('app/' . $state));
|
|
}),
|
|
|
|
FileUpload::make('gescon_zip_upload')
|
|
->label('Carica archivio GESCON (ZIP)')
|
|
->helperText('Consigliato: zip della cartella GESCON del condominio/amministratore (con dbc/ e le cartelle stabili). Dopo upload verrà estratto su server e usato come root temporaneo.')
|
|
->disk('local')
|
|
->directory('temp/gescon-ui')
|
|
->preserveFilenames()
|
|
->acceptedFileTypes([
|
|
'.zip',
|
|
'application/zip',
|
|
'application/x-zip-compressed',
|
|
'application/octet-stream',
|
|
])
|
|
->visible(fn($get): bool => (string) $get('source_mode') === 'windows_upload')
|
|
->afterStateUpdated(function (Set $set, ?string $state): void {
|
|
if (! is_string($state) || $state === '') {
|
|
return;
|
|
}
|
|
|
|
$zipPath = storage_path('app/' . $state);
|
|
$root = $this->extractGesconZipToTemp($zipPath);
|
|
if (! is_string($root) || $root === '') {
|
|
return;
|
|
}
|
|
|
|
// Normalizza slashes: i metodi a valle lavorano con path reali
|
|
$set('path_root', $root);
|
|
|
|
// Se nello zip è presente dbc/Stabili.mdb o Stabili.mdb, auto-aggancia.
|
|
$cand1 = rtrim($root, '/') . '/dbc/Stabili.mdb';
|
|
$cand2 = rtrim($root, '/') . '/Stabili.mdb';
|
|
if (is_file($cand1)) {
|
|
$set('stabili_mdb', $cand1);
|
|
} elseif (is_file($cand2)) {
|
|
$set('stabili_mdb', $cand2);
|
|
}
|
|
|
|
$candF1 = rtrim($root, '/') . '/dbc/Fornitori.mdb';
|
|
$candF2 = rtrim($root, '/') . '/Fornitori.mdb';
|
|
if (is_file($candF1)) {
|
|
$set('fornitori_mdb', $candF1);
|
|
} elseif (is_file($candF2)) {
|
|
$set('fornitori_mdb', $candF2);
|
|
}
|
|
}),
|
|
|
|
TextInput::make('fornitori_mdb')
|
|
->label('Percorso file Fornitori.mdb (auto)')
|
|
->helperText('Se vuoto, verrà usato <root>/dbc/Fornitori.mdb')
|
|
->maxLength(600)
|
|
->dehydrated(),
|
|
|
|
Select::make('amministratore_id')
|
|
->label('Carica per amministratore')
|
|
->helperText('Seleziona il proprietario (tenant) dello stabile/import. Visibile solo a super-admin/admin.')
|
|
->options(function (): array {
|
|
return Amministratore::query()
|
|
->orderBy('id')
|
|
->get()
|
|
->mapWithKeys(function (Amministratore $a): array {
|
|
$label = trim((string) ($a->denominazione_studio ?: $a->nome_completo));
|
|
if ($label === '') {
|
|
$label = 'Amministratore #' . $a->id;
|
|
}
|
|
$code = (string) ($a->codice_univoco ?: $a->codice_amministratore ?: '');
|
|
$suffix = $code !== '' ? (' [' . $code . ']') : '';
|
|
return [$a->id => $label . $suffix . ' (ID ' . $a->id . ')'];
|
|
})
|
|
->all();
|
|
})
|
|
->searchable()
|
|
->required()
|
|
->afterStateUpdated(function (Set $set, $get, ?string $state): void {
|
|
if ((string) $get('source_mode') === 'windows_upload') {
|
|
// In modalità Windows prima si caricano i file/cartelle su temp.
|
|
// L'eventuale "salvataggio definitivo" su /mnt verrà gestito da un'azione dedicata.
|
|
return;
|
|
}
|
|
$ammId = (int) ($state ?? 0);
|
|
if ($ammId <= 0) {
|
|
return;
|
|
}
|
|
$root = '/mnt/gescon-archives/gescon';
|
|
$set('path_root', $root);
|
|
$set('stabili_mdb', $root . '/dbc/Stabili.mdb');
|
|
$set('fornitori_mdb', $root . '/dbc/Fornitori.mdb');
|
|
})
|
|
->visible(function (): bool {
|
|
$user = Auth::user();
|
|
return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin']);
|
|
}),
|
|
|
|
Select::make('stabile_code')
|
|
->label('Stabile da importare')
|
|
->options(fn(): array=> $this->stabiliOptions)
|
|
->searchable()
|
|
->reactive()
|
|
->afterStateUpdated(function (Set $set, $get, ?string $state): void {
|
|
$root = (string) ($get('path_root') ?? '/mnt/gescon-archives/gescon');
|
|
$this->refreshAnniOptionsForStabile((string) ($state ?? ''), $root, $set, $get);
|
|
})
|
|
->helperText('Prima premi “Carica elenco stabili da Stabili.mdb”, poi seleziona lo stabile.'),
|
|
|
|
TextInput::make('anno')
|
|
->label('Anno gestione (opzionale)')
|
|
->helperText('Inserisci un anno (es. 2025) o "all" per tutte le cartelle. Serve per trovare <stabile>/<anno>/singolo_anno.mdb quando lo staging non è già caricato.')
|
|
->dehydrated(),
|
|
|
|
Select::make('align_years_selected')
|
|
->label('Gestioni da caricare (ultima aperta -> ritroso)')
|
|
->options(fn(): array=> $this->anniOptions)
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->dehydrateStateUsing(fn($state): array=> $this->normalizeSelectedOptionValues((array) ($state ?? []), $this->anniOptions))
|
|
->validationMessages(['in' => 'Seleziona anni validi dall elenco aggiornato.'])
|
|
->helperText('Seleziona solo le gestioni che vuoi importare, partendo dall ultima disponibile e andando a ritroso. Le directory legacy vengono mappate automaticamente da tabella anni (nome_dir).')
|
|
->dehydrated(),
|
|
|
|
Select::make('align_water_years_selected')
|
|
->label('Anni acqua target')
|
|
->options(fn(): array=> $this->anniOptions)
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->dehydrateStateUsing(fn($state): array=> $this->normalizeSelectedOptionValues((array) ($state ?? []), $this->anniOptions))
|
|
->validationMessages(['in' => 'Seleziona anni acqua validi dall elenco aggiornato.'])
|
|
->helperText('Per ACQUA (acqua_gen/acqua_dett/acqua_fatture).')
|
|
->dehydrated(),
|
|
|
|
Select::make('align_open_years_selected')
|
|
->label('Gestioni da lasciare aperte')
|
|
->options(fn(): array=> $this->anniOptions)
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->dehydrateStateUsing(fn($state): array=> $this->normalizeSelectedOptionValues((array) ($state ?? []), $this->anniOptions))
|
|
->validationMessages(['in' => 'Seleziona anni aperti validi dall elenco aggiornato.'])
|
|
->helperText('Normalmente qui resta selezionata l ultima gestione aperta. Gli anni fuori da questa selezione vengono chiusi secondo le regole di import.')
|
|
->dehydrated(),
|
|
|
|
Checkbox::make('align_mark_obsolete_years')
|
|
->label('Marca subito come obsoleti/chiusi gli anni importati')
|
|
->helperText('Utile per import storico a fini archivio.'),
|
|
|
|
Checkbox::make('align_with_f24')
|
|
->label('Importa anche modello F24 da generale_stabile.mdb')
|
|
->helperText('Legge F24.cod_tributo_1..6, Rateiz_1..6, Anno_1..6 e importi correlati.'),
|
|
|
|
\Filament\Schemas\Components\Section::make('Seleziona dati da importare / sincronizzare')
|
|
->description('Seleziona i moduli da importare e clicca sul pulsante in basso per avviare l\'importazione dei soli dati selezionati.')
|
|
->columns(3)
|
|
->schema([
|
|
Checkbox::make('with_unita')->label('Unità'),
|
|
Checkbox::make('with_soggetti')->label('Anagrafiche (soggetti)'),
|
|
Checkbox::make('with_diritti')->label('Diritti'),
|
|
Checkbox::make('with_millesimi')->label('Millesimi'),
|
|
Checkbox::make('with_voci')
|
|
->label('Voci di spesa')
|
|
->reactive()
|
|
->afterStateUpdated(function (Set $set, $state): void {
|
|
if ($state) {
|
|
$set('with_millesimi', true);
|
|
}
|
|
}),
|
|
Checkbox::make('with_banche')->label('Banche / casse'),
|
|
Checkbox::make('with_gestioni')->label('Gestioni (anni)'),
|
|
Checkbox::make('with_assemblee')->label('Assemblee legacy'),
|
|
Checkbox::make('with_operazioni')
|
|
->label('Operazioni (Movimenti)')
|
|
->reactive()
|
|
->afterStateUpdated(function (Set $set, $state): void {
|
|
if ($state) {
|
|
$set('with_voci', true);
|
|
$set('with_millesimi', true);
|
|
$set('with_banche', true);
|
|
$set('with_unita', true);
|
|
$set('with_soggetti', true);
|
|
$set('with_diritti', true);
|
|
}
|
|
}),
|
|
Checkbox::make('with_rate')->label('Rate'),
|
|
Checkbox::make('with_incassi')
|
|
->label('Incassi')
|
|
->reactive()
|
|
->afterStateUpdated(function (Set $set, $state): void {
|
|
if ($state) {
|
|
$set('with_rate', true);
|
|
$set('with_banche', true);
|
|
}
|
|
}),
|
|
Checkbox::make('with_giroconti')->label('Giroconti'),
|
|
Checkbox::make('with_straord')->label('Straordinari'),
|
|
Checkbox::make('with_addebiti')->label('Addebiti'),
|
|
Checkbox::make('with_detrazioni')->label('Detrazioni fiscali'),
|
|
Checkbox::make('sync_staging')->label('Aggiorna staging prima di importare'),
|
|
Checkbox::make('dry_run')->label('Dry-run (non scrive nel DB)'),
|
|
Checkbox::make('update_only')
|
|
->label('Solo aggiornamento incrementale (non cancella i record esistenti)')
|
|
->default(true),
|
|
|
|
\Filament\Schemas\Components\Actions::make([
|
|
\Filament\Actions\Action::make('importa_selezionati_btn')
|
|
->label('Avvia Sincronizzazione / Importazione Dati Selezionati')
|
|
->icon('heroicon-m-arrow-down-tray')
|
|
->color('success')
|
|
->requiresConfirmation()
|
|
->action(function (\App\Services\GesconImport\EssentialImportService $service) {
|
|
$this->eseguiImportSelezionato($service);
|
|
})
|
|
])->columnSpanFull()
|
|
]),
|
|
]);
|
|
}
|
|
|
|
public function loadStabili(): void
|
|
{
|
|
$state = $this->getImportFormState();
|
|
$mdb = (string) ($state['stabili_mdb'] ?? '');
|
|
|
|
if ($mdb === '' || ! is_file($mdb) || ! is_readable($mdb)) {
|
|
Notification::make()
|
|
->title('Stabili.mdb non disponibile')
|
|
->body('Verifica percorso e che il mount sia attivo: ' . $mdb)
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$rows = $this->mdbExportToRows($mdb, ['Stabili', 'stabili', 'Condomin', 'condomin']);
|
|
if (empty($rows)) {
|
|
Notification::make()
|
|
->title('Nessun record letto da Stabili.mdb')
|
|
->body('Controlla che mdbtools sia installato e che il file sia valido.')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$opts = [];
|
|
foreach ($rows as $r) {
|
|
$code = $this->firstNonEmpty($r, ['cod_stabile', 'codice_stabile', 'stabile', 'codice', 'cod']);
|
|
if (! $code) {
|
|
continue;
|
|
}
|
|
$code = str_pad(trim((string) $code), 4, '0', STR_PAD_LEFT);
|
|
$denom = $this->firstNonEmpty($r, ['denominazione', 'descrizione', 'desc', 'nome', 'ragione_sociale']);
|
|
$label = $code . ($denom ? (' — ' . trim((string) $denom)) : '');
|
|
$opts[$code] = $label;
|
|
}
|
|
|
|
if (empty($opts)) {
|
|
Notification::make()
|
|
->title('Elenco stabili vuoto')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
ksort($opts);
|
|
$this->stabiliOptions = $opts;
|
|
|
|
$state = $this->getImportFormState();
|
|
if (empty($state['stabile_code'])) {
|
|
$active = Auth::user() instanceof User ? StabileContext::getActiveStabile(Auth::user()) : null;
|
|
$activeCode = $active?->codice_stabile ?? null;
|
|
if (is_string($activeCode) && $activeCode !== '') {
|
|
$activeCode = str_pad(trim($activeCode), 4, '0', STR_PAD_LEFT);
|
|
if (isset($opts[$activeCode])) {
|
|
$state['stabile_code'] = $activeCode;
|
|
$this->getSchema('form')?->fill($state);
|
|
}
|
|
}
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$this->refreshAnniOptionsForStabile(
|
|
(string) ($state['stabile_code'] ?? ''),
|
|
(string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon')
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Elenco stabili caricato')
|
|
->body('Trovati: ' . count($opts))
|
|
->success()
|
|
->send();
|
|
|
|
$selectedCode = (string) (($this->getImportFormState())['stabile_code'] ?? '');
|
|
if ($selectedCode !== '') {
|
|
$this->hydrateImportSetupState($selectedCode, (string) (($this->getImportFormState())['path_root'] ?? '/mnt/gescon-archives/gescon'));
|
|
}
|
|
}
|
|
|
|
public function caricaSetupDaArchivio(): void
|
|
{
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
$path = (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon');
|
|
|
|
if ($code === '') {
|
|
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$legacyRow = $this->resolveLegacyStabileRow($code, $path);
|
|
$stabile = $this->resolveSelectedStabileByCode($code);
|
|
|
|
if ($legacyRow === null && ! $stabile instanceof Stabile) {
|
|
Notification::make()
|
|
->title('Nessun dato disponibile')
|
|
->body('Non ho trovato dati legacy o locali per lo stabile selezionato.')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$this->hydrateImportSetupState($code, $path, $stabile, $legacyRow);
|
|
|
|
Notification::make()
|
|
->title('Setup caricato')
|
|
->body('Dati stabile, banca e gestione caricati nella stessa schermata.')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function salvaSetupStabileImportazione(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$amm = $this->resolveImportAmministratore($user, $this->getImportFormState());
|
|
if (! $amm instanceof Amministratore) {
|
|
Notification::make()->title('Nessun amministratore disponibile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$stabile = $this->upsertSelectedStabileFromImportState($amm);
|
|
$this->normalizeImportedFinancialSetup($stabile);
|
|
|
|
if ((bool) (($this->getImportFormState())['gestione_setup_enabled'] ?? true)) {
|
|
$this->applyGestioneSetup($stabile);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Salvataggio setup fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
StabileContext::setActiveStabileId($user, (int) $stabile->id);
|
|
$this->hydrateImportSetupState($stabile->codice_stabile, (string) (($this->getImportFormState())['path_root'] ?? '/mnt/gescon-archives/gescon'), $stabile);
|
|
|
|
Notification::make()
|
|
->title('Setup stabile salvato')
|
|
->body('Lo stabile viene aggiornato in-place dalla stessa schermata, senza creare duplicati.')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function salvaSetupGestioneImportazione(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$amm = $this->resolveImportAmministratore($user, $this->getImportFormState());
|
|
if (! $amm instanceof Amministratore) {
|
|
Notification::make()->title('Nessun amministratore disponibile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$stabile = $this->upsertSelectedStabileFromImportState($amm);
|
|
$count = $this->applyGestioneSetup($stabile);
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Salvataggio gestione fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$this->hydrateImportSetupState($stabile->codice_stabile, (string) (($this->getImportFormState())['path_root'] ?? '/mnt/gescon-archives/gescon'), $stabile);
|
|
|
|
Notification::make()
|
|
->title('Gestione aggiornata')
|
|
->body('Gestioni create o aggiornate: ' . $count)
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function creaStabileDaMdb(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
if ($code === '') {
|
|
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
|
|
$amm = $this->resolveImportAmministratore($user, $state);
|
|
if (! $amm) {
|
|
Notification::make()
|
|
->title('Nessun amministratore disponibile')
|
|
->body('Serve almeno un record in tabella amministratori.')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$stabile = $this->upsertSelectedStabileFromImportState($amm);
|
|
$this->normalizeImportedFinancialSetup($stabile);
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Creazione o aggiornamento stabile fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
StabileContext::setActiveStabileId($user, (int) $stabile->id);
|
|
$this->hydrateImportSetupState($code, (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon'), $stabile);
|
|
|
|
Notification::make()
|
|
->title('Stabile creato o aggiornato')
|
|
->body('I dati anagrafici, la banca principale e la cassa CON vengono riallineati direttamente da questa schermata.')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function applicaEnrichmentStabile(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
$path = (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon');
|
|
|
|
if ($code === '') {
|
|
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$res = (new StabileEnrichmentService())->enrich($code, $path);
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Enrichment fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$body = 'Stabile: ' . $code
|
|
. ' | catasto=' . (! empty($res['catasto']) ? 'OK' : '-')
|
|
. ' | rate=' . (! empty($res['rate_flags']) ? 'OK' : '-')
|
|
. ' | banca/posta=' . (! empty($res['banca_posta']) ? 'OK' : '-');
|
|
|
|
Notification::make()
|
|
->title('Enrichment completato')
|
|
->body($body)
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function aggiornaAnagraficheDaCondomin(): void
|
|
{
|
|
@set_time_limit(0);
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
if ($code === '') {
|
|
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$dryRun = (bool) ($state['dry_run'] ?? false);
|
|
|
|
$params = [
|
|
'--stabile' => $code,
|
|
'--limit' => 20000,
|
|
'--only' => 'both',
|
|
'--link-ruolo' => true,
|
|
'--include-comproprietari' => true,
|
|
];
|
|
if (! $dryRun) {
|
|
$params['--apply'] = true;
|
|
}
|
|
|
|
Artisan::call('gescon:auto-sync-anagrafiche', $params);
|
|
$out = trim((string) Artisan::output());
|
|
$lines = $out === '' ? [] : preg_split('/\r\n|\r|\n/', $out);
|
|
$tail = $lines ? implode("\n", array_slice($lines, -8)) : 'Comando eseguito.';
|
|
|
|
Notification::make()
|
|
->title($dryRun ? 'Dry-run anagrafiche (condomin) completato' : 'Sync anagrafiche (condomin) completato')
|
|
->body($tail)
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function risincronizzaRelazioniImportate(): void
|
|
{
|
|
@set_time_limit(0);
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
if ($code === '') {
|
|
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$stabile = $this->resolveSelectedStabileByCode($code);
|
|
if (! $stabile instanceof Stabile) {
|
|
Notification::make()->title('Stabile locale non trovato')->body('Prima crea o apri lo stabile selezionato.')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$result = $this->runRelationSyncSequence($code, (int) $stabile->id, (bool) ($state['dry_run'] ?? false));
|
|
if (! $result['ok']) {
|
|
Notification::make()
|
|
->title('Risincronizzazione relazioni fallita')
|
|
->body((string) ($result['message'] ?? 'Errore non specificato.'))
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
Notification::make()
|
|
->title((bool) ($state['dry_run'] ?? false) ? 'Dry-run relazioni completato' : 'Risincronizzazione relazioni completata')
|
|
->body((string) ($result['message'] ?? 'Operazione completata'))
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function aggiornaFornitoriGescon(): void
|
|
{
|
|
@set_time_limit(0);
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$path = (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon');
|
|
|
|
$amm = null;
|
|
if ($user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$ammId = (int) ($state['amministratore_id'] ?? 0);
|
|
$amm = $ammId > 0 ? Amministratore::query()->find($ammId) : null;
|
|
} else {
|
|
$amm = $user->amministratore;
|
|
}
|
|
if (! $amm) {
|
|
$amm = Amministratore::query()->orderBy('id')->first();
|
|
}
|
|
if (! $amm) {
|
|
Notification::make()->title('Nessun amministratore disponibile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$mdb = (string) ($state['fornitori_mdb'] ?? '');
|
|
if ($mdb === '') {
|
|
$mdb = rtrim($path, '/') . '/dbc/Fornitori.mdb';
|
|
}
|
|
if (! is_file($mdb)) {
|
|
Notification::make()
|
|
->title('Fornitori.mdb non trovato')
|
|
->body('Percorso atteso: ' . $mdb)
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$exit = Artisan::call('gescon:sync-fornitori-legacy', [
|
|
'amministratore_id' => (string) $amm->id,
|
|
'--mdb' => $mdb,
|
|
]);
|
|
$out = trim((string) Artisan::output());
|
|
} catch (\Throwable $e) {
|
|
Notification::make()->title('Aggiornamento fornitori fallito')->body($e->getMessage())->danger()->send();
|
|
return;
|
|
}
|
|
|
|
if ($exit !== 0) {
|
|
$snippet = $out !== '' ? $out : '';
|
|
$snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : '';
|
|
|
|
Notification::make()
|
|
->title('Aggiornamento fornitori: errore')
|
|
->body('Exit sync=' . $exit . ($snippet !== '' ? "\n\n" . $snippet : ''))
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
Notification::make()
|
|
->title('Fornitori aggiornati')
|
|
->body('Amministratore: ' . ($amm->codice_univoco ?: ('ID ' . $amm->id)))
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function refreshImportTagLegacyFornitoriGlobale(): void
|
|
{
|
|
@set_time_limit(0);
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$path = (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon');
|
|
$dryRun = (bool) ($state['dry_run'] ?? false);
|
|
|
|
$amm = null;
|
|
if ($user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$ammId = (int) ($state['amministratore_id'] ?? 0);
|
|
$amm = $ammId > 0 ? Amministratore::query()->find($ammId) : null;
|
|
} else {
|
|
$amm = $user->amministratore;
|
|
}
|
|
if (! $amm) {
|
|
$amm = Amministratore::query()->orderBy('id')->first();
|
|
}
|
|
if (! $amm) {
|
|
Notification::make()->title('Nessun amministratore disponibile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$mdb = (string) ($state['fornitori_mdb'] ?? '');
|
|
if ($mdb === '') {
|
|
$mdb = rtrim($path, '/') . '/dbc/Fornitori.mdb';
|
|
}
|
|
if (! is_file($mdb)) {
|
|
Notification::make()
|
|
->title('Fornitori.mdb non trovato')
|
|
->body('Percorso atteso: ' . $mdb)
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$exit = Artisan::call('gescon:sync-fornitori-legacy', [
|
|
'amministratore_id' => (string) $amm->id,
|
|
'--mdb' => $mdb,
|
|
'--dry-run' => $dryRun,
|
|
]);
|
|
$out = trim((string) Artisan::output());
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Refresh + import TAG fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
if ($exit !== 0) {
|
|
$snippet = $out !== '' ? $out : '';
|
|
$snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : '';
|
|
|
|
Notification::make()
|
|
->title('Refresh + import TAG: errore')
|
|
->body('Exit sync=' . $exit . ($snippet !== '' ? "\n\n" . $snippet : ''))
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
Notification::make()
|
|
->title($dryRun ? 'Dry-run TAG globale completato' : 'Refresh + import TAG globale completato')
|
|
->body('Amministratore: ' . ($amm->codice_univoco ?: ('ID ' . $amm->id)))
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function applicaEnrichmentTuttiStabili(): void
|
|
{
|
|
@set_time_limit(0);
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$path = (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon');
|
|
|
|
$amm = null;
|
|
if ($user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$ammId = (int) ($state['amministratore_id'] ?? 0);
|
|
$amm = $ammId > 0 ? Amministratore::query()->find($ammId) : null;
|
|
} else {
|
|
$amm = $user->amministratore;
|
|
}
|
|
if (! $amm) {
|
|
$amm = Amministratore::query()->orderBy('id')->first();
|
|
}
|
|
if (! $amm) {
|
|
Notification::make()->title('Nessun amministratore disponibile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$exit = Artisan::call('gescon:enrich-stabili', [
|
|
'--path' => $path,
|
|
'--amministratore-id' => (string) $amm->id,
|
|
]);
|
|
$out = trim((string) Artisan::output());
|
|
} catch (\Throwable $e) {
|
|
Notification::make()->title('Enrichment massivo fallito')->body($e->getMessage())->danger()->send();
|
|
return;
|
|
}
|
|
|
|
if ($exit !== 0) {
|
|
$snippet = $out !== '' ? mb_substr($out, 0, 800) : '';
|
|
Notification::make()->title('Enrichment massivo: errore')->body($snippet)->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$lastLine = $out !== '' ? trim((string) collect(preg_split('/\R/', $out) ?: [])->last()): '';
|
|
|
|
Notification::make()
|
|
->title('Enrichment massivo completato')
|
|
->body($lastLine !== '' ? $lastLine : 'Operazione completata')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function eseguiImportSelezionato(EssentialImportService $service): void
|
|
{
|
|
@set_time_limit(0);
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
if ($code === '') {
|
|
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$amm = $this->resolveImportAmministratore($user, $state);
|
|
if (! $amm) {
|
|
Notification::make()->title('Nessun amministratore disponibile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$stabile = $this->upsertSelectedStabileFromImportState($amm);
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Preparazione stabile fallita')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$options = [
|
|
'path' => (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon'),
|
|
'anno' => isset($state['anno']) && trim((string) $state['anno']) !== '' ? trim((string) $state['anno']) : null,
|
|
'dry' => (bool) ($state['dry_run'] ?? false),
|
|
'sync_staging' => (bool) ($state['sync_staging'] ?? false),
|
|
'with_unita' => (bool) ($state['with_unita'] ?? false),
|
|
'with_soggetti' => (bool) ($state['with_soggetti'] ?? false),
|
|
'with_diritti' => (bool) ($state['with_diritti'] ?? false),
|
|
'with_millesimi' => (bool) ($state['with_millesimi'] ?? false),
|
|
'with_voci' => (bool) ($state['with_voci'] ?? false),
|
|
'with_banche' => (bool) ($state['with_banche'] ?? false),
|
|
'with_gestioni' => (bool) ($state['with_gestioni'] ?? false),
|
|
'with_assemblee' => (bool) ($state['with_assemblee'] ?? false),
|
|
'with_operazioni' => (bool) ($state['with_operazioni'] ?? false),
|
|
'with_rate' => (bool) ($state['with_rate'] ?? false),
|
|
'with_incassi' => (bool) ($state['with_incassi'] ?? false),
|
|
'with_giroconti' => (bool) ($state['with_giroconti'] ?? false),
|
|
'with_straord' => (bool) ($state['with_straord'] ?? false),
|
|
'with_addebiti' => (bool) ($state['with_addebiti'] ?? false),
|
|
'with_detrazioni' => (bool) ($state['with_detrazioni'] ?? false),
|
|
'update_only' => (bool) ($state['update_only'] ?? true),
|
|
];
|
|
|
|
if (! empty($options['with_operazioni'])) {
|
|
$options['with_voci'] = true;
|
|
$options['with_millesimi'] = true;
|
|
$options['with_banche'] = true;
|
|
$options['with_unita'] = true;
|
|
$options['with_soggetti'] = true;
|
|
$options['with_diritti'] = true;
|
|
|
|
// Sincronizzazione automatica Fornitori collegata
|
|
try {
|
|
$fornMdb = (string) ($state['fornitori_mdb'] ?? '');
|
|
if ($fornMdb === '') {
|
|
$fornMdb = rtrim($options['path'], '/') . '/dbc/Fornitori.mdb';
|
|
}
|
|
if (is_file($fornMdb)) {
|
|
Artisan::call('gescon:auto-sync-fornitori', [
|
|
'--fornitori-mdb' => $fornMdb,
|
|
'--path-root' => $options['path'],
|
|
'--apply' => true,
|
|
]);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Silently skip or log
|
|
}
|
|
}
|
|
|
|
if (! empty($options['with_rate'])) {
|
|
$options['with_unita'] = true;
|
|
$options['with_soggetti'] = true;
|
|
$options['with_diritti'] = true;
|
|
}
|
|
|
|
if (! empty($options['with_incassi'])) {
|
|
$options['with_rate'] = true;
|
|
$options['with_banche'] = true;
|
|
}
|
|
|
|
if (! empty($options['with_voci'])) {
|
|
$options['with_millesimi'] = true;
|
|
}
|
|
|
|
$order = [
|
|
'unita',
|
|
'soggetti',
|
|
'diritti',
|
|
'millesimi',
|
|
'voci',
|
|
'banche',
|
|
'assemblee',
|
|
'operazioni',
|
|
'rate',
|
|
'incassi',
|
|
'giroconti',
|
|
'straord',
|
|
'addebiti',
|
|
'detrazioni',
|
|
];
|
|
|
|
$steps = [];
|
|
foreach ($order as $step) {
|
|
$flag = 'with_' . $step;
|
|
if (! empty($options[$flag])) {
|
|
$steps[] = $step;
|
|
}
|
|
}
|
|
|
|
if (empty($steps) && ! empty($options['with_gestioni'])) {
|
|
$steps = [];
|
|
}
|
|
|
|
if (empty($steps) && empty($options['with_gestioni'])) {
|
|
Notification::make()
|
|
->title('Nessun dato selezionato')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$outputs = [];
|
|
|
|
if (! empty($options['with_operazioni'])) {
|
|
try {
|
|
$exit = Artisan::call('gescon:sync-fornitori-legacy', [
|
|
'amministratore_id' => (string) $amm->id,
|
|
'--mdb' => (string) ($state['fornitori_mdb'] ?? rtrim($options['path'], '/') . '/dbc/Fornitori.mdb'),
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
if ($exit !== 0) {
|
|
Notification::make()
|
|
->title('Aggiornamento fornitori: errore')
|
|
->body('Exit sync=' . $exit)
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Aggiornamento fornitori fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (! empty($options['sync_staging'])) {
|
|
$singoloMdb = null;
|
|
$annoFolder = $options['anno'] ?: '0001';
|
|
if (! empty($options['path']) && ! empty($code)) {
|
|
if (is_numeric($annoFolder) && strlen($annoFolder) === 4 && (int)$annoFolder > 1900) {
|
|
$annoFolder = $this->resolveYearToFolder($code, $options['path'], $annoFolder);
|
|
}
|
|
$singoloMdb = rtrim($options['path'], '/') . '/' . $code . '/' . $annoFolder . '/singolo_anno.mdb';
|
|
}
|
|
|
|
$generaleMdb = ! empty($options['path']) && ! empty($code)
|
|
? rtrim($options['path'], '/') . '/' . $code . '/generale_stabile.mdb'
|
|
: null;
|
|
|
|
try {
|
|
$legacyYearArg = $annoFolder;
|
|
|
|
if ((! empty($options['with_banche']) || ! empty($options['with_operazioni']) || ! empty($options['with_incassi'])) && ! empty($options['path']) && ! empty($code)) {
|
|
foreach ($this->resolveLegacySingoloMdbPaths($code, (string) $options['path'], $legacyYearArg !== '' ? $legacyYearArg : null) as $mdbPath => $legacyDir) {
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $mdbPath,
|
|
'--table' => 'anagr_casse',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $legacyDir,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
}
|
|
}
|
|
|
|
if (! empty($options['with_operazioni']) && is_string($singoloMdb)) {
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $singoloMdb,
|
|
'--table' => 's_cassa',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $annoFolder,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $singoloMdb,
|
|
'--table' => 'operazioni',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $annoFolder,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $singoloMdb,
|
|
'--table' => 'voc_spe',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $annoFolder,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $singoloMdb,
|
|
'--table' => 'tabelle',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $annoFolder,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
}
|
|
|
|
if (! empty($options['with_incassi']) && is_string($singoloMdb)) {
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $singoloMdb,
|
|
'--table' => 'incassi',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $annoFolder,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
}
|
|
|
|
if (! empty($options['with_rate']) && is_string($generaleMdb)) {
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $generaleMdb,
|
|
'--table' => 'emes_gen',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $annoFolder,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
Artisan::call('gescon:load-mdb', [
|
|
'--mdb' => $generaleMdb,
|
|
'--table' => 'emes_det',
|
|
'--stabile' => $code,
|
|
'--legacy-year' => $annoFolder,
|
|
'--refresh-slice' => true,
|
|
]);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Aggiornamento staging fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
}
|
|
foreach ($steps as $step) {
|
|
$params = [
|
|
'--stabile' => $code,
|
|
'--solo' => $step,
|
|
'--path' => $options['path'],
|
|
];
|
|
if (! empty($options['anno'])) {
|
|
$annoParam = $options['anno'];
|
|
if (is_numeric($annoParam) && strlen($annoParam) === 4 && (int)$annoParam > 1900) {
|
|
$annoParam = $this->resolveYearToFolder($code, $options['path'], $annoParam);
|
|
}
|
|
$params['--anno'] = $annoParam;
|
|
}
|
|
if (! empty($options['dry'])) {
|
|
$params['--dry-run'] = true;
|
|
}
|
|
if (! empty($options['update_only'])) {
|
|
$params['--update-only'] = true;
|
|
}
|
|
|
|
Artisan::call('gescon:import-full', $params);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
}
|
|
|
|
if (empty($options['dry'])) {
|
|
$stabile->refresh();
|
|
$this->normalizeImportedFinancialSetup($stabile);
|
|
$outputs[] = 'Normalizzazione cassa CON + banca principale completata';
|
|
|
|
if (! empty($options['with_gestioni'])) {
|
|
$gestioniUpdated = $this->applyGestioneSetup($stabile);
|
|
$outputs[] = 'Gestioni create o aggiornate: ' . $gestioniUpdated;
|
|
}
|
|
}
|
|
|
|
$shouldSyncRelations = ! empty($options['with_unita'])
|
|
|| ! empty($options['with_soggetti'])
|
|
|| ! empty($options['with_diritti']);
|
|
|
|
if ($shouldSyncRelations) {
|
|
$relationSync = $this->runRelationSyncSequence($code, (int) $stabile->id, (bool) $options['dry']);
|
|
if (! $relationSync['ok']) {
|
|
Notification::make()
|
|
->title('Import completato con errore nel re-sync relazioni')
|
|
->body((string) ($relationSync['message'] ?? 'Errore non specificato.'))
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
foreach ((array) ($relationSync['outputs'] ?? []) as $syncOutput) {
|
|
if (is_string($syncOutput) && trim($syncOutput) !== '') {
|
|
$outputs[] = trim($syncOutput);
|
|
}
|
|
}
|
|
}
|
|
|
|
$tail = collect($outputs)
|
|
->filter()
|
|
->map(fn(string $o) => trim((string) collect(preg_split('/\R/', $o) ?: [])->last()))
|
|
->filter()
|
|
->implode("\n");
|
|
|
|
Notification::make()
|
|
->title('Importazione completata')
|
|
->body($tail !== '' ? $tail : 'Operazione completata')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function eseguiImportAllineamentoCompleto(): void
|
|
{
|
|
@set_time_limit(0);
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
if ($code === '') {
|
|
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$path = trim((string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon'));
|
|
$years = implode(',', array_values(array_filter(array_map(
|
|
fn($v) => trim((string) $v),
|
|
(array) ($state['align_years_selected'] ?? ['2024', '2025'])
|
|
))));
|
|
$waterYears = implode(',', array_values(array_filter(array_map(
|
|
fn($v) => trim((string) $v),
|
|
(array) ($state['align_water_years_selected'] ?? ['2024'])
|
|
))));
|
|
$openYears = implode(',', array_values(array_filter(array_map(
|
|
fn($v) => trim((string) $v),
|
|
(array) ($state['align_open_years_selected'] ?? ['2024', '2025'])
|
|
))));
|
|
$dryRun = (bool) ($state['dry_run'] ?? false);
|
|
$markObsoleteYears = (bool) ($state['align_mark_obsolete_years'] ?? false);
|
|
$withF24 = (bool) ($state['align_with_f24'] ?? true);
|
|
|
|
if ($path === '') {
|
|
Notification::make()->title('Percorso archivio mancante')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
if ($years === '') {
|
|
Notification::make()->title('Anni contabili mancanti')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$params = [
|
|
'--stabile' => [$code],
|
|
'--path' => $path,
|
|
'--years' => $years,
|
|
'--water-years' => $waterYears !== '' ? $waterYears : '2024',
|
|
'--open-years' => $openYears !== '' ? $openYears : $years,
|
|
];
|
|
if ($markObsoleteYears) {
|
|
$params['--mark-obsolete-years'] = true;
|
|
}
|
|
if (! $withF24) {
|
|
$params['--skip-f24'] = true;
|
|
}
|
|
if ($dryRun) {
|
|
$params['--dry-run'] = true;
|
|
}
|
|
|
|
try {
|
|
$exit = Artisan::call('gescon:import-align', $params);
|
|
$out = trim((string) Artisan::output());
|
|
} catch (\Throwable $e) {
|
|
Notification::make()
|
|
->title('Import + allineamento fallito')
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$lines = $out === '' ? [] : preg_split('/\R/', $out);
|
|
$tail = $lines ? implode("\n", array_slice($lines, -10)) : 'Comando eseguito.';
|
|
|
|
Notification::make()
|
|
->title($exit === 0 ? 'Import + allineamento completato' : 'Import + allineamento con errori')
|
|
->body($tail)
|
|
->{$exit === 0 ? 'success' : 'danger'}()
|
|
->send();
|
|
|
|
if ($exit === 0 && ! $dryRun) {
|
|
$stabile = $this->resolveSelectedStabileByCode($code);
|
|
if ($stabile instanceof Stabile) {
|
|
$this->normalizeImportedFinancialSetup($stabile);
|
|
if ((bool) ($state['gestione_setup_enabled'] ?? true)) {
|
|
$this->applyGestioneSetup($stabile);
|
|
}
|
|
$this->hydrateImportSetupState($code, $path, $stabile);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function caricaAnniDaGenerale(): void
|
|
{
|
|
$state = $this->getImportFormState();
|
|
$code = (string) ($state['stabile_code'] ?? '');
|
|
$path = (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon');
|
|
|
|
$loaded = $this->refreshAnniOptionsForStabile($code, $path);
|
|
if ($loaded <= 0) {
|
|
Notification::make()
|
|
->title('Nessun anno trovato in generale_stabile.mdb')
|
|
->body('Verifica percorso archivio e stabile selezionato.')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
Notification::make()
|
|
->title('Anni caricati da generale_stabile.mdb')
|
|
->body('Anni disponibili: ' . $loaded)
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
public function getCommandPreviewData(): array
|
|
{
|
|
$state = $this->getImportFormState();
|
|
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
if ($code !== '') {
|
|
$code = str_pad($code, 4, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
$path = trim((string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon'));
|
|
$annoDir = trim((string) ($state['anno'] ?? ''));
|
|
$years = implode(',', array_values(array_filter(array_map(
|
|
fn($value) => trim((string) $value),
|
|
(array) ($state['align_years_selected'] ?? [])
|
|
))));
|
|
$waterYears = implode(',', array_values(array_filter(array_map(
|
|
fn($value) => trim((string) $value),
|
|
(array) ($state['align_water_years_selected'] ?? [])
|
|
))));
|
|
$openYears = implode(',', array_values(array_filter(array_map(
|
|
fn($value) => trim((string) $value),
|
|
(array) ($state['align_open_years_selected'] ?? [])
|
|
))));
|
|
|
|
$adminId = (int) ($state['amministratore_id'] ?? 0);
|
|
$stabile = $this->resolveSelectedStabileByCode($code);
|
|
$stabileId = $stabile instanceof Stabile ? (int) $stabile->id : null;
|
|
|
|
$fornitoriMdb = trim((string) ($state['fornitori_mdb'] ?? ''));
|
|
if ($fornitoriMdb === '' && $path !== '') {
|
|
$fornitoriMdb = rtrim($path, '/') . '/dbc/Fornitori.mdb';
|
|
}
|
|
|
|
$stabiliMdb = trim((string) ($state['stabili_mdb'] ?? ''));
|
|
if ($stabiliMdb === '' && $path !== '') {
|
|
$stabiliMdb = rtrim($path, '/') . '/dbc/Stabili.mdb';
|
|
}
|
|
|
|
$generaleMdb = ($path !== '' && $code !== '')
|
|
? rtrim($path, '/') . '/' . $code . '/generale_stabile.mdb'
|
|
: '';
|
|
$singoloMdb = ($path !== '' && $code !== '' && $annoDir !== '')
|
|
? rtrim($path, '/') . '/' . $code . '/' . $annoDir . '/singolo_anno.mdb'
|
|
: '';
|
|
|
|
return [
|
|
'source_mode' => (string) ($state['source_mode'] ?? 'linux_server'),
|
|
'stabile_code' => $code,
|
|
'stabile_id' => $stabileId,
|
|
'amministratore_id' => $adminId > 0 ? $adminId : null,
|
|
'anno_dir' => $annoDir,
|
|
'years_csv' => $years,
|
|
'water_years_csv' => $waterYears,
|
|
'open_years_csv' => $openYears,
|
|
'paths' => [
|
|
'root' => $path,
|
|
'stabili_mdb' => $stabiliMdb,
|
|
'fornitori_mdb' => $fornitoriMdb,
|
|
'generale_mdb' => $generaleMdb,
|
|
'singolo_mdb' => $singoloMdb,
|
|
],
|
|
'commands' => [
|
|
'load_stabili' => $stabiliMdb !== ''
|
|
? 'php artisan import:gescon-fornitori ' . max(1, $adminId) . ' --mdb="' . $fornitoriMdb . '" --preview --limit=5'
|
|
: null,
|
|
'fornitori_sync' => ($adminId > 0 && $fornitoriMdb !== '')
|
|
? 'php artisan gescon:sync-fornitori-legacy ' . $adminId . ' --mdb="' . $fornitoriMdb . '"'
|
|
: null,
|
|
'allineamento' => ($code !== '' && $path !== '' && $years !== '')
|
|
? 'php artisan gescon:import-align --stabile=' . $code . ' --path="' . $path . '" --years=' . $years . ' --water-years=' . ($waterYears !== '' ? $waterYears : $years) . ' --open-years=' . ($openYears !== '' ? $openYears : $years)
|
|
: null,
|
|
'unita' => ($code !== '' && $path !== '' && $annoDir !== '')
|
|
? 'php artisan gescon:import-full --stabile=' . $code . ' --solo=unita --anno=' . $annoDir . ' --path="' . $path . '"'
|
|
: null,
|
|
'soggetti' => ($code !== '' && $path !== '' && $annoDir !== '')
|
|
? 'php artisan gescon:import-full --stabile=' . $code . ' --solo=soggetti --anno=' . $annoDir . ' --path="' . $path . '"'
|
|
: null,
|
|
'diritti' => ($code !== '' && $path !== '' && $annoDir !== '')
|
|
? 'php artisan gescon:import-full --stabile=' . $code . ' --solo=diritti --anno=' . $annoDir . ' --path="' . $path . '"'
|
|
: null,
|
|
'anagrafiche_sync' => ($code !== '')
|
|
? 'php artisan gescon:auto-sync-anagrafiche --stabile=' . $code . ' --only=both --link-ruolo --include-comproprietari --apply'
|
|
: null,
|
|
'ruoli_sync' => ($stabileId !== null)
|
|
? 'php artisan gescon:sync-rubrica-ruoli --stabile=' . $stabileId . ' --apply'
|
|
: null,
|
|
'repair_contacts' => ($code !== '')
|
|
? 'php artisan gescon:repair-nominativi-contacts --stabile=' . $code . ' --apply'
|
|
: null,
|
|
],
|
|
];
|
|
}
|
|
|
|
private function refreshAnniOptionsForStabile(string $code, string $root, ?Set $set = null, mixed $get = null): int
|
|
{
|
|
$code = trim($code);
|
|
if ($code === '') {
|
|
$this->anniOptions = [];
|
|
return 0;
|
|
}
|
|
$code = str_pad($code, 4, '0', STR_PAD_LEFT);
|
|
$root = trim($root);
|
|
if ($root === '') {
|
|
$this->anniOptions = [];
|
|
return 0;
|
|
}
|
|
|
|
$mdb = rtrim($root, '/') . '/' . $code . '/generale_stabile.mdb';
|
|
if (! is_file($mdb) || ! is_readable($mdb)) {
|
|
$this->anniOptions = [];
|
|
return 0;
|
|
}
|
|
|
|
$rows = $this->mdbExportToRows($mdb, ['anni', 'Anni', 'ANNI']);
|
|
if (empty($rows)) {
|
|
$this->anniOptions = [];
|
|
return 0;
|
|
}
|
|
|
|
$opts = [];
|
|
foreach ($rows as $r) {
|
|
$yearRaw = $this->firstNonEmpty($r, ['anno_o', 'anno_r', 'anno']);
|
|
$year = null;
|
|
if (is_scalar($yearRaw) && preg_match('/(19|20)\d{2}/', (string) $yearRaw, $m)) {
|
|
$year = $m[0];
|
|
}
|
|
if ($year === null) {
|
|
continue;
|
|
}
|
|
|
|
$dirRaw = $this->firstNonEmpty($r, ['nome_dir', 'dir', 'cartella', 'id_anno']);
|
|
$dir = trim((string) ($dirRaw ?? ''));
|
|
if ($dir === '' && is_numeric((string) ($r['id_anno'] ?? ''))) {
|
|
$dir = str_pad((string) ((int) $r['id_anno']), 4, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
$opts[(string) $year] = $dir !== ''
|
|
? ((string) $year . ' (dir ' . $dir . ')')
|
|
: (string) $year;
|
|
}
|
|
|
|
if (empty($opts)) {
|
|
$this->anniOptions = [];
|
|
return 0;
|
|
}
|
|
|
|
krsort($opts);
|
|
$this->anniOptions = $opts;
|
|
|
|
$state = $this->getImportFormState();
|
|
$keys = array_keys($opts);
|
|
|
|
$selectedYears = array_values(array_filter(array_map(
|
|
fn($v) => trim((string) $v),
|
|
(array) ($state['align_years_selected'] ?? [])
|
|
), fn($v) => in_array($v, $keys, true)));
|
|
if (empty($selectedYears)) {
|
|
$selectedYears = array_slice($keys, 0, 2);
|
|
}
|
|
|
|
$selectedWater = array_values(array_filter(array_map(
|
|
fn($v) => trim((string) $v),
|
|
(array) ($state['align_water_years_selected'] ?? [])
|
|
), fn($v) => in_array($v, $keys, true)));
|
|
if (empty($selectedWater) && ! empty($selectedYears)) {
|
|
$selectedWater = [$selectedYears[0]];
|
|
}
|
|
|
|
$selectedOpen = array_values(array_filter(array_map(
|
|
fn($v) => trim((string) $v),
|
|
(array) ($state['align_open_years_selected'] ?? [])
|
|
), fn($v) => in_array($v, $keys, true)));
|
|
if (empty($selectedOpen)) {
|
|
$selectedOpen = isset($selectedYears[0]) ? [$selectedYears[0]] : [];
|
|
}
|
|
|
|
if ($set !== null && is_callable($get)) {
|
|
$set('align_years_selected', $selectedYears);
|
|
$set('align_water_years_selected', $selectedWater);
|
|
$set('align_open_years_selected', $selectedOpen);
|
|
} else {
|
|
$state['align_years_selected'] = $selectedYears;
|
|
$state['align_water_years_selected'] = $selectedWater;
|
|
$state['align_open_years_selected'] = $selectedOpen;
|
|
$this->getSchema('form')?->fill($state);
|
|
}
|
|
|
|
return count($opts);
|
|
}
|
|
|
|
private function resolveImportAmministratore(User $user, array $state): ?Amministratore
|
|
{
|
|
$amm = null;
|
|
if ($user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$ammId = (int) ($state['amministratore_id'] ?? 0);
|
|
$amm = $ammId > 0 ? Amministratore::query()->find($ammId) : null;
|
|
} else {
|
|
$amm = $user->amministratore;
|
|
}
|
|
|
|
return $amm ?: Amministratore::query()->orderBy('id')->first();
|
|
}
|
|
|
|
private function hydrateImportSetupState(string $code, string $path, ?Stabile $stabile = null, ?array $legacyRow = null): void
|
|
{
|
|
$normalizedCode = str_pad(trim($code), 4, '0', STR_PAD_LEFT);
|
|
$stabile ??= $this->resolveSelectedStabileByCode($normalizedCode);
|
|
$legacyRow ??= $this->resolveLegacyStabileRow($normalizedCode, $path);
|
|
|
|
$ordMonths = $this->normalizeMonthValues(
|
|
$stabile?->rate_ordinarie_mesi ?? $this->extractLegacyRateMonths($legacyRow ?? [], 'ord_rata_')
|
|
);
|
|
$risMonths = $this->normalizeMonthValues(
|
|
$stabile?->rate_riscaldamento_mesi ?? $this->extractLegacyRateMonths($legacyRow ?? [], 'ris_rata_')
|
|
);
|
|
$config = $stabile?->getOperationalConfiguration() ?? [];
|
|
|
|
$this->data = array_merge($this->data ?? [], [
|
|
'stabile_denominazione' => (string) ($stabile?->denominazione ?: ($this->firstNonEmpty($legacyRow ?? [], ['denominazione']) ?? '')),
|
|
'stabile_indirizzo' => (string) ($stabile?->indirizzo ?: ($this->firstNonEmpty($legacyRow ?? [], ['indirizzo']) ?? '')),
|
|
'stabile_cap' => (string) ($stabile?->cap ?: ($this->firstNonEmpty($legacyRow ?? [], ['cap']) ?? '')),
|
|
'stabile_citta' => (string) ($stabile?->citta ?: ($this->firstNonEmpty($legacyRow ?? [], ['citta']) ?? '')),
|
|
'stabile_provincia' => (string) ($stabile?->provincia ?: ($this->firstNonEmpty($legacyRow ?? [], ['provincia']) ?? '')),
|
|
'stabile_codice_fiscale' => (string) ($stabile?->codice_fiscale ?: ($this->firstNonEmpty($legacyRow ?? [], ['codice_fiscale_cond', 'codice_fiscale', 'cf']) ?? '')),
|
|
'stabile_banca_principale' => (string) ($stabile?->banca_principale ?: ($this->firstNonEmpty($legacyRow ?? [], ['denominazione_banca', 'banca', 'banca_principale']) ?? '')),
|
|
'stabile_iban_principale' => (string) ($stabile?->iban_principale ?: ($this->normalizeIbanValue($this->firstNonEmpty($legacyRow ?? [], ['iban_principale', 'iban_banca', 'iban'])) ?? '')),
|
|
'gestione_anno_iniziale' => $this->resolveDefaultGestioneYear($stabile),
|
|
'gestione_mostra_riscaldamento' => (bool) ($config['mostra_riscaldamento'] ?? ($risMonths !== [])),
|
|
'gestione_mesi_ordinaria' => $ordMonths,
|
|
'gestione_mesi_riscaldamento' => $risMonths,
|
|
]);
|
|
}
|
|
|
|
private function upsertSelectedStabileFromImportState(Amministratore $amm): Stabile
|
|
{
|
|
$state = $this->getImportFormState();
|
|
$code = trim((string) ($state['stabile_code'] ?? ''));
|
|
if ($code === '') {
|
|
throw new \RuntimeException('Seleziona uno stabile prima di salvare il setup.');
|
|
}
|
|
|
|
$code = str_pad($code, 4, '0', STR_PAD_LEFT);
|
|
$legacyRow = $this->resolveLegacyStabileRow($code, (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon'));
|
|
$stabile = $this->resolveSelectedStabileByCode($code, (int) $amm->id) ?? new Stabile();
|
|
|
|
$denominazione = trim((string) ($state['stabile_denominazione'] ?? ''));
|
|
if ($denominazione === '') {
|
|
$denominazione = trim((string) ($this->firstNonEmpty($legacyRow ?? [], ['denominazione']) ?? ''));
|
|
}
|
|
if ($denominazione === '') {
|
|
$label = $this->stabiliOptions[$code] ?? $code;
|
|
$denominazione = trim((string) preg_replace('/^' . preg_quote($code, '/') . '\s+—\s+/u', '', $label));
|
|
}
|
|
if ($denominazione === '' || $denominazione === $code) {
|
|
$denominazione = 'Stabile ' . $code;
|
|
}
|
|
|
|
$ordMonths = $this->normalizeMonthValues((array) ($state['gestione_mesi_ordinaria'] ?? $this->extractLegacyRateMonths($legacyRow ?? [], 'ord_rata_')));
|
|
$risMonths = $this->normalizeMonthValues((array) ($state['gestione_mesi_riscaldamento'] ?? $this->extractLegacyRateMonths($legacyRow ?? [], 'ris_rata_')));
|
|
$config = $stabile->getOperationalConfiguration();
|
|
$config['mostra_riscaldamento'] = (bool) ($state['gestione_mostra_riscaldamento'] ?? false);
|
|
|
|
$stabile->fill([
|
|
'amministratore_id' => (int) $amm->id,
|
|
'codice_stabile' => $code,
|
|
'denominazione' => $denominazione,
|
|
'indirizzo' => trim((string) ($state['stabile_indirizzo'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['indirizzo']) ?? ''))),
|
|
'cap' => trim((string) ($state['stabile_cap'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['cap']) ?? ''))),
|
|
'citta' => trim((string) ($state['stabile_citta'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['citta']) ?? ''))),
|
|
'provincia' => trim((string) ($state['stabile_provincia'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['provincia']) ?? ''))),
|
|
'codice_fiscale' => trim((string) ($state['stabile_codice_fiscale'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['codice_fiscale_cond', 'codice_fiscale', 'cf']) ?? ''))),
|
|
'banca_principale' => trim((string) ($state['stabile_banca_principale'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['denominazione_banca', 'banca', 'banca_principale']) ?? ''))),
|
|
'iban_principale' => $this->normalizeIbanValue($state['stabile_iban_principale'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['iban_principale', 'iban_banca', 'iban']) ?? null)),
|
|
'iban_condominio' => $this->normalizeIbanValue($state['stabile_iban_principale'] ?? ($this->firstNonEmpty($legacyRow ?? [], ['iban_principale', 'iban_banca', 'iban']) ?? null)),
|
|
'rate_ordinarie_mesi' => $ordMonths,
|
|
'rate_riscaldamento_mesi' => $config['mostra_riscaldamento'] ? $risMonths : [],
|
|
'configurazione_avanzata' => $config,
|
|
'stato' => 'attivo',
|
|
'attivo' => true,
|
|
]);
|
|
$stabile->save();
|
|
$stabile->syncRubricaContatto();
|
|
|
|
return $stabile->fresh();
|
|
}
|
|
|
|
private function applyGestioneSetup(Stabile $stabile): int
|
|
{
|
|
$state = $this->getImportFormState();
|
|
$replicaTuttiAnni = (bool) ($state['gestione_replica_tutti_anni'] ?? true);
|
|
$mostraRiscald = (bool) ($state['gestione_mostra_riscaldamento'] ?? false);
|
|
$ordMonths = $this->normalizeMonthValues((array) ($state['gestione_mesi_ordinaria'] ?? []));
|
|
$risMonths = $this->normalizeMonthValues((array) ($state['gestione_mesi_riscaldamento'] ?? []));
|
|
$years = $this->resolveGestioneTargetYears($stabile, $replicaTuttiAnni, (int) ($state['gestione_anno_iniziale'] ?? date('Y')));
|
|
|
|
$updated = 0;
|
|
foreach ($years as $year) {
|
|
$this->upsertGestioneRecord($stabile, $year, 'ordinaria', $ordMonths, true);
|
|
$updated++;
|
|
|
|
if ($mostraRiscald && $risMonths !== []) {
|
|
$this->upsertGestioneRecord($stabile, $year, 'riscaldamento', $risMonths, true);
|
|
$updated++;
|
|
continue;
|
|
}
|
|
|
|
$existingHeating = GestioneContabile::query()
|
|
->where('stabile_id', $stabile->id)
|
|
->where('anno_gestione', $year)
|
|
->where('tipo_gestione', 'riscaldamento')
|
|
->first();
|
|
if ($existingHeating instanceof GestioneContabile) {
|
|
$existingHeating->mesi_rate_riscaldamento = [];
|
|
$existingHeating->gestione_attiva = false;
|
|
$existingHeating->save();
|
|
}
|
|
}
|
|
|
|
$config = $stabile->getOperationalConfiguration();
|
|
$config['mostra_riscaldamento'] = $mostraRiscald;
|
|
$stabile->configurazione_avanzata = $config;
|
|
$stabile->rate_ordinarie_mesi = $ordMonths;
|
|
$stabile->rate_riscaldamento_mesi = $mostraRiscald ? $risMonths : [];
|
|
$stabile->save();
|
|
|
|
return $updated;
|
|
}
|
|
|
|
private function upsertGestioneRecord(Stabile $stabile, int $year, string $tipo, array $months, bool $active): void
|
|
{
|
|
$tenantId = GestioneContabile::query()
|
|
->where('stabile_id', $stabile->id)
|
|
->value('tenant_id');
|
|
if (! is_string($tenantId) || trim($tenantId) === '') {
|
|
$tenantId = (string) (Auth::user()?->tenant_id ?? 'default');
|
|
}
|
|
|
|
$gestione = GestioneContabile::query()->firstOrNew([
|
|
'tenant_id' => $tenantId,
|
|
'stabile_id' => $stabile->id,
|
|
'anno_gestione' => $year,
|
|
'tipo_gestione' => $tipo,
|
|
]);
|
|
|
|
$gestione->denominazione = ucfirst($tipo) . ' ' . $year;
|
|
$gestione->data_inizio = sprintf('%04d-01-01', $year);
|
|
$gestione->data_fine = sprintf('%04d-12-31', $year);
|
|
$gestione->stato = 'aperta';
|
|
$gestione->protocollo_prefix = strtoupper(substr($tipo, 0, 1)) . $year;
|
|
$gestione->ultimo_protocollo = (int) ($gestione->ultimo_protocollo ?? 0);
|
|
$gestione->gestione_attiva = $active;
|
|
$gestione->codice_archivio_legacy = $stabile->codice_stabile;
|
|
|
|
if ($tipo === 'ordinaria') {
|
|
$gestione->mesi_rate_ordinaria = $months;
|
|
} else {
|
|
$gestione->mesi_rate_riscaldamento = $months;
|
|
}
|
|
|
|
$gestione->save();
|
|
}
|
|
|
|
private function resolveGestioneTargetYears(Stabile $stabile, bool $replicaTuttiAnni, int $fallbackYear): array
|
|
{
|
|
$years = [$fallbackYear > 0 ? $fallbackYear : (int) date('Y')];
|
|
|
|
if ($replicaTuttiAnni) {
|
|
$years = array_merge(
|
|
$years,
|
|
array_map('intval', array_keys($this->anniOptions)),
|
|
GestioneContabile::query()->where('stabile_id', $stabile->id)->pluck('anno_gestione')->map(fn($value) => (int) $value)->all(),
|
|
array_map('intval', (array) (($this->getImportFormState())['align_years_selected'] ?? [])),
|
|
);
|
|
}
|
|
|
|
$years = array_values(array_unique(array_filter($years, fn(int $value): bool => $value > 0)));
|
|
sort($years);
|
|
|
|
return $years;
|
|
}
|
|
|
|
private function normalizeImportedFinancialSetup(Stabile $stabile): void
|
|
{
|
|
try {
|
|
$this->syncFinancialSetupFromLegacyAnagrCasse($stabile);
|
|
} catch (\Throwable) {
|
|
// Non bloccare l'import per la normalizzazione dei dati bancari.
|
|
}
|
|
}
|
|
|
|
private function syncFinancialSetupFromLegacyAnagrCasse(Stabile $stabile): void
|
|
{
|
|
$state = $this->getImportFormState();
|
|
$rows = $this->resolveLegacyAnagrCasseRows($stabile->codice_stabile, (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon'));
|
|
if ($rows === []) {
|
|
return;
|
|
}
|
|
|
|
$legacyStabile = $this->resolveLegacyStabileRow($stabile->codice_stabile, (string) ($state['path_root'] ?? '/mnt/gescon-archives/gescon'));
|
|
$primaryIban = $this->normalizeIbanValue($stabile->iban_principale ?: ($legacyStabile['iban_principale'] ?? null));
|
|
$primaryBank = trim((string) ($stabile->banca_principale ?: ($legacyStabile['denominazione_banca'] ?? '')));
|
|
$seenCodes = [];
|
|
$markedPrimary = DatiBancari::query()->where('stabile_id', $stabile->id)->where('is_nostro_conto', true)->exists();
|
|
|
|
foreach ($rows as $row) {
|
|
$codCassa = strtoupper(trim((string) ($row['codice'] ?? ($row['cod_cassa'] ?? ''))));
|
|
if ($codCassa === '' || isset($seenCodes[$codCassa])) {
|
|
continue;
|
|
}
|
|
$seenCodes[$codCassa] = true;
|
|
|
|
$descrizione = trim((string) ($row['descrizione'] ?? $codCassa));
|
|
$iban = $this->normalizeIbanValue($row['iban'] ?? ($row['iban_banca'] ?? null));
|
|
if ($iban === null && $codCassa !== 'CON') {
|
|
$iban = $primaryIban;
|
|
}
|
|
if ($codCassa !== 'CON' && $primaryBank !== '') {
|
|
$descrizione = $primaryBank;
|
|
}
|
|
|
|
if ($iban === null) {
|
|
$existsCassa = DB::table('casse')->where('stabile_id', $stabile->id)->where('cod_cassa', $codCassa)->first();
|
|
$payloadCassa = [
|
|
'tenant_id' => null,
|
|
'stabile_id' => $stabile->id,
|
|
'cod_cassa' => $codCassa,
|
|
'descrizione' => $descrizione,
|
|
'tipo' => $codCassa === 'CON' ? 'cassa_contanti' : 'altro',
|
|
'iban' => null,
|
|
'intestazione_conto' => trim((string) ($stabile->denominazione ?: ('Stabile ' . $stabile->codice_stabile))),
|
|
'attiva' => true,
|
|
'meta' => json_encode(['source' => 'Anagr_casse', 'legacy' => $row]),
|
|
'updated_at' => now(),
|
|
];
|
|
if (! $existsCassa) {
|
|
$payloadCassa['created_at'] = now();
|
|
DB::table('casse')->insert($payloadCassa);
|
|
} else {
|
|
DB::table('casse')->where('id', $existsCassa->id)->update($payloadCassa);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
DB::table('casse')
|
|
->where('stabile_id', $stabile->id)
|
|
->where('cod_cassa', $codCassa)
|
|
->delete();
|
|
|
|
$account = DatiBancari::query()
|
|
->where('stabile_id', $stabile->id)
|
|
->where(function ($query) use ($codCassa, $iban): void {
|
|
$query->where('legacy_cod_cassa', $codCassa)
|
|
->orWhere('iban', $iban);
|
|
})
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if (! $account instanceof DatiBancari) {
|
|
$account = new DatiBancari();
|
|
$account->stabile_id = $stabile->id;
|
|
}
|
|
|
|
$shouldMarkPrimary = ! $markedPrimary || ($primaryIban !== null && $iban === $primaryIban);
|
|
$account->tipo_conto = 'corrente';
|
|
$account->denominazione_banca = $descrizione;
|
|
$account->numero_conto = $codCassa;
|
|
$account->iban = $iban;
|
|
$account->intestazione_conto = trim((string) ($stabile->denominazione ?: ('Stabile ' . $stabile->codice_stabile)));
|
|
$account->stato_conto = 'attivo';
|
|
$account->is_nostro_conto = $shouldMarkPrimary;
|
|
$account->legacy_cod_cassa = $codCassa;
|
|
$account->save();
|
|
|
|
if ($shouldMarkPrimary) {
|
|
$markedPrimary = true;
|
|
}
|
|
}
|
|
|
|
DB::table('casse')
|
|
->where('stabile_id', $stabile->id)
|
|
->whereNotIn('cod_cassa', array_keys($seenCodes))
|
|
->whereIn('cod_cassa', ['CDL'])
|
|
->delete();
|
|
}
|
|
|
|
private function resolveLegacyStabileRow(string $code, string $path): ?array
|
|
{
|
|
$normalizedCode = str_pad(trim($code), 4, '0', STR_PAD_LEFT);
|
|
|
|
try {
|
|
$row = DB::connection('gescon_import')
|
|
->table('stabili')
|
|
->where('cod_stabile', $normalizedCode)
|
|
->first();
|
|
if ($row) {
|
|
return array_change_key_case((array) $row, CASE_LOWER);
|
|
}
|
|
} catch (\Throwable) {
|
|
// fallback MDB diretto
|
|
}
|
|
|
|
$state = $this->getImportFormState();
|
|
$mdb = trim((string) ($state['stabili_mdb'] ?? ''));
|
|
if ($mdb === '') {
|
|
$mdb = rtrim($path, '/') . '/dbc/Stabili.mdb';
|
|
}
|
|
if (! is_file($mdb) || ! is_readable($mdb)) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($this->mdbExportToRows($mdb, ['Stabili', 'stabili', 'Condomin', 'condomin']) as $row) {
|
|
$rowCode = $this->firstNonEmpty($row, ['cod_stabile', 'codice_stabile', 'stabile', 'codice', 'cod']);
|
|
if ($rowCode === null) {
|
|
continue;
|
|
}
|
|
|
|
if (str_pad(trim((string) $rowCode), 4, '0', STR_PAD_LEFT) === $normalizedCode) {
|
|
return $row;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/** @return array<int,array<string,mixed>> */
|
|
private function resolveLegacyAnagrCasseRows(string $code, string $path): array
|
|
{
|
|
$normalizedCode = str_pad(trim($code), 4, '0', STR_PAD_LEFT);
|
|
$rows = [];
|
|
|
|
try {
|
|
if (Schema::connection('gescon_import')->hasTable('anag_casse')) {
|
|
$rows = DB::connection('gescon_import')
|
|
->table('anag_casse')
|
|
->where('cod_stabile', $normalizedCode)
|
|
->orderBy('legacy_year')
|
|
->get()
|
|
->map(fn($row): array=> array_change_key_case((array) $row, CASE_LOWER))
|
|
->all();
|
|
}
|
|
} catch (\Throwable) {
|
|
$rows = [];
|
|
}
|
|
|
|
if ($rows === []) {
|
|
foreach ($this->resolveLegacySingoloMdbPaths($normalizedCode, $path) as $mdbPath => $legacyDir) {
|
|
foreach ($this->mdbExportToRows($mdbPath, ['Anagr_casse', 'anagr_casse', 'ANAGR_CASSE']) as $row) {
|
|
$row['legacy_year'] = $legacyDir;
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
}
|
|
|
|
$dedup = [];
|
|
foreach ($rows as $row) {
|
|
$codeKey = strtoupper(trim((string) ($row['codice'] ?? ($row['cod_cassa'] ?? ''))));
|
|
if ($codeKey === '') {
|
|
continue;
|
|
}
|
|
if (! isset($dedup[$codeKey])) {
|
|
$dedup[$codeKey] = $row;
|
|
continue;
|
|
}
|
|
$current = $dedup[$codeKey];
|
|
$currentScore = $this->scoreLegacyAnagrCasseRow($current);
|
|
$candidateScore = $this->scoreLegacyAnagrCasseRow($row);
|
|
if ($candidateScore >= $currentScore) {
|
|
$dedup[$codeKey] = array_merge($current, array_filter($row, fn($value) => $value !== null && $value !== ''));
|
|
}
|
|
}
|
|
|
|
return array_values($dedup);
|
|
}
|
|
|
|
/** @return array<string,string> */
|
|
private function resolveLegacySingoloMdbPaths(string $code, string $path, ?string $onlyLegacyDir = null): array
|
|
{
|
|
$stableDir = rtrim($path, '/') . '/' . str_pad(trim($code), 4, '0', STR_PAD_LEFT);
|
|
if (! is_dir($stableDir)) {
|
|
return [];
|
|
}
|
|
|
|
$paths = [];
|
|
foreach ((array) glob($stableDir . '/*/singolo_anno.mdb') as $mdbPath) {
|
|
$legacyDir = basename(dirname($mdbPath));
|
|
if ($onlyLegacyDir !== null && trim($onlyLegacyDir) !== '' && $legacyDir !== trim($onlyLegacyDir)) {
|
|
continue;
|
|
}
|
|
$paths[$mdbPath] = $legacyDir;
|
|
}
|
|
|
|
ksort($paths);
|
|
|
|
return $paths;
|
|
}
|
|
|
|
private function scoreLegacyAnagrCasseRow(array $row): int
|
|
{
|
|
$score = 0;
|
|
foreach (['descrizione', 'iban', 'iban_banca', 'codice', 'cod_cassa'] as $field) {
|
|
$value = trim((string) ($row[$field] ?? ''));
|
|
if ($value !== '') {
|
|
$score++;
|
|
}
|
|
}
|
|
|
|
return $score;
|
|
}
|
|
|
|
private function resolveDefaultGestioneYear(?Stabile $stabile): int
|
|
{
|
|
$state = $this->getImportFormState();
|
|
$anno = (int) ($state['gestione_anno_iniziale'] ?? 0);
|
|
if ($anno > 0) {
|
|
return $anno;
|
|
}
|
|
|
|
$selected = array_values(array_filter(array_map('intval', (array) ($state['align_years_selected'] ?? []))));
|
|
if ($selected !== []) {
|
|
sort($selected);
|
|
return (int) end($selected);
|
|
}
|
|
|
|
if ($this->anniOptions !== []) {
|
|
$years = array_map('intval', array_keys($this->anniOptions));
|
|
sort($years);
|
|
return (int) end($years);
|
|
}
|
|
|
|
if ($stabile instanceof Stabile) {
|
|
$gestioneYear = (int) GestioneContabile::query()->where('stabile_id', $stabile->id)->max('anno_gestione');
|
|
if ($gestioneYear > 0) {
|
|
return $gestioneYear;
|
|
}
|
|
}
|
|
|
|
return (int) date('Y');
|
|
}
|
|
|
|
private function extractLegacyRateMonths(array $legacyRow, string $prefix): array
|
|
{
|
|
$months = [];
|
|
foreach (range(1, 12) as $month) {
|
|
$key = strtolower($prefix . $month);
|
|
$value = $legacyRow[$key] ?? null;
|
|
if ($value === null) {
|
|
continue;
|
|
}
|
|
|
|
$normalized = strtolower(trim((string) $value));
|
|
if ($normalized !== '' && ! in_array($normalized, ['0', 'false', 'no', 'off', 'n'], true)) {
|
|
$months[] = $month;
|
|
}
|
|
}
|
|
|
|
return $months;
|
|
}
|
|
|
|
private function normalizeMonthValues(mixed $values): array
|
|
{
|
|
if (! is_array($values)) {
|
|
$values = (array) $values;
|
|
}
|
|
|
|
$months = array_values(array_unique(array_filter(array_map(
|
|
static fn($value): int => (int) $value,
|
|
$values,
|
|
), static fn(int $value): bool => $value >= 1 && $value <= 12)));
|
|
sort($months);
|
|
|
|
return $months;
|
|
}
|
|
|
|
private function normalizeIbanValue(mixed $value): ?string
|
|
{
|
|
$normalized = strtoupper(preg_replace('/\s+/', '', trim((string) ($value ?? ''))));
|
|
|
|
return $normalized !== '' ? $normalized : null;
|
|
}
|
|
|
|
private function resolveSelectedStabileByCode(?string $code, ?int $amministratoreId = null): ?Stabile
|
|
{
|
|
$normalized = trim((string) $code);
|
|
if ($normalized === '') {
|
|
return null;
|
|
}
|
|
|
|
if ($amministratoreId === null) {
|
|
$state = $this->getImportFormState();
|
|
$amministratoreId = (int) ($state['amministratore_id'] ?? 0);
|
|
}
|
|
|
|
$trimmed = ltrim($normalized, '0');
|
|
|
|
return Stabile::query()
|
|
->when($amministratoreId > 0, fn($query) => $query->where('amministratore_id', $amministratoreId))
|
|
->where(function ($query) use ($normalized, $trimmed): void {
|
|
$query->where('codice_stabile', $normalized);
|
|
if ($trimmed !== '' && $trimmed !== $normalized) {
|
|
$query->orWhere('codice_stabile', $trimmed);
|
|
}
|
|
})
|
|
->first();
|
|
}
|
|
|
|
/** @return array{ok:bool,message:string,outputs:array<int,string>} */
|
|
private function runRelationSyncSequence(string $code, int $stabileId, bool $dryRun): array
|
|
{
|
|
$outputs = [];
|
|
$statuses = [];
|
|
|
|
try {
|
|
$syncParams = [
|
|
'--stabile' => $code,
|
|
'--limit' => 20000,
|
|
'--only' => 'both',
|
|
'--link-ruolo' => true,
|
|
'--include-comproprietari' => true,
|
|
];
|
|
if (! $dryRun) {
|
|
$syncParams['--apply'] = true;
|
|
}
|
|
|
|
$statuses[] = Artisan::call('gescon:auto-sync-anagrafiche', $syncParams);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
$ruoliParams = [
|
|
'--stabile' => (string) $stabileId,
|
|
'--limit' => 5000,
|
|
];
|
|
if (! $dryRun) {
|
|
$ruoliParams['--apply'] = true;
|
|
}
|
|
|
|
$statuses[] = Artisan::call('gescon:sync-rubrica-ruoli', $ruoliParams);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
|
|
$repairParams = [
|
|
'--stabile' => $code,
|
|
'--limit' => 5000,
|
|
];
|
|
if (! $dryRun) {
|
|
$repairParams['--apply'] = true;
|
|
}
|
|
|
|
$statuses[] = Artisan::call('gescon:repair-nominativi-contacts', $repairParams);
|
|
$outputs[] = trim((string) Artisan::output());
|
|
} catch (\Throwable $e) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => $e->getMessage(),
|
|
'outputs' => $outputs,
|
|
];
|
|
}
|
|
|
|
$tail = collect($outputs)
|
|
->filter(fn($value) => is_string($value) && trim($value) !== '')
|
|
->map(fn(string $output) => trim((string) collect(preg_split('/\R/', $output) ?: [])->last()))
|
|
->filter(fn($value) => is_string($value) && $value !== '')
|
|
->implode("\n");
|
|
|
|
return [
|
|
'ok' => collect($statuses)->every(fn($status) => (int) $status === 0),
|
|
'message' => $tail !== '' ? $tail : 'Operazione completata.',
|
|
'outputs' => $outputs,
|
|
];
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
private function getImportFormState(): array
|
|
{
|
|
$state = is_array($this->data) ? $this->data : [];
|
|
|
|
$state['align_years_selected'] = $this->normalizeSelectedOptionValues(
|
|
(array) ($state['align_years_selected'] ?? []),
|
|
$this->anniOptions,
|
|
);
|
|
$state['align_water_years_selected'] = $this->normalizeSelectedOptionValues(
|
|
(array) ($state['align_water_years_selected'] ?? []),
|
|
$this->anniOptions,
|
|
);
|
|
$state['align_open_years_selected'] = $this->normalizeSelectedOptionValues(
|
|
(array) ($state['align_open_years_selected'] ?? []),
|
|
$this->anniOptions,
|
|
);
|
|
|
|
return $state;
|
|
}
|
|
|
|
/**
|
|
* @param array<int|string,mixed> $values
|
|
* @param array<string,string> $options
|
|
* @return array<int,string>
|
|
*/
|
|
private function normalizeSelectedOptionValues(array $values, array $options): array
|
|
{
|
|
$normalized = array_values(array_filter(array_map(
|
|
static fn($value): string => trim((string) $value),
|
|
$values,
|
|
), static fn(string $value): bool => $value !== ''));
|
|
|
|
if ($options === []) {
|
|
return $normalized;
|
|
}
|
|
|
|
$allowed = array_map('strval', array_keys($options));
|
|
|
|
return array_values(array_filter($normalized, static fn(string $value): bool => in_array($value, $allowed, true)));
|
|
}
|
|
|
|
private function firstNonEmpty(array $row, array $keys): mixed
|
|
{
|
|
foreach ($keys as $k) {
|
|
$lk = strtolower(trim((string) $k));
|
|
if (array_key_exists($lk, $row) && trim((string) $row[$lk]) !== '') {
|
|
return $row[$lk];
|
|
}
|
|
if (array_key_exists($k, $row) && trim((string) $row[$k]) !== '') {
|
|
return $row[$k];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Esporta una tabella MDB in array di righe associative (headers normalizzati lower-case).
|
|
*/
|
|
private function mdbExportToRows(string $mdbPath, array $tableCandidates): array
|
|
{
|
|
$binTables = trim((string) @shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
|
|
$binExport = trim((string) @shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
|
|
|
|
if (! is_file($mdbPath) || ! is_readable($mdbPath)) {
|
|
return [];
|
|
}
|
|
|
|
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($mdbPath))) ?: '';
|
|
$table = null;
|
|
|
|
if ($tables) {
|
|
$names = array_filter(preg_split('/\r?\n/', trim($tables)));
|
|
foreach ($tableCandidates as $cand) {
|
|
foreach ($names as $n) {
|
|
if (strcasecmp($n, $cand) === 0) {
|
|
$table = $n;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! $table) {
|
|
foreach ($tableCandidates as $cand) {
|
|
foreach ($names as $n) {
|
|
if (stripos($n, $cand) !== false) {
|
|
$table = $n;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! $table) {
|
|
return [];
|
|
}
|
|
|
|
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
|
|
$cmd = sprintf(
|
|
'%s -D %s -d %s -q %s %s %s > %s 2>/dev/null',
|
|
escapeshellarg($binExport),
|
|
escapeshellarg('%Y-%m-%d'),
|
|
escapeshellarg('|'),
|
|
escapeshellarg('"'),
|
|
escapeshellarg($mdbPath),
|
|
escapeshellarg($table),
|
|
escapeshellarg($tmp)
|
|
);
|
|
|
|
@shell_exec($cmd);
|
|
|
|
if (! is_file($tmp) || filesize($tmp) === 0) {
|
|
@unlink($tmp);
|
|
return [];
|
|
}
|
|
|
|
$fh = fopen($tmp, 'r');
|
|
if (! $fh) {
|
|
@unlink($tmp);
|
|
return [];
|
|
}
|
|
|
|
$headers = fgetcsv($fh, 0, '|');
|
|
if (! $headers) {
|
|
fclose($fh);
|
|
@unlink($tmp);
|
|
return [];
|
|
}
|
|
|
|
$headers = array_map(fn($h) => strtolower(trim((string) $h)), $headers);
|
|
|
|
$rows = [];
|
|
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
|
|
if ($cols === [null] || $cols === false) {
|
|
continue;
|
|
}
|
|
|
|
$cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols);
|
|
$cnt = count($headers);
|
|
|
|
if (count($cols) < $cnt) {
|
|
$cols = array_pad($cols, $cnt, null);
|
|
}
|
|
if (count($cols) > $cnt) {
|
|
$cols = array_slice($cols, 0, $cnt);
|
|
}
|
|
|
|
$row = @array_combine($headers, $cols) ?: [];
|
|
if (! empty($row)) {
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
|
|
fclose($fh);
|
|
@unlink($tmp);
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function resolveYearToFolder(string $code, string $path, string $year): string
|
|
{
|
|
$code = str_pad(trim($code), 4, '0', STR_PAD_LEFT);
|
|
$mdb = rtrim($path, '/') . '/' . $code . '/generale_stabile.mdb';
|
|
if (! is_file($mdb) || ! is_readable($mdb)) {
|
|
return $year;
|
|
}
|
|
|
|
$rows = $this->mdbExportToRows($mdb, ['anni', 'Anni', 'ANNI']);
|
|
if (empty($rows)) {
|
|
return $year;
|
|
}
|
|
|
|
foreach ($rows as $r) {
|
|
$yearRaw = $this->firstNonEmpty($r, ['anno_o', 'anno_r', 'anno']);
|
|
$y = null;
|
|
if (is_scalar($yearRaw) && preg_match('/(19|20)\d{2}/', (string) $yearRaw, $m)) {
|
|
$y = $m[0];
|
|
}
|
|
if ($y === null) {
|
|
continue;
|
|
}
|
|
|
|
if ($y === $year) {
|
|
$dirRaw = $this->firstNonEmpty($r, ['nome_dir', 'dir', 'cartella', 'id_anno']);
|
|
$dir = trim((string) ($dirRaw ?? ''));
|
|
if ($dir === '' && is_numeric((string) ($r['id_anno'] ?? ''))) {
|
|
$dir = str_pad((string) ((int) $r['id_anno']), 4, '0', STR_PAD_LEFT);
|
|
}
|
|
if ($dir !== '') {
|
|
return $dir;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $year;
|
|
}
|
|
}
|