提交
This commit is contained in:
+81
@@ -0,0 +1,81 @@
|
||||
package com.baiyue.lastleg.discard;
|
||||
|
||||
import com.baiyue.lastleg.discard.entity.*;
|
||||
import com.baiyue.lastleg.discard.repository.CargoDiscardStockLedgerRepository;
|
||||
import com.baiyue.lastleg.inventory.entity.*;
|
||||
import com.baiyue.lastleg.inventory.mapper.*;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.*;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.*;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.mockito.*;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.time.LocalDateTime;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/** 校验按件、按箱扣减与总账事实的数量口径。 */
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CargoDiscardStockLedgerRepositoryTest {
|
||||
@Mock ReturnInventoryBalanceMapper returnBalances;
|
||||
@Mock ReturnInventoryCartonMapper returnCartons;
|
||||
@Mock InventoryBalanceMapper balances;
|
||||
@Mock InventoryLedgerMapper ledgers;
|
||||
CargoDiscardStockLedgerRepository repository;
|
||||
|
||||
/** 装配真实仓储逻辑及受控数据库边界。 */
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
repository = new CargoDiscardStockLedgerRepository();
|
||||
ReflectionTestUtils.setField(repository, "returnBalances", returnBalances);
|
||||
ReflectionTestUtils.setField(repository, "returnCartons", returnCartons);
|
||||
ReflectionTestUtils.setField(repository, "balances", balances);
|
||||
ReflectionTestUtils.setField(repository, "ledgers", ledgers);
|
||||
}
|
||||
|
||||
/** 部分废弃保留箱数,整 SKU 耗尽减箱,包裹不占箱数。 */
|
||||
@ParameterizedTest
|
||||
@CsvSource({"BOX,10,5,0", "BOX,5,5,1", "PARCEL,5,5,0"})
|
||||
void returnBalanceUsesPiecesAndRemainingSku(String unit, long before, long count, long boxes) {
|
||||
var app = new CargoDiscardApplication(); app.setOverseasWarehouseId("W");
|
||||
var item = new CargoDiscardItem(); item.setReturnCartonId(1L); item.setSkuCode("SKU");
|
||||
var carton = new ReturnInventoryCarton(); carton.setCustomerId("C"); carton.setOverseasWarehouseId("W");
|
||||
carton.setStatus("DISCARD_PENDING"); carton.setSourceUnitType(unit);
|
||||
when(returnCartons.lockId(1L)).thenReturn(carton);
|
||||
when(returnBalances.lockSku("C", "W", "SKU")).thenReturn(new ReturnInventoryBalance());
|
||||
when(returnBalances.deductConversion("C", "W", "SKU", count, boxes)).thenReturn(1);
|
||||
repository.deductReturn("C", app, item, before, count);
|
||||
verify(returnBalances).deductConversion("C", "W", "SKU", count, boxes);
|
||||
}
|
||||
|
||||
/** 其他客户的退货箱不能扣当前客户库存。 */
|
||||
@Test
|
||||
void rejectsForeignCartonBeforeBalanceMutation() {
|
||||
var item = new CargoDiscardItem(); item.setReturnCartonId(1L);
|
||||
var carton = new ReturnInventoryCarton(); carton.setCustomerId("OTHER");
|
||||
when(returnCartons.lockId(1L)).thenReturn(carton);
|
||||
assertThrows(ResponseStatusException.class, () -> repository.deductReturn("C", new CargoDiscardApplication(), item, 5, 5));
|
||||
verifyNoInteractions(returnBalances);
|
||||
}
|
||||
|
||||
/** 一件代发混装箱只按实物箱数记总账,并关联废弃业务单。 */
|
||||
@Test
|
||||
void dropshipLedgerRecordsPhysicalBoxesAndBusinessReference() {
|
||||
var app = new CargoDiscardApplication(); app.setId(10L); app.setDiscardNo("CD-1");
|
||||
var item = new CargoDiscardItem(); item.setId(20L); item.setSkuCode("MIXED");
|
||||
var position = new InventoryPosition(); position.setId(30L); position.setBalanceId(40L); position.setPalletNo("P1");
|
||||
var balance = new InventoryBalance(); balance.setId(40L); balance.setCustomerId("C"); balance.setAvailablePackages(9L);
|
||||
when(balances.selectById(40L)).thenReturn(balance); when(ledgers.insert(any(InventoryLedger.class))).thenReturn(1);
|
||||
repository.recordDropship("C", app, item, position, 1, "operator", LocalDateTime.now());
|
||||
var fact = ArgumentCaptor.forClass(InventoryLedger.class); verify(ledgers).insert(fact.capture());
|
||||
assertEquals(10L, fact.getValue().getBeforePackages()); assertEquals(-1L, fact.getValue().getDeltaPackages());
|
||||
assertEquals(9L, fact.getValue().getAfterPackages()); assertEquals("CD-1", fact.getValue().getBusinessOrderNo());
|
||||
assertEquals("DISCARD", fact.getValue().getBusinessType()); assertEquals("P1", fact.getValue().getPalletNo());
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.baiyue.lastleg.discard;
|
||||
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnDiscardMovementMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryMovementMapper;
|
||||
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.UUID;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/** 用真实 SQL 验证完成事实、历史兼容及客户箱号隔离。 */
|
||||
class ReturnDiscardMovementMapperTest {
|
||||
/** 历史已完成废弃展示负件数;未完成、其他客户及其他仓记录均排除。 */
|
||||
@Test
|
||||
void completedFactsAreVisibleInMovementsAndCartonHistory() {
|
||||
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_cargo_discard_application(id BIGINT,customer_id VARCHAR(20),overseas_warehouse_id VARCHAR(20),discard_no VARCHAR(30))");
|
||||
db.execute("CREATE TABLE ll_return_inventory_carton(id BIGINT,customer_id VARCHAR(20),overseas_warehouse_id VARCHAR(20))");
|
||||
db.execute("ALTER TABLE ll_return_inventory_carton ADD carton_mark VARCHAR(30), ADD source_batch_no VARCHAR(30), ADD created_at TIMESTAMP");
|
||||
db.execute("CREATE TABLE ll_return_inventory_carton_item(carton_id BIGINT,sku_code VARCHAR(30),product_name_zh VARCHAR(30),quantity BIGINT)");
|
||||
db.execute("CREATE TABLE ll_return_receipt(id BIGINT,return_order_id BIGINT,receipt_no VARCHAR(30),signed_at TIMESTAMP)");
|
||||
db.execute("CREATE TABLE ll_return_order(id BIGINT,customer_id VARCHAR(20),overseas_warehouse_id VARCHAR(20))");
|
||||
db.execute("CREATE TABLE ll_return_receipt_item(receipt_id BIGINT,sku_code VARCHAR(30),return_order_item_id BIGINT,received_quantity BIGINT)");
|
||||
db.execute("CREATE TABLE ll_return_order_item(id BIGINT,product_name_zh VARCHAR(30))");
|
||||
db.execute("CREATE TABLE ll_return_inventory_outbound_movement(customer_id VARCHAR(20),overseas_warehouse_id VARCHAR(20),sku_code VARCHAR(30),product_name_zh VARCHAR(30),outbound_no VARCHAR(30),quantity BIGINT,occurred_at TIMESTAMP)");
|
||||
db.execute("CREATE TABLE ll_return_carton_stock_receipt(customer_id VARCHAR(20),overseas_warehouse_id VARCHAR(20),sku_code VARCHAR(30),product_name_zh VARCHAR(30),receipt_no VARCHAR(30),quantity BIGINT,received_at TIMESTAMP)");
|
||||
db.execute("CREATE TABLE ll_return_sku_conversion(customer_id VARCHAR(20),overseas_warehouse_id VARCHAR(20),old_sku_code VARCHAR(30),new_sku_code VARCHAR(30),product_name_zh VARCHAR(30),relabel_no VARCHAR(30),quantity BIGINT,completed_at TIMESTAMP,reversed_at TIMESTAMP,status VARCHAR(30))");
|
||||
db.execute("CREATE TABLE ll_cargo_discard_item(id BIGINT,application_id BIGINT,customer_id VARCHAR(20),return_carton_id BIGINT,sku_code VARCHAR(30),product_name VARCHAR(30),pda_status VARCHAR(20),available_packages_snapshot INT,actual_discard_packages INT,pda_operator_id VARCHAR(20),pda_completed_at TIMESTAMP)");
|
||||
db.update("INSERT INTO ll_cargo_discard_application VALUES(1,'C','W','CD-1'),(2,'OTHER','W','CD-2'),(3,'C','OTHER-W','CD-3')");
|
||||
db.update("INSERT INTO ll_return_inventory_carton(id,customer_id,overseas_warehouse_id) VALUES(10,'C','W'),(20,'OTHER','W')");
|
||||
db.update("INSERT INTO ll_cargo_discard_item VALUES(1,1,'C',10,'SKU','货物','COMPLETED',10,5,'OP',CURRENT_TIMESTAMP),(2,1,'C',10,'SKU','货物','PENDING',10,5,NULL,NULL),(3,2,'OTHER',20,'SKU','货物','COMPLETED',5,5,'OP',CURRENT_TIMESTAMP),(4,3,'C',10,'SKU','货物','COMPLETED',5,5,'OP',CURRENT_TIMESTAMP)");
|
||||
var config = new Configuration(new Environment("test", new JdbcTransactionFactory(), source));
|
||||
config.addMapper(ReturnDiscardMovementMapper.class);
|
||||
config.addMapper(ReturnInventoryMovementMapper.class);
|
||||
try (var session = new SqlSessionFactoryBuilder().build(config).openSession()) {
|
||||
var mapper = session.getMapper(ReturnDiscardMovementMapper.class);
|
||||
var history = mapper.cartonHistory("C", 10L);
|
||||
assertEquals(1, history.size()); assertEquals("CD-1", history.get(0).discardNo());
|
||||
assertEquals(5, history.get(0).quantity()); assertEquals(5, history.get(0).afterQuantity());
|
||||
assertTrue(mapper.cartonHistory("OTHER", 10L).isEmpty());
|
||||
assertEquals(2, db.queryForObject("SELECT COUNT(*) FROM (" + ReturnDiscardMovementMapper.MOVEMENTS + ") m", Integer.class));
|
||||
var inventory = session.getMapper(ReturnInventoryMovementMapper.class);
|
||||
assertEquals(1, inventory.platformCount("C", "W", "SKU"));
|
||||
var movement = inventory.platformPage("C", "W", "SKU", 0L, 20L).get(0);
|
||||
assertEquals("DISCARD", movement.movementType()); assertEquals("CD-1", movement.referenceNo());
|
||||
assertEquals(-5, movement.totalDelta()); assertEquals(-5, movement.availableDelta());
|
||||
assertEquals(0, inventory.platformCount("C", "W", "MISSING-SKU"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -50,11 +50,15 @@ class CargoDiscardInventoryAdjustmentServiceTest {
|
||||
@Mock DropshipOrderProfileMapper profiles;
|
||||
@Mock InventoryPositionMapper positions;
|
||||
@Mock InventoryBalanceMapper balances;
|
||||
@Mock com.baiyue.lastleg.discard.repository.CargoDiscardStockLedgerRepository stockLedger;
|
||||
@Mock com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper returnItems;
|
||||
private CargoDiscardInventoryAdjustmentService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new CargoDiscardInventoryAdjustmentService();
|
||||
ReflectionTestUtils.setField(service, "stockLedger", stockLedger);
|
||||
ReflectionTestUtils.setField(service, "returnCartonItems", returnItems);
|
||||
ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
ReflectionTestUtils.setField(service, "cartonLedgers", ledgers);
|
||||
ReflectionTestUtils.setField(service, "cartonSkus", cartonSkus);
|
||||
@@ -64,6 +68,21 @@ class CargoDiscardInventoryAdjustmentServiceTest {
|
||||
ReflectionTestUtils.setField(service, "balances", balances);
|
||||
}
|
||||
|
||||
/** 退货来源按 SKU 件数同时扣箱明细和客户库存汇总。 */
|
||||
@Test
|
||||
void returnDiscardSynchronizesCartonAndBalance() {
|
||||
var app = application(1);
|
||||
var line = item(5); line.setSourceType("RETURN_CARTON"); line.setReturnCartonId(31L);
|
||||
var content = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem();
|
||||
content.setId(32L); content.setCustomerId("100"); content.setQuantity(5L);
|
||||
when(returnItems.lockSku(31L, "SKU-1")).thenReturn(content);
|
||||
when(returnItems.deduct(32L, 5)).thenReturn(1);
|
||||
var result = service.discardPalletItem("100", app, line, List.of(), 5, "OP", LocalDateTime.now());
|
||||
assertEquals(5, result.discardedPackages()); assertEquals(0, result.afterPackages());
|
||||
verify(stockLedger).deductReturn("100", app, line, 5L, 5L);
|
||||
org.mockito.Mockito.verifyNoInteractions(positions, balances, ledgers);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaDiscardDeductsEverySkuInsideOnePhysicalCartonOnce() {
|
||||
CargoDiscardApplication application = application(1);
|
||||
@@ -96,6 +115,7 @@ class CargoDiscardInventoryAdjustmentServiceTest {
|
||||
verify(skuInventories).deduct("100", 201L, "SKU-A", 1, 2);
|
||||
verify(skuInventories).deduct("100", 201L, "SKU-B", 1, 3);
|
||||
verify(positions).deductAvailable("100", 801L, 1);
|
||||
verify(stockLedger).recordDropship(eq("100"), eq(application), eq(item), eq(position), eq(1L), eq("operator"), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
@@ -88,6 +88,7 @@ class ReturnInventoryServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new ReturnInventoryService();
|
||||
ReflectionTestUtils.setField(service, "discardMovements", org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.repository.ReturnDiscardMovementRepository.class));
|
||||
ReflectionTestUtils.setField(service, "skuConversions", org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnSkuConversionMapper.class));
|
||||
ReflectionTestUtils.setField(service, "cartons", cartons);
|
||||
ReflectionTestUtils.setField(service, "cartonItems", cartonItems);
|
||||
|
||||
Reference in New Issue
Block a user