471 lines
18 KiB
PHP
471 lines
18 KiB
PHP
<?php
|
|
namespace App\Filament\Pages\Condomini;
|
|
|
|
use App\Filament\Pages\Strumenti\DocumentiArchivio;
|
|
use App\Models\Documento;
|
|
use App\Models\Fornitore;
|
|
use App\Models\Scadenza;
|
|
use App\Models\Stabile;
|
|
use App\Models\StabileContratto;
|
|
use App\Models\User;
|
|
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\Textarea;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Str;
|
|
use UnitEnum;
|
|
|
|
class ContrattiHub extends Page
|
|
{
|
|
protected static ?string $navigationLabel = 'HUB Contratti';
|
|
|
|
protected static ?string $title = 'HUB Contratti';
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-clipboard-document-list';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'Stabile';
|
|
|
|
protected static ?int $navigationSort = 34;
|
|
|
|
protected static ?string $slug = 'condomini/contratti-hub';
|
|
|
|
protected string $view = 'filament.pages.condomini.contratti-hub';
|
|
|
|
public ?Stabile $stabileAttivo = null;
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = Auth::user();
|
|
|
|
return $user instanceof User
|
|
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return;
|
|
}
|
|
|
|
$activeId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $activeId) {
|
|
return;
|
|
}
|
|
|
|
$this->stabileAttivo = StabileContext::accessibleStabili($user)->firstWhere('id', $activeId);
|
|
}
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
Action::make('registra_contratto')
|
|
->label('Registra contratto')
|
|
->icon('heroicon-o-plus')
|
|
->form($this->getContrattoFormSchema())
|
|
->action(function (array $data): void {
|
|
$this->createContratto($data);
|
|
}),
|
|
Action::make('apri_documenti')
|
|
->label('Archivio documenti')
|
|
->icon('heroicon-o-folder-open')
|
|
->url(fn(): string => DocumentiArchivio::getUrl(panel: 'admin-filament')),
|
|
];
|
|
}
|
|
|
|
public function getStatsProperty(): array
|
|
{
|
|
$stabileId = (int) ($this->stabileAttivo?->id ?? 0);
|
|
if ($stabileId <= 0) {
|
|
return ['totale' => 0, 'attivi' => 0, 'scadenze' => 0, 'documenti' => 0];
|
|
}
|
|
|
|
$query = StabileContratto::query()->where('stabile_id', $stabileId);
|
|
|
|
return [
|
|
'totale' => (clone $query)->count(),
|
|
'attivi' => (clone $query)->where('stato', 'attivo')->count(),
|
|
'scadenze' => Scadenza::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereNotNull('stabile_contratto_id')
|
|
->whereDate('data_scadenza', '>=', now()->toDateString())
|
|
->whereDate('data_scadenza', '<=', now()->addDays(45)->toDateString())
|
|
->count(),
|
|
'documenti' => Documento::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('tipo_documento', 'contratto')
|
|
->count(),
|
|
];
|
|
}
|
|
|
|
public function getContrattiRowsProperty(): Collection
|
|
{
|
|
$stabileId = (int) ($this->stabileAttivo?->id ?? 0);
|
|
if ($stabileId <= 0) {
|
|
return collect();
|
|
}
|
|
|
|
return StabileContratto::query()
|
|
->with(['fornitore:id,ragione_sociale,nome,cognome', 'documentoPrincipale:id,nome,numero_protocollo'])
|
|
->where('stabile_id', $stabileId)
|
|
->orderByRaw("CASE WHEN stato = 'attivo' THEN 0 WHEN stato = 'in_revisione' THEN 1 ELSE 2 END")
|
|
->orderByDesc('decorrenza_dal')
|
|
->orderByDesc('id')
|
|
->get();
|
|
}
|
|
|
|
public function getUpcomingScadenzeProperty(): Collection
|
|
{
|
|
$stabileId = (int) ($this->stabileAttivo?->id ?? 0);
|
|
if ($stabileId <= 0) {
|
|
return collect();
|
|
}
|
|
|
|
return Scadenza::query()
|
|
->with('stabileContratto:id,titolo,tipo_contratto')
|
|
->where('stabile_id', $stabileId)
|
|
->whereNotNull('stabile_contratto_id')
|
|
->whereDate('data_scadenza', '>=', now()->subDays(7)->toDateString())
|
|
->orderBy('data_scadenza')
|
|
->limit(10)
|
|
->get();
|
|
}
|
|
|
|
protected function getContrattoFormSchema(): array
|
|
{
|
|
return [
|
|
Select::make('stabile_id')
|
|
->label('Stabile')
|
|
->options(fn(): array=> $this->getStabiliOptions())
|
|
->default(fn(): ?int => $this->stabileAttivo?->id)
|
|
->searchable()
|
|
->required(),
|
|
Select::make('fornitore_id')
|
|
->label('Fornitore / manutentore')
|
|
->options(fn(): array=> $this->getFornitoriOptions())
|
|
->searchable()
|
|
->nullable(),
|
|
Select::make('tipo_contratto')
|
|
->label('Tipo contratto')
|
|
->options($this->getTipoContrattoOptions())
|
|
->required(),
|
|
TextInput::make('titolo')
|
|
->label('Titolo')
|
|
->required()
|
|
->maxLength(255),
|
|
TextInput::make('servizio_label')
|
|
->label('Servizio / utenza')
|
|
->placeholder('Es. Ascensore scala A, Acqua potabile, Luce comune')
|
|
->maxLength(255),
|
|
TextInput::make('codice_contratto')
|
|
->label('Codice contratto')
|
|
->maxLength(80),
|
|
TextInput::make('riferimento_esterno')
|
|
->label('Riferimento esterno')
|
|
->placeholder('Es. numero cliente, riferimento pratica, ID POD/PDR principale')
|
|
->maxLength(120),
|
|
DatePicker::make('data_stipula')
|
|
->label('Data stipula'),
|
|
DatePicker::make('decorrenza_dal')
|
|
->label('Decorrenza dal')
|
|
->required(),
|
|
DatePicker::make('decorrenza_al')
|
|
->label('Decorrenza al'),
|
|
Select::make('frequenza_scadenze')
|
|
->label('Frequenza scadenze')
|
|
->options($this->getFrequenzaOptions())
|
|
->default('annuale')
|
|
->required(),
|
|
TextInput::make('giorno_scadenza')
|
|
->label('Giorno scadenza')
|
|
->numeric()
|
|
->minValue(1)
|
|
->maxValue(28),
|
|
TextInput::make('giorni_preavviso')
|
|
->label('Giorni preavviso')
|
|
->numeric()
|
|
->default(30)
|
|
->minValue(0)
|
|
->maxValue(365),
|
|
Toggle::make('rinnovo_automatico')
|
|
->label('Rinnovo automatico')
|
|
->default(false),
|
|
TextInput::make('importo_periodico')
|
|
->label('Importo periodico')
|
|
->numeric()
|
|
->step(0.01)
|
|
->minValue(0),
|
|
Select::make('documento_principale_id')
|
|
->label('Documento principale')
|
|
->options(fn(callable $get): array=> $this->getDocumentiContrattoOptions((int) ($get('stabile_id') ?: 0)))
|
|
->searchable()
|
|
->nullable(),
|
|
Repeater::make('codici_collegati')
|
|
->label('Codici collegati')
|
|
->schema([
|
|
TextInput::make('chiave')
|
|
->label('Chiave')
|
|
->placeholder('Es. POD, PDR, matricola_contatore, codice_cliente, utenza, matricola_ascensore')
|
|
->required(),
|
|
TextInput::make('valore')
|
|
->label('Valore')
|
|
->required(),
|
|
])
|
|
->addActionLabel('Aggiungi codice')
|
|
->defaultItems(0)
|
|
->columns(2),
|
|
Textarea::make('note')
|
|
->label('Note')
|
|
->rows(4),
|
|
];
|
|
}
|
|
|
|
protected function createContratto(array $data): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
Notification::make()->title('Utente non valido')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$stabileId = (int) ($data['stabile_id'] ?? 0);
|
|
$stabile = StabileContext::accessibleStabili($user)->firstWhere('id', $stabileId);
|
|
if (! $stabile instanceof Stabile) {
|
|
Notification::make()->title('Stabile non valido')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$contract = StabileContratto::query()->create([
|
|
'stabile_id' => $stabileId,
|
|
'fornitore_id' => isset($data['fornitore_id']) && is_numeric($data['fornitore_id']) ? (int) $data['fornitore_id'] : null,
|
|
'documento_principale_id' => isset($data['documento_principale_id']) && is_numeric($data['documento_principale_id']) ? (int) $data['documento_principale_id'] : null,
|
|
'titolo' => trim((string) ($data['titolo'] ?? '')),
|
|
'tipo_contratto' => trim((string) ($data['tipo_contratto'] ?? 'altro')),
|
|
'categoria_impianto' => trim((string) ($data['tipo_contratto'] ?? 'altro')),
|
|
'servizio_label' => $this->nullableString($data['servizio_label'] ?? null),
|
|
'codice_contratto' => $this->nullableString($data['codice_contratto'] ?? null),
|
|
'riferimento_esterno' => $this->nullableString($data['riferimento_esterno'] ?? null),
|
|
'stato' => 'attivo',
|
|
'data_stipula' => $data['data_stipula'] ?? null,
|
|
'decorrenza_dal' => $data['decorrenza_dal'] ?? null,
|
|
'decorrenza_al' => $data['decorrenza_al'] ?? null,
|
|
'frequenza_scadenze' => $this->nullableString($data['frequenza_scadenze'] ?? null),
|
|
'giorno_scadenza' => isset($data['giorno_scadenza']) && is_numeric($data['giorno_scadenza']) ? (int) $data['giorno_scadenza'] : null,
|
|
'giorni_preavviso' => isset($data['giorni_preavviso']) && is_numeric($data['giorni_preavviso']) ? (int) $data['giorni_preavviso'] : 30,
|
|
'rinnovo_automatico' => (bool) ($data['rinnovo_automatico'] ?? false),
|
|
'importo_periodico' => isset($data['importo_periodico']) && is_numeric($data['importo_periodico']) ? (float) $data['importo_periodico'] : null,
|
|
'valuta' => 'EUR',
|
|
'codici_collegati' => $this->normalizeKeyValueRows($data['codici_collegati'] ?? []),
|
|
'dati_contratto' => [
|
|
'origine' => 'hub_contratti_stabile',
|
|
'documenti_url' => DocumentiArchivio::getUrl(panel: 'admin-filament'),
|
|
],
|
|
'note' => $this->nullableString($data['note'] ?? null),
|
|
'created_by' => (int) $user->id,
|
|
'updated_by' => (int) $user->id,
|
|
]);
|
|
|
|
$this->syncScadenzeContratto($contract);
|
|
|
|
Notification::make()
|
|
->title('Contratto registrato')
|
|
->body('Scadenze programmate: ' . $contract->scadenze()->count())
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
protected function syncScadenzeContratto(StabileContratto $contract): void
|
|
{
|
|
$contract->scadenze()->delete();
|
|
|
|
foreach ($this->buildScheduleDates($contract) as $date) {
|
|
$popupDal = $date->copy()->subDays(max(0, (int) ($contract->giorni_preavviso ?? 0)));
|
|
|
|
Scadenza::query()->create([
|
|
'stabile_id' => (int) $contract->stabile_id,
|
|
'stabile_contratto_id' => (int) $contract->id,
|
|
'descrizione_sintetica' => $contract->titolo,
|
|
'data_scadenza' => $date->toDateString(),
|
|
'natura' => 'contratto_stabile',
|
|
'natura_label' => 'Scadenza contratto',
|
|
'gestione_tipo' => $contract->tipo_contratto,
|
|
'fatto' => 'N',
|
|
'tipo_riga' => trim((string) ($contract->servizio_label ?: $contract->tipo_contratto)),
|
|
'richiede_pop_up' => true,
|
|
'giorni_anticipo' => (int) ($contract->giorni_preavviso ?? 0),
|
|
'popup_dal' => $popupDal->toDateString(),
|
|
'legacy_payload' => [
|
|
'source' => 'stabile_contratti',
|
|
'codice_contratto' => $contract->codice_contratto,
|
|
'riferimento_esterno' => $contract->riferimento_esterno,
|
|
'codici_collegati' => $contract->codici_collegati ?? [],
|
|
],
|
|
]);
|
|
}
|
|
}
|
|
|
|
/** @return array<int, Carbon> */
|
|
protected function buildScheduleDates(StabileContratto $contract): array
|
|
{
|
|
$start = $contract->decorrenza_dal instanceof Carbon
|
|
? $contract->decorrenza_dal->copy()
|
|
: ($contract->decorrenza_al instanceof Carbon ? $contract->decorrenza_al->copy() : now()->copy());
|
|
$end = $contract->decorrenza_al instanceof Carbon ? $contract->decorrenza_al->copy() : null;
|
|
$frequency = trim((string) ($contract->frequenza_scadenze ?? 'una_tantum'));
|
|
|
|
if ($contract->giorno_scadenza) {
|
|
$start->day(min(28, (int) $contract->giorno_scadenza));
|
|
}
|
|
|
|
if ($frequency === '' || $frequency === 'una_tantum') {
|
|
return [$end ?: $start];
|
|
}
|
|
|
|
$dates = [];
|
|
$cursor = $start->copy();
|
|
$limit = $end
|
|
? $end->copy()
|
|
: ($contract->rinnovo_automatico ? $start->copy()->addMonths(24) : $start->copy()->addMonths(12));
|
|
|
|
while ($cursor <= $limit && count($dates) < 60) {
|
|
$dates[] = $cursor->copy();
|
|
$cursor = match ($frequency) {
|
|
'mensile' => $cursor->copy()->addMonthNoOverflow(),
|
|
'bimestrale' => $cursor->copy()->addMonthsNoOverflow(2),
|
|
'trimestrale' => $cursor->copy()->addMonthsNoOverflow(3),
|
|
'semestrale' => $cursor->copy()->addMonthsNoOverflow(6),
|
|
default => $cursor->copy()->addYearNoOverflow(),
|
|
};
|
|
|
|
if ($contract->giorno_scadenza) {
|
|
$cursor->day(min(28, (int) $contract->giorno_scadenza));
|
|
}
|
|
}
|
|
|
|
return $dates;
|
|
}
|
|
|
|
protected function getTipoContrattoOptions(): array
|
|
{
|
|
return [
|
|
'acqua' => 'Acqua',
|
|
'energia_elettrica' => 'Energia elettrica',
|
|
'ascensore' => 'Ascensore',
|
|
'gas' => 'Gas',
|
|
'pulizie' => 'Pulizie',
|
|
'portierato' => 'Portierato',
|
|
'assicurazione' => 'Assicurazione',
|
|
'antincendio' => 'Antincendio',
|
|
'raffrescamento' => 'Raffrescamento',
|
|
'riscaldamento' => 'Riscaldamento',
|
|
'telefonia_dati' => 'Telefonia / dati',
|
|
'altro' => 'Altro servizio',
|
|
];
|
|
}
|
|
|
|
protected function getFrequenzaOptions(): array
|
|
{
|
|
return [
|
|
'una_tantum' => 'Una tantum',
|
|
'mensile' => 'Mensile',
|
|
'bimestrale' => 'Bimestrale',
|
|
'trimestrale' => 'Trimestrale',
|
|
'semestrale' => 'Semestrale',
|
|
'annuale' => 'Annuale',
|
|
];
|
|
}
|
|
|
|
protected function getStabiliOptions(): array
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return [];
|
|
}
|
|
|
|
return StabileContext::accessibleStabili($user)
|
|
->mapWithKeys(fn(Stabile $stabile): array=> [(string) $stabile->id => trim((string) ($stabile->denominazione ?: ('Stabile #' . $stabile->id)))])
|
|
->all();
|
|
}
|
|
|
|
protected function getFornitoriOptions(): array
|
|
{
|
|
return Fornitore::query()
|
|
->orderByRaw("COALESCE(NULLIF(ragione_sociale, ''), CONCAT(COALESCE(nome, ''), ' ', COALESCE(cognome, ''))) asc")
|
|
->limit(1500)
|
|
->get(['id', 'ragione_sociale', 'nome', 'cognome'])
|
|
->mapWithKeys(function (Fornitore $fornitore): array {
|
|
$label = trim((string) ($fornitore->ragione_sociale ?: trim(($fornitore->nome ?? '') . ' ' . ($fornitore->cognome ?? ''))));
|
|
|
|
return [(string) $fornitore->id => ($label !== '' ? $label : ('Fornitore #' . $fornitore->id))];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
protected function getDocumentiContrattoOptions(int $stabileId): array
|
|
{
|
|
if ($stabileId <= 0) {
|
|
return [];
|
|
}
|
|
|
|
return Documento::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('tipo_documento', 'contratto')
|
|
->orderByDesc('data_documento')
|
|
->limit(250)
|
|
->get(['id', 'nome', 'numero_protocollo'])
|
|
->mapWithKeys(fn(Documento $documento): array=> [
|
|
(string) $documento->id => trim((string) (($documento->numero_protocollo ? $documento->numero_protocollo . ' · ' : '') . ($documento->nome ?: ('Documento #' . $documento->id)))),
|
|
])
|
|
->all();
|
|
}
|
|
|
|
/** @param array<int, mixed> $rows */
|
|
protected function normalizeKeyValueRows(array $rows): array
|
|
{
|
|
return collect($rows)
|
|
->map(function ($row): ?array {
|
|
$key = trim((string) data_get($row, 'chiave', ''));
|
|
$value = trim((string) data_get($row, 'valore', ''));
|
|
|
|
return ($key !== '' && $value !== '') ? ['chiave' => $key, 'valore' => $value] : null;
|
|
})
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
protected function nullableString(mixed $value): ?string
|
|
{
|
|
$value = trim((string) $value);
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
public function contractBadgeColor(string $status): string
|
|
{
|
|
return match ($status) {
|
|
'attivo' => 'emerald',
|
|
'in_revisione' => 'amber',
|
|
'scaduto' => 'rose',
|
|
default => 'slate',
|
|
};
|
|
}
|
|
|
|
public function contractTypeLabel(?string $type): string
|
|
{
|
|
$key = trim((string) $type);
|
|
|
|
return $this->getTipoContrattoOptions()[$key] ?? ($key !== '' ? Str::headline($key) : 'Contratto');
|
|
}
|
|
}
|