Sfoglia il codice sorgente

零售端-订单提交运费、包装费计算优化

ouyang 1 settimana fa
parent
commit
0a9d2e870a

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

@@ -22,7 +22,6 @@ 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
 {
@@ -197,9 +196,6 @@ class NoticeController extends PublicController
 
                         //来自商城的新订单微信通知
                         WxMessageClass::hdNewOrderInform($shop, $order);
-
-                        // 标记 xhPsMethod 未达门槛附加费当日已收(每日每客户每配送方式只收一次)
-                        self::markPsUnMeetFeeAfterPay($order);
                     }
                 }
             }
@@ -299,10 +295,6 @@ 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) {
@@ -417,7 +409,6 @@ class NoticeController extends PublicController
                 if ($order->fromType == dict::getDict("fromType", "mall")) {
                     //来自商城的新订单微信通知
                     WxMessageClass::hdNewOrderInform($shop, $order);
-                    self::markPsUnMeetFeeAfterPay($order);
                 }
             }
             exit();
@@ -560,12 +551,4 @@ class NoticeController extends PublicController
         }
     }
 
-    /**
-     * 商城订单支付成功后,标记 xhPsMethod 未达门槛附加费当日已收
-     */
-    protected static function markPsUnMeetFeeAfterPay($order)
-    {
-        PsMethodClass::markUnMeetFeePaidFromOrder($order);
-    }
-
 }

+ 14 - 21
app-mall/controllers/OrderController.php

