fix(firstleg): allow fee revision until final fulfillment lock

This commit is contained in:
2026-09-10 11:02:45 +08:00
parent a926a23b87
commit 8731efcbea
16 changed files with 294 additions and 108 deletions
@@ -77,48 +77,6 @@ class FbaPricingServiceTest {
verify(repository, never()).findLatestFee(TENANT, INBOUND);
}
@Test
@DisplayName("装柜完成后系统自动确认客户未确认的已发送费用")
void automaticallyConfirmsSentFeeAfterLoading() {
InboundFeeEntity sent = savedFee("SENT");
when(repository.findLatestFeeForUpdate(TENANT, INBOUND)).thenReturn(sent);
when(repository.confirmFeeByCustomer(TENANT, 501L, 1, 0L,
"装柜完成自动确认", NOW)).thenReturn(1);
service.autoConfirmAfterLoading(TENANT, INBOUND);
verify(repository).confirmFeeByCustomer(TENANT, 501L, 1, 0L,
"装柜完成自动确认", NOW);
}
@Test
@DisplayName("客户已经确认时装柜完成不覆盖原确认信息")
void keepsExistingCustomerConfirmationAfterLoading() {
InboundFeeEntity sent = savedFee("SENT");
sent.setCustomerConfirmedBy(88L);
sent.setCustomerConfirmedByName("客户成员");
sent.setCustomerConfirmedAt(NOW.minusHours(1));
when(repository.findLatestFeeForUpdate(TENANT, INBOUND)).thenReturn(sent);
service.autoConfirmAfterLoading(TENANT, INBOUND);
verify(repository, never()).confirmFeeByCustomer(anyLong(), anyLong(), anyInt(),
anyLong(), anyString(), any());
}
@Test
@DisplayName("没有已发送费用时装柜完成不受影响")
void ignoresMissingOrUnsentFeeAfterLoading() {
when(repository.findLatestFeeForUpdate(TENANT, INBOUND))
.thenReturn(null, savedFee("CONFIRMED"));
service.autoConfirmAfterLoading(TENANT, INBOUND);
service.autoConfirmAfterLoading(TENANT, INBOUND);
verify(repository, never()).confirmFeeByCustomer(anyLong(), anyLong(), anyInt(),
anyLong(), anyString(), any());
}
@Test
@DisplayName("包税KG按生效价格表计费且不依赖客户规则或承运商")
void calculatesTaxIncludedKgWithoutCustomerRuleOrCarrier() {
@@ -289,17 +247,27 @@ class FbaPricingServiceTest {
}
@Test
@DisplayName("装柜完成后禁止确认费用")
void rejectsConfirmAfterLoadingCompleted() {
@DisplayName("装柜完成但客户未确认时仍允许确认新费用版本")
void confirmsNewFeeVersionAfterLoadingCompleted() {
InboundPricingEntity inbound = inbound("10.000", "1.0000");
inbound.setProcessStatus("LOADED");
inbound.setFeeStatus("CALCULATED");
InboundFeeEntity fee = savedFee("CALCULATED");
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(fee);
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_CONFIRM", "confirm-loaded"))
.thenReturn(null);
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(899L); return null; })
.when(repository).insertIdempotency(any());
when(repository.confirmFee(TENANT, 501L, OPERATOR, NOW)).thenReturn(1);
when(repository.markInboundConfirmed(TENANT, INBOUND)).thenReturn(1);
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
when(repository.findFee(TENANT, 501L)).thenReturn(fee);
assertThatThrownBy(() -> service.confirm(TENANT, OPERATOR, INBOUND, "confirm-loaded"))
.isInstanceOf(BizException.class)
.hasMessageContaining("装柜流程");
verify(repository, never()).markInboundConfirmed(TENANT, INBOUND);
FeeSnapshotResponse response = service.confirm(TENANT, OPERATOR, INBOUND, "confirm-loaded");
assertThat(response.nextProcessStatus()).isEqualTo("LOADED");
verify(repository).markInboundConfirmed(TENANT, INBOUND);
}
@Test
@@ -335,11 +303,11 @@ class FbaPricingServiceTest {
}
@Test
@DisplayName("客户未确认时待装柜订单可重新计算并替换已发送费用")
@DisplayName("客户未确认时装柜完成订单仍可重新计算并替换已发送费用")
void recalculatesSentFeeBeforeCustomerConfirmation() {
readyRepository();
InboundPricingEntity inbound = inbound("10", "1");
inbound.setProcessStatus("WAIT_PALLET");
inbound.setProcessStatus("LOADED");
inbound.setFeeStatus("SENT");
InboundFeeEntity sent = savedFee("SENT");
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
@@ -354,18 +322,18 @@ class FbaPricingServiceTest {
assertThat(response.feeVersion()).isEqualTo(2);
assertThat(response.feeStatus()).isEqualTo("CALCULATED");
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_PALLET");
assertThat(response.nextProcessStatus()).isEqualTo("LOADED");
verify(repository).supersedeCurrent(TENANT, 501L, "SENT");
verify(repository).markInboundCalculated(TENANT, INBOUND, "WAIT_PALLET", "CBM",
verify(repository).markInboundCalculated(TENANT, INBOUND, "LOADED", "CBM",
new BigDecimal("1.0000"));
}
@Test
@DisplayName("客户已确认时拒绝重新计算已发送费用")
@DisplayName("客户已确认且装柜完成时拒绝重新计算已发送费用")
void rejectsRecalculationAfterCustomerConfirmation() {
readyRepository();
InboundPricingEntity inbound = inbound("10", "1");
inbound.setProcessStatus("WAIT_PALLET");
inbound.setProcessStatus("LOADED");
inbound.setFeeStatus("SENT");
InboundFeeEntity sent = savedFee("SENT");
sent.setCustomerConfirmedAt(NOW);
@@ -493,6 +461,7 @@ class FbaPricingServiceTest {
inbound.setProcessStatus("WAIT_SPLIT");
inbound.setFeeStatus("SENT");
InboundFeeEntity current = savedFee("SENT");
current.setCustomerConfirmedAt(NOW.minusHours(1));
AtomicReference<InboundFeeEntity> inserted = new AtomicReference<>();
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(current);
@@ -501,7 +470,7 @@ 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("SENT"), eq("KG"), any()))
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("SENT"), eq("KG"), any()))
.thenReturn(1);
doAnswer(invocation -> {
InboundFeeEntity fee = invocation.getArgument(0);
@@ -561,10 +530,10 @@ class FbaPricingServiceTest {
}
@Test
@DisplayName("客户已确认费用时拒绝平台再次修改")
@DisplayName("客户已确认且装柜完成时拒绝平台再次修改")
void rejectsAdjustmentAfterCustomerConfirmation() {
InboundPricingEntity inbound = inbound("10", "1");
inbound.setProcessStatus("WAIT_SPLIT");
inbound.setProcessStatus("LOADED");
InboundFeeEntity current = savedFee("SENT");
current.setCustomerConfirmedAt(NOW);
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
@@ -596,7 +565,7 @@ 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("CALCULATED"), eq("KG"), any()))
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("CALCULATED"), eq("KG"), any()))
.thenReturn(1);
doAnswer(invocation -> {
InboundFeeEntity fee = invocation.getArgument(0);
@@ -655,7 +624,7 @@ 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("CALCULATED"), eq("KG"), any()))
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("WAIT_SPLIT"), eq("CALCULATED"), eq("KG"), any()))
.thenReturn(1);
doAnswer(invocation -> { ((InboundFeeEntity) invocation.getArgument(0)).setId(504L); return null; })
.when(repository).insertFee(any());