打印箱唛修改 尾程垮桥事件提交

This commit is contained in:
2026-08-13 17:13:58 +08:00
parent b004edaf9e
commit c43d581ba6
59 changed files with 1609 additions and 96 deletions
@@ -17,6 +17,15 @@ public interface FirstlegInboundClient {
ApiResponse<Long> create(@RequestHeader("X-Internal-Service-Token") String token,
@RequestBody OrderApprovedInboundCommand command);
@PostMapping("/order-preparing")
ApiResponse<Long> prepare(@RequestHeader("X-Internal-Service-Token") String token,
@RequestBody OrderApprovedInboundCommand command);
@PostMapping("/order-preparing/batch")
ApiResponse<List<InboundReference>> prepareBatch(
@RequestHeader("X-Internal-Service-Token") String token,
@RequestBody List<OrderApprovedInboundCommand> commands);
@PostMapping("/order-references")
ApiResponse<List<InboundReference>> references(
@RequestHeader("X-Internal-Service-Token") String token,
@@ -8,6 +8,8 @@ import com.baiyue.common.core.api.ResultCode;
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.client.FirstlegInboundClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -68,6 +70,25 @@ public class InternalOrderCargoController {
.toList());
}
@GetMapping("/{tenantId}/{orderId}/inbound-snapshot")
public ApiResponse<FirstlegInboundClient.OrderApprovedInboundCommand> inboundSnapshot(
@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");
}
var order = orders.findOwned(tenantId, orderId);
if (order == null) {
throw new BizException(ResultCode.NOT_FOUND, "订单不存在或不属于当前客户");
}
int packageCount = orders.findCargoInheritedByOrder(tenantId, orderId).stream()
.map(OrderCargoEntity::getPackageCount).filter(java.util.Objects::nonNull)
.mapToInt(Integer::intValue).sum();
return ApiResponse.success(OrderApplicationService.inboundCommand(order, packageCount));
}
private void requireToken(String token) {
if (token == null || !MessageDigest.isEqual(expectedToken, token.getBytes(StandardCharsets.UTF_8))) {
throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
@@ -12,6 +12,7 @@ public record MerchantInboundOrderRow(
String orderNo,
Integer packageCount,
BigDecimal weightKg,
Long inboundId,
String inboundCode,
String channelName,
String destinationType,
@@ -36,12 +37,16 @@ public record MerchantInboundOrderRow(
Long operatorId,
String operatorName,
String remark,
String destinationWarehouseName,
String expressCompany,
String skuNos,
String shipmentId,
List<CargoColumn> cargoes
) {
public record CargoColumn(
Long cargoId, Integer itemNo, String cargoName, Integer packageCount,
Integer totalQuantity, BigDecimal grossWeightKg, BigDecimal netWeightKg,
BigDecimal volumeCbm, BigDecimal unitDeclaredValue, BigDecimal declaredValue,
String currency, String model, String material, String productUsage, String brand) {
String currency, String skuNo, String model, String material, String productUsage, String brand) {
}
}
@@ -46,11 +46,21 @@ public class MerchantInboundOrderListService {
.stream().collect(Collectors.toMap(
OrderStatusHistoryEntity::getOrderId, Function.identity(), (latest, ignored) -> latest));
Map<Long, FirstlegInboundClient.InboundReference> inboundReferences = references(tenantId, orderIds);
Map<Long, FirstlegInboundClient.InboundReference> initialReferences = inboundReferences;
List<OrderEntity> missing = source.records().stream()
.filter(order -> !initialReferences.containsKey(order.getId()))
.filter(order -> order.getStatus() == com.baiyue.order.entity.OrderStatus.DRAFT
|| order.getStatus() == com.baiyue.order.entity.OrderStatus.SUBMITTED)
.toList();
if (!missing.isEmpty()) {
inboundReferences = repairedReferences(tenantId, inboundReferences, missing, cargoes);
}
Map<Long, FirstlegInboundClient.InboundReference> finalReferences = inboundReferences;
List<MerchantInboundOrderRow> records = source.records().stream()
.map(order -> row(order, cargoes.getOrDefault(order.getId(), List.of()),
consignees.get(order.getId()), latestHistory.get(order.getId()),
inboundReferences.get(order.getId())))
finalReferences.get(order.getId())))
.toList();
return new PageResult<>(records, source.page(), source.size(), source.total(), source.pages());
}
@@ -69,6 +79,26 @@ public class MerchantInboundOrderListService {
}
}
private Map<Long, FirstlegInboundClient.InboundReference> repairedReferences(
long tenantId, Map<Long, FirstlegInboundClient.InboundReference> current,
List<OrderEntity> missing, Map<Long, List<OrderCargoEntity>> cargoes) {
try {
List<FirstlegInboundClient.OrderApprovedInboundCommand> commands = missing.stream().map(order -> {
int packages = cargoes.getOrDefault(order.getId(), List.of()).stream()
.map(OrderCargoEntity::getPackageCount).filter(Objects::nonNull)
.mapToInt(Integer::intValue).sum();
return OrderApplicationService.inboundCommand(order, packages);
}).toList();
var response = firstleg.prepareBatch(internalToken, commands);
if (response == null || !response.successful() || response.data() == null) return current;
Map<Long, FirstlegInboundClient.InboundReference> result = new java.util.HashMap<>(current);
response.data().forEach(reference -> result.put(reference.orderId(), reference));
return result;
} catch (RuntimeException ignored) {
return current;
}
}
private MerchantInboundOrderRow row(
OrderEntity order,
List<OrderCargoEntity> cargoes,
@@ -89,13 +119,15 @@ public class MerchantInboundOrderListService {
cargo.getId(), cargo.getItemNo(), cargo.getCargoName(), cargo.getPackageCount(),
cargo.getTotalQuantity(), cargo.getGrossWeightKg(), cargo.getNetWeightKg(),
cargo.getVolumeCbm(), cargo.getUnitDeclaredValue(), cargo.getDeclaredValue(),
cargo.getCurrency(), cargo.getModel(), cargo.getMaterial(), cargo.getProductUsage(),
cargo.getCurrency(), cargo.getSkuNo(), cargo.getModel(), cargo.getMaterial(), cargo.getProductUsage(),
cargo.getBrand()))
.toList();
return new MerchantInboundOrderRow(
order.getId(), order.getOrderNo(), packageCount, grossWeight,
inbound == null ? null : inbound.inboundCode(), order.getChannelName(),
inbound == null ? null : inbound.inboundId(),
inbound == null || inbound.inboundCode() == null || inbound.inboundCode().isBlank()
? order.getInboundCode() : inbound.inboundCode(), order.getChannelName(),
order.getDestinationType(), order.getCustomerName(),
consignee == null ? null : consignee.getCompanyName(), joined(cargoes, OrderCargoEntity::getModel),
order.getStatus(), totalQuantity, grossWeight, netWeight, volume,
@@ -106,6 +138,9 @@ public class MerchantInboundOrderListService {
order.getExpressTrackingNo(), order.getShipmentId(), order.getReferenceId(),
latestHistory == null ? order.getCreatedBy() : latestHistory.getOperatorId(),
latestHistory == null ? null : latestHistory.getOperatorName(), order.getCustomerRemark(),
order.getDestinationWarehouseName(), order.getExpressCompany(),
joined(cargoes, OrderCargoEntity::getSkuNo),
order.getShipmentId(),
cargoColumns);
}
@@ -1,6 +1,7 @@
package com.baiyue.order.service;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.dto.OrderDetailResponse;
@@ -18,6 +19,10 @@ import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.time.LocalDateTime;
import java.nio.charset.StandardCharsets;
@@ -37,6 +42,8 @@ public class OrderApplicationService {
@Resource private OrderMasterDataGuard masterDataGuard;
@Resource private OrderPricingChannelGuard pricingChannelGuard;
@Resource private ProductImageGuard productImageGuard;
@Resource private ObjectProvider<FirstlegInboundClient> firstlegProvider;
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
/** 创建草稿时使用数据库唯一键保证请求幂等。 */
@Transactional
@@ -63,6 +70,7 @@ public class OrderApplicationService {
repository.insertOrder(order);
saveChildren(tenantId, userId, order.getId(), request);
record(tenantId, order.getId(), null, OrderStatus.DRAFT, "CREATE", null, userId, null);
prepareDraftInbound(order, request);
return order.getId();
}
@@ -104,6 +112,7 @@ public class OrderApplicationService {
repository.replaceDraftChildren(tenantId, id);
saveChildren(tenantId, userId, id, request);
record(tenantId, id, before, order.getStatus(), "UPDATE", null, userId, null);
prepareDraftInbound(order, request);
}
@Transactional
@@ -482,10 +491,10 @@ public class OrderApplicationService {
event.setPublishStatus("PENDING"); event.setRetryCount(0); event.setCreatedAt(LocalDateTime.now());
repository.insertOutbox(event);
}
static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order) {
public static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order) {
return inboundCommand(order, null);
}
static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order,
public static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order,
Integer totalPackageCount) {
return new FirstlegInboundClient.OrderApprovedInboundCommand(
order.getTenantId(), order.getId(), order.getOrderNo(), order.getInboundCode(),
@@ -499,6 +508,28 @@ public class OrderApplicationService {
order.getDestinationWarehouseId(), order.getAmazonWarehouseCode(), order.getDestinationWarehouseName(),
order.getCustomerRemark(), order.getExpressCompany(), order.getExpressTrackingNo(), totalPackageCount);
}
private void prepareDraftInbound(OrderEntity order, OrderRequests.SaveRequest request) {
FirstlegInboundClient client = firstlegProvider == null ? null : firstlegProvider.getIfAvailable();
if (client == null || !StringUtils.hasText(internalToken)) return;
int packageCount = request.cargoes().stream().map(OrderRequests.Cargo::packageCount)
.filter(java.util.Objects::nonNull).mapToInt(Integer::intValue).sum();
FirstlegInboundClient.OrderApprovedInboundCommand command = inboundCommand(order, packageCount);
Runnable prepare = () -> {
try {
ApiResponse<Long> response = client.prepare(internalToken, command);
if (response == null || !response.successful() || response.data() == null) return;
} catch (RuntimeException ignored) {
// Repaired by MerchantInboundOrderListService on the next query.
}
};
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override public void afterCommit() { prepare.run(); }
});
} else {
prepare.run();
}
}
private int totalPackageCount(OrderEntity order) {
long total = repository.findCargo(order.getTenantId(), order.getId()).stream()
.map(com.baiyue.order.entity.OrderCargoEntity::getPackageCount)
@@ -665,6 +665,22 @@ components:
properties:
orderId: {type: string, pattern: '^[1-9][0-9]*$'}
orderNo: {type: string}
inboundId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
inboundCode: {type: string}
channelName: {type: string}
destinationWarehouseName: {type: string}
status: {type: string}
packageCount: {type: integer}
expressCompany: {type: string}
trackingNo: {type: string}
skuNos: {type: string, description: 多个 SKU 使用中文顿号分隔}
totalGrossWeightKg: {type: number}
totalNetWeightKg: {type: number}
totalVolumeCbm: {type: number}
totalQuantity: {type: integer}
remark: {type: string}
shipmentId: {type: string}
referenceId: {type: string}
pricingTaxMode: {$ref: '#/components/schemas/PricingTaxMode'}
ContainerSlice:
type: object
@@ -2,6 +2,7 @@ package com.baiyue.order.controller;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.entity.OrderCargoEntity;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.repository.OrderRepository;
import org.junit.jupiter.api.Test;
import jakarta.annotation.Resource;
@@ -104,6 +105,29 @@ class InternalOrderCargoControllerTest {
verify(orders).findCargoInheritedByOrder(11L, 201L, 31L);
}
@Test
void returnsOwnedOrderSnapshotForPreparingInboundRepair() {
OrderEntity order = new OrderEntity();
order.setId(21L);
order.setTenantId(11L);
order.setOrderNo("FL21");
order.setInboundCode("RK-21");
order.setCustomerName("Customer");
order.setReceivingWarehouseId(41L);
order.setReceivingWarehouseName("Warehouse");
order.setDestinationType("FBA");
order.setTransportMode("SEA");
when(orders.findOwned(11L, 21L)).thenReturn(order);
when(orders.findCargoInheritedByOrder(11L, 21L))
.thenReturn(List.of(cargo(31L, 1, 3, "60", "40", "30")));
var response = controller.inboundSnapshot("internal-token", 11L, 21L);
assertThat(response.data().orderId()).isEqualTo(21L);
assertThat(response.data().totalPackageCount()).isEqualTo(3);
verify(orders).findOwned(11L, 21L);
}
private OrderCargoEntity cargo(long id, int itemNo, int packageCount,
String length, String width, String height) {
OrderCargoEntity cargo = new OrderCargoEntity();
@@ -41,8 +41,10 @@ class MerchantInboundOrderListServiceTest {
order.setStatus(OrderStatus.APPROVED);
order.setChannelName("美森");
order.setDestinationType("FBA");
order.setDestinationWarehouseName("ONT8");
order.setCustomerName("测试客户");
order.setShipmentId("FBA-SHIPMENT-77");
order.setExpressCompany("UPS");
order.setExpressTrackingNo("TRACK-77");
order.setCustomerRemark("客户备注");
when(repository.merchantInboundPage(10001L, query))
@@ -63,10 +65,11 @@ class MerchantInboundOrderListServiceTest {
when(repository.findHistoryByOrderIds(10001L, List.of(77L))).thenReturn(List.of(history));
when(firstleg.references(org.mockito.ArgumentMatchers.eq("internal-token"),
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(List.of(
new FirstlegInboundClient.InboundReference(77L, 88L, "RK-77", "WAIT_MEASURE"))));
new FirstlegInboundClient.InboundReference(77L, 88L, "RK-77", "INBOUNDED"))));
var row = service.page(10001L, query).records().get(0);
assertThat(row.inboundId()).isEqualTo(88L);
assertThat(row.inboundCode()).isEqualTo("RK-77");
assertThat(row.packageCount()).isEqualTo(5);
assertThat(row.totalQuantity()).isEqualTo(50);
@@ -78,6 +81,10 @@ class MerchantInboundOrderListServiceTest {
assertThat(row.specificationModel()).isEqualTo("A、B");
assertThat(row.receivingCompanyName()).isEqualTo("收货公司");
assertThat(row.operatorName()).isEqualTo("操作员");
assertThat(row.destinationWarehouseName()).isEqualTo("ONT8");
assertThat(row.expressCompany()).isEqualTo("UPS");
assertThat(row.skuNos()).isEqualTo("SKU-A、SKU-B");
assertThat(row.shipmentId()).isEqualTo("FBA-SHIPMENT-77");
assertThat(row.cargoes()).hasSize(2);
verify(repository).merchantInboundPage(10001L, query);
}
@@ -97,6 +104,7 @@ class MerchantInboundOrderListServiceTest {
cargo.setUnitDeclaredValue(new BigDecimal(unitPrice));
cargo.setDeclaredValue(new BigDecimal(amount));
cargo.setModel(model);
cargo.setSkuNo("SKU-" + model);
return cargo;
}
}