优化
This commit is contained in:
@@ -751,6 +751,14 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab
|
||||
'ALTER TABLE ll_inventory_balance MODIFY COLUMN sku_code VARCHAR(128) NOT NULL DEFAULT ''UNSPECIFIED'' COMMENT ''SKU快照;多SKU汇总时为MIXED,不参与当前库存唯一维度''',
|
||||
'ALTER TABLE ll_inventory_balance ADD COLUMN sku_code VARCHAR(128) NOT NULL DEFAULT ''UNSPECIFIED'' COMMENT ''SKU快照;多SKU汇总时为MIXED,不参与当前库存唯一维度'' AFTER inbound_code'));
|
||||
PREPARE phase4_inventory_stmt FROM @ddl; EXECUTE phase4_inventory_stmt; DEALLOCATE PREPARE phase4_inventory_stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_balance' AND column_name='customer_name'),
|
||||
'ALTER TABLE ll_inventory_balance MODIFY COLUMN customer_name VARCHAR(255) NOT NULL COMMENT ''客户名称不可变快照''',
|
||||
'ALTER TABLE ll_inventory_balance ADD COLUMN customer_name VARCHAR(255) NOT NULL COMMENT ''客户名称不可变快照'' AFTER customer_id'));
|
||||
PREPARE phase4_inventory_stmt FROM @ddl; EXECUTE phase4_inventory_stmt; DEALLOCATE PREPARE phase4_inventory_stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='customer_name'),
|
||||
'ALTER TABLE ll_inventory_ledger MODIFY COLUMN customer_name VARCHAR(255) NOT NULL COMMENT ''客户名称不可变快照''',
|
||||
'ALTER TABLE ll_inventory_ledger ADD COLUMN customer_name VARCHAR(255) NOT NULL COMMENT ''客户名称不可变快照'' AFTER customer_id'));
|
||||
PREPARE phase4_inventory_stmt FROM @ddl; EXECUTE phase4_inventory_stmt; DEALLOCATE PREPARE phase4_inventory_stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_balance' AND column_name='product_name_zh'),
|
||||
'SELECT 1', 'ALTER TABLE ll_inventory_balance ADD COLUMN product_name_zh VARCHAR(255) NULL COMMENT ''中文品名快照'' AFTER sku_code'));
|
||||
PREPARE phase4_inventory_stmt FROM @ddl; EXECUTE phase4_inventory_stmt; DEALLOCATE PREPARE phase4_inventory_stmt;
|
||||
|
||||
+9
@@ -6,6 +6,8 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ReceivingContainerSessionMapper extends BaseMapper<ReceivingContainerSession> {
|
||||
@Select("SELECT * FROM ll_receiving_container_session " +
|
||||
@@ -16,6 +18,13 @@ public interface ReceivingContainerSessionMapper extends BaseMapper<ReceivingCon
|
||||
"WHERE active_operator_key=#{activeOperatorKey} LIMIT 1")
|
||||
ReceivingContainerSession findActiveByOperator(@Param("activeOperatorKey") String activeOperatorKey);
|
||||
|
||||
@Select("SELECT * FROM ll_receiving_container_session " +
|
||||
"WHERE active_container_key=#{activeContainerKey} LIMIT 1")
|
||||
ReceivingContainerSession findActiveByContainer(@Param("activeContainerKey") String activeContainerKey);
|
||||
|
||||
@Select("SELECT * FROM ll_receiving_container_session WHERE active_container_key IS NOT NULL")
|
||||
List<ReceivingContainerSession> findActiveContainers();
|
||||
|
||||
@Select("SELECT * FROM ll_receiving_container_session " +
|
||||
"WHERE active_container_key=#{activeContainerKey} LIMIT 1 FOR UPDATE")
|
||||
ReceivingContainerSession lockActiveByContainer(@Param("activeContainerKey") String activeContainerKey);
|
||||
|
||||
+13
-2
@@ -194,11 +194,22 @@ public class InboundStockReceiptService {
|
||||
}
|
||||
if (destinationCode == null) destinationCode = "";
|
||||
String operationKey = "PDA_STOCK_IN:" + task.getId() + ":" + allocation.getId() + ":" + sku.skuCode();
|
||||
String customerName = required(order.getCustomerName(),
|
||||
"Customer name snapshot is required for stock-in");
|
||||
String overseasWarehouseId = required(order.getOverseasWarehouseId(),
|
||||
"Actual overseas warehouse is required for stock-in");
|
||||
String overseasWarehouseName = required(order.getOverseasWarehouseName(),
|
||||
"Actual overseas warehouse name is required for stock-in");
|
||||
if (plan.getOverseasWarehouseId() == null
|
||||
|| !overseasWarehouseId.equals(String.valueOf(plan.getOverseasWarehouseId()))) {
|
||||
throw conflict("Inbound plan and tail-leg order overseas warehouses must match for stock-in");
|
||||
}
|
||||
if (overseasWarehouseId.equals(trim(order.getSourceWarehouseId()))) {
|
||||
throw conflict("Actual overseas warehouse must not use the domestic source warehouse");
|
||||
}
|
||||
InventoryCommandResponse result = inventory.stockIn(customerId, task.getOperatorId(),
|
||||
new InventoryCommandRequests.StockIn(null, order.getCustomerName(), overseasWarehouseId,
|
||||
null, order.getOverseasWarehouseName(), line.getDestinationWarehouseId(), destinationCode,
|
||||
new InventoryCommandRequests.StockIn(null, customerName, overseasWarehouseId,
|
||||
null, overseasWarehouseName, line.getDestinationWarehouseId(), destinationCode,
|
||||
line.getDestinationWarehouseName(), order.getId(), plan.getPlanNo(), line.getInboundCode(),
|
||||
sku.skuCode(), sku.productNameZh(), sku.productNameEn(),
|
||||
pallet.getWarehousePalletId(), pallet.getPalletNo(), plan.getPlanNo(),
|
||||
|
||||
+12
-1
@@ -103,7 +103,14 @@ public class ReceivingContainerService {
|
||||
grouped.computeIfAbsent(plan.getContainerNo(), ignored -> new ArrayList<>()).add(plan);
|
||||
}
|
||||
});
|
||||
return grouped.entrySet().stream().filter(entry -> eligible(entry.getValue(), overseasWarehouseId)).map(entry -> {
|
||||
String operatorLockKey = activeOperatorKey(operatorKey);
|
||||
Set<String> reservedByOthers = sessions.findActiveContainers().stream()
|
||||
.filter(session -> !operatorLockKey.equals(session.getActiveOperatorKey()))
|
||||
.map(ReceivingContainerSession::getActiveContainerKey).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
return grouped.entrySet().stream()
|
||||
.filter(entry -> eligible(entry.getValue(), overseasWarehouseId))
|
||||
.filter(entry -> !reservedByOthers.contains(activeContainerKey(entry.getKey())))
|
||||
.map(entry -> {
|
||||
requireSingleCustomer(entry.getValue(), entry.getKey());
|
||||
Totals totals = totals(entry.getValue());
|
||||
boolean active = current != null && entry.getKey().equals(current.getContainerNo());
|
||||
@@ -123,6 +130,10 @@ public class ReceivingContainerService {
|
||||
if (current != null && !containerNo.equals(current.getContainerNo())) {
|
||||
throw conflict("Current account is locked to another selected container");
|
||||
}
|
||||
ReceivingContainerSession reserved = sessions.findActiveByContainer(activeContainerKey(containerNo));
|
||||
if (reserved != null && !activeOperatorKey(operatorKey).equals(reserved.getActiveOperatorKey())) {
|
||||
throw conflict("Container is already signed or prepared by another account");
|
||||
}
|
||||
WarehouseResourceGateway.WarehouseReference receivingWarehouse =
|
||||
warehouse.requireActiveSharedWarehouse(overseasWarehouseId);
|
||||
return snapshot(current != null && containerNo.equals(current.getContainerNo()) ? current : waitingView(
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ public final class InventoryCommandRequests {
|
||||
|
||||
public record StockIn(
|
||||
String customerId,
|
||||
String customerName,
|
||||
@NotBlank @Size(max = 255) String customerName,
|
||||
@NotBlank String overseasWarehouseId,
|
||||
String overseasWarehouseCode,
|
||||
String overseasWarehouseName,
|
||||
@@ -39,7 +39,7 @@ public final class InventoryCommandRequests {
|
||||
}
|
||||
|
||||
public record InTransit(
|
||||
String customerName,
|
||||
@NotBlank @Size(max = 255) String customerName,
|
||||
@NotBlank String overseasWarehouseId,
|
||||
String overseasWarehouseCode,
|
||||
String overseasWarehouseName,
|
||||
|
||||
+15
-1
@@ -232,6 +232,7 @@ public class InventoryCommandService {
|
||||
String destinationWarehouseName, Long inboundOrderId, String inboundOrderNo,
|
||||
String inboundCode, String requestedSkuCode, String productNameZh,
|
||||
String productNameEn) {
|
||||
String frozenCustomerName = required(customerName, "Customer name snapshot is required");
|
||||
String skuCode = sku(requestedSkuCode);
|
||||
String destinationId = trim(destinationWarehouseId);
|
||||
String destinationCode = trim(destinationWarehouseCode);
|
||||
@@ -242,13 +243,14 @@ public class InventoryCommandService {
|
||||
InventoryBalance balance = balances.lockDimension(customerId, overseasWarehouseId,
|
||||
destinationCode, inboundOrderId, inboundCode);
|
||||
if (balance != null) {
|
||||
requireCustomerSnapshot(balance, frozenCustomerName);
|
||||
mergeSkuSnapshot(balance, skuCode);
|
||||
return balance;
|
||||
}
|
||||
balance = new InventoryBalance();
|
||||
balance.setId(IdWorker.getId());
|
||||
balance.setCustomerId(customerId);
|
||||
balance.setCustomerName(trim(customerName));
|
||||
balance.setCustomerName(frozenCustomerName);
|
||||
balance.setOverseasWarehouseId(overseasWarehouseId.trim());
|
||||
balance.setOverseasWarehouseCode(trim(overseasWarehouseCode));
|
||||
balance.setOverseasWarehouseName(trim(overseasWarehouseName));
|
||||
@@ -272,6 +274,7 @@ public class InventoryCommandService {
|
||||
InventoryBalance concurrent = balances.lockDimension(customerId, overseasWarehouseId,
|
||||
destinationCode, inboundOrderId, inboundCode);
|
||||
if (concurrent != null) {
|
||||
requireCustomerSnapshot(concurrent, frozenCustomerName);
|
||||
mergeSkuSnapshot(concurrent, skuCode);
|
||||
return concurrent;
|
||||
}
|
||||
@@ -384,6 +387,7 @@ public class InventoryCommandService {
|
||||
InventoryLedger ledger = new InventoryLedger();
|
||||
ledger.setId(IdWorker.getId());
|
||||
ledger.setCustomerId(customerId);
|
||||
ledger.setCustomerName(balance.getCustomerName());
|
||||
ledger.setBalanceId(balance.getId());
|
||||
ledger.setPositionId(position == null ? null : position.getId());
|
||||
ledger.setOverseasWarehouseId(balance.getOverseasWarehouseId());
|
||||
@@ -515,6 +519,16 @@ public class InventoryCommandService {
|
||||
if (actual < requested) throw conflict("Insufficient " + field + " packages");
|
||||
}
|
||||
|
||||
private static void requireCustomerSnapshot(InventoryBalance balance, String requestedCustomerName) {
|
||||
String frozenCustomerName = trim(balance.getCustomerName());
|
||||
if (frozenCustomerName == null) {
|
||||
throw conflict("Inventory balance customer name snapshot is missing");
|
||||
}
|
||||
if (!frozenCustomerName.equals(requestedCustomerName)) {
|
||||
throw conflict("Inventory balance customer name snapshot does not match");
|
||||
}
|
||||
}
|
||||
|
||||
private static String required(String value, String message) {
|
||||
String normalized = trim(value);
|
||||
if (normalized == null) throw badRequest(message);
|
||||
|
||||
@@ -15,6 +15,7 @@ public class InventoryLedger {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private String customerId;
|
||||
private String customerName;
|
||||
private Long balanceId;
|
||||
private Long positionId;
|
||||
private String overseasWarehouseId;
|
||||
|
||||
+1
@@ -5,6 +5,7 @@ import java.time.LocalDateTime;
|
||||
public record InventoryLedgerView(
|
||||
Long id,
|
||||
String customerId,
|
||||
String customerName,
|
||||
String overseasWarehouseId,
|
||||
String destinationWarehouseCode,
|
||||
Long inboundOrderId,
|
||||
|
||||
+3
-3
@@ -97,11 +97,11 @@ public class InventoryQueryService {
|
||||
public byte[] exportLedgers(String customerId, InventoryQueryRequests.LedgerQuery query) {
|
||||
validateDateRange(query);
|
||||
List<String> rows = new ArrayList<>();
|
||||
rows.add("occurredAt,customerId,overseasWarehouseId,destinationWarehouseCode,inboundOrderId,inboundOrderNo," +
|
||||
rows.add("occurredAt,customerId,customerName,overseasWarehouseId,destinationWarehouseCode,inboundOrderId,inboundOrderNo," +
|
||||
"inboundCode,businessType,businessOrderNo,outboundOrderNo,palletNo,batchNo,statusFrom,statusTo," +
|
||||
"beforePackages,deltaPackages,afterPackages,operatorId,remark");
|
||||
mapper.ledgers(customerId, query, 0, EXPORT_LIMIT).stream().map(this::ledgerView).forEach(v -> rows.add(
|
||||
String.join(",", csv(v.occurredAt()), csv(v.customerId()), csv(v.overseasWarehouseId()),
|
||||
String.join(",", csv(v.occurredAt()), csv(v.customerId()), csv(v.customerName()), csv(v.overseasWarehouseId()),
|
||||
csv(v.destinationWarehouseCode()), csv(v.inboundOrderId()), csv(v.inboundOrderNo()), csv(v.inboundCode()),
|
||||
csv(v.businessType()), csv(v.businessOrderNo()), csv(v.outboundOrderNo()), csv(v.palletNo()), csv(v.batchNo()),
|
||||
csv(v.statusFrom()), csv(v.statusTo()), csv(v.beforePackages()), csv(v.deltaPackages()),
|
||||
@@ -144,7 +144,7 @@ public class InventoryQueryService {
|
||||
}
|
||||
|
||||
private InventoryLedgerView ledgerView(InventoryLedger value) {
|
||||
return new InventoryLedgerView(value.getId(), value.getCustomerId(), value.getOverseasWarehouseId(),
|
||||
return new InventoryLedgerView(value.getId(), value.getCustomerId(), value.getCustomerName(), value.getOverseasWarehouseId(),
|
||||
value.getDestinationWarehouseCode(), value.getInboundOrderId(), value.getInboundOrderNo(), value.getInboundCode(),
|
||||
value.getPalletNo(), value.getBatchNo(),
|
||||
value.getWarehouseLocationCode(), value.getSkuCode(), value.getBusinessType(), value.getBusinessOrderNo(),
|
||||
|
||||
@@ -863,7 +863,7 @@ ALTER TABLE ll_loading_order_status_history
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ll_inventory_balance (
|
||||
id BIGINT NOT NULL COMMENT '主键ID',
|
||||
customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', customer_name VARCHAR(255) NULL COMMENT '客户名称快照',
|
||||
customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', customer_name VARCHAR(255) NOT NULL COMMENT '客户名称不可变快照',
|
||||
overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID',
|
||||
overseas_warehouse_code VARCHAR(128) NULL COMMENT '海外仓编码快照',
|
||||
overseas_warehouse_name VARCHAR(255) NULL COMMENT '海外仓名称快照',
|
||||
@@ -908,7 +908,7 @@ CREATE TABLE IF NOT EXISTS ll_inventory_position (
|
||||
) ENGINE=InnoDB COMMENT='托盘库位批次库存明细';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ll_inventory_ledger (
|
||||
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
|
||||
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', customer_name VARCHAR(255) NOT NULL COMMENT '客户名称不可变快照',
|
||||
balance_id BIGINT NOT NULL COMMENT '库存余额记录ID', position_id BIGINT NULL COMMENT '库存仓位记录ID', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID',
|
||||
destination_warehouse_code VARCHAR(128) NOT NULL DEFAULT '' COMMENT '目的仓编码快照', inbound_order_id BIGINT NOT NULL COMMENT '入仓单ID',
|
||||
pallet_id BIGINT NULL COMMENT '托盘ID', pallet_no VARCHAR(64) NULL COMMENT '托盘号', batch_no VARCHAR(128) NULL COMMENT '库存批次号',
|
||||
|
||||
+21
-3
@@ -83,6 +83,9 @@ class InboundStockReceiptServiceTest {
|
||||
assertEquals(101L, request.getValue().inboundOrderId());
|
||||
assertEquals("IN-1", request.getValue().inboundCode());
|
||||
assertEquals("SKU-1", request.getValue().skuCode());
|
||||
assertEquals("Customer", request.getValue().customerName());
|
||||
assertEquals("900", request.getValue().overseasWarehouseId());
|
||||
assertEquals("Los Angeles", request.getValue().overseasWarehouseName());
|
||||
ArgumentCaptor<InboundStockReceipt> receipt = ArgumentCaptor.forClass(InboundStockReceipt.class);
|
||||
verify(receipts).insert(receipt.capture());
|
||||
assertEquals(5L, receipt.getValue().getReceivedPackages());
|
||||
@@ -210,16 +213,31 @@ class InboundStockReceiptServiceTest {
|
||||
assertEquals(10L, activationReceipts.getValue().get(0).getInventoryPositionId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDomesticSourceWarehouseAsActualOverseasWarehouse() {
|
||||
Fixture fixture = fixture(5, 5, 5, 10);
|
||||
fixture.order.setOverseasWarehouseId(fixture.order.getSourceWarehouseId());
|
||||
fixture.plan.setOverseasWarehouseId(Long.valueOf(fixture.order.getSourceWarehouseId()));
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.stockIn("100", fixture.plan, fixture.task,
|
||||
List.of(fixture.line), List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now()));
|
||||
|
||||
verify(inventory, never()).stockIn(any(), any(), any());
|
||||
verify(receipts, never()).insert(any(InboundStockReceipt.class));
|
||||
}
|
||||
|
||||
private Fixture fixture(int required, int planned, int picked, int quantity) {
|
||||
InboundPlan plan = new InboundPlan(); plan.setId(1L); plan.setPlanNo("IP-1");
|
||||
InboundPlan plan = new InboundPlan(); plan.setId(1L); plan.setPlanNo("IP-1"); plan.setOverseasWarehouseId(900L);
|
||||
InboundPdaTask task = new InboundPdaTask(); task.setId(2L); task.setOperatorId("operator");
|
||||
InboundPlanLine line = new InboundPlanLine(); line.setId(12L); line.setPlanId(1L); line.setOrderId(101L);
|
||||
line.setInboundCode("IN-1"); line.setDestinationWarehouseId("WH-DEST");
|
||||
line.setDestinationWarehouseCode("ONT8"); line.setDestinationWarehouseName("ONT8");
|
||||
line.setRequiredPackageCount(required); line.setVolumeCbm(new BigDecimal("1.0"));
|
||||
LastlegOrder order = new LastlegOrder(); order.setId(101L); order.setCustomerId("100");
|
||||
order.setOrderNo("LMO-1"); order.setCustomerName("Customer"); order.setOverseasWarehouseId("OW-1");
|
||||
order.setOverseasWarehouseName("Warehouse"); order.setSourceEventId("evt-1"); order.setFbaWarehouseCode("ONT8");
|
||||
order.setOrderNo("LMO-1"); order.setCustomerName("Customer"); order.setSourceWarehouseId("100");
|
||||
order.setOverseasWarehouseId("900"); order.setOverseasWarehouseName("Los Angeles");
|
||||
order.setSourceEventId("evt-1"); order.setFbaWarehouseCode("ONT8");
|
||||
order.setCurrentStatus("INBOUND_PLANNED");
|
||||
org.mockito.Mockito.lenient().when(orders.lockById("100", 101L)).thenReturn(order);
|
||||
LastlegOrderDetail detail = detail(201L, "SKU-1", required, quantity);
|
||||
|
||||
+27
@@ -155,6 +155,33 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
.containsExactly("CONT-A", "CONT-B");
|
||||
}
|
||||
|
||||
@Test
|
||||
void candidatesHideContainersReservedByAnotherOperator() {
|
||||
InboundPlan available = plan("CONT-A");
|
||||
InboundPlan reserved = plan("CONT-B"); reserved.setId(3L);
|
||||
ReceivingContainerSession other = session("CONT-B", "WAIT_SIGN");
|
||||
other.setOperatorKey("8"); other.setActiveOperatorKey("OPERATOR:8");
|
||||
other.setActiveContainerKey("CONTAINER:CONT-B");
|
||||
when(plans.selectList(any())).thenReturn(List.of(available, reserved), List.of(available));
|
||||
when(lines.selectList(any())).thenReturn(List.of());
|
||||
when(sessions.findActiveContainers()).thenReturn(List.of(other));
|
||||
|
||||
assertThat(service.candidates("7", 100L)).extracting("containerNo").containsExactly("CONT-A");
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailRejectsContainerReservedByAnotherOperator() {
|
||||
when(plans.selectList(any())).thenReturn(List.of(plan("CONT-B")));
|
||||
ReceivingContainerSession other = session("CONT-B", "SIGNED");
|
||||
other.setOperatorKey("8"); other.setActiveOperatorKey("OPERATOR:8");
|
||||
other.setActiveContainerKey("CONTAINER:CONT-B");
|
||||
when(sessions.findActiveByContainer("CONTAINER:CONT-B")).thenReturn(other);
|
||||
|
||||
assertThatThrownBy(() -> service.detail("7", 100L, "CONT-B"))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
verifyNoInteractions(warehouse);
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitSignSelectionAlreadyLocksAccountFromViewingAnotherContainer() {
|
||||
when(plans.selectList(any())).thenReturn(List.of(plan("CONT-B")));
|
||||
|
||||
+33
-2
@@ -72,8 +72,11 @@ class InventoryCommandServiceTest {
|
||||
verify(balances).updateById(balance.capture());
|
||||
assertEquals(12L, balance.getValue().getAvailablePackages());
|
||||
assertEquals("SKU-1", balance.getValue().getSkuCode());
|
||||
assertEquals("Customer 100", balance.getValue().getCustomerName());
|
||||
verify(positions).insert(any(InventoryPosition.class));
|
||||
verify(ledgers).insert(any(InventoryLedger.class));
|
||||
ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(ledgers).insert(ledger.capture());
|
||||
assertEquals("Customer 100", ledger.getValue().getCustomerName());
|
||||
verify(operations).updateById(any(InventoryOperation.class));
|
||||
}
|
||||
|
||||
@@ -152,6 +155,33 @@ class InventoryCommandServiceTest {
|
||||
verify(ledgers).insert(any(InventoryLedger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockInRejectsMissingCustomerSnapshotBeforeInventoryMutation() {
|
||||
InventoryCommandRequests.StockIn request = new InventoryCommandRequests.StockIn(
|
||||
"100", null, "WH-1", "WH1", "Warehouse 1", "DEST-1", "ONT8", "ONT8",
|
||||
101L, "IN-1", "IN-CODE-1", "SKU-1", "Product 1", null, 300L, "PLT-1", null,
|
||||
null, null, 1, "ORD-1", "EVT-1", "missing-customer", null);
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.stockIn("100", "7", request));
|
||||
|
||||
verify(balances, never()).insert(any(InventoryBalance.class));
|
||||
verify(positions, never()).insert(any(InventoryPosition.class));
|
||||
verify(ledgers, never()).insert(any(InventoryLedger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockInRejectsCustomerSnapshotDrift() {
|
||||
InventoryBalance balance = balance(200L, 4);
|
||||
balance.setCustomerName("Frozen customer");
|
||||
when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(balance);
|
||||
|
||||
assertThrows(ResponseStatusException.class,
|
||||
() -> service.stockIn("100", "7", stockIn("IN-1", "customer-drift", 1, 1)));
|
||||
|
||||
verify(positions, never()).insert(any(InventoryPosition.class));
|
||||
verify(ledgers, never()).insert(any(InventoryLedger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void holdRejectsWhenAvailablePackagesAreInsufficient() {
|
||||
InventoryBalance balance = balance(200L, 5);
|
||||
@@ -233,7 +263,8 @@ class InventoryCommandServiceTest {
|
||||
|
||||
private InventoryBalance balance(Long id, long available) {
|
||||
InventoryBalance value = new InventoryBalance();
|
||||
value.setId(id); value.setCustomerId("100"); value.setOverseasWarehouseId("WH-1");
|
||||
value.setId(id); value.setCustomerId("100"); value.setCustomerName("Customer 100");
|
||||
value.setOverseasWarehouseId("WH-1");
|
||||
value.setDestinationWarehouseCode("ONT8"); value.setInboundOrderId(101L);
|
||||
value.setInboundOrderNo("IN-1"); value.setInboundCode("IN-CODE-1");
|
||||
value.setAvailablePackages(available);
|
||||
|
||||
Reference in New Issue
Block a user