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

这篇具有很好参考价值的文章主要介绍了流量阶梯 用量按照日、月、年、自然月、自然年,周期叠加分段计算各个阶梯金额。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、前言

1.1 商品有三个属性

周期时长、周期单位、叠加次数文章来源地址https://www.toymoban.com/news/detail-861871.html

商品 周期时长 周期单位 叠加次数
A 4 2
B 1 6

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月
用量 10KB 50KB 90KB 91KB 101KB 102KB
>0 30元 30元 30元 30元 30元 30元
>90 0元 0元 0元 0.19元 1.9元 1.9元
>100 0元 0元 0元 0元 0.12元 0.24元

2.1 A商品结果

A商品
1月 2月 3月 4月 5月 6月 7月 8月 9月
用量 10KB 50KB 31KB 10KB 10KB 50KB 31KB 10KB
>0 30元 0元 0元 0元 30元 0元 0元 0元
>90 0元 0元 0.19元 1.71元 0元 0元 0.19元 1.71元
>100 0元 0元 0元 0.12元 0元 0元 0元 0.12元

3、算法实现

@Slf4j
public class FlowSegmentGoodsClearingAlgorithm {

    private static String flowSegmentRuleStr = "{\"flowLadderList\":" + "[" +
            "{\"beginLadder\":0,\"calculationType\":1,\"unit\":\"KB\",\"unitPrice\":30.0000000000}," +
            "{\"beginLadder\":90,\"calculationType\":0,\"unit\":\"KB\",\"unitPrice\":0.1900000000}," +
            "{\"beginLadder\":100,\"calculationType\":0,\"unit\":\"KB\",\"unitPrice\":0.1200000000}" +
            "]}";

    public static void main(String[] args) {
        Map<String, BigDecimal> quantityMap = new HashMap<>();
//        quantityMap.put("2023-01", new BigDecimal("10"));
//        quantityMap.put("2023-02", new BigDecimal("50"));
//        quantityMap.put("2023-03", new BigDecimal("90"));
//        quantityMap.put("2023-04", new BigDecimal("91"));
//        quantityMap.put("2023-05", new BigDecimal("101"));
//        quantityMap.put("2023-06", new BigDecimal("102"));

        quantityMap.put("2023-01", new BigDecimal("10"));
        quantityMap.put("2023-02", new BigDecimal("50"));
        quantityMap.put("2023-03", new BigDecimal("31"));
        quantityMap.put("2023-04", new BigDecimal("10"));
//        quantityMap.put("2023-05", new BigDecimal("101"));
//        quantityMap.put("2023-06", new BigDecimal("102"));

        FlowSegmentRule flowSegmentRule = JSON.parseObject(flowSegmentRuleStr, FlowSegmentRule.class);
        List<FlowLadder> flowLadderList = flowSegmentRule.getFlowLadderList();

        // 将分段阶梯转换成算法阶梯
        List<FlowSegmentGoodsClearingAlgorithm.QuantityLadder> ladderList = new ArrayList<>();
        for (int i = 0; i < flowLadderList.size(); i++) {
            FlowLadder flowLadder = flowLadderList.get(i);
            if (i < flowLadderList.size() - 1) {
                ladderList.add(new FlowSegmentGoodsClearingAlgorithm.QuantityLadder(flowLadder.getBeginLadder(), flowLadderList.get(i + 1).getBeginLadder(), flowLadder.getUnitPrice(), flowLadder.getCalculationType(), flowLadder.getUnit()));
            } else {
                ladderList.add(new FlowSegmentGoodsClearingAlgorithm.QuantityLadder(flowLadder.getBeginLadder(), new BigDecimal(Long.MAX_VALUE), flowLadder.getUnitPrice(), flowLadder.getCalculationType(), flowLadder.getUnit()));
            }
        }

        List<QuantityLadderAmountResult> calc = calc(quantityMap, ladderList);
        calc.forEach(System.out::println);
    }


