Эх сурвалжийг харах

Merge remote-tracking branch 'origin/redesign‌-260706' into redesign‌-260706

ouyang 3 долоо хоног өмнө
parent
commit
c395111e8d

+ 89 - 17
app-ghs/controllers/OrderController.php

@@ -614,10 +614,11 @@ class OrderController extends BaseController
         OrderClass::valid($order, $this->shopId);
         if ($order->payStatus == 1) {
             util::success(['returnStatus' => 'SUCCESS']);
-        }else {
-            util::success(['returnStatus' => 'unPay']);
         }
-        OrderClass::valid($order, $this->shopId);
+        if ($order->status == 5) {
+            util::success(['returnStatus' => 'CANCEL']);
+        }
+
         $cgId = $order->purchaseId ?? 0;
         $cg = PurchaseClass::getById($cgId, true);
         $orderSn = $cg->orderSn ?? '';
@@ -690,8 +691,8 @@ class OrderController extends BaseController
                     $order = OrderClass::getById($saleId, true);
                     if (!empty($order)) {
 
-                        //解决扫码付重复打印问题
-                        $cacheKey = 'scan_pay_print_order_' . $orderSn;
+                        //解决付款成功后重复通知打印问题
+                        $cacheKey = 'pay_success_notice_' . $orderSn;
                         $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
                         if (!empty($has)) {
                             return false;
@@ -746,9 +747,11 @@ class OrderController extends BaseController
         OrderClass::valid($order, $this->shopId);
         if ($order->payStatus == 1) {
             util::success(['returnStatus' => 'SUCCESS']);
-        } else {
-            util::success(['returnStatus' => 'unPay']);
         }
+        if ($order->status == 5) {
+            util::success(['returnStatus' => 'CANCEL']);
+        }
+
         $cgId = $order->purchaseId ?? 0;
         $cg = PurchaseClass::getById($cgId, true);
         $orderSn = $cg->orderSn ?? '';
@@ -821,8 +824,8 @@ class OrderController extends BaseController
                     $order = OrderClass::getById($saleId, true);
                     if (!empty($order)) {
 
-                        //解决扫码付重复打印问题
-                        $cacheKey = 'scan_pay_print_order_' . $orderSn;
+                        //解决付款成功后重复通知打印问题
+                        $cacheKey = 'pay_success_notice_' . $orderSn;
                         $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
                         if (!empty($has)) {
                             return false;
@@ -1023,8 +1026,8 @@ class OrderController extends BaseController
                 $order = OrderClass::getById($saleId, true);
                 if (!empty($order)) {
 
-                    //解决扫码付重复打印问题
-                    $cacheKey = 'scan_pay_print_order_' . $orderSn;
+                    //解决付款成功后重复通知打印问题
+                    $cacheKey = 'pay_success_notice_' . $orderSn;
                     $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
                     if (!empty($has)) {
                         return false;
@@ -2752,19 +2755,88 @@ class OrderController extends BaseController
     public function actionCancel()
     {
         $shopAdmin = $this->shopAdmin;
-        if (isset($shopAdmin->super) == false || $shopAdmin->super != 1) {
+        if (!isset($shopAdmin->super) || $shopAdmin->super != 1) {
             util::fail('超管才能取消订单');
         }
-        $id = Yii::$app->request->get('id');
-        $order = OrderClass::getById($id, true);
+        //避免重复提交
+        $adminId = $this->adminId;
+        util::checkRepeatCommit($adminId, 3);
 
+        $get = Yii::$app->request->get();
+        $cashier = $get['cashier'] ?? 0;
+        //是否强制取消订单 0不要强制 1强制,没有关单成功也要取消
+        $forceCancel = $get['forceCancel'] ?? 0;
+        $id = $get['id'] ?? 0;
+        $order = OrderClass::getById($id, true);
+        OrderClass::valid($order, $this->shopId);
         if ($order->orderType == 2) {
             util::fail('客户下单,不能主动取消');
         }
+        //如果收银台的订单,取消时要关闭拉卡拉的订单
+        if ($cashier == 1) {
+            //已取消,不需要再走下面的流程
+            if ($order->status == 5) {
+                util::complete('已取消');
+            }
+            $cgId = $order->purchaseId ?? 0;
+            $cg = PurchaseClass::getById($cgId, true);
+            $orderSn = $cg->orderSn ?? '';
 
-        OrderClass::valid($order, $this->shopId);
-        OrderService::expire($order, true);
-        util::complete('已取消');
+            $shop = $this->shop;
+            $merchantPrivateKeyPath = Yii::getAlias("@vendor/lakala") . '/production/api_private_key.pem';
+            $lklCertificatePath = Yii::getAlias("@vendor/lakala") . '/production/lkl-apigw-v1.cer';
+            $params = [
+                'appid' => 'OP00002119',
+                'serial_no' => '018b08cfddbd',
+                'merchant_no' => $shop->lklSjNo,
+                'term_no' => $shop->lklScanTermNo,
+                'merchantPrivateKeyPath' => $merchantPrivateKeyPath,
+                'lklCertificatePath' => $lklCertificatePath,
+            ];
+            $laResource = new Lakala($params);
+
+            // 1. 先查询拉卡拉侧订单状态
+            $queryResponse = $laResource->query(['orderSn' => $orderSn]);
+            //响应CODE为BBS00000,仅表示查到了这笔交易。交易本身的成功与否状态,要查看响应报文中的trade_state这个值
+            if (isset($queryResponse['code']) && $queryResponse['code'] == 'BBS00000') {
+                $tradeState = $queryResponse['resp_data']['trade_state'] ?? '';
+
+                // 订单已支付,拦截取消操作
+                if ($tradeState == 'SUCCESS') {
+                    util::fail('已付款,不能取消');
+                }
+
+                // 根据拉卡拉聚合支付文档(id=116)及实际情况,判断是否为非终态
+                // INIT:初始化, CREATE:下单成功, DEAL:交易处理中, USERPAYING:支付中, NOTPAY:未支付
+                $pendingStates = ['INIT', 'CREATE', 'DEAL', 'USERPAYING', 'NOTPAY'];
+                if (in_array($tradeState, $pendingStates)) {
+                    $closeParams = ['orderSn' => $orderSn];
+                    $closeResponse = $laResource->close($closeParams);
+                    if (!isset($closeResponse['code']) || $closeResponse['code'] != 'BBS00000') {
+                        $msg = $closeResponse['msg'] ?? '关闭失败';
+                        noticeUtil::push("拉卡拉聚合支付关单失败:{$orderSn},原因:{$msg} 原单状态:{$tradeState}", '15280215347');
+                        if ($forceCancel == 0) {
+                            //返回禁止关单
+                            util::fail('取消失败,如必要请强制取消');
+                        }
+                    }else{
+                        noticeUtil::push("关单成功,订单号:{$orderSn} 原单状态:{$tradeState}", '15280215347');
+                    }
+                }
+                // 如果状态是 CLOSED 或 CANCELED,则视为已关单,直接继续本地流程
+            }
+        }
+        $connection = Yii::$app->db;
+        $transaction = $connection->beginTransaction();
+        try {
+            OrderService::expire($order);
+            $transaction->commit();
+            util::complete('已取消');
+        } catch (\Exception $exception) {
+            $transaction->rollBack();
+            Yii::info("取消原因:" . $exception->getMessage());
+            util::fail('取消失败');
+        }
     }
 
     //获取收款语音播报地址 ssh 20211231

+ 3 - 1
app-ghs/controllers/TestController.php

@@ -505,7 +505,9 @@ class TestController extends BaseController
             //['name' => '深圳每日鲜花批发', 'merchantNo' => '82258405992020E', 'wx' => [902309136, 902307468], 'zfb' => '2088780298610704'],
             //['name' => '滨州花镜鲜花', 'merchantNo' => '82246605992005U', 'wx' => [904373612, 904373338], 'zfb' => '2088780426621723'],
             //['name' => '广州情意鲜花', 'merchantNo' => '8225810599201U2', 'wx' => [904871059, 904872401], 'zfb' => '2088780455410378'],
-            ['name' => '我要花义乌店', 'merchantNo' => '8223380599200A6', 'wx' => [2000005220, 907253683], 'zfb' => '2088780613953851'],
+            //['name' => '我要花义乌店', 'merchantNo' => '8223380599200A6', 'wx' => [2000005220, 907253683], 'zfb' => '2088780613953851'],
+            ['name' => '宣城花农鲜花批发', 'merchantNo' => '82237705992001J', 'wx' => [908946077, 908945971], 'zfb' => '2088780700979012'],
+            ['name' => '亳州花鲜谷', 'merchantNo' => '82238105992004A', 'wx' => [908947821, 908946480], 'zfb' => '2088780700742980'],
             //['name' => '厦门纯彩花艺', 'merchantNo' => '8223930599200LD', 'wx' => [894984230, 894983290], 'zfb' => '2088680439152343'],
             //['name' => '东莞我要花鲜花', 'merchantNo' => '8226020599200R8', 'wx' => [894794554, 894794917], 'zfb' => '2088680405463378'],
             //['name' => '镇江花珩鲜花批发', 'merchantNo' => '82231405992003N', 'wx' => [894925711, 894925626], 'zfb' => '2088680426877183'],

+ 65 - 10
app-hd/controllers/OrderController.php

@@ -94,25 +94,78 @@ class OrderController extends BaseController
     //取消订单 ssh 20221231
     public function actionCancel()
     {
+        $shopAdmin = $this->shopAdmin;
         //避免重复提交
         $adminId = $this->adminId;
-        util::checkRepeatCommit($adminId, 8);
+        util::checkRepeatCommit($adminId, 3);
 
         $get = Yii::$app->request->get();
+        $cashier = $get['cashier'] ?? 0;
+        //是否强制取消订单 0不要强制 1强制,没有关单成功也要取消
+        $forceCancel = $get['forceCancel'] ?? 0;
         $id = $get['id'] ?? 0;
         $order = OrderClass::getById($id, true);
         if ($order->mainId != $this->mainId) {
             util::fail('不是你的订单');
         }
+        //如果收银台的订单,取消时要关闭拉卡拉的订单
+        if ($cashier == 1) {
+            //已取消,不需要再走下面的流程
+            if ($order->status == 5) {
+                util::complete('已取消');
+            }
+            $orderSn = $order->orderSn ?? '';
+
+            $shop = $this->shop;
+            $merchantPrivateKeyPath = Yii::getAlias("@vendor/lakala") . '/production/api_private_key.pem';
+            $lklCertificatePath = Yii::getAlias("@vendor/lakala") . '/production/lkl-apigw-v1.cer';
+            $params = [
+                'appid' => 'OP00002119',
+                'serial_no' => '018b08cfddbd',
+                'merchant_no' => $shop->lklSjNo,
+                'term_no' => $shop->lklScanTermNo,
+                'merchantPrivateKeyPath' => $merchantPrivateKeyPath,
+                'lklCertificatePath' => $lklCertificatePath,
+            ];
+            $laResource = new Lakala($params);
 
-        $staff = $this->shopAdmin;
+            // 1. 先查询拉卡拉侧订单状态
+            $queryResponse = $laResource->query(['orderSn' => $orderSn]);
+            //响应CODE为BBS00000,仅表示查到了这笔交易。交易本身的成功与否状态,要查看响应报文中的trade_state这个值
+            if (isset($queryResponse['code']) && $queryResponse['code'] == 'BBS00000') {
+                $tradeState = $queryResponse['resp_data']['trade_state'] ?? '';
+
+                // 订单已支付,拦截取消操作
+                if ($tradeState == 'SUCCESS') {
+                    util::fail('已付款,不能取消');
+                }
+
+                // 根据拉卡拉聚合支付文档(id=116)及实际情况,判断是否为非终态
+                // INIT:初始化, CREATE:下单成功, DEAL:交易处理中, USERPAYING:支付中, NOTPAY:未支付
+                $pendingStates = ['INIT', 'CREATE', 'DEAL', 'USERPAYING', 'NOTPAY'];
+                if (in_array($tradeState, $pendingStates)) {
+                    $closeParams = ['orderSn' => $orderSn];
+                    $closeResponse = $laResource->close($closeParams);
+                    if (!isset($closeResponse['code']) || $closeResponse['code'] != 'BBS00000') {
+                        $msg = $closeResponse['msg'] ?? '关闭失败';
+                        noticeUtil::push("拉卡拉聚合支付关单失败:{$orderSn},原因:{$msg} 原单状态:{$tradeState}", '15280215347');
+                        if ($forceCancel == 0) {
+                            //返回禁止关单
+                            util::fail('取消失败,如必要请强制取消');
+                        }
+                    }else{
+                        noticeUtil::push("关单成功,订单号:{$orderSn} 原单状态:{$tradeState}", '15280215347');
+                    }
+                }
+            }
+        }
 
         $connection = Yii::$app->db;
         $transaction = $connection->beginTransaction();
         try {
-            OrderClass::setExpire($order, true, $staff);
+            OrderClass::setExpire($order, true, $shopAdmin);
             $transaction->commit();
-            util::complete();
+            util::complete('已取消');
         } catch (\Exception $exception) {
             $transaction->rollBack();
             Yii::info("取消原因:" . $exception->getMessage());
@@ -349,8 +402,9 @@ class OrderController extends BaseController
         $totalFee = $order->mainPay ?? 0;
         if ($order->payStatus == 1) {
             util::success(['returnStatus' => 'SUCCESS']);
-        } else {
-            util::success(['returnStatus' => 'unPay']);
+        }
+        if ($order->status == 5) {
+            util::success(['returnStatus' => 'CANCEL']);
         }
 
         $connection = Yii::$app->db;
@@ -394,7 +448,7 @@ class OrderController extends BaseController
                     $transaction->commit();
 
                     //解决重复通知
-                    $cacheKey = 'hd_shop_order_pay_check_' . $orderSn;
+                    $cacheKey = 'pay_success_notice_' . $orderSn;
                     $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
                     if (empty($has)) {
                         Yii::$app->redis->executeCommand('SETEX', [$cacheKey, 300, 'has']);
@@ -445,8 +499,9 @@ class OrderController extends BaseController
         $totalFee = $order->mainPay ?? 0;
         if ($order->payStatus == 1) {
             util::success(['returnStatus' => 'SUCCESS']);
-        } else {
-            util::success(['returnStatus' => 'unPay']);
+        }
+        if ($order->status == 5) {
+            util::success(['returnStatus' => 'CANCEL']);
         }
 
         $connection = Yii::$app->db;
@@ -490,7 +545,7 @@ class OrderController extends BaseController
                     $transaction->commit();
 
                     //解决重复通知
-                    $cacheKey = 'hd_shop_order_pay_check_' . $orderSn;
+                    $cacheKey = 'pay_success_notice_' . $orderSn;
                     $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
                     if (empty($has)) {
                         Yii::$app->redis->executeCommand('SETEX', [$cacheKey, 300, 'has']);

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

@@ -164,7 +164,7 @@ class NoticeController extends PublicController
 
             if ($capitalType == dict::getDict('capitalType', 'xhOrder', 'id')) {
                 //解决重复通知,重要
-                $cacheKey = 'mall_order_pay_' . $orderSn;
+                $cacheKey = 'pay_success_notice_' . $orderSn;
                 $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
                 if (empty($has)) {
                     Yii::$app->redis->executeCommand('SETEX', [$cacheKey, 864000, 'has']);
@@ -272,7 +272,7 @@ class NoticeController extends PublicController
             echo '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
 
             //解决重复通知
-            $cacheKey = 'mall_order_pay_' . $orderSn;
+            $cacheKey = 'pay_success_notice_' . $orderSn;
             $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
             if (empty($has)) {
                 Yii::$app->redis->executeCommand('SETEX', [$cacheKey, 864000, 'has']);
@@ -507,7 +507,7 @@ class NoticeController extends PublicController
 
                 if ($capitalType == dict::getDict('capitalType', 'xhOrder', 'id')) {
                     //避免重复
-                    $cacheKey = 'mall_order_pay_' . $orderSn;
+                    $cacheKey = 'pay_success_notice_' . $orderSn;
                     $has = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
                     if (empty($has)) {
                         Yii::$app->redis->executeCommand('SETEX', [$cacheKey, 864000, 'has']);

+ 2 - 2
app-pt/views/main/app.php

@@ -237,8 +237,8 @@
             const downloadUrls = {
                 'xiaohuabao': 'https://api.shop.hzghd.com/1312.apk', // 销花宝APP下载链接
                 'huazhanggui': 'https://api.shop.hzghd.com/246.apk', // 花掌柜APP下载链接
-                'huazhanggui_cashier': 'https://api.shop.hzghd.com/215.apk', // 花掌柜收银下载链接
-                'xiaohuabao_cashier': 'https://api.shop.hzghd.com/175.apk' // 销花宝收银下载链接
+                'huazhanggui_cashier': 'https://api.shop.hzghd.com/220.apk', // 花掌柜收银下载链接
+                'xiaohuabao_cashier': 'https://api.shop.hzghd.com/178.apk' // 销花宝收银下载链接
             };
             const url = downloadUrls[appType];
             if (url && url !== '#') {

+ 1 - 1
biz-ghs/product/classes/ProductClass.php

@@ -2225,7 +2225,7 @@ class ProductClass extends BaseClass
         $ratioType = $data['ratioType'] ?? 0;
         if ($ratioType == 0) {
             if (!isset($data['ratio']) || $data['ratio'] <= 0) {
-                util::fail('请填写单位比');
+                util::fail('请填写几支装');
             }
             $ratio = $data['ratio'];
         } else {

+ 12 - 13
biz-hd/homePageConfig/classes/HomePageDisplayClass.php

@@ -153,7 +153,7 @@ class HomePageDisplayClass
         }
         $goods = self::filterActivityGoods($data['goods'] ?? []);
         $data['goodsTotal'] = count($goods);
-        $limit = HomePageModuleClass::resolveHomeDisplayLimit($data['displayCount'] ?? 0);
+        $limit = !empty($data['expand']) ? 0 : HomePageModuleClass::resolveHomeDisplayLimit($data['displayCount'] ?? 0);
         if ($limit > 0 && count($goods) > $limit) {
             $goods = array_slice($goods, 0, $limit);
         }
@@ -162,7 +162,7 @@ class HomePageDisplayClass
     }
 
     /**
-     * 格式化热门推荐/今日上新/下拉商品:关闭时清空商品;开启时按配置解析出真实商品列表
+     * 格式化热门推荐/今日上新/下拉商品:关闭时清空商品;开启时按真实商品数量自动推导列数与展示条数
      *
      * @param int $mainId
      * @param string $moduleKey hot|new|pullGoods
@@ -174,19 +174,18 @@ class HomePageDisplayClass
         if (empty($data['enabled'])) {
             $data['goods'] = [];
             $data['goodsTotal'] = 0;
+            $data['layoutCols'] = 1;
             return $data;
         }
-        $limit = HomePageModuleClass::resolveHomeDisplayLimit($data['displayCount'] ?? 0);
-        $paged = HomePageModuleClass::resolveDisplayGoodsPaged(
-            $mainId,
-            $data['type'],
-            $data['value'],
-            $data['sort'],
-            1,
-            $limit
-        );
-        $data['goods'] = $paged['list'];
-        $data['goodsTotal'] = $paged['total'];
+        $rows = HomePageModuleClass::matchGoodsRows($mainId, $data['type'], $data['value'], $data['sort']);
+        $total = count($rows);
+        $layout = HomePageModuleClass::resolveGoodsSectionLayout($total, $moduleKey);
+        $data['layoutCols'] = $layout['layoutCols'];
+        if ($layout['displayCount'] > 0 && $layout['displayCount'] < $total) {
+            $rows = array_slice($rows, 0, $layout['displayCount']);
+        }
+        $data['goods'] = HomePageModuleClass::formatGoodsRows($rows);
+        $data['goodsTotal'] = $total;
         return $data;
     }
 

+ 63 - 36
biz-hd/homePageConfig/classes/HomePageModuleClass.php

@@ -171,6 +171,10 @@ class HomePageModuleClass
         $data = self::normalizeActivityBase($saved);
         $data['enabled'] = HomePageConfigClass::getModuleEnabled($mainId, 'seckill');
         $data['goods'] = self::normalizeSeckillGoods($saved['goods'] ?? [], $mainId, $refreshStock);
+        // 按已添加商品数量 + 展开开关动态推导列数与展示数量(不依赖商家手动配置)
+        $layout = self::resolveActivityLayout(count($data['goods']), $data['expand']);
+        $data['layoutCols'] = $layout['layoutCols'];
+        $data['displayCount'] = $layout['displayCount'];
         $data['status'] = self::calcActivityStatus($data['startTime'], $data['endTime'], $data['enabled']);
         return $data;
     }
@@ -314,6 +318,10 @@ class HomePageModuleClass
         $data = self::normalizeActivityBase($saved);
         $data['enabled'] = HomePageConfigClass::getModuleEnabled($mainId, 'groupBuy');
         $data['goods'] = self::normalizeGroupBuyGoods($saved['goods'] ?? [], $mainId, $refreshStock);
+        // 按已添加商品数量 + 展开开关动态推导列数与展示数量(不依赖商家手动配置)
+        $layout = self::resolveActivityLayout(count($data['goods']), $data['expand']);
+        $data['layoutCols'] = $layout['layoutCols'];
+        $data['displayCount'] = $layout['displayCount'];
         $data['status'] = self::calcActivityStatus($data['startTime'], $data['endTime'], $data['enabled']);
         return $data;
     }
@@ -451,8 +459,6 @@ class HomePageModuleClass
             'type' => intval($saved['type'] ?? self::LINK_GOODS),
             'value' => strval($saved['value'] ?? ''),
             'sort' => intval($saved['sort'] ?? self::SORT_PRODUCT),
-            'layoutCols' => self::normalizeLayoutCols($saved['layoutCols'] ?? null),
-            'displayCount' => intval($saved['displayCount'] ?? 0),
         ];
     }
 
@@ -464,8 +470,7 @@ class HomePageModuleClass
         $type = intval($data['type'] ?? 0);
         $value = trim(strval($data['value'] ?? ''));
         $sort = intval($data['sort'] ?? self::SORT_PRODUCT);
-        $layoutCols = intval($data['layoutCols'] ?? self::DEFAULT_LAYOUT_COLS);
-        $displayCount = self::normalizeDisplayCount($data['displayCount'] ?? 0);
+        // layoutCols/displayCount 由读取时按真实商品数量自动推导,保存时不再接收商家手动配置
         if ($name === '') {
             util::fail('请输入首页展示名称');
         }
@@ -481,21 +486,41 @@ class HomePageModuleClass
         if (!in_array($sort, [self::SORT_PRODUCT, self::SORT_SALES, self::SORT_NEW], true)) {
             util::fail('排序规则无效');
         }
-        if (!in_array($layoutCols, self::LAYOUT_COLS_OPTIONS, true)) {
-            util::fail('排列规则无效');
-        }
         self::setJson($mainId, $suffix, [
             'name' => $name,
             'type' => $type,
             'value' => $value,
             'sort' => $sort,
-            'layoutCols' => $layoutCols,
-            'displayCount' => $displayCount,
         ]);
         HomePageConfigClass::updateModuleEnabled($mainId, $moduleKey, !empty($data['enabled']) ? 1 : 0);
         return true;
     }
 
+    /**
+     * 按真实商品总数与模块类型自动推导首页列数与展示数量
+     * - 1个商品:1排1列,展示全部
+     * - 2个商品:1排2列,展示全部
+     * - ≥3个:hot/new 按1排3列且只展示3个;pullGoods 按1排2列且展示全部
+     *
+     * @param int $goodsTotal 关联配置解析出的真实商品总数
+     * @param string $moduleKey hot|new|pullGoods
+     * @return array{layoutCols:int,displayCount:int}
+     */
+    public static function resolveGoodsSectionLayout($goodsTotal, $moduleKey)
+    {
+        $goodsTotal = intval($goodsTotal);
+        if ($goodsTotal <= 1) {
+            return ['layoutCols' => 1, 'displayCount' => 0];
+        }
+        if ($goodsTotal === 2) {
+            return ['layoutCols' => 2, 'displayCount' => 0];
+        }
+        if ($moduleKey === 'pullGoods') {
+            return ['layoutCols' => 2, 'displayCount' => 0];
+        }
+        return ['layoutCols' => 3, 'displayCount' => 3];
+    }
+
     /**
      * 规范化排列规则取值,非法/缺省时回退默认值
      */
@@ -667,26 +692,9 @@ class HomePageModuleClass
         ];
     }
 
-    /**
-     * 规范化首页商品展示数量:0=全部,1-10=具体数量
-     *
-     * @param mixed $value
-     * @return int
-     */
-    public static function normalizeDisplayCount($value)
-    {
-        if ($value === '' || $value === null || intval($value) === 0) {
-            return 0;
-        }
-        $count = intval($value);
-        if ($count < 1 || $count > 10) {
-            util::fail('商品展示数量范围为1-10,留空表示全部');
-        }
-        return $count;
-    }
-
     /**
      * 根据 displayCount 计算首页实际截断条数:1-10 用配置值,0(全部)回退默认上限
+     * 供秒杀/团购等活动模块使用
      *
      * @param mixed $displayCount
      * @return int
@@ -721,14 +729,38 @@ class HomePageModuleClass
             'title' => strval($saved['title'] ?? ''),
             'subtitle' => strval($saved['subtitle'] ?? ''),
             'showCountdown' => !empty($saved['showCountdown']) ? 1 : 0,
-            'displayCount' => intval($saved['displayCount'] ?? 0),
+            // 商品展开:开启后首页按1排1列展示全部商品,默认关闭
+            'expand' => !empty($saved['expand']) ? 1 : 0,
             'startTime' => intval($saved['startTime'] ?? 0),
             'endTime' => intval($saved['endTime'] ?? 0),
             'desc' => strval($saved['desc'] ?? ''),
-            'layoutCols' => self::normalizeLayoutCols($saved['layoutCols'] ?? null),
         ];
     }
 
+    /**
+     * 按已添加商品数量与「商品展开」开关自动推导首页列数与展示数量
+     * - expand 开启:1排1列,展示全部
+     * - 1个商品:1排1列;2个:1排2列;≥3个:1排3列且只展示3个
+     *
+     * @param int $goodsCount 已添加商品总数(含未上架)
+     * @param int $expand 商品展开开关 0/1
+     * @return array{layoutCols:int,displayCount:int}
+     */
+    public static function resolveActivityLayout($goodsCount, $expand)
+    {
+        if (!empty($expand)) {
+            return ['layoutCols' => 1, 'displayCount' => 0];
+        }
+        $goodsCount = intval($goodsCount);
+        if ($goodsCount <= 1) {
+            return ['layoutCols' => 1, 'displayCount' => 0];
+        }
+        if ($goodsCount === 2) {
+            return ['layoutCols' => 2, 'displayCount' => 0];
+        }
+        return ['layoutCols' => 3, 'displayCount' => 3];
+    }
+
     public static function validateActivityBase($data)
     {
         $title = trim(strval($data['title'] ?? ''));
@@ -736,8 +768,7 @@ class HomePageModuleClass
         $desc = trim(strval($data['desc'] ?? ''));
         $startTime = intval($data['startTime'] ?? 0);
         $endTime = intval($data['endTime'] ?? 0);
-        $layoutCols = intval($data['layoutCols'] ?? self::DEFAULT_LAYOUT_COLS);
-        $displayCount = self::normalizeDisplayCount($data['displayCount'] ?? 0);
+        // layoutCols/displayCount 由读取时按商品数量自动推导,保存时不再接收商家手动配置
         if ($title === '') {
             util::fail('请输入活动标题');
         }
@@ -762,18 +793,14 @@ class HomePageModuleClass
         if (mb_strlen($desc) > 500) {
             util::fail('活动说明不能超过500字');
         }
-        if (!in_array($layoutCols, self::LAYOUT_COLS_OPTIONS, true)) {
-            util::fail('排列规则无效');
-        }
         return [
             'title' => $title,
             'subtitle' => $subtitle,
             'showCountdown' => !empty($data['showCountdown']) ? 1 : 0,
-            'displayCount' => $displayCount,
+            'expand' => !empty($data['expand']) ? 1 : 0,
             'startTime' => $startTime,
             'endTime' => $endTime,
             'desc' => $desc,
-            'layoutCols' => $layoutCols,
         ];
     }
 

+ 6 - 3
biz-hd/purchase/services/PurchaseService.php

@@ -129,7 +129,7 @@ class PurchaseService extends BaseService
             'transType' => $transType,
             'sendDistance' => $sendDistance,
         ];
-        $result = OrderClass::addOrder($arr,$custom);
+        $result = OrderClass::addOrder($arr, $custom);
         $respond->saleId = $result['id'];
         $respond->save();
 
@@ -299,7 +299,7 @@ class PurchaseService extends BaseService
         $id = $purchase->id;
         $purchase = PurchaseClass::cancel($id);
         $saleId = $purchase->saleId;
-        
+
         //销售单失效
         OrderClass::cancel($saleId);
         $couponId = $purchase->hbId;
@@ -574,7 +574,10 @@ class PurchaseService extends BaseService
 
         $info->payStatus = 1;
         $payTime = date("Y-m-d H:i:s");
-        $info->payTime = $payTime;
+        if (empty($info->payTime) || $info->payTime == '0000-00-00 00:00:00') {
+            //如果外面没有设置支付时间,即账单时间,则赋值支付时间
+            $info->payTime = $payTime;
+        }
 
         if ($payWay == dict::getDict('payWay', 'debtPay')) {
             //欠款支付(批发开单若销售单已 payAfter,沿用其待结,支持开单余额抵挂账)

+ 4 - 4
common/components/book.php

@@ -104,7 +104,7 @@ class book
             ['style' => 'info', 'value' => '提现时间:第2天上午7点前,就会把昨天收入的钱打到你的银行卡,一般法人收款,也可以委托别人收款,签委托协议即可。',],
             ['style' => 'info', 'value' => '根据你自身门店情况,选择以下套餐。下面列出的套餐,已经过全国200多家鲜花批发店和13万家花店磨合使用,非常成熟好用,请放心购买使用,以下套餐已含第一年年费。',],
             ['style' => 'miniTitle', 'value' => '软件的研发成本巨大,不管是年费、费率和套餐,都已经底价,已无商谈的空间,请理解。付费后不支持退货。',],
-            ['style' => 'miniTitle', 'value' => '以下硬件请通过我们来购买,公司统一跟厂家采购,售后各方面比较有保障。',],
+            ['style' => 'miniTitle', 'value' => '小票机、收银机等硬件请通过我们这边购买,自行在外面购买,将无法连接使用,因为参数各方面不匹配。',],
             ['style' => 'info', 'value' => '小票机和标签机,使用的是飞鹅4G带切刀版本,比较好维护,里面带流量卡的,第二年开始,每年要充60块话费。请不要贪便宜使用wifi版,维护成本很高,很容易出问题。',],
             ['style' => 'info', 'value' => '提供上门培训服务,培训时间3天,需报销差旅住宿费用,公司地址在福建厦门。',],
             ['style' => 'info', 'value' => '首家年费1580元,第二家起每家年费800元',],
@@ -116,7 +116,7 @@ class book
             ['style' => 'blankLine', 'value' => '',],
             ['style' => 'smallTitle', 'value' => '门店版-单屏收银',],
             ['style' => 'info', 'value' => '整套软件功能(一年)',],
-            ['style' => 'info', 'value' => '色单屏收银机1台',],
+            ['style' => 'info', 'value' => '色单屏收银机1台',],
             ['style' => 'info', 'value' => '扫码枪1台',],
             ['style' => 'info', 'value' => '收款小闪1个',],
             ['style' => 'info', 'value' => '收钱箱1个',],
@@ -130,7 +130,7 @@ class book
             ['style' => 'blankLine', 'value' => '',],
             ['style' => 'smallTitle', 'value' => '门店版-双屏收银',],
             ['style' => 'info', 'value' => '整套软件功能(一年)',],
-            ['style' => 'info', 'value' => '色双屏收银机1台',],
+            ['style' => 'info', 'value' => '色双屏收银机1台',],
             ['style' => 'info', 'value' => '扫码枪1台',],
             ['style' => 'info', 'value' => '收款小闪1个',],
             ['style' => 'info', 'value' => '收钱箱1个',],
@@ -285,4 +285,4 @@ class book
         ],
     ];
 
-}
+}

+ 7 - 4
sql/20260714_goods_use_case.sql

@@ -8,16 +8,19 @@ CREATE TABLE `xhGoodsUseCase` (
   `delStatus` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除状态',
   `addTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   `updateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-  PRIMARY KEY (`id`), KEY `mainId` (`mainId`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+  PRIMARY KEY (`id`),
+  KEY `mainId` (`mainId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='花束场景';
 
 CREATE TABLE `xhGoodsUseCaseRelation` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `mainId` int(11) NOT NULL DEFAULT 0,
   `goodsId` int(11) NOT NULL DEFAULT 0,
   `useCaseId` int(11) NOT NULL DEFAULT 0,
-  PRIMARY KEY (`id`), UNIQUE KEY `goods_use_case` (`goodsId`,`useCaseId`), KEY `mainId` (`mainId`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `goods_use_case` (`goodsId`,`useCaseId`),
+  KEY `mainId` (`mainId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='花束与场景关联表';