优化
This commit is contained in:
@@ -748,12 +748,129 @@ class FbaPricingServiceTest {
|
||||
void scopesContainerTierMembersByCustomerAndContainer() throws NoSuchMethodException {
|
||||
String sql = ContainerTierPricingMapper.class.getMethod("members", long.class, long.class)
|
||||
.getAnnotation(Select.class).value()[0];
|
||||
String lockedSql = ContainerTierPricingMapper.class.getMethod("membersForUpdate", long.class, long.class)
|
||||
.getAnnotation(Select.class).value()[0];
|
||||
|
||||
assertThat(sql).contains("plan.container_booking_id=current_plan.container_booking_id")
|
||||
.contains("grouped_item.account_id=#{customerId}")
|
||||
.contains("grouped_inbound.tenant_id=#{customerId}")
|
||||
.doesNotContain("grouped_inbound.destination_code")
|
||||
.doesNotContain("grouped_inbound.destination_warehouse_id");
|
||||
assertThat(lockedSql).contains("plan.container_booking_id=#{containerBookingId}")
|
||||
.contains("item.account_id=#{customerId}").contains("FOR UPDATE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("柜内客户费用重算复用冻结选量和来源,并按统一阶梯分摊")
|
||||
void recalculatesUnlockedContainerFeesFromFrozenSelections() {
|
||||
readyRepository();
|
||||
InboundPricingEntity firstInbound = inbound("999", "9");
|
||||
InboundPricingEntity secondInbound = inbound("888", "8");
|
||||
secondInbound.setId(30002L); secondInbound.setInboundCode("RK-002");
|
||||
InboundFeeEntity first = savedFee("CALCULATED");
|
||||
first.setActualWeightKg(new BigDecimal("90.000")); first.setActualVolumeCbm(new BigDecimal("0.1000"));
|
||||
first.setMeasurementId(301L); first.setMeasurementSource("OUTER");
|
||||
InboundFeeEntity second = savedFee("CONFIRMED");
|
||||
second.setId(502L); second.setInboundId(30002L); second.setActualWeightKg(new BigDecimal("110.000"));
|
||||
second.setActualVolumeCbm(new BigDecimal("0.1000")); second.setMeasurementId(302L);
|
||||
second.setMeasurementSource("ACTUAL");
|
||||
when(repository.containerTierMembersForUpdate(901L, TENANT)).thenReturn(List.of(
|
||||
memberRow(901L, INBOUND, "999", "9"), memberRow(901L, 30002L, "888", "8")
|
||||
));
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(firstInbound);
|
||||
when(repository.findInbound(TENANT, 30002L)).thenReturn(secondInbound);
|
||||
when(repository.findLatestFeeForUpdate(TENANT, INBOUND)).thenReturn(first);
|
||||
when(repository.findLatestFeeForUpdate(TENANT, 30002L)).thenReturn(second);
|
||||
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(2);
|
||||
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()))
|
||||
.thenReturn(1);
|
||||
when(priceBookEstimator.estimate(eq(TENANT), any())).thenReturn(
|
||||
regionEstimate("200", "8", "1600"), regionEstimate("200", "8", "1600"));
|
||||
when(containerTierAllocator.allocate(any(), eq(new BigDecimal("8")))).thenReturn(
|
||||
new ContainerTierAmountAllocator.Allocation(new BigDecimal("200"), new BigDecimal("1600"),
|
||||
Map.of(INBOUND, new BigDecimal("720.00"), 30002L, new BigDecimal("880.00"))));
|
||||
|
||||
service.recalculateUnlockedContainerCustomerFees(901L, TENANT, "loading-change-1");
|
||||
|
||||
ArgumentCaptor<InboundFeeEntity> fees = ArgumentCaptor.forClass(InboundFeeEntity.class);
|
||||
verify(repository, times(2)).insertFee(fees.capture());
|
||||
assertThat(fees.getAllValues().get(0).getActualWeightKg()).isEqualByComparingTo("90.000");
|
||||
assertThat(fees.getAllValues().get(0).getActualVolumeCbm()).isEqualByComparingTo("0.1000");
|
||||
assertThat(fees.getAllValues().get(0).getMeasurementId()).isEqualTo(301L);
|
||||
assertThat(fees.getAllValues().get(0).getMeasurementSource()).isEqualTo("OUTER");
|
||||
assertThat(fees.getAllValues().get(0).getSubtotalAmount()).isEqualByComparingTo("720.00");
|
||||
assertThat(fees.getAllValues().get(1).getActualWeightKg()).isEqualByComparingTo("110.000");
|
||||
assertThat(fees.getAllValues().get(1).getMeasurementId()).isEqualTo(302L);
|
||||
assertThat(fees.getAllValues().get(1).getMeasurementSource()).isEqualTo("ACTUAL");
|
||||
ArgumentCaptor<com.baiyue.firstleg.dto.PriceBookDtos.EstimateRequest> requests =
|
||||
ArgumentCaptor.forClass(com.baiyue.firstleg.dto.PriceBookDtos.EstimateRequest.class);
|
||||
verify(priceBookEstimator, times(2)).estimate(eq(TENANT), requests.capture());
|
||||
assertThat(requests.getAllValues()).allSatisfy(request -> {
|
||||
assertThat(request.actualWeightKg()).isEqualByComparingTo("200");
|
||||
assertThat(request.actualVolumeCbm()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
});
|
||||
verify(financeOutbox, never()).publishInbound(any(), anyLong(), anyLong(), anyLong(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("柜内任一已发送或客户确认费用阻断整组重算")
|
||||
void rejectsContainerRecalculationWhenAnyFeeIsImmutable() {
|
||||
readyRepository();
|
||||
InboundPricingEntity inbound = inbound("100", "0.1");
|
||||
InboundFeeEntity sent = savedFee("SENT");
|
||||
when(repository.containerTierMembersForUpdate(901L, TENANT)).thenReturn(List.of(
|
||||
memberRow(901L, INBOUND, "100", "0.1")
|
||||
));
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(repository.findLatestFeeForUpdate(TENANT, INBOUND)).thenReturn(sent);
|
||||
|
||||
assertThatThrownBy(() -> service.recalculateUnlockedContainerCustomerFees(901L, TENANT, "loading-change-2"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("已发送或客户已确认");
|
||||
|
||||
verify(repository, never()).supersedeCurrent(anyLong(), anyLong(), anyString());
|
||||
verify(repository, never()).insertFee(any());
|
||||
verify(priceBookEstimator, never()).estimate(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("待审核或待测量成员无初算费用时保持占位并返回未就绪")
|
||||
void keepsContainerRelationshipWhenAnyMemberFeeIsNotReady() {
|
||||
readyRepository();
|
||||
InboundPricingEntity pricedInbound = inbound("100", "0.1");
|
||||
InboundPricingEntity pendingInbound = inbound("80", "0.1");
|
||||
pendingInbound.setId(30002L); pendingInbound.setInboundCode("RK-PENDING");
|
||||
InboundFeeEntity priced = savedFee("CALCULATED");
|
||||
priced.setActualWeightKg(new BigDecimal("100")); priced.setActualVolumeCbm(new BigDecimal("0.1"));
|
||||
when(repository.containerTierMembersForUpdate(901L, TENANT)).thenReturn(List.of(
|
||||
memberRow(901L, INBOUND, "100", "0.1"), memberRow(901L, 30002L, "80", "0.1")
|
||||
));
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(pricedInbound);
|
||||
when(repository.findInbound(TENANT, 30002L)).thenReturn(pendingInbound);
|
||||
when(repository.findLatestFeeForUpdate(TENANT, INBOUND)).thenReturn(priced);
|
||||
when(repository.findLatestFeeForUpdate(TENANT, 30002L)).thenReturn(null);
|
||||
|
||||
var result = service.recalculateUnlockedContainerCustomerFees(901L, TENANT, "pending-member");
|
||||
|
||||
assertThat(result.status()).isEqualTo(FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY);
|
||||
assertThat(result.recalculatedCount()).isZero();
|
||||
verify(repository, never()).insertFee(any());
|
||||
verify(priceBookEstimator, never()).estimate(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("相同柜次重算触发标识重放不重复创建费用版本")
|
||||
void replaysCompletedContainerRecalculationTrigger() {
|
||||
InboundPricingIdempotencyEntity completed = completedContainerRecalculation(901L, "same-trigger");
|
||||
when(repository.findIdempotency(TENANT, 0L, "CONTAINER_FEE_RECALCULATE", "CONTAINER:901:same-trigger"))
|
||||
.thenReturn(completed);
|
||||
|
||||
service.recalculateUnlockedContainerCustomerFees(901L, TENANT, "same-trigger");
|
||||
|
||||
verify(repository, never()).containerTierMembersForUpdate(anyLong(), anyLong());
|
||||
verify(repository, never()).insertFee(any());
|
||||
}
|
||||
|
||||
private void readyRepository() {
|
||||
@@ -834,6 +951,20 @@ class FbaPricingServiceTest {
|
||||
return value;
|
||||
}
|
||||
|
||||
private InboundPricingIdempotencyEntity completedContainerRecalculation(long containerBookingId, String trigger) {
|
||||
InboundPricingIdempotencyEntity value = new InboundPricingIdempotencyEntity();
|
||||
value.setId(900L); value.setOperation("CONTAINER_FEE_RECALCULATE"); value.setStatus("COMPLETED");
|
||||
value.setResultResourceId(containerBookingId);
|
||||
try {
|
||||
value.setRequestHash(java.util.HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256")
|
||||
.digest(("CONTAINER_FEE_RECALCULATE:" + containerBookingId + ':' + trigger)
|
||||
.getBytes(java.nio.charset.StandardCharsets.UTF_8))));
|
||||
} catch (java.security.NoSuchAlgorithmException exception) {
|
||||
throw new AssertionError(exception);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private InboundFeeEntity savedFee(String status) {
|
||||
InboundFeeEntity value = new InboundFeeEntity();
|
||||
value.setId(501L); value.setTenantId(TENANT); value.setInboundId(INBOUND); value.setFeeNo("FBAF-30001-1");
|
||||
|
||||
Reference in New Issue
Block a user