fix(lastleg): preserve dropship source pallets

This commit is contained in:
2026-09-07 21:10:43 +08:00
parent 98dafc13ca
commit ac18f69ec5
12 changed files with 46 additions and 10 deletions
@@ -1842,3 +1842,21 @@ CALL assert_receiving_warehouse_assignment_ready();
DROP PROCEDURE assert_receiving_warehouse_assignment_ready;
SELECT 'PASS' AS migration_status, '海外接柜仓预分配与柜级修改结构已就绪' AS message;
-- 一件代发来源托盘与海外仓实物托盘分离,实物绑定不得覆盖来源身份。
USE baiyue_lastleg;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name='ll_inbound_plan_pallet' AND column_name='source_pallet_no'),
'SELECT 1',
'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN source_pallet_no VARCHAR(128) NULL COMMENT ''一件代发不可变来源托盘号快照'' AFTER pallet_no'));
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
ALTER TABLE ll_inbound_plan_pallet
MODIFY COLUMN pallet_no VARCHAR(64) NOT NULL COMMENT '当前托盘编号:分配前为计划占位码,分配后为海外仓实物托盘码';
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name='ll_inbound_stock_receipt' AND column_name='source_pallet_no'),
'SELECT 1',
'ALTER TABLE ll_inbound_stock_receipt ADD COLUMN source_pallet_no VARCHAR(128) NULL COMMENT ''一件代发不可变来源托盘号快照'' AFTER pallet_no'));
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
ALTER TABLE ll_inbound_stock_receipt
MODIFY COLUMN pallet_no VARCHAR(64) NOT NULL COMMENT '海外仓实物托盘码';
@@ -135,8 +135,8 @@ public class DropshipOrderService {
throw conflict("Dropship pallet activation is incomplete; repair profiles before retrying");
}
Map<String, List<InboundStockReceipt>> receiptsByPallet = receipts.stream()
.filter(receipt -> receipt.getPalletNo() != null)
.collect(Collectors.groupingBy(InboundStockReceipt::getPalletNo));
.filter(receipt -> receipt.getSourcePalletNo() != null)
.collect(Collectors.groupingBy(InboundStockReceipt::getSourcePalletNo));
if (receiptsByPallet.size() != profilesForOrder.size()) {
throw conflict("PDA pallet count does not match dropship source pallet count");
}
@@ -16,6 +16,7 @@ public class InboundPlanPallet {
private String customerId;
private Long planId;
private String palletNo;
private String sourcePalletNo;
private String destinationWarehouseId;
private String destinationWarehouseCode;
private String destinationWarehouseName;
@@ -25,6 +25,7 @@ public class InboundStockReceipt {
private String productNameEn;
private Long warehousePalletId;
private String palletNo;
private String sourcePalletNo;
private Long warehouseLocationId;
private String warehouseLocationCode;
private String batchNo;
@@ -945,7 +945,7 @@ public class InboundPdaWorkflowService {
boolean wholePalletInbound = "ONE_CLICK_DROPSHIP".equals(orderType);
Map<String, InboundPlanPallet> palletsByNo = wholePalletInbound
? repository.pallets(plan.getId()).stream().collect(java.util.stream.Collectors.toMap(
InboundPlanPallet::getPalletNo, value -> value)) : Map.of();
InboundPlanPallet::getSourcePalletNo, value -> value)) : Map.of();
Map<Long, InboundPlanPalletAllocation> allocationsByPallet = wholePalletInbound
? allocations.stream().collect(java.util.stream.Collectors.toMap(
InboundPlanPalletAllocation::getPalletId, value -> value)) : Map.of();
@@ -116,7 +116,7 @@ public class InboundStockReceiptService {
if (value(allocation.getPickedPackageCount()) != value(allocation.getPlannedPackageCount())) {
throw conflict("One-click dropship inbound must receive a complete source pallet");
}
DropshipPalletSnapshot sourcePallet = dropshipPallets.get(pallet.getPalletNo());
DropshipPalletSnapshot sourcePallet = dropshipPallets.get(pallet.getSourcePalletNo());
if (sourcePallet == null || sourcePallet.packageCount() != value(allocation.getPickedPackageCount())) {
throw conflict("One-click dropship source pallet SKU package total does not match PDA receipt");
}
@@ -261,6 +261,7 @@ public class InboundStockReceiptService {
receipt.setProductNameEn(sku.productNameEn());
receipt.setWarehousePalletId(pallet.getWarehousePalletId());
receipt.setPalletNo(pallet.getPalletNo());
receipt.setSourcePalletNo(pallet.getSourcePalletNo());
receipt.setWarehouseLocationId(pallet.getWarehouseLocationId());
receipt.setBatchNo(plan.getPlanNo());
receipt.setReceivedPackages(packages);
@@ -297,7 +297,8 @@ public class InboundPlanService {
if (palletVolume.compareTo(remainingVolume) > 0) palletVolume = remainingVolume;
InboundPlanPallet pallet = new InboundPlanPallet();
pallet.setId(IdWorker.getId()); pallet.setCustomerId(plan.getCustomerId()); pallet.setPlanId(plan.getId());
pallet.setPalletNo(source.palletNo().trim()); pallet.setDestinationWarehouseId(order.destinationWarehouseId());
pallet.setPalletNo(source.palletNo().trim()); pallet.setSourcePalletNo(source.palletNo().trim());
pallet.setDestinationWarehouseId(order.destinationWarehouseId());
pallet.setDestinationWarehouseCode(order.destinationWarehouseCode());
pallet.setDestinationWarehouseName(order.destinationWarehouseName());
pallet.setCapacityCbm(PalletAllocator.PALLET_CAPACITY_CBM); pallet.setAllocatedVolumeCbm(palletVolume);
@@ -437,6 +438,7 @@ public class InboundPlanService {
String sourcePalletNo = palletIndex < sourcePalletNos.size() ? sourcePalletNos.get(palletIndex) : null;
pallet.setPalletNo(sourcePalletNo == null || sourcePalletNo.isBlank()
? plan.getPlanNo() + "-P" + String.format("%03d", palletIndex + 1) : sourcePalletNo.trim());
pallet.setSourcePalletNo(sourcePalletNo == null || sourcePalletNo.isBlank() ? null : sourcePalletNo.trim());
pallet.setDestinationWarehouseId(sample.destinationWarehouseId());
pallet.setDestinationWarehouseCode(sample.destinationWarehouseCode());
pallet.setDestinationWarehouseName(sample.destinationWarehouseName());
@@ -555,7 +555,8 @@ CREATE TABLE IF NOT EXISTS ll_inbound_plan_pallet (
id BIGINT NOT NULL COMMENT '计划托盘ID(雪花ID)',
customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识',
plan_id BIGINT NOT NULL COMMENT '入库计划ID',
pallet_no VARCHAR(64) NOT NULL COMMENT '计划托盘编号',
pallet_no VARCHAR(64) NOT NULL COMMENT '当前托盘编号:分配前为计划占位码,分配后为海外仓实物托盘码',
source_pallet_no VARCHAR(128) NULL COMMENT '一件代发不可变来源托盘号快照',
destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID快照',
destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照',
destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照',
@@ -617,7 +618,7 @@ ALTER TABLE ll_inbound_plan_pallet COMMENT='尾程入库计划托盘表:按目
ALTER TABLE ll_inbound_plan_pallet
MODIFY COLUMN id BIGINT NOT NULL COMMENT '计划托盘ID(雪花ID)',
MODIFY COLUMN plan_id BIGINT NOT NULL COMMENT '入库计划ID',
MODIFY COLUMN pallet_no VARCHAR(64) NOT NULL COMMENT '计划托盘编号',
MODIFY COLUMN pallet_no VARCHAR(64) NOT NULL COMMENT '当前托盘编号:分配前为计划占位码,分配后为海外仓实物托盘码',
MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID快照',
MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照',
MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照',
@@ -1105,7 +1106,8 @@ CREATE TABLE IF NOT EXISTS ll_inbound_stock_receipt (
line_id BIGINT NOT NULL COMMENT '入仓计划明细ID', order_id BIGINT NOT NULL COMMENT '订单ID', inbound_code VARCHAR(128) NOT NULL COMMENT '入仓单号快照',
sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码快照', product_name_zh VARCHAR(255) NULL COMMENT '中文品名快照',
product_name_en VARCHAR(255) NULL COMMENT '英文品名快照',
warehouse_pallet_id BIGINT NOT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(64) NOT NULL COMMENT '托盘号',
warehouse_pallet_id BIGINT NOT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(64) NOT NULL COMMENT '海外仓实物托盘码',
source_pallet_no VARCHAR(128) NULL COMMENT '一件代发不可变来源托盘号快照',
warehouse_location_id BIGINT NULL COMMENT '仓库库位ID', warehouse_location_code VARCHAR(128) NULL COMMENT '仓库库位编码',
batch_no VARCHAR(128) NOT NULL COMMENT '库存批次号', received_packages BIGINT NOT NULL COMMENT '实收箱数',
inventory_balance_id BIGINT NOT NULL COMMENT '库存余额记录ID', inventory_position_id BIGINT NOT NULL COMMENT '库存仓位记录ID',
@@ -141,7 +141,8 @@ class DropshipOrderServiceTest {
private static InboundStockReceipt receipt(long palletId, String palletNo, long positionId, long packages) {
InboundStockReceipt receipt = new InboundStockReceipt();
receipt.setOrderId(10L); receipt.setWarehousePalletId(palletId); receipt.setPalletNo(palletNo);
receipt.setOrderId(10L); receipt.setWarehousePalletId(palletId);
receipt.setPalletNo("WH-" + palletId); receipt.setSourcePalletNo(palletNo);
receipt.setInventoryPositionId(positionId); receipt.setReceivedPackages(packages);
return receipt;
}
@@ -557,6 +557,8 @@ class InboundPdaWorkflowServiceTest {
@Test
void dropshipTaskIsMarkedAndWholePalletPickReceivesThePlannedPalletAtOnce() {
plan.setSourceOrderId(101L);
targetPallet.setPalletNo("WH-PALLET-21");
targetPallet.setSourcePalletNo("PLT-21");
LastlegOrder order = new LastlegOrder();
order.setId(101L); order.setOrderType("ONE_CLICK_DROPSHIP");
when(repository.order(101L)).thenReturn(order);
@@ -218,6 +218,7 @@ class InboundStockReceiptServiceTest {
void activatesDropshipAfterGenericStockInReceiptIsCreated() {
Fixture fixture = fixture(5, 5, 5, 10);
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
fixture.pallet.setPalletNo("WH-PALLET-21");
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(dropshipPallet("PLT-21", 5)));
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
when(inventory.stockIn(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
@@ -230,6 +231,8 @@ class InboundStockReceiptServiceTest {
verify(dropshipOrders).activateAfterStockIn(eq("100"), eq(101L), eq(2L), activationReceipts.capture());
assertEquals(1, activationReceipts.getValue().size());
assertEquals(10L, activationReceipts.getValue().get(0).getInventoryPositionId());
assertEquals("PLT-21", activationReceipts.getValue().get(0).getSourcePalletNo());
assertEquals("WH-PALLET-21", activationReceipts.getValue().get(0).getPalletNo());
}
@Test
@@ -311,7 +314,8 @@ class InboundStockReceiptServiceTest {
private InboundPlanPallet pallet(long id, long warehousePalletId) {
InboundPlanPallet pallet = new InboundPlanPallet(); pallet.setId(id); pallet.setPlanId(1L);
pallet.setPalletNo("PLT-" + id); pallet.setWarehousePalletId(warehousePalletId);
pallet.setPalletNo("PLT-" + id); pallet.setSourcePalletNo("PLT-" + id);
pallet.setWarehousePalletId(warehousePalletId);
pallet.setWarehouseLocationId(3001L); return pallet;
}
@@ -253,6 +253,8 @@ class InboundPlanServiceTest {
verify(palletMapper, times(2)).insert(pallets.capture());
assertEquals(List.of("SOURCE-P-1", "SOURCE-P-2"),
pallets.getAllValues().stream().map(InboundPlanPallet::getPalletNo).toList());
assertEquals(List.of("SOURCE-P-1", "SOURCE-P-2"),
pallets.getAllValues().stream().map(InboundPlanPallet::getSourcePalletNo).toList());
ArgumentCaptor<InboundPlanPalletAllocation> allocations =
ArgumentCaptor.forClass(InboundPlanPalletAllocation.class);
verify(allocationMapper, times(2)).insert(allocations.capture());
@@ -354,6 +356,8 @@ class InboundPlanServiceTest {
verify(palletMapper, times(2)).insert(pallets.capture());
assertEquals(List.of("SOURCE-P-1", "SOURCE-P-2"),
pallets.getAllValues().stream().map(InboundPlanPallet::getPalletNo).toList());
assertEquals(List.of("SOURCE-P-1", "SOURCE-P-2"),
pallets.getAllValues().stream().map(InboundPlanPallet::getSourcePalletNo).toList());
ArgumentCaptor<InboundPlanPalletAllocation> allocations =
ArgumentCaptor.forClass(InboundPlanPalletAllocation.class);
verify(allocationMapper, times(2)).insert(allocations.capture());