升鲜宝供应链管理系统

统一小数位 / 舍入组件详细设计方案

sxb-precision-engine|正式研发设计文档

适用范围:OMS / PUR / WMS / HWMS / COST / FIN / POS / MALL / PRINT / EXPORT

文档版本

V1.0

组件名称

sxb-precision-engine(统一精度与舍入引擎)

文档用途

研发设计、架构评审、数据库建模、后端改造、测试验收

推荐技术栈

Spring Boot + MyBatis-Plus + BigDecimal + Redis + Caffeine

生成日期

2026-04-28

目录

1. 设计背景与核心目标

2. 总体架构设计

3. 精度分层模型

4. 字段类型与业务场景标准化

5. 舍入策略设计

6. 差额分摊引擎设计

7. 规则解析与优先级模型

8. 规则快照与计算追踪

9. 数据库模型设计

10. Java 组件与接口设计

11. API 与管理后台设计

12. 典型业务接入方案

13. 默认规则与配置建议

14. 测试用例与验收标准

15. 上线落地计划

16. 附录:枚举、检查清单、禁用规范

1. 设计背景与核心目标

升鲜宝供应链管理系统覆盖商品、采购、销售、仓库、成本、财务、POS、商城、打印、导出等多个业务域。只要存在数量、单价、金额、成本、税率、换算比例,就必然存在精度与舍入问题。

本方案将“小数位设置”升级为“统一精度与舍入引擎”。它不只是控制页面显示几位小数,而是统一处理录入、计算、存储、业务确认、财务入账、页面展示、打印、导出、差额分摊与历史规则快照。

1.1 当前常见问题

  • 销售订单金额、出库金额、应收金额、客户对账金额因为不同模块重复计算而产生几分钱差异。
  • 前端使用 toFixed(2),后端使用 setScale(4),打印模板又独立格式化,导致同一数据在不同入口显示不一致。
  • 库存数量、成本单价、移动加权平均成本使用金额精度计算,长期累积后形成库存成本偏差。
  • 规则变更后,历史单据重新打开或反审核时使用了新规则,导致历史金额不可追溯。
  • 商品存在双单位、多单位、计件计重并存时,换算比例和结算数量缺少统一精度规范。

1.2 组件目标

  1. 所有业务模块禁止自行调用 BigDecimal.setScale 作为业务规则,必须统一调用 PrecisionEngine。
  2. 所有精度配置统一进入规则中心,支持全局、租户、公司、门店、仓库、客户、供应商、商品、SKU、单位、场景多级覆盖。
  3. 所有关键业务单据审核时保存精度规则快照,保证历史单据稳定、可解释、可反审核。
  4. 支持明细合计与单据总额差异自动分摊,保证订单、库存、成本、财务链路一致。
  5. 支持页面、打印、PDF、Excel、API 返回统一格式化,不改变业务值。

1.3 组件边界

包含能力

不包含能力

精度规则配置、解析、缓存、版本管理

不替代订单、库存、财务自身业务审核流程

BigDecimal 统一处理、舍入策略、格式化

不负责商品价格策略、促销计算的业务规则本身

差额分摊、规则快照、计算追踪

不直接改变历史单据,除非业务流程要求重算

前后端展示规范、打印导出精度规范

不负责模板设计器本身,但向打印/导出提供格式化结果

2. 总体架构设计

组件建议命名为 sxb-precision-engine,作为 sxb-common 下的基础通用组件,被 OMS、PUR、WMS、HWMS、COST、FIN、POS、MALL、PRINT、EXPORT 等模块依赖。

2.1 架构分层

层级

核心对象

职责

接入层

业务模块、Controller、Service、打印导出适配器

将业务上下文 PrecisionContext 传入统一引擎,不直接处理小数位。

规则层

PrecisionPolicy、PrecisionRule、PrecisionScope

管理规则、版本、优先级、生效时间、适用范围。

解析层

PrecisionPolicyResolver

根据租户、门店、客户、商品、场景、字段类型解析最终规则。

引擎层

PrecisionEngine、CalculateService、RoundingStrategy

执行输入校验、计算、舍入、存储、格式化。

分摊层

PrecisionAllocationEngine

解决明细合计与单据总额、税额、优惠金额、成本金额差异。

审计层

PrecisionSnapshot、PrecisionTrace

固化历史规则并追踪每一步计算过程。

缓存层

Redis、Caffeine、事件通知

规则高频读取,变更后跨服务失效。

2.2 核心调用链路

3. 精度分层模型

优秀的小数位设计不能只有一个 scale。一个字段在录入、计算、存储、业务确认、财务入账、页面显示、打印和导出时,可能需要不同精度。

