netgescon-day0/app/Filament/Pages/Contabilita/PrimaNotaArchivio.php

1240 lines
50 KiB
PHP
Executable File

<?php
namespace App\Filament\Pages\Contabilita;
use App\Models\GestioneContabile;
use App\Models\User;
use App\Filament\Pages\Contabilita\PrimaNotaDettaglio;
use App\Filament\Pages\Contabilita\PrimaNotaModifica;
use App\Modules\Contabilita\Models\ChiusuraGestione;
use App\Modules\Contabilita\Models\Movimento;
use App\Modules\Contabilita\Models\PianoConti;
use App\Modules\Contabilita\Models\Registrazione;
use App\Modules\Contabilita\Services\ChiusuraGestioneService;
use App\Support\AnnoGestioneContext;
use App\Support\GestioneContext;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use UnitEnum;
class PrimaNotaArchivio extends Page implements HasTable
{
use InteractsWithTable;
protected static ?string $navigationLabel = 'Prima nota';
protected static ?string $title = 'Prima nota';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-book-open';
protected static UnitEnum|string|null $navigationGroup = 'Contabilità';
protected static ?int $navigationSort = 10;
protected static ?string $slug = 'contabilita/prima-nota';
protected string $view = 'filament.pages.contabilita.prima-nota-archivio';
/** @var array<int, array{causale: string, fornitore: string, totale: float, ritenuta: float}> */
private array $fatturaCache = [];
/** @var array<int, array{dare: float, avere: float}> */
private array $movimentiTotaliCache = [];
/** @var array<string, mixed>|null */
private ?array $bilancioCache = null;
public string $archivioScope = 'all';
public static function canAccess(): bool
{
$user = Auth::user();
if (! $user instanceof User) {
return false;
}
if ($user->hasAnyRole(['super-admin', 'admin'])) {
return true;
}
return $user->can('contabilita.prima-nota');
}
public function mount(): void
{
$scope = strtolower((string) request()->query('archivio', 'all'));
$this->archivioScope = in_array($scope, ['all', 'gescon', 'netgescon'], true) ? $scope : 'all';
$this->mountInteractsWithTable();
}
public function getActiveStabile(): bool
{
$user = Auth::user();
if (! $user instanceof User) {
return false;
}
return (bool) StabileContext::resolveActiveStabileId($user);
}
protected function getHeaderActions(): array
{
return [
Action::make('sync_legacy_operazioni')
->label('Aggiorna da legacy')
->icon('heroicon-o-arrow-path')
->form([
Select::make('tipo')
->label('Gestione')
->options([
'all' => 'Tutte',
'ordinaria' => 'Ordinaria',
'straordinaria' => 'Straordinaria',
'riscaldamento' => 'Riscaldamento',
])
->default('all')
->required(),
TextInput::make('anno')
->label('Anno (opzionale)')
->numeric()
->minValue(2000)
->maxValue(2100)
->nullable(),
\Filament\Forms\Components\Toggle::make('dry')
->label('Solo simulazione (dry-run)')
->default(false),
])
->action(function (array $data): void {
$user = Auth::user();
if (! $user instanceof User) {
Notification::make()->title('Utente non valido')->danger()->send();
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Seleziona uno stabile')->danger()->send();
return;
}
$params = [
'--stabile-id' => (string) $stabileId,
'--tipo' => (string) ($data['tipo'] ?? 'all'),
];
if (! empty($data['anno']) && is_numeric($data['anno'])) {
$params['--year'] = (string) ((int) $data['anno']);
}
if (! empty($data['dry'])) {
$params['--dry'] = true;
}
$code = Artisan::call('gescon:sync-operazioni-prima-nota', $params);
$output = trim(Artisan::output());
if ($code === 0) {
Notification::make()
->title('Sync legacy completato')
->body($output !== '' ? $output : 'Operazione completata con successo.')
->success()
->send();
$this->resetTable();
return;
}
Notification::make()
->title('Errore sync legacy')
->body($output !== '' ? $output : 'Comando terminato con errore.')
->danger()
->send();
}),
Action::make('archivio_chiusure')
->label('Archivio chiusure')
->icon('heroicon-o-archive-box')
->visible(fn () => Schema::hasTable('contabilita_chiusure_gestione'))
->url(fn (): string => ChiusureGestioneArchivio::getUrl()),
Action::make('chiudi_gestione')
->label('Chiudi gestione')
->icon('heroicon-o-lock-closed')
->visible(fn () => Schema::hasTable('gestioni_contabili')
&& Schema::hasTable('contabilita_registrazioni')
&& Schema::hasColumn('contabilita_registrazioni', 'gestione_id')
&& Schema::hasTable('contabilita_chiusure_gestione'))
->form([
Select::make('gestione_id')
->label('Gestione da chiudere')
->options(fn () => $this->getGestioniOptions())
->searchable()
->required(),
DatePicker::make('data_chiusura')
->label('Data chiusura')
->required()
->default(fn () => now()->toDateString()),
])
->action(function (array $data): void {
$user = Auth::user();
if (! $user instanceof User) {
Notification::make()->title('Utente non valido')->danger()->send();
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Seleziona uno stabile')->danger()->send();
return;
}
$gestione = GestioneContabile::query()->where('stabile_id', $stabileId)->find((int) $data['gestione_id']);
if (! $gestione) {
Notification::make()->title('Gestione non trovata')->danger()->send();
return;
}
try {
$service = app(ChiusuraGestioneService::class);
$service->chiudiGestione($user, $stabileId, $gestione, (string) $data['data_chiusura']);
Notification::make()->title('Gestione chiusa')->success()->send();
$this->resetTable();
} catch (\Throwable $e) {
Notification::make()->title('Errore chiusura')->body($e->getMessage())->danger()->send();
}
}),
Action::make('nuova_registrazione')
->label('Nuova registrazione')
->icon('heroicon-o-plus')
->form([
Select::make('gestione_id')
->label('Gestione')
->options(fn () => $this->getGestioniOptions())
->searchable()
->visible(fn () => Schema::hasTable('contabilita_registrazioni') && Schema::hasColumn('contabilita_registrazioni', 'gestione_id'))
->default(function (): ?int {
if (! Schema::hasTable('gestioni_contabili')) {
return null;
}
$user = Auth::user();
if (! $user instanceof User) {
return null;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
return null;
}
$anno = AnnoGestioneContext::resolveActiveAnno($user);
$tipo = GestioneContext::resolveActiveGestione($user);
$match = GestioneContabile::query()
->where('stabile_id', $stabileId)
->where('anno_gestione', $anno)
->where('tipo_gestione', $tipo)
->orderBy('numero_straordinaria')
->first();
return $match?->id;
}),
DatePicker::make('data_registrazione')
->label('Data')
->required()
->default(now()->toDateString()),
Textarea::make('descrizione')
->label('Descrizione')
->rows(2)
->required(),
Repeater::make('movimenti')
->label('Movimenti (Dare/Avere)')
->minItems(2)
->schema([
Select::make('conto_id')
->label('Conto')
->options(fn () => PianoConti::query()
->orderBy('codice')
->get()
->mapWithKeys(fn (PianoConti $c) => [(string) $c->id => $c->codice . ' - ' . $c->descrizione])
->all())
->searchable()
->required(),
Select::make('tipo')
->label('Tipo')
->options(['dare' => 'Dare', 'avere' => 'Avere'])
->required(),
TextInput::make('importo')
->label('Importo')
->required(),
])
->columns(3)
->required(),
])
->action(function (array $data): void {
$user = Auth::user();
if (! $user instanceof User) {
Notification::make()->title('Utente non valido')->danger()->send();
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Seleziona uno stabile')->danger()->send();
return;
}
$movimenti = $data['movimenti'] ?? [];
if (! is_array($movimenti) || count($movimenti) < 2) {
Notification::make()->title('Inserisci almeno 2 movimenti')->danger()->send();
return;
}
$totDare = 0.0;
$totAvere = 0.0;
foreach ($movimenti as $m) {
$tipo = (string) ($m['tipo'] ?? '');
$importo = (float) str_replace(',', '.', str_replace('.', '', (string) ($m['importo'] ?? '0')));
if ($tipo === 'dare') {
$totDare += $importo;
} elseif ($tipo === 'avere') {
$totAvere += $importo;
}
}
if (round($totDare, 2) !== round($totAvere, 2)) {
Notification::make()
->title('Registrazione non bilanciata')
->body('Totale Dare e Totale Avere devono coincidere.')
->danger()
->send();
return;
}
DB::transaction(function () use ($user, $stabileId, $data, $movimenti): void {
$regPayload = [
'stabile_id' => $stabileId,
'gestione_id' => (Schema::hasColumn('contabilita_registrazioni', 'gestione_id')
? (isset($data['gestione_id'])
? Registrazione::resolveGestioneIdOrDefault((int) $data['gestione_id'], $stabileId, (string) ($data['data_registrazione'] ?? ''))
: null)
: null),
'data_registrazione' => $data['data_registrazione'],
'description' => $data['descrizione'],
'created_by' => $user->id,
'updated_by' => $user->id,
];
if (Schema::hasColumn('contabilita_registrazioni', 'user_id')) {
$regPayload['user_id'] = (int) $user->id;
}
/** @var Registrazione $reg */
$reg = Registrazione::query()->create($regPayload);
foreach ($movimenti as $m) {
$contoId = (int) ($m['conto_id'] ?? 0);
$tipo = (string) ($m['tipo'] ?? '');
$imp = (float) str_replace(',', '.', str_replace('.', '', (string) ($m['importo'] ?? '0')));
if ($contoId <= 0 || ! in_array($tipo, ['dare', 'avere'], true) || $imp <= 0) {
continue;
}
$conto = PianoConti::query()->find($contoId);
$payload = $this->buildMovimentoPayload((int) $reg->id, $stabileId, $contoId, $conto, $tipo, $imp, (string) $data['descrizione']);
Movimento::query()->create($payload);
}
$reg->assertBilanciata();
});
Notification::make()->title('Registrazione creata')->success()->send();
$this->resetTable();
}),
Action::make('bilancio_apertura_manual')
->label('Bilancio di apertura')
->icon('heroicon-o-scale')
->visible(fn () => Schema::hasTable('contabilita_registrazioni')
&& Schema::hasTable('contabilita_movimenti')
&& Schema::hasTable('gestioni_contabili')
&& Schema::hasColumn('contabilita_registrazioni', 'gestione_id'))
->form([
Select::make('gestione_id')
->label('Gestione')
->options(fn () => $this->getGestioniOptions())
->searchable()
->required(),
DatePicker::make('data_registrazione')
->label('Data')
->required()
->default(fn () => now()->toDateString()),
Textarea::make('descrizione')
->label('Descrizione')
->default('Bilancio di apertura (manuale)')
->rows(2)
->required(),
Repeater::make('movimenti')
->label('Righe (Dare / Avere)')
->minItems(2)
->schema([
Select::make('conto_id')
->label('Conto')
->options(fn () => PianoConti::query()
->orderBy('codice')
->get()
->mapWithKeys(fn (PianoConti $c) => [(string) $c->id => $c->codice . ' - ' . $c->descrizione])
->all())
->searchable()
->required(),
Select::make('tipo')
->label('Tipo')
->options(['dare' => 'Dare', 'avere' => 'Avere'])
->required(),
TextInput::make('importo')
->label('Importo €')
->numeric()
->required(),
])
->columns(3)
->required(),
])
->action(function (array $data): void {
$user = Auth::user();
if (! $user instanceof User) {
Notification::make()->title('Utente non valido')->danger()->send();
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Seleziona uno stabile')->danger()->send();
return;
}
$gestioneId = (int) ($data['gestione_id'] ?? 0);
if ($gestioneId <= 0) {
Notification::make()->title('Seleziona una gestione')->danger()->send();
return;
}
if (Schema::hasTable('contabilita_chiusure_gestione')) {
$chiusa = app(ChiusuraGestioneService::class)->isGestioneChiusa($gestioneId);
if ($chiusa) {
Notification::make()->title('Gestione chiusa')->body('Non è possibile inserire movimenti.')->danger()->send();
return;
}
}
try {
DB::transaction(function () use ($user, $stabileId, $gestioneId, $data): void {
$regPayload = [
'stabile_id' => $stabileId,
'gestione_id' => Registrazione::resolveGestioneIdOrDefault($gestioneId, $stabileId, (string) ($data['data_registrazione'] ?? '')),
'data_registrazione' => (string) $data['data_registrazione'],
'description' => (string) $data['descrizione'],
'created_by' => (int) $user->id,
'updated_by' => (int) $user->id,
];
if (Schema::hasColumn('contabilita_registrazioni', 'user_id')) {
$regPayload['user_id'] = (int) $user->id;
}
/** @var Registrazione $reg */
$reg = Registrazione::query()->create($regPayload);
$righe = is_array($data['movimenti'] ?? null) ? $data['movimenti'] : [];
foreach ($righe as $r) {
$contoId = (int) ($r['conto_id'] ?? 0);
$tipo = (string) ($r['tipo'] ?? '');
$imp = round((float) ($r['importo'] ?? 0), 2);
if ($contoId <= 0 || ! in_array($tipo, ['dare', 'avere'], true) || $imp <= 0) {
continue;
}
$conto = PianoConti::query()->find($contoId);
$payload = $this->buildMovimentoPayload((int) $reg->id, $stabileId, $contoId, $conto, $tipo, $imp, (string) $data['descrizione']);
Movimento::query()->create($payload);
}
$reg->assertBilanciata();
});
Notification::make()->title('Bilancio di apertura salvato')->success()->send();
$this->resetTable();
} catch (\Throwable $e) {
Notification::make()->title('Errore')->body($e->getMessage())->danger()->send();
}
}),
Action::make('modifica_registrazione')
->label('Modifica registrazione')
->icon('heroicon-o-pencil-square')
->form([
TextInput::make('id')
->label('ID registrazione')
->required()
->numeric(),
Select::make('gestione_id')
->label('Gestione')
->options(fn () => $this->getGestioniOptions())
->searchable()
->nullable()
->visible(fn () => Schema::hasColumn('contabilita_registrazioni', 'gestione_id')),
DatePicker::make('data_registrazione')
->label('Data')
->required(),
Textarea::make('descrizione')
->label('Descrizione')
->rows(2)
->required(),
])
->action(function (array $data): void {
$user = Auth::user();
if (! $user instanceof User) {
Notification::make()->title('Utente non valido')->danger()->send();
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Seleziona uno stabile')->danger()->send();
return;
}
$id = (int) ($data['id'] ?? 0);
if ($id <= 0) {
Notification::make()->title('ID non valido')->danger()->send();
return;
}
/** @var Registrazione|null $reg */
$reg = Registrazione::query()->where('stabile_id', $stabileId)->find($id);
if (! $reg) {
Notification::make()->title('Registrazione non trovata')->danger()->send();
return;
}
$payload = [
'data_registrazione' => (string) $data['data_registrazione'],
'description' => (string) $data['descrizione'],
];
if (Schema::hasColumn('contabilita_registrazioni', 'gestione_id')) {
$payload['gestione_id'] = ! empty($data['gestione_id'])
? Registrazione::resolveGestioneIdOrDefault((int) $data['gestione_id'], $stabileId, (string) $data['data_registrazione'])
: null;
}
$reg->fill($payload);
$reg->save();
Notification::make()->title('Registrazione aggiornata')->success()->send();
$this->resetTable();
}),
Action::make('elimina_registrazione')
->label('Elimina registrazione')
->icon('heroicon-o-trash')
->requiresConfirmation()
->form([
TextInput::make('id')
->label('ID registrazione')
->required()
->numeric(),
])
->action(function (array $data): void {
$user = Auth::user();
if (! $user instanceof User) {
Notification::make()->title('Utente non valido')->danger()->send();
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Seleziona uno stabile')->danger()->send();
return;
}
$id = (int) ($data['id'] ?? 0);
if ($id <= 0) {
Notification::make()->title('ID non valido')->danger()->send();
return;
}
DB::transaction(function () use ($id, $stabileId): void {
$reg = Registrazione::query()->where('stabile_id', $stabileId)->find($id);
if (! $reg) {
return;
}
if (Schema::hasTable('contabilita_movimenti')) {
DB::table('contabilita_movimenti')->where('registrazione_id', $id)->delete();
}
$reg->delete();
});
Notification::make()->title('Registrazione eliminata')->success()->send();
$this->resetTable();
}),
];
}
protected function getTableQuery(): Builder
{
$user = Auth::user();
if (! $user instanceof User) {
return Registrazione::query()->whereRaw('1 = 0');
}
$activeStabileId = StabileContext::resolveActiveStabileId($user);
if (! $activeStabileId) {
return Registrazione::query()->whereRaw('1 = 0');
}
$query = Registrazione::query()
->where('stabile_id', $activeStabileId)
->orderByDesc('data_registrazione')
->orderByDesc('id');
if ($this->archivioScope === 'gescon') {
$this->applyGesconSourceCondition($query);
} elseif ($this->archivioScope === 'netgescon') {
$query->where(function ($q): void {
$q->whereNull('external_reference')
->orWhere(function ($sub): void {
$sub->where('external_reference', 'not like', 'GESCON:%')
->where('external_reference', 'not like', 'LEGACY:%')
->where('external_reference', 'not like', 'IMPORT_GESCON:%')
->where('external_reference', 'not like', 'OPERAZIONE_LEGACY:%');
});
})->where(function ($q): void {
$q->whereNull('document_type')
->orWhereNotIn(DB::raw('UPPER(document_type)'), ['GESCON', 'LEGACY', 'LEGACY_GESCON']);
});
}
return $query;
}
public function table(Table $table): Table
{
return $table
->striped()
->filters([
SelectFilter::make('gestione_id')
->label('Gestione')
->options(fn () => $this->getGestioniOptions())
->visible(fn () => Schema::hasColumn('contabilita_registrazioni', 'gestione_id')),
])
->columns([
TextColumn::make('protocol')
->label('Prot. unico')
->getStateUsing(function (Registrazione $record): string {
$protocol = trim((string) ($record->protocol ?? ''));
return $protocol !== '' ? $protocol : ('PN-' . (int) $record->id);
})
->searchable()
->toggleable(),
TextColumn::make('protocollo_completo')
->label('Prot. gestione')
->getStateUsing(function (Registrazione $record): string {
$value = trim((string) ($record->protocollo_completo ?? ''));
if ($value !== '') {
return $value;
}
$prefix = trim((string) ($record->protocollo_prefix ?? ''));
$num = (int) ($record->protocollo_numero ?? 0);
return $prefix !== '' && $num > 0 ? ($prefix . '-' . $num) : '—';
})
->toggleable(),
TextColumn::make('protocollo_prefix')
->label('Prot. tipo')
->badge()
->color('gray')
->getStateUsing(function (Registrazione $record): string {
$prefix = trim((string) ($record->protocollo_prefix ?? ''));
return $prefix !== '' ? $prefix : '—';
})
->toggleable(),
TextColumn::make('source')
->label('Archivio')
->badge()
->color(fn (string $state): string => $state === 'GESCON' ? 'warning' : 'success')
->getStateUsing(fn (Registrazione $record): string => $this->isGesconRecord($record) ? 'GESCON' : 'NetGescon')
->toggleable(),
TextColumn::make('data_registrazione')
->label('Data')
->date('d/m/Y')
->sortable(),
TextColumn::make('gestioneContabile.denominazione')
->label('Gestione')
->toggleable()
->visible(fn () => Schema::hasColumn('contabilita_registrazioni', 'gestione_id'))
->getStateUsing(function (Registrazione $record): string {
return $this->getGestioneLabel($record);
}),
TextColumn::make('tipo_movimento')
->label('Tipo movimento')
->getStateUsing(function (Registrazione $record): string {
$info = $this->getFatturaSummary($record);
$causale = $info['causale'] !== '' ? $info['causale'] : 'Operazione';
$fornitore = $info['fornitore'] !== '' ? (' · ' . $info['fornitore']) : '';
return $causale . $fornitore;
})
->wrap(),
TextColumn::make('description')
->label('Descrizione')
->wrap()
->searchable()
->getStateUsing(function (Registrazione $record): string {
$descr = trim((string) ($record->description ?? ''));
if ($descr === '') {
$descr = trim((string) ($record->descrizione ?? ''));
}
if ($descr === '') {
$descr = trim((string) ($record->notes ?? ''));
}
return $descr !== '' ? $descr : '—';
}),
TextColumn::make('totale_operazione')
->label('Totale')
->getStateUsing(function (Registrazione $record): string {
$info = $this->getFatturaSummary($record);
$tot = (float) ($info['totale'] ?? 0);
if ($tot <= 0) {
$totali = $this->getMovimentiTotals($record);
$tot = max($totali['dare'], $totali['avere']);
}
return '€ ' . number_format($tot, 2, ',', '.');
})
->alignRight(),
TextColumn::make('ritenuta_importo')
->label('RA')
->getStateUsing(function (Registrazione $record): string {
$info = $this->getFatturaSummary($record);
$rit = (float) ($info['ritenuta'] ?? 0);
return $rit > 0 ? ('€ ' . number_format($rit, 2, ',', '.')) : '—';
})
->alignRight(),
TextColumn::make('registro_rda')
->label('Registro RDA')
->getStateUsing(function (Registrazione $record): string {
$info = $this->getFatturaSummary($record);
return ((float) ($info['ritenuta'] ?? 0)) > 0 ? 'Apri' : '—';
})
->url(function (Registrazione $record): ?string {
$info = $this->getFatturaSummary($record);
if (((float) ($info['ritenuta'] ?? 0)) <= 0) {
return null;
}
return \App\Filament\Pages\Contabilita\RegistroRitenuteAccontoArchivio::getUrl(panel: 'admin-filament');
})
->openUrlInNewTab(),
TextColumn::make('dettaglio')
->label('Dettaglio')
->getStateUsing(fn (): string => 'Apri')
->url(fn (Registrazione $record): string => PrimaNotaDettaglio::getUrl(['record' => $record->id], panel: 'admin-filament'))
->openUrlInNewTab(),
TextColumn::make('modifica')
->label('Modifica')
->getStateUsing(fn (): string => 'Modifica')
->url(fn (Registrazione $record): string => PrimaNotaModifica::getUrl(['record' => $record->id], panel: 'admin-filament'))
->openUrlInNewTab(),
TextColumn::make('id')
->label('ID')
->toggleable(isToggledHiddenByDefault: true),
]);
}
private function applyGesconSourceCondition(Builder $query): void
{
$query->where(function ($q): void {
$q->where('external_reference', 'like', 'GESCON:%')
->orWhere('external_reference', 'like', 'LEGACY:%')
->orWhere('external_reference', 'like', 'IMPORT_GESCON:%')
->orWhere('external_reference', 'like', 'OPERAZIONE_LEGACY:%')
->orWhereIn(DB::raw('UPPER(document_type)'), ['GESCON', 'LEGACY', 'LEGACY_GESCON']);
});
}
private function isGesconRecord(Registrazione $record): bool
{
$ref = strtoupper(trim((string) ($record->external_reference ?? '')));
if (
str_starts_with($ref, 'GESCON:')
|| str_starts_with($ref, 'LEGACY:')
|| str_starts_with($ref, 'IMPORT_GESCON:')
|| str_starts_with($ref, 'OPERAZIONE_LEGACY:')
) {
return true;
}
$docType = strtoupper(trim((string) ($record->document_type ?? '')));
if (in_array($docType, ['GESCON', 'LEGACY', 'LEGACY_GESCON'], true)) {
return true;
}
return false;
}
/**
* @return array<string,string>
*/
private function getGestioniOptions(): array
{
if (! Schema::hasTable('gestioni_contabili')) {
return [];
}
$user = Auth::user();
if (! $user instanceof User) {
return [];
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
return [];
}
return GestioneContabile::query()
->where('stabile_id', $stabileId)
->orderByDesc('anno_gestione')
->orderBy('tipo_gestione')
->orderBy('numero_straordinaria')
->get()
->mapWithKeys(fn (GestioneContabile $g) => [
(string) $g->id => $g->denominazione
. ' (' . $g->tipo_gestione . ' ' . $g->anno_gestione
. ($g->numero_straordinaria ? ' #' . $g->numero_straordinaria : '')
. ')',
])
->all();
}
private function getGestioneLabel(Registrazione $record): string
{
$gestioneId = (int) ($record->gestione_id ?? 0);
if ($gestioneId <= 0) {
return '—';
}
if (Schema::hasTable('gestioni_contabili')) {
$g = GestioneContabile::query()
->select(['id', 'denominazione', 'anno_gestione', 'tipo_gestione', 'protocollo_prefix'])
->find($gestioneId);
if ($g) {
$label = trim((string) ($g->denominazione ?? ''));
if ($label === '') {
$label = (string) $g->anno_gestione . ' - ' . (string) $g->tipo_gestione;
}
$prefix = trim((string) ($g->protocollo_prefix ?? ''));
if ($prefix !== '') {
$label = $prefix . ' — ' . $label;
}
return $label;
}
}
if (Schema::hasTable('gestioni')) {
$g = DB::table('gestioni')
->select(['id_gestione', 'anno_gestione', 'tipo_gestione'])
->where('id_gestione', $gestioneId)
->first();
if ($g) {
return (string) ($g->anno_gestione ?? '') . ' - ' . (string) ($g->tipo_gestione ?? '');
}
}
return '—';
}
/**
* @return array{causale: string, fornitore: string, totale: float, ritenuta: float}
*/
private function getFatturaSummary(Registrazione $record): array
{
$id = (int) $record->id;
if (isset($this->fatturaCache[$id])) {
return $this->fatturaCache[$id];
}
$summary = [
'causale' => '',
'fornitore' => '',
'totale' => 0.0,
'ritenuta' => 0.0,
];
if (Schema::hasTable('contabilita_fatture_fornitori')) {
$select = [
'f.causale_contabile',
'f.totale',
'f.ritenuta_importo',
'fo.ragione_sociale',
];
if (Schema::hasColumn('fornitori', 'denominazione')) {
$select[] = 'fo.denominazione';
}
$row = DB::table('contabilita_fatture_fornitori as f')
->leftJoin('fornitori as fo', 'fo.id', '=', 'f.fornitore_id')
->where('f.registrazione_id', $id)
->select($select)
->first();
if ($row) {
$causale = trim((string) ($row->causale_contabile ?? ''));
$fornitore = trim((string) ($row->ragione_sociale ?? ''));
if ($fornitore === '' && Schema::hasColumn('fornitori', 'denominazione')) {
$fornitore = trim((string) ($row->denominazione ?? ''));
}
$summary = [
'causale' => $causale !== '' ? $causale : 'Fattura ricevuta',
'fornitore' => $fornitore,
'totale' => (float) ($row->totale ?? 0),
'ritenuta' => (float) ($row->ritenuta_importo ?? 0),
];
}
}
if ($summary['causale'] === '') {
$docType = trim((string) ($record->document_type ?? ''));
if ($docType !== '') {
$summary['causale'] = $docType;
}
}
$this->fatturaCache[$id] = $summary;
return $summary;
}
/**
* @return array{dare: float, avere: float}
*/
private function getMovimentiTotals(Registrazione $record): array
{
$id = (int) $record->id;
if (isset($this->movimentiTotaliCache[$id])) {
return $this->movimentiTotaliCache[$id];
}
$totali = ['dare' => 0.0, 'avere' => 0.0];
if (! Schema::hasTable('contabilita_movimenti')) {
return $totali;
}
$rows = DB::table('contabilita_movimenti')
->select('tipo', DB::raw('SUM(importo) as totale'))
->where('registrazione_id', $id)
->groupBy('tipo')
->get();
foreach ($rows as $row) {
$tipo = (string) ($row->tipo ?? '');
$val = (float) ($row->totale ?? 0);
if ($tipo === 'dare') {
$totali['dare'] = $val;
} elseif ($tipo === 'avere') {
$totali['avere'] = $val;
}
}
$this->movimentiTotaliCache[$id] = $totali;
return $totali;
}
/**
* @return array<string, mixed>
*/
public function getBilancioData(): array
{
if ($this->bilancioCache !== null) {
return $this->bilancioCache;
}
$user = Auth::user();
if (! $user instanceof User) {
return $this->bilancioCache = [
'totale_dare' => 0.0,
'totale_avere' => 0.0,
'sezioni' => [],
];
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId || ! Schema::hasTable('contabilita_movimenti') || ! Schema::hasTable('contabilita_registrazioni')) {
return $this->bilancioCache = [
'totale_dare' => 0.0,
'totale_avere' => 0.0,
'sezioni' => [],
];
}
$select = [
'm.conto_id',
'c.codice',
'c.denominazione',
'c.descrizione',
'c.tipo_conto',
DB::raw("SUM(CASE WHEN m.tipo = 'dare' THEN m.importo ELSE 0 END) as dare"),
DB::raw("SUM(CASE WHEN m.tipo = 'avere' THEN m.importo ELSE 0 END) as avere"),
];
$query = DB::table('contabilita_movimenti as m')
->join('contabilita_registrazioni as r', 'r.id', '=', 'm.registrazione_id')
->leftJoin('contabilita_piano_conti as c', 'c.id', '=', 'm.conto_id')
->where('r.stabile_id', $stabileId);
if (Schema::hasColumn('contabilita_registrazioni', 'deleted_at')) {
$query->whereNull('r.deleted_at');
}
$rows = $query
->select($select)
->groupBy('m.conto_id', 'c.codice', 'c.denominazione', 'c.descrizione', 'c.tipo_conto')
->get();
$totaleDare = 0.0;
$totaleAvere = 0.0;
$finanziari = [];
$variazioni = [];
$economici = [];
foreach ($rows as $row) {
$dare = (float) ($row->dare ?? 0);
$avere = (float) ($row->avere ?? 0);
if (abs($dare) < 0.005 && abs($avere) < 0.005) {
continue;
}
$totaleDare += $dare;
$totaleAvere += $avere;
$codice = trim((string) ($row->codice ?? ''));
$desc = trim((string) ($row->denominazione ?? $row->descrizione ?? ''));
$label = $codice !== '' ? $codice : ('Conto #' . (int) ($row->conto_id ?? 0));
if ($desc !== '') {
$label .= ' - ' . $desc;
}
$item = [
'conto_id' => (int) ($row->conto_id ?? 0),
'label' => $label,
'dare' => $dare,
'avere' => $avere,
'saldo' => $dare - $avere,
];
$tipoConto = strtoupper(trim((string) ($row->tipo_conto ?? '')));
if (in_array($tipoConto, ['ATTIVO', 'PASSIVO'], true)) {
$finanziari[] = $item;
} elseif (in_array($tipoConto, ['RICAVO', 'COSTO'], true)) {
$economici[] = $item;
} else {
$variazioni[] = $item;
}
}
$this->bilancioCache = [
'totale_dare' => $totaleDare,
'totale_avere' => $totaleAvere,
'sezioni' => [
[
'titolo' => 'Conti finanziari (crediti/debiti)',
'righe' => $finanziari,
],
[
'titolo' => 'Variazioni finanziarie (entrate/uscite)',
'righe' => $variazioni,
],
[
'titolo' => 'Conti economici (costi/ricavi)',
'righe' => $economici,
],
],
];
return $this->bilancioCache;
}
private function buildMovimentoPayload(
int $registrazioneId,
int $stabileId,
int $contoId,
?PianoConti $conto,
string $tipo,
float $importo,
string $descrizione
): array {
$payload = [
'registrazione_id' => $registrazioneId,
'conto_id' => $contoId,
'tipo' => $tipo,
'importo' => $importo,
];
if (Schema::hasColumn('contabilita_movimenti', 'entry_id')) {
$payload['entry_id'] = $registrazioneId;
}
$code = trim((string) ($conto?->codice ?? ''));
if ($code === '') {
$code = (string) $contoId;
}
$normalized = preg_replace('/[^A-Za-z0-9]/', '', strtoupper($code)) ?: (string) $contoId;
$masterCode = str_pad(substr($normalized, 0, 3), 3, '0', STR_PAD_LEFT);
$accountCode = str_pad(substr($normalized, 0, 8), 8, '0', STR_PAD_LEFT);
$subAccountCode = str_pad(substr($normalized, -5), 5, '0', STR_PAD_LEFT);
if (Schema::hasColumn('contabilita_movimenti', 'chart_account_id')) {
$chartAccountId = $this->resolveLegacyChartAccountId($stabileId, $contoId, $conto, $masterCode, $accountCode, $subAccountCode);
if ($chartAccountId > 0) {
$payload['chart_account_id'] = $chartAccountId;
}
}
if (Schema::hasColumn('contabilita_movimenti', 'master_code')) {
$payload['master_code'] = $masterCode;
}
if (Schema::hasColumn('contabilita_movimenti', 'account_code')) {
$payload['account_code'] = $accountCode;
}
if (Schema::hasColumn('contabilita_movimenti', 'subaccount_code')) {
$payload['subaccount_code'] = $subAccountCode;
}
if (Schema::hasColumn('contabilita_movimenti', 'debit_amount')) {
$payload['debit_amount'] = $tipo === 'dare' ? $importo : 0.0;
}
if (Schema::hasColumn('contabilita_movimenti', 'credit_amount')) {
$payload['credit_amount'] = $tipo === 'avere' ? $importo : 0.0;
}
if (Schema::hasColumn('contabilita_movimenti', 'movement_description')) {
$payload['movement_description'] = $descrizione !== '' ? $descrizione : null;
}
if (Schema::hasColumn('contabilita_movimenti', 'sequence')) {
$payload['sequence'] = 1;
}
return $payload;
}
private function resolveLegacyChartAccountId(
int $stabileId,
int $contoId,
?PianoConti $conto,
string $masterCode,
string $accountCode,
string $subAccountCode
): int {
if (! Schema::hasTable('piano_conti')) {
return 0;
}
$mastro = str_pad(substr($masterCode, 0, 4), 4, '0', STR_PAD_LEFT);
$contoCode = str_pad(substr($accountCode, 0, 8), 8, '0', STR_PAD_LEFT);
$sotto = str_pad(substr($subAccountCode, -5), 5, '0', STR_PAD_LEFT);
$quarto = '00000';
$query = DB::table('piano_conti')
->where('mastro', $mastro)
->where('conto', $contoCode)
->where('sottoconto', $sotto)
->where('quarto_livello', $quarto);
if ($stabileId > 0) {
$query->where(function ($q) use ($stabileId) {
$q->whereNull('stabile_id')->orWhere('stabile_id', $stabileId);
});
}
$existingId = $query->value('id');
if (is_numeric($existingId)) {
return (int) $existingId;
}
$descr = trim((string) ($conto?->descrizione ?? $conto?->denominazione ?? ''));
if ($descr === '') {
$descr = 'Conto ' . $mastro . '.' . $contoCode . '.' . $sotto;
}
$tipo = strtoupper(trim((string) ($conto?->tipo ?? '')));
$tipoEnum = match ($tipo) {
'ATTIVITÀ', 'ATTIVITA', 'ATTIVO' => 'ATTIVO',
'PASSIVITÀ', 'PASSIVITA', 'PASSIVO' => 'PASSIVO',
'RICAVO' => 'RICAVO',
'PATRIMONIO NETTO', 'PATRIMONIO' => 'PATRIMONIO',
default => 'COSTO',
};
$tipologia = match ($tipoEnum) {
'RICAVO' => 'RIC',
'ATTIVO' => 'ATT',
'PASSIVO' => 'PAS',
'PATRIMONIO' => 'PAT',
default => 'SPE',
};
$id = DB::table('piano_conti')->insertGetId([
'stabile_id' => $stabileId > 0 ? $stabileId : null,
'mastro' => $mastro,
'conto' => $contoCode,
'sottoconto' => $sotto,
'quarto_livello' => $quarto,
'denominazione' => $descr,
'tipo' => $tipoEnum,
'tipologia' => $tipologia,
'attivo' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
return is_numeric($id) ? (int) $id : 0;
}
}