fix(发货数据): 每晚重拉最近 40 天,并清除上游已作废的出库/采购退货包裹

- 聚水潭常在发货后数天到数周内换单/改快递公司(如崔桥顺丰干配→常州德邦、德邦→申通E物流)、作废采购退货出库;
  原先每天只重拉最近 3 天,上月数据停在旧值:9 月 111 个包裹快递名称与工作平台不一致,14 个已作废的采购退货单仍在。
- 01:28 定时同步改为最近 40 天(覆盖上月结算期),07:28 仍补拉最近 3 天。
- 窗口重跑时,出库单兜底/采购退货出库包裹若本次完整扫描中已不存在(作废/换单),直接删除;扫描为空时不删。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014nvGZkg27YMEdhRy6EiUwf
This commit is contained in:
limeng 2026-10-19 09:31:17 +08:00
parent ed58e8ef43
commit bcbb9e72af
3 changed files with 24 additions and 4 deletions

View File

@ -88,12 +88,13 @@ class Kernel extends ConsoleKernel
->runInBackground();
}
// 聚水潭发货数据每日重拉最近三天,覆盖副本库迟到更新。
// 聚水潭发货数据:凌晨重拉最近 40 天。聚水潭常在发货后数天到数周内换单/改快递公司/作废采购退货出库,
// 只拉最近几天会让上月数据停在旧值(快递名称不一致、已作废的单仍在);40 天覆盖上月结算期。
// 默认关闭(JST_SYNC_ENABLED=false);填好凭证并完成迁移后再打开。
if ((bool) env('JST_SYNC_ENABLED', false)) {
$schedule->command('jushuitan:sync-shipping --days=3')
$schedule->command('jushuitan:sync-shipping --days=40')
->dailyAt('01:28')
->withoutOverlapping(120)
->withoutOverlapping(300)
->runInBackground();
// 凌晨副本库最近一两天的订单/商品明细常未到齐(视图、订单商品表滞后),
// 早上再拉一次最近 3 天,补齐凌晨只写入单号、缺商品明细的包裹。

View File

@ -73,6 +73,8 @@ class ShippingReplicaSynchronizer
private int $packagesRetainedForUnmatchedAmount = 0;
private int $staleMirrorPackages = 0;
private int $supersededRemoved = 0;
private int $outboundScanned = 0;
private int $purchaseScanned = 0;
/** @var array<int,true> */
private array $seenSourceIds = [];
@ -183,6 +185,8 @@ class ShippingReplicaSynchronizer
$this->packagesRetainedForUnmatchedAmount = 0;
$this->staleMirrorPackages = 0;
$this->supersededRemoved = 0;
$this->outboundScanned = 0;
$this->purchaseScanned = 0;
$this->seenSourceIds = [];
$this->expressOrderIds = [];
$this->dwsFailedDays = [];
@ -857,6 +861,7 @@ class ShippingReplicaSynchronizer
[$a, $a + $step, $fromS, $toS]
);
$scanned += count($rows);
$this->outboundScanned += count($rows);
$pick = [];
foreach ($rows as $r) {
$io = (string) ($r['io_id'] ?? '');
@ -959,6 +964,7 @@ class ShippingReplicaSynchronizer
. 'receiver_address, receiver_name, receiver_mobile FROM ' . self::PURCHASE_OUT . ' WHERE io_date >= ? AND io_date < ?',
[$fromS, $toS]
);
$this->purchaseScanned += count($rows);
$pick = [];
$skip = ['done' => 0, 'cancel' => 0, 'noitem' => 0];
foreach ($rows as $r) {
@ -2389,6 +2395,12 @@ class ShippingReplicaSynchronizer
}
// 凌晨按自动发货仓入库、当时还没有快递单号的旧包裹:订单之后有了发货明细(改走发货明细来源键),
// 或同一交易单号本次已由其它包裹吐出 → 视为被取代
// 出库单兜底/采购退货出库:本次已完整扫描窗口内的出库单与采购退货单,未再出现说明上游已作废、换单或不再收录(工作平台也不再显示)
if (($id >= self::PURCHASE_OUT_OFFSET && $this->purchaseScanned > 0)
|| ($id >= self::OUTBOUND_OFFSET && $id < self::PURCHASE_OUT_OFFSET && $this->outboundScanned > 0)) {
$superseded[] = $id;
continue;
}
$so = trim((string) ($row->trade_no ?? ''));
$isAuto = $id >= self::AUTOSHIP_OFFSET && $id < self::OUTBOUND_OFFSET;
if (($isAuto && isset($this->expressOrderIds[(string) ($id - self::AUTOSHIP_OFFSET)]))

View File

@ -1326,6 +1326,9 @@ class ShippingReplicaSynchronizerTest extends TestCase
'trade_no' => null, 'barcode' => null, 'product_name' => null, 'amount' => null, 'receiver_state' => null],
['source_id' => 1000000888, 'sku_source_id' => 1000000888, 'ship_time' => '2026-09-02 14:00:00', 'express_no' => 'ZZ9',
'trade_no' => null, 'barcode' => null, 'product_name' => null, 'amount' => null, 'receiver_state' => null],
// 采购退货出库:上游已作废,本次未再出现
['source_id' => 3000000555, 'sku_source_id' => 3000000555, 'ship_time' => '2026-09-02 15:00:00', 'express_no' => 'SF555',
'trade_no' => null, 'barcode' => null, 'product_name' => null, 'amount' => null, 'receiver_state' => null],
// 凌晨入库时还没有快递单号的自动发货仓包裹,同一交易单号本次已由 6000 吐出
['source_id' => 1000000999, 'sku_source_id' => 1000000999, 'ship_time' => '2026-09-02 13:30:00', 'express_no' => null,
'trade_no' => 'so-2', 'barcode' => null, 'product_name' => null, 'amount' => null, 'receiver_state' => null],
@ -1346,6 +1349,9 @@ class ShippingReplicaSynchronizerTest extends TestCase
['o_id' => 'order-2', 'io_id' => 'outbound-2', 'l_id' => 'YT6'],
];
}
if (str_contains($sql, 'FROM rpt.jst_purchaseout WHERE io_date')) {
return [['io_id' => '555', 'io_date' => '2026-09-02 15:00:00', 'status' => 'Cancelled', 'l_id' => 'SF555']];
}
if (str_contains($sql, 'FROM rpt.Jst_SendData_View_extension')) {
return [
['io_id' => 'outbound-1', 'sku_ids' => 'new-sku', 'qty' => 1, 'type' => 0, 'sku_name' => '新商品甲', 'volume' => null],
@ -1363,7 +1369,8 @@ class ShippingReplicaSynchronizerTest extends TestCase
);
$this->assertSame(1, $result['packages_retained_for_unmatched_amount']);
$this->assertSame(2, $result['superseded_packages_removed']);
$this->assertSame(3, $result['superseded_packages_removed']);
$this->assertSame(0, $db->table('jst_oms_shipping')->where('source_id', 3000000555)->count());
$this->assertSame(0, $db->table('jst_oms_shipping')->where('source_id', 1000000999)->count());
$this->assertSame(1, $result['stale_mirror_packages']);
// 保留的旧包裹:商品金额不动,省市区按上游刷新