精度类型

编码

说明

示例

录入精度

INPUT

控制用户输入允许的小数位,超出时提示或自动处理。

销售数量最多录入 3 位。

计算精度

CALC

中间计算使用的高精度,避免过早舍入。

成本单价计算保留 12 位。

存储精度

STORAGE

数据库保存精度,保证数据稳定。

单价保存 4 位,成本单价保存 6 位。

业务确认精度

BUSINESS

单据审核或确认时的最终业务值。

销售订单明细金额最终 2 位。

财务入账精度

FINANCE

生成应收应付、对账、发票时的精度。

应收金额 2 位。

页面显示精度

DISPLAY

管理端、移动端显示文本精度。

页面金额显示 2 位。

打印精度

PRINT

小票、送货单、PDF 模板显示精度。

小票单价显示 4 位。

导出精度

EXPORT

Excel、报表、BI 输出精度。

导出数量显示 3 位。

3.1 不允许过早舍入

计算金额时,不能先把数量和单价按显示精度处理后再相乘。正确做法是:原始数量与原始单价进入 CALC 高精度计算,最后按金额 BUSINESS 或 STORAGE 精度确认。

// 错误示例:过早舍入

BigDecimal qty = inputQty.setScale(2, RoundingMode.HALF_UP);

BigDecimal price = inputPrice.setScale(2, RoundingMode.HALF_UP);

BigDecimal amount = qty.multiply(price).setScale(2, RoundingMode.HALF_UP);

// 正确示例:统一引擎处理

BigDecimal amount = precisionCalculateService.calculateLineAmount(inputQty, inputPrice, context);

3.2 业务值与显示值分离

字段

含义

是否参与后续计算

amount

业务金额 BigDecimal,保存和后续计算使用。

amountText

按照 DISPLAY/PRINT/EXPORT 格式化后的文本。

rawAmount

舍入前原始金额,用于追踪和解释。

否,通常只用于审计

allocatedDiffAmount

差额分摊金额,用于保证明细合计等于单据总额。

4. 字段类型与业务场景标准化

字段类型和业务场景必须标准化,否则规则中心会变成自由文本配置,无法稳定复用。

4.1 字段类型建议

大类

字段编码

字段名称

推荐用途

数量类

ORDER_QTY

订单数量

销售、采购、商城订单录入数量。

数量类

STOCK_QTY

库存数量

库存台账、库存流水、库位库存。

数量类

SETTLE_QTY

结算数量

按重量或换算后结算。

数量类

COUNT_QTY

计件数量

只、件、个等整数数量。

价格类

SALES_PRICE

销售单价

销售订单、POS、商城。

价格类

PURCHASE_PRICE

采购单价

采购订单、采购入库。

价格类

COST_PRICE

成本单价

FIFO、移动加权平均、标准成本。

金额类

LINE_AMOUNT

明细金额

订单、出入库、应收应付来源明细。

金额类

BILL_AMOUNT

单据金额

订单总额、入库总额、出库总额。

金额类

TAX_AMOUNT

税额

税额计算、发票、财务。

金额类

DISCOUNT_AMOUNT

优惠金额

促销、折扣、抹零。

金额类

RECEIVABLE_AMOUNT

应收金额

客户账款、对账。

金额类

PAYABLE_AMOUNT

应付金额

供应商账款、对账。

比例类

TAX_RATE

税率

税额计算。

比例类

DISCOUNT_RATE

折扣率

促销、折扣。

比例类

CONVERT_RATE

换算比例

多单位、双单位、库存单位换算。

4.2 业务场景建议

业务域

场景编码

场景名称

说明

OMS

SALES_ORDER

销售订单

数量、单价、优惠、税额、应收金额。

OMS

SALES_RETURN

销售退货

允许负数或反向金额处理。

PUR

PURCHASE_ORDER

采购订单

供应商单价、采购金额、应付金额。

WMS

PURCHASE_INBOUND

采购入库

入库数量、库存数量、入库成本。

WMS

SALES_OUTBOUND

销售出库

出库数量、成本金额、库存扣减。

WMS

STOCKTAKE

盘点

账面数量、实盘数量、盈亏数量。

COST

COST_CALCULATE

成本计算

FIFO、移动加权平均、标准成本。

FIN

CUSTOMER_STATEMENT

客户对账

读取业务审核金额,不重算。

FIN

SUPPLIER_STATEMENT

供应商对账

采购、入库、应付链路一致。

POS

POS_SALE

POS 收银

现金抹零、找零、退款。

PRINT

PRINT_BILL

单据打印

小票、送货单、PDF。

EXPORT

EXCEL_EXPORT

