From 4548c5045b3a5522ad14df7c939f0303c1e17587 Mon Sep 17 00:00:00 2001
From: xin <1099200748@qq.com>
Date: Sat, 11 Oct 2025 19:07:29 +0800
Subject: [PATCH] 配送费

---
 oying-system/src/main/java/com/oying/modules/sh/utils/ShippingFeeCalculator.java |  356 +++++++++++++++++++++++++++++++++--------------------------
 1 files changed, 198 insertions(+), 158 deletions(-)

diff --git a/oying-system/src/main/java/com/oying/modules/sh/utils/ShippingFeeCalculator.java b/oying-system/src/main/java/com/oying/modules/sh/utils/ShippingFeeCalculator.java
index d7ad514..b747117 100644
--- a/oying-system/src/main/java/com/oying/modules/sh/utils/ShippingFeeCalculator.java
+++ b/oying-system/src/main/java/com/oying/modules/sh/utils/ShippingFeeCalculator.java
@@ -1,208 +1,248 @@
 package com.oying.modules.sh.utils;
 
-import com.oying.utils.BigDecimalUtils;
-import com.oying.utils.StringUtils;
-
+import java.time.LocalTime;
 import java.util.HashMap;
 import java.util.Map;
 
-/**
- * @author xin
- * @description 运费计算器
- * @date 2025/9/27 21:08
- */
 public class ShippingFeeCalculator {
 
-    // 商品类型枚举
-    public enum ProductType {
-        FOOD,          // 食品小吃、饮料、商超
-        SPECIAL         // 鲜花、蛋糕、电商、高端餐饮
-    }
+    // 城市等级映射
+    private static final Map<String, String> CITY_LEVEL_MAP = new HashMap<>();
 
-    // 城市等级枚举
-    public enum CityLevel {
-        C, D, E
-    }
+    // 等级基础运费映射 (普通品类, 特殊品类)
+    private static final Map<String, Double[]> LEVEL_BASE_FEE_MAP = new HashMap<>();
 
-    // 基础运费配置
-    private static class BaseFeeConfig {
-        private final double foodFee;
-        private final double specialFee;
+    // 高峰时段及加价规则
+    private static final Object[][] PEAK_HOURS_RULES = {
+            {LocalTime.of(0, 0), LocalTime.of(6, 0), 4.0},    // 00:00-06:00 加价4元
+            {LocalTime.of(11, 0), LocalTime.of(13, 0), 2.0},  // 11:00-13:00 加价2元
+            {LocalTime.of(21, 0), LocalTime.of(23, 59), 3.0}  // 21:00-23:59 加价3元
+    };
 
-        public BaseFeeConfig(double foodFee, double specialFee) {
-            this.foodFee = foodFee;
-            this.specialFee = specialFee;
-        }
-
-        public double getFee(ProductType type) {
-            return type == ProductType.FOOD ? foodFee : specialFee;
-        }
-    }
-
-    // 各等级对应的基础运费
-    private static final Map<CityLevel, BaseFeeConfig> LEVEL_FEES = new HashMap<>();
+    // 特殊品类
+    private static final String[] SPECIAL_CATEGORIES = {
+            "鲜花", "蛋糕", "电商", "高端餐饮"
+    };
 
     static {
-        LEVEL_FEES.put(CityLevel.C, new BaseFeeConfig(5.2, 7.2));
-        LEVEL_FEES.put(CityLevel.D, new BaseFeeConfig(4.7, 6.7));
-        LEVEL_FEES.put(CityLevel.E, new BaseFeeConfig(4.5, 6.0));
+        // 初始化城市等级映射
+        initializeCityLevelMap();
+
+        // 初始化等级基础运费
+        initializeLevelBaseFee();
     }
 
-    // 城市到等级的映射
-    private static final Map<String, CityLevel> CITY_LEVELS = new HashMap<>();
-
-    static {
+    private static void initializeCityLevelMap() {
         // C等级城市
-        CITY_LEVELS.put("西安市", CityLevel.C);
-        CITY_LEVELS.put("福州市", CityLevel.C);
-        CITY_LEVELS.put("上海市", CityLevel.C);
-        CITY_LEVELS.put("哈尔滨", CityLevel.C);
-        CITY_LEVELS.put("杭州市", CityLevel.C);
-        CITY_LEVELS.put("北京市", CityLevel.C);
-        CITY_LEVELS.put("长春市", CityLevel.C);
-        CITY_LEVELS.put("珠海市", CityLevel.C);
-        CITY_LEVELS.put("重庆市", CityLevel.C);
-        CITY_LEVELS.put("舟山市", CityLevel.C);
+        String[] levelCCities = {"西安", "福州", "上海", "哈尔滨", "杭州", "北京", "长春", "珠海", "重庆", "舟山"};
+        for (String city : levelCCities) {
+            CITY_LEVEL_MAP.put(city, "C");
+        }
 
         // D等级城市
-        CITY_LEVELS.put("郑州市", CityLevel.D);
-        CITY_LEVELS.put("大连市", CityLevel.D);
-        CITY_LEVELS.put("天津市", CityLevel.D);
-        CITY_LEVELS.put("长沙市", CityLevel.D);
-        CITY_LEVELS.put("中山市", CityLevel.D);
-        CITY_LEVELS.put("东莞市", CityLevel.D);
-        CITY_LEVELS.put("广州市", CityLevel.D);
-        CITY_LEVELS.put("贵阳市", CityLevel.D);
-        CITY_LEVELS.put("兰州市", CityLevel.D);
-        CITY_LEVELS.put("温州市", CityLevel.D);
-        CITY_LEVELS.put("厦门市", CityLevel.D);
-        CITY_LEVELS.put("惠州市", CityLevel.D);
-        CITY_LEVELS.put("深圳市", CityLevel.D);
-        CITY_LEVELS.put("青岛市", CityLevel.D);
+        String[] levelDCities = {"郑州", "大连", "天津", "长沙", "中山", "东莞", "广州", "贵阳", "兰州", "温州",
+                "厦门", "惠州", "深圳"};
+        for (String city : levelDCities) {
+            CITY_LEVEL_MAP.put(city, "D");
+        }
 
         // E等级城市
-        CITY_LEVELS.put("邯郸市", CityLevel.E);
-        CITY_LEVELS.put("宿迁市", CityLevel.E);
-        CITY_LEVELS.put("湖州市", CityLevel.E);
-        CITY_LEVELS.put("绍兴市", CityLevel.E);
-        CITY_LEVELS.put("金华市", CityLevel.E);
-        CITY_LEVELS.put("衢州市", CityLevel.E);
-        CITY_LEVELS.put("台州市", CityLevel.E);
-        CITY_LEVELS.put("丽水市", CityLevel.E);
-        CITY_LEVELS.put("阜阳市", CityLevel.E);
-        CITY_LEVELS.put("三明市", CityLevel.E);
-        CITY_LEVELS.put("南平市", CityLevel.E);
-        CITY_LEVELS.put("龙岩市", CityLevel.E);
-        CITY_LEVELS.put("宁德市", CityLevel.E);
-        CITY_LEVELS.put("淄博市", CityLevel.E);
-        CITY_LEVELS.put("济宁市", CityLevel.E);
-        CITY_LEVELS.put("威海市", CityLevel.E);
-        CITY_LEVELS.put("日照市", CityLevel.E);
-        CITY_LEVELS.put("十堰市", CityLevel.E);
-        CITY_LEVELS.put("衡阳市", CityLevel.E);
-        CITY_LEVELS.put("泸州市", CityLevel.E);
-        CITY_LEVELS.put("绵阳市", CityLevel.E);
-        CITY_LEVELS.put("泰安市", CityLevel.E);
-        CITY_LEVELS.put("廊坊市", CityLevel.E);
+        String[] levelECities = {"青岛", "邯郸", "宿迁", "湖州", "绍兴", "金华", "衢州", "台州", "丽水", "阜阳",
+                "三明", "南平", "龙岩", "宁德", "淄博", "济宁", "威海", "十堰", "泸州", "绵阳",
+                "德阳", "汉阳", "襄阳", "武汉", "常德", "沈阳"};
+        for (String city : levelECities) {
+            CITY_LEVEL_MAP.put(city, "E");
+        }
     }
 
-    // 加价规则常量
-    private static final double BASE_DISTANCE = 1.0; // 起步距离1公里
-    private static final double BASE_WEIGHT = 5.0;   // 起步重量5公斤
-    private static final double DISTANCE_RATE = 1.0; // 距离加价:每公里1元
-    private static final double WEIGHT_RATE = 2.0;   // 重量加价:每公斤2元
+    private static void initializeLevelBaseFee() {
+        // 等级C: 普通品类5.2元,特殊品类6.7元
+        LEVEL_BASE_FEE_MAP.put("C", new Double[]{5.8, 7.9});
+        // 等级D: 普通品类4.7元,特殊品类6.7元
+        LEVEL_BASE_FEE_MAP.put("D", new Double[]{5.2, 7.3});
+        // 等级E: 普通品类4.5元,特殊品类6.0元
+        LEVEL_BASE_FEE_MAP.put("E", new Double[]{5.0, 6.6});
+    }
 
     /**
-     * 计算订单运费
+     * 计算运费
      *
-     * @param city        城市名称
-     * @param productType 商品类型
-     * @param distance    配送距离(公里)
-     * @param weight      商品重量(公斤)
-     * @param isPeakHour  是否午高峰时段
+     * @param city                城市名称
+     * @param category            商品品类
+     * @param weight              重量(公斤)
+     * @param distance            距离(公里)
+     * @param orderTime           下单时间
+     * @param isSpecialConditions 是否特殊条件(恶劣天气、运力紧张、特殊日期)
      * @return 总运费
      */
-    public static double calculateShippingFee(String city, ProductType productType,
-                                              double distance, double weight, boolean isPeakHour) {
-        if (StringUtils.isEmpty(city)) {
-            city = "重庆市";
-        }
-        // 获取城市等级
-        CityLevel level = CITY_LEVELS.get(city);
+    public static double calculateShippingFee(String city, String category,
+                                              double weight, double distance,
+                                              LocalTime orderTime, boolean isSpecialConditions) {
+        double totalFee = 0.0;
 
-        // 基础运费
-        double baseFee = LEVEL_FEES.get(level).getFee(productType);
+        // 1. 基础运费
+        totalFee += calculateBaseFee(city, category);
 
-        // 距离加价(超过1公里部分,每公里加1元)
-        double distanceFee = 0;
-        if (distance > BASE_DISTANCE) {
-            double extraDistance = Math.min(distance - BASE_DISTANCE, 2.0); // 1-3公里内加价
-            distanceFee = Math.ceil(extraDistance) * DISTANCE_RATE;
+        // 2. 重量加价
+        totalFee += calculateWeightSurcharge(weight);
+
+        // 3. 距离加价
+        totalFee += calculateDistanceSurcharge(distance);
+
+        // 4. 时段加价
+        totalFee += calculateTimeSurcharge(orderTime);
+
+        // 5. 特殊条件加价(需要外部传入具体加价金额,这里返回基础值)
+        if (isSpecialConditions) {
+            totalFee += 1.0; // 默认加价1元,实际应根据具体规则调整
         }
 
-        // 重量加价(超过5公斤部分,每公斤加2元)
-        double weightFee = 0;
-        if (weight > BASE_WEIGHT) {
-            double extraWeight = weight - BASE_WEIGHT;
-            weightFee = Math.ceil(extraWeight) * WEIGHT_RATE;
-        }
-
-        // 时段加价(午高峰时段,具体加价金额需补充)
-        double peakFee = isPeakHour ? getPeakHourFee() : 0;
-
-        return baseFee + distanceFee + weightFee + peakFee;
+        return totalFee;
     }
 
     /**
-     * 获取午高峰时段加价费用(需要根据具体规则补充)
+     * 计算基础运费
      */
-    private static double getPeakHourFee() {
-        // 根据实际午高峰加价规则返回具体金额
-        return 0.0; // 暂设为0,需补充具体规则
+    private static double calculateBaseFee(String city, String category) {
+        String cityLevel = CITY_LEVEL_MAP.getOrDefault(city, "E"); // 默认E等级
+        Double[] fees = LEVEL_BASE_FEE_MAP.get(cityLevel);
+
+        if (fees == null) {
+            fees = LEVEL_BASE_FEE_MAP.get("E"); // 默认使用E等级
+        }
+
+        // 判断是否为特殊品类
+        boolean isSpecialCategory = false;
+        for (String specialCat : SPECIAL_CATEGORIES) {
+            if (category.contains(specialCat)) {
+                isSpecialCategory = true;
+                break;
+            }
+        }
+
+        return isSpecialCategory ? fees[1] : fees[0];
     }
 
     /**
-     * 获取城市运费等级信息
+     * 计算重量加价
      */
-    public static String getCityLevelInfo(String city) {
-        if (StringUtils.isEmpty(city)) {
-            city = "重庆市";
-        }
-        CityLevel level = CITY_LEVELS.get(city);
+    private static double calculateWeightSurcharge(double weight) {
+        final double BASE_WEIGHT = 5.0; // 起步重量5公斤
+        final double SURCHARGE_PER_KG = 2.0; // 每公斤加价2元
 
-        BaseFeeConfig config = LEVEL_FEES.get(level);
-        return String.format("城市: %s, 等级: %s, 食品类起步费: %.1f元, 特殊类起步费: %.1f元",
-                city, level, config.foodFee, config.specialFee);
+        if (weight <= BASE_WEIGHT) {
+            return 0.0;
+        }
+
+        double excessWeight = weight - BASE_WEIGHT;
+        return Math.ceil(excessWeight) * SURCHARGE_PER_KG;
     }
 
     /**
-     * 重量换算kg
-     *
-     * @param grams 重量 单位:g
-     * @return double
+     * 计算距离加价
      */
-    public static double safeConvert(Integer grams) {
-        if (grams == null || grams < 0) {
-            return 0.0; // 返回默认值
+    private static double calculateDistanceSurcharge(double distance) {
+        double surcharge = 0.0;
+
+        // 分段计算加价
+        if (distance > 7.0) {
+            surcharge += (distance - 7.0) * 4.0; // 超过7km部分
+            distance = 7.0;
         }
-        return grams / 1000.0;
+        if (distance > 5.0) {
+            surcharge += (distance - 5.0) * 3.0; // 5-7km部分
+            distance = 5.0;
+        }
+        if (distance > 3.0) {
+            surcharge += (distance - 3.0) * 2.0; // 3-5km部分
+            distance = 3.0;
+        }
+        if (distance > 1.0) {
+            surcharge += (distance - 1.0); // 1-3km部分
+            // 0-1km部分不加价
+        }
+
+        return surcharge;
     }
 
-    // 测试示例
+    /**
+     * 计算时段加价
+     */
+    private static double calculateTimeSurcharge(LocalTime orderTime) {
+        for (Object[] rule : PEAK_HOURS_RULES) {
+            LocalTime startTime = (LocalTime) rule[0];
+            LocalTime endTime = (LocalTime) rule[1];
+            double surcharge = (Double) rule[2];
+
+            if (!orderTime.isBefore(startTime) && !orderTime.isAfter(endTime)) {
+                return surcharge;
+            }
+        }
+        return 0.0;
+    }
+
+    /**
+     * 使用示例 - 优化版
+     */
     public static void main(String[] args) {
-        // 测试不同城市的运费计算
-        System.out.println("=== 运费计算测试 ===");
-        double distance = 9.00;
-        double width = safeConvert(1500);
-        String[] testCities = {null, "", "淄博市", "天津市"};
-        for (String city : testCities) {
-            System.out.println(getCityLevelInfo(city));
-            // 计算示例订单
-            double fee1 = calculateShippingFee(city, ProductType.FOOD, distance, width, false);
-            double fee2 = calculateShippingFee(city, ProductType.SPECIAL, distance, width, true);
-            System.out.printf("  食品订单(" + distance + "km, " + width + "kg): %.1f元\n", fee1);
-            System.out.printf("  特殊订单(" + distance + "km, " + width + "kg, 高峰): %.1f元\n", fee2);
-            System.out.println();
+        System.out.println(
+                "等级C: 普通品类5.8元,特殊品类7.9元\n" +
+                        "等级D: 普通品类5.2元,特殊品类7.3元\n" +
+                        "等级E: 普通品类5.0元,特殊品类6.6元"
+        );
+        // 测试用例数组
+        Object[][] testCases = {
+                // 城市, 品类, 重量(kg), 距离(km), 时间, 特殊条件, 预期运费描述
+                {"上海", "食品小吃", 7.0, 3.5, LocalTime.of(14, 30), false, "普通商品,超重,中距离,非高峰"},
+                {"北京", "蛋糕", 10.0, 8.0, LocalTime.of(12, 30), false, "特殊商品,超重,长距离,高峰"},
+                {"青岛", "饮料", 3.0, 2.0, LocalTime.of(10, 0), false, "普通商品,未超重,中距离,非高峰"},
+                {"深圳", "高端餐饮", 5.0, 0.5, LocalTime.of(5, 30), true, "特殊商品,临界重量,短距离,高峰+特殊"},
+                {"武汉", "普通商品", 15.0, 6.0, LocalTime.of(20, 0), false, "普通商品,超重,中距离,高峰"},
+                {"西安", "电商", 2.0, 10.0, LocalTime.of(15, 0), true, "特殊商品,未超重,长距离,特殊"},
+                {"广州", "普通商品", 5.0, 1.0, LocalTime.of(12, 0), false, "普通商品,临界重量,临界距离,高峰"},
+                {"沈阳", "蛋糕", 8.0, 4.0, LocalTime.of(22, 30), true, "特殊商品,超重,中距离,高峰+特殊"},
+                {"杭州", "鲜花", 4.0, 7.0, LocalTime.of(9, 0), false, "特殊商品,未超重,临界距离,非高峰"},
+                {"重庆", "普通商品", 6.0, 3.0, LocalTime.of(0, 30), true, "普通商品,超重,临界距离,高峰+特殊"}
+        };
+
+        // 打印表头
+        System.out.println("+--------+------------+--------+--------+----------+--------------+----------+-----------------+");
+        System.out.println("| 测试ID | 城市        | 品类   | 重量(kg) | 距离(km) | 时间         | 特殊条件 | 运费(元) | 描述");
+        System.out.println("+--------+------------+--------+--------+----------+--------------+--------------+-----------------+");
+
+        for (int i = 0; i < testCases.length; i++) {
+            Object[] testCase = testCases[i];
+            String city = (String) testCase[0];
+            String category = (String) testCase[1];
+            double weight = (double) testCase[2];
+            double distance = (double) testCase[3];
+            LocalTime time = (LocalTime) testCase[4];
+            boolean special = (boolean) testCase[5];
+            String description = (String) testCase[6];
+
+            double fee = calculateShippingFee(city, category, weight, distance, time, special);
+
+            // 格式化输出
+            System.out.printf("| %-6d | %-8s | %-6s | %-6.1f | %-8.1f | %-12s | %-8s | %-8.2f | %s%n",
+                    i + 1,
+                    city + ":" + CITY_LEVEL_MAP.getOrDefault(city, "E"),
+                    category,
+                    weight,
+                    distance,
+                    time,
+                    special ? "是" : "否",
+                    fee,
+                    description);
         }
+
+        // 打印表尾
+        System.out.println("+--------+------------+--------+--------+----------+--------------+--------------+-----------------+");
+
+
+        // 边界值测试
+        System.out.println("\n边界值测试C等级城市:");
+        System.out.println("1. 重量边界(5kg): " + calculateShippingFee("上海", "普通", 5.0, 3.0, LocalTime.of(10, 0), false));
+        System.out.println("2. 距离边界(1km): " + calculateShippingFee("上海", "普通", 3.0, 1.0, LocalTime.of(10, 0), false));
+        System.out.println("3. 时间边界(06:00): " + calculateShippingFee("上海", "普通", 3.0, 3.0, LocalTime.of(6, 0), false));
     }
 }

--
Gitblit v1.9.3