1766 lines
77 KiB
PHP
1766 lines
77 KiB
PHP
<?php
|
|
namespace App\Filament\Pages\Condomini;
|
|
|
|
use App\Filament\Pages\Contabilita\FattureElettronicheP7mRicevute;
|
|
use App\Models\Fornitore;
|
|
use App\Models\StabileServizio;
|
|
use App\Models\StabileServizioLettura;
|
|
use App\Models\StabileServizioTariffa;
|
|
use App\Models\UnitaImmobiliare;
|
|
use App\Models\User;
|
|
use App\Models\VoceSpesa;
|
|
use App\Support\AnnoGestioneContext;
|
|
use App\Support\StabileContext;
|
|
use BackedEnum;
|
|
use Filament\Actions\Action;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Tables\Columns\IconColumn;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Concerns\InteractsWithTable;
|
|
use Filament\Tables\Contracts\HasTable;
|
|
use Filament\Tables\Filters\SelectFilter;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\URL;
|
|
use UnitEnum;
|
|
|
|
class ServiziStabileArchivio extends Page implements HasTable
|
|
{
|
|
use InteractsWithTable;
|
|
|
|
protected static ?string $navigationLabel = 'Servizi / Beni comuni';
|
|
|
|
protected static ?string $title = 'Servizi / Beni comuni';
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-rectangle-stack';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'Stabile';
|
|
|
|
protected static ?int $navigationSort = 33;
|
|
|
|
protected static ?string $slug = 'condomini/servizi-utenze';
|
|
|
|
protected string $view = 'filament.pages.condomini.servizi-stabile-archivio';
|
|
|
|
public string $acquaTab = 'dashboard';
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = Auth::user();
|
|
|
|
return $user instanceof User
|
|
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore'])
|
|
&& Schema::hasTable('stabile_servizi');
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->mountInteractsWithTable();
|
|
}
|
|
|
|
public function setAcquaTab(string $tab): void
|
|
{
|
|
$allowed = ['dashboard', 'fatture', 'letture', 'tariffe', 'servizi'];
|
|
$this->acquaTab = in_array($tab, $allowed, true) ? $tab : 'dashboard';
|
|
}
|
|
|
|
public function prepareAcquaReadingCampaign(): void
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
$servizio = $this->resolveActiveAcquaServizio();
|
|
|
|
if (! $stabileId || ! $servizio) {
|
|
Notification::make()->title('Servizio acqua attivo non disponibile per lo stabile selezionato.')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$units = UnitaImmobiliare::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereNull('deleted_at')
|
|
->orderBy('scala')
|
|
->orderBy('interno')
|
|
->orderBy('id')
|
|
->get(['id', 'codice_unita', 'scala', 'interno']);
|
|
|
|
if ($units->isEmpty()) {
|
|
Notification::make()->title('Nessuna unità immobiliare disponibile per avviare la campagna letture.')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$year = $this->resolveActiveAnnoGestione();
|
|
$created = 0;
|
|
$updated = 0;
|
|
|
|
foreach ($units as $unit) {
|
|
$completed = StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('stabile_servizio_id', (int) $servizio->id)
|
|
->where('unita_immobiliare_id', (int) $unit->id)
|
|
->whereYear('created_at', $year)
|
|
->whereNotNull('lettura_fine')
|
|
->exists();
|
|
|
|
if ($completed) {
|
|
continue;
|
|
}
|
|
|
|
$previous = StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('stabile_servizio_id', (int) $servizio->id)
|
|
->where('unita_immobiliare_id', (int) $unit->id)
|
|
->whereNotNull('lettura_fine')
|
|
->orderByDesc('periodo_al')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
$requestRow = StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('stabile_servizio_id', (int) $servizio->id)
|
|
->where('unita_immobiliare_id', (int) $unit->id)
|
|
->whereYear('created_at', $year)
|
|
->whereNull('lettura_fine')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
$requestReference = 'PUBREQ:' . (int) $servizio->id . ':' . (int) $unit->id . ':' . $year;
|
|
$requestPayload = [
|
|
'stabile_id' => $stabileId,
|
|
'stabile_servizio_id' => (int) $servizio->id,
|
|
'unita_immobiliare_id' => (int) $unit->id,
|
|
'fornitore_id' => $servizio->fornitore_id,
|
|
'periodo_dal' => $previous?->periodo_al,
|
|
'periodo_al' => null,
|
|
'tipologia_lettura' => 'richiesta_autolettura',
|
|
'canale_acquisizione' => 'portale_pubblico',
|
|
'riferimento_acquisizione' => $requestReference,
|
|
'workflow_stato' => 'richiesta_inviata',
|
|
'richiesta_lettura_inviata_at' => now(),
|
|
'deadline_lettura_at' => now()->addDays(7),
|
|
'lettura_precedente_valore' => $previous?->lettura_fine,
|
|
'lettura_inizio' => $previous?->lettura_fine,
|
|
'raw' => [
|
|
'source' => 'campagna_letture_acqua',
|
|
'year' => $year,
|
|
'unit_label' => trim((string) ($unit->codice_unita ?? '') . ' ' . (string) ($unit->interno ?? '')),
|
|
],
|
|
];
|
|
|
|
if ($requestRow) {
|
|
$requestRow->fill($requestPayload);
|
|
$requestRow->save();
|
|
$updated++;
|
|
continue;
|
|
}
|
|
|
|
StabileServizioLettura::query()->create($requestPayload);
|
|
$created++;
|
|
}
|
|
|
|
Notification::make()
|
|
->title('Campagna letture acqua preparata')
|
|
->body("Richieste create: {$created} · richieste aggiornate: {$updated}")
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function markAcquaReadingReminder(int $readingId): void
|
|
{
|
|
$reading = StabileServizioLettura::query()->find($readingId);
|
|
if (! $reading) {
|
|
return;
|
|
}
|
|
|
|
$reading->fill([
|
|
'sollecito_inviato_at' => now(),
|
|
'workflow_stato' => 'sollecito_inviato',
|
|
]);
|
|
$reading->save();
|
|
|
|
Notification::make()->title('Sollecito registrato')->success()->send();
|
|
}
|
|
|
|
public function ensureAcquaAceaContract(): void
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
Notification::make()->title('Stabile attivo non disponibile.')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$fornitoreAcea = Fornitore::query()
|
|
->whereRaw('LOWER(ragione_sociale) like ?', ['%acea%ato2%'])
|
|
->orderBy('ragione_sociale')
|
|
->first() ?? Fornitore::query()
|
|
->whereRaw('LOWER(ragione_sociale) like ?', ['%acea%'])
|
|
->orderBy('ragione_sociale')
|
|
->first();
|
|
|
|
if (! $fornitoreAcea) {
|
|
Notification::make()->title('Fornitore Acea Ato2 non trovato in anagrafica fornitori.')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$existing = StabileServizio::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('tipo', 'acqua')
|
|
->orderByDesc('attivo')
|
|
->orderBy('id')
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$existing->fornitore_id = (int) $fornitoreAcea->id;
|
|
if (trim((string) ($existing->nome ?? '')) === '') {
|
|
$existing->nome = 'Utenza acqua - Acea Ato2';
|
|
}
|
|
if (! $existing->attivo) {
|
|
$existing->attivo = true;
|
|
}
|
|
$existing->save();
|
|
|
|
Notification::make()->title('Contratto ACQUA aggiornato su fornitore Acea Ato2.')->success()->send();
|
|
return;
|
|
}
|
|
|
|
StabileServizio::query()->create([
|
|
'stabile_id' => $stabileId,
|
|
'tipo' => 'acqua',
|
|
'nome' => 'Utenza acqua - Acea Ato2',
|
|
'fornitore_id' => (int) $fornitoreAcea->id,
|
|
'attivo' => true,
|
|
'meta' => [
|
|
'canale_acquisizione' => 'manuale',
|
|
],
|
|
]);
|
|
|
|
Notification::make()->title('Contratto ACQUA Acea Ato2 creato.')->success()->send();
|
|
}
|
|
|
|
protected function getTableQuery(): Builder
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return StabileServizio::query()->whereRaw('1 = 0');
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
return StabileServizio::query()->whereRaw('1 = 0');
|
|
}
|
|
|
|
return StabileServizio::query()
|
|
->where('stabile_id', (int) $stabileId)
|
|
->with([
|
|
'fornitore:id,ragione_sociale',
|
|
'voceSpesa:id,descrizione,tipo_gestione',
|
|
])
|
|
->withCount('letture')
|
|
->orderBy('tipo')
|
|
->orderBy('nome')
|
|
->orderByDesc('id');
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
$tipoOptions = $this->getServiceTypeOptions();
|
|
$commonScopeOptions = $this->getCommonAssetScopeOptions();
|
|
$allocationOptions = $this->getAllocationScopeOptions();
|
|
$fiscalScopeOptions = $this->getFiscalScopeOptions();
|
|
$counterScopeOptions = $this->getCounterScopeOptions();
|
|
|
|
$gestioneOptions = [
|
|
'ordinaria' => 'Ordinaria',
|
|
'riscaldamento' => 'Riscaldamento',
|
|
'straordinaria' => 'Straordinaria',
|
|
];
|
|
|
|
return $table
|
|
->striped()
|
|
->filters([
|
|
SelectFilter::make('tipo')
|
|
->label('Tipo servizio')
|
|
->options($tipoOptions),
|
|
|
|
SelectFilter::make('common_asset_scope')
|
|
->label('Ambito bene comune')
|
|
->options($commonScopeOptions)
|
|
->query(function (Builder $query, array $data): Builder {
|
|
$value = trim((string) ($data['value'] ?? ''));
|
|
if ($value === '') {
|
|
return $query;
|
|
}
|
|
|
|
return $query->where('meta->common_asset_scope', $value);
|
|
}),
|
|
|
|
SelectFilter::make('counter_scope')
|
|
->label('Tipo contatore')
|
|
->options($counterScopeOptions)
|
|
->query(function (Builder $query, array $data): Builder {
|
|
$value = trim((string) ($data['value'] ?? ''));
|
|
if ($value === '') {
|
|
return $query;
|
|
}
|
|
|
|
return $query->where('meta->counter_scope', $value);
|
|
}),
|
|
|
|
SelectFilter::make('tipo_gestione')
|
|
->label('Gestione (O/R/S)')
|
|
->options($gestioneOptions)
|
|
->query(function (Builder $query, array $data): Builder {
|
|
$value = (string) ($data['value'] ?? '');
|
|
if ($value === '') {
|
|
return $query;
|
|
}
|
|
|
|
return $query->whereHas('voceSpesa', fn(Builder $q) => $q->where('tipo_gestione', $value));
|
|
}),
|
|
])
|
|
->columns([
|
|
TextColumn::make('tipo')
|
|
->label('Tipo')
|
|
->formatStateUsing(fn($state) => $this->formatMetaOptionLabel($tipoOptions, $state))
|
|
->sortable(),
|
|
|
|
TextColumn::make('nome')->label('Nome')->searchable()->wrap()->sortable(),
|
|
|
|
TextColumn::make('meta.common_asset_label')
|
|
->label('Bene / locale')
|
|
->formatStateUsing(fn($state): string => trim((string) $state) !== '' ? trim((string) $state) : '—')
|
|
->searchable()
|
|
->wrap()
|
|
->toggleable(),
|
|
|
|
TextColumn::make('meta.common_asset_scope')
|
|
->label('Ambito')
|
|
->formatStateUsing(fn($state) => $this->formatMetaOptionLabel($commonScopeOptions, $state))
|
|
->toggleable(),
|
|
|
|
TextColumn::make('meta.allocation_scope')
|
|
->label('Addebito')
|
|
->formatStateUsing(fn($state) => $this->formatMetaOptionLabel($allocationOptions, $state))
|
|
->wrap()
|
|
->toggleable(),
|
|
|
|
TextColumn::make('meta.fiscal_scope')
|
|
->label('Trattamento')
|
|
->formatStateUsing(fn($state) => $this->formatMetaOptionLabel($fiscalScopeOptions, $state))
|
|
->wrap()
|
|
->toggleable(isToggledHiddenByDefault: true),
|
|
|
|
TextColumn::make('fornitore.ragione_sociale')->label('Fornitore')->searchable()->wrap()->toggleable(),
|
|
|
|
TextColumn::make('voceSpesa.descrizione')->label('Voce spesa')->searchable()->wrap()->toggleable(),
|
|
|
|
TextColumn::make('voceSpesa.tipo_gestione')
|
|
->label('Gestione')
|
|
->formatStateUsing(fn($state) => $gestioneOptions[strtolower(trim((string) $state))] ?? (string) ($state ?? '—'))
|
|
->toggleable(isToggledHiddenByDefault: true),
|
|
|
|
TextColumn::make('letture_count')->label('Letture')->sortable(),
|
|
|
|
TextColumn::make('meta.canale_acquisizione')
|
|
->label('Canale')
|
|
->formatStateUsing(function ($state): string {
|
|
$v = strtolower(trim((string) $state));
|
|
return match ($v) {
|
|
'manuale' => 'Manuale',
|
|
'email' => 'Email',
|
|
'whatsapp' => 'WhatsApp',
|
|
'import_file' => 'Import file',
|
|
'm_bus', 'mbus' => 'Contatore elettronico',
|
|
default => $v !== '' ? strtoupper($v) : '—',
|
|
};
|
|
})
|
|
->toggleable(isToggledHiddenByDefault: true),
|
|
|
|
TextColumn::make('meta.email_raccolta_letture')->label('Email raccolta')->toggleable(isToggledHiddenByDefault: true),
|
|
TextColumn::make('meta.whatsapp_raccolta_letture')->label('WhatsApp raccolta')->toggleable(isToggledHiddenByDefault: true),
|
|
TextColumn::make('meta.costo_lettura_unitario')->label('Costo lettura')->money('EUR')->toggleable(isToggledHiddenByDefault: true),
|
|
TextColumn::make('meta.costo_ripartizione')->label('Costo ripartizione')->money('EUR')->toggleable(isToggledHiddenByDefault: true),
|
|
|
|
TextColumn::make('codice_utenza')->label('Utenza')->searchable()->toggleable(isToggledHiddenByDefault: true),
|
|
TextColumn::make('codice_cliente')->label('Cliente')->searchable()->toggleable(isToggledHiddenByDefault: true),
|
|
TextColumn::make('codice_contratto')->label('Contratto')->searchable()->toggleable(isToggledHiddenByDefault: true),
|
|
TextColumn::make('contatore_matricola')->label('Matricola')->searchable()->toggleable(),
|
|
|
|
TextColumn::make('meta.counter_scope')
|
|
->label('Contatore')
|
|
->formatStateUsing(fn($state) => $this->formatMetaOptionLabel($counterScopeOptions, $state))
|
|
->toggleable(),
|
|
|
|
IconColumn::make('meta.has_dedicated_meter')
|
|
->label('Ded.')
|
|
->boolean()
|
|
->toggleable(isToggledHiddenByDefault: true),
|
|
|
|
TextColumn::make('meta.served_area_label')
|
|
->label('Area servita')
|
|
->wrap()
|
|
->toggleable(isToggledHiddenByDefault: true),
|
|
|
|
IconColumn::make('attivo')->label('Attivo')->boolean()->sortable(),
|
|
TextColumn::make('updated_at')->label('Aggiornato')->dateTime('d/m/Y H:i')->toggleable(isToggledHiddenByDefault: true),
|
|
])
|
|
->headerActions([
|
|
Action::make('create')
|
|
->label('Nuovo')
|
|
->icon('heroicon-o-plus')
|
|
->form([
|
|
Select::make('tipo')
|
|
->label('Tipo servizio')
|
|
->options($tipoOptions)
|
|
->required(),
|
|
TextInput::make('nome')->label('Nome')->maxLength(255),
|
|
TextInput::make('common_asset_label')->label('Bene comune / locale / servizio')->maxLength(255),
|
|
Select::make('common_asset_scope')
|
|
->label('Ambito bene comune')
|
|
->options($commonScopeOptions),
|
|
Select::make('allocation_scope')
|
|
->label('Ripartizione / addebito')
|
|
->options($allocationOptions),
|
|
Select::make('fiscal_scope')
|
|
->label('Trattamento economico / fiscale')
|
|
->options($fiscalScopeOptions),
|
|
Select::make('fornitore_id')
|
|
->label('Fornitore')
|
|
->searchable()
|
|
->options(fn() => $this->getFornitoriOptions()),
|
|
Select::make('voce_spesa_id')
|
|
->label('Voce spesa')
|
|
->searchable()
|
|
->options(fn() => $this->getVociSpesaOptions()),
|
|
TextInput::make('codice_utenza')->label('Codice utenza')->maxLength(255),
|
|
TextInput::make('codice_cliente')->label('Codice cliente')->maxLength(255),
|
|
TextInput::make('codice_contratto')->label('Codice contratto')->maxLength(255),
|
|
TextInput::make('contatore_matricola')->label('Matricola contatore')->maxLength(255),
|
|
Toggle::make('has_dedicated_meter')->label('Contatore dedicato')->default(false),
|
|
Select::make('counter_scope')
|
|
->label('Tipo contatore')
|
|
->options($counterScopeOptions)
|
|
->default('assente'),
|
|
TextInput::make('served_area_label')->label('Area servita / utilizzo')->maxLength(255),
|
|
Select::make('canale_acquisizione')
|
|
->label('Canale acquisizione letture')
|
|
->options([
|
|
'manuale' => 'Manuale',
|
|
'email' => 'Email',
|
|
'whatsapp' => 'WhatsApp',
|
|
'import_file' => 'Import file',
|
|
'm_bus' => 'Contatore elettronico',
|
|
]),
|
|
TextInput::make('email_raccolta_letture')->label('Email raccolta letture')->email()->maxLength(255),
|
|
TextInput::make('whatsapp_raccolta_letture')->label('Numero WhatsApp raccolta')->maxLength(50),
|
|
TextInput::make('costo_lettura_unitario')->label('Costo lettura (unitario)')->numeric(),
|
|
TextInput::make('costo_ripartizione')->label('Costo ripartizione')->numeric(),
|
|
Toggle::make('ripartizione_esterna')->label('Ripartizione affidata a ditta esterna')->default(false),
|
|
Textarea::make('operative_notes')->label('Note operative')->rows(3)->maxLength(2000),
|
|
Toggle::make('attivo')->label('Attivo')->default(true),
|
|
])
|
|
->action(function (array $data): void {
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return;
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
return;
|
|
}
|
|
|
|
StabileServizio::query()->create([
|
|
'stabile_id' => (int) $stabileId,
|
|
'tipo' => strtolower(trim((string) ($data['tipo'] ?? ''))),
|
|
'nome' => $this->resolveServiceDisplayName($data),
|
|
'fornitore_id' => (int) ($data['fornitore_id'] ?? 0) ?: null,
|
|
'voce_spesa_id' => (int) ($data['voce_spesa_id'] ?? 0) ?: null,
|
|
'codice_utenza' => trim((string) ($data['codice_utenza'] ?? '')) ?: null,
|
|
'codice_cliente' => trim((string) ($data['codice_cliente'] ?? '')) ?: null,
|
|
'codice_contratto' => trim((string) ($data['codice_contratto'] ?? '')) ?: null,
|
|
'contatore_matricola' => trim((string) ($data['contatore_matricola'] ?? '')) ?: null,
|
|
'attivo' => (bool) ($data['attivo'] ?? true),
|
|
'meta' => $this->buildServiceMetaPayload([], $data),
|
|
]);
|
|
}),
|
|
])
|
|
->actions([
|
|
Action::make('letture')
|
|
->label('Letture')
|
|
->icon('heroicon-o-clipboard-document-list')
|
|
->url(fn(StabileServizio $record): string => LettureServiziArchivio::getUrl(['servizio' => (int) $record->id], panel: 'admin-filament')),
|
|
|
|
Action::make('fatture')
|
|
->label('Fatture')
|
|
->icon('heroicon-o-document-text')
|
|
->url(fn() => FattureElettronicheP7mRicevute::getUrl(panel: 'admin-filament')),
|
|
|
|
Action::make('edit')
|
|
->label('Modifica')
|
|
->icon('heroicon-o-pencil-square')
|
|
->form([
|
|
Select::make('tipo')
|
|
->label('Tipo servizio')
|
|
->options($tipoOptions)
|
|
->required(),
|
|
TextInput::make('nome')->label('Nome')->maxLength(255),
|
|
TextInput::make('common_asset_label')->label('Bene comune / locale / servizio')->maxLength(255),
|
|
Select::make('common_asset_scope')
|
|
->label('Ambito bene comune')
|
|
->options($commonScopeOptions),
|
|
Select::make('allocation_scope')
|
|
->label('Ripartizione / addebito')
|
|
->options($allocationOptions),
|
|
Select::make('fiscal_scope')
|
|
->label('Trattamento economico / fiscale')
|
|
->options($fiscalScopeOptions),
|
|
Select::make('fornitore_id')
|
|
->label('Fornitore')
|
|
->searchable()
|
|
->options(fn() => $this->getFornitoriOptions()),
|
|
Select::make('voce_spesa_id')
|
|
->label('Voce spesa')
|
|
->searchable()
|
|
->options(fn() => $this->getVociSpesaOptions()),
|
|
TextInput::make('codice_utenza')->label('Codice utenza')->maxLength(255),
|
|
TextInput::make('codice_cliente')->label('Codice cliente')->maxLength(255),
|
|
TextInput::make('codice_contratto')->label('Codice contratto')->maxLength(255),
|
|
TextInput::make('contatore_matricola')->label('Matricola contatore')->maxLength(255),
|
|
Toggle::make('has_dedicated_meter')->label('Contatore dedicato')->default(false),
|
|
Select::make('counter_scope')
|
|
->label('Tipo contatore')
|
|
->options($counterScopeOptions),
|
|
TextInput::make('served_area_label')->label('Area servita / utilizzo')->maxLength(255),
|
|
Select::make('canale_acquisizione')
|
|
->label('Canale acquisizione letture')
|
|
->options([
|
|
'manuale' => 'Manuale',
|
|
'email' => 'Email',
|
|
'whatsapp' => 'WhatsApp',
|
|
'import_file' => 'Import file',
|
|
'm_bus' => 'Contatore elettronico',
|
|
]),
|
|
TextInput::make('email_raccolta_letture')->label('Email raccolta letture')->email()->maxLength(255),
|
|
TextInput::make('whatsapp_raccolta_letture')->label('Numero WhatsApp raccolta')->maxLength(50),
|
|
TextInput::make('costo_lettura_unitario')->label('Costo lettura (unitario)')->numeric(),
|
|
TextInput::make('costo_ripartizione')->label('Costo ripartizione')->numeric(),
|
|
Toggle::make('ripartizione_esterna')->label('Ripartizione affidata a ditta esterna')->default(false),
|
|
Textarea::make('operative_notes')->label('Note operative')->rows(3)->maxLength(2000),
|
|
Toggle::make('attivo')->label('Attivo')->default(true),
|
|
])
|
|
->fillForm(fn(StabileServizio $record): array=> [
|
|
'tipo' => (string) ($record->tipo ?? ''),
|
|
'nome' => (string) ($record->nome ?? ''),
|
|
'common_asset_label' => (string) (($record->meta['common_asset_label'] ?? '') ?: ''),
|
|
'common_asset_scope' => (string) (($record->meta['common_asset_scope'] ?? '') ?: ''),
|
|
'allocation_scope' => (string) (($record->meta['allocation_scope'] ?? '') ?: ''),
|
|
'fiscal_scope' => (string) (($record->meta['fiscal_scope'] ?? '') ?: ''),
|
|
'fornitore_id' => $record->fornitore_id,
|
|
'voce_spesa_id' => $record->voce_spesa_id,
|
|
'codice_utenza' => (string) ($record->codice_utenza ?? ''),
|
|
'codice_cliente' => (string) ($record->codice_cliente ?? ''),
|
|
'codice_contratto' => (string) ($record->codice_contratto ?? ''),
|
|
'contatore_matricola' => (string) ($record->contatore_matricola ?? ''),
|
|
'has_dedicated_meter' => (bool) ($record->meta['has_dedicated_meter'] ?? false),
|
|
'counter_scope' => (string) (($record->meta['counter_scope'] ?? '') ?: ''),
|
|
'served_area_label' => (string) (($record->meta['served_area_label'] ?? '') ?: ''),
|
|
'canale_acquisizione' => (string) (($record->meta['canale_acquisizione'] ?? '') ?: ''),
|
|
'email_raccolta_letture' => (string) (($record->meta['email_raccolta_letture'] ?? '') ?: ''),
|
|
'whatsapp_raccolta_letture' => (string) (($record->meta['whatsapp_raccolta_letture'] ?? '') ?: ''),
|
|
'costo_lettura_unitario' => $record->meta['costo_lettura_unitario'] ?? null,
|
|
'costo_ripartizione' => $record->meta['costo_ripartizione'] ?? null,
|
|
'ripartizione_esterna' => (bool) ($record->meta['ripartizione_esterna'] ?? false),
|
|
'operative_notes' => (string) (($record->meta['operative_notes'] ?? '') ?: ''),
|
|
'attivo' => (bool) ($record->attivo ?? true),
|
|
])
|
|
->action(function (StabileServizio $record, array $data): void {
|
|
$meta = $this->buildServiceMetaPayload(is_array($record->meta ?? null) ? $record->meta : [], $data);
|
|
|
|
$record->fill([
|
|
'tipo' => strtolower(trim((string) ($data['tipo'] ?? ''))),
|
|
'nome' => $this->resolveServiceDisplayName($data, (string) ($record->nome ?? '')),
|
|
'fornitore_id' => (int) ($data['fornitore_id'] ?? 0) ?: null,
|
|
'voce_spesa_id' => (int) ($data['voce_spesa_id'] ?? 0) ?: null,
|
|
'codice_utenza' => trim((string) ($data['codice_utenza'] ?? '')) ?: null,
|
|
'codice_cliente' => trim((string) ($data['codice_cliente'] ?? '')) ?: null,
|
|
'codice_contratto' => trim((string) ($data['codice_contratto'] ?? '')) ?: null,
|
|
'contatore_matricola' => trim((string) ($data['contatore_matricola'] ?? '')) ?: null,
|
|
'attivo' => (bool) ($data['attivo'] ?? true),
|
|
'meta' => $meta,
|
|
]);
|
|
$record->save();
|
|
}),
|
|
|
|
Action::make('delete')
|
|
->label('Elimina')
|
|
->icon('heroicon-o-trash')
|
|
->color('danger')
|
|
->requiresConfirmation()
|
|
->action(fn(StabileServizio $record) => $record->delete()),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
private function getServiceTypeOptions(): array
|
|
{
|
|
return [
|
|
'acqua' => 'Acqua',
|
|
'energia' => 'Energia',
|
|
'gas' => 'Gas',
|
|
'riscaldamento' => 'Riscaldamento',
|
|
'ascensore' => 'Ascensore',
|
|
'pulizie' => 'Pulizie',
|
|
'assicurazione' => 'Assicurazione',
|
|
'privacy' => 'Privacy',
|
|
'antenna' => 'Antenna',
|
|
'antincendio' => 'Antincendio',
|
|
'citofonia' => 'Citofonia',
|
|
'locale_comune' => 'Locale comune',
|
|
'spazio_comune' => 'Spazio / area comune',
|
|
'impianto' => 'Impianto comune',
|
|
'altro' => 'Altro',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
private function getCommonAssetScopeOptions(): array
|
|
{
|
|
return [
|
|
'locale' => 'Locale',
|
|
'spazio' => 'Spazio comune',
|
|
'impianto' => 'Impianto',
|
|
'area_esterna' => 'Area esterna / giardino',
|
|
'copertura' => 'Terrazzo / copertura',
|
|
'accesso' => 'Portone / accesso',
|
|
'servizio' => 'Servizio comune',
|
|
'sicurezza' => 'Sicurezza / antincendio',
|
|
'amministrativo' => 'Privacy / assicurazione / studio',
|
|
'altro' => 'Altro',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
private function getAllocationScopeOptions(): array
|
|
{
|
|
return [
|
|
'tutti_millesimi' => 'Tutti per millesimi',
|
|
'condomini_utilizzatori' => 'Condomini utilizzatori',
|
|
'inquilini_utilizzatori' => 'Inquilini utilizzatori',
|
|
'condomini_inquilini_utilizzatori' => 'Condomini + inquilini utilizzatori',
|
|
'rimborso_spese' => 'Rimborso spese uso bene comune',
|
|
'gestione_studio' => 'Gestione studio / amministrazione',
|
|
'altro' => 'Altro criterio',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
private function getFiscalScopeOptions(): array
|
|
{
|
|
return [
|
|
'spesa_condominiale' => 'Spesa condominiale',
|
|
'rimborso_non_fiscale' => 'Rimborso spese non fiscale',
|
|
'da_fatturare' => 'Da fatturare',
|
|
'polizza_o_servizio' => 'Polizza / servizio',
|
|
'non_applicabile' => 'Non applicabile',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
private function getCounterScopeOptions(): array
|
|
{
|
|
return [
|
|
'generale' => 'Generale',
|
|
'particolare' => 'Particolare / dedicato',
|
|
'assente' => 'Senza contatore',
|
|
];
|
|
}
|
|
|
|
private function formatMetaOptionLabel(array $options, mixed $state): string
|
|
{
|
|
$value = strtolower(trim((string) $state));
|
|
|
|
return $options[$value] ?? ($value !== '' ? (string) $state : '—');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $currentMeta
|
|
* @param array<string, mixed> $data
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function buildServiceMetaPayload(array $currentMeta, array $data): array
|
|
{
|
|
$meta = $currentMeta;
|
|
$meta['common_asset_label'] = trim((string) ($data['common_asset_label'] ?? '')) ?: null;
|
|
$meta['common_asset_scope'] = trim((string) ($data['common_asset_scope'] ?? '')) ?: null;
|
|
$meta['allocation_scope'] = trim((string) ($data['allocation_scope'] ?? '')) ?: null;
|
|
$meta['fiscal_scope'] = trim((string) ($data['fiscal_scope'] ?? '')) ?: null;
|
|
$meta['has_dedicated_meter'] = (bool) ($data['has_dedicated_meter'] ?? false);
|
|
$meta['counter_scope'] = trim((string) ($data['counter_scope'] ?? '')) ?: (($meta['has_dedicated_meter'] ?? false) ? 'particolare' : 'assente');
|
|
$meta['served_area_label'] = trim((string) ($data['served_area_label'] ?? '')) ?: null;
|
|
$meta['canale_acquisizione'] = trim((string) ($data['canale_acquisizione'] ?? '')) ?: null;
|
|
$meta['email_raccolta_letture'] = trim((string) ($data['email_raccolta_letture'] ?? '')) ?: null;
|
|
$meta['whatsapp_raccolta_letture'] = trim((string) ($data['whatsapp_raccolta_letture'] ?? '')) ?: null;
|
|
$meta['costo_lettura_unitario'] = is_numeric($data['costo_lettura_unitario'] ?? null) ? (float) $data['costo_lettura_unitario'] : null;
|
|
$meta['costo_ripartizione'] = is_numeric($data['costo_ripartizione'] ?? null) ? (float) $data['costo_ripartizione'] : null;
|
|
$meta['ripartizione_esterna'] = (bool) ($data['ripartizione_esterna'] ?? false);
|
|
$meta['operative_notes'] = trim((string) ($data['operative_notes'] ?? '')) ?: null;
|
|
|
|
return $meta;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
*/
|
|
private function resolveServiceDisplayName(array $data, string $fallback = ''): string
|
|
{
|
|
$name = trim((string) ($data['nome'] ?? ''));
|
|
if ($name !== '') {
|
|
return $name;
|
|
}
|
|
|
|
$commonLabel = trim((string) ($data['common_asset_label'] ?? ''));
|
|
if ($commonLabel !== '') {
|
|
return $commonLabel;
|
|
}
|
|
|
|
return trim($fallback);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function getFornitoriOptions(): array
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return [];
|
|
}
|
|
|
|
$query = Fornitore::query();
|
|
|
|
if (! $user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$activeStabile = StabileContext::getActiveStabile($user);
|
|
$adminId = (int) ($activeStabile?->amministratore_id ?: 0);
|
|
if ($adminId > 0) {
|
|
$query->where('amministratore_id', $adminId);
|
|
}
|
|
}
|
|
|
|
return $query
|
|
->orderBy('ragione_sociale')
|
|
->get(['id', 'ragione_sociale'])
|
|
->mapWithKeys(fn(Fornitore $f) => [(int) $f->id => (string) ($f->ragione_sociale ?? ('Fornitore #' . $f->id))])
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function getVociSpesaOptions(): array
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return [];
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
return [];
|
|
}
|
|
|
|
return VoceSpesa::query()
|
|
->where('stabile_id', (int) $stabileId)
|
|
->orderBy('tipo_gestione')
|
|
->orderBy('descrizione')
|
|
->get(['id', 'descrizione', 'tipo_gestione'])
|
|
->mapWithKeys(function (VoceSpesa $v): array {
|
|
$tipo = strtolower(trim((string) ($v->tipo_gestione ?? '')));
|
|
$prefix = $tipo !== '' ? strtoupper(substr($tipo, 0, 1)) . ' - ' : '';
|
|
return [(int) $v->id => $prefix . (string) ($v->descrizione ?? ('Voce #' . $v->id))];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
private function resolveActiveStabileId(): ?int
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return null;
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
return $stabileId ? (int) $stabileId : null;
|
|
}
|
|
|
|
private function resolveActiveAnnoGestione(): int
|
|
{
|
|
$user = Auth::user();
|
|
|
|
return AnnoGestioneContext::resolveActiveAnno($user instanceof User ? $user : null);
|
|
}
|
|
|
|
private function resolveActiveAcquaServizio(): ?StabileServizio
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
return null;
|
|
}
|
|
|
|
return StabileServizio::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('tipo', 'acqua')
|
|
->orderByDesc('attivo')
|
|
->orderBy('id')
|
|
->first();
|
|
}
|
|
|
|
private function buildPublicAcquaReadingUrl(int $servizioId, int $unitaId, ?int $requestId = null): string
|
|
{
|
|
return URL::temporarySignedRoute(
|
|
'public.water-reading.show',
|
|
now()->addDays(30),
|
|
array_filter([
|
|
'servizio' => $servizioId,
|
|
'unita' => $unitaId,
|
|
'request' => $requestId,
|
|
], fn($value) => $value !== null)
|
|
);
|
|
}
|
|
|
|
/** @return array<int, string> */
|
|
private function resolveLegacyCodiciStabile(): array
|
|
{
|
|
$user = Auth::user();
|
|
|
|
return StabileContext::legacyCodeCandidates(StabileContext::getActiveStabile($user instanceof User ? $user : null));
|
|
}
|
|
|
|
private function applyActiveYearFilterToReadingQuery(Builder $query): Builder
|
|
{
|
|
$year = $this->resolveActiveAnnoGestione();
|
|
|
|
return $query->where(function (Builder $inner) use ($year): void {
|
|
$inner->whereYear('periodo_al', $year)
|
|
->orWhere(function (Builder $fallback) use ($year): void {
|
|
$fallback->whereNull('periodo_al')
|
|
->whereYear('periodo_dal', $year);
|
|
})
|
|
->orWhere(function (Builder $fallback) use ($year): void {
|
|
$fallback->whereNull('periodo_al')
|
|
->whereNull('periodo_dal')
|
|
->whereYear('created_at', $year);
|
|
});
|
|
});
|
|
}
|
|
|
|
/** @return array{totale_fatture: float, totale_operazioni_ac12_legacy: float, delta_operazioni_vs_fatture: float, totale_letture: int, totale_letture_con_riferimento: int, totale_consumi_mc: float, totale_tariffe: int} */
|
|
public function getAcquaDashboardStatsProperty(): array
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
return [
|
|
'totale_fatture' => 0.0,
|
|
'totale_operazioni_ac12_legacy' => 0.0,
|
|
'delta_operazioni_vs_fatture' => 0.0,
|
|
'totale_letture' => 0,
|
|
'totale_letture_con_riferimento' => 0,
|
|
'totale_consumi_mc' => 0.0,
|
|
'totale_tariffe' => 0,
|
|
];
|
|
}
|
|
|
|
$fattureRows = $this->acquaFatturePerGestione;
|
|
$totaleFatture = (float) collect($fattureRows)->sum(fn(array $r): float => (float) ($r['totale_fatture'] ?? 0));
|
|
|
|
$totaleLetture = (int) StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereHas('servizio', fn(Builder $q) => $q->where('tipo', 'acqua'))
|
|
->count();
|
|
|
|
$totaleLettureConRiferimento = (int) StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereHas('servizio', fn(Builder $q) => $q->where('tipo', 'acqua'))
|
|
->whereNotNull('riferimento_acquisizione')
|
|
->where('riferimento_acquisizione', '!=', '')
|
|
->count();
|
|
|
|
$totaleConsumi = (float) StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereHas('servizio', fn(Builder $q) => $q->where('tipo', 'acqua'))
|
|
->sum('consumo_valore');
|
|
|
|
$totaleTariffe = (int) StabileServizioTariffa::query()
|
|
->where('stabile_id', $stabileId)
|
|
->count();
|
|
|
|
$legacySummary = $this->acquaLegacyOperazioniSummary;
|
|
$totaleLegacyAc12 = (float) ($legacySummary['totale'] ?? 0);
|
|
|
|
return [
|
|
'totale_fatture' => round($totaleFatture, 2),
|
|
'totale_operazioni_ac12_legacy' => round($totaleLegacyAc12, 2),
|
|
'delta_operazioni_vs_fatture' => round($totaleFatture - $totaleLegacyAc12, 2),
|
|
'totale_letture' => $totaleLetture,
|
|
'totale_letture_con_riferimento' => $totaleLettureConRiferimento,
|
|
'totale_consumi_mc' => round($totaleConsumi, 3),
|
|
'totale_tariffe' => $totaleTariffe,
|
|
];
|
|
}
|
|
|
|
private function normalizeInvoiceNumber(mixed $number): ?string
|
|
{
|
|
if ($number === null) {
|
|
return null;
|
|
}
|
|
|
|
$raw = strtoupper(trim((string) $number));
|
|
if ($raw === '') {
|
|
return null;
|
|
}
|
|
|
|
$normalized = preg_replace('/[^A-Z0-9]/', '', $raw);
|
|
return $normalized !== '' ? $normalized : null;
|
|
}
|
|
|
|
private function parseLegacyDate(mixed $value): ?string
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return (string) \Carbon\Carbon::parse((string) $value)->format('Y-m-d');
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** @return array{totale: float, voci: array<string, float>, righe: int} */
|
|
public function getAcquaLegacyOperazioniSummaryProperty(): array
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('operazioni')) {
|
|
return ['totale' => 0.0, 'voci' => [], 'righe' => 0];
|
|
}
|
|
|
|
$legacyCodes = $this->resolveLegacyCodiciStabile();
|
|
$year = $this->resolveActiveAnnoGestione();
|
|
|
|
$query = DB::connection('gescon_import')
|
|
->table('operazioni')
|
|
->where('gestione', 'O')
|
|
->whereIn('cod_spe', ['AC1', 'AC2']);
|
|
|
|
if ($legacyCodes !== [] && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile')) {
|
|
$query->whereIn('cod_stabile', $legacyCodes);
|
|
}
|
|
|
|
if (Schema::connection('gescon_import')->hasColumn('operazioni', 'dt_spe')) {
|
|
$query->whereYear('dt_spe', $year);
|
|
}
|
|
|
|
$rows = $query
|
|
->select('cod_spe')
|
|
->selectRaw('COUNT(*) as righe')
|
|
->selectRaw('SUM(COALESCE(importo_euro, importo, 0)) as totale')
|
|
->groupBy('cod_spe')
|
|
->get();
|
|
|
|
$voci = [];
|
|
$totale = 0.0;
|
|
$righe = 0;
|
|
foreach ($rows as $row) {
|
|
$cod = strtoupper(trim((string) ($row->cod_spe ?? '')));
|
|
if ($cod === '') {
|
|
continue;
|
|
}
|
|
|
|
$imp = (float) ($row->totale ?? 0);
|
|
$voci[$cod] = round($imp, 2);
|
|
$totale += $imp;
|
|
$righe += (int) ($row->righe ?? 0);
|
|
}
|
|
|
|
return [
|
|
'totale' => round($totale, 2),
|
|
'voci' => $voci,
|
|
'righe' => $righe,
|
|
];
|
|
}
|
|
|
|
/** @return array<int, array<string,mixed>> */
|
|
public function getAcquaLegacyOperazioniRowsProperty(): array
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('operazioni')) {
|
|
return [];
|
|
}
|
|
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
return [];
|
|
}
|
|
|
|
$legacyCodes = $this->resolveLegacyCodiciStabile();
|
|
$year = $this->resolveActiveAnnoGestione();
|
|
|
|
$query = DB::connection('gescon_import')
|
|
->table('operazioni')
|
|
->where('gestione', 'O')
|
|
->whereIn('cod_spe', ['AC1', 'AC2']);
|
|
|
|
if ($legacyCodes !== [] && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile')) {
|
|
$query->whereIn('cod_stabile', $legacyCodes);
|
|
}
|
|
|
|
if (Schema::connection('gescon_import')->hasColumn('operazioni', 'dt_spe')) {
|
|
$query->whereYear('dt_spe', $year);
|
|
}
|
|
|
|
$legacyRows = $query
|
|
->orderByDesc('dt_spe')
|
|
->orderByDesc('id_operaz')
|
|
->limit(120)
|
|
->get([
|
|
'id_operaz',
|
|
'dt_spe',
|
|
'cod_spe',
|
|
'cod_for',
|
|
'num_fat',
|
|
'dt_fat',
|
|
'benef',
|
|
'importo_euro',
|
|
'importo',
|
|
'fe_uid',
|
|
]);
|
|
|
|
if ($legacyRows->isEmpty()) {
|
|
return [];
|
|
}
|
|
|
|
$codForList = $legacyRows
|
|
->pluck('cod_for')
|
|
->filter()
|
|
->map(fn($v) => trim((string) $v))
|
|
->filter()
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
$codForNoZero = array_values(array_filter(array_unique(array_map(fn($v) => ltrim((string) $v, '0'), $codForList))));
|
|
$codForAll = array_values(array_unique(array_merge($codForList, $codForNoZero)));
|
|
|
|
$legacyFornitoriMap = [];
|
|
$codFornToLegacyId = [];
|
|
if (! empty($codForAll)) {
|
|
$useStg = Schema::hasTable('stg_fornitori_gescon');
|
|
$fornRows = $useStg
|
|
? DB::table('stg_fornitori_gescon')
|
|
->whereIn('cod_forn', $codForAll)
|
|
->get(['cod_forn', 'legacy_id_fornitore', 'denominazione', 'cognome', 'nome'])
|
|
: DB::connection('gescon_import')
|
|
->table('mdb_fornitori')
|
|
->whereIn('cod_forn', $codForAll)
|
|
->get(['cod_forn', 'id_fornitore', 'denominazione', 'cognome', 'nome']);
|
|
|
|
foreach ($fornRows as $f) {
|
|
$code = trim((string) ($f->cod_forn ?? ''));
|
|
if ($code === '') {
|
|
continue;
|
|
}
|
|
|
|
$label = trim((string) ($f->denominazione ?? ''));
|
|
if ($label === '') {
|
|
$label = trim((string) (($f->nome ?? '') . ' ' . ($f->cognome ?? '')));
|
|
}
|
|
|
|
$legacyId = $useStg ? ($f->cod_forn ?? null) : ($f->id_fornitore ?? null);
|
|
$legacyFornitoriMap[$code] = $label !== '' ? $label : null;
|
|
$codFornToLegacyId[$code] = $legacyId;
|
|
|
|
$noZero = ltrim($code, '0');
|
|
if ($noZero !== '') {
|
|
$legacyFornitoriMap[$noZero] = $legacyFornitoriMap[$code];
|
|
$codFornToLegacyId[$noZero] = $legacyId;
|
|
}
|
|
}
|
|
}
|
|
|
|
$legacyIds = array_values(array_filter(array_unique(array_values($codFornToLegacyId))));
|
|
$fornitoriIdMap = [];
|
|
if (! empty($legacyIds) && Schema::hasTable('fornitori')) {
|
|
$fornitoriRows = DB::table('fornitori')
|
|
->whereIn('old_id', $legacyIds)
|
|
->get(['id', 'old_id']);
|
|
|
|
foreach ($fornitoriRows as $fr) {
|
|
$fornitoriIdMap[(string) ($fr->old_id ?? '')] = (int) ($fr->id ?? 0);
|
|
}
|
|
}
|
|
|
|
$fattureByFornitore = [];
|
|
$fattureByStrict = [];
|
|
$fattureByGlobal = [];
|
|
if (! empty($fornitoriIdMap) && Schema::hasTable('contabilita_fatture_fornitori')) {
|
|
$fornitoreIds = array_values(array_unique(array_filter(array_values($fornitoriIdMap))));
|
|
if (! empty($fornitoreIds)) {
|
|
$fatture = DB::table('contabilita_fatture_fornitori')
|
|
->where('stabile_id', $stabileId)
|
|
->whereIn('fornitore_id', $fornitoreIds)
|
|
->get([
|
|
'id',
|
|
'fornitore_id',
|
|
'fattura_elettronica_id',
|
|
'numero_documento',
|
|
'data_documento',
|
|
'totale',
|
|
'netto_da_pagare',
|
|
]);
|
|
|
|
foreach ($fatture as $f) {
|
|
$fid = (int) ($f->fornitore_id ?? 0);
|
|
if ($fid <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$fattureByFornitore[$fid][] = $f;
|
|
$docNumber = $this->normalizeInvoiceNumber($f->numero_documento ?? null);
|
|
$docDate = $this->parseLegacyDate($f->data_documento ?? null);
|
|
if ($docNumber && $docDate) {
|
|
$strictKey = $fid . '|' . $docNumber . '|' . $docDate;
|
|
$fattureByStrict[$strictKey][] = $f;
|
|
$globalKey = $docNumber . '|' . $docDate;
|
|
$fattureByGlobal[$globalKey][] = $f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $legacyRows->map(function ($row) use ($legacyFornitoriMap, $codFornToLegacyId, $fornitoriIdMap, $fattureByFornitore, $fattureByStrict, $fattureByGlobal): array {
|
|
$codFor = trim((string) ($row->cod_for ?? ''));
|
|
$codForNoZero = ltrim($codFor, '0');
|
|
$legacyId = $codFornToLegacyId[$codFor] ?? ($codForNoZero !== '' ? ($codFornToLegacyId[$codForNoZero] ?? null) : null);
|
|
$fornitoreId = ($legacyId !== null && isset($fornitoriIdMap[(string) $legacyId])) ? (int) $fornitoriIdMap[(string) $legacyId] : null;
|
|
$fornitoreLegacy = $legacyFornitoriMap[$codFor] ?? ($codForNoZero !== '' ? ($legacyFornitoriMap[$codForNoZero] ?? null) : null);
|
|
|
|
$numFatNorm = $this->normalizeInvoiceNumber($row->num_fat ?? null);
|
|
$dtFatIso = $this->parseLegacyDate($row->dt_fat ?? null);
|
|
$importo = (float) ($row->importo_euro ?? $row->importo ?? 0);
|
|
|
|
$matched = null;
|
|
$matchType = 'none';
|
|
|
|
if ($fornitoreId && $numFatNorm && $dtFatIso) {
|
|
$key = $fornitoreId . '|' . $numFatNorm . '|' . $dtFatIso;
|
|
if (! empty($fattureByStrict[$key])) {
|
|
$matched = $fattureByStrict[$key][0];
|
|
$matchType = 'strict';
|
|
}
|
|
}
|
|
|
|
if (! $matched && $numFatNorm && $dtFatIso) {
|
|
$gKey = $numFatNorm . '|' . $dtFatIso;
|
|
$globalMatches = $fattureByGlobal[$gKey] ?? [];
|
|
if (count($globalMatches) === 1) {
|
|
$matched = $globalMatches[0];
|
|
$matchType = 'global';
|
|
}
|
|
}
|
|
|
|
if (! $matched && $fornitoreId && ! empty($fattureByFornitore[$fornitoreId])) {
|
|
foreach ($fattureByFornitore[$fornitoreId] as $f) {
|
|
$tot = (float) ($f->totale ?? 0);
|
|
$net = (float) ($f->netto_da_pagare ?? 0);
|
|
if (abs(abs($importo) - abs($tot)) <= 0.01 || abs(abs($importo) - abs($net)) <= 0.01) {
|
|
$matched = $f;
|
|
$matchType = 'amount';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'id_operaz' => (int) ($row->id_operaz ?? 0),
|
|
'data_operazione' => $this->parseLegacyDate($row->dt_spe ?? null),
|
|
'cod_spe' => strtoupper(trim((string) ($row->cod_spe ?? ''))),
|
|
'cod_for' => $codFor,
|
|
'fornitore_legacy' => $fornitoreLegacy,
|
|
'num_fat' => (string) ($row->num_fat ?? ''),
|
|
'dt_fat' => $dtFatIso,
|
|
'benef' => (string) ($row->benef ?? ''),
|
|
'importo' => round($importo, 2),
|
|
'fe_uid' => (string) ($row->fe_uid ?? ''),
|
|
'match_type' => $matchType,
|
|
'matched_fattura_id' => $matched ? (int) ($matched->id ?? 0) : null,
|
|
'matched_fe_id' => $matched ? ((int) ($matched->fattura_elettronica_id ?? 0) ?: null): null,
|
|
'matched_numero' => $matched ? (string) ($matched->numero_documento ?? '') : null,
|
|
'matched_data' => $matched ? $this->parseLegacyDate($matched->data_documento ?? null) : null,
|
|
'matched_totale' => $matched ? (float) ($matched->totale ?? $matched->netto_da_pagare ?? 0) : null,
|
|
];
|
|
})->all();
|
|
}
|
|
|
|
/** @return array{servizio_id: int|null, servizio_nome: string|null, fornitore_id: int|null, fornitore_nome: string|null, codice_utenza: string|null, codice_cliente: string|null, codice_contratto: string|null, matricola: string|null, suggerito_fornitore: string|null} */
|
|
public function getAcquaContrattoStabileProperty(): array
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
return [
|
|
'servizio_id' => null,
|
|
'servizio_nome' => null,
|
|
'fornitore_id' => null,
|
|
'fornitore_nome' => null,
|
|
'codice_utenza' => null,
|
|
'codice_cliente' => null,
|
|
'codice_contratto' => null,
|
|
'matricola' => null,
|
|
'suggerito_fornitore' => null,
|
|
];
|
|
}
|
|
|
|
$servizio = StabileServizio::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('tipo', 'acqua')
|
|
->with('fornitore:id,ragione_sociale')
|
|
->orderByDesc('attivo')
|
|
->orderBy('id')
|
|
->first();
|
|
|
|
$fornitoreSuggerito = Fornitore::query()
|
|
->whereRaw('LOWER(ragione_sociale) like ?', ['%acea%ato2%'])
|
|
->value('ragione_sociale') ?? Fornitore::query()
|
|
->whereRaw('LOWER(ragione_sociale) like ?', ['%acea%'])
|
|
->value('ragione_sociale');
|
|
|
|
return [
|
|
'servizio_id' => $servizio?->id,
|
|
'servizio_nome' => $servizio?->nome,
|
|
'fornitore_id' => $servizio?->fornitore_id,
|
|
'fornitore_nome' => $servizio?->fornitore?->ragione_sociale,
|
|
'codice_utenza' => $servizio?->codice_utenza,
|
|
'codice_cliente' => $servizio?->codice_cliente,
|
|
'codice_contratto' => $servizio?->codice_contratto,
|
|
'matricola' => $servizio?->contatore_matricola,
|
|
'suggerito_fornitore' => $fornitoreSuggerito ? (string) $fornitoreSuggerito : null,
|
|
];
|
|
}
|
|
|
|
/** @return array<int, array{id_cond:int|null, ruolo:string, nominativo:string, scala:string, interno:string, quota_euro:float, lettura_iniziale:?float, consumo_mc:?float}> */
|
|
public function getAcquaRipartoNominativiLegacyProperty(): array
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('dett_tab') || ! Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
return [];
|
|
}
|
|
|
|
$legacyCodes = $this->resolveLegacyCodiciStabile();
|
|
if ($legacyCodes === []) {
|
|
return [];
|
|
}
|
|
|
|
$legacyYear = (string) $this->resolveActiveAnnoGestione();
|
|
|
|
$condQuery = DB::connection('gescon_import')
|
|
->table('condomin')
|
|
->whereIn('cod_stabile', $legacyCodes);
|
|
|
|
if ($legacyYear !== '' && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
|
$condQuery->where('legacy_year', $legacyYear);
|
|
}
|
|
|
|
$condRows = $condQuery->get([
|
|
'id_cond',
|
|
'scala',
|
|
'interno',
|
|
'cognome',
|
|
'nome',
|
|
'proprietario_denominazione',
|
|
'inquilino_denominazione',
|
|
'inquil_nome',
|
|
])
|
|
->keyBy('id_cond');
|
|
|
|
$rowsQuery = DB::connection('gescon_import')
|
|
->table('dett_tab')
|
|
->where('cod_tab', 'ACQUA')
|
|
->whereIn('cod_stabile', $legacyCodes);
|
|
|
|
if ($legacyYear !== '' && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
|
|
$rowsQuery->where('legacy_year', $legacyYear);
|
|
}
|
|
|
|
$rows = $rowsQuery
|
|
->orderBy('id_cond')
|
|
->orderBy('cond_inquil')
|
|
->get(['id_cond', 'cond_inquil', 'cons_euro']);
|
|
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
|
|
$letturaByUi = [];
|
|
if ($stabileId) {
|
|
$lettureRows = StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereHas('servizio', fn(Builder $q) => $q->where('tipo', 'acqua'))
|
|
->whereNotNull('unita_immobiliare_id')
|
|
->orderByDesc('created_at')
|
|
->get(['unita_immobiliare_id', 'lettura_inizio', 'consumo_valore']);
|
|
|
|
foreach ($lettureRows as $lr) {
|
|
$uiId = (int) ($lr->unita_immobiliare_id ?? 0);
|
|
if ($uiId <= 0 || isset($letturaByUi[$uiId])) {
|
|
continue;
|
|
}
|
|
|
|
$letturaByUi[$uiId] = [
|
|
'lettura_iniziale' => is_numeric($lr->lettura_inizio) ? (float) $lr->lettura_inizio : null,
|
|
'consumo_mc' => is_numeric($lr->consumo_valore) ? (float) $lr->consumo_valore : null,
|
|
];
|
|
}
|
|
}
|
|
|
|
$uiByLegacyCond = [];
|
|
$uiByScalaInt = [];
|
|
if ($stabileId && Schema::hasTable('unita_immobiliari')) {
|
|
$uiRows = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->get(['id', 'legacy_cond_id', 'scala', 'interno']);
|
|
|
|
foreach ($uiRows as $ui) {
|
|
$uiId = (int) ($ui->id ?? 0);
|
|
if ($uiId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
if (is_numeric($ui->legacy_cond_id ?? null)) {
|
|
$uiByLegacyCond[(int) $ui->legacy_cond_id] = $uiId;
|
|
}
|
|
|
|
$scala = strtoupper(trim((string) ($ui->scala ?? '')));
|
|
$interno = strtoupper(trim((string) ($ui->interno ?? '')));
|
|
if ($scala !== '' || $interno !== '') {
|
|
$uiByScalaInt[$scala . '|' . $interno] = $uiId;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $rows->map(function ($r) use ($condRows, $uiByLegacyCond, $uiByScalaInt, $letturaByUi): ?array {
|
|
$cond = $condRows[$r->id_cond] ?? null;
|
|
$ruolo = strtoupper(trim((string) ($r->cond_inquil ?? '')));
|
|
$nominativo = '';
|
|
if ($cond) {
|
|
if ($ruolo === 'I') {
|
|
$nominativo = trim((string) ($cond->inquilino_denominazione ?? ''));
|
|
if ($nominativo === '') {
|
|
$nominativo = trim((string) ($cond->inquil_nome ?? ''));
|
|
}
|
|
} else {
|
|
$nominativo = trim((string) ($cond->proprietario_denominazione ?? ''));
|
|
if ($nominativo === '') {
|
|
$nominativo = trim((string) (($cond->cognome ?? '') . ' ' . ($cond->nome ?? '')));
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($nominativo === '') {
|
|
return null;
|
|
}
|
|
|
|
$uiId = null;
|
|
if (is_numeric($r->id_cond ?? null)) {
|
|
$uiId = $uiByLegacyCond[(int) $r->id_cond] ?? null;
|
|
}
|
|
|
|
if (! $uiId) {
|
|
$scala = strtoupper(trim((string) ($cond->scala ?? '')));
|
|
$interno = strtoupper(trim((string) ($cond->interno ?? '')));
|
|
$uiId = $uiByScalaInt[$scala . '|' . $interno] ?? null;
|
|
}
|
|
|
|
$lettura = ($uiId && isset($letturaByUi[$uiId]))
|
|
? $letturaByUi[$uiId]
|
|
: ['lettura_iniziale' => null, 'consumo_mc' => null];
|
|
|
|
return [
|
|
'id_cond' => is_numeric($r->id_cond ?? null) ? (int) $r->id_cond : null,
|
|
'ruolo' => $ruolo !== '' ? $ruolo : 'C',
|
|
'nominativo' => $nominativo,
|
|
'scala' => trim((string) ($cond->scala ?? '')),
|
|
'interno' => trim((string) ($cond->interno ?? '')),
|
|
'quota_euro' => round((float) ($r->cons_euro ?? 0), 2),
|
|
'lettura_iniziale' => isset($lettura['lettura_iniziale']) && is_numeric($lettura['lettura_iniziale']) ? (float) $lettura['lettura_iniziale'] : null,
|
|
'consumo_mc' => isset($lettura['consumo_mc']) && is_numeric($lettura['consumo_mc']) ? (float) $lettura['consumo_mc'] : null,
|
|
];
|
|
})->filter()->values()->all();
|
|
}
|
|
|
|
/** @return array<int, array{gestione: string, fatture_count: int, totale_fatture: float}> */
|
|
public function getAcquaFatturePerGestioneProperty(): array
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
return [];
|
|
}
|
|
|
|
$year = $this->resolveActiveAnnoGestione();
|
|
|
|
if (Schema::hasTable('contabilita_fatture_fornitori')) {
|
|
$fornitoreIds = StabileServizio::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('tipo', 'acqua')
|
|
->whereNotNull('fornitore_id')
|
|
->pluck('fornitore_id')
|
|
->map(fn($v) => (int) $v)
|
|
->filter(fn($v) => $v > 0)
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
if ($fornitoreIds !== []) {
|
|
$contabili = DB::table('contabilita_fatture_fornitori as f')
|
|
->leftJoin('gestioni_contabili as g', 'g.id', '=', 'f.gestione_id')
|
|
->where('f.stabile_id', $stabileId)
|
|
->whereIn('f.fornitore_id', $fornitoreIds)
|
|
->when(Schema::hasColumn('gestioni_contabili', 'anno_gestione'), fn($q) => $q->where('g.anno_gestione', $year))
|
|
->when(! Schema::hasColumn('gestioni_contabili', 'anno_gestione') && Schema::hasColumn('contabilita_fatture_fornitori', 'data_documento'), fn($q) => $q->whereYear('f.data_documento', $year))
|
|
->get(['f.id', 'f.totale', 'f.fattura_elettronica_id', 'g.tipo_gestione']);
|
|
|
|
if ($contabili->isNotEmpty()) {
|
|
$grouped = [];
|
|
foreach ($contabili as $row) {
|
|
$g = strtolower(trim((string) ($row->tipo_gestione ?? '')));
|
|
if ($g === '') {
|
|
$g = 'non definita';
|
|
}
|
|
|
|
if (! isset($grouped[$g])) {
|
|
$grouped[$g] = [
|
|
'gestione' => $g,
|
|
'fatture_count' => 0,
|
|
'totale_fatture' => 0.0,
|
|
'fe_count' => 0,
|
|
'manual_count' => 0,
|
|
];
|
|
}
|
|
|
|
$grouped[$g]['fatture_count']++;
|
|
$grouped[$g]['totale_fatture'] += (float) ($row->totale ?? 0);
|
|
|
|
if (is_numeric($row->fattura_elettronica_id ?? null) && (int) $row->fattura_elettronica_id > 0) {
|
|
$grouped[$g]['fe_count']++;
|
|
} else {
|
|
$grouped[$g]['manual_count']++;
|
|
}
|
|
}
|
|
|
|
$out = array_values($grouped);
|
|
usort($out, fn($a, $b) => strcmp((string) $a['gestione'], (string) $b['gestione']));
|
|
foreach ($out as &$row) {
|
|
$row['totale_fatture'] = round((float) $row['totale_fatture'], 2);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
}
|
|
}
|
|
|
|
$rows = StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereHas('servizio', fn(Builder $q) => $q->where('tipo', 'acqua'))
|
|
->whereNotNull('fattura_elettronica_id')
|
|
->with(['voceSpesa:id,tipo_gestione'])
|
|
->tap(fn(Builder $query) => $this->applyActiveYearFilterToReadingQuery($query))
|
|
->get(['fattura_elettronica_id', 'voce_spesa_id', 'importo_totale']);
|
|
|
|
$byFattura = [];
|
|
foreach ($rows as $row) {
|
|
$fatturaId = (int) ($row->fattura_elettronica_id ?? 0);
|
|
if ($fatturaId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
if (! isset($byFattura[$fatturaId])) {
|
|
$byFattura[$fatturaId] = [
|
|
'gestione' => 'non definita',
|
|
'importo' => 0.0,
|
|
];
|
|
}
|
|
|
|
$tipoGestione = strtolower(trim((string) ($row->voceSpesa?->tipo_gestione ?? '')));
|
|
if ($tipoGestione !== '') {
|
|
$byFattura[$fatturaId]['gestione'] = $tipoGestione;
|
|
}
|
|
|
|
$imp = is_numeric($row->importo_totale) ? (float) $row->importo_totale : 0.0;
|
|
if ($imp > (float) $byFattura[$fatturaId]['importo']) {
|
|
$byFattura[$fatturaId]['importo'] = $imp;
|
|
}
|
|
}
|
|
|
|
$grouped = [];
|
|
foreach ($byFattura as $item) {
|
|
$g = (string) ($item['gestione'] ?? 'non definita');
|
|
if (! isset($grouped[$g])) {
|
|
$grouped[$g] = ['gestione' => $g, 'fatture_count' => 0, 'totale_fatture' => 0.0, 'fe_count' => 0, 'manual_count' => 0];
|
|
}
|
|
$grouped[$g]['fatture_count']++;
|
|
$grouped[$g]['totale_fatture'] += (float) ($item['importo'] ?? 0);
|
|
$grouped[$g]['fe_count']++;
|
|
}
|
|
|
|
$out = array_values($grouped);
|
|
usort($out, fn($a, $b) => strcmp((string) $a['gestione'], (string) $b['gestione']));
|
|
|
|
foreach ($out as &$row) {
|
|
$row['totale_fatture'] = round((float) $row['totale_fatture'], 2);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/** @return array<int, array<string,mixed>> */
|
|
public function getAcquaLettureCondominiProperty(): array
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
return [];
|
|
}
|
|
|
|
return StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereHas('servizio', fn(Builder $q) => $q->where('tipo', 'acqua'))
|
|
->with([
|
|
'unitaImmobiliare:id,codice_unita,interno,scala',
|
|
'servizio:id,nome,contatore_matricola',
|
|
])
|
|
->tap(fn(Builder $query) => $this->applyActiveYearFilterToReadingQuery($query))
|
|
->orderByDesc('created_at')
|
|
->limit(60)
|
|
->get()
|
|
->map(function (StabileServizioLettura $row): array {
|
|
return [
|
|
'id' => (int) $row->id,
|
|
'data_ricezione' => optional($row->created_at)?->format('d/m/Y H:i'),
|
|
'servizio' => (string) ($row->servizio?->nome ?? 'Acqua'),
|
|
'matricola' => (string) ($row->servizio?->contatore_matricola ?? '—'),
|
|
'unita' => trim((string) ($row->unitaImmobiliare?->codice_unita ?? '') . ' ' . (string) ($row->unitaImmobiliare?->interno ?? '')),
|
|
'canale' => (string) ($row->canale_acquisizione ?? '—'),
|
|
'riferimento' => (string) ($row->riferimento_acquisizione ?? '—'),
|
|
'periodo' => ((string) optional($row->periodo_dal)->format('d/m/Y')) . ' - ' . ((string) optional($row->periodo_al)->format('d/m/Y')),
|
|
'consumo_valore' => is_numeric($row->consumo_valore) ? (float) $row->consumo_valore : null,
|
|
'consumo_unita' => (string) ($row->consumo_unita ?: 'mc'),
|
|
];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
/** @return array{totale:int,completate:int,pendenti:int,sollecitate:int} */
|
|
public function getAcquaCampagnaStatsProperty(): array
|
|
{
|
|
$rows = $this->acquaCampagnaRows;
|
|
|
|
return [
|
|
'totale' => count($rows),
|
|
'completate' => count(array_filter($rows, fn(array $row): bool => $row['status'] === 'fatta')),
|
|
'pendenti' => count(array_filter($rows, fn(array $row): bool => in_array($row['status'], ['richiesta', 'non_richiesta'], true))),
|
|
'sollecitate' => count(array_filter($rows, fn(array $row): bool => $row['status'] === 'sollecito')),
|
|
];
|
|
}
|
|
|
|
/** @return array<int,array<string,mixed>> */
|
|
public function getAcquaCampagnaRowsProperty(): array
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
$servizio = $this->resolveActiveAcquaServizio();
|
|
if (! $stabileId || ! $servizio) {
|
|
return [];
|
|
}
|
|
|
|
$year = $this->resolveActiveAnnoGestione();
|
|
|
|
$units = UnitaImmobiliare::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereNull('deleted_at')
|
|
->orderBy('scala')
|
|
->orderBy('interno')
|
|
->orderBy('id')
|
|
->get(['id', 'codice_unita', 'denominazione', 'scala', 'interno']);
|
|
|
|
$nominativi = DB::table('unita_immobiliare_nominativi')
|
|
->whereIn('unita_immobiliare_id', $units->pluck('id')->all())
|
|
->orderByDesc('updated_at')
|
|
->orderByDesc('id')
|
|
->get(['unita_immobiliare_id', 'nominativo']);
|
|
|
|
$nominativiByUnit = [];
|
|
foreach ($nominativi as $row) {
|
|
$unitId = (int) ($row->unita_immobiliare_id ?? 0);
|
|
if ($unitId > 0 && ! isset($nominativiByUnit[$unitId])) {
|
|
$nominativiByUnit[$unitId] = trim((string) ($row->nominativo ?? ''));
|
|
}
|
|
}
|
|
|
|
$readingRows = StabileServizioLettura::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('stabile_servizio_id', (int) $servizio->id)
|
|
->whereIn('unita_immobiliare_id', $units->pluck('id')->all())
|
|
->orderByDesc('created_at')
|
|
->orderByDesc('id')
|
|
->get();
|
|
|
|
$currentYearByUnit = [];
|
|
$latestRealByUnit = [];
|
|
foreach ($readingRows as $row) {
|
|
$unitId = (int) ($row->unita_immobiliare_id ?? 0);
|
|
if ($unitId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$rowYear = optional($row->created_at)->year ?: optional($row->periodo_al)->year;
|
|
if ((int) $rowYear === $year && ! isset($currentYearByUnit[$unitId])) {
|
|
$currentYearByUnit[$unitId] = $row;
|
|
}
|
|
|
|
if ($row->lettura_fine !== null && ! isset($latestRealByUnit[$unitId])) {
|
|
$latestRealByUnit[$unitId] = $row;
|
|
}
|
|
}
|
|
|
|
return $units->map(function (UnitaImmobiliare $unit) use ($currentYearByUnit, $latestRealByUnit, $nominativiByUnit, $servizio): array {
|
|
$current = $currentYearByUnit[(int) $unit->id] ?? null;
|
|
$latestReal = $latestRealByUnit[(int) $unit->id] ?? null;
|
|
|
|
$status = 'non_richiesta';
|
|
if ($current && $current->lettura_fine !== null) {
|
|
$status = 'fatta';
|
|
} elseif ($current && $current->sollecito_inviato_at) {
|
|
$status = 'sollecito';
|
|
} elseif ($current && $current->richiesta_lettura_inviata_at) {
|
|
$status = 'richiesta';
|
|
}
|
|
|
|
return [
|
|
'unit_id' => (int) $unit->id,
|
|
'unit_label' => trim((string) ($unit->codice_unita ?? '') . ' ' . (string) ($unit->interno ?? '')) ?: ('UI #' . (int) $unit->id),
|
|
'nominativo' => $nominativiByUnit[(int) $unit->id] ?? (trim((string) ($unit->denominazione ?? '')) ?: 'Nominativo da completare'),
|
|
'previous_reading' => $latestReal?->lettura_fine,
|
|
'request_id' => $current?->id,
|
|
'status' => $status,
|
|
'requested_at' => optional($current?->richiesta_lettura_inviata_at)->format('d/m/Y H:i'),
|
|
'deadline_at' => optional($current?->deadline_lettura_at)->format('d/m/Y'),
|
|
'completed_at' => optional($current?->created_at)->format('d/m/Y H:i'),
|
|
'latest_value' => $current?->lettura_fine,
|
|
'public_url' => $this->buildPublicAcquaReadingUrl((int) $servizio->id, (int) $unit->id, $current?->id ? (int) $current->id : null),
|
|
];
|
|
})->all();
|
|
}
|
|
|
|
/** @return array<int, array<string,mixed>> */
|
|
public function getAcquaTariffeRowsProperty(): array
|
|
{
|
|
$stabileId = $this->resolveActiveStabileId();
|
|
if (! $stabileId) {
|
|
return [];
|
|
}
|
|
|
|
return StabileServizioTariffa::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereYear('data_fattura', $this->resolveActiveAnnoGestione())
|
|
->orderByDesc('data_fattura')
|
|
->orderByDesc('id')
|
|
->limit(80)
|
|
->get()
|
|
->map(function (StabileServizioTariffa $row): array {
|
|
$scaglioni = is_array($row->tariffe_scaglioni) ? $row->tariffe_scaglioni : [];
|
|
$componenti = is_array($row->tariffe_componenti) ? $row->tariffe_componenti : [];
|
|
$ui = is_array($componenti['componenti_ui'] ?? null) ? $componenti['componenti_ui'] : [];
|
|
|
|
return [
|
|
'id' => (int) $row->id,
|
|
'fattura_elettronica_id' => (int) $row->fattura_elettronica_id,
|
|
'data_fattura' => optional($row->data_fattura)?->format('d/m/Y'),
|
|
'decorrenza_tariffe' => optional($row->decorrenza_tariffe)?->format('d/m/Y'),
|
|
'profilo' => (string) ($row->profilo_tariffario ?? '—'),
|
|
'delibera_ato' => (string) ($row->delibera_ato ?? '—'),
|
|
'delibera_arera' => (string) ($row->delibera_arera ?? '—'),
|
|
'iva_codice' => (string) ($row->codice_iva ?? '—'),
|
|
'iva_aliquota' => is_numeric($row->aliquota_iva_percentuale) ? (float) $row->aliquota_iva_percentuale : null,
|
|
'scaglioni_count' => count($scaglioni),
|
|
'ui1' => $ui['ui1_euro_mc'] ?? null,
|
|
'ui2' => $ui['ui2_euro_mc'] ?? null,
|
|
'ui3' => $ui['ui3_euro_mc'] ?? null,
|
|
'ui4' => $ui['ui4_euro_mc'] ?? null,
|
|
];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
/** @return array{contratti_acea: bool, tariffe_acea_standard: bool, note: string} */
|
|
public function getAcquaLegacyArchiveAvailabilityProperty(): array
|
|
{
|
|
$hasContratti = Schema::connection('gescon_import')->hasTable('contratti_ACEA')
|
|
|| Schema::connection('gescon_import')->hasTable('contratti_acea');
|
|
$hasTariffe = Schema::connection('gescon_import')->hasTable('Tariffe_ACEA_Standard')
|
|
|| Schema::connection('gescon_import')->hasTable('tariffe_acea_standard');
|
|
|
|
return [
|
|
'contratti_acea' => $hasContratti,
|
|
'tariffe_acea_standard' => $hasTariffe,
|
|
'note' => ($hasContratti || $hasTariffe)
|
|
? 'Archivio legacy disponibile su connessione gescon_import.'
|
|
: 'Tabelle legacy non presenti ora su gescon_import: caricare import da parti_comuni.mdb per abilitarle.',
|
|
];
|
|
}
|
|
|
|
/** @return array<int, array{cod_spe: string, totale: float}> */
|
|
public function getAcquaAltreVociLegacyProperty(): array
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('operazioni')) {
|
|
return [];
|
|
}
|
|
|
|
$legacyCodes = $this->resolveLegacyCodiciStabile();
|
|
$year = $this->resolveActiveAnnoGestione();
|
|
|
|
$query = DB::connection('gescon_import')
|
|
->table('operazioni')
|
|
->where('gestione', 'O')
|
|
->where('cod_spe', 'like', 'AC%')
|
|
->where('cod_spe', '!=', 'AC1')
|
|
->where('cod_spe', '!=', 'AC2');
|
|
|
|
if ($legacyCodes !== [] && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile')) {
|
|
$query->whereIn('cod_stabile', $legacyCodes);
|
|
}
|
|
|
|
if (Schema::connection('gescon_import')->hasColumn('operazioni', 'dt_spe')) {
|
|
$query->whereYear('dt_spe', $year);
|
|
}
|
|
|
|
$rows = $query
|
|
->select('cod_spe')
|
|
->selectRaw('SUM(COALESCE(importo_euro, importo, 0)) as totale')
|
|
->groupBy('cod_spe')
|
|
->orderBy('cod_spe')
|
|
->get();
|
|
|
|
return $rows->map(fn($r): array=> [
|
|
'cod_spe' => (string) ($r->cod_spe ?? '—'),
|
|
'totale' => round((float) ($r->totale ?? 0), 2),
|
|
])->all();
|
|
}
|
|
}
|