From e0a1f157772466bccb1c566b22662173ac6180fa Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 10:27:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/FbaPricingRepository.java | 2 +- .../OrderChannelChangePricingService.java | 3 ++- .../main/resources/openapi/firstleg-v1.yaml | 4 ++-- .../src/main/resources/openapi/order-v1.yaml | 2 ++ .../OrderDeclarationUpdateContractTest.java | 7 ++++++- .../OrderInboundOutboxPublisherTest.java | 19 +++++++++++++++++++ 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/repository/FbaPricingRepository.java b/firstleg/src/main/java/com/baiyue/firstleg/repository/FbaPricingRepository.java index 5a9a0f1fe..1f8929b1c 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/repository/FbaPricingRepository.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/repository/FbaPricingRepository.java @@ -202,7 +202,7 @@ public class FbaPricingRepository { LambdaUpdateWrapper update = new LambdaUpdateWrapper() .eq(InboundPricingEntity::getTenantId, inbound.getTenantId()) .eq(InboundPricingEntity::getId, inbound.getId()) - .in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED")) + .in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED", "VOIDED")) .set(InboundPricingEntity::getChannelCode, channelCode) .set(InboundPricingEntity::getChannelName, channelName) .set(InboundPricingEntity::getFeeStatus, "UNPRICED") diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/OrderChannelChangePricingService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/OrderChannelChangePricingService.java index eb20ed3cc..6b1d7c4f8 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/OrderChannelChangePricingService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/OrderChannelChangePricingService.java @@ -129,7 +129,8 @@ public class OrderChannelChangePricingService { || feeItems.findPublishedFreight(inbound.getTenantId(), inbound.getId()) != null) { throw conflict("费用已确认或已发布,渠道不可变更;请先按费用作废流程处理"); } - if (!"UNPRICED".equals(inbound.getFeeStatus()) && !"CALCULATED".equals(inbound.getFeeStatus())) { + if (!"UNPRICED".equals(inbound.getFeeStatus()) && !"CALCULATED".equals(inbound.getFeeStatus()) + && !"VOIDED".equals(inbound.getFeeStatus())) { throw conflict("当前费用状态不可逆,渠道不可变更"); } } diff --git a/firstleg/src/main/resources/openapi/firstleg-v1.yaml b/firstleg/src/main/resources/openapi/firstleg-v1.yaml index 55a733e37..ce4a0753f 100644 --- a/firstleg/src/main/resources/openapi/firstleg-v1.yaml +++ b/firstleg/src/main/resources/openapi/firstleg-v1.yaml @@ -93,7 +93,7 @@ paths: 仅供 Order 在渠道变更成功后调用。tenantId + orderId + orderVersion 构成幂等边界; 旧版本重放不会覆盖新版本。只将 CALCULATED 费用及草稿运费标记为 SUPERSEDED, 入库单恢复 UNPRICED 并可重新计价;实际净重、实际体积和测量审核事实保持不变。 - 已确认、已发送或已发布费用一律拒绝,不能由本命令覆盖。 + 已确认、已发送或已发布费用一律拒绝,不能由本命令覆盖;费用正式作废为 VOIDED 后可重新计价。 security: - internalServiceToken: [] requestBody: @@ -1066,7 +1066,7 @@ components: orderVersion: {type: integer, format: int64} channelCode: {type: string} priceBookVersionId: {type: integer, format: int64, nullable: true} - feeStatus: {type: string, enum: [UNPRICED, CALCULATED]} + feeStatus: {type: string, enum: [UNPRICED, CALCULATED, VOIDED]} calculatedFeeWillBeInvalidated: {type: boolean} invalidatedFeeId: {type: integer, format: int64, nullable: true} UnifiedMainStatus: diff --git a/order/src/main/resources/openapi/order-v1.yaml b/order/src/main/resources/openapi/order-v1.yaml index e5c213751..a31375333 100644 --- a/order/src/main/resources/openapi/order-v1.yaml +++ b/order/src/main/resources/openapi/order-v1.yaml @@ -218,6 +218,8 @@ paths: /internal/firstleg/v1/pricing/order-channel-changes/validate,两个服务均失败关闭。 国内收货仓或中转仓如需变化必须重新下单。成功后通过 ORDER_DECLARATION_UPDATED_V1 Outbox 同步 Firstleg;消费者按 eventId 幂等、version 顺序刷新申报快照,不覆盖实测、箱码或已确认费用。 + 本次 changes 含渠道字段时,同版本 Outbox 还必须调用 + /internal/firstleg/v1/pricing/order-channel-changes/apply,使未确认价格预估显式失效;普通申报更新不得调用。 parameters: - $ref: '#/components/parameters/UnifiedOrderId' - $ref: '#/components/parameters/UnifiedTenantId' diff --git a/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java b/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java index b1157847f..d7b229429 100644 --- a/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java +++ b/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java @@ -51,7 +51,8 @@ class OrderDeclarationUpdateContractTest { assertThat(map(row.get("actualVolumeCbm")).get("description").toString()).contains("仅 Firstleg 实测"); assertThat(source).contains("ORDER_DECLARATION_UPDATED_V1", "platform:order:declaration:edit", "A0409", "/internal/firstleg/v1/inbounds/order-channel-changes/validate", - "/internal/firstleg/v1/pricing/order-channel-changes/validate"); + "/internal/firstleg/v1/pricing/order-channel-changes/validate", + "/internal/firstleg/v1/pricing/order-channel-changes/apply"); } @Test @@ -60,10 +61,14 @@ class OrderDeclarationUpdateContractTest { String.class, FirstlegInboundClient.OrderChannelChangeValidationRequest.class); var pricing = FirstlegPricingClient.class.getDeclaredMethod("validateOrderChannelChange", String.class, FirstlegPricingClient.OrderChannelChangeRequest.class); + var applyPricing = FirstlegPricingClient.class.getDeclaredMethod("applyOrderChannelChange", + String.class, FirstlegPricingClient.OrderChannelChangeRequest.class); assertThat(fulfillment.getAnnotation(PostMapping.class).value()) .containsExactly("/order-channel-changes/validate"); assertThat(pricing.getAnnotation(PostMapping.class).value()) .containsExactly("/order-channel-changes/validate"); + assertThat(applyPricing.getAnnotation(PostMapping.class).value()) + .containsExactly("/order-channel-changes/apply"); } @SuppressWarnings("unchecked") diff --git a/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java b/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java index c95d1f3e8..33285db1b 100644 --- a/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java +++ b/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java @@ -171,6 +171,25 @@ class OrderInboundOutboxPublisherTest { verify(fixture.repository).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class)); } + @Test + void channelPricingApplyFailureKeepsOutboxPendingAndDoesNotAdvanceFulfillmentSnapshot() { + String payload = "{\"schemaVersion\":1,\"eventId\":\"evt-3\",\"tenantId\":11," + + "\"orderId\":21,\"orderNo\":\"ORD-21\",\"inboundCode\":\"RK-21\"," + + "\"version\":4,\"operatorId\":7,\"channelCode\":\"SEA\"," + + "\"channelName\":\"海运\",\"channelChanged\":true,\"cargoes\":[]," + + "\"changes\":[{\"field\":\"channelName\",\"beforeValue\":\"旧渠道\",\"afterValue\":\"海运\"}]}"; + Fixture fixture = fixture(event("ORDER_DECLARATION_UPDATED_V1", 11L, 21L, payload)); + when(fixture.pricingClient.applyOrderChannelChange(eq("internal-token"), any())).thenReturn( + ApiResponse.failure(com.baiyue.common.core.api.ResultCode.CONFLICT, + "费用已确认或已发布,渠道不可变更", null, null)); + + fixture.publisher.publishApprovedOrders(); + + verify(fixture.repository).markOutboxRetry(eq(11L), eq(41L), any(LocalDateTime.class)); + verify(fixture.client, never()).updateDeclaration(any(), any()); + verify(fixture.repository, never()).markOutboxPublished(anyLong(), anyLong(), any()); + } + private Fixture fixture(OutboxEventEntity event) { return fixture(event, "BUY_DECLARATION"); }