恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。 == 此快照内容 == - 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091) - 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static) - 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB) - 交接文档 go.md + go-code-reference/endpoints/entities/database.md - 多代理建设脚本 .claude/wf-*.js == 状态 == - 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板) - 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用 - W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成) - 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456 == 排除(gitignore, 可再生) == node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui) 完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules) 时间戳: 20260615-191517 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
2.8 KiB
Java
82 lines
2.8 KiB
Java
package com.kaidi.oa.common;
|
||
|
||
import java.math.BigDecimal;
|
||
import java.math.RoundingMode;
|
||
|
||
/**
|
||
* 金额工具(统一标度与空值处理)。
|
||
*
|
||
* 所有"钱"字段一律用 {@link BigDecimal} 而非 double,避免二进制浮点累加误差导致
|
||
* 对账(已付=合同额、分包累计、发票回写)判等不可靠。本工具把请求层传来的装箱
|
||
* {@code Double}(前端 JSON number 反序列化结果)安全收口成标度 2、HALF_UP 的
|
||
* BigDecimal,并提供 null 安全的加减比较,供各控制器/服务复用。
|
||
*/
|
||
public final class Money {
|
||
|
||
/** 金额标准标度:分(2 位小数)。 */
|
||
public static final int SCALE = 2;
|
||
|
||
public static final BigDecimal ZERO = scaled(BigDecimal.ZERO);
|
||
|
||
private Money() {
|
||
}
|
||
|
||
/** 装箱 Double(可空)→ 标度 2 的 BigDecimal;null 视为 0。非有限数(NaN/Infinity)→干净 400。 */
|
||
public static BigDecimal of(Double v) {
|
||
if (v == null) {
|
||
return ZERO;
|
||
}
|
||
requireFinite(v);
|
||
return scaled(BigDecimal.valueOf(v));
|
||
}
|
||
|
||
/** double → 标度 2 的 BigDecimal。非有限数(NaN/Infinity)→干净 400。 */
|
||
public static BigDecimal of(double v) {
|
||
requireFinite(v);
|
||
return scaled(BigDecimal.valueOf(v));
|
||
}
|
||
|
||
/**
|
||
* 金额必须是有限数值:拒绝 NaN/Infinity/-Infinity(JSON 里的 "NaN"/"Infinity"/1e400 等)。
|
||
* 统一在金额收口处给出干净的 400 中文提示,杜绝把 {@code BigDecimal.valueOf(非有限)} 的底层
|
||
* NumberFormatException 原文("Character I is neither a decimal digit...")外泄给前端。
|
||
*/
|
||
private static void requireFinite(double v) {
|
||
if (!Double.isFinite(v)) {
|
||
throw new ApiException(400, "金额必须是有限数值");
|
||
}
|
||
}
|
||
|
||
/** BigDecimal(可空)→ 规整标度;null 视为 0。供已是 BigDecimal 的表达式统一收口。 */
|
||
public static BigDecimal of(BigDecimal v) {
|
||
return nz(v);
|
||
}
|
||
|
||
/** null 安全:null → 0,否则规整标度。 */
|
||
public static BigDecimal nz(BigDecimal v) {
|
||
return v == null ? ZERO : scaled(v);
|
||
}
|
||
|
||
public static BigDecimal add(BigDecimal a, BigDecimal b) {
|
||
return scaled(nz(a).add(nz(b)));
|
||
}
|
||
|
||
public static BigDecimal sub(BigDecimal a, BigDecimal b) {
|
||
return scaled(nz(a).subtract(nz(b)));
|
||
}
|
||
|
||
/** a 是否 > b(null 当 0)。 */
|
||
public static boolean gt(BigDecimal a, BigDecimal b) {
|
||
return nz(a).compareTo(nz(b)) > 0;
|
||
}
|
||
|
||
/** a 是否 <= 0(null 当 0)。 */
|
||
public static boolean lte0(BigDecimal a) {
|
||
return nz(a).compareTo(ZERO) <= 0;
|
||
}
|
||
|
||
private static BigDecimal scaled(BigDecimal v) {
|
||
return v.setScale(SCALE, RoundingMode.HALF_UP);
|
||
}
|
||
}
|