'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 $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]; } }