This commit is contained in:
2026-08-13 11:57:33 +08:00
parent 5a48992d78
commit 16dc03a2e2
102 changed files with 3656 additions and 120 deletions
@@ -15,18 +15,20 @@ class FinancePhase1PermissionContractTest {
@Test
void freezesG1AndG2ControllerPermissionsAndHttpActions() throws Exception {
assertEndpoint("listDerivedCustomerAccounts", GetMapping.class,
"hasAuthority('platform:finance:account:view')", int.class, int.class, String.class, String.class);
"platform:finance:customer-account:list", "platform:finance:account:view",
int.class, int.class, String.class, String.class);
assertEndpoint("listDerivedCustomerAccountLedger", GetMapping.class,
"hasAuthority('platform:finance:account:view')", long.class, int.class, int.class,
"platform:finance:customer-account:list", "platform:finance:account:view",
long.class, int.class, int.class,
String.class, java.time.LocalDate.class, java.time.LocalDate.class);
assertEndpoint("getFinanceFeeSource", GetMapping.class,
"hasAuthority('platform:finance:fee-source:view')", long.class);
"platform:finance:customer-arap:list", "platform:finance:fee-source:view", long.class);
assertEndpoint("reviewFinanceFeeSource", PostMapping.class,
"hasAuthority('platform:finance:fee-source:review')",
"platform:finance:fee-source:review", null,
com.baiyue.common.security.AuthenticatedUser.class, long.class, String.class,
com.baiyue.finance.dto.FinancePhase1Dtos.ReviewRequest.class);
assertEndpoint("updateFeeSourceSettlementInclusion", PutMapping.class,
"hasAuthority('platform:finance:fee-source:settlement')",
"platform:finance:fee-source:settlement", null,
com.baiyue.common.security.AuthenticatedUser.class, long.class, String.class,
com.baiyue.finance.dto.FinancePhase1Dtos.SettlementInclusionRequest.class);
}
@@ -34,9 +36,13 @@ class FinancePhase1PermissionContractTest {
private static void assertEndpoint(String methodName,
Class<? extends java.lang.annotation.Annotation> mapping,
String authority,
String compatibleAuthority,
Class<?>... parameterTypes) throws Exception {
Method method = FinancePhase1OperationController.class.getMethod(methodName, parameterTypes);
assertThat(method.isAnnotationPresent(mapping)).isTrue();
assertThat(method.getAnnotation(PreAuthorize.class).value()).isEqualTo(authority);
assertThat(method.getAnnotation(PreAuthorize.class).value()).contains(authority);
if (compatibleAuthority != null) {
assertThat(method.getAnnotation(PreAuthorize.class).value()).contains(compatibleAuthority);
}
}
}
@@ -28,12 +28,12 @@ class FinancePlatformListContractTest {
@Test
void platformListPermissionsRemainSeparatedByReceivableAndPayableData() throws Exception {
Map<String, String> expected = Map.of(
"feePool", "hasAuthority('platform:finance:bill:view')",
"statements", "hasAuthority('platform:finance:bill:view')",
"receivableSettlements", "hasAuthority('platform:finance:settlement-ar:view')",
"payableSettlements", "hasAuthority('platform:finance:settlement-ap:view')");
"feePool", "platform:finance:customer-arap:list",
"statements", "platform:finance:customer-statement:list",
"receivableSettlements", "platform:finance:settlement-ar:view",
"payableSettlements", "platform:finance:settlement-ap:view");
for (var entry : expected.entrySet()) {
assertThat(find(entry.getKey()).getAnnotation(PreAuthorize.class).value()).isEqualTo(entry.getValue());
assertThat(find(entry.getKey()).getAnnotation(PreAuthorize.class).value()).contains(entry.getValue());
}
}
@@ -9,6 +9,7 @@ import com.baiyue.finance.dto.FinanceSettlementDtos.CreateReceivableSettlementRe
import com.baiyue.finance.dto.FinanceSettlementDtos.CreateSettlementPaymentRequest;
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementDetail;
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementSummary;
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementActionRequest;
import com.baiyue.finance.service.FinanceEventException;
import com.baiyue.finance.service.FinanceSettlementService;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -161,6 +162,55 @@ class FinanceSettlementBehaviorTest {
verify(jdbc, never()).update(startsWith("INSERT INTO fin_settlement_payment"), (Object[]) any());
}
@Test
void unpaidSettlementCanBeVoidedAndReopenedWithAuditAndVersionCheck() {
JdbcTemplate jdbc = org.mockito.Mockito.mock(JdbcTemplate.class, invocation ->
"update".equals(invocation.getMethod().getName()) ? 1 : RETURNS_DEFAULTS.answer(invocation));
Map<String, Object> issued = settlementRow("PAYABLE", "ISSUED", BigDecimal.ZERO,
new BigDecimal("100.00"), 2);
Map<String, Object> voided = settlementRow("PAYABLE", "VOIDED", BigDecimal.ZERO,
new BigDecimal("100.00"), 3);
when(jdbc.queryForList(startsWith("SELECT id,tenant_id,total_amount"), eq(88L)))
.thenReturn(List.of(issued));
when(jdbc.queryForList(startsWith("SELECT request_hash"), eq(10L), eq(7L),
eq("SETTLEMENT_STATE_ACTION"), eq("void-key"))).thenReturn(List.of());
stubSettlementDetail(jdbc, voided);
FinanceSettlementService service = service(jdbc, org.mockito.Mockito.mock(FinanceFileClient.class));
SettlementDetail result = service.actionSettlement(88L, 7L, "void-key",
new SettlementActionRequest("VOID", "业务取消", 2));
assertThat(result.summary().status()).isEqualTo("VOIDED");
verify(jdbc).update(startsWith("UPDATE fin_settlement SET status="), eq("VOIDED"), eq(7L),
eq(88L), eq(10L), eq(2));
verify(jdbc).update(startsWith("INSERT INTO fin_audit_log"), eq(10L), eq("FIN_SETTLEMENT"),
eq(88L), eq("VOID"), eq("业务取消"), any(), any(), eq(7L), any(), any(), any());
}
@Test
void settlementActionRejectsPaidInvalidStateAndStaleVersion() {
JdbcTemplate paidJdbc = org.mockito.Mockito.mock(JdbcTemplate.class);
when(paidJdbc.queryForList(startsWith("SELECT id,tenant_id,total_amount"), eq(88L)))
.thenReturn(List.of(settlementRow("PAYABLE", "PARTIALLY_PAID", new BigDecimal("40.00"),
new BigDecimal("60.00"), 2)));
when(paidJdbc.queryForList(startsWith("SELECT request_hash"), eq(10L), eq(7L),
eq("SETTLEMENT_STATE_ACTION"), anyString())).thenReturn(List.of());
FinanceSettlementService paidService = service(paidJdbc, org.mockito.Mockito.mock(FinanceFileClient.class));
assertCode(() -> paidService.actionSettlement(88L, 7L, "paid-key",
new SettlementActionRequest("VOID", "不能作废", 2)), "FIN_409");
JdbcTemplate staleJdbc = org.mockito.Mockito.mock(JdbcTemplate.class);
when(staleJdbc.queryForList(startsWith("SELECT id,tenant_id,total_amount"), eq(88L)))
.thenReturn(List.of(settlementRow("PAYABLE", "VOIDED", BigDecimal.ZERO,
new BigDecimal("100.00"), 3)));
when(staleJdbc.queryForList(startsWith("SELECT request_hash"), eq(10L), eq(7L),
eq("SETTLEMENT_STATE_ACTION"), eq("stale-action"))).thenReturn(List.of());
FinanceSettlementService staleService = service(staleJdbc, org.mockito.Mockito.mock(FinanceFileClient.class));
assertCode(() -> staleService.actionSettlement(88L, 7L, "stale-action",
new SettlementActionRequest("REOPEN", "恢复结算", 2)), "FIN_409");
verify(staleJdbc, never()).update(startsWith("UPDATE fin_settlement SET status="), (Object[]) any());
}
@Test
void partialPaymentUpdatesOutstandingAndReturnsTypedDetail() {
JdbcTemplate jdbc = org.mockito.Mockito.mock(JdbcTemplate.class, invocation ->