This commit is contained in:
2026-09-03 13:49:38 +08:00
parent 59b42c7579
commit a927903a66
4 changed files with 98 additions and 0 deletions
@@ -212,6 +212,7 @@ paths:
description: >-
需要 platform:order:declaration:edit。货物使用订单内 itemNo 定位,不接受货物数据库 ID;
不允许修改客户、国内收货仓、订单号、入仓码、状态、仓库/PDA/设备实测、测量审核或计费事实。
仅待审核、待测量数据、待分柜阶段允许修改;进入装柜中及后续阶段后拒绝修改。
已有测量或计费事实不阻断客户申报快照更新,详情继续以最新申报与既有实测分别展示;
只有渠道发生变化时才在写入前校验同柜/分柜/装柜可逆性和重新计价资格,任何校验失败均不写入。
正式门禁分别为 /internal/firstleg/v1/inbounds/order-channel-changes/validate 与
@@ -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");
@@ -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;
}
}
@@ -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);