Excel 导出

导出格式化,不改变业务值。

5. 舍入策略设计

舍入方式是业务规则的一部分。组件内置 Java 标准舍入方式,同时提供业务舍入策略扩展点。

策略编码

Java 对应

中文名称

适用场景

HALF_UP

RoundingMode.HALF_UP

四舍五入

订单、金额、常规业务默认。

DOWN

RoundingMode.DOWN

直接截断

部分客户结算、重量截断。

UP

RoundingMode.UP

向上进位

包装数、不足一箱按一箱。

HALF_EVEN

RoundingMode.HALF_EVEN

银行家舍入

财务精细核算或特定会计要求。

CEILING

RoundingMode.CEILING

向正无穷

特殊结算。

FLOOR

RoundingMode.FLOOR

向负无穷

特殊结算。

NONE

不处理

中间计算阶段可使用,但最终业务值必须落规则。

5.1 业务扩展舍入策略

扩展策略

说明

建议场景

CASH_ROUNDING

现金抹零,例如 POS 收银到角或到元。

POS 收银、现金结算。

PACKAGE_UP

包装向上取整,不足一箱按一箱。

物流包装、箱规计算。

WEIGHT_DOWN

重量按指定小数位截断。

水产称重、客户协议结算。

YUAN_HALF_UP

金额四舍五入到元。

特殊财务处理。

CUSTOM_SCRIPT

保留扩展接口,不建议一开始开放给普通用户。

复杂客户定制。

5.2 舍入策略接口

public interface RoundingStrategy {

    /**

     * 策略编码,例如 HALF_UP、DOWN、CASH_ROUNDING。

     */

    String strategyCode();

    /**

     * 对数值进行舍入处理。

     */

    BigDecimal round(BigDecimal value, int scale, PrecisionContext context);

}

6. 差额分摊引擎设计

差额分摊是统一精度组件的核心能力。没有差额分摊,明细金额、税额、优惠金额、成本金额在汇总时很容易与单据总额不一致。

6.1 差额产生示例

行号

原始金额

舍入后金额

1

3.335

3.34

2

3.335

3.34

3

3.335

3.34

合计

10.005

10.02

如果单据总额按原始总额 10.005 四舍五入为 10.01,而明细合计为 10.02,就产生 -0.01 差额。系统必须明确由哪一行承担差额。

6.2 分摊策略

策略编码

名称

说明

推荐程度

MAX_AMOUNT

金额最大行承担差额

优先调整金额最大的明细,业务影响最小。

推荐默认

LAST_LINE

最后一行承担差额

简单稳定,但最后一行可能频繁有差异。

可选

MAX_QTY

数量最大行承担差额

适合数量主导类单据。

可选

PROPORTION

按比例分摊

适合大额多行单据,但实现复杂。

高级

MANUAL

人工指定

财务特殊处理。

特殊

NONE

不分摊,仅提示

允许业务保留差异,但财务链路不推荐。

谨慎

6.3 分摊结果要求

  • 分摊后,明细最终金额合计必须等于单据最终金额。
  • 分摊差额必须单独保存 allocatedDiffAmount,不能覆盖原始舍入前数据。
  • 差额分摊必须写入计算追踪,便于解释“为什么这一行少了 0.01”。
  • 超过允许最大差额时,应根据配置执行阻断、警告或忽略。

public interface PrecisionAllocationEngine {

    AllocationResult allocate(

            List<AllocationLine> lines,

            BigDecimal targetTotalAmount,

            AllocationStrategy strategy,

            PrecisionContext context

    );

}

7. 规则解析与优先级模型

规则解析不能简单按固定 if/else 写死。推荐采用“匹配分 + 优先级 + 版本号 + 生效时间”的组合模型。

7.1 适用范围

范围编码

说明

示例

GLOBAL

系统全局默认规则

所有租户都可继承。

TENANT

租户规则

某个商户整套系统默认规则。

COMPANY

公司规则

集团/公司维度配置。

SHOP

门店规则

门店收银或门店仓库规则。

WAREHOUSE

仓库规则

不同仓库库存数量精度不同。

CUSTOMER

客户规则

客户 A 要求金额 3 位或重量截断。

SUPPLIER

供应商规则

供应商结算数量/单价精度。

CATEGORY

商品分类规则

水产、蔬菜、冻品不同数量精度。

GOODS

商品规则

波士顿龙虾按计件+计重。

SKU

SKU 规则

具体规格或包装单位精度。

UNIT

单位规则

斤、公斤、箱、只、件。

