Files
api/services/nginx/app/tests/Unit/Invoicing/EconomicV2CompareEngineTest.php
T

136 lines
4.8 KiB
PHP

<?php
app_require('classes/economic_v2_compare_engine.php');
use classes\economic_v2_compare_engine;
function economic_v2_test_invoice(array $overrides = []): array
{
$base = [
'source' => 'internal',
'totals' => [
'net_total' => 100.0,
'line_net_total' => 100.0,
'line_count' => 1,
'billable_line_count' => 1,
],
'departments' => [
'75' => 100.0,
],
'lines' => [
[
'source' => 'internal',
'source_line_id' => 1,
'line_type' => 'product',
'billable' => true,
'product_number' => '1',
'description' => 'Trakker',
'reference' => '',
'quantity' => 1.0,
'unit_net_price' => 100.0,
'line_net_amount' => 100.0,
'department_distribution' => ['75' => 100.0],
'match_key' => 'product:1|ref:',
],
],
'warnings' => [],
];
return array_replace_recursive($base, $overrides);
}
it('returns exact_match when totals lines and departments are identical', function (): void {
$internal = economic_v2_test_invoice();
$draft = economic_v2_test_invoice(['source' => 'draft']);
$booked = economic_v2_test_invoice(['source' => 'booked']);
$result = economic_v2_compare_engine::compare($internal, $draft, $booked);
expect($result['targets']['draft']['status'])->toBe('exact_match');
expect($result['targets']['booked']['status'])->toBe('exact_match');
expect($result['targets']['draft']['overall_match'])->toBeTrue();
expect($result['targets']['booked']['overall_match'])->toBeTrue();
});
it('returns total_mismatch when only totals differ', function (): void {
$internal = economic_v2_test_invoice();
$draft = economic_v2_test_invoice([
'source' => 'draft',
'totals' => ['net_total' => 125.0],
]);
$result = economic_v2_compare_engine::compareTarget($internal, $draft, 'draft');
expect($result['status'])->toBe('total_mismatch');
expect($result['totals']['matches'])->toBeFalse();
expect($result['mismatch_reasons'])->toContain('total_mismatch');
});
it('detects line-level mismatches for quantity and price', function (): void {
$internal = economic_v2_test_invoice();
$draft = economic_v2_test_invoice([
'source' => 'draft',
'lines' => [[
'source' => 'draft',
'source_line_id' => 1,
'line_type' => 'product',
'billable' => true,
'product_number' => '1',
'description' => 'Trakker',
'reference' => '',
'quantity' => 2.0,
'unit_net_price' => 95.0,
'line_net_amount' => 190.0,
'department_distribution' => ['75' => 100.0],
'match_key' => 'product:1|ref:',
]],
'totals' => ['net_total' => 100.0],
'departments' => ['75' => 100.0],
]);
$result = economic_v2_compare_engine::compareTarget($internal, $draft, 'draft');
$reasons = $result['lines']['diff'][0]['reasons'] ?? [];
expect($result['lines']['summary']['mismatch_count'])->toBeGreaterThan(0);
expect($reasons)->toContain('quantity_mismatch');
expect($reasons)->toContain('unit_price_mismatch');
});
it('detects departmental distribution mismatches', function (): void {
$internal = economic_v2_test_invoice();
$draft = economic_v2_test_invoice([
'source' => 'draft',
'lines' => [[
'source' => 'draft',
'source_line_id' => 1,
'line_type' => 'product',
'billable' => true,
'product_number' => '1',
'description' => 'Trakker',
'reference' => '',
'quantity' => 1.0,
'unit_net_price' => 100.0,
'line_net_amount' => 100.0,
'department_distribution' => ['10' => 100.0],
'match_key' => 'product:1|ref:',
]],
'departments' => ['10' => 100.0],
]);
$result = economic_v2_compare_engine::compareTarget($internal, $draft, 'draft');
$lineReasons = $result['lines']['diff'][0]['reasons'] ?? [];
expect($lineReasons)->toContain('departmental_distribution_mismatch');
expect($result['departments']['matches'])->toBeFalse();
expect($result['mismatch_reasons'])->toContain('department_total_mismatch');
});
it('returns missing_target when draft or booked target is unavailable', function (): void {
$internal = economic_v2_test_invoice();
$result = economic_v2_compare_engine::compareTarget($internal, null, 'booked');
expect($result['status'])->toBe('missing_target');
expect($result['overall_match'])->toBeFalse();
expect($result['mismatch_reasons'])->toContain('missing_target');
});