提交
This commit is contained in:
Binary file not shown.
@@ -16629,6 +16629,16 @@ CREATE TABLE IF NOT EXISTS iam_customer_dimension_initialization_task (
|
||||
KEY idx_customer_dimension_task_diagnosis (status,updated_at)
|
||||
) ENGINE=InnoDB COMMENT='IAM客户默认尺寸可靠后台初始化任务及诊断状态';
|
||||
|
||||
-- 兼容已存在的旧表:CREATE TABLE IF NOT EXISTS 不会为旧表补充新字段。
|
||||
SET @customer_dimension_lease_ddl = (SELECT IF(
|
||||
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||
AND table_name='iam_customer_dimension_initialization_task' AND column_name='lease_until'),
|
||||
'SELECT 1',
|
||||
'ALTER TABLE iam_customer_dimension_initialization_task ADD COLUMN lease_until DATETIME(3) NULL COMMENT ''当前任务租约截止时间'' AFTER lease_owner'));
|
||||
PREPARE customer_dimension_lease_stmt FROM @customer_dimension_lease_ddl;
|
||||
EXECUTE customer_dimension_lease_stmt;
|
||||
DEALLOCATE PREPARE customer_dimension_lease_stmt;
|
||||
|
||||
CALL baiyue_firstleg.assert_unified_final_schema();
|
||||
DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema;
|
||||
SELECT 'PASS' AS migration_status,
|
||||
|
||||
+2
-2
@@ -290,7 +290,7 @@ public class DeliveryTransferService {
|
||||
if (delivery != null) {
|
||||
for (LastlegOrder order : tailOrders.selectList(new LambdaQueryWrapper<LastlegOrder>().eq(LastlegOrder::getCustomerId, customerId).eq(LastlegOrder::getOutboundOrderNo, outboundNo))) {
|
||||
String from = order.getCurrentStatus(); order.setCurrentStatus("RECEIVED"); order.setSubOrderStatus(null); order.setOutboundOrderNo(null); order.setDeliveryOrderNo(null); order.setUpdatedAt(now); tailOrders.updateById(order);
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setOrderId(order.getId()); history.setFromStatus(from); history.setToStatus("RECEIVED"); history.setEventId("CANCEL_OUTBOUND:" + outboundNo); history.setRemark(request.reason().trim()); history.setCreatedAt(now); histories.insert(history);
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setCustomerId(customerId); history.setOrderId(order.getId()); history.setFromStatus(from); history.setToStatus("RECEIVED"); history.setEventId("CANCEL_OUTBOUND:" + outboundNo); history.setRemark(request.reason().trim()); history.setCreatedAt(now); histories.insert(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,7 +375,7 @@ public class DeliveryTransferService {
|
||||
order.setDeliveryOrderNo(deliveryNo); order.setCurrentStatus("WAIT_OUTBOUND"); order.setUpdatedAt(now);
|
||||
order.setSubOrderStatus("OUTBOUND:WAIT_OUTBOUND;DELIVERY:WAIT_DELIVERY");
|
||||
tailOrders.updateById(order);
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setOrderId(order.getId());
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setCustomerId(customerId); history.setOrderId(order.getId());
|
||||
history.setFromStatus(from); history.setToStatus("WAIT_OUTBOUND");
|
||||
history.setEventId("TRANSFER_DELIVERY:" + deliveryNo);
|
||||
history.setRemark(StringUtils.hasText(remark) ? remark.trim() : "Transferred to outbound and delivery documents");
|
||||
|
||||
+1
@@ -84,6 +84,7 @@ public class DispatchPickOrderAttributionService {
|
||||
long shipped = value(before.getShippedPackageCount()) + entry.getValue();
|
||||
String to = shipped == value(before.getPackageCount()) ? "COMPLETED" : "PARTIALLY_SHIPPED";
|
||||
LastlegOrderStatusHistory row = new LastlegOrderStatusHistory();
|
||||
row.setCustomerId(customerId);
|
||||
row.setOrderId(entry.getKey());
|
||||
row.setFromStatus(before.getCurrentStatus());
|
||||
row.setToStatus(to);
|
||||
|
||||
+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 InventoryCommandMapper {
|
||||
@Select("SELECT * FROM ll_inventory_position WHERE id=#{positionId} LIMIT 1")
|
||||
@@ -22,4 +24,11 @@ public interface InventoryCommandMapper {
|
||||
@Select("SELECT COALESCE(SUM(available_packages+hold_packages+allocated_packages+picked_packages+receiving_packages),0) " +
|
||||
"FROM ll_inventory_position WHERE customer_id=#{customerId} AND pallet_id=#{palletId}")
|
||||
long palletPackages(@Param("customerId") String customerId, @Param("palletId") Long palletId);
|
||||
|
||||
@Select("SELECT * FROM ll_inventory_balance WHERE customer_id=#{customerId} " +
|
||||
"AND inbound_order_id=#{inboundOrderId} AND inbound_code=#{inboundCode} " +
|
||||
"AND in_transit_packages>0 ORDER BY id FOR UPDATE")
|
||||
List<InventoryBalance> lockInboundTransitBalances(@Param("customerId") String customerId,
|
||||
@Param("inboundOrderId") Long inboundOrderId,
|
||||
@Param("inboundCode") String inboundCode);
|
||||
}
|
||||
|
||||
+32
-2
@@ -27,6 +27,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class InventoryCommandService {
|
||||
@@ -60,6 +61,8 @@ public class InventoryCommandService {
|
||||
public InventoryCommandResponse stockIn(String customerId, String operatorId,
|
||||
InventoryCommandRequests.StockIn request) {
|
||||
return execute(customerId, operatorId, "STOCK_IN", request.idempotencyKey(), request, operation -> {
|
||||
List<InventoryBalance> transitBalances = commands.lockInboundTransitBalances(customerId,
|
||||
request.inboundOrderId(), request.inboundCode());
|
||||
InventoryBalance balance = balance(customerId, request);
|
||||
InventoryPosition position = position(customerId, balance, request);
|
||||
long before = value(position.getAvailablePackages());
|
||||
@@ -69,8 +72,8 @@ public class InventoryCommandService {
|
||||
position.setUpdatedAt(LocalDateTime.now());
|
||||
positions.updateById(position);
|
||||
|
||||
long transferred = Math.min(value(balance.getInTransitPackages()), request.packages());
|
||||
balance.setInTransitPackages(value(balance.getInTransitPackages()) - transferred);
|
||||
long transferred = transferInTransit(customerId, operatorId, operation.getId(), balance,
|
||||
transitBalances, request);
|
||||
balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages());
|
||||
touch(balance);
|
||||
balances.updateById(balance);
|
||||
@@ -83,6 +86,33 @@ public class InventoryCommandService {
|
||||
});
|
||||
}
|
||||
|
||||
private long transferInTransit(String customerId, String operatorId, Long operationId,
|
||||
InventoryBalance target, List<InventoryBalance> sources,
|
||||
InventoryCommandRequests.StockIn request) {
|
||||
long pending = request.packages();
|
||||
long transferred = 0;
|
||||
for (InventoryBalance source : sources) {
|
||||
if (pending == 0) break;
|
||||
boolean targetDimension = source.getId().equals(target.getId());
|
||||
long sourceBefore = value(targetDimension ? target.getInTransitPackages() : source.getInTransitPackages());
|
||||
long moved = Math.min(sourceBefore, pending);
|
||||
if (moved == 0) continue;
|
||||
if (targetDimension) target.setInTransitPackages(sourceBefore - moved);
|
||||
else source.setInTransitPackages(sourceBefore - moved);
|
||||
transferred += moved;
|
||||
pending -= moved;
|
||||
if (targetDimension) continue;
|
||||
touch(source);
|
||||
balances.updateById(source);
|
||||
ledger(customerId, operatorId, operationId, source, null, sku(request.skuCode()),
|
||||
"STOCK_IN_TRANSIT_RELEASE", "IN_TRANSIT", "STOCK_IN", sourceBefore, -moved,
|
||||
sourceBefore - moved, request.businessOrderNo(), request.inboundOrderNo(), null,
|
||||
request.sourceEventId(), request.idempotencyKey() + ":TRANSIT:" + source.getId(),
|
||||
"In-transit inventory transferred to overseas warehouse " + target.getOverseasWarehouseId());
|
||||
}
|
||||
return transferred;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InventoryCommandResponse hold(String customerId, String operatorId, InventoryCommandRequests.Move request) {
|
||||
return move(customerId, operatorId, "HOLD", Bucket.AVAILABLE, Bucket.HOLD, request);
|
||||
|
||||
+3
-1
@@ -192,7 +192,9 @@ class DeliveryTransferServiceTest {
|
||||
assertNotNull(tailOrder.getOutboundOrderNo());
|
||||
assertNotNull(tailOrder.getDeliveryOrderNo());
|
||||
verify(tailOrders).updateById(tailOrder);
|
||||
verify(histories).insert(any(LastlegOrderStatusHistory.class));
|
||||
ArgumentCaptor<LastlegOrderStatusHistory> history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class);
|
||||
verify(histories).insert(history.capture());
|
||||
assertEquals("TENANT-A", history.getValue().getCustomerId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -75,8 +75,8 @@ class DispatchPickOrderAttributionServiceTest {
|
||||
"t1", "OUT1", "D1", List.of(pick), LocalDateTime.of(2026, 8, 17, 15, 0));
|
||||
|
||||
assertEquals(List.of(new DispatchPickOrderAttributionService.OrderPackages(101L, 4L)), result);
|
||||
verify(history).insert(argThat((LastlegOrderStatusHistory value) -> "PARTIALLY_SHIPPED".equals(value.getToStatus())
|
||||
&& value.getRemark().contains("6/10")));
|
||||
verify(history).insert(argThat((LastlegOrderStatusHistory value) -> "t1".equals(value.getCustomerId())
|
||||
&& "PARTIALLY_SHIPPED".equals(value.getToStatus()) && value.getRemark().contains("6/10")));
|
||||
}
|
||||
|
||||
private DispatchPickOrderAttributionService service() {
|
||||
|
||||
+46
@@ -24,6 +24,8 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -57,6 +59,7 @@ class InventoryCommandServiceTest {
|
||||
ReflectionTestUtils.setField(service, "warehouseGateway", warehouseGateway);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
lenient().when(operations.findIdempotency(any(), any())).thenReturn(null);
|
||||
lenient().when(commands.lockInboundTransitBalances(any(), any(), any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,6 +117,7 @@ class InventoryCommandServiceTest {
|
||||
balance.setSkuCode("SKU-1");
|
||||
balance.setInTransitPackages(12L);
|
||||
when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(balance);
|
||||
when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(balance));
|
||||
|
||||
InventoryCommandResponse result = service.stockIn("100", "7", stockIn("IN-1", "stock-in-transit", 12, 24));
|
||||
|
||||
@@ -122,6 +126,48 @@ class InventoryCommandServiceTest {
|
||||
assertEquals(12L, balance.getAvailablePackages());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockInTransfersInTransitFromTheOriginalOverseasWarehouse() {
|
||||
InventoryBalance target = balance(200L, 0);
|
||||
target.setSkuCode("SKU-1");
|
||||
target.setInTransitPackages(0L);
|
||||
InventoryBalance source = balance(201L, 0);
|
||||
source.setOverseasWarehouseId("WH-OLD");
|
||||
source.setSkuCode("SKU-1");
|
||||
source.setInTransitPackages(12L);
|
||||
when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(target);
|
||||
when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(source));
|
||||
|
||||
InventoryCommandResponse result = service.stockIn("100", "7",
|
||||
stockIn("IN-1", "stock-in-cross-warehouse", 12, 24));
|
||||
|
||||
assertEquals(12, result.availablePackages());
|
||||
assertEquals(0L, source.getInTransitPackages());
|
||||
ArgumentCaptor<InventoryLedger> written = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(ledgers, times(2)).insert(written.capture());
|
||||
assertEquals("STOCK_IN_TRANSIT_RELEASE", written.getAllValues().get(0).getBusinessType());
|
||||
assertEquals(-12L, written.getAllValues().get(0).getDeltaPackages());
|
||||
assertEquals("STOCK_IN", written.getAllValues().get(1).getBusinessType());
|
||||
assertEquals("IN_TRANSIT", written.getAllValues().get(1).getStatusFrom());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockInKeepsOutsideSourceWhenInTransitIsInsufficient() {
|
||||
InventoryBalance target = balance(200L, 0);
|
||||
target.setSkuCode("SKU-1");
|
||||
target.setInTransitPackages(2L);
|
||||
when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(target);
|
||||
when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(target));
|
||||
|
||||
service.stockIn("100", "7", stockIn("IN-1", "stock-in-partial-transit", 5, 10));
|
||||
|
||||
ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(ledgers).insert(ledger.capture());
|
||||
assertEquals("OUTSIDE", ledger.getValue().getStatusFrom());
|
||||
assertEquals(0L, target.getInTransitPackages());
|
||||
assertEquals(5L, target.getAvailablePackages());
|
||||
}
|
||||
|
||||
@Test
|
||||
void differentSkusShareInboundBalanceAndKeepSkuInLedger() {
|
||||
InventoryBalance balance = balance(200L, 4);
|
||||
|
||||
@@ -3895,3 +3895,17 @@ CREATE TABLE IF NOT EXISTS ll_customer_visit (
|
||||
KEY idx_ll_customer_visit_list(customer_id,status,delivery_completed_at),
|
||||
KEY idx_ll_customer_visit_delivery_no(customer_id,delivery_no)
|
||||
) ENGINE=InnoDB COMMENT='派送单维度客户回访记录';
|
||||
|
||||
-- ============================================================================
|
||||
-- 2026-08-27 / local legacy inventory-ledger cleanup
|
||||
-- ============================================================================
|
||||
USE baiyue_lastleg;
|
||||
|
||||
-- These quantity columns are absent from the current ledger contract. Legacy
|
||||
-- NOT NULL definitions without defaults block package-only ledger writes.
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='before_quantity'), 'ALTER TABLE ll_inventory_ledger DROP COLUMN before_quantity', 'SELECT 1'));
|
||||
PREPARE lastleg_ledger_legacy_001 FROM @ddl; EXECUTE lastleg_ledger_legacy_001; DEALLOCATE PREPARE lastleg_ledger_legacy_001;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='delta_quantity'), 'ALTER TABLE ll_inventory_ledger DROP COLUMN delta_quantity', 'SELECT 1'));
|
||||
PREPARE lastleg_ledger_legacy_002 FROM @ddl; EXECUTE lastleg_ledger_legacy_002; DEALLOCATE PREPARE lastleg_ledger_legacy_002;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='after_quantity'), 'ALTER TABLE ll_inventory_ledger DROP COLUMN after_quantity', 'SELECT 1'));
|
||||
PREPARE lastleg_ledger_legacy_003 FROM @ddl; EXECUTE lastleg_ledger_legacy_003; DEALLOCATE PREPARE lastleg_ledger_legacy_003;
|
||||
|
||||
Reference in New Issue
Block a user