@@ -1280,11 +1280,8 @@ class OrderController extends BaseController
             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(
+            $customInfo = \bizHd\order\classes\PsMethodClass::buildCustomInfo($hd, $custom);
+            $additionalCosts = \bizHd\order\classes\PsMethodClass::calcFeeForMallContext(
                 $this->shopId,
                 $this->mainId,
                 $sendType,
@@ -1323,20 +1320,17 @@ class OrderController extends BaseController
                     if (empty($post['deliveryPlatform'])) {
                         util::fail('请选择跑腿');
                     }
-                    $quoteCustom = $this->user;
-                    if ($quoteGoodsType === 0) {
-                        $quoteCustom = (object)[
-                            'name' => $post['receiveUserName'] ?? '',
-                            'mobile' => $post['receiveMobile'] ?? '',
-                            'fullAddress' => $post['address'] ?? '',
-                            'floor' => $post['floor'] ?? '',
-                            'dist' => '',
-                            'lat' => $post['lat'],
-                            'long' => $post['long'],
-                            'address' => $post['address'] ?? '',
-                            'city' => $post['city'] ?? '',
-                        ];
-                    }
+                    $quoteCustom = (object)[
+                        'name' => $post['receiveUserName'] ?? '',
+                        'mobile' => $post['receiveMobile'] ?? '',
+                        'fullAddress' => $post['address'] ?? '',
+                        'floor' => $post['floor'] ?? '',
+                        'dist' => '',
+                        'lat' => $post['lat'],
+                        'long' => $post['long'],
+                        'address' => $post['address'] ?? '',
+                        'city' => $post['city'] ?? '',
+                    ];
                     $quoteResult = DeliveryQuoteUtil::getDeliveryQuote([
                         'productList' => $resolvedProduct,
                         'deliveryPlatform' => $post['deliveryPlatform'],
@@ -1436,7 +1430,7 @@ class OrderController extends BaseController
 
             $orderSn = $return->orderSn ?? '';
             if (!empty($orderSn) && (float)$unMeetSendCost > 0) {
-                $unMeetSendKey = 'hd_ps_order_unmeet_send_' . $orderSn;
+                $unMeetSendKey = 'hd_order_unmeet_send:' . $orderSn;
                 Yii::$app->redis->executeCommand('SETEX', [$unMeetSendKey, 864000, (string)$unMeetSendCost]);
             }
 
@@ -1547,7 +1541,6 @@ class OrderController extends BaseController
                     if ($order->fromType == 2) {
                         //来自商城的新订单微信通知
                         WxMessageClass::hdNewOrderInform($shop, $order);
-                        \bizHd\order\classes\PsMethodClass::markUnMeetFeePaidFromOrder($order);
                     }
                 }
             }

+ 83 - 39
app-mall/controllers/PsMethodController.php

@@ -8,12 +8,13 @@ use Yii;
 
 /**
  * 商城端配送方式接口(xhPsMethod / xhPsExplain)
- * 用途:混合结算页展示各购买方式对应的配送说明
+ * 用途:混合结算页展示各购买方式对应的配送说明;预览未达门槛附加费(与下单 calcFee 同源)
  */
 class PsMethodController extends BaseController
 {
     /**
      * 获取门店全部配送方式及说明项
+     * 可选 GET:itemTotalAmount、bigNum — 一并返回各 style 附加费预览(进页一次算好)
      */
     public function actionGetAllMethod()
     {
@@ -23,48 +24,91 @@ class PsMethodController extends BaseController
 
         try {
             $configs = PsMethodClass::getAllConfigs($this->shopId, $this->mainId);
+            $customInfo = PsMethodClass::buildCustomInfo($this->hd ?? null, $this->custom ?? null);
+            $configs = PsMethodClass::applyCustomHomeRuleToConfigs($configs, $customInfo);
+            $configs = PsMethodClass::applyDailyFeeLockFlags($configs, (int)($this->customId ?? 0));
 
-            // 客户单独送货上门规则覆盖 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);
+            $result = ['method' => $configs];
+            $itemTotalAmount = Yii::$app->request->get('itemTotalAmount');
+            $bigNum = Yii::$app->request->get('bigNum');
+            if ($itemTotalAmount !== null && $itemTotalAmount !== '' && $bigNum !== null && $bigNum !== '') {
+                $result['fees'] = PsMethodClass::previewMixUnMeetFeeBatch(
+                    $this->shopId,
+                    $this->mainId,
+                    (float)$itemTotalAmount,
+                    (float)$bigNum,
+                    (int)($this->customId ?? 0),
+                    $this->hd ?? null,
+                    $this->custom ?? null
+                );
             }
 
-            // 今日已对附加费做过决策时,前端不再展示门槛加收(每日每客户每购买方式只收一次;首单未收则当日不再收)
-            $customId = (int)($this->customId ?? 0);
-            if ($customId > 0) {
-                foreach ($configs as &$config) {
-                    $style = (int)($config['style'] ?? 0);
-                    $unMeet = (int)($config['unMeet'] ?? 0);
-                    $hasSendDecision = PsMethodClass::hasSendFeeDecisionToday($customId, $style);
-                    $hasPackDecision = PsMethodClass::hasPackFeeDecisionToday($customId, $style);
-                    $config['todaySendFeeLocked'] = $hasSendDecision ? 1 : 0;
-                    $config['todayPackFeeLocked'] = $hasPackDecision ? 1 : 0;
-                    if ($unMeet === 0 && $hasSendDecision) {
-                        $config['minAmount'] = 0;
-                        $config['minNum'] = 0;
-                        $config['unMeetFee'] = 0;
-                    } elseif ($unMeet === 2 && $hasPackDecision) {
-                        $config['minAmount'] = 0;
-                        $config['minNum'] = 0;
-                        $config['unMeetFee'] = 0;
-                    }
-                }
-                unset($config);
-            }
+            util::success($result);
+        } catch (\Exception $e) {
+            util::fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 混合结算页批量预览未达门槛附加费(商品金额/扎数变化时刷新,切换购买方式不再重复请求)
+     * POST: itemTotalAmount, bigNum
+     */
+    public function actionCalcMixFeeBatch()
+    {
+        if (empty($this->shopId) || empty($this->mainId)) {
+            util::fail('门店信息无效');
+        }
+
+        $itemTotalAmount = (float)Yii::$app->request->post('itemTotalAmount', 0);
+        $bigNum = (float)Yii::$app->request->post('bigNum', 0);
+
+        try {
+            $fees = PsMethodClass::previewMixUnMeetFeeBatch(
+                $this->shopId,
+                $this->mainId,
+                $itemTotalAmount,
+                $bigNum,
+                (int)($this->customId ?? 0),
+                $this->hd ?? null,
+                $this->custom ?? null
+            );
+            util::success(['fees' => $fees]);
+        } catch (\Exception $e) {
+            util::fail($e->getMessage());
+        }
+    }
 
-            util::success(['method' => $configs]);
+    /**
+     * 单购买方式预览(保留兼容)
+     * POST: sendType, itemTotalAmount, bigNum
+     */
+    public function actionCalcMixFee()
+    {
+        if (empty($this->shopId) || empty($this->mainId)) {
+            util::fail('门店信息无效');
+        }
+
+        $sendType = (int)Yii::$app->request->post('sendType', -1);
+        if ($sendType < 0 || $sendType > 4) {
+            util::fail('配送方式无效');
+        }
+
+        $itemTotalAmount = (float)Yii::$app->request->post('itemTotalAmount', 0);
+        $bigNum = (float)Yii::$app->request->post('bigNum', 0);
+        $customInfo = PsMethodClass::buildCustomInfo($this->hd ?? null, $this->custom ?? null);
+        $customId = (int)($this->customId ?? 0);
+
+        try {
+            $result = PsMethodClass::previewMixUnMeetFee(
+                $this->shopId,
+                $this->mainId,
+                $sendType,
+                $itemTotalAmount,
+                $bigNum,
+                $customId,
+                $customInfo
+            );
+            util::success($result);
         } catch (\Exception $e) {
             util::fail($e->getMessage());
         }

+ 6 - 0
biz-hd/order/classes/OrderClass.php

@@ -676,6 +676,12 @@ class OrderClass extends BaseClass
         if ((int)($order->forward ?? 0) === 0) {
             DistributionCommissionClass::tryCalcDistributionAfterPay((int)$orderId);
         }
+
+        // 商城订单支付成功:标记 xhPsMethod 未达门槛附加费当日决策(与 NoticeController / 余额支付同源)
+        if ((int)($order->forward ?? 0) === 0
+            && (int)($order->fromType ?? 0) === (int)dict::getDict('fromType', 'mall')) {
+            PsMethodClass::markUnMeetFeePaidFromOrder($order);
+        }
     }
 
     //转化出送到时间 ssh 2020.3.15

+ 202 - 9
biz-hd/order/classes/PsMethodClass.php

@@ -344,7 +344,7 @@ class PsMethodClass extends BaseClass
             return false;
         }
         $current = date('Y_m_d');
-        $key = 'hd_ps_custom_today_has_get_pack_cost_' . (int)$sendType . '_' . $current . '_' . $customId;
+        $key = 'hdCustom_hasGetPackCost:' . (int)$sendType . '_' . $current . ':' . $customId;
         $has = Yii::$app->redis->executeCommand('GET', [$key]);
         return (!empty($has) && $has == 1);
     }
@@ -358,7 +358,7 @@ class PsMethodClass extends BaseClass
             return false;
         }
         $current = date('Y_m_d');
-        $key = 'hd_ps_custom_today_skip_pack_cost_' . (int)$sendType . '_' . $current . '_' . $customId;
+        $key = 'hdCustom_skipPackCost:' . (int)$sendType . '_' . $current . ':' . $customId;
         $has = Yii::$app->redis->executeCommand('GET', [$key]);
         return (!empty($has) && $has == 1);
     }
@@ -380,7 +380,7 @@ class PsMethodClass extends BaseClass
             return false;
         }
         $current = date('Y_m_d');
