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
+48
View File
@@ -0,0 +1,48 @@
<?php
use Tempo\Purchasing\Economic\ProposalScoring;
use Tempo\Purchasing\Economic\QuotationPricing;
require_once __DIR__.'/../../packages/purchasing/bootstrap.php';
function economicRow(int $id, int $price, int $quality = 8): array
{
return ['id' => $id, 'eligible' => true, 'subtotal_minor' => $price, 'delivery_days' => 2,
'weights' => ['price' => 50, 'quality' => 50, 'delivery' => 0, 'history' => 0, 'payment' => 0],
'evaluation_count' => 2, 'qualified_evaluations' => array_fill(0, 2, ['quality' => (string) $quality, 'history' => '8', 'payment' => '8'])];
}
test('shared unit pricing preserves discounts and half up net unit cents', function () {
$pricing = new QuotationPricing;
expect($pricing->unit('19.99', 'percent', '12.50'))->toBe(['gross_unit_minor' => 1999, 'discount_type' => 'percent', 'discount_value' => '12.50', 'net_unit_minor' => 1749])
->and($pricing->unit('0.03', 'percent', '50')['net_unit_minor'])->toBe(2)
->and($pricing->unit('100', 'amount', '7.50')['net_unit_minor'])->toBe(9250)
->and($pricing->unit('100')['net_unit_minor'])->toBe(10000);
});
test('shared pricing rejects invalid discounts without Laravel or a database', function () {
foreach ([['0', 'none', '0'], ['100', 'percent', '101'], ['100', 'amount', '100'], ['100', 'none', '1'], ['100', 'unknown', '0']] as $input) {
expect(fn () => (new QuotationPricing)->unit(...$input))->toThrow(InvalidArgumentException::class);
}
});
test('shared scoring preserves exact weighted results all ties and host supplied ineligibility', function () {
$rows = [economicRow(1, 10000), economicRow(2, 20000, 10), economicRow(3, 10000), [...economicRow(4, 1, 10), 'eligible' => false]];
expect((new ProposalScoring)->rank($rows, 2))->toBe(['scores' => [1 => 900, 2 => 750, 3 => 900], 'top_ids' => [1, 3]]);
});
test('shared scoring retains the existing reference basis when eligible rows lack sufficient evaluations', function () {
$pending = economicRow(5, 5000);
$pending['qualified_evaluations'] = [$pending['qualified_evaluations'][0]];
$pending['evaluation_count'] = 1;
expect((new ProposalScoring)->rank([economicRow(1, 10000), economicRow(2, 20000, 10), $pending], 2))->toBe(['scores' => [1 => 650, 2 => 625], 'top_ids' => [1]]);
expect((new ProposalScoring)->rank([], 2))->toBe(['scores' => [], 'top_ids' => []]);
});
test('shared scoring refuses inconsistent projected inputs rather than inventing a result', function () {
$row = economicRow(1, 10000);
foreach ([[$row, $row], [[...$row, 'subtotal_minor' => 0]], [[...$row, 'evaluation_count' => 3]], [[...$row, 'weights' => ['price' => 100]]]] as $rows) {
expect(fn () => (new ProposalScoring)->rank($rows, 2))->toThrow(InvalidArgumentException::class);
}
expect(fn () => (new ProposalScoring)->rank([$row], 0))->toThrow(InvalidArgumentException::class);
});