CaiWuXiTong/app/Business/JstOrderLabelImporter.php
qiuxiang e5d6c76f27 账单流水数据新增聚水潭订单标签明细页
新增「账单流水数据 › 聚水潭订单标签明细」(紧邻种菜明细表):四列(线上订单号/店铺名称/下单时间/标签),按标签拆行(一个标签一行)、订单不去重、标签不汇总,无标签订单记为「(无标签)」。新增 jst_order_labels 表(含公司/时间/标签/店铺索引)、JstOrderLabel 模型、JstOrderLabelImporter(从镜像 ops_jst_orders 按区间拆行导入,--replace 只清同区间)与 jst-order-labels:import 命令、导出路由与用例。页面筛选:下单时间区间(点整块开日历、可单独清除)、店铺、标签、线上订单号。
2026-09-30 16:40:16 +08:00

155 lines
5.4 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\Business;
use Illuminate\Database\DatabaseManager;
use InvalidArgumentException;
use RuntimeException;
/**
* 聚水潭订单标签明细导入(从共享镜像 ops_jst_orders 拆标签写入 jst_order_labels)。
*
* 一个标签一行:一单有 N 个标签就写 N 行;订单不去重、标签不汇总。
* 无标签的订单保留一行,标签记为「(无标签)」,方便与订单总数核对。
*/
class JstOrderLabelImporter
{
public const NO_LABEL = '(无标签)';
private const READ_CHUNK = 2000;
private const INSERT_CHUNK = 2000;
public function __construct(private readonly DatabaseManager $databases) {}
/**
* @return array{from:string,to:string,orders:int,rows:int,no_label_orders:int,labels:int,seconds:float}
*/
public function import(string $from, string $to, int $companyId = 1, bool $replace = false, string $source = 'jst-order-label-import'): array
{
$from = $this->normalizeDate($from, '起始日期');
$to = $this->normalizeDate($to, '结束日期');
if ($from > $to) {
throw new InvalidArgumentException('起始日期不能晚于结束日期。');
}
$start = $from . ' 00:00:00';
$end = date('Y-m-d', strtotime($to . ' +1 day')) . ' 00:00:00';
$mirror = $this->databases->connection(config('upstream_mirrors.connection', 'business_mirror'));
if (! $mirror->getSchemaBuilder()->hasTable('ops_jst_orders')) {
throw new RuntimeException('聚水潭订单镜像尚未建立(ops_jst_orders)。');
}
$target = $this->databases->connection();
$startedAt = microtime(true);
if ($replace) {
$target->table('jst_order_labels')
->where('company_id', $companyId)
->where('ordered_at', '>=', $start)
->where('ordered_at', '<', $end)
->delete();
}
$now = now();
$orders = 0;
$rows = 0;
$noLabelOrders = 0;
$buffer = [];
$flush = function () use (&$buffer, &$rows, $target): void {
foreach (array_chunk($buffer, self::INSERT_CHUNK) as $chunk) {
$target->table('jst_order_labels')->insert($chunk);
$rows += count($chunk);
}
$buffer = [];
};
$mirror->table('ops_jst_orders')
->where('order_date', '>=', $start)
->where('order_date', '<', $end)
->orderBy('Id')
->chunkById(self::READ_CHUNK, function ($records) use (&$buffer, &$orders, &$noLabelOrders, $flush, $companyId, $source, $now): void {
foreach ($records as $record) {
$orders++;
$orderedAt = $record->order_date === null ? null : substr((string) $record->order_date, 0, 19);
if ($orderedAt === null || $orderedAt === '') {
continue;
}
$labels = $this->splitLabels((string) ($record->labels ?? ''));
if ($labels === []) {
$labels = [self::NO_LABEL];
$noLabelOrders++;
}
foreach ($labels as $label) {
$buffer[] = [
'company_id' => $companyId,
'order_no' => mb_substr((string) $record->so_id, 0, 100),
'shop_name' => mb_substr((string) ($record->shop_name ?? ''), 0, 191),
'ordered_at' => $orderedAt,
'label' => $label,
'created_from' => $source,
'created_at' => $now,
'updated_at' => $now,
];
}
if (count($buffer) >= self::INSERT_CHUNK * 2) {
$flush();
}
}
}, 'Id');
$flush();
$labels = $target->table('jst_order_labels')
->where('company_id', $companyId)
->where('ordered_at', '>=', $start)
->where('ordered_at', '<', $end)
->distinct()
->count('label');
return [
'from' => $from,
'to' => $to,
'orders' => $orders,
'rows' => $rows,
'no_label_orders' => $noLabelOrders,
'labels' => (int) $labels,
'seconds' => round(microtime(true) - $startedAt, 1),
];
}
/** @return list<string> */
private function splitLabels(string $labels): array
{
$out = [];
foreach (explode(',', $labels) as $label) {
$label = trim($label);
if ($label !== '') {
$out[] = mb_substr($label, 0, 191);
}
}
return array_values(array_unique($out));
}
private function normalizeDate(string $value, string $label): string
{
$value = trim($value);
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) {
throw new InvalidArgumentException($label . '必须是 YYYY-MM-DD。');
}
[$year, $month, $day] = array_map('intval', explode('-', $value));
if (! checkdate($month, $day, $year)) {
throw new InvalidArgumentException($label . '不是有效日期。');
}
return $value;
}
}