-        $key = 'hd_ps_custom_today_has_get_send_cost_' . (int)$sendType . '_' . $current . '_' . $customId;
+        $key = 'hdCustom_hasGetSendCost:' . (int)$sendType . '_' . $current . ':' . $customId;
         $has = Yii::$app->redis->executeCommand('GET', [$key]);
         return (!empty($has) && $has == 1);
     }
@@ -394,7 +394,7 @@ class PsMethodClass extends BaseClass
             return false;
         }
         $current = date('Y_m_d');
-        $key = 'hd_ps_custom_today_skip_send_cost_' . (int)$sendType . '_' . $current . '_' . $customId;
+        $key = 'hdCustom_skipSendCost:' . (int)$sendType . '_' . $current . ':' . $customId;
         $has = Yii::$app->redis->executeCommand('GET', [$key]);
         return (!empty($has) && $has == 1);
     }
@@ -429,20 +429,20 @@ class PsMethodClass extends BaseClass
 
         if (!self::hasSendFeeDecisionToday($customId, $style)) {
             if ((float)$unMeetSendCost > 0) {
-                $sendKey = 'hd_ps_custom_today_has_get_send_cost_' . $style . '_' . $current . '_' . $customId;
+                $sendKey = 'hdCustom_hasGetSendCost:' . $style . '_' . $current . ':' . $customId;
                 Yii::$app->redis->executeCommand('SETEX', [$sendKey, $ttl, '1']);
             } else {
-                $skipKey = 'hd_ps_custom_today_skip_send_cost_' . $style . '_' . $current . '_' . $customId;
+                $skipKey = 'hdCustom_skipSendCost:' . $style . '_' . $current . ':' . $customId;
                 Yii::$app->redis->executeCommand('SETEX', [$skipKey, $ttl, '1']);
             }
         }
 
         if (!self::hasPackFeeDecisionToday($customId, $style)) {
             if ((float)$packingFee > 0) {
-                $packKey = 'hd_ps_custom_today_has_get_pack_cost_' . $style . '_' . $current . '_' . $customId;
+                $packKey = 'hdCustom_hasGetPackCost:' . $style . '_' . $current . ':' . $customId;
                 Yii::$app->redis->executeCommand('SETEX', [$packKey, $ttl, '1']);
             } else {
-                $skipKey = 'hd_ps_custom_today_skip_pack_cost_' . $style . '_' . $current . '_' . $customId;
+                $skipKey = 'hdCustom_skipPackCost:' . $style . '_' . $current . ':' . $customId;
                 Yii::$app->redis->executeCommand('SETEX', [$skipKey, $ttl, '1']);
             }
         }
