$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); });