feat: 应付云仓车费流程时间与状态筛选 (#82)

This commit is contained in:
zhoushengchen 2026-08-29 11:38:14 +08:00
parent 17f5b7719e
commit 255d47f69e
4 changed files with 122 additions and 2 deletions

View File

@ -329,8 +329,22 @@ class Pages extends Controller
protected function contentForDingPayableWorkflows(): array
{
$normalizeDate = static function (mixed $value): string {
$date = trim((string) $value);
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $matches) !== 1
|| ! checkdate((int) $matches[2], (int) $matches[3], (int) $matches[1])) {
return '';
}
return $date;
};
$status = strtoupper(mb_substr(trim((string) request('status', '')), 0, 30));
$filters = [
'process_instance_id' => mb_substr(trim((string) request('process_instance_id', '')), 0, 100),
'created_from' => $normalizeDate(request('created_from', '')),
'finished_to' => $normalizeDate(request('finished_to', '')),
'status' => preg_match('/^[A-Z_]+$/', $status) === 1 ? $status : '',
];
$page = max((int) request('page', 1), 1);
$limit = min(max((int) setting('default.list_limit', 25), 1), 100);

View File

@ -63,6 +63,9 @@ class DingPayableWorkflowMirrorRepository
&& ! str_contains((string) ($workflow['businessId'] ?? ''), (string) $filters['process_instance_id'])) {
continue;
}
if (! $this->matchesWorkflowFilters($workflow, $filters)) {
continue;
}
$scheme = $this->decode((string) $record->scheme_payload, []);
$forms = $this->formFields($scheme);
@ -94,6 +97,46 @@ class DingPayableWorkflowMirrorRepository
];
}
/** @param array<string,mixed> $workflow @param array<string,mixed> $filters */
private function matchesWorkflowFilters(array $workflow, array $filters): bool
{
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
if ($createdFrom !== '') {
$createdAt = $this->workflowDate($workflow['createAt'] ?? null);
if ($createdAt === null || $createdAt < $createdFrom) {
return false;
}
}
$finishedTo = trim((string) ($filters['finished_to'] ?? ''));
if ($finishedTo !== '') {
$finishTime = $this->workflowDate($workflow['finishTime'] ?? null);
if ($finishTime === null || $finishTime > $finishedTo) {
return false;
}
}
$requestedStatus = strtoupper(trim((string) ($filters['status'] ?? '')));
if ($requestedStatus !== '') {
$actualStatus = strtoupper(trim((string) ($workflow['status'] ?? '')));
$statusMatches = $requestedStatus === 'CANCELED'
? in_array($actualStatus, ['CANCELED', 'CANCELLED'], true)
: $actualStatus === $requestedStatus;
if (! $statusMatches) {
return false;
}
}
return true;
}
private function workflowDate(mixed $value): ?string
{
$date = substr(trim((string) ($value ?? '')), 0, 10);
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) === 1 ? $date : null;
}
/** @return array<string,mixed> */
private function formFields(array $rows): array
{

View File

@ -32,6 +32,15 @@
'DELETED' => '已删除',
][strtoupper($status)] ?? $status;
};
$statusOptions = [
'' => '全部状态',
'RUNNING' => '进行中',
'COMPLETED' => '已完成',
'TERMINATED' => '已终止',
'CANCELED' => '已取消',
'REJECTED' => '已驳回',
'FAILED' => '失败',
];
@endphp
<div class="ding-payable-page">
@ -40,8 +49,11 @@
.ding-payable-toolbar { display:flex; flex-wrap:wrap; align-items:flex-end; gap:.75rem; padding:1rem; margin-bottom:1rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:var(--vip-surface,#fff); }
.ding-payable-field { display:flex; flex-direction:column; gap:.35rem; min-width:16rem; }
.ding-payable-field label { color:var(--vip-content-muted,#595959); font-size:.75rem; font-weight:600; }
.ding-payable-field input { height:2.25rem; padding:0 .65rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:#fff; color:var(--vip-content-ink,#171717); }
.ding-payable-field input:focus { border-color:var(--vip-content-accent-dark,#856632); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.2); }
.ding-payable-field input,.ding-payable-field select { height:2.25rem; padding:0 .65rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:#fff; color:var(--vip-content-ink,#171717); }
.ding-payable-field input:focus,.ding-payable-field select:focus { border-color:var(--vip-content-accent-dark,#856632); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.2); }
.ding-payable-date-range { display:flex; align-items:center; gap:.5rem; }
.ding-payable-date-range input { min-width:9rem; }
.ding-payable-date-range span { color:var(--vip-content-muted,#595959); }
.ding-payable-actions { display:flex; gap:.5rem; }
.ding-payable-button { display:inline-flex; align-items:center; gap:.35rem; height:2.25rem; padding:0 .85rem; border:1px solid var(--vip-border,#D8D2C7); border-radius:.5rem; background:#fff; color:var(--vip-content-ink,#171717); text-decoration:none; font-weight:600; }
.ding-payable-button.primary { border-color:var(--vip-content-accent-dark,#856632); background:var(--vip-content-accent-dark,#856632); color:#fff; }
@ -66,6 +78,22 @@
<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-field ding-payable-date-field">
<label for="ding-payable-created-from">流程时间(创建时间至完成时间)</label>
<div class="ding-payable-date-range">
<input id="ding-payable-created-from" type="date" name="created_from" value="{{ $filters['created_from'] ?? '' }}" aria-label="创建时间起">
<span aria-hidden="true">至</span>
<input id="ding-payable-finished-to" type="date" name="finished_to" value="{{ $filters['finished_to'] ?? '' }}" aria-label="完成时间止">
</div>
</div>
<div class="ding-payable-field ding-payable-status-field">
<label for="ding-payable-status">状态</label>
<select id="ding-payable-status" name="status">
@foreach ($statusOptions as $optionValue => $optionLabel)
<option value="{{ $optionValue }}" @selected(($filters['status'] ?? '') === $optionValue)>{{ $optionLabel }}</option>
@endforeach
</select>
</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>
<a class="ding-payable-button" href="{{ route('business.pages.show', ['section' => $section['slug'], 'page' => $page['slug']]) }}"><span class="material-icons-outlined" aria-hidden="true">refresh</span>重置</a>

View File

@ -66,4 +66,39 @@ class DingPayableWorkflowPageTest extends FeatureTestCase
->assertSee('钉钉应付流程镜像尚未完成同步,请稍后重试。')
->assertSee('镜像读取失败,请稍后重试。');
}
public function test_cloud_warehouse_vehicle_fee_page_passes_workflow_time_and_status_filters(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$repository = Mockery::mock(DingPayableWorkflowMirrorRepository::class);
$repository->shouldReceive('paginate')->once()->with(
[
'process_instance_id' => '',
'created_from' => '2026-07-01',
'finished_to' => '2026-07-31',
'status' => 'COMPLETED',
],
1,
Mockery::type('int'),
)->andReturn([
'data' => [],
'total' => 0,
'status' => 'succeeded',
'last_synced_at' => '2026-08-29 10:11:24',
]);
$this->app->instance(DingPayableWorkflowMirrorRepository::class, $repository);
$this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'payable-cloud-warehouse-vehicle-fee',
'created_from' => '2026-07-01',
'finished_to' => '2026-07-31',
'status' => 'completed',
]))->assertOk()
->assertSee('流程时间(创建时间至完成时间)')
->assertSee('2026-07-01')
->assertSee('2026-07-31')
->assertSee('状态')
->assertSee('全部状态');
}
}