7.2 默认优先级建议

  1. SKU + 客户 + 业务场景规则
  2. 商品 + 客户 + 业务场景规则
  3. 商品分类 + 客户 + 业务场景规则
  4. 客户 + 业务场景规则
  5. SKU + 供应商 + 业务场景规则
  6. 商品 + 供应商 + 业务场景规则
  7. 供应商 + 业务场景规则
  8. 门店 + 业务场景规则
  9. 仓库 + 业务场景规则
  10. 公司 + 业务场景规则
  11. 租户 + 业务场景规则
  12. 系统全局 + 业务场景规则
  13. 系统默认规则

7.3 解析算法

1. 输入 PrecisionContext:tenantId、shopId、customerId、goodsId、skuId、sceneCode、fieldType。

2. 查询当前时间有效、enabled=true、deleted=false 的候选规则。

3. 按 scope 与上下文匹配,计算 matchScore。

4. matchScore 越高越优先。

5. matchScore 相同,priority 越小越优先。

6. priority 相同,version 越大越优先。

7. 返回命中的 PrecisionRule;未命中则返回系统默认规则。

8. 规则快照与计算追踪

8.1 为什么必须保存规则快照

精度规则会随着客户协议、财务要求、业务调整发生变化。历史审核单据不能因为当前规则变化而改变金额、数量或成本。

业务操作

规则使用原则

新增单据

使用当前最新有效规则。

编辑未审核单据

通常使用当前规则,必要时保留草稿规则。

审核单据

计算最终业务值,并保存 Precision Snapshot。

反审核单据

读取原审核快照回滚,不使用当前新规则。

查看历史单据

按历史快照格式解释和显示。

复制历史单据

作为新单据,使用当前规则重新计算。

财务对账

读取审核后的业务金额,不重新用数量 × 单价计算。

8.2 快照 JSON 示例

{

  "policyId": 10001,

  "policyCode": "SALES_ORDER_DEFAULT",

  "policyVersion": 3,

  "sceneCode": "SALES_ORDER",

  "rules": [

    {"fieldType": "ORDER_QTY", "businessScale": 3, "roundingMode": "HALF_UP"},

    {"fieldType": "SALES_PRICE", "businessScale": 4, "roundingMode": "HALF_UP"},

    {"fieldType": "LINE_AMOUNT", "businessScale": 2, "roundingMode": "HALF_UP"}

  ]

}

8.3 计算追踪内容

  • 原始输入值:例如数量 65.3456、单价 28.5678。
  • 计算表达式:例如 quantity × unitPrice。
  • 舍入前值与舍入后值:例如 1866.92697168 -> 1866.93。
  • 使用的小数位、舍入方式、规则 ID、版本号。
  • 差额分摊明细:哪一行承担了 +0.01 或 -0.01。

9. 数据库模型设计

以下为全新设计建议,不依赖现有表结构。实际落地时可以根据升鲜宝现有命名规范调整前缀和公共字段。

9.1 核心表清单

表名

名称

职责

sxb_precision_policy

精度规则主表

规则编码、场景、范围、版本、优先级、生效时间。

sxb_precision_rule

精度规则明细表

每个字段类型的录入、计算、存储、显示、打印、导出精度。

sxb_precision_allocation_policy

差额分摊策略表

按场景与字段定义差额处理方式。

sxb_precision_snapshot

单据精度快照表

审核时固化历史规则。

sxb_precision_trace

精度计算追踪表

记录关键计算步骤和舍入结果。

sxb_precision_change_log

规则变更日志表

记录规则新增、修改、发布、停用。

9.2 精度规则主表 DDL

