fix(fleet): preserve unknown commercial facts for pooled freight
This commit is contained in:
+4
-1
@@ -55,7 +55,9 @@ public class FleetPoolOrderBridge {
|
||||
String transport=in.destinationKey().startsWith("FBA:")?"WAREHOUSE_TO_WAREHOUSE":"WAREHOUSE_TO_DOOR";
|
||||
if("LOCAL_LOOSEBOARD".equals(in.businessType())) {
|
||||
var order=new LocalLooseboardOrder();order.setId(row.getBusinessOrderId());order.setCustomerId(in.customerId());order.setOrderNo(row.getBusinessOrderNo());
|
||||
order.setTransportMode("SHARED_TRUCK");order.setPalletRequired("YES");order.setTransportType(transport);
|
||||
order.setTransportMode("SHARED_TRUCK");order.setTransportType(transport);
|
||||
// A pallet count/space is not a palletizing service request or a container specification.
|
||||
order.setPalletRequired(null);order.setContainerType(null);
|
||||
order.setOrigin(in.warehouseName());order.setDestination(in.destinationLabel());order.setPackageCount(in.packageCount());
|
||||
order.setTotalWeightKg(in.weightKg());order.setTotalVolumeCbm(in.volumeCbm());order.setCurrency(in.currency());
|
||||
order.setContactName(in.contactName());order.setContactInfo(in.contactPhone());order.setRequestedDate(in.plannedDepartureAt().toLocalDate());
|
||||
@@ -65,6 +67,7 @@ public class FleetPoolOrderBridge {
|
||||
} else {
|
||||
var order=new UsTruckOrder();order.setId(row.getBusinessOrderId());order.setCustomerId(in.customerId());order.setOrderNo(row.getBusinessOrderNo());
|
||||
order.setTransportType(transport);order.setOrigin(in.warehouseName());order.setDestination(in.destinationLabel());
|
||||
order.setContainerType(null); // Pool inquiry does not collect a container specification.
|
||||
order.setPalletCount(in.expectedPalletCount());order.setPackageCount(in.packageCount());order.setReceivedPalletCount(0);order.setAllocatedPalletCount(0);
|
||||
order.setTotalWeightKg(in.weightKg());order.setTotalVolumeCbm(in.volumeCbm());order.setCurrency(in.currency());
|
||||
order.setContactName(in.contactName());order.setContactInfo(in.contactPhone());order.setRequestedDate(in.plannedDepartureAt().toLocalDate());
|
||||
|
||||
+1
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
public final class LocalLooseboardResponses {
|
||||
private LocalLooseboardResponses() { }
|
||||
|
||||
/** Pool orders retain null containerType/palletRequired when the inquiry did not specify these facts. */
|
||||
public record Summary(
|
||||
Long id, String customerId, String orderNo, String transportMode, String palletRequired,
|
||||
String transportType, String origin, String destination, String containerType, int packageCount,
|
||||
|
||||
+1
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
public final class UsTruckOrderResponses {
|
||||
private UsTruckOrderResponses() {}
|
||||
|
||||
/** Pool orders retain null containerType; forecast physical pallets do not imply a container type. */
|
||||
public record Summary(
|
||||
Long id, String customerId, String orderNo, String transportType,
|
||||
String origin, String destination, String containerType, int palletCount, int packageCount,
|
||||
|
||||
@@ -2661,7 +2661,7 @@ CREATE TABLE IF NOT EXISTS ll_us_truck_order (
|
||||
origin_address VARCHAR(500) NULL COMMENT '起运地址',
|
||||
destination VARCHAR(255) NOT NULL COMMENT '目的地',
|
||||
destination_address VARCHAR(500) NULL COMMENT '目的地址',
|
||||
container_type VARCHAR(32) NOT NULL DEFAULT '40HQ' COMMENT '平台统一柜型:40GP、40HQ、45HQ',
|
||||
container_type VARCHAR(32) NULL DEFAULT NULL COMMENT '柜型;拼车来源未提供时为空,不推断为整柜',
|
||||
pallet_count INT NOT NULL DEFAULT 1 COMMENT '客户申报托盘数',
|
||||
package_count INT NULL COMMENT '可选箱数,零或空值均有效',
|
||||
received_pallet_count INT NOT NULL DEFAULT 0 COMMENT 'PDA实收托盘数',
|
||||
@@ -2736,10 +2736,10 @@ CREATE TABLE IF NOT EXISTS ll_local_looseboard_order (
|
||||
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
|
||||
lastleg_order_id BIGINT NULL COMMENT '关联尾程主订单ID',
|
||||
order_no VARCHAR(64) NOT NULL COMMENT '本土散板订单号', transport_mode VARCHAR(32) NOT NULL COMMENT '运输方式:拼车或优先拼载',
|
||||
pallet_required VARCHAR(8) NOT NULL COMMENT '是否需要打盘:YES或NO', transport_type VARCHAR(32) NOT NULL COMMENT '运输类型',
|
||||
pallet_required VARCHAR(8) NULL DEFAULT NULL COMMENT '是否需要打盘:YES或NO;拼车来源未提供时为空', transport_type VARCHAR(32) NOT NULL COMMENT '运输类型',
|
||||
origin VARCHAR(255) NOT NULL COMMENT '起运地', origin_address VARCHAR(500) NULL COMMENT '起运详细地址',
|
||||
destination VARCHAR(255) NOT NULL COMMENT '目的地', destination_address VARCHAR(500) NULL COMMENT '目的详细地址、码头或仓号',
|
||||
container_type VARCHAR(32) NOT NULL DEFAULT '40HQ' COMMENT '平台统一柜型:40GP、40HQ、45HQ', package_count INT NOT NULL DEFAULT 0 COMMENT '货物件数',
|
||||
container_type VARCHAR(32) NULL DEFAULT NULL COMMENT '柜型;拼车来源未提供时为空,不推断为整柜', package_count INT NOT NULL DEFAULT 0 COMMENT '货物件数',
|
||||
total_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '总重量(千克)',
|
||||
total_volume_cbm DECIMAL(18,6) NOT NULL DEFAULT 0 COMMENT '总体积(立方米)',
|
||||
budget_amount DECIMAL(18,2) NULL COMMENT '预算总价', currency CHAR(3) NOT NULL DEFAULT 'USD' COMMENT '预算币种',
|
||||
|
||||
@@ -3362,7 +3362,7 @@ components:
|
||||
transportType: { type: string, nullable: true }
|
||||
origin: { type: string }
|
||||
destination: { type: string }
|
||||
containerType: { type: string, enum: [40GP, 40HQ, 45HQ], description: 平台统一柜型 }
|
||||
containerType: { type: string, nullable: true, enum: [40GP, 40HQ, 45HQ], description: 已知时为平台统一柜型;池询价未提供时未知,不默认40HQ }
|
||||
palletCount: { type: integer }
|
||||
packageCount: { type: integer }
|
||||
receivedPalletCount: { type: integer }
|
||||
@@ -3411,17 +3411,17 @@ components:
|
||||
LooseboardSummary:
|
||||
type: object
|
||||
description: 散板商业主单摘要;池来源附带同客户fleetArrangement,独立订单该字段为null。池来源不创建第二张占位尾程来源。
|
||||
required: [id, customerId, orderNo, transportMode, palletRequired, transportType, origin, destination, packageCount, totalWeightKg, totalVolumeCbm, status, version, createdAt, updatedAt]
|
||||
required: [id, customerId, orderNo, transportMode, transportType, origin, destination, packageCount, totalWeightKg, totalVolumeCbm, status, version, createdAt, updatedAt]
|
||||
properties:
|
||||
id: { type: string, pattern: '^[0-9]+$' }
|
||||
customerId: { type: string }
|
||||
orderNo: { type: string }
|
||||
transportMode: { type: string, enum: [SHARED_TRUCK, PRIORITY_CONSOLIDATION] }
|
||||
palletRequired: { type: string, enum: [YES, NO] }
|
||||
palletRequired: { type: string, nullable: true, enum: [YES, NO], description: 是否委托打盘服务;池询价未提供时未知,不从实托数或托位推断YES }
|
||||
transportType: { type: string }
|
||||
origin: { type: string }
|
||||
destination: { type: string }
|
||||
containerType: { type: string, enum: [40GP, 40HQ, 45HQ], description: 平台统一柜型 }
|
||||
containerType: { type: string, nullable: true, enum: [40GP, 40HQ, 45HQ], description: 已知时为平台统一柜型;池询价未提供时未知,不默认40HQ }
|
||||
packageCount: { type: integer }
|
||||
totalWeightKg: { type: number }
|
||||
totalVolumeCbm: { type: number }
|
||||
|
||||
+21
@@ -74,6 +74,27 @@ class FleetPoolOrderBridgeTest {
|
||||
assertThat(truckOrder.getBudgetAmount()).isNull();assertThat(truckOrder.getStatus()).isEqualTo("PENDING_DISPATCH");
|
||||
verifyNoInteractions(loose);
|
||||
}
|
||||
@Test void looseboardInquiryDoesNotInventContainerOrPalletizingServiceFromForecastPallets(){
|
||||
var source=input("LOCAL_LOOSEBOARD");service.confirm(source,"op");
|
||||
assertThat(looseOrder.getContainerType()).isNull();assertThat(looseOrder.getPalletRequired()).isNull();
|
||||
assertThat(looseOrder.getTransportMode()).isEqualTo("SHARED_TRUCK");
|
||||
assertThat(looseOrder.getTransportType()).isEqualTo("WAREHOUSE_TO_WAREHOUSE");
|
||||
assertThat(looseOrder.getOrigin()).isEqualTo(source.warehouseName());assertThat(looseOrder.getDestination()).isEqualTo(source.destinationLabel());
|
||||
assertThat(looseOrder.getPackageCount()).isEqualTo(source.packageCount());
|
||||
assertThat(looseOrder.getTotalWeightKg()).isEqualByComparingTo(source.weightKg());assertThat(looseOrder.getTotalVolumeCbm()).isEqualByComparingTo(source.volumeCbm());
|
||||
assertThat(looseOrder.getOriginAddress()).isNull();assertThat(looseOrder.getDestinationAddress()).isNull();assertThat(looseOrder.getBudgetAmount()).isNull();
|
||||
incoming();service.receiving("9","WT-7","pda");service.acceptedReceipt(7,receipt(),3,"op");
|
||||
assertThat(looseOrder.getPalletRequired()).isNull();assertThat(looseOrder.getContainerType()).isNull();
|
||||
}
|
||||
@Test void truckInquiryPreservesUnknownContainerWhileZeroActualCountsRemainRealUntilReceipt(){
|
||||
var source=input("US_TRUCK");service.confirm(source,"op");
|
||||
assertThat(truckOrder.getContainerType()).isNull();assertThat(truckOrder.getPalletCount()).isEqualTo(source.expectedPalletCount());
|
||||
assertThat(truckOrder.getReceivedPalletCount()).isZero();assertThat(truckOrder.getAllocatedPalletCount()).isZero();
|
||||
assertThat(truckOrder.getPackageCount()).isEqualTo(source.packageCount());assertThat(truckOrder.getTransportType()).isEqualTo("WAREHOUSE_TO_WAREHOUSE");
|
||||
assertThat(truckOrder.getOriginAddress()).isNull();assertThat(truckOrder.getDestinationAddress()).isNull();assertThat(truckOrder.getBudgetAmount()).isNull();
|
||||
incoming();service.receiving("9","WT-7","pda");service.acceptedReceipt(7,receipt(),3,"op");
|
||||
assertThat(truckOrder.getReceivedPalletCount()).isEqualTo(2);assertThat(truckOrder.getContainerType()).isNull();
|
||||
}
|
||||
@Test void incomingMustMatchCustomerWarehouseAndDestinationAndNeverBindSecondTailOrder(){
|
||||
service.confirm(input("LOCAL_LOOSEBOARD"),"op");
|
||||
assertThatThrownBy(()->service.bindIncoming("FB-7","another","WT-7",77,"92","FBA:ONT8","op")).hasMessageContaining("客户");
|
||||
|
||||
+16
@@ -43,4 +43,20 @@ class LocalLooseboardSchemaContractTest {
|
||||
int end = sql.indexOf(";", start);
|
||||
return start < 0 || end < 0 ? "" : sql.substring(start, end);
|
||||
}
|
||||
|
||||
@Test
|
||||
void poolUnknownContainerAndPalletizingSurviveOmittedNullInsertColumnsInBothBaselines() throws Exception {
|
||||
String baseline;
|
||||
try (var input = getClass().getResourceAsStream("/db/lastleg.sql")) {
|
||||
baseline = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
String unified = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8);
|
||||
for (String sql : java.util.List.of(baseline, unified)) {
|
||||
String block = tableBlock(sql.replace("CREATE TABLE IF NOT EXISTS", "CREATE TABLE"), "ll_local_looseboard_order").toLowerCase();
|
||||
assertTrue(java.util.regex.Pattern.compile("(?:^|,)\\s*container_type\\s+varchar\\(32\\)\\s+(?:collate\\s+\\w+\\s+)?(?:null\\s+)?default\\s+null\\b").matcher(block).find(),
|
||||
"Unknown container must remain null when MyBatis omits its insert column");
|
||||
assertTrue(java.util.regex.Pattern.compile("(?:^|,)\\s*pallet_required\\s+varchar\\(8\\)\\s+(?:collate\\s+\\w+\\s+)?(?:null\\s+)?default\\s+null\\b").matcher(block).find(),
|
||||
"Forecast pallets must not invent YES or NO palletizing service");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -48,4 +48,19 @@ class UsTruckOrderSchemaContractTest {
|
||||
int end = sql.indexOf(';', start);
|
||||
return start < 0 || end < 0 ? "" : sql.substring(start, end);
|
||||
}
|
||||
|
||||
@Test
|
||||
void poolUnknownContainerSurvivesOmittedNullInsertColumnInBothBaselines() throws Exception {
|
||||
String baseline;
|
||||
try (var input = getClass().getResourceAsStream("/db/lastleg.sql")) {
|
||||
baseline = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
String unified = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8);
|
||||
for (String sql : java.util.List.of(baseline, unified)) {
|
||||
String normalized = sql.toLowerCase().replace("create table if not exists", "create table");
|
||||
String block = tableBlock(normalized, "ll_us_truck_order");
|
||||
assertTrue(java.util.regex.Pattern.compile("(?:^|,)\\s*container_type\\s+varchar\\(32\\)\\s+(?:collate\\s+\\w+\\s+)?(?:null\\s+)?default\\s+null\\b").matcher(block).find(),
|
||||
"Unknown container must remain null when MyBatis omits its insert column");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user