4596 lines
187 KiB
PHP
Executable File
4596 lines
187 KiB
PHP
Executable File
<?php
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Filament\Pages\Condomini\LettureServiziArchivio;
|
|
use App\Filament\Pages\Condomini\ServiziStabileArchivio;
|
|
use App\Filament\Pages\Gescon\RubricaUniversaleScheda;
|
|
use App\Filament\Pages\Supporto\TicketAcquaLight;
|
|
use App\Models\DettaglioRipartizioneSpese;
|
|
use App\Models\GestioneContabile;
|
|
use App\Models\Incasso;
|
|
use App\Models\Persona;
|
|
use App\Models\PersonaUnitaRelazione;
|
|
use App\Models\Proprieta;
|
|
use App\Models\RataEmessaNg;
|
|
use App\Models\RubricaRuolo;
|
|
use App\Models\RubricaUniversale;
|
|
use App\Models\Soggetto;
|
|
use App\Models\StabileServizio;
|
|
use App\Models\StabileServizioLettura;
|
|
use App\Models\UnitaImmobiliare;
|
|
use App\Models\UnitaRecapitoServizio;
|
|
use App\Models\User;
|
|
use App\Models\VoceSpesa;
|
|
use App\Services\Comunicazioni\RecapitiServizioResolver;
|
|
use App\Services\ProgramAclService;
|
|
use App\Support\AnnoGestioneContext;
|
|
use App\Support\GestioneVisibility;
|
|
use App\Support\StabileContext;
|
|
use BackedEnum;
|
|
use Carbon\Carbon;
|
|
use Filament\Actions\Action;
|
|
use Filament\Forms\Components\DatePicker;
|
|
use Filament\Forms\Components\Hidden;
|
|
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\Forms\Concerns\InteractsWithForms;
|
|
use Filament\Forms\Contracts\HasForms;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Schemas\Schema;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema as DbSchema;
|
|
use UnitEnum;
|
|
|
|
class UnitaImmobiliarePage extends Page implements HasForms
|
|
{
|
|
use InteractsWithForms;
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = Auth::user();
|
|
|
|
if (! $user instanceof User) {
|
|
return false;
|
|
}
|
|
|
|
if (class_exists(ProgramAclService::class)) {
|
|
return app(ProgramAclService::class)->canAccessProgram($user, 'condomini.unita-hub');
|
|
}
|
|
|
|
return $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
|
}
|
|
|
|
protected int $stabileId = 0;
|
|
|
|
protected ?string $backUrl = null;
|
|
|
|
public string $millesimiGruppo = 'ordinarie';
|
|
|
|
public ?int $unitaId = null;
|
|
|
|
public ?array $unitaPickerData = [];
|
|
|
|
public string $unitaVisibilita = 'attive';
|
|
|
|
/**
|
|
* @var array<int, string>
|
|
*/
|
|
protected array $unitaOptionsCache = [];
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-building-office-2';
|
|
|
|
protected static ?string $navigationLabel = 'Unità immobiliare';
|
|
|
|
protected static ?string $title = 'Unità immobiliare';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'Stabile';
|
|
|
|
protected string $view = 'filament.pages.unita-immobiliare';
|
|
|
|
protected static ?string $slug = 'unita-immobiliare';
|
|
|
|
public ?UnitaImmobiliare $unita = null;
|
|
|
|
public string $tab = 'riepilogo';
|
|
|
|
public array $millesimiPerTabella = [];
|
|
|
|
public array $dirittiProprieta = [];
|
|
|
|
public array $relazioniPerTipo = [];
|
|
|
|
public bool $mostraStorico = false;
|
|
|
|
public array $canaliComunicazione = [];
|
|
|
|
public array $nominativiStorici = [];
|
|
|
|
public array $recapitiServizio = [];
|
|
|
|
public array $acquaLettureStorico = [];
|
|
|
|
public array $acquaStats = [];
|
|
|
|
public array $acquaQuickLinks = [];
|
|
|
|
protected ?object $legacyCondominRow = null;
|
|
|
|
public array $ripartizioniPerTabella = [];
|
|
|
|
public array $preventiviPerTabella = [];
|
|
|
|
public array $totaliPerGestione = [];
|
|
|
|
public array $rateEmessePerCategoria = [];
|
|
|
|
public ?int $inquilinoAnagraficaId = null;
|
|
public ?string $inquilinoDataInizio = null;
|
|
public ?string $inquilinoDataFine = null;
|
|
public float $inquilinoPercentualeSpesa = 100.000;
|
|
|
|
public array $pianoContiGerarchico = [];
|
|
public array $millesimiInputs = [];
|
|
public array $preventivoInputs = [];
|
|
public array $consuntivoInputs = [];
|
|
|
|
/** Estratto conto compatto: rate emesse */
|
|
public array $estrattoCompattoRateRows = [];
|
|
|
|
/** Estratto conto compatto: incassi */
|
|
public array $estrattoCompattoIncassi = [];
|
|
|
|
public array $estrattoRateRowsProprietario = [];
|
|
public array $estrattoRateRowsInquilino = [];
|
|
public array $estrattoIncassiProprietario = [];
|
|
public array $estrattoIncassiInquilino = [];
|
|
|
|
/** Conguagli iniziali (legacy) */
|
|
public array $estrattoConguagliIniziali = [];
|
|
|
|
/**
|
|
* Sezione estratto conto: quale categoria mostrare (condomini|inquilini)
|
|
*/
|
|
public string $estrattoTipo = 'condomini';
|
|
|
|
/**
|
|
* @var array<int, string>
|
|
*/
|
|
protected array $ownerByUnitaLocal = [];
|
|
|
|
public function getBackUrl(): ?string
|
|
{
|
|
$candidate = request()->query('back');
|
|
if (is_string($candidate) && trim($candidate) !== '') {
|
|
return $candidate;
|
|
}
|
|
|
|
$prevUrl = url()->previous();
|
|
$currentUrl = request()->fullUrl();
|
|
$isLivewire = request()->hasHeader('X-Livewire') || request()->filled('_token') || str_contains($prevUrl, '/livewire/message');
|
|
|
|
if ($prevUrl && $prevUrl !== $currentUrl && !$isLivewire) {
|
|
return $prevUrl;
|
|
}
|
|
|
|
try {
|
|
return \App\Filament\Pages\Condomini\UnitaImmobiliariArchivio::getUrl(panel: 'admin-filament');
|
|
} catch (\Throwable $e) {
|
|
return '/admin-filament/condomini';
|
|
}
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$paramUnitaId = request()->integer('unita_id');
|
|
if ($paramUnitaId > 0) {
|
|
$u = UnitaImmobiliare::withTrashed()->find($paramUnitaId);
|
|
if ($u) {
|
|
StabileContext::setActiveStabileId($user, (int)$u->stabile_id);
|
|
}
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
abort(403);
|
|
}
|
|
$this->stabileId = (int) $stabileId;
|
|
|
|
$this->unitaVisibilita = $this->normalizeUnitaVisibilita(request()->query('visibilita'));
|
|
|
|
$this->refreshUnitaOptions();
|
|
|
|
$this->unitaId = $this->pickUnitaId(request()->integer('unita_id'));
|
|
$this->backUrl = $this->getBackUrl();
|
|
|
|
$this->getSchema('unitaPicker')?->fill([
|
|
'unita_id' => $this->unitaId,
|
|
]);
|
|
|
|
$this->loadUnita();
|
|
}
|
|
|
|
public function hydrate(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return;
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if ($stabileId) {
|
|
$this->stabileId = (int) $stabileId;
|
|
}
|
|
|
|
if (empty($this->unitaOptionsCache)) {
|
|
$this->refreshUnitaOptions();
|
|
}
|
|
|
|
if (empty($this->unitaPickerData)) {
|
|
$this->getSchema('unitaPicker')?->fill([
|
|
'unita_id' => $this->unitaId,
|
|
]);
|
|
}
|
|
|
|
if ($this->unitaId) {
|
|
$this->loadUnita();
|
|
}
|
|
}
|
|
|
|
public function dehydrate(): void
|
|
{
|
|
$this->unita = null;
|
|
$this->millesimiPerTabella = [];
|
|
$this->dirittiProprieta = [];
|
|
$this->relazioniPerTipo = [];
|
|
$this->canaliComunicazione = [];
|
|
$this->nominativiStorici = [];
|
|
$this->recapitiServizio = [];
|
|
$this->acquaLettureStorico = [];
|
|
$this->acquaStats = [];
|
|
$this->acquaQuickLinks = [];
|
|
$this->ripartizioniPerTabella = [];
|
|
$this->preventiviPerTabella = [];
|
|
$this->totaliPerGestione = [];
|
|
$this->rateEmessePerCategoria = [];
|
|
$this->estrattoCompattoRateRows = [];
|
|
$this->estrattoCompattoIncassi = [];
|
|
$this->estrattoConguagliIniziali = [];
|
|
$this->legacyCondominRow = null;
|
|
}
|
|
|
|
public function unitaPicker(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->statePath('unitaPickerData')
|
|
->components([
|
|
Select::make('unita_id')
|
|
->label('Unità')
|
|
->hiddenLabel()
|
|
->placeholder('Cerca unità…')
|
|
->options(fn(): array=> $this->getUnitaOptions())
|
|
->searchable()
|
|
->preload()
|
|
->native(false)
|
|
->live()
|
|
->afterStateUpdated(function ($state): void {
|
|
$id = is_numeric($state) ? (int) $state : null;
|
|
$this->unitaId = $this->pickUnitaId($id);
|
|
if (! $this->unitaId) {
|
|
return;
|
|
}
|
|
|
|
$this->redirect($this->makeSelfUrl($this->unitaId));
|
|
}),
|
|
]);
|
|
}
|
|
|
|
public function updatedUnitaId(): void
|
|
{
|
|
$this->unitaId = $this->pickUnitaId($this->unitaId);
|
|
|
|
if (! $this->unitaId) {
|
|
$this->loadUnita();
|
|
return;
|
|
}
|
|
|
|
$this->redirect($this->makeSelfUrl($this->unitaId));
|
|
}
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
$prevId = $this->getPrevUnitaId();
|
|
$nextId = $this->getNextUnitaId();
|
|
|
|
return [
|
|
Action::make('modifica_unita')
|
|
->label('Modifica unità')
|
|
->icon('heroicon-o-pencil-square')
|
|
->modalWidth('5xl')
|
|
->visible(fn(): bool => (bool) $this->unitaId && $this->unita instanceof UnitaImmobiliare)
|
|
->fillForm(fn(): array=> $this->getEditUnitaFormData())
|
|
->form([
|
|
TextInput::make('codice_unita')
|
|
->label('Codice unità')
|
|
->maxLength(100)
|
|
->columnSpan(3),
|
|
TextInput::make('denominazione')
|
|
->label('Denominazione')
|
|
->maxLength(255)
|
|
->columnSpan(5),
|
|
TextInput::make('tipo_unita')
|
|
->label('Tipologia')
|
|
->maxLength(100)
|
|
->columnSpan(2),
|
|
TextInput::make('categoria_catastale')
|
|
->label('Categoria catastale')
|
|
->maxLength(20)
|
|
->columnSpan(2),
|
|
TextInput::make('scala')
|
|
->label('Scala')
|
|
->maxLength(50)
|
|
->columnSpan(2),
|
|
TextInput::make('piano')
|
|
->label('Piano')
|
|
->maxLength(50)
|
|
->columnSpan(2),
|
|
TextInput::make('interno')
|
|
->label('Interno')
|
|
->maxLength(50)
|
|
->columnSpan(2),
|
|
TextInput::make('stato_occupazione')
|
|
->label('Stato occupazione')
|
|
->maxLength(100)
|
|
->columnSpan(3),
|
|
TextInput::make('utilizzo_attuale')
|
|
->label('Utilizzo attuale')
|
|
->maxLength(100)
|
|
->columnSpan(3),
|
|
Toggle::make('attiva')
|
|
->label('Unità attiva')
|
|
->columnSpan(2),
|
|
TextInput::make('acqua_contatore_seriale')
|
|
->label('Seriale contatore acqua')
|
|
->maxLength(100)
|
|
->columnSpan(3),
|
|
TextInput::make('acqua_gateway_device_id')
|
|
->label('ID dispositivo gateway acqua')
|
|
->helperText('Identificativo inviato dal gateway per associare le letture a questa unità.')
|
|
->maxLength(120)
|
|
->columnSpan(3),
|
|
Textarea::make('note')
|
|
->label('Note')
|
|
->rows(4)
|
|
->columnSpanFull(),
|
|
])
|
|
->action(function (array $data): void {
|
|
$this->saveEditedUnita($data);
|
|
}),
|
|
Action::make('prev_unita')
|
|
->label('Precedente')
|
|
->icon('heroicon-o-arrow-left')
|
|
->color('gray')
|
|
->visible(fn(): bool => (bool) $this->getPrevUnitaId())
|
|
->url(fn(): string => $this->makeSelfUrl($prevId)),
|
|
Action::make('next_unita')
|
|
->label('Successiva')
|
|
->icon('heroicon-o-arrow-right')
|
|
->color('gray')
|
|
->visible(fn(): bool => (bool) $this->getNextUnitaId())
|
|
->url(fn(): string => $this->makeSelfUrl($nextId)),
|
|
Action::make('storico_ruoli')
|
|
->label('Storico ruoli')
|
|
->icon('heroicon-o-clock')
|
|
->modalWidth('7xl')
|
|
->visible(fn(): bool => (bool) $this->unitaId)
|
|
->form([
|
|
Repeater::make('ruoli')
|
|
->label('Storico proprietari / inquilini')
|
|
->defaultItems(0)
|
|
->reorderable(true)
|
|
->schema([
|
|
Hidden::make('id'),
|
|
Select::make('rubrica_id')
|
|
->label('Persona')
|
|
->required()
|
|
->searchable()
|
|
->getSearchResultsUsing(function (string $search): array {
|
|
return RubricaUniversale::query()
|
|
->ricerca($search)
|
|
->limit(30)
|
|
->get()
|
|
->mapWithKeys(fn(RubricaUniversale $r) => [
|
|
$r->id => $r->nome_completo . ($r->email ? (' · ' . $r->email) : ''),
|
|
])
|
|
->all();
|
|
})
|
|
->getOptionLabelUsing(function ($value): ?string {
|
|
if (! $value) {
|
|
return null;
|
|
}
|
|
$r = RubricaUniversale::query()->find($value);
|
|
return $r ? ($r->nome_completo . ($r->email ? (' · ' . $r->email) : '')) : null;
|
|
})
|
|
->columnSpan(5),
|
|
DatePicker::make('data_inizio')
|
|
->label('Dal')
|
|
->columnSpan(3),
|
|
DatePicker::make('data_fine')
|
|
->label('Fino al')
|
|
->columnSpan(3),
|
|
Select::make('ruolo')
|
|
->label('Ruolo')
|
|
->options($this->getRuoliLabels())
|
|
->columnSpan(3),
|
|
Toggle::make('attivo')
|
|
->label('Attivo')
|
|
->columnSpan(2),
|
|
])
|
|
->columns(9),
|
|
]),
|
|
Action::make('recapiti_email')
|
|
->label('Recapiti email')
|
|
->icon('heroicon-o-envelope')
|
|
->modalWidth('7xl')
|
|
->visible(fn(): bool => (bool) $this->unitaId)
|
|
->form([
|
|
Repeater::make('recapiti')
|
|
->label('Recapiti email per servizio')
|
|
->defaultItems(0)
|
|
->reorderable(true)
|
|
->schema([
|
|
Hidden::make('id'),
|
|
Select::make('tipo_servizio')
|
|
->label('Servizio')
|
|
->options(UnitaRecapitoServizio::serviceLabels())
|
|
->native(false)
|
|
->required()
|
|
->columnSpan(3),
|
|
Select::make('persona_id')
|
|
->label('Persona collegata')
|
|
->options(fn(): array=> $this->getPersonaRecapitoOptions())
|
|
->searchable()
|
|
->preload()
|
|
->native(false)
|
|
->columnSpan(4),
|
|
TextInput::make('email')
|
|
->label('Email da usare')
|
|
->email()
|
|
->required()
|
|
->placeholder('nome@example.it')
|
|
->helperText('Scegli la persona collegata e usa una delle email mostrate nel relativo riepilogo.')
|
|
->columnSpan(5),
|
|
TextInput::make('etichetta')
|
|
->label('Etichetta')
|
|
->maxLength(120)
|
|
->columnSpan(4),
|
|
TextInput::make('note')
|
|
->label('Note')
|
|
->maxLength(255)
|
|
->columnSpan(4),
|
|
Toggle::make('is_default')
|
|
->label('Predefinita')
|
|
->default(true)
|
|
->columnSpan(2),
|
|
Toggle::make('attivo')
|
|
->label('Attiva')
|
|
->default(true)
|
|
->columnSpan(2),
|
|
])
|
|
->columns(12),
|
|
])
|
|
->fillForm(fn(): array=> ['recapiti' => $this->getRecapitiServizioFormRows()])
|
|
->action(function (array $data): void {
|
|
$this->saveRecapitiServizioOverrides($data['recapiti'] ?? []);
|
|
$this->loadUnita();
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
->title('Recapiti email aggiornati')
|
|
->success()
|
|
->send();
|
|
}),
|
|
];
|
|
}
|
|
|
|
protected function getRuoliLabels(): array
|
|
{
|
|
return [
|
|
'condomino' => 'Condomino',
|
|
'inquilino' => 'Inquilino',
|
|
'proprietario' => 'Proprietario',
|
|
'fornitore' => 'Fornitore',
|
|
'altro' => 'Altro',
|
|
];
|
|
}
|
|
|
|
private function getPersonaRecapitoOptions(): array
|
|
{
|
|
if (! $this->unita) {
|
|
return [];
|
|
}
|
|
|
|
return $this->unita->relazioniPersoneAttive
|
|
->map(fn($relazione) => $relazione->persona)
|
|
->filter(fn($persona) => $persona instanceof Persona)
|
|
->unique(fn(Persona $persona) => (int) $persona->id)
|
|
->mapWithKeys(function (Persona $persona): array {
|
|
$emails = $this->extractPersonaEmails($persona);
|
|
$label = $persona->nome_completo;
|
|
if ($emails !== []) {
|
|
$label .= ' · ' . implode(', ', $emails);
|
|
}
|
|
|
|
return [(int) $persona->id => $label];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
private function getRecapitiServizioFormRows(): array
|
|
{
|
|
if (! $this->unita) {
|
|
return [];
|
|
}
|
|
|
|
return $this->unita->unitaRecapitiServizio
|
|
->sortBy(['tipo_servizio', 'ordine', 'id'])
|
|
->values()
|
|
->map(function (UnitaRecapitoServizio $row): array {
|
|
return [
|
|
'id' => (int) $row->id,
|
|
'tipo_servizio' => (string) $row->tipo_servizio,
|
|
'persona_id' => $row->persona_id ? (int) $row->persona_id : null,
|
|
'email' => (string) $row->email,
|
|
'etichetta' => (string) ($row->etichetta ?? ''),
|
|
'note' => (string) ($row->note ?? ''),
|
|
'is_default' => (bool) $row->is_default,
|
|
'attivo' => (bool) $row->attivo,
|
|
];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
private function saveRecapitiServizioOverrides(array $rows): void
|
|
{
|
|
if (! $this->unita) {
|
|
return;
|
|
}
|
|
|
|
$labels = UnitaRecapitoServizio::serviceLabels();
|
|
|
|
DB::transaction(function () use ($rows, $labels): void {
|
|
UnitaRecapitoServizio::query()
|
|
->where('unita_id', (int) $this->unita->id)
|
|
->delete();
|
|
|
|
$ordine = 1;
|
|
foreach ($rows as $row) {
|
|
$tipoServizio = trim((string) ($row['tipo_servizio'] ?? ''));
|
|
$email = mb_strtolower(trim((string) ($row['email'] ?? '')));
|
|
|
|
if ($tipoServizio === '' || $email === '' || ! array_key_exists($tipoServizio, $labels)) {
|
|
continue;
|
|
}
|
|
|
|
UnitaRecapitoServizio::create([
|
|
'unita_id' => (int) $this->unita->id,
|
|
'persona_id' => isset($row['persona_id']) && is_numeric($row['persona_id']) ? (int) $row['persona_id'] : null,
|
|
'tipo_servizio' => $tipoServizio,
|
|
'email' => $email,
|
|
'etichetta' => $this->normalizeOptionalString($row['etichetta'] ?? null) ?: ($labels[$tipoServizio] ?? null),
|
|
'note' => $this->normalizeOptionalString($row['note'] ?? null),
|
|
'ordine' => $ordine++,
|
|
'is_default' => (bool) ($row['is_default'] ?? true),
|
|
'attivo' => (bool) ($row['attivo'] ?? true),
|
|
'sorgente' => 'manuale',
|
|
'last_confirmed_at' => now(),
|
|
]);
|
|
}
|
|
});
|
|
}
|
|
|
|
protected function makeSelfUrl(?int $unitaId = null): string
|
|
{
|
|
$base = static::getUrl(panel: 'admin-filament');
|
|
|
|
$query = [];
|
|
if ($unitaId) {
|
|
$query['unita_id'] = (int) $unitaId;
|
|
}
|
|
if (is_string($this->backUrl) && trim($this->backUrl) !== '') {
|
|
$query['back'] = $this->backUrl;
|
|
}
|
|
|
|
return $base . (empty($query) ? '' : ('?' . http_build_query($query)));
|
|
}
|
|
|
|
protected function getPrevUnitaId(): ?int
|
|
{
|
|
if (! $this->unitaId || empty($this->unitaOptionsCache)) {
|
|
return null;
|
|
}
|
|
|
|
$ids = array_map('intval', array_keys($this->unitaOptionsCache));
|
|
$index = array_search((int) $this->unitaId, $ids, true);
|
|
if ($index === false || $index <= 0) {
|
|
return null;
|
|
}
|
|
|
|
return (int) $ids[$index - 1];
|
|
}
|
|
|
|
protected function getNextUnitaId(): ?int
|
|
{
|
|
if (! $this->unitaId || empty($this->unitaOptionsCache)) {
|
|
return null;
|
|
}
|
|
|
|
$ids = array_map('intval', array_keys($this->unitaOptionsCache));
|
|
$index = array_search((int) $this->unitaId, $ids, true);
|
|
if ($index === false) {
|
|
return null;
|
|
}
|
|
|
|
$nextIndex = $index + 1;
|
|
if (! isset($ids[$nextIndex])) {
|
|
return null;
|
|
}
|
|
|
|
return (int) $ids[$nextIndex];
|
|
}
|
|
|
|
public function getPrevUnitaUrl(): ?string
|
|
{
|
|
$id = $this->getPrevUnitaId();
|
|
return $id ? $this->makeSelfUrl($id) : null;
|
|
}
|
|
|
|
public function getNextUnitaUrl(): ?string
|
|
{
|
|
$id = $this->getNextUnitaId();
|
|
return $id ? $this->makeSelfUrl($id) : null;
|
|
}
|
|
|
|
public function setTab(string $tab): void
|
|
{
|
|
$this->tab = $tab;
|
|
}
|
|
|
|
public function setUnitaVisibilita(string $visibilita): void
|
|
{
|
|
$this->unitaVisibilita = $this->normalizeUnitaVisibilita($visibilita);
|
|
$this->refreshUnitaOptions();
|
|
$this->unitaId = $this->pickUnitaId($this->unitaId);
|
|
|
|
$this->getSchema('unitaPicker')?->fill([
|
|
'unita_id' => $this->unitaId,
|
|
]);
|
|
|
|
if ($this->unitaId) {
|
|
$this->redirect($this->makeSelfUrl($this->unitaId));
|
|
return;
|
|
}
|
|
|
|
$this->loadUnita();
|
|
}
|
|
|
|
protected function refreshUnitaOptions(): void
|
|
{
|
|
$unita = $this->buildUnitaIndexQuery()
|
|
->orderBy('palazzina_id')
|
|
->orderBy('scala')
|
|
->orderBy('piano')
|
|
->orderBy('id')
|
|
->get(['id', 'palazzina_id', 'codice_unita', 'denominazione', 'scala', 'piano', 'interno', 'deleted_at']);
|
|
|
|
$unita = $unita->filter(function (UnitaImmobiliare $u): bool {
|
|
$denominazione = trim((string) ($u->denominazione ?? ''));
|
|
$scala = trim((string) ($u->scala ?? ''));
|
|
$interno = trim((string) ($u->interno ?? ''));
|
|
$piano = $u->piano;
|
|
$codice = trim((string) ($u->codice_unita ?? ''));
|
|
|
|
$isEmptyCore = $denominazione === ''
|
|
&& $scala === ''
|
|
&& $interno === ''
|
|
&& $piano === null;
|
|
|
|
if ($isEmptyCore && empty($u->palazzina_id)) {
|
|
return false;
|
|
}
|
|
|
|
if ($codice === '0019-A-3-C5') {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
})->values();
|
|
|
|
$ownerByUnita = [];
|
|
if (DbSchema::hasTable('proprieta')) {
|
|
try {
|
|
$ownerByUnita = Proprieta::query()
|
|
->select('unita_immobiliare_id', 'anagrafica_id')
|
|
->whereIn('unita_immobiliare_id', $unita->pluck('id'))
|
|
->get()
|
|
->groupBy('unita_immobiliare_id')
|
|
->map(function ($rows) {
|
|
$ids = $rows->pluck('anagrafica_id')
|
|
->filter(fn($v) => is_numeric($v) && (int) $v > 0)
|
|
->map(fn($v) => (int) $v)
|
|
->values()
|
|
->all();
|
|
|
|
if (empty($ids)) {
|
|
return null;
|
|
}
|
|
|
|
$nome = DB::table('anagrafiche')
|
|
->whereIn('id', $ids)
|
|
->select('id', 'ragione_sociale', 'nome', 'cognome')
|
|
->get()
|
|
->map(function ($s) {
|
|
$nome = trim((string) ($s->ragione_sociale ?? ''));
|
|
if ($nome === '') {
|
|
$nome = trim((string) ($s->nome ?? '') . ' ' . (string) ($s->cognome ?? ''));
|
|
}
|
|
return $nome !== '' ? $nome : null;
|
|
})
|
|
->filter()
|
|
->values()
|
|
->first();
|
|
|
|
return $nome ?: null;
|
|
})
|
|
->filter()
|
|
->all();
|
|
} catch (\Throwable $e) {
|
|
$ownerByUnita = [];
|
|
}
|
|
}
|
|
|
|
$currentNominativoByUnita = [];
|
|
if (DbSchema::hasTable('unita_immobiliare_nominativi')) {
|
|
try {
|
|
$today = now()->toDateString();
|
|
$rows = DB::table('unita_immobiliare_nominativi')
|
|
->whereIn('unita_immobiliare_id', $unita->pluck('id'))
|
|
->where(function ($query) use ($today): void {
|
|
$query->whereNull('data_inizio')->orWhere('data_inizio', '<=', $today);
|
|
})
|
|
->where(function ($query) use ($today): void {
|
|
$query->whereNull('data_fine')->orWhere('data_fine', '>=', $today);
|
|
})
|
|
->orderByRaw("CASE WHEN ruolo = 'C' THEN 0 ELSE 1 END")
|
|
->orderByDesc('data_inizio')
|
|
->orderByDesc('id')
|
|
->get(['unita_immobiliare_id', 'nominativo']);
|
|
|
|
foreach ($rows as $row) {
|
|
$unitId = (int) ($row->unita_immobiliare_id ?? 0);
|
|
if ($unitId <= 0 || isset($currentNominativoByUnita[$unitId])) {
|
|
continue;
|
|
}
|
|
|
|
$name = trim((string) ($row->nominativo ?? ''));
|
|
if ($name !== '') {
|
|
$currentNominativoByUnita[$unitId] = $name;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$currentNominativoByUnita = [];
|
|
}
|
|
}
|
|
|
|
if (DbSchema::hasTable('persone_unita_relazioni') && DbSchema::hasTable('persone')) {
|
|
try {
|
|
$today = now()->toDateString();
|
|
$rows = DB::table('persone_unita_relazioni as pur')
|
|
->join('persone as p', 'pur.persona_id', '=', 'p.id')
|
|
->whereIn('pur.unita_id', $unita->pluck('id'))
|
|
->where(function ($query) use ($today): void {
|
|
$query->whereNull('pur.data_inizio')->orWhere('pur.data_inizio', '<=', $today);
|
|
})
|
|
->where(function ($query) use ($today): void {
|
|
$query->whereNull('pur.data_fine')->orWhere('pur.data_fine', '>=', $today);
|
|
})
|
|
->where('pur.attivo', 1)
|
|
->orderByRaw("CASE WHEN pur.tipo_relazione = 'proprietario' THEN 0 ELSE 1 END")
|
|
->orderByDesc('pur.data_inizio')
|
|
->select('pur.unita_id', 'p.cognome', 'p.nome', 'p.ragione_sociale')
|
|
->get();
|
|
|
|
foreach ($rows as $row) {
|
|
$unitId = (int) ($row->unita_id ?? 0);
|
|
if ($unitId <= 0 || isset($currentNominativoByUnita[$unitId])) {
|
|
continue;
|
|
}
|
|
|
|
$name = trim((string) ($row->ragione_sociale ?? ''));
|
|
if ($name === '') {
|
|
$name = trim((string) ($row->cognome ?? '') . ' ' . (string) ($row->nome ?? ''));
|
|
}
|
|
if ($name !== '') {
|
|
$currentNominativoByUnita[$unitId] = $name;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Ignore fallback error
|
|
}
|
|
}
|
|
|
|
if (DbSchema::hasTable('rubrica_ruoli') && DbSchema::hasTable('rubrica_universale')) {
|
|
try {
|
|
$today = now()->toDateString();
|
|
$rows = DB::table('rubrica_ruoli as rr')
|
|
->join('rubrica_universale as ru', 'rr.rubrica_id', '=', 'ru.id')
|
|
->whereIn('rr.unita_id', $unita->pluck('id'))
|
|
->where(function ($query) use ($today): void {
|
|
$query->whereNull('rr.data_inizio')->orWhere('rr.data_inizio', '<=', $today);
|
|
})
|
|
->where(function ($query) use ($today): void {
|
|
$query->whereNull('rr.data_fine')->orWhere('rr.data_fine', '>=', $today);
|
|
})
|
|
->where('rr.attivo', 1)
|
|
->orderByRaw("CASE WHEN rr.ruolo = 'proprietario' THEN 0 ELSE 1 END")
|
|
->orderByDesc('rr.data_inizio')
|
|
->select('rr.unita_id', 'ru.cognome', 'ru.nome', 'ru.ragione_sociale')
|
|
->get();
|
|
|
|
foreach ($rows as $row) {
|
|
$unitId = (int) ($row->unita_id ?? 0);
|
|
if ($unitId <= 0 || isset($currentNominativoByUnita[$unitId])) {
|
|
continue;
|
|
}
|
|
|
|
$name = trim((string) ($row->ragione_sociale ?? ''));
|
|
if ($name === '') {
|
|
$name = trim((string) ($row->cognome ?? '') . ' ' . (string) ($row->nome ?? ''));
|
|
}
|
|
if ($name !== '') {
|
|
$currentNominativoByUnita[$unitId] = $name;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Ignore fallback error
|
|
}
|
|
}
|
|
|
|
$sorted = $unita->sort(function (UnitaImmobiliare $a, UnitaImmobiliare $b): int {
|
|
$pa = (int) ($a->palazzina_id ?? 0);
|
|
$pb = (int) ($b->palazzina_id ?? 0);
|
|
if ($pa !== $pb) {
|
|
return $pa <=> $pb;
|
|
}
|
|
|
|
$sa = strtoupper(trim((string) ($a->scala ?? '')));
|
|
$sb = strtoupper(trim((string) ($b->scala ?? '')));
|
|
if ($sa !== $sb) {
|
|
return strcmp($sa, $sb);
|
|
}
|
|
|
|
$pia = is_null($a->piano) ? 0 : (int) $a->piano;
|
|
$pib = is_null($b->piano) ? 0 : (int) $b->piano;
|
|
if ($pia !== $pib) {
|
|
return $pia <=> $pib;
|
|
}
|
|
|
|
$ka = $this->internoSortKey($a->interno !== null ? (string) $a->interno : null);
|
|
$kb = $this->internoSortKey($b->interno !== null ? (string) $b->interno : null);
|
|
|
|
if ($ka[0] !== $kb[0]) {
|
|
return strcmp($ka[0], $kb[0]);
|
|
}
|
|
if ($ka[1] !== $kb[1]) {
|
|
return $ka[1] <=> $kb[1];
|
|
}
|
|
if ($ka[2] !== $kb[2]) {
|
|
return strnatcasecmp($ka[2], $kb[2]);
|
|
}
|
|
return ((int) $a->id) <=> ((int) $b->id);
|
|
});
|
|
|
|
$this->ownerByUnitaLocal = $ownerByUnita;
|
|
|
|
$this->unitaOptionsCache = $sorted
|
|
->mapWithKeys(function (UnitaImmobiliare $u) use ($currentNominativoByUnita) {
|
|
$codice = $u->codice_unita ?: ('ID ' . $u->id);
|
|
$stabileCode = $this->unita?->stabile?->codice_stabile ?? '0021';
|
|
if ($stabileCode && str_starts_with($codice, $stabileCode . '-')) {
|
|
$codice = substr($codice, strlen($stabileCode . '-'));
|
|
}
|
|
|
|
$details = [];
|
|
if ($u->scala) {
|
|
$details[] = 'Scala ' . $u->scala;
|
|
}
|
|
if ($u->interno) {
|
|
$details[] = 'Int. ' . $u->interno;
|
|
}
|
|
if ($u->tipo_unita) {
|
|
$details[] = '(' . ucfirst(strtolower($u->tipo_unita)) . ')';
|
|
}
|
|
|
|
$detStr = implode(' - ', array_filter($details));
|
|
$label = $detStr !== '' ? ($detStr . ' [' . $codice . ']') : trim($codice);
|
|
|
|
$owner = $currentNominativoByUnita[(int) $u->id] ?? ($this->ownerByUnitaLocal[(int) $u->id] ?? null);
|
|
if ($this->unita && (int) $u->id === (int) $this->unita->id) {
|
|
$legacyOwner = $this->getLegacyCondominoRiferimentoNome();
|
|
if (is_string($legacyOwner) && trim($legacyOwner) !== '') {
|
|
$owner = $legacyOwner;
|
|
}
|
|
}
|
|
if (is_string($owner) && trim($owner) !== '') {
|
|
$label .= ' — ' . trim($owner);
|
|
}
|
|
if ($u->trashed()) {
|
|
$label .= ' [archiviata]';
|
|
}
|
|
return [(int) $u->id => $label];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
public function getUnitaOptions(): array
|
|
{
|
|
if (empty($this->unitaOptionsCache)) {
|
|
$this->refreshUnitaOptions();
|
|
}
|
|
|
|
return $this->unitaOptionsCache;
|
|
}
|
|
|
|
public function setEstrattoTipo(string $tipo): void
|
|
{
|
|
$tipo = strtolower(trim($tipo));
|
|
if (! in_array($tipo, ['condomini', 'inquilini'], true)) {
|
|
return;
|
|
}
|
|
|
|
$this->estrattoTipo = $tipo;
|
|
}
|
|
|
|
/**
|
|
* @return array{0:string,1:int,2:string}
|
|
*/
|
|
protected function internoSortKey(?string $interno): array
|
|
{
|
|
$s = trim((string) ($interno ?? ''));
|
|
if ($s === '' || $s === '—') {
|
|
return ['~', PHP_INT_MAX, ''];
|
|
}
|
|
$u = strtoupper($s);
|
|
|
|
$m = null;
|
|
if (preg_match('/^([A-Z]+)\s*[-\/]\s*(\d+)(.*)$/', $u, $m)) {
|
|
return [$m[1], (int) $m[2], trim((string) ($m[3] ?? ''))];
|
|
}
|
|
if (preg_match('/^(\d+)(.*)$/', $u, $m)) {
|
|
return ['', (int) $m[1], trim((string) ($m[2] ?? ''))];
|
|
}
|
|
|
|
return [$u, PHP_INT_MAX, $u];
|
|
}
|
|
|
|
protected function pickUnitaId(?int $candidate): ?int
|
|
{
|
|
if (empty($this->unitaOptionsCache)) {
|
|
$this->refreshUnitaOptions();
|
|
}
|
|
|
|
if ($candidate && array_key_exists($candidate, $this->unitaOptionsCache)) {
|
|
return $candidate;
|
|
}
|
|
|
|
$first = array_key_first($this->unitaOptionsCache);
|
|
return is_int($first) ? $first : null;
|
|
}
|
|
|
|
protected function loadUnita(): void
|
|
{
|
|
$this->unita = null;
|
|
$this->millesimiPerTabella = [];
|
|
$this->dirittiProprieta = [];
|
|
$this->relazioniPerTipo = [];
|
|
$this->nominativiStorici = [];
|
|
$this->recapitiServizio = [];
|
|
$this->acquaLettureStorico = [];
|
|
$this->acquaStats = [];
|
|
$this->acquaQuickLinks = [];
|
|
$this->ripartizioniPerTabella = [];
|
|
$this->preventiviPerTabella = [];
|
|
$this->totaliPerGestione = [];
|
|
$this->rateEmessePerCategoria = [];
|
|
|
|
if (! $this->unitaId) {
|
|
$this->tab = 'riepilogo';
|
|
return;
|
|
}
|
|
|
|
$this->unita = UnitaImmobiliare::withTrashed()
|
|
->with([
|
|
'palazzinaObj',
|
|
'soggetti',
|
|
'stabile',
|
|
'dettagliMillesimi.tabellaMillesimale',
|
|
'relazioniPersoneAttive.persona.emailMultiple',
|
|
'unitaRecapitiServizio.persona',
|
|
])
|
|
->where('stabile_id', $this->stabileId)
|
|
->whereKey($this->unitaId)
|
|
->first();
|
|
|
|
if ($this->unita) {
|
|
$this->hydrateMillesimi();
|
|
$this->hydrateDiritti();
|
|
$this->hydrateRelazioni();
|
|
$this->popolaCanaliComunicazione();
|
|
$this->hydrateNominativiStorici();
|
|
$this->hydrateRecapitiServizio();
|
|
$this->hydrateRateEmesse();
|
|
$this->hydrateEstrattoCompatto();
|
|
$this->hydrateConguagliIniziali();
|
|
$this->hydrateAcquaStorico();
|
|
$this->hydrateRipartizioni();
|
|
$this->hydratePreventivi();
|
|
$this->hydrateInquilinoAttivo();
|
|
$this->hydratePianoContiGerarchico();
|
|
}
|
|
|
|
$this->tab = 'riepilogo';
|
|
}
|
|
|
|
protected function hydrateAcquaStorico(): void
|
|
{
|
|
$this->acquaLettureStorico = [];
|
|
$this->acquaStats = [
|
|
'totale_letture' => 0,
|
|
'totale_consumi_mc' => 0.0,
|
|
'ultima_lettura' => null,
|
|
'ultima_data' => null,
|
|
'richieste_aperte' => 0,
|
|
'servizio_label' => null,
|
|
'matricola' => null,
|
|
'utenza' => null,
|
|
'contratto' => null,
|
|
];
|
|
$this->acquaQuickLinks = [];
|
|
|
|
if (! $this->unita || ! DbSchema::hasTable('stabile_servizi') || ! DbSchema::hasTable('stabile_servizio_letture')) {
|
|
return;
|
|
}
|
|
|
|
$servizio = StabileServizio::query()
|
|
->where('stabile_id', $this->stabileId)
|
|
->where('tipo', 'acqua')
|
|
->orderByDesc('attivo')
|
|
->orderBy('id')
|
|
->first();
|
|
|
|
$rows = StabileServizioLettura::query()
|
|
->with(['servizio:id,nome,contatore_matricola', 'fatturaElettronica:id,numero_fattura,data_fattura'])
|
|
->where('stabile_id', $this->stabileId)
|
|
->where('unita_immobiliare_id', (int) $this->unita->id)
|
|
->whereHas('servizio', fn($query) => $query->where('tipo', 'acqua'))
|
|
->orderByDesc('periodo_al')
|
|
->orderByDesc('created_at')
|
|
->orderByDesc('id')
|
|
->limit(24)
|
|
->get();
|
|
|
|
if ($servizio) {
|
|
$this->acquaQuickLinks = [
|
|
'ticket_light' => TicketAcquaLight::getUrl(panel: 'admin-filament', parameters: [
|
|
'stabile' => $this->stabileId,
|
|
'servizio' => (int) $servizio->id,
|
|
'unita' => (int) $this->unita->id,
|
|
]),
|
|
'archivio' => LettureServiziArchivio::getUrl(panel: 'admin-filament', parameters: [
|
|
'servizio' => (int) $servizio->id,
|
|
]),
|
|
'hub_stabile' => ServiziStabileArchivio::getUrl(panel: 'admin-filament', parameters: [
|
|
'tab' => 'letture',
|
|
]),
|
|
];
|
|
}
|
|
|
|
$this->acquaLettureStorico = $rows->map(function (StabileServizioLettura $row): array {
|
|
$data = $row->periodo_al?->format('d/m/Y') ?: optional($row->created_at)->format('d/m/Y H:i');
|
|
$fattura = $row->fatturaElettronica;
|
|
|
|
return [
|
|
'id' => (int) $row->id,
|
|
'data' => $data,
|
|
'servizio' => trim((string) ($row->servizio?->nome ?? 'Acqua')),
|
|
'periodo' => $this->formatStoricoPeriodo($row->periodo_dal?->format('d/m/Y'), $row->periodo_al?->format('d/m/Y')),
|
|
'lettura_inizio' => is_numeric($row->lettura_inizio) ? (float) $row->lettura_inizio : null,
|
|
'lettura_fine' => is_numeric($row->lettura_fine) ? (float) $row->lettura_fine : null,
|
|
'consumo_valore' => is_numeric($row->consumo_valore) ? (float) $row->consumo_valore : null,
|
|
'consumo_unita' => trim((string) ($row->consumo_unita ?? 'mc')) ?: 'mc',
|
|
'canale' => trim((string) ($row->canale_acquisizione ?? '—')),
|
|
'workflow' => trim((string) ($row->workflow_stato ?? '—')),
|
|
'riferimento' => trim((string) ($row->riferimento_acquisizione ?? '')),
|
|
'protocollo' => trim((string) ($row->protocollo_numero ?? '')),
|
|
'fattura_label' => $fattura ? trim((string) ($fattura->numero_fattura ?? ('FE #' . (int) $fattura->id))) : null,
|
|
];
|
|
})->all();
|
|
|
|
$latestCompleted = collect($this->acquaLettureStorico)->first(fn(array $row): bool => $row['lettura_fine'] !== null);
|
|
$openRequests = collect($rows)->filter(fn(StabileServizioLettura $row): bool => $row->lettura_fine === null && in_array((string) ($row->workflow_stato ?? ''), ['richiesta_inviata', 'sollecito_inviato'], true))->count();
|
|
|
|
$this->acquaStats = [
|
|
'totale_letture' => collect($rows)->filter(fn(StabileServizioLettura $row): bool => $row->lettura_fine !== null)->count(),
|
|
'totale_consumi_mc' => round((float) collect($rows)->sum(fn(StabileServizioLettura $row): float => is_numeric($row->consumo_valore) ? (float) $row->consumo_valore : 0.0), 3),
|
|
'ultima_lettura' => $latestCompleted['lettura_fine'] ?? null,
|
|
'ultima_data' => $latestCompleted['data'] ?? null,
|
|
'richieste_aperte' => $openRequests,
|
|
'servizio_label' => $servizio?->nome,
|
|
'matricola' => $servizio?->contatore_matricola,
|
|
'utenza' => $servizio?->codice_utenza,
|
|
'contratto' => $servizio?->codice_contratto,
|
|
];
|
|
}
|
|
|
|
private function formatStoricoPeriodo(?string $from, ?string $to): string
|
|
{
|
|
if ($from && $to) {
|
|
return $from . ' - ' . $to;
|
|
}
|
|
|
|
if ($from) {
|
|
return 'Dal ' . $from;
|
|
}
|
|
|
|
if ($to) {
|
|
return 'Fino al ' . $to;
|
|
}
|
|
|
|
return '—';
|
|
}
|
|
|
|
protected function buildUnitaIndexQuery()
|
|
{
|
|
$query = UnitaImmobiliare::query()
|
|
->where('stabile_id', $this->stabileId);
|
|
|
|
return match ($this->unitaVisibilita) {
|
|
'tutte' => $query->withTrashed(),
|
|
'archiviate' => $query->onlyTrashed(),
|
|
default => $query,
|
|
};
|
|
}
|
|
|
|
protected function normalizeUnitaVisibilita(mixed $value): string
|
|
{
|
|
$candidate = strtolower(trim((string) $value));
|
|
|
|
return in_array($candidate, ['attive', 'tutte', 'archiviate'], true)
|
|
? $candidate
|
|
: 'attive';
|
|
}
|
|
|
|
protected function getEditUnitaFormData(): array
|
|
{
|
|
if (! $this->unita) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
'codice_unita' => (string) ($this->unita->codice_unita ?? ''),
|
|
'denominazione' => (string) ($this->unita->denominazione ?? ''),
|
|
'tipo_unita' => (string) ($this->unita->tipo_unita ?? ''),
|
|
'categoria_catastale' => (string) ($this->unita->categoria_catastale ?? ''),
|
|
'scala' => (string) ($this->unita->scala ?? ''),
|
|
'piano' => (string) ($this->unita->piano ?? ''),
|
|
'interno' => (string) ($this->unita->interno ?? ''),
|
|
'stato_occupazione' => (string) ($this->unita->stato_occupazione ?? ''),
|
|
'utilizzo_attuale' => (string) ($this->unita->utilizzo_attuale ?? ''),
|
|
'attiva' => (bool) ($this->unita->attiva ?? true),
|
|
'acqua_contatore_seriale' => (string) ($this->unita->acqua_contatore_seriale ?? ''),
|
|
'acqua_gateway_device_id' => (string) ($this->unita->acqua_gateway_device_id ?? ''),
|
|
'note' => (string) ($this->unita->note ?? ''),
|
|
];
|
|
}
|
|
|
|
protected function saveEditedUnita(array $data): void
|
|
{
|
|
if (! $this->unita) {
|
|
return;
|
|
}
|
|
|
|
$this->unita->fill([
|
|
'codice_unita' => $this->normalizeOptionalString($data['codice_unita'] ?? null),
|
|
'denominazione' => $this->normalizeOptionalString($data['denominazione'] ?? null),
|
|
'tipo_unita' => $this->normalizeOptionalString($data['tipo_unita'] ?? null),
|
|
'categoria_catastale' => $this->normalizeOptionalString($data['categoria_catastale'] ?? null),
|
|
'scala' => $this->normalizeOptionalString($data['scala'] ?? null),
|
|
'piano' => $this->normalizeOptionalString($data['piano'] ?? null),
|
|
'interno' => $this->normalizeOptionalString($data['interno'] ?? null),
|
|
'stato_occupazione' => $this->normalizeOptionalString($data['stato_occupazione'] ?? null),
|
|
'utilizzo_attuale' => $this->normalizeOptionalString($data['utilizzo_attuale'] ?? null),
|
|
'attiva' => (bool) ($data['attiva'] ?? false),
|
|
'acqua_contatore_seriale' => $this->normalizeOptionalString($data['acqua_contatore_seriale'] ?? null),
|
|
'acqua_gateway_device_id' => $this->normalizeOptionalString($data['acqua_gateway_device_id'] ?? null),
|
|
'note' => $this->normalizeOptionalString($data['note'] ?? null),
|
|
]);
|
|
$this->unita->updated_by = Auth::id();
|
|
$this->unita->save();
|
|
|
|
$this->refreshUnitaOptions();
|
|
$this->loadUnita();
|
|
|
|
Notification::make()
|
|
->title('Unità aggiornata')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function hydrateNominativiStorici(): void
|
|
{
|
|
$this->nominativiStorici = [];
|
|
|
|
if (! $this->unita) {
|
|
return;
|
|
}
|
|
|
|
if (! DbSchema::hasTable('unita_immobiliare_nominativi')) {
|
|
return;
|
|
}
|
|
|
|
$rows = $this->unita->nominativiStorici()
|
|
->orderBy('data_inizio')
|
|
->orderByDesc('ruolo')
|
|
->orderByDesc('id')
|
|
->get();
|
|
|
|
if ($rows->isEmpty()) {
|
|
$this->populateNominativiStoriciFallback();
|
|
return;
|
|
}
|
|
|
|
$hasComproprietari = $rows->contains(fn($r) => $r->fonte === 'legacy_comproprietari');
|
|
|
|
$filteredRows = $rows->filter(function ($row) use ($hasComproprietari) {
|
|
if ($row->percentuale !== null && is_numeric($row->percentuale) && (float)$row->percentuale <= 0.0) {
|
|
return false;
|
|
}
|
|
if ($hasComproprietari && $row->fonte === 'legacy_condomin' && $row->ruolo === 'C') {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
$groupedNominativi = [];
|
|
|
|
foreach ($filteredRows as $row) {
|
|
$nomUpper = mb_strtoupper(trim((string) $row->nominativo));
|
|
$ruoloKey = (string) $row->ruolo;
|
|
$key = $ruoloKey . ':' . $nomUpper;
|
|
|
|
$payload = is_array($row->legacy_payload) ? $row->legacy_payload : [];
|
|
$gYear = $payload['legacy_year'] ?? $payload['source_year'] ?? null;
|
|
|
|
if (! isset($groupedNominativi[$key])) {
|
|
$groupedNominativi[$key] = [
|
|
'row' => $row,
|
|
'nominativo' => $row->nominativo,
|
|
'ruolo' => $row->ruolo === 'C' ? ($row->fonte === 'legacy_comproprietari' ? 'Comproprietario' : 'Proprietario') : ($row->ruolo === 'I' ? 'Inquilino' : $row->ruolo),
|
|
'percentuale' => is_numeric($row->percentuale) ? number_format((float) $row->percentuale, 2, ',', '.') . '%' : null,
|
|
'fonte' => $row->fonte === 'legacy_comproprietari' ? 'Legacy comproprietari' : 'Archivio storico legacy',
|
|
'gestioni' => $gYear ? [(string) $gYear] : [],
|
|
'payload' => $payload,
|
|
];
|
|
} else {
|
|
if ($gYear && ! in_array((string) $gYear, $groupedNominativi[$key]['gestioni'], true)) {
|
|
$groupedNominativi[$key]['gestioni'][] = (string) $gYear;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->nominativiStorici = [];
|
|
foreach ($groupedNominativi as $item) {
|
|
$gList = $item['gestioni'];
|
|
$periodo = count($gList) > 1
|
|
? 'Gestioni ' . implode(', ', $gList) . ' (Titolare storico)'
|
|
: (count($gList) === 1 ? 'Gestione ' . $gList[0] : 'In carica');
|
|
|
|
$details = [];
|
|
$payload = $item['payload'];
|
|
$dirittoLabel = trim((string) ($payload['diritto_label'] ?? ''));
|
|
if ($dirittoLabel !== '') {
|
|
$details[] = 'Diritto: ' . $dirittoLabel;
|
|
}
|
|
|
|
$cf = trim((string) ($payload['codice_fiscale'] ?? $payload['cond_cod_fisc'] ?? $payload['inquilin_cod_fisc'] ?? $payload['inquil_cod_fisc'] ?? ''));
|
|
if ($cf !== '') {
|
|
$details[] = 'CF: ' . $cf;
|
|
}
|
|
|
|
$this->nominativiStorici[] = [
|
|
'periodo' => $periodo,
|
|
'nominativo' => $item['nominativo'],
|
|
'ruolo' => $item['ruolo'],
|
|
'percentuale' => $item['percentuale'],
|
|
'fonte' => $item['fonte'],
|
|
'detail' => count($details) > 0 ? implode(' · ', $details) : null,
|
|
];
|
|
}
|
|
|
|
if (empty($this->nominativiStorici)) {
|
|
$this->populateNominativiStoriciFallback();
|
|
}
|
|
}
|
|
|
|
private function populateNominativiStoriciFallback(): void
|
|
{
|
|
$fallbackList = [];
|
|
|
|
if (! $this->unita) {
|
|
return;
|
|
}
|
|
|
|
if ($this->hasImportConnection() && DbSchema::connection('gescon_import')->hasTable('condomin_mirror')) {
|
|
$codStabile = trim((string) ($this->unita->stabile?->codice_stabile ?? ''));
|
|
$scala = trim((string) ($this->unita->scala ?? ''));
|
|
$interno = trim((string) ($this->unita->interno ?? ''));
|
|
|
|
if ($codStabile !== '' && $interno !== '') {
|
|
$q = DB::connection('gescon_import')
|
|
->table('condomin_mirror')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('interno', $interno);
|
|
if ($scala !== '') {
|
|
$q->where('scala', $scala);
|
|
}
|
|
|
|
$mirrorRows = $q->orderBy('source_year')->get();
|
|
$grouped = [];
|
|
|
|
foreach ($mirrorRows as $m) {
|
|
$gCode = (string) $m->source_year;
|
|
$yLabel = 'Gestione ' . $gCode;
|
|
|
|
$owner = trim((string) ($m->nom_cond ?? $m->proprietario ?? ''));
|
|
if ($owner !== '') {
|
|
$key = 'proprietario:' . mb_strtoupper($owner) . ':' . trim((string) $m->codice_fiscale);
|
|
if (! isset($grouped[$key])) {
|
|
$grouped[$key] = [
|
|
'persona_id' => null,
|
|
'nome' => $owner,
|
|
'codice_fiscale'=> ! empty($m->codice_fiscale) ? $m->codice_fiscale : null,
|
|
'tipo_relazione'=> 'proprietario',
|
|
'gestioni' => [$gCode],
|
|
'quota' => ($m->source_table === 'comproprietari' || (float)($m->perc_diritto_reale ?? 0) > 0) ? (float)($m->perc_diritto_reale ?? 50) : 100.0,
|
|
'quota_label' => ($m->source_table === 'comproprietari' || (float)($m->perc_diritto_reale ?? 0) > 0) ? number_format((float)($m->perc_diritto_reale ?? 50), 2, ',', '.') : '100,00',
|
|
'attivo' => false,
|
|
'fonte' => $m->source_table === 'comproprietari' ? 'Legacy comproprietari' : 'Archivio storico legacy',
|
|
];
|
|
} else {
|
|
if (! in_array($gCode, $grouped[$key]['gestioni'])) {
|
|
$grouped[$key]['gestioni'][] = $gCode;
|
|
}
|
|
}
|
|
}
|
|
|
|
$inquil = trim((string) ($m->inquilino ?? $m->inquil_nome ?? ''));
|
|
if ($inquil !== '') {
|
|
$inqCf = trim((string) ($m->inquil_cod_fisc ?? $m->codice_fiscale ?? ''));
|
|
$key = 'inquilino:' . mb_strtoupper($inquil) . ':' . $inqCf;
|
|
if (! isset($grouped[$key])) {
|
|
$grouped[$key] = [
|
|
'persona_id' => null,
|
|
'nome' => $inquil,
|
|
'codice_fiscale'=> $inqCf !== '' ? $inqCf : null,
|
|
'tipo_relazione'=> 'inquilino',
|
|
'gestioni' => [$gCode],
|
|
'quota' => 100.0,
|
|
'quota_label' => '100,00',
|
|
'attivo' => false,
|
|
'fonte' => 'Archivio storico legacy',
|
|
];
|
|
} else {
|
|
if (! in_array($gCode, $grouped[$key]['gestioni'])) {
|
|
$grouped[$key]['gestioni'][] = $gCode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($grouped as $item) {
|
|
$gList = $item['gestioni'];
|
|
$gSpan = count($gList) > 1
|
|
? 'Gestioni ' . implode(', ', $gList) . ' (Titolare storico)'
|
|
: 'Gestione ' . $gList[0];
|
|
|
|
$fallbackList[] = [
|
|
'persona_id' => $item['persona_id'],
|
|
'nome' => $item['nome'],
|
|
'codice_fiscale'=> $item['codice_fiscale'],
|
|
'tipo_relazione'=> $item['tipo_relazione'],
|
|
'ruolo_label' => ucfirst($item['tipo_relazione']) . ' (' . $gSpan . ')',
|
|
'quota' => $item['quota'],
|
|
'quota_label' => $item['quota_label'],
|
|
'data_inizio' => $gSpan,
|
|
'data_fine' => null,
|
|
'attivo' => $item['attivo'],
|
|
'fonte' => $item['fonte'],
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($fallbackList) && DbSchema::hasTable('persone_unita_relazioni') && DbSchema::hasTable('persone')) {
|
|
$purRows = DB::table('persone_unita_relazioni as pur')
|
|
->join('persone as p', 'pur.persona_id', '=', 'p.id')
|
|
->where('pur.unita_id', $this->unita->id)
|
|
->orderBy('pur.data_inizio')
|
|
->orderByDesc('pur.id')
|
|
->select('pur.*', 'p.cognome', 'p.nome', 'p.ragione_sociale', 'p.codice_fiscale')
|
|
->get();
|
|
|
|
foreach ($purRows as $r) {
|
|
$nome = trim((string) ($r->ragione_sociale ?? ''));
|
|
if ($nome === '') {
|
|
$nome = trim(($r->cognome ?? '') . ' ' . ($r->nome ?? ''));
|
|
}
|
|
if ($nome === '') {
|
|
continue;
|
|
}
|
|
|
|
$inizio = $r->data_inizio ? Carbon::parse($r->data_inizio)->format('d/m/Y') : null;
|
|
$fine = $r->data_fine ? Carbon::parse($r->data_fine)->format('d/m/Y') : null;
|
|
$periodo = ($inizio && $fine) ? ('Da ' . $inizio . ' · fino a ' . $fine) : ($inizio ? ('Da ' . $inizio) : ($fine ? ('Fino al ' . $fine) : 'Da sempre'));
|
|
|
|
$quota = is_numeric($r->quota_relazione) ? (number_format((float) $r->quota_relazione, 2, ',', '.') . '%') : null;
|
|
$ruolo = ucfirst(strtolower((string) ($r->tipo_relazione ?? 'Condomino')));
|
|
|
|
$fallbackList[] = [
|
|
'ruolo' => $ruolo,
|
|
'nominativo' => $nome,
|
|
'periodo' => $periodo,
|
|
'percentuale' => $quota,
|
|
'fonte' => 'Anagrafica consolidata',
|
|
'detail' => $r->codice_fiscale ? ('CF: ' . $r->codice_fiscale) : null,
|
|
];
|
|
}
|
|
}
|
|
|
|
if (empty($fallbackList) && DbSchema::hasTable('rubrica_ruoli') && DbSchema::hasTable('rubrica_universale')) {
|
|
$rrRows = DB::table('rubrica_ruoli as rr')
|
|
->join('rubrica_universale as ru', 'rr.rubrica_id', '=', 'ru.id')
|
|
->where('rr.unita_immobiliare_id', $this->unita->id)
|
|
->orderBy('rr.data_inizio')
|
|
->orderByDesc('rr.id')
|
|
->select('rr.*', 'ru.cognome', 'ru.nome', 'ru.ragione_sociale', 'ru.codice_fiscale')
|
|
->get();
|
|
|
|
foreach ($rrRows as $r) {
|
|
$nome = trim((string) ($r->ragione_sociale ?? ''));
|
|
if ($nome === '') {
|
|
$nome = trim(($r->cognome ?? '') . ' ' . ($r->nome ?? ''));
|
|
}
|
|
if ($nome === '') {
|
|
continue;
|
|
}
|
|
|
|
$inizio = $r->data_inizio ? Carbon::parse($r->data_inizio)->format('d/m/Y') : null;
|
|
$fine = $r->data_fine ? Carbon::parse($r->data_fine)->format('d/m/Y') : null;
|
|
$periodo = ($inizio && $fine) ? ('Da ' . $inizio . ' · fino a ' . $fine) : ($inizio ? ('Da ' . $inizio) : ($fine ? ('Fino al ' . $fine) : 'Da sempre'));
|
|
|
|
$ruolo = ucfirst(strtolower((string) ($r->ruolo_standard ?? 'Condomino')));
|
|
|
|
$fallbackList[] = [
|
|
'ruolo' => $ruolo,
|
|
'nominativo' => $nome,
|
|
'periodo' => $periodo,
|
|
'percentuale' => null,
|
|
'fonte' => 'Rubrica universale',
|
|
'detail' => $r->codice_fiscale ? ('CF: ' . $r->codice_fiscale) : null,
|
|
];
|
|
}
|
|
}
|
|
|
|
if (empty($fallbackList)) {
|
|
foreach (['proprietari', 'inquilini'] as $relType) {
|
|
foreach ($this->relazioniPerTipo[$relType] ?? [] as $rel) {
|
|
$fallbackList[] = [
|
|
'ruolo' => $relType === 'inquilini' ? 'Inquilino' : 'Proprietario',
|
|
'nominativo' => $rel['nome'] ?? '—',
|
|
'periodo' => $rel['data_inizio'] ? ('Da ' . $rel['data_inizio']) : 'Da sempre',
|
|
'percentuale' => isset($rel['quota']) && is_numeric($rel['quota']) ? (number_format((float) $rel['quota'], 2, ',', '.') . '%') : null,
|
|
'fonte' => 'Relazione attiva',
|
|
'detail' => ! empty($rel['codice_fiscale']) ? ('CF: ' . $rel['codice_fiscale']) : null,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->nominativiStorici = $fallbackList;
|
|
}
|
|
|
|
public function hydrateRecapitiServizio(): void
|
|
{
|
|
$this->recapitiServizio = [];
|
|
|
|
if (! $this->unita) {
|
|
return;
|
|
}
|
|
|
|
$resolver = app(RecapitiServizioResolver::class);
|
|
foreach (UnitaRecapitoServizio::serviceLabels() as $serviceType => $label) {
|
|
$rows = $resolver->resolveForUnita($this->unita, $serviceType);
|
|
|
|
$this->recapitiServizio[$serviceType] = [
|
|
'label' => $label,
|
|
'rows' => array_map(function (array $row): array {
|
|
$source = (string) ($row['source'] ?? 'persona');
|
|
|
|
return [
|
|
'email' => (string) ($row['email'] ?? ''),
|
|
'persona' => (string) ($row['persona'] ?? ''),
|
|
'label' => (string) ($row['label'] ?? ''),
|
|
'source' => $source,
|
|
'sourceLabel' => $source === 'override_unita' ? 'Override unita' : 'Anagrafica persona',
|
|
];
|
|
}, $rows),
|
|
];
|
|
}
|
|
}
|
|
|
|
private function formatNominativoStoricoRuolo(string $ruolo, string $fonte, array $payload): string
|
|
{
|
|
if ($fonte === 'legacy_comproprietari') {
|
|
$dirittoLabel = trim((string) ($payload['diritto_label'] ?? ''));
|
|
return $dirittoLabel !== '' ? $dirittoLabel : 'Comproprietario';
|
|
}
|
|
|
|
return match (strtoupper($ruolo)) {
|
|
'I' => 'Inquilino',
|
|
'C' => 'Condomino',
|
|
default => $ruolo !== '' ? $ruolo : '—',
|
|
};
|
|
}
|
|
|
|
private function extractPersonaEmails(Persona $persona): array
|
|
{
|
|
$emails = [];
|
|
|
|
$primary = mb_strtolower(trim((string) ($persona->email_principale ?? '')));
|
|
if ($primary !== '') {
|
|
$emails[] = $primary;
|
|
}
|
|
|
|
foreach ($persona->emailMultiple as $row) {
|
|
if (! $row->attiva) {
|
|
continue;
|
|
}
|
|
|
|
$email = mb_strtolower(trim((string) ($row->email ?? '')));
|
|
if ($email !== '') {
|
|
$emails[] = $email;
|
|
}
|
|
}
|
|
|
|
return array_values(array_unique($emails));
|
|
}
|
|
|
|
private function normalizeOptionalString(mixed $value): ?string
|
|
{
|
|
$string = trim((string) ($value ?? ''));
|
|
|
|
return $string !== '' ? $string : null;
|
|
}
|
|
|
|
protected function hydrateRateEmesse(): void
|
|
{
|
|
$this->rateEmessePerCategoria = [
|
|
'condomini' => [],
|
|
'inquilini' => [],
|
|
];
|
|
|
|
if (! $this->unita) {
|
|
return;
|
|
}
|
|
|
|
$user = Auth::user();
|
|
$annoGestione = $user instanceof User ? AnnoGestioneContext::resolveActiveAnno($user) : null;
|
|
$policy = GestioneVisibility::policyForStabile((int) $this->stabileId, $annoGestione ?: null);
|
|
$visibleYears = (array) ($policy['visible_years'] ?? []);
|
|
|
|
$legacySummaries = $this->buildLegacyRateSummariesForUnita($visibleYears);
|
|
if ($legacySummaries['condomini'] !== [] || $legacySummaries['inquilini'] !== []) {
|
|
$this->rateEmessePerCategoria = $legacySummaries;
|
|
|
|
if ($this->estrattoTipo !== 'condomini' && $this->estrattoTipo !== 'inquilini') {
|
|
$this->estrattoTipo = 'condomini';
|
|
}
|
|
if ($this->estrattoTipo === 'condomini' && empty($this->rateEmessePerCategoria['condomini']) && ! empty($this->rateEmessePerCategoria['inquilini'])) {
|
|
$this->estrattoTipo = 'inquilini';
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (! DbSchema::hasTable('rate_emesse')) {
|
|
return;
|
|
}
|
|
|
|
$ownerIds = Proprieta::query()
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->whereIn('tipo_diritto', $this->ownershipTipoDiritti())
|
|
->pluck('anagrafica_id')
|
|
->filter(fn($v) => is_numeric($v) && (int) $v > 0)
|
|
->map(fn($v) => (int) $v)
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
$ownerLookup = array_fill_keys($ownerIds, true);
|
|
|
|
$rowsQuery = RataEmessaNg::query()
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->with([
|
|
'soggettoResponsabile:id,nome,cognome,ragione_sociale,codice_fiscale',
|
|
'pianoRateizzazione:id,stabile_id,descrizione',
|
|
])
|
|
->orderByDesc('data_emissione')
|
|
->orderByDesc('id')
|
|
->limit(5000);
|
|
|
|
$rows = $rowsQuery->get();
|
|
|
|
if ($rows->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$groups = $rows
|
|
->groupBy('soggetto_responsabile_id')
|
|
->map(function ($items, $soggettoId) use ($ownerLookup): array {
|
|
$first = $items->first();
|
|
|
|
$s = $first?->soggettoResponsabile;
|
|
$nome = '—';
|
|
if ($s) {
|
|
$nome = trim((string) ($s->ragione_sociale ?? ''));
|
|
if ($nome === '') {
|
|
$nome = trim((string) ($s->nome ?? '') . ' ' . (string) ($s->cognome ?? ''));
|
|
}
|
|
if ($nome === '') {
|
|
$nome = 'Soggetto #' . (int) $s->id;
|
|
}
|
|
}
|
|
|
|
$totAddebitato = (float) $items->sum(fn(RataEmessaNg $r) => (float) $r->importo_addebitato_soggetto);
|
|
$totPagato = (float) $items->sum(fn(RataEmessaNg $r) => (float) $r->importo_pagato);
|
|
$residuo = $totAddebitato - $totPagato;
|
|
|
|
$scadute = $items->filter(function (RataEmessaNg $r): bool {
|
|
try {
|
|
return strtoupper((string) $r->stato_rata) === 'EMESSA'
|
|
&& $r->data_scadenza
|
|
&& $r->data_scadenza->isPast();
|
|
} catch (\Throwable $e) {
|
|
return false;
|
|
}
|
|
})->count();
|
|
|
|
$categoria = isset($ownerLookup[(int) $soggettoId]) ? 'condomini' : 'inquilini';
|
|
|
|
return [
|
|
'categoria' => $categoria,
|
|
'soggetto_id' => is_numeric($soggettoId) ? (int) $soggettoId : null,
|
|
'nome' => $nome,
|
|
'codice_fiscale' => $s?->codice_fiscale,
|
|
'numero_rate' => $items->count(),
|
|
'scadute' => $scadute,
|
|
'totale_addebitato' => $totAddebitato,
|
|
'totale_pagato' => $totPagato,
|
|
'residuo' => $residuo,
|
|
'ultimo_piano' => $first?->pianoRateizzazione?->descrizione,
|
|
];
|
|
})
|
|
->values();
|
|
|
|
foreach ($groups as $g) {
|
|
$cat = $g['categoria'] ?? 'inquilini';
|
|
$this->rateEmessePerCategoria[$cat][] = $g;
|
|
}
|
|
|
|
foreach (['condomini', 'inquilini'] as $cat) {
|
|
usort($this->rateEmessePerCategoria[$cat], function (array $a, array $b): int {
|
|
return ($b['residuo'] <=> $a['residuo'])
|
|
?: (($b['totale_addebitato'] ?? 0) <=> ($a['totale_addebitato'] ?? 0));
|
|
});
|
|
}
|
|
|
|
// Default tab estratto: preferisci condomini se presenti, altrimenti inquilini.
|
|
if ($this->estrattoTipo !== 'condomini' && $this->estrattoTipo !== 'inquilini') {
|
|
$this->estrattoTipo = 'condomini';
|
|
}
|
|
if ($this->estrattoTipo === 'condomini' && empty($this->rateEmessePerCategoria['condomini'])) {
|
|
if (! empty($this->rateEmessePerCategoria['inquilini'])) {
|
|
$this->estrattoTipo = 'inquilini';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<int, int|string> $visibleYears
|
|
* @return array{condomini: array<int, array<string, mixed>>, inquilini: array<int, array<string, mixed>>}
|
|
*/
|
|
private function buildLegacyRateSummariesForUnita(array $visibleYears = []): array
|
|
{
|
|
$out = [
|
|
'condomini' => [],
|
|
'inquilini' => [],
|
|
];
|
|
|
|
if (! $this->unita) {
|
|
return $out;
|
|
}
|
|
|
|
$legacyRateRows = $this->loadLegacyRateRowsForUnita($visibleYears);
|
|
if ($legacyRateRows === []) {
|
|
return $out;
|
|
}
|
|
|
|
$legacyIncassi = $this->loadLegacyIncassiForUnita(false, $visibleYears);
|
|
$legacyRow = $this->getLegacyCondominRow();
|
|
|
|
$ownerName = $this->getLegacyCondominoRiferimentoNome();
|
|
$ownerCf = $this->getLegacyCondominoRiferimentoCodiceFiscale();
|
|
$tenantName = trim((string) ($legacyRow?->inquil_nome ?? '')) ?: null;
|
|
$tenantCf = trim((string) ($legacyRow?->inquil_cod_fisc ?? '')) ?: null;
|
|
|
|
$ownerSoggettoId = $this->resolveLegacySoggettoId($ownerCf, $legacyRow?->id_cond ?? $legacyRow?->legacy_id_cond ?? null);
|
|
$tenantSoggettoId = $this->resolveLegacySoggettoId($tenantCf, null);
|
|
|
|
foreach ([
|
|
'condomini' => ['role' => 'C', 'name' => $ownerName, 'cf' => $ownerCf, 'soggetto_id' => $ownerSoggettoId],
|
|
'inquilini' => ['role' => 'I', 'name' => $tenantName, 'cf' => $tenantCf, 'soggetto_id' => $tenantSoggettoId],
|
|
] as $category => $config) {
|
|
$role = $config['role'];
|
|
|
|
$rateRows = array_values(array_filter($legacyRateRows, fn(array $row): bool => strtoupper(trim((string) ($row['tipo'] ?? ''))) === $role));
|
|
$incassi = array_values(array_filter($legacyIncassi, fn(array $row): bool => strtoupper(trim((string) ($row['tipo'] ?? ''))) === $role));
|
|
|
|
if ($rateRows === [] && $incassi === []) {
|
|
continue;
|
|
}
|
|
|
|
$totaleAddebitato = (float) collect($rateRows)->sum(fn(array $row): float => (float) ($row['dovuto'] ?? 0));
|
|
$totalePagato = (float) collect($incassi)->sum(fn(array $row): float => (float) ($row['importo'] ?? 0));
|
|
|
|
$out[$category][] = [
|
|
'categoria' => $category,
|
|
'soggetto_id' => $config['soggetto_id'],
|
|
'nome' => $config['name'] ?: ($category === 'condomini' ? 'Condomino legacy' : 'Inquilino legacy'),
|
|
'codice_fiscale' => $config['cf'],
|
|
'numero_rate' => count($rateRows),
|
|
'scadute' => 0,
|
|
'totale_addebitato' => $totaleAddebitato,
|
|
'totale_pagato' => $totalePagato,
|
|
'residuo' => $totaleAddebitato - $totalePagato,
|
|
'ultimo_piano' => collect($rateRows)->pluck('gestione')->filter()->last(),
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
protected function hydrateEstrattoCompatto(): void
|
|
{
|
|
$this->estrattoCompattoRateRows = [];
|
|
$this->estrattoCompattoIncassi = [];
|
|
|
|
if (! DbSchema::hasTable('rate_emesse')) {
|
|
$this->splitEstrattoContoProperties();
|
|
return;
|
|
}
|
|
|
|
if (! $this->unita) {
|
|
$this->splitEstrattoContoProperties();
|
|
return;
|
|
}
|
|
|
|
$rateRowsQuery = RataEmessaNg::query()
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->orderBy('data_emissione')
|
|
->orderBy('id')
|
|
->limit(5000);
|
|
|
|
$user = Auth::user();
|
|
$annoGestione = $user instanceof User ? AnnoGestioneContext::resolveActiveAnno($user) : null;
|
|
$policy = GestioneVisibility::policyForStabile((int) $this->stabileId, $annoGestione ?: null);
|
|
$visibleYears = (array) ($policy['visible_years'] ?? []);
|
|
$legacyRateRows = $this->loadLegacyRateRowsForUnita($visibleYears);
|
|
|
|
if ($legacyRateRows !== []) {
|
|
$this->estrattoCompattoRateRows = $legacyRateRows;
|
|
$this->estrattoCompattoIncassi = $this->loadLegacyIncassiForUnita(false, $visibleYears);
|
|
$this->splitEstrattoContoProperties();
|
|
return;
|
|
}
|
|
|
|
$rateRows = $rateRowsQuery->get()
|
|
->filter(fn(RataEmessaNg $r): bool => GestioneVisibility::matchesYear($this->resolveEstrattoRateCalendarYear($r), $visibleYears))
|
|
->values();
|
|
|
|
if ($rateRows->isNotEmpty()) {
|
|
$this->estrattoCompattoRateRows = $rateRows->map(function (RataEmessaNg $r): array {
|
|
$note = (string) ($r->note ?? '');
|
|
$tipo = $this->extractLegacyRoleFromRateNote($note);
|
|
$gestioneLabel = $this->extractAnnoGestioneFromRateNote($note);
|
|
$avvisoCode = $this->extractAvvisoCodeFromRateNote($note) ?? $this->buildLegacyAvvisoCode($gestioneLabel, is_numeric($r->numero_rata_progressivo ?? null) ? (int) $r->numero_rata_progressivo : null, $r->data_emissione?->format('Y-m-d'));
|
|
|
|
$dovuto = (float) ($r->importo_addebitato_soggetto ?? 0);
|
|
$pagato = (float) ($r->importo_pagato ?? 0);
|
|
$residuo = $dovuto - $pagato;
|
|
|
|
return [
|
|
'id' => (int) $r->id,
|
|
'data_emissione' => $r->data_emissione?->format('d/m/Y'),
|
|
'data_scadenza' => $r->data_scadenza?->format('d/m/Y'),
|
|
'data_pagamento' => $r->data_ultimo_pagamento?->format('d/m/Y'),
|
|
'descrizione' => (string) ($r->descrizione ?? ''),
|
|
'avviso' => $avvisoCode,
|
|
'gestione' => $gestioneLabel,
|
|
'tipo' => $tipo,
|
|
'dovuto' => $dovuto,
|
|
'pagato' => $pagato,
|
|
'residuo' => $residuo,
|
|
];
|
|
})->all();
|
|
|
|
$this->estrattoCompattoIncassi = $this->loadIncassiForUnita($rateRows, $visibleYears);
|
|
$this->splitEstrattoContoProperties();
|
|
return;
|
|
}
|
|
|
|
$this->estrattoCompattoRateRows = [];
|
|
$this->estrattoCompattoIncassi = [];
|
|
$this->splitEstrattoContoProperties();
|
|
}
|
|
|
|
private function splitEstrattoContoProperties(): void
|
|
{
|
|
$this->estrattoRateRowsProprietario = collect($this->estrattoCompattoRateRows)->filter(fn($r) => strtoupper(trim((string)($r['tipo'] ?? ''))) !== 'I')->values()->all();
|
|
$this->estrattoRateRowsInquilino = collect($this->estrattoCompattoRateRows)->filter(fn($r) => strtoupper(trim((string)($r['tipo'] ?? ''))) === 'I')->values()->all();
|
|
|
|
$this->estrattoIncassiProprietario = collect($this->estrattoCompattoIncassi)->filter(fn($i) => strtoupper(trim((string)($i['tipo'] ?? ''))) !== 'I')->values()->all();
|
|
$this->estrattoIncassiInquilino = collect($this->estrattoCompattoIncassi)->filter(fn($i) => strtoupper(trim((string)($i['tipo'] ?? ''))) === 'I')->values()->all();
|
|
}
|
|
|
|
protected function hydrateConguagliIniziali(): void
|
|
{
|
|
$this->estrattoConguagliIniziali = [];
|
|
|
|
if (! $this->unita) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->hasImportConnection()) {
|
|
return;
|
|
}
|
|
|
|
$legacyCondId = is_numeric($this->unita->legacy_cond_id ?? null) ? (int) $this->unita->legacy_cond_id : null;
|
|
if (! $legacyCondId) {
|
|
return;
|
|
}
|
|
|
|
if (! DbSchema::connection('gescon_import')->hasTable('dett_tab')) {
|
|
return;
|
|
}
|
|
|
|
$codStabile = trim((string) ($this->unita->stabile?->codice_stabile ?? ''));
|
|
if ($codStabile === '') {
|
|
return;
|
|
}
|
|
|
|
$rows = DB::connection('gescon_import')
|
|
->table('dett_tab')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('id_cond', (string) $legacyCondId)
|
|
->where('cod_tab', 'like', 'CONG.%')
|
|
->select([
|
|
'legacy_year',
|
|
'cod_tab',
|
|
'n_stra',
|
|
DB::raw('SUM(cons_euro) as totale'),
|
|
])
|
|
->groupBy('legacy_year', 'cod_tab', 'n_stra')
|
|
->orderBy('legacy_year')
|
|
->orderBy('cod_tab')
|
|
->get();
|
|
|
|
if ($rows->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$emittedConguaglioKeys = [];
|
|
if (DbSchema::hasTable('rate_emesse')) {
|
|
RataEmessaNg::query()
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->where('descrizione', 'like', 'Conguaglio%')
|
|
->get(['note', 'importo_addebitato_soggetto'])
|
|
->each(function (RataEmessaNg $r) use (&$emittedConguaglioKeys): void {
|
|
$note = (string) ($r->note ?? '');
|
|
$role = $this->extractLegacyRoleFromRateNote($note);
|
|
$year = $this->extractLegacyYearFromRateNote($note);
|
|
$amount = round((float) ($r->importo_addebitato_soggetto ?? 0), 2);
|
|
if ($role === null || $year === null) {
|
|
return;
|
|
}
|
|
|
|
$emittedConguaglioKeys[$this->makeConguaglioDedupKey($role, $year, $amount)] = true;
|
|
});
|
|
}
|
|
|
|
foreach ($rows as $r) {
|
|
$importo = is_numeric($r->totale ?? null) ? (float) $r->totale : 0.0;
|
|
if (abs($importo) < 0.00001) {
|
|
continue;
|
|
}
|
|
|
|
$legacyYear = trim((string) ($r->legacy_year ?? ''));
|
|
if (isset($emittedConguaglioKeys[$this->makeConguaglioDedupKey('C', $legacyYear, $importo)])) {
|
|
continue;
|
|
}
|
|
$label = $this->resolveLegacyYearLabel($codStabile, $legacyYear);
|
|
$year = $this->extractYearFromLabel($label);
|
|
$data = $year ? ('31/12/' . $year) : null;
|
|
|
|
$codTab = trim((string) ($r->cod_tab ?? ''));
|
|
$tipo = 'Straordinaria';
|
|
if (str_starts_with($codTab, 'CONG.O')) {
|
|
$tipo = 'Ordinaria';
|
|
} elseif (str_starts_with($codTab, 'CONG.R')) {
|
|
$tipo = 'Riscaldamento';
|
|
}
|
|
$nStra = $r->n_stra !== null ? (int) $r->n_stra : null;
|
|
if ($tipo === 'Straordinaria' && $nStra) {
|
|
$tipo .= ' #' . $nStra;
|
|
}
|
|
$descr = 'Conguaglio iniziale ' . $tipo;
|
|
|
|
$this->estrattoConguagliIniziali[] = [
|
|
'legacy_year' => $legacyYear,
|
|
'gestione_label' => $label,
|
|
'data' => $data,
|
|
'cod_tab' => $codTab,
|
|
'tipo' => $tipo,
|
|
'importo' => $importo,
|
|
'descrizione' => $descr,
|
|
];
|
|
}
|
|
}
|
|
|
|
private function resolveLegacyYearLabel(string $codStabileLegacy, ?string $legacyYear): string
|
|
{
|
|
$legacyYear = trim((string) ($legacyYear ?? ''));
|
|
if ($legacyYear === '') {
|
|
return '';
|
|
}
|
|
|
|
if (! $this->hasImportConnection()) {
|
|
return $legacyYear;
|
|
}
|
|
|
|
if (! DbSchema::connection('gescon_import')->hasTable('gestioni_annuali')) {
|
|
return $legacyYear;
|
|
}
|
|
|
|
$row = DB::connection('gescon_import')
|
|
->table('gestioni_annuali')
|
|
->where('cod_stabile', $codStabileLegacy)
|
|
->where('cartella', $legacyYear)
|
|
->first(['anno_ordinario', 'anno_riscaldamento']);
|
|
|
|
$label = trim((string) ($row?->anno_ordinario ?? ''));
|
|
if ($label === '') {
|
|
$label = trim((string) ($row?->anno_riscaldamento ?? ''));
|
|
}
|
|
|
|
return $label !== '' ? $label : $legacyYear;
|
|
}
|
|
|
|
private function extractYearFromLabel(string $label): ?string
|
|
{
|
|
if (preg_match('/(\d{4})/', $label, $m)) {
|
|
return $m[1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function loadIncassiForUnita($rateItems, array $visibleYears = []): array
|
|
{
|
|
if (! DbSchema::hasTable('incassi')) {
|
|
return [];
|
|
}
|
|
|
|
$codCol = DbSchema::hasColumn('incassi', 'cod_cond_gescon')
|
|
? 'cod_cond_gescon'
|
|
: (DbSchema::hasColumn('incassi', 'cod_cond') ? 'cod_cond' : null);
|
|
|
|
$tipoCol = DbSchema::hasColumn('incassi', 'cond_inquil')
|
|
? 'cond_inquil'
|
|
: (DbSchema::hasColumn('incassi', 'cond_inq') ? 'cond_inq' : null);
|
|
|
|
if (! $codCol || ! $tipoCol) {
|
|
return [];
|
|
}
|
|
|
|
$pairs = [];
|
|
foreach ($rateItems as $r) {
|
|
try {
|
|
$note = (string) ($r->note ?? '');
|
|
if ($note === '') {
|
|
continue;
|
|
}
|
|
$tipo = '';
|
|
$cod = '';
|
|
|
|
if (preg_match('/\btipo_soggetto=([CI])\b/', $note, $mTipo)) {
|
|
$tipo = (string) ($mTipo[1] ?? '');
|
|
} elseif (preg_match('/\bcond_inq=([CI])\b/', $note, $mTipoAlt)) {
|
|
$tipo = (string) ($mTipoAlt[1] ?? '');
|
|
}
|
|
|
|
if (preg_match('/\bcod_cond_dest=([^\s|]+)/', $note, $mCod)) {
|
|
$cod = trim((string) ($mCod[1] ?? ''));
|
|
} elseif (preg_match('/\bcod_cond_src=([^\s|]+)/', $note, $mCodAlt)) {
|
|
$cod = trim((string) ($mCodAlt[1] ?? ''));
|
|
}
|
|
|
|
if ($tipo === '' || $cod === '') {
|
|
continue;
|
|
}
|
|
|
|
$pairs[$tipo . '|' . $cod] = ['tipo' => $tipo, 'cod' => $cod];
|
|
} catch (\Throwable $e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (empty($pairs)) {
|
|
return [];
|
|
}
|
|
|
|
$incassiQuery = Incasso::query();
|
|
|
|
if (DbSchema::hasColumn('incassi', 'condominio_id')) {
|
|
$incassiQuery->where('condominio_id', $this->stabileId);
|
|
}
|
|
|
|
$incassiQuery->where(function ($q) use ($pairs, $codCol, $tipoCol): void {
|
|
foreach ($pairs as $p) {
|
|
$q->orWhere(function ($qq) use ($p, $codCol, $tipoCol): void {
|
|
$qq->where($codCol, (string) $p['cod'])
|
|
->where($tipoCol, (string) $p['tipo']);
|
|
});
|
|
}
|
|
});
|
|
|
|
$annoCol = DbSchema::hasColumn('incassi', 'anno_rif')
|
|
? 'anno_rif'
|
|
: (DbSchema::hasColumn('incassi', 'anno_ref') ? 'anno_ref' : (DbSchema::hasColumn('incassi', 'anno') ? 'anno' : null));
|
|
|
|
if ($annoCol) {
|
|
$incassiQuery->where($annoCol, '=', '2026');
|
|
}
|
|
|
|
if (DbSchema::hasColumn('incassi', 'dt_empag')) {
|
|
$incassiQuery->orWhereYear('dt_empag', 2026);
|
|
}
|
|
if (DbSchema::hasColumn('incassi', 'data_pagamento')) {
|
|
$incassiQuery->orWhereYear('data_pagamento', 2026);
|
|
}
|
|
|
|
$orderCol = DbSchema::hasColumn('incassi', 'dt_empag')
|
|
? 'dt_empag'
|
|
: (DbSchema::hasColumn('incassi', 'data_pagamento') ? 'data_pagamento' : 'id');
|
|
|
|
$incassi = $incassiQuery
|
|
->orderByDesc($orderCol)
|
|
->orderByDesc('id')
|
|
->limit(2000)
|
|
->get();
|
|
|
|
if ($visibleYears !== []) {
|
|
$incassi = $incassi
|
|
->filter(fn(Incasso $i): bool => GestioneVisibility::matchesYear($this->resolveEstrattoIncassoCalendarYear($i), $visibleYears))
|
|
->values();
|
|
}
|
|
|
|
return $incassi->map(function (Incasso $i) use ($codCol, $tipoCol): array {
|
|
$importoRaw = $i->importo_pagato_euro ?? $i->importo_euro ?? 0;
|
|
$importo = $this->normalizeLegacyIncassoImporto(is_numeric($importoRaw) ? (float) $importoRaw : 0.0);
|
|
|
|
$dt = null;
|
|
if (! empty($i->dt_empag)) {
|
|
try {
|
|
$dt = Carbon::parse($i->dt_empag)->format('d/m/Y');
|
|
} catch (\Throwable $e) {
|
|
$dt = (string) $i->dt_empag;
|
|
}
|
|
} elseif (! empty($i->data_pagamento)) {
|
|
try {
|
|
$dt = Carbon::parse($i->data_pagamento)->format('d/m/Y');
|
|
} catch (\Throwable $e) {
|
|
$dt = (string) $i->data_pagamento;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'id' => (int) $i->id,
|
|
'data' => $dt,
|
|
'tipo' => (string) (data_get($i, $tipoCol) ?? ''),
|
|
'cod_cond' => (string) (data_get($i, $codCol) ?? ''),
|
|
'importo' => $importo,
|
|
'descrizione' => (string) ($i->descrizione ?? ''),
|
|
'n_ricevuta' => $i->n_ricevuta ?? $i->n_riferimento ?? null,
|
|
];
|
|
})->all();
|
|
}
|
|
|
|
private function loadLegacyRateRowsForUnita(array $visibleYears = []): array
|
|
{
|
|
if (! $this->unita) {
|
|
return [];
|
|
}
|
|
|
|
if (! $this->hasImportConnection()) {
|
|
return [];
|
|
}
|
|
|
|
if (DbSchema::connection('gescon_import')->hasTable('rate_emissioni_dettaglio')) {
|
|
return $this->loadLegacyEmissioniDettaglioRowsForUnita($visibleYears);
|
|
}
|
|
|
|
if (! DbSchema::connection('gescon_import')->hasTable('rate')) {
|
|
return [];
|
|
}
|
|
|
|
$legacyCondIds = $this->resolveLegacyCondIdsForUnita();
|
|
$codStabile = trim((string) ($this->unita->stabile?->codice_stabile ?? ''));
|
|
if ($legacyCondIds === [] || $codStabile === '') {
|
|
return [];
|
|
}
|
|
|
|
$annoAttivo = \App\Support\AnnoGestioneContext::resolveActiveAnno(Auth::user());
|
|
$rows = DB::connection('gescon_import')
|
|
->table('rate')
|
|
->where('cod_stabile', $codStabile)
|
|
->whereIn('cod_cond', $legacyCondIds)
|
|
->where('legacy_year', $annoAttivo)
|
|
->orderBy('data_emissione')
|
|
->orderBy('id')
|
|
->limit(5000)
|
|
->get();
|
|
|
|
$mapped = $rows->map(function ($row): array {
|
|
$year = null;
|
|
try {
|
|
if (! empty($row->data_emissione)) {
|
|
$year = GestioneVisibility::normalizeCalendarYear(Carbon::parse($row->data_emissione)->format('Y'));
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$year = null;
|
|
}
|
|
|
|
$dovuto = is_numeric($row->importo_euro ?? null) ? (float) $row->importo_euro : 0.0;
|
|
|
|
$pagato = is_numeric($row->importo_pagato ?? null)
|
|
? (float) $row->importo_pagato
|
|
: 0.0;
|
|
|
|
return [
|
|
'id' => (int) ($row->id ?? 0),
|
|
'data_emissione' => $this->formatLegacyDate($row->data_emissione ?? null),
|
|
'data_scadenza' => $this->formatLegacyDate($row->data_scadenza ?? null),
|
|
'data_pagamento' => $this->formatLegacyDate($row->data_pagamento ?? null),
|
|
'descrizione' => trim((string) ($row->causale ?? $row->note ?? '')),
|
|
'avviso' => is_numeric($row->n_emissione ?? null) ? (string) $row->n_emissione : null,
|
|
'gestione' => null,
|
|
'tipo' => null,
|
|
'dovuto' => $dovuto,
|
|
'pagato' => $pagato,
|
|
'residuo' => $dovuto - $pagato,
|
|
'year' => $year,
|
|
];
|
|
});
|
|
|
|
if ($visibleYears !== []) {
|
|
$mapped = $mapped->filter(fn(array $row): bool => GestioneVisibility::matchesYear(isset($row['year']) && is_numeric($row['year']) ? (int) $row['year'] : null, $visibleYears));
|
|
}
|
|
|
|
return $mapped->values()->all();
|
|
}
|
|
|
|
private function loadLegacyEmissioniDettaglioRowsForUnita(array $visibleYears = []): array
|
|
{
|
|
if (! $this->unita) {
|
|
return [];
|
|
}
|
|
|
|
$legacyCondIds = $this->resolveLegacyCondIdsForUnita();
|
|
$codStabile = trim((string) ($this->unita->stabile?->codice_stabile ?? ''));
|
|
if ($legacyCondIds === [] || $codStabile === '') {
|
|
return [];
|
|
}
|
|
|
|
$legacyCondIds = $this->preferStoredLegacyCondIdForImportTable(
|
|
'rate_emissioni_dettaglio',
|
|
'cod_cond',
|
|
$legacyCondIds,
|
|
$codStabile,
|
|
);
|
|
|
|
$annoAttivo = \App\Support\AnnoGestioneContext::resolveActiveAnno(Auth::user());
|
|
$rows = DB::connection('gescon_import')
|
|
->table('rate_emissioni_dettaglio')
|
|
->where('cod_stabile', $codStabile)
|
|
->whereIn('cod_cond', $legacyCondIds)
|
|
->where('legacy_year', $annoAttivo)
|
|
->orderBy('data_emissione')
|
|
->orderBy('numero_emissione')
|
|
->orderBy('numero_ricevuta')
|
|
->orderBy('id')
|
|
->limit(5000)
|
|
->get();
|
|
|
|
|
|
$mapped = $rows->map(function ($row): array {
|
|
$gestioneLabel = trim((string) ($row->anno_gestione ?? ''));
|
|
$year = $this->extractEffectiveGestioneEndYear($gestioneLabel);
|
|
|
|
if ($year === null) {
|
|
try {
|
|
if (! empty($row->data_emissione)) {
|
|
$year = GestioneVisibility::normalizeCalendarYear(Carbon::parse($row->data_emissione)->format('Y'));
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$year = null;
|
|
}
|
|
}
|
|
|
|
$dovuto = is_numeric($row->importo_dovuto_euro ?? null) ? (float) $row->importo_dovuto_euro : 0.0;
|
|
|
|
$pagato = is_numeric($row->gia_pagato ?? null)
|
|
? (float) $row->gia_pagato
|
|
: 0.0;
|
|
|
|
$residuo = is_numeric($row->residuo_emesso ?? null)
|
|
? (float) $row->residuo_emesso
|
|
: ($dovuto - $pagato);
|
|
|
|
return [
|
|
'id' => (int) ($row->id ?? 0),
|
|
'data_emissione' => $this->formatLegacyDate($row->data_emissione ?? null),
|
|
'data_scadenza' => null,
|
|
'data_pagamento' => null,
|
|
'descrizione' => trim((string) ($row->descrizione ?? '')),
|
|
'avviso' => $this->buildLegacyAvvisoCode($gestioneLabel !== '' ? $gestioneLabel : (string) ($row->anno_emissione ?? ''), is_numeric($row->numero_mese ?? null) ? (int) $row->numero_mese : null, $row->data_emissione ?? null),
|
|
'numero_ricevuta' => is_numeric($row->numero_ricevuta ?? null) ? (int) $row->numero_ricevuta : null,
|
|
'gestione' => $gestioneLabel !== '' ? $gestioneLabel : null,
|
|
'tipo' => trim((string) ($row->cond_inq ?? $row->tipo_soggetto ?? '')) ?: null,
|
|
'dovuto' => $dovuto,
|
|
'pagato' => $pagato,
|
|
'residuo' => $residuo,
|
|
'year' => $year,
|
|
];
|
|
});
|
|
|
|
return $mapped->values()->all();
|
|
}
|
|
|
|
private function loadLegacyIncassiForUnita(bool $filterByActiveYear = true, array $visibleYears = []): array
|
|
{
|
|
if (! $this->unita || ! DbSchema::hasTable('incassi')) {
|
|
return [];
|
|
}
|
|
|
|
$legacyCondIds = $this->resolveLegacyCondIdsForUnita();
|
|
if ($legacyCondIds === []) {
|
|
return [];
|
|
}
|
|
|
|
$codCol = DbSchema::hasColumn('incassi', 'cod_cond_gescon')
|
|
? 'cod_cond_gescon'
|
|
: (DbSchema::hasColumn('incassi', 'cod_cond') ? 'cod_cond' : null);
|
|
|
|
$tipoCol = DbSchema::hasColumn('incassi', 'cond_inquil')
|
|
? 'cond_inquil'
|
|
: (DbSchema::hasColumn('incassi', 'cond_inq') ? 'cond_inq' : null);
|
|
|
|
if (! $codCol) {
|
|
return [];
|
|
}
|
|
|
|
$incassiQuery = Incasso::query();
|
|
|
|
if (DbSchema::hasColumn('incassi', 'condominio_id')) {
|
|
$incassiQuery->where('condominio_id', $this->stabileId);
|
|
}
|
|
|
|
$incassiQuery->whereIn($codCol, $legacyCondIds);
|
|
|
|
if ($visibleYears === [] && $filterByActiveYear) {
|
|
$user = Auth::user();
|
|
$annoGestione = $user instanceof User ? AnnoGestioneContext::resolveActiveAnno($user) : null;
|
|
if ($annoGestione) {
|
|
$annoCol = DbSchema::hasColumn('incassi', 'anno_rif')
|
|
? 'anno_rif'
|
|
: (DbSchema::hasColumn('incassi', 'anno') ? 'anno' : null);
|
|
$annoShort = (int) substr((string) $annoGestione, -2);
|
|
|
|
$incassiQuery->where(function ($q) use ($annoCol, $annoGestione, $annoShort): void {
|
|
if ($annoCol) {
|
|
$q->where($annoCol, (string) $annoGestione)
|
|
->orWhere($annoCol, (string) $annoShort)
|
|
->orWhere($annoCol, str_pad((string) $annoShort, 2, '0', STR_PAD_LEFT));
|
|
}
|
|
if (DbSchema::hasColumn('incassi', 'dt_empag')) {
|
|
$q->orWhereYear('dt_empag', (int) $annoGestione);
|
|
}
|
|
if (DbSchema::hasColumn('incassi', 'data_pagamento')) {
|
|
$q->orWhereYear('data_pagamento', (int) $annoGestione);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
$orderCol = DbSchema::hasColumn('incassi', 'dt_empag')
|
|
? 'dt_empag'
|
|
: (DbSchema::hasColumn('incassi', 'data_pagamento') ? 'data_pagamento' : 'id');
|
|
|
|
$incassi = $incassiQuery
|
|
->orderByDesc($orderCol)
|
|
->orderByDesc('id')
|
|
->limit(2000)
|
|
->get();
|
|
|
|
if ($visibleYears !== []) {
|
|
$incassi = $incassi
|
|
->filter(fn(Incasso $i): bool => GestioneVisibility::matchesYear($this->resolveEstrattoIncassoCalendarYear($i), $visibleYears))
|
|
->values();
|
|
}
|
|
|
|
return $incassi
|
|
->map(function (Incasso $i) use ($codCol, $tipoCol): array {
|
|
$importoRaw = $i->importo_pagato_euro ?? $i->importo_pagato ?? $i->importo_euro ?? $i->importo ?? 0;
|
|
$importo = $this->normalizeLegacyIncassoImporto(is_numeric($importoRaw) ? (float) $importoRaw : 0.0);
|
|
|
|
return [
|
|
'id' => (int) $i->id,
|
|
'data' => $this->formatLegacyDate($i->dt_empag ?? $i->data_pagamento ?? null),
|
|
'tipo' => $tipoCol ? (string) (data_get($i, $tipoCol) ?? '') : '',
|
|
'cod_cond' => (string) (data_get($i, $codCol) ?? ''),
|
|
'importo' => $importo,
|
|
'descrizione' => (string) ($i->descrizione ?? ''),
|
|
'n_ricevuta' => $i->n_ricevuta ?? $i->n_riferimento ?? null,
|
|
];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
private function resolveLegacyCondIdsForUnita(): array
|
|
{
|
|
if (! $this->unita) {
|
|
return [];
|
|
}
|
|
|
|
$fallbackIds = [];
|
|
$storedId = trim((string) ($this->unita->legacy_cond_id ?? ''));
|
|
if ($storedId !== '') {
|
|
$fallbackIds[] = $storedId;
|
|
}
|
|
|
|
if (! $this->hasImportConnection()) {
|
|
return array_values(array_unique($fallbackIds));
|
|
}
|
|
|
|
$codStabile = trim((string) ($this->unita->stabile?->codice_stabile ?? ''));
|
|
$interno = trim((string) ($this->unita->interno ?? ''));
|
|
if ($codStabile === '' || $interno === '' || ! DbSchema::connection('gescon_import')->hasTable('condomin')) {
|
|
return array_values(array_unique($fallbackIds));
|
|
}
|
|
|
|
$query = DB::connection('gescon_import')
|
|
->table('condomin')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('interno', $interno);
|
|
|
|
$scala = trim((string) ($this->unita->scala ?? ''));
|
|
if ($scala !== '') {
|
|
$query->where('scala', $scala);
|
|
}
|
|
|
|
$resolved = $query
|
|
->orderByDesc('legacy_year')
|
|
->pluck('cod_cond')
|
|
->map(fn($value) => trim((string) $value))
|
|
->filter(fn(string $value): bool => $value !== '')
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
return collect($fallbackIds)
|
|
->concat($resolved)
|
|
->map(fn($value) => trim((string) $value))
|
|
->filter(fn(string $value): bool => $value !== '')
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function preferStoredLegacyCondIdForImportTable(string $table, string $column, array $candidateIds, string $codStabile): array
|
|
{
|
|
$storedId = trim((string) ($this->unita?->legacy_cond_id ?? ''));
|
|
if ($storedId === '' || $candidateIds === []) {
|
|
return $candidateIds;
|
|
}
|
|
|
|
if (! $this->hasImportConnection()) {
|
|
return $candidateIds;
|
|
}
|
|
|
|
if (! DbSchema::connection('gescon_import')->hasTable($table)) {
|
|
return $candidateIds;
|
|
}
|
|
|
|
$hasStoredRows = DB::connection('gescon_import')
|
|
->table($table)
|
|
->where('cod_stabile', $codStabile)
|
|
->where($column, $storedId)
|
|
->exists();
|
|
|
|
if (! $hasStoredRows) {
|
|
return $candidateIds;
|
|
}
|
|
|
|
return [$storedId];
|
|
}
|
|
|
|
public function getLegacyCondominoRiferimentoNome(): ?string
|
|
{
|
|
$legacyRow = $this->getLegacyCondominRow();
|
|
$nome = trim((string) ($legacyRow?->nom_cond ?? ''));
|
|
|
|
return $nome !== '' ? $nome : null;
|
|
}
|
|
|
|
public function getLegacyCondominoRiferimentoCodiceFiscale(): ?string
|
|
{
|
|
$legacyRow = $this->getLegacyCondominRow();
|
|
$cf = trim((string) ($legacyRow?->cond_cod_fisc ?? $legacyRow?->codice_fiscale ?? ''));
|
|
|
|
return $cf !== '' ? $cf : null;
|
|
}
|
|
|
|
private function resolveLegacySoggettoId(?string $codiceFiscale, mixed $legacyId = null): ?int
|
|
{
|
|
$cf = strtoupper(trim((string) ($codiceFiscale ?? '')));
|
|
if ($cf !== '') {
|
|
$id = Soggetto::query()
|
|
->whereRaw('UPPER(codice_fiscale) = ?', [$cf])
|
|
->value('id');
|
|
|
|
if (is_numeric($id) && (int) $id > 0) {
|
|
return (int) $id;
|
|
}
|
|
}
|
|
|
|
if (is_numeric($legacyId) && (int) $legacyId > 0) {
|
|
$id = Soggetto::query()->where('old_id', (int) $legacyId)->value('id');
|
|
if (is_numeric($id) && (int) $id > 0) {
|
|
return (int) $id;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function resolveEstrattoRateCalendarYear(RataEmessaNg $rate): ?int
|
|
{
|
|
$year = GestioneVisibility::normalizeCalendarYear($rate->data_emissione?->format('Y'));
|
|
if ($year !== null) {
|
|
return $year;
|
|
}
|
|
|
|
$year = GestioneVisibility::normalizeCalendarYear($rate->data_scadenza?->format('Y'));
|
|
if ($year !== null) {
|
|
return $year;
|
|
}
|
|
|
|
return GestioneVisibility::normalizeCalendarYear($this->extractLegacyYearFromRateNote((string) ($rate->note ?? '')));
|
|
}
|
|
|
|
private function resolveEstrattoIncassoCalendarYear(Incasso $incasso): ?int
|
|
{
|
|
$year = GestioneVisibility::normalizeCalendarYear($incasso->anno_rif ?? null);
|
|
if ($year !== null) {
|
|
return $year;
|
|
}
|
|
|
|
$year = GestioneVisibility::normalizeCalendarYear($incasso->anno ?? null);
|
|
if ($year !== null) {
|
|
return $year;
|
|
}
|
|
|
|
try {
|
|
if (! empty($incasso->dt_empag)) {
|
|
return GestioneVisibility::normalizeCalendarYear(Carbon::parse($incasso->dt_empag)->format('Y'));
|
|
}
|
|
if (! empty($incasso->data_pagamento)) {
|
|
return GestioneVisibility::normalizeCalendarYear(Carbon::parse($incasso->data_pagamento)->format('Y'));
|
|
}
|
|
} catch (\Throwable $e) {
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function formatLegacyDate(mixed $value): ?string
|
|
{
|
|
if (empty($value)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return Carbon::parse($value)->format('d/m/Y');
|
|
} catch (\Throwable $e) {
|
|
return (string) $value;
|
|
}
|
|
}
|
|
|
|
private function relationIdentityKey(array $rel): string
|
|
{
|
|
$cf = strtoupper(trim((string) ($rel['codice_fiscale'] ?? '')));
|
|
if ($cf !== '') {
|
|
return 'cf:' . $cf;
|
|
}
|
|
|
|
$rawName = trim((string) ($rel['nome'] ?? ''));
|
|
if ($rawName !== '') {
|
|
$parts = preg_split('/\s+/', mb_strtolower($rawName));
|
|
sort($parts);
|
|
return 'name:' . implode(' ', $parts);
|
|
}
|
|
|
|
$personaId = is_numeric($rel['persona_id'] ?? null) ? (int) $rel['persona_id'] : 0;
|
|
if ($personaId > 0) {
|
|
return 'persona:' . $personaId;
|
|
}
|
|
|
|
return 'id:0';
|
|
}
|
|
|
|
public function toggleMostraStorico(): void
|
|
{
|
|
$this->mostraStorico = ! $this->mostraStorico;
|
|
$this->hydrateRelazioni();
|
|
}
|
|
|
|
private function loadCurrentArchiveRelazioni(string $ruolo): array
|
|
{
|
|
if (! $this->unita || ! DbSchema::hasTable('unita_immobiliare_nominativi')) {
|
|
return [];
|
|
}
|
|
|
|
$rows = $this->unita->nominativiStorici()
|
|
->where('ruolo', strtoupper(trim($ruolo)))
|
|
->orderByDesc('data_inizio')
|
|
->orderByDesc('id')
|
|
->get();
|
|
|
|
if ($rows->isEmpty()) {
|
|
return [];
|
|
}
|
|
|
|
$today = now()->toDateString();
|
|
$currentRows = $rows->filter(function ($row) use ($today): bool {
|
|
$start = $this->normalizeLegacyDateValue(method_exists($row, 'getRawOriginal') ? $row->getRawOriginal('data_inizio') : $row->data_inizio);
|
|
$end = $this->normalizeLegacyDateValue(method_exists($row, 'getRawOriginal') ? $row->getRawOriginal('data_fine') : $row->data_fine);
|
|
|
|
if ($start && $start > $today) {
|
|
return false;
|
|
}
|
|
|
|
if ($end && $end < $today) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
})->values();
|
|
|
|
if ($currentRows->isEmpty()) {
|
|
$latestStart = $rows
|
|
->map(fn($row) => $this->normalizeLegacyDateValue(method_exists($row, 'getRawOriginal') ? $row->getRawOriginal('data_inizio') : $row->data_inizio))
|
|
->filter()
|
|
->sortDesc()
|
|
->first();
|
|
|
|
$currentRows = $latestStart
|
|
? $rows->filter(function ($row) use ($latestStart) : bool {
|
|
$start = $this->normalizeLegacyDateValue(method_exists($row, 'getRawOriginal') ? $row->getRawOriginal('data_inizio') : $row->data_inizio);
|
|
|
|
return $start === $latestStart;
|
|
})->values()
|
|
: collect([$rows->first()])->filter();
|
|
}
|
|
|
|
return $currentRows
|
|
->map(fn($row) => $this->mapArchiveNominativoToRelazione($row))
|
|
->filter(fn(array $row): bool => trim((string) ($row['nome'] ?? '')) !== '')
|
|
->unique(fn(array $row) => $this->relationIdentityKey($row))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function mapArchiveNominativoToRelazione(object $row): array
|
|
{
|
|
$payload = is_array($row->legacy_payload ?? null) ? $row->legacy_payload : [];
|
|
$ruolo = strtoupper(trim((string) ($row->ruolo ?? 'C')));
|
|
$quota = is_numeric($row->percentuale ?? null) ? (float) $row->percentuale : null;
|
|
$source = (string) ($row->fonte ?? '');
|
|
$tipoRaw = $ruolo === 'I'
|
|
? 'inquilino'
|
|
: ($source === 'legacy_comproprietari' ? 'comproprietario' : 'condomino');
|
|
|
|
$tipo = match ($tipoRaw) {
|
|
'inquilino' => 'Inquilino (archivio unità)',
|
|
'comproprietario' => 'Comproprietario (archivio unità)',
|
|
default => 'Condomino (archivio unità)',
|
|
};
|
|
|
|
return [
|
|
'id' => (int) ($row->id ?? 0),
|
|
'persona_id' => 0,
|
|
'legacy_persona_id' => is_numeric($payload['id_compr'] ?? null) ? (int) $payload['id_compr'] : null,
|
|
'rubrica_id' => null,
|
|
'nome' => trim((string) ($row->nominativo ?? '')),
|
|
'codice_fiscale' => $payload['codice_fiscale'] ?? $payload['cond_cod_fisc'] ?? $payload['inquilin_cod_fisc'] ?? $payload['inquil_cod_fisc'] ?? null,
|
|
'tipo_raw' => $tipoRaw,
|
|
'tipo' => $tipo,
|
|
'ruolo_custom' => null,
|
|
'has_custom_role' => false,
|
|
'ruolo_rate' => $ruolo,
|
|
'quota' => $quota,
|
|
'quota_label' => $quota !== null ? number_format($quota, 2, ',', '.') : null,
|
|
'data_inizio' => $this->formatLegacyDate($row->data_inizio ?? null),
|
|
'data_fine' => $this->formatLegacyDate($row->data_fine ?? null),
|
|
'attivo' => true,
|
|
'telefono' => null,
|
|
'email' => null,
|
|
'rubrica_url' => null,
|
|
'riceve_comunicazioni' => false,
|
|
'riceve_convocazioni' => false,
|
|
'vota_assemblea' => false,
|
|
];
|
|
}
|
|
|
|
private function loadCurrentLegacyComproprietariRelazioni(): array
|
|
{
|
|
if (! $this->unita || ! $this->hasImportConnection() || ! DbSchema::connection('gescon_import')->hasTable('comproprietari')) {
|
|
return [];
|
|
}
|
|
|
|
$codStabile = trim((string) ($this->unita->stabile?->codice_stabile ?? ''));
|
|
$legacyCondId = trim((string) ($this->unita->legacy_cond_id ?? ''));
|
|
if ($codStabile === '' || $legacyCondId === '') {
|
|
return [];
|
|
}
|
|
|
|
$baseQuery = DB::connection('gescon_import')->table('comproprietari')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('id_cond', $legacyCondId);
|
|
|
|
$latestYear = (clone $baseQuery)->max('legacy_year');
|
|
if ($latestYear === null || $latestYear === '') {
|
|
return [];
|
|
}
|
|
|
|
return $baseQuery
|
|
->where('legacy_year', $latestYear)
|
|
->orderBy('id_compr')
|
|
->get()
|
|
->map(function (object $row): array {
|
|
$quota = is_numeric($row->perc_diritto_reale ?? null) ? (float) $row->perc_diritto_reale : null;
|
|
$diritto = trim((string) ($row->descriz ?? $row->diritto_reale ?? ''));
|
|
$tipo = $diritto !== ''
|
|
? 'Comproprietario (' . $diritto . ')'
|
|
: 'Comproprietario (legacy)';
|
|
|
|
return [
|
|
'id' => (int) ($row->id ?? 0),
|
|
'persona_id' => 0,
|
|
'legacy_persona_id' => is_numeric($row->id_compr ?? null) ? (int) $row->id_compr : null,
|
|
'rubrica_id' => null,
|
|
'nome' => trim((string) ($row->nom_cond ?? '')),
|
|
'codice_fiscale' => $row->cond_cod_fisc ?? null,
|
|
'tipo_raw' => 'comproprietario',
|
|
'tipo' => $tipo,
|
|
'ruolo_custom' => null,
|
|
'has_custom_role' => false,
|
|
'ruolo_rate' => 'C',
|
|
'quota' => $quota,
|
|
'quota_label' => $quota !== null ? number_format($quota, 2, ',', '.') : null,
|
|
'data_inizio' => null,
|
|
'data_fine' => null,
|
|
'attivo' => true,
|
|
'telefono' => null,
|
|
'email' => trim((string) ($row->e_mail_condomino ?? '')) ?: null,
|
|
'rubrica_url' => null,
|
|
'riceve_comunicazioni' => false,
|
|
'riceve_convocazioni' => false,
|
|
'vota_assemblea' => false,
|
|
];
|
|
})
|
|
->filter(fn(array $row): bool => trim((string) ($row['nome'] ?? '')) !== '')
|
|
->unique(fn(array $row) => $this->relationIdentityKey($row))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function normalizeLegacyIncassoImporto(float $val): float
|
|
{
|
|
$abs = abs($val);
|
|
if ($abs >= 1000000) {
|
|
return $val / 10000;
|
|
}
|
|
if ($abs >= 100000 && fmod($abs, 1.0) === 0.0) {
|
|
return $val / 100;
|
|
}
|
|
if ($abs >= 1000 && fmod($abs, 1.0) === 0.0 && ((int) $abs % 100) === 0) {
|
|
return $val / 100;
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
protected function hydrateMillesimi(): void
|
|
{
|
|
$this->millesimiPerTabella = $this->unita->dettagliMillesimi
|
|
->sortBy(function ($d) {
|
|
$tabella = $d->tabellaMillesimale;
|
|
$tipo = (string) ($tabella?->tipo_tabella ?? ($tabella?->tipo ?? ''));
|
|
$ord = (int) ($d->nord ?? $tabella?->nord ?? $tabella?->ordinamento ?? 999999);
|
|
return sprintf('%s|%09d', $tipo, $ord);
|
|
})
|
|
->map(function ($d) {
|
|
$tabella = $d->tabellaMillesimale;
|
|
$tipo = (string) ($tabella?->tipo_tabella ?? ($tabella?->tipo ?? ''));
|
|
if ($tipo === '') {
|
|
$tipo = 'altro';
|
|
}
|
|
return [
|
|
'tipo' => $tipo,
|
|
'gruppo' => $this->mapTipoToGruppo($tipo),
|
|
'id' => $tabella?->id,
|
|
'codice' => $tabella?->codice_tabella ?? $tabella?->nome_tabella ?? 'TAB',
|
|
'nome' => $tabella?->denominazione ?? $tabella?->nome_tabella_millesimale ?? 'Tabella millesimale',
|
|
'millesimi' => (float) ($d->millesimi ?? 0),
|
|
];
|
|
})
|
|
->values()
|
|
->all();
|
|
|
|
if (empty($this->millesimiPerTabella)) {
|
|
$this->millesimiPerTabella = $this->loadMillesimiFromLegacyMdb();
|
|
}
|
|
}
|
|
|
|
protected function loadMillesimiFromLegacyMdb(): array
|
|
{
|
|
if (! $this->unita || ! $this->unita->stabile) {
|
|
return [];
|
|
}
|
|
|
|
$codStabile = trim((string) $this->unita->stabile->codice_stabile);
|
|
$scala = trim((string) $this->unita->scala);
|
|
$interno = trim((string) $this->unita->interno);
|
|
|
|
if ($codStabile === '' || $interno === '') {
|
|
return [];
|
|
}
|
|
|
|
$idCond = null;
|
|
if ($this->hasImportConnection() && DbSchema::connection('gescon_import')->hasTable('condomin_mirror')) {
|
|
$q = DB::connection('gescon_import')->table('condomin_mirror')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('interno', $interno);
|
|
if ($scala !== '') {
|
|
$q->where('scala', $scala);
|
|
}
|
|
$mRow = $q->first();
|
|
if ($mRow) {
|
|
$idCond = $mRow->id_cond ?: $mRow->cod_cond;
|
|
}
|
|
}
|
|
|
|
if (! $idCond) {
|
|
return [];
|
|
}
|
|
|
|
$candidatePaths = [
|
|
"/mnt/gescon-archives/gescon/{$codStabile}/0914/singolo_anno.mdb",
|
|
"/mnt/gescon-archives/gescon/{$codStabile}/0004/singolo_anno.mdb",
|
|
];
|
|
|
|
$mdbPath = null;
|
|
foreach ($candidatePaths as $p) {
|
|
if (file_exists($p) && is_readable($p)) {
|
|
$mdbPath = $p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! $mdbPath) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$tabRows = $this->extractMdbTableRows($mdbPath, 'tabelle');
|
|
$dettRows = $this->extractMdbTableRows($mdbPath, 'dett_tab');
|
|
|
|
$tabMap = [];
|
|
foreach ($tabRows as $tr) {
|
|
$c = isset($tr['cod_tab']) ? (string) $tr['cod_tab'] : null;
|
|
if ($c) {
|
|
$tabMap[$c] = isset($tr['des_tab']) && trim((string) $tr['des_tab']) !== '' ? (string) $tr['des_tab'] : $c;
|
|
}
|
|
}
|
|
|
|
$res = [];
|
|
foreach ($dettRows as $dr) {
|
|
$drIdCond = isset($dr['id_cond']) ? (string) $dr['id_cond'] : null;
|
|
$drInq = isset($dr['cond_inquil']) ? (string) $dr['cond_inquil'] : 'C';
|
|
$mm = isset($dr['mm']) && is_numeric($dr['mm']) ? (float) $dr['mm'] : 0.0;
|
|
|
|
if ($drIdCond === (string) $idCond && strtoupper($drInq) === 'C' && $mm > 0) {
|
|
$cTab = isset($dr['cod_tab']) ? (string) $dr['cod_tab'] : 'TAB';
|
|
$nomeTab = $tabMap[$cTab] ?? $cTab;
|
|
$res[] = [
|
|
'tipo' => str_contains(strtolower($cTab), 'risc') ? 'riscaldamento' : (str_contains(strtolower($cTab), 'stra') ? 'straordinaria' : 'ordinaria'),
|
|
'gruppo' => $this->mapTipoToGruppo($cTab),
|
|
'id' => null,
|
|
'codice' => $cTab,
|
|
'nome' => $nomeTab,
|
|
'millesimi' => $mm,
|
|
];
|
|
}
|
|
}
|
|
|
|
return $res;
|
|
} catch (\Throwable $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private function extractMdbTableRows(string $mdbPath, string $tableName): array
|
|
{
|
|
$proc = new \Symfony\Component\Process\Process(['mdb-export', $mdbPath, $tableName]);
|
|
$proc->run();
|
|
|
|
if (! $proc->isSuccessful()) {
|
|
return [];
|
|
}
|
|
|
|
$csvContent = $proc->getOutput();
|
|
$stream = fopen('php://memory', 'r+');
|
|
fwrite($stream, $csvContent);
|
|
rewind($stream);
|
|
|
|
$header = fgetcsv($stream);
|
|
if (! $header) {
|
|
fclose($stream);
|
|
return [];
|
|
}
|
|
|
|
$rows = [];
|
|
while (($data = fgetcsv($stream)) !== false) {
|
|
if (count($data) === count($header)) {
|
|
$rows[] = array_combine($header, $data);
|
|
}
|
|
}
|
|
|
|
fclose($stream);
|
|
return $rows;
|
|
}
|
|
|
|
public function setMillesimiGruppo(string $gruppo): void
|
|
{
|
|
$allowed = ['ordinarie', 'riscaldamento', 'straordinarie'];
|
|
if (! in_array($gruppo, $allowed, true)) {
|
|
return;
|
|
}
|
|
|
|
$this->millesimiGruppo = $gruppo;
|
|
}
|
|
|
|
protected function mapTipoToGruppo(string $tipo): string
|
|
{
|
|
$t = strtolower(trim($tipo));
|
|
|
|
if ($t === 'r' || str_contains($t, 'risc')) {
|
|
return 'riscaldamento';
|
|
}
|
|
|
|
if ($t === 's' || str_contains($t, 'stra')) {
|
|
return 'straordinarie';
|
|
}
|
|
|
|
return 'ordinarie';
|
|
}
|
|
|
|
protected function hydrateDiritti(): void
|
|
{
|
|
$formatDate = static function ($value) {
|
|
if (empty($value)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return Carbon::parse($value)->format('d/m/Y');
|
|
} catch (\Throwable $e) {
|
|
return $value;
|
|
}
|
|
};
|
|
|
|
$this->dirittiProprieta = Proprieta::with('soggetto')
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->orderByDesc('percentuale_possesso')
|
|
->get()
|
|
->map(function (Proprieta $diritto) use ($formatDate) {
|
|
$soggetto = $diritto->soggetto;
|
|
$nome = $soggetto?->ragione_sociale
|
|
?: trim(($soggetto?->cognome ?? '') . ' ' . ($soggetto?->nome ?? ''));
|
|
|
|
if ($nome === '') {
|
|
$nome = $soggetto?->nome ?? ($soggetto ? 'Soggetto #' . $soggetto->id : 'Soggetto non definito');
|
|
}
|
|
|
|
$percentuale = is_numeric($diritto->percentuale_possesso)
|
|
? (float) $diritto->percentuale_possesso
|
|
: null;
|
|
|
|
$codiceFiscale = $soggetto?->codice_fiscale;
|
|
if (str_starts_with((string) $nome, 'Soggetto #') && empty($codiceFiscale) && $percentuale === null) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'id' => $diritto->id,
|
|
'soggetto_id' => $soggetto?->id,
|
|
'nome' => $nome,
|
|
'codice_fiscale' => $codiceFiscale,
|
|
'tipo' => $diritto->tipo_diritto,
|
|
'percentuale' => $percentuale,
|
|
'percentuale_label' => $percentuale !== null
|
|
? number_format($percentuale, 2, ',', '.')
|
|
: null,
|
|
'data_inizio' => $formatDate($diritto->data_inizio),
|
|
'data_fine' => $formatDate($diritto->data_fine),
|
|
];
|
|
})
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
protected function hydrateRelazioni(): void
|
|
{
|
|
$this->legacyCondominRow = null;
|
|
|
|
$formatDate = static function ($value) {
|
|
if (empty($value)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return Carbon::parse($value)->format('d/m/Y');
|
|
} catch (\Throwable $e) {
|
|
return $value;
|
|
}
|
|
};
|
|
|
|
$labelsRuoli = [
|
|
'condomino' => 'Condomino',
|
|
'inquilino' => 'Inquilino',
|
|
'fornitore' => 'Fornitore',
|
|
'altro' => 'Altro',
|
|
];
|
|
|
|
$labelsRuoli = [
|
|
'condomino' => 'Condomino',
|
|
'inquilino' => 'Inquilino',
|
|
'fornitore' => 'Fornitore',
|
|
'altro' => 'Altro',
|
|
];
|
|
|
|
$tipoDirittoLabels = [
|
|
'piena_proprieta' => 'Condomino (piena_proprietà)',
|
|
'proprieta' => 'Condomino (proprietà)',
|
|
'comproprieta' => 'Condomino (comproprietà)',
|
|
'nuda_proprieta' => 'Condomino (nuda proprietà)',
|
|
'usufrutto' => 'Condomino (usufrutto)',
|
|
'usufruttuario' => 'Condomino (usufrutto)',
|
|
];
|
|
|
|
$legacyRow = $this->getLegacyCondominRow();
|
|
$legacyOwnerStart = $legacyRow?->subentrato_dal ?? null;
|
|
$legacyOwnerEnd = $legacyRow?->attivo_fino_al ?? null;
|
|
$legacyInqStart = $legacyRow?->inquil_dal ?? null;
|
|
$legacyInqEnd = $legacyRow?->inquil_al ?? null;
|
|
$legacyOwnerName = trim((string) ($legacyRow?->nom_cond ?? ''));
|
|
$legacyInqName = trim((string) ($legacyRow?->inquil_nome ?? ''));
|
|
|
|
$proprietari = collect($this->dirittiProprieta)
|
|
->filter(function (array $d): bool {
|
|
$tipo = strtolower(trim((string) ($d['tipo'] ?? '')));
|
|
|
|
return $tipo === '' || in_array($tipo, $this->ownershipTipoDiritti(), true);
|
|
})
|
|
->filter(fn(array $d) => ! empty($d['soggetto_id']))
|
|
->map(function (array $d) use ($tipoDirittoLabels): array {
|
|
$tipoRaw = strtolower(trim((string) ($d['tipo'] ?? 'condomino')));
|
|
$tipoLabel = $tipoDirittoLabels[$tipoRaw] ?? 'Condomino';
|
|
|
|
$quota = $d['percentuale'];
|
|
$quota = is_numeric($quota) ? (float) $quota : null;
|
|
|
|
return [
|
|
'id' => (int) ($d['id'] ?? 0),
|
|
'soggetto_id' => (int) ($d['soggetto_id'] ?? 0),
|
|
'nome' => (string) ($d['nome'] ?? '—'),
|
|
'codice_fiscale' => $d['codice_fiscale'] ?? null,
|
|
'tipo_raw' => 'condomino',
|
|
'tipo' => $tipoLabel,
|
|
'ruolo_rate' => 'C',
|
|
'quota' => $quota,
|
|
'quota_label' => $quota !== null ? number_format($quota, 2, ',', '.') : null,
|
|
'data_inizio' => $d['data_inizio'] ?? null,
|
|
'data_fine' => $d['data_fine'] ?? null,
|
|
'attivo' => true,
|
|
];
|
|
})
|
|
->unique(fn(array $d) => (int) ($d['soggetto_id'] ?? 0))
|
|
->values();
|
|
|
|
$activeAnno = \App\Support\AnnoGestioneContext::resolveActiveAnno(\Illuminate\Support\Facades\Auth::user());
|
|
$today = sprintf('%04d-12-31', $activeAnno);
|
|
$startOfYear = sprintf('%04d-01-01', $activeAnno);
|
|
|
|
$purList = PersonaUnitaRelazione::with('persona')
|
|
->where('unita_id', $this->unita->id)
|
|
->get();
|
|
$cfDatesMap = [];
|
|
foreach ($purList as $pur) {
|
|
$cf = strtoupper(trim((string) ($pur->persona?->codice_fiscale ?? '')));
|
|
if ($cf !== '') {
|
|
$cfDatesMap[$cf] = [
|
|
'data_inizio' => $pur->data_inizio ?: $pur->subentrato_dal,
|
|
'data_fine' => $pur->data_fine ?: $pur->attivo_fino_al,
|
|
];
|
|
}
|
|
}
|
|
|
|
$relazioniMapped = RubricaRuolo::query()
|
|
->with(['contatto'])
|
|
->where('stabile_id', $this->stabileId)
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $q) use ($today) {
|
|
$q->whereNull('data_inizio')->orWhere('data_inizio', '<=', $today);
|
|
})
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $q) use ($startOfYear) {
|
|
$q->whereNull('data_fine')->orWhere('data_fine', '>=', $startOfYear);
|
|
})
|
|
->orderByDesc('is_preferito')
|
|
->orderByDesc('data_inizio')
|
|
->orderByDesc('id')
|
|
->limit(200)
|
|
->get()
|
|
->filter(fn(RubricaRuolo $relazione) => (bool) $relazione->contatto)
|
|
->map(function (RubricaRuolo $relazione) use ($formatDate, $labelsRuoli, $cfDatesMap) {
|
|
$contatto = $relazione->contatto;
|
|
$nome = trim((string) ($contatto?->nome_completo ?? ''));
|
|
if ($nome === '') {
|
|
$nome = $contatto ? 'Contatto #' . $contatto->id : 'Contatto non definito';
|
|
}
|
|
|
|
$cf = strtoupper(trim((string) ($contatto?->codice_fiscale ?? '')));
|
|
$purDates = $cf !== '' ? ($cfDatesMap[$cf] ?? null) : null;
|
|
$dInizioRaw = $relazione->data_inizio ?: ($purDates['data_inizio'] ?? null);
|
|
$dFineRaw = $relazione->data_fine ?: ($purDates['data_fine'] ?? null);
|
|
|
|
$tipoRaw = strtolower(trim((string) ($relazione->ruolo_standard ?? '')));
|
|
$customRaw = trim((string) ($relazione->ruolo_custom ?? ''));
|
|
$hasMeaningfulCustomRole = $customRaw !== '' && ! in_array(strtolower($customRaw), ['p', 'i', 'c', 'u'], true);
|
|
$tipoLabel = $labelsRuoli[$tipoRaw] ?? ($tipoRaw !== '' ? $tipoRaw : 'Altro');
|
|
if ($hasMeaningfulCustomRole) {
|
|
$tipoLabel .= ' (' . $customRaw . ')';
|
|
}
|
|
|
|
$quota = data_get($relazione->meta, 'right_percent');
|
|
if ($quota === null) {
|
|
$quota = data_get($relazione->meta, 'percentuale_possesso');
|
|
}
|
|
if (is_string($quota)) {
|
|
$quota = str_replace(['%', ' '], ['', ''], $quota);
|
|
$quota = str_replace(',', '.', $quota);
|
|
}
|
|
$quota = is_numeric($quota) ? (float) $quota : null;
|
|
|
|
$ruoloRate = null;
|
|
if ($tipoRaw === 'inquilino') {
|
|
$ruoloRate = 'I';
|
|
} elseif ($tipoRaw === 'condomino') {
|
|
$ruoloRate = 'C';
|
|
}
|
|
|
|
return [
|
|
'id' => (int) $relazione->id,
|
|
'persona_id' => (int) ($relazione->rubrica_id ?? 0),
|
|
'legacy_persona_id' => is_numeric(data_get($relazione->meta, 'persona_id_orig')) ? (int) data_get($relazione->meta, 'persona_id_orig') : null,
|
|
'rubrica_id' => (int) ($relazione->rubrica_id ?? 0),
|
|
'nome' => $nome,
|
|
'codice_fiscale' => $contatto?->codice_fiscale,
|
|
'tipo_raw' => $tipoRaw,
|
|
'tipo' => $tipoLabel,
|
|
'ruolo_custom' => $hasMeaningfulCustomRole ? $customRaw : null,
|
|
'has_custom_role' => $hasMeaningfulCustomRole,
|
|
'ruolo_rate' => $ruoloRate,
|
|
'quota' => $quota,
|
|
'quota_label' => $quota !== null ? number_format($quota, 2, ',', '.') : null,
|
|
'data_inizio' => $formatDate($dInizioRaw),
|
|
'data_fine' => $formatDate($dFineRaw),
|
|
'attivo' => (bool) $relazione->is_attivo,
|
|
'telefono' => trim((string) ($contatto?->telefono_cellulare ?: $contatto?->telefono_ufficio ?: $contatto?->telefono_casa ?: '')),
|
|
'email' => trim((string) ($contatto?->email ?? '')),
|
|
'rubrica_url' => $contatto ? RubricaUniversaleScheda::getUrl(['record' => (int) $contatto->id], panel: 'admin-filament'): null,
|
|
'riceve_comunicazioni' => false,
|
|
'riceve_convocazioni' => false,
|
|
'vota_assemblea' => false,
|
|
];
|
|
});
|
|
|
|
$fallbackRelations = PersonaUnitaRelazione::with(['persona', 'tipoRelazioneDettagli'])
|
|
->where('unita_id', $this->unita->id)
|
|
->orderByDesc('attivo')
|
|
->orderBy('tipo_relazione')
|
|
->orderByDesc('quota_relazione')
|
|
->get()
|
|
->map(function (PersonaUnitaRelazione $relazione) use ($formatDate) {
|
|
$persona = $relazione->persona;
|
|
$nome = $persona?->nome_completo ?? $persona?->ragione_sociale ?? trim(($persona?->cognome ?? '') . ' ' . ($persona?->nome ?? ''));
|
|
|
|
if ($nome === '') {
|
|
$nome = $persona ? 'Persona #' . $persona->id : 'Persona non definita';
|
|
}
|
|
|
|
$quota = is_numeric($relazione->quota_relazione)
|
|
? (float) $relazione->quota_relazione
|
|
: null;
|
|
|
|
return [
|
|
'id' => (int) $relazione->id,
|
|
'persona_id' => (int) ($persona?->id ?? 0),
|
|
'legacy_persona_id' => (int) ($persona?->id ?? 0),
|
|
'rubrica_id' => null,
|
|
'nome' => $nome,
|
|
'codice_fiscale' => $persona?->codice_fiscale,
|
|
'tipo_raw' => strtolower(trim((string) ($relazione->tipo_relazione ?? ''))),
|
|
'tipo' => $relazione->tipoRelazioneDettagli?->descrizione ?? ucfirst(str_replace('_', ' ', $relazione->tipo_relazione ?? '')),
|
|
'ruolo_custom' => null,
|
|
'has_custom_role' => false,
|
|
'ruolo_rate' => $relazione->ruolo_rate ?: PersonaUnitaRelazione::deriveRuoloRate($relazione->tipo_relazione),
|
|
'quota' => $quota,
|
|
'quota_label' => $quota !== null ? number_format($quota, 2, ',', '.') : null,
|
|
'data_inizio' => $formatDate($relazione->data_inizio ?: $relazione->subentrato_dal),
|
|
'data_fine' => $formatDate($relazione->data_fine ?: $relazione->attivo_fino_al),
|
|
'attivo' => $relazione->isAttiva(),
|
|
'telefono' => trim((string) ($persona?->telefono ?? '')),
|
|
'email' => trim((string) ($persona?->email ?? '')),
|
|
'rubrica_url' => null,
|
|
'riceve_comunicazioni' => (bool) $relazione->riceve_comunicazioni,
|
|
'riceve_convocazioni' => (bool) $relazione->riceve_convocazioni,
|
|
'vota_assemblea' => (bool) $relazione->vota_assemblea,
|
|
];
|
|
});
|
|
|
|
$fallbackKeys = $fallbackRelations
|
|
->map(fn(array $rel) => strtolower(trim((string) ($rel['tipo_raw'] ?? ''))) . '|' . $this->relationIdentityKey($rel))
|
|
->all();
|
|
|
|
$relazioniMapped = $fallbackRelations
|
|
->concat($relazioniMapped->reject(function (array $rel) use ($fallbackKeys): bool {
|
|
$key = strtolower(trim((string) ($rel['tipo_raw'] ?? ''))) . '|' . $this->relationIdentityKey($rel);
|
|
|
|
return in_array($key, $fallbackKeys, true);
|
|
}))
|
|
->values();
|
|
|
|
$proprietariDaRelazioni = $relazioniMapped
|
|
->filter(function ($rel) use ($activeAnno) {
|
|
$isProp = in_array(strtolower($rel['tipo_raw'] ?? ''), [
|
|
'condomino',
|
|
'proprietario',
|
|
'comproprietario',
|
|
'nudo_proprietario',
|
|
'usufruttuario',
|
|
'usufrutto',
|
|
], true);
|
|
|
|
if (! $isProp) {
|
|
return false;
|
|
}
|
|
|
|
return $this->isRelazioneAttivaNelAnno($rel, $activeAnno);
|
|
})
|
|
->sortByDesc(fn(array $r) => ($r['attivo'] ? 1 : 0))
|
|
->unique(fn(array $r) => $this->relationIdentityKey($r))
|
|
->values();
|
|
|
|
if ($proprietariDaRelazioni->isNotEmpty()) {
|
|
$proprietari = $proprietariDaRelazioni;
|
|
}
|
|
|
|
$inquilini = $relazioniMapped
|
|
->filter(function ($rel) use ($activeAnno) {
|
|
$isInq = in_array(strtolower($rel['tipo_raw'] ?? ''), [
|
|
'inquilino',
|
|
'locatario',
|
|
'conduttore',
|
|
], true);
|
|
|
|
if (! $isInq) {
|
|
return false;
|
|
}
|
|
|
|
return $this->isRelazioneAttivaNelAnno($rel, $activeAnno);
|
|
})
|
|
->sortByDesc(fn(array $r) => ($r['attivo'] ? 1 : 0))
|
|
->unique(fn(array $r) => $this->relationIdentityKey($r))
|
|
->values();
|
|
|
|
$archivioProprietari = collect($this->loadCurrentArchiveRelazioni('C'))
|
|
->concat($this->loadCurrentLegacyComproprietariRelazioni())
|
|
->unique(fn(array $row) => $this->relationIdentityKey($row))
|
|
->values();
|
|
if ($archivioProprietari->isNotEmpty()) {
|
|
$proprietari = $proprietari->isNotEmpty() ? $proprietari : $archivioProprietari;
|
|
}
|
|
|
|
$archivioInquilini = collect($this->loadCurrentArchiveRelazioni('I'))
|
|
->unique(fn(array $row) => $this->relationIdentityKey($row))
|
|
->values();
|
|
if ($archivioInquilini->isNotEmpty()) {
|
|
$inquilini = $inquilini->isNotEmpty() ? $inquilini : $archivioInquilini;
|
|
}
|
|
|
|
if ($inquilini->isNotEmpty()) {
|
|
$inquiliniIds = $inquilini
|
|
->pluck('persona_id')
|
|
->filter(fn($id) => is_numeric($id) && (int) $id > 0)
|
|
->map(fn($id) => (int) $id)
|
|
->values()
|
|
->all();
|
|
$inquiliniCf = $inquilini
|
|
->pluck('codice_fiscale')
|
|
->filter()
|
|
->map(fn($cf) => strtoupper(trim((string) $cf)))
|
|
->filter(fn($cf) => $cf !== '')
|
|
->values()
|
|
->all();
|
|
|
|
$proprietari = $proprietari->reject(function (array $r) use ($inquiliniIds, $inquiliniCf): bool {
|
|
$id = is_numeric($r['soggetto_id'] ?? null) ? (int) $r['soggetto_id'] : 0;
|
|
if ($id > 0 && in_array($id, $inquiliniIds, true)) {
|
|
return true;
|
|
}
|
|
|
|
$cf = strtoupper(trim((string) ($r['codice_fiscale'] ?? '')));
|
|
return $cf !== '' && in_array($cf, $inquiliniCf, true);
|
|
})->values();
|
|
}
|
|
|
|
if ($proprietari->isNotEmpty() && $inquilini->isNotEmpty()) {
|
|
$proprietariCf = $proprietari
|
|
->pluck('codice_fiscale')
|
|
->filter()
|
|
->map(fn($cf) => strtoupper(trim((string) $cf)))
|
|
->filter(fn($cf) => $cf !== '')
|
|
->values()
|
|
->all();
|
|
$proprietariNomi = $proprietari
|
|
->pluck('nome')
|
|
->filter()
|
|
->map(fn($n) => strtoupper(trim((string) $n)))
|
|
->filter(fn($n) => $n !== '')
|
|
->values()
|
|
->all();
|
|
|
|
$inquilini = $inquilini->reject(function (array $i) use ($proprietariCf, $proprietariNomi): bool {
|
|
$cf = strtoupper(trim((string) ($i['codice_fiscale'] ?? '')));
|
|
if ($cf !== '' && in_array($cf, $proprietariCf, true)) {
|
|
return true;
|
|
}
|
|
$nome = strtoupper(trim((string) ($i['nome'] ?? '')));
|
|
if ($nome !== '' && in_array($nome, $proprietariNomi, true)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
})->values();
|
|
}
|
|
|
|
$existingKeys = $proprietari->concat($inquilini)
|
|
->map(fn(array $r) => $this->relationIdentityKey($r))
|
|
->all();
|
|
|
|
$altri = $relazioniMapped
|
|
->filter(function ($rel) use ($existingKeys) {
|
|
$key = $this->relationIdentityKey($rel);
|
|
if (in_array($key, $existingKeys, true)) {
|
|
return false;
|
|
}
|
|
|
|
$tipoRaw = strtolower(trim((string) ($rel['tipo_raw'] ?? '')));
|
|
if (in_array($tipoRaw, [
|
|
'condomino',
|
|
'proprietario',
|
|
'comproprietario',
|
|
'nudo_proprietario',
|
|
'usufruttuario',
|
|
'usufrutto',
|
|
'inquilino',
|
|
'locatario',
|
|
'conduttore',
|
|
], true)) {
|
|
return false;
|
|
}
|
|
|
|
return (bool) ($rel['attivo'] ?? false);
|
|
})
|
|
->unique(fn(array $r) => $this->relationIdentityKey($r))
|
|
->values();
|
|
|
|
$isRelazioneCorrenteNelAnno = function (array $r, int $targetAnno): bool {
|
|
$dFine = $r['data_fine'] ?? null;
|
|
$endY = null;
|
|
if (! empty($dFine) && preg_match('/(\d{4})/', (string) $dFine, $m)) {
|
|
$endY = (int) $m[1];
|
|
}
|
|
if ($endY !== null && $endY < $targetAnno) {
|
|
return false;
|
|
}
|
|
if ($endY !== null && $endY === $targetAnno && ! ($r['attivo'] ?? false)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
$relazioniStoriche = [];
|
|
if (! $this->mostraStorico) {
|
|
$relazioniStoriche = $proprietari->filter(fn($r) => ! $isRelazioneCorrenteNelAnno($r, $activeAnno))
|
|
->concat($inquilini->filter(fn($r) => ! $isRelazioneCorrenteNelAnno($r, $activeAnno)))
|
|
->concat($relazioniMapped->filter(fn($r) => ! ($r['attivo'] ?? false)))
|
|
->unique(fn(array $r) => $this->relationIdentityKey($r))
|
|
->values()
|
|
->all();
|
|
|
|
$proprietari = $proprietari->filter(fn($r) => $isRelazioneCorrenteNelAnno($r, $activeAnno))->values();
|
|
$inquilini = $inquilini->filter(fn($r) => $isRelazioneCorrenteNelAnno($r, $activeAnno))->values();
|
|
}
|
|
|
|
$legacyRow = $this->getLegacyCondominRow();
|
|
$legacyOwnerName = trim((string) ($legacyRow?->nom_cond ?? $legacyRow?->proprietario ?? ''));
|
|
if ($legacyOwnerName !== '') {
|
|
$ownerNameUpper = strtoupper($legacyOwnerName);
|
|
$matchedOwner = $proprietari->filter(function (array $p) use ($ownerNameUpper): bool {
|
|
$nome = strtoupper(trim((string) ($p['nome'] ?? '')));
|
|
return $nome !== '' && (str_contains($nome, $ownerNameUpper) || str_contains($ownerNameUpper, $nome));
|
|
})->values();
|
|
|
|
if ($matchedOwner->isNotEmpty()) {
|
|
$unmatchedOwners = $proprietari->reject(function (array $p) use ($ownerNameUpper): bool {
|
|
$nome = strtoupper(trim((string) ($p['nome'] ?? '')));
|
|
return $nome !== '' && (str_contains($nome, $ownerNameUpper) || str_contains($ownerNameUpper, $nome));
|
|
})->values()->all();
|
|
|
|
$proprietari = $matchedOwner;
|
|
$relazioniStoriche = array_values(array_unique(array_merge($relazioniStoriche, $unmatchedOwners), SORT_REGULAR));
|
|
} else {
|
|
$legacyOwnerRecord = [
|
|
'persona_id' => null,
|
|
'nome' => $legacyOwnerName,
|
|
'codice_fiscale'=> trim((string) ($legacyRow?->cond_cod_fisc ?? $legacyRow?->codice_fiscale ?? '')),
|
|
'tipo_relazione'=> 'proprietario',
|
|
'ruolo_label' => 'Proprietario 100%',
|
|
'quota' => 100.0,
|
|
'quota_label' => '100,00',
|
|
'data_inizio' => 'Gestione ' . $activeAnno,
|
|
'data_fine' => null,
|
|
'attivo' => true,
|
|
'fonte' => 'legacy_condomin_mirror',
|
|
];
|
|
$relazioniStoriche = array_values(array_unique(array_merge($relazioniStoriche, $proprietari->all()), SORT_REGULAR));
|
|
$proprietari = collect([$legacyOwnerRecord]);
|
|
}
|
|
}
|
|
|
|
$legacyInqName = trim((string) ($legacyRow?->inquil_nome ?? $legacyRow?->inquilino ?? ''));
|
|
if ($legacyInqName !== '') {
|
|
$inqNameUpper = strtoupper($legacyInqName);
|
|
|
|
$matchedInquilini = $inquilini->filter(function (array $i) use ($inqNameUpper): bool {
|
|
$nome = strtoupper(trim((string) ($i['nome'] ?? '')));
|
|
return $nome !== '' && (str_contains($nome, $inqNameUpper) || str_contains($inqNameUpper, $nome));
|
|
})->values();
|
|
|
|
$unmatchedInquilini = $inquilini->reject(function (array $i) use ($inqNameUpper): bool {
|
|
$nome = strtoupper(trim((string) ($i['nome'] ?? '')));
|
|
return $nome !== '' && (str_contains($nome, $inqNameUpper) || str_contains($inqNameUpper, $nome));
|
|
})->values()->all();
|
|
|
|
if ($matchedInquilini->isNotEmpty()) {
|
|
$inquilini = $matchedInquilini;
|
|
$relazioniStoriche = array_values(array_unique(array_merge($relazioniStoriche, $unmatchedInquilini), SORT_REGULAR));
|
|
}
|
|
}
|
|
|
|
$inqArr = $inquilini->values()->all();
|
|
$inqCount = count($inqArr);
|
|
if ($inqCount > 0) {
|
|
$equalQuota = round(100.0 / $inqCount, 2);
|
|
$startOfYearDate = '01/01/' . $activeAnno;
|
|
|
|
foreach ($inqArr as $idx => $inq) {
|
|
if ($inq['quota'] === null || (float) $inq['quota'] <= 0 || $inqCount === 1) {
|
|
$inqQuota = $inqCount === 1 ? 100.0 : $equalQuota;
|
|
$inqArr[$idx]['quota'] = $inqQuota;
|
|
$inqArr[$idx]['quota_label'] = number_format($inqQuota, 2, ',', '.');
|
|
}
|
|
$dInizio = trim((string) ($inq['data_inizio'] ?? ''));
|
|
if ($dInizio === '' || $dInizio === 'Da 01/01/2000' || $dInizio === '01/01/2000') {
|
|
$inqArr[$idx]['data_inizio'] = $startOfYearDate;
|
|
}
|
|
}
|
|
}
|
|
|
|
$activeOwnerNames = $proprietari->pluck('nome')->map(fn($n) => strtoupper(trim((string) $n)))->all();
|
|
$fallbackHist = (array) ($this->populateNominativiStoriciFallback() ?? []);
|
|
$relazioniStoriche = collect(array_merge($relazioniStoriche, $fallbackHist))
|
|
->reject(fn($r) => in_array(strtoupper(trim((string) ($r['nome'] ?? ''))), $activeOwnerNames, true))
|
|
->unique(fn(array $r) => $this->relationIdentityKey($r))
|
|
->values()
|
|
->all();
|
|
|
|
$this->relazioniPerTipo = [
|
|
'proprietari' => $proprietari->values()->all(),
|
|
'inquilini' => $inqArr,
|
|
'altri' => $altri->values()->all(),
|
|
'storico' => ! empty($relazioniStoriche) ? $relazioniStoriche : (array) ($this->nominativiStorici ?? []),
|
|
];
|
|
}
|
|
|
|
/** @return array<int, string> */
|
|
private function ownershipTipoDiritti(): array
|
|
{
|
|
return [
|
|
'piena_proprieta',
|
|
'proprieta',
|
|
'comproprieta',
|
|
'nuda_proprieta',
|
|
'usufrutto',
|
|
'usufruttuario',
|
|
];
|
|
}
|
|
|
|
private function isRelazioneAttivaNelAnno(array $rel, int $targetAnno): bool
|
|
{
|
|
$note = (string) ($rel['note_relazione'] ?? '');
|
|
if (! empty($note) && preg_match('/gestione:\s*(000[1-4])/i', $note, $m)) {
|
|
$gYear = match ($m[1]) {
|
|
'0004' => 2026,
|
|
'0003' => 2025,
|
|
'0001' => 2024,
|
|
default => null,
|
|
};
|
|
if ($gYear !== null && $gYear !== $targetAnno) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$dInizio = $rel['data_inizio'] ?? null;
|
|
$dFine = $rel['data_fine'] ?? null;
|
|
|
|
$startY = null;
|
|
if (! empty($dInizio) && preg_match('/(\d{4})/', (string) $dInizio, $m)) {
|
|
$startY = (int) $m[1];
|
|
}
|
|
|
|
$endY = null;
|
|
if (! empty($dFine) && preg_match('/(\d{4})/', (string) $dFine, $m)) {
|
|
$endY = (int) $m[1];
|
|
}
|
|
|
|
if ($startY !== null && $startY > $targetAnno) {
|
|
return false;
|
|
}
|
|
|
|
if ($endY !== null && $endY < $targetAnno) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function extractLegacyRoleFromRateNote(string $note): ?string
|
|
{
|
|
$note = trim($note);
|
|
if ($note === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach (['tipo_soggetto', 'cond_inq', 'o_r_s'] as $field) {
|
|
if (preg_match('/\b' . preg_quote($field, '/') . '=([CI])\b/', $note, $m)) {
|
|
return (string) ($m[1] ?? null);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function extractLegacyYearFromRateNote(string $note): ?string
|
|
{
|
|
if (preg_match('/\banno_emissione=([^|\s]+)/', $note, $m)) {
|
|
$value = trim((string) ($m[1] ?? ''));
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function extractAnnoGestioneFromRateNote(string $note): ?string
|
|
{
|
|
if (preg_match('/\banno_gestione=([^|]+)/', $note, $m)) {
|
|
$value = trim((string) ($m[1] ?? ''));
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function extractAvvisoCodeFromRateNote(string $note): ?string
|
|
{
|
|
if (preg_match('/\bavviso_code=([^|\s]+)/', $note, $m)) {
|
|
$value = trim((string) ($m[1] ?? ''));
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function buildLegacyAvvisoCode(?string $gestioneLabel, ?int $numeroMese, ?string $dataEmissione = null): ?string
|
|
{
|
|
if ($numeroMese === null || $numeroMese <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$year = null;
|
|
if ($dataEmissione !== null && trim($dataEmissione) !== '') {
|
|
try {
|
|
$year = (int) Carbon::parse($dataEmissione)->format('Y');
|
|
} catch (\Throwable $e) {
|
|
$year = null;
|
|
}
|
|
}
|
|
|
|
if ($year === null) {
|
|
$year = $this->extractEffectiveGestioneEndYear($gestioneLabel);
|
|
}
|
|
|
|
if ($year === null) {
|
|
return null;
|
|
}
|
|
|
|
return sprintf('%04d%02d', $year, $numeroMese);
|
|
}
|
|
|
|
private function extractEffectiveGestioneEndYear(?string $gestioneLabel): ?int
|
|
{
|
|
$value = trim((string) ($gestioneLabel ?? ''));
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/(19|20)(\d{2})\s*\/\s*(\d{2,4})/', $value, $m)) {
|
|
$startYear = (int) ($m[1] . $m[2]);
|
|
$suffix = trim((string) ($m[3] ?? ''));
|
|
if (strlen($suffix) === 2) {
|
|
$century = (int) floor($startYear / 100) * 100;
|
|
return $century + (int) $suffix;
|
|
}
|
|
|
|
return (int) $suffix;
|
|
}
|
|
|
|
if (preg_match('/(19|20)\d{2}/', $value, $m)) {
|
|
return (int) $m[0];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function makeConguaglioDedupKey(string $role, string $legacyYear, float $amount): string
|
|
{
|
|
return strtoupper(trim($role)) . '|' . trim($legacyYear) . '|' . number_format(round($amount, 2), 2, '.', '');
|
|
}
|
|
|
|
private function getLegacyCondominRow(): ?object
|
|
{
|
|
if ($this->legacyCondominRow !== null) {
|
|
return $this->legacyCondominRow;
|
|
}
|
|
|
|
if (! $this->hasImportConnection()) {
|
|
return $this->legacyCondominRow = null;
|
|
}
|
|
|
|
$tableName = DbSchema::connection('gescon_import')->hasTable('condomin_mirror') ? 'condomin_mirror' : 'condomin';
|
|
if (! DbSchema::connection('gescon_import')->hasTable($tableName)) {
|
|
return $this->legacyCondominRow = null;
|
|
}
|
|
|
|
if (! $this->unita) {
|
|
return $this->legacyCondominRow = null;
|
|
}
|
|
|
|
$codStabile = trim((string) ($this->unita->stabile?->codice_stabile ?? ''));
|
|
if ($codStabile === '') {
|
|
return $this->legacyCondominRow = null;
|
|
}
|
|
|
|
$scala = trim((string) ($this->unita->scala ?? ''));
|
|
$interno = trim((string) ($this->unita->interno ?? ''));
|
|
|
|
if ($interno === '') {
|
|
return $this->legacyCondominRow = null;
|
|
}
|
|
|
|
$activeAnno = \App\Support\AnnoGestioneContext::resolveActiveAnno(\Illuminate\Support\Facades\Auth::user());
|
|
$legacyYear = match ($activeAnno) {
|
|
2026 => '0004',
|
|
2025 => '0003',
|
|
2024 => '0001',
|
|
default => sprintf('%04d', $activeAnno),
|
|
};
|
|
|
|
$yearCol = DbSchema::connection('gescon_import')->hasColumn($tableName, 'source_year') ? 'source_year' : 'legacy_year';
|
|
|
|
$query = DB::connection('gescon_import')
|
|
->table($tableName)
|
|
->where('cod_stabile', $codStabile)
|
|
->where('interno', $interno);
|
|
|
|
if ($scala !== '') {
|
|
$query->where('scala', $scala);
|
|
}
|
|
|
|
if (DbSchema::connection('gescon_import')->hasColumn($tableName, $yearCol)) {
|
|
$query->where($yearCol, $legacyYear);
|
|
}
|
|
|
|
$desiredCols = [
|
|
'legacy_id_cond',
|
|
'id_cond',
|
|
'cod_cond',
|
|
'subentrato_dal',
|
|
'attivo_fino_al',
|
|
'inquil_dal',
|
|
'inquil_al',
|
|
'nom_cond',
|
|
'proprietario',
|
|
'inquilino',
|
|
'codice_fiscale',
|
|
'cond_cod_fisc',
|
|
'inquil_nome',
|
|
'inquil_cod_fisc',
|
|
'source_year',
|
|
'legacy_year',
|
|
];
|
|
$actualCols = \Illuminate\Support\Facades\Schema::connection('gescon_import')->getColumnListing($tableName);
|
|
$selectCols = array_intersect($desiredCols, $actualCols);
|
|
|
|
$row = $query
|
|
->orderByDesc('id')
|
|
->first($selectCols);
|
|
|
|
if (! $row) {
|
|
return $this->legacyCondominRow = null;
|
|
}
|
|
|
|
$row->subentrato_dal = $this->normalizeLegacyDateValue($row->subentrato_dal ?? null);
|
|
$row->attivo_fino_al = $this->normalizeLegacyDateValue($row->attivo_fino_al ?? null);
|
|
$row->inquil_dal = $this->normalizeLegacyDateValue($row->inquil_dal ?? null);
|
|
$row->inquil_al = $this->normalizeLegacyDateValue($row->inquil_al ?? null);
|
|
|
|
return $this->legacyCondominRow = $row;
|
|
}
|
|
|
|
private function normalizeLegacyDateValue(mixed $value): mixed
|
|
{
|
|
if (empty($value)) {
|
|
return null;
|
|
}
|
|
|
|
$normalized = null;
|
|
if (is_string($value)) {
|
|
$raw = trim($value);
|
|
if ($raw === '') {
|
|
return null;
|
|
}
|
|
if (preg_match('/^\d{2}\/\d{2}\/\d{2}$/', $raw)) {
|
|
try {
|
|
$normalized = Carbon::createFromFormat('d/m/y', $raw)->format('Y-m-d');
|
|
} catch (\Throwable) {
|
|
$normalized = $raw;
|
|
}
|
|
} elseif (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $raw)) {
|
|
try {
|
|
$normalized = Carbon::createFromFormat('d/m/Y', $raw)->format('Y-m-d');
|
|
} catch (\Throwable) {
|
|
$normalized = $raw;
|
|
}
|
|
} else {
|
|
$normalized = $raw;
|
|
}
|
|
} else {
|
|
$normalized = $value;
|
|
}
|
|
|
|
if ($normalized) {
|
|
try {
|
|
$parsed = Carbon::parse($normalized);
|
|
if ($parsed->format('Y-m-d') === '1970-01-01') {
|
|
return null;
|
|
}
|
|
} catch (\Throwable) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
protected function hasImportConnection(): bool
|
|
{
|
|
if (! config()->has('database.connections.gescon_import')) {
|
|
return false;
|
|
}
|
|
try {
|
|
DB::connection('gescon_import')->getPdo();
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function parseLegacyDateToCarbon(mixed $value): ?Carbon
|
|
{
|
|
$norm = $this->normalizeLegacyDateValue($value);
|
|
if (empty($norm)) {
|
|
return null;
|
|
}
|
|
try {
|
|
return Carbon::parse($norm);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected function hydrateRipartizioni(): void
|
|
{
|
|
if (! DbSchema::hasTable('dettaglio_ripartizione_spese')) {
|
|
$this->ripartizioniPerTabella = $this->fallbackVociSpesa();
|
|
return;
|
|
}
|
|
|
|
$details = DettaglioRipartizioneSpese::with([
|
|
'ripartizioneSpese.voceSpesa',
|
|
'ripartizioneSpese.tabellaMillesimale',
|
|
])
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->orderByDesc('created_at')
|
|
->limit(100)
|
|
->get();
|
|
|
|
$grouped = $details->map(function (DettaglioRipartizioneSpese $d) {
|
|
$rip = $d->ripartizioneSpese;
|
|
$tabella = $rip?->tabellaMillesimale;
|
|
$voce = $rip?->voceSpesa;
|
|
|
|
return [
|
|
'tabella_id' => $tabella?->id,
|
|
'tabella_codice' => $tabella?->codice_tabella ?? $tabella?->nome_tabella ?? 'TAB',
|
|
'tabella_nome' => $tabella?->denominazione ?? $tabella?->nome_tabella_millesimale ?? 'Tabella millesimale',
|
|
'voce_codice' => $voce?->codice,
|
|
'voce_descrizione' => $voce?->descrizione,
|
|
'tipo_gestione' => $voce?->tipo_gestione,
|
|
'calcolo' => $rip?->tipo_ripartizione,
|
|
'percentuale_applicata' => (float) ($d->percentuale_applicata ?? 0),
|
|
'percentuale_inquilino' => (float) ($d->percentuale_inquilino ?? 0),
|
|
'quota_finale' => (float) ($d->quota_finale ?? 0),
|
|
'importo_proprietario' => (float) ($d->importo_proprietario ?? 0),
|
|
'importo_inquilino' => (float) ($d->importo_inquilino ?? 0),
|
|
];
|
|
})->groupBy('tabella_id');
|
|
|
|
$this->ripartizioniPerTabella = $grouped->map(function ($items) {
|
|
return [
|
|
'tabella_codice' => $items->first()['tabella_codice'] ?? 'TAB',
|
|
'tabella_nome' => $items->first()['tabella_nome'] ?? 'Tabella millesimale',
|
|
'righe' => $items->values()->all(),
|
|
];
|
|
})->values()->all();
|
|
}
|
|
|
|
protected function fallbackVociSpesa(): array
|
|
{
|
|
if (! DbSchema::hasTable('voci_spesa')) {
|
|
return [];
|
|
}
|
|
|
|
$voci = VoceSpesa::query()
|
|
->where('stabile_id', $this->stabileId)
|
|
->with('tabellaMillesimaleDefault')
|
|
->orderBy('ordinamento')
|
|
->get();
|
|
|
|
$grouped = $voci->map(function (VoceSpesa $voce) {
|
|
$tabella = $voce->tabellaMillesimaleDefault;
|
|
return [
|
|
'tabella_id' => $tabella?->id,
|
|
'tabella_codice' => $tabella?->codice_tabella ?? $tabella?->nome_tabella ?? 'TAB',
|
|
'tabella_nome' => $tabella?->denominazione ?? $tabella?->nome_tabella_millesimale ?? 'Tabella millesimale',
|
|
'voce_codice' => $voce->codice,
|
|
'voce_descrizione' => $voce->descrizione,
|
|
'tipo_gestione' => $voce->tipo_gestione,
|
|
'calcolo' => $voce->categoria,
|
|
'percentuale_applicata' => 0.0,
|
|
'percentuale_inquilino' => 0.0,
|
|
'quota_finale' => 0.0,
|
|
'importo_proprietario' => 0.0,
|
|
'importo_inquilino' => 0.0,
|
|
];
|
|
})->groupBy('tabella_id');
|
|
|
|
return $grouped->map(function ($items) {
|
|
return [
|
|
'tabella_codice' => $items->first()['tabella_codice'] ?? 'TAB',
|
|
'tabella_nome' => $items->first()['tabella_nome'] ?? 'Tabella millesimale',
|
|
'righe' => $items->values()->all(),
|
|
];
|
|
})->values()->all();
|
|
}
|
|
|
|
protected function hydratePreventivi(): void
|
|
{
|
|
$this->preventiviPerTabella = [];
|
|
$this->totaliPerGestione = [];
|
|
|
|
// Preferenza: usa le voci dal dominio (pagina "Voci di spesa") così le modifiche importi/%
|
|
// si riflettono direttamente nella scheda Unità immobiliare.
|
|
if (DbSchema::hasTable('voci_spesa')) {
|
|
$user = Auth::user();
|
|
|
|
// Se disponibile gestioni_contabili, limita alle gestioni dell'anno attivo (tutte le tipologie).
|
|
$gestioneIds = [];
|
|
if ($user instanceof User && DbSchema::hasTable('gestioni_contabili') && DbSchema::hasColumn('voci_spesa', 'gestione_contabile_id')) {
|
|
$anno = AnnoGestioneContext::resolveActiveAnno($user);
|
|
|
|
foreach (['ordinaria', 'riscaldamento', 'straordinaria'] as $tipo) {
|
|
$match = GestioneContabile::query()
|
|
->where('stabile_id', $this->stabileId)
|
|
->where('anno_gestione', $anno)
|
|
->where('tipo_gestione', $tipo)
|
|
->where('stato', 'aperta')
|
|
->orderByDesc('gestione_attiva')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if (! $match) {
|
|
$match = GestioneContabile::query()
|
|
->where('stabile_id', $this->stabileId)
|
|
->where('tipo_gestione', $tipo)
|
|
->where('stato', 'aperta')
|
|
->orderByDesc('gestione_attiva')
|
|
->orderByDesc('anno_gestione')
|
|
->orderByDesc('id')
|
|
->first();
|
|
}
|
|
|
|
if ($match?->id) {
|
|
$gestioneIds[] = (int) $match->id;
|
|
}
|
|
}
|
|
|
|
$gestioneIds = array_values(array_unique(array_filter($gestioneIds)));
|
|
}
|
|
|
|
$vociDomain = VoceSpesa::query()
|
|
->where('stabile_id', $this->stabileId)
|
|
->when(! empty($gestioneIds), fn($q) => $q->whereIn('gestione_contabile_id', $gestioneIds))
|
|
->with('tabellaMillesimaleDefault')
|
|
->orderBy('ordinamento')
|
|
->orderBy('codice')
|
|
->get();
|
|
|
|
if ($vociDomain->isNotEmpty()) {
|
|
$tabellaCodes = $vociDomain
|
|
->map(fn(VoceSpesa $v) => $v->tabellaMillesimaleDefault?->codice_tabella)
|
|
->filter()
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
$millesimiPerCodice = $this->unita->dettagliMillesimi
|
|
->filter(function ($d) {
|
|
return $d->tabellaMillesimale !== null;
|
|
})
|
|
->keyBy(function ($d) {
|
|
$tabella = $d->tabellaMillesimale;
|
|
return $tabella?->codice_tabella ?? $tabella?->nome_tabella ?? null;
|
|
});
|
|
|
|
// Importi reali (prev_euro) per unità e tabella, separati per ruolo C/I.
|
|
$importiPerTabella = collect();
|
|
if (DbSchema::hasTable('dettaglio_importi_tabella') && ! empty($tabellaCodes)) {
|
|
$baseImporti = DB::table('dettaglio_importi_tabella as dit')
|
|
->join('tabelle_millesimali as tm', 'tm.id', '=', 'dit.tabella_millesimale_id')
|
|
->where('tm.stabile_id', $this->stabileId)
|
|
->where('dit.unita_immobiliare_id', $this->unita->id)
|
|
->whereIn('tm.codice_tabella', $tabellaCodes)
|
|
->selectRaw('tm.codice_tabella as codice_tabella, dit.ruolo_legacy as ruolo_legacy, SUM(dit.prev_euro) as prev_euro')
|
|
->groupBy('tm.codice_tabella', 'dit.ruolo_legacy');
|
|
|
|
if (DbSchema::hasColumn('dettaglio_importi_tabella', 'legacy_year')) {
|
|
$maxYears = DB::table('dettaglio_importi_tabella')
|
|
->selectRaw('tabella_millesimale_id, unita_immobiliare_id, ruolo_legacy, MAX(legacy_year) as legacy_year')
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->groupBy('tabella_millesimale_id', 'unita_immobiliare_id', 'ruolo_legacy');
|
|
|
|
$baseImporti->joinSub($maxYears, 'mx', function ($join) {
|
|
$join->on('mx.tabella_millesimale_id', '=', 'dit.tabella_millesimale_id')
|
|
->on('mx.unita_immobiliare_id', '=', 'dit.unita_immobiliare_id')
|
|
->on('mx.ruolo_legacy', '=', 'dit.ruolo_legacy')
|
|
->on('mx.legacy_year', '=', 'dit.legacy_year');
|
|
});
|
|
}
|
|
|
|
$importiPerTabella = $baseImporti->get()->groupBy('codice_tabella');
|
|
}
|
|
|
|
$tabelleDomain = DB::table('tabelle_millesimali')
|
|
->where('stabile_id', $this->stabileId)
|
|
->when(! empty($tabellaCodes), fn($q) => $q->whereIn('codice_tabella', $tabellaCodes))
|
|
->get(['id', 'codice_tabella', 'denominazione', 'tipo_tabella', 'tipo_calcolo', 'totale_millesimi'])
|
|
->keyBy('codice_tabella');
|
|
|
|
$totaliGestione = ['O' => 0.0, 'R' => 0.0, 'S' => 0.0];
|
|
|
|
$this->preventiviPerTabella = $vociDomain
|
|
->filter(fn(VoceSpesa $v) => (bool) $v->tabellaMillesimaleDefault?->codice_tabella)
|
|
->groupBy(fn(VoceSpesa $v) => (string) $v->tabellaMillesimaleDefault?->codice_tabella)
|
|
->map(function ($items, $tabellaCode) use ($importiPerTabella, $tabelleDomain, $millesimiPerCodice, &$totaliGestione) {
|
|
$totalPreventivo = (float) $items->sum(fn(VoceSpesa $v) => (float) ($v->importo_default ?? 0));
|
|
|
|
$domainTab = $tabelleDomain->get($tabellaCode);
|
|
$tabellaNome = $domainTab?->denominazione ?: (string) $tabellaCode;
|
|
|
|
// Tipo gestione (O/R/S) per i box totali
|
|
$tipoRaw = (string) ($items->first()?->tipo_gestione ?? 'ordinaria');
|
|
$tipoGestioneKey = match ($tipoRaw) {
|
|
'riscaldamento' => 'R',
|
|
'straordinaria' => 'S',
|
|
default => 'O',
|
|
};
|
|
|
|
$tabellaMillesimi = $millesimiPerCodice->get($tabellaCode);
|
|
$unitMillesimi = (float) ($tabellaMillesimi->millesimi ?? 0);
|
|
$tabTotalMm = (float) ($domainTab?->totale_millesimi ?? 0);
|
|
$ratio = ($tabTotalMm > 0) ? ($unitMillesimi / $tabTotalMm) : 0.0;
|
|
|
|
// Importi reali per tabella e unità (prev_euro)
|
|
$impRows = $importiPerTabella->get($tabellaCode) ?? collect();
|
|
$impProp = (float) $impRows
|
|
->filter(fn($r) => (string) ($r->ruolo_legacy ?? '') !== 'I')
|
|
->sum(fn($r) => (float) ($r->prev_euro ?? 0));
|
|
$impInq = (float) $impRows
|
|
->filter(fn($r) => (string) ($r->ruolo_legacy ?? '') === 'I')
|
|
->sum(fn($r) => (float) ($r->prev_euro ?? 0));
|
|
$impTot = $impProp + $impInq;
|
|
$hasImporti = abs($impTot) > 0.00001;
|
|
$useImportiSplit = $hasImporti && abs($impInq) > 0.00001;
|
|
|
|
// Quota unità tabella
|
|
$quotaUnitTabella = $hasImporti
|
|
? $impTot
|
|
: ($totalPreventivo * $ratio);
|
|
|
|
$rows = $items->map(function (VoceSpesa $voce) use ($hasImporti, $useImportiSplit, $impInq, $quotaUnitTabella, $totalPreventivo, $ratio) {
|
|
$importoVoce = (float) ($voce->importo_default ?? 0);
|
|
$percInq = is_numeric($voce->percentuale_inquilino ?? null) ? (float) $voce->percentuale_inquilino : 0.0;
|
|
|
|
if ($hasImporti) {
|
|
$share = ($totalPreventivo != 0.0) ? ($importoVoce / $totalPreventivo) : 0.0;
|
|
$quotaUnita = $quotaUnitTabella * $share;
|
|
if ($useImportiSplit) {
|
|
$quotaInquilinoVoce = $impInq * $share;
|
|
} else {
|
|
$quotaInquilinoVoce = $quotaUnita * ($percInq / 100);
|
|
}
|
|
} else {
|
|
$quotaUnita = $importoVoce * $ratio;
|
|
$quotaInquilinoVoce = $quotaUnita * ($percInq / 100);
|
|
}
|
|
|
|
$quotaProprietarioVoce = $quotaUnita - $quotaInquilinoVoce;
|
|
$percentualeInquilinoVoce = $quotaUnita != 0.0 ? ($quotaInquilinoVoce / $quotaUnita) * 100 : 0.0;
|
|
|
|
return [
|
|
'codice' => (string) ($voce->codice ?? ''),
|
|
'descrizione' => (string) ($voce->descrizione ?? ''),
|
|
'importo_preventivato' => $importoVoce,
|
|
'quota_unita' => $quotaUnita,
|
|
'importo_proprietario' => $quotaProprietarioVoce,
|
|
'importo_inquilino' => $quotaInquilinoVoce,
|
|
'percentuale_inquilino' => $percentualeInquilinoVoce,
|
|
];
|
|
})->values()->all();
|
|
|
|
if ($useImportiSplit) {
|
|
$quotaProprietario = $impProp;
|
|
$quotaInquilino = $impInq;
|
|
$percentualeInquilino = ($impProp + $impInq) != 0.0 ? ($impInq / ($impProp + $impInq)) * 100 : 0.0;
|
|
} else {
|
|
$quotaProprietario = 0.0;
|
|
$quotaInquilino = 0.0;
|
|
foreach ($rows as $row) {
|
|
$quotaProprietario += (float) ($row['importo_proprietario'] ?? 0);
|
|
$quotaInquilino += (float) ($row['importo_inquilino'] ?? 0);
|
|
}
|
|
$percentualeInquilino = ($quotaProprietario + $quotaInquilino) != 0.0
|
|
? ($quotaInquilino / ($quotaProprietario + $quotaInquilino)) * 100
|
|
: 0.0;
|
|
}
|
|
|
|
if (array_key_exists($tipoGestioneKey, $totaliGestione)) {
|
|
$totaliGestione[$tipoGestioneKey] += ($quotaProprietario + $quotaInquilino);
|
|
}
|
|
|
|
return [
|
|
'tabella_codice' => $tabellaCode,
|
|
'tabella_nome' => $tabellaNome,
|
|
'tipo' => $tipoGestioneKey,
|
|
'total_preventivo' => $totalPreventivo,
|
|
'quota_unit' => $quotaUnitTabella,
|
|
'importo_proprietario' => $quotaProprietario,
|
|
'importo_inquilino' => $quotaInquilino,
|
|
'percentuale_inquilino' => $percentualeInquilino,
|
|
'tabella_millesimi' => $tabTotalMm,
|
|
'unit_millesimi' => $unitMillesimi,
|
|
'voci' => $rows,
|
|
];
|
|
})
|
|
->values()
|
|
->all();
|
|
|
|
$this->totaliPerGestione = $totaliGestione;
|
|
return;
|
|
}
|
|
}
|
|
|
|
$importConn = 'gescon_import';
|
|
$hasVocSpe = DbSchema::connection($importConn)->hasTable('voc_spe');
|
|
$hasDetTab = DbSchema::connection($importConn)->hasTable('dett_tab');
|
|
|
|
if (! $hasVocSpe || ! $hasDetTab) {
|
|
return;
|
|
}
|
|
|
|
$codiceStabile = $this->unita->stabile?->codice_stabile ?? $this->unita->stabile?->codice_interno ?? $this->unita->stabile?->old_id;
|
|
|
|
$voci = DB::connection($importConn)
|
|
->table('voc_spe')
|
|
->when($codiceStabile && DbSchema::connection($importConn)->hasColumn('voc_spe', 'cod_stabile'), function ($q) use ($codiceStabile) {
|
|
$q->where('cod_stabile', $codiceStabile);
|
|
})
|
|
->get();
|
|
|
|
if ($voci->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$tabellaCodes = $voci->pluck('tabella')->filter()->unique()->values()->all();
|
|
|
|
$millesimiPerCodice = $this->unita->dettagliMillesimi
|
|
->filter(function ($d) {
|
|
return $d->tabellaMillesimale !== null;
|
|
})
|
|
->keyBy(function ($d) {
|
|
$tabella = $d->tabellaMillesimale;
|
|
return $tabella?->codice_tabella ?? $tabella?->nome_tabella ?? null;
|
|
});
|
|
|
|
// Importi reali (prev_euro) per unità e tabella, separati per ruolo C/I.
|
|
// Usati per tabelle a consumo/individuali (ACQUA/RISC.L/INDIV./CONG.* ecc) quando presenti.
|
|
$importiPerTabella = collect();
|
|
if (DbSchema::hasTable('dettaglio_importi_tabella')) {
|
|
$baseImporti = DB::table('dettaglio_importi_tabella as dit')
|
|
->join('tabelle_millesimali as tm', 'tm.id', '=', 'dit.tabella_millesimale_id')
|
|
->where('tm.stabile_id', $this->stabileId)
|
|
->where('dit.unita_immobiliare_id', $this->unita->id)
|
|
->whereIn('tm.codice_tabella', $tabellaCodes)
|
|
->selectRaw('tm.codice_tabella as codice_tabella, dit.ruolo_legacy as ruolo_legacy, SUM(dit.prev_euro) as prev_euro')
|
|
->groupBy('tm.codice_tabella', 'dit.ruolo_legacy');
|
|
|
|
if (DbSchema::hasColumn('dettaglio_importi_tabella', 'legacy_year')) {
|
|
$maxYears = DB::table('dettaglio_importi_tabella')
|
|
->selectRaw('tabella_millesimale_id, unita_immobiliare_id, ruolo_legacy, MAX(legacy_year) as legacy_year')
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->groupBy('tabella_millesimale_id', 'unita_immobiliare_id', 'ruolo_legacy');
|
|
|
|
$baseImporti->joinSub($maxYears, 'mx', function ($join) {
|
|
$join->on('mx.tabella_millesimale_id', '=', 'dit.tabella_millesimale_id')
|
|
->on('mx.unita_immobiliare_id', '=', 'dit.unita_immobiliare_id')
|
|
->on('mx.ruolo_legacy', '=', 'dit.ruolo_legacy')
|
|
->on('mx.legacy_year', '=', 'dit.legacy_year');
|
|
});
|
|
}
|
|
|
|
$importiPerTabella = $baseImporti->get()->groupBy('codice_tabella');
|
|
}
|
|
|
|
// Cache info tabella millesimale nel dominio
|
|
$tabelleDomain = DB::table('tabelle_millesimali')
|
|
->where('stabile_id', $this->stabileId)
|
|
->whereIn('codice_tabella', $tabellaCodes)
|
|
->get(['id', 'codice_tabella', 'denominazione', 'tipo_tabella', 'tipo_calcolo', 'totale_millesimi'])
|
|
->keyBy('codice_tabella');
|
|
|
|
$totaliGestione = ['O' => 0.0, 'R' => 0.0, 'S' => 0.0];
|
|
|
|
$this->preventiviPerTabella = collect($voci)
|
|
->groupBy('tabella')
|
|
->map(function ($items, $tabellaCode) use ($importiPerTabella, $tabelleDomain, $millesimiPerCodice, &$totaliGestione) {
|
|
$totalPreventivo = (float) $items->sum(function ($v) {
|
|
return (float) ($v->preventivo_euro ?? 0);
|
|
});
|
|
|
|
$domainTab = $tabelleDomain->get($tabellaCode);
|
|
$tabellaNome = $domainTab?->denominazione
|
|
?: ($items->first()->tabella_descrizione ?? $items->first()->descrizione ?? $tabellaCode);
|
|
|
|
$tipoGestione = $items->first()->v_ors ?? $items->first()->tipo_gestione ?? $items->first()->tipo ?? 'O';
|
|
$tipoGestioneKey = strtoupper(substr((string) $tipoGestione, 0, 1));
|
|
|
|
$tabellaMillesimi = $millesimiPerCodice->get($tabellaCode);
|
|
$unitMillesimi = (float) ($tabellaMillesimi->millesimi ?? 0);
|
|
$tabTotalMm = (float) ($domainTab?->totale_millesimi ?? 0);
|
|
$ratio = ($tabTotalMm > 0) ? ($unitMillesimi / $tabTotalMm) : 0.0;
|
|
|
|
// Importi reali per tabella e unità (prev_euro) se presenti
|
|
$impRows = $importiPerTabella->get($tabellaCode) ?? collect();
|
|
$impProp = (float) $impRows
|
|
->filter(fn($r) => (string) ($r->ruolo_legacy ?? '') !== 'I')
|
|
->sum(fn($r) => (float) ($r->prev_euro ?? 0));
|
|
$impInq = (float) $impRows
|
|
->filter(fn($r) => (string) ($r->ruolo_legacy ?? '') === 'I')
|
|
->sum(fn($r) => (float) ($r->prev_euro ?? 0));
|
|
$impTot = $impProp + $impInq;
|
|
$hasImporti = abs($impTot) > 0.00001;
|
|
$percentInqFromImporti = $impTot != 0.0 ? ($impInq / $impTot) * 100 : 0.0;
|
|
|
|
// Usa lo split importato solo quando c'è effettivamente una quota Inquilino.
|
|
// In molti casi (es. tabelle ordinarie) lo staging fornisce solo la quota unità totale e lo split va applicato via percentuali voce.
|
|
$useImportiSplit = $hasImporti && abs($impInq) > 0.00001;
|
|
|
|
// Quota unità tabella: preferisci importi reali (consumi/individuali), altrimenti riparto millesimale
|
|
$quotaUnitTabella = $hasImporti
|
|
? $impTot
|
|
: ($totalPreventivo * $ratio);
|
|
|
|
$rows = $items->map(function ($voce) use ($hasImporti, $useImportiSplit, $impInq, $quotaUnitTabella, $totalPreventivo, $ratio) {
|
|
$voceCodice = $voce->codice ?? $voce->cod_voce ?? $voce->codice_voce ?? $voce->cod ?? null;
|
|
|
|
$voceDescrizione = $voce->descrizione ?? $voce->descr ?? $voce->voce ?? $voce->voce_descrizione ?? '';
|
|
|
|
$importoVoce = (float) ($voce->preventivo_euro ?? 0);
|
|
|
|
// Quota unità voce
|
|
if ($hasImporti) {
|
|
$share = ($totalPreventivo != 0.0) ? ($importoVoce / $totalPreventivo) : 0.0;
|
|
$quotaUnita = $quotaUnitTabella * $share;
|
|
if ($useImportiSplit) {
|
|
$quotaInquilinoVoce = $impInq * $share;
|
|
} else {
|
|
$percInq = is_numeric($voce->perc_inquilino ?? null) ? (float) $voce->perc_inquilino : 0.0;
|
|
$quotaInquilinoVoce = $quotaUnita * ($percInq / 100);
|
|
}
|
|
} else {
|
|
$quotaUnita = $importoVoce * $ratio;
|
|
$percInq = is_numeric($voce->perc_inquilino ?? null) ? (float) $voce->perc_inquilino : 0.0;
|
|
$quotaInquilinoVoce = $quotaUnita * ($percInq / 100);
|
|
}
|
|
|
|
$quotaProprietarioVoce = $quotaUnita - $quotaInquilinoVoce;
|
|
$percentualeInquilinoVoce = $quotaUnita != 0.0 ? ($quotaInquilinoVoce / $quotaUnita) * 100 : 0.0;
|
|
|
|
return [
|
|
'codice' => $voceCodice,
|
|
'descrizione' => $voceDescrizione,
|
|
'importo_preventivato' => $importoVoce,
|
|
'quota_unita' => $quotaUnita,
|
|
'importo_proprietario' => $quotaProprietarioVoce,
|
|
'importo_inquilino' => $quotaInquilinoVoce,
|
|
'percentuale_inquilino' => $percentualeInquilinoVoce,
|
|
];
|
|
})->values()->all();
|
|
|
|
// Totali tabella (prop/inq):
|
|
// - se importi reali disponibili, usa quelli
|
|
// - altrimenti deriva dalle percentuali voce
|
|
if ($useImportiSplit) {
|
|
$quotaProprietario = $impProp;
|
|
$quotaInquilino = $impInq;
|
|
$percentualeInquilino = ($impProp + $impInq) != 0.0 ? ($impInq / ($impProp + $impInq)) * 100 : 0.0;
|
|
} else {
|
|
$quotaProprietario = 0.0;
|
|
$quotaInquilino = 0.0;
|
|
foreach ($rows as $row) {
|
|
$quotaProprietario += (float) ($row['importo_proprietario'] ?? 0);
|
|
$quotaInquilino += (float) ($row['importo_inquilino'] ?? 0);
|
|
}
|
|
$percentualeInquilino = ($quotaProprietario + $quotaInquilino) != 0.0
|
|
? ($quotaInquilino / ($quotaProprietario + $quotaInquilino)) * 100
|
|
: 0.0;
|
|
}
|
|
|
|
if (array_key_exists($tipoGestioneKey, $totaliGestione)) {
|
|
$totaliGestione[$tipoGestioneKey] += ($quotaProprietario + $quotaInquilino);
|
|
}
|
|
|
|
return [
|
|
'tabella_codice' => $tabellaCode,
|
|
'tabella_nome' => $tabellaNome,
|
|
'tipo' => $tipoGestione,
|
|
'total_preventivo' => $totalPreventivo,
|
|
'quota_unit' => $quotaUnitTabella,
|
|
'importo_proprietario' => $quotaProprietario,
|
|
'importo_inquilino' => $quotaInquilino,
|
|
'percentuale_inquilino' => $percentualeInquilino,
|
|
'tabella_millesimi' => $tabTotalMm,
|
|
'unit_millesimi' => $unitMillesimi,
|
|
'voci' => $rows,
|
|
];
|
|
})
|
|
->values()
|
|
->all();
|
|
|
|
$this->totaliPerGestione = $totaliGestione;
|
|
}
|
|
|
|
protected function hydrateInquilinoAttivo(): void
|
|
{
|
|
$today = now()->toDateString();
|
|
|
|
$activeInq = DB::table('unita_anagrafica_periodo')
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->where('ruolo_occupazione', 'inquilino')
|
|
->where('data_inizio', '<=', $today)
|
|
->where(function ($q) use ($today) {
|
|
$q->whereNull('data_fine')->orWhere('data_fine', '>=', $today);
|
|
})
|
|
->first();
|
|
|
|
if ($activeInq) {
|
|
$this->inquilinoAnagraficaId = $activeInq->anagrafica_id;
|
|
$this->inquilinoDataInizio = $activeInq->data_inizio;
|
|
$this->inquilinoDataFine = $activeInq->data_fine;
|
|
$this->inquilinoPercentualeSpesa = (float) $activeInq->percentuale_spesa;
|
|
} else {
|
|
$this->inquilinoAnagraficaId = null;
|
|
$this->inquilinoDataInizio = $today;
|
|
$this->inquilinoDataFine = null;
|
|
$this->inquilinoPercentualeSpesa = 100.0;
|
|
}
|
|
}
|
|
|
|
public function saveInquilino(): void
|
|
{
|
|
$this->validate([
|
|
'inquilinoAnagraficaId' => 'nullable|integer|exists:anagrafiche,id',
|
|
'inquilinoDataInizio' => 'required|date',
|
|
'inquilinoDataFine' => 'nullable|date|after_or_equal:inquilinoDataInizio',
|
|
'inquilinoPercentualeSpesa' => 'required|numeric|between:0,100',
|
|
]);
|
|
|
|
$today = now()->toDateString();
|
|
|
|
// Close current active inquilino if none is selected
|
|
if ($this->inquilinoAnagraficaId === null) {
|
|
DB::table('unita_anagrafica_periodo')
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->where('ruolo_occupazione', 'inquilino')
|
|
->where('data_inizio', '<=', $today)
|
|
->where(function ($q) use ($today) {
|
|
$q->whereNull('data_fine')->orWhere('data_fine', '>=', $today);
|
|
})
|
|
->update(['data_fine' => Carbon::parse($this->inquilinoDataInizio)->subDay()->toDateString()]);
|
|
} else {
|
|
// Update or Insert active inquilino
|
|
DB::table('unita_anagrafica_periodo')->updateOrInsert(
|
|
[
|
|
'unita_immobiliare_id' => $this->unita->id,
|
|
'ruolo_occupazione' => 'inquilino',
|
|
'anagrafica_id' => $this->inquilinoAnagraficaId,
|
|
],
|
|
[
|
|
'data_inizio' => $this->inquilinoDataInizio,
|
|
'data_fine' => $this->inquilinoDataFine,
|
|
'percentuale_spesa' => $this->inquilinoPercentualeSpesa,
|
|
'updated_at' => now(),
|
|
'created_at' => now()
|
|
]
|
|
);
|
|
}
|
|
|
|
$this->loadUnita();
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
->title('Inquilino salvato correttamente')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function hydratePianoContiGerarchico(): void
|
|
{
|
|
$this->pianoContiGerarchico = [];
|
|
if (!$this->unita) {
|
|
return;
|
|
}
|
|
|
|
$today = now()->toDateString();
|
|
|
|
$tabelleAssegnate = \App\Models\TabellaMillesimale::query()
|
|
->where('stabile_id', $this->stabileId)
|
|
->whereHas('dettagliMillesimi', function ($q) {
|
|
$q->where('unita_immobiliare_id', $this->unita->id);
|
|
})
|
|
->get();
|
|
|
|
foreach ($tabelleAssegnate as $tabella) {
|
|
$detMillesimi = $tabella->dettagliMillesimi()
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->first();
|
|
|
|
$millesimiValue = $detMillesimi ? (float) $detMillesimi->millesimi : 0.0;
|
|
$tabellaId = $tabella->id;
|
|
|
|
$this->millesimiInputs[$tabellaId] = $millesimiValue;
|
|
|
|
$vociSpesa = \App\Models\VoceSpesa::query()
|
|
->where('tabella_millesimale_default_id', $tabellaId)
|
|
->orderBy('ordinamento')
|
|
->orderBy('codice')
|
|
->get();
|
|
|
|
$subAccounts = [];
|
|
foreach ($vociSpesa as $voce) {
|
|
$importoRow = DB::table('dettaglio_importi_tabella')
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->where('tabella_millesimale_id', $tabellaId)
|
|
->first();
|
|
|
|
$prev = $importoRow ? (float) $importoRow->prev_euro : 0.0;
|
|
$cons = $importoRow ? (float) $importoRow->cons_euro : 0.0;
|
|
|
|
$rowKey = $tabellaId . '_' . $voce->id;
|
|
|
|
$this->preventivoInputs[$rowKey] = $prev;
|
|
$this->consuntivoInputs[$rowKey] = $cons;
|
|
|
|
$subAccounts[] = [
|
|
'voce_id' => $voce->id,
|
|
'codice' => $voce->codice,
|
|
'descrizione' => $voce->descrizione,
|
|
'tipo_gestione' => $voce->tipo_gestione,
|
|
'row_key' => $rowKey,
|
|
];
|
|
}
|
|
|
|
$this->pianoContiGerarchico[] = [
|
|
'tabella_id' => $tabellaId,
|
|
'codice_tabella' => $tabella->codice_tabella ?: 'TAB',
|
|
'denominazione' => $tabella->denominazione,
|
|
'millesimi' => $millesimiValue,
|
|
'sottoconti' => $subAccounts,
|
|
];
|
|
}
|
|
}
|
|
|
|
public function savePianoConti(): void
|
|
{
|
|
foreach ($this->millesimiInputs as $tabellaId => $value) {
|
|
DB::table('dettaglio_millesimi')->updateOrInsert(
|
|
[
|
|
'unita_immobiliare_id' => $this->unita->id,
|
|
'tabella_millesimale_id' => $tabellaId,
|
|
],
|
|
[
|
|
'millesimi' => $value,
|
|
'updated_at' => now(),
|
|
'created_at' => now(),
|
|
]
|
|
);
|
|
|
|
DB::table('dettaglio_importi_tabella')
|
|
->where('unita_immobiliare_id', $this->unita->id)
|
|
->where('tabella_millesimale_id', $tabellaId)
|
|
->update(['millesimi' => $value]);
|
|
}
|
|
|
|
foreach ($this->preventivoInputs as $rowKey => $value) {
|
|
[$tabellaId, $voceId] = explode('_', $rowKey);
|
|
$consValue = $this->consuntivoInputs[$rowKey] ?? 0.0;
|
|
|
|
DB::table('dettaglio_importi_tabella')->updateOrInsert(
|
|
[
|
|
'unita_immobiliare_id' => $this->unita->id,
|
|
'tabella_millesimale_id' => $tabellaId,
|
|
'ruolo_legacy' => 'C',
|
|
],
|
|
[
|
|
'prev_euro' => $value,
|
|
'cons_euro' => $consValue,
|
|
'millesimi' => $this->millesimiInputs[$tabellaId] ?? 0.0,
|
|
'updated_at' => now(),
|
|
'created_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
|
|
$this->loadUnita();
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
->title('Valori salvati con successo')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
protected function popolaCanaliComunicazione(): void
|
|
{
|
|
$this->canaliComunicazione = [];
|
|
$comproprietari = $this->relazioniPerTipo['proprietari'] ?? [];
|
|
|
|
$stabile = \App\Models\Stabile::find($this->stabileId);
|
|
$adminCode = $stabile?->amministratore ? trim((string)$stabile->amministratore->codice_amministratore) : 'ADM';
|
|
$adminFolder = "AMMINISTRATORE_" . preg_replace('/[^a-zA-Z0-9_]/', '', str_replace(' ', '_', $adminCode));
|
|
$stabileCode = $stabile ? trim((string)$stabile->codice_stabile) : 'STB';
|
|
$stabileName = $stabile ? trim((string)$stabile->denominazione) : 'STABILE';
|
|
$stabileFolder = "STABILE_" . preg_replace('/[^a-zA-Z0-9_]/', '', str_replace(' ', '_', "{$stabileCode}_{$stabileName}"));
|
|
$adminFolder = preg_replace('/_+/', '_', $adminFolder);
|
|
$stabileFolder = preg_replace('/_+/', '_', $stabileFolder);
|
|
$overlayDir = "{$adminFolder}/{$stabileFolder}/catasto/anno_2026";
|
|
$overlayFile = "{$overlayDir}/canali_invio_unita_{$this->unitaId}.json";
|
|
|
|
$overlayData = [];
|
|
if (\Illuminate\Support\Facades\Storage::disk('public')->exists($overlayFile)) {
|
|
$overlayData = json_decode(\Illuminate\Support\Facades\Storage::disk('public')->get($overlayFile), true) ?: [];
|
|
}
|
|
|
|
$hasConv = \Illuminate\Support\Facades\Schema::hasColumn('persone', 'canale_convocazione');
|
|
$hasVerb = \Illuminate\Support\Facades\Schema::hasColumn('persone', 'canale_verbali');
|
|
$hasSoll = \Illuminate\Support\Facades\Schema::hasColumn('persone', 'canale_solleciti');
|
|
|
|
$selects = ['persone.*'];
|
|
if (! $hasConv) {
|
|
$selects[] = DB::raw('NULL as canale_convocazione');
|
|
}
|
|
if (! $hasVerb) {
|
|
$selects[] = DB::raw('NULL as canale_verbali');
|
|
}
|
|
if (! $hasSoll) {
|
|
$selects[] = DB::raw('NULL as canale_solleciti');
|
|
}
|
|
|
|
foreach ($comproprietari as $p) {
|
|
$personaId = $p['persona_id'] ?? null;
|
|
if ($personaId) {
|
|
$persona = DB::table('persone')->select($selects)->where('id', $personaId)->first();
|
|
if ($persona) {
|
|
$convRaw = $persona->canale_convocazione ?? null;
|
|
$verbRaw = $persona->canale_verbali ?? null;
|
|
$sollRaw = $persona->canale_solleciti ?? null;
|
|
|
|
$conv = $overlayData[$personaId]['convocazione'] ?? ($convRaw !== null && $convRaw !== '' ? $convRaw : 'Raccomandata AR');
|
|
$verb = $overlayData[$personaId]['verbali'] ?? ($verbRaw !== null && $verbRaw !== '' ? $verbRaw : 'Raccomandata AR');
|
|
$soll = $overlayData[$personaId]['solleciti'] ?? ($sollRaw !== null && $sollRaw !== '' ? $sollRaw : 'PEC');
|
|
|
|
$this->canaliComunicazione[$personaId] = [
|
|
'id' => $personaId,
|
|
'nominativo' => trim($persona->cognome . ' ' . $persona->nome) ?: $p['nome'],
|
|
'codice_fiscale' => $persona->codice_fiscale,
|
|
'convocazione' => $conv,
|
|
'verbali' => $verb,
|
|
'solleciti' => $soll,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function salvaCanaleComunicazione(int $personaId, string $campo, string $valore): void
|
|
{
|
|
$fieldMap = [
|
|
'convocazione' => 'canale_convocazione',
|
|
'verbali' => 'canale_verbali',
|
|
'solleciti' => 'canale_solleciti',
|
|
];
|
|
|
|
if (isset($fieldMap[$campo])) {
|
|
$dbCol = $fieldMap[$campo];
|
|
if (\Illuminate\Support\Facades\Schema::hasColumn('persone', $dbCol)) {
|
|
DB::table('persone')
|
|
->where('id', $personaId)
|
|
->update([
|
|
$dbCol => $valore,
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
if (isset($this->canaliComunicazione[$personaId])) {
|
|
$this->canaliComunicazione[$personaId][$campo] = $valore;
|
|
}
|
|
|
|
$stabile = \App\Models\Stabile::find($this->stabileId);
|
|
$adminCode = $stabile?->amministratore ? trim((string)$stabile->amministratore->codice_amministratore) : 'ADM';
|
|
$adminFolder = "AMMINISTRATORE_" . preg_replace('/[^a-zA-Z0-9_]/', '', str_replace(' ', '_', $adminCode));
|
|
$stabileCode = $stabile ? trim((string)$stabile->codice_stabile) : 'STB';
|
|
$stabileName = $stabile ? trim((string)$stabile->denominazione) : 'STABILE';
|
|
$stabileFolder = "STABILE_" . preg_replace('/[^a-zA-Z0-9_]/', '', str_replace(' ', '_', "{$stabileCode}_{$stabileName}"));
|
|
$adminFolder = preg_replace('/_+/', '_', $adminFolder);
|
|
$stabileFolder = preg_replace('/_+/', '_', $stabileFolder);
|
|
$overlayDir = "{$adminFolder}/{$stabileFolder}/catasto/anno_2026";
|
|
|
|
\Illuminate\Support\Facades\Storage::disk('public')->makeDirectory($overlayDir);
|
|
$overlayFile = "{$overlayDir}/canali_invio_unita_{$this->unitaId}.json";
|
|
|
|
\Illuminate\Support\Facades\Storage::disk('public')->put($overlayFile, json_encode($this->canaliComunicazione, JSON_PRETTY_PRINT));
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
->title('Canale aggiornato ed archiviato')
|
|
->success()
|
|
->send();
|
|
}
|
|
}
|
|
}
|