# 积分计算优化 Spec ## Why 积分相关代码存在多个精度和计算正确性问题:积分发放使用 `ROUND_CEILING`(进位取整)而非用户要求的四舍五入;多店铺积分分摊使用 `double` 算术存在浮点精度风险;`preAllocatedIntegral` 使用 `Double` 类型不符合积分必须为整数的约束;多店铺分摊中 cap 修正后未回写累计值导致最后一个店铺减法兜底结果错误。 ## What Changes - 积分发放取整方式从 `ROUND_CEILING` 改为 `ROUND_HALF_UP`(四舍五入) - `FsStoreOrderComputedParam.preAllocatedIntegral` 类型从 `Double` 改为 `Integer` - `computedOrders` 中积分分摊计算全部改用 `BigDecimal` 算术,消除 `double` 精度风险 - 修复多店铺分摊中 `subDeduction` 超限 cap 后未更新 `allocatedDeductionSum` / `allocatedIntegralSum` 的 bug - `user.getIntegral().intValue()` 改为 `longValue()`,`totalIntegral` 改为 `long` 类型,避免大值截断 - `totalDeduction` 计算改用 `BigDecimal.divide` 确保精度 ## Impact - Affected code: `FsUserIntegralLogsServiceImpl.java`、`FsStoreOrderScrmServiceImpl.java`、`FsStoreOrderComputedParam.java` - 不影响旧的单店铺积分逻辑(`computedOrder` / `store.integral` 配置) - 不影响退款积分逻辑(`refundIntegral`) - `preAllocatedIntegral` 类型变更需同步修改 `computedOrders` 和 `computedOrderMultiStore` 中的使用 ## MODIFIED Requirements ### Requirement: 积分发放取整 积分发放时,`amount × rate` 的结果 SHALL 使用 `RoundingMode.HALF_UP`(四舍五入)取整,不得使用 `ROUND_CEILING`(进位取整)。 #### Scenario: 正常发放 - **WHEN** 消费金额为 10.5 元,发放比例为 0.1(1元=0.1积分) - **THEN** 计算结果 1.05 → 四舍五入为 1 积分(非进位取整的 2) ### Requirement: 多店铺积分分摊计算精度 多店铺积分分摊 SHALL 全程使用 `BigDecimal` 算术,禁止使用 `double` 中间运算。积分值 SHALL 始终为整数,不得出现小数。 #### Scenario: 比例分摊 - **WHEN** 合计 100 元,子订单 A=95 元、B=5 元,总积分 100 - **THEN** A 分摊 95 积分,B 分摊 5 积分(最后一个用总计减去已分配) #### Scenario: 超限 cap 后累计值修正 - **WHEN** 子订单分摊的抵扣金额超过该子订单应付金额时 - **THEN** cap 后的值 SHALL 回写到 `allocatedDeductionSum` 和 `allocatedIntegralSum`,确保最后一个店铺的减法兜底结果正确 ### Requirement: preAllocatedIntegral 类型 `FsStoreOrderComputedParam.preAllocatedIntegral` SHALL 为 `Integer` 类型,不得使用 `Double`。 ### Requirement: 用户积分取值精度 读取用户积分余额时 SHALL 使用 `longValue()`,不得使用 `intValue()` 以防大值截断。