统一人工报价时间精度以保证原键精确重放

This commit is contained in:
2026-11-04 12:28:47 +08:00
parent 12617e35a0
commit 8b56af2cae
2 changed files with 31 additions and 2 deletions
@@ -229,9 +229,10 @@ public class FbaPricingService {
if (current != null && !List.of("CALCULATED", "CONFIRMED", "SENT").contains(current.getFeeStatus())) {
throw conflict("当前费用状态不允许重新定价");
}
LocalDateTime now = LocalDateTime.now(clock);
// 与DATETIME(3)保存精度一致,首次响应和原键重放保留同一时间事实。
LocalDateTime now = LocalDateTime.now(clock).truncatedTo(java.time.temporal.ChronoUnit.MILLIS);
PricingPolicy policy = pricingPolicy(inbound, now);
// 私人地址使用人工报价;FBA仍须先证明价格表未命中,避免绕开有效报价。
// 私人地址使用人工报价。FBA仍须先证明价格表未命中,避免绕开有效报价。
if ("FBA".equals(inbound.getDestinationType())) {
try {
priceBookEstimator.estimate(tenantId, new EstimateRequest(
@@ -560,6 +560,34 @@ class FbaPricingServiceTest {
org.mockito.Mockito.verifyNoInteractions(priceBookEstimator);
}
@Test
void manualQuoteFreezesDatabaseMillisecondPrecisionForExactReplay() {
readyRepository();
InboundPricingEntity inbound = inbound("62.500", "0.3840");
inbound.setDestinationType("PRIVATE");
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
measurement.setId(301L);
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
when(measurementRepository.findLatestMeasurement(TENANT, INBOUND)).thenReturn(measurement);
when(measurementRepository.listItems(TENANT, 301L)).thenReturn(List.of(new InboundMeasurementItemEntity()));
when(repository.completeIdempotency(any(), eq(501L), any())).thenReturn(1);
Clock original = (Clock) org.springframework.test.util.ReflectionTestUtils.getField(service, "clock");
var preciseClock = Clock.fixed(Instant.parse("2026-08-03T02:00:00.123456789Z"), ZoneId.of("Asia/Shanghai"));
org.springframework.test.util.ReflectionTestUtils.setField(service, "clock", preciseClock);
try {
var result = service.manualQuote(TENANT, OPERATOR, INBOUND,
new ManualQuoteRequest(new BigDecimal("62.500"), new BigDecimal("0.3840"),
BigDecimal.ONE, "私人地址测试人工报价"), "private-precision");
assertThat(result.calculatedAt().getNano()).isEqualTo(123000000);
ArgumentCaptor<InboundFeeEntity> fee = ArgumentCaptor.forClass(InboundFeeEntity.class);
verify(repository).insertFee(fee.capture());
assertThat(fee.getValue().getCreatedAt()).isEqualTo(result.calculatedAt());
assertThat(fee.getValue().getUpdatedAt()).isEqualTo(result.calculatedAt());
} finally {
org.springframework.test.util.ReflectionTestUtils.setField(service, "clock", original);
}
}
@Test
@DisplayName("已有匹配价格时拒绝绕过价格表创建人工报价")
void rejectsManualQuoteWhenRateExists() {