当前位置:   article > 正文

流量阶梯 用量按照日、月、年、自然月、自然年,周期叠加分段计算各个阶梯金额

流量阶梯 用量按照日、月、年、自然月、自然年,周期叠加分段计算各个阶梯金额

1、前言

1.1 商品有三个属性

周期时长、周期单位、叠加次数

商品周期时长周期单位叠加次数
A42
B16

1.2 商品配置阶梯

用量大于0GB, 流量总价30元
用量大于90GB, 流量单价0.19元/GB
用量大于100GB, 流量单价0.12元/GB

2、根据不同属性获取阶梯下金额

2.1 B商品结果

B商品
1月2月3月4月5月6月7月8月9月
用量10KB50KB90KB91KB101KB102KB
>030元30元30元30元30元30元
>900元0元0元0.19元1.9元1.9元
>1000元0元0元0元0.12元0.24元

2.1 A商品结果

A商品
1月2月3月4月5月6月7月8月9月
用量10KB50KB31KB10KB10KB50KB31KB10KB
>030元0元0元0元30元0元0元0元
>900元0元0.19元1.71元0元0元0.19元1.71元
>1000元0元0元0.12元0元0元0元0.12元

3、算法实现

  1. @Slf4j
  2. public class FlowSegmentGoodsClearingAlgorithm {
  3. private static String flowSegmentRuleStr = "{\"flowLadderList\":" + "[" +
  4. "{\"beginLadder\":0,\"calculationType\":1,\"unit\":\"KB\",\"unitPrice\":30.0000000000}," +
  5. "{\"beginLadder\":90,\"calculationType\":0,\"unit\":\"KB\",\"unitPrice\":0.1900000000}," +
  6. "{\"beginLadder\":100,\"calculationType\":0,\"unit\":\"KB\",\"unitPrice\":0.1200000000}" +
  7. "]}";
  8. public static void main(String[] args) {
  9. Map<String, BigDecimal> quantityMap = new HashMap<>();
  10. // quantityMap.put("2023-01", new BigDecimal("10"));
  11. // quantityMap.put("2023-02", new BigDecimal("50"));
  12. // quantityMap.put("2023-03", new BigDecimal("90"));
  13. // quantityMap.put("2023-04", new BigDecimal("91"));
  14. // quantityMap.put("2023-05", new BigDecimal("101"));
  15. // quantityMap.put("2023-06", new BigDecimal("102"));
  16. quantityMap.put("2023-01", new BigDecimal("10"));
  17. quantityMap.put("2023-02", new BigDecimal("50"));
  18. quantityMap.put("2023-03", new BigDecimal("31"));
  19. quantityMap.put("2023-04", new BigDecimal("10"));
  20. // quantityMap.put("2023-05", new BigDecimal("101"));
  21. // quantityMap.put("2023-06", new BigDecimal("102"));
  22. FlowSegmentRule flowSegmentRule = JSON.parseObject(flowSegmentRuleStr, FlowSegmentRule.class);
  23. List<FlowLadder> flowLadderList = flowSegmentRule.getFlowLadderList();
  24. // 将分段阶梯转换成算法阶梯
  25. List<FlowSegmentGoodsClearingAlgorithm.QuantityLadder> ladderList = new ArrayList<>();
  26. for (int i = 0; i < flowLadderList.size(); i++) {
  27. FlowLadder flowLadder = flowLadderList.get(i);
  28. if (i < flowLadderList.size() - 1) {
  29. ladderList.add(new FlowSegmentGoodsClearingAlgorithm.QuantityLadder(flowLadder.getBeginLadder(), flowLadderList.get(i + 1).getBeginLadder(), flowLadder.getUnitPrice(), flowLadder.getCalculationType(), flowLadder.getUnit()));
  30. } else {
  31. ladderList.add(new FlowSegmentGoodsClearingAlgorithm.QuantityLadder(flowLadder.getBeginLadder(), new BigDecimal(Long.MAX_VALUE), flowLadder.getUnitPrice(), flowLadder.getCalculationType(), flowLadder.getUnit()));
  32. }
  33. }
  34. List<QuantityLadderAmountResult> calc = calc(quantityMap, ladderList);
  35. calc.forEach(System.out::println);
  36. }
  37. /**
  38. * 账期内数量(流量)分段计算
  39. *
  40. * @param quantityMap key 账期 value 账期使用流量
  41. * @param quantityLadderList 分段配置
  42. * @return
  43. */
  44. public static List<QuantityLadderAmountResult> calc(Map<String, BigDecimal> quantityMap, List<QuantityLadder> quantityLadderList) {
  45. Assert.notEmpty(quantityMap, "quantityMap cannot empty");
  46. Assert.notEmpty(quantityLadderList, "quantityLadderList cannot empty");
  47. List<String> billDateSortList = quantityMap.keySet().stream().sorted(String::compareTo).collect(Collectors.toList());
  48. BigDecimal totalFlowMB = BigDecimal.ZERO;
  49. List<QuantityLadderAmountResult> resultList = new ArrayList<>();
  50. String unit = quantityLadderList.get(0).getUnit();
  51. int index = 0;
  52. for (String billDate : billDateSortList) {
  53. if (null == quantityMap.get(billDate) || BigDecimal.ZERO.equals(quantityMap.get(billDate))) {
  54. log.debug("calc 账期 {} 内流量为O,直接返回0元账单", billDate);
  55. quantityLadderList.forEach(i -> resultList.add(assembleZeroBill(billDate, i)));
  56. continue;
  57. }
  58. totalFlowMB = totalFlowMB.add(quantityMap.get(billDate));
  59. Map<Integer, QuantityLadderAmountResult> resultMap = new HashMap<>();
  60. BigDecimal converterUsage;
  61. if (FlowUnitEnum.GB.name().equals(unit)) {
  62. converterUsage = FlowUnitConverter.toGB(FlowUnitEnum.KB.name(), totalFlowMB);
  63. } else if (FlowUnitEnum.MB.name().equals(unit)) {
  64. converterUsage = FlowUnitConverter.toMB(FlowUnitEnum.KB.name(), totalFlowMB);
  65. } else {
  66. converterUsage = totalFlowMB;
  67. }
  68. // 补已达标废弃的阶梯
  69. if (index > 0) {
  70. for (int i = 0; i < index; i++) {
  71. if (!resultMap.containsKey(i)) {
  72. resultMap.put(i, assembleZeroBill(billDate, quantityLadderList.get(i)));
  73. }
  74. }
  75. }
  76. for (int i = index; i < quantityLadderList.size(); i++) {
  77. QuantityLadder e = quantityLadderList.get(i);
  78. BigDecimal matchFlowMB = e.match(converterUsage, quantityMap.get(billDate));
  79. if (null == matchFlowMB) {
  80. index = i;
  81. break;
  82. }
  83. if (i == quantityLadderList.size() - 1) {
  84. index = i;
  85. }
  86. log.info("calc 账期 {} 总流量 {} 当期流量 {} 中有 {} 命中 {} - ({} , {}]", billDate, converterUsage, quantityMap.get(billDate), matchFlowMB, i, e.getBeginQuantity(), e.getEndQuantity());
  87. QuantityLadderAmountResult result = new QuantityLadderAmountResult();
  88. result.setBillDate(billDate);
  89. result.setQuantity(quantityMap.get(billDate));
  90. result.setMatchQuantity(matchFlowMB);
  91. result.setAmount(e.calcAmount(matchFlowMB));
  92. result.setPriceType(e.getPriceType());
  93. result.setPrice(e.getPrice());
  94. result.setTotalQuantity(converterUsage);
  95. result.setLadderDesc(e.getLadderDesc());
  96. result.setLadderIndex(i);
  97. result.setUnit(e.getUnit());
  98. resultMap.put(i, result);
  99. if (converterUsage.compareTo(e.getEndQuantity()) < 0) {
  100. index = i;
  101. break;
  102. }
  103. }
  104. // 补其他阶梯
  105. for (int i = index; i < quantityLadderList.size(); i++) {
  106. if (!resultMap.containsKey(i)) {
  107. resultMap.put(i, assembleZeroBill(billDate, quantityLadderList.get(i)));
  108. }
  109. }
  110. resultList.addAll(resultMap.values());
  111. }
  112. return resultList;
  113. }
  114. private static QuantityLadderAmountResult assembleZeroBill(String billDate, QuantityLadder ladder) {
  115. QuantityLadderAmountResult result = new QuantityLadderAmountResult();
  116. result.setBillDate(billDate);
  117. result.setQuantity(BigDecimal.ZERO);
  118. result.setMatchQuantity(BigDecimal.ZERO);
  119. result.setAmount(BigDecimal.ZERO);
  120. result.setPrice(ladder.getPrice());
  121. result.setPriceType(ladder.priceType);
  122. result.setUnit(ladder.getUnit());
  123. result.setLadderDesc(ladder.getLadderDesc());
  124. return result;
  125. }
  126. @Data
  127. public static class QuantityLadder {
  128. private static final Integer TOTAL_PRICE_FLAG = 1;
  129. private boolean isTotalPriceLock = false;
  130. /**
  131. * 分段开始数量
  132. */
  133. private BigDecimal beginQuantity;
  134. /**
  135. * 分段结束数量
  136. */
  137. private BigDecimal endQuantity;
  138. /**
  139. * 分段价格
  140. */
  141. private BigDecimal price;
  142. /**
  143. * 单位
  144. */
  145. private String unit;
  146. /**
  147. * 价格类型: 0-单价(默认),1-总价
  148. */
  149. private Integer priceType;
  150. public QuantityLadder() {
  151. }
  152. public QuantityLadder(BigDecimal beginQuantity, BigDecimal endQuantity, BigDecimal price, Integer priceType, String unit) {
  153. this.beginQuantity = beginQuantity;
  154. this.endQuantity = endQuantity;
  155. this.price = price;
  156. this.priceType = priceType;
  157. this.unit = unit;
  158. }
  159. /**
  160. * 分段阶梯匹配
  161. *
  162. * @param totalQuantity 账期在整改计算周期内总数量
  163. * @param quantity 账期的数量
  164. * @return 账期内匹配该阶梯的数量 null 则标识未匹配该阶梯
  165. */
  166. public BigDecimal match(BigDecimal totalQuantity, BigDecimal quantity) {
  167. boolean isMatch = beginQuantity.compareTo(totalQuantity) < 0;
  168. if (!isMatch) {
  169. return null;
  170. }
  171. if (endQuantity.compareTo(totalQuantity) < 0) {
  172. return min(endQuantity.subtract(totalQuantity.subtract(quantity)), endQuantity.subtract(beginQuantity));
  173. } else {
  174. return min(totalQuantity.subtract(beginQuantity), quantity);
  175. }
  176. }
  177. /**
  178. * 阶梯内金额计算
  179. * 1 当阶梯配置是总价,如果该阶梯没有被计算过,直接返回总价格; 如果被计算过直接返回0
  180. * 2 当阶梯计算是单价,
  181. *
  182. * @param matchQuantity
  183. * @return
  184. */
  185. public BigDecimal calcAmount(BigDecimal matchQuantity) {
  186. if (TOTAL_PRICE_FLAG.equals(priceType)) {
  187. BigDecimal amount = isTotalPriceLock ? BigDecimal.ZERO : price;
  188. isTotalPriceLock = true;
  189. return amount;
  190. } else {
  191. return price.multiply(matchQuantity).setScale(2, RoundingMode.HALF_UP);
  192. }
  193. }
  194. /**
  195. * 阶梯描述
  196. *
  197. * @return
  198. */
  199. public String getLadderDesc() {
  200. if (String.valueOf(Long.MAX_VALUE).equals(endQuantity.toString())) {
  201. return MessageFormat.format("({0},{1})", beginQuantity, "+∞");
  202. }
  203. return MessageFormat.format("({0},{1}]", beginQuantity.toString(), endQuantity.toString());
  204. }
  205. }
  206. private static BigDecimal min(BigDecimal a, BigDecimal b) {
  207. return (a.compareTo(b) <= 0) ? a : b;
  208. }
  209. @Data
  210. public static class QuantityLadderAmountResult {
  211. /**
  212. * 账期 实际是业务账期
  213. */
  214. private String billDate;
  215. /**
  216. * 账期内数量
  217. */
  218. private BigDecimal quantity;
  219. /**
  220. * 账期内计算数量
  221. */
  222. private BigDecimal matchQuantity;
  223. /**
  224. * 价格类型: 0-单价(默认),1-总价
  225. */
  226. private Integer priceType;
  227. /**
  228. * 分段价格
  229. */
  230. private BigDecimal price;
  231. /**
  232. * 计算金额
  233. */
  234. private BigDecimal amount;
  235. /**
  236. * 账期在周期内总数量
  237. */
  238. private BigDecimal totalQuantity;
  239. /**
  240. * 匹配分段描述
  241. */
  242. private String ladderDesc;
  243. /**
  244. * 命中阶梯
  245. */
  246. private Integer ladderIndex;
  247. /**
  248. * 流量单位 KB MB GB
  249. */
  250. private String unit;
  251. /**
  252. * 扩展信息
  253. */
  254. private String extendInfo;
  255. }
  256. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/447971
推荐阅读
相关标签
  

闽ICP备14008679号