Просмотр исходного кода

Merge branch 'redesign‌-260706' of git.huaml.com:zhh/huahuibao into redesign‌-260706

shizhongqi 1 неделя назад
Родитель
Сommit
86e6d74d5e

+ 70 - 0
app-hd/controllers/CustomController.php

@@ -734,4 +734,74 @@ class CustomController extends BaseController
 
         util::success(['customId' => $customId, 'buyAmount' => $buyAmount]);
     }
+
+    /**
+     * 切换客户是否启用送货上门(同步 xhCustom 与 xhHd)
+     */
+    public function actionChangeHome()
+    {
+        $get = Yii::$app->request->get();
+        $home = isset($get['home']) ? (int)$get['home'] : 0;
+        $customId = !empty($get['customId']) ? (int)$get['customId'] : 0;
+        $custom = CustomClass::getById($customId, true);
+        if (empty($custom)) {
+            util::fail('没有找到客户');
+        }
+        if ($custom->shopId != $this->shopId) {
+            util::fail('不是你的客户');
+        }
+        $hdId = $custom->hdId ?? 0;
+        $hd = HdClass::getById($hdId, true);
+        if (empty($hd)) {
+            util::fail('客户信息缺失');
+        }
+        $custom->home = $home;
+        $custom->save();
+        $hd->home = $home;
+        $hd->save();
+        util::complete('修改成功');
+    }
+
+    /**
+     * 客户送货上门单独规则(homeRule=1 时覆盖 xhPsMethod style=0 总设置)
+     */
+    public function actionModifyHomeAmount()
+    {
+        $get = Yii::$app->request->get();
+        $id = $get['id'] ?? 0;
+        $homeAmount = $get['homeAmount'] ?? 0;
+        $homeNum = $get['homeNum'] ?? 0;
+        $homeUnMeet = $get['homeUnMeet'] ?? 0;
+        $homeUnFee = $get['homeUnFee'] ?? 0;
+        $homeRule = $get['homeRule'] ?? 0;
+
+        $custom = CustomClass::getById($id, true);
+        if (empty($custom)) {
+            util::fail('没有找到客户');
+        }
+        if ($custom->shopId != $this->shopId) {
+            util::fail('不是你的客户');
+        }
+        $hdId = $custom->hdId ?? 0;
+        $hd = HdClass::getById($hdId, true);
+        if (empty($hd)) {
+            util::fail('客户信息缺失');
+        }
+
+        $custom->homeAmount = $homeAmount;
+        $custom->homeNum = $homeNum;
+        $custom->homeUnMeet = $homeUnMeet;
+        $custom->homeUnFee = $homeUnFee;
+        $custom->homeRule = $homeRule;
+        $custom->save();
+
+        $hd->homeAmount = $homeAmount;
+        $hd->homeNum = $homeNum;
+        $hd->homeUnMeet = $homeUnMeet;
+        $hd->homeUnFee = $homeUnFee;
+        $hd->homeRule = $homeRule;
+        $hd->save();
+
+        util::complete('修改成功');
+    }
 }

+ 48 - 38
app-mall/controllers/DeliveryController.php

@@ -56,45 +56,55 @@ class DeliveryController extends BaseController
         $prefix = 'PT-XSD-' . $this->mainId . '-';
         $orderSn = $prefix . round(microtime(true) * 1000);
 
