diff --git a/firstleg/src/main/java/com/baiyue/firstleg/controller/MerchantInboundBillingController.java b/firstleg/src/main/java/com/baiyue/firstleg/controller/MerchantInboundBillingController.java index 49043ecc8..854814203 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/controller/MerchantInboundBillingController.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/controller/MerchantInboundBillingController.java @@ -29,7 +29,7 @@ public class MerchantInboundBillingController { @GetMapping("/{orderId}/inbound-billing") @PreAuthorize("hasAuthority('merchant:order:view')") @Operation(summary = "查询订单入库结果和最新费用", - description = "客户只读查看客服审核通过后发布的费用,不需要确认费用。") + description = "客户只读查看已达到 WAIT_SPLIT + APPROVED + CONFIRMED 的二期费用,不需要确认费用。") public ApiResponse find( @AuthenticationPrincipal AuthenticatedUser user, @PathVariable long orderId) { return ApiResponse.success(service.find(FirstlegScopeGuard.merchant(user), orderId)); diff --git a/firstleg/src/main/java/com/baiyue/firstleg/domain/InboundProcessStatus.java b/firstleg/src/main/java/com/baiyue/firstleg/domain/InboundProcessStatus.java index 1e5e621b0..9309bc9b5 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/domain/InboundProcessStatus.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/domain/InboundProcessStatus.java @@ -20,6 +20,8 @@ public final class InboundProcessStatus { public static final String INBOUNDING = "INBOUNDING"; /** 已入库完成,费用已发送给客户。 */ public static final String INBOUNDED = "INBOUNDED"; + /** 二期处理完成,等待三期分柜。 */ + public static final String WAIT_SPLIT = "WAIT_SPLIT"; /** 已进入装柜作业,PDA 可执行拣货。 */ public static final String LOADING = "LOADING"; /** 装柜完成,等待派送完成。 */ @@ -31,7 +33,7 @@ public final class InboundProcessStatus { public static final Set VALUES = Set.of( WAIT_MEASURE, MEASURING, MEASURE_PENDING, MEASURE_REJECTED, - WAIT_PRICING, FEE_CONFIRMING, INBOUNDING, INBOUNDED, + WAIT_PRICING, FEE_CONFIRMING, INBOUNDING, INBOUNDED, WAIT_SPLIT, LOADING, LOADED, DELIVERED, VOIDED ); diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/MerchantInboundBillingService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/MerchantInboundBillingService.java index 2f3a12913..62f46de96 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/MerchantInboundBillingService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/MerchantInboundBillingService.java @@ -10,6 +10,7 @@ import com.baiyue.firstleg.dto.InboundMeasurementResponses; import com.baiyue.firstleg.dto.InboundOrderDetail; import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse; import com.baiyue.firstleg.client.MeasurementImageClient; +import com.baiyue.firstleg.domain.InboundProcessStatus; import com.baiyue.firstleg.entity.InboundOrderEntity; import com.baiyue.firstleg.entity.InboundFeeDetailEntity; import com.baiyue.firstleg.repository.InboundOrderRepository; @@ -37,7 +38,7 @@ public class MerchantInboundBillingService { if (inbound == null) { throw new BizException(ResultCode.NOT_FOUND, "订单尚未生成入库单"); } - if (!"INBOUNDED".equals(inbound.getProcessStatus()) || !"SENT".equals(inbound.getFeeStatus())) { + if (!isPublished(inbound)) { throw new BizException(ResultCode.NOT_FOUND, "入库费用尚未由客服审核并发布"); } InboundOrderDetail platformDetail = orders.detail(tenantId, inbound); @@ -106,12 +107,18 @@ public class MerchantInboundBillingService { private InboundOrderEntity requirePublished(long tenantId, long orderId) { InboundOrderEntity inbound = orders.findByOrderId(tenantId, orderId); if (inbound == null) throw new BizException(ResultCode.NOT_FOUND, "订单尚未生成入库单"); - if (!"INBOUNDED".equals(inbound.getProcessStatus()) || !"SENT".equals(inbound.getFeeStatus())) { + if (!isPublished(inbound)) { throw new BizException(ResultCode.NOT_FOUND, "入库费用尚未由客服审核并发布"); } return inbound; } + private static boolean isPublished(InboundOrderEntity inbound) { + return InboundProcessStatus.WAIT_SPLIT.equals(inbound.getProcessStatus()) + && "APPROVED".equals(inbound.getMeasureReviewStatus()) + && "CONFIRMED".equals(inbound.getFeeStatus()); + } + private String previewUrl(long orderId, long fileId) { return "/api/merchant/v1/first-leg/orders/" + orderId + "/inbound-billing/images/" + fileId; diff --git a/firstleg/src/main/resources/openapi/firstleg-v1.yaml b/firstleg/src/main/resources/openapi/firstleg-v1.yaml index ac95a92fd..64ca23f09 100644 --- a/firstleg/src/main/resources/openapi/firstleg-v1.yaml +++ b/firstleg/src/main/resources/openapi/firstleg-v1.yaml @@ -434,7 +434,7 @@ paths: /api/merchant/v1/first-leg/orders/{orderId}/inbound-billing: get: summary: 客户查询已完成入库结果和最新已发布费用 - description: 需要 merchant:order:view 权限;客户只读查看,不需要确认费用,未完成客服审核发布时不可见。 + description: 需要 merchant:order:view 权限;客户只读查看,不需要确认费用,仅 WAIT_SPLIT + APPROVED + CONFIRMED 的二期终态可见。 parameters: - {name: orderId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} responses: diff --git a/firstleg/src/test/java/com/baiyue/firstleg/controller/InboundOrderControllerContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/controller/InboundOrderControllerContractTest.java index 24fee87f7..cf1b61443 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/controller/InboundOrderControllerContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/controller/InboundOrderControllerContractTest.java @@ -53,7 +53,8 @@ class InboundOrderControllerContractTest { .contains("tenantId: {type: string, pattern: '^[1-9][0-9]*$', description:") .contains("[WAIT_MEASURE, WAIT_REVIEW, WAIT_FEE_SEND, INBOUNDED]") .contains("platform:firstleg:inbound:list") - .doesNotContain("WAIT_CUSTOMER_CONFIRM"); + .doesNotContain("WAIT_CUSTOMER_CONFIRM") + .contains("WAIT_SPLIT"); assertThat(inboundList).contains("#/components/schemas/InboundOrderPageEnvelope"); assertThat(taskList).doesNotContain("InboundOrderPageEnvelope"); } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/controller/MerchantInboundBillingControllerContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/controller/MerchantInboundBillingControllerContractTest.java index 7de6ce9e5..f360beaec 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/controller/MerchantInboundBillingControllerContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/controller/MerchantInboundBillingControllerContractTest.java @@ -8,7 +8,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.core.io.ClassPathResource; +import java.nio.charset.StandardCharsets; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +34,11 @@ class MerchantInboundBillingControllerContractTest { .isEqualTo("hasAuthority('merchant:order:view')"); assertThat(MerchantInboundBillingController.class.getDeclaredMethods()) .noneMatch(candidate -> candidate.getName().toLowerCase().contains("confirm")); + + String openapi = new ClassPathResource("openapi/firstleg-v1.yaml") + .getContentAsString(StandardCharsets.UTF_8); + assertThat(openapi).contains("/api/merchant/v1/first-leg/orders/{orderId}/inbound-billing:") + .contains("仅 WAIT_SPLIT + APPROVED + CONFIRMED 的二期终态可见"); } @Test diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderQueryServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderQueryServiceTest.java index d168fe3f8..f7a94a2c5 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderQueryServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderQueryServiceTest.java @@ -40,6 +40,18 @@ class InboundOrderQueryServiceTest { verify(repository).page(null, query); } + @Test + void shouldAcceptCurrentPhase2TerminalStatusFilters() { + InboundOrderQuery query = new InboundOrderQuery(null, null, "WAIT_SPLIT", null, + "APPROVED", "CONFIRMED", null, null, null, null, null, null, null, + null, null, null, null, 1, 200); + PageResult expected = new PageResult<>(List.of(), 1, 200, 0, 0); + when(repository.page(10001L, query)).thenReturn(expected); + + assertThat(service.page(10001L, query)).isSameAs(expected); + verify(repository).page(10001L, query); + } + @Test void shouldKeepV2TabMappingWithoutCustomerConfirmation() { assertThat(InboundListTab.WAIT_MEASURE.processStatuses()) diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/MerchantInboundBillingServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/MerchantInboundBillingServiceTest.java index ff8bd200d..a01cd91b6 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/MerchantInboundBillingServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/MerchantInboundBillingServiceTest.java @@ -36,7 +36,7 @@ class MerchantInboundBillingServiceTest { @Test void shouldHandleCase01() { - InboundOrderEntity inbound = inbound("INBOUNDED", "SENT"); + InboundOrderEntity inbound = inbound("WAIT_SPLIT", "APPROVED", "CONFIRMED"); InboundOrderDetail detail = new InboundOrderDetail(mock(com.baiyue.firstleg.dto.InboundOrderSummary.class), "客户备注", List.of(), List.of( new InboundOrderDetail.FileSummary(1L, "RELATED", "public.pdf", "PUBLIC", 1, 1L, null), @@ -83,7 +83,7 @@ class MerchantInboundBillingServiceTest { @Test void shouldHandleCase02() { - InboundOrderEntity inbound = inbound("FEE_CONFIRMING", "CONFIRMED"); + InboundOrderEntity inbound = inbound("WAIT_SPLIT", "PENDING", "CONFIRMED"); when(orders.findByOrderId(10001L, 77L)).thenReturn(inbound); assertThatThrownBy(() -> service.find(10001L, 77L)) @@ -104,7 +104,7 @@ class MerchantInboundBillingServiceTest { @Test void shouldHandleCase04() { - InboundOrderEntity inbound = inbound("INBOUNDED", "SENT"); + InboundOrderEntity inbound = inbound("WAIT_SPLIT", "APPROVED", "CONFIRMED"); InboundMeasurementResponses.Detail measurement = mock(InboundMeasurementResponses.Detail.class); InboundMeasurementResponses.Item item = mock(InboundMeasurementResponses.Item.class); when(item.intelligentImageFileId()).thenReturn(41L); @@ -127,7 +127,7 @@ class MerchantInboundBillingServiceTest { @Test void shouldHandleCase05() { - InboundOrderEntity inbound = inbound("INBOUNDED", "SENT"); + InboundOrderEntity inbound = inbound("WAIT_SPLIT", "APPROVED", "CONFIRMED"); InboundMeasurementResponses.Detail measurement = mock(InboundMeasurementResponses.Detail.class); when(measurement.items()).thenReturn(List.of()); when(measurement.weightResults()).thenReturn(List.of()); @@ -140,10 +140,22 @@ class MerchantInboundBillingServiceTest { verify(imageClient, never()).download("internal-token", 10001L, 88L, 99L); } - private static InboundOrderEntity inbound(String process, String feeStatus) { + @Test + void shouldRejectHistoricalPublishedStatusCombination() { + InboundOrderEntity inbound = inbound("INBOUNDED", "APPROVED", "SENT"); + when(orders.findByOrderId(10001L, 77L)).thenReturn(inbound); + + assertThatThrownBy(() -> service.find(10001L, 77L)) + .isInstanceOf(BizException.class) + .hasMessageContaining("尚未由客服审核并发布"); + verify(pricing, never()).latest(10001L, 88L); + } + + private static InboundOrderEntity inbound(String process, String reviewStatus, String feeStatus) { InboundOrderEntity inbound = new InboundOrderEntity(); inbound.setId(88L); inbound.setProcessStatus(process); + inbound.setMeasureReviewStatus(reviewStatus); inbound.setFeeStatus(feeStatus); return inbound; }