This commit is contained in:
2026-11-02 08:08:05 +02:00
parent 13a6cb9922
commit 504b01b244
246 changed files with 24499 additions and 185 deletions
+77
View File
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
use Tempo\Distribution\Release;
use Tempo\Purchasing\Economic\ProposalScoring;
use Tempo\Purchasing\Economic\QuotationPricing;
use Tempo\Purchasing\Supply\ReplenishmentProjection;
require dirname(__DIR__).'/vendor/autoload.php';
require_once dirname(__DIR__).'/packages/module-updates/Release.php';
$stage = null;
$exitCode = 0;
try {
if (count($argv) !== 3) {
throw new RuntimeException('Indique pacote e SHA-256 do manifesto.');
}
$checked = Release::inspect($argv[1], $argv[2]);
$stage = sys_get_temp_dir().'/sgc-purchasing-probe-'.bin2hex(random_bytes(8));
if (! mkdir($stage, 0700)) {
throw new RuntimeException('Não foi possível preparar a prova isolada.');
}
foreach ($checked['contents'] as $path => $content) {
if (! is_dir(dirname($stage.'/'.$path))) {
mkdir(dirname($stage.'/'.$path), 0700, true);
}
file_put_contents($stage.'/'.$path, $content);
}
require $stage.'/bootstrap.php';
$engine = new ReplenishmentProjection;
$policy = ['daily_milli' => 10000, 'safety_milli' => 20000, 'target_milli' => 100000, 'maximum_milli' => 120000, 'lead_days' => 4, 'approval_days' => 1, 'minimum_milli' => 24000, 'pack_milli' => 12000, 'horizon_days' => 30];
$result = $engine->calculate($policy, 120000, '2026-11-01');
$rate = $engine->calculate([...$policy, 'horizon_days' => 3, 'daily_rate' => ['numerator_milli' => 1000, 'denominator_days' => 3]], 10000, '2026-11-01');
$lots = $engine->calculate([...$policy, 'horizon_days' => 2], 10000, '2026-11-01', [], [], 0, [], [['quantity' => 10000, 'last_usable_on' => '2026-11-01']]);
if ($result['risk_date'] !== '2026-11-10' || $result['quantity_milli'] !== 84000 || array_column($rate['series'], 'demand_milli') !== [334, 333, 333]
|| $lots['series'][1]['expired_milli'] !== 0 || $lots['series'][1]['balance_milli'] !== -10000) {
throw new RuntimeException('A versão candidata não preserva os resultados do contrato de integração.');
}
$scenarios = ['manual', 'exact_historical_rate', 'lot_expiry'];
if (in_array('procurement.unit_discounts', $checked['manifest']['module']['capabilities'], true)) {
$pricing = new QuotationPricing;
if ($pricing->unit('19.99', 'percent', '12.50')['net_unit_minor'] !== 1749 || $pricing->unit('0.03', 'percent', '50')['net_unit_minor'] !== 2) {
throw new RuntimeException('Descontos incompatíveis com o contrato.');
}
$scenarios[] = 'unit_discounts';
}
if (in_array('procurement.proposal_scoring', $checked['manifest']['module']['capabilities'], true)) {
$row = ['eligible' => true, 'subtotal_minor' => 10000, 'delivery_days' => 2, 'weights' => ['price' => 50, 'quality' => 50, 'delivery' => 0, 'history' => 0, 'payment' => 0],
'evaluation_count' => 2, 'qualified_evaluations' => array_fill(0, 2, ['quality' => '8', 'history' => '8', 'payment' => '8'])];
$ranking = (new ProposalScoring)->rank([['id' => 1, ...$row], ['id' => 2, ...$row]], 2);
if ($ranking !== ['scores' => [1 => 900, 2 => 900], 'top_ids' => [1, 2]]) {
throw new RuntimeException('Pontuação ou empates incompatíveis com o contrato.');
}
$scenarios[] = 'proposal_scores_and_ties';
}
echo json_encode(['version' => $checked['manifest']['version'], 'source_sha256' => $checked['manifest']['source_sha256'], 'runtime' => PHP_VERSION, 'scenarios' => $scenarios, 'passed' => true, 'database_connected' => false], JSON_THROW_ON_ERROR)."\n";
} catch (Throwable $error) {
fwrite(STDERR, $error->getMessage()."\n");
$exitCode = 1;
} finally {
if ($stage !== null && is_dir($stage)) {
$real = realpath($stage);
$temporary = str_replace('\\', '/', realpath(sys_get_temp_dir()));
if ($real !== false && str_starts_with(str_replace('\\', '/', $real), $temporary.'/sgc-purchasing-probe-')) {
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($real, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $file) {
if ($file->isDir() && ! $file->isLink()) {
rmdir($file->getPathname());
} else {
unlink($file->getPathname());
}
}
rmdir($real);
}
}
}
exit($exitCode);