- 超长费按报价规则从销售出库单拉取;退回费/拦截费从查件扣单拉取,并补出库时间、平台商品ID、线上订单号等字段 - 页面新增导入核验(单号+附加费类型+快递名称+可选金额),返回正常结/不结原因与差异金额,并提供导入模板下载
684 lines
24 KiB
PHP
684 lines
24 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use Illuminate\Console\Command;
|
||
use Illuminate\Support\Carbon;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
|
||
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
|
||
use Throwable;
|
||
|
||
/**
|
||
* 按「附加费报价维护」里启用的超长费规则,从销售出库单拉取结算行。
|
||
*
|
||
* 口径(第一期只做超长费,且只处理“按尺寸/最长边”的规则):
|
||
* - 规则状态必须是 active;
|
||
* - 生效条件解析出单边阈值(如“单边超160cm”);
|
||
* - 计价标准解析出每单金额(如“8元/单”);
|
||
* - 出库单按 logistics_company = 快递名称 匹配,运单号取 l_id,只取已分配运单号的单;
|
||
* - 长宽高按明细条形码 sku_id 到商品及库存管理(商品资料 jst_sku)换算,一票多件取最大边;
|
||
* - 查件类型/查件原因/退回单号/平台商品ID 在出库单与商品资料里取不到,留空。
|
||
*/
|
||
class PullCourierSurchargeOverlength extends Command
|
||
{
|
||
protected $signature = 'courier-surcharge:pull-overlength
|
||
{--days=90 : 默认拉取最近多少天(无 --from/--to 时生效)}
|
||
{--from= : 出库日期起(含),Y-m-d}
|
||
{--to= : 出库日期止(不含),Y-m-d}
|
||
{--rule=* : 只跑指定规则 ID,可多次传入}
|
||
{--chunk=300 : 每批处理的出库单数量}';
|
||
|
||
protected $description = '按启用的超长费报价规则,从销售出库单拉取附加费结算行';
|
||
|
||
public function handle(): int
|
||
{
|
||
$from = $this->option('from') !== null && $this->option('from') !== ''
|
||
? Carbon::parse((string) $this->option('from'))->startOfDay()
|
||
: now()->subDays(max((int) $this->option('days'), 1))->startOfDay();
|
||
$to = $this->option('to') !== null && $this->option('to') !== ''
|
||
? Carbon::parse((string) $this->option('to'))->startOfDay()
|
||
: now()->addDay()->startOfDay();
|
||
$onlyRules = array_filter(array_map('intval', (array) $this->option('rule')));
|
||
$chunkSize = min(max((int) $this->option('chunk'), 50), 500);
|
||
|
||
$connection = (string) config('upstream_mirrors.connection', 'business_mirror');
|
||
$schema = DB::connection($connection)->getSchemaBuilder();
|
||
|
||
if (! $schema->hasTable('courier_surcharge_settlement_rows')) {
|
||
$this->error('结算表 courier_surcharge_settlement_rows 不存在,请先执行迁移。');
|
||
|
||
return self::FAILURE;
|
||
}
|
||
|
||
$rules = $this->overlengthRules($onlyRules);
|
||
|
||
if ($rules === []) {
|
||
$this->warn('没有启用且可自动计算的超长费规则。');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
$upstream = app(UpstreamReadOnlyQueryFactory::class)->forSource('jiansun');
|
||
$totalWritten = 0;
|
||
|
||
foreach ($rules as $rule) {
|
||
$threshold = $rule['threshold'];
|
||
$amount = $rule['amount'];
|
||
|
||
$this->info(sprintf(
|
||
'规则 #%d %s:单边超 %scm 计 %s 元/单,取数 %s ~ %s',
|
||
$rule['id'],
|
||
$rule['courier'],
|
||
$threshold,
|
||
$amount,
|
||
$from->toDateString(),
|
||
$to->toDateString(),
|
||
));
|
||
|
||
$orders = $this->pullOrders($upstream, $rule['courier'], $from, $to, $chunkSize);
|
||
|
||
if ($orders === []) {
|
||
$this->line(' 没有运单号非空的出库单,跳过。');
|
||
continue;
|
||
}
|
||
|
||
$written = $this->writeSettlementRows($connection, $rule, $orders, $threshold, $amount, $from, $to);
|
||
$totalWritten += $written;
|
||
|
||
$this->line(' 出库单 ' . count($orders) . ' 单,命中超长并写入 ' . $written . ' 行。');
|
||
}
|
||
|
||
$this->info('完成,共写入 ' . $totalWritten . ' 行结算数据。');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
/**
|
||
* @param list<int> $onlyRules
|
||
* @return list<array{id:int,courier:string,threshold:float,amount:float}>
|
||
*/
|
||
private function overlengthRules(array $onlyRules): array
|
||
{
|
||
$rules = [];
|
||
|
||
foreach (DB::table('business_courier_surcharge_quotes')->where('status', 'active')->orderBy('id')->get() as $rule) {
|
||
$types = json_decode((string) $rule->types, true) ?: [];
|
||
|
||
if (! in_array('超长费', $types, true)) {
|
||
continue;
|
||
}
|
||
|
||
if (! str_contains((string) $rule->mode, '按尺寸') && ! str_contains((string) $rule->mode, '最长边')) {
|
||
continue;
|
||
}
|
||
|
||
if ($onlyRules !== [] && ! in_array((int) $rule->id, $onlyRules, true)) {
|
||
continue;
|
||
}
|
||
|
||
$condition = (string) $rule->condition;
|
||
$standard = (string) $rule->standard;
|
||
|
||
if (preg_match('/单边超\s*(\d+(?:\.\d+)?)\s*(?:cm|厘米|CM|㎝)/u', $condition, $matched) !== 1) {
|
||
$this->warn('规则 #' . $rule->id . ' 的生效条件解析不出单边阈值,已跳过:' . $condition);
|
||
continue;
|
||
}
|
||
|
||
$threshold = (float) $matched[1];
|
||
|
||
if (preg_match('/(\d+(?:\.\d+)?)\s*元\s*\/?\s*单/u', $standard, $priceMatched) !== 1) {
|
||
$this->warn('规则 #' . $rule->id . ' 的计价标准解析不出每单金额,已跳过:' . $standard);
|
||
continue;
|
||
}
|
||
|
||
$rules[] = [
|
||
'id' => (int) $rule->id,
|
||
'courier' => trim((string) $rule->courier),
|
||
'threshold' => $threshold,
|
||
'amount' => (float) $priceMatched[1],
|
||
];
|
||
}
|
||
|
||
return $rules;
|
||
}
|
||
|
||
/**
|
||
* 拉取出库单(只取已分配运单号的单)。
|
||
*
|
||
* @return list<array<string,mixed>>
|
||
*/
|
||
private function pullOrders(
|
||
UpstreamReadOnlyQuery $upstream,
|
||
string $courier,
|
||
Carbon $from,
|
||
Carbon $to,
|
||
int $chunkSize,
|
||
): array {
|
||
$orders = [];
|
||
$lastId = 0;
|
||
|
||
while (true) {
|
||
$rows = $upstream->select(
|
||
'SELECT TOP ' . $chunkSize . ' Id, io_id, o_id, l_id, so_id, shop_name, io_date, weight, f_weight '
|
||
. 'FROM dbo.jst_salesOutboundInfo '
|
||
. 'WHERE logistics_company = ? AND io_date >= ? AND io_date < ? '
|
||
. "AND LEN(ISNULL(l_id, '')) > 0 AND Id > ? "
|
||
. 'ORDER BY Id',
|
||
[$courier, $from->toDateTimeString(), $to->toDateTimeString(), $lastId],
|
||
);
|
||
|
||
if ($rows === []) {
|
||
break;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$orders[] = $row;
|
||
$lastId = max($lastId, (int) $row['Id']);
|
||
}
|
||
|
||
if (count($rows) < $chunkSize) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
return $orders;
|
||
}
|
||
|
||
/**
|
||
* @param list<array<string,mixed>> $orders
|
||
*/
|
||
private function writeSettlementRows(
|
||
string $connection,
|
||
array $rule,
|
||
array $orders,
|
||
float $threshold,
|
||
float $amount,
|
||
Carbon $from,
|
||
Carbon $to,
|
||
): int {
|
||
$ioIds = array_values(array_filter(array_map(
|
||
static fn (array $order): int => (int) ($order['io_id'] ?? 0),
|
||
$orders,
|
||
), static fn (int $value): bool => $value > 0));
|
||
|
||
$items = $this->pullItems($ioIds);
|
||
$dimensions = $this->dimensionMap($items);
|
||
$platformProductIds = $this->platformProductIds($items, $orders);
|
||
$onlineOrderNumbers = $this->onlineOrderNumbers($orders);
|
||
$now = now();
|
||
$written = 0;
|
||
$rows = [];
|
||
|
||
foreach ($orders as $order) {
|
||
$ioId = (int) ($order['io_id'] ?? 0);
|
||
|
||
if ($ioId <= 0) {
|
||
continue;
|
||
}
|
||
|
||
$best = null;
|
||
|
||
foreach ($items[$ioId] ?? [] as $item) {
|
||
$skuId = trim((string) ($item['sku_id'] ?? ''));
|
||
$dim = $dimensions[$skuId] ?? null;
|
||
|
||
if ($dim === null) {
|
||
continue;
|
||
}
|
||
|
||
$maxSide = max($dim['l'], $dim['w'], $dim['h']);
|
||
|
||
if ($best === null || $maxSide > $best['max_side']) {
|
||
$best = [
|
||
'max_side' => $maxSide,
|
||
'sku_id' => $skuId,
|
||
'i_id' => trim((string) ($item['i_id'] ?? '')),
|
||
'length' => $dim['l'],
|
||
'width' => $dim['w'],
|
||
'height' => $dim['h'],
|
||
'platform_product_id' => $platformProductIds[$ioId][$skuId] ?? ($platformProductIds[$ioId][''] ?? null),
|
||
];
|
||
}
|
||
}
|
||
|
||
if ($best === null || $best['max_side'] <= $threshold) {
|
||
continue;
|
||
}
|
||
|
||
$rows[] = [
|
||
'rule_id' => $rule['id'],
|
||
'surcharge_type' => '超长费',
|
||
'courier_name' => $rule['courier'],
|
||
'waybill' => trim((string) ($order['l_id'] ?? '')),
|
||
'outbound_io_id' => $ioId,
|
||
'platform_product_id' => $best['platform_product_id'],
|
||
'product_code' => $best['sku_id'] !== '' ? $best['sku_id'] : ($best['i_id'] !== '' ? $best['i_id'] : null),
|
||
'shop_name' => mb_substr(trim((string) ($order['shop_name'] ?? '')), 0, 191) ?: null,
|
||
'online_order_no' => $onlineOrderNumbers[(int) ($order['o_id'] ?? 0)] ?? null,
|
||
'length' => $best['length'],
|
||
'width' => $best['width'],
|
||
'height' => $best['height'],
|
||
'volume' => round($best['length'] * $best['width'] * $best['height'], 2),
|
||
'inspection_type' => null,
|
||
'inspection_reason' => null,
|
||
'return_waybill_no' => null,
|
||
'settlement_weight' => null,
|
||
'settlement_amount' => $amount,
|
||
'settlement_status' => '已计算',
|
||
'evidence' => mb_substr(sprintf(
|
||
'条形码 %s 尺寸 %s×%s×%s,最长边 %scm > %scm',
|
||
$best['sku_id'] !== '' ? $best['sku_id'] : '(无)',
|
||
$this->trimNumber($best['length']),
|
||
$this->trimNumber($best['width']),
|
||
$this->trimNumber($best['height']),
|
||
$this->trimNumber($best['max_side']),
|
||
$this->trimNumber($threshold),
|
||
), 0, 500),
|
||
'shipped_at' => $order['io_date'] ?? null,
|
||
'pulled_at' => $now,
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
];
|
||
|
||
$written++;
|
||
}
|
||
|
||
$target = DB::connection($connection)->table('courier_surcharge_settlement_rows');
|
||
|
||
// 同一规则、同一取数窗口内重跑时先清掉旧行,避免残留历史判断。
|
||
$target->where('rule_id', $rule['id'])
|
||
->where('shipped_at', '>=', $from->toDateTimeString())
|
||
->where('shipped_at', '<', $to->toDateTimeString())
|
||
->delete();
|
||
|
||
foreach (array_chunk($rows, 500) as $chunk) {
|
||
$target->insert($chunk);
|
||
}
|
||
|
||
return $written;
|
||
}
|
||
|
||
/**
|
||
* @param list<int> $ioIds
|
||
* @return array<int,list<array<string,mixed>>>
|
||
*/
|
||
private function pullItems(array $ioIds): array
|
||
{
|
||
if ($ioIds === []) {
|
||
return [];
|
||
}
|
||
|
||
$upstream = app(UpstreamReadOnlyQueryFactory::class)->forSource('jiansun');
|
||
$items = [];
|
||
|
||
foreach (array_chunk($ioIds, 500) as $chunk) {
|
||
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
||
$rows = $upstream->select(
|
||
'SELECT io_id, sku_id, i_id, name, qty FROM dbo.jst_salesOutboundItem WHERE io_id IN (' . $placeholders . ')',
|
||
$chunk,
|
||
);
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$items[(int) $row['io_id']][] = $row;
|
||
}
|
||
}
|
||
|
||
return $items;
|
||
}
|
||
|
||
/**
|
||
* 线上订单号:优先用出库单自带 so_id,为空时按 o_id 到「未发货订单」表兜底。
|
||
*
|
||
* 部分渠道(如阿里巴巴-常州玖阔)的出库单 so_id 为空,但发货中订单表
|
||
* jst_nosend_orders 里仍能按 o_id 找到线上单号。
|
||
*
|
||
* @param list<array<string,mixed>> $orders
|
||
* @return array<int,string>
|
||
*/
|
||
private function onlineOrderNumbers(array $orders): array
|
||
{
|
||
$map = [];
|
||
$missing = [];
|
||
|
||
foreach ($orders as $order) {
|
||
$oId = (int) ($order['o_id'] ?? 0);
|
||
$soId = trim((string) ($order['so_id'] ?? ''));
|
||
|
||
if ($soId !== '') {
|
||
$map[$oId] = mb_substr($soId, 0, 191);
|
||
} elseif ($oId > 0) {
|
||
$missing[$oId] = true;
|
||
}
|
||
}
|
||
|
||
if ($missing === []) {
|
||
return $map;
|
||
}
|
||
|
||
$upstream = app(UpstreamReadOnlyQueryFactory::class)->forSource('jiansun');
|
||
|
||
foreach (array_chunk(array_keys($missing), 500) as $chunk) {
|
||
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
||
|
||
try {
|
||
$rows = $upstream->select(
|
||
"SELECT o_id, so_id FROM dbo.jst_nosend_orders WHERE o_id IN (" . $placeholders . ')'
|
||
. " AND LEN(ISNULL(so_id, '')) > 0",
|
||
$chunk,
|
||
);
|
||
} catch (Throwable) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$map[(int) $row['o_id']] = mb_substr(trim((string) $row['so_id']), 0, 191);
|
||
}
|
||
}
|
||
|
||
// 仍未补到的(多为已发货、已离开未发货订单表的单),到订单视图兜底;
|
||
// 该视图覆盖部分不在 jst_Orders 主表里的店铺(如阿里巴巴-常州玖阔)。
|
||
$remaining = array_values(array_diff(array_keys($missing), array_keys($map)));
|
||
|
||
foreach (array_chunk($remaining, 500) as $chunk) {
|
||
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
||
|
||
try {
|
||
$rows = $upstream->select(
|
||
"SELECT o_id, so_id FROM dbo.jst_ordersView WHERE o_id IN (" . $placeholders . ')'
|
||
. " AND LEN(ISNULL(so_id, '')) > 0",
|
||
$chunk,
|
||
);
|
||
} catch (Throwable) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$map[(int) $row['o_id']] = mb_substr(trim((string) $row['so_id']), 0, 191);
|
||
}
|
||
}
|
||
|
||
// 再兜底:发货数据视图在订单离开 jst_Orders/ordersView/nosend 后仍保留 o_id → so_id,
|
||
// 例如 2026-07-31 新佰广1688 已归档的出库单。
|
||
$remaining = array_values(array_diff(array_keys($missing), array_keys($map)));
|
||
|
||
foreach (array_chunk($remaining, 200) as $chunk) {
|
||
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
||
|
||
try {
|
||
$rows = $upstream->select(
|
||
'SELECT DISTINCT o_id, so_id FROM dbo.Jst_SendData_View WHERE o_id IN (' . $placeholders . ')'
|
||
. ' AND LEN(so_id) > 0',
|
||
$chunk,
|
||
);
|
||
} catch (Throwable) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$map[(int) $row['o_id']] = mb_substr(trim((string) $row['so_id']), 0, 191);
|
||
}
|
||
}
|
||
|
||
// 最后兜底:全渠道订单列表按月归档,部分天猫订单只在该表保留 o_id → so_id。
|
||
$remaining = array_values(array_diff(array_keys($missing), array_keys($map)));
|
||
|
||
if ($remaining !== []) {
|
||
$monthById = [];
|
||
|
||
foreach ($orders as $order) {
|
||
$oId = (int) ($order['o_id'] ?? 0);
|
||
$ioDate = trim((string) ($order['io_date'] ?? ''));
|
||
|
||
if ($oId <= 0 || $ioDate === '') {
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
$monthById[$oId] = Carbon::parse($ioDate)->format('Ym');
|
||
} catch (Throwable) {
|
||
// io_date 无法解析时只查当前表。
|
||
}
|
||
}
|
||
|
||
$tables = ['jst_qmcust_orderlist' => array_values($remaining)];
|
||
|
||
foreach ($remaining as $oId) {
|
||
$month = $monthById[$oId] ?? null;
|
||
|
||
if ($month !== null) {
|
||
$tables['jst_qmcust_orderlist_' . $month][] = $oId;
|
||
}
|
||
}
|
||
|
||
foreach ($tables as $table => $ids) {
|
||
foreach (array_chunk($ids, 200) as $chunk) {
|
||
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
||
|
||
try {
|
||
$rows = $upstream->select(
|
||
'SELECT o_id, so_id FROM dbo.' . $table . ' WHERE o_id IN (' . $placeholders . ')'
|
||
. ' AND LEN(so_id) > 0',
|
||
$chunk,
|
||
);
|
||
} catch (Throwable) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$map[(int) $row['o_id']] = mb_substr(trim((string) $row['so_id']), 0, 191);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $map;
|
||
}
|
||
|
||
/**
|
||
* 平台商品ID:查件工单里的 platformItemId 与订单/发货明细的 shop_i_id 同源。
|
||
* 优先按 io_id + sku_id 从发货明细视图取;缺行时按 o_id 到全渠道订单明细(当前表 + 月度归档)兜底。
|
||
*
|
||
* @param array<int,list<array<string,mixed>>> $items
|
||
* @param list<array<string,mixed>> $orders
|
||
* @return array<int,array<string,string>>
|
||
*/
|
||
private function platformProductIds(array $items, array $orders): array
|
||
{
|
||
$map = [];
|
||
$ioIds = array_keys($items);
|
||
|
||
if ($ioIds === []) {
|
||
return $map;
|
||
}
|
||
|
||
$upstream = app(UpstreamReadOnlyQueryFactory::class)->forSource('jiansun');
|
||
|
||
foreach (array_chunk($ioIds, 200) as $chunk) {
|
||
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
||
|
||
try {
|
||
$rows = $upstream->select(
|
||
'SELECT io_id, sku_id, shop_i_id FROM dbo.Jst_SendDataDetail_View WHERE io_id IN (' . $placeholders . ')'
|
||
. ' AND LEN(shop_i_id) > 0',
|
||
$chunk,
|
||
);
|
||
} catch (Throwable) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$ioId = (int) ($row['io_id'] ?? 0);
|
||
$skuId = trim((string) ($row['sku_id'] ?? ''));
|
||
$shopIId = mb_substr(trim((string) ($row['shop_i_id'] ?? '')), 0, 191);
|
||
|
||
if ($ioId <= 0 || $shopIId === '') {
|
||
continue;
|
||
}
|
||
|
||
if ($skuId !== '') {
|
||
$map[$ioId][$skuId] = $shopIId;
|
||
}
|
||
|
||
$map[$ioId][''] ??= $shopIId;
|
||
}
|
||
}
|
||
|
||
// 剩余按 o_id 到全渠道订单明细(当前表 + 月度归档)兜底。
|
||
$oIdByIoId = [];
|
||
$monthByIoId = [];
|
||
|
||
foreach ($orders as $order) {
|
||
$ioId = (int) ($order['io_id'] ?? 0);
|
||
$oId = (int) ($order['o_id'] ?? 0);
|
||
$ioDate = trim((string) ($order['io_date'] ?? ''));
|
||
|
||
if ($ioId <= 0 || $oId <= 0) {
|
||
continue;
|
||
}
|
||
|
||
$oIdByIoId[$ioId] = $oId;
|
||
|
||
if ($ioDate === '') {
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
$monthByIoId[$ioId] = Carbon::parse($ioDate)->format('Ym');
|
||
} catch (Throwable) {
|
||
// io_date 无法解析时只查当前表。
|
||
}
|
||
}
|
||
|
||
$missing = [];
|
||
|
||
foreach ($ioIds as $ioId) {
|
||
if (($map[$ioId] ?? []) !== []) {
|
||
continue;
|
||
}
|
||
|
||
$oId = $oIdByIoId[$ioId] ?? 0;
|
||
|
||
if ($oId > 0) {
|
||
$missing[$ioId] = $oId;
|
||
}
|
||
}
|
||
|
||
if ($missing === []) {
|
||
return $map;
|
||
}
|
||
|
||
$tables = ['jst_qmcust_orderlist_item' => $missing];
|
||
|
||
foreach ($missing as $ioId => $oId) {
|
||
$month = $monthByIoId[$ioId] ?? null;
|
||
|
||
if ($month !== null) {
|
||
$tables['jst_qmcust_orderlist_item_' . $month][$ioId] = $oId;
|
||
}
|
||
}
|
||
|
||
foreach ($tables as $table => $idsByIo) {
|
||
$oIds = array_values(array_unique(array_values($idsByIo)));
|
||
|
||
foreach (array_chunk($oIds, 200) as $chunk) {
|
||
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
|
||
|
||
try {
|
||
$rows = $upstream->select(
|
||
'SELECT o_id, sku_id, shop_i_id FROM dbo.' . $table . ' WHERE o_id IN (' . $placeholders . ')'
|
||
. ' AND LEN(shop_i_id) > 0',
|
||
$chunk,
|
||
);
|
||
} catch (Throwable) {
|
||
continue;
|
||
}
|
||
|
||
$byOrder = [];
|
||
|
||
foreach ($rows as $row) {
|
||
$row = (array) $row;
|
||
$skuId = trim((string) ($row['sku_id'] ?? ''));
|
||
$shopIId = mb_substr(trim((string) ($row['shop_i_id'] ?? '')), 0, 191);
|
||
|
||
if ($shopIId === '') {
|
||
continue;
|
||
}
|
||
|
||
$byOrder[(int) ($row['o_id'] ?? 0)][] = ['sku_id' => $skuId, 'shop_i_id' => $shopIId];
|
||
}
|
||
|
||
foreach ($idsByIo as $ioId => $oId) {
|
||
foreach ($byOrder[$oId] ?? [] as $entry) {
|
||
if ($entry['sku_id'] !== '') {
|
||
$map[$ioId][$entry['sku_id']] = $entry['shop_i_id'];
|
||
}
|
||
|
||
$map[$ioId][''] ??= $entry['shop_i_id'];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $map;
|
||
}
|
||
|
||
/**
|
||
* @param array<int,list<array<string,mixed>>> $items
|
||
* @return array<string,array{l:float,w:float,h:float}>
|
||
*/
|
||
private function dimensionMap(array $items): array
|
||
{
|
||
$skuIds = [];
|
||
|
||
foreach ($items as $rows) {
|
||
foreach ($rows as $row) {
|
||
$skuId = trim((string) ($row['sku_id'] ?? ''));
|
||
|
||
if ($skuId !== '') {
|
||
$skuIds[$skuId] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
$skuIds = array_keys($skuIds);
|
||
$dimensions = [];
|
||
|
||
// 商品及库存管理(商品资料)已在共享镜像里,直接读镜像,不再压上游。
|
||
foreach (array_chunk($skuIds, 500) as $chunk) {
|
||
try {
|
||
$rows = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror'))
|
||
->table('ops_jst_sku')
|
||
->whereIn('sku_id', $chunk)
|
||
->get(['sku_id', 'l', 'w', 'h']);
|
||
} catch (Throwable) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
$l = (float) $row->l;
|
||
$w = (float) $row->w;
|
||
$h = (float) $row->h;
|
||
|
||
if ($l > 0 && $w > 0 && $h > 0) {
|
||
$dimensions[trim((string) $row->sku_id)] = ['l' => $l, 'w' => $w, 'h' => $h];
|
||
}
|
||
}
|
||
}
|
||
|
||
return $dimensions;
|
||
}
|
||
|
||
private function trimNumber(float $value): string
|
||
{
|
||
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
|
||
}
|
||
}
|