This commit is contained in:
2026-08-12 18:23:57 +08:00
parent bac46a0f54
commit ae40d3f173
14 changed files with 339 additions and 9 deletions
@@ -126,9 +126,11 @@ class FinanceServiceValidationTest {
service.createReceipt(9L, 2L, request, "receipt-key");
verify(jdbc).update(startsWith("UPDATE fin_settlement SET paid_amount="),
eq(new BigDecimal("40.00")), eq(new BigDecimal("40.00")), eq(new BigDecimal("40.00")),
eq(new BigDecimal("40.00")), eq(2L), eq(9L), eq(50L), eq(new BigDecimal("40.00")));
verify(jdbc).update(contains("payment_status=CASE WHEN outstanding_amount=0"),
eq(new BigDecimal("40.00")), eq(new BigDecimal("40.00")),
eq(2L), eq(9L), eq(50L), eq(new BigDecimal("40.00")));
verify(jdbc).update(contains("status=CASE WHEN outstanding_amount=0"),
eq(new BigDecimal("40.00")), eq(new BigDecimal("40.00")), eq(2L), eq(50L), eq(9L));
}
@Test
@@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.RETURNS_DEFAULTS;
@@ -276,6 +277,47 @@ class FinanceSettlementBehaviorTest {
assertThat(result.summary().outstandingAmount()).isZero();
}
@Test
void finalPaymentReplayReturnsFirstResultWithoutDuplicatePayment() {
JdbcTemplate jdbc = org.mockito.Mockito.mock(JdbcTemplate.class, invocation ->
"update".equals(invocation.getMethod().getName()) ? 1 : RETURNS_DEFAULTS.answer(invocation));
FinanceFileClient fileClient = org.mockito.Mockito.mock(FinanceFileClient.class);
Map<String, Object> before = settlementRow("PAYABLE", "PARTIALLY_PAID", new BigDecimal("40.00"),
new BigDecimal("60.00"), 1);
Map<String, Object> after = settlementRow("PAYABLE", "PAID", new BigDecimal("100.00"), BigDecimal.ZERO, 2);
when(jdbc.queryForList(startsWith("SELECT id,tenant_id,direction"), eq(88L)))
.thenReturn(List.of(before), List.of(after));
when(jdbc.queryForList(startsWith("SELECT request_hash"), eq(10L), eq(7L),
eq("SETTLEMENT_PAYMENT_CREATE"), eq("final-replay-key"))).thenReturn(List.of());
stubSettlementDetail(jdbc, after);
when(fileClient.metadata("internal-token", 10L, 10L, 91L)).thenReturn(ApiResponse.success(
new FinanceFileClient.Metadata(91L, 10L, "FINANCE_VOUCHER", "10", "pay.png", "abc", "image/png", 10L)));
FinanceSettlementService service = service(jdbc, fileClient);
SettlementDetail first = service.createSettlementPayment(88L, 7L, "final-replay-key",
payment(new BigDecimal("60.00"), 1));
ArgumentCaptor<String> requestHash = ArgumentCaptor.forClass(String.class);
verify(jdbc).update(startsWith("INSERT INTO fin_idempotency_record"), eq(10L), eq(7L),
eq("SETTLEMENT_PAYMENT_CREATE"), eq("final-replay-key"), requestHash.capture());
when(jdbc.queryForList(startsWith("SELECT request_hash"), eq(10L), eq(7L),
eq("SETTLEMENT_PAYMENT_CREATE"), eq("final-replay-key"))).thenReturn(List.of(Map.of(
"request_hash", requestHash.getValue(), "status", "SUCCEEDED", "result_resource_id", 88L,
"updated_at", LocalDateTime.now())));
SettlementDetail replay = service.createSettlementPayment(88L, 7L, "final-replay-key",
payment(new BigDecimal("60.00"), 1));
assertThat(first.summary().status()).isEqualTo("PAID");
assertThat(replay.summary().status()).isEqualTo("PAID");
assertThat(replay.summary().outstandingAmount()).isZero();
long paymentInserts = org.mockito.Mockito.mockingDetails(jdbc).getInvocations().stream()
.filter(invocation -> invocation.getMethod().getName().equals("update")
&& invocation.getArguments().length > 0
&& String.valueOf((Object) invocation.getArgument(0)).startsWith("INSERT INTO fin_settlement_payment"))
.count();
assertThat(paymentInserts).isEqualTo(1);
}
@Test
void receivableSettlementDetailIncludesAppliedReceiptHistory() {
JdbcTemplate jdbc = org.mockito.Mockito.mock(JdbcTemplate.class);