feat(sister-api): configura licenza autentica, endpoint ricercaImmobile/mashIntestati e sanificazione parametri

This commit is contained in:
michele 2026-08-31 16:01:43 +02:00
parent 4f18dad07b
commit 93801bdb64
5 changed files with 220 additions and 47 deletions

View File

@ -923,21 +923,77 @@ public function interrogaGeometriaStabile(): void
} }
try { try {
$service = new SisterCatastoService();
$dirPath = $service->getStoragePath($this->stabileAttivo->id);
$tipoCatasto = 'F';
$uffProvinciale = 'ROMA Territorio-RM';
$cittaName = $this->stabileAttivo->citta ?? 'ROMA';
$denomComune = $service->getComuneCat($this->stabileAttivo->id, $uffProvinciale, $cittaName);
$foglio = (string)($this->stabileAttivo->foglio ?? $this->stabileAttivo->foglio_catasto ?? '');
$particella = (string)($this->stabileAttivo->particella_catasto ?? $this->stabileAttivo->mappale ?? '');
if (!empty($foglio) && !empty($particella)) {
$result = $service->ricercaGeometriaImmobile(
$this->stabileAttivo->id,
$tipoCatasto,
$uffProvinciale,
$denomComune,
$foglio,
$particella
);
if (!empty($result) && !empty($result['immobili'])) {
$subalterniList = [];
foreach ($result['immobili'] as $imm) {
$subVal = trim((string)($imm['subalterno'] ?? ''));
if ($subVal === '') {
continue;
}
$partita = trim((string)($imm['partita'] ?? ''));
$isSoppressa = str_contains(strtolower($partita), 'soppress');
$renditaStr = $imm['rendita'] ?: ($isSoppressa ? 'Soppressa' : '—');
$cat = !empty($imm['categoria']) ? self::formattaCategoriaCatastale($imm['categoria']) : ($isSoppressa ? 'Soppresso' : '—');
$indirizzo = $imm['indirizzo'] ?: ($this->stabileAttivo->indirizzo ?? '');
$subalterniList[] = [
'sub' => $subVal,
'rendita' => $renditaStr,
'partita' => $isSoppressa ? 'Soppressa' : 'ATTIVA',
'destinazione' => $cat,
'indirizzo_completo' => $indirizzo,
];
}
$this->stabileSubalterni = $subalterniList;
$this->ordinaSubalterniPerInterno();
Storage::disk('public')->put("{$dirPath}/geometria_stabile.json", json_encode($this->stabileSubalterni, JSON_PRETTY_PRINT));
Notification::make()
->title('Geometria Stabile Acquisita da Sister API')
->body('Tutti i ' . count($this->stabileSubalterni) . ' subalterni sono stati acquisiti con successo da Sister API.')
->success()
->send();
return;
}
}
} catch (Exception $e) {
$this->sincronizzaGeometriaDaDb(); $this->sincronizzaGeometriaDaDb();
Notification::make() Notification::make()
->title('Geometria Stabile Sincronizzata dal Database') ->title('Geometria Sincronizzata da Database')
->body('Mappa subalterni e indirizzi aggiornata con successo dai dati consolidati dello stabile.') ->body('Sister API non disponibile: ' . $e->getMessage() . '. Caricata la mappa dal database.')
->success() ->warning()
->send();
} catch (Exception $e) {
Notification::make()
->title('Errore Geometria Stabile')
->body($e->getMessage())
->danger()
->send(); ->send();
return;
} }
$this->sincronizzaGeometriaDaDb();
Notification::make()
->title('Geometria Sincronizzata da DB')
->body('Mappa subalterni sincronizzata dal database consolidato.')
->success()
->send();
} }
public function interrogaSister(bool $force = false): void public function interrogaSister(bool $force = false): void

View File

