diff --git a/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php b/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php index 155b5daa..859be1ea 100644 --- a/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php +++ b/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php @@ -142,7 +142,9 @@ class ProcessDetails extends Controller /** @return array */ private function filters(Request $request): array { - $status = trim((string) $request->query('status', '')); + $status = DingProcessDetailMirrorRepository::normalizeStatusFilter( + $request->query('status', ''), + ); [$paidStart, $paidEnd] = ProcessDetailsPageData::normalizePaidDateRange( $request->query('paid_start'), $request->query('paid_end'), diff --git a/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php b/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php index e9ca5be5..ee04eaf7 100644 --- a/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php @@ -34,6 +34,11 @@ class DingProcessDetailMirrorRepository '流程终止' => '不涉及付款', ]; + public const LEGACY_STATUS_MAP = [ + '已终止' => '已撤销', + '已撤回' => '已撤销', + ]; + private readonly ConnectionInterface $connection; private ?bool $paymentSourceStateColumn = null; @@ -65,6 +70,13 @@ class DingProcessDetailMirrorRepository return in_array($paymentState, self::PAYMENT_STATES, true) ? $paymentState : ''; } + public static function normalizeStatusFilter(mixed $value): string + { + $status = trim((string) $value); + + return self::LEGACY_STATUS_MAP[$status] ?? $status; + } + public function datasetKey(): string { return self::DATASET_PREFIX . $this->scope(); @@ -304,6 +316,9 @@ class DingProcessDetailMirrorRepository 'status' => 'status', ] as $filter => $column) { $value = trim((string) ($filters[$filter] ?? '')); + if ($filter === 'status') { + $value = self::normalizeStatusFilter($value); + } if ($value !== '') { $query->where('ding_process_detail_mirror.' . $column, $value); } @@ -344,7 +359,8 @@ class DingProcessDetailMirrorRepository return "CASE WHEN {$sourceState} = 'not_applicable' OR {$cashierState} = 'terminated' - OR (({$cashierState} IS NULL OR {$cashierState} = '') AND {$workflowStatus} = '已终止') + OR (({$cashierState} IS NULL OR {$cashierState} = '') + AND {$workflowStatus} IN ('已撤销', '已终止')) THEN '不涉及付款' WHEN {$cashierState} = 'completed' OR (({$cashierState} IS NULL OR {$cashierState} = '') diff --git a/modules/EcommerceFinance/Services/DingProcessDetailMirrorSynchronizer.php b/modules/EcommerceFinance/Services/DingProcessDetailMirrorSynchronizer.php index ca51be64..037c8afc 100644 --- a/modules/EcommerceFinance/Services/DingProcessDetailMirrorSynchronizer.php +++ b/modules/EcommerceFinance/Services/DingProcessDetailMirrorSynchronizer.php @@ -1661,7 +1661,7 @@ class DingProcessDetailMirrorSynchronizer 'RUNNING' => '审批中', 'COMPLETED' => '已完成', 'REJECTED' => '已拒绝', - 'WITHDRAWN' => '已终止', + 'WITHDRAWN' => '已撤销', 'FAILED' => '失败', 'EXPIRED' => '已过期', 'DELETED' => '已删除', diff --git a/modules/EcommerceFinance/View/ProcessDetailsPageData.php b/modules/EcommerceFinance/View/ProcessDetailsPageData.php index 5269b24b..32e3f30e 100644 --- a/modules/EcommerceFinance/View/ProcessDetailsPageData.php +++ b/modules/EcommerceFinance/View/ProcessDetailsPageData.php @@ -117,7 +117,9 @@ class ProcessDetailsPageData /** @return array */ private function filters(Request $request): array { - $status = trim((string) $request->query('status', '')); + $status = DingProcessDetailMirrorRepository::normalizeStatusFilter( + $request->query('status', ''), + ); [$paidStart, $paidEnd] = self::normalizePaidDateRange( $request->query('paid_start'), $request->query('paid_end'), diff --git a/resources/views/business/pages/financial-operations/process-details-content.blade.php b/resources/views/business/pages/financial-operations/process-details-content.blade.php index 9ecfd819..e899e79b 100644 --- a/resources/views/business/pages/financial-operations/process-details-content.blade.php +++ b/resources/views/business/pages/financial-operations/process-details-content.blade.php @@ -29,6 +29,11 @@ '不涉及付款' => 'is-neutral', default => 'is-neutral', }; + $paymentSummaryLabel = match ($filters['payment_state'] ?? '') { + '已付款' => '已确认付款合计', + '付款待核实' => '待核实付款明细合计', + default => '付款明细合计', + }; $pageUrl ??= route('business.pages.show', [ 'section' => 'process-data', 'page' => 'oa-process', @@ -736,12 +741,13 @@
明细行数{{ number_format((int) ($summary['rows'] ?? 0)) }}
审批单数{{ number_format((int) ($summary['approvals'] ?? 0)) }}
金额合计{{ $money($summary['amount_total'] ?? null) }}
-
付款合计{{ $money($summary['bank_amount_total'] ?? null) }}
+
{{ $paymentSummaryLabel }}{{ $money($summary['bank_amount_total'] ?? null) }}

- 金额合计按审批费用明细汇总,付款合计按结构化付款明细汇总,同一笔付款不会因关联多条费用而重复计算。 - 付款状态按钉钉“出纳打款”节点判断;付款差额按“费用合计 − 已付款合计”单独计算,不参与付款状态判断。 + 金额合计按审批费用明细汇总,付款明细合计按审批表单中的结构化付款明细汇总,不等同于出纳已确认付款; + 同一笔付款不会因关联多条费用而重复计算。 + 付款状态按钉钉“出纳打款”节点判断;付款差额按“费用合计 − 付款明细合计”单独计算,不参与付款状态判断。 @if (in_array('付款待核实', $paymentStates, true)) “付款待核实”表示尚未取得可靠的出纳节点任务,不能据此推断已付款或待付款。 @endif @@ -767,7 +773,7 @@ 金额(元) 银行科目编码 银行科目名称 - 付款金额 + 付款明细金额 付款差额 付款方式 打款时间 diff --git a/tests/Feature/Business/ProcessDetailsPageTest.php b/tests/Feature/Business/ProcessDetailsPageTest.php index 091fa75f..b0381738 100644 --- a/tests/Feature/Business/ProcessDetailsPageTest.php +++ b/tests/Feature/Business/ProcessDetailsPageTest.php @@ -75,6 +75,7 @@ class ProcessDetailsPageTest extends FeatureTestCase 'section' => 'process-data', 'page' => 'oa-process', 'per_page' => 100, + 'payment_state' => '付款待核实', ]))->assertOk() ->assertSee('流程数据') ->assertSee('OA流程') @@ -92,12 +93,13 @@ class ProcessDetailsPageTest extends FeatureTestCase ->assertSee('name="payment_state"', false) ->assertSee('审批状态') ->assertSee('付款状态') + ->assertSee('待核实付款明细合计') ->assertSee('已付款') ->assertSee('付款待核实') - ->assertSee('付款金额') + ->assertSee('付款明细金额') ->assertSee('付款差额') ->assertSee('0.20') - ->assertSee('付款合计') + ->assertDontSee('付款合计', false) ->assertDontSee('银行付款合计') ->assertSee('流程数据已超过 15 分钟未成功同步') ->assertSee('202601011200000000001') diff --git a/tests/Unit/EcommerceFinance/DingProcessDetailMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/DingProcessDetailMirrorRepositoryTest.php index 92ec96f2..7f9bbb2e 100644 --- a/tests/Unit/EcommerceFinance/DingProcessDetailMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/DingProcessDetailMirrorRepositoryTest.php @@ -75,6 +75,27 @@ class DingProcessDetailMirrorRepositoryTest extends TestCase ); } + public function test_legacy_revoked_status_filter_is_normalized(): void + { + DB::connection('business_mirror') + ->table('ding_process_detail_mirror') + ->where('business_id', '202602031200000000002') + ->update(['status' => '已撤销']); + + $this->assertSame( + 2, + $this->repository->paginate(['status' => '已终止'], 1, 50)['total'], + ); + $this->assertSame( + 2, + $this->repository->paginate(['status' => '已撤回'], 1, 50)['total'], + ); + $this->assertSame( + 2, + $this->repository->paginate(['status' => '已撤销'], 1, 50)['total'], + ); + } + public function test_paginate_applies_paid_date_range_status_and_keyword_filters(): void { $this->assertSame( diff --git a/tests/Unit/EcommerceFinance/DingProcessDetailMirrorSynchronizerTest.php b/tests/Unit/EcommerceFinance/DingProcessDetailMirrorSynchronizerTest.php index e291ccc8..6259d220 100644 --- a/tests/Unit/EcommerceFinance/DingProcessDetailMirrorSynchronizerTest.php +++ b/tests/Unit/EcommerceFinance/DingProcessDetailMirrorSynchronizerTest.php @@ -178,6 +178,36 @@ class DingProcessDetailMirrorSynchronizerTest extends TestCase $this->assertSame('不涉及付款', $row->payment_state); } + public function test_terminated_workflow_is_labelled_revoked(): void + { + $this->query->workflows = [[ + 'processInstanceId' => 'process-cashier-terminated', + 'processCode' => 'PROC-ACF09A14-B094-46B1-A9E2-8BB1F4E1FDCF', + 'businessId' => '202610191640000251028', + 'title' => '雁兰提交的应付申请流程', + 'status' => 'TERMINATED', + 'result' => 'AGREE', + 'originatorUserId' => 'user-1', + 'joinAt' => '2026-10-19 16:40:00', + ]]; + $this->query->scheme = [ + '收款费用明细' => json_encode([[ + 'rowValue' => [ + ['label' => '代发订单金额', 'value' => '1.00'], + ], + ]], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), + '收款合计' => 1, + '付款明细' => '[]', + ]; + + $this->synchronizer->sync(true); + + $row = app(DingProcessDetailMirrorRepository::class) + ->paginate(['business_id' => '202610191640000251028'], 1, 50)['data'][0]; + $this->assertSame('已撤销', $row->status); + $this->assertSame('不涉及付款', $row->payment_state); + } + public function test_running_workflow_detects_cashier_returned_from_official_tasks(): void { $this->query->workflows = [[ diff --git a/tests/Unit/EcommerceFinance/ProcessDetailsPageDataTest.php b/tests/Unit/EcommerceFinance/ProcessDetailsPageDataTest.php index fb89be25..b46b8e86 100644 --- a/tests/Unit/EcommerceFinance/ProcessDetailsPageDataTest.php +++ b/tests/Unit/EcommerceFinance/ProcessDetailsPageDataTest.php @@ -178,6 +178,47 @@ class ProcessDetailsPageDataTest extends TestCase $this->assertStringNotContainsString('payment_state', $data['processRows']->url(2)); } + public function test_legacy_terminated_status_is_normalized_to_revoked(): void + { + $repository = $this->createMock(DingProcessDetailMirrorRepository::class); + $repository->expects($this->once()) + ->method('filterOptions') + ->with($this->callback(static fn (array $filters): bool => + $filters['status'] === '已撤销' + )) + ->willReturn([ + 'document_types' => [], + 'statuses' => ['已撤销'], + 'payment_states' => [], + ]); + $repository->expects($this->once()) + ->method('paginate') + ->with($this->callback(static fn (array $filters): bool => + $filters['status'] === '已撤销' + ), 1, 50) + ->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', + ]); + + $data = (new ProcessDetailsPageData($repository))->page(Request::create( + '/1/workspace/process-data/oa-process', + 'GET', + ['status' => '已终止'], + )); + + $this->assertSame('已撤销', $data['filters']['status']); + } + #[DataProvider('perPageProvider')] public function test_page_size_only_accepts_supported_options(mixed $requested, int $expected): void {