@@ -462,15 +462,208 @@ class PsMethodClass extends BaseClass
         $packingFee = (float)(is_object($order) ? ($order->packingFee ?? 0) : ($order['packingFee'] ?? 0));
         $unMeetSendCost = 0;
         if (!empty($orderSn)) {
-            $unMeetSendKey = 'hd_ps_order_unmeet_send_' . $orderSn;
+            $unMeetSendKey = 'hd_order_unmeet_send:' . $orderSn;
             $cached = Yii::$app->redis->executeCommand('GET', [$unMeetSendKey]);
             if (!empty($cached)) {
                 $unMeetSendCost = (float)$cached;
             }
         }
+        // 无附加包装费/附加运费时不写入(避免满足门槛的订单误记 skip)
+        if ($packingFee <= 0 && $unMeetSendCost <= 0) {
+            return;
+        }
         self::recordDailyUnMeetFeeState($customId, $sendType, $packingFee, $unMeetSendCost);
     }
 
+    /**
+     * 合并 hd + custom 为 calcFee / applyCustomHomeRule 使用的客户信息(与商城 getAllMethod 一致)
+     * @param mixed $hd 花店关系 xhHd
+     * @param mixed $custom 客户 xhCustom
+     * @return array
+     */
+    public static function buildCustomInfo($hd = null, $custom = null)
+    {
+        $customInfo = [];
+        if (!empty($hd)) {
+            if (is_array($hd)) {
+                $customInfo = $hd;
+            } elseif (is_object($hd) && method_exists($hd, 'toArray')) {
+                $customInfo = $hd->toArray();
+            } elseif (is_object($hd) && isset($hd->attributes)) {
+                $customInfo = $hd->attributes;
+            } else {
+                $customInfo = (array)$hd;
+            }
+        }
+        if (!empty($custom)) {
+            if (is_array($custom)) {
+                $customArr = $custom;
+            } elseif (is_object($custom) && method_exists($custom, 'toArray')) {
+                $customArr = $custom->toArray();
+            } elseif (is_object($custom) && isset($custom->attributes)) {
+                $customArr = $custom->attributes;
+            } else {
+                $customArr = (array)$custom;
+            }
+            $customInfo = array_merge($customInfo, $customArr);
+        }
+        return $customInfo;
+    }
+
+    /**
+     * 对配送方式列表应用 style=0 客户单独送货上门规则
+     * @param array $configs getAllConfigs 结果
+     * @param array $customInfo buildCustomInfo 结果
+     * @return array
+     */
+    public static function applyCustomHomeRuleToConfigs(array $configs, array $customInfo)
+    {
+        if (empty($customInfo)) {
+            return $configs;
+        }
+        foreach ($configs as &$config) {
+            if ((int)($config['style'] ?? 0) === 0) {
+                $config = self::applyCustomHomeRule($config, $customInfo);
+            }
+        }
+        unset($config);
+        return $configs;
+    }
+
+    /**
+     * 标记当日附加费是否已决策(供商城 getAllMethod 下发锁定态)
+     * @param array $configs 配送方式列表
+     * @param int $customId 花店客户 id
+     * @return array
+     */
+    public static function applyDailyFeeLockFlags(array $configs, $customId)
+    {
+        $customId = (int)$customId;
+        if ($customId <= 0) {
+            return $configs;
+        }
+        foreach ($configs as &$config) {
+            $style = (int)($config['style'] ?? 0);
+            $unMeet = (int)($config['unMeet'] ?? 0);
+            $hasSendDecision = self::hasSendFeeDecisionToday($customId, $style);
+            $hasPackDecision = self::hasPackFeeDecisionToday($customId, $style);
+            $config['todaySendFeeLocked'] = $hasSendDecision ? 1 : 0;
+            $config['todayPackFeeLocked'] = $hasPackDecision ? 1 : 0;
+            if ($unMeet === 0 && $hasSendDecision) {
+                $config['minAmount'] = 0;
+                $config['minNum'] = 0;
+                $config['unMeetFee'] = 0;
+            } elseif ($unMeet === 2 && $hasPackDecision) {
+                $config['minAmount'] = 0;
+                $config['minNum'] = 0;
+                $config['unMeetFee'] = 0;
+            }
+        }
+        unset($config);
+        return $configs;
+    }
+
+    /**
+     * 商城混合结算:统一入口调用 calcFee(预览与下单共用)
+     */
+    public static function calcFeeForMallContext($shopId, $mainId, $sendType, $itemTotalAmount, $bigNum, $customId = 0, $customInfo = [])
+    {
+        return self::calcFee($shopId, $mainId, $sendType, $itemTotalAmount, $bigNum, $customId, is_array($customInfo) ? $customInfo : []);
+    }
+
+    /**
+     * 商城结算页预览未达门槛附加运费/包装费(与 create-mix-order 内 calcFee 同源)
+     * @return array
+     */
+    public static function previewMixUnMeetFee($shopId, $mainId, $sendType, $itemTotalAmount, $bigNum, $customId = 0, $customInfo = [])
+    {
+        $costs = self::calcFeeForMallContext($shopId, $mainId, $sendType, $itemTotalAmount, $bigNum, $customId, $customInfo);
+        $style = (int)$sendType;
+        $config = self::getConfig($shopId, $mainId, $style);
+        if (empty($config)) {
+            return [
+                'sendCost' => 0,
+                'packCost' => 0,
+                'unMeetSendCost' => 0,
+                'packingFee' => 0,
+                'belowMinimum' => false,
+                'canOrder' => true,
+                'tip' => '',
+            ];
+        }
+        $config = self::mergeCustomHomeRule($config, $style, $customInfo);
+        $belowMinimum = self::isBelowMinimum($itemTotalAmount, $bigNum, $config);
+        $tip = '';
+        $canOrder = true;
+
+        if ($belowMinimum) {
+            $unMeet = (int)($config['unMeet'] ?? 0);
+            $unMeetFee = (float)($config['unMeetFee'] ?? 0);
+            $conditionText = self::buildUnMeetConditionText($config);
+            if ($unMeet === 1) {
+                $canOrder = false;
+                $tip = $conditionText !== '' ? "订单不满{$conditionText},不能下单" : '未达到最低消费,不能下单';
+            } elseif (($unMeet === 0 || $unMeet === 2) && $unMeetFee > 0) {
+                $locked = ($unMeet === 0 && self::hasSendFeeDecisionToday($customId, $style))
+                    || ($unMeet === 2 && self::hasPackFeeDecisionToday($customId, $style));
+                if (!$locked) {
+                    $feeLabel = $unMeet === 2 ? '包装费' : '运费';
+                    if ($conditionText !== '') {
+                        $tip = "订单不满{$conditionText},加{$feeLabel}{$unMeetFee}元";
+                    }
+                }
+            }
+        }
+
+        $checkErr = self::checkLimit(
+            $shopId,
+            $mainId,
+            $sendType,
+            $itemTotalAmount,
+            $bigNum,
+            $costs['packCost'],
+            $costs['sendCost'],
+            $customId,
+            $customInfo
+        );
+        if ($checkErr !== '') {
+            $canOrder = false;
+            $tip = $checkErr;
+        }
+
+        return [
+            'sendCost' => (float)$costs['sendCost'],
+            'packCost' => (float)$costs['packCost'],
+            'unMeetSendCost' => (float)$costs['sendCost'],
+            'packingFee' => (float)$costs['packCost'],
+            'belowMinimum' => $belowMinimum,
+            'canOrder' => $canOrder,
+            'tip' => $tip,
+        ];
+    }
+
+    /**
+     * 混合结算页:一次预览各购买方式未达门槛附加费(不含 style=2 跑腿距离运费)
+     * @return array 键为 style 字符串,如 ['0' => [...], '1' => [...]]
+     */
+    public static function previewMixUnMeetFeeBatch($shopId, $mainId, $itemTotalAmount, $bigNum, $customId = 0, $hd = null, $custom = null)
+    {
+        $customInfo = self::buildCustomInfo($hd, $custom);
+        $fees = [];
+        for ($style = 0; $style <= 4; $style++) {
+            $fees[(string)$style] = self::previewMixUnMeetFee(
+                $shopId,
+                $mainId,
+                $style,
+                $itemTotalAmount,
+                $bigNum,
+                (int)$customId,
+                $customInfo
+            );
+        }
+        return $fees;
+    }
+
     /**
      * 计算未达最低消费时应写入订单的附加运费/包装费
      * @return array ['sendCost' => 附加运费, 'packCost' => 附加包装费]

+ 0 - 1
common/components/delivery/util/DeliveryQuoteUtil.php

@@ -135,7 +135,6 @@ class DeliveryQuoteUtil
             }
         } else {
             //新免费规则走这里,欧阳写到这里有问题沟通 ssh 20260730
-
             $freightType = $params['order']['freightType'] ?? 0; //运费计算方式,0 按距离计算 1 免运费,包运费
             $property = $order['goodsType'] ?? 1;//订单是什么类型订单 0 花束订单 1 花材订单,多种商品,只要含一把花束,就是花束订单
             $totalPrice = $params['order']['itemTotalAmount'] ?? 0;