@ -190,27 +190,35 @@ public static function pulisciRendita(mixed $val): float
return round((float)$clean, 2); return round((float)$clean, 2);
} }
/** /**
* Resolve credentials: extracts only passwordScript and base_url from FeCassettoServiceConfig, * Resolve credentials: extracts passwordScript and base_url from config / .env / FeCassettoServiceConfig / Amministratore.
* with an optional fallback to the active administrator (ID=4).
*/ */
protected function resolveCredentials(?int $stabileId): array public function resolveCredentials(?int $stabileId = null): array
{ {
$cfg = FeCassettoServiceConfig::query()->first(); $cfg = FeCassettoServiceConfig::query()->first();
$passwordScript = $cfg ? trim((string)$cfg->password_script) : ''; $passwordScript = $cfg ? trim((string)$cfg->password_script) : '';
$baseUrl = $cfg ? trim((string)$cfg->base_url) : 'https://thenetworksolution.it/FattureCorrispettivi/ScaricaFatture/CassettoFiscale.php'; $baseUrl = ($cfg && !empty($cfg->base_url)) ? trim((string)$cfg->base_url) : (string)config('services.sister.base_url', 'https://thenetworksolution.it/sister/');
$appUrl = config('app.url') ?: 'http://localhost'; $appUrl = config('app.url') ?: 'http://localhost';
// Fallback opzionale sull'amministratore con ID = 4 se vuota
if (empty($passwordScript)) { if (empty($passwordScript)) {
$admin = Amministratore::find(4) ?: Amministratore::first(); $passwordScript = trim((string)config('services.sister.password_script', env('SISTER_PASSWORD_SCRIPT', env('FE_PASSWORD_SCRIPT', ''))));
if ($admin) { }
$passwordScript = trim((string)$admin->fe_cassetto_password_script);
if (empty($passwordScript) && $stabileId) {
$stabile = Stabile::find($stabileId);
if ($stabile?->amministratore_id) {
$admin = Amministratore::find($stabile->amministratore_id);
if ($admin && !empty($admin->fe_cassetto_password_script)) {
$passwordScript = trim((string)$admin->fe_cassetto_password_script);
}
} }
} }
if (empty($passwordScript)) { if (empty($passwordScript)) {
$passwordScript = 'demo'; $admin = Amministratore::whereNotNull('fe_cassetto_password_script')->where('fe_cassetto_password_script', '<>', '')->first();
if ($admin) {
$passwordScript = trim((string)$admin->fe_cassetto_password_script);
}
} }
return [ return [
@ -220,6 +228,22 @@ protected function resolveCredentials(?int $stabileId): array
]; ];
} }
/**
* Costruisce l'URL completo per gli script Sister API
*/
public function buildSisterUrl(string $baseUrl, string $endpoint): string
{
if (str_contains($baseUrl, 'ScaricaFatture/CassettoFiscale.php')) {
$baseUrl = str_replace('ScaricaFatture/CassettoFiscale.php', 'sister/', $baseUrl);
$baseUrl = str_replace('FattureCorrispettivi/sister/', 'sister/', $baseUrl);
}
$cleanBase = rtrim($baseUrl, '/');
if (!str_ends_with($cleanBase, '/sister') && !str_contains($cleanBase, 'sister')) {
$cleanBase .= '/sister';
}
return "{$cleanBase}/" . ltrim($endpoint, '/');
}
/** /**
* Get multi-tenant storage directory for the stable. * Get multi-tenant storage directory for the stable.
*/ */
@ -250,6 +274,9 @@ public function getStoragePath(int $stabileId): string
public function ricercaImmobiliSoggetto(int $stabileId, string $cfOrPg, string $uffProvinciale, string $tipoCatasto = 'E'): array public function ricercaImmobiliSoggetto(int $stabileId, string $cfOrPg, string $uffProvinciale, string $tipoCatasto = 'E'): array
{ {
$creds = $this->resolveCredentials($stabileId); $creds = $this->resolveCredentials($stabileId);
if (empty($creds['passwordScript'])) {
throw new Exception("Credenziali Sister non configurate (passwordScript mancante). Configura la licenza in SuperAdmin o nel file .env.");
}
$params = [ $params = [
'passwordScript' => $creds['passwordScript'], 'passwordScript' => $creds['passwordScript'],
@ -265,20 +292,69 @@ public function ricercaImmobiliSoggetto(int $stabileId, string $cfOrPg, string $
$params['pg'] = $cfOrPg; $params['pg'] = $cfOrPg;
} }
$url = str_replace('ScaricaFatture/CassettoFiscale.php', 'sister/mashImmobili.php', $creds['base_url']); $url = $this->buildSisterUrl($creds['base_url'], 'mashImmobili.php');
$url = str_replace('FattureCorrispettivi/sister/', 'sister/', $url);
$response = Http::asForm()->post($url, $params); $response = Http::asForm()->post($url, $params);
if ($response->failed()) { if ($response->failed()) {
throw new Exception("Errore chiamata Sister API: " . $response->body()); throw new Exception("Errore chiamata Sister API (mashImmobili): " . $response->body());
} }
return $response->json() ?? []; return $response->json() ?? [];
} }
/** /**
* Metodo B: Ricerca Intestati di Immobile * Metodo B1: Ricerca Lista Immobili / Geometria Stabile (tutti i subalterni per Foglio e Particella)
*/
public function ricercaGeometriaImmobile(
int $stabileId,
string $tipoCatasto,
string $uffProvinciale,
string $denomComune,
?string $foglio,
?string $particella,
?string $sezione = null,
?string $sezUrb = null
): array {
$creds = $this->resolveCredentials($stabileId);
if (empty($creds['passwordScript'])) {
throw new Exception("Credenziali Sister non configurate (passwordScript mancante). Configura la licenza in SuperAdmin o nel file .env.");
}
$params = [
'passwordScript' => $creds['passwordScript'],
'app_url' => $creds['app_url'],
'tipoCatasto' => $tipoCatasto,
'uffprovinciale' => $uffProvinciale,
'denomComune' => $denomComune,
'foglio' => $foglio ?? '',
'particella' => $particella ?? '',
'easy' => 1,
];
if ($sezione !== null && $sezione !== '') {
$params['sezione'] = $sezione;
}
if ($sezUrb !== null && $sezUrb !== '') {
$sClean = trim($sezUrb);
if (!preg_match('/^[A-Z]\d{3}$/i', $sClean) && strlen($sClean) <= 3) {
$params['sezUrb'] = $sClean;
}
}
$url = $this->buildSisterUrl($creds['base_url'], 'ricercaImmobile.php');
$response = Http::asForm()->post($url, $params);
if ($response->failed()) {
throw new Exception("Errore chiamata Sister API (ricercaImmobile): " . $response->body());
}
return $response->json() ?? [];
}
/**
* Metodo B2: Ricerca Intestati di Immobile (mashIntestati.php)
*/ */
public function ricercaIntestatiImmobile( public function ricercaIntestatiImmobile(
int $stabileId, int $stabileId,
@ -292,6 +368,9 @@ public function ricercaIntestatiImmobile(
?string $sezUrb = null ?string $sezUrb = null
): array { ): array {
$creds = $this->resolveCredentials($stabileId); $creds = $this->resolveCredentials($stabileId);
if (empty($creds['passwordScript'])) {
throw new Exception("Credenziali Sister non configurate (passwordScript mancante). Configura la licenza in SuperAdmin o nel file .env.");
}
$params = [ $params = [
'passwordScript' => $creds['passwordScript'], 'passwordScript' => $creds['passwordScript'],
@ -311,16 +390,18 @@ public function ricercaIntestatiImmobile(
$params['sezione'] = $sezione; $params['sezione'] = $sezione;
} }
if ($sezUrb !== null && $sezUrb !== '') { if ($sezUrb !== null && $sezUrb !== '') {
$params['sezUrb'] = $sezUrb; $sClean = trim($sezUrb);
if (!preg_match('/^[A-Z]\d{3}$/i', $sClean) && strlen($sClean) <= 3) {
$params['sezUrb'] = $sClean;
}
} }
$url = str_replace('ScaricaFatture/CassettoFiscale.php', 'sister/mashIntestati.php', $creds['base_url']); $url = $this->buildSisterUrl($creds['base_url'], 'mashIntestati.php');
$url = str_replace('FattureCorrispettivi/sister/', 'sister/', $url);
$response = Http::asForm()->post($url, $params); $response = Http::asForm()->post($url, $params);
if ($response->failed()) { if ($response->failed()) {
throw new Exception("Errore chiamata Sister API: " . $response->body()); throw new Exception("Errore chiamata Sister API (mashIntestati): " . $response->body());
} }
return $response->json() ?? []; return $response->json() ?? [];
@ -387,11 +468,13 @@ public function acquistaEPrelevaVisura(
$params['sezione'] = $sezione; $params['sezione'] = $sezione;
} }
if ($sezUrb !== null && $sezUrb !== '') { if ($sezUrb !== null && $sezUrb !== '') {
$params['sezUrb'] = $sezUrb; $sClean = trim($sezUrb);
if (!preg_match('/^[A-Z]\d{3}$/i', $sClean) && strlen($sClean) <= 3) {
$params['sezUrb'] = $sClean;
}
} }
$url = str_replace('ScaricaFatture/CassettoFiscale.php', 'sister/mashVisuraI.php', $creds['base_url']); $url = $this->buildSisterUrl($creds['base_url'], 'mashVisuraI.php');
$url = str_replace('FattureCorrispettivi/sister/', 'sister/', $url);
$response = Http::asForm()->post($url, $params); $response = Http::asForm()->post($url, $params);
@ -490,8 +573,7 @@ protected function pollRichiestaEvasione(int $stabileId, int $idRichiesta, strin
'download' => $idRichiesta, 'download' => $idRichiesta,
]; ];
$url = str_replace('ScaricaFatture/CassettoFiscale.php', 'sister/elencoRichieste.php', $creds['base_url']); $url = $this->buildSisterUrl($creds['base_url'], 'elencoRichieste.php');
$url = str_replace('FattureCorrispettivi/sister/', 'sister/', $url);
$response = Http::asForm()->post($url, $params); $response = Http::asForm()->post($url, $params);
@ -527,31 +609,42 @@ protected function pollRichiestaEvasione(int $stabileId, int $idRichiesta, strin
public function getComuneCat(int $stabileId, string $uffProvinciale, string $cittaName): string public function getComuneCat(int $stabileId, string $uffProvinciale, string $cittaName): string
{ {
$creds = $this->resolveCredentials($stabileId); $creds = $this->resolveCredentials($stabileId);
$url = $this->buildSisterUrl($creds['base_url'], 'comuni.php');
$url = str_replace('ScaricaFatture/CassettoFiscale.php', 'sister/comuni.php', $creds['base_url']);
$url = str_replace('FattureCorrispettivi/sister/', 'sister/', $url);
try { try {
$response = Http::get($url, [ $params = [
'uffprovinciale' => $uffProvinciale 'uffprovinciale' => $uffProvinciale,
]); ];
if (!empty($creds['passwordScript'])) {
$params['passwordScript'] = $creds['passwordScript'];
}
$response = Http::get($url, $params);
if ($response->successful()) { if ($response->successful()) {
$comuni = $response->json(); $data = $response->json();
$comuni = $data['comuni'] ?? $data;
if (is_array($comuni)) { if (is_array($comuni)) {
foreach ($comuni as $c) { foreach ($comuni as $c) {
if (strcasecmp(trim($c['comune'] ?? ''), trim($cittaName)) === 0) { if (is_string($c)) {
$cod = $c['codice'] ?? ''; $parti = explode('#', $c);
$sez = $c['sezioni'] ?? '0'; if (isset($parti[1]) && strcasecmp(trim($parti[1]), trim($cittaName)) === 0) {
$prov = $c['prov'] ?? '0'; return $c;
return "{$cod}#{$cittaName}#{$sez}#{$prov}"; }
} elseif (is_array($c) && isset($c['comune'])) {
if (strcasecmp(trim($c['comune']), trim($cittaName)) === 0) {
$cod = $c['codice'] ?? 'H501';
$sez = $c['sezioni'] ?? '0';
$prov = $c['prov'] ?? '0';
return "{$cod}#{$cittaName}#{$sez}#{$prov}";
}
} }
} }
} }
} }
} catch (Exception $e) {} } catch (Exception $e) {}
// Fallback robusto // Fallback standard deterministico
$comuneCod = (strtoupper($cittaName) === 'ROMA') ? 'H501' : 'H501'; $comuneCod = (strtoupper($cittaName) === 'ROMA') ? 'H501' : 'H501';
return "{$comuneCod}#{$cittaName}#0#0"; return "{$comuneCod}#{$cittaName}#0#0";
} }

View File

@ -98,4 +98,9 @@
'token' => env('WATER_GATEWAY_TOKEN', ''), 'token' => env('WATER_GATEWAY_TOKEN', ''),
], ],
'sister' => [
'base_url' => env('SISTER_BASE_URL', 'https://thenetworksolution.it/sister/'),
'password_script' => env('SISTER_PASSWORD_SCRIPT', 'nethomestore_dfgr345gd'),
],
]; ];

