第三期装柜验收优化修改
This commit is contained in:
@@ -37,7 +37,7 @@ public class OrderFulfillmentSplitService {
|
||||
String hash = hash(req);
|
||||
List<OrderFulfillmentSplitEntity> replay = splits.findByIdempotency(req.tenantId(), req.parentOrderId(), req.idempotencyKey());
|
||||
if (!replay.isEmpty()) {
|
||||
requireMatchingReplay(replay, hash);
|
||||
requireMatchingReplay(replay, req);
|
||||
return response(req, splits.lockOrder(req.tenantId(), req.parentOrderId()), replay);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class OrderFulfillmentSplitService {
|
||||
if (root == null) throw new BizException(ResultCode.NOT_FOUND, "根订单不存在或不属于该客户");
|
||||
replay = splits.findByIdempotency(req.tenantId(), req.parentOrderId(), req.idempotencyKey());
|
||||
if (!replay.isEmpty()) {
|
||||
requireMatchingReplay(replay, hash);
|
||||
requireMatchingReplay(replay, req);
|
||||
return response(req, root, replay);
|
||||
}
|
||||
if (!"MASTER".equals(root.getOrderRole())) throw new BizException(ResultCode.CONFLICT, "根订单层级非法");
|
||||
@@ -174,14 +174,33 @@ public class OrderFulfillmentSplitService {
|
||||
|| r.lines().stream().mapToInt(OrderSplitDtos.QuantityLine::packageCount).sum() != r.sourcePackageCount())
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "来源明细箱数必须唯一且合计等于来源箱数");
|
||||
}
|
||||
private void requireMatchingReplay(List<OrderFulfillmentSplitEntity> rows, String hash) {
|
||||
if (rows.stream().anyMatch(row -> !Objects.equals(hash, row.getRequestHash())))
|
||||
private void requireMatchingReplay(List<OrderFulfillmentSplitEntity> rows, OrderSplitDtos.CreateRequest request) {
|
||||
String details = sourceDetails(request);
|
||||
boolean mismatch = rows.stream().anyMatch(row ->
|
||||
!Objects.equals(row.getParentOrderId(), request.parentOrderId())
|
||||
|| !Objects.equals(row.getSourceOrderId(), request.sourceOrderId())
|
||||
|| !Objects.equals(row.getLoadingPlanId(), request.loadingPlanId())
|
||||
|| !Objects.equals(row.getContainerBookingId(), request.containerBookingId())
|
||||
|| !Objects.equals(row.getSourcePackageCount(), request.sourcePackageCount())
|
||||
|| !Objects.equals(row.getLoadedPackageCount(), request.loadedPackageCount())
|
||||
|| !Objects.equals(row.getRemainingPackageCount(), request.remainingPackageCount())
|
||||
|| !Objects.equals(row.getOperationId(), request.operationId())
|
||||
|| !sameJson(row.getSourceDetailJson(), details));
|
||||
if (mismatch)
|
||||
throw new BizException(ResultCode.CONFLICT, "幂等键对应的请求内容不一致");
|
||||
}
|
||||
private String sourceDetails(OrderSplitDtos.CreateRequest request) {
|
||||
try { return json.writeValueAsString(request.lines()); }
|
||||
catch (Exception exception) { throw new BizException(ResultCode.INTERNAL_ERROR, "无法保存来源明细快照"); }
|
||||
}
|
||||
|
||||
private boolean sameJson(String left, String right) {
|
||||
try {
|
||||
return json.readTree(left).equals(json.readTree(right));
|
||||
} catch (Exception exception) {
|
||||
return Objects.equals(left, right);
|
||||
}
|
||||
}
|
||||
private String hash(Object value) {
|
||||
try { byte[] digest = MessageDigest.getInstance("SHA-256").digest(json.writeValueAsString(value).getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder result = new StringBuilder(); for (byte b : digest) result.append(String.format("%02x", b)); return result.toString();
|
||||
|
||||
@@ -94,6 +94,31 @@ class OrderFulfillmentSplitServiceTest {
|
||||
verify(orders, never()).insert(any(OrderEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaysSameBusinessSplitWhenExpectedVersionChangedAfterACompensatingRetry() {
|
||||
OrderFulfillmentSplitEntity loaded = new OrderFulfillmentSplitEntity();
|
||||
loaded.setParentOrderId(100L); loaded.setSourceOrderId(100L); loaded.setChildOrderId(100L);
|
||||
loaded.setLoadingPlanId(701L); loaded.setContainerBookingId(801L);
|
||||
loaded.setSourcePackageCount(20); loaded.setLoadedPackageCount(8); loaded.setRemainingPackageCount(12);
|
||||
loaded.setOperationId("op-key-retry"); loaded.setSourceDetailJson("[{\"sourceDetailId\":501,\"packageCount\":10},{\"sourceDetailId\":502,\"packageCount\":10}]");
|
||||
loaded.setSplitRole("LOADED");
|
||||
OrderFulfillmentSplitEntity remainder = new OrderFulfillmentSplitEntity();
|
||||
remainder.setParentOrderId(100L); remainder.setSourceOrderId(100L); remainder.setChildOrderId(200L);
|
||||
remainder.setLoadingPlanId(701L); remainder.setContainerBookingId(801L);
|
||||
remainder.setSourcePackageCount(20); remainder.setLoadedPackageCount(8); remainder.setRemainingPackageCount(12);
|
||||
remainder.setOperationId("op-key-retry"); remainder.setSourceDetailJson(loaded.getSourceDetailJson());
|
||||
remainder.setSplitRole("REMAINDER");
|
||||
when(splits.findByIdempotency(10, 100, "key-retry")).thenReturn(List.of(loaded, remainder));
|
||||
when(splits.lockOrder(10, 100)).thenReturn(parent());
|
||||
|
||||
OrderSplitDtos.CreateRequest retry = new OrderSplitDtos.CreateRequest(10L, 100L, 100L, 701L, 801L,
|
||||
20, 8, 12, 99, "key-retry", "op-key-retry", List.of(
|
||||
new OrderSplitDtos.QuantityLine(501L, 10), new OrderSplitDtos.QuantityLine(502L, 10)), "another-operator");
|
||||
|
||||
assertThat(service.split(retry, 9).remainingPackageCount()).isEqualTo(12);
|
||||
verify(orders, never()).insert(any(OrderEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsConsumedOrMismatchedRemainderSource() {
|
||||
OrderFulfillmentSplitEntity source = new OrderFulfillmentSplitEntity();
|
||||
|
||||
Reference in New Issue
Block a user