-        //构建出 Order 数据
-        if($buyType == 'huaCai'){//订单类型是:花材
-            $order = [
-                'orderSn' => $orderSn,
-                'customName' => $user['name'],
-                'customMobile' => $user['mobile'],
-                'fullAddress' => $user['fullAddress'],
-                'floor' => $user['floor'],
-                'dist' => $user['dist'],
-                'lat' => $user['lat'],
-                'long' => $user['long'],
-                'address' => $user['address'], //'toAddress' => $order['address'],
-                'city' => $user['city'],
-                'weight' => $post['weight'] ?? 1,
-                'remark' => $post['remark'] ?? '',
-                'prePrice' => floatval($post['totalPrice'] ?? 0),
-                'actPrice' => floatval($post['totalPrice'] ?? 0),
-            ];
-        }elseif($buyType == 'huaShu'){//订单类型是:花束
-            $order = [
-                'orderSn' => $orderSn,
-                'customName' => $post['receiveUserName'],
-                'customMobile' => $post['receiveMobile'],
-                'fullAddress' => $post['address'],
-                'floor' => $post['floor'] ?? '',
-                'dist' => $post['dist'] ?? '',
-                'lat' => $post['region']['latitude'],
-                'long' => $post['region']['longitude'],
-                'address' => $post['address'], //'toAddress' => $order['address'],
-                'city' => $post['city'],
-                'weight' => $post['weight'] ?? 1,
-                'remark' => $post['remark'] ?? '',
-                'prePrice' => floatval($post['totalPrice'] ?? 0),
-                'actPrice' => floatval($post['totalPrice'] ?? 0),
-            ];
-        }else{
-            util::fail('不存在此订单类型');
+        // 收货信息:优先结算页所选地址坐标,避免距离按登录用户资料计算
+        $receive = $this->resolveReceiveInfo($user, $post);
+        if (empty($receive['lat']) || empty($receive['long'])) {
+            util::fail('请选择收货地址');
         }
 
-        return $order;
+        $baseOrder = array_merge($receive, [
+            'orderSn' => $orderSn,
+            'weight' => $post['weight'] ?? 1,
+            'remark' => $post['remark'] ?? '',
+            'prePrice' => floatval($post['totalPrice'] ?? 0),
+            'actPrice' => floatval($post['totalPrice'] ?? 0),
+        ]);
+
+        if ($buyType == 'huaCai') {
+            return $baseOrder;
+        }
+        if ($buyType == 'huaShu') {
+            return $baseOrder;
+        }
+
+        util::fail('不存在此订单类型');
+    }
+
+    /**
+     * 解析收货地址与坐标:前端所选地址优先,其次登录用户资料
+     * @param object|array $user 商城登录用户
+     * @param array $post 报价请求参数(含 lat/long、receiveUserName 等)
+     * @return array
+     */
+    private function resolveReceiveInfo($user, $post)
+    {
+        $userArr = is_object($user) ? $user->attributes : (array)$user;
+        $region = isset($post['region']) && is_array($post['region']) ? $post['region'] : [];
+        $lat = $post['lat'] ?? ($region['latitude'] ?? ($userArr['lat'] ?? ''));
+        $long = $post['long'] ?? ($region['longitude'] ?? ($userArr['long'] ?? ''));
+        $address = $post['address'] ?? ($userArr['address'] ?? '');
+        $fullAddress = $post['fullAddress'] ?? ($address ?: ($userArr['fullAddress'] ?? ''));
+
+        return [
+            'customName' => $post['receiveUserName'] ?? ($userArr['name'] ?? ''),
+            'customMobile' => $post['receiveMobile'] ?? ($userArr['mobile'] ?? ''),
+            'fullAddress' => $fullAddress,
+            'floor' => $post['floor'] ?? ($userArr['floor'] ?? ''),
+            'dist' => $post['dist'] ?? ($userArr['dist'] ?? ''),
+            'lat' => $lat,
+            'long' => $long,
+            'address' => $address,
+            'city' => $post['city'] ?? ($userArr['city'] ?? ''),
+        ];
     }
 }

+ 17 - 3
app-mall/controllers/NoticeController.php

@@ -22,6 +22,7 @@ use common\components\lakala\Lakala;
 use bizHd\recharge\classes\RechargeShbClass;
 use bizHd\hb\classes\HbClass;
 use bizHd\hb\classes\HbManageClass;
+use bizHd\order\classes\PsMethodClass;
 
 class NoticeController extends PublicController
 {
@@ -196,6 +197,9 @@ class NoticeController extends PublicController
 
                         //来自商城的新订单微信通知
                         WxMessageClass::hdNewOrderInform($shop, $order);
+
+                        // 标记 xhPsMethod 未达门槛附加费当日已收(每日每客户每配送方式只收一次)
+                        self::markPsUnMeetFeeAfterPay($order);
                     }
                 }
             }
@@ -295,6 +299,10 @@ class NoticeController extends PublicController
 
                 $shopExt = ShopExtClass::getByCondition(['shopId' => $shopId], true);
                 ShopExtClass::newWorkRemind($shopExt, $order);
+
+                if ($order->fromType == dict::getDict("fromType", "mall")) {
+                    self::markPsUnMeetFeeAfterPay($order);
+                }
             }
             exit();
         } catch (\Exception $e) {
@@ -409,11 +417,9 @@ class NoticeController extends PublicController
                 if ($order->fromType == dict::getDict("fromType", "mall")) {
                     //来自商城的新订单微信通知
                     WxMessageClass::hdNewOrderInform($shop, $order);
+                    self::markPsUnMeetFeeAfterPay($order);
                 }
-
             }
