feat: complete tail order controls and terminal drayage
This commit is contained in:
+161
@@ -0,0 +1,161 @@
|
||||
package com.baiyue.lastleg.service;
|
||||
|
||||
import com.baiyue.lastleg.dto.LastlegOrderCommandDtos;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderCommandRecord;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderHoldHistory;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderManualNodeHistory;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderManualNode;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderCommandRecordMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderHoldHistoryMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderManualNodeHistoryMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderManualNodeMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderOperationLogMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LastlegOrderCommandServiceTest {
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderCommandRecordMapper commands;
|
||||
@Mock LastlegOrderHoldHistoryMapper holdHistory;
|
||||
@Mock LastlegOrderManualNodeMapper manualNodes;
|
||||
@Mock LastlegOrderManualNodeHistoryMapper manualNodeHistory;
|
||||
@Mock LastlegOrderOperationLogMapper operationLogs;
|
||||
@Mock InboundOrderDetainedOutboxService detainedOutbox;
|
||||
LastlegOrderCommandService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new LastlegOrderCommandService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "commands", commands);
|
||||
ReflectionTestUtils.setField(service, "holdHistory", holdHistory);
|
||||
ReflectionTestUtils.setField(service, "manualNodes", manualNodes);
|
||||
ReflectionTestUtils.setField(service, "manualNodeHistory", manualNodeHistory);
|
||||
ReflectionTestUtils.setField(service, "operationLogs", operationLogs);
|
||||
ReflectionTestUtils.setField(service, "detainedOutbox", detainedOutbox);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", JsonMapper.builder().findAndAddModules().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void holdsOnlySelectedOrderWithVersionAndImmutableHistory() {
|
||||
LastlegOrder order = order(11L, 3);
|
||||
when(orders.selectById(11L)).thenReturn(order);
|
||||
when(orders.lockById("100", 11L)).thenReturn(order);
|
||||
when(orders.updateHold(eq("100"), eq(11L), eq(true), eq(true), eq("海关查验"), eq(3),
|
||||
any(LocalDateTime.class))).thenReturn(1);
|
||||
|
||||
var response = service.hold("100", 11L, "hold-11",
|
||||
new LastlegOrderCommandDtos.HoldRequest(true, true, "海关查验", 3), "900", "管理员");
|
||||
|
||||
assertTrue(response.held());
|
||||
assertTrue(response.chargeable());
|
||||
assertEquals(4, response.version());
|
||||
verify(orders).updateHold(eq("100"), eq(11L), eq(true), eq(true), eq("海关查验"), eq(3), any());
|
||||
verify(holdHistory).insert(any(LastlegOrderHoldHistory.class));
|
||||
verify(commands).insert(any(LastlegOrderCommandRecord.class));
|
||||
verify(orders, never()).lockContainerOrders(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsStaleHoldWithoutAnyWrite() {
|
||||
LastlegOrder order = order(11L, 4);
|
||||
when(orders.selectById(11L)).thenReturn(order);
|
||||
when(orders.lockById("100", 11L)).thenReturn(order);
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.hold("100", 11L,
|
||||
"hold-stale", new LastlegOrderCommandDtos.HoldRequest(true, false, "待核对", 3), "900", "管理员"));
|
||||
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
verify(orders, never()).updateHold(anyString(), anyLong(), anyBoolean(), anyBoolean(),
|
||||
nullable(String.class), anyInt(), any(LocalDateTime.class));
|
||||
verify(commands, never()).insert(any(LastlegOrderCommandRecord.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void savesExactEightRowManualSnapshotInFrozenDisplayOrder() {
|
||||
LastlegOrder order = order(12L, 0);
|
||||
when(orders.selectById(12L)).thenReturn(order);
|
||||
when(orders.lockById("100", 12L)).thenReturn(order);
|
||||
when(orders.updateManualNodes(eq("100"), eq(12L), any(), any(), any(), eq(0), any())).thenReturn(1);
|
||||
LocalDateTime filled = LocalDateTime.of(2026, 9, 8, 10, 0);
|
||||
List<LastlegOrderCommandDtos.ManualNode> nodes = List.of(
|
||||
node("UNLOADED", null), node("PICKUP_APPOINTED", null),
|
||||
node("ACTUAL_ARRIVAL_WAITING_UNLOAD", null), node("OVERSEAS_CUSTOMS_COMPLETED", null),
|
||||
node("ESTIMATED_ARRIVAL", filled), node("ACTUAL_DEPARTURE", null),
|
||||
node("ESTIMATED_DEPARTURE", null), node("DOMESTIC_CUSTOMS_COMPLETED", null));
|
||||
|
||||
var response = service.saveManualNodes("100", 12L, "nodes-12",
|
||||
new LastlegOrderCommandDtos.ManualNodesRequest(0, nodes), "900", "管理员");
|
||||
|
||||
assertTrue(response.manualOverrideEnabled());
|
||||
assertEquals(8, response.nodes().size());
|
||||
assertEquals("DOMESTIC_CUSTOMS_COMPLETED", response.nodes().get(0).nodeCode());
|
||||
assertEquals("UNLOADED", response.nodes().get(7).nodeCode());
|
||||
ArgumentCaptor<LastlegOrderManualNode> rows = ArgumentCaptor.forClass(LastlegOrderManualNode.class);
|
||||
verify(manualNodes, org.mockito.Mockito.times(8)).insert(rows.capture());
|
||||
assertEquals(8, rows.getAllValues().size());
|
||||
verify(manualNodeHistory).insert(any(LastlegOrderManualNodeHistory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsIncompleteOrFullyEmptyManualSnapshot() {
|
||||
LastlegOrder order = order(12L, 0);
|
||||
when(orders.selectById(12L)).thenReturn(order);
|
||||
when(orders.lockById("100", 12L)).thenReturn(order);
|
||||
List<LastlegOrderCommandDtos.ManualNode> nodes = List.of(
|
||||
node("DOMESTIC_CUSTOMS_COMPLETED", null), node("ESTIMATED_DEPARTURE", null),
|
||||
node("ACTUAL_DEPARTURE", null), node("ESTIMATED_ARRIVAL", null),
|
||||
node("OVERSEAS_CUSTOMS_COMPLETED", null), node("ACTUAL_ARRIVAL_WAITING_UNLOAD", null),
|
||||
node("PICKUP_APPOINTED", null), node("UNLOADED", null));
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () ->
|
||||
service.saveManualNodes("100", 12L, "nodes-empty",
|
||||
new LastlegOrderCommandDtos.ManualNodesRequest(0, nodes), "900", "管理员"));
|
||||
|
||||
assertEquals(400, error.getStatusCode().value());
|
||||
verify(orders, never()).updateManualNodes(anyString(), anyLong(), nullable(LocalDateTime.class),
|
||||
nullable(LocalDateTime.class), nullable(LocalDateTime.class), anyInt(), any(LocalDateTime.class));
|
||||
}
|
||||
|
||||
private LastlegOrderCommandDtos.ManualNode node(String code, LocalDateTime time) {
|
||||
return new LastlegOrderCommandDtos.ManualNode(code, time);
|
||||
}
|
||||
|
||||
private LastlegOrder order(long id, int version) {
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setId(id);
|
||||
order.setCustomerId("100");
|
||||
order.setOrderNo("LL-" + id);
|
||||
order.setCurrentStatus("PENDING_ASSIGNMENT");
|
||||
order.setDetained(false);
|
||||
order.setVersion(version);
|
||||
return order;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderFeeMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderOperationLogMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderManualNodeMapper;
|
||||
import com.baiyue.lastleg.mapper.ReceivingWarehouseOperationMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -54,6 +55,7 @@ class LastlegOrderServiceTest {
|
||||
@Mock LastlegOrderStatusHistoryMapper histories;
|
||||
@Mock LastlegOrderFeeMapper fees;
|
||||
@Mock LastlegOrderOperationLogMapper operations;
|
||||
@Mock LastlegOrderManualNodeMapper manualNodes;
|
||||
@Mock InboundPlanCartonMapper cartons;
|
||||
@Mock FbaWarehouseValidator fbaWarehouses;
|
||||
@Mock InventoryCommandService inventory;
|
||||
@@ -70,6 +72,7 @@ class LastlegOrderServiceTest {
|
||||
ReflectionTestUtils.setField(service, "historyMapper", histories);
|
||||
ReflectionTestUtils.setField(service, "feeMapper", fees);
|
||||
ReflectionTestUtils.setField(service, "operationLogMapper", operations);
|
||||
ReflectionTestUtils.setField(service, "manualNodeMapper", manualNodes);
|
||||
ReflectionTestUtils.setField(service, "cartonMapper", cartons);
|
||||
ReflectionTestUtils.setField(service, "fbaWarehouseValidator", fbaWarehouses);
|
||||
lenient().when(fbaWarehouses.required(any(), any(), any())).thenAnswer(invocation ->
|
||||
@@ -133,6 +136,7 @@ class LastlegOrderServiceTest {
|
||||
when(histories.selectList(any())).thenReturn(List.of());
|
||||
when(fees.selectList(any())).thenReturn(List.of());
|
||||
when(operations.selectList(any())).thenReturn(List.of());
|
||||
when(manualNodes.selectList(any())).thenReturn(List.of());
|
||||
|
||||
var response = service.detail("100", 11L);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user