CREATE TABLE sxb_precision_policy (

    id BIGINT NOT NULL COMMENT '主键ID',

    policy_code VARCHAR(64) NOT NULL COMMENT '规则编码',

    policy_name VARCHAR(100) NOT NULL COMMENT '规则名称',

    tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户ID,0表示系统级',

    domain_code VARCHAR(32) NOT NULL COMMENT '业务域:OMS/PUR/WMS/COST/FIN/POS',

    scene_code VARCHAR(64) NOT NULL COMMENT '业务场景',

    scope_type VARCHAR(32) NOT NULL COMMENT '适用范围',

    scope_id BIGINT NOT NULL DEFAULT 0 COMMENT '适用对象ID',

    priority INT NOT NULL DEFAULT 100 COMMENT '优先级,越小越优先',

    version INT NOT NULL DEFAULT 1 COMMENT '版本号',

    effective_start_time DATETIME NULL COMMENT '生效开始时间',

    effective_end_time DATETIME NULL COMMENT '生效结束时间',

    enabled TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用',

    snapshot_required TINYINT NOT NULL DEFAULT 1 COMMENT '是否需要快照',

    allow_override TINYINT NOT NULL DEFAULT 1 COMMENT '是否允许下级覆盖',

    remark VARCHAR(500) NULL COMMENT '备注',

    create_user_id BIGINT NULL COMMENT '创建人',

    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

    update_user_id BIGINT NULL COMMENT '更新人',

    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

    deleted TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记',

    PRIMARY KEY (id),

    UNIQUE KEY uk_policy_code_version (tenant_id, policy_code, version, deleted),

    KEY idx_scene_scope (tenant_id, domain_code, scene_code, scope_type, scope_id),

    KEY idx_enabled_priority (tenant_id, enabled, priority)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一精度规则主表';

9.3 精度规则明细表 DDL

CREATE TABLE sxb_precision_rule (

    id BIGINT NOT NULL COMMENT '主键ID',

    policy_id BIGINT NOT NULL COMMENT '规则主表ID',

    field_type VARCHAR(64) NOT NULL COMMENT '字段类型',

    input_scale INT NOT NULL DEFAULT 4 COMMENT '录入精度',

    calc_scale INT NOT NULL DEFAULT 8 COMMENT '计算精度',

    storage_scale INT NOT NULL DEFAULT 4 COMMENT '存储精度',

    business_scale INT NOT NULL DEFAULT 2 COMMENT '业务确认精度',

    finance_scale INT NOT NULL DEFAULT 2 COMMENT '财务入账精度',

    display_scale INT NOT NULL DEFAULT 2 COMMENT '页面显示精度',

    print_scale INT NOT NULL DEFAULT 2 COMMENT '打印精度',

    export_scale INT NOT NULL DEFAULT 2 COMMENT '导出精度',

    rounding_mode VARCHAR(32) NOT NULL DEFAULT 'HALF_UP' COMMENT '舍入方式',

    custom_rounding_code VARCHAR(64) NULL COMMENT '自定义舍入策略编码',

    strip_trailing_zeros TINYINT NOT NULL DEFAULT 0 COMMENT '是否去掉末尾0',

    allow_negative TINYINT NOT NULL DEFAULT 0 COMMENT '是否允许负数',

    allow_zero TINYINT NOT NULL DEFAULT 1 COMMENT '是否允许0',

    min_value DECIMAL(30,10) NULL COMMENT '最小值',

    max_value DECIMAL(30,10) NULL COMMENT '最大值',

    remark VARCHAR(500) NULL COMMENT '备注',

    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

    deleted TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记',

    PRIMARY KEY (id),

    UNIQUE KEY uk_policy_field (policy_id, field_type, deleted),

    KEY idx_field_type (field_type)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一精度规则明细表';

9.4 快照与追踪表 DDL

CREATE TABLE sxb_precision_snapshot (

    id BIGINT NOT NULL COMMENT '主键ID',

    tenant_id BIGINT NOT NULL COMMENT '租户ID',

    bill_type VARCHAR(64) NOT NULL COMMENT '单据类型',

    bill_id BIGINT NOT NULL COMMENT '单据ID',

    bill_code VARCHAR(64) NULL COMMENT '单据编号',

    domain_code VARCHAR(32) NOT NULL COMMENT '业务域',

    scene_code VARCHAR(64) NOT NULL COMMENT '业务场景',

    policy_id BIGINT NULL COMMENT '规则ID',

    policy_code VARCHAR(64) NULL COMMENT '规则编码',

    policy_version INT NULL COMMENT '规则版本',

    snapshot_json JSON NOT NULL COMMENT '规则快照JSON',

    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

    PRIMARY KEY (id),

    UNIQUE KEY uk_bill (tenant_id, bill_type, bill_id),

    KEY idx_bill_code (tenant_id, bill_code),

    KEY idx_policy (tenant_id, policy_code, policy_version)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='业务单据精度规则快照表';

CREATE TABLE sxb_precision_trace (

    id BIGINT NOT NULL COMMENT '主键ID',

    tenant_id BIGINT NOT NULL COMMENT '租户ID',

    trace_no VARCHAR(64) NOT NULL COMMENT '追踪号',

    bill_type VARCHAR(64) NULL COMMENT '单据类型',

    bill_id BIGINT NULL COMMENT '单据ID',

    bill_code VARCHAR(64) NULL COMMENT '单据编号',

    line_id BIGINT NULL COMMENT '明细ID',

    domain_code VARCHAR(32) NOT NULL COMMENT '业务域',

    scene_code VARCHAR(64) NOT NULL COMMENT '业务场景',

    field_type VARCHAR(64) NOT NULL COMMENT '字段类型',

    usage_type VARCHAR(32) NOT NULL COMMENT '用途',

    original_value DECIMAL(30,10) NULL COMMENT '原始值',

    before_round_value DECIMAL(30,10) NULL COMMENT '舍入前值',

    after_round_value DECIMAL(30,10) NULL COMMENT '舍入后值',

    scale_value INT NOT NULL COMMENT '使用小数位',

    rounding_mode VARCHAR(32) NOT NULL COMMENT '舍入方式',

    policy_id BIGINT NULL COMMENT '命中规则ID',

    rule_id BIGINT NULL COMMENT '命中字段规则ID',

    expression_text VARCHAR(500) NULL COMMENT '计算表达式',

    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

    PRIMARY KEY (id),

    KEY idx_trace_no (trace_no),

    KEY idx_bill (tenant_id, bill_type, bill_id),

    KEY idx_scene_field (tenant_id, scene_code, field_type),

    KEY idx_create_time (create_time)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一精度计算追踪表';

10. Java 组件与接口设计

10.1 推荐项目结构

sxb-common-precision

├── annotation

│   ├── PrecisionField.java

│   └── PrecisionTrace.java

├── context

│   └── PrecisionContext.java

├── enums

│   ├── PrecisionFieldType.java

│   ├── PrecisionUsage.java

│   ├── PrecisionRoundingMode.java

│   └── AllocationStrategy.java

├── model

│   ├── PrecisionPolicy.java

│   ├── PrecisionRule.java

│   ├── PrecisionScope.java

│   └── PrecisionTraceStep.java

├── resolver

│   └── PrecisionPolicyResolver.java

├── engine

│   ├── PrecisionEngine.java

│   ├── PrecisionCalculateService.java

│   └── PrecisionAllocationEngine.java

├── formatter

│   └── PrecisionFormatter.java

├── snapshot

│   └── PrecisionSnapshotService.java

├── trace

│   └── PrecisionTraceService.java

└── cache

    └── PrecisionCacheService.java

10.2 PrecisionContext

@Data

@Builder

public class PrecisionContext {

    private Long tenantId;

    private Long companyId;

    private Long shopId;

    private Long warehouseId;

    private Long customerId;

    private Long supplierId;

    private Long categoryId;

    private Long goodsId;

    private Long skuId;

    private Long unitId;

    private String domainCode;

    private String sceneCode;

    private String fieldType;

    private String billType;

    private Long billId;

    private String billCode;

    private PrecisionUsage usage;

}

10.3 PrecisionEngine

public interface PrecisionEngine {

    BigDecimal apply(BigDecimal value, PrecisionContext context);

    BigDecimal apply(BigDecimal value,

                     String fieldType,

                     PrecisionUsage usage,

                     PrecisionContext context);

    String format(BigDecimal value,

                  String fieldType,

                  PrecisionUsage usage,

                  PrecisionContext context);

}

10.4 PrecisionCalculateService

public interface PrecisionCalculateService {

    BigDecimal calculateLineAmount(BigDecimal quantity,

                                   BigDecimal unitPrice,

                                   PrecisionContext context);

    BigDecimal calculateTaxAmount(BigDecimal amount,

                                  BigDecimal taxRate,

                                  PrecisionContext context);

    BigDecimal calculateReceivableAmount(BigDecimal amount,

                                          BigDecimal discountAmount,

                                          BigDecimal taxAmount,

                                          PrecisionContext context);

    BigDecimal convertQuantity(BigDecimal sourceQty,

                               BigDecimal convertRate,

                               PrecisionContext context);

    BigDecimal calculateCostPrice(BigDecimal costAmount,

                                  BigDecimal quantity,

                                  PrecisionContext context);

}

10.5 注解设计

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface PrecisionField {

    String fieldType();

    String sceneCode() default "";

    PrecisionUsage usage() default PrecisionUsage.DISPLAY;

}

11. API 与管理后台设计

11.1 后台菜单

系统设置

└── 业务规则中心

    └── 精度与舍入规则

        ├── 精度规则

        ├── 舍入策略

        ├── 差额分摊策略

        ├── 规则模拟器

        ├── 规则版本

        └── 变更日志

11.2 API 清单

接口

方法

说明

/admin/system/precision-policy/page

GET

分页查询精度规则。

/admin/system/precision-policy/create

POST

新增精度规则草稿。

/admin/system/precision-policy/update

POST

修改精度规则草稿。

/admin/system/precision-policy/publish

POST

发布规则并递增版本。

/admin/system/precision-policy/disable

POST

停用规则。

/admin/system/precision-policy/resolve

POST

根据上下文解析命中规则。

/admin/system/precision-policy/simulate

POST

规则模拟器,输出计算步骤。

/admin/system/precision-trace/page

GET

查询计算追踪记录。

/admin/system/precision-snapshot/getByBill

GET

查看单据规则快照。

11.3 规则模拟器请求示例

{

  "tenantId": 1,

  "domainCode": "OMS",

  "sceneCode": "SALES_ORDER",

  "customerId": 1001,

  "goodsId": 2001,

  "skuId": 3001,

  "quantity": "65.3456",

  "unitPrice": "28.5678",

  "taxRate": "0.0600"

}

11.4 规则模拟器返回示例

{

  "hitPolicy": {

    "policyId": 10001,

    "policyName": "销售订单默认精度",

    "policyVersion": 3

  },

  "calculateResult": {

    "quantity": "65.346",

    "unitPrice": "28.5678",

    "rawAmount": "1866.92697168",

    "lineAmount": "1866.93",

    "taxAmount": "112.02",

    "totalAmount": "1978.95"

  },

  "steps": [

    {"fieldType":"ORDER_QTY","usage":"INPUT","scale":3,"resultValue":"65.346"},

    {"fieldType":"LINE_AMOUNT","usage":"BUSINESS","scale":2,"resultValue":"1866.93"}

  ]

}

12. 典型业务接入方案

12.1 销售订单

  • 订单录入时,数量使用 ORDER_QTY 的 INPUT 精度校验,销售单价使用 SALES_PRICE 的 INPUT 精度校验。
  • 明细金额通过 calculateLineAmount 计算,最终按 LINE_AMOUNT 的 BUSINESS 精度确认。
  • 单据总额与明细合计不一致时执行差额分摊。
  • 审核时保存 Precision Snapshot,后续客户对账读取审核金额,不重新乘算。

12.2 采购与入库

  • 采购数量、采购单价、采购金额可按供应商规则覆盖。
  • 采购入库写库存时,采购单位数量需要通过 CONVERT_RATE 转换为库存单位数量。
  • 入库金额进入成本模块前,必须先完成业务金额确认,成本模块再按成本精度处理。

12.3 WMS 库存

  • 库存数量使用 STOCK_QTY,不要直接复用订单数量精度。
  • 库位库存、批次库存、库存流水应保存稳定的库存数量精度。
  • 盘点盈亏数量可允许负数,但必须明确 allowNegative 规则。

12.4 成本模块

  • 成本单价建议计算精度 12 位,存储精度 6 位。
  • 成本金额建议最终 2 位,但中间计算不能用 2 位。
  • FIFO 批次消耗、移动加权平均成本、反审核回滚必须使用审核时规则快照。

12.5 双单位与水产生鲜场景

例如波士顿龙虾:采购按“只”,库存按“斤”,销售结算按“斤”,页面同时展示“50 只 / 65 斤”。建议不要用一个 quantity 字段混用到底,而是区分 orderQty、stockQty、settleQty、assistQty、countQty、weightQty。

字段

含义

示例

countQty

计件数量

50 只

weightQty

计重数量

65.000 斤

settleQty

结算数量

65.000 斤

salesPrice

销售单价

28.5000 元/斤

lineAmount

销售金额

1852.50 元

13. 默认规则与配置建议

13.1 生鲜配送默认精度

字段

录入

计算

存储

业务

财务

显示

打印

导出

订单数量

3

6

3

3

3

3

3

3

库存数量

3

8

4

3

3

3

3

3

计件数量

0

0

0

0

0

0

0

0

箱数/包装数

2

4

2

2

2

2

2

2

销售单价

4

8

4

4

4

4

4

4

采购单价

4

8

4

4

4

4

4

4

金额

2

8

2

2

2

2

2

2

成本单价

6

12

6

6

6

6

6

6

成本金额

2

10

2

2

2

2

2

2

税率

4

8

4

4

4

4

4

4

换算比例

8

12

8

8

8

8

8

8

13.2 推荐默认舍入方式

字段类型

默认舍入方式

说明

数量

HALF_UP

常规数量四舍五入;计件数量固定 0 位。

单价

HALF_UP

销售/采购单价默认四舍五入。

金额

HALF_UP

订单金额、应收应付默认四舍五入。

成本单价

HALF_UP

成本单价高精度四舍五入。

换算比例

HALF_UP

换算比例保存高精度。

POS 现金结算

CASH_ROUNDING

根据门店规则抹零。

包装数

PACKAGE_UP

不足整箱向上取整。

14. 测试用例与验收标准

14.1 核心测试用例

用例

输入

规则

期望结果

四舍五入

12.345

金额 2 位,HALF_UP

12.35

截断

12.345

金额 2 位,DOWN

12.34

金额计算

数量 3.456,单价 2.3456

金额 2 位,HALF_UP

8.11

客户特殊规则

客户 A 金额 3 位

优先客户规则

金额显示 3 位

差额分摊

3 行 3.335

目标总额 10.01

某行承担 -0.01,明细合计 10.01

历史快照

规则从 2 位改 3 位

历史审核单据

仍按 2 位显示和反审核

库存成本

移动加权平均

成本单价计算 12 位,存储 6 位

库存金额可追溯

打印导出

金额业务值 12.50

打印去 0,导出保留 0

业务值不变

14.2 验收标准

  1. 系统中新增或改造的业务计算不得直接出现裸 setScale,必须使用 PrecisionEngine 或受控工具类。
  2. 销售订单、采购订单、出入库、应收应付、对账单的金额链路必须一致。
  3. 规则变更后,历史审核单据金额不发生变化。
  4. 规则模拟器能展示命中规则、计算步骤、舍入前后值和差额分摊结果。
  5. 打印、PDF、Excel 只改变显示格式,不改变业务金额。

15. 上线落地计划

阶段

目标

重点任务

第一阶段:基础引擎

建立统一组件能力

PrecisionContext、PrecisionRule、PrecisionEngine、RoundingStrategy、Formatter、缓存。

第二阶段:订单接入

解决客户最容易看到的金额差异

销售订单、采购订单、退货单、商城订单。

第三阶段:库存接入

统一库存数量和单位换算

采购入库、销售出库、盘点、报损报溢、库存流水。

第四阶段:成本接入

保证成本精度和反审核可逆

FIFO、移动加权平均、批次成本、成本流水。

第五阶段:财务接入

保证对账稳定

应收、应付、客户对账、供应商对账、发票、收付款。

第六阶段:输出接入

统一展示和交付

页面 VO、打印模板、PDF、Excel、BI 报表。

15.1 开发禁用规范

  • 禁止在业务 Service 中直接写 value.setScale(2, RoundingMode.HALF_UP)。
  • 禁止前端自行使用 toFixed 控制业务金额显示。
  • 禁止对账、发票、收付款重新通过数量 × 单价计算历史金额。
  • 禁止反审核时使用当前规则替代原单据审核快照。
  • 禁止用 double/float 处理业务金额、数量、成本。

15.2 推荐代码扫描规则

需要重点扫描的关键词:

- .setScale(

- RoundingMode.

- toFixed(

- double amount

- float price

- new BigDecimal(double)

允许出现的位置:

- sxb-common-precision 组件内部

- 经过评审的 BigDecimalUtils

- 单元测试代码

16. 附录:枚举、检查清单、禁用规范

16.1 PrecisionUsage 枚举

public enum PrecisionUsage {

    INPUT,      // 用户录入

    CALC,       // 中间计算

    STORAGE,    // 数据库存储

    BUSINESS,   // 业务确认

    FINANCE,    // 财务入账

    DISPLAY,    // 页面显示

    PRINT,      // 打印显示

    EXPORT      // 导出显示

}

16.2 PrecisionFieldType 建议枚举

ORDER_QTY, STOCK_QTY, SETTLE_QTY, COUNT_QTY, WEIGHT_QTY,

SALES_PRICE, PURCHASE_PRICE, COST_PRICE,

LINE_AMOUNT, BILL_AMOUNT, TAX_AMOUNT, DISCOUNT_AMOUNT,

RECEIVABLE_AMOUNT, PAYABLE_AMOUNT, PAID_AMOUNT, BALANCE_AMOUNT,

TAX_RATE, DISCOUNT_RATE, CONVERT_RATE, PROFIT_RATE

16.3 研发检查清单

检查项

是否必须

是否传入 PrecisionContext,不直接使用简单 scale 参数。

必须

是否区分 INPUT、CALC、STORAGE、BUSINESS、FINANCE、DISPLAY、PRINT、EXPORT。

必须

审核时是否保存 Precision Snapshot。

关键单据必须

明细汇总是否执行差额分摊。

金额/税额/优惠/成本必须

对账是否读取审核后金额,不重新计算。

必须

打印导出是否使用文本格式化,不改变业务值。

必须

是否有单元测试覆盖四舍五入、截断、分摊、快照。

必须

是否有缓存失效机制。

必须

16.4 最终结论

升鲜宝统一小数位/舍入组件建议正式定位为 sxb-precision-engine。它应成为所有业务域共同依赖的底层能力,负责统一精度规则、舍入策略、差额分摊、规则快照、计算追踪和格式化输出。

只有把精度规则从各业务模块中抽离出来,形成统一组件,才能真正做到:算得准、看得一致、查得清、改得动、可追溯、可扩展。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