问题复现:
//总支付金额(不包含奖学金)
BigDecimal sumPayedAmountNotScholarship = new BigDecimal(orderItems.stream().mapToInt(OrderAmountUtil::getOrderItemPayAmountNotScholarship).sum());
//记录已经使用的奖学金
BigDecimal useItemScholarShip = new BigDecimal("0");
for (int i = 0; i < orderItems.size(); i++) {
OrderItem orderItem = orderItems.get(i);
//三级订单支付金额(不包含奖学金)
BigDecimal itemPayedAmountNotScholarship = new BigDecimal(String.valueOf(OrderAmountUtil.getOrderItemPayAmountNotScholarship(orderItem)));
if (itemPayedAmountNotScholarship.compareTo(BigDecimal.ZERO) < 0) {
log.error("奖学金优惠后分摊金额小于0,{}", JsonUtil.toString(orderItem));
return PlatformReturn.failure("优惠后分摊金额不能小于0,请重新下单");
}
//奖学金平摊金额 注意:这里必须先乘再除 否则除了之后直接变为0
BigDecimal itemScholarShip = itemPayedAmountNotScholarship.multiply(sumScholarship).divide(sumPayedAmountNotScholarship, RoundingMode.HALF_UP);
if (i == orderItems.size() - 1) {
itemScholarShip = sumScholarship.subtract(useItemScholarShip);
}
orderItem.setScholarship(itemScholarShip.intValue());
useItemScholarShip = useItemScholarShip.add(itemScholarShip);
}
问题分析:
1.这里注意,BigDecimal 的 除法 和成法运算 顺序必须是 先乘后除,否则先除小数点都会直接变为0
2.这里数据库金额存的是分为单位的int类型,所以这里分摊不用BigDecaimal 参数来承接也可以,但是运算需要用BigDecimal 来进行乘除
3.由于四舍五入,itemSchlarShip 算出来的金额 可能比 useItemScholarShip 大,这也是最终算出负数的原因
4.注意这里的itemPayedAmountNotScholarship必须大于0,否则最后一笔0元订单可能分摊部分奖学金
解决方案:
//总支付金额(不包含奖学金)
BigDecimal sumPayedAmountNotScholarship = new BigDecimal(orderItems.stream().mapToInt(OrderAmountUtil::getOrderItemPayAmountNotScholarship).sum());
//记录已经使用的奖学金
int useItemScholarShip = 0;
for (int i = 0; i < orderItems.size(); i++) {
OrderItem orderItem = orderItems.get(i);
//三级订单支付金额(不包含奖学金)
BigDecimal itemPayedAmountNotScholarship = new BigDecimal(String.valueOf(OrderAmountUtil.getOrderItemPayAmountNotScholarship(orderItem)));
if (itemPayedAmountNotScholarship.compareTo(BigDecimal.ZERO) < 0) {
log.error("奖学金优惠后分摊金额小于0,{}", JsonUtil.toString(orderItem));
return PlatformReturn.failure("优惠后分摊金额不能小于0,请重新下单");
}
int itemScholarShip = 0;
//最后一个三级订单
if (i == orderItems.size() - 1) {
itemScholarShip = sumScholarship.intValue() - useItemScholarShip;
} else {
//奖学金平摊金额 注意:这里必须先乘再除 否则除了之后直接变为0
itemScholarShip = itemPayedAmountNotScholarship.multiply(sumScholarship).divide(sumPayedAmountNotScholarship, RoundingMode.HALF_UP).intValue();
//判断当前三级订单平摊的奖学金,是否超出了总奖学金
if (itemScholarShip > sumScholarship.intValue()) {
itemScholarShip = sumScholarship.intValue();
}
//判断所有三级订单合计平摊的奖学金,是否超出了总奖学金
if (useItemScholarShip + itemScholarShip > sumScholarship.intValue()) {
itemScholarShip = sumScholarship.intValue() - useItemScholarShip;
}
}
if (itemScholarShip < 0) {
log.error("奖学金分摊异常,itemScholarShip < 0,context:{},orderItems:{},sumScholarship:{}",
JsonUtil.toString(context), JsonUtil.toString(orderItems), sumScholarship);
return PlatformReturn.failure("网络异常,请稍候重试");
}
useItemScholarShip += itemScholarShip;
orderItem.setScholarship(itemScholarShip);
}
//奖学金都为0,设置最后一个三级订单为奖学金总额
if (orderItems.stream().noneMatch(a -> a.getScholarship() > 0)) {
orderItems.get(orderItems.size() - 1).setScholarship(sumScholarship.intValue());
}