-            //请不要修改
-            echo "success";
             exit();
         } catch (\Exception $e) {
             $transaction->rollBack();
@@ -554,4 +560,12 @@ class NoticeController extends PublicController
         }
     }
 
+    /**
+     * 商城订单支付成功后,标记 xhPsMethod 未达门槛附加费当日已收
+     */
+    protected static function markPsUnMeetFeeAfterPay($order)
+    {
+        PsMethodClass::markUnMeetFeePaidFromOrder($order);
+    }
+
 }

+ 52 - 1
app-mall/controllers/OrderController.php

@@ -1100,6 +1100,7 @@ class OrderController extends BaseController
             $modifyPrice = 0;
             $totalWeight = 0;
             $totalNum = 0;
+            $itemBigNum = 0;
             $totalReachDiscount = 0;
             $resolvedProduct = [];
 
@@ -1132,6 +1133,7 @@ class OrderController extends BaseController
                     $currentWeight = bcmul($num, $weight, 2);
                     $totalWeight = bcadd($totalWeight, $currentWeight, 2);
                     $totalNum = bcadd($totalNum, $num);
+                    $itemBigNum = bcadd($itemBigNum, $num);
                     if (($product['limitBuy'] ?? 0) > 0) {
                         $product['productId'] = $productId;
                         \bizHd\product\classes\ProductClass::handleLimitBuy($product, $hdCustomId, floatval($num));
@@ -1256,7 +1258,25 @@ class OrderController extends BaseController
 
             $post['product'] = $resolvedProduct;
             $post['reachDiscountPrice'] = $totalReachDiscount;
-            unset($post['sendCost']);
+            $goodsOnlyPrice = $modifyPrice;
+            unset($post['sendCost'], $post['packCost'], $post['packingFee'], $post['unMeetSendCost']);
+
+            $sendType = intval($post['sendType'] ?? 0);
+            $customInfo = array_merge(
+                is_array($custom) ? $custom : ($custom ? $custom->attributes : []),
+                is_array($hd) ? $hd : ($hd ? $hd->attributes : [])
+            );
+            $additionalCosts = \bizHd\order\classes\PsMethodClass::calcFee(
+                $this->shopId,
+                $this->mainId,
+                $sendType,
+                $goodsOnlyPrice,
+                $itemBigNum,
+                $hdCustomId,
+                $customInfo
+            );
+            $unMeetSendCost = $additionalCosts['sendCost'] ?? 0;
+            $packingFee = $additionalCosts['packCost'] ?? 0;
 
             $mapSet = ShopClass::hasIntraCity($this->shop);
             $openIntraCity = $mapSet['openIntraCity'] ?? 0;
@@ -1324,6 +1344,15 @@ class OrderController extends BaseController
                 $modifyPrice = bcadd($modifyPrice, $sendCost, 2);
             }
 
+            if ($unMeetSendCost > 0) {
+                $post['sendCost'] = bcadd($post['sendCost'] ?? 0, $unMeetSendCost, 2);
+                $modifyPrice = bcadd($modifyPrice, $unMeetSendCost, 2);
+            }
+            if ($packingFee > 0) {
+                $post['packingFee'] = $packingFee;
+                $modifyPrice = bcadd($modifyPrice, $packingFee, 2);
+            }
+
             // 红包(归属花店客户,与 buy-item 一致)
             $hbId = $post['hbId'] ?? 0;
             $hb = null;
@@ -1359,6 +1388,21 @@ class OrderController extends BaseController
                 }
             }
 