    /**
     * 账期内数量(流量)分段计算
     *
     * @param quantityMap        key 账期 value 账期使用流量
     * @param quantityLadderList 分段配置
     * @return
     */
    public static List<QuantityLadderAmountResult> calc(Map<String, BigDecimal> quantityMap, List<QuantityLadder> quantityLadderList) {
        Assert.notEmpty(quantityMap, "quantityMap cannot empty");
        Assert.notEmpty(quantityLadderList, "quantityLadderList cannot empty");

        List<String> billDateSortList = quantityMap.keySet().stream().sorted(String::compareTo).collect(Collectors.toList());
        BigDecimal totalFlowMB = BigDecimal.ZERO;

        List<QuantityLadderAmountResult> resultList = new ArrayList<>();

        String unit = quantityLadderList.get(0).getUnit();

        int index = 0;
        for (String billDate : billDateSortList) {
            if (null == quantityMap.get(billDate) || BigDecimal.ZERO.equals(quantityMap.get(billDate))) {
                log.debug("calc 账期 {} 内流量为O,直接返回0元账单", billDate);
                quantityLadderList.forEach(i -> resultList.add(assembleZeroBill(billDate, i)));
                continue;
            }

            totalFlowMB = totalFlowMB.add(quantityMap.get(billDate));
            Map<Integer, QuantityLadderAmountResult> resultMap = new HashMap<>();
            BigDecimal converterUsage;
            if (FlowUnitEnum.GB.name().equals(unit)) {
                converterUsage = FlowUnitConverter.toGB(FlowUnitEnum.KB.name(), totalFlowMB);
            } else if (FlowUnitEnum.MB.name().equals(unit)) {
                converterUsage = FlowUnitConverter.toMB(FlowUnitEnum.KB.name(), totalFlowMB);
            } else {
                converterUsage = totalFlowMB;
            }
            // 补已达标废弃的阶梯
            if (index > 0) {
                for (int i = 0; i < index; i++) {
                    if (!resultMap.containsKey(i)) {
                        resultMap.put(i, assembleZeroBill(billDate, quantityLadderList.get(i)));
                    }
                }
            }

            for (int i = index; i < quantityLadderList.size(); i++) {

                QuantityLadder e = quantityLadderList.get(i);

                BigDecimal matchFlowMB = e.match(converterUsage, quantityMap.get(billDate));
                if (null == matchFlowMB) {
                    index = i;
                    break;
                }
                if (i == quantityLadderList.size() - 1) {
                    index = i;
                }
                log.info("calc 账期 {} 总流量 {} 当期流量 {} 中有 {} 命中  {} - ({} , {}]", billDate, converterUsage, quantityMap.get(billDate), matchFlowMB, i, e.getBeginQuantity(), e.getEndQuantity());

                QuantityLadderAmountResult result = new QuantityLadderAmountResult();
                result.setBillDate(billDate);
                result.setQuantity(quantityMap.get(billDate));
                result.setMatchQuantity(matchFlowMB);
                result.setAmount(e.calcAmount(matchFlowMB));

                result.setPriceType(e.getPriceType());
                result.setPrice(e.getPrice());
                result.setTotalQuantity(converterUsage);
                result.setLadderDesc(e.getLadderDesc());
                result.setLadderIndex(i);
                result.setUnit(e.getUnit());
                resultMap.put(i, result);
                if (converterUsage.compareTo(e.getEndQuantity()) < 0) {
                    index = i;
                    break;
                }
            }

            // 补其他阶梯
            for (int i = index; i < quantityLadderList.size(); i++) {
                if (!resultMap.containsKey(i)) {
                    resultMap.put(i, assembleZeroBill(billDate, quantityLadderList.get(i)));
                }
            }

            resultList.addAll(resultMap.values());

        }
        return resultList;
    }

    private static QuantityLadderAmountResult assembleZeroBill(String billDate, QuantityLadder ladder) {
        QuantityLadderAmountResult result = new QuantityLadderAmountResult();
        result.setBillDate(billDate);
        result.setQuantity(BigDecimal.ZERO);
        result.setMatchQuantity(BigDecimal.ZERO);
        result.setAmount(BigDecimal.ZERO);
        result.setPrice(ladder.getPrice());
        result.setPriceType(ladder.priceType);
        result.setUnit(ladder.getUnit());
        result.setLadderDesc(ladder.getLadderDesc());
        return result;
    }


