167 lines
5.7 KiB
PHP
167 lines
5.7 KiB
PHP
<?php
|
|
|
|
app_require('routes/InvoicingPeriodRoute.php');
|
|
|
|
use routes\InvoicingPeriodRoute;
|
|
|
|
function invoicing_period_invoice_state_invoke(string $method, array $args = []): mixed
|
|
{
|
|
$reflection = new ReflectionClass(InvoicingPeriodRoute::class);
|
|
$target = $reflection->getMethod($method);
|
|
return $target->invokeArgs(null, $args);
|
|
}
|
|
|
|
final class InvoicingPeriodInvoiceStateFakeResult
|
|
{
|
|
public int $num_rows;
|
|
|
|
public function __construct(private array $rows)
|
|
{
|
|
$this->num_rows = count($rows);
|
|
}
|
|
|
|
public function fetch_assoc(): ?array
|
|
{
|
|
return array_shift($this->rows);
|
|
}
|
|
}
|
|
|
|
final class InvoicingPeriodInvoiceStateFakeDb
|
|
{
|
|
public int $queryCount = 0;
|
|
public string $sql = '';
|
|
|
|
public function __construct(private array $rows)
|
|
{
|
|
}
|
|
|
|
public function query(string $sql): InvoicingPeriodInvoiceStateFakeResult
|
|
{
|
|
$this->queryCount++;
|
|
$this->sql = $sql;
|
|
return new InvoicingPeriodInvoiceStateFakeResult($this->rows);
|
|
}
|
|
}
|
|
|
|
it('applies canonical invoice collection state precedence', function (): void {
|
|
expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[
|
|
'booked_invoice_id' => 9001,
|
|
'processor' => 1,
|
|
'external_id' => 'draft-1',
|
|
'closed_at' => '2026-06-30 12:00:00',
|
|
]]))->toBe('economic_booked');
|
|
|
|
expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[
|
|
'booked_invoice_id' => null,
|
|
'processor' => 1,
|
|
'external_id' => 'draft-2',
|
|
'error_message' => null,
|
|
'closed_at' => '2026-06-30 12:00:00',
|
|
]]))->toBe('economic_draft');
|
|
|
|
expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[
|
|
'processor' => 2,
|
|
'external_id' => 'stripe-id',
|
|
'closed_at' => '2026-06-30 12:00:00',
|
|
]]))->toBe('closed');
|
|
|
|
expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[]]))->toBe('open');
|
|
});
|
|
|
|
it('adds transaction states and collection summaries without losing logical orders', function (): void {
|
|
$customer = [
|
|
'customer_number' => 42424242,
|
|
'transactions' => [
|
|
['id' => 11, 'amount' => 100.5, 'invoice_collection_id' => 501, 'booked' => false],
|
|
['id' => 12, 'amount' => 50.0, 'invoice_collection_id' => 501, 'booked' => false],
|
|
['id' => 13, 'amount' => 25.0, 'invoice_collection_id' => 502, 'booked' => false],
|
|
['id' => 14, 'amount' => 10.0, 'invoice_collection_id' => null, 'booked' => false, 'completed_at' => '2026-06-30 10:00:00'],
|
|
],
|
|
'queue' => ['invoice_collection_ids' => []],
|
|
'draft' => ['invoice_collection_ids' => [503]],
|
|
];
|
|
$metadata = [
|
|
501 => ['id' => 501, 'invoice_collection_id' => 501, 'customer_number' => 42424242, 'state' => 'economic_booked'],
|
|
502 => ['id' => 502, 'invoice_collection_id' => 502, 'customer_number' => 42424242, 'state' => 'closed'],
|
|
503 => ['id' => 503, 'invoice_collection_id' => 503, 'customer_number' => 42424242, 'state' => 'economic_draft'],
|
|
];
|
|
|
|
$result = invoicing_period_invoice_state_invoke('applyPeriodInvoiceStateOverlayToCustomer', [$customer, $metadata]);
|
|
|
|
expect(array_column($result['transactions'], 'invoice_state'))->toBe([
|
|
'economic_booked',
|
|
'economic_booked',
|
|
'closed',
|
|
'closed',
|
|
]);
|
|
expect(array_column($result['transactions'], 'booked'))->toBe([true, true, false, false]);
|
|
expect($result['invoice_collections'])->toHaveCount(3);
|
|
expect($result['invoice_collections'][0])->toMatchArray([
|
|
'id' => 501,
|
|
'order_ids' => [11, 12],
|
|
'order_count' => 2,
|
|
'total_net_amount' => 150.5,
|
|
]);
|
|
expect($result['invoice_collections'][2])->toMatchArray([
|
|
'id' => 503,
|
|
'order_ids' => [],
|
|
'order_count' => 0,
|
|
'total_net_amount' => 0.0,
|
|
]);
|
|
});
|
|
|
|
it('loads all referenced invoice collection metadata in one local query', function (): void {
|
|
$hadDb = array_key_exists('db', $GLOBALS);
|
|
$originalDb = $GLOBALS['db'] ?? null;
|
|
$fakeDb = new InvoicingPeriodInvoiceStateFakeDb([
|
|
[
|
|
'id' => '501',
|
|
'customer_number' => '42424242',
|
|
'processor' => '1',
|
|
'external_id' => 'draft-501',
|
|
'booked_invoice_id' => null,
|
|
'error_message' => null,
|
|
'closed_at' => '2026-06-30 12:00:00',
|
|
],
|
|
[
|
|
'id' => '502',
|
|
'customer_number' => '42424242',
|
|
'processor' => '1',
|
|
'external_id' => 'draft-502',
|
|
'booked_invoice_id' => '7002',
|
|
'error_message' => null,
|
|
'closed_at' => '2026-06-30 12:00:00',
|
|
],
|
|
]);
|
|
$GLOBALS['db'] = $fakeDb;
|
|
|
|
try {
|
|
$metadata = invoicing_period_invoice_state_invoke('getPeriodInvoiceCollectionMetadata', [[
|
|
'all' => [[
|
|
'customer_number' => 42424242,
|
|
'transactions' => [
|
|
['invoice_collection_id' => 501],
|
|
['invoice_collection_id' => 501],
|
|
],
|
|
'queue' => ['invoice_collection_ids' => [502]],
|
|
'draft' => ['invoice_collection_ids' => []],
|
|
]],
|
|
'fixed_pricing' => [[
|
|
'customer_number' => 42424242,
|
|
'transactions' => [['invoice_collection_id' => 502]],
|
|
]],
|
|
]]);
|
|
} finally {
|
|
if ($hadDb) {
|
|
$GLOBALS['db'] = $originalDb;
|
|
} else {
|
|
unset($GLOBALS['db']);
|
|
}
|
|
}
|
|
|
|
expect($fakeDb->queryCount)->toBe(1);
|
|
expect($fakeDb->sql)->toContain('WHERE id IN (501,502)');
|
|
expect($metadata[501]['state'])->toBe('economic_draft');
|
|
expect($metadata[502]['state'])->toBe('economic_booked');
|
|
});
|