This commit is contained in:
2026-09-10 11:18:31 +08:00
parent 8731efcbea
commit e9aa2ac047
22 changed files with 374 additions and 52 deletions
@@ -22,6 +22,8 @@ class InboundOrderControllerContractTest {
AuthenticatedUser.class, long.class, long.class);
assertAuthority("printCartonLabels", "hasAuthority('platform:firstleg:inbound:view')",
AuthenticatedUser.class, long.class, long.class, List.class, String.class);
assertAuthority("printCartonLabelsBatch", "hasAuthority('platform:firstleg:inbound:view')",
AuthenticatedUser.class, List.class, String.class);
Method page = PlatformInboundOrderController.class.getDeclaredMethod(
"page", AuthenticatedUser.class, Long.class, InboundOrderQuery.class);
@@ -47,6 +49,7 @@ class InboundOrderControllerContractTest {
.contains("/api/platform/inbounds:")
.contains("/api/platform/inbounds/{inboundId}:")
.contains("/api/platform/inbounds/{inboundId}/carton-label-pdf:")
.contains("/api/platform/inbounds/carton-label-pdf:")
.contains("/api/platform/v1/first-leg/inbounds/{inboundId}/measurement/carton-scans:")
.contains("required: false")
.contains("InboundOrderSummary:")
@@ -209,20 +209,42 @@ class FbaPricingRepositoryRuleListTest {
.thenReturn(1);
assertThat(repository.markInboundCalculated(
10001L, 88L, "WAIT_PALLETIZE", "CBM", new java.math.BigDecimal("1.0000")))
10001L, 88L, "WAIT_PALLETIZE", "CBM", new java.math.BigDecimal("10.000"),
new java.math.BigDecimal("1.0000"), new java.math.BigDecimal("1.0000")))
.isEqualTo(1);
@SuppressWarnings("unchecked")
ArgumentCaptor<LambdaUpdateWrapper<InboundPricingEntity>> wrapper =
ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
verify(pricing).update(org.mockito.ArgumentMatchers.isNull(), wrapper.capture());
assertThat(wrapper.getValue().getSqlSet()).contains("feeStatus", "chargeableVolumeCbm")
assertThat(wrapper.getValue().getSqlSet()).contains(
"feeStatus", "chargeableWeightKg", "chargeableVolumeCbm")
.doesNotContain("processStatus");
assertThat(wrapper.getValue().getCustomSqlSegment())
.contains("tenantId", "id", "processStatus", "feeStatus");
assertThat(params(wrapper.getValue()).values()).contains(10001L, 88L, "WAIT_PALLETIZE");
}
@Test
void kilogramPricingStoresChargeableWeightAndSelectedVolumeTogether() {
when(pricing.update(org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.any()))
.thenReturn(1);
assertThat(repository.markInboundCalculated(
10001L, 88L, "WAIT_SPLIT", "KG", new java.math.BigDecimal("297.000"),
new java.math.BigDecimal("9.0000"), new java.math.BigDecimal("1503.000")))
.isEqualTo(1);
@SuppressWarnings("unchecked")
ArgumentCaptor<LambdaUpdateWrapper<InboundPricingEntity>> wrapper =
ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
verify(pricing).update(org.mockito.ArgumentMatchers.isNull(), wrapper.capture());
assertThat(wrapper.getValue().getSqlSet())
.contains("chargeableWeightKg", "chargeableVolumeCbm");
assertThat(params(wrapper.getValue()).values()).contains(
new java.math.BigDecimal("1503.000"), new java.math.BigDecimal("9.0000"));
}
private Collection<Object> queryValues(Long tenantId, String ruleCode) {
when(rules.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
@@ -216,7 +216,7 @@ class FbaPricingServiceTest {
assertThat(response.feeStatus()).isEqualTo("CALCULATED");
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_SPLIT");
verify(repository).markInboundCalculated(TENANT, INBOUND, "WAIT_SPLIT", "KG",
new BigDecimal("167.000"));
new BigDecimal("100.000"), new BigDecimal("1.0000"), new BigDecimal("167.000"));
verify(feeDetails).materializeCalculatedFreight(TENANT, INBOUND, 501L, OPERATOR, NOW);
verify(financeOutbox, never()).publishInbound(any(), anyLong(), anyLong(), anyLong(), anyInt(), any());
}
@@ -299,7 +299,7 @@ class FbaPricingServiceTest {
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_PALLETIZE");
verify(repository).markInboundCalculated(TENANT, INBOUND, "WAIT_PALLETIZE", "CBM",
new BigDecimal("1.0000"));
new BigDecimal("10.000"), new BigDecimal("1.0000"), new BigDecimal("1.0000"));
}
@Test
@@ -325,7 +325,7 @@ class FbaPricingServiceTest {
assertThat(response.nextProcessStatus()).isEqualTo("LOADED");
verify(repository).supersedeCurrent(TENANT, 501L, "SENT");
verify(repository).markInboundCalculated(TENANT, INBOUND, "LOADED", "CBM",
new BigDecimal("1.0000"));
new BigDecimal("10.000"), new BigDecimal("1.0000"), new BigDecimal("1.0000"));
}
@Test
@@ -470,7 +470,8 @@ class FbaPricingServiceTest {
.when(repository).insertIdempotency(any());
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(2);
when(repository.supersedeCurrent(TENANT, 501L, "SENT")).thenReturn(1);
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("SENT"), eq("KG"), any()))
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("SENT"),
any(), any(), eq("KG"), any()))
.thenReturn(1);
doAnswer(invocation -> {
InboundFeeEntity fee = invocation.getArgument(0);
@@ -565,7 +566,8 @@ class FbaPricingServiceTest {
.when(repository).insertIdempotency(any());
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(2);
when(repository.supersedeCurrent(TENANT, 501L, "CALCULATED")).thenReturn(1);
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("CALCULATED"), eq("KG"), any()))
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("CALCULATED"),
any(), any(), eq("KG"), any()))
.thenReturn(1);
doAnswer(invocation -> {
InboundFeeEntity fee = invocation.getArgument(0);
@@ -624,7 +626,8 @@ class FbaPricingServiceTest {
.when(repository).insertIdempotency(any());
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(2);
when(repository.supersedeCurrent(TENANT, 501L, "CALCULATED")).thenReturn(1);
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("CALCULATED"), eq("KG"), any()))
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("CALCULATED"),
any(), any(), eq("KG"), any()))
.thenReturn(1);
doAnswer(invocation -> { ((InboundFeeEntity) invocation.getArgument(0)).setId(504L); return null; })
.when(repository).insertFee(any());
@@ -772,7 +775,8 @@ class FbaPricingServiceTest {
when(repository.nextFeeVersion(TENANT, 30002L)).thenReturn(2);
when(repository.completeIdempotency(any(), eq(901L), eq(NOW))).thenReturn(1);
when(repository.supersedeCurrent(eq(TENANT), anyLong(), anyString())).thenReturn(1);
when(repository.markInboundContainerFeeRecalculated(eq(TENANT), anyLong(), anyString(), anyString(), any()))
when(repository.markInboundContainerFeeRecalculated(eq(TENANT), anyLong(), anyString(),
any(), any(), anyString(), any()))
.thenReturn(1);
when(priceBookEstimator.estimate(eq(TENANT), any())).thenReturn(
regionEstimate("200", "8", "1600"), regionEstimate("200", "8", "1600"));
@@ -824,7 +828,8 @@ class FbaPricingServiceTest {
when(repository.nextFeeVersion(TENANT, 30002L)).thenReturn(2);
when(repository.completeIdempotency(any(), eq(901L), eq(NOW))).thenReturn(1);
when(repository.supersedeCurrent(TENANT, 502L, "CALCULATED")).thenReturn(1);
when(repository.markInboundContainerFeeRecalculated(eq(TENANT), eq(30002L), anyString(), anyString(), any()))
when(repository.markInboundContainerFeeRecalculated(eq(TENANT), eq(30002L), anyString(),
any(), any(), anyString(), any()))
.thenReturn(1);
when(priceBookEstimator.estimate(eq(TENANT), any())).thenReturn(regionEstimate("180", "8", "1440"));
when(containerTierAllocator.allocate(any(), eq(new BigDecimal("8")))).thenReturn(
@@ -908,7 +913,8 @@ class FbaPricingServiceTest {
doAnswer(invocation -> { ((InboundFeeEntity) invocation.getArgument(0)).setId(501L); return null; })
.when(repository).insertFee(any());
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(1);
when(repository.markInboundCalculated(eq(TENANT), eq(INBOUND), any(), any(), any())).thenReturn(1);
when(repository.markInboundCalculated(eq(TENANT), eq(INBOUND), any(), any(), any(), any(), any()))
.thenReturn(1);
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
}
@@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
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.verifyNoInteractions;
import static org.mockito.Mockito.when;
class InboundCartonLabelPrintServiceTest {
@@ -118,6 +119,39 @@ class InboundCartonLabelPrintServiceTest {
org.mockito.ArgumentMatchers.eq("request-batch"), org.mockito.ArgumentMatchers.any());
}
@Test
void platformBatchCombinesLabelsAcrossCustomers() {
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
when(labels.list(10002L, 70002L, null)).thenReturn(batch());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.eq("platform-batch"), org.mockito.ArgumentMatchers.any()))
.thenReturn(new ResponseEntity<>(new byte[] {1, 2, 3}, headers,
org.springframework.http.HttpStatus.OK));
ResponseEntity<byte[]> response = service.generateForPlatformInbounds(List.of(
new InboundCartonLabels.PlatformBatchItem(10001L, 70001L),
new InboundCartonLabels.PlatformBatchItem(10002L, 70002L)), "platform-batch");
assertThat(response.getHeaders().getFirst("X-Carton-Label-Order-Count")).isEqualTo("2");
assertThat(response.getBody()).containsExactly(1, 2, 3);
}
@Test
void platformBatchFailsWhenAnySelectedInboundCannotGenerateLabels() {
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
when(labels.list(10002L, 70002L, null))
.thenThrow(new BizException(ResultCode.NOT_FOUND, "箱唛入库单不存在"));
assertThatThrownBy(() -> service.generateForPlatformInbounds(List.of(
new InboundCartonLabels.PlatformBatchItem(10001L, 70001L),
new InboundCartonLabels.PlatformBatchItem(10002L, 70002L)), "platform-batch"))
.isInstanceOf(BizException.class)
.hasMessage("箱唛入库单不存在");
verifyNoInteractions(print);
}
private InboundCartonLabels.Batch batch() {
InboundCartonLabels.Label label = new InboundCartonLabels.Label(
1L, 2L, 1, "音箱", 19, "PCHK2608040174#19-1", 19, 1,
@@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
@@ -233,6 +234,21 @@ class InboundCartonLabelServiceTest {
.hasMessageContaining("订单服务暂不可用");
}
@Test
void convertsOrderCargoCircuitBreakerFailureToActionableBusinessError() {
InboundOrderEntity inbound = inbound();
when(repository.lockInbound(10001L, 70001L)).thenReturn(inbound);
when(repository.findActiveByInbound(10001L, 70001L)).thenReturn(new ArrayList<>());
when(repository.findByInbound(10001L, 70001L)).thenReturn(new ArrayList<>());
when(orderCargoClient.listForCartonLabels("internal-token", 10001L, 60001L))
.thenThrow(new NoFallbackAvailableException("order unavailable",
new IllegalStateException("downstream failure")));
assertThatThrownBy(() -> service.getOrCreate(10001L, 70001L))
.isInstanceOf(BizException.class)
.hasMessage("订单服务暂不可用,无法生成唛头");
}
@Test
void repairsMissingHistoricalInboundFromOwnedOrderSnapshot() {
var snapshot = new com.baiyue.firstleg.dto.OrderApprovedInboundCommand(