客户运费计费

This commit is contained in:
2026-08-05 20:32:43 +08:00
parent a11e47fe3b
commit 4df8b8c355
15 changed files with 608 additions and 4 deletions
@@ -47,6 +47,7 @@ class FbaPricingServiceTest {
private static final long INBOUND = 30001L;
private static final LocalDateTime NOW = LocalDateTime.of(2026, 8, 3, 10, 0);
@MockBean private FbaPricingRepository repository;
@MockBean private InboundFeeDetailService feeDetails;
@Resource private FbaPricingService service;
@Test
@@ -172,6 +173,7 @@ class FbaPricingServiceTest {
verify(repository, times(1)).insertOutbox(any());
verify(repository, times(1)).sendFee(TENANT, 501L, OPERATOR, NOW);
verify(feeDetails, times(2)).publishFreight(TENANT, INBOUND, 501L, OPERATOR, null, NOW);
}
@Test
@@ -6,22 +6,29 @@ import com.baiyue.firstleg.dto.FeeItemDtos;
import com.baiyue.firstleg.dto.InboundFeeDetailDtos;
import com.baiyue.firstleg.entity.FeeItemEntity;
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
import com.baiyue.firstleg.entity.InboundFeeEntity;
import com.baiyue.firstleg.entity.InboundFeeItemEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.repository.FeeItemRepository;
import com.baiyue.firstleg.repository.FbaPricingRepository;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,6 +38,8 @@ import static org.mockito.Mockito.when;
class FeeManagementServiceTest {
@MockBean
private FeeItemRepository repository;
@MockBean
private FbaPricingRepository pricingRepository;
@Resource
private FeeItemService feeItems;
@Resource
@@ -96,6 +105,92 @@ class FeeManagementServiceTest {
assertThat(created.getReviewStatus()).isEqualTo("PENDING");
}
@Test
@DisplayName("发布计费快照生成客户与后台共用的入库运费明细")
void publishesSharedFreightDetail() {
InboundOrderEntity inbound = inbound();
inbound.setCustomerId(101L);
inbound.setCustomerName("客户A");
inbound.setCompanyName("客户A有限公司");
when(repository.findInbound(10001L, 21L)).thenReturn(inbound);
when(repository.findBySourceFee(10001L, "FBA_PRICING", 501L)).thenReturn(null);
when(repository.findItemByCode(10001L, "FBA_HEAD_FREIGHT")).thenReturn(null);
doAnswer(invocation -> {
FeeItemEntity value = invocation.getArgument(0);
value.setId(11L);
return null;
}).when(repository).insertItem(any());
InboundFeeEntity fee = new InboundFeeEntity();
fee.setId(501L);
fee.setInboundId(21L);
fee.setFeeNo("FBAF-21-1");
fee.setFeeVersion(1);
fee.setFeeStatus("SENT");
fee.setActualWeightKg(new BigDecimal("100.0000"));
fee.setActualVolumeCbm(new BigDecimal("1.000000"));
fee.setChargeableQuantity(new BigDecimal("167.0000"));
fee.setChargeUnit("KG");
fee.setCurrency("USD");
fee.setTotalAmount(new BigDecimal("354.00"));
when(pricingRepository.findFee(10001L, 501L)).thenReturn(fee);
InboundFeeItemEntity base = new InboundFeeItemEntity();
base.setUnitPrice(new BigDecimal("2.0000"));
when(pricingRepository.findBaseItem(10001L, 501L)).thenReturn(base);
LocalDateTime publishedAt = LocalDateTime.of(2026, 8, 5, 12, 0);
InboundFeeDetailEntity result = details.publishFreight(
10001L, 21L, 501L, 9L, "admin", publishedAt);
ArgumentCaptor<InboundFeeDetailEntity> captor = ArgumentCaptor.forClass(InboundFeeDetailEntity.class);
verify(repository).insertDetail(captor.capture());
InboundFeeDetailEntity saved = captor.getValue();
assertThat(result).isSameAs(saved);
assertThat(saved.getFeeItemId()).isEqualTo(11L);
assertThat(saved.getSourceFeeId()).isEqualTo(501L);
assertThat(saved.getDirection()).isEqualTo("RECEIVABLE");
assertThat(saved.getAmount()).isEqualByComparingTo("354.00");
assertThat(saved.getUnitPrice()).isEqualByComparingTo("2.1198");
assertThat(saved.getReviewStatus()).isEqualTo("APPROVED");
assertThat(saved.getPublicationStatus()).isEqualTo("PUBLISHED");
assertThat(saved.getSettlementIncluded()).isTrue();
}
@Test
@DisplayName("并发发布同一计费快照时返回已生成的运费明细")
void reusesConcurrentFreightDetail() {
InboundFeeDetailEntity concurrent = new InboundFeeDetailEntity();
concurrent.setId(99L);
when(repository.findBySourceFee(10001L, "FBA_PRICING", 501L))
.thenReturn(null, concurrent);
when(repository.findInbound(10001L, 21L)).thenReturn(inbound());
FeeItemEntity freightItem = item(true, false);
freightItem.setFeeCode("FBA_HEAD_FREIGHT");
when(repository.findItemByCode(10001L, "FBA_HEAD_FREIGHT")).thenReturn(freightItem);
InboundFeeEntity fee = new InboundFeeEntity();
fee.setId(501L);
fee.setInboundId(21L);
fee.setFeeNo("FBAF-21-1");
fee.setFeeVersion(1);
fee.setFeeStatus("SENT");
fee.setActualWeightKg(new BigDecimal("100.0000"));
fee.setActualVolumeCbm(new BigDecimal("1.000000"));
fee.setChargeableQuantity(new BigDecimal("1.0000"));
fee.setChargeUnit("CBM");
fee.setCurrency("USD");
fee.setTotalAmount(new BigDecimal("200.00"));
when(pricingRepository.findFee(10001L, 501L)).thenReturn(fee);
doThrow(new DuplicateKeyException("duplicate source fee"))
.when(repository).insertDetail(any());
InboundFeeDetailEntity result = details.publishFreight(
10001L, 21L, 501L, 9L, "admin", LocalDateTime.of(2026, 8, 5, 12, 0));
assertThat(result).isSameAs(concurrent);
}
@Test
@DisplayName("拒绝绑定未启用仓储应收的项目")
void rejectsInapplicableItem() {
@@ -16,6 +16,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -28,6 +29,8 @@ class InboundCompletionOrchestratorTest {
private InboundWorkflowRepository repository;
@MockBean
private InboundIdempotencyService idempotency;
@MockBean
private InboundFeeDetailService feeDetails;
@Resource
private InboundCompletionOrchestrator service;
@Test
@@ -81,6 +84,8 @@ class InboundCompletionOrchestratorTest {
verify(fixture.repository).completeInbound(10001L, 10L, 9L, 5);
verify(fixture.repository).markFeeSent(10001L, 10L, 30L, 9L);
verify(feeDetails).publishFreight(eq(10001L), eq(10L), eq(30L), eq(9L), eq("操作员"),
org.mockito.ArgumentMatchers.any(java.time.LocalDateTime.class));
verify(fixture.repository).insertOutbox(any());
}
@@ -6,6 +6,7 @@ import com.baiyue.firstleg.dto.InboundOrderDetail;
import com.baiyue.firstleg.dto.InboundMeasurementResponses;
import com.baiyue.firstleg.client.MeasurementImageClient;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
import com.baiyue.firstleg.repository.InboundOrderRepository;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
@@ -30,6 +31,7 @@ class MerchantInboundBillingServiceTest {
@MockBean private FbaPricingService pricing;
@MockBean private InboundMeasurementService measurements;
@MockBean private MeasurementImageClient imageClient;
@MockBean private InboundFeeDetailService feeDetails;
@Resource private MerchantInboundBillingService service;
@Test
@@ -50,6 +52,15 @@ class MerchantInboundBillingServiceTest {
when(measurement.items()).thenReturn(List.of());
when(measurement.weightResults()).thenReturn(List.of());
when(pricing.latest(10001L, 88L)).thenReturn(fee);
InboundFeeDetailEntity published = new InboundFeeDetailEntity();
published.setId(601L);
published.setInboundId(88L);
published.setSourceFeeId(501L);
published.setSourceFeeVersion(1);
published.setFeeTypeNameSnapshot("FBA头程运费");
published.setAmount(new BigDecimal("334.00"));
published.setPublicationStatus("PUBLISHED");
when(feeDetails.publishedFreight(10001L, 88L)).thenReturn(published);
when(fee.chargeMode()).thenReturn("KG");
when(fee.chargeUnit()).thenReturn("KG");
when(fee.actualWeightKg()).thenReturn(new BigDecimal("20"));
@@ -65,6 +76,8 @@ class MerchantInboundBillingServiceTest {
assertThat(result.inbound().statusHistory()).isEmpty();
assertThat(result.billingBasis().chargeableBasis()).isEqualTo("VOLUME_WEIGHT");
assertThat(result.latestFee()).isSameAs(fee);
assertThat(result.publishedFeeDetail().id()).isEqualTo(601L);
assertThat(result.publishedFeeDetail().amount()).isEqualByComparingTo("334.00");
verify(orders).findByOrderId(10001L, 77L);
}