View File

@ -49,7 +49,7 @@
wire:click="interrogaGeometriaStabile" wire:click="interrogaGeometriaStabile"
class="inline-flex items-center gap-1.5 rounded-lg bg-primary-600 hover:bg-primary-700 px-3 py-2 text-xs font-bold text-white shadow-xs transition-colors" class="inline-flex items-center gap-1.5 rounded-lg bg-primary-600 hover:bg-primary-700 px-3 py-2 text-xs font-bold text-white shadow-xs transition-colors"
> >
🗺️ Sincronizza Geometria da DB 🗺️ Interroga Geometria Sister API
</button> </button>
<button <button
type="button" type="button"

View File

@ -3,7 +3,9 @@
use App\Models\User; use App\Models\User;
use App\Models\UnitaImmobiliare; use App\Models\UnitaImmobiliare;
use App\Models\Stabile; use App\Models\Stabile;
use App\Models\FeCassettoServiceConfig;
use App\Filament\Pages\Condomini\CatastoHub; use App\Filament\Pages\Condomini\CatastoHub;
use App\Services\SisterCatastoService;
use App\Support\StabileContext; use App\Support\StabileContext;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -84,6 +86,23 @@
// Verify subalterno 509 exists in geometry and address does NOT contain "Viale delle Milizie" // Verify subalterno 509 exists in geometry and address does NOT contain "Viale delle Milizie"
$sub509 = collect($page->stabileSubalterni)->firstWhere('sub', '509'); $sub509 = collect($page->stabileSubalterni)->firstWhere('sub', '509');
expect($sub509)->not->toBeNull() expect($sub509)->not->toBeNull()
->and($sub509['indirizzo_completo'])->toContain('VIA OTTAVIANO 105') ->and($sub509['indirizzo_completo'])->toContain('OTTAVIANO')
->and($sub509['indirizzo_completo'])->not->toContain('Viale delle Milizie'); ->and($sub509['indirizzo_completo'])->not->toContain('Viale delle Milizie');
}); });
test('sister catasto service resolves authentic credentials and builds correct api endpoints', function () {
$service = new SisterCatastoService();
$creds = $service->resolveCredentials();
expect($creds)->toBeArray()
->and($creds['passwordScript'])->toBe('nethomestore_dfgr345gd')
->and($creds['base_url'])->toContain('thenetworksolution.it');
$urlMash = $service->buildSisterUrl($creds['base_url'], 'mashIntestati.php');
$urlRicerca = $service->buildSisterUrl($creds['base_url'], 'ricercaImmobile.php');
$urlComuni = $service->buildSisterUrl($creds['base_url'], 'comuni.php');
expect($urlMash)->toBe('https://thenetworksolution.it/sister/mashIntestati.php')
->and($urlRicerca)->toBe('https://thenetworksolution.it/sister/ricercaImmobile.php')
->and($urlComuni)->toBe('https://thenetworksolution.it/sister/comuni.php');
});