netgescon-day0/app/Filament/Pages/Condomini/ServiziStabileArchivio.php

1214 lines
54 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\User;
use App\Models\VoceSpesa;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
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 UnitEnum;
class ServiziStabileArchivio extends Page implements HasTable
{
use InteractsWithTable;
protected static ?string $navigationLabel = 'Servizi / Utenze';
protected static ?string $title = 'Servizi / Utenze';
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 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 = [
'acqua' => 'Acqua',
'energia' => 'Energia',
'gas' => 'Gas',
'riscaldamento' => 'Riscaldamento',
'ascensore' => 'Ascensore',
'pulizie' => 'Pulizie',
'assicurazione' => 'Assicurazione',
'privacy' => 'Privacy',
'antenna' => 'Antenna',
'altro' => 'Altro',
];
$gestioneOptions = [
'ordinaria' => 'Ordinaria',
'riscaldamento' => 'Riscaldamento',
'straordinaria' => 'Straordinaria',
];
return $table
->striped()
->filters([
SelectFilter::make('tipo')
->label('Tipo servizio')
->options($tipoOptions),
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) => $tipoOptions[strtolower(trim((string) $state))] ?? (string) ($state ?? '—'))
->sortable(),
TextColumn::make('nome')->label('Nome')->searchable()->wrap()->sortable(),
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(),
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),
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),
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),
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' => trim((string) ($data['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' => [
'canale_acquisizione' => trim((string) ($data['canale_acquisizione'] ?? '')) ?: null,
'email_raccolta_letture' => trim((string) ($data['email_raccolta_letture'] ?? '')) ?: null,
'whatsapp_raccolta_letture' => trim((string) ($data['whatsapp_raccolta_letture'] ?? '')) ?: null,
'costo_lettura_unitario' => is_numeric($data['costo_lettura_unitario'] ?? null) ? (float) $data['costo_lettura_unitario'] : null,
'costo_ripartizione' => is_numeric($data['costo_ripartizione'] ?? null) ? (float) $data['costo_ripartizione'] : null,
'ripartizione_esterna' => (bool) ($data['ripartizione_esterna'] ?? false),
],
]);
}),
])
->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),
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),
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),
Toggle::make('attivo')->label('Attivo')->default(true),
])
->fillForm(fn(StabileServizio $record): array=> [
'tipo' => (string) ($record->tipo ?? ''),
'nome' => (string) ($record->nome ?? ''),
'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 ?? ''),
'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),
'attivo' => (bool) ($record->attivo ?? true),
])
->action(function (StabileServizio $record, array $data): void {
$meta = is_array($record->meta ?? null) ? $record->meta : [];
$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);
$record->fill([
'tipo' => strtolower(trim((string) ($data['tipo'] ?? ''))),
'nome' => trim((string) ($data['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<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;
}
/** @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];
}
$rows = DB::connection('gescon_import')
->table('operazioni')
->where('gestione', 'O')
->whereIn('cod_spe', ['AC1', 'AC2'])
->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 [];
}
$legacyRows = DB::connection('gescon_import')
->table('operazioni')
->where('gestione', 'O')
->whereIn('cod_spe', ['AC1', 'AC2'])
->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 [];
}
$user = Auth::user();
$legacyCode = trim((string) (StabileContext::getActiveStabile($user)?->codice_stabile ?? ''));
if ($legacyCode === '') {
return [];
}
$legacyYear = (string) (DB::connection('gescon_import')
->table('dett_tab')
->where('cod_stabile', $legacyCode)
->max('legacy_year') ?: '');
$condRows = DB::connection('gescon_import')
->table('condomin')
->where('cod_stabile', $legacyCode)
->when($legacyYear !== '', fn($q) => $q->where('legacy_year', $legacyYear))
->get([
'id_cond',
'scala',
'interno',
'cognome',
'nome',
'proprietario_denominazione',
'inquilino_denominazione',
'inquil_nome',
])
->keyBy('id_cond');
$rows = DB::connection('gescon_import')
->table('dett_tab')
->where('cod_tab', 'ACQUA')
->where('cod_stabile', $legacyCode)
->when($legacyYear !== '', fn($q) => $q->where('legacy_year', $legacyYear))
->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 [];
}
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)
->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'])
->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',
])
->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<int, array<string,mixed>> */
public function getAcquaTariffeRowsProperty(): array
{
$stabileId = $this->resolveActiveStabileId();
if (! $stabileId) {
return [];
}
return StabileServizioTariffa::query()
->where('stabile_id', $stabileId)
->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 [];
}
$rows = DB::connection('gescon_import')
->table('operazioni')
->where('gestione', 'O')
->where('cod_spe', 'like', 'AC%')
->where('cod_spe', '!=', 'AC1')
->where('cod_spe', '!=', 'AC2')
->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();
}
}