修复历史发货代发仓扫描超时 #265

Merged
limeng merged 1 commits from fix/limeng-shipping-autoship-scan into main 2026-09-24 14:42:28 +08:00
2 changed files with 104 additions and 6 deletions

View File

@ -63,6 +63,9 @@ class ShippingReplicaSynchronizer
/** @var array<int,true> */
private array $seenSourceIds = [];
/** @var array<string,true> 发货明细已占用的窗口内订单号 */
private array $expressOrderIds = [];
/** @var list<string> */
private array $dwsFailedDays = [];
@ -106,6 +109,7 @@ class ShippingReplicaSynchronizer
$this->packagesRetainedForUnmatchedAmount = 0;
$this->staleMirrorPackages = 0;
$this->seenSourceIds = [];
$this->expressOrderIds = [];
$this->dwsFailedDays = [];
$now = now();
$fromS = $windowed ? $from->format('Y-m-d H:i:s') : null;
@ -187,6 +191,7 @@ class ShippingReplicaSynchronizer
$this->replaceWindow = false;
$this->dwsFailedDays = [];
$this->seenSourceIds = [];
$this->expressOrderIds = [];
gc_collect_cycles();
}
}
@ -276,11 +281,16 @@ class ShippingReplicaSynchronizer
if ($windowed) {
$kept = [];
foreach ($rows as $r) {
$jd = $r['jo_send'] ?? null;
$joTs = ($jd !== null && $jd !== '') ? strtotime((string) $jd) : null;
$oid = (string) ($r['inner_order_id'] ?? '');
if ($oid !== '' && $joTs !== null && $joTs !== false && $joTs >= $fromTs && $joTs < $toTs) {
$this->expressOrderIds[$oid] = true;
}
$st = $r['send_time'] ?? null;
$ts = ($st !== null && $st !== '') ? strtotime((string) $st) : null;
if ($ts === null) {
$jd = $r['jo_send'] ?? null;
$ts = ($jd !== null && $jd !== '') ? strtotime((string) $jd) : null;
$ts = $joTs;
}
if ($ts !== null && $ts >= $fromTs && $ts < $toTs) {
$kept[] = $r;
@ -382,8 +392,7 @@ class ShippingReplicaSynchronizer
$lastRid = null;
for ($b = 0; $b <= $maxBatches; $b++) {
$where = "o.status = 'Sent' AND o.send_date >= ? AND o.send_date < ? "
. 'AND NOT EXISTS (SELECT 1 FROM rpt.jst_order_express_details e WHERE e.inner_order_id = o.o_id)';
$where = "o.status = 'Sent' AND o.send_date >= ? AND o.send_date < ?";
$bind = [$fromS, $toS];
if ($lastRid !== null) {
$where .= ' AND o.Id < ?';
@ -398,6 +407,17 @@ class ShippingReplicaSynchronizer
throw new RuntimeException('代发仓扫描达到批次上限,仍有未读取的上游数据。');
}
$cnt = count($rows);
$eligible = [];
foreach ($rows as $r) {
$lastRid = $r['rid'];
$oid = (string) ($r['o_id'] ?? '');
if ($oid !== '' && ! isset($this->expressOrderIds[$oid])) {
$eligible[] = $r;
}
}
$rows = $eligible;
$oids = [];
$soIds = [];
$logs = [];
@ -414,7 +434,6 @@ class ShippingReplicaSynchronizer
if ($lg !== '') {
$logs[$lg] = true;
}
$lastRid = $r['rid'];
}
[$byOid, $bySo] = $this->loadAllItems(array_keys($oids), array_keys($soIds), false);
$dws = [];
@ -435,7 +454,6 @@ class ShippingReplicaSynchronizer
$skus = $this->loadSkus($this->collectSkuIds($byOid, $bySo, $dws, $viewItems));
$freight = $this->skipFreight ? [] : $this->loadFreight(array_keys($soIds));
$cnt = count($rows);
foreach ($rows as $r) {
$oid = (string) ($r['o_id'] ?? '');
if ($oid === '') {

View File

@ -234,6 +234,86 @@ class ShippingReplicaSynchronizerTest extends TestCase
->where('dataset_key', 'jst.sctrade')->value('status'));
}
public function test_auto_ship_excludes_every_order_with_express_detail_even_when_detail_is_outside_window_or_label_excluded(): void
{
$orderQueries = [];
$upstream = $this->createMock(UpstreamReadOnlyQuery::class);
$upstream->method('select')->willReturnCallback(function (string $sql, array $bindings) use (&$orderQueries): array {
if (str_contains($sql, 'FROM rpt.jst_order_express_details e LEFT JOIN')) {
return [
['id' => 1001, 'inner_order_id' => '123', 'jo_send' => '2026-09-02 13:00:00',
'send_time' => '2026-08-31 12:00:00'],
['id' => 1002, 'inner_order_id' => '456', 'jo_send' => '2026-09-02 13:00:00',
'send_time' => '2026-09-02 12:00:00', 'order_labels' => '线上发货'],
['id' => 1003, 'inner_order_id' => '789', 'jo_send' => '2026-09-02 13:00:00',
'send_time' => '2026-09-02 12:00:00'],
];
}
if (str_contains($sql, 'FROM rpt.jst_Orders o WHERE')) {
$orderQueries[] = [$sql, $bindings];
return array_map(fn (int $oid): array => [
'rid' => $oid, 'o_id' => (string) $oid, 'send_date' => '2026-09-02 13:00:00',
], [789, 456, 321, 123]);
}
return [];
});
$factory = $this->createMock(UpstreamReadOnlyQueryFactory::class);
$factory->method('forSource')->with('operations_replica')->willReturn($upstream);
$result = (new ShippingReplicaSynchronizer($factory))->sync(
Carbon::parse('2026-09-01'), Carbon::parse('2026-09-05'), 2000, null, true,
);
$this->assertCount(1, $orderQueries);
$this->assertStringNotContainsString('NOT EXISTS', $orderQueries[0][0]);
$this->assertSame(['2026-09-01 00:00:00', '2026-09-05 00:00:00'], $orderQueries[0][1]);
$this->assertSame(1, $result['processed']);
$this->assertSame(1, $result['autoship']);
$this->assertSame([1003, 1000000321], DB::connection('business_mirror')->table('jst_oms_shipping')
->orderBy('source_id')->pluck('source_id')->map(fn ($id) => (int) $id)->all());
}
public function test_auto_ship_advances_raw_page_cursor_when_all_400_orders_have_express_details(): void
{
$orderBindings = [];
$upstream = $this->createMock(UpstreamReadOnlyQuery::class);
$upstream->method('select')->willReturnCallback(function (string $sql, array $bindings) use (&$orderBindings): array {
if (str_contains($sql, 'FROM rpt.jst_order_express_details e LEFT JOIN')) {
return array_map(fn (int $oid): array => [
'id' => $oid, 'inner_order_id' => (string) $oid,
'jo_send' => '2026-09-02 13:00:00', 'send_time' => '2026-08-31 12:00:00',
], range(1000, 601));
}
if (str_contains($sql, 'FROM rpt.jst_Orders o WHERE')) {
$orderBindings[] = $bindings;
if (count($orderBindings) === 1) {
return array_map(fn (int $oid): array => [
'rid' => $oid, 'o_id' => (string) $oid, 'send_date' => '2026-09-02 13:00:00',
], range(1000, 601));
}
return [['rid' => 600, 'o_id' => '500', 'send_date' => '2026-09-02 13:00:00']];
}
return [];
});
$factory = $this->createMock(UpstreamReadOnlyQueryFactory::class);
$factory->method('forSource')->with('operations_replica')->willReturn($upstream);
$result = (new ShippingReplicaSynchronizer($factory))->sync(
Carbon::parse('2026-09-01'), Carbon::parse('2026-09-05'), 2000, null, true,
);
$this->assertSame([
['2026-09-01 00:00:00', '2026-09-05 00:00:00'],
['2026-09-01 00:00:00', '2026-09-05 00:00:00', 601],
], $orderBindings);
$this->assertSame(0, $result['processed']);
$this->assertSame(1, $result['autoship']);
$this->assertSame([1000000500], DB::connection('business_mirror')->table('jst_oms_shipping')
->pluck('source_id')->map(fn ($id) => (int) $id)->all());
}
public function test_skip_freight_preserves_existing_amounts_and_marks_new_amounts_incomplete(): void
{
$db = DB::connection('business_mirror');