+            $psMethodError = \bizHd\order\classes\PsMethodClass::checkLimit(
+                $this->shopId,
+                $this->mainId,
+                $sendType,
+                $goodsOnlyPrice,
+                $itemBigNum,
+                $packingFee,
+                $unMeetSendCost,
+                $hdCustomId,
+                $customInfo
+            );
+            if ($psMethodError !== '') {
+                util::fail($psMethodError);
+            }
+
             $sendType = $post['sendType'] ?? 0;
             if ($sendType != 1) {
                 if (empty($post['reachPeriod'])) {
@@ -1371,6 +1415,12 @@ class OrderController extends BaseController
             $post['modifyPrice'] = $modifyPrice;
             $return = \bizHd\order\services\OrderService::createHdOrder($post, $custom, $hasPay);
 
+            $orderSn = $return->orderSn ?? '';
+            if (!empty($orderSn) && (float)$unMeetSendCost > 0) {
+                $unMeetSendKey = 'hd_ps_order_unmeet_send_' . $orderSn;
+                Yii::$app->redis->executeCommand('SETEX', [$unMeetSendKey, 864000, (string)$unMeetSendCost]);
+            }
+
             if (!empty($hb)) {
                 $hb->status = 1;
                 $hb->orderId = $return->id;
@@ -1478,6 +1528,7 @@ class OrderController extends BaseController
                     if ($order->fromType == 2) {
                         //来自商城的新订单微信通知
                         WxMessageClass::hdNewOrderInform($shop, $order);
+                        \bizHd\order\classes\PsMethodClass::markUnMeetFeePaidFromOrder($order);
                     }
                 }
             }

+ 70 - 0
app-mall/controllers/PsMethodController.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace mall\controllers;
+
+use bizHd\order\classes\PsMethodClass;
+use common\components\util;
+use Yii;
+
+/**
+ * 商城端配送方式接口(xhPsMethod / xhPsExplain)
+ * 用途:混合结算页展示各购买方式对应的配送说明
+ */
+class PsMethodController extends BaseController
+{
+    /**
+     * 获取门店全部配送方式及说明项
+     */
+    public function actionGetAllMethod()
+    {
+        if (empty($this->shopId) || empty($this->mainId)) {
+            util::fail('门店信息无效');
+        }
+
+        try {
+            $configs = PsMethodClass::getAllConfigs($this->shopId, $this->mainId);
+
+            // 客户单独送货上门规则覆盖 style=0
+            if (!empty($this->custom) || !empty($this->hd)) {
+                $customInfo = [];
+                if (!empty($this->hd)) {
+                    $customInfo = is_array($this->hd) ? $this->hd : $this->hd->toArray();
+                }
+                if (!empty($this->custom)) {
+                    $customInfo = array_merge($customInfo, is_array($this->custom) ? $this->custom : $this->custom->toArray());
+                }
+                foreach ($configs as &$config) {
+                    if ((int)($config['style'] ?? 0) === 0) {
+                        $config = PsMethodClass::applyCustomHomeRule($config, $customInfo);
+                    }
+                }
+                unset($config);
+            }
+
+            // 今日已收过附加费时,前端不再展示门槛提示(每日每客户每配送方式只收一次)
+            $customId = (int)($this->customId ?? 0);
+            if ($customId > 0) {
+                foreach ($configs as &$config) {
+                    $style = (int)($config['style'] ?? 0);
+                    $unMeet = (int)($config['unMeet'] ?? 0);
+                    $hasPaidSendCost = PsMethodClass::isSendCostPaidToday($customId, $style);
+                    $hasPaidPackCost = PsMethodClass::isPackPaidToday($customId, $style);
+                    if ($unMeet === 0 && $hasPaidSendCost) {
+                        $config['minAmount'] = 0;
+                        $config['minNum'] = 0;
+                        $config['unMeetFee'] = 0;
+                    } elseif ($unMeet === 2 && $hasPaidPackCost) {
+                        $config['minAmount'] = 0;
+                        $config['minNum'] = 0;
+                        $config['unMeetFee'] = 0;
+                    }
+                }
+                unset($config);
+            }
+
+            util::success(['method' => $configs]);
+        } catch (\Exception $e) {
+            util::fail($e->getMessage());
+        }
+    }
+}

+ 313 - 0
biz-hd/order/classes/PsMethodClass.php

@@ -93,6 +93,100 @@ class PsMethodClass extends BaseClass
         return $config;
     }
 
