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

441 lines
16 KiB
PHP
Executable File

<?php
namespace App\Filament\Pages\Contabilita;
use App\Models\GestioneContabile;
use App\Models\User;
use App\Modules\Contabilita\Models\Movimento;
use App\Modules\Contabilita\Models\PianoConti;
use App\Modules\Contabilita\Models\Registrazione;
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\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema as DbSchema;
use UnitEnum;
class PrimaNotaModifica extends Page implements HasForms
{
use InteractsWithForms;
protected static bool $shouldRegisterNavigation = false;
protected static ?string $navigationLabel = 'Modifica registrazione';
protected static ?string $title = 'Modifica registrazione';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-pencil-square';
protected static UnitEnum|string|null $navigationGroup = 'Contabilità';
protected static ?string $slug = 'contabilita/prima-nota/registrazione/{record}';
protected string $view = 'filament.pages.contabilita.prima-nota-modifica';
public ?array $data = [];
public Registrazione $registrazione;
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(int|string $record): void
{
$user = Auth::user();
if (! $user instanceof User) {
abort(403);
}
$reg = Registrazione::query()->find((int) $record);
if (! $reg) {
abort(404);
}
$allowed = StabileContext::accessibleStabili($user)->pluck('id')->map(fn ($v) => (int) $v)->all();
$recordStabileId = (int) ($reg->stabile_id ?? 0);
if ($recordStabileId > 0 && ! in_array($recordStabileId, $allowed, true)) {
abort(403);
}
if ($recordStabileId > 0) {
StabileContext::setActiveStabileId($user, $recordStabileId);
}
$this->registrazione = $reg;
$movimenti = Movimento::query()
->where('registrazione_id', (int) $reg->id)
->orderBy('id')
->get()
->map(fn (Movimento $m) => [
'conto_id' => $m->conto_id,
'tipo' => $m->tipo,
'importo' => $m->importo,
])
->all();
$this->getSchema('form')?->fill([
'gestione_id' => $reg->gestione_id,
'data_registrazione' => optional($reg->data_registrazione)->toDateString(),
'descrizione' => $reg->description ?? $reg->descrizione ?? $reg->notes ?? '',
'movimenti' => $movimenti,
]);
}
public function form(Schema $schema): Schema
{
return $schema
->statePath('data')
->schema([
Section::make('Dati registrazione')
->columns(2)
->schema([
Select::make('gestione_id')
->label('Gestione')
->options(fn () => $this->getGestioniOptions())
->searchable()
->visible(fn () => DbSchema::hasColumn('contabilita_registrazioni', 'gestione_id')),
DatePicker::make('data_registrazione')
->label('Data')
->required(),
Textarea::make('descrizione')
->label('Descrizione')
->rows(2)
->columnSpanFull()
->required(),
]),
Section::make('Movimenti (Partita doppia)')
->schema([
Repeater::make('movimenti')
->label('Righe')
->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(),
]),
]);
}
protected function getHeaderActions(): array
{
return [
Action::make('salva')
->label('Salva')
->icon('heroicon-o-check')
->action('submit'),
Action::make('torna')
->label('Torna alla lista')
->icon('heroicon-o-arrow-left')
->url(fn (): string => PrimaNotaArchivio::getUrl(panel: 'admin-filament')),
];
}
public function submit(): void
{
$state = $this->getSchema('form')?->getState() ?? [];
if (is_array($state['data'] ?? null)) {
$state = $state['data'];
} elseif (is_array($this->data ?? null) && ! empty($this->data)) {
$state = $this->data;
}
$movimenti = $state['movimenti'] ?? [];
if (! is_array($movimenti) || count($movimenti) < 2) {
Notification::make()->title('Inserisci almeno 2 movimenti')->danger()->send();
return;
}
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) {
Notification::make()->title('Righe non valide')->body('Ogni riga deve avere Conto, Tipo e Importo validi.')->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;
}
$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;
}
DB::transaction(function () use ($stabileId, $state, $movimenti): void {
$payload = [
'data_registrazione' => (string) ($state['data_registrazione'] ?? ''),
'description' => (string) ($state['descrizione'] ?? ''),
];
if (DbSchema::hasColumn('contabilita_registrazioni', 'gestione_id')) {
$payload['gestione_id'] = ! empty($state['gestione_id'])
? Registrazione::resolveGestioneIdOrDefault((int) $state['gestione_id'], $stabileId, (string) ($state['data_registrazione'] ?? ''))
: null;
}
$this->registrazione->fill($payload);
$this->registrazione->save();
if (DbSchema::hasTable('contabilita_movimenti')) {
DB::table('contabilita_movimenti')
->where('registrazione_id', (int) $this->registrazione->id)
->delete();
}
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) $this->registrazione->id, $stabileId, $contoId, $conto, $tipo, $imp, (string) ($state['descrizione'] ?? ''));
Movimento::query()->create($payload);
}
$this->registrazione->assertBilanciata();
});
Notification::make()->title('Registrazione aggiornata')->success()->send();
}
/**
* @return array<string,string>
*/
private function getGestioniOptions(): array
{
if (! DbSchema::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 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 (DbSchema::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 (DbSchema::hasColumn('contabilita_movimenti', 'chart_account_id')) {
$chartAccountId = $this->resolveLegacyChartAccountId($stabileId, $contoId, $conto, $masterCode, $accountCode, $subAccountCode);
if ($chartAccountId > 0) {
$payload['chart_account_id'] = $chartAccountId;
}
}
if (DbSchema::hasColumn('contabilita_movimenti', 'master_code')) {
$payload['master_code'] = $masterCode;
}
if (DbSchema::hasColumn('contabilita_movimenti', 'account_code')) {
$payload['account_code'] = $accountCode;
}
if (DbSchema::hasColumn('contabilita_movimenti', 'subaccount_code')) {
$payload['subaccount_code'] = $subAccountCode;
}
if (DbSchema::hasColumn('contabilita_movimenti', 'debit_amount')) {
$payload['debit_amount'] = $tipo === 'dare' ? $importo : 0.0;
}
if (DbSchema::hasColumn('contabilita_movimenti', 'credit_amount')) {
$payload['credit_amount'] = $tipo === 'avere' ? $importo : 0.0;
}
if (DbSchema::hasColumn('contabilita_movimenti', 'movement_description')) {
$payload['movement_description'] = $descrizione !== '' ? $descrizione : null;
}
if (DbSchema::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 (! DbSchema::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;
}
}