This commit is contained in:
2026-10-20 11:33:20 +08:00
parent 16ac817524
commit 6fa5d281ec
61 changed files with 31358 additions and 8 deletions
@@ -0,0 +1,61 @@
package com.baiyue.lastleg.discard;
import com.baiyue.lastleg.discard.mapper.CargoDiscardInventoryMapper;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.util.List;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
/** 使用真实 SQL 核对转运实收箱与库存仓位的关联及废弃状态流转。 */
class CargoDiscardTransferCartonMapperTest {
/** 同批多托盘、多 SKU 时仅返回本仓位实收箱,排除预报、短收、占用及其他客户的箱。 */
@Test
void receivedBoxesFollowTheirActualPalletAndSku() {
var source = new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "");
var db = new JdbcTemplate(source);
db.execute("CREATE TABLE ll_inbound_plan_carton(id BIGINT,customer_id VARCHAR(20),order_id BIGINT,warehouse_pallet_id BIGINT,status VARCHAR(30),received_at TIMESTAMP,destination_warehouse_code VARCHAR(20),carton_mark VARCHAR(80),sku_code VARCHAR(20))");
db.execute("CREATE TABLE ll_inventory_balance(id BIGINT,customer_id VARCHAR(20),inbound_order_id BIGINT,destination_warehouse_code VARCHAR(20),sku_code VARCHAR(20))");
db.execute("CREATE TABLE ll_inventory_position(id BIGINT,customer_id VARCHAR(20),balance_id BIGINT,pallet_id BIGINT,batch_no VARCHAR(80),overseas_warehouse_id VARCHAR(20))");
db.execute("CREATE TABLE ll_warehouse_transfer_order(id BIGINT,customer_id VARCHAR(20),inbound_order_id BIGINT,transfer_no VARCHAR(80),operating_warehouse_id VARCHAR(20))");
db.execute("CREATE TABLE ll_warehouse_transfer_pallet(id BIGINT,customer_id VARCHAR(20),transfer_order_id BIGINT,warehouse_pallet_id BIGINT,status VARCHAR(30))");
db.execute("CREATE TABLE ll_warehouse_transfer_item(id BIGINT,customer_id VARCHAR(20),transfer_order_id BIGINT,sku_code VARCHAR(20))");
db.execute("CREATE TABLE ll_warehouse_transfer_carton(id BIGINT,customer_id VARCHAR(20),transfer_order_id BIGINT,transfer_item_id BIGINT,transfer_pallet_id BIGINT,status VARCHAR(30),carton_mark VARCHAR(80),updated_at TIMESTAMP)");
db.update("INSERT INTO ll_inventory_balance VALUES(1,'C',10,'FBA','SKU'),(2,'C',10,'FBA','OTHER')");
db.update("INSERT INTO ll_inventory_position VALUES(41,'C',1,100,'ZY-BYWL-261019194301','W'),(42,'C',1,200,'ZY-BYWL-261019194301','W'),(43,'C',2,100,'ZY-BYWL-261019194301','W')");
db.update("INSERT INTO ll_warehouse_transfer_order VALUES(10,'C',10,'ZY-BYWL-261019194301','W')");
db.update("INSERT INTO ll_warehouse_transfer_pallet VALUES(11,'C',10,100,'COMPLETED'),(12,'C',10,200,'COMPLETED'),(13,'C',10,100,'BOUND')");
db.update("INSERT INTO ll_warehouse_transfer_item VALUES(21,'C',10,'SKU'),(22,'C',10,'OTHER')");
db.update("INSERT INTO ll_warehouse_transfer_carton(id,customer_id,transfer_order_id,transfer_item_id,transfer_pallet_id,status,carton_mark) VALUES " +
"(1,'C',10,21,11,'COUNTED','BOX-1'),(2,'C',10,21,11,'COUNTED','BOX-2')," +
"(3,'C',10,21,12,'COUNTED','OTHER-PALLET'),(4,'C',10,22,11,'COUNTED','OTHER-SKU')," +
"(5,'C',10,21,11,'EXPECTED','FORECAST'),(6,'C',10,21,11,'NOT_RECEIVED','SHORT')," +
"(7,'C',10,21,11,'DISCARD_PENDING','HELD'),(8,'OTHER',10,21,11,'COUNTED','FOREIGN')," +
"(9,'C',10,21,13,'COUNTED','NOT-STOCKED')");
var config = new Configuration(new Environment("test", new JdbcTransactionFactory(), source));
config.setMapUnderscoreToCamelCase(true);
config.addMapper(CargoDiscardInventoryMapper.class);
try (var session = new SqlSessionFactoryBuilder().build(config).openSession(true)) {
var mapper = session.getMapper(CargoDiscardInventoryMapper.class);
assertEquals(List.of("BOX-1", "BOX-2"), mapper.genericCartons("C", 41L).stream().map(c -> c.getCartonMark()).toList());
assertEquals(List.of("OTHER-PALLET"), mapper.genericCartons("C", 42L).stream().map(c -> c.getCartonMark()).toList());
assertEquals(List.of("OTHER-SKU"), mapper.genericCartons("C", 43L).stream().map(c -> c.getCartonMark()).toList());
assertTrue(mapper.genericCartons("OTHER", 41L).isEmpty());
assertEquals(1, mapper.changeTransferCartonStatus("C", 2L, "COUNTED", "DISCARD_PENDING"));
assertEquals(1, mapper.genericCartons("C", 41L).size());
assertEquals(0, mapper.changeTransferCartonStatus("OTHER", 2L, "DISCARD_PENDING", "COUNTED"));
assertEquals(1, mapper.changeTransferCartonStatus("C", 2L, "DISCARD_PENDING", "COUNTED"));
assertEquals(2, mapper.genericCartons("C", 41L).size());
assertEquals(1, mapper.changeTransferCartonStatus("C", 2L, "COUNTED", "DISCARD_PENDING"));
assertEquals(1, mapper.changeTransferCartonStatus("C", 2L, "DISCARD_PENDING", "DISCARDED"));
assertEquals(1, mapper.genericCartons("C", 41L).size());
}
}
}
@@ -34,7 +34,7 @@ class MerchantCargoDiscardControllerTest {
private final CargoDiscardService service = mock(CargoDiscardService.class);
private final MerchantCargoDiscardController controller = controller();
private final CargoDiscardRequests.Query query = new CargoDiscardRequests.Query(
1, 20, null, null, null, null, null, null);
1, 20, null, null, null, null, null, null, null, null);
@Test
void merchantQueriesUseOnlyAuthenticatedTenant() {
@@ -0,0 +1,60 @@
package com.baiyue.lastleg.discard.service;
import com.baiyue.lastleg.discard.dto.CargoDiscardRequests;
import com.baiyue.lastleg.discard.entity.CargoDiscardApplication;
import com.baiyue.lastleg.discard.entity.CargoDiscardItem;
import com.baiyue.lastleg.discard.repository.CargoDiscardQueryRepository;
import com.baiyue.lastleg.discard.repository.CargoDiscardDimensionRepository;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
/** 全筛选统计不能受当前页影响,混装退货箱和缺失维度必须分别处理。 */
class CargoDiscardStatisticsServiceTest {
private final CargoDiscardQueryRepository queries = mock(CargoDiscardQueryRepository.class);
private final CargoDiscardDimensionRepository dimensions = mock(CargoDiscardDimensionRepository.class);
private final CargoDiscardStatisticsService service = new CargoDiscardStatisticsService();
private final CargoDiscardRequests.Query query = new CargoDiscardRequests.Query(1, 1, null, null, null, null, null, null, null, null);
/** 给当前服务注入隔离只读仓储。 */
CargoDiscardStatisticsServiceTest() {
ReflectionTestUtils.setField(service, "queries", queries);
ReflectionTestUtils.setField(service, "dimensions", dimensions);
}
/** 合成两票同托盘退货,第一页大小为一仍汇总两票;同箱SKU不重复计箱。 */
@Test void allFilteredOrdersDeduplicateMixedCartonsAndPallets() {
var a = application(1L); var b = application(2L);
when(queries.list("C", query)).thenReturn(List.of(a, b));
when(dimensions.items("C", 1L)).thenReturn(List.of(item(10L, "A", 2, 2), item(10L, "B", 3, 3)));
when(dimensions.items("C", 2L)).thenReturn(List.of(item(20L, "A", 7, null)));
when(dimensions.pallets("C", 1L)).thenReturn(List.of("P", "P"));
when(dimensions.pallets("C", 2L)).thenReturn(List.of("P"));
var result = service.statistics("C", query);
assertEquals(2, result.orderCount()); assertEquals(2, result.skuCount());
assertEquals(2, result.requestedPackages()); assertEquals(1L, result.actualPackages());
assertEquals(12L, result.requestedPieces()); assertEquals(5L, result.actualPieces()); assertEquals(1, result.palletCount());
}
/** 非退货箱级记录不包含件数,实际零与未作业空值必须区分。 */
@Test void unknownPiecesAndLoosePalletNeverBecomeZero() {
when(queries.list("C", query)).thenReturn(List.of(application(1L)));
when(dimensions.items("C", 1L)).thenReturn(List.of(item(null, "A", 2, 0)));
when(dimensions.pallets("C", 1L)).thenReturn(List.of(""));
var result = service.statistics("C", query);
assertEquals(2, result.requestedPackages()); assertEquals(0L, result.actualPackages());
assertNull(result.requestedPieces()); assertNull(result.actualPieces()); assertNull(result.palletCount());
}
/** 空筛选有零票零申请箱,但不能伪造实际作业或商品、托盘事实。 */
@Test void emptySelectionHasNoInventedActualFacts() {
when(queries.list("C", query)).thenReturn(List.of());
var result = service.statistics("C", query);
assertEquals(0, result.orderCount()); assertEquals(0, result.requestedPackages());
assertNull(result.actualPackages()); assertNull(result.requestedPieces()); assertNull(result.palletCount());
verifyNoInteractions(dimensions);
}
/** 创建一票同客户同仓的隔离单据。 */
private static CargoDiscardApplication application(long id) { var a = new CargoDiscardApplication(); a.setId(id); a.setCustomerId("C"); a.setOverseasWarehouseId("W"); return a; }
/** 明确退货件数和实际值,不引入固定箱规。 */
private static CargoDiscardItem item(Long carton, String sku, int planned, Integer actual) { var i = new CargoDiscardItem(); i.setReturnCartonId(carton); i.setSkuCode(sku); i.setDiscardPackages(planned); i.setActualDiscardPackages(actual); return i; }
}
@@ -0,0 +1,75 @@
package com.baiyue.lastleg.relabel.service;
import com.baiyue.lastleg.relabel.entity.RelabelItem;
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
import com.baiyue.lastleg.relabel.repository.RelabelQuantityRepository;
import com.baiyue.lastleg.relabel.dto.RelabelRequests;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class RelabelQuantityServiceTest {
/** 以真实对象测试件数、箱数的不同业务口径。 */
private RelabelQuantityService service() {
var service = new RelabelQuantityService();
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
return service;
}
/** 构造单个箱任务,件数由SKU子项提供。 */
private RelabelItem item(String type, String mark, int quantity) {
var item = new RelabelItem();
item.setOperationType(type); item.setCartonMark(mark); item.setSkuCode("A");
item.setNotifiedPackageCount(quantity); item.setTotalPackageCount(quantity);
item.setSkuRelabelItemsJson("[{\"skuCode\":\"A\",\"newSkuCode\":\"B\",\"quantity\":8}]");
return item;
}
@Test void bulkPiecesNeverBecomeCartons() {
var result = service().measure(List.of(item("RETURN_BULK_SKU_LABEL", "PALLET-1", 50)));
assertEquals(50L, result.labelPieceCount()); assertNull(result.cartonCount());
assertNull(result.sourcePalletCount()); assertNull(result.targetPalletCount());
}
@Test void skuPiecesAndCartonCountAreIndependent() {
var result = service().measure(List.of(item("SKU_LABEL", "BOX-1", 1)));
assertEquals(8L, result.labelPieceCount()); assertEquals(1L, result.cartonCount()); assertEquals(1, result.skuCount());
}
@Test void duplicateAssociationIsFlaggedAndCannotInflateCartonsOrGuessPieces() {
var result = service().measure(List.of(item("SKU_LABEL", "BOX-1", 1), item("SKU_LABEL", "BOX-1", 1)));
assertTrue(result.duplicateCarton()); assertEquals(1L, result.cartonCount()); assertNull(result.labelPieceCount());
}
@Test void cartonOnlyAndMissingDataDoNotInventZeroPieces() {
var result = service().measure(List.of(item("CARTON_LABEL", "BOX-1", 1)));
assertNull(result.labelPieceCount()); assertEquals(1L, result.cartonCount());
assertNull(service().measure(List.of()).cartonCount());
}
@Test void mixedStagesDoNotDoubleCountTheBox() {
var result = service().measure(List.of(item("CARTON_AND_SKU", "BOX-1", 1)));
assertEquals(1L, result.cartonCount()); assertEquals(8L, result.labelPieceCount());
}
@Test void differentBulkSkusInOneSourceAreNotDuplicateTasks() {
var first = item("RETURN_BULK_SKU_LABEL", "PALLET-1", 2);
var second = item("RETURN_BULK_SKU_LABEL", "PALLET-1", 3);
second.setSkuCode("C"); second.setSkuRelabelItemsJson("[]"); first.setSkuRelabelItemsJson("[]");
var result = service().measure(List.of(first, second));
assertFalse(result.duplicateCarton()); assertEquals(5L, result.labelPieceCount());
assertNull(result.cartonCount()); assertEquals(2, result.skuCount());
}
@Test void totalsUseAllFilteredApplicationsAndPreserveUnknownDimensions() {
var service = service(); var repository = mock(RelabelQuantityRepository.class);
ReflectionTestUtils.setField(service, "repository", repository);
var query = new RelabelRequests.Query(2, 1, null, null, "A", null, null, null, null);
var first = new RelabelApplication(); first.setRelabelNo("R1"); first.setCustomerId("C1");
var second = new RelabelApplication(); second.setRelabelNo("R2"); second.setCustomerId("C1");
when(repository.filtered("C1", query)).thenReturn(List.of(first, second));
when(repository.items(first)).thenReturn(List.of(item("RETURN_BULK_SKU_LABEL", "P1", 50)));
when(repository.items(second)).thenReturn(List.of(item("CARTON_LABEL", "B1", 1)));
var totals = service.totals("C1", query);
assertEquals(2, totals.orderCount()); assertEquals(50, totals.knownLabelPieceCount());
assertEquals(1, totals.unknownPieceOrders()); assertEquals(1, totals.knownCartonCount());
assertEquals(1, totals.unknownCartonOrders()); assertEquals(1, totals.skuCount());
verify(repository).filtered("C1", query);
assertEquals(2, totals.orders().size());
}
}