修改提交

This commit is contained in:
2026-08-21 18:12:07 +08:00
parent d1d269e96f
commit 4f14367eba
5 changed files with 134 additions and 33 deletions
@@ -38,6 +38,28 @@ public final class InventoryCommandRequests {
) { ) {
} }
public record InTransit(
String customerName,
@NotBlank String overseasWarehouseId,
String overseasWarehouseCode,
String overseasWarehouseName,
String destinationWarehouseId,
@NotBlank String destinationWarehouseCode,
String destinationWarehouseName,
@NotNull Long inboundOrderId,
@NotBlank String inboundOrderNo,
@NotBlank String inboundCode,
String skuCode,
String productNameZh,
String productNameEn,
@Positive long packages,
String businessOrderNo,
String sourceEventId,
@NotBlank @Size(max = 255) String idempotencyKey,
@Size(max = 1000) String remark
) {
}
public record Move( public record Move(
@NotNull Long positionId, @NotNull Long positionId,
@Positive long packages, @Positive long packages,
@@ -38,6 +38,24 @@ public class InventoryCommandService {
@Resource private WarehouseResourceGateway warehouseGateway; @Resource private WarehouseResourceGateway warehouseGateway;
@Resource private ObjectMapper objectMapper; @Resource private ObjectMapper objectMapper;
@Transactional
public InventoryCommandResponse registerInTransit(String customerId, String operatorId,
InventoryCommandRequests.InTransit request) {
if (request.packages() <= 0) throw badRequest("In-transit packages must be positive");
return execute(customerId, operatorId, "IN_TRANSIT", request.idempotencyKey(), request, operation -> {
InventoryBalance balance = balance(customerId, request);
long before = value(balance.getInTransitPackages());
balance.setInTransitPackages(before + request.packages());
touch(balance);
balances.updateById(balance);
ledger(customerId, operatorId, operation.getId(), balance, null, "IN_TRANSIT",
"OUTSIDE", "IN_TRANSIT", before, request.packages(), before + request.packages(),
request.businessOrderNo(), request.inboundOrderNo(), null, request.sourceEventId(),
request.idempotencyKey(), request.remark());
return response(operation, balance, null, false);
});
}
@Transactional @Transactional
public InventoryCommandResponse stockIn(String customerId, String operatorId, public InventoryCommandResponse stockIn(String customerId, String operatorId,
InventoryCommandRequests.StockIn request) { InventoryCommandRequests.StockIn request) {
@@ -51,11 +69,14 @@ public class InventoryCommandService {
position.setUpdatedAt(LocalDateTime.now()); position.setUpdatedAt(LocalDateTime.now());
positions.updateById(position); positions.updateById(position);
long transferred = Math.min(value(balance.getInTransitPackages()), request.packages());
balance.setInTransitPackages(value(balance.getInTransitPackages()) - transferred);
balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages()); balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages());
touch(balance); touch(balance);
balances.updateById(balance); balances.updateById(balance);
ledger(customerId, operatorId, operation.getId(), balance, position, "STOCK_IN", ledger(customerId, operatorId, operation.getId(), balance, position, "STOCK_IN",
"OUTSIDE", "AVAILABLE", before, request.packages(), before + request.packages(), transferred == request.packages() ? "IN_TRANSIT" : "OUTSIDE", "AVAILABLE",
before, request.packages(), before + request.packages(),
request.businessOrderNo(), request.inboundOrderNo(), null, request.sourceEventId(), request.businessOrderNo(), request.inboundOrderNo(), null, request.sourceEventId(),
request.idempotencyKey(), request.remark()); request.idempotencyKey(), request.remark());
return response(operation, balance, position, false); return response(operation, balance, position, false);
@@ -190,26 +211,47 @@ public class InventoryCommandService {
} }
private InventoryBalance balance(String customerId, InventoryCommandRequests.StockIn request) { private InventoryBalance balance(String customerId, InventoryCommandRequests.StockIn request) {
String skuCode = sku(request.skuCode()); return balance(customerId, request.customerName(), request.overseasWarehouseId(),
InventoryBalance balance = balances.lockDimension(customerId, request.overseasWarehouseId(), request.overseasWarehouseCode(), request.overseasWarehouseName(), request.destinationWarehouseId(),
request.destinationWarehouseCode(), request.inboundOrderId(), request.inboundCode(), skuCode); request.destinationWarehouseCode(), request.destinationWarehouseName(), request.inboundOrderId(),
request.inboundOrderNo(), request.inboundCode(), request.skuCode(), request.productNameZh(),
request.productNameEn());
}
private InventoryBalance balance(String customerId, InventoryCommandRequests.InTransit request) {
return balance(customerId, request.customerName(), request.overseasWarehouseId(),
request.overseasWarehouseCode(), request.overseasWarehouseName(), request.destinationWarehouseId(),
request.destinationWarehouseCode(), request.destinationWarehouseName(), request.inboundOrderId(),
request.inboundOrderNo(), request.inboundCode(), request.skuCode(), request.productNameZh(),
request.productNameEn());
}
private InventoryBalance balance(String customerId, String customerName, String overseasWarehouseId,
String overseasWarehouseCode, String overseasWarehouseName,
String destinationWarehouseId, String destinationWarehouseCode,
String destinationWarehouseName, Long inboundOrderId, String inboundOrderNo,
String inboundCode, String requestedSkuCode, String productNameZh,
String productNameEn) {
String skuCode = sku(requestedSkuCode);
InventoryBalance balance = balances.lockDimension(customerId, overseasWarehouseId,
destinationWarehouseCode, inboundOrderId, inboundCode, skuCode);
if (balance != null) return balance; if (balance != null) return balance;
balance = new InventoryBalance(); balance = new InventoryBalance();
balance.setId(IdWorker.getId()); balance.setId(IdWorker.getId());
balance.setCustomerId(customerId); balance.setCustomerId(customerId);
balance.setCustomerName(trim(request.customerName())); balance.setCustomerName(trim(customerName));
balance.setOverseasWarehouseId(request.overseasWarehouseId().trim()); balance.setOverseasWarehouseId(overseasWarehouseId.trim());
balance.setOverseasWarehouseCode(trim(request.overseasWarehouseCode())); balance.setOverseasWarehouseCode(trim(overseasWarehouseCode));
balance.setOverseasWarehouseName(trim(request.overseasWarehouseName())); balance.setOverseasWarehouseName(trim(overseasWarehouseName));
balance.setDestinationWarehouseId(trim(request.destinationWarehouseId())); balance.setDestinationWarehouseId(trim(destinationWarehouseId));
balance.setDestinationWarehouseCode(request.destinationWarehouseCode().trim()); balance.setDestinationWarehouseCode(destinationWarehouseCode.trim());
balance.setDestinationWarehouseName(trim(request.destinationWarehouseName())); balance.setDestinationWarehouseName(trim(destinationWarehouseName));
balance.setInboundOrderId(request.inboundOrderId()); balance.setInboundOrderId(inboundOrderId);
balance.setInboundOrderNo(request.inboundOrderNo().trim()); balance.setInboundOrderNo(inboundOrderNo.trim());
balance.setInboundCode(request.inboundCode().trim()); balance.setInboundCode(inboundCode.trim());
balance.setSkuCode(skuCode); balance.setSkuCode(skuCode);
balance.setProductNameZh(trim(request.productNameZh())); balance.setProductNameZh(trim(productNameZh));
balance.setProductNameEn(trim(request.productNameEn())); balance.setProductNameEn(trim(productNameEn));
zero(balance); zero(balance);
balance.setVersion(0L); balance.setVersion(0L);
balance.setCreatedAt(LocalDateTime.now()); balance.setCreatedAt(LocalDateTime.now());
@@ -218,8 +260,8 @@ public class InventoryCommandService {
balances.insert(balance); balances.insert(balance);
return balance; return balance;
} catch (DuplicateKeyException exception) { } catch (DuplicateKeyException exception) {
InventoryBalance concurrent = balances.lockDimension(customerId, request.overseasWarehouseId(), InventoryBalance concurrent = balances.lockDimension(customerId, overseasWarehouseId,
request.destinationWarehouseCode(), request.inboundOrderId(), request.inboundCode(), skuCode); destinationWarehouseCode, inboundOrderId, inboundCode, skuCode);
if (concurrent != null) return concurrent; if (concurrent != null) return concurrent;
throw exception; throw exception;
} }
@@ -331,14 +373,14 @@ public class InventoryCommandService {
ledger.setId(IdWorker.getId()); ledger.setId(IdWorker.getId());
ledger.setCustomerId(customerId); ledger.setCustomerId(customerId);
ledger.setBalanceId(balance.getId()); ledger.setBalanceId(balance.getId());
ledger.setPositionId(position.getId()); ledger.setPositionId(position == null ? null : position.getId());
ledger.setOverseasWarehouseId(balance.getOverseasWarehouseId()); ledger.setOverseasWarehouseId(balance.getOverseasWarehouseId());
ledger.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); ledger.setDestinationWarehouseCode(balance.getDestinationWarehouseCode());
ledger.setInboundOrderId(balance.getInboundOrderId()); ledger.setInboundOrderId(balance.getInboundOrderId());
ledger.setPalletId(position.getPalletId()); ledger.setPalletId(position == null ? null : position.getPalletId());
ledger.setPalletNo(position.getPalletNo()); ledger.setPalletNo(position == null ? null : position.getPalletNo());
ledger.setBatchNo(position.getBatchNo()); ledger.setBatchNo(position == null ? null : position.getBatchNo());
ledger.setWarehouseLocationCode(position.getWarehouseLocationCode()); ledger.setWarehouseLocationCode(position == null ? null : position.getWarehouseLocationCode());
ledger.setSkuCode(balance.getSkuCode()); ledger.setSkuCode(balance.getSkuCode());
ledger.setBusinessType(businessType); ledger.setBusinessType(businessType);
ledger.setBusinessOrderId(balance.getInboundOrderId()); ledger.setBusinessOrderId(balance.getInboundOrderId());
@@ -367,7 +409,7 @@ public class InventoryCommandService {
private InventoryCommandResponse response(InventoryOperation operation, InventoryBalance balance, private InventoryCommandResponse response(InventoryOperation operation, InventoryBalance balance,
InventoryPosition position, boolean palletEmpty) { InventoryPosition position, boolean palletEmpty) {
return new InventoryCommandResponse(operation.getId(), operation.getOperationType(), balance.getId(), return new InventoryCommandResponse(operation.getId(), operation.getOperationType(), balance.getId(),
position.getId(), value(balance.getAvailablePackages()), value(balance.getHoldPackages()), position == null ? null : position.getId(), value(balance.getAvailablePackages()), value(balance.getHoldPackages()),
value(balance.getAllocatedPackages()), value(balance.getPickedPackages()), palletEmpty, false); value(balance.getAllocatedPackages()), value(balance.getPickedPackages()), palletEmpty, false);
} }
@@ -41,10 +41,10 @@ public class InventoryQueryService {
long inTransitPackages = number(value.getInTransitPackages()); long inTransitPackages = number(value.getInTransitPackages());
long receivingPackages = number(value.getReceivingPackages()); long receivingPackages = number(value.getReceivingPackages());
long procurementPackages = number(value.getProcurementPackages()); long procurementPackages = number(value.getProcurementPackages());
long totalPackages = availablePackages + holdPackages + allocatedPackages + pickedPackages + receivingPackages;
return new InventoryDashboardResponse(number(value.getDimensionCount()), mapper.countPallets(customerId, query), return new InventoryDashboardResponse(number(value.getDimensionCount()), mapper.countPallets(customerId, query),
availablePackages + holdPackages + allocatedPackages + pickedPackages + inTransitPackages totalPackages, Math.max(0, totalPackages - holdPackages), holdPackages, allocatedPackages,
+ receivingPackages + procurementPackages, pickedPackages, inTransitPackages,
availablePackages, holdPackages, allocatedPackages, pickedPackages, inTransitPackages,
receivingPackages, procurementPackages); receivingPackages, procurementPackages);
} }
@@ -117,13 +117,13 @@ public class InventoryQueryService {
long inTransitPackages = number(value.getInTransitPackages()); long inTransitPackages = number(value.getInTransitPackages());
long receivingPackages = number(value.getReceivingPackages()); long receivingPackages = number(value.getReceivingPackages());
long procurementPackages = number(value.getProcurementPackages()); long procurementPackages = number(value.getProcurementPackages());
long totalPackages = availablePackages + holdPackages + allocatedPackages + pickedPackages + receivingPackages;
return new InventoryBalanceView(value.getId(), value.getCustomerId(), value.getCustomerName(), value.getOverseasWarehouseId(), return new InventoryBalanceView(value.getId(), value.getCustomerId(), value.getCustomerName(), value.getOverseasWarehouseId(),
value.getOverseasWarehouseCode(), value.getOverseasWarehouseName(), value.getDestinationWarehouseId(), value.getOverseasWarehouseCode(), value.getOverseasWarehouseName(), value.getDestinationWarehouseId(),
value.getDestinationWarehouseCode(), value.getDestinationWarehouseName(), value.getInboundOrderId(), value.getDestinationWarehouseCode(), value.getDestinationWarehouseName(), value.getInboundOrderId(),
value.getInboundOrderNo(), value.getInboundCode(), value.getSkuCode(), value.getProductNameZh(), value.getProductNameEn(), value.getInboundOrderNo(), value.getInboundCode(), value.getSkuCode(), value.getProductNameZh(), value.getProductNameEn(),
availablePackages + holdPackages + allocatedPackages + pickedPackages totalPackages, Math.max(0, totalPackages - holdPackages), holdPackages, allocatedPackages,
+ inTransitPackages + receivingPackages + procurementPackages, pickedPackages, inTransitPackages,
availablePackages, holdPackages, allocatedPackages, pickedPackages, inTransitPackages,
receivingPackages, procurementPackages, value.getUpdatedAt()); receivingPackages, procurementPackages, value.getUpdatedAt());
} }
@@ -133,12 +133,13 @@ public class InventoryQueryService {
long allocated = number(value.getAllocatedPackages()); long allocated = number(value.getAllocatedPackages());
long picked = number(value.getPickedPackages()); long picked = number(value.getPickedPackages());
long receiving = number(value.getReceivingPackages()); long receiving = number(value.getReceivingPackages());
long total = available + hold + allocated + picked + receiving;
return new InventoryPositionView(value.getId(), value.getBalanceId(), value.getCustomerId(), return new InventoryPositionView(value.getId(), value.getBalanceId(), value.getCustomerId(),
value.getOverseasWarehouseId(), value.getDestinationWarehouseCode(), balance.getInboundOrderId(), value.getOverseasWarehouseId(), value.getDestinationWarehouseCode(), balance.getInboundOrderId(),
balance.getInboundOrderNo(), balance.getInboundCode(), balance.getInboundOrderNo(), balance.getInboundCode(),
value.getPalletId(), value.getPalletNo(), value.getBatchNo(), value.getWarehouseLocationId(), value.getPalletId(), value.getPalletNo(), value.getBatchNo(), value.getWarehouseLocationId(),
value.getWarehouseLocationCode(), available + hold + allocated + picked + receiving, value.getWarehouseLocationCode(), total, Math.max(0, total - hold), hold, allocated, picked,
available, hold, allocated, picked, receiving, value.getStatus(), receiving, value.getStatus(),
value.getUpdatedAt()); value.getUpdatedAt());
} }
@@ -8,6 +8,9 @@ import com.baiyue.lastleg.dropship.service.DropshipOrderService;
import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton; import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton;
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper; import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper;
import com.baiyue.lastleg.inboundplan.service.InboundPlanService; import com.baiyue.lastleg.inboundplan.service.InboundPlanService;
import com.baiyue.lastleg.inboundplan.dto.InboundPlanResponse;
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests;
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -35,6 +38,7 @@ public class LoadingCompletedConsumer {
@Resource private ObjectMapper objectMapper; @Resource private ObjectMapper objectMapper;
@Resource private LastlegOrderNoGenerator orderNoGenerator; @Resource private LastlegOrderNoGenerator orderNoGenerator;
@Resource private DropshipOrderService dropshipOrders; @Resource private DropshipOrderService dropshipOrders;
@Resource private InventoryCommandService inventory;
@Transactional @Transactional
public int consume(EventEnvelope envelope) { public int consume(EventEnvelope envelope) {
@@ -184,7 +188,10 @@ public class LoadingCompletedConsumer {
LastlegOrderStatusHistory history=new LastlegOrderStatusHistory(); LastlegOrderStatusHistory history=new LastlegOrderStatusHistory();
history.setOrderId(order.getId()); history.setToStatus("PENDING_ASSIGNMENT"); history.setEventId(event.eventId()); history.setOrderId(order.getId()); history.setToStatus("PENDING_ASSIGNMENT"); history.setEventId(event.eventId());
history.setRemark("头程装柜完成自动生成"); history.setCreatedAt(LocalDateTime.now()); historyMapper.insert(history); history.setRemark("头程装柜完成自动生成"); history.setCreatedAt(LocalDateTime.now()); historyMapper.insert(history);
if ("FIRST_LEG".equals(orderType)) inboundPlans.convert(customerId, order.getId()); if ("FIRST_LEG".equals(orderType)) {
InboundPlanResponse plan = inboundPlans.convert(customerId, order.getId());
registerInTransit(event, payload, source, order, plan.planNo());
}
if ("ONE_CLICK_DROPSHIP".equals(orderType)) { if ("ONE_CLICK_DROPSHIP".equals(orderType)) {
long warehouseId; long warehouseId;
try { warehouseId = Long.parseLong(payload.warehouseId()); } try { warehouseId = Long.parseLong(payload.warehouseId()); }
@@ -194,6 +201,23 @@ public class LoadingCompletedConsumer {
} }
} }
private void registerInTransit(EventEnvelope event, FirstLegLoadingPlanCompletedV1 payload,
FirstLegLoadingPlanCompletedV1.Order source, LastlegOrder order,
String inboundOrderNo) {
for (int index = 0; index < source.items().size(); index++) {
FirstLegLoadingPlanCompletedV1.Item item = source.items().get(index);
if (item.packageCount() <= 0) continue;
inventory.registerInTransit(order.getCustomerId(), "SYSTEM",
new InventoryCommandRequests.InTransit(order.getCustomerName(), payload.warehouseId(), null,
payload.warehouseName(), item.destinationWarehouseId(), item.destinationWarehouseCode(),
item.destinationWarehouseName(), order.getId(), inboundOrderNo, source.inboundCode(),
item.skuCode(), item.productNameZh(), item.productNameEn(), item.packageCount(),
order.getOrderNo(), event.eventId(),
"TAIL_IN_TRANSIT:" + event.eventId() + ":" + order.getId() + ":" + index,
"Tail-leg order created from completed loading plan"));
}
}
private void createCartonLabelPool(String customerId, FirstLegLoadingPlanCompletedV1.Item item) { private void createCartonLabelPool(String customerId, FirstLegLoadingPlanCompletedV1.Item item) {
List<String> marks = item.cartonMarks() == null ? List.of() : item.cartonMarks(); List<String> marks = item.cartonMarks() == null ? List.of() : item.cartonMarks();
if (marks.isEmpty()) return; // 历史 V1 事件保持原有按箱数入库路径。 if (marks.isEmpty()) return; // 历史 V1 事件保持原有按箱数入库路径。
@@ -971,6 +971,18 @@ CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_order (
KEY idx_ll_wh_transfer_list (customer_id,status,created_at), KEY idx_ll_wh_transfer_inbound (customer_id,inbound_code) KEY idx_ll_wh_transfer_list (customer_id,status,created_at), KEY idx_ll_wh_transfer_inbound (customer_id,inbound_code)
) ENGINE=InnoDB COMMENT='客户转仓单'; ) ENGINE=InnoDB COMMENT='客户转仓单';
-- 兼容已存在的旧版转仓单表:CREATE TABLE IF NOT EXISTS 不会补充新增字段。
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='allocation_idempotency_key'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN allocation_idempotency_key VARCHAR(255) NULL COMMENT ''库存分配幂等键'' AFTER status'));
PREPARE transfer_allocation_key_stmt FROM @ddl; EXECUTE transfer_allocation_key_stmt; DEALLOCATE PREPARE transfer_allocation_key_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='allocation_request_hash'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN allocation_request_hash CHAR(64) NULL COMMENT ''库存分配请求哈希'' AFTER allocation_idempotency_key'));
PREPARE transfer_allocation_hash_stmt FROM @ddl; EXECUTE transfer_allocation_hash_stmt; DEALLOCATE PREPARE transfer_allocation_hash_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='outbound_idempotency_key'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN outbound_idempotency_key VARCHAR(255) NULL COMMENT ''出库幂等键'' AFTER allocation_request_hash'));
PREPARE transfer_outbound_key_stmt FROM @ddl; EXECUTE transfer_outbound_key_stmt; DEALLOCATE PREPARE transfer_outbound_key_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='outbound_request_hash'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN outbound_request_hash CHAR(64) NULL COMMENT ''出库请求哈希'' AFTER outbound_idempotency_key'));
PREPARE transfer_outbound_hash_stmt FROM @ddl; EXECUTE transfer_outbound_hash_stmt; DEALLOCATE PREPARE transfer_outbound_hash_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='outbound_at'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN outbound_at DATETIME(3) NULL COMMENT ''出库时间'' AFTER outbound_request_hash'));
PREPARE transfer_outbound_at_stmt FROM @ddl; EXECUTE transfer_outbound_at_stmt; DEALLOCATE PREPARE transfer_outbound_at_stmt;
CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_source_order ( CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_source_order (
id BIGINT NOT NULL COMMENT '主键ID', transfer_order_id BIGINT NOT NULL COMMENT '转仓单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', id BIGINT NOT NULL COMMENT '主键ID', transfer_order_id BIGINT NOT NULL COMMENT '转仓单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
source_lastleg_order_id BIGINT NOT NULL COMMENT '来源尾程订单ID', source_lastleg_order_no VARCHAR(32) NOT NULL COMMENT '来源尾程订单号', source_lastleg_order_id BIGINT NOT NULL COMMENT '来源尾程订单ID', source_lastleg_order_no VARCHAR(32) NOT NULL COMMENT '来源尾程订单号',