feat: complete phase 5 overseas fulfillment contracts
This commit is contained in:
+1
-1
@@ -35,7 +35,7 @@ class SmartDispatchOpenApiContractTest {
|
||||
yaml.indexOf("name: orderNo", yaml.indexOf("name: planIds")));
|
||||
assertTrue(planIdsParameter.contains("required: false"));
|
||||
String dispatchContract = yaml.substring(yaml.indexOf("/api/platform/v1/last-leg/dispatches:"),
|
||||
yaml.indexOf("/api/platform/v1/last-leg/overseas-procurements:"));
|
||||
yaml.indexOf("/api/platform/v1/last-leg/outbound-orders:"));
|
||||
assertFalse(dispatchContract.contains("name: customerId"));
|
||||
assertFalse(yaml.contains("/api/platform/v1/last-leg/dispatches/{dispatchNo}/depart:"));
|
||||
}
|
||||
|
||||
+179
-112
@@ -1,10 +1,12 @@
|
||||
package com.baiyue.lastleg.dispatch.pda.service;
|
||||
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundOrder;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchPalletLockMapper;
|
||||
import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaRequests;
|
||||
import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaResponses;
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaOperation;
|
||||
@@ -13,7 +15,10 @@ import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaTask;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaOperationMapper;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickMapper;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaTaskMapper;
|
||||
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -21,21 +26,52 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
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.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DispatchPdaWorkflowServiceTest {
|
||||
@Mock private DispatchOrderMapper orders;
|
||||
@Mock private DispatchPdaOperationMapper operations;
|
||||
@Mock private OutboundOrderMapper outboundOrders;
|
||||
@Mock private DispatchItemMapper items;
|
||||
@Mock private DispatchPdaTaskMapper tasks;
|
||||
@Mock private DispatchPdaPickMapper picks;
|
||||
@Mock DispatchOrderMapper orders;
|
||||
@Mock OutboundOrderMapper outboundOrders;
|
||||
@Mock DispatchItemMapper items;
|
||||
@Mock DispatchPdaTaskMapper tasks;
|
||||
@Mock DispatchPdaPickMapper picks;
|
||||
@Mock DispatchPdaOperationMapper operations;
|
||||
@Mock DispatchPalletLockMapper palletLocks;
|
||||
@Mock InventoryPositionMapper positions;
|
||||
@Mock InventoryCommandService inventory;
|
||||
@Mock DispatchPickOrderAttributionService attribution;
|
||||
private DispatchPdaWorkflowService service;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new DispatchPdaWorkflowService();
|
||||
objectMapper = new ObjectMapper();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "tasks", tasks);
|
||||
ReflectionTestUtils.setField(service, "picks", picks);
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
ReflectionTestUtils.setField(service, "palletLocks", palletLocks);
|
||||
ReflectionTestUtils.setField(service, "positions", positions);
|
||||
ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
ReflectionTestUtils.setField(service, "pickAttribution", attribution);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", objectMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void labelsTransferredPalletsWithOriginalAndCurrentFbaWarehouses() {
|
||||
@@ -45,126 +81,157 @@ class DispatchPdaWorkflowServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushRequiresVehiclePlateBeforeCreatingPdaTask() {
|
||||
DispatchPdaWorkflowService service = new DispatchPdaWorkflowService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
DispatchOrder order = new DispatchOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1"); order.setVehiclePlate(" ");
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(order);
|
||||
void taskListUsesOnlyTokenBoundWarehouseAndResolvedCustomer() {
|
||||
DispatchOrder order = order("100", "DSP-1", "WH-1", "PENDING_DEPARTURE");
|
||||
DispatchPdaTask task = task(11L, "100", "WH-1", "PDA_PENDING");
|
||||
when(tasks.listByWarehouse("WH-1", null)).thenReturn(List.of(task));
|
||||
when(orders.selectById(1L)).thenReturn(order);
|
||||
when(items.findByDispatchOrder("100", 1L)).thenReturn(List.of());
|
||||
|
||||
List<DispatchPdaResponses.Task> result = service.list("WH-1", null);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("DSP-1", result.get(0).dispatchNo());
|
||||
verify(tasks).listByWarehouse("WH-1", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailRejectsDispatchFromAnotherWarehouseBeforeCustomerLookup() {
|
||||
when(orders.findGlobalByDispatchNo("DSP-1"))
|
||||
.thenReturn(order("100", "DSP-1", "WH-2", "PENDING_DEPARTURE"));
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.push("100", "PC1", new DispatchPdaRequests.Push("push-1", null, null)));
|
||||
() -> service.task("WH-1", "DSP-1"));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
assertEquals(404, exception.getStatusCode().value());
|
||||
verify(tasks, never()).findByDispatch(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushRequiresWarehouseSelection() {
|
||||
DispatchPdaWorkflowService service = new DispatchPdaWorkflowService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
DispatchOrder order = new DispatchOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1");
|
||||
order.setStatus("PENDING_DEPARTURE"); order.setVehiclePlate("CA-100");
|
||||
order.setOverseasWarehouseId("WH-1");
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(order);
|
||||
void exactPickRetryReturnsPersistedResultWithoutInventoryDeduction() throws Exception {
|
||||
DispatchOrder order = order("100", "DSP-1", "WH-1", "PICKING");
|
||||
order.setVehiclePlate("CA-100");
|
||||
DispatchPdaTask task = task(11L, "100", "WH-1", "PICKING");
|
||||
task.setOperatorId("7");
|
||||
DispatchItem item = item(21L, "100", 5);
|
||||
DispatchPdaRequests.Pick request = new DispatchPdaRequests.Pick(31L, 2, 2, "pick-1", null);
|
||||
DispatchPdaResponses.Picked persisted = new DispatchPdaResponses.Picked(21L, 31L, 2, 2, false, false);
|
||||
DispatchPdaOperation replay = operation("PICK", request, persisted);
|
||||
when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order);
|
||||
when(tasks.lockByDispatch("100", 1L)).thenReturn(task);
|
||||
when(items.selectById(21L)).thenReturn(item);
|
||||
when(operations.findKey("100", "pick-1")).thenReturn(replay);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.push("100", "PC1", new DispatchPdaRequests.Push("push-1", null, null)));
|
||||
DispatchPdaResponses.Picked result = service.pick("WH-1", "DSP-1", 21L, "7", "Picker", request);
|
||||
|
||||
assertEquals(400, exception.getStatusCode().value());
|
||||
assertEquals(2, result.packages());
|
||||
verify(inventory, never()).allocate(any(), any(), any());
|
||||
verify(picks, never()).insert((DispatchPdaPick) any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void departRequiresVehiclePlate() {
|
||||
DispatchPdaWorkflowService service = new DispatchPdaWorkflowService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
DispatchOrder order = new DispatchOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1");
|
||||
order.setStatus("READY_TO_DEPART"); order.setVehiclePlate(" ");
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(order);
|
||||
void completionRejectsPartialPickingWithoutStatusDrift() {
|
||||
DispatchOrder order = order("100", "DSP-1", "WH-1", "PICKING");
|
||||
order.setVehiclePlate("CA-100");
|
||||
DispatchPdaTask task = task(11L, "100", "WH-1", "PICKING");
|
||||
DispatchItem item = item(21L, "100", 6);
|
||||
DispatchPdaPick pick = new DispatchPdaPick(); pick.setPackages(3L);
|
||||
when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
when(operations.findKey("100", "depart-1")).thenReturn(null);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.depart("100", "PC1", "operator", new DispatchPdaRequests.Depart("depart-1", null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void departRequiresPendingOutboundOrder() {
|
||||
DispatchPdaWorkflowService service = new DispatchPdaWorkflowService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
DispatchOrder order = new DispatchOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1");
|
||||
order.setStatus("READY_TO_DEPART"); order.setVehiclePlate("CA-100");
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
when(operations.findKey("100", "depart-1")).thenReturn(null);
|
||||
when(outboundOrders.lockByDispatch("100", 1L)).thenReturn(null);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.depart("100", "PC1", "operator", new DispatchPdaRequests.Depart("depart-1", null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void departedRequestReplaysAfterStatusChanged() throws Exception {
|
||||
DispatchPdaWorkflowService service = new DispatchPdaWorkflowService();
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", objectMapper);
|
||||
DispatchOrder order = new DispatchOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1"); order.setStatus("DEPARTED");
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
DispatchPdaOperation operation = new DispatchPdaOperation();
|
||||
operation.setOperationType("DEPART"); operation.setStatus("COMPLETED");
|
||||
operation.setResultJson(objectMapper.writeValueAsString(new DispatchPdaResponses.Departed("PC1", "DEPARTED", 12, false)));
|
||||
when(operations.findKey("100", "depart-1")).thenReturn(operation);
|
||||
|
||||
// This exact key/hash pair models the persisted first execution result.
|
||||
DispatchPdaRequests.Depart request = new DispatchPdaRequests.Depart("depart-1", null);
|
||||
operation.setRequestHash(java.util.HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256")
|
||||
.digest(objectMapper.writeValueAsString(request).getBytes(java.nio.charset.StandardCharsets.UTF_8))));
|
||||
DispatchPdaResponses.Departed result = service.depart("100", "PC1", "operator", request);
|
||||
|
||||
assertEquals("DEPARTED", result.status());
|
||||
assertEquals(true, result.replayed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completingAnAutomaticallyCompletedTaskReturnsIdempotentSuccess() {
|
||||
DispatchPdaWorkflowService service = new DispatchPdaWorkflowService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "tasks", tasks);
|
||||
ReflectionTestUtils.setField(service, "picks", picks);
|
||||
DispatchOrder order = new DispatchOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1");
|
||||
order.setStatus("READY_TO_DEPART"); order.setVehiclePlate("CA-100");
|
||||
DispatchPdaTask task = new DispatchPdaTask();
|
||||
task.setId(2L); task.setStatus("COMPLETED");
|
||||
DispatchItem item = new DispatchItem(); item.setPackageCount(6);
|
||||
DispatchPdaPick pick = new DispatchPdaPick(); pick.setPackages(6L);
|
||||
DispatchPdaRequests.CompletePicking request = new DispatchPdaRequests.CompletePicking("complete-1", "normal");
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
when(operations.findKey("100", "complete-1")).thenReturn(null);
|
||||
when(tasks.lockByDispatch("100", 1L)).thenReturn(task);
|
||||
when(items.findByDispatchOrder("100", 1L)).thenReturn(List.of(item));
|
||||
when(picks.listTask("100", 2L)).thenReturn(List.of(pick));
|
||||
when(picks.listTask("100", 11L)).thenReturn(List.of(pick));
|
||||
|
||||
DispatchPdaResponses.PickingCompleted result = service.completePicking("100", "PC1", request);
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.completePicking("WH-1", "DSP-1", new DispatchPdaRequests.CompletePicking("done-1")));
|
||||
|
||||
assertEquals("READY_TO_DEPART", result.status());
|
||||
assertEquals(6, result.pickedPackages());
|
||||
assertEquals(true, result.replayed());
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
assertEquals("PICKING", order.getStatus());
|
||||
verify(orders, never()).updateById((DispatchOrder) any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void automaticallyCompletedTaskPersistsFirstCompleteKeyThenReplaysIt() throws Exception {
|
||||
DispatchOrder order = order("100", "DSP-1", "WH-1", "READY_TO_DEPART");
|
||||
order.setVehiclePlate("CA-100");
|
||||
DispatchPdaTask task = task(11L, "100", "WH-1", "COMPLETED");
|
||||
DispatchItem item = item(21L, "100", 6);
|
||||
DispatchPdaPick pick = new DispatchPdaPick(); pick.setPackages(6L);
|
||||
DispatchPdaRequests.CompletePicking request = new DispatchPdaRequests.CompletePicking("done-1");
|
||||
when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
when(tasks.lockByDispatch("100", 1L)).thenReturn(task);
|
||||
when(items.findByDispatchOrder("100", 1L)).thenReturn(List.of(item));
|
||||
when(picks.listTask("100", 11L)).thenReturn(List.of(pick));
|
||||
|
||||
DispatchPdaResponses.PickingCompleted first = service.completePicking("WH-1", "DSP-1", request);
|
||||
assertFalse(first.replayed());
|
||||
verify(operations).insert((DispatchPdaOperation) any());
|
||||
|
||||
DispatchPdaOperation replay = operation("COMPLETE_PICKING", request, first);
|
||||
when(operations.findKey("100", "done-1")).thenReturn(replay);
|
||||
DispatchPdaResponses.PickingCompleted second = service.completePicking("WH-1", "DSP-1", request);
|
||||
assertTrue(second.replayed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void departRequiresFullyCompletedPickingAndPendingOutbound() {
|
||||
DispatchOrder order = order("100", "DSP-1", "WH-1", "PARTIAL_READY_TO_DEPART");
|
||||
order.setVehiclePlate("CA-100");
|
||||
OutboundOrder outbound = new OutboundOrder(); outbound.setStatus("WAIT_OUTBOUND");
|
||||
when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
when(outboundOrders.lockByDispatch("100", 1L)).thenReturn(outbound);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.depart("WH-1", "DSP-1", "7", new DispatchPdaRequests.Depart("depart-1", null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(outboundOrders, never()).updateById((OutboundOrder) any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void exactDepartRetryReturnsPersistedResultAfterStatusChanged() throws Exception {
|
||||
DispatchOrder order = order("100", "DSP-1", "WH-1", "DEPARTED");
|
||||
DispatchPdaRequests.Depart request = new DispatchPdaRequests.Depart("depart-1", null);
|
||||
DispatchPdaResponses.Departed persisted = new DispatchPdaResponses.Departed("DSP-1", "DEPARTED", 12, false);
|
||||
when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
when(operations.findKey("100", "depart-1")).thenReturn(operation("DEPART", request, persisted));
|
||||
|
||||
DispatchPdaResponses.Departed result = service.depart("WH-1", "DSP-1", "7", request);
|
||||
|
||||
assertEquals("DEPARTED", result.status());
|
||||
assertTrue(result.replayed());
|
||||
}
|
||||
|
||||
private DispatchPdaOperation operation(String type, Object request, Object result) throws Exception {
|
||||
DispatchPdaOperation value = new DispatchPdaOperation();
|
||||
value.setOperationType(type); value.setStatus("COMPLETED");
|
||||
value.setRequestHash(HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
||||
.digest(objectMapper.writeValueAsString(request).getBytes(StandardCharsets.UTF_8))));
|
||||
value.setResultJson(objectMapper.writeValueAsString(result));
|
||||
return value;
|
||||
}
|
||||
|
||||
private static DispatchOrder order(String customerId, String dispatchNo, String warehouseId, String status) {
|
||||
DispatchOrder value = new DispatchOrder();
|
||||
value.setId(1L); value.setCustomerId(customerId); value.setDispatchNo(dispatchNo);
|
||||
value.setOverseasWarehouseId(warehouseId); value.setStatus(status);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static DispatchPdaTask task(Long id, String customerId, String warehouseId, String status) {
|
||||
DispatchPdaTask value = new DispatchPdaTask();
|
||||
value.setId(id); value.setCustomerId(customerId); value.setDispatchOrderId(1L);
|
||||
value.setTaskNo("DPDA-1"); value.setWarehouseId(warehouseId); value.setStatus(status);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static DispatchItem item(Long id, String customerId, int packages) {
|
||||
DispatchItem value = new DispatchItem();
|
||||
value.setId(id); value.setCustomerId(customerId); value.setDispatchOrderId(1L);
|
||||
value.setOverseasWarehouseId("WH-1"); value.setWarehousePalletId(41L); value.setPackageCount(packages);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+54
-8
@@ -3,6 +3,7 @@ package com.baiyue.lastleg.dispatch.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.lastleg.dispatch.dto.DispatchRequests;
|
||||
import com.baiyue.lastleg.dispatch.dto.DispatchResponse;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchPalletLock;
|
||||
@@ -13,6 +14,8 @@ import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchPalletLockMapper;
|
||||
import com.baiyue.lastleg.dispatch.model.DispatchPalletCandidate;
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaTask;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaTaskMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -45,6 +48,7 @@ class DispatchServiceTest {
|
||||
@Mock DispatchCandidateMapper candidates;
|
||||
@Mock DispatchDraftMapper drafts;
|
||||
@Mock DispatchDraftContainerMapper draftContainers;
|
||||
@Mock DispatchPdaTaskMapper pdaTasks;
|
||||
private DispatchService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -56,6 +60,7 @@ class DispatchServiceTest {
|
||||
ReflectionTestUtils.setField(service, "candidates", candidates);
|
||||
ReflectionTestUtils.setField(service, "drafts", drafts);
|
||||
ReflectionTestUtils.setField(service, "draftContainers", draftContainers);
|
||||
ReflectionTestUtils.setField(service, "pdaTasks", pdaTasks);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator());
|
||||
lenient().when(items.findByDispatchOrder(any(), any())).thenReturn(List.of());
|
||||
}
|
||||
@@ -73,6 +78,11 @@ class DispatchServiceTest {
|
||||
assertEquals("ONT8", result.route());
|
||||
ArgumentCaptor<DispatchOrder> dispatch = ArgumentCaptor.forClass(DispatchOrder.class);
|
||||
verify(orders).insert(dispatch.capture());
|
||||
ArgumentCaptor<DispatchPdaTask> pdaTask = ArgumentCaptor.forClass(DispatchPdaTask.class);
|
||||
verify(pdaTasks).insert(pdaTask.capture());
|
||||
assertEquals(dispatch.getValue().getId(), pdaTask.getValue().getDispatchOrderId());
|
||||
assertEquals("WH-1", pdaTask.getValue().getWarehouseId());
|
||||
assertEquals("PDA_PENDING", pdaTask.getValue().getStatus());
|
||||
assertEquals(1, dispatch.getValue().getPalletCount());
|
||||
verify(locks).insert(any(DispatchPalletLock.class));
|
||||
verify(items).insert(any(DispatchItem.class));
|
||||
@@ -204,20 +214,35 @@ class DispatchServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void editCannotBypassPdaManagedDispatchStatus() {
|
||||
void platformEditResolvesCustomerFromGlobalDispatchNumberAndPreservesStatus() {
|
||||
DispatchOrder dispatch = new DispatchOrder();
|
||||
dispatch.setId(1L); dispatch.setCustomerId("100"); dispatch.setDispatchNo("PC1");
|
||||
dispatch.setStatus(DispatchService.PENDING_DEPARTURE);
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(dispatch);
|
||||
when(orders.lockById("100", 1L)).thenReturn(dispatch);
|
||||
when(orders.lockGlobalByDispatchNo("PC1")).thenReturn(dispatch);
|
||||
DispatchRequests.Update request = new DispatchRequests.Update("ABC-1", "Driver", null, null, null,
|
||||
null, null, null, LocalDateTime.now());
|
||||
|
||||
DispatchResponse result = service.update("PC1", request);
|
||||
|
||||
assertEquals(DispatchService.PENDING_DEPARTURE, dispatch.getStatus());
|
||||
assertEquals("ABC-1", result.vehiclePlate());
|
||||
verify(orders).updateById(dispatch);
|
||||
verify(items).findByDispatchOrder("100", 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformEditRejectsAnExecutedDispatchWithoutWriting() {
|
||||
DispatchOrder dispatch = new DispatchOrder();
|
||||
dispatch.setId(1L); dispatch.setCustomerId("100"); dispatch.setDispatchNo("PC1");
|
||||
dispatch.setStatus("DEPARTED");
|
||||
when(orders.lockGlobalByDispatchNo("PC1")).thenReturn(dispatch);
|
||||
DispatchRequests.Update request = new DispatchRequests.Update(null, null, null, null, null,
|
||||
LocalDateTime.now(), null, null, null, null, "COMPLETED");
|
||||
null, null, null, null);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.update("100", "PC1", request));
|
||||
() -> service.update("PC1", request));
|
||||
|
||||
assertEquals(400, exception.getStatusCode().value());
|
||||
assertEquals(DispatchService.PENDING_DEPARTURE, dispatch.getStatus());
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(orders, never()).updateById(any(DispatchOrder.class));
|
||||
}
|
||||
|
||||
@@ -255,13 +280,34 @@ class DispatchServiceTest {
|
||||
void platformDetailUsesCustomerFromMatchedDispatch() {
|
||||
DispatchOrder dispatch = new DispatchOrder();
|
||||
dispatch.setId(1L); dispatch.setCustomerId("200"); dispatch.setDispatchNo("PC1");
|
||||
when(orders.findAcrossCustomersByDispatchNo("PC1")).thenReturn(List.of(dispatch));
|
||||
when(orders.findGlobalByDispatchNo("PC1")).thenReturn(dispatch);
|
||||
|
||||
service.platformDetail("PC1");
|
||||
|
||||
verify(items).findByDispatchOrder("200", 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalCancelIsIdempotentAndReleasesOnlyTheResolvedCustomerLocks() {
|
||||
DispatchOrder dispatch = new DispatchOrder();
|
||||
dispatch.setId(1L); dispatch.setCustomerId("200"); dispatch.setDispatchNo("PC1");
|
||||
dispatch.setStatus(DispatchService.PENDING_DEPARTURE);
|
||||
DispatchPalletLock lock = new DispatchPalletLock();
|
||||
lock.setStatus("ACTIVE"); lock.setActiveKey("PALLET:1");
|
||||
when(orders.lockGlobalByDispatchNo("PC1")).thenReturn(dispatch);
|
||||
when(locks.selectList(any())).thenReturn(List.of(lock));
|
||||
|
||||
service.cancel("PC1");
|
||||
|
||||
assertEquals(DispatchService.CANCELLED, dispatch.getStatus());
|
||||
assertEquals("RELEASED", lock.getStatus());
|
||||
assertEquals(null, lock.getActiveKey());
|
||||
verify(locks).updateById(lock);
|
||||
|
||||
service.cancel("PC1");
|
||||
verify(locks, org.mockito.Mockito.times(1)).selectList(any());
|
||||
}
|
||||
|
||||
private DispatchPalletCandidate candidate(long planPalletId, Long warehousePalletId, String destination) {
|
||||
return candidate(planPalletId, warehousePalletId, destination, 12);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user