Supporto/fornitori sync and Google OAuth refresh hardening
This commit is contained in:
parent
a9204e125b
commit
432f0565ab
399
app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php
Normal file
399
app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php
Normal file
|
|
@ -0,0 +1,399 @@
|
||||||
|
<?php
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class NetgesconQaMillesimiSpeseCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'netgescon:qa-millesimi-spese
|
||||||
|
{--stabile= : Codice stabile legacy/domino}
|
||||||
|
{--stabile_id= : ID stabile dominio}
|
||||||
|
{--anno= : Legacy year staging (es. 0001) per filtrare il confronto}
|
||||||
|
{--show-missing=20 : Massimo numero di codici mancanti da mostrare}';
|
||||||
|
|
||||||
|
protected $description = 'QA su tabelle millesimali, voci spesa e frazionamenti, evitando fallback vuoti e verificando l allineamento con nominativi/unita.';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
if (! Schema::hasTable('stabili')) {
|
||||||
|
$this->error('Tabella stabili non disponibile.');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$stabileId, $codiceStabile] = $this->resolveStabileScope();
|
||||||
|
if (! $stabileId || $codiceStabile === '') {
|
||||||
|
$this->error('Serve --stabile=CODICE oppure --stabile_id=ID valido.');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$legacyYear = $this->normalizeOption($this->option('anno'));
|
||||||
|
$showMissing = max(1, (int) ($this->option('show-missing') ?: 20));
|
||||||
|
|
||||||
|
$this->info('QA millesimi/spese per stabile ' . $codiceStabile . ' (#' . $stabileId . ')' . ($legacyYear ? ' anno ' . $legacyYear : ''));
|
||||||
|
|
||||||
|
$activeUnitsQuery = DB::table('unita_immobiliari')
|
||||||
|
->where('stabile_id', $stabileId)
|
||||||
|
->whereNull('deleted_at');
|
||||||
|
|
||||||
|
$activeUnits = (clone $activeUnitsQuery)->count();
|
||||||
|
|
||||||
|
$unitsWithNominativi = 0;
|
||||||
|
if (Schema::hasTable('unita_immobiliare_nominativi')) {
|
||||||
|
$unitsWithNominativi = DB::table('unita_immobiliare_nominativi as uin')
|
||||||
|
->join('unita_immobiliari as ui', 'ui.id', '=', 'uin.unita_immobiliare_id')
|
||||||
|
->where('ui.stabile_id', $stabileId)
|
||||||
|
->whereNull('ui.deleted_at')
|
||||||
|
->distinct('uin.unita_immobiliare_id')
|
||||||
|
->count('uin.unita_immobiliare_id');
|
||||||
|
}
|
||||||
|
$unitsWithoutNominativi = max(0, $activeUnits - $unitsWithNominativi);
|
||||||
|
|
||||||
|
$tabelleCount = Schema::hasTable('tabelle_millesimali')
|
||||||
|
? DB::table('tabelle_millesimali')->where('stabile_id', $stabileId)->count()
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
$detailRows = 0;
|
||||||
|
$detailZeroRows = 0;
|
||||||
|
$detailZeroOnUnitsWithoutNominativi = 0;
|
||||||
|
$tablesWithoutPositiveRows = collect();
|
||||||
|
if (Schema::hasTable('dettaglio_millesimi') && Schema::hasTable('tabelle_millesimali')) {
|
||||||
|
$detailBase = DB::table('dettaglio_millesimi as dm')
|
||||||
|
->join('tabelle_millesimali as tm', 'tm.id', '=', 'dm.tabella_millesimale_id')
|
||||||
|
->where('tm.stabile_id', $stabileId);
|
||||||
|
|
||||||
|
$detailRows = (clone $detailBase)->count();
|
||||||
|
$detailZeroRows = (clone $detailBase)
|
||||||
|
->where(function ($q): void {
|
||||||
|
$q->whereNull('dm.millesimi')->orWhere('dm.millesimi', '<=', 0);
|
||||||
|
})
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if (Schema::hasTable('unita_immobiliare_nominativi')) {
|
||||||
|
$detailZeroOnUnitsWithoutNominativi = (clone $detailBase)
|
||||||
|
->join('unita_immobiliari as ui', 'ui.id', '=', 'dm.unita_immobiliare_id')
|
||||||
|
->where(function ($q): void {
|
||||||
|
$q->whereNull('dm.millesimi')->orWhere('dm.millesimi', '<=', 0);
|
||||||
|
})
|
||||||
|
->whereNotExists(function ($sub): void {
|
||||||
|
$sub->selectRaw('1')
|
||||||
|
->from('unita_immobiliare_nominativi as uin')
|
||||||
|
->whereColumn('uin.unita_immobiliare_id', 'dm.unita_immobiliare_id');
|
||||||
|
})
|
||||||
|
->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
$tablesWithoutPositiveRows = DB::table('tabelle_millesimali as tm')
|
||||||
|
->leftJoin('dettaglio_millesimi as dm', function ($join): void {
|
||||||
|
$join->on('dm.tabella_millesimale_id', '=', 'tm.id')
|
||||||
|
->where('dm.millesimi', '>', 0);
|
||||||
|
})
|
||||||
|
->where('tm.stabile_id', $stabileId)
|
||||||
|
->groupBy('tm.id', 'tm.codice_tabella', 'tm.nome_tabella', 'tm.denominazione')
|
||||||
|
->havingRaw('COUNT(dm.id) = 0')
|
||||||
|
->selectRaw('COALESCE(tm.codice_tabella, tm.nome_tabella, tm.denominazione, CAST(tm.id AS CHAR)) as codice')
|
||||||
|
->pluck('codice');
|
||||||
|
}
|
||||||
|
|
||||||
|
$vociCount = Schema::hasTable('voci_spesa')
|
||||||
|
? DB::table('voci_spesa')->where('stabile_id', $stabileId)->count()
|
||||||
|
: 0;
|
||||||
|
$vociSenzaTabella = Schema::hasTable('voci_spesa')
|
||||||
|
? DB::table('voci_spesa')
|
||||||
|
->where('stabile_id', $stabileId)
|
||||||
|
->where(function ($q) : void {
|
||||||
|
$q->whereNull('tabella_millesimale_default_id')->orWhere('tabella_millesimale_default_id', 0);
|
||||||
|
})
|
||||||
|
->count()
|
||||||
|
: 0;
|
||||||
|
$vociConTabellaOrfana = (Schema::hasTable('voci_spesa') && Schema::hasTable('tabelle_millesimali'))
|
||||||
|
? DB::table('voci_spesa as vs')
|
||||||
|
->leftJoin('tabelle_millesimali as tm', 'tm.id', '=', 'vs.tabella_millesimale_default_id')
|
||||||
|
->where('vs.stabile_id', $stabileId)
|
||||||
|
->whereNotNull('vs.tabella_millesimale_default_id')
|
||||||
|
->whereNull('tm.id')
|
||||||
|
->count()
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
$staging = $this->collectStagingMetrics($stabileId, $codiceStabile, $legacyYear);
|
||||||
|
|
||||||
|
$this->line('Unita attive: ' . $activeUnits);
|
||||||
|
$this->line('Unita con nominativi: ' . $unitsWithNominativi);
|
||||||
|
$this->line('Unita senza nominativi: ' . $unitsWithoutNominativi);
|
||||||
|
$this->line('Tabelle millesimali dominio: ' . $tabelleCount);
|
||||||
|
$this->line('Righe dettaglio millesimi: ' . $detailRows);
|
||||||
|
$this->line('Righe dettaglio a zero/null: ' . $detailZeroRows);
|
||||||
|
$this->line('Righe a zero su unita senza nominativi: ' . $detailZeroOnUnitsWithoutNominativi);
|
||||||
|
$this->line('Voci spesa dominio: ' . $vociCount);
|
||||||
|
$this->line('Voci senza tabella default: ' . $vociSenzaTabella);
|
||||||
|
$this->line('Voci con tabella orfana: ' . $vociConTabellaOrfana);
|
||||||
|
|
||||||
|
if ($staging['available']) {
|
||||||
|
$this->line('Staging tabelle legacy: ' . $staging['headers_count'] . ' intestazioni, ' . $staging['detail_codes_count'] . ' codici con dettagli');
|
||||||
|
$this->line('Staging voci legacy: ' . $staging['voci_count']);
|
||||||
|
$this->line('Staging straordinarie legacy: ' . $staging['straord_count']);
|
||||||
|
$this->line('Staging frazionamenti: Fraz_gen=' . $staging['fraz_gen_count'] . ', fraz_dett=' . $staging['fraz_dett_count']);
|
||||||
|
} else {
|
||||||
|
$this->warn('Connessione/tabelle staging non disponibili per il confronto completo.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exitCode = self::SUCCESS;
|
||||||
|
|
||||||
|
if ($tablesWithoutPositiveRows->isNotEmpty()) {
|
||||||
|
$exitCode = self::INVALID;
|
||||||
|
$this->warn('Tabelle senza alcuna riga millesimale positiva: ' . $tablesWithoutPositiveRows->take($showMissing)->implode(', '));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($staging['available']) {
|
||||||
|
if (! empty($staging['missing_table_codes'])) {
|
||||||
|
$exitCode = self::INVALID;
|
||||||
|
$this->warn('Codici tabella presenti in staging ma mancanti nel dominio: ' . implode(', ', array_slice($staging['missing_table_codes'], 0, $showMissing)));
|
||||||
|
}
|
||||||
|
if (! empty($staging['missing_voci_codes'])) {
|
||||||
|
$exitCode = self::INVALID;
|
||||||
|
$this->warn('Codici voce presenti in staging ma mancanti nel dominio: ' . implode(', ', array_slice($staging['missing_voci_codes'], 0, $showMissing)));
|
||||||
|
}
|
||||||
|
if ($staging['fraz_gen_count'] > 0 || $staging['fraz_dett_count'] > 0) {
|
||||||
|
$this->warn('Archivio frazionamenti legacy presente: verificare Z01/Z02 prima del via libera contabile.');
|
||||||
|
if (($staging['z_fraz_voci_missing'] ?? []) !== []) {
|
||||||
|
$exitCode = self::INVALID;
|
||||||
|
$this->warn('Voci Z con frazionamento presenti in staging ma non allineate nel dominio: ' . implode(', ', array_slice($staging['z_fraz_voci_missing'], 0, $showMissing)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($unitsWithoutNominativi > 0 || $detailZeroOnUnitsWithoutNominativi > 0 || $vociConTabellaOrfana > 0) {
|
||||||
|
$exitCode = self::INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exitCode === self::SUCCESS) {
|
||||||
|
$this->info('QA completato senza anomalie bloccanti.');
|
||||||
|
} else {
|
||||||
|
$this->warn('QA completato con anomalie da correggere prima del riallineamento definitivo.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveStabileScope(): array
|
||||||
|
{
|
||||||
|
$stabileId = (int) ($this->option('stabile_id') ?: 0);
|
||||||
|
if ($stabileId > 0) {
|
||||||
|
$codice = DB::table('stabili')->where('id', $stabileId)->value('codice_stabile');
|
||||||
|
return [$stabileId, $this->normalizeOption($codice) ?? ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
$codiceInput = $this->normalizeCode((string) ($this->option('stabile') ?? ''));
|
||||||
|
if ($codiceInput === '') {
|
||||||
|
return [0, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
$trimmed = ltrim($codiceInput, '0');
|
||||||
|
$stabile = DB::table('stabili')
|
||||||
|
->where(function ($q) use ($codiceInput, $trimmed): void {
|
||||||
|
$q->where('codice_stabile', $codiceInput);
|
||||||
|
if ($trimmed !== '' && $trimmed !== $codiceInput) {
|
||||||
|
$q->orWhere('codice_stabile', $trimmed);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->select('id', 'codice_stabile')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $stabile) {
|
||||||
|
return [0, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [(int) $stabile->id, (string) $stabile->codice_stabile];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function collectStagingMetrics(int $stabileId, string $codiceStabile, ?string $legacyYear): array
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'available' => false,
|
||||||
|
'headers_count' => 0,
|
||||||
|
'detail_codes_count' => 0,
|
||||||
|
'voci_count' => 0,
|
||||||
|
'straord_count' => 0,
|
||||||
|
'fraz_gen_count' => 0,
|
||||||
|
'fraz_dett_count' => 0,
|
||||||
|
'missing_table_codes' => [],
|
||||||
|
'missing_voci_codes' => [],
|
||||||
|
'z_fraz_voci_missing' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! config('database.connections.gescon_import')) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['available'] = true;
|
||||||
|
$code = $this->normalizeCode($codiceStabile);
|
||||||
|
|
||||||
|
$domainTableCodes = Schema::hasTable('tabelle_millesimali')
|
||||||
|
? DB::table('tabelle_millesimali')
|
||||||
|
->where('stabile_id', $stabileId)
|
||||||
|
->selectRaw('UPPER(TRIM(COALESCE(codice_tabella, nome_tabella, denominazione))) as codice')
|
||||||
|
->pluck('codice')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$domainVoceCodes = Schema::hasTable('voci_spesa')
|
||||||
|
? DB::table('voci_spesa')
|
||||||
|
->where('stabile_id', $stabileId)
|
||||||
|
->selectRaw('UPPER(TRIM(codice)) as codice')
|
||||||
|
->pluck('codice')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all()
|
||||||
|
: [];
|
||||||
|
if (Schema::hasTable('voci_spesa') && Schema::hasColumn('voci_spesa', 'legacy_codice')) {
|
||||||
|
$domainLegacyVoceCodes = DB::table('voci_spesa')
|
||||||
|
->where('stabile_id', $stabileId)
|
||||||
|
->selectRaw('UPPER(TRIM(legacy_codice)) as codice')
|
||||||
|
->pluck('codice')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
$domainVoceCodes = array_values(array_unique(array_merge($domainVoceCodes, $domainLegacyVoceCodes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) {
|
||||||
|
$headersQuery = DB::connection('gescon_import')->table('tabelle_millesimali');
|
||||||
|
if (Schema::connection('gescon_import')->hasColumn('tabelle_millesimali', 'cod_stabile')) {
|
||||||
|
$headersQuery->where('cod_stabile', $code);
|
||||||
|
}
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('tabelle_millesimali', 'legacy_year')) {
|
||||||
|
$headersQuery->where('legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
$headerCodes = $headersQuery
|
||||||
|
->selectRaw('UPPER(TRIM(COALESCE(cod_tabella, denominazione, descrizione))) as codice')
|
||||||
|
->pluck('codice')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
$result['headers_count'] = count($headerCodes);
|
||||||
|
$result['missing_table_codes'] = array_values(array_diff($headerCodes, $domainTableCodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::connection('gescon_import')->hasTable('dett_tab')) {
|
||||||
|
$detailQuery = DB::connection('gescon_import')->table('dett_tab as d');
|
||||||
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
||||||
|
$detailQuery->join('condomin as c', 'c.cod_cond', '=', 'd.id_cond')
|
||||||
|
->where('c.cod_stabile', $code);
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
||||||
|
$detailQuery->where('c.legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
|
||||||
|
$detailQuery->where('d.legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
$detailCodes = $detailQuery
|
||||||
|
->selectRaw('UPPER(TRIM(d.cod_tab)) as codice')
|
||||||
|
->pluck('codice')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
$result['detail_codes_count'] = count($detailCodes);
|
||||||
|
if ($result['missing_table_codes'] === []) {
|
||||||
|
$result['missing_table_codes'] = array_values(array_diff($detailCodes, $domainTableCodes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::connection('gescon_import')->hasTable('voc_spe')) {
|
||||||
|
$vociQuery = DB::connection('gescon_import')->table('voc_spe');
|
||||||
|
if (Schema::connection('gescon_import')->hasColumn('voc_spe', 'cod_stabile')) {
|
||||||
|
$vociQuery->where('cod_stabile', $code);
|
||||||
|
}
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year')) {
|
||||||
|
$vociQuery->where('legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
$vocCodes = $vociQuery
|
||||||
|
->selectRaw('UPPER(TRIM(cod)) as codice')
|
||||||
|
->pluck('codice')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
$result['voci_count'] = count($vocCodes);
|
||||||
|
$result['missing_voci_codes'] = array_values(array_diff($vocCodes, $domainVoceCodes));
|
||||||
|
|
||||||
|
$zFractionsQuery = DB::connection('gescon_import')->table('voc_spe')
|
||||||
|
->where('cod_stabile', $code);
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year')) {
|
||||||
|
$zFractionsQuery->where('legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
$zFractions = $zFractionsQuery
|
||||||
|
->selectRaw('UPPER(TRIM(cod)) as codice')
|
||||||
|
->whereIn('cod', ['Z01', 'Z02'])
|
||||||
|
->pluck('codice')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
$result['z_fraz_voci_missing'] = array_values(array_diff($zFractions, $domainVoceCodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::connection('gescon_import')->hasTable('straordinarie')) {
|
||||||
|
$straordQuery = DB::connection('gescon_import')->table('straordinarie');
|
||||||
|
if (Schema::connection('gescon_import')->hasColumn('straordinarie', 'cod_stabile')) {
|
||||||
|
$straordQuery->where('cod_stabile', $code);
|
||||||
|
}
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('straordinarie', 'legacy_year')) {
|
||||||
|
$straordQuery->where('legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
$result['straord_count'] = $straordQuery->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::connection('gescon_import')->hasTable('fraz_gen')) {
|
||||||
|
$query = DB::connection('gescon_import')->table('fraz_gen');
|
||||||
|
if (Schema::connection('gescon_import')->hasColumn('fraz_gen', 'cod_stabile')) {
|
||||||
|
$query->where('cod_stabile', $code);
|
||||||
|
}
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('fraz_gen', 'legacy_year')) {
|
||||||
|
$query->where('legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
$result['fraz_gen_count'] = $query->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::connection('gescon_import')->hasTable('fraz_dett')) {
|
||||||
|
$query = DB::connection('gescon_import')->table('fraz_dett');
|
||||||
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('fraz_dett', 'legacy_year')) {
|
||||||
|
$query->where('legacy_year', $legacyYear);
|
||||||
|
}
|
||||||
|
$result['fraz_dett_count'] = $query->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeOption(mixed $value): ?string
|
||||||
|
{
|
||||||
|
if (! is_string($value) && ! is_numeric($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalized = trim((string) $value);
|
||||||
|
return $normalized === '' ? null : $normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeCode(string $code): string
|
||||||
|
{
|
||||||
|
$code = trim($code);
|
||||||
|
if ($code === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($code)) {
|
||||||
|
return str_pad((string) ((int) $code), 4, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoupper($code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -198,7 +198,7 @@ public function handle(): int
|
||||||
'source_port' => $port,
|
'source_port' => $port,
|
||||||
'assigned_user_id' => $assignedUserId,
|
'assigned_user_id' => $assignedUserId,
|
||||||
'stabile_id' => $stabileId,
|
'stabile_id' => $stabileId,
|
||||||
'amministratore_id'=> $amministratoreId,
|
'amministratore_id' => $amministratoreId,
|
||||||
'studio_role' => $routing['studio_role'],
|
'studio_role' => $routing['studio_role'],
|
||||||
'studio_mode' => $routing['studio_mode'],
|
'studio_mode' => $routing['studio_mode'],
|
||||||
'smdr' => [
|
'smdr' => [
|
||||||
|
|
@ -245,6 +245,8 @@ public function handle(): int
|
||||||
try {
|
try {
|
||||||
$lineSuffix = ! empty($parsed['co']) ? ' - linea ' . (string) $parsed['co'] : '';
|
$lineSuffix = ! empty($parsed['co']) ? ' - linea ' . (string) $parsed['co'] : '';
|
||||||
|
|
||||||
|
$isMissedResponseGroup = $this->isMissedResponseGroupCall($parsed, $routing);
|
||||||
|
|
||||||
ChiamataPostIt::query()->create([
|
ChiamataPostIt::query()->create([
|
||||||
'telefono' => $phone,
|
'telefono' => $phone,
|
||||||
'stabile_id' => $stabileId,
|
'stabile_id' => $stabileId,
|
||||||
|
|
@ -253,7 +255,7 @@ public function handle(): int
|
||||||
'oggetto' => 'Chiamata in entrata da SMDR' . $lineSuffix,
|
'oggetto' => 'Chiamata in entrata da SMDR' . $lineSuffix,
|
||||||
'nota' => $line,
|
'nota' => $line,
|
||||||
'direzione' => 'in_arrivo',
|
'direzione' => 'in_arrivo',
|
||||||
'esito' => ($durationSeconds ?? 0) > 0 ? 'answered' : 'missed',
|
'esito' => $isMissedResponseGroup || ($durationSeconds ?? 0) <= 0 ? 'missed' : 'answered',
|
||||||
'origine' => 'smdr',
|
'origine' => 'smdr',
|
||||||
'origine_id' => $fingerprint,
|
'origine_id' => $fingerprint,
|
||||||
'durata_secondi' => $durationSeconds,
|
'durata_secondi' => $durationSeconds,
|
||||||
|
|
@ -518,4 +520,18 @@ private function shouldCreateOperationalPostIt(array $parsed): bool
|
||||||
|
|
||||||
return ! empty($parsed['co']);
|
return ! empty($parsed['co']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param array<string,mixed> $parsed */
|
||||||
|
private function isMissedResponseGroupCall(array $parsed, array $routing): bool
|
||||||
|
{
|
||||||
|
$groupExtensions = ['601', '603'];
|
||||||
|
$groupExtension = preg_replace('/\D+/', '', (string) ($parsed['extension'] ?? '')) ?: '';
|
||||||
|
if (! in_array($groupExtension, $groupExtensions, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetExtension = preg_replace('/\D+/', '', (string) ($routing['target_extension'] ?? '')) ?: '';
|
||||||
|
|
||||||
|
return $targetExtension === '' || in_array($targetExtension, $groupExtensions, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
103
app/Console/Commands/SyncGesconFornitoriLegacyCommand.php
Normal file
103
app/Console/Commands/SyncGesconFornitoriLegacyCommand.php
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
|
||||||
|
class SyncGesconFornitoriLegacyCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'gescon:sync-fornitori-legacy
|
||||||
|
{amministratore_id : ID amministratore a cui associare i fornitori}
|
||||||
|
{--mdb=/mnt/gescon-archives/gescon/dbc/Fornitori.mdb : Percorso file Fornitori.mdb}
|
||||||
|
{--table=Fornitori : Nome tabella Access}
|
||||||
|
{--truncate-staging : Svuota la tabella staging prima dell\'import}
|
||||||
|
{--skip-tags : Salta l\'aggiornamento tag legacy}
|
||||||
|
{--dry-run : Esegue preview staging e dry-run dell\'import locale senza scrivere}
|
||||||
|
{--preview-limit=10 : Limite righe preview staging in dry-run}';
|
||||||
|
|
||||||
|
protected $description = 'Esegue la sync completa fornitori legacy -> staging -> archivio locale -> tag legacy.';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$adminId = (int) $this->argument('amministratore_id');
|
||||||
|
$mdb = (string) $this->option('mdb');
|
||||||
|
$table = (string) $this->option('table');
|
||||||
|
$dryRun = (bool) $this->option('dry-run');
|
||||||
|
$skipTags = (bool) $this->option('skip-tags');
|
||||||
|
$truncateStaging = (bool) $this->option('truncate-staging');
|
||||||
|
$previewLimit = max(1, (int) ($this->option('preview-limit') ?: 10));
|
||||||
|
|
||||||
|
if ($adminId <= 0) {
|
||||||
|
$this->error('amministratore_id non valido.');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Sync fornitori legacy per amministratore #' . $adminId . ($dryRun ? ' [DRY-RUN]' : ''));
|
||||||
|
|
||||||
|
$stageParams = [
|
||||||
|
'tenant' => (string) $adminId,
|
||||||
|
'--mdb' => $mdb,
|
||||||
|
'--table' => $table,
|
||||||
|
];
|
||||||
|
if ($dryRun) {
|
||||||
|
$stageParams['--preview'] = true;
|
||||||
|
$stageParams['--limit'] = $previewLimit;
|
||||||
|
}
|
||||||
|
if ($truncateStaging && ! $dryRun) {
|
||||||
|
$stageParams['--truncate'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stageExit = Artisan::call('import:gescon-fornitori', $stageParams);
|
||||||
|
$stageOutput = trim((string) Artisan::output());
|
||||||
|
$this->line('Step staging exit=' . $stageExit);
|
||||||
|
if ($stageOutput !== '') {
|
||||||
|
$this->line($stageOutput);
|
||||||
|
}
|
||||||
|
if ($stageExit !== 0) {
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$importParams = [
|
||||||
|
'amministratore_id' => (string) $adminId,
|
||||||
|
'--mdb' => $mdb,
|
||||||
|
'--table' => $table,
|
||||||
|
];
|
||||||
|
if ($dryRun) {
|
||||||
|
$importParams['--dry-run'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$importExit = Artisan::call('gescon:import-fornitori-legacy', $importParams);
|
||||||
|
$importOutput = trim((string) Artisan::output());
|
||||||
|
$this->line('Step archivio locale exit=' . $importExit);
|
||||||
|
if ($importOutput !== '') {
|
||||||
|
$this->line($importOutput);
|
||||||
|
}
|
||||||
|
if ($importExit !== 0) {
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $skipTags) {
|
||||||
|
$tagParams = [];
|
||||||
|
if ($dryRun) {
|
||||||
|
$tagParams['--dry-run'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tagExit = Artisan::call('fornitori:import-legacy-tags', $tagParams);
|
||||||
|
$tagOutput = trim((string) Artisan::output());
|
||||||
|
$this->line('Step tag legacy exit=' . $tagExit);
|
||||||
|
if ($tagOutput !== '') {
|
||||||
|
$this->line($tagOutput);
|
||||||
|
}
|
||||||
|
if ($tagExit !== 0) {
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info($dryRun
|
||||||
|
? 'Dry-run sync fornitori legacy completato.'
|
||||||
|
: 'Sync fornitori legacy completato.');
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -106,11 +106,9 @@ public function refreshRows(): void
|
||||||
|
|
||||||
$this->rows = $products->map(function (Product $product): array {
|
$this->rows = $products->map(function (Product $product): array {
|
||||||
$identifier = $product->identifiers
|
$identifier = $product->identifiers
|
||||||
->first(fn(ProductIdentifier $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId)
|
->first(fn(ProductIdentifier $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId) ?? $product->identifiers->first();
|
||||||
?? $product->identifiers->first();
|
|
||||||
$offer = $product->offers
|
$offer = $product->offers
|
||||||
->first(fn(ProductOffer $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId)
|
->first(fn(ProductOffer $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId) ?? $product->offers->first();
|
||||||
?? $product->offers->first();
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => (int) $product->id,
|
'id' => (int) $product->id,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class TicketInterventoScheda extends Page
|
||||||
'sorgente' => '',
|
'sorgente' => '',
|
||||||
'riferimento' => '',
|
'riferimento' => '',
|
||||||
'rubrica_url' => null,
|
'rubrica_url' => null,
|
||||||
'estratto_url'=> null,
|
'estratto_url' => null,
|
||||||
'stabile_url' => null,
|
'stabile_url' => null,
|
||||||
'unita_label' => '',
|
'unita_label' => '',
|
||||||
];
|
];
|
||||||
|
|
@ -1101,8 +1101,8 @@ protected function resolveCallerData(?Ticket $ticket): array
|
||||||
'sorgente' => 'Descrizione ticket',
|
'sorgente' => 'Descrizione ticket',
|
||||||
'riferimento' => $this->buildCallerReference($ticket, (string) ($parsed['riferimento'] ?? '')),
|
'riferimento' => $this->buildCallerReference($ticket, (string) ($parsed['riferimento'] ?? '')),
|
||||||
'rubrica_url' => null,
|
'rubrica_url' => null,
|
||||||
'estratto_url'=> null,
|
'estratto_url' => null,
|
||||||
'stabile_url' => $ticket?->stabile ? StabileScheda::getUrl(['record' => (int) $ticket->stabile->id], panel: 'admin-filament') : null,
|
'stabile_url' => $ticket?->stabile ? StabileScheda::getUrl(['record' => (int) $ticket->stabile->id], panel : 'admin-filament'): null,
|
||||||
'unita_label' => $ticket?->unitaImmobiliare ? $this->formatUnitaLabel($ticket->unitaImmobiliare) : '',
|
'unita_label' => $ticket?->unitaImmobiliare ? $this->formatUnitaLabel($ticket->unitaImmobiliare) : '',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -717,29 +717,23 @@ public function aggiornaFornitoriGescon(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$exit1 = Artisan::call('import:gescon-fornitori', [
|
$exit = Artisan::call('gescon:sync-fornitori-legacy', [
|
||||||
'tenant' => (string) $amm->id,
|
|
||||||
'--mdb' => $mdb,
|
|
||||||
]);
|
|
||||||
$out1 = trim((string) Artisan::output());
|
|
||||||
|
|
||||||
$exit2 = Artisan::call('gescon:import-fornitori-legacy', [
|
|
||||||
'amministratore_id' => (string) $amm->id,
|
'amministratore_id' => (string) $amm->id,
|
||||||
'--mdb' => $mdb,
|
'--mdb' => $mdb,
|
||||||
]);
|
]);
|
||||||
$out2 = trim((string) Artisan::output());
|
$out = trim((string) Artisan::output());
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Notification::make()->title('Aggiornamento fornitori fallito')->body($e->getMessage())->danger()->send();
|
Notification::make()->title('Aggiornamento fornitori fallito')->body($e->getMessage())->danger()->send();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($exit1 !== 0 || $exit2 !== 0) {
|
if ($exit !== 0) {
|
||||||
$snippet = $out2 !== '' ? $out2 : $out1;
|
$snippet = $out !== '' ? $out : '';
|
||||||
$snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : '';
|
$snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : '';
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Aggiornamento fornitori: errore')
|
->title('Aggiornamento fornitori: errore')
|
||||||
->body('Exit staging=' . $exit1 . ' | exit import=' . $exit2 . ($snippet !== '' ? "\n\n" . $snippet : ''))
|
->body('Exit sync=' . $exit . ($snippet !== '' ? "\n\n" . $snippet : ''))
|
||||||
->danger()
|
->danger()
|
||||||
->send();
|
->send();
|
||||||
return;
|
return;
|
||||||
|
|
@ -792,24 +786,12 @@ public function refreshImportTagLegacyFornitoriGlobale(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$exit1 = Artisan::call('import:gescon-fornitori', [
|
$exit = Artisan::call('gescon:sync-fornitori-legacy', [
|
||||||
'tenant' => (string) $amm->id,
|
|
||||||
'--mdb' => $mdb,
|
|
||||||
]);
|
|
||||||
$out1 = trim((string) Artisan::output());
|
|
||||||
|
|
||||||
$exit2 = Artisan::call('gescon:import-fornitori-legacy', [
|
|
||||||
'amministratore_id' => (string) $amm->id,
|
'amministratore_id' => (string) $amm->id,
|
||||||
'--mdb' => $mdb,
|
'--mdb' => $mdb,
|
||||||
|
'--dry-run' => $dryRun,
|
||||||
]);
|
]);
|
||||||
$out2 = trim((string) Artisan::output());
|
$out = trim((string) Artisan::output());
|
||||||
|
|
||||||
$tagParams = [];
|
|
||||||
if ($dryRun) {
|
|
||||||
$tagParams['--dry-run'] = true;
|
|
||||||
}
|
|
||||||
$exit3 = Artisan::call('fornitori:import-legacy-tags', $tagParams);
|
|
||||||
$out3 = trim((string) Artisan::output());
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Refresh + import TAG fallito')
|
->title('Refresh + import TAG fallito')
|
||||||
|
|
@ -819,13 +801,13 @@ public function refreshImportTagLegacyFornitoriGlobale(): void
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($exit1 !== 0 || $exit2 !== 0 || $exit3 !== 0) {
|
if ($exit !== 0) {
|
||||||
$snippet = $out3 !== '' ? $out3 : ($out2 !== '' ? $out2 : $out1);
|
$snippet = $out !== '' ? $out : '';
|
||||||
$snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : '';
|
$snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : '';
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Refresh + import TAG: errore')
|
->title('Refresh + import TAG: errore')
|
||||||
->body('Exit staging=' . $exit1 . ' | import=' . $exit2 . ' | tags=' . $exit3 . ($snippet !== '' ? "\n\n" . $snippet : ''))
|
->body('Exit sync=' . $exit . ($snippet !== '' ? "\n\n" . $snippet : ''))
|
||||||
->danger()
|
->danger()
|
||||||
->send();
|
->send();
|
||||||
return;
|
return;
|
||||||
|
|
@ -1013,22 +995,16 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
||||||
|
|
||||||
if (! empty($options['with_operazioni'])) {
|
if (! empty($options['with_operazioni'])) {
|
||||||
try {
|
try {
|
||||||
$exit1 = Artisan::call('import:gescon-fornitori', [
|
$exit = Artisan::call('gescon:sync-fornitori-legacy', [
|
||||||
'tenant' => (string) $amm->id,
|
|
||||||
'--mdb' => (string) ($state['fornitori_mdb'] ?? rtrim($options['path'], '/') . '/dbc/Fornitori.mdb'),
|
|
||||||
]);
|
|
||||||
$outputs[] = trim((string) Artisan::output());
|
|
||||||
|
|
||||||
$exit2 = Artisan::call('gescon:import-fornitori-legacy', [
|
|
||||||
'amministratore_id' => (string) $amm->id,
|
'amministratore_id' => (string) $amm->id,
|
||||||
'--mdb' => (string) ($state['fornitori_mdb'] ?? rtrim($options['path'], '/') . '/dbc/Fornitori.mdb'),
|
'--mdb' => (string) ($state['fornitori_mdb'] ?? rtrim($options['path'], '/') . '/dbc/Fornitori.mdb'),
|
||||||
]);
|
]);
|
||||||
$outputs[] = trim((string) Artisan::output());
|
$outputs[] = trim((string) Artisan::output());
|
||||||
|
|
||||||
if ($exit1 !== 0 || $exit2 !== 0) {
|
if ($exit !== 0) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Aggiornamento fornitori: errore')
|
->title('Aggiornamento fornitori: errore')
|
||||||
->body('Exit staging=' . $exit1 . ' | exit import=' . $exit2)
|
->body('Exit sync=' . $exit)
|
||||||
->danger()
|
->danger()
|
||||||
->send();
|
->send();
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -612,7 +612,7 @@ public function creaTicketRapido(): void
|
||||||
|
|
||||||
$ticket = Ticket::query()->create([
|
$ticket = Ticket::query()->create([
|
||||||
'stabile_id' => $stabileId,
|
'stabile_id' => $stabileId,
|
||||||
'unita_immobiliare_id'=> $callerContext['unita_id'] ?? null,
|
'unita_immobiliare_id' => $callerContext['unita_id'] ?? null,
|
||||||
'soggetto_richiedente_id' => $callerContext['soggetto_id'] ?? null,
|
'soggetto_richiedente_id' => $callerContext['soggetto_id'] ?? null,
|
||||||
'aperto_da_user_id' => $user->id,
|
'aperto_da_user_id' => $user->id,
|
||||||
'titolo' => (string) $this->newTicketTitolo,
|
'titolo' => (string) $this->newTicketTitolo,
|
||||||
|
|
@ -1213,7 +1213,7 @@ private function resolveCallerTicketContext(?RubricaUniversale $caller, ?int $fa
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'stabile_id' => $stabile?->id,
|
'stabile_id' => $stabile?->id,
|
||||||
'stabile_label' => $stabile ? (string) ($stabile->denominazione ?: ('Stabile #' . $stabile->id)) : '',
|
'stabile_label' => $stabile ? (string) ($stabile->denominazione ?: ('Stabile #' . $stabile->id)): '',
|
||||||
'soggetto_id' => $soggetto?->id,
|
'soggetto_id' => $soggetto?->id,
|
||||||
'unita_id' => $unita?->id,
|
'unita_id' => $unita?->id,
|
||||||
'unita_label' => $unita ? $this->formatUnitaLabel($unita) : '',
|
'unita_label' => $unita ? $this->formatUnitaLabel($unita) : '',
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public function redirect(): RedirectResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
|
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
|
||||||
|
$oauth = Arr::get($google, 'oauth', []);
|
||||||
|
|
||||||
$clientId = (string) ($google['client_id'] ?? config('services.google.client_id'));
|
$clientId = (string) ($google['client_id'] ?? config('services.google.client_id'));
|
||||||
$clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret'));
|
$clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret'));
|
||||||
|
|
@ -48,16 +49,21 @@ public function redirect(): RedirectResponse
|
||||||
$state = bin2hex(random_bytes(20));
|
$state = bin2hex(random_bytes(20));
|
||||||
session()->put('google_oauth_state', $state);
|
session()->put('google_oauth_state', $state);
|
||||||
|
|
||||||
$query = http_build_query([
|
$queryParams = [
|
||||||
'client_id' => $clientId,
|
'client_id' => $clientId,
|
||||||
'redirect_uri' => $redirectUri,
|
'redirect_uri' => $redirectUri,
|
||||||
'response_type' => 'code',
|
'response_type' => 'code',
|
||||||
'scope' => implode(' ', self::GOOGLE_SCOPES),
|
'scope' => implode(' ', self::GOOGLE_SCOPES),
|
||||||
'access_type' => 'offline',
|
'access_type' => 'offline',
|
||||||
'prompt' => 'consent',
|
|
||||||
'include_granted_scopes' => 'true',
|
'include_granted_scopes' => 'true',
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if (trim((string) Arr::get($oauth, 'refresh_token', '')) === '') {
|
||||||
|
$queryParams['prompt'] = 'consent';
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = http_build_query($queryParams);
|
||||||
|
|
||||||
return redirect()->away('https://accounts.google.com/o/oauth2/v2/auth?' . $query);
|
return redirect()->away('https://accounts.google.com/o/oauth2/v2/auth?' . $query);
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +77,7 @@ public function callback(): RedirectResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
|
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
|
||||||
|
$existingOauth = Arr::get($google, 'oauth', []);
|
||||||
$clientId = (string) ($google['client_id'] ?? config('services.google.client_id'));
|
$clientId = (string) ($google['client_id'] ?? config('services.google.client_id'));
|
||||||
$clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret'));
|
$clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret'));
|
||||||
$redirectUri = (string) ($google['redirect_uri'] ?? config('services.google.redirect'));
|
$redirectUri = (string) ($google['redirect_uri'] ?? config('services.google.redirect'));
|
||||||
|
|
@ -106,6 +113,9 @@ public function callback(): RedirectResponse
|
||||||
$payload = $tokenResponse->json();
|
$payload = $tokenResponse->json();
|
||||||
$accessToken = (string) Arr::get($payload, 'access_token', '');
|
$accessToken = (string) Arr::get($payload, 'access_token', '');
|
||||||
$refreshToken = (string) Arr::get($payload, 'refresh_token', '');
|
$refreshToken = (string) Arr::get($payload, 'refresh_token', '');
|
||||||
|
if ($refreshToken === '') {
|
||||||
|
$refreshToken = (string) Arr::get($existingOauth, 'refresh_token', '');
|
||||||
|
}
|
||||||
$expiresIn = (int) Arr::get($payload, 'expires_in', 3600);
|
$expiresIn = (int) Arr::get($payload, 'expires_in', 3600);
|
||||||
|
|
||||||
$profileResponse = Http::withToken($accessToken)
|
$profileResponse = Http::withToken($accessToken)
|
||||||
|
|
@ -128,6 +138,7 @@ public function callback(): RedirectResponse
|
||||||
'refresh_token' => $refreshToken,
|
'refresh_token' => $refreshToken,
|
||||||
'expires_in' => $expiresIn,
|
'expires_in' => $expiresIn,
|
||||||
'connected_at' => now()->toDateTimeString(),
|
'connected_at' => now()->toDateTimeString(),
|
||||||
|
'refreshed_at' => now()->toDateTimeString(),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ public function resolveByIncomingLine(?string $line): array
|
||||||
if ($mappedUser instanceof User) {
|
if ($mappedUser instanceof User) {
|
||||||
$targetRouting = [
|
$targetRouting = [
|
||||||
'user_id' => (int) $mappedUser->id,
|
'user_id' => (int) $mappedUser->id,
|
||||||
'stabile_id' => method_exists($mappedUser, 'stabiliAssegnati') ? ((int) ($mappedUser->stabiliAssegnati()->value('stabili.id') ?? 0) ?: null) : null,
|
'stabile_id' => method_exists($mappedUser, 'stabiliAssegnati') ? ((int) ($mappedUser->stabiliAssegnati()->value('stabili.id') ?? 0) ?: null): null,
|
||||||
'user' => $mappedUser,
|
'user' => $mappedUser,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ # NetGescon — TODO Copilot Tracker
|
||||||
Data aggiornamento: 6 aprile 2026
|
Data aggiornamento: 6 aprile 2026
|
||||||
|
|
||||||
Questo file tiene traccia di:
|
Questo file tiene traccia di:
|
||||||
|
|
||||||
- cosa è stato fatto (implementato e/o corretto)
|
- cosa è stato fatto (implementato e/o corretto)
|
||||||
- cosa resta da fare
|
- cosa resta da fare
|
||||||
- priorità e dipendenze
|
- priorità e dipendenze
|
||||||
|
|
@ -12,22 +13,27 @@ # NetGescon — TODO Copilot Tracker
|
||||||
## 1) Fatto (stato attuale)
|
## 1) Fatto (stato attuale)
|
||||||
|
|
||||||
### UX / Filament (navigazione “web-like”)
|
### UX / Filament (navigazione “web-like”)
|
||||||
|
|
||||||
- Breadcrumbs compatibili Filament: wrapper view per `<x-filament.components.page-breadcrumbs>`.
|
- Breadcrumbs compatibili Filament: wrapper view per `<x-filament.components.page-breadcrumbs>`.
|
||||||
- Pagine Filament con back/breadcrumb coerenti (dove già applicato).
|
- Pagine Filament con back/breadcrumb coerenti (dove già applicato).
|
||||||
|
|
||||||
### Anagrafica
|
### Anagrafica
|
||||||
|
|
||||||
- `Persona`: gestione multi-ruolo via JSON `persone.ruoli` + UI di editing in Filament.
|
- `Persona`: gestione multi-ruolo via JSON `persone.ruoli` + UI di editing in Filament.
|
||||||
|
|
||||||
### Contabilità — base
|
### Contabilità — base
|
||||||
|
|
||||||
- Tabelle contabilità e migrazioni sbloccate (incl. `contabilita_regole_prima_nota`, movimenti banca).
|
- Tabelle contabilità e migrazioni sbloccate (incl. `contabilita_regole_prima_nota`, movimenti banca).
|
||||||
- Import movimenti banca UniCredit `.wri` con dedup (`row_hash`).
|
- Import movimenti banca UniCredit `.wri` con dedup (`row_hash`).
|
||||||
- Azione Filament su movimenti banca per generare registrazione/movimenti contabili da regole prima nota (scaffold funzionante).
|
- Azione Filament su movimenti banca per generare registrazione/movimenti contabili da regole prima nota (scaffold funzionante).
|
||||||
|
|
||||||
### Fatture ricevute XML (FatturaPA)
|
### Fatture ricevute XML (FatturaPA)
|
||||||
|
|
||||||
- Archivio + scheda Filament per fatture ricevute XML.
|
- Archivio + scheda Filament per fatture ricevute XML.
|
||||||
- Import multiplo XML con dedup (hash + chiave logica) e persistenza su `fatture_elettroniche`.
|
- Import multiplo XML con dedup (hash + chiave logica) e persistenza su `fatture_elettroniche`.
|
||||||
|
|
||||||
### Scheda Stabile (riabilitata)
|
### Scheda Stabile (riabilitata)
|
||||||
|
|
||||||
- Ripristinato accesso dalla Filament “Cruscotto stabile” alla scheda stabile completa (UI classica) con:
|
- Ripristinato accesso dalla Filament “Cruscotto stabile” alla scheda stabile completa (UI classica) con:
|
||||||
- Dati stabile
|
- Dati stabile
|
||||||
- Banche/dati finanziari
|
- Banche/dati finanziari
|
||||||
|
|
@ -37,10 +43,12 @@ ### Scheda Stabile (riabilitata)
|
||||||
- Aggiunto deep-link `?tab=...` sulla scheda stabile UI classica per aprire direttamente una tab specifica.
|
- Aggiunto deep-link `?tab=...` sulla scheda stabile UI classica per aprire direttamente una tab specifica.
|
||||||
|
|
||||||
### “Vista Excel” (riabilitata)
|
### “Vista Excel” (riabilitata)
|
||||||
|
|
||||||
- Ripristinato accesso immediato dalla Filament “Cruscotto stabile” alla tab “Tabelle millesimali” (UI classica) tramite deep-link.
|
- Ripristinato accesso immediato dalla Filament “Cruscotto stabile” alla tab “Tabelle millesimali” (UI classica) tramite deep-link.
|
||||||
- Esiste anche la Filament “Tabelle millesimali · Prospetto” che è già una vista tabellare/Excel-like.
|
- Esiste anche la Filament “Tabelle millesimali · Prospetto” che è già una vista tabellare/Excel-like.
|
||||||
|
|
||||||
### Nuovo lavoro già avviato (base dati + UI)
|
### Nuovo lavoro già avviato (base dati + UI)
|
||||||
|
|
||||||
- Nuove tabelle per “fatture fornitori” (testata + righe, importi `_euro`, ritenuta, tab pagamento).
|
- Nuove tabelle per “fatture fornitori” (testata + righe, importi `_euro`, ritenuta, tab pagamento).
|
||||||
- Prime pagine Filament:
|
- Prime pagine Filament:
|
||||||
- Archivio fatture fornitori
|
- Archivio fatture fornitori
|
||||||
|
|
@ -51,17 +59,21 @@ ### Nuovo lavoro già avviato (base dati + UI)
|
||||||
## 2) In corso (priorità alta)
|
## 2) In corso (priorità alta)
|
||||||
|
|
||||||
### A) Riallineare le “Gestioni” per la contabilità
|
### A) Riallineare le “Gestioni” per la contabilità
|
||||||
|
|
||||||
Obiettivo: avere gestione ordinaria/riscaldamento/straordinaria *coerenti* e selezionabili per stabile, perché la contabilità deve agganciarsi a:
|
Obiettivo: avere gestione ordinaria/riscaldamento/straordinaria *coerenti* e selezionabili per stabile, perché la contabilità deve agganciarsi a:
|
||||||
|
|
||||||
- anno gestione / esercizio
|
- anno gestione / esercizio
|
||||||
- tipo gestione
|
- tipo gestione
|
||||||
- conti/casse/banca
|
- conti/casse/banca
|
||||||
- registrazioni/movimenti
|
- registrazioni/movimenti
|
||||||
|
|
||||||
Output atteso:
|
Output atteso:
|
||||||
|
|
||||||
- schermate Filament “Gestione ordinaria / riscaldamento / straordinaria” che leggono e usano la gestione attiva (non solo lo stabile attivo)
|
- schermate Filament “Gestione ordinaria / riscaldamento / straordinaria” che leggono e usano la gestione attiva (non solo lo stabile attivo)
|
||||||
- link e contesti coerenti tra: Prima Nota, Casse e banche, Fatture, Movimenti banca
|
- link e contesti coerenti tra: Prima Nota, Casse e banche, Fatture, Movimenti banca
|
||||||
|
|
||||||
Dipendenze:
|
Dipendenze:
|
||||||
|
|
||||||
- definire/standardizzare “gestione attiva” (session/key, o tabella/relazione)
|
- definire/standardizzare “gestione attiva” (session/key, o tabella/relazione)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -69,6 +81,7 @@ ### A) Riallineare le “Gestioni” per la contabilità
|
||||||
## 3) Da fare (funzionalità richieste, in ordine consigliato)
|
## 3) Da fare (funzionalità richieste, in ordine consigliato)
|
||||||
|
|
||||||
### 3.0 Area fornitore/manutentore 2026
|
### 3.0 Area fornitore/manutentore 2026
|
||||||
|
|
||||||
- Unificare la rubrica operativa usando la stessa maschera principale condivisa tra amministratore, fornitore e moduli collegati; differenziare accesso e persistenza per ruolo, non l impianto della view.
|
- Unificare la rubrica operativa usando la stessa maschera principale condivisa tra amministratore, fornitore e moduli collegati; differenziare accesso e persistenza per ruolo, non l impianto della view.
|
||||||
- Aggiungere una pagina ad hoc per i ticket interni del fornitore con elenco unico di lavorazioni: ticket passati dall amministratore + lavorazioni interne + storico operativo.
|
- Aggiungere una pagina ad hoc per i ticket interni del fornitore con elenco unico di lavorazioni: ticket passati dall amministratore + lavorazioni interne + storico operativo.
|
||||||
- Estendere `fornitore/collaboratori` con gestione orari di lavoro, disponibilita e futura assegnazione lavori ricorrenti.
|
- Estendere `fornitore/collaboratori` con gestione orari di lavoro, disponibilita e futura assegnazione lavori ricorrenti.
|
||||||
|
|
@ -77,6 +90,7 @@ ### 3.0 Area fornitore/manutentore 2026
|
||||||
- Portare avanti catalogo prodotti unificato con vari fornitori, listini acquisto/vendita multipli, seriali, varianti modello/colore, foto, allegati e riferimenti esterni tipo ICECAT.
|
- Portare avanti catalogo prodotti unificato con vari fornitori, listini acquisto/vendita multipli, seriali, varianti modello/colore, foto, allegati e riferimenti esterni tipo ICECAT.
|
||||||
|
|
||||||
### 3.1 Contabilità — fatture fornitori “alla Gescon”
|
### 3.1 Contabilità — fatture fornitori “alla Gescon”
|
||||||
|
|
||||||
- Rendere la maschera fattura fornitore più fedele allo screenshot (nomi/ordine tab + colonna azioni).
|
- Rendere la maschera fattura fornitore più fedele allo screenshot (nomi/ordine tab + colonna azioni).
|
||||||
- Aggancio/creazione fornitore (contabilità + anagrafica unica/rubrica) direttamente dalla maschera.
|
- Aggancio/creazione fornitore (contabilità + anagrafica unica/rubrica) direttamente dalla maschera.
|
||||||
- Contabilizzazione:
|
- Contabilizzazione:
|
||||||
|
|
@ -85,6 +99,7 @@ ### 3.1 Contabilità — fatture fornitori “alla Gescon”
|
||||||
- RA (ritenuta) come debito separato da versare
|
- RA (ritenuta) come debito separato da versare
|
||||||
|
|
||||||
### 3.2 Ritenute d’acconto + F24
|
### 3.2 Ritenute d’acconto + F24
|
||||||
|
|
||||||
- Gestione codici tributo: 1019/1020 e 1040.
|
- Gestione codici tributo: 1019/1020 e 1040.
|
||||||
- Regole:
|
- Regole:
|
||||||
- RA si versa con F24 entro il 16 del mese successivo al pagamento.
|
- RA si versa con F24 entro il 16 del mese successivo al pagamento.
|
||||||
|
|
@ -94,6 +109,7 @@ ### 3.2 Ritenute d’acconto + F24
|
||||||
- (fase successiva) aggregazione per mese/tributo e stampa/distinta
|
- (fase successiva) aggregazione per mese/tributo e stampa/distinta
|
||||||
|
|
||||||
### 3.3 Import MDB (GESCON) → contabilità operativa
|
### 3.3 Import MDB (GESCON) → contabilità operativa
|
||||||
|
|
||||||
- Predisporre import da `singolo_stabile.MDB` e `generale_stabile.mdb`:
|
- Predisporre import da `singolo_stabile.MDB` e `generale_stabile.mdb`:
|
||||||
- operazioni/fatture/fornitori
|
- operazioni/fatture/fornitori
|
||||||
- rispettare sempre i campi `*_euro`
|
- rispettare sempre i campi `*_euro`
|
||||||
|
|
@ -103,6 +119,7 @@ ### 3.3 Import MDB (GESCON) → contabilità operativa
|
||||||
- collegamenti a tabelle millesimali/voci spesa
|
- collegamenti a tabelle millesimali/voci spesa
|
||||||
|
|
||||||
### 3.4 XML multiplo → passaggio in contabilità
|
### 3.4 XML multiplo → passaggio in contabilità
|
||||||
|
|
||||||
- Oggi: import XML alimenta archivio `fatture_elettroniche`.
|
- Oggi: import XML alimenta archivio `fatture_elettroniche`.
|
||||||
- Da fare:
|
- Da fare:
|
||||||
- creazione/aggancio fornitore automatico (rubrica + fornitore)
|
- creazione/aggancio fornitore automatico (rubrica + fornitore)
|
||||||
|
|
@ -113,31 +130,35 @@ ### 3.4 XML multiplo → passaggio in contabilità
|
||||||
## 4) Parti discusse ma non ancora implementate (da non perdere)
|
## 4) Parti discusse ma non ancora implementate (da non perdere)
|
||||||
|
|
||||||
### Ticket
|
### Ticket
|
||||||
|
|
||||||
- Esiste base legacy (controller/CRUD e categorie ticket). Da definire:
|
- Esiste base legacy (controller/CRUD e categorie ticket). Da definire:
|
||||||
- UI Filament per ticket (lista, scheda, stati, priorità)
|
- UI Filament per ticket (lista, scheda, stati, priorità)
|
||||||
- permessi/ruoli
|
- permessi/ruoli
|
||||||
- collegamento ticket ↔ stabile ↔ documenti
|
- collegamento ticket ↔ stabile ↔ documenti
|
||||||
|
|
||||||
### Gestione documentale
|
### Gestione documentale
|
||||||
|
|
||||||
- Esiste sistema “Documenti collegati” (con rotte e API statistiche/scadenze). Da definire:
|
- Esiste sistema “Documenti collegati” (con rotte e API statistiche/scadenze). Da definire:
|
||||||
- UI Filament per documenti del condominio
|
- UI Filament per documenti del condominio
|
||||||
- workflow scadenze/rinnovi
|
- workflow scadenze/rinnovi
|
||||||
- permessi e visibilità per ruolo
|
- permessi e visibilità per ruolo
|
||||||
|
|
||||||
### Protocollo documentazione
|
### Protocollo documentazione
|
||||||
|
|
||||||
- Da definire:
|
- Da definire:
|
||||||
- numerazione protocolli per stabile/anno
|
- numerazione protocolli per stabile/anno
|
||||||
- registrazione (data, mittente/destinatario, oggetto)
|
- registrazione (data, mittente/destinatario, oggetto)
|
||||||
- collegamento a documenti/pec
|
- collegamento a documenti/pec
|
||||||
|
|
||||||
### Consumi / acqua / contatori
|
### Consumi / acqua / contatori
|
||||||
|
|
||||||
- Backend (tabelle contatori/letture/algoritmi) esiste.
|
- Backend (tabelle contatori/letture/algoritmi) esiste.
|
||||||
- UI Filament non ancora esposta: oggi la gestione è in view legacy.
|
- UI Filament non ancora esposta: oggi la gestione è in view legacy.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5) Note operative / rischi
|
## 5) Note operative / rischi
|
||||||
|
|
||||||
- Coesistenza Filament + UI classica: va mantenuta, ma i punti chiave devono essere raggiungibili dal “Cruscotto stabile”.
|
- Coesistenza Filament + UI classica: va mantenuta, ma i punti chiave devono essere raggiungibili dal “Cruscotto stabile”.
|
||||||
- “Gestione attiva” è il nodo per rendere contabilità consistente (oltre a “stabile attivo”).
|
- “Gestione attiva” è il nodo per rendere contabilità consistente (oltre a “stabile attivo”).
|
||||||
- Import MDB richiede mapping rigoroso (soprattutto `_euro`) per evitare contabilità sbilanciata.
|
- Import MDB richiede mapping rigoroso (soprattutto `_euro`) per evitare contabilità sbilanciata.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ ## Topics
|
||||||
|
|
||||||
- [Fatture Fornitori](topics/fatture-fornitori.md)
|
- [Fatture Fornitori](topics/fatture-fornitori.md)
|
||||||
- [Filo Logico Contabilita](topics/filo-logico-contabilita.md)
|
- [Filo Logico Contabilita](topics/filo-logico-contabilita.md)
|
||||||
|
- [Import Unita Cod Cond](topics/import-unita-cod-cond.md)
|
||||||
- [Affitti ISTAT e Contratti](topics/affitti-istat-contratti.md)
|
- [Affitti ISTAT e Contratti](topics/affitti-istat-contratti.md)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Filo Logico Contabilita
|
Import Unita Cod Cond
|
||||||
|
|
|
||||||
57
docs/ai/topics/import-unita-cod-cond.md
Normal file
57
docs/ai/topics/import-unita-cod-cond.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Topic: Import Unita Cod Cond
|
||||||
|
|
||||||
|
## Obiettivo
|
||||||
|
|
||||||
|
- Rendere l'import legacy di unita, nominativi, millesimi e voci coerente con la realta archivistica Gescon, evitando fallback vuoti o identificazioni deboli.
|
||||||
|
|
||||||
|
## Regole vincolanti
|
||||||
|
|
||||||
|
- L'ultimo anno disponibile e la fotografia autorevole dello stato corrente.
|
||||||
|
- La chiave certa dell'unita e `condomin.cod_cond`.
|
||||||
|
- `scala`, `interno`, `catasto_sub` e il codice mnemonico servono per riconciliazione e visualizzazione, non come chiave inventata in fallback.
|
||||||
|
- Il codice visibile all'operatore deve restare leggibile in forma `Palazzina/Scala/Interno`.
|
||||||
|
- Gli anni precedenti servono a costruire la cronologia dei soggetti collegati all'unita, non a sostituire il nominativo corrente trovato nell'ultimo anno.
|
||||||
|
- Non creare righe di completamento artificiale a zero per millesimi o nominativi se la sorgente non le fornisce.
|
||||||
|
|
||||||
|
## Modello archivistico chiarito dall'utente
|
||||||
|
|
||||||
|
- `condomin.cod_cond` identifica l'unita legacy in modo stabile.
|
||||||
|
- `comproprietari` integra la storia proprietari e quote.
|
||||||
|
- `emes_gen` definisce il contesto di emissione e l'anno gestionale.
|
||||||
|
- `emes_det.cod_cond` collega la rata emessa all'unita.
|
||||||
|
- `incassi.cod_cond` collega l'incasso all'unita.
|
||||||
|
- `cond_inq` distingue il ruolo storico: condomino/proprietario (`C`) o inquilino (`I`).
|
||||||
|
|
||||||
|
## Stato attuale del codice
|
||||||
|
|
||||||
|
- `app/Console/Commands/ImportGesconFullPipeline.php`
|
||||||
|
- fixato il riempimento automatico di dettagli millesimali a zero: ora e opt-in con `--fill-missing-millesimi`
|
||||||
|
- fixato il fallback di aggancio tabella per `stepVoci()`
|
||||||
|
- fixato il preload staging per `legacy_year`
|
||||||
|
- `app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php`
|
||||||
|
- aggiunto per QA su allineamento unita/nominativi/millesimi/voci
|
||||||
|
|
||||||
|
## Evidenze validate su 0021
|
||||||
|
|
||||||
|
- Lo staging dell'ultimo anno utile non veniva caricato correttamente finche il preflight controllava solo `cod_stabile`.
|
||||||
|
- Dopo il fix sul preload, `0021/0004` viene caricato e reso disponibile per confronto/import.
|
||||||
|
- Le unita ancora senza nominativo corrente non vanno risolte con fallback automatici: almeno i casi `A/0`, `A/42`, `A/43`, `A/215`, `A/216` non hanno corrispondenza trovata nel controllo staging eseguito.
|
||||||
|
|
||||||
|
## Prossimi step operativi
|
||||||
|
|
||||||
|
1. Rifattorizzare la costruzione snapshot corrente unita/nominativi a partire dall'ultimo anno disponibile.
|
||||||
|
2. Usare `cod_cond` come chiave di merge tra staging e dominio.
|
||||||
|
3. Inserire gli anni precedenti solo come variazioni storiche in `unita_immobiliare_nominativi`.
|
||||||
|
4. Verificare che rate, incassi e ruoli `C`/`I` restino riconciliabili per anno e unita.
|
||||||
|
5. Gestire le unita dominio senza sorgente corrente con una decisione esplicita di review/disattivazione, non con dati inventati.
|
||||||
|
|
||||||
|
## Comandi utili gia usati
|
||||||
|
|
||||||
|
- `php artisan netgescon:qa-millesimi-spese --stabile=0021 --anno=0003`
|
||||||
|
- `php artisan netgescon:qa-unita-nominativi --stabile_id=25`
|
||||||
|
- `php artisan gescon:import-align --stabile=0021 --path=/mnt/gescon-archives/gescon --years=2025,2026 --water-years=2025,2026 --open-years=2025,2026`
|
||||||
|
- `php artisan gescon:import-full --stabile=0021 --solo=unita --anno=0004 --dry-run`
|
||||||
|
|
||||||
|
## Vincolo di continuita
|
||||||
|
|
||||||
|
- Le vecchie note in `docs/ai` o nei chat export che propongono fallback tipo unita inventate, `interno=0` come riempitivo o codici sintetici non fondati sulla sorgente vanno considerate superate se confliggono con questo topic.
|
||||||
|
|
@ -121,6 +121,16 @@ ## Rilascio operativo 2026-04-06
|
||||||
|
|
||||||
Questo e il blocco che puo essere aggiornato adesso con la procedura Git-first senza correzioni manuali extra su staging.
|
Questo e il blocco che puo essere aggiornato adesso con la procedura Git-first senza correzioni manuali extra su staging.
|
||||||
|
|
||||||
|
### Sync fornitori legacy 2026-04-08
|
||||||
|
|
||||||
|
- La procedura fornitori va eseguita con un solo entry point: `php artisan gescon:sync-fornitori-legacy <amministratore_id> --mdb=/mnt/gescon-archives/gescon/dbc/Fornitori.mdb`
|
||||||
|
- La sync esegue in sequenza:
|
||||||
|
- import `Fornitori.mdb` in staging
|
||||||
|
- aggiornamento archivio locale `fornitori` + `rubrica_universale`
|
||||||
|
- aggiornamento TAG da descrizione legacy
|
||||||
|
- In `--dry-run` la staging resta in preview e i passaggi locali non persistono, cosi il legacy resta la sorgente da verificare prima del consolidamento.
|
||||||
|
- Regola operativa: per i fornitori il legacy aggiorna l archivio locale, non il contrario.
|
||||||
|
|
||||||
Incluso nel rilascio:
|
Incluso nel rilascio:
|
||||||
|
|
||||||
- Ticket Mobile: anteprima immediata locale di foto e allegati prima dell invio definitivo.
|
- Ticket Mobile: anteprima immediata locale di foto e allegati prima dell invio definitivo.
|
||||||
|
|
@ -136,10 +146,11 @@ ## Rilascio operativo 2026-04-06
|
||||||
3. usare `Aggiorna staging da Gitea`
|
3. usare `Aggiorna staging da Gitea`
|
||||||
4. aspettare la conclusione del sync Git e della QA sicura inclusa
|
4. aspettare la conclusione del sync Git e della QA sicura inclusa
|
||||||
5. validare subito i punti visibili:
|
5. validare subito i punti visibili:
|
||||||
- Ticket Mobile mostra l anteprima prima di inviare
|
|
||||||
- Ticket Gestione mostra `Tab 4 - Assicurazioni`
|
- Ticket Mobile mostra l anteprima prima di inviare
|
||||||
- `/admin-filament/fornitore/tickets/{id}` non produce 403 per i casi operativi normali
|
- Ticket Gestione mostra `Tab 4 - Assicurazioni`
|
||||||
- la lista ticket fornitore apre anche la modal rapida
|
- `/admin-filament/fornitore/tickets/{id}` non produce 403 per i casi operativi normali
|
||||||
|
- la lista ticket fornitore apre anche la modal rapida
|
||||||
|
|
||||||
Regola per evitare nuovi problemi:
|
Regola per evitare nuovi problemi:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,37 @@
|
||||||
/** @var \App\Models\Stabile|null $stabileAttivo */
|
/** @var \App\Models\Stabile|null $stabileAttivo */
|
||||||
$activeId = $stabileAttivo?->id;
|
$activeId = $stabileAttivo?->id;
|
||||||
|
|
||||||
|
$stabileHasRiscaldamento = static function (?\App\Models\Stabile $stabile): bool {
|
||||||
|
if (! $stabile) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((bool) ($stabile->riscaldamento_centralizzato ?? false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rateRiscaldamento = $stabile->rate_riscaldamento_mesi ?? [];
|
||||||
|
if (is_string($rateRiscaldamento)) {
|
||||||
|
$rateRiscaldamento = json_decode($rateRiscaldamento, true);
|
||||||
|
}
|
||||||
|
if (is_array($rateRiscaldamento) && $rateRiscaldamento !== []) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $stabile->configurazione_avanzata ?? [];
|
||||||
|
if (is_string($config)) {
|
||||||
|
$config = json_decode($config, true);
|
||||||
|
}
|
||||||
|
if (is_array($config) && array_key_exists('mostra_riscaldamento', $config) && $config['mostra_riscaldamento']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Illuminate\Support\Facades\DB::table('gestioni_contabili')
|
||||||
|
->where('stabile_id', $stabile->id)
|
||||||
|
->where('tipo_gestione', 'riscaldamento')
|
||||||
|
->exists();
|
||||||
|
};
|
||||||
|
|
||||||
$stabileLabel = static function (?\App\Models\Stabile $stabile): string {
|
$stabileLabel = static function (?\App\Models\Stabile $stabile): string {
|
||||||
if (! $stabile) {
|
if (! $stabile) {
|
||||||
return '';
|
return '';
|
||||||
|
|
@ -28,7 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500">
|
<div class="text-xs text-gray-500">
|
||||||
Ordinari: {{ ($stabileAttivo->numero_rate_ordinarie ?? 0) > 0 ? 'Sì' : 'No' }} ·
|
Ordinari: {{ ($stabileAttivo->numero_rate_ordinarie ?? 0) > 0 ? 'Sì' : 'No' }} ·
|
||||||
Riscaldamento: {{ (bool) ($stabileAttivo->riscaldamento_centralizzato ?? false) ? 'Sì' : 'No' }}
|
Riscaldamento: {{ $stabileHasRiscaldamento($stabileAttivo) ? 'Sì' : 'No' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
<x-filament-panels::page>
|
<x-filament-panels::page>
|
||||||
@php($supportUpdateAuthUser = auth()->user())
|
@php
|
||||||
@php($supportCanRunUpdate = $supportUpdateAuthUser && method_exists($supportUpdateAuthUser, 'hasAnyRole') && $supportUpdateAuthUser->hasAnyRole(['super-admin', 'admin', 'amministratore']))
|
$supportUpdateAuthUser = auth()->user();
|
||||||
|
$supportCanRunUpdate = $supportUpdateAuthUser && method_exists($supportUpdateAuthUser, 'hasAnyRole') && $supportUpdateAuthUser->hasAnyRole(['super-admin', 'admin', 'amministratore']);
|
||||||
|
@endphp
|
||||||
|
|
||||||
<div class="space-y-4 text-xs">
|
<div class="space-y-4 text-xs">
|
||||||
<div class="rounded-xl border border-sky-200 bg-sky-50 p-4">
|
<div class="rounded-xl border border-sky-200 bg-sky-50 p-4">
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-xl border bg-white p-4">
|
<div class="rounded-xl border bg-white p-4">
|
||||||
@php($selected = $this->selectedScheda)
|
@php
|
||||||
|
$selected = $this->selectedScheda;
|
||||||
|
@endphp
|
||||||
@if(! $selected)
|
@if(! $selected)
|
||||||
<div class="rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
|
<div class="rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
|
||||||
Seleziona una riga dall'elenco per aprire la scheda tecnica legacy con cliente, seriali, allegati e note operative.
|
Seleziona una riga dall'elenco per aprire la scheda tecnica legacy con cliente, seriali, allegati e note operative.
|
||||||
|
|
@ -135,7 +137,9 @@
|
||||||
<div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">Fornitore e allegati</div>
|
<div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">Fornitore e allegati</div>
|
||||||
<div class="mt-1 text-sm text-slate-800">
|
<div class="mt-1 text-sm text-slate-800">
|
||||||
@if($selected->fornitore)
|
@if($selected->fornitore)
|
||||||
@php($fornitoreUrl = $this->getFornitoreUrl((int) $selected->fornitore->id))
|
@php
|
||||||
|
$fornitoreUrl = $this->getFornitoreUrl((int) $selected->fornitore->id);
|
||||||
|
@endphp
|
||||||
@if($fornitoreUrl)
|
@if($fornitoreUrl)
|
||||||
<a href="{{ $fornitoreUrl }}" class="font-medium text-sky-700 hover:text-sky-600">{{ $selected->fornitore->ragione_sociale ?: ('Fornitore #' . $selected->fornitore->id) }}</a>
|
<a href="{{ $fornitoreUrl }}" class="font-medium text-sky-700 hover:text-sky-600">{{ $selected->fornitore->ragione_sociale ?: ('Fornitore #' . $selected->fornitore->id) }}</a>
|
||||||
@else
|
@else
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@
|
||||||
Ticket non selezionato o non accessibile. Apri questa pagina da Gestione Ticket.
|
Ticket non selezionato o non accessibile. Apri questa pagina da Gestione Ticket.
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
@php($ticket = $this->ticket)
|
@php
|
||||||
|
$ticket = $this->ticket;
|
||||||
|
@endphp
|
||||||
<div class="rounded-xl border bg-white p-4">
|
<div class="rounded-xl border bg-white p-4">
|
||||||
<div class="text-lg font-semibold">#{{ $ticket->id }} - {{ $ticket->titolo }}</div>
|
<div class="text-lg font-semibold">#{{ $ticket->id }} - {{ $ticket->titolo }}</div>
|
||||||
<div class="mt-1 text-xs text-gray-600">
|
<div class="mt-1 text-xs text-gray-600">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user