441 lines
16 KiB
PHP
Executable File
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;
|
|
}
|
|
}
|