fix: 统一OA撤销状态和付款明细口径 (#709)

OA流程展示已撤销;付款待核实改用付款明细口径。
This commit is contained in:
yueyang 2026-10-28 10:07:29 +08:00
parent 48b433be4d
commit 965b70df7e
9 changed files with 130 additions and 10 deletions

View File

@ -142,7 +142,9 @@ class ProcessDetails extends Controller
/** @return array<string,string> */
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'),

View File

@ -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} = '')

View File

@ -1661,7 +1661,7 @@ class DingProcessDetailMirrorSynchronizer
'RUNNING' => '审批中',
'COMPLETED' => '已完成',
'REJECTED' => '已拒绝',
'WITHDRAWN' => '已终止',
'WITHDRAWN' => '已撤销',
'FAILED' => '失败',
'EXPIRED' => '已过期',
'DELETED' => '已删除',

View File

@ -117,7 +117,9 @@ class ProcessDetailsPageData
/** @return array<string,string> */
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'),

View File

@ -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 @@
<div class="item"><span>明细行数</span><b>{{ number_format((int) ($summary['rows'] ?? 0)) }}</b></div>
<div class="item"><span>审批单数</span><b>{{ number_format((int) ($summary['approvals'] ?? 0)) }}</b></div>
<div class="item"><span>金额合计</span><b>{{ $money($summary['amount_total'] ?? null) }}</b></div>
<div class="item"><span>付款合计</span><b>{{ $money($summary['bank_amount_total'] ?? null) }}</b></div>
<div class="item"><span>{{ $paymentSummaryLabel }}</span><b>{{ $money($summary['bank_amount_total'] ?? null) }}</b></div>
</div>
<p class="process-details-note">
金额合计按审批费用明细汇总,付款合计按结构化付款明细汇总,同一笔付款不会因关联多条费用而重复计算。
付款状态按钉钉“出纳打款”节点判断;付款差额按“费用合计 − 已付款合计”单独计算,不参与付款状态判断。
金额合计按审批费用明细汇总,付款明细合计按审批表单中的结构化付款明细汇总,不等同于出纳已确认付款;
同一笔付款不会因关联多条费用而重复计算。
付款状态按钉钉“出纳打款”节点判断;付款差额按“费用合计 − 付款明细合计”单独计算,不参与付款状态判断。
@if (in_array('付款待核实', $paymentStates, true))
“付款待核实”表示尚未取得可靠的出纳节点任务,不能据此推断已付款或待付款。
@endif
@ -767,7 +773,7 @@
<th class="num">金额(元)</th>
<th>银行科目编码</th>
<th>银行科目名称</th>
<th class="num">付款金额</th>
<th class="num">付款明细金额</th>
<th class="num">付款差额</th>
<th>付款方式</th>
<th>打款时间</th>

View File

@ -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('<span>付款合计</span>', false)
->assertDontSee('银行付款合计')
->assertSee('流程数据已超过 15 分钟未成功同步')
->assertSee('202601011200000000001')

View File

@ -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(

View File

@ -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 = [[

View File

@ -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
{