打印箱唛修改 尾程垮桥事件提交
This commit is contained in:
@@ -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();
|
||||
|
||||
+9
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user