feat: show ding payable approval details

This commit is contained in:
zhoushengchen 2026-08-29 10:04:12 +08:00 committed by WuYongJie
parent f8993f814a
commit a054241331
4 changed files with 103 additions and 8 deletions

View File

@ -59,7 +59,8 @@ class DingPayableWorkflowMirrorRepository
continue;
}
if (($filters['process_instance_id'] ?? '') !== ''
&& ! str_contains((string) ($workflow['processInstanceId'] ?? ''), (string) $filters['process_instance_id'])) {
&& ! str_contains((string) ($workflow['processInstanceId'] ?? ''), (string) $filters['process_instance_id'])
&& ! str_contains((string) ($workflow['businessId'] ?? ''), (string) $filters['process_instance_id'])) {
continue;
}

View File

@ -57,7 +57,9 @@ class DingPayableWorkflowMirrorSynchronizer
$generation = $resume ? (string) $state['generation'] : (string) Str::uuid();
$cursor = $resume
? (string) ($state['cursor']['processInstanceId'] ?? '')
: (string) ($this->decodeJson($state['high_watermark'] ?? null)['processInstanceId'] ?? '');
: ($full
? ''
: (string) ($this->decodeJson($state['high_watermark'] ?? null)['processInstanceId'] ?? ''));
$rows = $resume ? (int) ($state['last_upserted_rows'] ?? 0) : 0;
$batchSize = min(max($batchSize ?? (int) ($config['batch_size'] ?? 100), 1), 500);
$maxBatches = max($maxBatches ?? (int) ($config['max_batches_per_run'] ?? 2), 0);
@ -78,8 +80,13 @@ class DingPayableWorkflowMirrorSynchronizer
static fn (array $row): string => (string) ($row['processInstanceId'] ?? ''),
$workflows,
)));
$originatorIds = array_values(array_unique(array_filter(array_map(
static fn (array $row): string => trim((string) ($row['originatorUserId'] ?? '')),
$workflows,
))));
$schemes = $this->readRelated($source, 'ding_WorkflowScheme', ['id', 'processInstanceId', 'componentType', 'name', 'value', 'extValue'], $ids, 'id');
$tasks = $this->readRelated($source, 'ding_WorkflowTask', ['processInstanceId', 'result', 'activityId', 'finishTime', 'createTime', 'userId', 'taskId', 'status', 'id'], $ids, 'id');
$originatorNames = $this->readOriginatorNames($source, $originatorIds);
$schemeById = $this->groupBy($schemes, 'processInstanceId');
$taskById = $this->groupBy($tasks, 'processInstanceId');
@ -91,6 +98,13 @@ class DingPayableWorkflowMirrorSynchronizer
if ($id === '') {
continue;
}
$originatorId = trim((string) ($workflow['originatorUserId'] ?? ''));
$originatorName = trim((string) ($originatorNames[$originatorId] ?? ''));
if ($originatorName !== '') {
// 保留钉钉原始用户 ID,同时把稳定的中文姓名写入镜像,
// 页面无需在请求期间再次直连上游或猜测标题内容。
$workflow['originatorUserName'] = $originatorName;
}
$payloads[] = [
'process_instance_id' => $id,
'workflow_payload' => json_encode($workflow, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
@ -262,6 +276,57 @@ class DingPayableWorkflowMirrorSynchronizer
);
}
/** @param list<string> $userIds @return array<string,string> */
private function readOriginatorNames(string $source, array $userIds): array
{
if ($userIds === []) {
return [];
}
$names = [];
try {
$names = $this->readUserNameRows($source, 'hr_employee', ['userid', 'name'], $userIds);
} catch (Throwable) {
// 姓名目录短暂不可用时不应阻断应付流程同步;页面会回退显示原始用户 ID。
}
$missing = array_values(array_diff($userIds, array_keys($names)));
if ($missing !== []) {
try {
// 已离职申请人不一定还存在于 hr_employee,使用同一只读来源的
// 离职视图补齐姓名;两个查询均为固定字段和参数化 IN 条件。
$retired = $this->readUserNameRows($source, 'hr_employee_dimission', ['userid', 'realName', 'name'], $missing);
$names += $retired;
} catch (Throwable) {
// 离职目录不可用时保留已解析的在职姓名。
}
}
return $names;
}
/** @param list<string> $columns @param list<string> $userIds @return array<string,string> */
private function readUserNameRows(string $source, string $table, array $columns, array $userIds): array
{
$placeholders = implode(',', array_fill(0, count($userIds), '?'));
$rows = $this->queries->forSource($source)->select(
'SELECT ' . implode(', ', array_map(static fn (string $column): string => '[' . $column . ']', $columns))
. ' FROM [rpt].[' . $table . '] WHERE [userid] IN (' . $placeholders . ')',
$userIds,
);
$names = [];
foreach ($rows as $row) {
$id = trim((string) ($row['userid'] ?? $row['userId'] ?? ''));
$name = trim((string) ($row['name'] ?? $row['realName'] ?? ''));
if ($id !== '' && $name !== '') {
$names[$id] = $name;
}
}
return $names;
}
/** @param list<array<string,mixed>> $rows @return array<string,list<array<string,mixed>>> */
private function groupBy(array $rows, string $column): array
{

View File

@ -11,6 +11,27 @@
$v = $detail[$key] ?? null;
return $v === null || $v === '' ? '-' : (string) $v;
};
$statusLabel = static function (mixed $raw): string {
$status = trim((string) ($raw ?? ''));
if ($status === '') {
return '-';
}
return [
'NEW' => '待提交',
'DRAFT' => '草稿',
'RUNNING' => '进行中',
'PENDING' => '待处理',
'COMPLETED' => '已完成',
'TERMINATED' => '已终止',
'CANCELED' => '已取消',
'CANCELLED' => '已取消',
'REJECTED' => '已驳回',
'FAILED' => '失败',
'EXPIRED' => '已过期',
'DELETED' => '已删除',
][strtoupper($status)] ?? $status;
};
@endphp
<div class="ding-payable-page">
@ -42,8 +63,8 @@
<form method="GET" class="ding-payable-toolbar" aria-label="应付云仓车费筛选">
<div class="ding-payable-field">
<label for="ding-payable-process-id">流程实例 ID(可选)</label>
<input id="ding-payable-process-id" name="process_instance_id" value="{{ $filters['process_instance_id'] ?? '' }}" placeholder="输入流程实例 ID">
<label for="ding-payable-process-id">审批编号或流程实例 ID(可选)</label>
<input id="ding-payable-process-id" name="process_instance_id" value="{{ $filters['process_instance_id'] ?? '' }}" placeholder="输入钉钉审批编号或流程实例 ID">
</div>
<div class="ding-payable-actions">
<button class="ding-payable-button primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">search</span>查询</button>
@ -66,7 +87,7 @@
<div class="ding-payable-table-wrap">
<table class="ding-payable-table">
<thead><tr>
<th>流程实例 ID</th><th>标题</th><th>状态</th><th>申请人</th><th>申请部门</th><th>创建时间</th><th>完成时间</th>
<th>钉钉审批编号</th><th>标题</th><th>状态</th><th>申请人</th><th>申请部门</th><th>创建时间</th><th>完成时间</th>
<th>类型</th><th>用途</th><th class="num">付款金额</th><th>货款月份</th><th>快递</th><th>支付方式</th><th>收款人全称</th><th>开户银行</th><th>银行账号</th>
<th>全部表单字段</th><th>付款明细</th><th>附件</th><th>审批任务</th>
</tr></thead>
@ -75,11 +96,13 @@
@php($workflow = $row['workflow'] ?? [])
@php($detail = $row['detail'] ?? [])
@php($fields = $row['fields'] ?? [])
@php($approvalNumber = trim((string) ($workflow['businessId'] ?? '')) ?: (string) ($row['process_instance_id'] ?? '-'))
@php($originator = trim((string) ($workflow['originatorUserName'] ?? '')) ?: trim((string) ($workflow['originatorUserId'] ?? '-')))
<tr>
<td>{{ $row['process_instance_id'] ?? '-' }}</td>
<td>{{ $approvalNumber }}</td>
<td>{{ $workflow['title'] ?? '-' }}</td>
<td>{{ $workflow['status'] ?? '-' }}</td>
<td>{{ $workflow['originatorUserId'] ?? '-' }}</td>
<td>{{ $statusLabel($workflow['status'] ?? null) }}</td>
<td>{{ $originator }}</td>
<td>{{ $workflow['originatorDeptName'] ?? '-' }}</td>
<td>{{ $workflow['createAt'] ?? '-' }}</td>
<td>{{ $workflow['finishTime'] ?? '-' }}</td>

View File

@ -17,9 +17,11 @@ class DingPayableWorkflowPageTest extends FeatureTestCase
'data' => [[
'process_instance_id' => 'PROC-TEST-001',
'workflow' => [
'businessId' => '202607031324000376786',
'title' => '应付申请流程',
'status' => 'COMPLETED',
'originatorUserId' => 'user-1',
'originatorUserName' => '扬灵',
'originatorDeptName' => '财务部',
'createAt' => '2026-08-28 10:00:00',
'finishTime' => '2026-08-28 11:00:00',
@ -39,6 +41,10 @@ class DingPayableWorkflowPageTest extends FeatureTestCase
'page' => 'payable-cloud-warehouse-vehicle-fee',
]))->assertOk()
->assertSee('应付云仓车费')
->assertSee('钉钉审批编号')
->assertSee('202607031324000376786')
->assertSee('扬灵')
->assertSee('已完成')
->assertSee('拉到江西仓的车费')
->assertSee('用途同时包含“仓”“车费”')
->assertSee('全部表单字段')