178 lines
6.1 KiB
PHP
178 lines
6.1 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Services;
|
||
|
||
use Carbon\CarbonInterface;
|
||
use Illuminate\Support\Carbon;
|
||
use Modules\EcommerceFinance\Repositories\NonJingxiRefundMirrorRepository;
|
||
use Throwable;
|
||
|
||
class RefundPendingSyncStatus
|
||
{
|
||
private const DATASETS = [
|
||
NonJingxiRefundMirrorRepository::INFO_DATASET => '售后退款主表',
|
||
NonJingxiRefundMirrorRepository::ITEM_DATASET => '售后商品明细',
|
||
NonJingxiRefundMirrorRepository::SUPPLEMENT_DATASET => '淘系退款清单',
|
||
NonJingxiRefundMirrorRepository::SUPPLEMENT_ITEM_DATASET => '淘系退款明细',
|
||
];
|
||
|
||
public function __construct(
|
||
private readonly NonJingxiRefundMirrorRepository $refunds,
|
||
) {}
|
||
|
||
/**
|
||
* @return array{
|
||
* state:string,
|
||
* title:string,
|
||
* message:string,
|
||
* last_succeeded_at:?string,
|
||
* last_attempted_at:?string,
|
||
* issues:list<array{dataset:string,label:string,message:string,error_code:?string}>
|
||
* }
|
||
*/
|
||
public function current(?CarbonInterface $at = null): array
|
||
{
|
||
$now = $at ? Carbon::instance($at) : now();
|
||
$scheduledAt = $now->copy()->startOfDay()->setTime(6, 0);
|
||
$deadline = $now->copy()->startOfDay()->setTime(10, 0);
|
||
|
||
try {
|
||
$states = $this->refunds->pendingConfirmationDailySyncStates();
|
||
} catch (Throwable) {
|
||
return [
|
||
'state' => 'unavailable',
|
||
'title' => '退款待确认同步状态不可用',
|
||
'message' => '暂时无法读取同步状态,请检查共享镜像连接。',
|
||
'last_succeeded_at' => null,
|
||
'last_attempted_at' => null,
|
||
'issues' => [[
|
||
'dataset' => '',
|
||
'label' => '退款待确认',
|
||
'message' => '同步状态读取失败',
|
||
'error_code' => 'status_unavailable',
|
||
]],
|
||
];
|
||
}
|
||
|
||
$issues = [];
|
||
$running = false;
|
||
$danger = false;
|
||
$successTimes = [];
|
||
$attemptTimes = [];
|
||
|
||
foreach (self::DATASETS as $key => $label) {
|
||
$state = $states[$key] ?? null;
|
||
if (! is_array($state)) {
|
||
$danger = true;
|
||
$issues[] = $this->issue($key, $label, '尚未建立共享镜像', null);
|
||
continue;
|
||
}
|
||
|
||
$status = (string) ($state['status'] ?? '');
|
||
$errorCode = trim((string) ($state['last_error_code'] ?? '')) ?: null;
|
||
$attemptedAt = $this->parseTime($state['last_attempted_at'] ?? null);
|
||
$succeededAt = $this->parseTime($state['last_succeeded_at'] ?? null);
|
||
|
||
if ($attemptedAt !== null) {
|
||
$attemptTimes[] = $attemptedAt;
|
||
}
|
||
if ($succeededAt !== null) {
|
||
$successTimes[] = $succeededAt;
|
||
}
|
||
|
||
if ($status === 'failed') {
|
||
$danger = true;
|
||
$issues[] = $this->issue($key, $label, '同步失败,已保留上次成功数据', $errorCode);
|
||
continue;
|
||
}
|
||
|
||
if (empty($state['initial_completed_at'])) {
|
||
$danger = true;
|
||
$issues[] = $this->issue($key, $label, '首次同步尚未完成', $errorCode);
|
||
continue;
|
||
}
|
||
|
||
if ($status === 'running') {
|
||
$running = true;
|
||
if ($now->greaterThanOrEqualTo($deadline)) {
|
||
$issues[] = $this->issue($key, $label, '今日 10:00 前同步尚未完成', $errorCode);
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if ($now->greaterThanOrEqualTo($deadline)
|
||
&& (
|
||
$attemptedAt === null
|
||
|| $attemptedAt->lessThan($scheduledAt)
|
||
|| $succeededAt === null
|
||
|| $succeededAt->greaterThan($deadline)
|
||
)) {
|
||
$issues[] = $this->issue($key, $label, '今日 10:00 前同步未完成', $errorCode);
|
||
}
|
||
}
|
||
|
||
if ($issues !== []) {
|
||
$state = $danger ? 'failed' : 'warning';
|
||
|
||
return [
|
||
'state' => $state,
|
||
'title' => $danger ? '退款待确认同步异常' : '退款待确认同步未完成',
|
||
'message' => $danger
|
||
? '部分退款数据同步失败,页面仍显示最近一次成功数据。'
|
||
: '每日同步未在 10:00 前完成,请检查同步任务。',
|
||
'last_succeeded_at' => $this->latest($successTimes),
|
||
'last_attempted_at' => $this->latest($attemptTimes),
|
||
'issues' => $issues,
|
||
];
|
||
}
|
||
|
||
return [
|
||
'state' => $running ? 'syncing' : 'healthy',
|
||
'title' => $running ? '退款待确认正在同步' : '退款待确认同步正常',
|
||
'message' => $running
|
||
? '每日同步正在执行,完成后会自动更新。'
|
||
: '每日 06:00 自动同步,10:00 前完成。',
|
||
'last_succeeded_at' => $this->latest($successTimes),
|
||
'last_attempted_at' => $this->latest($attemptTimes),
|
||
'issues' => [],
|
||
];
|
||
}
|
||
|
||
/** @return array{dataset:string,label:string,message:string,error_code:?string} */
|
||
private function issue(string $dataset, string $label, string $message, ?string $errorCode): array
|
||
{
|
||
return [
|
||
'dataset' => $dataset,
|
||
'label' => $label,
|
||
'message' => $message,
|
||
'error_code' => $errorCode,
|
||
];
|
||
}
|
||
|
||
private function parseTime(mixed $value): ?Carbon
|
||
{
|
||
$value = trim((string) $value);
|
||
if ($value === '') {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
return Carbon::parse($value);
|
||
} catch (Throwable) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/** @param list<Carbon> $times */
|
||
private function latest(array $times): ?string
|
||
{
|
||
if ($times === []) {
|
||
return null;
|
||
}
|
||
|
||
usort($times, static fn (Carbon $left, Carbon $right): int => $right <=> $left);
|
||
|
||
return $times[0]->toDateTimeString();
|
||
}
|
||
}
|