    @Data
    public static class QuantityLadder {
        private static final Integer TOTAL_PRICE_FLAG = 1;
        private boolean isTotalPriceLock = false;

        /**
         * 分段开始数量
         */
        private BigDecimal beginQuantity;
        /**
         * 分段结束数量
         */
        private BigDecimal endQuantity;
        /**
         * 分段价格
         */
        private BigDecimal price;
        /**
         * 单位
         */
        private String unit;
        /**
         * 价格类型: 0-单价(默认),1-总价
         */
        private Integer priceType;

        public QuantityLadder() {
        }

        public QuantityLadder(BigDecimal beginQuantity, BigDecimal endQuantity, BigDecimal price, Integer priceType, String unit) {
            this.beginQuantity = beginQuantity;
            this.endQuantity = endQuantity;
            this.price = price;
            this.priceType = priceType;
            this.unit = unit;
        }

        /**
         * 分段阶梯匹配
         *
         * @param totalQuantity 账期在整改计算周期内总数量
         * @param quantity      账期的数量
         * @return 账期内匹配该阶梯的数量 null 则标识未匹配该阶梯
         */
        public BigDecimal match(BigDecimal totalQuantity, BigDecimal quantity) {
            boolean isMatch = beginQuantity.compareTo(totalQuantity) < 0;
            if (!isMatch) {
                return null;
            }

            if (endQuantity.compareTo(totalQuantity) < 0) {
                return min(endQuantity.subtract(totalQuantity.subtract(quantity)), endQuantity.subtract(beginQuantity));
            } else {
                return min(totalQuantity.subtract(beginQuantity), quantity);
            }
        }

        /**
         * 阶梯内金额计算
         * 1 当阶梯配置是总价,如果该阶梯没有被计算过,直接返回总价格; 如果被计算过直接返回0
         * 2 当阶梯计算是单价,
         *
         * @param matchQuantity
         * @return
         */
        public BigDecimal calcAmount(BigDecimal matchQuantity) {
            if (TOTAL_PRICE_FLAG.equals(priceType)) {
                BigDecimal amount = isTotalPriceLock ? BigDecimal.ZERO : price;
                isTotalPriceLock = true;
                return amount;
            } else {
                return price.multiply(matchQuantity).setScale(2, RoundingMode.HALF_UP);
            }
        }

        /**
         * 阶梯描述
         *
         * @return
         */
        public String getLadderDesc() {
            if (String.valueOf(Long.MAX_VALUE).equals(endQuantity.toString())) {
                return MessageFormat.format("({0},{1})", beginQuantity, "+∞");
            }
            return MessageFormat.format("({0},{1}]", beginQuantity.toString(), endQuantity.toString());
        }

    }

    private static BigDecimal min(BigDecimal a, BigDecimal b) {
        return (a.compareTo(b) <= 0) ? a : b;
    }

    @Data
    public static class QuantityLadderAmountResult {

        /**
         * 账期 实际是业务账期
         */
        private String billDate;
        /**
         * 账期内数量
         */
        private BigDecimal quantity;
        /**
         * 账期内计算数量
         */
        private BigDecimal matchQuantity;
        /**
         * 价格类型: 0-单价(默认),1-总价
         */
        private Integer priceType;
        /**
         * 分段价格
         */
        private BigDecimal price;
        /**
         * 计算金额
         */
        private BigDecimal amount;
        /**
         * 账期在周期内总数量
         */
        private BigDecimal totalQuantity;
        /**
         * 匹配分段描述
         */
        private String ladderDesc;
        /**
         * 命中阶梯
         */
        private Integer ladderIndex;
        /**
         * 流量单位 KB MB GB
         */
        private String unit;
        /**
         * 扩展信息
         */
        private String extendInfo;
    }

}

到了这里,关于流量阶梯 用量按照日、月、年、自然月、自然年,周期叠加分段计算各个阶梯金额的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包