fix: reserve stable inbound code for draft label printing

This commit is contained in:
2026-09-08 17:33:44 +08:00
parent eead11e99d
commit 60a41ec3ea
10 changed files with 289 additions and 3 deletions
@@ -9,10 +9,12 @@ import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.entity.OrderCargoEntity;
import com.baiyue.order.repository.OrderRepository;
import com.baiyue.order.service.OrderApplicationService;
import com.baiyue.order.service.PrintableInboundSnapshotService;
import com.baiyue.order.client.FirstlegInboundClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -29,6 +31,7 @@ import java.util.stream.Collectors;
@RequestMapping("/internal/order/v1/cargoes")
public class InternalOrderCargoController {
@Resource private OrderRepository orders;
@Resource private PrintableInboundSnapshotService printableInboundSnapshots;
@Value("${baiyue.security.internal-service-token}") private String internalToken;
private byte[] expectedToken;
@@ -98,6 +101,18 @@ public class InternalOrderCargoController {
return ApiResponse.success(OrderApplicationService.inboundCommand(order, packageCount));
}
@PostMapping("/{tenantId}/{orderId}/printable-inbound-snapshot")
public ApiResponse<FirstlegInboundClient.OrderApprovedInboundCommand> printableInboundSnapshot(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@PathVariable long tenantId,
@PathVariable long orderId) {
requireToken(token);
if (tenantId <= 0 || orderId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "租户和订单ID必须大于0");
}
return ApiResponse.success(printableInboundSnapshots.reserve(tenantId, orderId));
}
@GetMapping("/{tenantId}/{orderId}/measurement-declaration")
public ApiResponse<MeasurementDeclarationSnapshot> measurementDeclaration(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@@ -54,6 +54,17 @@ public class OrderRepository {
.eq(OrderEntity::getId, order.getId())
.eq(OrderEntity::getTenantId, tenantId));
}
public int assignInboundCodeIfBlank(long tenantId, long orderId, String inboundCode) {
return orderMapper.update(null, new LambdaUpdateWrapper<OrderEntity>()
.eq(OrderEntity::getTenantId, tenantId)
.eq(OrderEntity::getId, orderId)
.eq(OrderEntity::getDeleted, false)
.and(wrapper -> wrapper.isNull(OrderEntity::getInboundCode)
.or().eq(OrderEntity::getInboundCode, ""))
.set(OrderEntity::getInboundCode, inboundCode)
.set(OrderEntity::getUpdatedAt, LocalDateTime.now())
.setSql("version = version + 1"));
}
public int updateDeclarationHeader(long tenantId, long orderId, int expectedVersion, long operatorId,
String customerRemark, LocalDateTime expectedArrivalAt,
java.math.BigDecimal estimatedVolumeCbm,
@@ -0,0 +1,64 @@
package com.baiyue.order.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.client.FirstlegInboundClient;
import com.baiyue.order.config.InboundCodeModeProvider;
import com.baiyue.order.entity.OrderCargoEntity;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.repository.OrderRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
/** 为草稿箱唛打印预留稳定入仓码;订单提交时继续复用该码。 */
@Service
public class PrintableInboundSnapshotService {
private final OrderRepository orders;
private final OrderPricingChannelGuard pricingChannels;
private final InboundCodeModeProvider inboundCodeMode;
private final OrderApplicationService orderApplication;
public PrintableInboundSnapshotService(OrderRepository orders,
OrderPricingChannelGuard pricingChannels,
InboundCodeModeProvider inboundCodeMode,
OrderApplicationService orderApplication) {
this.orders = orders;
this.pricingChannels = pricingChannels;
this.inboundCodeMode = inboundCodeMode;
this.orderApplication = orderApplication;
}
@Transactional
public FirstlegInboundClient.OrderApprovedInboundCommand reserve(long tenantId, long orderId) {
OrderEntity order = requireOwned(tenantId, orderId);
if (!StringUtils.hasText(order.getInboundCode())) {
if (inboundCodeMode.legacyManual()) {
throw new BizException(ResultCode.VALIDATION_FAILED, "当前系统使用旧系统入仓码,请先填写入仓码");
}
String prefix = pricingChannels.requireAvailable(tenantId, order.getReceivingWarehouseId(),
order.getChannelCode(), order.getDestinationType());
String allocated = orderApplication.nextInboundCode(prefix);
if (orders.assignInboundCodeIfBlank(tenantId, orderId, allocated) == 1) {
order.setInboundCode(allocated);
} else {
order = requireOwned(tenantId, orderId);
if (!StringUtils.hasText(order.getInboundCode())) {
throw new BizException(ResultCode.CONFLICT, "入仓码预留冲突,请刷新后重试");
}
}
}
int packageCount = orders.findCargoInheritedByOrder(tenantId, orderId).stream()
.map(OrderCargoEntity::getPackageCount).filter(java.util.Objects::nonNull)
.mapToInt(Integer::intValue).sum();
return OrderApplicationService.inboundCommand(order, packageCount);
}
private OrderEntity requireOwned(long tenantId, long orderId) {
OrderEntity order = orders.findOwned(tenantId, orderId);
if (order == null) {
throw new BizException(ResultCode.NOT_FOUND, "订单不存在或不属于当前客户");
}
return order;
}
}
@@ -28,6 +28,23 @@ paths:
application/json:
schema: {$ref: '#/components/schemas/CargoLabelBindingListEnvelope'}
'403': {description: 内部服务令牌无效}
/internal/order/v1/cargoes/{tenantId}/{orderId}/printable-inbound-snapshot:
post:
security: []
summary: 为 Firstleg 箱唛打印读取并预留稳定入仓快照
description: >-
仅接受内部服务令牌。若 AUTO 模式草稿尚无入仓码,则按订单渠道原子预留全局唯一入仓码;
并发重放返回已胜出的同一码,后续订单提交继续复用,避免已打印二维码失效。
parameters:
- name: X-Internal-Service-Token
in: header
required: true
schema: {type: string}
- {name: tenantId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: orderId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
responses:
'200': {description: 返回带稳定入仓码及总箱数的订单快照}
'403': {description: 内部服务令牌无效}
/internal/order/v1/pending-review-orders:
get:
security: []