CaiWuXiTong/app/Console/Commands/SyncJstOrderLabelItemCodes.php

183 lines
7.5 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 App\Business\JstOrderLabelItemCodes;
use Illuminate\Console\Command;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Facades\DB;
use RuntimeException;
use Throwable;
/**
* 把聚水潭商品明细(共享镜像)里的商品编码回填到「聚水潭订单标签明细」的货号列。
*
* 两个来源:
* - 主镜像 ops_jst_orders + ops_jst_order_item;
* - 全渠道镜像 ops_jst_qmcust_orders + ops_jst_qmcust_order_items。
*
* 页面/命令导入(jst-order-labels:import)已自带货号;本命令用于历史数据回填与修复,可反复执行。
*/
class SyncJstOrderLabelItemCodes extends Command
{
protected $signature = 'jst-order-labels:sync-item-codes
{--from= : 起始下单日期 YYYY-MM-DD(默认取标签明细里最早的下单日期)}
{--to= : 结束下单日期 YYYY-MM-DD(默认取标签明细里最晚的下单日期)}
{--company=1 : 公司 ID}
{--chunk=800 : 每批处理的订单数}';
protected $description = '按订单号把聚水潭商品明细里的货号回填到聚水潭订单标签明细';
/** 订单镜像 → 商品明细镜像。 */
private const SOURCES = [
['orders' => 'ops_jst_orders', 'items' => 'ops_jst_order_item'],
['orders' => 'ops_jst_qmcust_orders', 'items' => 'ops_jst_qmcust_order_items'],
];
public function handle(DatabaseManager $databases, JstOrderLabelItemCodes $itemCodes): int
{
$companyId = (int) $this->option('company');
$chunk = max((int) $this->option('chunk'), 100);
$prefix = $databases->connection()->getTablePrefix();
try {
[$from, $to] = $this->resolveWindow($databases, $companyId);
} catch (Throwable $exception) {
$this->error($exception->getMessage());
return self::FAILURE;
}
$start = $from . ' 00:00:00';
$end = date('Y-m-d', strtotime($to . ' +1 day')) . ' 00:00:00';
$mirror = $databases->connection(config('upstream_mirrors.connection', 'business_mirror'));
$this->info(sprintf('同步货号:%s ~ %s(公司 %d)', $from, $to, $companyId));
$startedAt = microtime(true);
$totalUpdated = 0;
foreach (self::SOURCES as $source) {
if (! $mirror->getSchemaBuilder()->hasTable($source['orders']) || ! $mirror->getSchemaBuilder()->hasTable($source['items'])) {
$this->warn(sprintf('跳过 %s:镜像表不存在。', $source['orders']));
continue;
}
$scanned = 0;
$matched = 0;
$updated = 0;
$chunks = 0;
$mirrorName = $source['orders'];
try {
$mirror->table($source['orders'])
->select(['Id', 'o_id', 'so_id'])
->where('order_date', '>=', $start)
->where('order_date', '<', $end)
->orderBy('Id')
->chunkById($chunk, function ($records) use ($mirror, $itemCodes, $source, $prefix, $companyId, $start, $end, $mirrorName, &$scanned, &$matched, &$updated, &$chunks): void {
$chunks++;
$scanned += count($records);
$orderCodes = $itemCodes->forOrderIds($mirror, $source['items'], $records->pluck('o_id')->all());
$pairs = [];
foreach ($records as $record) {
$orderNo = trim((string) $record->so_id);
$codes = $orderCodes[(int) $record->o_id] ?? null;
if ($orderNo !== '' && $codes !== null && $codes !== '') {
$pairs[] = [$orderNo, $codes];
}
}
$matched += count($pairs);
if ($pairs !== []) {
$updated += $this->applyMap($prefix, $pairs, $companyId, $start, $end);
}
if ($chunks % 20 === 0) {
$this->line(sprintf(' …%s:已扫描 %d 单,匹配 %d,更新行 %d', $mirrorName, $scanned, $matched, $updated));
}
}, 'Id');
} catch (Throwable $exception) {
$this->error(sprintf('%s 同步失败:%s', $mirrorName, $exception->getMessage()));
return self::FAILURE;
}
$totalUpdated += $updated;
$this->info(sprintf('%s:扫描 %d 单,匹配 %d,更新标签行 %d', $mirrorName, $scanned, $matched, $updated));
}
$this->info(sprintf('完成:共更新标签行 %d,耗时 %.0f 秒。', $totalUpdated, microtime(true) - $startedAt));
return self::SUCCESS;
}
/**
* 一批订单的货号按订单号写回标签表(CASE 更新,命中 order_no 索引)。
*
* 订单号必须全程按字符串绑定:纯数字订单号一旦被 PHP 数组键或 PDO 当成整数,
* MariaDB 会把字符列 order_no 强转成数字比较,带「-」的订单号直接报 1292。
*
* @param list<array{0:string,1:string}> $pairs [[线上订单号, 货号], ...]
*/
private function applyMap(string $prefix, array $pairs, int $companyId, string $start, string $end): int
{
$cases = [];
$bindings = [];
$orderNos = [];
foreach ($pairs as [$orderNo, $codes]) {
$orderNo = (string) $orderNo;
$cases[] = 'WHEN ? THEN ?';
$bindings[] = $orderNo;
$bindings[] = $codes;
$orderNos[] = $orderNo;
}
$sql = 'UPDATE ' . $prefix . 'jst_order_labels SET item_code = CASE order_no ' . implode(' ', $cases) . ' END '
. 'WHERE company_id = ? AND order_no IN (' . implode(',', array_fill(0, count($orderNos), '?')) . ') '
. 'AND ordered_at >= ? AND ordered_at < ?';
$params = array_merge($bindings, [$companyId], $orderNos, [$start, $end]);
$updated = DB::connection()->update($sql, $params);
if ($updated > 0) {
app(\App\Business\PlantingOrderLabelLookup::class)->forget($companyId);
}
return $updated;
}
/** @return array{0:string,1:string} 起止日期(Y-m-d) */
private function resolveWindow(DatabaseManager $databases, int $companyId): array
{
$from = trim((string) $this->option('from'));
$to = trim((string) $this->option('to'));
if ($from === '' || $to === '') {
$row = $databases->table('jst_order_labels')
->where('company_id', $companyId)
->selectRaw('MIN(ordered_at) AS min_at, MAX(ordered_at) AS max_at')
->first();
if ($row === null || $row->min_at === null || $row->max_at === null) {
throw new RuntimeException('标签明细里没有数据,无法推断同步区间,请用 --from/--to 指定。');
}
$from = $from === '' ? substr((string) $row->min_at, 0, 10) : $from;
$to = $to === '' ? substr((string) $row->max_at, 0, 10) : $to;
}
foreach (['起始' => $from, '结束' => $to] as $label => $value) {
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) {
throw new RuntimeException($label . '日期必须是 YYYY-MM-DD:' . $value);
}
}
if ($from > $to) {
throw new RuntimeException('起始日期不能晚于结束日期。');
}
return [$from, $to];
}
}