提交
This commit is contained in:
+57
-2
@@ -38,7 +38,7 @@ class PdaCargoDiscardServiceTest {
|
||||
private CargoDiscardItem item;
|
||||
private final PdaWarehouseIdentity identity = new PdaWarehouseIdentity("OP", "operator", 99L);
|
||||
private final com.baiyue.lastleg.discard.service.CargoDiscardPhotoService photoService = mock(com.baiyue.lastleg.discard.service.CargoDiscardPhotoService.class);
|
||||
private PdaCargoDiscardDtos.Confirm request(int actual) { return new PdaCargoDiscardDtos.Confirm(List.of(new PdaCargoDiscardDtos.ActualLine(11L, actual)), List.of(101L)); }
|
||||
private PdaCargoDiscardDtos.Confirm request(int actual) { return new PdaCargoDiscardDtos.Confirm(List.of(new PdaCargoDiscardDtos.ActualLine(11L, actual, marks(11L, actual))), List.of(101L)); }
|
||||
|
||||
@BeforeEach void setup() {
|
||||
service = new PdaCargoDiscardService();
|
||||
@@ -162,9 +162,55 @@ class PdaCargoDiscardServiceTest {
|
||||
assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-1", request(actual)));
|
||||
noWrites(); verifyNoInteractions(photoService);
|
||||
}
|
||||
/** 数量申请可仅使用一个托盘,也可跨托盘完成,剩余预留只释放一次。 */
|
||||
@ParameterizedTest @ValueSource(ints = {0, 2})
|
||||
void quantityTargetUsesChosenPalletsAndReleasesUnusedReservations(int firstActual) {
|
||||
var stock = mock(com.baiyue.lastleg.discard.service.CargoDiscardStockService.class);
|
||||
ReflectionTestUtils.setField(service, "stockService", stock); scannedStock(stock);
|
||||
application.setTotalPackages(5); application.setRemark("只废弃破损外箱"); application.setStatus(CargoDiscardService.APPROVED);
|
||||
item.setSourceType("INVENTORY"); item.setInventoryPositionId(42L); item.setDiscardPackages(12);
|
||||
var second = new CargoDiscardItem(); second.setId(12L); second.setCustomerId("CUSTOMER"); second.setApplicationId(1L);
|
||||
second.setSourceType("INVENTORY"); second.setInventoryPositionId(43L); second.setSkuCode("SKU"); second.setDiscardPackages(10); second.setPdaStatus("PENDING");
|
||||
when(items.listApplication("CUSTOMER", 1L)).thenAnswer(call -> List.of(item, second));
|
||||
when(items.selectById(12L)).thenReturn(second);
|
||||
for (var line : List.of(item, second)) {
|
||||
var position = new com.baiyue.lastleg.inventory.entity.InventoryPosition(); position.setOverseasWarehouseId("99");
|
||||
position.setPalletNo(line == item ? "PALLET-1" : "PALLET-2");
|
||||
when(stock.position("CUSTOMER", line.getInventoryPositionId())).thenReturn(position);
|
||||
when(stock.actualQuantityEditable(line)).thenReturn(true);
|
||||
}
|
||||
when(applications.selectList(any())).thenReturn(List.of(application));
|
||||
var task = service.resolve(identity, "PALLET-2", "CD-1");
|
||||
assertEquals("只废弃破损外箱", task.remark()); assertEquals(5, task.requestedPackages()); assertEquals(5, task.remainingPackages());
|
||||
assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-1", request(6)));
|
||||
verifyNoInteractions(photoService);
|
||||
if (firstActual > 0) {
|
||||
service.confirm(identity, "CD-1", "PALLET-1", request(firstActual));
|
||||
assertEquals(CargoDiscardService.APPROVED, application.getStatus());
|
||||
assertEquals(3, service.resolve(identity, "PALLET-2", "CD-1").remainingPackages());
|
||||
}
|
||||
var secondRequest = new PdaCargoDiscardDtos.Confirm(List.of(new PdaCargoDiscardDtos.ActualLine(12L, 5-firstActual, marks(12L, 5-firstActual))), List.of(101L));
|
||||
if (firstActual > 0) assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-2",
|
||||
new PdaCargoDiscardDtos.Confirm(List.of(new PdaCargoDiscardDtos.ActualLine(12L, 3, List.of("FIELD-11-0", "SECOND-1", "SECOND-2"))), List.of(101L))));
|
||||
service.confirm(identity, "CD-1", "PALLET-2", secondRequest);
|
||||
assertEquals(CargoDiscardService.PDA_COMPLETED, application.getStatus()); assertEquals(5, application.getTotalPackages());
|
||||
verify(stock).discard("CUSTOMER", "OP", application, second, 5-firstActual);
|
||||
verify(stock).discard("CUSTOMER", "OP", application, item, firstActual);
|
||||
service.confirm(identity, "CD-1", "PALLET-2", secondRequest);
|
||||
verify(stock, times(1)).discard("CUSTOMER", "OP", application, second, 5-firstActual);
|
||||
verifyNoInteractions(adjustments);
|
||||
}
|
||||
/** 生成现场扫码样本,各托盘箱唛独立。 */
|
||||
private List<String> marks(long itemId, int count) { return java.util.stream.IntStream.range(0, count).mapToObj(i -> "FIELD-" + itemId + "-" + i).toList(); }
|
||||
/** Mock仍执行真实JSON序列化,验证重放时箱唛不可篡改。 */
|
||||
private void scannedStock(com.baiyue.lastleg.discard.service.CargoDiscardStockService stock) {
|
||||
ReflectionTestUtils.setField(stock, "objectMapper", new com.fasterxml.jackson.databind.ObjectMapper());
|
||||
doCallRealMethod().when(stock).saveActualCartonMarks(any(), anyList());
|
||||
when(stock.actualCartonMarks(any())).thenCallRealMethod();
|
||||
}
|
||||
private void looseTask() {
|
||||
var stockService = mock(com.baiyue.lastleg.discard.service.CargoDiscardStockService.class);
|
||||
ReflectionTestUtils.setField(service, "stockService", stockService);
|
||||
ReflectionTestUtils.setField(service, "stockService", stockService); scannedStock(stockService);
|
||||
item.setSourceType("INVENTORY"); item.setInventoryPositionId(42L); item.setDiscardPackages(2);
|
||||
var position = new com.baiyue.lastleg.inventory.entity.InventoryPosition();
|
||||
position.setId(42L); position.setCustomerId("CUSTOMER"); position.setOverseasWarehouseId("99");
|
||||
@@ -180,6 +226,7 @@ class PdaCargoDiscardServiceTest {
|
||||
void unboundPalletUsesActualQuantityForInventoryFeeAndReplay(int actual) {
|
||||
var stockService = mock(com.baiyue.lastleg.discard.service.CargoDiscardStockService.class);
|
||||
ReflectionTestUtils.setField(service, "stockService", stockService);
|
||||
scannedStock(stockService);
|
||||
item.setSourceType("INVENTORY"); item.setInventoryPositionId(42L); item.setDiscardPackages(3);
|
||||
var position = new com.baiyue.lastleg.inventory.entity.InventoryPosition();
|
||||
position.setOverseasWarehouseId("99"); position.setPalletNo("PALLET-1");
|
||||
@@ -190,13 +237,21 @@ class PdaCargoDiscardServiceTest {
|
||||
var task = service.resolve(identity, "PALLET-1", "CD-1");
|
||||
assertTrue(task.skus().get(0).actualQuantityEditable()); assertEquals("PALLET", task.scanMode());
|
||||
assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-1", request(4)));
|
||||
assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-1",
|
||||
new PdaCargoDiscardDtos.Confirm(List.of(new PdaCargoDiscardDtos.ActualLine(11L, 1)), List.of(101L))));
|
||||
assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-1",
|
||||
new PdaCargoDiscardDtos.Confirm(List.of(new PdaCargoDiscardDtos.ActualLine(11L, 2, List.of("SAME", "SAME"))), List.of(101L))));
|
||||
verifyNoInteractions(photoService);
|
||||
var completed = service.confirm(identity, "CD-1", "PALLET-1", request(actual));
|
||||
assertEquals(actual, completed.skus().get(0).actualPackages());
|
||||
assertEquals(actual, application.getTotalPackages());
|
||||
assertEquals(new BigDecimal("5.00").multiply(BigDecimal.valueOf(actual)), application.getTotalFee());
|
||||
assertEquals(marks(11L, actual), stockService.actualCartonMarks(item));
|
||||
service.confirm(identity, "CD-1", "PALLET-1", request(actual));
|
||||
verify(stockService, times(1)).discard("CUSTOMER", "OP", application, item, actual);
|
||||
if (actual > 0) assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-1",
|
||||
new PdaCargoDiscardDtos.Confirm(List.of(new PdaCargoDiscardDtos.ActualLine(11L, actual,
|
||||
java.util.stream.IntStream.range(0, actual).mapToObj(i -> "CHANGED-" + i).toList())), List.of(101L))));
|
||||
verifyNoInteractions(adjustments);
|
||||
assertThrows(ResponseStatusException.class, () -> service.confirm(identity, "CD-1", "PALLET-1", request(3)));
|
||||
verify(items, times(1)).updateById(item);
|
||||
|
||||
+26
@@ -142,6 +142,31 @@ class RelabelApplicationServiceTest {
|
||||
assertEquals(0, occupied.cartonAvailablePackages());
|
||||
assertEquals(2, occupied.skuItems().size(), "处理中库存保留占用信息,但不可再次选择");
|
||||
}
|
||||
|
||||
@Test void sharedCandidatesExcludeDropshipButKeepOrdinaryInbound() {
|
||||
ReflectionTestUtils.setField(service, "returnCartons", org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class));
|
||||
when(orders.listDropshipInboundCodes("10001", "WH-1")).thenReturn(List.of("DROP-1"));
|
||||
var excluded = new InventoryBalance(); excluded.setId(1L); excluded.setInboundCode("DROP-1");
|
||||
var ordinary = new InventoryBalance(); ordinary.setId(2L); ordinary.setInboundCode("NORMAL-1");
|
||||
when(inventoryBalances.selectList(any())).thenReturn(List.of(excluded, ordinary));
|
||||
var position = new InventoryPosition(); position.setId(3L); position.setBalanceId(2L); position.setAvailablePackages(4L);
|
||||
when(inventoryPositions.listBalances("10001", List.of(2L))).thenReturn(List.of(position));
|
||||
|
||||
var result = service.candidates("10001", "WH-1", null);
|
||||
|
||||
assertEquals(List.of("NORMAL-1"), result.stream().map(RelabelResponses.InventoryCandidate::inboundCode).toList());
|
||||
}
|
||||
|
||||
@Test void staleDropshipSelectionCannotCreateRelabelApplication() {
|
||||
when(orders.listDropshipInboundCodes("10001", "WH-1")).thenReturn(List.of("CODE-1"));
|
||||
|
||||
var error = assertThrows(ResponseStatusException.class,
|
||||
() -> service.create("10001", "operator-1", request("dropship", 1, 1)));
|
||||
|
||||
assertEquals("一件代发订单不支持贴标申请", error.getReason());
|
||||
verify(applications, never()).insert(any(RelabelApplication.class));
|
||||
verifyNoInteractions(inventoryCommands);
|
||||
}
|
||||
@Test void convertedSkuRemainsAvailableForCartonLabelOnly() {
|
||||
var cartons = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
||||
var lines = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
||||
@@ -245,6 +270,7 @@ class RelabelApplicationServiceTest {
|
||||
ReflectionTestUtils.setField(service, "walletCommands", walletCommands);
|
||||
ReflectionTestUtils.setField(service, "inventoryCommands", inventoryCommands);
|
||||
ReflectionTestUtils.setField(service, "inventoryBalances", inventoryBalances);
|
||||
ReflectionTestUtils.setField(service, "sourceRepository", new com.baiyue.lastleg.relabel.repository.RelabelSourceRepository(orders));
|
||||
ReflectionTestUtils.setField(service, "inventoryPositions", inventoryPositions);
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
ReflectionTestUtils.setField(service, "outboundItems", outboundItems);
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.baiyue.lastleg.relabel.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.relabel.repository.RelabelSourceRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/** 用真实 Mapper 验证业务类型、客户和仓库隔离,避免跨来源误隐藏。 */
|
||||
class RelabelSourceRepositoryTest {
|
||||
@Test void onlyDropshipSourcesInCurrentCustomerAndWarehouseAreExcluded() throws Exception {
|
||||
var ds = new DriverManagerDataSource("jdbc:h2:mem:relabel_sources;MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "");
|
||||
var jdbc = new JdbcTemplate(ds);
|
||||
jdbc.execute("CREATE TABLE ll_order(id BIGINT,customer_id VARCHAR(64),overseas_warehouse_id VARCHAR(64),order_type VARCHAR(64),inbound_code VARCHAR(64))");
|
||||
jdbc.execute("INSERT INTO ll_order VALUES (1,'C1','W1','ONE_CLICK_DROPSHIP','DROP'),(2,'C1','W1','WAREHOUSE_TRANSFER','NORMAL'),(3,'C2','W1','ONE_CLICK_DROPSHIP','FOREIGN'),(4,'C1','W2','ONE_CLICK_DROPSHIP','OTHER-WAREHOUSE')");
|
||||
var configuration = new MybatisConfiguration();
|
||||
configuration.addMapper(LastlegOrderMapper.class);
|
||||
var factory = new MybatisSqlSessionFactoryBean();
|
||||
factory.setDataSource(ds); factory.setConfiguration(configuration);
|
||||
var repository = new RelabelSourceRepository(new SqlSessionTemplate(factory.getObject()).getMapper(LastlegOrderMapper.class));
|
||||
|
||||
assertThat(repository.dropshipInboundCodes("C1", "W1")).containsExactly("DROP");
|
||||
assertThat(repository.dropshipInboundCodes("C2", "W1")).containsExactly("FOREIGN");
|
||||
assertThat(repository.dropshipInboundCodes("C1", "W2")).containsExactly("OTHER-WAREHOUSE");
|
||||
}
|
||||
}
|
||||
+14
@@ -1283,6 +1283,20 @@ class WarehouseTransferServiceTest {
|
||||
verify(orders, never()).insert(any(LastlegOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void staleDropshipSelectionCannotCreateWarehouseTransfer() {
|
||||
when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L);
|
||||
var dropship = historicalOrder(); dropship.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(orders.selectList(any())).thenReturn(List.of(dropship));
|
||||
|
||||
var error = assertThrows(ResponseStatusException.class,
|
||||
() -> service.create("TENANT-A", "USER-1", request(false)));
|
||||
|
||||
assertEquals("一件代发订单不支持仓储转运", error.getReason());
|
||||
verify(transfers, never()).insert(any(WarehouseTransferOrder.class));
|
||||
verifyNoInteractions(inventory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingOrForeignCustomerNameStopsCreationBeforeAnyBusinessWrites() {
|
||||
for (var customer : List.of(
|
||||
|
||||
Reference in New Issue
Block a user