From 8b56af2cae58db68284421897c8f8e0fabdc895b Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 4 Nov 2026 12:28:47 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=BA=BA=E5=B7=A5=E6=8A=A5?= =?UTF-8?q?=E4=BB=B7=E6=97=B6=E9=97=B4=E7=B2=BE=E5=BA=A6=E4=BB=A5=E4=BF=9D?= =?UTF-8?q?=E8=AF=81=E5=8E=9F=E9=94=AE=E7=B2=BE=E7=A1=AE=E9=87=8D=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../firstleg/service/FbaPricingService.java | 5 ++-- .../service/FbaPricingServiceTest.java | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java index 7bef3057f..55a675e00 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java @@ -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( diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java index e37d60daa..0c160535b 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java @@ -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 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() {