This commit is contained in:
2026-08-20 09:43:31 +08:00
parent f25e270f22
commit 7cf4a8d562
25 changed files with 804 additions and 41 deletions
@@ -1,6 +1,8 @@
package com.baiyue.finance;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.finance.client.FirstlegFeeSourceClient;
import com.baiyue.finance.controller.FinancePhase1OperationController;
import com.baiyue.finance.dto.FinancePhase1Dtos;
import com.baiyue.finance.dto.FinancePhase1Dtos.FeeSourceDetail;
@@ -35,6 +37,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,6 +60,7 @@ class FinancePhase1OperationBehaviorTest {
controller.getFinanceFeeSource(88L);
controller.reviewFinanceFeeSource(user, 88L, "review-key", review);
controller.submitFinanceFeeSource(user, 88L, "submit-key", action);
controller.voidFinanceFeeSource(user, 88L, "void-key", action);
verify(customerAccountService).list(1, 20, "柏悦", "ACTIVE", 9L);
verify(operationService).listDerivedCustomerAccountLedger(eq(9L), eq(2), eq(50), eq("RECEIPT"), any(), any());
@@ -65,6 +69,7 @@ class FinancePhase1OperationBehaviorTest {
verify(operationService).getFinanceFeeSource(88L);
verify(operationService).reviewFinanceFeeSource(88L, 71L, "review-key", review);
verify(operationService).submitFinanceFeeSource(88L, 71L, "submit-key", action);
verify(operationService).voidFinanceFeeSource(88L, 71L, "finance", "void-key", action);
}
@Test
@@ -220,29 +225,49 @@ class FinancePhase1OperationBehaviorTest {
}
@Test
void rejectedFeeCanBeEditedBackToDraftWithExpectedVersion() throws Exception {
JdbcTemplate jdbc = mock(JdbcTemplate.class, invocation ->
"update".equals(invocation.getMethod().getName())
? 1 : org.mockito.Mockito.RETURNS_DEFAULTS.answer(invocation));
AtomicInteger reads = new AtomicInteger();
when(jdbc.query(startsWith("SELECT * FROM fin_fee_source"), any(RowMapper.class), (Object[]) any())).thenAnswer(invocation -> {
@SuppressWarnings("unchecked") RowMapper<FeeSourceDetail> mapper = invocation.getArgument(1);
boolean changed = reads.getAndIncrement() > 0;
return List.of(mapper.mapRow(resultSet(changed ? "DRAFT" : "REJECTED", false, changed ? 2 : 1), 0));
});
when(jdbc.queryForObject(startsWith("SELECT COUNT(*)>0"), eq(Boolean.class), eq(88L), eq(3))).thenReturn(false);
when(jdbc.query(startsWith("SELECT created_at,action"), any(RowMapper.class), eq(9L), eq(88L))).thenReturn(List.of());
when(jdbc.queryForList(startsWith("SELECT request_hash"), eq(9L), eq(71L),
eq("FEE_SOURCE_EDIT"), eq("edit-key"))).thenReturn(List.of());
void firstlegFeeEditAndAdjustAreBlockedInsteadOfMutatingFinanceCopy() throws Exception {
JdbcTemplate jdbc = feeSourceJdbc("REJECTED", false, 1);
FinancePhase1OperationService service = new FinancePhase1OperationService(jdbc, objectMapper());
FeeSourceDetail result = service.editFinanceFeeSource(88L, 71L, "edit-key",
new FinancePhase1Dtos.FeeSourceEditRequest(new BigDecimal("120.00"), "更正金额", 1), false);
var request = new FinancePhase1Dtos.FeeSourceEditRequest(
new BigDecimal("120.00"), "更正金额", 1);
assertThatThrownBy(() -> service.editFinanceFeeSource(88L, 71L, "edit-key", request, false))
.isInstanceOf(FinanceEventException.class).hasMessageContaining("Firstleg费用必须");
assertThatThrownBy(() -> service.editFinanceFeeSource(88L, 71L, "adjust-key", request, true))
.isInstanceOf(FinanceEventException.class).hasMessageContaining("Firstleg费用必须");
verify(jdbc, never()).update(startsWith("UPDATE fin_fee_source SET amount="),
(Object[]) any());
}
assertThat(result.reviewStatus()).isEqualTo("DRAFT");
assertThat(result.operationVersion()).isEqualTo(2);
verify(jdbc).update(startsWith("UPDATE fin_fee_source SET amount="),
eq(new BigDecimal("120.00")), eq(88L), eq(1), eq("REJECTED"));
@Test
void voidingNyexx2608180003DelegatesToFirstlegAndWaitsForEvent() throws Exception {
JdbcTemplate jdbc = feeSourceJdbc("APPROVED", false, 2);
when(jdbc.queryForList(startsWith("SELECT request_hash"), eq(9L), eq(71L),
eq("FEE_SOURCE_VOID"), eq("void-key"))).thenReturn(List.of());
FirstlegFeeSourceClient firstleg = mock(FirstlegFeeSourceClient.class);
when(firstleg.voidSource(eq("internal-token"), eq("void-key"), anyString(), any()))
.thenReturn(ApiResponse.success(new FirstlegFeeSourceClient.CommandResult(
"INBOUND_FEE", 700L, 3, "VOIDED", false)));
FinancePhase1OperationService service = new FinancePhase1OperationService(
jdbc, objectMapper(), firstleg, "internal-token");
FeeSourceDetail result = service.voidFinanceFeeSource(88L, 71L, "finance",
"void-key", new FinancePhase1Dtos.FeeSourceActionRequest("测试作废", 2));
assertThat(result.sourceStatus()).isEqualTo("AVAILABLE");
var command = org.mockito.ArgumentCaptor.forClass(FirstlegFeeSourceClient.VoidCommand.class);
verify(firstleg).voidSource(eq("internal-token"), eq("void-key"), anyString(), command.capture());
assertThat(command.getValue()).satisfies(value -> {
assertThat(value.sourceType()).isEqualTo("INBOUND_FEE");
assertThat(value.sourceFeeId()).isEqualTo(700L);
assertThat(value.sourceVersion()).isEqualTo(3);
assertThat(value.customerId()).isEqualTo(9L);
assertThat(value.inboundId()).isEqualTo(100L);
assertThat(value.operatorName()).isEqualTo("finance");
assertThat(value.reason()).isEqualTo("测试作废");
});
verify(jdbc, never()).update(startsWith("UPDATE fin_fee_source SET status='VOIDED'"),
(Object[]) any());
}
@Test
@@ -299,6 +324,7 @@ class FinancePhase1OperationBehaviorTest {
when(rs.getLong("order_id")).thenReturn(90L);
when(rs.getLong("inbound_id")).thenReturn(100L);
when(rs.getLong("container_id")).thenReturn(200L);
when(rs.wasNull()).thenReturn(false, false, true, false, false, true);
when(rs.getInt("source_fee_version")).thenReturn(3);
when(rs.getString("source_system")).thenReturn("FIRSTLEG");
when(rs.getString("source_no")).thenReturn("IN-100");
@@ -312,7 +338,7 @@ class FinancePhase1OperationBehaviorTest {
when(rs.getString("review_reason")).thenReturn(null);
when(rs.getInt("operation_version")).thenReturn(version);
when(rs.getBigDecimal("amount")).thenReturn(new BigDecimal("100.00"));
when(rs.getString("source_snapshot")).thenReturn("{\"customerName\":\"客户9\",\"businessDate\":\"2026-08-11\",\"chargeableQuantity\":\"10\",\"reviewStatus\":\"" + reviewStatus + "\",\"publicationStatus\":\"PUBLISHED\",\"operationVersion\":" + version + "}");
when(rs.getString("source_snapshot")).thenReturn("{\"customerName\":\"客户9\",\"inboundCode\":\"NYEXX2608180003\",\"businessDate\":\"2026-08-11\",\"chargeableQuantity\":\"10\",\"reviewStatus\":\"" + reviewStatus + "\",\"publicationStatus\":\"PUBLISHED\",\"operationVersion\":" + version + "}");
return rs;
}