Files
SGF/scripts/payables-demo-entry.php
2026-10-30 23:20:27 +02:00

74 lines
6.1 KiB
PHP

<?php
// Registered exclusively by the isolated payables router, never by production routes.
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
$isSgf = str_starts_with($s['DB_DATABASE'], 'payable_sgf_');
$destination = $isSgf ? '/supplier-payables' : '/obrigacoes-fornecedores';
$guardDemo = static function (): void {
$db = DB::connection();
abort_unless(app()->environment('payable-testing') && $db->getDriverName() === 'pgsql'
&& $db->getConfig('host') === '127.0.0.1'
&& preg_match('/^(compras_receipts_test_|payable_sgf_)[a-f0-9]{12}$/D', $db->getDatabaseName())
&& $db->selectOne('select current_database() as d')->d === $db->getDatabaseName(), 403);
};
Route::middleware('web')->group(function () use ($guardDemo, $isSgf, $destination) {
Route::get('/demo', function (Request $r) use ($guardDemo, $isSgf, $destination) {
$guardDemo();
$next = $r->query('next') === 'payments' ? 'payments' : 'obligations';
$continue = $next === 'payments' ? '/supplier-payments' : $destination;
$e = static fn ($v) => htmlspecialchars((string) $v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$title = $isSgf ? 'SGF — demonstração' : 'Compras — demonstração';
$html = '<!doctype html><html lang="pt"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'.$title.'</title><style>body{font:18px system-ui;background:#f3f6f5;color:#142b28;margin:0}main{max-width:850px;margin:auto;padding:30px}section{background:white;padding:24px;border-radius:12px;margin:20px 0}button,a{font:inherit}button{padding:14px;background:#125a4b;color:white;border:0;border-radius:8px;cursor:pointer}form{margin:18px 0}a{color:#125a4b}</style><main><h1>'.$title.'</h1><p>Entre para experimentar e identificar melhorias. Não precisa de conta nem de palavra-passe.</p><section><h2>Escolha a função que quer experimentar</h2><p>Todos os dados são sintéticos. A função escolhida determina as acções disponíveis.</p>';
foreach (['preparacao' => ['Preparar', 'Consultar a factura e propor a obrigação, o pagamento sintético ou a afectação.'], 'validacao' => ['Validar', 'Rever e decidir propostas preparadas por outra identidade sintética.'], 'consulta' => ['Consultar', 'Explorar valores, origem e histórico, sem alterar dados.']] as $role => [$label, $description]) {
$html .= '<form method="post" action="/demo/entrar"><input type="hidden" name="_token" value="'.$e(csrf_token()).'"><input type="hidden" name="role" value="'.$role.'"><input type="hidden" name="next" value="'.$next.'"><button>'.$label.'</button> '.$description.'</form>';
}
$html .= '</section><section><h2>O que experimentar</h2><p>Factura: base de 10 000 MZN + imposto documental de 1 600 MZN = 11 600 MZN. Preparar, trocar para validar e consultar o resultado. Repetir deve recuperar a mesma obrigação.</p><p>Depois, em Pagamentos e afectações: preparar 4 000 MZN, confirmar com outra identidade e conferir que a dívida continua em 11 600 MZN. Só a afectação confirmada de 4 000 MZN reduz o saldo para 7 600 MZN.</p><p>O vencimento permanece pendente e o atraso indeterminado. Não há execução bancária. Datas e referências do ensaio devem ser sintéticas; o cenário partilhado conserva as operações anteriores, sem reiniciar os valores a cada visita.</p><p>As funções usam identidades de demonstração partilhadas; não identificam a pessoa real que está a testar. O cenário e o histórico são partilhados e conservados entre visitas.</p></section>';
if ($r->user()) {
$html .= '<p>Sessão actual: '.$e($r->user()->name).' · <a href="'.$continue.'">Continuar o percurso</a> · <a href="/">Abrir a aplicação</a></p><form method="post" action="/demo/sair"><input type="hidden" name="_token" value="'.$e(csrf_token()).'"><button>Sair da demonstração</button></form>';
}
return response($html.'</main></html>')->header('Cache-Control', 'no-store')->header('X-Robots-Tag', 'noindex, nofollow');
});
Route::post('/demo/entrar', function (Request $r) use ($guardDemo, $isSgf, $destination) {
$guardDemo();
$d = $r->validate(['role' => 'required|in:preparacao,validacao,consulta', 'next' => 'sometimes|in:payments,obligations']);
abort_if(array_diff(array_keys($r->all()), ['role', '_token', 'next']) !== [], 422);
// No arbitrary user identifier or permissions may be supplied by a visitor.
$u = User::where('email', $d['role'].'-obrigacoes@example.test')->firstOrFail();
$grants = DB::table('pa_access')->where(['user_id' => $u->id, 'action' => 'view'])->get();
abort_unless($grants->count() === 1, 403);
$g = $grants->first();
if ($isSgf) {
app(\App\Shared\Context\CompanyContext::class)->selectFor($u->id, $g->tenant_id, $g->company_id, $g->unit_id);
} else {
abort_unless((int) $u->tenant_id === (int) $g->tenant_id && (int) $g->company_id === 0 && (int) $g->unit_id === 0, 403);
}
Auth::logout();
$r->session()->invalidate();
$r->session()->regenerateToken();
Auth::login($u, false);
$r->session()->regenerate();
$r->session()->put('payables_demo_entry', true);
if ($isSgf) {
$r->session()->put('auth_version', (int) $u->auth_version);
$r->session()->put('company_context', ['tenant_id' => (int) $g->tenant_id, 'company_id' => (int) $g->company_id, 'unit_id' => (int) $g->unit_id, 'token' => bin2hex(random_bytes(24))]);
\App\Modules\Identity\Services\Audit::record('demo.entered', $u->id, $g->tenant_id, $g->company_id, $g->unit_id, details: ['role' => $d['role'], 'synthetic' => true]);
}
return redirect(($d['next'] ?? '') === 'payments' ? '/supplier-payments' : $destination);
})->middleware('throttle:30,1');
Route::post('/demo/sair', function (Request $r) use ($guardDemo) {
$guardDemo();
Auth::logout();
$r->session()->invalidate();
$r->session()->regenerateToken();
return redirect('/demo');
});
});