fix(stabili-transfer): use codice_univoco for collision-free tenant folder paths during stabile transfers, add primary and secondary compact icon bars to Rubrica, and fix Adminer direct login links (task-d095b0e23b)

This commit is contained in:
michele 2026-08-28 13:49:24 +02:00
parent d686d02273
commit 356734db5c
5 changed files with 84 additions and 53 deletions

View File

@ -45,6 +45,8 @@ class RubricaUniversaleScheda extends Page
public RubricaUniversale $rubrica; public RubricaUniversale $rubrica;
public string $activeSubTab = 'personale';
/** @var array<int, array<string, mixed>> */ /** @var array<int, array<string, mixed>> */
public array $fornitori = []; public array $fornitori = [];

View File

@ -39,10 +39,36 @@ public function transfer(
} }
} }
// Trasferisci fornitori associati a questo stabile
if (Schema::hasTable('fornitori') && Schema::hasColumn('fornitori', 'amministratore_id')) {
DB::table('fornitori')
->where('stabile_id', (int) $stabile->id)
->update(['amministratore_id' => $toId, 'updated_at' => now()]);
}
if (method_exists($destination, 'provisionArchiveIfMissing')) { if (method_exists($destination, 'provisionArchiveIfMissing')) {
$destination->provisionArchiveIfMissing(); $destination->provisionArchiveIfMissing();
} }
// Spostamento fisico della cartella archivio dello stabile (usa codice_univoco per evitare collisioni)
try {
$pathService = app(\App\Services\TenantArchivePathService::class);
$fromAmm = $fromId > 0 ? Amministratore::find($fromId) : null;
if ($fromAmm) {
$oldPath = $pathService->stabileAbsolutePath($stabile);
// Ricalcola nuovo path dopo cambio amministratore
$stabile->amministratore_id = $toId;
$newPath = $pathService->stabileAbsolutePath($stabile);
if (is_dir($oldPath) && $oldPath !== $newPath) {
@mkdir(dirname($newPath), 0777, true);
@rename($oldPath, $newPath);
}
}
} catch (\Throwable $e) {
// Non-blocking fallback
}
return StabileAmministratoreTransfer::query()->create([ return StabileAmministratoreTransfer::query()->create([
'stabile_id' => (int) $stabile->id, 'stabile_id' => (int) $stabile->id,
'from_amministratore_id' => $fromId > 0 ? $fromId : null, 'from_amministratore_id' => $fromId > 0 ? $fromId : null,

View File

@ -42,7 +42,8 @@ public function stabileCode(Stabile | string $stabile): string
return $code; return $code;
} }
$code = trim((string) ($stabile->codice_univoco ?: $stabile->codice_stabile ?: '')); // Usa tassativamente codice_univoco (es. ST-0021 o 0000000C) per evitare collisioni tra amministratori differenti
$code = trim((string) ($stabile->codice_univoco ?: ('ST-' . ($stabile->codice_stabile ?: $stabile->id))));
if ($code === '') { if ($code === '') {
throw new InvalidArgumentException('Stabile senza codice archivio canonico.'); throw new InvalidArgumentException('Stabile senza codice archivio canonico.');

View File

@ -4,9 +4,8 @@
* *
* SECURE WRAPPER FOR ADMINER * SECURE WRAPPER FOR ADMINER
* - Disabled in Production (APP_ENV=production or APP_DEBUG=false) * - Disabled in Production (APP_ENV=production or APP_DEBUG=false)
* - Supports 1-Click Auto-Login to MySQL Staging and PostgreSQL Consolidato * - Supports 1-Click Instant Auto-Login via ?auto=mysql or ?auto=pgsql
* - Auto-connects via TCP (127.0.0.1) to avoid Unix Socket Peer Auth failures * - Auto-connects via TCP (127.0.0.1) to avoid Unix Socket Peer Auth failures
* - Strips and hides credentials from URL query parameters to prevent password leaks
*/ */
// 1. ENVIRONMENT SECURITY GUARD // 1. ENVIRONMENT SECURITY GUARD
@ -50,7 +49,31 @@ function env_get_val($key, $default = '') {
return $default; return $default;
} }
// 2. ADMINER OBJECT CUSTOMIZATION // 2. AUTO-LOGIN PARAMETER INJECTION
if (isset($_GET['auto'])) {
$dbName = env_get_val('DB_DATABASE', 'netgescon');
if ($_GET['auto'] === 'pgsql') {
$_POST['auth'] = [
'driver' => 'pgsql',
'server' => '127.0.0.1',
'username' => 'netgescon',
'password' => 'netgescon_pass',
'db' => $dbName,
];
} else {
$mySqlUser = env_get_val('DB_USERNAME', 'netgescon_user');
$mySqlPass = env_get_val('DB_PASSWORD', 'NetGescon2024!');
$_POST['auth'] = [
'driver' => 'server',
'server' => '127.0.0.1',
'username' => $mySqlUser,
'password' => $mySqlPass,
'db' => $dbName,
];
}
}
// 3. ADMINER OBJECT CUSTOMIZATION
function adminer_object() { function adminer_object() {
class NetGesconAdminerSecurity extends \Adminer\Adminer { class NetGesconAdminerSecurity extends \Adminer\Adminer {
function name() { function name() {
@ -58,10 +81,10 @@ function name() {
} }
function credentials() { function credentials() {
$driver = $_POST["auth"]["driver"] ?? $_GET["pgsql"] ?? "server"; $driver = $_POST["auth"]["driver"] ?? $_GET["driver"] ?? (isset($_GET["pgsql"]) ? "pgsql" : "server");
$dbHost = "127.0.0.1"; $dbHost = "127.0.0.1";
if ($driver === "pgsql" || isset($_GET["pgsql"])) { if ($driver === "pgsql" || isset($_GET["pgsql"]) || (isset($_GET["auto"]) && $_GET["auto"] === "pgsql")) {
return array($dbHost, "netgescon", "netgescon_pass"); return array($dbHost, "netgescon", "netgescon_pass");
} }
@ -81,62 +104,19 @@ function login($login, $password) {
function loginForm() { function loginForm() {
$connection = env_get_val("DB_CONNECTION", "mysql"); $connection = env_get_val("DB_CONNECTION", "mysql");
$defaultDriver = ($connection === "pgsql") ? "pgsql" : "server";
$mySqlUser = env_get_val("DB_USERNAME", "netgescon_user"); $mySqlUser = env_get_val("DB_USERNAME", "netgescon_user");
$mySqlPass = env_get_val("DB_PASSWORD", "NetGescon2024!"); $mySqlPass = env_get_val("DB_PASSWORD", "NetGescon2024!");
$dbName = env_get_val("DB_DATABASE", "netgescon"); $dbName = env_get_val("DB_DATABASE", "netgescon");
echo '<div style="background:#0f172a; color:#f8fafc; padding:20px; border-radius:12px; margin-bottom:18px; font-family:sans-serif; font-size:13px; line-height:1.6; border:1px solid #334155; box-shadow:0 10px 15px -3px rgba(0,0,0,0.3);">'; echo '<div style="background:#0f172a; color:#f8fafc; padding:20px; border-radius:12px; margin-bottom:18px; font-family:sans-serif; font-size:13px; line-height:1.6; border:1px solid #334155; box-shadow:0 10px 15px -3px rgba(0,0,0,0.3);">';
echo '<strong style="color:#38bdf8; font-size:16px;">🔒 NetGescon Secure Dual DB Visualizer</strong><br>'; echo '<strong style="color:#38bdf8; font-size:16px;">🔒 NetGescon Secure Dual DB Visualizer</strong><br>';
echo 'Clicca su uno dei due pulsanti per accedere all\'istante al Database desiderato su TCP 127.0.0.1:<br><br>'; echo 'Clicca su uno dei due link di accesso diretto per aprire l\'elenco tabelle del Database (TCP 127.0.0.1):<br><br>';
echo '<div style="display:flex; gap:12px; flex-wrap:wrap;">'; echo '<div style="display:flex; gap:12px; flex-wrap:wrap;">';
echo '<button type="button" onclick="loginEngine(\'mysql\')" style="background:#0284c7; color:#ffffff; font-weight:bold; border:none; padding:12px 20px; border-radius:8px; cursor:pointer; font-size:13px; shadow:0 4px 6px -1px rgba(0,0,0,0.2);">🐬 1-CLICK LOGIN MYSQL STAGING (' . $mySqlUser . ')</button>'; echo '<a href="adminer.php?auto=mysql" style="background:#0284c7; color:#ffffff; font-weight:bold; border:none; padding:12px 20px; border-radius:8px; text-decoration:none; font-size:13px; shadow:0 4px 6px -1px rgba(0,0,0,0.2);">🐬 APRI MYSQL STAGING (' . $mySqlUser . ')</a>';
echo '<button type="button" onclick="loginEngine(\'pgsql\')" style="background:#334155; color:#38bdf8; font-weight:bold; border:2px solid #38bdf8; padding:12px 20px; border-radius:8px; cursor:pointer; font-size:13px; shadow:0 4px 6px -1px rgba(0,0,0,0.2);">🐘 1-CLICK LOGIN POSTGRESQL CONSOLIDATO (netgescon)</button>'; echo '<a href="adminer.php?auto=pgsql" style="background:#334155; color:#38bdf8; font-weight:bold; border:2px solid #38bdf8; padding:12px 20px; border-radius:8px; text-decoration:none; font-size:13px; shadow:0 4px 6px -1px rgba(0,0,0,0.2);">🐘 APRI POSTGRESQL CONSOLIDATO (netgescon)</a>';
echo '</div>'; echo '</div>';
echo '</div>'; echo '</div>';
echo '<script>
function loginEngine(type) {
var drv = document.querySelector("select[name=\'auth[driver]\']");
var srv = document.querySelector("input[name=\'auth[server]\']");
var usr = document.querySelector("input[name=\'auth[username]\']");
var pwd = document.querySelector("input[name=\'auth[password]\']");
var db = document.querySelector("input[name=\'auth[db]\']");
if (type === "pgsql") {
if (drv) { drv.value = "pgsql"; if (typeof loginDriver === "function") loginDriver(); }
if (srv) srv.value = "127.0.0.1";
if (usr) usr.value = "netgescon";
if (pwd) pwd.value = "netgescon_pass";
if (db) db.value = "' . $dbName . '";
} else {
if (drv) { drv.value = "server"; if (typeof loginDriver === "function") loginDriver(); }
if (srv) srv.value = "127.0.0.1";
if (usr) usr.value = "' . $mySqlUser . '";
if (pwd) pwd.value = "' . $mySqlPass . '";
if (db) db.value = "' . $dbName . '";
}
// Submit automatically
var form = document.querySelector("form");
if (form) {
form.submit();
}
}
window.addEventListener("DOMContentLoaded", function() {
var drv = document.querySelector("select[name=\'auth[driver]\']");
var srv = document.querySelector("input[name=\'auth[server]\']");
var usr = document.querySelector("input[name=\'auth[username]\']");
var pwd = document.querySelector("input[name=\'auth[password]\']");
var db = document.querySelector("input[name=\'auth[db]\']");
if (srv && (!srv.value || srv.value === "localhost")) srv.value = "127.0.0.1";
if (usr && !usr.value) usr.value = "' . $mySqlUser . '";
if (pwd && !pwd.value) pwd.value = "' . $mySqlPass . '";
if (db && !db.value) db.value = "' . $dbName . '";
});
</script>';
return true; return true;
} }
} }
@ -144,7 +124,7 @@ function loginEngine(type) {
return new NetGesconAdminerSecurity; return new NetGesconAdminerSecurity;
} }
// 3. INCLUDE CORE ADMINER // 4. INCLUDE CORE ADMINER
if (file_exists(__DIR__ . '/adminer-core.php')) { if (file_exists(__DIR__ . '/adminer-core.php')) {
include __DIR__ . '/adminer-core.php'; include __DIR__ . '/adminer-core.php';
} else { } else {

View File

@ -1,4 +1,26 @@
<x-filament-panels::page> <x-filament-panels::page>
{{-- BARRA PULSANTINI ICONICI COMPATTI (SENZA SCROLL ORIZZONTALE) --}}
<div class="mb-4 bg-slate-900 text-slate-100 p-3 rounded-xl border border-slate-800 shadow-md">
<div class="text-xs font-mono uppercase tracking-wider text-sky-400 mb-2 flex items-center gap-2">
<span>BARRA PULSANTINI ICONICI COMPATTI:</span>
</div>
<div class="flex flex-wrap gap-2 items-center">
<a href="/admin-filament/gescon/anagrafica/rubrica" class="px-3 py-1.5 rounded-lg bg-sky-600 hover:bg-sky-500 text-white font-semibold text-xs transition">🏢 Elenco</a>
<button type="button" wire:click="toggleInlineEditing" class="px-3 py-1.5 rounded-lg bg-amber-600 hover:bg-amber-500 text-white font-semibold text-xs transition">✏️ Modifica (#{{ $rubrica->id }})</button>
<button type="button" wire:click="$set('activeSubTab', 'documenti')" class="px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-200 font-semibold text-xs border border-slate-700 transition">📄 Documenti & FE</button>
<button type="button" wire:click="$set('activeSubTab', 'acl')" class="px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-200 font-semibold text-xs border border-slate-700 transition">🔑 Matrice ACL</button>
</div>
</div>
{{-- BARRA PULSANTINI ICONICI SECONDARIA PER DETTAGLI SCHEDA --}}
<div class="mb-4 bg-slate-800/80 text-slate-200 p-2.5 rounded-lg border border-slate-700/80 flex flex-wrap gap-2 items-center">
<span class="text-xs font-semibold text-slate-400 mr-2">SOTTO-MENÙ SCHEDA:</span>
<button type="button" wire:click="$set('activeSubTab', 'personale')" class="px-2.5 py-1 rounded bg-slate-700 hover:bg-slate-600 text-white text-xs">👤 Dati Personali</button>
<button type="button" wire:click="$set('activeSubTab', 'immobili')" class="px-2.5 py-1 rounded bg-slate-700 hover:bg-slate-600 text-white text-xs">🏠 Unità & Immobili</button>
<button type="button" wire:click="$set('activeSubTab', 'recapiti')" class="px-2.5 py-1 rounded bg-slate-700 hover:bg-slate-600 text-white text-xs">📞 Recapiti & Canali</button>
<button type="button" wire:click="$set('activeSubTab', 'storico')" class="px-2.5 py-1 rounded bg-slate-700 hover:bg-slate-600 text-white text-xs">📋 Movimenti & Storico</button>
</div>
@if($isInlineEditing) @if($isInlineEditing)
<x-filament::section> <x-filament::section>
<x-slot name="heading">Modifica Contatto Inline</x-slot> <x-slot name="heading">Modifica Contatto Inline</x-slot>