parent
7484b25e46
commit
a67d458310
@ -63,6 +63,7 @@ class ProcessDetails extends Controller
|
||||
'statuses' => $options['statuses'],
|
||||
'paymentStates' => $options['payment_states'],
|
||||
'currentApprovers' => $options['current_approvers'] ?? [],
|
||||
'waitingBuckets' => DingProcessDetailMirrorRepository::WAITING_BUCKET_LABELS,
|
||||
'mirrorStatus' => $result['status'],
|
||||
'mirrorSyncedAt' => $result['last_synced_at'],
|
||||
'mirrorStale' => $result['sync_stale'] ?? false,
|
||||
@ -91,6 +92,7 @@ class ProcessDetails extends Controller
|
||||
'statuses' => [],
|
||||
'paymentStates' => [],
|
||||
'currentApprovers' => [],
|
||||
'waitingBuckets' => DingProcessDetailMirrorRepository::WAITING_BUCKET_LABELS,
|
||||
'mirrorStatus' => 'unavailable',
|
||||
'mirrorSyncedAt' => null,
|
||||
'mirrorStale' => false,
|
||||
|
||||
@ -22,6 +22,14 @@ class DingProcessDetailMirrorRepository
|
||||
'不涉及付款',
|
||||
];
|
||||
|
||||
public const WAITING_BUCKET_LABELS = [
|
||||
'lt_1h' => '不足1小时',
|
||||
'1_12h' => '1–12小时',
|
||||
'12_24h' => '12–24小时',
|
||||
'1_2d' => '1–2天',
|
||||
'gte_2d' => '2天以上',
|
||||
];
|
||||
|
||||
public const LEGACY_PAYMENT_STATE = '未确认';
|
||||
|
||||
public const LEGACY_PAYMENT_STATE_MAP = [
|
||||
@ -78,6 +86,13 @@ class DingProcessDetailMirrorRepository
|
||||
return self::LEGACY_STATUS_MAP[$status] ?? $status;
|
||||
}
|
||||
|
||||
public static function normalizeWaitingBucketFilter(mixed $value): string
|
||||
{
|
||||
$bucket = trim((string) $value);
|
||||
|
||||
return array_key_exists($bucket, self::WAITING_BUCKET_LABELS) ? $bucket : '';
|
||||
}
|
||||
|
||||
public function datasetKey(): string
|
||||
{
|
||||
return self::DATASET_PREFIX . $this->scope();
|
||||
@ -384,26 +399,31 @@ class DingProcessDetailMirrorRepository
|
||||
);
|
||||
}
|
||||
|
||||
$entryStart = $this->filterDate($filters['entry_start'] ?? '');
|
||||
$entryEnd = $this->filterDate($filters['entry_end'] ?? '');
|
||||
if ($entryStart !== '' && $entryEnd !== '' && $entryStart > $entryEnd) {
|
||||
[$entryStart, $entryEnd] = [$entryEnd, $entryStart];
|
||||
}
|
||||
if ($entryStart !== '') {
|
||||
$query->where(
|
||||
'ding_process_detail_mirror.current_task_started_at',
|
||||
'>=',
|
||||
$entryStart . ' 00:00:00',
|
||||
);
|
||||
}
|
||||
if ($entryEnd !== '') {
|
||||
$query->where(
|
||||
'ding_process_detail_mirror.current_task_started_at',
|
||||
'<',
|
||||
Carbon::createFromFormat('Y-m-d', $entryEnd)
|
||||
->addDay()
|
||||
->format('Y-m-d 00:00:00'),
|
||||
);
|
||||
$waitingBucket = self::normalizeWaitingBucketFilter($filters['waiting_bucket'] ?? '');
|
||||
if ($waitingBucket !== '') {
|
||||
$now = OaProcessTime::now();
|
||||
$column = 'ding_process_detail_mirror.current_task_started_at';
|
||||
$query
|
||||
->whereNotNull($column)
|
||||
->where($column, '<=', $now->format('Y-m-d H:i:s'));
|
||||
|
||||
if ($waitingBucket === 'lt_1h') {
|
||||
$query->where($column, '>', $now->subHour()->format('Y-m-d H:i:s'));
|
||||
} elseif ($waitingBucket === '1_12h') {
|
||||
$query
|
||||
->where($column, '>', $now->subHours(12)->format('Y-m-d H:i:s'))
|
||||
->where($column, '<=', $now->subHour()->format('Y-m-d H:i:s'));
|
||||
} elseif ($waitingBucket === '12_24h') {
|
||||
$query
|
||||
->where($column, '>', $now->subHours(24)->format('Y-m-d H:i:s'))
|
||||
->where($column, '<=', $now->subHours(12)->format('Y-m-d H:i:s'));
|
||||
} elseif ($waitingBucket === '1_2d') {
|
||||
$query
|
||||
->where($column, '>', $now->subHours(48)->format('Y-m-d H:i:s'))
|
||||
->where($column, '<=', $now->subHours(24)->format('Y-m-d H:i:s'));
|
||||
} else {
|
||||
$query->where($column, '<=', $now->subHours(48)->format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ([
|
||||
|
||||
@ -89,6 +89,7 @@ class ProcessDetailsPageData
|
||||
'statuses' => $options['statuses'],
|
||||
'paymentStates' => $options['payment_states'],
|
||||
'currentApprovers' => $options['current_approvers'] ?? [],
|
||||
'waitingBuckets' => DingProcessDetailMirrorRepository::WAITING_BUCKET_LABELS,
|
||||
'mirrorStatus' => $result['status'],
|
||||
'mirrorSyncedAt' => $result['last_synced_at'],
|
||||
'mirrorStale' => $result['sync_stale'] ?? false,
|
||||
@ -115,6 +116,7 @@ class ProcessDetailsPageData
|
||||
'statuses' => [],
|
||||
'paymentStates' => [],
|
||||
'currentApprovers' => [],
|
||||
'waitingBuckets' => DingProcessDetailMirrorRepository::WAITING_BUCKET_LABELS,
|
||||
'mirrorStatus' => 'unavailable',
|
||||
'mirrorSyncedAt' => null,
|
||||
'mirrorStale' => false,
|
||||
@ -136,13 +138,12 @@ class ProcessDetailsPageData
|
||||
$request->query('paid_end'),
|
||||
$request->query('paid_date'),
|
||||
);
|
||||
[$entryStart, $entryEnd] = self::normalizeDateRange(
|
||||
$request->query('entry_start'),
|
||||
$request->query('entry_end'),
|
||||
);
|
||||
$paymentState = DingProcessDetailMirrorRepository::normalizePaymentStateFilter(
|
||||
$request->query('payment_state'),
|
||||
);
|
||||
$waitingBucket = DingProcessDetailMirrorRepository::normalizeWaitingBucketFilter(
|
||||
$request->query('waiting_bucket'),
|
||||
);
|
||||
|
||||
return [
|
||||
'paid_start' => $paidStart,
|
||||
@ -153,8 +154,7 @@ class ProcessDetailsPageData
|
||||
'business_id' => mb_substr(trim((string) $request->query('business_id', '')), 0, 32),
|
||||
'originator_name' => mb_substr(trim((string) $request->query('originator_name', '')), 0, 100),
|
||||
'current_approver_name' => mb_substr(trim((string) $request->query('current_approver_name', '')), 0, 100),
|
||||
'entry_start' => $entryStart,
|
||||
'entry_end' => $entryEnd,
|
||||
'waiting_bucket' => $waitingBucket,
|
||||
'keyword' => mb_substr(trim((string) $request->query('keyword', '')), 0, 100),
|
||||
];
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
$documentTypes ??= [];
|
||||
$statuses ??= [];
|
||||
$currentApprovers ??= [];
|
||||
$waitingBuckets ??= [];
|
||||
$error ??= null;
|
||||
$paymentStates = $paymentStates ?? ($pageData['paymentStates'] ?? []);
|
||||
$mirrorStale = $mirrorStale ?? ($pageData['mirrorStale'] ?? false);
|
||||
@ -203,6 +204,10 @@
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.process-details-field.is-waiting {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.process-details-field.is-business {
|
||||
grid-column: span 3;
|
||||
}
|
||||
@ -799,6 +804,7 @@
|
||||
}
|
||||
|
||||
.process-details-field.is-date-range,
|
||||
.process-details-field.is-waiting,
|
||||
.process-details-field.is-keyword,
|
||||
.process-details-actions {
|
||||
grid-column: span 6;
|
||||
@ -848,6 +854,7 @@
|
||||
.process-details-field.is-business,
|
||||
.process-details-field.is-approver,
|
||||
.process-details-field.is-date-range,
|
||||
.process-details-field.is-waiting,
|
||||
.process-details-field.is-keyword,
|
||||
.process-details-actions {
|
||||
grid-column: 1;
|
||||
@ -1063,51 +1070,16 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="process-details-field is-date-range">
|
||||
<label for="process-details-entry-start">进入时间</label>
|
||||
<div class="upstream-data-date-range" role="group" aria-label="审批进入开始至结束时间">
|
||||
<span class="upstream-data-date" data-date-picker>
|
||||
<input
|
||||
id="process-details-entry-start"
|
||||
type="text"
|
||||
name="entry_start"
|
||||
value="{{ $filters['entry_start'] ?? '' }}"
|
||||
readonly
|
||||
autocomplete="off"
|
||||
placeholder="开始日期"
|
||||
aria-label="审批进入开始日期"
|
||||
data-date-input
|
||||
>
|
||||
<button type="button" class="upstream-data-date__clear" data-date-clear hidden aria-label="清除审批进入开始日期">
|
||||
<span class="material-icons-outlined" aria-hidden="true">close</span>
|
||||
</button>
|
||||
<button type="button" class="upstream-data-date__pick" data-date-toggle aria-label="选择审批进入开始日期">
|
||||
<span class="material-icons-outlined" aria-hidden="true">calendar_today</span>
|
||||
</button>
|
||||
<div class="upstream-data-calendar" role="dialog" aria-label="审批进入开始日期日历" data-date-calendar hidden></div>
|
||||
</span>
|
||||
<b class="upstream-data-date-range__sep">至</b>
|
||||
<span class="upstream-data-date" data-date-picker>
|
||||
<input
|
||||
id="process-details-entry-end"
|
||||
type="text"
|
||||
name="entry_end"
|
||||
value="{{ $filters['entry_end'] ?? '' }}"
|
||||
readonly
|
||||
autocomplete="off"
|
||||
placeholder="结束日期"
|
||||
aria-label="审批进入结束日期"
|
||||
data-date-input
|
||||
>
|
||||
<button type="button" class="upstream-data-date__clear" data-date-clear hidden aria-label="清除审批进入结束日期">
|
||||
<span class="material-icons-outlined" aria-hidden="true">close</span>
|
||||
</button>
|
||||
<button type="button" class="upstream-data-date__pick" data-date-toggle aria-label="选择审批进入结束日期">
|
||||
<span class="material-icons-outlined" aria-hidden="true">calendar_today</span>
|
||||
</button>
|
||||
<div class="upstream-data-calendar" role="dialog" aria-label="审批进入结束日期日历" data-date-calendar hidden></div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="process-details-field is-waiting">
|
||||
<label for="process-details-waiting-bucket">已等待</label>
|
||||
<select id="process-details-waiting-bucket" name="waiting_bucket">
|
||||
<option value="">全部时长</option>
|
||||
@foreach ($waitingBuckets as $bucket => $label)
|
||||
<option value="{{ $bucket }}" @selected(($filters['waiting_bucket'] ?? '') === $bucket)>
|
||||
{{ $label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="process-details-field">
|
||||
<label for="process-details-originator">发起人</label>
|
||||
|
||||
@ -89,8 +89,7 @@ class ProcessDetailsPageTest extends FeatureTestCase
|
||||
'per_page' => 100,
|
||||
'payment_state' => '待付款',
|
||||
'current_approver_name' => '王五',
|
||||
'entry_start' => '2026-10-20',
|
||||
'entry_end' => '2026-10-30',
|
||||
'waiting_bucket' => '1_2d',
|
||||
]))->assertOk()
|
||||
->assertSee('流程数据')
|
||||
->assertSee('OA流程')
|
||||
@ -106,15 +105,15 @@ class ProcessDetailsPageTest extends FeatureTestCase
|
||||
->assertSee('role="combobox"', false)
|
||||
->assertSee('placeholder="输入姓名或展开选择"', false)
|
||||
->assertSee('data-value="赵六"', false)
|
||||
->assertSee('name="entry_start"', false)
|
||||
->assertSee('name="entry_end"', false)
|
||||
->assertSee('name="waiting_bucket"', false)
|
||||
->assertSee('value="1_2d" selected', false)
|
||||
->assertSee('2天以上', false)
|
||||
->assertDontSee('name="entry_start"', false)
|
||||
->assertDontSee('name="entry_end"', false)
|
||||
->assertSee('placeholder="开始日期"', false)
|
||||
->assertSee('placeholder="结算日期"', false)
|
||||
->assertSee('placeholder="结束日期"', false)
|
||||
->assertSee('aria-label="选择打款开始日期"', false)
|
||||
->assertSee('aria-label="选择打款结算日期"', false)
|
||||
->assertSee('aria-label="选择审批进入开始日期"', false)
|
||||
->assertSee('aria-label="选择审批进入结束日期"', false)
|
||||
->assertSee('CaiWuDatePicker.setup', false)
|
||||
->assertSee('class="process-details-filter-grid"', false)
|
||||
->assertSee('class="process-details-note-disclosure"', false)
|
||||
|
||||
@ -197,20 +197,43 @@ class DingProcessDetailMirrorRepositoryTest extends TestCase
|
||||
2,
|
||||
$this->repository->paginate(['current_approver_name' => '王五'], 1, 50)['total'],
|
||||
);
|
||||
$this->assertSame(
|
||||
2,
|
||||
$this->repository->paginate([
|
||||
'entry_start' => '2026-02-01',
|
||||
'entry_end' => '2026-02-28',
|
||||
], 1, 50)['total'],
|
||||
);
|
||||
$this->assertSame(
|
||||
2,
|
||||
$this->repository->paginate([
|
||||
'entry_start' => '2026-02-28',
|
||||
'entry_end' => '2026-02-01',
|
||||
], 1, 50)['total'],
|
||||
);
|
||||
}
|
||||
|
||||
public function test_waiting_bucket_filter_uses_real_elapsed_hours(): void
|
||||
{
|
||||
try {
|
||||
Carbon::setTestNow(Carbon::parse('2026-02-03 10:00:00', 'Asia/Shanghai'));
|
||||
$this->assertSame(
|
||||
2,
|
||||
$this->repository->paginate(['waiting_bucket' => '1_12h'], 1, 50)['total'],
|
||||
);
|
||||
$this->assertSame(
|
||||
2,
|
||||
$this->repository->paginate(['waiting_bucket' => 'gte_2d'], 1, 50)['total'],
|
||||
);
|
||||
|
||||
Carbon::setTestNow(Carbon::parse('2026-01-02 21:00:00', 'Asia/Shanghai'));
|
||||
$this->assertSame(
|
||||
2,
|
||||
$this->repository->paginate(['waiting_bucket' => '12_24h'], 1, 50)['total'],
|
||||
);
|
||||
$this->assertSame(
|
||||
0,
|
||||
$this->repository->paginate(['waiting_bucket' => '1_12h'], 1, 50)['total'],
|
||||
);
|
||||
|
||||
Carbon::setTestNow(Carbon::parse('2026-01-02 09:30:00', 'Asia/Shanghai'));
|
||||
$this->assertSame(
|
||||
2,
|
||||
$this->repository->paginate(['waiting_bucket' => 'lt_1h'], 1, 50)['total'],
|
||||
);
|
||||
$this->assertSame(
|
||||
'',
|
||||
DingProcessDetailMirrorRepository::normalizeWaitingBucketFilter('invalid'),
|
||||
);
|
||||
} finally {
|
||||
Carbon::setTestNow();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_pending_cashier_status_without_payment_facts_is_waiting_for_payment(): void
|
||||
|
||||
@ -53,8 +53,7 @@ class ProcessDetailsPageDataTest extends TestCase
|
||||
&& $filters['paid_end'] === '2026-10-24'
|
||||
&& $filters['payment_state'] === '已付款'
|
||||
&& $filters['current_approver_name'] === '王五'
|
||||
&& $filters['entry_start'] === '2026-10-21'
|
||||
&& $filters['entry_end'] === '2026-10-25'
|
||||
&& $filters['waiting_bucket'] === '12_24h'
|
||||
), 2, 50)
|
||||
->willReturn([
|
||||
'data' => [$row],
|
||||
@ -92,8 +91,7 @@ class ProcessDetailsPageDataTest extends TestCase
|
||||
'paid_end' => '2026-10-24',
|
||||
'payment_state' => '出纳已打款',
|
||||
'current_approver_name' => '王五',
|
||||
'entry_start' => '2026-10-21',
|
||||
'entry_end' => '2026-10-25',
|
||||
'waiting_bucket' => '12_24h',
|
||||
'page' => 2,
|
||||
],
|
||||
));
|
||||
@ -109,8 +107,7 @@ class ProcessDetailsPageDataTest extends TestCase
|
||||
$this->assertSame('2026-10-24', $data['filters']['paid_end']);
|
||||
$this->assertSame('已付款', $data['filters']['payment_state']);
|
||||
$this->assertSame('王五', $data['filters']['current_approver_name']);
|
||||
$this->assertSame('2026-10-21', $data['filters']['entry_start']);
|
||||
$this->assertSame('2026-10-25', $data['filters']['entry_end']);
|
||||
$this->assertSame('12_24h', $data['filters']['waiting_bucket']);
|
||||
$this->assertSame(
|
||||
[
|
||||
'待付款',
|
||||
@ -120,6 +117,10 @@ class ProcessDetailsPageDataTest extends TestCase
|
||||
$data['paymentStates'],
|
||||
);
|
||||
$this->assertSame(['王五', '赵六'], $data['currentApprovers']);
|
||||
$this->assertSame(
|
||||
DingProcessDetailMirrorRepository::WAITING_BUCKET_LABELS,
|
||||
$data['waitingBuckets'],
|
||||
);
|
||||
$this->assertTrue($data['mirrorStale']);
|
||||
$this->assertSame(15, $data['mirrorStaleAfterMinutes']);
|
||||
$this->assertArrayNotHasKey('months', $data);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user