183 lines
7.5 KiB
PHP
183 lines
7.5 KiB
PHP
<?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];
|
||
}
|
||
}
|