优化
This commit is contained in:
@@ -18,6 +18,7 @@ import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
@@ -111,6 +112,37 @@ class FinancePhase1OperationBehaviorTest {
|
||||
assertThat(jdbc.ledgerCustomerIds).containsExactly(9L, 9L, 9L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ledgerReturnsStableBusinessTypeDocumentAndOperatorNameSnapshot() throws Exception {
|
||||
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||
when(jdbc.query(startsWith("SELECT id,bill_no"), any(RowMapper.class), eq(9L))).thenAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked") RowMapper<Object> mapper = invocation.getArgument(1);
|
||||
ResultSet rs = mock(ResultSet.class);
|
||||
when(rs.getLong("id")).thenReturn(81L);
|
||||
when(rs.getLong("created_by")).thenReturn(71L);
|
||||
when(rs.getString("bill_no")).thenReturn("REC-81");
|
||||
when(rs.getString("created_by_name")).thenReturn("platform_admin");
|
||||
when(rs.getString("remark")).thenReturn("八月账单");
|
||||
when(rs.getBigDecimal("total_amount")).thenReturn(new BigDecimal("5200.00"));
|
||||
when(rs.getTimestamp("created_at")).thenReturn(Timestamp.valueOf("2026-08-17 13:00:00"));
|
||||
return List.of(mapper.mapRow(rs, 0));
|
||||
});
|
||||
when(jdbc.query(startsWith("SELECT id,receipt_no"), any(RowMapper.class), eq(9L))).thenReturn(List.of());
|
||||
when(jdbc.query(startsWith("SELECT id,cost_no"), any(RowMapper.class), eq(9L))).thenReturn(List.of());
|
||||
FinancePhase1OperationService service = new FinancePhase1OperationService(jdbc, objectMapper());
|
||||
|
||||
PageEnvelope<FinancePhase1Dtos.LedgerEntry> result =
|
||||
service.listDerivedCustomerAccountLedger(9L, 1, 20, null, null, null);
|
||||
|
||||
assertThat(result.data()).singleElement().satisfies(entry -> {
|
||||
assertThat(entry.businessType()).isEqualTo("SETTLEMENT_ISSUED");
|
||||
assertThat(entry.documentType()).isEqualTo("BILL");
|
||||
assertThat(entry.documentNo()).isEqualTo("REC-81");
|
||||
assertThat(entry.occurredAt()).isEqualTo(LocalDateTime.of(2026, 8, 17, 13, 0));
|
||||
assertThat(entry.operatorName()).isEqualTo("platform_admin");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectedFeeCannotBeIncludedInSettlement() throws Exception {
|
||||
JdbcTemplate jdbc = feeSourceJdbc("REJECTED", false, 2);
|
||||
@@ -182,7 +214,7 @@ class FinancePhase1OperationBehaviorTest {
|
||||
eq("APPROVED"), eq("核对无误"), eq(88L), eq(0))).thenReturn(1);
|
||||
when(jdbc.update(startsWith("INSERT INTO fin_audit_log"), eq(9L), eq("FIN_FEE_SOURCE"),
|
||||
eq(88L), eq("FEE_SOURCE_REVIEW_APPROVE"), eq("核对无误"), anyString(), anyString(),
|
||||
eq(71L), eq("operator-71"), any(), any())).thenReturn(1);
|
||||
eq(71L), eq("操作人 71"), any(), any())).thenReturn(1);
|
||||
when(jdbc.update(startsWith("UPDATE fin_idempotency_record SET status='SUCCEEDED'"),
|
||||
eq(88L), eq(9L), eq(71L), eq("FEE_SOURCE_REVIEW"), eq("review-key"))).thenReturn(1);
|
||||
FinancePhase1OperationService service = new FinancePhase1OperationService(jdbc, objectMapper());
|
||||
|
||||
@@ -71,6 +71,29 @@ class FinancePlatformListContractTest {
|
||||
.contains("customerId");
|
||||
}
|
||||
|
||||
@Test
|
||||
void financeDetailsKeepOperatorIdsAndExposeNameSnapshots() {
|
||||
assertRecordFields(com.baiyue.finance.dto.FinanceReadModels.Statement.class,
|
||||
"createdBy", "createdByName", "updatedBy", "updatedByName");
|
||||
assertRecordFields(com.baiyue.finance.dto.FinanceReadModels.BillRevision.class,
|
||||
"createdBy", "createdByName");
|
||||
assertRecordFields(com.baiyue.finance.dto.ReconciliationStatementDtos.ReconciliationDetail.class,
|
||||
"createdBy", "createdByName", "updatedBy", "updatedByName");
|
||||
assertRecordFields(com.baiyue.finance.dto.ReconciliationStatementDtos.PaymentHistory.class,
|
||||
"createdBy", "createdByName");
|
||||
assertRecordFields(com.baiyue.finance.dto.ReconciliationStatementDtos.ReconciliationRevision.class,
|
||||
"operatorId", "operatorName");
|
||||
assertRecordFields(com.baiyue.finance.dto.FinanceSettlementDtos.SettlementTransaction.class,
|
||||
"createdBy", "createdByName");
|
||||
assertRecordFields(com.baiyue.finance.dto.FinanceSettlementDtos.SettlementRevision.class,
|
||||
"operatorId", "operatorName");
|
||||
}
|
||||
|
||||
private static void assertRecordFields(Class<?> type, String... names) {
|
||||
assertThat(Arrays.stream(type.getRecordComponents()).map(java.lang.reflect.RecordComponent::getName))
|
||||
.contains(names);
|
||||
}
|
||||
|
||||
private static Method find(String name) throws Exception {
|
||||
return switch (name) {
|
||||
case "feePool" -> PlatformFinanceController.class.getMethod(name, Long.class, String.class);
|
||||
|
||||
@@ -171,7 +171,7 @@ class FinanceServiceValidationTest {
|
||||
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));
|
||||
eq(new BigDecimal("40.00")), eq(new BigDecimal("40.00")), eq(2L), isNull(), eq(50L), eq(9L));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -234,11 +234,14 @@ class FinanceSettlementBehaviorTest {
|
||||
eq("SETTLEMENT_PAYMENT_CREATE"), eq("pay-key"))).thenReturn(List.of());
|
||||
when(jdbc.queryForList(startsWith("SELECT id,tenant_id,settlement_no"), eq(88L))).thenReturn(List.of(after));
|
||||
when(jdbc.queryForList(startsWith("SELECT f.id fee_source_id"), eq(10L), eq(88L))).thenReturn(List.of());
|
||||
when(jdbc.queryForList(startsWith("SELECT id,transaction_type"), eq(10L), eq(88L))).thenReturn(List.of(Map.of(
|
||||
"id", 501L, "transaction_type", "PAYMENT", "transaction_date", Date.valueOf(LocalDate.of(2026, 8, 11)),
|
||||
"amount", new BigDecimal("40.00"), "method", "BANK_TRANSFER", "reference_no", "BANK-1",
|
||||
"voucher_file_ids", "[\"91\"]", "created_by", 7L,
|
||||
"created_at", Timestamp.valueOf(LocalDateTime.of(2026, 8, 11, 10, 0)), "remark", "首笔付款")));
|
||||
when(jdbc.queryForList(startsWith("SELECT id,transaction_type"), eq(10L), eq(88L))).thenReturn(List.of(Map.ofEntries(
|
||||
Map.entry("id", 501L), Map.entry("transaction_type", "PAYMENT"),
|
||||
Map.entry("transaction_date", Date.valueOf(LocalDate.of(2026, 8, 11))),
|
||||
Map.entry("amount", new BigDecimal("40.00")), Map.entry("method", "BANK_TRANSFER"),
|
||||
Map.entry("reference_no", "BANK-1"), Map.entry("voucher_file_ids", "[\"91\"]"),
|
||||
Map.entry("created_by", 7L), Map.entry("created_by_name", "platform_admin"),
|
||||
Map.entry("created_at", Timestamp.valueOf(LocalDateTime.of(2026, 8, 11, 10, 0))),
|
||||
Map.entry("remark", "首笔付款"))));
|
||||
when(jdbc.queryForList(startsWith("SELECT action,reason"), eq(10L), eq(88L))).thenReturn(List.of());
|
||||
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)));
|
||||
@@ -252,6 +255,7 @@ class FinanceSettlementBehaviorTest {
|
||||
assertThat(result.summary().outstandingAmount()).isEqualByComparingTo("60.00");
|
||||
assertThat(result.transactions()).singleElement().extracting(transaction -> transaction.transactionType())
|
||||
.isEqualTo("PAYMENT");
|
||||
assertThat(result.transactions().get(0).createdByName()).isEqualTo("platform_admin");
|
||||
verify(jdbc).update(startsWith("UPDATE fin_settlement SET paid_amount"),
|
||||
any(), any(), any(), any(), any(), any(), any(), any());
|
||||
}
|
||||
@@ -431,6 +435,7 @@ class FinanceSettlementBehaviorTest {
|
||||
Map.entry("transaction_date", Date.valueOf("2026-08-11")), Map.entry("amount", new BigDecimal("40.00")),
|
||||
Map.entry("method", "BANK_TRANSFER"), Map.entry("reference_no", "RCPT-701"),
|
||||
Map.entry("voucher_file_ids", "[\"91\"]"), Map.entry("created_by", 7L),
|
||||
Map.entry("created_by_name", "platform_admin"),
|
||||
Map.entry("created_at", Timestamp.valueOf("2026-08-11 10:00:00")), Map.entry("remark", "首笔收款"))));
|
||||
FinanceSettlementService service = service(jdbc, org.mockito.Mockito.mock(FinanceFileClient.class));
|
||||
|
||||
@@ -439,6 +444,7 @@ class FinanceSettlementBehaviorTest {
|
||||
assertThat(result.transactions()).singleElement().extracting(transaction -> transaction.transactionType())
|
||||
.isEqualTo("RECEIPT");
|
||||
assertThat(result.transactions().get(0).amount()).isEqualByComparingTo("40.00");
|
||||
assertThat(result.transactions().get(0).createdByName()).isEqualTo("platform_admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -44,7 +44,7 @@ class FinanceSqlCommentContractTest {
|
||||
}
|
||||
}
|
||||
assertThat(tableCount).isEqualTo(16);
|
||||
assertThat(columnCount).isEqualTo(233);
|
||||
assertThat(columnCount).isEqualTo(239);
|
||||
assertThat(missing).as("缺少中文业务注释的Finance字段").isEmpty();
|
||||
}
|
||||
|
||||
@@ -70,4 +70,25 @@ class FinanceSqlCommentContractTest {
|
||||
.contains("ALTER TABLE baiyue_finance.fin_bill_line ADD COLUMN adjustment_reason")
|
||||
.contains("queue_column_comment('baiyue_finance', 'fin_bill_line', 'adjustment_reason'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void financeLedgerSourcesPersistOperatorNameSnapshotsInBaselineAndUnifiedSql() throws Exception {
|
||||
String baseline = Files.readString(Path.of("src/main/resources/db/finance.sql"), StandardCharsets.UTF_8);
|
||||
Path direct = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
Path unifiedPath = Files.exists(direct) ? direct : Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
String unified = Files.readString(unifiedPath, StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(baseline.split("created_by_name VARCHAR\\(128\\) NULL COMMENT '创建操作人名称快照'", -1))
|
||||
.hasSize(4);
|
||||
assertThat(baseline)
|
||||
.contains("updated_by_name VARCHAR(128) NULL COMMENT '最后修改操作人名称快照'")
|
||||
.contains("created_by_name VARCHAR(128) NULL COMMENT '登记操作人名称快照'");
|
||||
assertThat(unified)
|
||||
.contains("CALL finance_add_operator_snapshot_column('fin_bill', 'created_by_name', 'created_by')")
|
||||
.contains("CALL finance_add_operator_snapshot_column('fin_bill', 'updated_by_name', 'updated_by')")
|
||||
.contains("CALL finance_add_operator_snapshot_column('fin_receipt', 'created_by_name', 'created_by')")
|
||||
.contains("CALL finance_add_operator_snapshot_column('fin_cost', 'created_by_name', 'created_by')")
|
||||
.contains("CALL finance_add_operator_snapshot_column('fin_settlement_payment', 'created_by_name', 'created_by')")
|
||||
.contains("CALL finance_add_operator_snapshot_column('fin_bill_revision', 'created_by_name', 'created_by')");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.RETURNS_DEFAULTS;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -103,6 +104,8 @@ class ReconciliationStatementBehaviorTest {
|
||||
assertThat(result.statementId()).isEqualTo(501L);
|
||||
assertThat(result.statementNo()).isEqualTo("REC-00000000000000000501");
|
||||
assertThat(result.currency()).isEqualTo("CNY");
|
||||
assertThat(result.createdByName()).isEqualTo("platform_admin");
|
||||
assertThat(result.updatedByName()).isEqualTo("platform_admin");
|
||||
assertThat(result.totalReceivable()).isEqualByComparingTo("100.00");
|
||||
assertThat(result.lines()).hasSize(1);
|
||||
verify(jdbc).update(org.mockito.ArgumentMatchers.startsWith("UPDATE fin_fee_source SET status='LOCKED'"), eq(88L), eq(9L));
|
||||
@@ -147,11 +150,11 @@ class ReconciliationStatementBehaviorTest {
|
||||
assertThat(result.status()).isEqualTo("PENDING_REVIEW");
|
||||
assertThat(result.version()).isEqualTo(1);
|
||||
assertThat(result.periodStart()).isEqualTo(LocalDate.of(2026, 8, 1));
|
||||
verify(jdbc).update(anyString(), eq(9L), eq(501L), eq(0), eq("提交审核"), anyString(), anyString(), eq(71L));
|
||||
verify(jdbc).update(anyString(), eq(9L), eq(501L), eq(0), eq("提交审核"), anyString(), anyString(), eq(71L), isNull());
|
||||
verify(jdbc).update(org.mockito.ArgumentMatchers.startsWith("UPDATE fin_bill SET status="),
|
||||
eq("PENDING_REVIEW"), eq("PENDING_REVIEW"), eq("PENDING_REVIEW"), eq(71L),
|
||||
eq("PENDING_REVIEW"), eq("PENDING_REVIEW"), eq(71L), eq("PENDING_REVIEW"),
|
||||
eq("提交审核"), eq(71L), eq(501L), eq(9L), eq("DRAFT"), eq(0));
|
||||
eq("提交审核"), eq(71L), isNull(), eq(501L), eq(9L), eq("DRAFT"), eq(0));
|
||||
verify(jdbc, never()).update(org.mockito.ArgumentMatchers.startsWith("UPDATE fin_fee_source f JOIN"), (Object[]) any());
|
||||
}
|
||||
|
||||
@@ -230,7 +233,7 @@ class ReconciliationStatementBehaviorTest {
|
||||
verify(jdbc).update(org.mockito.ArgumentMatchers.startsWith("UPDATE fin_bill_line SET amount="),
|
||||
eq(new BigDecimal("280.00")), eq("运费更正"), eq(601L), eq(501L), eq(9L));
|
||||
verify(jdbc).update(org.mockito.ArgumentMatchers.startsWith("UPDATE fin_bill SET total_amount="),
|
||||
eq(new BigDecimal("280.00")), eq(new BigDecimal("280.00")), eq(71L), eq(501L), eq(9L), eq(3));
|
||||
eq(new BigDecimal("280.00")), eq(new BigDecimal("280.00")), eq(71L), isNull(), eq(501L), eq(9L), eq(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -465,6 +468,8 @@ class ReconciliationStatementBehaviorTest {
|
||||
when(rs.getInt("version")).thenReturn(version);
|
||||
when(rs.getInt("credit_days")).thenReturn(30);
|
||||
when(rs.getString("remark")).thenReturn("八月对账");
|
||||
when(rs.getString("created_by_name")).thenReturn("platform_admin");
|
||||
when(rs.getString("updated_by_name")).thenReturn("platform_admin");
|
||||
Timestamp timestamp = Timestamp.valueOf("2026-08-11 10:00:00");
|
||||
when(rs.getTimestamp("created_at")).thenReturn(timestamp);
|
||||
when(rs.getTimestamp("updated_at")).thenReturn(timestamp);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.baiyue.finance.service;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class FinanceOperatorSnapshotTest {
|
||||
@AfterEach
|
||||
void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void capturesAuthenticatedUsernameForMatchingOperator() {
|
||||
AuthenticatedUser user = new AuthenticatedUser(71L, null, null, null,
|
||||
"platform_admin", "ADMIN", "WEB", Set.of());
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(user, null));
|
||||
|
||||
assertThat(FinanceOperatorSnapshot.currentName(71L)).isEqualTo("platform_admin");
|
||||
assertThat(FinanceOperatorSnapshot.requiredName(71L)).isEqualTo("platform_admin");
|
||||
assertThat(FinanceOperatorSnapshot.currentName(72L)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void labelsSystemGeneratedEntriesWithoutInventingAnOperator() {
|
||||
assertThat(FinanceOperatorSnapshot.currentName(0L)).isEqualTo("系统");
|
||||
assertThat(FinanceOperatorSnapshot.currentName(71L)).isNull();
|
||||
assertThat(FinanceOperatorSnapshot.requiredName(71L)).isEqualTo("操作人 71");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user