diff --git a/modules/EcommerceFinance/Services/Jushuitan/ShippingReplicaSynchronizer.php b/modules/EcommerceFinance/Services/Jushuitan/ShippingReplicaSynchronizer.php index b93884eb..edcc74ae 100644 --- a/modules/EcommerceFinance/Services/Jushuitan/ShippingReplicaSynchronizer.php +++ b/modules/EcommerceFinance/Services/Jushuitan/ShippingReplicaSynchronizer.php @@ -135,6 +135,12 @@ class ShippingReplicaSynchronizer /** @var array 最近一次 loadViewItems 的 io_id => 视图店铺名(发货明细缺店铺时兜底) */ private array $ioShop = []; + + /** @var array 最近一次 loadViewItems 的 io_id => 视图快递名称 expressName(工作平台显示的快递名称) */ + private array $ioExpress = []; + + /** @var array 最近一次 loadViewItems 的 io_id => 视图发货仓 wareHoursName(工作平台显示的仓库) */ + private array $ioWare = []; /** @var array 已查询订单元数据缓存 o_id=>字段 */ private array $orderMetadata = []; @@ -489,6 +495,7 @@ class ShippingReplicaSynchronizer ?? $this->ts($r['send_time'] ?? null) ?? $this->ts($r['jo_send'] ?? null); $pkg = $this->pkgFromExpress($r, $tracking, $shipTime); $pkg = $this->withOutboundCarrier($io, $r, $pkg, $pkgId); + $pkg = $this->withViewCarrier($io, $pkg, $pkgId); $pkg['platform'] = $this->platformOf($io, $pkg['platform']); $pkg = $this->withViewAddr($io, $pkg); $total += $this->emitPackage($pkgId, $pkg, $items, $skus, $freight, $so, $tracking, $now); @@ -705,6 +712,7 @@ class ShippingReplicaSynchronizer $pkg['platform'] = $this->platformOf($io, $pkg['platform']); $pkg = $this->withViewAddr($io, $pkg); $pkgId = self::AUTOSHIP_OFFSET + (int) $oid; + $pkg = $this->withViewCarrier($io, $pkg, $pkgId); $total += $this->emitPackage($pkgId, $pkg, $items, $skus, $freight, $so, $tracking, $now, true); } unset($rows, $byOid, $bySo, $dws, $skus, $freight); @@ -900,8 +908,11 @@ class ShippingReplicaSynchronizer } $pkg = $this->pkgFromOutbound($r, $tracking !== '' ? $tracking : null, $this->ts($r['io_date'] ?? null), $h, $so); $pkg = $this->withViewAddr($io, $pkg); + $pkg = $this->withViewCarrier($io, $pkg, self::OUTBOUND_OFFSET + (int) $io); if (trim((string) ($pkg['express_name1'] ?? '')) === self::NO_LOGISTICS) { - $pkg['warehouse_name'] = self::NO_LOGISTICS_WAREHOUSE; + if (empty($this->ioWare[$io])) { + $pkg['warehouse_name'] = self::NO_LOGISTICS_WAREHOUSE; + } $virtualEmitted++; } $total += $this->emitPackage(self::OUTBOUND_OFFSET + (int) $io, $pkg, $viewItems[$io], $skus, $freight, $so, $tracking !== '' ? $tracking : null, $now, true); @@ -1030,7 +1041,7 @@ class ShippingReplicaSynchronizer private function loadViewHeads(array $ioIds): array { $out = []; - foreach (array_chunk(array_values(array_unique(array_filter($ioIds))), 800) as $chunk) { + foreach (array_chunk(array_values(array_unique(array_filter($ioIds))), 300) as $chunk) { if ($chunk === []) { continue; } @@ -1260,6 +1271,26 @@ class ShippingReplicaSynchronizer return $pkg; } + /** + * 工作平台的快递名称/仓库即发货数据视图的 expressName / wareHoursName(抽样 14/14 一致): + * 视图有值时以视图为准;视图无该出库单时保留出库单/发货明细推断的值。 + */ + private function withViewCarrier(string $io, array $pkg, int $pkgId): array + { + if ($io === '') { + return $pkg; + } + if (! empty($this->ioExpress[$io])) { + $pkg['express_name1'] = $this->ioExpress[$io]; + } + if (! empty($this->ioWare[$io])) { + $pkg['warehouse_name'] = $this->ioWare[$io]; + unset($this->pendingWarehouse[$pkgId]); + } + + return $pkg; + } + private function wmsName(string $wms): ?string { if (empty($this->wmsNames[$wms])) { @@ -1723,12 +1754,15 @@ class ShippingReplicaSynchronizer $this->ioSite = []; $this->ioAddr = []; $this->ioShop = []; - foreach (array_chunk(array_values(array_unique(array_filter($ioIds))), 800) as $chunk) { + $this->ioExpress = []; + $this->ioWare = []; + // 发货数据视图是计算视图,IN 列表过长时副本库容易超时:按 300 个出库单一批 + foreach (array_chunk(array_values(array_unique(array_filter($ioIds))), 300) as $chunk) { if ($chunk === []) { continue; } $ph = implode(',', array_fill(0, count($chunk), '?')); - $sql = 'SELECT io_id, sku_ids, qty, type, sku_name, shop_i_id, wareHoursName, l, w, h, volume, weight, supplier_name, shop_site, shop_name, receiver_state, receiver_city, receiver_district FROM ' + $sql = 'SELECT io_id, sku_ids, qty, type, sku_name, shop_i_id, wareHoursName, expressName, l, w, h, volume, weight, supplier_name, shop_site, shop_name, receiver_state, receiver_city, receiver_district FROM ' . self::VIEW . " WHERE io_id IN ({$ph})"; foreach ($this->selectWithReconnect($sql, $chunk) as $r) { $io = (string) ($r['io_id'] ?? ''); @@ -1738,6 +1772,12 @@ class ShippingReplicaSynchronizer if ($io !== '' && ! isset($this->ioShop[$io]) && trim((string) ($r['shop_name'] ?? '')) !== '') { $this->ioShop[$io] = trim((string) $r['shop_name']); } + if ($io !== '' && ! isset($this->ioExpress[$io]) && trim((string) ($r['expressName'] ?? '')) !== '') { + $this->ioExpress[$io] = trim((string) $r['expressName']); + } + if ($io !== '' && ! isset($this->ioWare[$io]) && trim((string) ($r['wareHoursName'] ?? '')) !== '') { + $this->ioWare[$io] = trim((string) $r['wareHoursName']); + } if ($io !== '') { foreach (['st' => 'receiver_state', 'ci' => 'receiver_city', 'di' => 'receiver_district'] as $ak => $ac) { $av = trim((string) ($r[$ac] ?? '')); diff --git a/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php b/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php index 213392af..85da9083 100644 --- a/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php +++ b/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php @@ -1271,6 +1271,50 @@ class ShippingReplicaSynchronizerTest extends TestCase $this->assertSame(1, $skipped->getValue($sync)); } + public function test_view_express_and_warehouse_override_inferred_carrier_like_the_work_platform(): void + { + $chunks = []; + $upstream = $this->createMock(UpstreamReadOnlyQuery::class); + $upstream->method('select')->willReturnCallback(function (string $sql, array $bindings) use (&$chunks): array { + $this->assertStringContainsString('expressName', $sql); + $chunks[] = count($bindings); + + return [ + ['io_id' => '12', 'sku_ids' => 'a', 'qty' => 1, 'type' => 0, 'volume' => null, 'expressName' => '常州德邦', 'wareHoursName' => '工厂-河南千优代发仓'], + ['io_id' => '12', 'sku_ids' => 'b', 'qty' => 1, 'type' => 0, 'volume' => null, 'expressName' => '', 'wareHoursName' => ''], + ['io_id' => '41', 'sku_ids' => 'c', 'qty' => 1, 'type' => 0, 'volume' => null, 'expressName' => '无需物流', 'wareHoursName' => '简尚-自动发货仓'], + ]; + }); + $factory = $this->createMock(UpstreamReadOnlyQueryFactory::class); + $factory->method('forSource')->willReturn($upstream); + $sync = new ShippingReplicaSynchronizer($factory); + $load = new \ReflectionMethod($sync, 'loadViewItems'); + $load->setAccessible(true); + $load->invoke($sync, array_merge(['12', '41', '99'], array_map('strval', range(1000, 1400)))); + $this->assertSame([300, 104], $chunks); // 视图按 300 个出库单一批 + + $pending = new \ReflectionProperty($sync, 'pendingWarehouse'); + $pending->setAccessible(true); + $pending->setValue($sync, [1 => '14138566', 2 => '15170678']); + $carrier = new \ReflectionMethod($sync, 'withViewCarrier'); + $carrier->setAccessible(true); + + // 视图有值:快递名称、仓库按视图(工作平台口径),不再按发货主体回填 + $pkg = $carrier->invoke($sync, '12', ['express_name1' => '崔桥顺丰干配', 'warehouse_name' => '工厂-河北浩浦代发仓'], 1); + $this->assertSame('常州德邦', $pkg['express_name1']); + $this->assertSame('工厂-河南千优代发仓', $pkg['warehouse_name']); + $this->assertArrayNotHasKey(1, $pending->getValue($sync)); + + $none = $carrier->invoke($sync, '41', ['express_name1' => '无需物流', 'warehouse_name' => '工厂-山东恒思代发仓'], 4); + $this->assertSame('简尚-自动发货仓', $none['warehouse_name']); + + // 视图没有该出库单:保留出库单/发货明细推断值和待回填仓库 + $keep = $carrier->invoke($sync, '99', ['express_name1' => '外采快递', 'warehouse_name' => '常州简尚家居有限公司'], 2); + $this->assertSame('外采快递', $keep['express_name1']); + $this->assertSame('常州简尚家居有限公司', $keep['warehouse_name']); + $this->assertSame('15170678', $pending->getValue($sync)[2]); + } + private function setScanBatchLimit(ShippingReplicaSynchronizer $synchronizer, string $property, int $limit): void { $reflection = new \ReflectionProperty($synchronizer, $property);