141 lines
8.5 KiB
PHP
141 lines
8.5 KiB
PHP
<?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);
|
|
$reservationInstall = in_array('--warehouse-reservations', $argv ?? [], true);
|
|
$recoveryInstall = in_array('--replenishment-recovery', $argv ?? [], true);
|
|
if ((int) $lotInstall + (int) $countInstall + (int) $contextsInstall + (int) $noticesInstall + (int) $warehouseInstall + (int) $fulfilmentInstall + (int) $reservationInstall + (int) $recoveryInstall > 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 ($reservationInstall) {
|
|
$migration = '2026_11_02_000049_create_warehouse_reservations';
|
|
$newTable = 'warehouse_reservations';
|
|
}
|
|
if ($recoveryInstall) {
|
|
$migration = '2026_11_02_000050_create_replenishment_work';
|
|
$newTable = 'replenishment_work';
|
|
}
|
|
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')))));
|
|
if ($reservationInstall) {
|
|
$proofName = 'warehouse-reservations-install';
|
|
}
|
|
if ($recoveryInstall) {
|
|
$proofName = 'replenishment-recovery-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.'"');
|
|
}
|