This commit is contained in:
2026-10-20 14:22:31 +08:00
parent 19745e46c1
commit a870b920fe
95 changed files with 2616 additions and 275 deletions
@@ -0,0 +1,118 @@
package com.baiyue.lastleg.delivery.service;
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
import com.baiyue.lastleg.delivery.entity.*;
import com.baiyue.lastleg.delivery.mapper.*;
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests;
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
import com.baiyue.lastleg.inventory.entity.*;
import com.baiyue.lastleg.inventory.mapper.*;
import com.baiyue.lastleg.relabel.entity.*;
import com.baiyue.lastleg.relabel.mapper.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
class RelabelOutboundPickingTest {
private DeliveryTransferService service;
private InventoryCommandService commands;
private InventoryPosition position;
private InventoryBalance balance;
private OutboundPalletItem item;
private OutboundPdaPickMapper picks;
private OutboundPdaTaskMapper tasks;
private OutboundOrderMapper orders;
private OutboundPalletItemMapper pallets;
private InventoryPositionMapper positions;
private InventoryBalanceMapper balances;
private RelabelApplicationMapper applications;
private RelabelItemMapper items;
/** 构造返工后新目标位:两箱冻结,账面混合SKU,与空出库SKU不作通配匹配。 */
@BeforeEach void prepare() {
service = new DeliveryTransferService(); commands = mock(InventoryCommandService.class);
picks=mock(OutboundPdaPickMapper.class); tasks=mock(OutboundPdaTaskMapper.class);
orders=mock(OutboundOrderMapper.class); pallets=mock(OutboundPalletItemMapper.class);
positions=mock(InventoryPositionMapper.class); balances=mock(InventoryBalanceMapper.class);
applications=mock(RelabelApplicationMapper.class); items=mock(RelabelItemMapper.class);
Map.of("inventoryCommands",commands,"pdaPicks",picks,"pdaTasks",tasks,"outboundOrders",orders,
"outboundPallets",pallets,"inventoryPositions",positions,"inventoryBalances",balances,
"relabelApplications",applications,"relabelItems",items,"objectMapper",new ObjectMapper())
.forEach((name,value)->ReflectionTestUtils.setField(service,name,value));
var outbound=new OutboundOrder(); outbound.setId(1L); outbound.setCustomerId("C");
outbound.setOverseasWarehouseId("W"); outbound.setOutboundNo("OUT"); outbound.setOutboundType("RELABEL_OUTBOUND");
var task=new OutboundPdaTask(); task.setId(2L); task.setCustomerId("C"); task.setOutboundOrderId(1L);
task.setTaskNo("TASK"); task.setStatus("PENDING");
item=new OutboundPalletItem(); item.setId(3L); item.setCustomerId("C"); item.setOutboundOrderId(1L);
item.setWarehousePalletId(4L); item.setPalletNo("NEW-TARGET"); item.setInboundCode("IN");
item.setPackageCount(2); item.setPickedPackageCount(0); item.setCartonMarks("BOX-A,BOX-B");
item.setOrderRefs("""
{"sourceType":"RELABEL_ORDER","relabelNo":"RL","sourceInventoryPositionId":9,
"items":[{"itemId":11,"cartonMark":"BOX-A","packages":1},{"itemId":12,"cartonMark":"BOX-B","packages":1}]}
""");
position=new InventoryPosition(); position.setId(9L); position.setCustomerId("C"); position.setBalanceId(8L);
position.setOverseasWarehouseId("W"); position.setPalletId(4L); position.setPalletNo("NEW-TARGET");
position.setAvailablePackages(0L); position.setHoldPackages(2L);
balance=new InventoryBalance(); balance.setId(8L); balance.setCustomerId("C");
balance.setOverseasWarehouseId("W"); balance.setInboundCode("IN"); balance.setSkuCode("MIXED");
when(tasks.lockByNoAndWarehouse("TASK","W")).thenReturn(task);
when(tasks.findByNoAndWarehouse("TASK","W")).thenReturn(task);
when(orders.selectById(1L)).thenReturn(outbound); when(orders.lockByNo("C","OUT")).thenReturn(outbound);
when(pallets.findByOutbound("C",1L)).thenReturn(List.of(item)); when(pallets.selectById(3L)).thenReturn(item);
when(positions.listPallet("C",4L)).thenReturn(List.of(position)); when(positions.selectById(9L)).thenReturn(position);
when(balances.selectById(8L)).thenReturn(balance);
var app=new RelabelApplication(); app.setId(10L); app.setRelabelNo("RL"); app.setStatus("READY_FOR_OUTBOUND");
when(applications.lockByOutbound("C","OUT")).thenReturn(app);
var first=new RelabelItem(); first.setId(11L); first.setCartonMark("BOX-A"); first.setNotifiedPackageCount(1);
var second=new RelabelItem(); second.setId(12L); second.setCartonMark("BOX-B"); second.setNotifiedPackageCount(1);
when(items.selectList(any())).thenReturn(List.of(first,second));
}
/** 指定箱从冻结到出库数量守恒;整托重扫不重复释放或出库。 */
@Test void picksExactFrozenBoxesAndReplayDoesNotShipAgain() {
assertEquals(List.of("BOX-A","BOX-B"),service.pdaTaskDetail("W","TASK").items().get(0).positions()
.stream().map(value->value.cartonMark()).toList());
service.pdaPickPallet("W","U","TASK","NEW-TARGET",new DeliveryRequests.PdaPalletPick("KEY"));
service.pdaPickPallet("W","U","TASK","NEW-TARGET",new DeliveryRequests.PdaPalletPick("KEY"));
var release=org.mockito.ArgumentCaptor.forClass(InventoryCommandRequests.Move.class);
verify(commands,times(2)).release(eq("C"),eq("U"),release.capture());
assertEquals(2,release.getAllValues().stream().mapToLong(InventoryCommandRequests.Move::packages).sum());
assertTrue(release.getAllValues().stream().allMatch(move->move.positionId()==9L&&"RELABEL:RL".equals(move.sourceEventId())));
var shipped=org.mockito.ArgumentCaptor.forClass(InventoryCommandRequests.Move.class);
verify(commands).ship(eq("C"),eq("U"),shipped.capture());
assertEquals(2,shipped.getValue().packages()); assertEquals(9L,shipped.getValue().positionId());
verify(commands).allocate(eq("C"),eq("U"),any()); verify(commands).pick(eq("C"),eq("U"),any());
verify(picks).insert(any(OutboundPdaPick.class)); assertEquals(2,item.getPickedPackageCount());
}
/** 其他位置、客户、仓、入仓或不足冻结均拒绝,不能用空SKU放宽范围。 */
@ParameterizedTest @ValueSource(strings={"POSITION","CUSTOMER","WAREHOUSE","INBOUND","HOLD","SNAPSHOT"})
void rejectsUnrelatedOrIncompleteSourcesWithoutInventoryEffects(String invalid) {
switch(invalid) {
case "POSITION" -> position.setId(99L);
case "CUSTOMER" -> balance.setCustomerId("OTHER");
case "WAREHOUSE" -> position.setOverseasWarehouseId("OTHER");
case "INBOUND" -> balance.setInboundCode("OTHER");
case "HOLD" -> position.setHoldPackages(1L);
case "SNAPSHOT" -> item.setOrderRefs("{\"sourceType\":\"RELABEL_ORDER\"}");
}
assertTrue(service.pdaTaskDetail("W","TASK").items().get(0).positions().isEmpty());
assertThrows(ResponseStatusException.class,()->service.pdaPickPallet("W","U","TASK","NEW-TARGET",new DeliveryRequests.PdaPalletPick("KEY")));
verifyNoInteractions(commands,picks);
}
/** 同箱唛也不能代替来源任务主键,损坏ID必须在释放冻结前拒绝。 */
@Test void rejectsWrongSnapshotItemIdBeforeReleasingAnyStock() {
item.setOrderRefs(item.getOrderRefs().replace("\"itemId\":11", "\"itemId\":999"));
assertEquals(2,service.pdaTaskDetail("W","TASK").items().get(0).positions().size());
assertThrows(ResponseStatusException.class,()->service.pdaPickPallet("W","U","TASK","NEW-TARGET",new DeliveryRequests.PdaPalletPick("KEY")));
verifyNoInteractions(commands); verify(picks,never()).insert(any(OutboundPdaPick.class));
}
}