+    /**
+     * 客户 homeRule=1 时,用 xhCustom/xhHd 上的送货上门字段覆盖 style=0 配置
+     * @param array $config xhPsMethod 送货配置
+     * @param array $customInfo 客户信息(含 home、homeRule、homeAmount 等)
+     * @return array
+     */
+    public static function applyCustomHomeRule($config, $customInfo)
+    {
+        if (empty($config) || empty($customInfo)) {
+            return $config;
+        }
+        if (!isset($customInfo['homeRule']) || (int)$customInfo['homeRule'] !== 1) {
+            return $config;
+        }
+        $config['status'] = isset($customInfo['home']) ? (int)$customInfo['home'] : ($config['status'] ?? 1);
+        $config['minAmount'] = $customInfo['homeAmount'] ?? ($config['minAmount'] ?? 0);
+        $config['minNum'] = $customInfo['homeNum'] ?? ($config['minNum'] ?? 0);
+        $config['unMeet'] = $customInfo['homeUnMeet'] ?? ($config['unMeet'] ?? 0);
+        $config['unMeetFee'] = $customInfo['homeUnFee'] ?? ($config['unMeetFee'] ?? 0);
+        return $config;
+    }
+
+    /**
+     * 一次性获取商户全部配送方式配置(含 xhPsExplain / xhPsReduceRule)
+     * @param int $shopId 门店ID
+     * @param int $mainId 商户ID
+     * @return array
+     */
+    public static function getAllConfigs($shopId, $mainId)
+    {
+        $configsMap = self::getAllByCondition(['mainId' => $mainId], 'sort asc', '*', 'style', true);
+
+        for ($style = 0; $style <= 4; $style++) {
+            if (!isset($configsMap[$style])) {
+                $defaultConfig = dict::getDict('shMethod', $style);
+                $initData = array_merge([
+                    'shopId' => $shopId,
+                    'mainId' => $mainId,
+                    'style' => $style,
+                ], $defaultConfig);
+
+                $configObj = self::add($initData, true);
+                $configsMap[$style] = $configObj;
+            }
+        }
+
+        $configsArray = [];
+        $methodIds = [];
+        foreach ($configsMap as $style => $configObj) {
+            $configArr = $configObj instanceof \yii\db\ActiveRecord ? $configObj->toArray() : $configObj;
+            $configsArray[$style] = $configArr;
+            $methodIds[] = (int)$configArr['id'];
+        }
+
+        $allExplains = [];
+        if (!empty($methodIds)) {
+            $allExplains = PsExplainClass::getAllByCondition(['methodId' => ['in', $methodIds]], 'sort ASC, id ASC');
+        }
+        $explainsMap = [];
+        foreach ($allExplains as $exp) {
+            $explainsMap[(int)$exp['methodId']][] = $exp;
+        }
+
+        $allReduceRules = [];
+        if (!empty($methodIds)) {
+            $allReduceRules = PsReduceRuleClass::getAllByCondition(['methodId' => ['in', $methodIds]], 'sort ASC, id ASC');
+        }
+        $reduceRulesMap = [];
+        foreach ($allReduceRules as $rule) {
+            $reduceRulesMap[(int)$rule['methodId']][] = $rule;
+        }
+
+        $result = [];
+        for ($style = 0; $style <= 4; $style++) {
+            $config = $configsArray[$style];
+            $methodId = (int)$config['id'];
+
+            $config['explains'] = $explainsMap[$methodId] ?? [];
+            $config['reduceRules'] = ($style == 2) ? ($reduceRulesMap[$methodId] ?? []) : [];
+
+            $result[] = $config;
+        }
+
+        usort($result, function ($a, $b) {
+            $sortCompare = (int)($a['sort'] ?? 0) <=> (int)($b['sort'] ?? 0);
+            if ($sortCompare !== 0) {
+                return $sortCompare;
+            }
+            return (int)($a['style'] ?? 0) <=> (int)($b['style'] ?? 0);
+        });
+
+        return $result;
+    }
+
     /**
      * 保存指定配送方式配置及关联说明项、满减规则
      * @param int $shopId 门店ID
@@ -192,4 +286,223 @@ class PsMethodClass extends BaseClass
             throw $e;
         }
     }
+
+    /**
+     * 合并客户送货上门单独规则(style=0 且 homeRule=1)
+     * @param array $config xhPsMethod 配置
+     * @param int $style 配送方式
+     * @param array $customInfo 客户+花店关系信息
+     * @return array
+     */
+    protected static function mergeCustomHomeRule($config, $style, $customInfo)
+    {
+        if ((int)$style === 0 && !empty($customInfo)) {
+            return self::applyCustomHomeRule($config, $customInfo);
+        }
+        return $config;
+    }
+
+    /**
+     * 判断当前订单是否未达最低消费
+     * 规则:满 minAmount 或 minNum 任一达标即视为达标;两者均未达标才返回 true
+     */
+    public static function isBelowMinimum($itemTotalAmount, $bigNum, $config)
+    {
+        $minAmount = isset($config['minAmount']) ? (float)$config['minAmount'] : 0.00;
+        $minNum = isset($config['minNum']) ? (int)$config['minNum'] : 0;
+        if ($minAmount <= 0 && $minNum <= 0) {
+            return false;
+        }
+        $amountOk = $minAmount <= 0 || (float)$itemTotalAmount >= $minAmount;
+        $numOk = $minNum <= 0 || (float)$bigNum >= $minNum;
+        return !($amountOk || $numOk);
+    }
+
+    /**
+     * 拼接最低消费门槛文案,如 200元或30扎
+     */
+    protected static function buildUnMeetConditionText($config)
+    {
+        $parts = [];
+        $minAmount = isset($config['minAmount']) ? (float)$config['minAmount'] : 0.00;
+        $minNum = isset($config['minNum']) ? (int)$config['minNum'] : 0;
+        if ($minAmount > 0) {
+            $parts[] = $minAmount . '元';
+        }
+        if ($minNum > 0) {
+            $parts[] = $minNum . '扎';
+        }
+        return implode('或', $parts);
+    }
+
+    /**
+     * 判断当天是否已收过包装类附加费(xhPsMethod unMeet=2)
+     */
+    public static function isPackPaidToday($customId, $sendType = 0)
+    {
+        if (empty($customId)) {
+            return false;
+        }
+        $current = date('Y_m_d');
+        $key = 'hd_ps_custom_today_has_get_pack_cost_' . (int)$sendType . '_' . $current . '_' . $customId;
+        $has = Yii::$app->redis->executeCommand('GET', [$key]);
+        return (!empty($has) && $has == 1);
+    }
+
+    /**
+     * 判断当天是否已收过运费类附加费(xhPsMethod unMeet=0)
+     */
+    public static function isSendCostPaidToday($customId, $sendType = 0)
+    {
+        if (empty($customId)) {
+            return false;
+        }
+        $current = date('Y_m_d');
+        $key = 'hd_ps_custom_today_has_get_send_cost_' . (int)$sendType . '_' . $current . '_' . $customId;
+        $has = Yii::$app->redis->executeCommand('GET', [$key]);
+        return (!empty($has) && $has == 1);
+    }
+
+    /**
+     * 支付成功后标记当天已收附加费,保证同一客户同配送方式每日只收一次
+     */
+    public static function markUnMeetFeePaid($customId, $sendType, $packingFee = 0, $unMeetSendCost = 0)
+    {
+        if (empty($customId)) {
+            return;
+        }
+        $current = date('Y_m_d');
+        $style = (int)$sendType;
+        if ((float)$packingFee > 0) {
+            $packKey = 'hd_ps_custom_today_has_get_pack_cost_' . $style . '_' . $current . '_' . $customId;
+            Yii::$app->redis->executeCommand('SETEX', [$packKey, 86400, '1']);
+        }
+        if ((float)$unMeetSendCost > 0) {
+            $sendKey = 'hd_ps_custom_today_has_get_send_cost_' . $style . '_' . $current . '_' . $customId;
+            Yii::$app->redis->executeCommand('SETEX', [$sendKey, 86400, '1']);
+        }
+    }
+
+    /**
+     * 根据订单信息标记当日附加费(支付成功回调/余额支付)
+     */
+    public static function markUnMeetFeePaidFromOrder($order)
+    {
+        if (empty($order)) {
+            return;
+        }
+        $orderSn = is_object($order) ? ($order->orderSn ?? '') : ($order['orderSn'] ?? '');
+        $customId = (int)(is_object($order) ? ($order->customId ?? 0) : ($order['customId'] ?? 0));
+        $sendType = (int)(is_object($order) ? ($order->sendType ?? 0) : ($order['sendType'] ?? 0));
+        $packingFee = (float)(is_object($order) ? ($order->packingFee ?? 0) : ($order['packingFee'] ?? 0));
+        $unMeetSendCost = 0;
+        if (!empty($orderSn)) {
+            $unMeetSendKey = 'hd_ps_order_unmeet_send_' . $orderSn;
+            $cached = Yii::$app->redis->executeCommand('GET', [$unMeetSendKey]);
+            if (!empty($cached)) {
+                $unMeetSendCost = (float)$cached;
+            }
+        }
+        self::markUnMeetFeePaid($customId, $sendType, $packingFee, $unMeetSendCost);
+    }
+
+    /**
+     * 计算未达最低消费时应写入订单的附加运费/包装费
+     * @return array ['sendCost' => 附加运费, 'packCost' => 附加包装费]
+     */
+    public static function calcFee($shopId, $mainId, $sendType, $itemTotalAmount, $bigNum, $customId = 0, $customInfo = [])
+    {
+        $costs = ['sendCost' => 0, 'packCost' => 0];
+        $style = (int)$sendType;
+        if ($style < 0 || $style > 4) {
+            return $costs;
+        }
+
+        $config = self::getConfig($shopId, $mainId, $style);
+        if (empty($config)) {
+            return $costs;
+        }
+        $config = self::mergeCustomHomeRule($config, $style, $customInfo);
+
+        if (!self::isBelowMinimum($itemTotalAmount, $bigNum, $config)) {
+            return $costs;
+        }
+
+        $unMeet = isset($config['unMeet']) ? (int)$config['unMeet'] : 0;
+        $unMeetFee = isset($config['unMeetFee']) ? (float)$config['unMeetFee'] : 0;
+        if ($unMeetFee <= 0) {
+            return $costs;
+        }
+
+        // unMeet: 0 加收运费,2 加收包装费
+        if ($unMeet === 0 && !self::isSendCostPaidToday($customId, $style)) {
+            $costs['sendCost'] = $unMeetFee;
+        } elseif ($unMeet === 2 && !self::isPackPaidToday($customId, $style)) {
+            $costs['packCost'] = $unMeetFee;
+        }
+
+        return $costs;
+    }
+
+    /**
+     * 下单前校验配送方式最低消费与附加费是否已正确写入
+     * @param float $packCost 附加包装费
+     * @param float $unMeetSendCost 未达门槛附加运费(不含跑腿报价)
+     * @return string 空字符串表示通过
+     */
+    public static function checkLimit($shopId, $mainId, $sendType, $actPrice, $bigNum, $packCost = 0, $unMeetSendCost = 0, $customId = 0, $customInfo = [])
+    {
+        $style = (int)$sendType;
+        if ($style < 0 || $style > 4) {
+            return '配送方式无效';
+        }
+
+        $config = self::getConfig($shopId, $mainId, $style);
+        if (empty($config)) {
+            return '配送方式配置异常';
+        }
+        $config = self::mergeCustomHomeRule($config, $style, $customInfo);
+
+        $methodName = !empty($config['name']) ? $config['name'] : '该配送方式';
+        if (isset($config['status']) && (int)$config['status'] === 0) {
+            return "暂不支持{$methodName},请选其它配送方式";
+        }
+
+        if (!self::isBelowMinimum($actPrice, $bigNum, $config)) {
+            return '';
+        }
+
+        $minAmount = isset($config['minAmount']) ? (float)$config['minAmount'] : 0.00;
+        $minNum = isset($config['minNum']) ? (int)$config['minNum'] : 0;
+        $unMeet = isset($config['unMeet']) ? (int)$config['unMeet'] : 0;
+        $unMeetFee = isset($config['unMeetFee']) ? (float)$config['unMeetFee'] : 0;
+
+        if ($unMeet === 1) {
+            $conditionText = self::buildUnMeetConditionText($config);
+            if ($conditionText !== '') {
+                return "订单不满{$conditionText},不能下单";
+            }
+            return '未达到最低消费,不能下单';
+        }
+
+        if (($unMeet === 0 || $unMeet === 2) && $unMeetFee > 0) {
+            $feeToCheck = $unMeet === 2 ? $packCost : $unMeetSendCost;
+            if ($unMeet === 2 && self::isPackPaidToday($customId, $style)) {
+                return '';
+            }
+            if ($unMeet === 0 && self::isSendCostPaidToday($customId, $style)) {
+                return '';
+            }
+            if (bccomp((string)$feeToCheck, (string)$unMeetFee, 2) < 0) {
+                $feeLabel = $unMeet === 2 ? '包装费' : '运费';
+                $conditionText = self::buildUnMeetConditionText($config);
+                if ($conditionText !== '') {
+                    return "订单不满{$conditionText},加{$feeLabel}{$unMeetFee}元";
+                }
+                return "未达最低消费,需加收{$feeLabel}{$unMeetFee}元";
+            }
+        }
+
+        return '';
+    }
 }