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