From 775dd712277e58f60fcae22473221adec8d1db35 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 29 Aug 2026 11:00:19 +0800 Subject: [PATCH] fix(firstleg): keep published billing visible after loading --- .../MerchantInboundBillingController.java | 4 ++- .../MerchantInboundBillingService.java | 12 +++++-- .../main/resources/openapi/firstleg-v1.yaml | 2 +- ...tInboundBillingControllerContractTest.java | 6 +++- .../MerchantInboundBillingServiceTest.java | 36 +++++++++++++++++-- 5 files changed, 51 insertions(+), 9 deletions(-) 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 854814203..ff6675307 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,9 @@ public class MerchantInboundBillingController { @GetMapping("/{orderId}/inbound-billing") @PreAuthorize("hasAuthority('merchant:order:view')") @Operation(summary = "查询订单入库结果和最新费用", - description = "客户只读查看已达到 WAIT_SPLIT + APPROVED + CONFIRMED 的二期费用,不需要确认费用。") + description = "客户按登录态客户范围只读查看;入库流程达到 WAIT_SPLIT 后," + + "在 LOADING、LOADED、DELIVERED 仍持续可见,且始终要求 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/service/MerchantInboundBillingService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/MerchantInboundBillingService.java index d3693f300..eae943048 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/MerchantInboundBillingService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/MerchantInboundBillingService.java @@ -17,9 +17,16 @@ import com.baiyue.firstleg.repository.InboundOrderRepository; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Set; @Service public class MerchantInboundBillingService { + private static final Set PUBLISHED_BILLING_PROCESS_STATUSES = Set.of( + InboundProcessStatus.WAIT_SPLIT, + InboundProcessStatus.LOADING, + InboundProcessStatus.LOADED, + InboundProcessStatus.DELIVERED); + @Resource private InboundOrderRepository orders; @Resource @@ -41,6 +48,7 @@ public class MerchantInboundBillingService { if (!isPublished(inbound)) { throw new BizException(ResultCode.NOT_FOUND, "入库费用尚未由客服审核并发布"); } + InboundFeeDetailEntity publishedFee = feeDetails.publishedFreight(tenantId, inbound.getId()); InboundOrderDetail platformDetail = orders.detail(tenantId, inbound); InboundOrderDetail merchantDetail = new InboundOrderDetail( platformDetail.summary(), platformDetail.publicRemark(), platformDetail.palletLocations(), @@ -49,7 +57,6 @@ public class MerchantInboundBillingService { .toList(), List.of(), List.of()); InboundMeasurementResponses.Detail measurement = measurements.detail(tenantId, inbound.getId()); FeeSnapshotResponse fee = pricing.latest(tenantId, inbound.getId()); - InboundFeeDetailEntity publishedFee = feeDetails.publishedFreight(tenantId, inbound.getId()); var summary = new MerchantInboundBillingResponse.MeasurementSummary( measurement.measurementId(), measurement.sourceType(), measurement.measurementStatus(), measurement.actualPackageCount(), measurement.actualGrossWeightKg(), @@ -117,8 +124,7 @@ public class MerchantInboundBillingService { } private static boolean isPublished(InboundOrderEntity inbound) { - return (InboundProcessStatus.WAIT_SPLIT.equals(inbound.getProcessStatus()) - || "WAIT_SPLIT".equals(inbound.getProcessStatus())) + return PUBLISHED_BILLING_PROCESS_STATUSES.contains(inbound.getProcessStatus()) && "APPROVED".equals(inbound.getMeasureReviewStatus()) && "CONFIRMED".equals(inbound.getFeeStatus()); } diff --git a/firstleg/src/main/resources/openapi/firstleg-v1.yaml b/firstleg/src/main/resources/openapi/firstleg-v1.yaml index 593bc4cdc..506c12ebb 100644 --- a/firstleg/src/main/resources/openapi/firstleg-v1.yaml +++ b/firstleg/src/main/resources/openapi/firstleg-v1.yaml @@ -905,7 +905,7 @@ paths: /api/merchant/v1/first-leg/orders/{orderId}/inbound-billing: get: summary: 客户查询已完成入库结果和最新已发布费用 - description: 需要 merchant:order:view 权限;客户只读查看,不需要确认费用,仅 WAIT_SPLIT + APPROVED + CONFIRMED 的二期终态可见。 + description: 需要 merchant:order:view 权限;客户仅能按登录态客户范围只读查看。入库流程达到 WAIT_SPLIT 后,费用在 LOADING、LOADED、DELIVERED 仍持续可见;始终要求 measureReviewStatus=APPROVED、feeStatus=CONFIRMED 且存在 PUBLISHED 入库运费明细,不需要客户确认费用。 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/MerchantInboundBillingControllerContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/controller/MerchantInboundBillingControllerContractTest.java index 76426d658..d7b90579d 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/controller/MerchantInboundBillingControllerContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/controller/MerchantInboundBillingControllerContractTest.java @@ -9,6 +9,7 @@ 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 io.swagger.v3.oas.annotations.Operation; import java.nio.charset.StandardCharsets; import java.util.Set; @@ -32,13 +33,16 @@ class MerchantInboundBillingControllerContractTest { "find", AuthenticatedUser.class, long.class); assertThat(method.getAnnotation(PreAuthorize.class).value()) .isEqualTo("hasAuthority('merchant:order:view')"); + assertThat(method.getAnnotation(Operation.class).description()) + .contains("WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED", "APPROVED + CONFIRMED", "已发布入库运费明细"); 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"); + .contains("WAIT_SPLIT", "LOADING、LOADED、DELIVERED", "measureReviewStatus=APPROVED", + "feeStatus=CONFIRMED", "PUBLISHED 入库运费明细"); } @Test 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 1e2e43395..def56bac8 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/MerchantInboundBillingServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/MerchantInboundBillingServiceTest.java @@ -10,6 +10,8 @@ import com.baiyue.firstleg.entity.InboundFeeDetailEntity; import com.baiyue.firstleg.repository.InboundOrderRepository; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -34,9 +36,10 @@ class MerchantInboundBillingServiceTest { @MockBean private InboundFeeDetailService feeDetails; @Resource private MerchantInboundBillingService service; - @Test - void shouldHandleCase01() { - InboundOrderEntity inbound = inbound("WAIT_SPLIT", "APPROVED", "CONFIRMED"); + @ParameterizedTest + @ValueSource(strings = {"WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED"}) + void shouldKeepPublishedBillingVisibleAfterFulfillmentProgresses(String processStatus) { + InboundOrderEntity inbound = inbound(processStatus, "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), @@ -151,6 +154,33 @@ class MerchantInboundBillingServiceTest { verify(pricing, never()).latest(10001L, 88L); } + @ParameterizedTest + @ValueSource(strings = {"FEE_CONFIRMING", "VOIDED"}) + void shouldRejectProcessStatusesOutsidePublishedFulfillmentLifecycle(String processStatus) { + InboundOrderEntity inbound = inbound(processStatus, "APPROVED", "CONFIRMED"); + when(orders.findByOrderId(10001L, 77L)).thenReturn(inbound); + + assertThatThrownBy(() -> service.find(10001L, 77L)) + .isInstanceOf(BizException.class) + .hasMessageContaining("尚未由客服审核并发布"); + verify(feeDetails, never()).publishedFreight(10001L, 88L); + } + + @Test + void shouldRequirePublishedFreightDetailAfterLoadingCompletes() { + InboundOrderEntity inbound = inbound("LOADED", "APPROVED", "CONFIRMED"); + when(orders.findByOrderId(10001L, 77L)).thenReturn(inbound); + when(feeDetails.publishedFreight(10001L, 88L)) + .thenThrow(new BizException(com.baiyue.common.core.api.ResultCode.NOT_FOUND, + "入库运费明细尚未发布")); + + assertThatThrownBy(() -> service.find(10001L, 77L)) + .isInstanceOf(BizException.class) + .hasMessageContaining("运费明细尚未发布"); + verify(orders, never()).detail(10001L, inbound); + verify(pricing, never()).latest(10001L, 88L); + } + private static InboundOrderEntity inbound(String process, String reviewStatus, String feeStatus) { InboundOrderEntity inbound = new InboundOrderEntity(); inbound.setId(88L);