分账公共计算方法

This commit is contained in:
Jack 2025-09-17 16:58:51 +08:00
parent 3e64c19f82
commit ede9a26810

View File

@ -13,32 +13,12 @@ import java.math.RoundingMode;
/**
* 拉卡拉订单分账信息对象
* <p>
* 分账计算逻辑说明
* 前提条件分账总金额必须大于0配送费不能小于0拉卡拉商户平台分账比例必须大于0
* 一级二级代理商分账比例可以为null或小于等于0
* 分账计算支持两种模式
* 1. 基于可分账金额模式默认先扣除拉卡拉手续费和配送费再对剩余金额进行分账
* 2. 基于总金额模式基于总分账金额进行分账计算
* <p>
* 分账计算步骤
* 1. 计算拉卡拉分账金额 = 分账总金额 × 拉卡拉分账比例结果向上取整
* 2. 计算可分账金额 = 分账总金额 - 配送费 - 拉卡拉分账金额
* 3. 调整分账比例确保商户平台一级代理商二级代理商的有效比例之和等于1.0
* - 如果有二级代理商参与分账则调整二级代理商比例
* - 否则如果有一级代理商参与分账则调整一级代理商比例
* - 否则调整平台比例
* 4. 按优先级计算各参与方分账金额结果四舍五入
* - 商户分账金额 = 可分账金额 × 商户分账比例
* - 平台分账金额 = 可分账金额 × 平台分账比例
* - 一级代理商分账金额 = 可分账金额 × 一级代理商分账比例如果参与分账
* - 二级代理商分账金额 = 可分账金额 × 二级代理商分账比例如果参与分账
* 5. 如分配总额与可分账金额不符则调整最后一个分账方按优先级顺序以确保总额平衡
* - 优先调整二级代理商如果参与分账
* - 否则调整一级代理商如果参与分账
* - 否则调整平台
* - 最后调整商户
* 6. 保障各参与方分账比例不能低于指定属性的比例按优先级处理
* - 商户优先级最高
* - 平台优先级次之
* - 一级代理商优先级再次之
* - 二级代理商优先级最低
* 分账优先级顺序拉卡拉 > 平台 > 一级代理商 > 二级代理商 > 商户
* 商户作为最后分账方其金额为剩余金额不强制遵循分账比例
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ -54,9 +34,13 @@ public class LklSeparateDTO implements java.io.Serializable {
@ApiModelProperty(value = "可分账金额(分)")
private Integer canSeparateAmount;
@ApiModelProperty(value = "拉卡拉参考可分账金额(分)")
private Integer refCanSeparateAmount;
@ApiModelProperty(value = "配送费(分)")
private Integer shippingFee;
@ApiModelProperty(value = "拉卡拉分账比例(如 0.0025=0.25%)")
private BigDecimal lklRatio;
@ -72,6 +56,7 @@ public class LklSeparateDTO implements java.io.Serializable {
@ApiModelProperty(value = "二级代理商分账比例(如 0.03=3%)")
private BigDecimal agent2ndRatio;
@ApiModelProperty(value = "拉卡拉分账金额(分)")
private Integer lklAmount;
@ -83,7 +68,7 @@ public class LklSeparateDTO implements java.io.Serializable {
@ApiModelProperty(value = "一级代理商分账金额(分)")
private Integer agent1stAmount;
@ApiModelProperty(value = "二级代理商分账金额(分)")
private Integer agent2ndAmount;
@ -93,49 +78,6 @@ public class LklSeparateDTO implements java.io.Serializable {
public static void main(String[] args) {
logger.info("开始测试分账计算功能...");
// 临界点测试用例1: 极小金额测试
logger.info("\n\n临界点测试用例1 - 极小金额测试:");
LklSeparateDTO dto1 = new LklSeparateDTO();
dto1.setTotalSeparateAmount(3);
dto1.setShippingFee(0);
dto1.setLklRatio(new BigDecimal("0.0025"));
dto1.setMchRatio(new BigDecimal("0.94"));
dto1.setPlatRatio(new BigDecimal("0.06"));
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}",
dto1.getTotalSeparateAmount(), dto1.getShippingFee(), dto1.getLklRatio(),
dto1.getMchRatio(), dto1.getPlatRatio());
boolean result1 = dto1.calculateProfitSharing();
logger.info("分账计算结果: {}", (result1 ? "成功" : "失败"));
if (result1) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto1.getTotalSeparateAmount(), dto1.getLklAmount(), dto1.getShippingFee(), dto1.getCanSeparateAmount(),
dto1.getMchAmount(), dto1.getPlatAmount(), dto1.getAgent1stAmount(), dto1.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto1.getLklAmount() / dto1.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto1.getMchAmount() / dto1.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto1.getPlatAmount() / dto1.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto1.getAgent1stAmount() / dto1.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto1.getAgent2ndAmount() / dto1.getTotalSeparateAmount() * 100;
double lklRatioOfAvailable = (double) dto1.getLklAmount() / dto1.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto1.getMchAmount() / dto1.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto1.getPlatAmount() / dto1.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto1.getAgent1stAmount() / dto1.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto1.getAgent2ndAmount() / dto1.getCanSeparateAmount() * 100;
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
String.format("%.4f", mchRatioOfTotal), String.format("%.4f", mchRatioOfAvailable),
String.format("%.4f", platRatioOfTotal), String.format("%.4f", platRatioOfAvailable),
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto1.getLklAmount() + dto1.getMchAmount() + dto1.getPlatAmount() + dto1.getAgent1stAmount() + dto1.getAgent2ndAmount() + dto1.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto1.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto1.getTotalSeparateAmount());
}
// 临界点测试用例2: 小金额测试
logger.info("\n\n临界点测试用例2 - 小金额测试:");
LklSeparateDTO dto2 = new LklSeparateDTO();
@ -149,24 +91,40 @@ public class LklSeparateDTO implements java.io.Serializable {
dto2.getTotalSeparateAmount(), dto2.getShippingFee(), dto2.getLklRatio(),
dto2.getMchRatio(), dto2.getPlatRatio());
boolean result2 = dto2.calculateProfitSharing();
boolean result2 = dto2.calcOnCanAmount();
logger.info("分账计算结果: {}", (result2 ? "成功" : "失败"));
if (result2) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto2.getTotalSeparateAmount(), dto2.getLklAmount(), dto2.getShippingFee(), dto2.getCanSeparateAmount(),
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 参考可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto2.getTotalSeparateAmount(), dto2.getLklAmount(), dto2.getShippingFee(), dto2.getCanSeparateAmount(), dto2.getRefCanSeparateAmount(),
dto2.getMchAmount(), dto2.getPlatAmount(), dto2.getAgent1stAmount(), dto2.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto2.getLklAmount() / dto2.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto2.getMchAmount() / dto2.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto2.getPlatAmount() / dto2.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto2.getAgent1stAmount() / dto2.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto2.getAgent2ndAmount() / dto2.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = 0.0;
double agent2ndRatioOfTotal = 0.0;
// 安全检查避免空指针异常
if (dto2.getAgent1stAmount() != null) {
agent1stRatioOfTotal = (double) dto2.getAgent1stAmount() / dto2.getTotalSeparateAmount() * 100;
}
if (dto2.getAgent2ndAmount() != null) {
agent2ndRatioOfTotal = (double) dto2.getAgent2ndAmount() / dto2.getTotalSeparateAmount() * 100;
}
double lklRatioOfAvailable = (double) dto2.getLklAmount() / dto2.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto2.getMchAmount() / dto2.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto2.getPlatAmount() / dto2.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto2.getAgent1stAmount() / dto2.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto2.getAgent2ndAmount() / dto2.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = 0.0;
double agent2ndRatioOfAvailable = 0.0;
// 安全检查避免空指针异常
if (dto2.getAgent1stAmount() != null) {
agent1stRatioOfAvailable = (double) dto2.getAgent1stAmount() / dto2.getCanSeparateAmount() * 100;
}
if (dto2.getAgent2ndAmount() != null) {
agent2ndRatioOfAvailable = (double) dto2.getAgent2ndAmount() / dto2.getCanSeparateAmount() * 100;
}
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
@ -175,41 +133,62 @@ public class LklSeparateDTO implements java.io.Serializable {
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto2.getLklAmount() + dto2.getMchAmount() + dto2.getPlatAmount() + dto2.getAgent1stAmount() + dto2.getAgent2ndAmount() + dto2.getShippingFee();
int total = dto2.getLklAmount() + dto2.getMchAmount() + dto2.getPlatAmount() +
(dto2.getAgent1stAmount() != null ? dto2.getAgent1stAmount() : 0) +
(dto2.getAgent2ndAmount() != null ? dto2.getAgent2ndAmount() : 0) + dto2.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto2.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto2.getTotalSeparateAmount());
}
// 临界点测试用例3: 中等金额测试
logger.info("\n\n临界点测试用例3 - 中等金额测试:");
LklSeparateDTO dto3 = new LklSeparateDTO();
dto3.setTotalSeparateAmount(100);
dto3.setShippingFee(30);
dto3.setLklRatio(new BigDecimal("0.0025"));
dto3.setMchRatio(new BigDecimal("0.94"));
dto3.setPlatRatio(new BigDecimal("0.06"));
// 基于总金额的分账计算测试用例带refCanSeparateAmount
logger.info("\n\n基于总金额的分账计算测试用例 - 带参考可分账金额:");
LklSeparateDTO dto7 = new LklSeparateDTO();
dto7.setTotalSeparateAmount(502);
dto7.setShippingFee(500);
dto7.setLklRatio(new BigDecimal("0.0025")); // 拉卡拉比例0.25%
dto7.setMchRatio(new BigDecimal("0.94")); // 商户比例90%
dto7.setPlatRatio(new BigDecimal("0.01")); // 平台比例5%
// dto7.setAgent1stRatio(new BigDecimal("0.01"));
// dto7.setAgent2ndRatio(new BigDecimal("0.04"));
// dto7.setRefCanSeparateAmount(8079); // 设置参考可分账金额
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}",
dto3.getTotalSeparateAmount(), dto3.getShippingFee(), dto3.getLklRatio(),
dto3.getMchRatio(), dto3.getPlatRatio());
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}, 一级代理商比例={}, 二级代理商比例={}, 参考可分账金额={}分",
dto7.getTotalSeparateAmount(), dto7.getShippingFee(), dto7.getLklRatio(), dto7.getMchRatio(),
dto7.getPlatRatio(), dto7.getAgent1stRatio(), dto7.getAgent2ndRatio(), dto7.getRefCanSeparateAmount());
boolean result3 = dto3.calculateProfitSharing();
logger.info("分账计算结果: {}", (result3 ? "成功" : "失败"));
if (result3) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto3.getTotalSeparateAmount(), dto3.getLklAmount(), dto3.getShippingFee(), dto3.getCanSeparateAmount(),
dto3.getMchAmount(), dto3.getPlatAmount(), dto3.getAgent1stAmount(), dto3.getAgent2ndAmount());
boolean result7 = dto7.calcOnTotalAmount();
logger.info("基于总金额的分账计算结果(带参考可分账金额): {}", (result7 ? "成功" : "失败"));
if (result7) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 参考可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto7.getTotalSeparateAmount(), dto7.getLklAmount(), dto7.getShippingFee(), dto7.getCanSeparateAmount(), dto7.getRefCanSeparateAmount(),
dto7.getMchAmount(), dto7.getPlatAmount(), dto7.getAgent1stAmount(), dto7.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto3.getLklAmount() / dto3.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto3.getMchAmount() / dto3.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto3.getPlatAmount() / dto3.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto3.getAgent1stAmount() / dto3.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto3.getAgent2ndAmount() / dto3.getTotalSeparateAmount() * 100;
double lklRatioOfTotal = (double) dto7.getLklAmount() / dto7.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto7.getMchAmount() / dto7.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto7.getPlatAmount() / dto7.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = 0.0;
double agent2ndRatioOfTotal = 0.0;
double lklRatioOfAvailable = (double) dto3.getLklAmount() / dto3.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto3.getMchAmount() / dto3.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto3.getPlatAmount() / dto3.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto3.getAgent1stAmount() / dto3.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto3.getAgent2ndAmount() / dto3.getCanSeparateAmount() * 100;
// 安全检查避免空指针异常
if (dto7.getAgent1stAmount() != null) {
agent1stRatioOfTotal = (double) dto7.getAgent1stAmount() / dto7.getTotalSeparateAmount() * 100;
}
if (dto7.getAgent2ndAmount() != null) {
agent2ndRatioOfTotal = (double) dto7.getAgent2ndAmount() / dto7.getTotalSeparateAmount() * 100;
}
double lklRatioOfAvailable = (double) dto7.getLklAmount() / dto7.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto7.getMchAmount() / dto7.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto7.getPlatAmount() / dto7.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = 0.0;
double agent2ndRatioOfAvailable = 0.0;
// 安全检查避免空指针异常
if (dto7.getAgent1stAmount() != null) {
agent1stRatioOfAvailable = (double) dto7.getAgent1stAmount() / dto7.getCanSeparateAmount() * 100;
}
if (dto7.getAgent2ndAmount() != null) {
agent2ndRatioOfAvailable = (double) dto7.getAgent2ndAmount() / dto7.getCanSeparateAmount() * 100;
}
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
@ -218,96 +197,124 @@ public class LklSeparateDTO implements java.io.Serializable {
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto3.getLklAmount() + dto3.getMchAmount() + dto3.getPlatAmount() + dto3.getAgent1stAmount() + dto3.getAgent2ndAmount() + dto3.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto3.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto3.getTotalSeparateAmount());
}
int total = dto7.getLklAmount() + dto7.getMchAmount() + dto7.getPlatAmount() +
(dto7.getAgent1stAmount() != null ? dto7.getAgent1stAmount() : 0) +
(dto7.getAgent2ndAmount() != null ? dto7.getAgent2ndAmount() : 0) + dto7.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto7.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto7.getTotalSeparateAmount());
// 临界点测试用例4: 大金额测试
logger.info("\n\n临界点测试用例4 - 大金额测试:");
LklSeparateDTO dto4 = new LklSeparateDTO();
dto4.setTotalSeparateAmount(10000);
dto4.setShippingFee(500);
dto4.setLklRatio(new BigDecimal("0.0025"));
dto4.setMchRatio(new BigDecimal("0.94"));
dto4.setPlatRatio(new BigDecimal("0.06"));
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}",
dto4.getTotalSeparateAmount(), dto4.getShippingFee(), dto4.getLklRatio(),
dto4.getMchRatio(), dto4.getPlatRatio());
boolean result4 = dto4.calculateProfitSharing();
logger.info("分账计算结果: {}", (result4 ? "成功" : "失败"));
if (result4) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto4.getTotalSeparateAmount(), dto4.getLklAmount(), dto4.getShippingFee(), dto4.getCanSeparateAmount(),
dto4.getMchAmount(), dto4.getPlatAmount(), dto4.getAgent1stAmount(), dto4.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto4.getLklAmount() / dto4.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto4.getMchAmount() / dto4.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto4.getPlatAmount() / dto4.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto4.getAgent1stAmount() / dto4.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto4.getAgent2ndAmount() / dto4.getTotalSeparateAmount() * 100;
double lklRatioOfAvailable = (double) dto4.getLklAmount() / dto4.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto4.getMchAmount() / dto4.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto4.getPlatAmount() / dto4.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto4.getAgent1stAmount() / dto4.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto4.getAgent2ndAmount() / dto4.getCanSeparateAmount() * 100;
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
String.format("%.4f", mchRatioOfTotal), String.format("%.4f", mchRatioOfAvailable),
String.format("%.4f", platRatioOfTotal), String.format("%.4f", platRatioOfAvailable),
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto4.getLklAmount() + dto4.getMchAmount() + dto4.getPlatAmount() + dto4.getAgent1stAmount() + dto4.getAgent2ndAmount() + dto4.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto4.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto4.getTotalSeparateAmount());
// 验证refCanSeparateAmount是否生效
if (dto7.getCanSeparateAmount().equals(dto7.getRefCanSeparateAmount())) {
logger.info("参考可分账金额生效: 计算出的可分账金额={}分, 参考可分账金额={}分", dto7.getCanSeparateAmount(), dto7.getRefCanSeparateAmount());
} else {
logger.info("参考可分账金额未生效: 计算出的可分账金额={}分, 参考可分账金额={}分", dto7.getCanSeparateAmount(), dto7.getRefCanSeparateAmount());
}
}
}
/**
* 根据实体类的属性扣除拉卡拉手续费配送费之后从可用余额中进行分账计算
* 基于可分账金额的分账计算方法默认
* <p>
* 计算逻辑
* 1. 计算拉卡拉分账金额 = 总分账金额 × 拉卡拉分账比例向上取整
* 2. 计算可分账金额 = 总分账金额 - 配送费 - 拉卡拉分账金额
* 3. 如果refCanSeparateAmount有效且与计算出的canSeparateAmount不一致则使用refCanSeparateAmount作为分账基数
* 4. 调整分账比例确保有效比例之和等于1.0
* 5. 按优先级计算各参与方分账金额商户 > 平台 > 一级代理商 > 二级代理商
* 6. 确保总额平衡并保障各参与方分账比例
*
* @return 分账结果是否成功
*/
public boolean calculateProfitSharing() {
public boolean calcOnCanAmount() {
// 检查前提条件
if (!validateInputs()) {
return false;
}
logger.info("开始分账计算,分账总金额={}分,配送费={}分", totalSeparateAmount, shippingFee);
logger.info("开始基于可分账金额的分账计算,总金额={}分,配送费={}分", totalSeparateAmount, shippingFee);
// 1. 计算拉卡拉分账金额 = 分账总金额 × 拉卡拉分账比例向上取整
// 计算拉卡拉分账金额
calculateLklAmount();
// 2. 计算可分账金额 = 分账总金额 - 配送费 - 拉卡拉分账金额
// 计算可分账金额
calculateCanSeparateAmount();
if (canSeparateAmount <= 0) {
logger.info("分账计算失败可分账金额必须大于0当前值={}", canSeparateAmount);
return false;
}
// 3. 调整分账比例确保有效值比例相加等于1.0
// 检查并应用参考可分账金额
checkAndApplyRefCanSeparateAmount();
// 调整分账比例
adjustRatios();
// 4. 按优先级分账商户 > 平台 > 一级代理商 > 二级代理商
// 按优先级分账
performSeparate();
// 5. 确保总额平衡
// 确保总额平衡
balanceAmounts();
// 6. 保障各参与方分账比例不低于指定值
// 保障各参与方分账比例
guaranteeRatios();
logger.info("分账计算完成:商户={},平台={},一级代理商={},二级代理商={}",
mchAmount, platAmount, agent1stAmount, agent2ndAmount);
logger.info("基于可分账金额的分账计算完成:拉卡拉={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分, 商户={}分",
lklAmount, platAmount, agent1stAmount, agent2ndAmount, mchAmount);
return true;
}
/**
* 验证输入参数
* 基于总金额的分账计算方法
* <p>
* 计算逻辑
* 1. 计算拉卡拉分账金额 = 总分账金额 × 拉卡拉分账比例四舍五入保留两位小数
* 2. 计算可分账金额 = 总分账金额 - 配送费 - 拉卡拉分账金额
* 3. 如果refCanSeparateAmount有效且与计算出的canSeparateAmount不一致则使用refCanSeparateAmount作为分账基数
* 4. 调整分账比例确保有效比例之和等于1.0
* 5. 按优先级计算各参与方分账金额平台 > 一级代理商 > 二级代理商 > 商户基于可分账金额进行分账
* 6. 确保总额平衡并保障各参与方分账比例
*
* @return 分账结果是否成功
*/
public boolean calcOnTotalAmount() {
// 检查前提条件
if (!validateInputsForTotalAmount()) {
return false;
}
logger.info("开始基于总金额的分账计算,总金额={}分,配送费={}分", totalSeparateAmount, shippingFee);
// 计算拉卡拉分账金额
calculateLklAmount();
// 计算可分账金额
calculateCanSeparateAmount();
if (canSeparateAmount <= 0) {
logger.info("基于总金额的分账计算失败可分账金额必须大于0当前值={}", canSeparateAmount);
return false;
}
// 调整分账比例
adjustRatios();
// 检查并应用参考可分账金额
checkAndApplyRefCanSeparateAmount();
// 按优先级分账基于总金额
performSeparateBasedOnTotalAmount();
// 确保总额平衡
balanceAmounts();
// 保障各参与方分账比例忽略商户比例保障
guaranteeRatiosForTotalAmount();
logger.info("基于总金额的分账计算完成:拉卡拉={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分, 商户={}分",
lklAmount, platAmount, agent1stAmount, agent2ndAmount, mchAmount);
return true;
}
/**
* 验证基于可分账金额分账的输入参数
*/
private boolean validateInputs() {
if (totalSeparateAmount == null || totalSeparateAmount <= 0) {
@ -338,13 +345,22 @@ public class LklSeparateDTO implements java.io.Serializable {
return true;
}
/**
* 验证基于总金额分账的输入参数
*/
private boolean validateInputsForTotalAmount() {
// 基于总金额的分账计算参数验证与基于可分账金额的验证相同
return validateInputs();
}
/**
* 计算拉卡拉分账金额
*/
private void calculateLklAmount() {
BigDecimal lklAmountDecimal = new BigDecimal(totalSeparateAmount).multiply(lklRatio);
lklAmount = lklAmountDecimal.setScale(0, RoundingMode.UP).intValue();
logger.debug("拉卡拉分账计算:分账总金额{} × 拉卡拉比例{} = {},向上取整后={}", totalSeparateAmount, lklRatio, lklAmountDecimal, lklAmount);
lklAmount = lklAmountDecimal.setScale(2, RoundingMode.HALF_UP).intValue();
logger.debug("拉卡拉分账计算:总金额{} × 拉卡拉比例{} = {},四舍五入保留两位小数后={}",
totalSeparateAmount, lklRatio, lklAmountDecimal, lklAmount);
}
/**
@ -352,7 +368,59 @@ public class LklSeparateDTO implements java.io.Serializable {
*/
private void calculateCanSeparateAmount() {
canSeparateAmount = totalSeparateAmount - shippingFee - lklAmount;
logger.debug("可分账金额计算:分账总金额{} - 配送费{} - 拉卡拉分账{} = {}", totalSeparateAmount, shippingFee, lklAmount, canSeparateAmount);
logger.debug("可分账金额计算:总金额{} - 配送费{} - 拉卡拉分账{} = {}",
totalSeparateAmount, shippingFee, lklAmount, canSeparateAmount);
}
/**
* 检查并应用参考可分账金额
* 当refCanSeparateAmount有效且与计算出的canSeparateAmount不一致时
* 将差额分配给指定优先级的分账对象平台 > 一级代理商 > 二级代理商 > 商户
*/
private void checkAndApplyRefCanSeparateAmount() {
// 判断refCanSeparateAmount是否有效(大于0且小于等于totalSeparateAmount)
if (refCanSeparateAmount != null && refCanSeparateAmount > 0 && refCanSeparateAmount <= totalSeparateAmount) {
// 比较refCanSeparateAmount与计算出的canSeparateAmount
if (!refCanSeparateAmount.equals(canSeparateAmount)) {
int difference = refCanSeparateAmount - canSeparateAmount;
logger.info("检测到refCanSeparateAmount与计算出的canSeparateAmount不一致差额={}分。" +
"refCanSeparateAmount={}分计算出的canSeparateAmount={}分。",
difference, refCanSeparateAmount, canSeparateAmount);
// 使用refCanSeparateAmount作为分账基数
canSeparateAmount = refCanSeparateAmount;
// 将差额分配给指定优先级的分账对象商户 > 二级代理商 > 一级代理商 > 平台
if (difference != 0) {
if (mchAmount == null) mchAmount = 0;
if (agent2ndAmount == null) agent2ndAmount = 0;
if (agent1stAmount == null) agent1stAmount = 0;
if (platAmount == null) platAmount = 0;
if (difference > 0) {
// 如果差额为正优先增加高优先级参与方的金额
mchAmount += difference;
} else {
// 如果差额为负优先从低优先级参与方扣除
int diff = -difference;
if (mchAmount >= diff) {
mchAmount -= diff;
} else if (agent2ndAmount > 0 && agent2ndAmount >= diff) {
agent2ndAmount -= diff;
} else if (agent1stAmount > 0 && agent1stAmount >= diff) {
agent1stAmount -= diff;
} else {
platAmount -= diff;
}
}
logger.debug("已将差额{}分应用于分账金额调整", difference);
}
} else {
logger.debug("refCanSeparateAmount与计算出的canSeparateAmount一致值={}分", canSeparateAmount);
}
} else {
logger.debug("refCanSeparateAmount无效或未设置使用计算出的canSeparateAmount={}分作为分账基数", canSeparateAmount);
}
}
/**
@ -363,8 +431,8 @@ public class LklSeparateDTO implements java.io.Serializable {
BigDecimal effectiveRatioSum = BigDecimal.ZERO
.add(mchRatio)
.add(platRatio)
.add(getOrDefault(agent1stRatio, BigDecimal.ZERO))
.add(getOrDefault(agent2ndRatio, BigDecimal.ZERO));
.add(agent1stRatio != null ? agent1stRatio : BigDecimal.ZERO)
.add(agent2ndRatio != null ? agent2ndRatio : BigDecimal.ZERO);
logger.debug("调整前各分账比例:商户={},平台={},一级代理商={},二级代理商={},有效比例总和={}",
mchRatio, platRatio, agent1stRatio, agent2ndRatio, effectiveRatioSum);
@ -387,7 +455,7 @@ public class LklSeparateDTO implements java.io.Serializable {
}
/**
* 按优先级进行分账
* 按优先级进行分账基于可分账金额
*/
private void performSeparate() {
// 商户分账金额 = 可分账金额 × 商户分账比例四舍五入
@ -419,6 +487,39 @@ public class LklSeparateDTO implements java.io.Serializable {
}
}
/**
* 按优先级进行分账基于总金额
*/
private void performSeparateBasedOnTotalAmount() {
// 平台分账金额 = 可分账金额 × 平台分账比例四舍五入
BigDecimal platAmountDecimal = new BigDecimal(canSeparateAmount).multiply(platRatio);
platAmount = platAmountDecimal.setScale(0, RoundingMode.HALF_UP).intValue();
logger.debug("平台分账计算:可分账金额{} × 平台比例{} = {},四舍五入后={}", canSeparateAmount, platRatio, platAmountDecimal, platAmount);
// 一级代理商分账金额 = 可分账金额 × 一级代理商分账比例四舍五入
if (agent1stRatio != null && agent1stRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal agent1stAmountDecimal = new BigDecimal(canSeparateAmount).multiply(agent1stRatio);
agent1stAmount = agent1stAmountDecimal.setScale(0, RoundingMode.HALF_UP).intValue();
logger.debug("一级代理商分账计算:可分账金额{} × 一级代理商比例{} = {},四舍五入后={}", canSeparateAmount, agent1stRatio, agent1stAmountDecimal, agent1stAmount);
} else {
agent1stAmount = 0;
}
// 二级代理商分账金额 = 可分账金额 × 二级代理商分账比例四舍五入
if (agent2ndRatio != null && agent2ndRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal agent2ndAmountDecimal = new BigDecimal(canSeparateAmount).multiply(agent2ndRatio);
agent2ndAmount = agent2ndAmountDecimal.setScale(0, RoundingMode.HALF_UP).intValue();
logger.debug("二级代理商分账计算:可分账金额{} × 二级代理商比例{} = {},四舍五入后={}", canSeparateAmount, agent2ndRatio, agent2ndAmountDecimal, agent2ndAmount);
} else {
agent2ndAmount = 0;
}
// 商户分账金额 = 可分账金额 - 平台分账金额 - 一级代理商分账金额 - 二级代理商分账金额
mchAmount = canSeparateAmount - platAmount - agent1stAmount - agent2ndAmount;
logger.debug("商户分账计算:可分账金额{} - 平台分账{} - 一级代理商分账{} - 二级代理商分账{} = {}",
canSeparateAmount, platAmount, agent1stAmount, agent2ndAmount, mchAmount);
}
/**
* 确保总额平衡
*/
@ -429,7 +530,10 @@ public class LklSeparateDTO implements java.io.Serializable {
logger.debug("分账总额与可分账金额不符,差额={},开始调整", diff);
// 将差额分配给优先级最低的参与方确保总额平衡
if (agent2ndAmount > 0 && agent2ndAmount + diff >= 0) {
if (mchAmount + diff >= 0) {
mchAmount += diff;
logger.debug("调整商户分账金额:{} + {} = {}", mchAmount - diff, diff, mchAmount);
} else if (agent2ndAmount > 0 && agent2ndAmount + diff >= 0) {
agent2ndAmount += diff;
logger.debug("调整二级代理商分账金额:{} + {} = {}", agent2ndAmount - diff, diff, agent2ndAmount);
} else if (agent1stAmount > 0 && agent1stAmount + diff >= 0) {
@ -438,13 +542,24 @@ public class LklSeparateDTO implements java.io.Serializable {
} else if (platAmount + diff >= 0) {
platAmount += diff;
logger.debug("调整平台分账金额:{} + {} = {}", platAmount - diff, diff, platAmount);
} else {
mchAmount += diff;
logger.debug("调整商户分账金额:{} + {} = {}", mchAmount - diff, diff, mchAmount);
}
}
}
/**
* 保障各参与方分账比例不低于指定值基于总金额的分账计算专用
* 忽略商户比例保障只保障平台一级代理商二级代理商的比例
*/
private void guaranteeRatiosForTotalAmount() {
// 平台优先级最高
guaranteePlatformRatio();
// 一级代理商优先级次之
guaranteeAgent1stRatio();
// 不保障商户比例商户只分到剩余余额
}
/**
* 保障各参与方分账比例不低于指定值
*/