assertStringContainsString('$processDetailsEmbedded', $page); $this->assertStringContainsString( "@include('business.pages.financial-operations.process-details-content')", $page, ); $this->assertStringNotContainsString('', $content); $this->assertStringContainsString('class="process-details-page"', $content); $this->assertStringContainsString('class="process-details-table-card"', $content); $this->assertStringContainsString('class="process-details-pagination"', $content); $this->assertStringContainsString('class="process-details-page-jump"', $content); $this->assertStringContainsString('$pageUrl ??=', $content); $this->assertStringContainsString('$exportUrl ??=', $content); $this->assertStringContainsString('$statusClass ??=', $content); $this->assertStringNotContainsString('$processRows->links()', $content); } public function test_page_data_contains_real_paginator_filters_and_summary(): void { $row = (object) [ 'business_id' => '202610241052000241951', 'line_no' => 1, 'status' => '审批中', ]; $repository = $this->createMock(DingProcessDetailMirrorRepository::class); $repository->expects($this->once()) ->method('paginate') ->with($this->callback(static fn (array $filters): bool => $filters['paid_start'] === '2026-10-20' && $filters['paid_end'] === '2026-10-24' && $filters['payment_state'] === '已付款' ), 2, 50) ->willReturn([ 'data' => [$row], 'total' => 11331, 'summary' => [ 'rows' => 11331, 'approvals' => 4775, 'amount_total' => 225953332.98, 'bank_amount_total' => 225862410.66, ], 'status' => 'succeeded', 'last_synced_at' => '2026-10-26 16:55:11', 'sync_stale' => true, 'sync_stale_after_minutes' => 15, 'scope' => 'personal_permission', ]); $repository->expects($this->once()) ->method('filterOptions') ->willReturn([ 'document_types' => ['种菜核销/种菜报销'], 'statuses' => ['审批中'], 'payment_states' => [ '待付款', '已付款', '付款待核实', '不涉及付款', ], ]); $data = (new ProcessDetailsPageData($repository))->page(Request::create( '/1/workspace/process-data/oa-process', 'GET', [ 'paid_start' => '2026-10-20', 'paid_end' => '2026-10-24', 'payment_state' => '出纳已打款', 'page' => 2, ], )); $this->assertInstanceOf(LengthAwarePaginator::class, $data['processRows']); $this->assertSame(11331, $data['processRows']->total()); $this->assertSame(50, $data['processRows']->perPage()); $this->assertSame(2, $data['processRows']->currentPage()); $this->assertSame($row, $data['processRows']->items()[0]); $this->assertSame('succeeded', $data['mirrorStatus']); $this->assertSame(11331, $data['summary']['rows']); $this->assertSame('2026-10-20', $data['filters']['paid_start']); $this->assertSame('2026-10-24', $data['filters']['paid_end']); $this->assertSame('已付款', $data['filters']['payment_state']); $this->assertSame( [ '待付款', '已付款', '付款待核实', '不涉及付款', ], $data['paymentStates'], ); $this->assertTrue($data['mirrorStale']); $this->assertSame(15, $data['mirrorStaleAfterMinutes']); $this->assertArrayNotHasKey('months', $data); $this->assertNull($data['error']); } public function test_paid_date_range_accepts_legacy_date_and_normalizes_reversed_range(): void { $this->assertSame( ['2026-10-24', '2026-10-24'], ProcessDetailsPageData::normalizePaidDateRange('', '', '2026-10-24'), ); $this->assertSame( ['2026-10-20', '2026-10-24'], ProcessDetailsPageData::normalizePaidDateRange('2026-10-24', '2026-10-20'), ); $this->assertSame( ['', ''], ProcessDetailsPageData::normalizePaidDateRange('2026-02-30', 'invalid'), ); } public function test_unavailable_payment_state_filter_is_cleared_before_querying(): void { $repository = $this->createMock(DingProcessDetailMirrorRepository::class); $repository->expects($this->once()) ->method('filterOptions') ->with($this->callback(static fn (array $filters): bool => $filters['status'] === '已完成' && $filters['payment_state'] === '付款待核实' )) ->willReturn([ 'document_types' => ['公摊日常报销'], 'statuses' => ['已完成'], 'payment_states' => ['已付款'], ]); $repository->expects($this->once()) ->method('paginate') ->with($this->callback(static fn (array $filters): bool => $filters['status'] === '已完成' && $filters['payment_state'] === '' ), 1, 50) ->willReturn([ 'data' => [], 'total' => 51, 'summary' => [ 'rows' => 51, 'approvals' => 0, 'amount_total' => 0, 'bank_amount_total' => 0, ], 'status' => 'succeeded', 'last_synced_at' => null, 'scope' => 'personal_permission', ]); $data = (new ProcessDetailsPageData($repository))->page(Request::create( '/1/workspace/process-data/oa-process', 'GET', ['status' => '已完成', 'payment_state' => '付款待核实'], )); $this->assertSame('', $data['filters']['payment_state']); $this->assertSame(['已付款'], $data['paymentStates']); $this->assertStringNotContainsString('payment_state', $data['processRows']->url(2)); } #[DataProvider('perPageProvider')] public function test_page_size_only_accepts_supported_options(mixed $requested, int $expected): void { $repository = $this->createMock(DingProcessDetailMirrorRepository::class); $repository->expects($this->once()) ->method('paginate') ->with($this->isType('array'), 1, $expected) ->willReturn([ 'data' => [], 'total' => 0, 'summary' => [ 'rows' => 0, 'approvals' => 0, 'amount_total' => 0, 'bank_amount_total' => 0, ], 'status' => 'succeeded', 'last_synced_at' => null, 'scope' => 'personal_permission', ]); $repository->expects($this->once()) ->method('filterOptions') ->willReturn([ 'document_types' => [], 'statuses' => [], 'payment_states' => [], ]); $data = (new ProcessDetailsPageData($repository))->page(Request::create( '/1/workspace/process-data/oa-process', 'GET', ['per_page' => $requested], )); $this->assertSame($expected, $data['processRows']->perPage()); } /** @return array */ public static function perPageProvider(): array { return [ '50 条' => ['50', 50], '100 条' => ['100', 100], '200 条' => ['200', 200], '非法值回退' => ['500', 50], ]; } }