getMethod($method); $target->setAccessible(true); return $target->invokeArgs(null, $args); } function invoicing_period_customer_card( int $customerNumber, string $customerName, array $transactions, bool $requiresAction = false, array $extra = [] ): array { return array_merge([ 'id' => $customerNumber, 'customer_number' => $customerNumber, 'customer_name' => $customerName, 'requires_action' => $requiresAction, 'transactions' => $transactions, 'meta' => [], 'queue' => [ 'has_active_job' => false, 'statuses' => [], 'invoice_collection_ids' => [], 'is_action_blocked' => false, ], 'draft' => [ 'has_valid_draft' => false, 'invoice_collection_ids' => [], 'is_action_blocked' => false, ], ], $extra); } function invoicing_period_transaction(array $overrides = []): array { return array_merge([ 'id' => 9001, 'date' => '2026-04-10 12:00:00', 'amount' => 125.5, 'booked' => false, 'department_id' => 1, 'customer_number' => 1001, 'reference' => 'REF-9001', 'po' => 'PO-9001', 'notes' => 'Gate note', 'reg_1' => 'AB12345', 'reg_2' => '', 'reg_3' => '', 'excluded' => false, 'invoice_collection_id' => 3001, 'queue_status' => null, 'queue_job_id' => null, ], $overrides); } it('detects paginated period mode only when pagination parameters are present', function (): void { global $response; $previousResponse = $GLOBALS['response'] ?? null; $previousGet = $_GET; $previousMethod = $_SERVER['REQUEST_METHOD'] ?? null; $response = new response(); try { $_SERVER['REQUEST_METHOD'] = 'GET'; $_GET = [ 'dateFrom' => '2026-04-01', 'dateTo' => '2026-04-30', ]; expect(invoicing_period_pagination_invoke('getPeriodPaginationOptionsFromRequest'))->toBeNull(); $_GET['page'] = '2'; expect(invoicing_period_pagination_invoke('getPeriodPaginationOptionsFromRequest'))->toMatchArray([ 'periodView' => 'all', 'page' => 2, 'limit' => 100, 'search' => '', 'includeRequiresAction' => true, 'includeBooked' => true, ]); } finally { $_GET = $previousGet; if ($previousMethod === null) { unset($_SERVER['REQUEST_METHOD']); } else { $_SERVER['REQUEST_METHOD'] = $previousMethod; } if ($previousResponse === null) { unset($GLOBALS['response']); } else { $response = $previousResponse; } } }); it('normalizes period pagination options and clamps invalid page and limit values', function (): void { $options = invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[ 'periodView' => 'not-a-view', 'page' => '-4', 'limit' => '500', 'search' => ' Nordic ', 'includeRequiresAction' => '0', 'includeBooked' => 'false', 'flagTab' => 'invalid-tab', ]]); expect($options)->toBe([ 'periodView' => 'all', 'page' => 1, 'limit' => 500, 'search' => 'Nordic', 'flagTab' => 'all', 'includeRequiresAction' => false, 'includeBooked' => false, ]); expect(invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[ 'limit' => '900', ]]))->toMatchArray([ 'limit' => 500, ]); expect(invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[ 'limit' => 'all', ]]))->toMatchArray([ 'limit' => 'all', ]); expect(invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[ 'periodView' => 'invoice_per_order', 'page' => '3', 'limit' => '0', 'flagTab' => 'yellow', ]]))->toMatchArray([ 'periodView' => 'invoice_per_order', 'page' => 3, 'limit' => 100, 'flagTab' => 'yellow', ]); }); it('slices only the active period view and keeps exact full-result type counts', function (): void { $period = [ 'dateFrom' => '2026-04-01 00:00:00', 'dateTo' => '2026-04-30 23:59:59', 'types' => [ 'all' => [ invoicing_period_customer_card(1001, 'Alpha Transport', [ invoicing_period_transaction(['id' => 11, 'customer_number' => 1001, 'amount' => 50]), ], false), invoicing_period_customer_card(1002, 'Beta Transport', [ invoicing_period_transaction([ 'id' => 12, 'customer_number' => 1002, 'amount' => 75, 'booked' => true, 'excluded' => true, 'reference' => 'REF-BETA', 'po' => 'PO-BETA', 'reg_1' => 'BB22222', ]), ], true), invoicing_period_customer_card(1003, 'Gamma Transport', [ invoicing_period_transaction([ 'id' => 13, 'customer_number' => 1003, 'amount' => 125, 'booked' => true, ]), ], false, [ 'draft' => [ 'has_valid_draft' => true, 'invoice_collection_ids' => [3013], 'is_action_blocked' => true, ], ]), ], 'fixed_pricing' => [ invoicing_period_customer_card(1002, 'Beta Transport', [], true, [ 'meta' => [ 'fixed_pricing' => [ 'price' => 999, ], ], ]), ], 'invoice_per_order' => [ invoicing_period_customer_card(1001, 'Alpha Transport', [], true, [ 'flags' => [ ['source' => 'manual', 'status' => 'active'], ], ]), ], ], ]; $result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [ 'periodView' => 'all', 'page' => 2, 'limit' => 1, 'search' => '', 'includeRequiresAction' => true, 'includeBooked' => true, ]]); expect($result['pagination'])->toMatchArray([ 'page' => 2, 'per_page' => 1, 'total' => 3, 'search' => '', ]); expect($result['period']['types']['all'])->toHaveCount(1); expect($result['period']['types']['all'][0])->toMatchArray([ 'customer_number' => 1002, 'customer_name' => 'Beta Transport', 'requires_action' => true, 'meta' => [ 'fixed_pricing' => [ 'price' => 999, ], ], ]); expect($result['period']['types']['all'][0]['transactions'][0])->toMatchArray([ 'id' => 12, 'amount' => 75, 'booked' => true, 'excluded' => true, 'reference' => 'REF-BETA', 'po' => 'PO-BETA', 'reg_1' => 'BB22222', ]); expect($result['period']['types']['fixed_pricing'])->toBe([]); expect($result['period']['type_counts']['all'])->toBe([ 'requires_action' => 1, 'draft' => 1, 'manual_flags' => 0, 'automatic_flags' => 0, 'completed' => 1, 'total' => 3, ]); expect($result['period']['type_totals']['all'])->toBe([ 'total' => 1174.0, 'booked' => 125.0, 'not_booked' => 1049.0, ]); expect($result['period']['type_totals']['fixed_pricing'])->toBe([ 'total' => 999.0, 'booked' => 0.0, 'not_booked' => 999.0, ]); expect($result['period']['type_counts']['invoice_per_order']['manual_flags'])->toBe(1); }); it('returns the entire active period view when the limit is all', function (): void { $period = [ 'dateFrom' => '2026-04-01 00:00:00', 'dateTo' => '2026-04-30 23:59:59', 'types' => [ 'all' => [ invoicing_period_customer_card(1101, 'Alpha', [ invoicing_period_transaction(['id' => 31, 'customer_number' => 1101]), ]), invoicing_period_customer_card(1102, 'Beta', [ invoicing_period_transaction(['id' => 32, 'customer_number' => 1102]), ]), invoicing_period_customer_card(1103, 'Gamma', [ invoicing_period_transaction(['id' => 33, 'customer_number' => 1103]), ]), ], ], ]; $result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [ 'periodView' => 'all', 'page' => 3, 'limit' => 'all', 'search' => '', 'includeRequiresAction' => true, 'includeBooked' => true, ]]); expect($result['pagination'])->toMatchArray([ 'page' => 1, 'per_page' => 'all', 'total' => 3, ]); expect(array_column($result['period']['types']['all'], 'customer_number'))->toBe([1101, 1102, 1103]); }); it('searches customer fields and order fields at the customer-card level', function (): void { $period = [ 'dateFrom' => '2026-04-01 00:00:00', 'dateTo' => '2026-04-30 23:59:59', 'types' => [ 'all' => [ invoicing_period_customer_card(2001, 'Solaris Fleet', [ invoicing_period_transaction([ 'id' => 21, 'customer_number' => 2001, 'reference' => 'REF-KEEP', 'po' => 'PO-KEEP', ]), ]), invoicing_period_customer_card(2002, 'Nordic Logistics', [ invoicing_period_transaction([ 'id' => 22, 'customer_number' => 2002, 'reference' => 'MISS', 'po' => 'PO-777', 'notes' => 'Driver waits at gate', 'reg_1' => 'CD33333', ]), invoicing_period_transaction([ 'id' => 23, 'customer_number' => 2002, 'reference' => 'SECOND-LINE', ]), ]), ], ], ]; $result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [ 'periodView' => 'all', 'page' => 1, 'limit' => 25, 'search' => 'po-777', 'includeRequiresAction' => true, 'includeBooked' => true, ]]); expect($result['pagination']['total'])->toBe(1); expect($result['period']['types']['all'])->toHaveCount(1); expect($result['period']['types']['all'][0]['customer_number'])->toBe(2002); expect($result['period']['types']['all'][0]['transactions'])->toHaveCount(2); $registrationMatch = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [ 'periodView' => 'all', 'page' => 1, 'limit' => 25, 'search' => 'cd33333', 'includeRequiresAction' => true, 'includeBooked' => true, ]]); expect($registrationMatch['period']['types']['all'][0]['customer_name'])->toBe('Nordic Logistics'); }); it('applies requires-action and booked visibility filters before counting and slicing', function (): void { $period = [ 'dateFrom' => '2026-04-01 00:00:00', 'dateTo' => '2026-04-30 23:59:59', 'types' => [ 'all' => [ invoicing_period_customer_card(3001, 'Needs Action', [ invoicing_period_transaction(['customer_number' => 3001, 'booked' => false]), ], true), invoicing_period_customer_card(3002, 'Already Booked', [ invoicing_period_transaction(['customer_number' => 3002, 'booked' => true]), ], false), invoicing_period_customer_card(3003, 'Still Open', [ invoicing_period_transaction(['customer_number' => 3003, 'booked' => false]), ], false), ], ], ]; $result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [ 'periodView' => 'all', 'page' => 1, 'limit' => 25, 'search' => '', 'includeRequiresAction' => false, 'includeBooked' => false, ]]); expect($result['pagination']['total'])->toBe(1); expect($result['period']['type_counts']['all']['total'])->toBe(1); expect($result['period']['types']['all'][0]['customer_number'])->toBe(3003); }); it('filters period flag tabs using active customer-scoped flag counts', function (): void { $period = [ 'dateFrom' => '2026-04-01 00:00:00', 'dateTo' => '2026-04-30 23:59:59', 'types' => [ 'all' => [ invoicing_period_customer_card(4001, 'Yellow Order Flag', [ invoicing_period_transaction(['customer_number' => 4001, 'id' => 41]), ], false, [ 'flags' => [ [ 'source' => 'automatic', 'status' => 'active', 'target_type' => 'order', 'order_id' => 41, ], ], ]), invoicing_period_customer_card(4002, 'Ignored Manual Flag', [ invoicing_period_transaction(['customer_number' => 4002, 'id' => 42]), ], false, [ 'flags' => [ [ 'source' => 'manual', 'status' => 'ignored', 'target_type' => 'customer', ], ], ]), invoicing_period_customer_card(4003, 'Red Customer Flag', [ invoicing_period_transaction(['customer_number' => 4003, 'id' => 43]), ], false, [ 'flags' => [ [ 'source' => 'manual', 'status' => 'active', 'target_type' => 'customer', ], ], ]), ], ], ]; $yellowResult = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [ 'periodView' => 'all', 'page' => 1, 'limit' => 25, 'search' => '', 'flagTab' => 'yellow', 'includeRequiresAction' => true, 'includeBooked' => true, ]]); expect($yellowResult['pagination']['total'])->toBe(1); expect(array_column($yellowResult['period']['types']['all'], 'customer_number'))->toBe([4001]); $noneResult = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [ 'periodView' => 'all', 'page' => 1, 'limit' => 25, 'search' => '', 'flagTab' => 'none', 'includeRequiresAction' => true, 'includeBooked' => true, ]]); expect($noneResult['pagination']['total'])->toBe(1); expect(array_column($noneResult['period']['types']['all'], 'customer_number'))->toBe([4002]); expect($noneResult['period']['type_counts']['all'])->toMatchArray([ 'manual_flags' => 1, 'automatic_flags' => 1, 'total' => 3, ]); });