优化
This commit is contained in:
@@ -70,8 +70,10 @@ public final class FinanceReadModels {
|
||||
String status,
|
||||
int version,
|
||||
long createdBy,
|
||||
String createdByName,
|
||||
LocalDateTime createdAt,
|
||||
long updatedBy,
|
||||
String updatedByName,
|
||||
LocalDateTime updatedAt) {}
|
||||
|
||||
public record StatementDetail(Statement statement, List<StatementLine> lines) {}
|
||||
@@ -168,6 +170,7 @@ public final class FinanceReadModels {
|
||||
String billSnapshot,
|
||||
String lineSnapshot,
|
||||
long createdBy,
|
||||
String createdByName,
|
||||
LocalDateTime createdAt) {}
|
||||
|
||||
public record CustomerTerms(long customerId, String customerName, int creditDays, String status) {}
|
||||
|
||||
@@ -35,10 +35,11 @@ public final class FinanceSettlementDtos {
|
||||
boolean statementGenerated) {}
|
||||
public record SettlementTransaction(long transactionId, String transactionType, LocalDate transactionDate,
|
||||
BigDecimal amount, String method, String referenceNo,
|
||||
List<String> voucherFileIds, long createdBy, LocalDateTime createdAt,
|
||||
List<String> voucherFileIds, long createdBy, String createdByName,
|
||||
LocalDateTime createdAt,
|
||||
String remark) {}
|
||||
public record SettlementRevision(int revisionNo, String action, BigDecimal beforeAmount,
|
||||
BigDecimal afterAmount, String reason, long operatorId,
|
||||
BigDecimal afterAmount, String reason, long operatorId, String operatorName,
|
||||
LocalDateTime occurredAt) {}
|
||||
public record SettlementDetail(@JsonUnwrapped SettlementSummary summary, List<SettlementLine> lines,
|
||||
List<SettlementTransaction> transactions,
|
||||
|
||||
@@ -23,8 +23,8 @@ public final class ReconciliationStatementDtos {
|
||||
int creditDays, String currency, BigDecimal totalReceivable,
|
||||
BigDecimal receivedAmount, BigDecimal outstandingAmount,
|
||||
boolean settled, String status, int version,
|
||||
long createdBy, LocalDateTime createdAt,
|
||||
long updatedBy, LocalDateTime updatedAt, String remark,
|
||||
long createdBy, String createdByName, LocalDateTime createdAt,
|
||||
long updatedBy, String updatedByName, LocalDateTime updatedAt, String remark,
|
||||
List<ReconciliationLine> lines, List<PaymentHistory> paymentHistory,
|
||||
List<ReconciliationRevision> revisions) {}
|
||||
|
||||
@@ -36,10 +36,10 @@ public final class ReconciliationStatementDtos {
|
||||
|
||||
public record PaymentHistory(long transactionId, String transactionType,
|
||||
LocalDate transactionDate, BigDecimal amount,
|
||||
String method, String referenceNo, long createdBy,
|
||||
String method, String referenceNo, long createdBy, String createdByName,
|
||||
LocalDateTime createdAt, String remark) {}
|
||||
|
||||
public record ReconciliationRevision(int revisionNo, String action, BigDecimal beforeAmount,
|
||||
BigDecimal afterAmount, String reason, long operatorId,
|
||||
BigDecimal afterAmount, String reason, long operatorId, String operatorName,
|
||||
LocalDateTime occurredAt) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.baiyue.finance.service;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
final class FinanceOperatorSnapshot {
|
||||
private FinanceOperatorSnapshot() { }
|
||||
|
||||
static String currentName(long operatorId) {
|
||||
if (operatorId == 0) return "系统";
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.getPrincipal() instanceof AuthenticatedUser user
|
||||
&& user.userId() != null && user.userId() == operatorId
|
||||
&& StringUtils.hasText(user.username())) {
|
||||
return user.username().trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String requiredName(long operatorId) {
|
||||
String name = currentName(operatorId);
|
||||
return name == null ? "操作人 " + operatorId : name;
|
||||
}
|
||||
}
|
||||
+10
-7
@@ -66,9 +66,9 @@ public class FinancePhase1OperationService {
|
||||
customer(customerId); page = page(page); pageSize = pageSize(pageSize);
|
||||
if (startDate != null && endDate != null && startDate.isAfter(endDate)) throw error("FIN-REQ-400", "开始日期不能晚于结束日期");
|
||||
List<RawLedger> rows = new ArrayList<>();
|
||||
rows.addAll(jdbc.query("SELECT id,bill_no,created_at,total_amount,created_by,remark FROM fin_bill WHERE tenant_id=? AND status<>'VOIDED'", (rs, n) -> raw(rs, "SETTLEMENT_ISSUED", "BILL", "bill_no", "total_amount", null), customerId));
|
||||
rows.addAll(jdbc.query("SELECT id,receipt_no,received_at received_time,received_amount,created_by,remark FROM fin_receipt WHERE tenant_id=? AND status='CONFIRMED'", (rs, n) -> raw(rs, "RECEIPT", "RECEIPT", "receipt_no", null, "received_amount"), customerId));
|
||||
rows.addAll(jdbc.query("SELECT id,cost_no,created_at,cost_amount,created_by,remark FROM fin_cost WHERE tenant_id=? AND status<>'VOIDED'", (rs, n) -> raw(rs, "PAYMENT", "COST", "cost_no", null, null), customerId));
|
||||
rows.addAll(jdbc.query("SELECT id,bill_no,created_at,total_amount,created_by,created_by_name,remark FROM fin_bill WHERE tenant_id=? AND status<>'VOIDED'", (rs, n) -> raw(rs, "SETTLEMENT_ISSUED", "BILL", "bill_no", "total_amount", null), customerId));
|
||||
rows.addAll(jdbc.query("SELECT id,receipt_no,received_at received_time,received_amount,created_by,created_by_name,remark FROM fin_receipt WHERE tenant_id=? AND status='CONFIRMED'", (rs, n) -> raw(rs, "RECEIPT", "RECEIPT", "receipt_no", null, "received_amount"), customerId));
|
||||
rows.addAll(jdbc.query("SELECT id,cost_no,created_at,cost_amount,created_by,created_by_name,remark FROM fin_cost WHERE tenant_id=? AND status<>'VOIDED'", (rs, n) -> raw(rs, "PAYMENT", "COST", "cost_no", null, null), customerId));
|
||||
rows.sort(Comparator.comparing(RawLedger::occurredAt).thenComparing(RawLedger::id));
|
||||
BigDecimal outstanding = BigDecimal.ZERO;
|
||||
List<LedgerEntry> projected = new ArrayList<>();
|
||||
@@ -76,7 +76,7 @@ public class FinancePhase1OperationService {
|
||||
BigDecimal receivableChange = row.receivable();
|
||||
outstanding = outstanding.add(receivableChange);
|
||||
BigDecimal payable = "COST".equals(row.documentType()) ? row.amount() : BigDecimal.ZERO;
|
||||
LedgerEntry entry = new LedgerEntry(row.id(), row.occurredAt(), row.businessType(), row.documentType(), row.id(), row.documentNo(), receivableChange, payable, outstanding, "operator-" + row.operatorId(), row.remark());
|
||||
LedgerEntry entry = new LedgerEntry(row.id(), row.occurredAt(), row.businessType(), row.documentType(), row.id(), row.documentNo(), receivableChange, payable, outstanding, row.operatorName(), row.remark());
|
||||
if ((businessType == null || businessType.isBlank() || businessType.equals(entry.businessType())) && inRange(entry.occurredAt(), startDate, endDate)) projected.add(entry);
|
||||
}
|
||||
projected.sort(Comparator.comparing(LedgerEntry::occurredAt).reversed()
|
||||
@@ -166,17 +166,20 @@ public class FinancePhase1OperationService {
|
||||
|
||||
private void audit(long customerId, long id, String action, String reason, Object before, Object after, long operatorId) {
|
||||
jdbc.update("INSERT INTO fin_audit_log(tenant_id,resource_type,resource_id,action,reason,before_data,after_data,operator_id,operator_name_snapshot,request_id,trace_id) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
|
||||
customerId, "FIN_FEE_SOURCE", id, action, reason, json(before), json(after), operatorId, "operator-" + operatorId, MDC.get("requestId"), MDC.get("traceId"));
|
||||
customerId, "FIN_FEE_SOURCE", id, action, reason, json(before), json(after), operatorId,
|
||||
FinanceOperatorSnapshot.requiredName(operatorId), MDC.get("requestId"), MDC.get("traceId"));
|
||||
}
|
||||
|
||||
private static RawLedger raw(ResultSet rs, String businessType, String documentType, String noColumn, String debitColumn, String creditColumn) throws SQLException {
|
||||
LocalDateTime occurred = time(rs, documentType.equals("RECEIPT") ? "received_time" : "created_at");
|
||||
BigDecimal amount = documentType.equals("COST") ? money(rs, "cost_amount") : debitColumn != null ? money(rs, debitColumn) : money(rs, creditColumn);
|
||||
BigDecimal receivable = debitColumn != null ? amount : creditColumn != null ? amount.negate() : BigDecimal.ZERO;
|
||||
return new RawLedger(rs.getLong("id"), occurred, businessType, documentType, rs.getString(noColumn), amount, receivable, rs.getLong("created_by"), rs.getString("remark"));
|
||||
return new RawLedger(rs.getLong("id"), occurred, businessType, documentType, rs.getString(noColumn), amount, receivable,
|
||||
rs.getLong("created_by"), rs.getString("created_by_name"), rs.getString("remark"));
|
||||
}
|
||||
private record RawLedger(long id, LocalDateTime occurredAt, String businessType, String documentType,
|
||||
String documentNo, BigDecimal amount, BigDecimal receivable, long operatorId, String remark) {}
|
||||
String documentNo, BigDecimal amount, BigDecimal receivable, long operatorId,
|
||||
String operatorName, String remark) {}
|
||||
|
||||
private static int page(int value) { if (value < 1) throw error("FIN-REQ-400", "页码必须大于0"); return value; }
|
||||
private static int pageSize(int value) { if (value < 1 || value > 200) throw error("FIN-REQ-400", "每页数量必须在1到200之间"); return value; }
|
||||
|
||||
@@ -124,24 +124,26 @@ public class FinanceQueryService {
|
||||
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,created_by,created_at,updated_by,updated_at FROM fin_bill WHERE 1=1";
|
||||
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,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.getLong("created_by"), time(rs, "created_at"), rs.getLong("updated_by"), time(rs, "updated_at")), args);
|
||||
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,created_by,created_at,updated_by,updated_at FROM fin_bill WHERE tenant_id=? AND id=?" + visible,
|
||||
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,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.getLong("created_by"), time(rs, "created_at"), rs.getLong("updated_by"), time(rs, "updated_at")), customerId, billId);
|
||||
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"),
|
||||
@@ -158,9 +160,10 @@ public class FinanceQueryService {
|
||||
|
||||
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_at FROM fin_bill_revision WHERE tenant_id=? AND bill_id=? ORDER BY version DESC,id DESC",
|
||||
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"), time(rs, "created_at")), customerId, billId);
|
||||
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) {
|
||||
|
||||
@@ -79,7 +79,7 @@ public class FinanceService {
|
||||
requireCustomer(customerId);
|
||||
Number count = jdbc.queryForObject("SELECT COUNT(*) FROM fin_bill WHERE id=? AND tenant_id=?", Number.class, billId, customerId);
|
||||
if (count == null || count.intValue() != 1) throw bad("账单不存在或不属于客户");
|
||||
return jdbc.queryForList("SELECT id,bill_id,version,reason,bill_snapshot,line_snapshot,created_by,created_at FROM fin_bill_revision WHERE tenant_id=? AND bill_id=? ORDER BY version DESC,id DESC", customerId, billId);
|
||||
return jdbc.queryForList("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", customerId, billId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -130,10 +130,10 @@ public class FinanceService {
|
||||
LocalDate billDate = request.billDate() == null ? LocalDate.now() : request.billDate();
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
jdbc.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement("INSERT INTO fin_bill(tenant_id,bill_no,subject_id,customer_name_snapshot,currency_code,bill_date,due_date,total_amount,outstanding_amount,remark,created_by,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
|
||||
PreparedStatement ps = connection.prepareStatement("INSERT INTO fin_bill(tenant_id,bill_no,subject_id,customer_name_snapshot,currency_code,bill_date,due_date,total_amount,outstanding_amount,remark,created_by,created_by_name,updated_by,updated_by_name) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setLong(1, tenantId); ps.setString(2, temporaryNo("BILL")); ps.setLong(3, request.subjectId()); ps.setString(4, customerName);
|
||||
ps.setString(5, "CNY"); ps.setObject(6, billDate); ps.setObject(7, billDate.plusDays(days)); ps.setBigDecimal(8, total); ps.setBigDecimal(9, total);
|
||||
ps.setString(10, request.remark()); ps.setLong(11, operatorId); ps.setLong(12, operatorId); return ps;
|
||||
ps.setString(10, request.remark()); ps.setLong(11, operatorId); ps.setString(12, FinanceOperatorSnapshot.currentName(operatorId)); ps.setLong(13, operatorId); ps.setString(14, FinanceOperatorSnapshot.currentName(operatorId)); return ps;
|
||||
}, holder);
|
||||
long billId = holder.getKey().longValue();
|
||||
jdbc.update("UPDATE fin_bill SET bill_no=? WHERE id=?", stableNo("BILL", billId), billId);
|
||||
@@ -192,8 +192,8 @@ public class FinanceService {
|
||||
public Map<String, Object> issueBill(long tenantId, long operatorId, long billId, int expectedVersion, String idempotencyKey) {
|
||||
requireCustomer(tenantId); Long replay = reserve(tenantId, operatorId, "BILL_ISSUE", idempotencyKey, Map.of("billId",billId,"expectedVersion",expectedVersion));
|
||||
if (replay != null) return bill(tenantId, replay);
|
||||
int updated = jdbc.update("UPDATE fin_bill SET status='ISSUED', issued_at=?, issued_by=?, updated_by=?, version=version+1 WHERE id=? AND tenant_id=? AND status='DRAFT' AND version=?",
|
||||
LocalDateTime.now(), operatorId, operatorId, billId, tenantId, expectedVersion);
|
||||
int updated = jdbc.update("UPDATE fin_bill SET status='ISSUED', issued_at=?, issued_by=?, updated_by=?, updated_by_name=?, version=version+1 WHERE id=? AND tenant_id=? AND status='DRAFT' AND version=?",
|
||||
LocalDateTime.now(), operatorId, operatorId, FinanceOperatorSnapshot.currentName(operatorId), billId, tenantId, expectedVersion);
|
||||
if (updated != 1) throw bad("账单不存在或已出账");
|
||||
complete(tenantId, operatorId, "BILL_ISSUE", idempotencyKey, billId);
|
||||
return jdbc.queryForMap("SELECT * FROM fin_bill WHERE id=? AND tenant_id=?", billId, tenantId);
|
||||
@@ -224,15 +224,15 @@ public class FinanceService {
|
||||
}
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
jdbc.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement("INSERT INTO fin_receipt(tenant_id,receipt_no,subject_id,received_amount,received_at,payment_method,voucher_no,voucher_file_ids,remark,created_by,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setLong(1, tenantId); ps.setString(2, temporaryNo("RCPT")); ps.setLong(3, request.subjectId()); ps.setBigDecimal(4, total); ps.setObject(5, request.receivedAt() == null ? LocalDateTime.now() : request.receivedAt()); ps.setString(6, request.paymentMethod()); ps.setString(7, request.voucherNo()); ps.setString(8, json(request.voucherFileIds())); ps.setString(9, request.remark()); ps.setLong(10, operatorId); ps.setLong(11, operatorId); return ps;
|
||||
PreparedStatement ps = connection.prepareStatement("INSERT INTO fin_receipt(tenant_id,receipt_no,subject_id,received_amount,received_at,payment_method,voucher_no,voucher_file_ids,remark,created_by,created_by_name,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setLong(1, tenantId); ps.setString(2, temporaryNo("RCPT")); ps.setLong(3, request.subjectId()); ps.setBigDecimal(4, total); ps.setObject(5, request.receivedAt() == null ? LocalDateTime.now() : request.receivedAt()); ps.setString(6, request.paymentMethod()); ps.setString(7, request.voucherNo()); ps.setString(8, json(request.voucherFileIds())); ps.setString(9, request.remark()); ps.setLong(10, operatorId); ps.setString(11, FinanceOperatorSnapshot.currentName(operatorId)); ps.setLong(12, operatorId); return ps;
|
||||
}, holder);
|
||||
long receiptId = holder.getKey().longValue();
|
||||
jdbc.update("UPDATE fin_receipt SET receipt_no=? WHERE id=?", stableNo("RCPT", receiptId), receiptId);
|
||||
for (var entry : byBill.entrySet()) {
|
||||
BigDecimal applied = entry.getValue();
|
||||
jdbc.update("INSERT INTO fin_receipt_bill(tenant_id,receipt_id,bill_id,applied_amount,created_by) VALUES(?,?,?,?,?)", tenantId, receiptId, entry.getKey(), applied, operatorId);
|
||||
jdbc.update("UPDATE fin_bill SET received_amount=received_amount+?, outstanding_amount=outstanding_amount-?, status=CASE WHEN outstanding_amount=0 THEN 'PAID' ELSE 'PARTIALLY_PAID' END, updated_by=?, version=version+1 WHERE id=? AND tenant_id=?", applied, applied, operatorId, entry.getKey(), tenantId);
|
||||
jdbc.update("UPDATE fin_bill SET received_amount=received_amount+?, outstanding_amount=outstanding_amount-?, status=CASE WHEN outstanding_amount=0 THEN 'PAID' ELSE 'PARTIALLY_PAID' END, updated_by=?, updated_by_name=?, version=version+1 WHERE id=? AND tenant_id=?", applied, applied, operatorId, FinanceOperatorSnapshot.currentName(operatorId), entry.getKey(), tenantId);
|
||||
Integer settlementCount = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM fin_settlement WHERE tenant_id=? AND direction='RECEIVABLE' AND reconciliation_statement_id=? AND status<>'VOIDED'",
|
||||
Integer.class, tenantId, entry.getKey());
|
||||
@@ -283,8 +283,8 @@ public class FinanceService {
|
||||
validateContainerOrders(tenantId, request.containerId(), containerOrders);
|
||||
String supplierNameSnapshot = supplierName;
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
jdbc.update(connection -> { PreparedStatement ps = connection.prepareStatement("INSERT INTO fin_cost(tenant_id,cost_no,cost_type,order_id,inbound_id,container_id,supplier_id,supplier_name_snapshot,fee_item_code,fee_item_name,cost_amount,voucher_file_ids,remark,created_by,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setLong(1, tenantId); ps.setString(2, temporaryNo("COST")); ps.setString(3, request.costType()); ps.setObject(4, request.orderId()); ps.setObject(5, request.inboundId()); ps.setObject(6, request.containerId()); ps.setObject(7, request.supplierId()); ps.setString(8, supplierNameSnapshot); ps.setString(9, request.feeItemCode()); ps.setString(10, request.feeItemName()); ps.setBigDecimal(11, request.amount()); ps.setString(12, json(request.voucherFileIds())); ps.setString(13, request.remark()); ps.setLong(14, operatorId); ps.setLong(15, operatorId); return ps; }, holder);
|
||||
jdbc.update(connection -> { PreparedStatement ps = connection.prepareStatement("INSERT INTO fin_cost(tenant_id,cost_no,cost_type,order_id,inbound_id,container_id,supplier_id,supplier_name_snapshot,fee_item_code,fee_item_name,cost_amount,voucher_file_ids,remark,created_by,created_by_name,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setLong(1, tenantId); ps.setString(2, temporaryNo("COST")); ps.setString(3, request.costType()); ps.setObject(4, request.orderId()); ps.setObject(5, request.inboundId()); ps.setObject(6, request.containerId()); ps.setObject(7, request.supplierId()); ps.setString(8, supplierNameSnapshot); ps.setString(9, request.feeItemCode()); ps.setString(10, request.feeItemName()); ps.setBigDecimal(11, request.amount()); ps.setString(12, json(request.voucherFileIds())); ps.setString(13, request.remark()); ps.setLong(14, operatorId); ps.setString(15, FinanceOperatorSnapshot.currentName(operatorId)); ps.setLong(16, operatorId); return ps; }, holder);
|
||||
long costId = holder.getKey().longValue();
|
||||
jdbc.update("UPDATE fin_cost SET cost_no=? WHERE id=?", stableNo("COST", costId), costId);
|
||||
if ("CONTAINER_SHARED".equals(request.costType())) {
|
||||
@@ -323,7 +323,8 @@ public class FinanceService {
|
||||
if (allocation.roundingAdjustment()) {
|
||||
jdbc.update("INSERT INTO fin_audit_log(tenant_id,resource_type,resource_id,action,reason,before_data,after_data,operator_id,operator_name_snapshot) VALUES(?,?,?,?,?,?,?,?,?)",
|
||||
tenantId, "FIN_COST_ALLOCATION", costId, "ROUNDING_ADJUSTMENT", "尾差归集到最后一票", null,
|
||||
json(Map.of("orderId", row.orderId(), "allocatedAmount", allocation.amount(), "totalAmount", amount)), operatorId, "operator-" + operatorId);
|
||||
json(Map.of("orderId", row.orderId(), "allocatedAmount", allocation.amount(), "totalAmount", amount)),
|
||||
operatorId, FinanceOperatorSnapshot.requiredName(operatorId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,20 +418,20 @@ public class FinanceService {
|
||||
Object credit = customerTerms(tenantId).get("credit_days");
|
||||
if (credit == null) credit = customerTerms(tenantId).get("creditDays");
|
||||
LocalDate dueDate = effectiveDate.plusDays(credit == null ? 30 : ((Number) credit).intValue());
|
||||
int changed = jdbc.update("UPDATE fin_bill SET bill_date=?,due_date=?,version=version+1,updated_by=? WHERE id=? AND tenant_id=? AND status='DRAFT' AND version=?", effectiveDate,dueDate,operatorId,billId,tenantId,expectedVersion);
|
||||
int changed = jdbc.update("UPDATE fin_bill SET bill_date=?,due_date=?,version=version+1,updated_by=?,updated_by_name=? WHERE id=? AND tenant_id=? AND status='DRAFT' AND version=?", effectiveDate,dueDate,operatorId,FinanceOperatorSnapshot.currentName(operatorId),billId,tenantId,expectedVersion);
|
||||
if (changed != 1) throw bad("仅草稿账单可修改且版本必须匹配");
|
||||
jdbc.update("UPDATE fin_bill SET total_amount=?, outstanding_amount=? WHERE id=? AND tenant_id=?", total, total, billId, tenantId);
|
||||
complete(tenantId, operatorId, "BILL_UPDATE", key, billId);
|
||||
return bill(tenantId,billId);
|
||||
}
|
||||
@Transactional
|
||||
public Map<String,Object> adjustBill(long operatorId,long billId,List<com.baiyue.finance.controller.PlatformFinanceController.BillLineInput> lines,int expectedVersion,String reason,String key){ Long tenantId=billTenant(billId); Long replay=reserve(tenantId,operatorId,"BILL_ADJUST",key,Map.of("billId",billId,"expectedVersion",expectedVersion,"reason",reason,"lines",lines)); if(replay!=null) return bill(tenantId,replay); if (lines == null || lines.isEmpty()) throw bad("调整后至少需要一条费用明细"); Map<String,Object> current = bill(tenantId,billId); snapshot(tenantId,billId,expectedVersion,reason,current,operatorId); BigDecimal total = lineTotal(lines); BigDecimal received = new BigDecimal(current.get("received_amount").toString()); if (total.compareTo(received) < 0) throw bad("调整金额不能低于已收金额"); BigDecimal outstanding=total.subtract(received); String status=outstanding.signum()==0 ? "PAID" : (received.signum()==0 ? "ISSUED" : "PARTIALLY_PAID"); int c=jdbc.update("UPDATE fin_bill SET remark=?,total_amount=?,outstanding_amount=?,status=?,version=version+1,updated_by=? WHERE id=? AND tenant_id=? AND status IN ('ISSUED','PARTIALLY_PAID','PAID') AND version=?",reason,total,outstanding,status,operatorId,billId,tenantId,expectedVersion); if(c!=1) throw bad("账单状态或版本不允许调整"); jdbc.update("DELETE FROM fin_bill_line WHERE tenant_id=? AND bill_id=?",tenantId,billId); insertLines(tenantId,billId,lines); complete(tenantId,operatorId,"BILL_ADJUST",key,billId); return bill(tenantId,billId); }
|
||||
public Map<String,Object> adjustBill(long operatorId,long billId,List<com.baiyue.finance.controller.PlatformFinanceController.BillLineInput> lines,int expectedVersion,String reason,String key){ Long tenantId=billTenant(billId); Long replay=reserve(tenantId,operatorId,"BILL_ADJUST",key,Map.of("billId",billId,"expectedVersion",expectedVersion,"reason",reason,"lines",lines)); if(replay!=null) return bill(tenantId,replay); if (lines == null || lines.isEmpty()) throw bad("调整后至少需要一条费用明细"); Map<String,Object> current = bill(tenantId,billId); snapshot(tenantId,billId,expectedVersion,reason,current,operatorId); BigDecimal total = lineTotal(lines); BigDecimal received = new BigDecimal(current.get("received_amount").toString()); if (total.compareTo(received) < 0) throw bad("调整金额不能低于已收金额"); BigDecimal outstanding=total.subtract(received); String status=outstanding.signum()==0 ? "PAID" : (received.signum()==0 ? "ISSUED" : "PARTIALLY_PAID"); int c=jdbc.update("UPDATE fin_bill SET remark=?,total_amount=?,outstanding_amount=?,status=?,version=version+1,updated_by=?,updated_by_name=? WHERE id=? AND tenant_id=? AND status IN ('ISSUED','PARTIALLY_PAID','PAID') AND version=?",reason,total,outstanding,status,operatorId,FinanceOperatorSnapshot.currentName(operatorId),billId,tenantId,expectedVersion); if(c!=1) throw bad("账单状态或版本不允许调整"); jdbc.update("DELETE FROM fin_bill_line WHERE tenant_id=? AND bill_id=?",tenantId,billId); insertLines(tenantId,billId,lines); complete(tenantId,operatorId,"BILL_ADJUST",key,billId); return bill(tenantId,billId); }
|
||||
@Transactional
|
||||
public Map<String,Object> voidBill(long operatorId,long billId,int expectedVersion,String reason,String key){ Long tenantId=billTenant(billId); Long replay=reserve(tenantId,operatorId,"BILL_VOID",key,Map.of("billId",billId,"expectedVersion",expectedVersion,"reason",reason)); if(replay!=null) return bill(tenantId,replay); if (!StringUtils.hasText(reason)) throw bad("作废原因不能为空"); int c=jdbc.update("UPDATE fin_bill SET status='VOIDED',voided_at=?,voided_by=?,void_reason=?,updated_by=?,version=version+1 WHERE id=? AND tenant_id=? AND status IN ('DRAFT','ISSUED') AND received_amount=0 AND version=?",LocalDateTime.now(),operatorId,reason.trim(),operatorId,billId,tenantId,expectedVersion); if(c!=1) throw bad("已有收款、账单状态或版本不允许作废"); complete(tenantId,operatorId,"BILL_VOID",key,billId); return bill(tenantId,billId); }
|
||||
public Map<String,Object> voidBill(long operatorId,long billId,int expectedVersion,String reason,String key){ Long tenantId=billTenant(billId); Long replay=reserve(tenantId,operatorId,"BILL_VOID",key,Map.of("billId",billId,"expectedVersion",expectedVersion,"reason",reason)); if(replay!=null) return bill(tenantId,replay); if (!StringUtils.hasText(reason)) throw bad("作废原因不能为空"); int c=jdbc.update("UPDATE fin_bill SET status='VOIDED',voided_at=?,voided_by=?,void_reason=?,updated_by=?,updated_by_name=?,version=version+1 WHERE id=? AND tenant_id=? AND status IN ('DRAFT','ISSUED') AND received_amount=0 AND version=?",LocalDateTime.now(),operatorId,reason.trim(),operatorId,FinanceOperatorSnapshot.currentName(operatorId),billId,tenantId,expectedVersion); if(c!=1) throw bad("已有收款、账单状态或版本不允许作废"); complete(tenantId,operatorId,"BILL_VOID",key,billId); return bill(tenantId,billId); }
|
||||
private long billTenant(long billId){ Long t=jdbc.queryForObject("SELECT tenant_id FROM fin_bill WHERE id=?",Long.class,billId); if(t==null) throw bad("账单不存在"); return t; }
|
||||
private void snapshot(long tenantId,long billId,int version,String reason,Map<String,Object> bill,long operatorId){
|
||||
var lines=jdbc.queryForList("SELECT * FROM fin_bill_line WHERE tenant_id=? AND bill_id=? ORDER BY id",tenantId,billId);
|
||||
jdbc.update("INSERT INTO fin_bill_revision(tenant_id,bill_id,version,reason,bill_snapshot,line_snapshot,created_by) VALUES(?,?,?,?,?,?,?)",tenantId,billId,version,reason,json(bill),json(lines),operatorId);
|
||||
jdbc.update("INSERT INTO fin_bill_revision(tenant_id,bill_id,version,reason,bill_snapshot,line_snapshot,created_by,created_by_name) VALUES(?,?,?,?,?,?,?,?)",tenantId,billId,version,reason,json(bill),json(lines),operatorId,FinanceOperatorSnapshot.currentName(operatorId));
|
||||
}
|
||||
private BigDecimal lineTotal(List<com.baiyue.finance.controller.PlatformFinanceController.BillLineInput> lines) { if (lines == null || lines.isEmpty()) throw bad("账单至少需要一条费用明细"); java.util.Set<String> sources = new java.util.HashSet<>(); return lines.stream().map(l -> { if (l.sourceFeeId() <= 0 || l.sourceFeeVersion() <= 0) throw bad("费用来源版本无效"); if (!sources.add(l.sourceFeeId()+":"+l.sourceFeeVersion())) throw bad("重复费用来源不允许合并"); if (l.amount() == null || l.amount().signum() < 0) throw bad("费用金额不能为负数"); return l.amount(); }).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||
private void replaceDraftLines(long tenantId, long billId, List<com.baiyue.finance.controller.PlatformFinanceController.BillLineInput> lines) { lineTotal(lines); Map<String,Object> current=bill(tenantId,billId); if (!"DRAFT".equals(current.get("status"))) throw bad("仅草稿账单可修改"); jdbc.update("DELETE FROM fin_bill_line WHERE tenant_id=? AND bill_id=?",tenantId,billId); insertLines(tenantId,billId,lines); }
|
||||
|
||||
@@ -176,18 +176,18 @@ public class FinanceSettlementService {
|
||||
"WHERE sl.tenant_id=? AND sl.settlement_id=? ORDER BY sl.id", summary.customerId(), settlementId)
|
||||
.stream().map(source -> line(source, summary)).toList();
|
||||
List<SettlementTransaction> transactions = new ArrayList<>(jdbc.queryForList(
|
||||
"SELECT id,transaction_type,transaction_date,amount,method,reference_no,voucher_file_ids,created_by,created_at,remark " +
|
||||
"SELECT id,transaction_type,transaction_date,amount,method,reference_no,voucher_file_ids,created_by,created_by_name,created_at,remark " +
|
||||
"FROM fin_settlement_payment WHERE tenant_id=? AND settlement_id=? ORDER BY created_at,id",
|
||||
summary.customerId(), settlementId).stream().map(this::transaction).toList());
|
||||
if ("RECEIVABLE".equals(summary.direction()) && summary.reconciliationStatementId() != null) {
|
||||
transactions.addAll(jdbc.queryForList(
|
||||
"SELECT r.id,'RECEIPT' transaction_type,DATE(r.received_at) transaction_date,rb.applied_amount amount,r.payment_method method,r.voucher_no reference_no,r.voucher_file_ids,r.created_by,r.created_at,r.remark " +
|
||||
"SELECT r.id,'RECEIPT' transaction_type,DATE(r.received_at) transaction_date,rb.applied_amount amount,r.payment_method method,r.voucher_no reference_no,r.voucher_file_ids,r.created_by,r.created_by_name,r.created_at,r.remark " +
|
||||
"FROM fin_receipt_bill rb JOIN fin_receipt r ON r.id=rb.receipt_id AND r.tenant_id=rb.tenant_id " +
|
||||
"WHERE rb.tenant_id=? AND rb.bill_id=? AND r.status='CONFIRMED' ORDER BY r.created_at,r.id",
|
||||
summary.customerId(), summary.reconciliationStatementId()).stream().map(this::transaction).toList());
|
||||
}
|
||||
List<Map<String, Object>> audits = jdbc.queryForList(
|
||||
"SELECT action,reason,before_data,after_data,operator_id,created_at FROM fin_audit_log " +
|
||||
"SELECT action,reason,before_data,after_data,operator_id,operator_name_snapshot,created_at FROM fin_audit_log " +
|
||||
"WHERE tenant_id=? AND resource_type='FIN_SETTLEMENT' AND resource_id=? ORDER BY created_at,id",
|
||||
summary.customerId(), settlementId);
|
||||
List<SettlementRevision> revisions = new ArrayList<>();
|
||||
@@ -317,10 +317,11 @@ public class FinanceSettlementService {
|
||||
private void insertPayment(long customerId, long settlementId, CreateSettlementPaymentRequest request,
|
||||
long operatorId) {
|
||||
jdbc.update("INSERT INTO fin_settlement_payment(tenant_id,settlement_id,transaction_type,transaction_no," +
|
||||
"transaction_date,amount,currency_code,method,reference_no,voucher_file_ids,remark,created_by,created_at) " +
|
||||
"VALUES(?,?,'PAYMENT',?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP(3))",
|
||||
"transaction_date,amount,currency_code,method,reference_no,voucher_file_ids,remark,created_by,created_by_name,created_at) " +
|
||||
"VALUES(?,?,'PAYMENT',?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP(3))",
|
||||
customerId, settlementId, documentNo("PAY"), request.transactionDate(), request.amount(), CNY,
|
||||
request.method(), blank(request.referenceNo()), json(request.voucherFileIds()), blank(request.remark()), operatorId);
|
||||
request.method(), blank(request.referenceNo()), json(request.voucherFileIds()), blank(request.remark()),
|
||||
operatorId, FinanceOperatorSnapshot.currentName(operatorId));
|
||||
}
|
||||
|
||||
private void validatePayableRequest(CreatePayableSettlementRequest request) {
|
||||
@@ -418,7 +419,8 @@ public class FinanceSettlementService {
|
||||
jdbc.update("INSERT INTO fin_audit_log(tenant_id,resource_type,resource_id,action,reason,before_data,after_data," +
|
||||
"operator_id,operator_name_snapshot,request_id,trace_id) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
|
||||
customerId, "FIN_SETTLEMENT", resourceId, action, reason, before == null ? null : json(before),
|
||||
json(after), operatorId, "operator-" + operatorId, MDC.get("requestId"), MDC.get("traceId"));
|
||||
json(after), operatorId, FinanceOperatorSnapshot.requiredName(operatorId),
|
||||
MDC.get("requestId"), MDC.get("traceId"));
|
||||
}
|
||||
|
||||
private SettlementSummary summary(Map<String, Object> row) {
|
||||
@@ -454,6 +456,7 @@ public class FinanceSettlementService {
|
||||
return new SettlementTransaction(number(row, "id").longValue(), text(row, "transaction_type"),
|
||||
date(row, "transaction_date"), decimal(row, "amount"), text(row, "method"), text(row, "reference_no"),
|
||||
stringList(text(row, "voucher_file_ids")), number(row, "created_by").longValue(),
|
||||
text(row, "created_by_name"),
|
||||
time(row, "created_at"), text(row, "remark"));
|
||||
}
|
||||
|
||||
@@ -462,6 +465,7 @@ public class FinanceSettlementService {
|
||||
JsonNode after = parseTree(text(row, "after_data"));
|
||||
return new SettlementRevision(revisionNo, text(row, "action"), jsonDecimal(before, "totalAmount"),
|
||||
jsonDecimal(after, "totalAmount"), text(row, "reason"), number(row, "operator_id").longValue(),
|
||||
text(row, "operator_name_snapshot"),
|
||||
time(row, "created_at"));
|
||||
}
|
||||
|
||||
|
||||
+26
-16
@@ -65,7 +65,7 @@ public class ReconciliationStatementService {
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
jdbc.update(connection -> {
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"INSERT INTO fin_bill(tenant_id,bill_no,subject_id,customer_name_snapshot,currency_code,bill_date,due_date,status,total_amount,received_amount,outstanding_amount,remark,created_by,updated_by) VALUES(?,?,?,?,? ,?,?,'DRAFT',?,0,?,?,?,?)",
|
||||
"INSERT INTO fin_bill(tenant_id,bill_no,subject_id,customer_name_snapshot,currency_code,bill_date,due_date,status,total_amount,received_amount,outstanding_amount,remark,created_by,created_by_name,updated_by,updated_by_name) VALUES(?,?,?,?,? ,?,?,'DRAFT',?,0,?,?,?,?,?,?)",
|
||||
Statement.RETURN_GENERATED_KEYS);
|
||||
statement.setLong(1, request.customerId());
|
||||
statement.setString(2, "REC-PENDING-" + UUID.randomUUID());
|
||||
@@ -78,7 +78,9 @@ public class ReconciliationStatementService {
|
||||
statement.setBigDecimal(9, total);
|
||||
statement.setString(10, request.remark());
|
||||
statement.setLong(11, operatorId);
|
||||
statement.setLong(12, operatorId);
|
||||
statement.setString(12, FinanceOperatorSnapshot.currentName(operatorId));
|
||||
statement.setLong(13, operatorId);
|
||||
statement.setString(14, FinanceOperatorSnapshot.currentName(operatorId));
|
||||
return statement;
|
||||
}, holder);
|
||||
if (holder.getKey() == null) throw error("FIN_001", "对账单主键生成失败");
|
||||
@@ -115,9 +117,10 @@ public class ReconciliationStatementService {
|
||||
if ("VOID".equals(action)) requireVoidable(before.customerId(), statementId);
|
||||
if ("REOPEN".equals(action) && "VOIDED".equals(before.status())) reacquireSources(before.customerId(), statementId);
|
||||
snapshot(before, request.reason(), operatorId);
|
||||
int changed = jdbc.update("UPDATE fin_bill SET status=?,issued_at=CASE WHEN ?='ISSUED' THEN CURRENT_TIMESTAMP(3) ELSE issued_at END,issued_by=CASE WHEN ?='ISSUED' THEN ? ELSE issued_by END,voided_at=CASE WHEN ?='VOIDED' THEN CURRENT_TIMESTAMP(3) ELSE voided_at END,voided_by=CASE WHEN ?='VOIDED' THEN ? ELSE voided_by END,void_reason=CASE WHEN ?='VOIDED' THEN ? ELSE void_reason END,updated_by=?,version=version+1 WHERE id=? AND tenant_id=? AND status=? AND version=?",
|
||||
int changed = jdbc.update("UPDATE fin_bill SET status=?,issued_at=CASE WHEN ?='ISSUED' THEN CURRENT_TIMESTAMP(3) ELSE issued_at END,issued_by=CASE WHEN ?='ISSUED' THEN ? ELSE issued_by END,voided_at=CASE WHEN ?='VOIDED' THEN CURRENT_TIMESTAMP(3) ELSE voided_at END,voided_by=CASE WHEN ?='VOIDED' THEN ? ELSE voided_by END,void_reason=CASE WHEN ?='VOIDED' THEN ? ELSE void_reason END,updated_by=?,updated_by_name=?,version=version+1 WHERE id=? AND tenant_id=? AND status=? AND version=?",
|
||||
transition.to(), transition.to(), transition.to(), operatorId, transition.to(), transition.to(), operatorId,
|
||||
transition.to(), request.reason(), operatorId, statementId, before.customerId(), transition.from(), request.expectedVersion());
|
||||
transition.to(), request.reason(), operatorId, FinanceOperatorSnapshot.currentName(operatorId),
|
||||
statementId, before.customerId(), transition.from(), request.expectedVersion());
|
||||
if (changed != 1) throw error("FIN_409", "对账单状态或版本已变化");
|
||||
if ("ISSUE".equals(action)) {
|
||||
jdbc.update("UPDATE fin_fee_source f JOIN fin_bill_line l ON l.source_id=f.id SET f.status='BILLED' WHERE l.tenant_id=? AND l.bill_id=? AND f.customer_id=? AND f.status='LOCKED'",
|
||||
@@ -146,8 +149,9 @@ public class ReconciliationStatementService {
|
||||
BigDecimal.class, before.customerId(), statementId);
|
||||
if (total == null) total = BigDecimal.ZERO;
|
||||
if (total.compareTo(before.received()) < 0) throw error("FIN_409", "调整后金额不得低于已收金额");
|
||||
int changed = jdbc.update("UPDATE fin_bill SET total_amount=?,outstanding_amount=?-received_amount,updated_by=?,version=version+1 WHERE id=? AND tenant_id=? AND status='ISSUED' AND version=?",
|
||||
total, total, operatorId, statementId, before.customerId(), request.expectedVersion());
|
||||
int changed = jdbc.update("UPDATE fin_bill SET total_amount=?,outstanding_amount=?-received_amount,updated_by=?,updated_by_name=?,version=version+1 WHERE id=? AND tenant_id=? AND status='ISSUED' AND version=?",
|
||||
total, total, operatorId, FinanceOperatorSnapshot.currentName(operatorId), statementId,
|
||||
before.customerId(), request.expectedVersion());
|
||||
if (changed != 1) throw error("FIN_409", "对账单状态或版本已变化");
|
||||
ReconciliationDetail after = detail(before.customerId(), statementId);
|
||||
audit(before.customerId(), statementId, "RECONCILIATION_ADJUST", request.reason(), before, after, operatorId);
|
||||
@@ -162,15 +166,16 @@ public class ReconciliationStatementService {
|
||||
"SELECT f.id,l.id statement_line_id,f.source_type,f.source_fee_id,f.source_fee_version,f.source_hash,f.direction,f.customer_id,f.order_id,f.inbound_id,f.container_id,f.fee_item_code,f.fee_item_name,f.amount source_amount,l.amount statement_amount,l.adjustment_reason,f.currency_code,f.status FROM fin_bill_line l JOIN fin_fee_source f ON f.id=l.source_id AND f.customer_id=l.tenant_id WHERE l.tenant_id=? AND l.bill_id=? ORDER BY l.id",
|
||||
(rs, row) -> line(rs), customerId, statementId);
|
||||
List<PaymentHistory> payments = jdbc.query(
|
||||
"SELECT r.id,r.received_at,rb.applied_amount,r.payment_method,r.voucher_no,r.created_by,r.created_at,r.remark FROM fin_receipt_bill rb JOIN fin_receipt r ON r.id=rb.receipt_id AND r.tenant_id=rb.tenant_id WHERE rb.tenant_id=? AND rb.bill_id=? AND r.status='CONFIRMED' ORDER BY r.received_at,r.id",
|
||||
"SELECT r.id,r.received_at,rb.applied_amount,r.payment_method,r.voucher_no,r.created_by,r.created_by_name,r.created_at,r.remark FROM fin_receipt_bill rb JOIN fin_receipt r ON r.id=rb.receipt_id AND r.tenant_id=rb.tenant_id WHERE rb.tenant_id=? AND rb.bill_id=? AND r.status='CONFIRMED' ORDER BY r.received_at,r.id",
|
||||
(rs, row) -> payment(rs), customerId, statementId);
|
||||
List<ReconciliationRevision> revisions = jdbc.query(
|
||||
"SELECT action,reason,before_data,after_data,operator_id,created_at FROM fin_audit_log WHERE tenant_id=? AND resource_type='RECONCILIATION_STATEMENT' AND resource_id=? AND before_data IS NOT NULL ORDER BY created_at,id",
|
||||
"SELECT action,reason,before_data,after_data,operator_id,operator_name_snapshot,created_at FROM fin_audit_log WHERE tenant_id=? AND resource_type='RECONCILIATION_STATEMENT' AND resource_id=? AND before_data IS NOT NULL ORDER BY created_at,id",
|
||||
(rs, row) -> revision(rs, row + 1), customerId, statementId);
|
||||
return new ReconciliationDetail(bill.id(), bill.number(), bill.customerId(), bill.customerName(),
|
||||
metadata.periodStart(), metadata.periodEnd(), bill.creditDays(), "CNY", bill.total(), bill.received(),
|
||||
bill.outstanding(), bill.outstanding().signum() == 0, reconciliationStatus(bill.status()), bill.version(),
|
||||
bill.createdBy(), bill.createdAt(), bill.updatedBy(), bill.updatedAt(), bill.remark(), lines, payments, revisions);
|
||||
bill.createdBy(), bill.createdByName(), bill.createdAt(), bill.updatedBy(), bill.updatedByName(),
|
||||
bill.updatedAt(), bill.remark(), lines, payments, revisions);
|
||||
}
|
||||
|
||||
public ReconciliationDetail detailForPlatform(long statementId) {
|
||||
@@ -248,8 +253,9 @@ public class ReconciliationStatementService {
|
||||
|
||||
private void snapshot(Bill bill, String reason, long operatorId) {
|
||||
List<Map<String, Object>> lines = jdbc.queryForList("SELECT * FROM fin_bill_line WHERE tenant_id=? AND bill_id=? ORDER BY id", bill.customerId(), bill.id());
|
||||
jdbc.update("INSERT INTO fin_bill_revision(tenant_id,bill_id,version,reason,bill_snapshot,line_snapshot,created_by) VALUES(?,?,?,?,?,?,?)",
|
||||
bill.customerId(), bill.id(), bill.version(), reason, json(bill), json(lines), operatorId);
|
||||
jdbc.update("INSERT INTO fin_bill_revision(tenant_id,bill_id,version,reason,bill_snapshot,line_snapshot,created_by,created_by_name) VALUES(?,?,?,?,?,?,?,?)",
|
||||
bill.customerId(), bill.id(), bill.version(), reason, json(bill), json(lines), operatorId,
|
||||
FinanceOperatorSnapshot.currentName(operatorId));
|
||||
}
|
||||
|
||||
private void requireVoidable(long customerId, long statementId) {
|
||||
@@ -301,7 +307,8 @@ public class ReconciliationStatementService {
|
||||
Object before, Object after, long operatorId) {
|
||||
jdbc.update("INSERT INTO fin_audit_log(tenant_id,resource_type,resource_id,action,reason,before_data,after_data,operator_id,operator_name_snapshot,request_id,trace_id) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
|
||||
customerId, "RECONCILIATION_STATEMENT", statementId, action, reason, before == null ? null : json(before),
|
||||
after == null ? null : json(after), operatorId, "operator-" + operatorId, MDC.get("requestId"), MDC.get("traceId"));
|
||||
after == null ? null : json(after), operatorId, FinanceOperatorSnapshot.requiredName(operatorId),
|
||||
MDC.get("requestId"), MDC.get("traceId"));
|
||||
}
|
||||
|
||||
private static void validateCreate(CreateReconciliationRequest request) {
|
||||
@@ -396,12 +403,14 @@ public class ReconciliationStatementService {
|
||||
Timestamp received = rs.getTimestamp("received_at");
|
||||
return new PaymentHistory(rs.getLong("id"), "RECEIPT", received.toLocalDateTime().toLocalDate(),
|
||||
rs.getBigDecimal("applied_amount"), rs.getString("payment_method"), rs.getString("voucher_no"),
|
||||
rs.getLong("created_by"), rs.getTimestamp("created_at").toLocalDateTime(), rs.getString("remark"));
|
||||
rs.getLong("created_by"), rs.getString("created_by_name"),
|
||||
rs.getTimestamp("created_at").toLocalDateTime(), rs.getString("remark"));
|
||||
}
|
||||
|
||||
private ReconciliationRevision revision(ResultSet rs, int revisionNo) throws SQLException {
|
||||
return new ReconciliationRevision(revisionNo, rs.getString("action"), amountFromJson(rs.getString("before_data")),
|
||||
amountFromJson(rs.getString("after_data")), rs.getString("reason"), rs.getLong("operator_id"),
|
||||
rs.getString("operator_name_snapshot"),
|
||||
rs.getTimestamp("created_at").toLocalDateTime());
|
||||
}
|
||||
|
||||
@@ -421,7 +430,8 @@ public class ReconciliationStatementService {
|
||||
rs.getString("customer_name_snapshot"), rs.getString("status"), rs.getBigDecimal("total_amount"),
|
||||
rs.getBigDecimal("received_amount"), rs.getBigDecimal("outstanding_amount"), rs.getInt("version"),
|
||||
rs.getInt("credit_days"), rs.getString("remark"), rs.getLong("created_by"),
|
||||
rs.getTimestamp("created_at").toLocalDateTime(), rs.getLong("updated_by"),
|
||||
rs.getString("created_by_name"), rs.getTimestamp("created_at").toLocalDateTime(), rs.getLong("updated_by"),
|
||||
rs.getString("updated_by_name"),
|
||||
rs.getTimestamp("updated_at").toLocalDateTime());
|
||||
}
|
||||
|
||||
@@ -462,6 +472,6 @@ public class ReconciliationStatementService {
|
||||
String feeItemName, BigDecimal amount) {}
|
||||
private record Bill(long id, long customerId, String number, String customerName, String status,
|
||||
BigDecimal total, BigDecimal received, BigDecimal outstanding, int version,
|
||||
int creditDays, String remark, long createdBy, LocalDateTime createdAt,
|
||||
long updatedBy, LocalDateTime updatedAt) {}
|
||||
int creditDays, String remark, long createdBy, String createdByName,
|
||||
LocalDateTime createdAt, long updatedBy, String updatedByName, LocalDateTime updatedAt) {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user