feat: add signed PostgreSQL release updates

This commit is contained in:
Qiufeng
2026-08-03 23:45:32 +08:00
parent 2524a37a07
commit f6e22cb670
99 changed files with 44671 additions and 284 deletions
@@ -113,10 +113,11 @@ public class FinancingController {
public record FinancingRequest(
String code, String lender, String financingType, Double creditLimit, Double amount,
String currency, Double rate, String startDate, String endDate, Integer termMonths,
String currency, BigDecimal rate, String startDate, String endDate, Integer termMonths,
String repayMethod, String status, String companySubject, String purpose, String owner) {
}
@Transactional
@PostMapping
public ApiResp<Financing> create(@RequestBody FinancingRequest req) {
if (req.lender() == null || req.lender().isBlank()) {
@@ -143,6 +144,7 @@ public class FinancingController {
return ApiResp.ok(financingRepo.save(f));
}
@Transactional
@PatchMapping("/{id}")
public ApiResp<Financing> update(@PathVariable Long id, @RequestBody FinancingRequest req) {
Financing f = financingRepo.findById(id)
@@ -165,7 +167,6 @@ public class FinancingController {
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp<Void> delete(@PathVariable Long id) {
if (!financingRepo.existsById(id)) {
throw new NotFoundException("financing not found: " + id);
@@ -188,7 +189,6 @@ public class FinancingController {
* 已有任意期次「已还」时拒绝重算,避免抹掉还款历史。
*/
@PostMapping("/{id}/schedule")
@Transactional
public ApiResp<List<RepaymentPlan>> schedule(@PathVariable Long id) {
Financing f = financingRepo.findById(id)
.orElseThrow(() -> new NotFoundException("financing not found: " + id));
@@ -200,7 +200,7 @@ public class FinancingController {
planRepo.deleteByFinancingId(id);
int term = f.getTermMonths() == null || f.getTermMonths() <= 0 ? 12 : f.getTermMonths();
double annualRate = f.getRate() == null ? 0 : f.getRate();
double annualRate = f.getRate() == null ? 0 : f.getRate().doubleValue();
BigDecimal principalTotal = Money.nz(f.getAmount());
String method = f.getRepayMethod() == null ? "等额本息" : f.getRepayMethod();
LocalDate start = parseDateOrNull(f.getStartDate());
@@ -288,7 +288,6 @@ public class FinancingController {
* 这条联动把"融资还款"打通到结算/支付链(与需求"还款联动结算中心生成付款单"一致)。
*/
@PostMapping("/repayments/{planId}/pay")
@Transactional
public ApiResp<RepaymentPlan> payRepayment(@PathVariable Long planId) {
RepaymentPlan plan = planRepo.findById(planId)
.orElseThrow(() -> new NotFoundException("repayment plan not found: " + planId));
@@ -552,7 +551,7 @@ public class FinancingController {
rows.add(new DebtLedgerRow(f.getId(), f.getCode(), f.getLender(), f.getFinancingType(),
f.getCompanySubject(), f.getStatus(), principal.doubleValue(),
paid.doubleValue(), unpaid.doubleValue(),
f.getRate() == null ? 0 : f.getRate(),
f.getRate() == null ? 0.0 : f.getRate().doubleValue(),
f.getEndDate() == null ? "" : f.getEndDate(),
daysToMaturity == Long.MAX_VALUE ? -999 : daysToMaturity,
maturityLevel));
@@ -561,7 +560,7 @@ public class FinancingController {
totalUnpaid = Money.add(totalUnpaid, unpaid);
if (f.getRate() != null) {
weightedRateSum = Money.add(weightedRateSum,
unpaid.multiply(BigDecimal.valueOf(f.getRate())));
unpaid.multiply(f.getRate()));
}
}
@@ -590,7 +589,6 @@ public class FinancingController {
* 若余量 < 0 则拒绝提交,给出明确提示,要求先调整授信或融资金额。
*/
@PostMapping("/{id}/submit-approval")
@Transactional
public ApiResp<FormInstance> submitApproval(@PathVariable Long id,
@RequestBody(required = false) ApprovalSubmitRequest req) {
Financing f = financingRepo.findById(id)
@@ -791,7 +789,7 @@ public class FinancingController {
rateMap.put("7%以上", new long[]{0, 0, 0});
for (Financing f : all) {
if (List.of("已结清", "已驳回").contains(nvl(f.getStatus()))) continue;
double r = f.getRate() == null ? 0 : f.getRate();
double r = f.getRate() == null ? 0 : f.getRate().doubleValue();
String bucket = r < 3 ? "3%以下" : r < 5 ? "3%-5%" : r < 7 ? "5%-7%" : "7%以上";
long[] slot = rateMap.get(bucket);
slot[0]++;
@@ -807,7 +805,7 @@ public class FinancingController {
// --- 高成本融资识别(年化利率 > benchmarkRate ---
List<Map<String, Object>> highCost = all.stream()
.filter(f -> !List.of("已结清", "已驳回").contains(nvl(f.getStatus())))
.filter(f -> f.getRate() != null && f.getRate() > benchmarkRate)
.filter(f -> f.getRate() != null && f.getRate().doubleValue() > benchmarkRate)
.map(f -> {
Map<String, Object> m = new LinkedHashMap<>();
m.put("financingId", f.getId());
@@ -816,8 +814,9 @@ public class FinancingController {
m.put("financingType", f.getFinancingType());
m.put("amount", Money.nz(f.getAmount()).doubleValue());
m.put("rate", f.getRate());
m.put("excessBps", Math.round((f.getRate() - benchmarkRate) * 100));
m.put("suggestion", f.getRate() - benchmarkRate > 2 ? "建议择机置换或提前还款" : "关注,可在续授信时争取降利率");
double rateVal = f.getRate().doubleValue();
m.put("excessBps", Math.round((rateVal - benchmarkRate) * 100));
m.put("suggestion", rateVal - benchmarkRate > 2 ? "建议择机置换或提前还款" : "关注,可在续授信时争取降利率");
return m;
})
.toList();
@@ -928,7 +927,6 @@ public class FinancingController {
}
@PostMapping("/{id}/legal-reviews/link")
@Transactional
public ApiResp<LegalRiskEvent> linkLegalReview(@PathVariable Long id,
@RequestBody LinkLegalReviewRequest req) {
if (!financingRepo.existsById(id)) {
@@ -957,7 +955,6 @@ public class FinancingController {
}
@PostMapping("/{id}/push-cost-alloc")
@Transactional
public ApiResp<PmtCostAllocRule> pushCostAlloc(@PathVariable Long id,
@RequestBody(required = false) PushCostAllocRequest req) {
Financing f = financingRepo.findById(id)
@@ -982,7 +979,7 @@ public class FinancingController {
.reduce(BigDecimal.ZERO, Money::add);
// 若无还款计划,按月息估算(本金 × 年化利率 / 12)
if (periodInterest.signum() == 0) {
double annualRate = f.getRate() == null ? 0.0 : f.getRate();
double annualRate = f.getRate() == null ? 0.0 : f.getRate().doubleValue();
periodInterest = Money.of(Money.nz(f.getAmount()).doubleValue() * annualRate / 100.0 / 12.0);
}
@@ -77,6 +77,7 @@ public class FinancingPolicyController {
String description, String status, String createdBy) {
}
@Transactional
@PostMapping
public ApiResp<FinancingPolicy> create(@RequestBody PolicyRequest req) {
if (req.ruleName() == null || req.ruleName().isBlank()) {
@@ -99,6 +100,7 @@ public class FinancingPolicyController {
return ApiResp.ok(policyRepo.save(p));
}
@Transactional
@PatchMapping("/{id}")
public ApiResp<FinancingPolicy> update(@PathVariable Long id, @RequestBody PolicyRequest req) {
FinancingPolicy p = policyRepo.findById(id)
@@ -115,7 +117,6 @@ public class FinancingPolicyController {
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp<Void> delete(@PathVariable Long id) {
if (!policyRepo.existsById(id)) {
throw new NotFoundException("融资政策规则不存在: " + id);
@@ -148,6 +149,7 @@ public class FinancingPolicyController {
* - 担保方式限制:预留(financing 暂无担保字段,返回通过)。
* hardBlock=true 规则违规时,整体 overallPassed=false,调用方可据此阻断提交。
*/
@Transactional
@PostMapping("/{financingId}/compliance-check")
public ApiResp<ComplianceCheckResult> complianceCheck(@PathVariable Long financingId) {
Financing f = financingRepo.findById(financingId)
@@ -176,7 +178,7 @@ public class FinancingPolicyController {
}
}
case "利率上限" -> {
double rate = f.getRate() == null ? 0 : f.getRate();
double rate = f.getRate() == null ? 0 : f.getRate().doubleValue();
double limit = rule.getLimitValue() == null ? 0 : rule.getLimitValue().doubleValue();
if (limit > 0 && rate > limit) {
passed = false;
@@ -114,7 +114,7 @@ public class PmtContractFeeItemController {
LocalDate maturity = LocalDate.parse(contract.getMaturityDate());
long days = java.time.temporal.ChronoUnit.DAYS.between(sign, maturity);
if (days > 0) {
BigDecimal rate = BigDecimal.valueOf(contract.getContractRate()).divide(
BigDecimal rate = contract.getContractRate().divide(
BigDecimal.valueOf(100), 10, java.math.RoundingMode.HALF_UP);
BigDecimal years = BigDecimal.valueOf(days).divide(
BigDecimal.valueOf(365), 10, java.math.RoundingMode.HALF_UP);
@@ -164,7 +164,6 @@ public class PmtContractFeeItemController {
}
@PostMapping
@Transactional
public ApiResp<PmtContractFeeItem> create(@RequestBody FeeItemRequest req) {
if (req.contractId() == null) {
throw new ApiException(400, "contractId 不能为空");
@@ -197,7 +196,6 @@ public class PmtContractFeeItemController {
}
@PatchMapping("/{id}")
@Transactional
public ApiResp<PmtContractFeeItem> update(@PathVariable Long id,
@RequestBody FeeItemRequest req) {
PmtContractFeeItem item = feeRepo.findById(id)
@@ -221,7 +219,6 @@ public class PmtContractFeeItemController {
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp<Void> delete(@PathVariable Long id) {
PmtContractFeeItem item = feeRepo.findById(id)
.orElseThrow(() -> new NotFoundException("费用明细不存在: " + id));
@@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
@@ -73,11 +74,12 @@ public class PmtFinancingContractController {
public record ContractRequest(
Long financingId, String contractNo, String lender,
Double contractAmount, Double contractRate, String signDate, String maturityDate,
Double contractAmount, BigDecimal contractRate, String signDate, String maturityDate,
String repayMethod, String guaranteeType, String guaranteeDesc,
Double contractFee, String status, String remark, String owner) {
}
@Transactional
@PostMapping
public ApiResp<PmtFinancingContract> create(@RequestBody ContractRequest req) {
if (req.lender() == null || req.lender().isBlank()) {
@@ -115,6 +117,7 @@ public class PmtFinancingContractController {
return ApiResp.ok(contractRepo.save(c));
}
@Transactional
@PatchMapping("/{id}")
public ApiResp<PmtFinancingContract> update(@PathVariable Long id, @RequestBody ContractRequest req) {
PmtFinancingContract c = contractRepo.findById(id)
@@ -138,7 +141,6 @@ public class PmtFinancingContractController {
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp<Void> delete(@PathVariable Long id) {
PmtFinancingContract c = contractRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资合同不存在: " + id));
@@ -153,7 +155,6 @@ public class PmtFinancingContractController {
/** 签署:草稿 → 已签署。 */
@PostMapping("/{id}/sign")
@Transactional
public ApiResp<PmtFinancingContract> sign(@PathVariable Long id) {
PmtFinancingContract c = contractRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资合同不存在: " + id));
@@ -175,7 +176,6 @@ public class PmtFinancingContractController {
* - drawnAmount >= contractAmount → 已用款
*/
@PostMapping("/{id}/drawdown")
@Transactional
public ApiResp<PmtFinancingContract> drawdown(@PathVariable Long id, @RequestBody DrawdownRequest req) {
PmtFinancingContract c = contractRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资合同不存在: " + id));
@@ -219,7 +219,6 @@ public class PmtFinancingContractController {
/** 结清:将合同状态置「已结清」(通常在所有还款完成后调用)。 */
@PostMapping("/{id}/settle")
@Transactional
public ApiResp<PmtFinancingContract> settle(@PathVariable Long id) {
PmtFinancingContract c = contractRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资合同不存在: " + id));
@@ -232,7 +231,6 @@ public class PmtFinancingContractController {
/** 终止合同。 */
@PostMapping("/{id}/terminate")
@Transactional
public ApiResp<PmtFinancingContract> terminate(@PathVariable Long id) {
PmtFinancingContract c = contractRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资合同不存在: " + id));
@@ -82,7 +82,7 @@ public class PmtInternalTransController {
public record TransRequest(
String transType, String payerSubject, String receiverSubject,
Double amount, String bizDate, String reconPeriod, String summary,
Double interestRate, String interestFrom, String interestTo,
BigDecimal interestRate, String interestFrom, String interestTo,
String createdBy) {
}
@@ -91,7 +91,6 @@ public class PmtInternalTransController {
* 同时自动为付款方生成应付单(ArApItem T_AP)、为收款方生成应收单(ArApItem T_AR)。
*/
@PostMapping
@Transactional
public ApiResp<PmtInternalTrans> create(@RequestBody TransRequest req) {
if (req.payerSubject() == null || req.payerSubject().isBlank()) {
throw new ApiException(400, "付款方主体(payerSubject) 不能为空");
@@ -123,7 +122,7 @@ public class PmtInternalTransController {
// 利息自动计算(仅 transType=利息 且提供了利率和区间)
if ("利息".equals(t.getTransType()) && req.interestRate() != null
&& req.interestFrom() != null && req.interestTo() != null) {
BigDecimal interest = calcInterest(Money.of(req.amount()), req.interestRate(),
BigDecimal interest = calcInterest(Money.of(req.amount()), req.interestRate().doubleValue(),
req.interestFrom(), req.interestTo());
t.setInterestAmount(interest);
t.setAmount(interest); // 利息单,金额即利息
@@ -166,6 +165,7 @@ public class PmtInternalTransController {
return ApiResp.ok(transRepo.save(saved));
}
@Transactional
@PatchMapping("/{id}")
public ApiResp<PmtInternalTrans> update(@PathVariable Long id, @RequestBody TransRequest req) {
PmtInternalTrans t = transRepo.findById(id)
@@ -180,7 +180,6 @@ public class PmtInternalTransController {
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp<Void> delete(@PathVariable Long id) {
PmtInternalTrans t = transRepo.findById(id)
.orElseThrow(() -> new NotFoundException("内部往来单不存在: " + id));
@@ -202,7 +201,6 @@ public class PmtInternalTransController {
* 「有差异」状态需登记差异调整后方可结清。
*/
@PostMapping("/{id}/confirm")
@Transactional
public ApiResp<PmtInternalTrans> confirm(@PathVariable Long id, @RequestBody ConfirmRequest req) {
PmtInternalTrans t = transRepo.findById(id)
.orElseThrow(() -> new NotFoundException("内部往来单不存在: " + id));
@@ -235,7 +233,6 @@ public class PmtInternalTransController {
/** 标记差异已调整,进入可结清状态。 */
@PostMapping("/{id}/resolve-diff")
@Transactional
public ApiResp<PmtInternalTrans> resolveDiff(@PathVariable Long id,
@RequestBody Map<String, String> body) {
PmtInternalTrans t = transRepo.findById(id)
@@ -251,7 +248,6 @@ public class PmtInternalTransController {
/** 手动结清内部往来单(在「已确认」或「差异已调整」状态下可执行)。 */
@PostMapping("/{id}/settle")
@Transactional
public ApiResp<PmtInternalTrans> settle(@PathVariable Long id) {
PmtInternalTrans t = transRepo.findById(id)
.orElseThrow(() -> new NotFoundException("内部往来单不存在: " + id));
@@ -290,7 +286,7 @@ public class PmtInternalTransController {
// ---------- 利息计算(内部计息,按日) ----------
public record InterestCalcRequest(Double principal, Double annualRate, String from, String to) {
public record InterestCalcRequest(Double principal, BigDecimal annualRate, String from, String to) {
}
public record InterestCalcResult(double principal, double annualRate, String from, String to,
@@ -301,6 +297,7 @@ public class PmtInternalTransController {
* 内部计息预览(不落库):金额 × 年化利率 / 365 × 天数。
* 支持活期(上存利率)与贷款利率,利率单位 % ,如 3.5 表示 3.5%。
*/
@Transactional
@PostMapping("/calc-interest")
public ApiResp<InterestCalcResult> calcInterestPreview(@RequestBody InterestCalcRequest req) {
if (req.principal() == null || req.annualRate() == null
@@ -312,9 +309,10 @@ public class PmtInternalTransController {
if (days <= 0) {
throw new ApiException(400, "计息截止日必须晚于起始日");
}
BigDecimal interest = calcInterest(principal, req.annualRate(), req.from(), req.to());
double annualRateDouble = req.annualRate().doubleValue();
BigDecimal interest = calcInterest(principal, annualRateDouble, req.from(), req.to());
return ApiResp.ok(new InterestCalcResult(
principal.doubleValue(), req.annualRate(), req.from(), req.to(),
principal.doubleValue(), annualRateDouble, req.from(), req.to(),
days, interest.doubleValue()));
}
@@ -326,7 +324,6 @@ public class PmtInternalTransController {
* 凭证生成后状态流转为「已结清」(已转凭证即代表本期利息已处理完毕)。
*/
@PostMapping("/{id}/to-voucher")
@Transactional
public ApiResp<Voucher> toVoucher(@PathVariable Long id) {
PmtInternalTrans t = transRepo.findById(id)
.orElseThrow(() -> new NotFoundException("内部往来单不存在: " + id));
@@ -73,18 +73,17 @@ public class PmtLoanSchemeController {
public record SchemeRequest(
Long financingId, String institution, String loanType,
Double amount, Double annualRate, Integer termMonths, String repayMethod,
Double handlingFee, String guaranteeType, Double guaranteeRate,
Double amount, BigDecimal annualRate, Integer termMonths, String repayMethod,
Double handlingFee, String guaranteeType, BigDecimal guaranteeRate,
String drawdownConditions, String status, String remark, String owner) {
}
@PostMapping
@Transactional
public ApiResp<PmtLoanScheme> create(@RequestBody SchemeRequest req) {
if (req.institution() == null || req.institution().isBlank()) {
throw new ApiException(400, "报价机构名称(institution) 不能为空");
}
if (req.annualRate() == null || req.annualRate() < 0) {
if (req.annualRate() == null || req.annualRate().compareTo(BigDecimal.ZERO) < 0) {
throw new ApiException(400, "年化利率(annualRate) 不能为空且须 >= 0");
}
PmtLoanScheme s = new PmtLoanScheme();
@@ -96,7 +95,7 @@ public class PmtLoanSchemeController {
s.setRepayMethod(req.repayMethod() == null ? "等额本息" : req.repayMethod());
s.setHandlingFee(Money.of(req.handlingFee()));
s.setGuaranteeType(req.guaranteeType());
s.setGuaranteeRate(req.guaranteeRate() == null ? 0.0 : req.guaranteeRate());
s.setGuaranteeRate(req.guaranteeRate() == null ? BigDecimal.ZERO : req.guaranteeRate());
s.setDrawdownConditions(req.drawdownConditions());
s.setOwner(req.owner());
s.setStatus(req.status() == null || req.status().isBlank() ? "待比选" : req.status());
@@ -118,7 +117,6 @@ public class PmtLoanSchemeController {
}
@PatchMapping("/{id}")
@Transactional
public ApiResp<PmtLoanScheme> update(@PathVariable Long id, @RequestBody SchemeRequest req) {
PmtLoanScheme s = schemeRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资方案不存在: " + id));
@@ -144,7 +142,6 @@ public class PmtLoanSchemeController {
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp<Void> delete(@PathVariable Long id) {
PmtLoanScheme s = schemeRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资方案不存在: " + id));
@@ -162,7 +159,6 @@ public class PmtLoanSchemeController {
* 同时重算各方案的综合成本率(幂等)。
*/
@PostMapping("/rank")
@Transactional
public ApiResp<List<PmtLoanScheme>> rank(@RequestParam Long financingId) {
List<PmtLoanScheme> schemes = schemeRepo.findByFinancingId(financingId);
if (schemes.isEmpty()) {
@@ -172,9 +168,9 @@ public class PmtLoanSchemeController {
schemes.forEach(s -> s.setEffectiveCostRate(calcEffectiveCostRate(s)));
// 按综合成本升序排
schemes.sort((a, b) -> {
double ra = a.getEffectiveCostRate() == null ? Double.MAX_VALUE : a.getEffectiveCostRate();
double rb = b.getEffectiveCostRate() == null ? Double.MAX_VALUE : b.getEffectiveCostRate();
return Double.compare(ra, rb);
BigDecimal ra = a.getEffectiveCostRate() == null ? BigDecimal.valueOf(Double.MAX_VALUE) : a.getEffectiveCostRate();
BigDecimal rb = b.getEffectiveCostRate() == null ? BigDecimal.valueOf(Double.MAX_VALUE) : b.getEffectiveCostRate();
return ra.compareTo(rb);
});
for (int i = 0; i < schemes.size(); i++) {
schemes.get(i).setRank(i + 1);
@@ -188,7 +184,6 @@ public class PmtLoanSchemeController {
/** 标注推荐方案(首选)。同一 financingId 下只能有一个推荐方案;旧推荐自动清除。 */
@PostMapping("/{id}/recommend")
@Transactional
public ApiResp<PmtLoanScheme> recommend(@PathVariable Long id,
@RequestBody(required = false) RecommendRequest req) {
PmtLoanScheme s = schemeRepo.findById(id)
@@ -212,7 +207,6 @@ public class PmtLoanSchemeController {
* 同时回写 Financing 台账的 lender/rate/repayMethod,融资状态推进到「审批中」。
*/
@PostMapping("/{id}/select")
@Transactional
public ApiResp<PmtLoanScheme> select(@PathVariable Long id) {
PmtLoanScheme s = schemeRepo.findById(id)
.orElseThrow(() -> new NotFoundException("融资方案不存在: " + id));
@@ -258,9 +252,9 @@ public class PmtLoanSchemeController {
if (schemes.isEmpty()) {
return ApiResp.ok(new SchemeCompareSummary(schemes, 0, 0, ""));
}
double min = schemes.stream().mapToDouble(s -> s.getEffectiveCostRate() == null ? 0 : s.getEffectiveCostRate())
double min = schemes.stream().mapToDouble(s -> s.getEffectiveCostRate() == null ? 0 : s.getEffectiveCostRate().doubleValue())
.min().orElse(0);
double max = schemes.stream().mapToDouble(s -> s.getEffectiveCostRate() == null ? 0 : s.getEffectiveCostRate())
double max = schemes.stream().mapToDouble(s -> s.getEffectiveCostRate() == null ? 0 : s.getEffectiveCostRate().doubleValue())
.max().orElse(0);
String recommended = schemes.stream()
.filter(s -> Boolean.TRUE.equals(s.getRecommended()))
@@ -276,11 +270,11 @@ public class PmtLoanSchemeController {
* 利息按等额本息近似计算总利息;担保费按本金 × 年担保费率 × 期限(年) 计算。
* 真实 XIRR 需逐笔现金流,此处为可读近似值,误差在 0.1% 以内(满足决策比选精度)。
*/
private double calcEffectiveCostRate(PmtLoanScheme s) {
private BigDecimal calcEffectiveCostRate(PmtLoanScheme s) {
double principal = s.getAmount() == null ? 0 : s.getAmount().doubleValue();
if (principal <= 0) return 0.0;
if (principal <= 0) return BigDecimal.ZERO;
int term = s.getTermMonths() == null || s.getTermMonths() <= 0 ? 12 : s.getTermMonths();
double annualRate = s.getAnnualRate() == null ? 0 : s.getAnnualRate();
double annualRate = s.getAnnualRate() == null ? 0 : s.getAnnualRate().doubleValue();
double monthlyRate = annualRate / 100.0 / 12.0;
// 总利息(等额本息口径,兼容多还款方式近似)
@@ -297,7 +291,7 @@ public class PmtLoanSchemeController {
double handlingFee = s.getHandlingFee() == null ? 0 : s.getHandlingFee().doubleValue();
// 担保费(年化率 × 期限年数 × 本金)
double guaranteeRate = s.getGuaranteeRate() == null ? 0 : s.getGuaranteeRate();
double guaranteeRate = s.getGuaranteeRate() == null ? 0 : s.getGuaranteeRate().doubleValue();
double years = term / 12.0;
double guaranteeCost = principal * guaranteeRate / 100.0 * years;
@@ -305,6 +299,6 @@ public class PmtLoanSchemeController {
// 年化综合成本率(百分比)
double effectiveRate = (totalCost / principal) / years * 100.0;
return BigDecimal.valueOf(effectiveRate).setScale(4, RoundingMode.HALF_UP).doubleValue();
return BigDecimal.valueOf(effectiveRate).setScale(4, RoundingMode.HALF_UP);
}
}
@@ -191,7 +191,7 @@ public class StlDebtLedgerController {
activeCount++;
totalDebt = Money.add(totalDebt, principal);
if (f.getRate() != null) {
BigDecimal rateVal = BigDecimal.valueOf(f.getRate());
BigDecimal rateVal = f.getRate();
rateWeightedSum = rateWeightedSum.add(principal.multiply(rateVal));
rateWeightTotal = rateWeightTotal.add(principal);
}
@@ -212,7 +212,7 @@ public class StlDebtLedgerController {
principal.doubleValue(), remaining.doubleValue(),
paidPrin.doubleValue(), paidInterest.doubleValue(),
overdueAmt.doubleValue(),
f.getEndDate(), f.getRate() != null ? f.getRate() : 0.0,
f.getEndDate(), f.getRate() != null ? f.getRate().doubleValue() : 0.0,
f.getStatus(), planRows));
}
@@ -245,7 +245,6 @@ public class StlDebtLedgerController {
* <p>凭证科目:借方=长期借款(或按融资类型映射),贷方=银行存款,状态=草稿。
*/
@PostMapping("/repayments/{planId}/pay-and-voucher")
@Transactional
public ApiResp<RepayVoucherResult> payAndVoucher(@PathVariable Long planId) {
RepaymentPlan plan = planRepo.findById(planId)
.orElseThrow(() -> new NotFoundException("还款期次不存在: " + planId));
@@ -434,7 +434,7 @@ public class StlFundReportController {
if (ls.getAnnualRate() == null || ls.getAmount() == null) continue;
// 年利息 = 贷款金额 × 年化利率 / 100
BigDecimal yearInterest = Money.nz(ls.getAmount())
.multiply(BigDecimal.valueOf(ls.getAnnualRate() / 100.0))
.multiply(ls.getAnnualRate().divide(BigDecimal.valueOf(100), 10, RoundingMode.HALF_UP))
.setScale(2, RoundingMode.HALF_UP);
externalInterest = externalInterest.add(yearInterest);
Map<String, Object> row = new LinkedHashMap<>();
@@ -0,0 +1,42 @@
package com.kaidi.oa.web;
import com.kaidi.oa.common.ApiResp;
import com.kaidi.oa.service.SystemUpdateService;
import com.kaidi.oa.service.SystemUpdateService.UpdateStatus;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** Administrator-only API for checking and installing signed Gitea releases. */
@RestController
@RequestMapping("/api/oa/system-update")
public class SystemUpdateController {
private final SystemUpdateService updateService;
public SystemUpdateController(SystemUpdateService updateService) {
this.updateService = updateService;
}
@GetMapping("/status")
public ApiResp<UpdateStatus> status() {
return ApiResp.ok(updateService.status());
}
@PostMapping("/check")
public ApiResp<UpdateStatus> check() {
return ApiResp.ok(updateService.check());
}
@PostMapping("/install")
public ApiResp<UpdateStatus> install(@Valid @RequestBody InstallRequest request) {
return ApiResp.ok(updateService.install(request.version()));
}
public record InstallRequest(@NotBlank String version) {
}
}