1469 lines
56 KiB
PHP
1469 lines
56 KiB
PHP
<?php
|
||
namespace App\Filament\Pages\Gescon;
|
||
|
||
use App\Models\Amministratore;
|
||
use App\Models\Stabile;
|
||
use App\Models\User;
|
||
use App\Services\GesconImport\EssentialImportService;
|
||
use App\Services\GesconImport\StabileEnrichmentService;
|
||
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
|
||
{
|
||
$user = Auth::user();
|
||
|
||
return $user instanceof User
|
||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore'])
|
||
&& $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_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,
|
||
]);
|
||
|
||
if (is_string($defaultStabileCode) && $defaultStabileCode !== '') {
|
||
$this->refreshAnniOptionsForStabile($defaultStabileCode, $defaultPathRoot);
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
$amm = Amministratore::query()->find($ammId);
|
||
if (! $amm) {
|
||
return;
|
||
}
|
||
$code = trim((string) ($amm->codice_univoco ?: $amm->codice_amministratore ?: ''));
|
||
if ($code === '') {
|
||
$code = 'ID' . $amm->id;
|
||
}
|
||
$safe = preg_replace('/[^A-Za-z0-9_-]+/', '_', $code);
|
||
$safe = trim((string) $safe, '_');
|
||
if ($safe === '') {
|
||
$safe = 'ID' . $amm->id;
|
||
}
|
||
|
||
$root = '/mnt/gescon-archives/gescon/' . $safe;
|
||
$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('Anni contabili target (comando unico)')
|
||
->options(fn(): array=> $this->anniOptions)
|
||
->multiple()
|
||
->searchable()
|
||
->helperText('Seleziona uno o piu anni. 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()
|
||
->helperText('Per ACQUA (acqua_gen/acqua_dett/acqua_fatture).')
|
||
->dehydrated(),
|
||
|
||
Select::make('align_open_years_selected')
|
||
->label('Anni da tenere aperti')
|
||
->options(fn(): array=> $this->anniOptions)
|
||
->multiple()
|
||
->searchable()
|
||
->helperText('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.'),
|
||
|
||
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')
|
||
->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_operazioni')
|
||
->label('Operazioni')
|
||
->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)'),
|
||
]);
|
||
}
|
||
|
||
public function loadStabili(): void
|
||
{
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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->getSchema('form')?->getState() ?? [];
|
||
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->getSchema('form')?->getState() ?? [];
|
||
$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();
|
||
}
|
||
|
||
public function creaStabileDaMdb(): void
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$code = trim((string) ($state['stabile_code'] ?? ''));
|
||
if ($code === '') {
|
||
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
||
return;
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
|
||
$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')
|
||
->body('Serve almeno un record in tabella amministratori.')
|
||
->danger()
|
||
->send();
|
||
return;
|
||
}
|
||
|
||
$stabile = Stabile::query()->where('codice_stabile', $code)->first();
|
||
if (! $stabile) {
|
||
$label = $this->stabiliOptions[$code] ?? $code;
|
||
$denom = trim((string) preg_replace('/^' . preg_quote($code, '/') . '\s+—\s+/u', '', $label));
|
||
if ($denom === '' || $denom === $code) {
|
||
$denom = 'Stabile ' . $code;
|
||
}
|
||
|
||
$stabile = Stabile::query()->create([
|
||
'amministratore_id' => $amm->id,
|
||
'codice_stabile' => $code,
|
||
'denominazione' => $denom,
|
||
'stato' => 'attivo',
|
||
'attivo' => true,
|
||
]);
|
||
|
||
Notification::make()
|
||
->title('Stabile creato')
|
||
->body('Codice: ' . $code)
|
||
->success()
|
||
->send();
|
||
} else {
|
||
// Se un admin/super-admin ha selezionato un amministratore diverso, riallinea per evitare "sparizioni" da filtri tenant.
|
||
if ($user->hasAnyRole(['super-admin', 'admin']) && (int) $stabile->amministratore_id !== (int) $amm->id) {
|
||
$stabile->amministratore_id = (int) $amm->id;
|
||
$stabile->save();
|
||
|
||
Notification::make()
|
||
->title('Stabile riassegnato')
|
||
->body('Ora lo stabile è assegnato all’amministratore selezionato.')
|
||
->warning()
|
||
->send();
|
||
}
|
||
|
||
Notification::make()
|
||
->title('Stabile già presente')
|
||
->body('Userò lo stabile esistente: ' . $code)
|
||
->success()
|
||
->send();
|
||
}
|
||
|
||
StabileContext::setActiveStabileId($user, (int) $stabile->id);
|
||
|
||
Notification::make()
|
||
->title('Stabile impostato come attivo')
|
||
->body('Ora puoi eseguire l\'import selezionato o proseguire da Elenco stabili.')
|
||
->success()
|
||
->send();
|
||
}
|
||
|
||
public function applicaEnrichmentStabile(): void
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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 aggiornaFornitoriGescon(): void
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$code = trim((string) ($state['stabile_code'] ?? ''));
|
||
if ($code === '') {
|
||
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
||
return;
|
||
}
|
||
|
||
$stabile = Stabile::query()->where('codice_stabile', $code)->first();
|
||
if (! $stabile) {
|
||
Notification::make()
|
||
->title('Stabile non creato')
|
||
->body('Prima crea lo stabile dal pulsante dedicato.')
|
||
->danger()
|
||
->send();
|
||
return;
|
||
}
|
||
|
||
$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;
|
||
}
|
||
|
||
$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_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),
|
||
];
|
||
|
||
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;
|
||
}
|
||
|
||
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',
|
||
'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;
|
||
if (! empty($options['path']) && ! empty($code)) {
|
||
$annoFolder = $options['anno'] ?: '0001';
|
||
$singoloMdb = rtrim($options['path'], '/') . '/' . $code . '/' . $annoFolder . '/singolo_anno.mdb';
|
||
}
|
||
|
||
$generaleMdb = ! empty($options['path']) && ! empty($code)
|
||
? rtrim($options['path'], '/') . '/' . $code . '/generale_stabile.mdb'
|
||
: null;
|
||
|
||
try {
|
||
if (! empty($options['with_operazioni']) && is_string($singoloMdb)) {
|
||
Artisan::call('gescon:load-mdb', [
|
||
'--mdb' => $singoloMdb,
|
||
'--table' => 'operazioni',
|
||
'--stabile' => $code,
|
||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||
'--truncate' => true,
|
||
]);
|
||
$outputs[] = trim((string) Artisan::output());
|
||
|
||
Artisan::call('gescon:load-mdb', [
|
||
'--mdb' => $singoloMdb,
|
||
'--table' => 'voc_spe',
|
||
'--stabile' => $code,
|
||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||
'--truncate' => true,
|
||
]);
|
||
$outputs[] = trim((string) Artisan::output());
|
||
|
||
Artisan::call('gescon:load-mdb', [
|
||
'--mdb' => $singoloMdb,
|
||
'--table' => 'tabelle',
|
||
'--stabile' => $code,
|
||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||
'--truncate' => true,
|
||
]);
|
||
$outputs[] = trim((string) Artisan::output());
|
||
|
||
Artisan::call('gescon:load-mdb', [
|
||
'--mdb' => $singoloMdb,
|
||
'--table' => 'anagr_casse',
|
||
'--stabile' => $code,
|
||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||
'--truncate' => 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' => (string) ($options['anno'] ?? ''),
|
||
'--truncate' => 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' => (string) ($options['anno'] ?? ''),
|
||
'--truncate' => true,
|
||
]);
|
||
$outputs[] = trim((string) Artisan::output());
|
||
|
||
Artisan::call('gescon:load-mdb', [
|
||
'--mdb' => $generaleMdb,
|
||
'--table' => 'emes_det',
|
||
'--stabile' => $code,
|
||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||
'--truncate' => 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'])) {
|
||
$params['--anno'] = $options['anno'];
|
||
}
|
||
if (! empty($options['dry'])) {
|
||
$params['--dry-run'] = true;
|
||
}
|
||
|
||
Artisan::call('gescon:import-full', $params);
|
||
$outputs[] = trim((string) Artisan::output());
|
||
}
|
||
|
||
if (! empty($options['with_gestioni']) && empty($options['dry'])) {
|
||
$gestParams = [
|
||
'--stabile' => $code,
|
||
'--root' => $options['path'],
|
||
];
|
||
Artisan::call('gescon:import-gestioni', $gestParams);
|
||
$outputs[] = trim((string) Artisan::output());
|
||
}
|
||
|
||
$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
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
abort(403);
|
||
}
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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();
|
||
}
|
||
|
||
public function caricaAnniDaGenerale(): void
|
||
{
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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();
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
ksort($opts);
|
||
$this->anniOptions = $opts;
|
||
|
||
$state = $this->getSchema('form')?->getState() ?? [];
|
||
$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(array_reverse($keys), 0, 2);
|
||
sort($selectedYears);
|
||
}
|
||
|
||
$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 = [end($selectedYears)];
|
||
}
|
||
|
||
$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 = $selectedYears;
|
||
}
|
||
|
||
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 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;
|
||
}
|
||
}
|