| | |
| | | package com.xiaopeng.order.utils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalTime; |
| | |
| | | PricingConfig config = setupMockConfig(); |
| | | |
| | | Calendar cal = Calendar.getInstance(); |
| | | cal.set(2023, Calendar.OCTOBER, 10, 16, 50, 0); |
| | | cal.set(2026, Calendar.MARCH, 13, 14, 40, 15); |
| | | Date orderTime = cal.getTime(); |
| | | |
| | | // 模拟总里程 25.0 km,以触发多级远途加价 |
| | | double totalMillige = 25.0; |
| | | double totalMillige = 7.874; |
| | | |
| | | // 模拟 GPS 轨迹点 |
| | | List<GpsPoint> points = createMockGpsPoints(orderTime); |
| | |
| | | BigDecimal totalMileage = BigDecimal.valueOf(totalMileageKm); |
| | | |
| | | PricingRule baseRule = config.findMatchedRule(orderTime); |
| | | result.put("起步价(" + baseRule.ruleName + ")", baseRule.baseFare); |
| | | result.put("起步价(" + baseRule.ruleName + ")"+"-"+baseRule.start+"-"+baseRule.end, baseRule.baseFare); |
| | | |
| | | if (totalMileage.compareTo(baseRule.baseKm) <= 0) { |
| | | return formatMap(result); |
| | |
| | | BigDecimal remainBaseM = baseRule.baseKm.multiply(new BigDecimal("1000")); |
| | | BigDecimal remainBaseMs = baseRule.baseMin.multiply(new BigDecimal("60000")); |
| | | BigDecimal currentTotalM = BigDecimal.ZERO; |
| | | BigDecimal currentTotalMs = BigDecimal.ZERO; |
| | | |
| | | boolean isOverBaseM = false; |
| | | boolean isOverBaseMs = false; |
| | | |
| | | // 超出里程和分钟数的集合 |
| | | Map<String, BigDecimal> bill = new LinkedHashMap<>(); |
| | | Map<String, BigDecimal> activeRuleNameRate = new LinkedHashMap<>(); |
| | | Set<String> activeRuleNameList = new HashSet<>(); |
| | | |
| | | for (int i = 1; i < points.size(); i++) { |
| | | GpsPoint curr = points.get(i); |
| | |
| | | BigDecimal segM = BigDecimal.valueOf(curr.getDistance()); |
| | | BigDecimal segMs = BigDecimal.valueOf(curr.getLocatetime() - prev.getLocatetime()); |
| | | currentTotalM = currentTotalM.add(segM); // 累计当前已行驶的总里程 |
| | | currentTotalMs = currentTotalMs.add(segMs); // 累计当前已行驶的总时间 |
| | | |
| | | PricingRule activeRule = config.findMatchedRule(new Date(curr.getLocatetime())); |
| | | |
| | | // 扣除起步额度逻辑 |
| | | BigDecimal billM = BigDecimal.ZERO; |
| | | if (remainBaseM.compareTo(BigDecimal.ZERO) > 0) { |
| | | if (segM.compareTo(remainBaseM) > 0) { |
| | | billM = segM.subtract(remainBaseM); |
| | | remainBaseM = BigDecimal.ZERO; |
| | | } else { |
| | | remainBaseM = remainBaseM.subtract(segM); |
| | | } |
| | | } else { |
| | | billM = segM; |
| | | if (currentTotalM.compareTo(remainBaseM) > 0) { |
| | | // billM = currentTotalM.subtract(remainBaseM); |
| | | isOverBaseM = true; |
| | | } |
| | | |
| | | BigDecimal billMs = BigDecimal.ZERO; |
| | | if (remainBaseMs.compareTo(BigDecimal.ZERO) > 0) { |
| | | if (segMs.compareTo(remainBaseMs) > 0) { |
| | | billMs = segMs.subtract(remainBaseMs); |
| | | remainBaseMs = BigDecimal.ZERO; |
| | | } else { |
| | | remainBaseMs = remainBaseMs.subtract(segMs); |
| | | } |
| | | } else { |
| | | billMs = segMs; |
| | | if (currentTotalMs.compareTo(remainBaseMs) > 0) { |
| | | // billMs = currentTotalMs.subtract(remainBaseMs); |
| | | isOverBaseMs = true; |
| | | } |
| | | |
| | | // 1. 基础费率计算 |
| | | BigDecimal distFee = billM.divide(new BigDecimal(1000), 10, RoundingMode.HALF_UP).multiply(activeRule.perKmRate); |
| | | BigDecimal timeFee = billMs.divide(new BigDecimal(60000), 10, RoundingMode.HALF_UP).multiply(activeRule.perMinRate); |
| | | // BigDecimal distFee = billM.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP).multiply(activeRule.perKmRate); |
| | | // BigDecimal timeFee = billMs.multiply(activeRule.perMinRate); |
| | | |
| | | // 2. 远途加价计算 (基于区间判定) |
| | | BigDecimal longExtra = calculateLongDistExtra(currentTotalM, billM, config.longSteps); |
| | | // BigDecimal longExtra = calculateLongDistExtra(currentTotalM, billM, config.longSteps); |
| | | |
| | | BigDecimal totalSeg = distFee.add(timeFee).add(longExtra); |
| | | result.put(activeRule.ruleName, result.getOrDefault(activeRule.ruleName, BigDecimal.ZERO).add(totalSeg)); |
| | | // BigDecimal totalSeg = distFee.add(timeFee).add(longExtra); |
| | | // 超出里程 |
| | | if (isOverBaseM) { |
| | | bill.put(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ml", bill.getOrDefault(activeRule.ruleName + activeRule.start + "-" + activeRule.end + "_Ml", BigDecimal.ZERO).add(segM)); |
| | | } |
| | | // 超出时间 |
| | | if (isOverBaseMs) { |
| | | bill.put(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ms", bill.getOrDefault(activeRule.ruleName + activeRule.start + "-" + activeRule.end + "_Ms", BigDecimal.ZERO).add(segMs)); |
| | | } |
| | | |
| | | // 若不存在规则名称,则添加 |
| | | activeRuleNameRate.computeIfAbsent(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ml", k -> activeRule.perKmRate); // 超出里程的加价 |
| | | activeRuleNameRate.computeIfAbsent(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ms",k ->activeRule.perMinRate); // 超出时间的加价 |
| | | |
| | | activeRuleNameList.add(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ml"); |
| | | activeRuleNameList.add(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ms"); |
| | | // result.put(activeRule.ruleName, result.getOrDefault(activeRule.ruleName, BigDecimal.ZERO).add(totalSeg)); |
| | | } |
| | | // 1. 基础费率计算 |
| | | for (String ruleName : activeRuleNameList) { |
| | | String[] split = ruleName.split("_"); |
| | | if(ruleName.endsWith("_Ml")){ |
| | | BigDecimal exceedValue = bill.get(ruleName); |
| | | if(Objects.nonNull(exceedValue)){ |
| | | exceedValue = exceedValue.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP); |
| | | }else{ |
| | | exceedValue = BigDecimal.ZERO; |
| | | } |
| | | BigDecimal exceedRate = activeRuleNameRate.get(ruleName); |
| | | result.put(split[0], result.getOrDefault(split[0], BigDecimal.ZERO).add(exceedValue.multiply(exceedRate))); |
| | | }else{ |
| | | BigDecimal exceedValue = bill.get(ruleName); |
| | | if(Objects.nonNull(exceedValue)){ |
| | | exceedValue = exceedValue.divide(new BigDecimal(60000), 2, RoundingMode.HALF_UP); |
| | | }else{ |
| | | exceedValue = BigDecimal.ZERO; |
| | | } |
| | | BigDecimal exceedRate = activeRuleNameRate.get(ruleName); |
| | | result.put(split[0], result.getOrDefault(split[0], BigDecimal.ZERO).add(exceedValue.multiply(exceedRate))); |
| | | } |
| | | } |
| | | // 2. 远途加价计算 (基于区间判定) |
| | | BigDecimal longExtra = calculateLongDistExtra(BigDecimal.valueOf(totalMileageKm), config.longSteps); |
| | | result.put("远途费", longExtra); |
| | | return formatMap(result); |
| | | } |
| | | |
| | | /** |
| | | * 远途加价核心逻辑:判断当前行驶到的里程处于哪个加价区间 |
| | | */ |
| | | private BigDecimal calculateLongDistExtra(BigDecimal currentTotalM, BigDecimal billM, List<LongRangeStep> steps) { |
| | | private BigDecimal calculateLongDistExtra(BigDecimal billM, List<LongRangeStep> steps) { |
| | | if (billM.compareTo(BigDecimal.ZERO) <= 0) return BigDecimal.ZERO; |
| | | |
| | | // 当前点所在的公里数 |
| | | BigDecimal currentKm = currentTotalM.divide(new BigDecimal(1000), 10, RoundingMode.HALF_UP); |
| | | BigDecimal extraStart = BigDecimal.ZERO; |
| | | BigDecimal extraRate = BigDecimal.ZERO; |
| | | |
| | | // 匹配区间规则 |
| | | for (LongRangeStep step : steps) { |
| | | if (step.isMatch(currentKm)) { |
| | | if (step.isMatch(billM)) { |
| | | extraRate = step.addPrice; |
| | | extraStart = step.numStart; |
| | | break; |
| | | } |
| | | } |
| | | return billM.divide(new BigDecimal(1000), 10, RoundingMode.HALF_UP).multiply(extraRate); |
| | | return billM.subtract(extraStart).multiply(extraRate); |
| | | } |
| | | |
| | | private Map<String, BigDecimal> formatMap(Map<String, BigDecimal> map) { |
| | |
| | | PricingRule night; |
| | | List<PricingRule> normals = new ArrayList<>(); |
| | | List<LongRangeStep> longSteps = new ArrayList<>(); // 修改为区间列表 |
| | | boolean isHoliday = false; |
| | | |
| | | public PricingRule findMatchedRule(Date date) { |
| | | LocalTime t = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); |
| | | if (isHoliday) return holiday; |
| | | if (holiday.isMatch(t)) return holiday; |
| | | if (peak.isMatch(t)) return peak; |
| | | if (night.isMatch(t)) return night; |
| | | for (PricingRule r : normals) if (r.isMatch(t)) return r; |
| | |
| | | |
| | | private static PricingConfig setupMockConfig() { |
| | | PricingConfig config = new PricingConfig(); |
| | | config.peak = new PricingRule("高峰期", "17:00", "19:00", 15.0, 3.0, 5.0, 3.0, 0.8); |
| | | config.night = new PricingRule("夜间行驶", "22:00", "05:00", 18.0, 3.0, 5.0, 3.5, 1.0); |
| | | config.normals.add(new PricingRule("普通时段", "05:00", "17:00", 12.0, 3.0, 5.0, 2.4, 0.5)); |
| | | config.peak = new PricingRule("高峰期", "14:40", "14:46", 15.0, 3.0, 5.0, 3.0, 0.8); |
| | | config.night = new PricingRule("夜间行驶", "14:47", "14:48", 18.0, 3.0, 5.0, 3.5, 1.0); |
| | | config.holiday = new PricingRule("节假日", "18:47", "18:48", 18.0, 3.0, 5.0, 4, 2.0); |
| | | config.normals.add(new PricingRule("普通时段", "14:46", "14:47", 10.0, 3.0, 5.0, 2.4, 0.5)); |
| | | config.normals.add(new PricingRule("普通时段", "14:48", "17:59", 10.0, 3.0, 5.0, 2.4, 0.5)); |
| | | |
| | | // --- 远途加价区间配置 (关键修改) --- |
| | | // 1. 超过 num1(12) 不足 num2(20), 加收 num3(1.2) |
| | | config.longSteps.add(new LongRangeStep(12.0, 20.0, 1.2)); |
| | | config.longSteps.add(new LongRangeStep(5.0, 6.0, 1.2)); |
| | | // 2. 超过 num4(20) 不足 num5(35), 加收 num6(2.0) |
| | | config.longSteps.add(new LongRangeStep(20.0, 35.0, 2.0)); |
| | | config.longSteps.add(new LongRangeStep(6.0, 7.0, 2.0)); |
| | | // 3. 超过 num7(35) 以后, 加收 num8(3.5) -> numEnd 传个极大值 |
| | | config.longSteps.add(new LongRangeStep(35.0, 999999.0, 3.5)); |
| | | config.longSteps.add(new LongRangeStep(7.0, 8.0, 3.5)); |
| | | |
| | | return config; |
| | | } |
| | |
| | | long startTs = startTime.getTime(); |
| | | list.add(new GpsPoint(0, startTs)); |
| | | // 模拟行驶了 20km |
| | | list.add(new GpsPoint(12000, startTs + 15 * 60 * 1000)); |
| | | list.add(new GpsPoint(8000, startTs + 25 * 60 * 1000)); |
| | | list.add(new GpsPoint(5000, startTs + 35 * 60 * 1000)); |
| | | list.add(new GpsPoint(120, startTs + 2 * 10 * 1000)); |
| | | list.add(new GpsPoint(204, startTs + 2 * 20 * 1000)); |
| | | list.add(new GpsPoint(231, startTs + 2 * 30 * 1000)); |
| | | list.add(new GpsPoint(252, startTs + 2 * 40 * 1000)); |
| | | list.add(new GpsPoint(188, startTs + 2 * 50 * 1000)); |
| | | list.add(new GpsPoint(150, startTs + 2 * 60 * 1000)); |
| | | list.add(new GpsPoint(126, startTs + 2 * 70 * 1000)); |
| | | list.add(new GpsPoint(138, startTs + 2 * 80 * 1000)); |
| | | list.add(new GpsPoint(119, startTs + 2 * 90 * 1000)); |
| | | list.add(new GpsPoint(147, startTs + 2 * 100 * 1000)); |
| | | list.add(new GpsPoint(155, startTs + 2 * 110 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 120 * 1000)); |
| | | list.add(new GpsPoint(172, startTs + 2 * 130 * 1000)); |
| | | list.add(new GpsPoint(202, startTs + 2 * 140 * 1000)); |
| | | list.add(new GpsPoint(162, startTs + 2 * 150 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 160 * 1000)); |
| | | list.add(new GpsPoint(132, startTs + 2 * 170 * 1000)); |
| | | list.add(new GpsPoint(142, startTs + 2 * 180 * 1000)); |
| | | list.add(new GpsPoint(262, startTs + 2 * 190 * 1000)); |
| | | list.add(new GpsPoint(242, startTs + 2 * 200 * 1000)); |
| | | list.add(new GpsPoint(282, startTs + 2 * 210 * 1000)); |
| | | list.add(new GpsPoint(222, startTs + 2 * 220 * 1000)); |
| | | list.add(new GpsPoint(212, startTs + 2 * 230 * 1000)); |
| | | list.add(new GpsPoint(142, startTs + 2 * 240 * 1000)); |
| | | list.add(new GpsPoint(262, startTs + 2 * 250 * 1000)); |
| | | list.add(new GpsPoint(169, startTs + 2 * 260 * 1000)); |
| | | list.add(new GpsPoint(170, startTs + 2 * 270 * 1000)); |
| | | list.add(new GpsPoint(180, startTs + 2 * 280 * 1000)); |
| | | list.add(new GpsPoint(165, startTs + 2 * 290 * 1000)); |
| | | list.add(new GpsPoint(180, startTs + 2 * 300 * 1000)); |
| | | list.add(new GpsPoint(198, startTs + 2 * 310 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 320 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 330 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 340 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 350 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 360 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 370 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 380 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 390 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 400 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 410 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 420 * 1000)); |
| | | list.add(new GpsPoint(182, startTs + 2 * 430 * 1000)); |
| | | return list; |
| | | } |
| | | } |