CaiWuXiTong/app/Console/Commands/SyncProductPurchasePriceMirror.php

111 lines
3.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
use Modules\EcommerceFinance\Repositories\ProductPurchasePriceMirrorRepository;
use Modules\EcommerceFinance\Services\ProductPurchasePriceMirrorSynchronizer;
use Throwable;
class SyncProductPurchasePriceMirror extends Command
{
protected $signature = 'business-mirror:sync-product-purchase-prices
{--full : 强制启动或继续首次全量校准}
{--batch= : 每批最多读取行数}
{--max-batches= : 本轮最多批次数,0 表示直到完成}';
protected $description = '同步采购单管理明细和普通商品单价,并保留可断点的共享业务镜像';
public function handle(
ProductPurchasePriceMirrorRepository $mirror,
ProductPurchasePriceMirrorSynchronizer $synchronizer,
): int {
if (! (bool) config('upstream_mirrors.refresh_enabled', false) && ! $this->option('full')) {
return self::SUCCESS;
}
try {
$state = $mirror->state();
$full = $this->shouldRunFull($state);
$result = $synchronizer->sync(
$full,
$this->option('batch') === null ? null : (int) $this->option('batch'),
$this->option('max-batches') === null ? null : (int) $this->option('max-batches'),
);
} catch (Throwable $exception) {
Log::error('采购单价共享镜像同步失败。', ['exception' => $exception::class]);
$this->error('采购单价同步失败,已保留上一版共享数据。');
return self::FAILURE;
}
if ($result['status'] === 'requires_full_sync') {
$this->warn('采购单价尚未完成首次全量同步,请完成备份后显式使用 --full。');
return self::SUCCESS;
}
$this->info(sprintf(
'采购单价%s:%s,本轮 %d 批,累计处理 %d 行。',
$result['mode'] === 'full' ? '全量校准' : '增量同步',
$result['status'] === 'succeeded' ? '完成' : '进行中',
$result['batches'],
$result['rows'],
));
return self::SUCCESS;
}
/** @param array<string,mixed>|null $state */
private function shouldRunFull(?array $state): bool
{
if ((bool) $this->option('full')) {
return true;
}
if (is_array($state)
&& ($state['run_mode'] ?? null) === 'full'
&& in_array($state['status'] ?? null, ['running', 'failed'], true)) {
return true;
}
if (! is_array($state) || ($state['initial_completed_at'] ?? null) === null) {
return false;
}
$now = now();
$weekday = min(max((int) config(
'upstream_mirrors.product_purchase_prices.reconcile_weekday',
0,
), 0), 6);
$startHour = min(max((int) config(
'upstream_mirrors.product_purchase_prices.reconcile_start_hour',
2,
), 0), 23);
$windowHours = min(max((int) config(
'upstream_mirrors.product_purchase_prices.reconcile_window_hours',
6,
), 1), 24 - $startHour);
if ($now->dayOfWeek !== $weekday
|| $now->hour < $startHour
|| $now->hour >= $startHour + $windowHours) {
return false;
}
$lastFull = $state['last_full_succeeded_at'] ?? null;
if ($lastFull === null || $lastFull === '') {
return true;
}
$hours = max((int) config(
'upstream_mirrors.product_purchase_prices.full_reconcile_hours',
168,
), 1);
return Carbon::parse((string) $lastFull)->addHours($hours)->lte($now);
}
}