feat(trucking): refine truck demand contacts vehicle and service times
This commit is contained in:
+4
-4
@@ -96,14 +96,14 @@ class FleetPoolOrderBridgeTest {
|
||||
incoming();service.receiving("9","WT-7","pda");service.acceptedReceipt(7,receipt(),3,"op");
|
||||
assertThat(looseOrder.getPalletRequired()).isNull();assertThat(looseOrder.getContainerType()).isNull();
|
||||
}
|
||||
@Test void truckInquiryPreservesUnknownContainerWhileZeroActualCountsRemainRealUntilReceipt(){
|
||||
@Test void truckInquiryPreservesUnknownVehicleWhileZeroActualCountsRemainRealUntilReceipt(){
|
||||
var source=input("US_TRUCK");service.confirm(source,"op");
|
||||
assertThat(truckOrder.getContainerType()).isNull();assertThat(truckOrder.getPalletCount()).isEqualTo(source.expectedPalletCount());
|
||||
assertThat(truckOrder.getVehicleType()).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.getPackageCount()).isEqualTo(source.packageCount());assertThat(truckOrder.getDestinationContactName()).isNull();assertThat(truckOrder.getDeliveryAt()).isNull();
|
||||
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();
|
||||
assertThat(truckOrder.getReceivedPalletCount()).isEqualTo(2);assertThat(truckOrder.getVehicleType()).isNull();
|
||||
}
|
||||
@Test void incomingMustMatchCustomerWarehouseAndDestinationAndNeverBindSecondTailOrder(){
|
||||
service.confirm(input("LOCAL_LOOSEBOARD"),"op");
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.baiyue.lastleg.trucking;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrder;
|
||||
import com.baiyue.lastleg.trucking.mapper.*;
|
||||
import com.baiyue.lastleg.trucking.repository.UsTruckOrderRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class UsTruckOrderPersistenceTest {
|
||||
/** 使用模块基线和真实 Mapper 验证新字段保存、回读、更新与客户隔离。 */
|
||||
@Test
|
||||
void contactsVehicleAndLocalMinuteTimesRoundTripThroughActualMapper() throws Exception {
|
||||
var ds = new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "");
|
||||
String baseline;
|
||||
try (var input = getClass().getResourceAsStream("/db/lastleg.sql")) {
|
||||
baseline = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
int start = baseline.indexOf("CREATE TABLE IF NOT EXISTS ll_us_truck_order (");
|
||||
String ddl = baseline.substring(start, baseline.indexOf(';', start));
|
||||
// H2 使用真实字段定义,去掉 MySQL 专属的索引和表选项。
|
||||
ddl = ddl.replaceAll("(?m)^\\s*(?:UNIQUE KEY|KEY) [^\\n]*\\n?", "");
|
||||
ddl = ddl.substring(0, ddl.indexOf(") ENGINE=")) + ")";
|
||||
ddl = ddl.replaceAll(",\\s*\\)$", ")");
|
||||
new JdbcTemplate(ds).execute(ddl);
|
||||
var config = new MybatisConfiguration();
|
||||
config.setMapUnderscoreToCamelCase(true);
|
||||
config.addMapper(UsTruckOrderMapper.class);
|
||||
var factory = new MybatisSqlSessionFactoryBean();
|
||||
factory.setDataSource(ds); factory.setConfiguration(config);
|
||||
var mapper = new SqlSessionTemplate(factory.getObject()).getMapper(UsTruckOrderMapper.class);
|
||||
var repository = new UsTruckOrderRepository(mapper, mock(UsTruckOrderPalletMapper.class), mock(UsTruckOrderStatusHistoryMapper.class), mock(UsTruckOrderOperationMapper.class));
|
||||
var order = new UsTruckOrder();
|
||||
order.setId(1L); order.setCustomerId("C-1"); order.setOrderNo("TRUCK-TEST-1");
|
||||
order.setVehicleType("PALLET_15"); order.setOrigin("Los Angeles"); order.setDestination("Ontario");
|
||||
order.setOriginContactName("发货人"); order.setOriginContactPhone("+1 (555) 0100");
|
||||
order.setDestinationContactName("收货人"); order.setDestinationContactPhone("+1-555-0101");
|
||||
order.setPickupAt(LocalDateTime.of(2026, 10, 20, 9, 30)); order.setDeliveryAt(LocalDateTime.of(2026, 10, 20, 15, 45));
|
||||
order.setTotalWeightKg(BigDecimal.ZERO); order.setTotalVolumeCbm(BigDecimal.ZERO);
|
||||
order.setCurrency("USD"); order.setStatus("PENDING_REVIEW"); order.setIdempotencyKey("roundtrip"); order.setRequestHash("0".repeat(64));
|
||||
repository.insertOrder(order);
|
||||
var saved = repository.findNo("C-1", order.getOrderNo());
|
||||
assertEquals("PALLET_15", saved.getVehicleType());
|
||||
assertEquals("发货人", saved.getOriginContactName()); assertEquals("+1 (555) 0100", saved.getOriginContactPhone());
|
||||
assertEquals("收货人", saved.getDestinationContactName()); assertEquals("+1-555-0101", saved.getDestinationContactPhone());
|
||||
assertEquals(order.getPickupAt(), saved.getPickupAt()); assertEquals(order.getDeliveryAt(), saved.getDeliveryAt());
|
||||
assertEquals(0, saved.getPalletCount());
|
||||
assertNull(repository.findNo("C-2", order.getOrderNo())); assertNull(repository.findKey("C-2", "roundtrip"));
|
||||
saved.setVehicleType("PALLET_30"); saved.setDestinationContactPhone("+1-555-0102"); saved.setDeliveryAt(saved.getDeliveryAt().plusMinutes(15));
|
||||
repository.updateOrder(saved);
|
||||
var updated = repository.findKey("C-1", "roundtrip");
|
||||
assertEquals("PALLET_30", updated.getVehicleType()); assertEquals("+1-555-0102", updated.getDestinationContactPhone());
|
||||
assertEquals(LocalDateTime.of(2026, 10, 20, 16, 0), updated.getDeliveryAt());
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.baiyue.lastleg.trucking;
|
||||
|
||||
import com.baiyue.lastleg.trucking.dto.UsTruckOrderRequests;
|
||||
import jakarta.validation.Validation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.stream.Collectors;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class UsTruckOrderRequestsTest {
|
||||
/** 新增的七项需求必须由服务端校验,不能仅依赖页面星号。 */
|
||||
@Test
|
||||
void bothEndpointsVehicleAndTimesAreMandatory() {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
var request = new UsTruckOrderRequests.Create(null, "LA", null, "ONT", null, 0,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, null, "USD", " ", null, null, "", null, null, null, "required");
|
||||
var fields = factory.getValidator().validate(request).stream().map(v -> v.getPropertyPath().toString()).collect(Collectors.toSet());
|
||||
assertEquals(java.util.Set.of("vehicleType", "originContactName", "originContactPhone", "destinationContactName", "destinationContactPhone", "pickupAt", "deliveryAt"), fields);
|
||||
}
|
||||
}
|
||||
|
||||
/** 两种当前车型及国际电话原样受理,未知车型拒绝。 */
|
||||
@Test
|
||||
void onlyCurrentVehicleTypesAreAccepted() {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
for (String vehicle : java.util.List.of("PALLET_15", "PALLET_30", "OTHER")) {
|
||||
var request = new UsTruckOrderRequests.Create(vehicle, "LA", null, "ONT", null, 0,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, null, "USD", "A", "+1 (555) 0100", "B", "+1-555-0101",
|
||||
LocalDateTime.of(2026, 10, 20, 9, 30), LocalDateTime.of(2026, 10, 20, 15, 45), null, "vehicle");
|
||||
assertEquals(!vehicle.equals("OTHER"), factory.getValidator().validate(request).isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -18,7 +18,7 @@ class UsTruckOrderSchemaContractTest {
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order_status_history"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order_pallet"));
|
||||
assertTrue(sql.contains("pallet_count INT NOT NULL DEFAULT 1"));
|
||||
assertTrue(sql.contains("pallet_count INT NOT NULL DEFAULT 0"));
|
||||
assertTrue(sql.contains("received_pallet_count INT NOT NULL DEFAULT 0"));
|
||||
assertTrue(sql.contains("allocated_pallet_count INT NOT NULL DEFAULT 0"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order_operation"));
|
||||
@@ -50,7 +50,7 @@ class UsTruckOrderSchemaContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void poolUnknownContainerSurvivesOmittedNullInsertColumnInBothBaselines() throws Exception {
|
||||
void newTruckDemandFieldsExistAndRemovedInputsAreAbsentInBothBaselines() throws Exception {
|
||||
String baseline;
|
||||
try (var input = getClass().getResourceAsStream("/db/lastleg.sql")) {
|
||||
baseline = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
@@ -59,8 +59,9 @@ class UsTruckOrderSchemaContractTest {
|
||||
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");
|
||||
for (String column : java.util.List.of("vehicle_type", "origin_contact_name", "origin_contact_phone", "destination_contact_name", "destination_contact_phone", "pickup_at", "delivery_at")) assertTrue(block.contains(column));
|
||||
for (String removed : java.util.List.of("transport_type", "container_type", "requested_date")) org.junit.jupiter.api.Assertions.assertFalse(block.contains(removed));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -75,9 +75,9 @@ class UsTruckOrderControllerTest {
|
||||
|
||||
@Test
|
||||
void platformCreateUsesExplicitCustomerAndCreatePermission() {
|
||||
var request = new UsTruckOrderRequests.Create("PORT_PICKUP", "Los Angeles", null,
|
||||
"Ontario", null, "40HQ", 1, 0, java.math.BigDecimal.ZERO, java.math.BigDecimal.ZERO,
|
||||
null, "USD", "Alice", "+1-555-0100", java.time.LocalDate.now(), null, "create-key");
|
||||
var request = new UsTruckOrderRequests.Create("PALLET_15", "Los Angeles", null,
|
||||
"Ontario", null, 0, java.math.BigDecimal.ZERO, java.math.BigDecimal.ZERO,
|
||||
null, "USD", "Alice", "+1-555-0100", "Bob", "+1-555-0101", java.time.LocalDateTime.now(), java.time.LocalDateTime.now().plusHours(1), null, "create-key");
|
||||
platform.create(user(0L, "PLATFORM", Set.of(com.baiyue.lastleg.security.OverseasPermissions.TRUCK_CREATE)),
|
||||
"100", request);
|
||||
verify(service).create("100", "7", request);
|
||||
@@ -85,8 +85,8 @@ class UsTruckOrderControllerTest {
|
||||
|
||||
@Test
|
||||
void platformUpdateUsesExplicitCustomerAndEditPermission() {
|
||||
var request = new UsTruckOrderRequests.Update(0L, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null);
|
||||
var request = new UsTruckOrderRequests.Update(0L, "PALLET_30", null, null, null, null,
|
||||
null, null, null, null, null, "Alice", "100", "Bob", "200", java.time.LocalDateTime.now(), java.time.LocalDateTime.now().plusHours(1), null);
|
||||
platform.update(user(0L, "PLATFORM", Set.of(com.baiyue.lastleg.security.OverseasPermissions.TRUCK_EDIT)),
|
||||
"100", "TK1", request);
|
||||
verify(service).update("100", "TK1", "7", request);
|
||||
|
||||
+38
-20
@@ -54,18 +54,15 @@ class UsTruckOrderServiceTest {
|
||||
void setUp() {
|
||||
service = new UsTruckOrderService();
|
||||
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPool", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderQueries.class));
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "pallets", pallets);
|
||||
ReflectionTestUtils.setField(service, "histories", histories);
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
ReflectionTestUtils.setField(service, "repository", new com.baiyue.lastleg.trucking.repository.UsTruckOrderRepository(orders, pallets, histories, operations));
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", orderNoGenerator);
|
||||
order = order(UsTruckOrderService.PENDING_REVIEW, 0L, 2);
|
||||
lenient().when(orders.lockNo("100", "TK1")).thenAnswer(call -> order);
|
||||
when(orders.findNo("100", "TK1")).thenAnswer(call -> order);
|
||||
when(pallets.listOrder(anyString(), anyLong())).thenAnswer(call -> palletRows);
|
||||
lenient().when(orders.findNo("100", "TK1")).thenAnswer(call -> order);
|
||||
lenient().when(pallets.listOrder(anyString(), anyLong())).thenAnswer(call -> palletRows);
|
||||
lenient().when(histories.listOrder("100", 1L)).thenAnswer(call -> historyRows);
|
||||
when(histories.insert(any(UsTruckOrderStatusHistory.class))).thenAnswer(call -> {
|
||||
lenient().when(histories.insert(any(UsTruckOrderStatusHistory.class))).thenAnswer(call -> {
|
||||
historyRows.add(call.getArgument(0)); return 1;
|
||||
});
|
||||
lenient().when(pallets.insert(any(UsTruckOrderPallet.class))).thenAnswer(call -> {
|
||||
@@ -82,18 +79,39 @@ class UsTruckOrderServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createDefaultsTheContainerTypeAndRejectsRemovedTypes() {
|
||||
void createStoresBothContactsAndMinuteTimesWithoutInventingPallets() {
|
||||
when(orderNoGenerator.next(anyString(), anyString(), anyLong())).thenReturn("TK1");
|
||||
when(histories.listOrder(anyString(), anyLong())).thenReturn(historyRows);
|
||||
when(orders.insert(any(UsTruckOrder.class))).thenAnswer(call -> {
|
||||
order = call.getArgument(0);
|
||||
return 1;
|
||||
});
|
||||
when(orders.insert(any(UsTruckOrder.class))).thenAnswer(call -> { order = call.getArgument(0); return 1; });
|
||||
var request = create("PALLET_15", "new-truck");
|
||||
var detail = service.create("100", "operator", request);
|
||||
assertEquals("PALLET_15", detail.summary().vehicleType());
|
||||
assertEquals("Alice", detail.summary().originContactName());
|
||||
assertEquals("Bob", detail.summary().destinationContactName());
|
||||
assertEquals("+1-555-0101", detail.summary().destinationContactPhone());
|
||||
assertEquals(request.pickupAt(), detail.summary().pickupAt());
|
||||
assertEquals(request.deliveryAt(), detail.summary().deliveryAt());
|
||||
assertEquals(0, detail.summary().palletCount());
|
||||
assertTrue(detail.pallets().isEmpty());
|
||||
assertThrows(ResponseStatusException.class, () -> service.create("100", "operator", create("OTHER", "invalid")));
|
||||
}
|
||||
|
||||
var detail = service.create("100", "operator", create(null, "default-type"));
|
||||
@Test
|
||||
void firstActualReceiptAcceptsRealQuantityWithoutForecastOverage() {
|
||||
order.setPalletCount(0);
|
||||
service.review("100", "TK1", "operator", new UsTruckOrderRequests.Review(0L, "APPROVE", "review-new", null));
|
||||
assertTrue(palletRows.isEmpty());
|
||||
var received = service.receivePallets("100", "TK1", "receiver", new UsTruckOrderRequests.ReceivePallets(1L, 3, 6, List.of(2, 2, 2), "first-real", null));
|
||||
assertEquals(UsTruckOrderService.WAITING_CONSOLIDATION, received.summary().status());
|
||||
assertEquals(3, received.summary().receivedPalletCount());
|
||||
assertTrue(palletRows.stream().allMatch(row -> "RECEIVED".equals(row.getStatus())));
|
||||
}
|
||||
|
||||
assertEquals("40HQ", detail.summary().containerType());
|
||||
assertThrows(BizException.class, () -> service.create("100", "operator", create("40HC", "retired-type")));
|
||||
@Test
|
||||
void reversedTimesAreRejectedBeforeCreatingAnOrder() {
|
||||
var request = create("PALLET_30", "reversed");
|
||||
var invalid = new UsTruckOrderRequests.Create(request.vehicleType(), request.origin(), request.originAddress(), request.destination(), request.destinationAddress(), request.packageCount(), request.totalWeightKg(), request.totalVolumeCbm(), request.budgetAmount(), request.currency(), request.originContactName(), request.originContactPhone(), request.destinationContactName(), request.destinationContactPhone(), request.deliveryAt(), request.pickupAt(), request.remark(), request.idempotencyKey());
|
||||
assertThrows(ResponseStatusException.class, () -> service.create("100", "operator", invalid));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -188,16 +206,16 @@ class UsTruckOrderServiceTest {
|
||||
return new UsTruckOrderRequests.Command(version, key, key);
|
||||
}
|
||||
|
||||
private UsTruckOrderRequests.Create create(String containerType, String key) {
|
||||
return new UsTruckOrderRequests.Create("PORT_PICKUP", "Los Angeles", null, "Ontario", null,
|
||||
containerType, 1, 0, BigDecimal.ZERO, BigDecimal.ZERO, null, "USD", "Alice",
|
||||
"+1-555-0100", LocalDate.now().plusDays(1), null, key);
|
||||
private UsTruckOrderRequests.Create create(String vehicleType, String key) {
|
||||
return new UsTruckOrderRequests.Create(vehicleType, "Los Angeles", null, "Ontario", null,
|
||||
0, BigDecimal.ZERO, BigDecimal.ZERO, null, "USD", "Alice", "+1-555-0100", "Bob", "+1-555-0101",
|
||||
LocalDateTime.of(2026, 10, 20, 9, 30), LocalDateTime.of(2026, 10, 20, 15, 45), null, key);
|
||||
}
|
||||
|
||||
private UsTruckOrder order(String status, long version, int pallets) {
|
||||
UsTruckOrder value = new UsTruckOrder();
|
||||
value.setId(1L); value.setCustomerId("100"); value.setOrderNo("TK1");
|
||||
value.setTransportType("PORT_PICKUP"); value.setOrigin("Los Angeles"); value.setDestination("Ontario");
|
||||
value.setVehicleType("PALLET_15"); value.setOrigin("Los Angeles"); value.setDestination("Ontario");
|
||||
value.setPalletCount(pallets); value.setPackageCount(0); value.setReceivedPalletCount(0);
|
||||
value.setAllocatedPalletCount(0); value.setStatus(status); value.setVersion(version);
|
||||
value.setCreatedAt(LocalDateTime.now()); value.setUpdatedAt(value.getCreatedAt());
|
||||
|
||||
Reference in New Issue
Block a user