From a927903a66163725f5d96967e068506dfb64e776 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 3 Sep 2026 13:49:38 +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 --- .../src/main/resources/openapi/order-v1.yaml | 1 + .../OrderDeclarationUpdateContractTest.java | 1 + .../OrderDeclarationEditGuardTest.java | 77 +++++++++++++++++++ .../OrderDeclarationUpdateServiceTest.java | 19 +++++ 4 files changed, 98 insertions(+) create mode 100644 order/src/test/java/com/baiyue/order/service/OrderDeclarationEditGuardTest.java diff --git a/order/src/main/resources/openapi/order-v1.yaml b/order/src/main/resources/openapi/order-v1.yaml index 3e9ea4fb9..f55de4006 100644 --- a/order/src/main/resources/openapi/order-v1.yaml +++ b/order/src/main/resources/openapi/order-v1.yaml @@ -212,6 +212,7 @@ paths: description: >- 需要 platform:order:declaration:edit。货物使用订单内 itemNo 定位,不接受货物数据库 ID; 不允许修改客户、国内收货仓、订单号、入仓码、状态、仓库/PDA/设备实测、测量审核或计费事实。 + 仅待审核、待测量数据、待分柜阶段允许修改;进入装柜中及后续阶段后拒绝修改。 已有测量或计费事实不阻断客户申报快照更新,详情继续以最新申报与既有实测分别展示; 只有渠道发生变化时才在写入前校验同柜/分柜/装柜可逆性和重新计价资格,任何校验失败均不写入。 正式门禁分别为 /internal/firstleg/v1/inbounds/order-channel-changes/validate 与 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 378d9164b..c479e3e98 100644 --- a/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java +++ b/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java @@ -66,6 +66,7 @@ class OrderDeclarationUpdateContractTest { assertThat(map(row.get("actualVolumeCbm")).get("description").toString()).contains("仅 Firstleg 实测"); assertThat(map(row.get("estimatedVolumeCbm")).get("description").toString()).contains("平台运营"); 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/apply"); diff --git a/order/src/test/java/com/baiyue/order/service/OrderDeclarationEditGuardTest.java b/order/src/test/java/com/baiyue/order/service/OrderDeclarationEditGuardTest.java new file mode 100644 index 000000000..09e878869 --- /dev/null +++ b/order/src/test/java/com/baiyue/order/service/OrderDeclarationEditGuardTest.java @@ -0,0 +1,77 @@ +package com.baiyue.order.service; + +import com.baiyue.common.core.api.ApiResponse; +import com.baiyue.common.core.exception.BizException; +import com.baiyue.order.client.FirstlegUnifiedOrderClient; +import com.baiyue.order.entity.OrderEntity; +import com.baiyue.order.entity.OrderStatus; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringJUnitConfig(OrderDeclarationEditGuard.class) +@TestPropertySource(properties = "baiyue.security.internal-service-token=token") +class OrderDeclarationEditGuardTest { + @MockBean private FirstlegUnifiedOrderClient firstleg; + @Resource private OrderDeclarationEditGuard guard; + + @Test + void allowsPendingReviewWithoutFirstlegAndWaitSplitWithAuthoritativeSummary() { + OrderEntity pendingReview = order(OrderStatus.SUBMITTED); + assertThatCode(() -> guard.requireEditable(pendingReview)).doesNotThrowAnyException(); + + OrderEntity waitSplit = order(OrderStatus.APPROVED); + var request = new FirstlegUnifiedOrderClient.BatchRequest(11L, List.of(21L)); + var waitSplitSummary = summary("WAIT_SPLIT"); + when(firstleg.summaries("token", request)).thenReturn(ApiResponse.success(List.of(waitSplitSummary))); + assertThatCode(() -> guard.requireEditable(waitSplit)).doesNotThrowAnyException(); + verify(firstleg).summaries("token", request); + } + + @Test + void rejectsLoadingAndLaterStatuses() { + OrderEntity order = order(OrderStatus.APPROVED); + var request = new FirstlegUnifiedOrderClient.BatchRequest(11L, List.of(21L)); + for (String status : List.of("LOADING", "LOADED", "DELIVERED")) { + var fulfillmentSummary = summary(status); + when(firstleg.summaries("token", request)).thenReturn(ApiResponse.success(List.of(fulfillmentSummary))); + assertThatThrownBy(() -> guard.requireEditable(order)) + .isInstanceOf(BizException.class).hasMessageContaining("已进入装柜或后续阶段"); + } + } + + @Test + void failsClosedWhenFirstlegSummaryIsMissing() { + OrderEntity order = order(OrderStatus.APPROVED); + var request = new FirstlegUnifiedOrderClient.BatchRequest(11L, List.of(21L)); + when(firstleg.summaries("token", request)).thenReturn(ApiResponse.success(List.of())); + + assertThatThrownBy(() -> guard.requireEditable(order)) + .isInstanceOf(BizException.class).hasMessageContaining("状态校验服务暂时不可用"); + } + + private FirstlegUnifiedOrderClient.Summary summary(String mainStatus) { + var inbound = mock(FirstlegUnifiedOrderClient.Inbound.class); + when(inbound.orderId()).thenReturn(21L); + var summary = mock(FirstlegUnifiedOrderClient.Summary.class); + when(summary.inbound()).thenReturn(inbound); + when(summary.mainStatus()).thenReturn(mainStatus); + return summary; + } + + private OrderEntity order(OrderStatus status) { + OrderEntity order = new OrderEntity(); + order.setId(21L); order.setTenantId(11L); order.setStatus(status); + return order; + } +} diff --git a/order/src/test/java/com/baiyue/order/service/OrderDeclarationUpdateServiceTest.java b/order/src/test/java/com/baiyue/order/service/OrderDeclarationUpdateServiceTest.java index ea15e8241..67e7d6530 100644 --- a/order/src/test/java/com/baiyue/order/service/OrderDeclarationUpdateServiceTest.java +++ b/order/src/test/java/com/baiyue/order/service/OrderDeclarationUpdateServiceTest.java @@ -33,6 +33,7 @@ class OrderDeclarationUpdateServiceTest { @MockBean private OrderRepository repository; @MockBean private OrderPricingChannelGuard pricingChannelGuard; @MockBean private OrderFulfillmentChannelChangeGuard fulfillmentChannelGuard; + @MockBean private OrderDeclarationEditGuard declarationEditGuard; @MockBean private ObjectMapper json; @Resource private OrderDeclarationUpdateService service; @@ -57,6 +58,7 @@ class OrderDeclarationUpdateServiceTest { .contains("channelCode", "channelName", "customerRemark", "expectedArrivalAt", "estimatedVolumeCbm", "cargoName", "packageCount", "totalQuantity", "lengthCm", "grossWeightKg"); verify(pricingChannelGuard).requireAvailable(100L, 31L, "NEW", "FBA"); + verify(declarationEditGuard).requireEditable(order); verify(repository).insertHistory(any()); verify(repository, never()).insertOutbox(any()); assertThat(cargo.getPackageCount()).isEqualTo(20); @@ -128,6 +130,23 @@ class OrderDeclarationUpdateServiceTest { any(), any(), any(), any(), any(), anyBoolean()); } + @Test + void rejectsDeclarationUpdateWhenFulfillmentHasReachedLoading() { + OrderEntity order = order(OrderStatus.APPROVED, 3); + when(repository.findOwned(100L, 200L)).thenReturn(order); + org.mockito.Mockito.doThrow(new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT, + "订单已进入装柜或后续阶段,不能修改客户申报资料")) + .when(declarationEditGuard).requireEditable(order); + + assertThatThrownBy(() -> service.update(100L, 200L, 700L, "平台操作员", + request(3, "OLD", "旧渠道", "备注"))) + .isInstanceOf(BizException.class).hasMessageContaining("已进入装柜"); + + verify(repository, never()).findCargo(anyLong(), anyLong()); + verify(repository, never()).updateDeclarationHeader(anyLong(), anyLong(), anyInt(), anyLong(), + any(), any(), any(), any(), any(), anyBoolean()); + } + @Test void updatesPlanningFieldsWithCurrentDeclarationSnapshotAndVersion() { OrderEntity order = order(OrderStatus.SUBMITTED, 3);