CaiWuXiTong/modules/EcommerceFinance/Console/SyncJushuitanShipping.php
limeng 63799de416 fix(发货数据): 快递公司/仓库按实际出库单取,同步命令内存上限调至 4G
- 发货明细(jst_order_express_details)可能停在作废前的旧分配(如先分工厂代发仓、后改外采由总仓出库;或换单后仍是旧快递公司),
  工作平台按实际出库单显示。快递公司与出库单不一致或为空时改用出库单 logistics_company,
  仓库按出库单 wms_co_id 映射(从快递公司一致的明细行学习仓库名,扫描结束统一回填)。
- 9 月核对:快递名称不一致 434/331,612,仓库不一致 85,其中外采快递、线下快递、换单类为此原因。
- jushuitan:sync-shipping 内存上限 1536M -> 4096M:整月窗口重跑在扫描后段内存耗尽(退出码 255)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014nvGZkg27YMEdhry6eiuwf
2026-10-08 10:52:10 +08:00

95 lines
5.0 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 Modules\EcommerceFinance\Console;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Modules\EcommerceFinance\Services\Jushuitan\ShippingReplicaSynchronizer;
/**
* 发货数据同步:从经营分析副本库(operations_replica)直读聚水潭发货明细,清洗进本地展示表。
*
* php artisan jushuitan:sync-shipping --from=2026-09-01 --to=2026-09-17 # 按发货时间窗口拉全量
* php artisan jushuitan:sync-shipping --days=3 # 滚动最近 3 天(每日调度用)
* php artisan jushuitan:sync-shipping # 最近 2000 条(快速看效果)
*
* 按 id 去重 upsert,任何时候补拉早期区间都不会重复。
*/
class SyncJushuitanShipping extends Command
{
protected $signature = 'jushuitan:sync-shipping {--from=} {--to=} {--days=} {--limit=2000}
{--skip-freight : 仅补发货数量;保留已有运费,新记录运费留空并标记部分完成}';
protected $description = '从经营分析副本库直读聚水潭发货明细,清洗写入 caiwu 发货数据表';
public function handle(): int
{
@ini_set('memory_limit', '4096M'); // 整月窗口重跑约需 2GB+,原 1536M 会在扫描后段内存耗尽
@set_time_limit(0);
$from = $this->option('from') ? Carbon::parse($this->option('from')) : null;
$to = $this->option('to') ? Carbon::parse($this->option('to')) : null;
// --days=N:滚动最近 N 天(含今天)。显式 --from/--to 优先。
if (! $from && ! $to && $this->option('days')) {
$days = max((int) $this->option('days'), 1);
$from = now()->startOfDay()->subDays($days - 1);
$to = now()->startOfDay()->addDay();
}
$skipFreight = (bool) $this->option('skip-freight');
if ($skipFreight && (! $from || ! $to)) {
$this->error('--skip-freight 需要指定 --from 和 --to,或使用 --days。');
return self::FAILURE;
}
$limit = (int) ($this->option('limit') ?: 2000);
$range = ($from && $to) ? $from->toDateString() . ' ~ ' . $to->toDateString() : '最近 ' . $limit . ' 条';
$this->info('开始从副本库同步发货明细(' . $range . ')...');
$progress = function (string $msg): void {
$this->info(' ' . $msg);
if (function_exists('fflush')) {
@fflush(STDOUT);
}
};
try {
$result = ShippingReplicaSynchronizer::make()->sync($from, $to, $limit, $progress, $skipFreight);
} catch (\Throwable $e) {
$this->error('同步失败:' . $e->getMessage());
return self::FAILURE;
}
$expressRows = (int) ($result['processed'] ?? 0);
$autoRows = (int) ($result['autoship'] ?? 0);
$outboundRows = (int) ($result['outbound'] ?? 0);
$purchaseOutRows = (int) ($result['purchase_out'] ?? 0);
$summary = sprintf('发货明细 %d 条,代发仓 %d 条,销售出库补漏 %d 条,采购出库补漏 %d 条,合计处理 %d 条明细', $expressRows, $autoRows, $outboundRows, $purchaseOutRows, $expressRows + $autoRows + $outboundRows + $purchaseOutRows);
$packageSummary = isset($result['unique_packages'])
? sprintf('唯一包裹源键 %d 个,包裹记录处理 %d 次;', $result['unique_packages'], $result['packages'] ?? 0)
: '';
if ($result['freight_incomplete'] ?? false) {
$this->warn('本次窗口' . $packageSummary . $summary . ';新记录运费与结算重量待补齐,空白不代表 0。');
} else {
$this->info('完成:' . $packageSummary . $summary . '。');
}
if (($result['packages_without_items'] ?? 0) > 0 || ($result['dws_failed_days'] ?? []) !== []) {
$this->warn(sprintf(
'商品或金额待核对:%d 个包裹未匹配商品,DWS 预载失败 %d 天;页面包裹数不等于商品明细完整性。',
$result['packages_without_items'] ?? 0,
count($result['dws_failed_days'] ?? []),
));
}
if (($result['packages_retained_for_missing_items'] ?? 0) > 0) {
$this->warn('其中 ' . $result['packages_retained_for_missing_items'] . ' 个包裹因新商品明细缺失而保留原有商品及金额,本次未完成这些包裹的更新。');
}
if (($result['packages_retained_for_unmatched_amount'] ?? 0) > 0) {
$this->warn('其中 ' . $result['packages_retained_for_unmatched_amount'] . ' 个包裹因商品金额无法安全匹配而保留原记录,金额待核对。');
}
if (($result['stale_mirror_packages'] ?? 0) > 0) {
$this->error('本窗口仍有 ' . $result['stale_mirror_packages'] . ' 个镜像包裹未在上游扫描中出现;已保留原记录,需按源键核对后才能认定数量完整。');
return self::FAILURE;
}
return self::SUCCESS;
}
}