From 4f14367ebae9f08c51b495c6fd8b12906f343b33 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Fri, 21 Aug 2026 18:12:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/dto/InventoryCommandRequests.java | 22 +++++ .../service/InventoryCommandService.java | 90 ++++++++++++++----- .../query/service/InventoryQueryService.java | 17 ++-- .../service/LoadingCompletedConsumer.java | 26 +++++- .../src/main/resources/db/lastleg.sql | 12 +++ 5 files changed, 134 insertions(+), 33 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java index 8f113eb8d..48e7b0a3d 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java @@ -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, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java index 212a492b3..0dfdb2e1e 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java @@ -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); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java index cf2dc6de0..504d31f03 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java @@ -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()); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java index 4c07fbe51..3bc24e61b 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java @@ -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 marks = item.cartonMarks() == null ? List.of() : item.cartonMarks(); if (marks.isEmpty()) return; // 历史 V1 事件保持原有按箱数入库路径。 diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index 48bf8e2a2..f30924493 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -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 '来源尾程订单号',