Test #1

Merged
admin merged 1726 commits from test into main 2026-09-17 11:52:45 +08:00
5 changed files with 134 additions and 33 deletions
Showing only changes of commit 4f14367eba - Show all commits
@@ -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(
@NotNull Long positionId,
@Positive long packages,
@@ -38,6 +38,24 @@ public class InventoryCommandService {
@Resource private WarehouseResourceGateway warehouseGateway;
@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
public InventoryCommandResponse stockIn(String customerId, String operatorId,
InventoryCommandRequests.StockIn request) {
@@ -51,11 +69,14 @@ 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);
balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages());
touch(balance);
balances.updateById(balance);
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.idempotencyKey(), request.remark());
return response(operation, balance, position, false);
@@ -190,26 +211,47 @@ public class InventoryCommandService {
}
private InventoryBalance balance(String customerId, InventoryCommandRequests.StockIn request) {
String skuCode = sku(request.skuCode());
InventoryBalance balance = balances.lockDimension(customerId, request.overseasWarehouseId(),
request.destinationWarehouseCode(), request.inboundOrderId(), request.inboundCode(), skuCode);
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, 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;
balance = new InventoryBalance();
balance.setId(IdWorker.getId());
balance.setCustomerId(customerId);
balance.setCustomerName(trim(request.customerName()));
balance.setOverseasWarehouseId(request.overseasWarehouseId().trim());
balance.setOverseasWarehouseCode(trim(request.overseasWarehouseCode()));
balance.setOverseasWarehouseName(trim(request.overseasWarehouseName()));
balance.setDestinationWarehouseId(trim(request.destinationWarehouseId()));
balance.setDestinationWarehouseCode(request.destinationWarehouseCode().trim());
balance.setDestinationWarehouseName(trim(request.destinationWarehouseName()));
balance.setInboundOrderId(request.inboundOrderId());
balance.setInboundOrderNo(request.inboundOrderNo().trim());
balance.setInboundCode(request.inboundCode().trim());
balance.setCustomerName(trim(customerName));
balance.setOverseasWarehouseId(overseasWarehouseId.trim());
balance.setOverseasWarehouseCode(trim(overseasWarehouseCode));
balance.setOverseasWarehouseName(trim(overseasWarehouseName));
balance.setDestinationWarehouseId(trim(destinationWarehouseId));
balance.setDestinationWarehouseCode(destinationWarehouseCode.trim());
balance.setDestinationWarehouseName(trim(destinationWarehouseName));
balance.setInboundOrderId(inboundOrderId);
balance.setInboundOrderNo(inboundOrderNo.trim());
balance.setInboundCode(inboundCode.trim());
balance.setSkuCode(skuCode);
balance.setProductNameZh(trim(request.productNameZh()));
balance.setProductNameEn(trim(request.productNameEn()));
balance.setProductNameZh(trim(productNameZh));
balance.setProductNameEn(trim(productNameEn));
zero(balance);
balance.setVersion(0L);
balance.setCreatedAt(LocalDateTime.now());
@@ -218,8 +260,8 @@ public class InventoryCommandService {
balances.insert(balance);
return balance;
} catch (DuplicateKeyException exception) {
InventoryBalance concurrent = balances.lockDimension(customerId, request.overseasWarehouseId(),
request.destinationWarehouseCode(), request.inboundOrderId(), request.inboundCode(), skuCode);
InventoryBalance concurrent = balances.lockDimension(customerId, overseasWarehouseId,
destinationWarehouseCode, inboundOrderId, inboundCode, skuCode);
if (concurrent != null) return concurrent;
throw exception;
}
@@ -331,14 +373,14 @@ public class InventoryCommandService {
ledger.setId(IdWorker.getId());
ledger.setCustomerId(customerId);
ledger.setBalanceId(balance.getId());
ledger.setPositionId(position.getId());
ledger.setPositionId(position == null ? null : position.getId());
ledger.setOverseasWarehouseId(balance.getOverseasWarehouseId());
ledger.setDestinationWarehouseCode(balance.getDestinationWarehouseCode());
ledger.setInboundOrderId(balance.getInboundOrderId());
ledger.setPalletId(position.getPalletId());
ledger.setPalletNo(position.getPalletNo());
ledger.setBatchNo(position.getBatchNo());
ledger.setWarehouseLocationCode(position.getWarehouseLocationCode());
ledger.setPalletId(position == null ? null : position.getPalletId());
ledger.setPalletNo(position == null ? null : position.getPalletNo());
ledger.setBatchNo(position == null ? null : position.getBatchNo());
ledger.setWarehouseLocationCode(position == null ? null : position.getWarehouseLocationCode());
ledger.setSkuCode(balance.getSkuCode());
ledger.setBusinessType(businessType);
ledger.setBusinessOrderId(balance.getInboundOrderId());
@@ -367,7 +409,7 @@ public class InventoryCommandService {
private InventoryCommandResponse response(InventoryOperation operation, InventoryBalance balance,
InventoryPosition position, boolean palletEmpty) {
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);
}
@@ -41,10 +41,10 @@ public class InventoryQueryService {
long inTransitPackages = number(value.getInTransitPackages());
long receivingPackages = number(value.getReceivingPackages());
long procurementPackages = number(value.getProcurementPackages());
long totalPackages = availablePackages + holdPackages + allocatedPackages + pickedPackages + receivingPackages;
return new InventoryDashboardResponse(number(value.getDimensionCount()), mapper.countPallets(customerId, query),
availablePackages + holdPackages + allocatedPackages + pickedPackages + inTransitPackages
+ receivingPackages + procurementPackages,
availablePackages, holdPackages, allocatedPackages, pickedPackages, inTransitPackages,
totalPackages, Math.max(0, totalPackages - holdPackages), holdPackages, allocatedPackages,
pickedPackages, inTransitPackages,
receivingPackages, procurementPackages);
}
@@ -117,13 +117,13 @@ public class InventoryQueryService {
long inTransitPackages = number(value.getInTransitPackages());
long receivingPackages = number(value.getReceivingPackages());
long procurementPackages = number(value.getProcurementPackages());
long totalPackages = availablePackages + holdPackages + allocatedPackages + pickedPackages + receivingPackages;
return new InventoryBalanceView(value.getId(), value.getCustomerId(), value.getCustomerName(), value.getOverseasWarehouseId(),
value.getOverseasWarehouseCode(), value.getOverseasWarehouseName(), value.getDestinationWarehouseId(),
value.getDestinationWarehouseCode(), value.getDestinationWarehouseName(), value.getInboundOrderId(),
value.getInboundOrderNo(), value.getInboundCode(), value.getSkuCode(), value.getProductNameZh(), value.getProductNameEn(),
availablePackages + holdPackages + allocatedPackages + pickedPackages
+ inTransitPackages + receivingPackages + procurementPackages,
availablePackages, holdPackages, allocatedPackages, pickedPackages, inTransitPackages,
totalPackages, Math.max(0, totalPackages - holdPackages), holdPackages, allocatedPackages,
pickedPackages, inTransitPackages,
receivingPackages, procurementPackages, value.getUpdatedAt());
}
@@ -133,12 +133,13 @@ public class InventoryQueryService {
long allocated = number(value.getAllocatedPackages());
long picked = number(value.getPickedPackages());
long receiving = number(value.getReceivingPackages());
long total = available + hold + allocated + picked + receiving;
return new InventoryPositionView(value.getId(), value.getBalanceId(), value.getCustomerId(),
value.getOverseasWarehouseId(), value.getDestinationWarehouseCode(), balance.getInboundOrderId(),
balance.getInboundOrderNo(), balance.getInboundCode(),
value.getPalletId(), value.getPalletNo(), value.getBatchNo(), value.getWarehouseLocationId(),
value.getWarehouseLocationCode(), available + hold + allocated + picked + receiving,
available, hold, allocated, picked, receiving, value.getStatus(),
value.getWarehouseLocationCode(), total, Math.max(0, total - hold), hold, allocated, picked,
receiving, value.getStatus(),
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.mapper.InboundPlanCartonMapper;
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.DeserializationFeature;
import jakarta.annotation.Resource;
@@ -35,6 +38,7 @@ public class LoadingCompletedConsumer {
@Resource private ObjectMapper objectMapper;
@Resource private LastlegOrderNoGenerator orderNoGenerator;
@Resource private DropshipOrderService dropshipOrders;
@Resource private InventoryCommandService inventory;
@Transactional
public int consume(EventEnvelope envelope) {
@@ -184,7 +188,10 @@ public class LoadingCompletedConsumer {
LastlegOrderStatusHistory history=new LastlegOrderStatusHistory();
history.setOrderId(order.getId()); history.setToStatus("PENDING_ASSIGNMENT"); history.setEventId(event.eventId());
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)) {
long 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) {
List<String> marks = item.cartonMarks() == null ? List.of() : item.cartonMarks();
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)
) 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 (
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 '来源尾程订单号',