Files
baiyue-firstleg/finance/src/main/java/com/baiyue/finance/service/FinanceQueryService.java
T
2026-08-20 18:21:04 +08:00

305 lines
24 KiB
Java

package com.baiyue.finance.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.finance.dto.FinanceReadModels.AccountEntry;
import com.baiyue.finance.dto.FinanceReadModels.ClientReceipt;
import com.baiyue.finance.dto.FinanceReadModels.ClientReceiptAllocation;
import com.baiyue.finance.dto.FinanceReadModels.BillRevision;
import com.baiyue.finance.dto.FinanceReadModels.CustomerAccount;
import com.baiyue.finance.dto.FinanceReadModels.CustomerAccountSummary;
import com.baiyue.finance.dto.FinanceReadModels.CostAllocation;
import com.baiyue.finance.dto.FinanceReadModels.CostDetail;
import com.baiyue.finance.dto.FinanceReadModels.FeePoolItem;
import com.baiyue.finance.dto.FinanceReadModels.PayableSettlement;
import com.baiyue.finance.dto.FinanceReadModels.PlatformReceipt;
import com.baiyue.finance.dto.FinanceReadModels.ReceivableSettlement;
import com.baiyue.finance.dto.FinanceReadModels.Statement;
import com.baiyue.finance.dto.FinanceReadModels.ReconciliationSummary;
import com.baiyue.finance.dto.FinanceReadModels.StatementDetail;
import com.baiyue.finance.dto.FinanceReadModels.StatementLine;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/** Typed query facade for the five Finance pages and client-safe read APIs. */
@Service
public class FinanceQueryService {
private static final String CNY = "CNY";
private final JdbcTemplate jdbc;
private final ObjectMapper objectMapper;
public FinanceQueryService(JdbcTemplate jdbc, ObjectMapper objectMapper) {
this.jdbc = jdbc;
this.objectMapper = objectMapper;
}
public CustomerAccount customerAccount(long customerId) {
requireCustomer(customerId);
BigDecimal total = amount("SELECT COALESCE(SUM(total_amount),0) FROM fin_bill WHERE tenant_id=? AND status<>'VOIDED'", customerId);
BigDecimal received = amount("SELECT COALESCE(SUM(received_amount),0) FROM fin_bill WHERE tenant_id=? AND status<>'VOIDED'", customerId);
BigDecimal outstanding = amount("SELECT COALESCE(SUM(outstanding_amount),0) FROM fin_bill WHERE tenant_id=? AND status<>'VOIDED'", customerId);
List<AccountEntry> entries = new ArrayList<>();
entries.addAll(jdbc.query("SELECT id,bill_no,created_at,total_amount,currency_code,status FROM fin_bill WHERE tenant_id=? AND status<>'VOIDED'", (rs, rowNum) ->
new AccountEntry("RECEIVABLE", rs.getLong("id"), rs.getString("bill_no"), time(rs, "created_at"),
rs.getBigDecimal("total_amount"), BigDecimal.ZERO, rs.getString("currency_code"), rs.getString("status")), customerId));
entries.addAll(jdbc.query("SELECT id,receipt_no,received_at,received_amount,currency_code,status FROM fin_receipt WHERE tenant_id=? AND status<>'VOIDED'", (rs, rowNum) ->
new AccountEntry("RECEIPT", rs.getLong("id"), rs.getString("receipt_no"), time(rs, "received_at"),
BigDecimal.ZERO, rs.getBigDecimal("received_amount"), rs.getString("currency_code"), rs.getString("status")), customerId));
entries.sort(Comparator.comparing(AccountEntry::occurredAt, Comparator.nullsLast(Comparator.reverseOrder())));
return new CustomerAccount(customerId, CNY, "MANUAL_RECEIVABLE_LEDGER", total, received, outstanding, List.copyOf(entries));
}
public CustomerAccountSummary customerAccountSummary(long customerId) {
requireCustomer(customerId);
String sql = "SELECT " +
"COALESCE(SUM(CASE WHEN s.direction='RECEIVABLE' AND s.status<>'VOIDED' THEN s.total_amount ELSE 0 END),0) total_receivable," +
"COALESCE(SUM(CASE WHEN s.direction='RECEIVABLE' AND s.status<>'VOIDED' THEN s.paid_amount ELSE 0 END),0) received_amount," +
"COALESCE(SUM(CASE WHEN s.direction='RECEIVABLE' AND s.status<>'VOIDED' THEN s.outstanding_amount ELSE 0 END),0) outstanding_receivable," +
"COALESCE(SUM(CASE WHEN s.direction='PAYABLE' AND s.status<>'VOIDED' THEN s.outstanding_amount ELSE 0 END),0) outstanding_payable," +
"(SELECT COALESCE(SUM(f.amount),0) FROM fin_fee_source f WHERE f.customer_id=? AND f.direction='RECEIVABLE' " +
"AND f.currency_code='CNY' AND f.status='AVAILABLE' " +
"AND f.review_status='APPROVED' AND f.amount>0 " +
"AND NOT EXISTS (SELECT 1 FROM fin_bill_line bl JOIN fin_bill b ON b.id=bl.bill_id AND b.tenant_id=bl.tenant_id " +
"WHERE bl.tenant_id=f.customer_id AND bl.source_id=f.id AND b.status<>'VOIDED')) pending_billing," +
"((SELECT COALESCE(SUM(f.amount),0) FROM fin_fee_source f WHERE f.customer_id=? AND f.direction='PAYABLE' AND f.settlement_mode='ON_ACCOUNT' AND f.review_status='APPROVED' AND f.status NOT IN ('VOIDED','SUPERSEDED'))+" +
"(SELECT COALESCE(SUM(c.cost_amount),0) FROM fin_cost c WHERE c.tenant_id=? AND c.status='CONFIRMED')) total_cost," +
"((SELECT COALESCE(SUM(c.cost_amount),0) FROM fin_cost c WHERE c.tenant_id=? AND c.status='CONFIRMED')+" +
"COALESCE(SUM(CASE WHEN s.direction='PAYABLE' AND s.status<>'VOIDED' THEN s.paid_amount ELSE 0 END),0)) paid_cost," +
"GREATEST(COALESCE(MAX(CASE WHEN s.status<>'VOIDED' THEN s.updated_at END),'1970-01-01')," +
"COALESCE((SELECT MAX(f.updated_at) FROM fin_fee_source f WHERE f.customer_id=?),'1970-01-01')," +
"COALESCE((SELECT MAX(c.updated_at) FROM fin_cost c WHERE c.tenant_id=?),'1970-01-01')) last_changed_at " +
"FROM fin_settlement s WHERE s.tenant_id=?";
try {
return jdbc.queryForObject(sql, (rs, rowNum) -> new CustomerAccountSummary(customerId, CNY,
money(rs, "total_receivable"), money(rs, "received_amount"), money(rs, "outstanding_receivable"),
money(rs, "pending_billing"), money(rs, "total_cost"), money(rs, "paid_cost"),
money(rs, "outstanding_payable"), Instant.now(), time(rs, "last_changed_at")),
customerId, customerId, customerId, customerId, customerId, customerId, customerId);
} catch (BadSqlGrammarException ignored) {
// Empty/partially migrated Finance databases may not have a settlement projection yet.
// The read-only account summary must still be usable as a zero CNY baseline.
return new CustomerAccountSummary(customerId, CNY, BigDecimal.ZERO, BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
Instant.now(), null);
}
}
public List<FeePoolItem> feePool(Long customerId, String direction) {
validateOptionalCustomer(customerId);
String normalized = direction == null || direction.isBlank() ? "ALL" : direction.trim().toUpperCase(java.util.Locale.ROOT);
if (!List.of("ALL", "RECEIVABLE", "PAYABLE").contains(normalized)) throw bad("费用方向只能是RECEIVABLE或PAYABLE");
List<FeePoolItem> result = new ArrayList<>();
String sourceSql = "SELECT id,customer_id,direction,source_type,source_fee_id,source_fee_version,order_id,inbound_id,container_id,fee_item_name,amount,currency_code,settlement_mode,paid_at,payment_method,payment_reference_no,payment_voucher_file_ids,status,created_at FROM fin_fee_source WHERE status='AVAILABLE'";
List<Object> sourceArgs = new ArrayList<>();
if (customerId != null) { sourceSql += " AND customer_id=?"; sourceArgs.add(customerId); }
if (!"ALL".equals(normalized)) { sourceSql += " AND direction=?"; sourceArgs.add(normalized); }
sourceSql += " ORDER BY created_at DESC,id DESC";
result.addAll(jdbc.query(sourceSql, (rs, rowNum) -> {
String rowDirection = rs.getString("direction");
return new FeePoolItem(rowDirection + ":" + rs.getLong("id"), rs.getLong("customer_id"), rowDirection,
rs.getString("source_type"), rs.getLong("source_fee_id"), rs.getInt("source_fee_version"),
nullableLong(rs, "order_id"), nullableLong(rs, "inbound_id"), nullableLong(rs, "container_id"),
rs.getString("fee_item_name"), rs.getBigDecimal("amount"), rs.getString("currency_code"),
rs.getString("settlement_mode"), time(rs, "paid_at"), rs.getString("payment_method"),
rs.getString("payment_reference_no"), fileIds(rs.getString("payment_voucher_file_ids")),
rs.getString("status"), time(rs, "created_at"));
}, sourceArgs.toArray()));
result.sort(Comparator.comparing(FeePoolItem::createdAt, Comparator.nullsLast(Comparator.reverseOrder())));
return List.copyOf(result);
}
public List<Statement> statements(Long customerId, boolean clientVisibleOnly) {
validateOptionalCustomer(customerId);
String visible = clientVisibleOnly ? " AND status IN ('ISSUED','PARTIALLY_PAID','PAID')" : "";
String sql = "SELECT id,tenant_id,bill_no,customer_name_snapshot,bill_date,due_date,total_amount,received_amount,outstanding_amount,currency_code,status,version,customer_confirmation_status,customer_confirmed_by,customer_confirmed_by_name,customer_confirmed_at,created_by,created_by_name,created_at,updated_by,updated_by_name,updated_at FROM fin_bill WHERE 1=1";
Object[] args = new Object[0];
if (customerId != null) { sql += " AND tenant_id=?"; args = new Object[]{customerId}; }
return jdbc.query(sql + visible + " ORDER BY bill_date DESC,id DESC",
(rs, rowNum) -> new Statement(rs.getLong("id"), rs.getLong("tenant_id"), rs.getString("bill_no"), rs.getString("customer_name_snapshot"),
rs.getDate("bill_date").toLocalDate(), rs.getDate("due_date").toLocalDate(), rs.getBigDecimal("total_amount"), rs.getBigDecimal("received_amount"),
rs.getBigDecimal("outstanding_amount"), rs.getString("currency_code"), rs.getString("status"), rs.getInt("version"),
rs.getString("customer_confirmation_status"), nullableLong(rs, "customer_confirmed_by"),
rs.getString("customer_confirmed_by_name"), time(rs, "customer_confirmed_at"),
rs.getLong("created_by"), rs.getString("created_by_name"), time(rs, "created_at"),
rs.getLong("updated_by"), rs.getString("updated_by_name"), time(rs, "updated_at")), args);
}
public List<ReconciliationSummary> reconciliationStatements(Long customerId) {
validateOptionalCustomer(customerId);
String sql = "SELECT b.id,b.tenant_id,b.bill_no,b.customer_name_snapshot,b.currency_code,b.status,b.version,b.remark," +
"b.total_amount,b.received_amount,b.outstanding_amount,b.issued_at,b.customer_confirmation_status,b.customer_confirmed_by,b.customer_confirmed_by_name,b.customer_confirmed_at,b.created_by,b.created_by_name,b.created_at,b.updated_by,b.updated_by_name,b.updated_at," +
"COALESCE(p.credit_days,30) credit_days," +
"CAST(JSON_UNQUOTE(JSON_EXTRACT(c.after_data,'$.periodStart')) AS DATE) period_start," +
"CAST(JSON_UNQUOTE(JSON_EXTRACT(c.after_data,'$.periodEnd')) AS DATE) period_end," +
"COALESCE(l.line_count,0) line_count,COALESCE(l.source_count,0) source_count,COALESCE(l.source_amount,0) source_amount," +
"b.total_amount-COALESCE(l.source_amount,0) adjustment_amount,r.last_receipt_at," +
"a.submitted_at,a.reviewed_at " +
"FROM fin_bill b LEFT JOIN fin_customer_settlement_profile p ON p.tenant_id=b.tenant_id " +
"LEFT JOIN fin_audit_log c ON c.tenant_id=b.tenant_id AND c.resource_type='RECONCILIATION_STATEMENT' AND c.resource_id=b.id AND c.action='RECONCILIATION_CREATED' AND c.id=(SELECT MIN(c2.id) FROM fin_audit_log c2 WHERE c2.tenant_id=b.tenant_id AND c2.resource_type='RECONCILIATION_STATEMENT' AND c2.resource_id=b.id AND c2.action='RECONCILIATION_CREATED') " +
"LEFT JOIN (SELECT bl.tenant_id,bl.bill_id,COUNT(*) line_count,COUNT(DISTINCT bl.source_id) source_count,SUM(f.amount) source_amount FROM fin_bill_line bl JOIN fin_fee_source f ON f.id=bl.source_id AND f.customer_id=bl.tenant_id GROUP BY bl.tenant_id,bl.bill_id) l ON l.tenant_id=b.tenant_id AND l.bill_id=b.id " +
"LEFT JOIN (SELECT rb.tenant_id,rb.bill_id,MAX(r.received_at) last_receipt_at FROM fin_receipt_bill rb JOIN fin_receipt r ON r.id=rb.receipt_id AND r.tenant_id=rb.tenant_id AND r.status='CONFIRMED' GROUP BY rb.tenant_id,rb.bill_id) r ON r.tenant_id=b.tenant_id AND r.bill_id=b.id " +
"LEFT JOIN (SELECT tenant_id,resource_id,MAX(CASE WHEN action='RECONCILIATION_SUBMIT' THEN created_at END) submitted_at,MAX(CASE WHEN action IN ('RECONCILIATION_APPROVE','RECONCILIATION_REJECT') THEN created_at END) reviewed_at FROM fin_audit_log WHERE resource_type='RECONCILIATION_STATEMENT' GROUP BY tenant_id,resource_id) a ON a.tenant_id=b.tenant_id AND a.resource_id=b.id WHERE 1=1";
Object[] args = new Object[0];
if (customerId != null) { sql += " AND b.tenant_id=?"; args = new Object[]{customerId}; }
return jdbc.query(sql + " ORDER BY b.bill_date DESC,b.id DESC", (rs, rowNum) -> new ReconciliationSummary(
rs.getLong("id"), rs.getString("bill_no"), rs.getLong("tenant_id"), rs.getString("customer_name_snapshot"),
date(rs, "period_start"), date(rs, "period_end"), rs.getInt("credit_days"), rs.getString("currency_code"),
rs.getInt("line_count"), rs.getInt("source_count"), rs.getBigDecimal("source_amount"), rs.getBigDecimal("adjustment_amount"),
rs.getBigDecimal("total_amount"), rs.getBigDecimal("received_amount"), rs.getBigDecimal("outstanding_amount"),
time(rs, "last_receipt_at"), rs.getBigDecimal("outstanding_amount").signum() == 0, rs.getString("status"),
time(rs, "submitted_at"), time(rs, "reviewed_at"), time(rs, "issued_at"), rs.getInt("version"),
rs.getString("customer_confirmation_status"), nullableLong(rs, "customer_confirmed_by"),
rs.getString("customer_confirmed_by_name"), time(rs, "customer_confirmed_at"),
rs.getString("remark"),
rs.getLong("created_by"), rs.getString("created_by_name"), time(rs, "created_at"),
rs.getLong("updated_by"), rs.getString("updated_by_name"), time(rs, "updated_at")), args);
}
public StatementDetail statement(long customerId, long billId, boolean clientVisibleOnly) {
requireCustomer(customerId);
String visible = clientVisibleOnly ? " AND status IN ('ISSUED','PARTIALLY_PAID','PAID')" : "";
List<Statement> rows = jdbc.query("SELECT id,tenant_id,bill_no,customer_name_snapshot,bill_date,due_date,total_amount,received_amount,outstanding_amount,currency_code,status,version,customer_confirmation_status,customer_confirmed_by,customer_confirmed_by_name,customer_confirmed_at,created_by,created_by_name,created_at,updated_by,updated_by_name,updated_at FROM fin_bill WHERE tenant_id=? AND id=?" + visible,
(rs, rowNum) -> new Statement(rs.getLong("id"), rs.getLong("tenant_id"), rs.getString("bill_no"), rs.getString("customer_name_snapshot"),
rs.getDate("bill_date").toLocalDate(), rs.getDate("due_date").toLocalDate(), rs.getBigDecimal("total_amount"), rs.getBigDecimal("received_amount"),
rs.getBigDecimal("outstanding_amount"), rs.getString("currency_code"), rs.getString("status"), rs.getInt("version"),
rs.getString("customer_confirmation_status"), nullableLong(rs, "customer_confirmed_by"),
rs.getString("customer_confirmed_by_name"), time(rs, "customer_confirmed_at"),
rs.getLong("created_by"), rs.getString("created_by_name"), time(rs, "created_at"),
rs.getLong("updated_by"), rs.getString("updated_by_name"), time(rs, "updated_at")), customerId, billId);
if (rows.isEmpty()) throw new BizException(ResultCode.NOT_FOUND, "账单不存在或不属于客户");
List<StatementLine> lines = jdbc.query("SELECT id,source_type,source_id,source_version,order_id,inbound_id,fee_item_code,fee_item_name,amount,currency_code FROM fin_bill_line WHERE tenant_id=? AND bill_id=? ORDER BY id",
(rs, rowNum) -> new StatementLine(rs.getLong("id"), rs.getString("source_type"), rs.getLong("source_id"), rs.getInt("source_version"),
nullableLong(rs, "order_id"), nullableLong(rs, "inbound_id"), rs.getString("fee_item_code"), rs.getString("fee_item_name"),
rs.getBigDecimal("amount"), rs.getString("currency_code")), customerId, billId);
return new StatementDetail(rows.get(0), lines);
}
public StatementDetail platformStatement(long billId) {
Long customerId = jdbc.queryForObject("SELECT tenant_id FROM fin_bill WHERE id=?", Long.class, billId);
if (customerId == null) throw new BizException(ResultCode.NOT_FOUND, "账单不存在");
return statement(customerId, billId, false);
}
public List<BillRevision> billRevisions(long customerId, long billId) {
statement(customerId, billId, false);
return jdbc.query("SELECT id,bill_id,version,reason,bill_snapshot,line_snapshot,created_by,created_by_name,created_at FROM fin_bill_revision WHERE tenant_id=? AND bill_id=? ORDER BY version DESC,id DESC",
(rs, rowNum) -> new BillRevision(rs.getLong("id"), rs.getLong("bill_id"), rs.getInt("version"), rs.getString("reason"),
rs.getString("bill_snapshot"), rs.getString("line_snapshot"), rs.getLong("created_by"),
rs.getString("created_by_name"), time(rs, "created_at")), customerId, billId);
}
public List<ReceivableSettlement> receivableSettlements(Long customerId, boolean clientVisibleOnly) {
return statements(customerId, clientVisibleOnly).stream().map(row -> new ReceivableSettlement(
row.id(), row.customerId(), row.billNo(), row.billDate(), row.dueDate(), row.totalAmount(), row.receivedAmount(),
row.outstandingAmount(), row.currency(), row.status(), row.version())).toList();
}
public List<PayableSettlement> payableSettlements(Long customerId) {
validateOptionalCustomer(customerId);
String sql = "SELECT id,tenant_id,cost_no,cost_type,order_id,inbound_id,container_id,supplier_name_snapshot,fee_item_name,cost_amount,currency_code,status,created_at,version FROM fin_cost WHERE 1=1";
Object[] args = new Object[0];
if (customerId != null) { sql += " AND tenant_id=?"; args = new Object[]{customerId}; }
return jdbc.query(sql + " ORDER BY created_at DESC,id DESC",
(rs, rowNum) -> {
BigDecimal amount = rs.getBigDecimal("cost_amount");
return new PayableSettlement(rs.getLong("id"), rs.getLong("tenant_id"), rs.getString("cost_no"), rs.getString("cost_type"),
nullableLong(rs, "order_id"), nullableLong(rs, "inbound_id"), nullableLong(rs, "container_id"), rs.getString("supplier_name_snapshot"),
rs.getString("fee_item_name"), amount, BigDecimal.ZERO, amount, rs.getString("currency_code"), rs.getString("status"),
time(rs, "created_at"), rs.getInt("version"));
}, args);
}
public CostDetail cost(long customerId, long costId) {
PayableSettlement cost = payableSettlements(customerId).stream()
.filter(item -> item.id() == costId).findFirst()
.orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "成本不存在或不属于客户"));
List<CostAllocation> allocations = jdbc.query(
"SELECT id,cost_id,order_id,inbound_id,volume_cbm,total_volume_cbm,allocation_rate,allocated_amount,rounding_adjustment FROM fin_cost_allocation WHERE tenant_id=? AND cost_id=? ORDER BY id",
(rs, rowNum) -> new CostAllocation(rs.getLong("id"), rs.getLong("cost_id"), rs.getLong("order_id"),
nullableLong(rs, "inbound_id"), rs.getBigDecimal("volume_cbm"), rs.getBigDecimal("total_volume_cbm"),
rs.getBigDecimal("allocation_rate"), rs.getBigDecimal("allocated_amount"), rs.getBoolean("rounding_adjustment")),
customerId, costId);
return new CostDetail(cost, allocations);
}
public List<ClientReceipt> clientReceipts(long customerId) {
requireCustomer(customerId);
return jdbc.query("SELECT id,receipt_no,received_amount,received_at,payment_method,currency_code,status FROM fin_receipt WHERE tenant_id=? AND status='CONFIRMED' ORDER BY received_at DESC,id DESC",
(rs, rowNum) -> new ClientReceipt(rs.getLong("id"), rs.getString("receipt_no"), rs.getBigDecimal("received_amount"),
time(rs, "received_at"), rs.getString("payment_method"), rs.getString("currency_code"), rs.getString("status"), allocations(customerId, rs.getLong("id"))), customerId);
}
public List<PlatformReceipt> platformReceipts(long customerId) {
requireCustomer(customerId);
return jdbc.query("SELECT id,tenant_id,receipt_no,received_amount,received_at,payment_method,voucher_no,voucher_file_ids,currency_code,status,remark,version FROM fin_receipt WHERE tenant_id=? ORDER BY received_at DESC,id DESC",
(rs, rowNum) -> new PlatformReceipt(rs.getLong("id"), rs.getLong("tenant_id"), rs.getString("receipt_no"), rs.getBigDecimal("received_amount"),
time(rs, "received_at"), rs.getString("payment_method"), rs.getString("voucher_no"), fileIds(rs.getString("voucher_file_ids")),
rs.getString("currency_code"), rs.getString("status"), rs.getString("remark"), rs.getInt("version"), allocations(customerId, rs.getLong("id"))), customerId);
}
private List<String> fileIds(String json) {
if (json == null || json.isBlank()) return List.of();
try {
return objectMapper.readValue(json, new TypeReference<List<String>>() {});
} catch (Exception error) {
throw new IllegalStateException("财务凭证文件字段损坏", error);
}
}
private List<ClientReceiptAllocation> allocations(long customerId, long receiptId) {
return jdbc.query("SELECT rb.bill_id,b.bill_no,rb.applied_amount FROM fin_receipt_bill rb JOIN fin_bill b ON b.id=rb.bill_id AND b.tenant_id=rb.tenant_id WHERE rb.tenant_id=? AND rb.receipt_id=? ORDER BY rb.id",
(rs, rowNum) -> new ClientReceiptAllocation(rs.getLong("bill_id"), rs.getString("bill_no"), rs.getBigDecimal("applied_amount")), customerId, receiptId);
}
private BigDecimal amount(String sql, long customerId) {
BigDecimal value = jdbc.queryForObject(sql, BigDecimal.class, customerId);
return value == null ? BigDecimal.ZERO : value;
}
private static BigDecimal money(ResultSet rs, String column) throws SQLException {
BigDecimal value = rs.getBigDecimal(column);
return value == null ? BigDecimal.ZERO : value;
}
private static Long nullableLong(ResultSet rs, String column) throws SQLException {
long value = rs.getLong(column);
return rs.wasNull() ? null : value;
}
private static LocalDateTime time(ResultSet rs, String column) throws SQLException {
Timestamp timestamp = rs.getTimestamp(column);
return timestamp == null ? null : timestamp.toLocalDateTime();
}
private static LocalDate date(ResultSet rs, String column) throws SQLException {
java.sql.Date date = rs.getDate(column);
return date == null ? null : date.toLocalDate();
}
private static void requireCustomer(long customerId) {
if (customerId <= 0) throw bad("客户范围无效");
}
private static void validateOptionalCustomer(Long customerId) {
if (customerId != null) requireCustomer(customerId);
}
private static BizException bad(String message) {
return new BizException(ResultCode.BAD_REQUEST, message);
}
}