commit13
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
// Installs only the selected supply migration on the existing isolated local evaluation.
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$app = require dirname(__DIR__).'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
|
||||
$settings = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($settings['APP_ENV'] ?? '') !== 'payable-testing' || ($settings['DB_HOST'] ?? '') !== '127.0.0.1'
|
||||
|| ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $settings['DB_DATABASE'] ?? '', $match)
|
||||
|| ($settings['DB_USERNAME'] ?? '') !== 'payable_qa_'.$match[1]
|
||||
|| realpath($settings['HOST_ROOT'] ?? '') !== realpath(dirname(__DIR__))) {
|
||||
throw new LogicException('Apenas avaliação local isolada existente.');
|
||||
}
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Restauro de controlo apenas local.');
|
||||
}
|
||||
$qa = [...$owner, 'url' => null, 'host' => $settings['DB_HOST'], 'port' => $settings['DB_PORT'] ?? '5432', 'database' => $settings['DB_DATABASE'], 'username' => $settings['DB_USERNAME'], 'password' => $settings['DB_PASSWORD']];
|
||||
config(['database.connections.supply_install' => $qa, 'database.connections.supply_owner' => [...$owner, 'database' => 'postgres', 'url' => null]]);
|
||||
$connection = DB::connection('supply_install');
|
||||
$identity = $connection->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $settings['DB_DATABASE'] || $identity->role !== $settings['DB_USERNAME']) {
|
||||
throw new LogicException('Identidade do destino inesperada.');
|
||||
}
|
||||
$lotInstall = in_array('--lots', $argv ?? [], true);
|
||||
$countInstall = in_array('--counts', $argv ?? [], true);
|
||||
$contextsInstall = in_array('--contexts', $argv ?? [], true);
|
||||
$noticesInstall = in_array('--notices', $argv ?? [], true);
|
||||
$warehouseInstall = in_array('--warehouse-drafts', $argv ?? [], true);
|
||||
$fulfilmentInstall = in_array('--warehouse-events', $argv ?? [], true);
|
||||
if ((int) $lotInstall + (int) $countInstall + (int) $contextsInstall + (int) $noticesInstall + (int) $warehouseInstall + (int) $fulfilmentInstall > 1) {
|
||||
throw new LogicException('Seleccione uma única migração por instalação.');
|
||||
}
|
||||
$migration = $contextsInstall ? '2026_11_01_000045_add_work_context_configuration' : ($countInstall ? '2026_11_01_000044_allow_signed_lot_adjustments' : ($lotInstall ? '2026_11_01_000043_create_inventory_lot_allocations' : '2026_11_01_000042_create_supply_records'));
|
||||
$newTable = $countInstall || $contextsInstall ? null : ($lotInstall ? 'inventory_lot_allocations' : 'supply_records');
|
||||
if ($noticesInstall) {
|
||||
$migration = '2026_11_01_000046_create_workflow_notices';
|
||||
$newTable = 'workflow_notices';
|
||||
}
|
||||
if ($warehouseInstall) {
|
||||
$migration = '2026_11_01_000047_create_warehouse_requests';
|
||||
$newTable = 'warehouse_requests';
|
||||
}
|
||||
if ($fulfilmentInstall) {
|
||||
$migration = '2026_11_01_000048_create_warehouse_request_events';
|
||||
$newTable = 'warehouse_request_events';
|
||||
}
|
||||
if ($connection->table('migrations')->where('migration', $migration)->exists()) {
|
||||
echo "A migração de fornecimento já está instalada; nenhum registo alterado.\n";
|
||||
exit;
|
||||
}
|
||||
$tables = array_map(fn ($row) => $row->tablename, $connection->select("select tablename from pg_tables where schemaname='public' order by tablename"));
|
||||
function supplyFingerprint($db, array $tables, bool $contexts = false): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($tables as $table) {
|
||||
$quoted = '"'.str_replace('"', '""', $table).'"';
|
||||
$projection = $contexts ? "(to_jsonb(t) - ARRAY['work_context_policy','work_contexts','work_context_key'])::text" : 'row_to_json(t)::text';
|
||||
$rows = $db->select('select '.$projection.' as row from '.$quoted.' t order by '.$projection);
|
||||
$result[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
$baseline = supplyFingerprint($connection, $tables, $contextsInstall);
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-supply-'.gmdate('Ymd-His');
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Não foi possível criar a pasta privada de backup.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$bin = 'C:/Program Files/PostgreSQL/17/bin/';
|
||||
function supplyPg(string $binary, array $arguments, array $config): void
|
||||
{
|
||||
$process = new Process([$binary, ...$arguments], null, ['PGHOST' => $config['host'], 'PGPORT' => (string) $config['port'], 'PGUSER' => $config['username'], 'PGPASSWORD' => $config['password']], timeout: 120);
|
||||
$process->run();
|
||||
if (! $process->isSuccessful()) {
|
||||
throw new RuntimeException('Operação PostgreSQL de backup/restauro falhou; instalação interrompida.');
|
||||
}
|
||||
}
|
||||
supplyPg($bin.'pg_dump.exe', ['--format=custom', '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$restore = 'compras_supply_restore_'.bin2hex(random_bytes(6));
|
||||
$admin = DB::connection('supply_owner');
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
try {
|
||||
supplyPg($bin.'pg_restore.exe', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.supply_restore' => [...$owner, 'database' => $restore, 'url' => null]]);
|
||||
if (supplyFingerprint(DB::connection('supply_restore'), $tables, $contextsInstall) !== $baseline) {
|
||||
throw new RuntimeException('Restauro diferente da origem. Nenhuma migração aplicada.');
|
||||
}
|
||||
DB::setDefaultConnection('supply_install');
|
||||
$exit = Artisan::call('migrate', ['--database' => 'supply_install', '--path' => 'database/migrations/'.$migration.'.php', '--force' => true]);
|
||||
if ($exit !== 0) {
|
||||
throw new RuntimeException('Migração dirigida falhou.');
|
||||
}
|
||||
$preserved = array_values(array_diff($tables, ['migrations']));
|
||||
if (supplyFingerprint($connection, $preserved, $contextsInstall) !== array_intersect_key($baseline, array_flip($preserved)) || ($newTable !== null && $connection->table($newTable)->count() !== 0)) {
|
||||
throw new RuntimeException('Conferência posterior falhou; conservar backup e investigar.');
|
||||
}
|
||||
$proof = ['checked_at' => gmdate('c'), 'migration' => $migration, 'environment' => 'local evaluation 8005', 'backup_file' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restore_verified' => true,
|
||||
'existing_tables_preserved' => count($preserved), 'new_tables' => $newTable === null ? [] : [$newTable => 0], 'accounts_or_grants_changed' => false, 'domain_installed' => false];
|
||||
if ($contextsInstall) {
|
||||
foreach (['requisitions' => 'procurement.internal', 'people_processes' => 'people.internal'] as $table => $key) {
|
||||
if ($connection->table($table)->where('work_context_key', '!=', $key)->exists()) {
|
||||
throw new RuntimeException('Classificação interna inesperada após migração.');
|
||||
}
|
||||
}
|
||||
$proof['existing_processes_classified_internal'] = true;
|
||||
$proof['commercial_policy_activated'] = false;
|
||||
}
|
||||
$proofName = $fulfilmentInstall ? 'warehouse-fulfilment-install' : ($warehouseInstall ? 'warehouse-drafts-install' : ($noticesInstall ? 'workflow-notices-install' : ($contextsInstall ? 'work-context-install' : ($countInstall ? 'inventory-count-install' : ($lotInstall ? 'inventory-lot-install' : 'supply-install')))));
|
||||
file_put_contents(dirname(__DIR__).'/docs/proofs/'.$proofName.'.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
echo 'Migração dirigida instalada; restauro conferido, '.count($preserved)." tabelas preservadas. Sem atribuições ou dados de negócio.\n";
|
||||
} finally {
|
||||
DB::purge('supply_restore');
|
||||
$admin->select('select pg_terminate_backend(pid) from pg_stat_activity where datname = ? and pid <> pg_backend_pid()', [$restore]);
|
||||
$admin->statement('DROP DATABASE "'.$restore.'"');
|
||||
}
|
||||
Reference in New Issue
Block a user