166 lines
5.2 KiB
PHP
166 lines
5.2 KiB
PHP
<?php
|
||
|
||
namespace App\Business;
|
||
|
||
use Illuminate\Database\DatabaseManager;
|
||
use Illuminate\Support\Carbon;
|
||
use InvalidArgumentException;
|
||
use RuntimeException;
|
||
use Throwable;
|
||
|
||
/**
|
||
* 每日编排:先核对标签覆盖,再同步种菜报销工单,最后重算业绩明细种菜金额。
|
||
*/
|
||
class StoreReceiptPlantingDailySynchronizer
|
||
{
|
||
public const WORK_ORDER_DATASET = 'jiansun.dbo.jst_workOrderInfo';
|
||
|
||
public function __construct(
|
||
private readonly DatabaseManager $databases,
|
||
private readonly JstOrderLabelDailySynchronizer $labelCoverage,
|
||
private readonly PlantingWorkOrderSynchronizer $plantingWorkOrders,
|
||
) {}
|
||
|
||
/**
|
||
* @param list<string> $months
|
||
* @return array<string,mixed>
|
||
*/
|
||
public function sync(
|
||
string $from,
|
||
string $to,
|
||
int $companyId,
|
||
array $months = [],
|
||
bool $dryRun = false,
|
||
): array {
|
||
[$fromDate, $toDate] = $this->dateRange($from, $to);
|
||
if ($companyId <= 0) {
|
||
throw new InvalidArgumentException('公司 ID 无效。');
|
||
}
|
||
|
||
$coverage = $this->labelCoverage->assertCovered(
|
||
$fromDate->toDateString(),
|
||
$toDate->toDateString(),
|
||
$companyId,
|
||
);
|
||
$months = $this->months($months, $toDate);
|
||
$this->assertWorkOrderSourceReady();
|
||
|
||
if ($dryRun) {
|
||
return [
|
||
'status' => 'ready',
|
||
'from' => $fromDate->toDateString(),
|
||
'to' => $toDate->toDateString(),
|
||
'checked_days' => count($coverage),
|
||
'months' => $months,
|
||
'dry_run' => true,
|
||
];
|
||
}
|
||
|
||
$plantingSync = [];
|
||
foreach ($months as $month) {
|
||
$plantingSync[$month] = $this->plantingWorkOrders->sync(
|
||
$month . '-01',
|
||
$this->monthEnd($month),
|
||
$companyId,
|
||
);
|
||
}
|
||
|
||
return [
|
||
'status' => 'ready_to_refresh',
|
||
'from' => $fromDate->toDateString(),
|
||
'to' => $toDate->toDateString(),
|
||
'checked_days' => count($coverage),
|
||
'months' => $months,
|
||
'planting_sync' => $plantingSync,
|
||
'dry_run' => false,
|
||
];
|
||
}
|
||
|
||
/** @return list<string> */
|
||
private function months(array $requested, Carbon $through): array
|
||
{
|
||
if ($requested === []) {
|
||
return [
|
||
$through->copy()->subMonthNoOverflow()->format('Y-m'),
|
||
$through->format('Y-m'),
|
||
];
|
||
}
|
||
|
||
$months = [];
|
||
foreach ($requested as $month) {
|
||
$month = trim((string) $month);
|
||
$normalized = null;
|
||
if (preg_match('/^\d{4}-\d{2}$/', $month) === 1) {
|
||
try {
|
||
$normalized = Carbon::createFromFormat('!Y-m', $month, 'Asia/Shanghai')->format('Y-m');
|
||
} catch (Throwable) {
|
||
$normalized = null;
|
||
}
|
||
}
|
||
if ($normalized !== $month) {
|
||
throw new InvalidArgumentException('月份格式无效,应为 YYYY-MM:' . $month);
|
||
}
|
||
$months[$month] = true;
|
||
}
|
||
$months = array_keys($months);
|
||
sort($months);
|
||
|
||
return $months;
|
||
}
|
||
|
||
private function assertWorkOrderSourceReady(): void
|
||
{
|
||
$mirror = $this->databases->connection(
|
||
(string) config('upstream_mirrors.connection', 'business_mirror'),
|
||
);
|
||
$schema = $mirror->getSchemaBuilder();
|
||
if (! $schema->hasTable('business_mirror_table_states')) {
|
||
throw new RuntimeException('聚水潭协同工单镜像同步状态表尚未建立。');
|
||
}
|
||
|
||
$state = $mirror->table('business_mirror_table_states')
|
||
->where('dataset_key', self::WORK_ORDER_DATASET)
|
||
->first(['status', 'initial_completed_at']);
|
||
if ($state === null || $state->initial_completed_at === null) {
|
||
throw new RuntimeException('聚水潭协同工单镜像尚未完成首次同步。');
|
||
}
|
||
if ((string) $state->status !== 'succeeded') {
|
||
throw new RuntimeException('聚水潭协同工单镜像最近同步未成功。');
|
||
}
|
||
}
|
||
|
||
/** @return array{0:Carbon,1:Carbon} */
|
||
private function dateRange(string $from, string $to): array
|
||
{
|
||
$fromDate = $this->date($from);
|
||
$toDate = $this->date($to);
|
||
if ($fromDate === null || $toDate === null || $fromDate->gt($toDate)) {
|
||
throw new InvalidArgumentException('日期范围无效,应为 from <= to,格式 YYYY-MM-DD。');
|
||
}
|
||
|
||
return [$fromDate, $toDate];
|
||
}
|
||
|
||
private function date(string $value): ?Carbon
|
||
{
|
||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
$date = Carbon::createFromFormat('!Y-m-d', $value, 'Asia/Shanghai');
|
||
} catch (Throwable) {
|
||
return null;
|
||
}
|
||
|
||
return $date->format('Y-m-d') === $value ? $date : null;
|
||
}
|
||
|
||
private function monthEnd(string $month): string
|
||
{
|
||
return Carbon::createFromFormat('!Y-m', $month, 'Asia/Shanghai')
|
||
->endOfMonth()
|
||
->toDateString();
|
||
}
|
||
}
|