暂存提交
This commit is contained in:
+33
@@ -0,0 +1,33 @@
|
||||
package com.baiyue.lastleg.dispatch;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class DispatchSchemaContractTest {
|
||||
@Test
|
||||
void baselineDefinesDispatchHeadersItemsAndActivePalletLock() throws Exception {
|
||||
String sql = Files.readString(Path.of("src/main/resources/db/lastleg.sql"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_dispatch_order"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_dispatch_item"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_dispatch_pallet_lock"));
|
||||
assertTrue(sql.contains("uk_ll_dispatch_active_pallet"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_dispatch_pda_task"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_dispatch_pda_pick"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_dispatch_pda_operation"));
|
||||
assertTrue(sql.contains("active_key"));
|
||||
assertTrue(sql.contains("order_refs"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCandidatesExcludePalletsReservedByWarehouseTransfers() throws Exception {
|
||||
String mapper = Files.readString(Path.of(
|
||||
"src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchCandidateMapper.java"));
|
||||
assertTrue(mapper.contains("ll_warehouse_transfer_inventory_allocation"));
|
||||
assertTrue(mapper.contains("ta.status='RESERVED'"));
|
||||
assertTrue(mapper.contains("tp.pallet_id=p.warehouse_pallet_id"));
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.baiyue.lastleg.dispatch.pda.service;
|
||||
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper;
|
||||
import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaRequests;
|
||||
import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaResponses;
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaOperation;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaOperationMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DispatchPdaWorkflowServiceTest {
|
||||
@Mock private DispatchOrderMapper orders;
|
||||
@Mock private DispatchPdaOperationMapper operations;
|
||||
@Mock private OutboundOrderMapper outboundOrders;
|
||||
|
||||
@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);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.push("100", "PC1", new DispatchPdaRequests.Push("push-1", null, null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.push("100", "PC1", new DispatchPdaRequests.Push("push-1", null, null)));
|
||||
|
||||
assertEquals(400, exception.getStatusCode().value());
|
||||
}
|
||||
|
||||
@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);
|
||||
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());
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.baiyue.lastleg.dispatch.pda.service;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaPick;
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaPickOrder;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.InboundStockReceipt;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
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 java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DispatchPickOrderAttributionServiceTest {
|
||||
@Mock private DispatchPdaPickOrderMapper attributions;
|
||||
@Mock private LastlegOrderMapper orders;
|
||||
@Mock private LastlegOrderStatusHistoryMapper history;
|
||||
|
||||
@Test
|
||||
void completionStatusIsCalculatedBeforeMysqlMutatesShippedPackages() throws NoSuchMethodException {
|
||||
Update update = LastlegOrderMapper.class.getMethod("transferPickedToShipped",
|
||||
String.class, Long.class, long.class, String.class).getAnnotation(Update.class);
|
||||
String sql = String.join("", Arrays.asList(update.value())).replaceAll("\\s+", " ");
|
||||
|
||||
assertTrue(sql.indexOf("current_status=CASE")
|
||||
< sql.indexOf("shipped_package_count=shipped_package_count+"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void attributesPackagesAcrossInboundOrdersInReceiptFifoOrder() {
|
||||
DispatchPickOrderAttributionService service = service();
|
||||
InboundStockReceipt first = receipt(11L, 101L, 3L, 30L);
|
||||
InboundStockReceipt second = receipt(12L, 102L, 4L, 40L);
|
||||
when(attributions.availableReceipts("t1", 91L)).thenReturn(List.of(first, second));
|
||||
when(attributions.attributedPackages(eq("t1"), anyLong())).thenReturn(0L);
|
||||
when(orders.increasePicked(eq("t1"), anyLong(), anyLong())).thenReturn(1);
|
||||
DispatchPdaPick pick = pick(81L, 91L, 5L, 50L);
|
||||
|
||||
List<DispatchPickOrderAttributionService.OrderPackages> result = service.attribute("t1", pick);
|
||||
|
||||
assertEquals(List.of(new DispatchPickOrderAttributionService.OrderPackages(101L, 3L),
|
||||
new DispatchPickOrderAttributionService.OrderPackages(102L, 2L)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void departureMovesOnlyAttributedPackagesAndWritesOrderHistory() {
|
||||
DispatchPickOrderAttributionService service = service();
|
||||
DispatchPdaPick pick = pick(81L, 91L, 4L, 40L);
|
||||
DispatchPdaPickOrder row = new DispatchPdaPickOrder();
|
||||
row.setOrderId(101L); row.setPackages(4L);
|
||||
when(attributions.listPick("t1", 81L)).thenReturn(List.of(row));
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setId(101L); order.setPackageCount(10); order.setPickedPackageCount(4);
|
||||
order.setShippedPackageCount(2); order.setCurrentStatus("INBOUND_COMPLETED");
|
||||
when(orders.lockById("t1", 101L)).thenReturn(order);
|
||||
when(orders.transferPickedToShipped("t1", 101L, 4L, "OUT1")).thenReturn(1);
|
||||
|
||||
List<DispatchPickOrderAttributionService.OrderPackages> result = service.depart(
|
||||
"t1", "OUT1", "D1", List.of(pick), LocalDateTime.of(2026, 8, 17, 15, 0));
|
||||
|
||||
assertEquals(List.of(new DispatchPickOrderAttributionService.OrderPackages(101L, 4L)), result);
|
||||
verify(history).insert(argThat((LastlegOrderStatusHistory value) -> "PARTIALLY_SHIPPED".equals(value.getToStatus())
|
||||
&& value.getRemark().contains("6/10")));
|
||||
}
|
||||
|
||||
private DispatchPickOrderAttributionService service() {
|
||||
DispatchPickOrderAttributionService service = new DispatchPickOrderAttributionService();
|
||||
ReflectionTestUtils.setField(service, "attributions", attributions);
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "history", history);
|
||||
return service;
|
||||
}
|
||||
|
||||
private static InboundStockReceipt receipt(long id, long orderId, long packages, long quantity) {
|
||||
InboundStockReceipt receipt = new InboundStockReceipt();
|
||||
receipt.setId(id); receipt.setOrderId(orderId); receipt.setReceivedPackages(packages);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
private static DispatchPdaPick pick(long id, long positionId, long packages, long quantity) {
|
||||
DispatchPdaPick pick = new DispatchPdaPick();
|
||||
pick.setId(id); pick.setInventoryPositionId(positionId); pick.setPackages(packages);
|
||||
pick.setProductQuantity(quantity); pick.setPickedAt(LocalDateTime.of(2026, 8, 17, 14, 0));
|
||||
return pick;
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package com.baiyue.lastleg.dispatch.service;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.dto.DispatchRequests;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchPalletLock;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchCandidateMapper;
|
||||
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 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.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DispatchServiceTest {
|
||||
@Mock DispatchOrderMapper orders;
|
||||
@Mock DispatchItemMapper items;
|
||||
@Mock DispatchPalletLockMapper locks;
|
||||
@Mock DispatchCandidateMapper candidates;
|
||||
private DispatchService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new DispatchService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "locks", locks);
|
||||
ReflectionTestUtils.setField(service, "candidates", candidates);
|
||||
lenient().when(items.findByDispatchOrder(any(), any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
void preArrangeLocksPalletWithoutVehicleCapacityValidation() throws Exception {
|
||||
assertNotNull(DispatchService.class.getMethod("preArrange", String.class, String.class,
|
||||
DispatchRequests.PreArrange.class).getAnnotation(Transactional.class));
|
||||
when(candidates.lockSelected("100", List.of(10L))).thenReturn(List.of(candidate(10L, null, "ONT8")));
|
||||
|
||||
var result = service.preArrange("100", "7", new DispatchRequests.PreArrange(List.of(10L), null,
|
||||
null, null, null));
|
||||
|
||||
assertEquals(DispatchService.PENDING_DEPARTURE, result.status());
|
||||
assertEquals("ONT8", result.route());
|
||||
ArgumentCaptor<DispatchOrder> dispatch = ArgumentCaptor.forClass(DispatchOrder.class);
|
||||
verify(orders).insert(dispatch.capture());
|
||||
assertEquals(1, dispatch.getValue().getPalletCount());
|
||||
verify(locks).insert(any(DispatchPalletLock.class));
|
||||
verify(items).insert(any(DispatchItem.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsPalletAlreadyLockedByAnotherDispatch() {
|
||||
when(candidates.lockSelected("100", List.of(10L))).thenReturn(List.of(candidate(10L, null, "ONT8")));
|
||||
DispatchPalletLock locked = new DispatchPalletLock();
|
||||
when(locks.lockActive(eq("100"), eq("PLAN:10"))).thenReturn(locked);
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.preArrange("100", "7",
|
||||
new DispatchRequests.PreArrange(List.of(10L), null, null, null, null)));
|
||||
verify(orders, never()).insert(any(DispatchOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidCandidateStageIsRejected() {
|
||||
assertThrows(ResponseStatusException.class, () -> service.candidates("100", null, null, "INVALID"));
|
||||
verify(candidates, never()).available(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void editCannotBypassPdaManagedDispatchStatus() {
|
||||
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);
|
||||
DispatchRequests.Update request = new DispatchRequests.Update(null, null, null, null, null,
|
||||
LocalDateTime.now(), null, null, null, null, "COMPLETED");
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.update("100", "PC1", request));
|
||||
|
||||
assertEquals(400, exception.getStatusCode().value());
|
||||
assertEquals(DispatchService.PENDING_DEPARTURE, dispatch.getStatus());
|
||||
verify(orders, never()).updateById(any(DispatchOrder.class));
|
||||
}
|
||||
|
||||
private DispatchPalletCandidate candidate(long planPalletId, Long warehousePalletId, String destination) {
|
||||
return new DispatchPalletCandidate(1L, planPalletId, warehousePalletId, 2L, "WH-1", "Overseas warehouse", "LMO1", "LMO1", "RK1",
|
||||
"LIP1-P001", destination, destination, destination, destination, 12,
|
||||
new BigDecimal("1.2000"), warehousePalletId == null ? "PLAN" : "RECEIVED");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user