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

Merge branch 'new-version' into feature/3369-pushNoticeToAlarmService

# Conflicts:
#	common/config/params.php
#	common/helpers/Tool.php
#	composer.json
kevin_zhangl 2 лет назад
Родитель
Сommit
468751b2a0
45 измененных файлов с 3881 добавлено и 177 удалено
  1. 3 1
      .gitignore
  2. 2 0
      backendApi/config/menu.php
  3. 2 0
      backendApi/config/urlManagerRules.php
  4. 7 0
      backendApi/modules/v1/controllers/ShopController.php
  5. 38 0
      backendApi/modules/v1/controllers/UserController.php
  6. 1 0
      backendApi/modules/v1/models/lists/log/AdminHandleList.php
  7. 7 7
      backendApi/modules/v1/models/lists/shop/GoodsList.php
  8. 58 1
      backendApi/modules/v1/models/lists/shop/OrderList.php
  9. 102 0
      backendApi/modules/v1/models/lists/user/ChangeHighestEmpLevelList.php
  10. 1 1
      backendEle/package.json
  11. 13 1
      backendEle/src/router/index.js
  12. 5 9
      backendEle/src/views/shop/goods-add.vue
  13. 4 5
      backendEle/src/views/shop/index.vue
  14. 126 9
      backendEle/src/views/shop/order-list.vue
  15. 201 0
      backendEle/src/views/user/change-highest-emp-level-list.vue
  16. 24 11
      common/config/main.php
  17. 159 0
      common/helpers/IPay88.php
  18. 379 0
      common/helpers/Logistics.php
  19. 10 0
      common/helpers/user/Info.php
  20. 97 0
      common/models/ApproachDecOrder.php
  21. 141 0
      common/models/ApproachOrder.php
  22. 71 0
      common/models/ApproachOrderCall.php
  23. 75 0
      common/models/ApproachOrderGoods.php
  24. 62 0
      common/models/HighestEmpLevelLog.php
  25. 2 0
      common/models/ReceiveAddress.php
  26. 7 1
      common/models/ShopGoods.php
  27. 101 0
      common/models/WstOrderCall.php
  28. 492 0
      common/models/forms/ApproachOrderForm.php
  29. 146 0
      common/models/forms/HighestEmpLevelLogForm.php
  30. 7 3
      common/models/forms/ReceiveAddressForm.php
  31. 18 7
      common/models/forms/ShopGoodsForm.php
  32. 24 2
      console/controllers/ToolController.php
  33. 22 2
      frontendApi/config/params.php
  34. 6 1
      frontendApi/config/urlManagerRules.php
  35. 416 0
      frontendApi/modules/v1/components/IPay88.php
  36. 1 1
      frontendApi/modules/v1/controllers/ConfigController.php
  37. 307 0
      frontendApi/modules/v1/controllers/ShopController.php
  38. 1 1
      frontendEle/.gitignore
  39. 12 0
      frontendEle/src/router/index.js
  40. 10 0
      frontendEle/src/views/config/receive-address-edit.vue
  41. 2 1
      frontendEle/src/views/config/receive-address-list.vue
  42. 47 10
      frontendEle/src/views/shop/index.vue
  43. 207 103
      frontendEle/src/views/shop/order-list.vue
  44. 462 0
      frontendEle/src/views/shop/order-overseas.vue
  45. 3 0
      frontendEle/src/views/shop/order.vue

+ 3 - 1
.gitignore

@@ -18,4 +18,6 @@ Desktop.ini
 #composer.json
 
 #App
-common/config/config.php
+common/config/config.php
+common/config/config-development.php
+frontendEle/src/utils/config_production.js

+ 2 - 0
backendApi/config/menu.php

@@ -145,6 +145,8 @@ return [
 //            ['name'=>'会员历史年度最高聘级表', 'class'=>'', 'icon'=>'', 'controller'=>'user', 'action'=>'year-highest-emp-lv', 'routePath'=>'user/year-highest-emp-lv', 'show'=>1,],
 //            ['name'=>'会员历史年度最高聘级表导出', 'class'=>'', 'icon'=>'', 'controller'=>'user', 'action'=>'user/year-highest-emp-lv-export', 'routePath'=>'user/user/year-highest-emp-lv-export', 'show'=>0,],
             //['name'=>'修改会员前台显示聘级', 'class'=>'', 'icon'=>'', 'controller'=>'user', 'action'=>'change-show-emp-level', 'routePath'=>'user/change-show-emp-level', 'show'=>1,],
+            ['name'=>'会员最高聘级调整', 'class'=>'', 'icon'=>'', 'controller'=>'user', 'action'=>'change-user-dec-level', 'routePath'=>'user/change-highest-emp-level-list', 'show'=>1,],
+            ['name'=>'修改会员最高聘级', 'class'=>'', 'icon'=>'', 'controller'=>'user', 'action'=>'change-user-dec-level', 'routePath'=>'user/change-highest-emp-level', 'show'=>0,],
         ]
     ],
     'atlas'=>[

+ 2 - 0
backendApi/config/urlManagerRules.php

@@ -170,6 +170,8 @@ return [
             'GET status-audit-export' => 'status-audit-export',
             'GET year-highest-emp-lv-export' => 'year-highest-emp-lv-export',
             'POST recharge-to-user' => 'recharge-to-user',
+            'GET change-highest-emp-level-list' => 'change-highest-emp-level-list',
+            'POST change-highest-emp-level' => 'change-highest-emp-level',
         ],
     ],
     [

+ 7 - 0
backendApi/modules/v1/controllers/ShopController.php

@@ -368,9 +368,14 @@ class ShopController extends BaseController {
             'PERIOD_NUM'=> 'O.PERIOD_NUM',
             'CREATED_AT'=> 'O.CREATED_AT',
         ]);
+//        $condition = ' 1=1 ' . $filter['condition'];
+//        $params = $filter['params'];
+//        $condition .= $condition ? ' AND O.IS_DELETE=0' : ' O.IS_DELETE=0';
+
         $condition = $filter['condition'];
         $params = $filter['params'];
         $condition .= ' AND O.IS_DELETE=0';
+
         $listObj = new OrderList();
         $data = $listObj->getList(['condition'=>$condition, 'params'=>$params]);
         return static::notice($data);
@@ -403,7 +408,9 @@ class ShopController extends BaseController {
             'PERIOD_NUM'=> 'O.PERIOD_NUM',
             'CREATED_AT'=> 'O.CREATED_AT',
         ]);
+//        $filter['condition'] = !$filter['condition'] ? '1=1 AND O.IS_DELETE=0' : ('O.IS_DELETE=0 ' . $filter['condition']);
         $filter['condition'] .= ' AND O.IS_DELETE=0';
+
         $form = new ShopExportForm();
         $result = $form->run($filter, '订单列表');
         if (!$result) {

+ 38 - 0
backendApi/modules/v1/controllers/UserController.php

@@ -12,6 +12,8 @@ use backendApi\modules\v1\components\UserAuth;
 use backendApi\modules\v1\models\Admin;
 use backendApi\modules\v1\models\exportForms\UserExportForm;
 use backendApi\modules\v1\models\lists\user\BindList;
+use backendApi\modules\v1\models\lists\user\ChangeEmpLevelList;
+use backendApi\modules\v1\models\lists\user\ChangeHighestEmpLevelList;
 use backendApi\modules\v1\models\lists\user\DecLevelList;
 use backendApi\modules\v1\models\lists\user\GroupList;
 use backendApi\modules\v1\models\lists\user\IndexList;
@@ -37,6 +39,7 @@ use common\models\forms\CloseDecForm;
 use common\models\forms\CloseLoginForm;
 use common\models\forms\DecLevelLogForm;
 use common\models\forms\DecRoleLogForm;
+use common\models\forms\HighestEmpLevelLogForm;
 use common\models\forms\NetMoveForm;
 use common\models\forms\RechargeForm;
 use common\models\forms\ReconsumeForm;
@@ -2229,4 +2232,39 @@ class UserController extends BaseController
         }
         return static::notice(Form::formatErrorsForApi($formModel->getErrors()), 400);
     }
+
+    /**
+     * 会员最高聘级变动记录.
+     * @return mixed
+     * @throws \yii\base\Exception
+     * @throws \yii\web\HttpException
+     */
+    public function actionChangeHighestEmpLevelList()
+    {
+        $filter = $this->filterCondition([
+            'USER_NAME' => 'CU.USER_NAME',
+            'CREATED_AT' => 'LL.CREATED_AT',
+            'ADMIN_NAME' => 'ADM.ADMIN_NAME',
+        ]);
+        $condition = $filter['condition'];
+        $params = $filter['params'];
+        $listObj = new ChangeHighestEmpLevelList();
+        $data = $listObj->getList(['condition' => $condition, 'params' => $params]);
+
+        return static::notice($data);
+    }
+    /**
+     * 调整会员最高聘级.
+     * @return mixed
+     * @throws \yii\web\HttpException
+     */
+    public function actionChangeHighestEmpLevel()
+    {
+        if (Yii::$app->request->isPost) {
+            return parent::edit(HighestEmpLevelLogForm::class, '修改会员最高聘级成功', 'adminChange', ['adminChange',], null, function ($form, $result) {
+
+            });
+        }
+        return static::notice('无效请求', 400);
+    }
 }

+ 1 - 0
backendApi/modules/v1/models/lists/log/AdminHandleList.php

@@ -156,6 +156,7 @@ class AdminHandleList extends \common\libs\dataList\DataList implements DataList
                 ['id'=>'编辑体系','name'=>'编辑体系'],
                 ['id'=>'恢复体系','name'=>'恢复体系'],
                 ['id'=>'调整业绩','name'=>'调整业绩'],
+                ['id'=>'调整会员最高聘级','name'=>'调整会员最高聘级'],
             ];
             $this->filterTypes = [
                 'opt_type' => ['isUserTable'=>false, 'name'=>'操作类型', 'other'=> 'select', 'selectData'=> $selectData],

+ 7 - 7
backendApi/modules/v1/models/lists/shop/GoodsList.php

@@ -47,13 +47,13 @@ class GoodsList extends \common\libs\dataList\DataList implements DataListInterf
                     'header' => '商品编号',
                     'headerOther' => ['width' => '200'],
                 ],
-                // 'TYPE' => [
-                //     'header' => '商品来源',
-                //     'value' => function($row){
-                //         return ShopGoods::GOODS_TYPE[$row['TYPE']]['name'];
-                //     },
-                //     'headerOther' => ['width' => '150'],
-                // ],
+                'CATE_ID' => [
+                    'header' => '商品来源',
+                    'value' => function($row){
+                        return ShopGoods::GOODS_TYPE[$row['CATE_ID']]['name'];
+                    },
+                    'headerOther' => ['width' => '150'],
+                ],
                 'SELL_DISCOUNT' => [
                     'header' => '商品折扣',
                     'value' => function($row) {

+ 58 - 1
backendApi/modules/v1/models/lists/shop/OrderList.php

@@ -5,10 +5,15 @@ use common\helpers\Cache;
 use common\helpers\Tool;
 use common\helpers\user\Info;
 use common\libs\dataList\DataListInterface;
+use common\models\ApproachOrder;
+use common\models\ApproachOrderGoods;
 use common\models\Order;
 use common\models\OrderGoods;
+use common\models\ShopGoods;
 use common\models\User;
 use common\libs\dataList\column\DateTime;
+use yii\data\Pagination;
+use yii\db\Query;
 
 class OrderList extends \common\libs\dataList\DataList implements DataListInterface
 {
@@ -44,6 +49,53 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
         }
     }
 
+    /**
+     * 列表筛选到的数据
+     */
+//    public function dataHandle()
+//    {
+//        $orderQuery = Order::find()
+//            ->alias('O')
+//            ->where($this->condition, $this->params)
+//            ->select('O.*,U.REAL_NAME,U.DEC_ID,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+//            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+//            ->join('LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+//            ->orderBy('O.CREATED_AT DESC');
+//
+//        // 订单中间表只查询待支付和支付失败的订单
+//        $this->params[':NOT_PAID'] = \Yii::$app->params['orderStatus']['notPaid']['value'];   // 待支付
+//        $this->params[':FAIL_PAID'] = \Yii::$app->params['orderStatus']['failPaid']['value'];   // 支付失败
+//        $orderStandardQuery = ApproachOrder::find()
+//            ->alias('O')
+//            ->where($this->condition . ' AND (O.STATUS = :NOT_PAID OR O.STATUS = :FAIL_PAID)', $this->params)
+//            ->select('O.*,U.REAL_NAME,U.DEC_ID,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+//            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+//            ->join('LEFT JOIN', ApproachOrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+//            ->orderBy('O.CREATED_AT DESC');
+//
+//        $queryAll = $orderQuery->union($orderStandardQuery, true);
+//        $query = (new Query())->from(['Q' => $queryAll])->select('Q.*')->distinct()->orderBy(['CREATED_AT' => SORT_DESC]);
+//
+//        $totalCount = $query->count();
+//        $pagination = new Pagination(['totalCount' => $totalCount, 'pageSize' => \Yii::$app->request->get('pageSize')]);
+//        $lists = $query->offset($pagination->offset)->limit($pagination->limit)->all();
+//
+//        $this->listData = [
+//            'list' => $lists,
+//            'currentPage'=>$pagination->page,
+//            'totalPages'=>$pagination->pageCount,
+//            'startNum' => $pagination->page * $pagination->pageSize + 1,
+//            'totalCount' => $pagination->totalCount,
+//            'pageSize' => $pagination->pageSize,
+//        ];
+//
+//        foreach ($this->listData['list'] as $key => $value) {
+//            $CREATE_USER_ID = Info::getUserIdByUserName($value['CREATE_USER']);
+//            $this->listData['list'][$key]['DEC_USER_NAME'] = Info::getUserNameByUserId($value['DEC_ID']);
+//            $this->listData['list'][$key]['CREATE_USER_NAME'] = Info::getUserRealNameByUserId($CREATE_USER_ID);
+//        }
+//    }
+
     /**
      * 要展示和导出的所有字段
      * @return array
@@ -53,7 +105,6 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
         if(!$this->columns){
             $this->columns = [
                 'ID' => null,
-                'SN' => null,
                 'DEC_SN' => null,
                 'USER_NAME' => [
                     'header' => '会员编号',
@@ -183,6 +234,8 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
                                 return '充值账户';
                             case 'exchange':
                                 return '兑换点数';
+                            case 'online':
+                                return '在线支付';
                             default:
                                 return '复消积分';
                         }
@@ -221,6 +274,10 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
                     },
                     'headerOther' => ['width' => '190'],
                 ],
+                'SEND_AT' => [
+                    'header' => '推送时间',
+                    'headerOther' => ['width' => '190'],
+                ],
                 'DELIVERY_AT' => [
                     'header' => '发货时间',
                     'value' => function ($row) {

+ 102 - 0
backendApi/modules/v1/models/lists/user/ChangeHighestEmpLevelList.php

@@ -0,0 +1,102 @@
+<?php
+namespace backendApi\modules\v1\models\lists\user;
+
+use backendApi\modules\v1\models\Admin;
+use common\helpers\Cache;
+use common\libs\dataList\DataListInterface;
+use common\models\HighestEmpLevelLog;
+use common\models\User;
+use common\libs\dataList\column\DateTime;
+use Yii;
+
+class ChangeHighestEmpLevelList extends \common\libs\dataList\DataList implements DataListInterface
+{
+    /**
+     * 列表名称
+     * @return string
+     */
+    public function getListName(){
+        return '会员最高聘级调整列表';
+    }
+
+    /**
+     * 列表筛选到的数据
+     */
+    public function dataHandle()
+    {
+        $this->listData = HighestEmpLevelLog::lists($this->condition, $this->params, [
+            'select' => 'LL.*, CU.USER_NAME, ADM.ADMIN_NAME',
+            'orderBy' => 'LL.CREATED_AT DESC, LL.ID DESC',
+            'from' => HighestEmpLevelLog::tableName() . ' AS LL',
+            'join' => [
+                ['LEFT JOIN', Admin::tableName() . ' AS ADM', 'LL.ADMIN_ID=ADM.ID'],
+                ['LEFT JOIN', User::tableName() . ' AS CU', 'LL.USER_ID=CU.ID'],
+            ],
+            'page' => $this->page,
+            'pageSize' => $this->pageSize,
+        ]);
+    }
+
+    /**
+     * 要展示和导出的所有字段
+     * @return array
+     */
+    public function getColumn() {
+        $empLevelConfig = Cache::getEmpLevelConfig();
+        if (!$this->columns) {
+            $this->columns = [
+                'ID' => null,
+                'USER_ID' => null,
+                'USER_NAME' => [
+                    'header' => '会员编号',
+                ],
+                'FROM_HIGHEST_EMP_LV_NAME' => [
+                    'header' => '调整前聘级',
+                    'value' => function($row) use($empLevelConfig) {
+                        return $empLevelConfig[$row['FROM_ID']]['LEVEL_NAME'] ?? '';
+                    },
+                    'valueOther' => [
+                        'tag'=>['type'=>'warning', 'size' => 'small', 'class'=>'no-border']
+                    ],
+                ],
+                'TO_HIGHEST_EMP_LV_NAME' => [
+                    'header' => '调整后聘级',
+                    'value' => function($row) use($empLevelConfig) {
+                        return $empLevelConfig[$row['TO_ID']]['LEVEL_NAME'] ?? '';
+                    },
+                    'valueOther' => [
+                        'tag'=>['type'=>'warning', 'size' => 'small', 'class'=>'no-border']
+                    ],
+                ],
+                'ADMIN_NAME' => [
+                    'header' => '操作管理员',
+                ],
+                'CREATED_AT' => [
+                    'header' => '创建时间',
+                    'value' => function($row) {
+                        return (new DateTime([
+                            'value' => $row['CREATED_AT'],
+                        ]))->result();
+                    },
+                ],
+            ];
+        }
+        return $this->columns;
+    }
+
+    /**
+     * 前台用于筛选的类型集合
+     * @return mixed
+     */
+    public function getFilterTypes()
+    {
+        if(!$this->filterTypes){
+            $this->filterTypes = [
+                'USER_NAME'=> ['isUserTable' => false,'name'=> '会员编号'],
+                'CREATED_AT' => ['isUserTable' => false, 'name' => '创建时间', 'other' => 'date'],
+                'ADMIN_NAME' => ['isUserTable' => false, 'name' => '操作管理员'],
+            ];
+        }
+        return $this->filterTypes;
+    }
+}

+ 1 - 1
backendEle/package.json

@@ -22,7 +22,6 @@
     "better-scroll": "^1.14.1",
     "countup.js": "^1.9.3",
     "echarts": "^4.1.0",
-    "element-ui": "2.9.1",
     "http": "0.0.1-security",
     "js": "^0.1.0",
     "nix-tinymce": "^1.0.7",
@@ -55,6 +54,7 @@
     "copy-webpack-plugin": "^4.0.1",
     "cross-spawn": "^5.0.1",
     "css-loader": "^0.28.0",
+    "element-ui": "^2.13.0",
     "eslint": "^4.19.1",
     "eslint-config-standard": "^10.2.1",
     "eslint-friendly-formatter": "^3.0.0",

+ 13 - 1
backendEle/src/router/index.js

@@ -531,7 +531,19 @@ export const constantRouterMap = [
             {title: '会员管理', path: '/user/index'}
           ]
         }
-      }
+      },
+      {
+        path: '/user/change-highest-emp-level-list',
+        component: _import('user/change-highest-emp-level-list'),
+        name: 'change-highest-emp-level-list',
+        meta: {
+          title: '会员最高聘级调整',
+          breadcrumb: [
+            {title: '首页', path: '/dashboard/index'},
+            {title: '会员管理', path: '/user/index'}
+          ]
+        }
+      },
     ]
   },
   {

+ 5 - 9
backendEle/src/views/shop/goods-add.vue

@@ -5,12 +5,11 @@
         <el-form-item label="商品名称">
           <el-input v-model="form.goodsName"></el-input>
         </el-form-item>
-      <!-- <el-form-item label="商品来源">
-          <el-select v-model="form.type" placeholder="请选择商品来源">
-              <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name"
-                         :value="index"></el-option>
-          </el-select>
-      </el-form-item> -->
+        <el-form-item label="商品来源">
+            <el-select v-model="form.type" placeholder="请选择商品来源">
+                <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name" :value="index"></el-option>
+            </el-select>
+        </el-form-item>
         <el-form-item label="会员折扣">
             <el-input v-model="form.sellDiscount"></el-input>
         </el-form-item>
@@ -189,10 +188,7 @@ export default {
       })
     },
     getData (page, pageSize) {
-      let filterData = this.filterModel
-      let vueObj = this
       network.getPageData(this, 'shop/goods-add', page, pageSize, this.filterModel, response => {
-        console.log(response)
         this.goodsType = response.goodsType
         this.GiftTypeArr = response.giftType
         this.sellType = response.sellType

+ 4 - 5
backendEle/src/views/shop/index.vue

@@ -70,12 +70,11 @@
                     <el-form-item label="商品名称">
                         <el-input v-model="form.goodsName"></el-input>
                     </el-form-item>
-                    <!-- <el-form-item label="商品来源">
+                    <el-form-item label="商品来源">
                         <el-select v-model="form.type">
-                            <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name"
-                                       :value="index"></el-option>
+                            <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name" :value="index"></el-option>
                         </el-select>
-                    </el-form-item> -->
+                    </el-form-item>
                     <el-form-item label="会员折扣">
                         <el-input v-model="form.sellDiscount"></el-input>
                     </el-form-item>
@@ -353,7 +352,7 @@ export default {
         vueObj.form.goodsName = response.goodsInfo.GOODS_NAME
         vueObj.form.sellDiscount = response.goodsInfo.SELL_DISCOUNT
         vueObj.form.goodsNo = response.goodsInfo.GOODS_NO
-        vueObj.form.type = response.goodsInfo.TYPE
+        vueObj.form.type = response.goodsInfo.CATE_ID
         vueObj.form.unit = response.goodsInfo.UNIT
         vueObj.form.marketPrice = response.goodsInfo.MARKET_PRICE
         vueObj.form.sellPrice = response.goodsInfo.SELL_PRICE

+ 126 - 9
backendEle/src/views/shop/order-list.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-loading="loading">
+  <div v-loading="loading" class="orderList">
     <div class="white-box">
       <div class="filter-box">
         <filter-user :filter-types="filterTypes" @select-value="handleFilterUser"></filter-user>
@@ -7,6 +7,7 @@
       <el-table ref="multipleTable" :data="tableData" style="width: 100%;"
                 :row-class-name="rowClassName"
                 @selection-change="handleSelectionChange"
+                @cell-click="openLogisticsQuery"
                 :height="tool.getTableHeight()">
         <el-table-column type="selection" width="70" v-if="tableHeaders"></el-table-column>
         <el-table-column v-for="(tableHeader, key) in tableHeaders" :key="key" :label="tableHeader.header" :width="tableHeader.other.width ? tableHeader.other.width : ''" :prop="tableHeader.other.prop ? tableHeader.other.prop : null">
@@ -55,24 +56,56 @@
         <el-button type="primary" @click.native="handleDelivery">发货</el-button>
       </div>
     </el-dialog>
+
+    <el-dialog
+      :width="dialogWidth"
+      custom-class="logisticsQueryDialog"
+      title="物流动态"
+      :visible.sync="logisticsQueryDialog"
+      :before-close="logisticsQueryDialogHandleClose">
+      <div class="diaBody" v-loading="logisticsQueryLoading">
+        <el-tabs style="width: 100%!important" type="border-card" v-if="logisticsQueryList.length > 0">
+          <el-tab-pane  v-for="( item, index ) in logisticsQueryList" :key="index" :label="item.name">
+            <div class="block">
+              <el-timeline :reverse="reverse">
+                <el-timeline-item
+                  v-for="(activity, index2) in item.children"
+                  :key="index2"
+                  :icon="activity.icon"
+                  :color="activity.color"
+                  :size="activity.size"
+                  :timestamp="activity.date_action">
+                  <span class="timelineStatus">{{activity.order_status}}</span>
+                </el-timeline-item>
+              </el-timeline>
+            </div>
+
+          </el-tab-pane>
+        </el-tabs>
+        <el-empty v-if="logisticsQueryEmpty" :image-size="200"></el-empty>
+
+        <!--        <span slot="footer" class="dialog-footer">-->
+        <!--          <el-button @click="dialogVisible = false">取 消</el-button>-->
+        <!--        <el-button type="primary" @click="logisticsQueryDialog = false">确 定</el-button>-->
+        <!--      </span>-->
+      </div>
+
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import network from '@/utils/network'
-import tool from '@/utils/tool'
-import baseInfo from '@/utils/baseInfo'
 import FilterUser from '@/components/FilterUser'
-import permission from '@/utils/permission'
 import Pagination from '@/components/Pagination'
 import filterHelper from '@/utils/filterHelper'
+import network from '@/utils/network'
+import permission from '@/utils/permission'
+import tool from '@/utils/tool'
+import axios from 'axios'
 
 export default {
   name: 'shop_order-list',
   components: {FilterUser, Pagination},
-  mounted () {
-    this.getData()
-  },
   data () {
     return {
       tableHeaders: null,
@@ -94,9 +127,87 @@ export default {
         expressCompany: '',
         orderTrackNo: '',
       },
+      logisticsQueryDialog: false,
+      logisticsQueryList: [],
+      logisticsQueryLoading: false,
+      reverse: true,
+      logisticsQueryEmpty: false,
+      dialogWidth:0,
+      dialogTop:0
+    }
+  },
+  created() {
+    this.setDialogWidth()
+  },
+  mounted() {
+    this.getData()
+    window.onresize = () => {
+      return (() => {
+        this.setDialogWidth()
+      })()
     }
   },
   methods: {
+    setDialogWidth() {
+      var val = document.body.clientWidth
+      const def = 800 // 默认宽度
+      if (val > def) {
+        this.dialogWidth = '60%'
+        this.dialogTop = '15vh'
+      } else {
+        this.dialogWidth = '90%'
+        this.dialogTop = '1vh'
+      }
+    },
+    openLogisticsQuery(row, column, cell, event){
+      this.logisticsQueryEmpty = false
+      
+      if(column.label == "订单号"){
+        console.log(row.SEND_AT.value , row.STATUS, row.PAY_TYPE.value )
+        if((row.SEND_AT.value > 0 && row.STATUS == '1' && row.PAY_TYPE.value === '在线支付') === false ) return
+        let id = row.SN.value
+        this.logisticsQueryList = []
+        this.logisticsQueryDialog = true
+        this.logisticsQueryLoading = true
+        this.queryData(id).then(res => {
+          const data = res.data.data
+          let keysList = Object.keys(data)
+          let list = []
+          keysList.forEach((key, index) => {
+            let oneList = data[key]
+            oneList[ oneList.length - 1 ].color = '#0bbd87'
+            oneList[ oneList.length - 1 ].size = 'large'
+            // oneList[ oneList.length - 1 ].icon = 'el-icon-s-promotion'
+            let one = {
+              'name': key,
+              'children': oneList
+            }
+            list.push(one)
+          })
+          console.log(list)
+          this.logisticsQueryList = list
+          this.logisticsQueryLoading = false
+        }).catch((error) =>{
+          console.log(error);
+          this.logisticsQueryList = []
+          this.logisticsQueryEmpty = true
+          this.logisticsQueryLoading = false
+        })
+      }
+    },
+    openLogisticsQuery1 (data) {
+
+    },
+    async queryData (id) {
+      const link = 'https://taoplus.com.my/index.php?route=information/order_tracking/jsons&order_tracking_search=' + id
+      const ret = await axios.get(link)// await 后面也可以跟的是Promise实例对象
+      // console.log(ret)
+      return ret
+    },
+    logisticsQueryDialogHandleClose (done) {
+      this.logisticsQueryDialog = false
+    },
+
     rowClassName (val) {
       if (val.row) {
         let index = this.multipleSelection.findIndex(item => {
@@ -199,8 +310,14 @@ export default {
 </script>
 
 <style scoped>
-/deep/.el-table .selected-row {
+::v-deep.el-table .selected-row {
   background: #bce5ef;
   /*color: white;*/
 }
+.timelineStatus{
+  color: #409EFF;
+}
+.diaBody{
+  min-height: 20vh;
+}
 </style>

+ 201 - 0
backendEle/src/views/user/change-highest-emp-level-list.vue

@@ -0,0 +1,201 @@
+<template>
+  <div v-loading="loading">
+    <div class="white-box">
+      <div class="filter-box">
+        <filter-user ref="filterUser" :filter-types="filterTypes" @select-value="handleFilterUser"></filter-user>
+      </div>
+
+      <el-table :data="tableData" stripe style="width: 100%;" :height="tool.getTableHeight(true)">
+        <el-table-column v-for="(tableHeader, key) in tableHeaders" :key="key" :label="tableHeader.header" :width="tableHeader.other.width ? tableHeader.other.width : ''">
+          <template slot-scope="{row}">
+            <template v-if="row[tableHeader.index].other.tag" >
+              <el-tag :type="row[tableHeader.index].other.tag.type ? row[tableHeader.index].other.tag.type : null" :size="row[tableHeader.index].other.tag.size ? row[tableHeader.index].other.tag.size : null" :class="row[tableHeader.index].other.tag.class ? row[tableHeader.index].other.tag.class : null" >{{ row[tableHeader.index].value }}</el-tag>
+            </template>
+            <template v-else>
+              <div v-html="row[tableHeader.index].value"></div>
+            </template>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="white-box-footer">
+        <el-button type="primary" size="small" @click="dialog = true" icon="el-icon-plus" v-show="permission.hasPermission(`user/change-user-dec-level`)">调整最高聘级</el-button>
+
+        <pagination :total="totalCount" :page_size="pageSize" @size-change="handleSizeChange" @current-change="handleCurrentChange"></pagination>
+      </div>
+    </div>
+
+    <el-dialog title="调整最高聘级" :visible.sync="dialog" width="450px" :before-close="handleClose">
+      <el-form ref="form" :model="form" label-width="100px" class="form-dialog" size="mini">
+        <el-form-item label="会员编号" required>
+          <el-input placeholder="请输入会员编号" v-model.trim="form.userName" class="input-with-select">
+            <el-button slot="append" icon="el-icon-search" @click="handleQuery">查询</el-button>
+          </el-input>
+
+          <el-tag v-show="userInfo.REAL_NAME !== null" style="margin-top: 5px;">
+            会员姓名:{{ userInfo.REAL_NAME }}
+          </el-tag>
+          <el-tag v-show="userInfo.REAL_NAME !== null" v-for="(item,key) in empLevels" :label="item.LEVEL_NAME" :value="item.ID" :key="key" v-if="key === userInfo.EMP_LV" style="margin-top: 5px;">
+            最高聘级:{{item.LEVEL_NAME}}
+          </el-tag>
+        </el-form-item>
+        <el-form-item label="调整后聘级" required>
+          <el-select v-model="form.levelId" placeholder="请选择调整后聘级">
+            <el-option v-for="(item,key) in empLevels" :label="item.LEVEL_NAME" :value="item.ID" :key="key" :disabled="key === userInfo.EMP_LV"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input type="textarea" :rows="2" placeholder="" v-model="form.remark">
+          </el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button size="mini" type="primary" @click="onSubmit" :loading="submitButtonStat">保 存</el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import network from '@/utils/network'
+  import tool from '@/utils/tool'
+  import FilterUser from '@/components/FilterUser'
+  import baseInfo from '@/utils/baseInfo'
+  import permission from '@/utils/permission'
+  import Pagination from '@/components/Pagination'
+  import filterHelper from '@/utils/filterHelper'
+
+  export default {
+    name: 'change-highest-emp-level-list',
+    components: {FilterUser,Pagination},
+    mounted() {
+      this.getData()
+    },
+    data() {
+      return {
+        tableHeaders: null,
+        tableData: null,
+        loading: true,
+        currentPage: 1,
+        totalPages: 1,
+        totalCount: 1,
+        pageSize: 20,
+        tool: tool,
+        permission: permission,
+        empLevels: baseInfo.empLevels(),
+        filterTypes: {},
+        filterModel: {},
+        filterStatus: '0',
+        dialog: false,
+        form: {
+          userName: null,
+          periodNum: null,
+          levelId: null,
+          remark: null
+        },
+        disabled: 0,
+        submitButtonStat: false,
+        userInfo: {
+          REAL_NAME: null,
+          EMP_LV: null
+        },
+      }
+    },
+    methods: {
+      handleCurrentChange(page) {
+        this.getData(page, this.pageSize)
+      },
+      handleSizeChange(pageSize) {
+        this.getData(this.currentPage, pageSize)
+      },
+      handleFilterUser(filterData) {
+        filterHelper.handleFilterUser(this, filterData)
+      },
+      getData(page, pageSize) {
+        let filterData = this.filterModel
+        let vueObj = this
+        filterData.filterStatus = this.filterStatus != -1 ? `=,${this.filterStatus}` : ''
+        network.getPageData(this, 'user/change-highest-emp-level-list', page, pageSize, filterData, function (response) {
+            vueObj.allData = response
+            vueObj.filterTypes = response.filterTypes
+        })
+      },
+      handleClose() {
+        this._clearData()
+        this.dialog = false
+      },
+      handleQuery() {
+        if (!this.form.userName.length) {
+          this.$message({
+            message: '请输入会员编号',
+            type: 'warning'
+          })
+          return false
+        }
+        network.getData('user/full-info', { userName: this.form.userName }).then(response => {
+          this.userInfo = response
+        })
+      },
+      onSubmit() {
+        if (!this.form.userName.length) {
+          this.$message({
+            message: '请输入会员编号',
+            type: 'warning'
+          })
+          return false
+        }
+        if (!this.form.levelId) {
+          this.$message({
+            message: '请选择调整后的最高聘级',
+            type: 'warning'
+          })
+          return false
+        }
+
+        let thisObj = this
+        thisObj.$confirm('是否要调整会员的最高聘级?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          thisObj._handleSubmit()
+        }).catch(() => {
+          this._clearData()
+        })
+      },
+      _handleSubmit() {
+        this.submitButtonStat = true
+        network.postData('user/change-highest-emp-level', this.form).then(response => {
+          this.$message({
+            message: response,
+            type: 'success'
+          })
+          this.submitButtonStat = false
+          this.dialog = false
+          this._clearData()
+          this.getData()
+        }).catch(() => {
+          this.submitButtonStat = false
+        })
+      },
+      _clearData() {
+        this.form = {
+          userName: null,
+          periodNum: null,
+          levelId: null,
+          remark: null
+        }
+        this.userInfo = {
+          REAL_NAME: null,
+          EMP_LV: null
+        }
+      }
+    }
+  }
+</script>
+
+<style scoped>
+.form-dialog .el-form-item > .el-form-item__content > .el-input, .form-dialog .el-form-item > .el-form-item__content > .el-cascader, .form-dialog .el-form-item > .el-form-item__content > .el-select, .form-dialog .el-form-item > .el-form-item__content > .el-textarea, .form-dialog .el-form-item > .el-form-item__content > .el-slider {
+  width: 300px !important;
+}
+</style>

+ 24 - 11
common/config/main.php

@@ -10,17 +10,17 @@ return [
     ],
     'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
     'components' => [
-//        'log' => [
-//            'targets' => [
-//                [
-//                    'class' => 'yii\log\FileTarget',
-//                    'levels' => ['error', 'warning', 'info'],
-//                    'categories' => ['yii\db\*', 'app\models\*'],
-//                    'logFile' => '@runtime/logs/sql.log',
-//                ],
-//                'db' => [ 'class' => 'yii\log\FileTarget'],
-//            ],
-//        ],
+        'log' => [
+            'targets' => [
+                [
+                    'class' => 'yii\log\FileTarget',
+                    'levels' => ['error', 'warning', 'info'],
+                    'categories' => ['yii\db\*', 'app\models\*'],
+                    'logFile' => '@runtime/logs/sql.log',
+                ],
+                'db' => [ 'class' => 'yii\log\FileTarget'],
+            ],
+        ],
         'db' => array_merge([
             'class' => 'yii\db\Connection',
             'enableSlaves' => false,
@@ -60,6 +60,19 @@ return [
         'swooleAsyncTimer' => [
             'class' => 'common\components\SwooleAsyncTimer',
         ],
+        // iPay88配置
+        'iPay88' => [
+            'class' => 'frontendApi\modules\v1\components\IPay88',
+            'merchantCode' => 'M08669', // 'M08669_S0002',  // M08669(prod)
+            'merchantKey' => 'SiYF6a0QYy', // '5Mb154IrY8',   // SiYF6a0QYy(prod)
+            'currencyCode' => 'MYR', //'CNY', // MYR(马来币) CNY(人民币)
+            'responseUrl' => 'http://fele.ncshop.elken.com/#/shop/order-list', //'https://www.ncshop2023.com/#/shop/order-list', // TODO: https://nc-fele-mips.elken.com:8015/#/shop/order-list(test)
+            'backendUrl' => 'http://fapi.ncshop.elken.com/v1/shop/verify-approach-order', //'https://fapi.ekhkad.com/v1/shop/verify-approach-order',  // https://nc-fapi-mips.elken.com:8013/v1/shop/verify-approach-order(test)
+            'requeryUrl' => 'https://payment.ipay88.com.my/epayment/enquiry.asp',
+            'paymentUrl' => 'https://payment.ipay88.com.my/epayment/entry.asp',
+            'recurringUrlSubscription' => 'https://www.ipay88.com/recurringpayment/webservice/RecurringPayment.asmx/Subscription',
+            'recurringUrlTermination' => 'https://www.ipay88.com/recurringpayment/webservice/RecurringPayment.asmx/Termination'
+        ],
     ],
     'controllerMap' => [
         'swoole_server' => [

+ 159 - 0
common/helpers/IPay88.php

@@ -0,0 +1,159 @@
+<?php
+
+namespace common\helpers;
+
+class IPay88
+{
+    /**
+     * 根据运行环境加载secretKey
+     */
+    public static function getSecretKey(): string
+    {
+         return YII_ENV == YII_ENV_PROD ? \Yii::$app->iPay88->liveSecretKey : \Yii::$app->iPay88->testSecretKey;
+//       return \Yii::$app->iPay88->testSecretKey;
+    }
+
+    /**
+     * 支付.
+     */
+    public static function transactionInit($currency, $amount, $email)
+    {
+        $secretKey = self::getSecretKey();
+
+        try {
+            $url = "https://api.paystack.co/transaction/initialize";
+            $fields = [
+                'email' => $email,
+                'amount' => $amount * 100
+            ];
+            $fields_string = http_build_query($fields);
+            //open connection
+            $curl = curl_init();
+
+            //set the url, number of POST vars, POST data
+            curl_setopt($curl,CURLOPT_URL, $url);
+            curl_setopt($curl,CURLOPT_POST, true);
+            curl_setopt($curl,CURLOPT_POSTFIELDS, $fields_string);
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                "Authorization: Bearer {$secretKey}",
+                "Cache-Control: no-cache",
+            ));
+            //So that curl_exec returns the contents of the cURL; rather than echoing it
+            curl_setopt($curl,CURLOPT_RETURNTRANSFER, true);
+            //execute post
+            $result = curl_exec($curl);
+            $err = curl_error($curl);
+            curl_close($curl);
+            if ($err) {
+                return [
+                    'status'    => false,
+                    'message'   => $err,
+                ];
+            }
+
+            return json_decode($result, true);
+        } catch (\Exception $e) {
+            return [
+                'status'    => false,
+                'message'   => $e->getMessage(),
+            ];
+        }
+    }
+    /**
+     * 交易结果校验.
+     * @param $ref
+     * @return array
+     */
+    public static function transactionVerify($ref): array
+    {
+        $secretKey = self::getSecretKey();
+
+        try {
+            $curl = curl_init();
+            curl_setopt_array($curl, [
+                CURLOPT_URL => "https://api.paystack.co/transaction/verify/{$ref}",
+                CURLOPT_RETURNTRANSFER => true,
+                CURLOPT_ENCODING => "",
+                CURLOPT_MAXREDIRS => 10,
+                CURLOPT_TIMEOUT => 30,
+                CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+                CURLOPT_CUSTOMREQUEST => "GET",
+                CURLOPT_HTTPHEADER => array(
+                    "Authorization: Bearer {$secretKey}",
+                    "Cache-Control: no-cache",
+                ),
+            ]);
+
+            $response = curl_exec($curl);
+            $err = curl_error($curl);
+            curl_close($curl);
+
+            if ($err) {
+                return [
+                    'status'    => false,
+                    'message'   => $err,
+                ];
+            }
+
+            return json_decode($response, true);
+        } catch (\Exception $e) {
+            return [
+                'status'    => false,
+                'message'   => $e->getMessage(),
+            ];
+        }
+    }
+
+    /**
+     * 交易退款.
+     * @param string $reference
+     * @param int $amount
+     * @return array
+     */
+    public static function transactionRefund(string $reference, int $amount)
+    {
+        $secretKey = self::getSecretKey();
+
+        try {
+            $url = "https://api.paystack.co/refund";
+            $fields = [
+                'transaction' => $reference,
+                'amount'      => $amount,
+            ];
+            $fields_string = http_build_query($fields);
+            //open connection
+            $curl = curl_init();
+
+            //set the url, number of POST vars, POST data
+            curl_setopt($curl, CURLOPT_URL, $url);
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $fields_string);
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                "Authorization: Bearer {$secretKey}",
+                "Cache-Control: no-cache",
+            ));
+
+            //So that curl_exec returns the contents of the cURL; rather than echoing it
+            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+
+            //execute post
+            $response = curl_exec($curl);
+            $err = curl_error($curl);
+            curl_close($curl);
+
+            if ($err) {
+                return [
+                    'status'    => false,
+                    'message'   => $err,
+                ];
+            }
+
+            return json_decode($response, true);
+        } catch (\Exception $e) {
+            return [
+                'status'    => false,
+                'message'   => $e->getMessage(),
+            ];
+        }
+    }
+}

+ 379 - 0
common/helpers/Logistics.php

@@ -0,0 +1,379 @@
+<?php
+
+namespace common\helpers;
+
+use common\models\OrderGoods;
+use common\models\Region;
+use Yii;
+
+class Logistics
+{
+    const prodDomain = 'https//warehouse.taoplus.com.my';
+    const testDomain = 'http://warehouse.worldsyntech.com';
+
+    // 1.获取bearer token,此token是其他api调用时的必传参数.
+    const authenticationUrl = 'http://warehouse.taoplus.com.my/index.php?route=rest/admin_security/api_login&grant_type=client_credentials';
+    // 2.创建订单和产品(以前不存在/已提交的产品).
+    const createOrderUrl = 'http://warehouse.taoplus.com.my/index.php?route=rest/warehouse_api/add_order';
+    // 3.获取产品/包裹的重量和状态.
+    const getProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/get_order_product';
+    // 4.获取订单重量,m3,包装的包裹数量,费用跟踪号码和状态.
+    const getOrderUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/get_order';
+    // 5.通知仓库已经付款,包裹可以投递.
+    const notifyDeliveryUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/ warehouse_api/notify_delivery';
+    // 6.创建产品/包裹.
+    const addProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/add_order_product';
+    // 7.创建交付订单并将现有产品绑定到订单.需要先调用接口6创建商品.
+    const createOrderSimpleUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/add_order_simple';
+    // 8.计算订单费用,并将订单状态改为待付款.
+    const stockOutUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/stock_out';
+    // 9.通知物流系统订单不可删除.error包含错误的订单ID.
+    const notifyOrderUndeletableUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/notify_order_undeletable';
+    // 10.取消订单产品/包裹。它将状态更改为无效,并保留订单产品/包裹.
+    const cancelOrderProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/cancel_order_product';
+    // 11.从数据库中删除订购产品/包裹.
+    const deleteOrderProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/delete_order_product';
+    // 12.取消订单,状态改为无效,保留订单不删除.
+    const cancelOrderUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/cancel_order';
+    // 13.从数据库中删除订单.
+    const deleteOrderUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/delete_order';
+    // 14.运单追踪.
+    const trackOrderUrl = 'https://taoplus.com.my/index.php?route=information/order_tracking/json&order_tracking_search=';
+
+    // accessToken的缓存key
+    const wstLogisticsBearerTokenKey = 'wstLogisticsBearerToken';
+
+    // 国家
+    const country = 'China';
+
+    function __construct()
+    {
+//         if (!\Yii::$app->redis->get(self::wstLogisticsBearerTokenKey)) {
+            $this->getAuthentication();
+//         }
+    }
+
+    /**
+     * 通用curl接口.
+     * @param string $url api接口url
+     * @param array $request_body 参数
+     * @return mixed
+     */
+    function curl($url, $request_body)
+    {
+        $accessToken = \Yii::$app->redis->get(self::wstLogisticsBearerTokenKey);
+
+        $ch = curl_init($url);
+        header('Content-Type: application/json');
+        $authorization = "Authorization: Bearer " . $accessToken;
+        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', $authorization]);
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request_body));
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+        $response = curl_exec($ch);
+        curl_close($ch);
+
+        return json_decode($response, true);
+    }
+
+    // 1.获取bearer token,此token是其他api调用时的必传参数.
+    function getAuthentication()
+    {
+        $request = [
+            'user_name' => Yii::$app->params['wst']['userName'],
+            'password' => Yii::$app->params['wst']['password'],
+            'agent_id' => Yii::$app->params['wst']['agentId'],
+        ];
+
+        $ch = curl_init(self::authenticationUrl);
+        header('Content-Type: application/json');
+        $authorization = "Authorization: Basic " . Yii::$app->params['wst']['baseToken'];
+        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', $authorization]);
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request));
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+        $response = curl_exec($ch);
+
+        curl_close($ch);
+
+        $result = json_decode($response, true);
+        if ($result['success']) {
+            // token和过期时间写入redis
+            \Yii::$app->redis->set(self::wstLogisticsBearerTokenKey, $result['data']['access_token']);
+            \Yii::$app->redis->expire(self::wstLogisticsBearerTokenKey, $result['data']['expires_in']);
+        }
+    }
+
+    // 2.创建订单和产品(以前不存在/已提交的产品).
+    function createOrder($order)
+    {
+        $request = [
+            'order_no' => $order['SN'],   // 客户系统中的订单号
+            'delivery_method_id' => '60', // 快递方式ID. TODO: 133(test)
+            'warehouse_id' => '1',   // 仓库ID. TODO:
+            'country' => 'China',    // 收件人国家 Malaysia
+            'state' => Region::getCnName($order['PROVINCE']), // ,   // 收件人省 Johor(test)
+            'city' => Region::getCnName($order['CITY']) . Region::getCnName($order['COUNTY']),   // 收件市县
+            'post_code' => $order['ZIP_CODE'], // 收件人邮政编码
+            'address' => $order['ADDRESS'],    // 收件人送货地址
+            'consignee' => $order['CONSIGNEE_REAL_NAME'],  // 收货人姓名,使用订单中的收货人真实姓名
+            'consignee_ic_number' => $order['CONSIGNEE_ID_NO'], // 收件人身份证号
+            'telephone' => $order['MOBILE'],  // 收件人电话号码
+            'sender' => 'Elken',    // 发件人名字
+            'sender_country' => 'Malaysia',    // 发件人国家
+            'sender_state' => 'Selangor', // ,   // 发件人省
+            'sender_city' => 'Kuala Lumpur',   // 发件人市县
+            'sender_post_code' => '47620', // 发件人邮政编码
+            'sender_address' => '11, 2nd Floor, Jalan TP5, Taman Perindustrian UEP, Subang Jaya',    // 发件人地址
+            'sender_telephone' => '0380210088',  // 发件人电话号码
+            'volumetric_details' => '3*4*20',    // 规格
+        ];
+
+        // 查询商品
+        $orderGoods = OrderGoods::find()->where('ORDER_SN=:ORDER_SN', [':ORDER_SN' => $order['SN']])->select('ORDER_SN,REAL_PRICE,BUY_NUMS,SKU_CODE,GOODS_TITLE')->asArray()->all();
+
+        $products = [];
+        foreach ($orderGoods as $item) {
+            $products[] = [
+                'product_no' => $item['SKU_CODE'], // 客户系统中的产品编号
+                'product_name' => $item['GOODS_TITLE'],   // 产品名称
+                'tracking_number' => Date::today('Ymd') . $this->_random(10, 1),
+                'quantity' => $item['BUY_NUMS'],       // 产品数量
+                'total_price' => number_format(100, 2, '.', ''),    // 订单总金额,Decimal 方便通关,固定100马币
+                'currency_code' => 'MYR',  // 产品的货币代码. 如USD(美元),MYR(马来西亚林吉特),SGD(新加坡元),CNY(人民币)
+                'supplier' => 'MRT',    // 品牌
+                'remark' => '300ML', // 备注:规格
+            ];
+        }
+        $request['products'] = $products;
+
+        return $this->curl(self::createOrderUrl, $request);
+    }
+
+    // 3.获取产品/包裹的重量和状态.
+    function getProduct($order)
+    {
+        $request = [
+            'tracking_number' => '027300027300',
+        ];
+
+        $response = $this->curl(self::getProductUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 4.获取订单重量,m3,包装的包裹数量,费用跟踪号码和状态
+    function getOrder($order)
+    {
+        $request = [
+            'order_id' => 'elg8-785',
+        ];
+
+        $response = $this->curl(self::getOrderUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 5.通知仓库已经付款,包裹可以投递.
+    function notifyDelivery($order)
+    {
+        $request = [
+            'order_id' => 'elg8-785',
+        ];
+
+        $response = $this->curl(self::notifyDeliveryUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 6.创建产品/包裹.
+    function addProduct($orderProducts)
+    {
+        $products = [];
+        foreach ($orderProducts as $item) {
+            $products[] = [
+                "product_no" => $item['SKU_CODE'],
+                "product_name" => $item['GOODS_TITLE'],
+                "tracking_number" => '',
+                "quantity" => $item['BUY_NUMS'],
+                "total_price" => $item['REAL_PV'],
+                "currency_code" => "CNY",
+            ];
+        }
+
+        $request = [
+            'warehouse_id' => '1',
+            "products" => $products
+        ];
+
+        $response = $this->curl(self::addProductUrl, $request);
+        LoggerTool::info($response);
+        return $response;
+    }
+
+    // 7.创建交付订单并将现有产品绑定到订单.需要先调用接口6创建商品.
+    function createOrderSimple($order)
+    {
+        $request = [
+            "order_no" => "T-1000",
+            "delivery_method_id" => "39",
+            "warehouse_id" => "1",
+            "country" => "Malaysia",
+            "state" => "Pulau Pinang",
+            "city" => "Bukit Mertajam",
+            "post_code" => "14000",
+            "address" => "1584,JALAN NANGKA,TAMAN JAMBU MAWAR,14000 BUKIT MERTAJAM",
+            "consignee" => "TSA",
+            "consignee_ic_number" => "111111-11-1111",
+            "telephone" => "017-5423223",
+            "cod" => "CNY",
+            "cod_currency_code" => "100",
+            "address_area" => "",
+            "address_subdivision" => "",
+            "products" => [
+                [
+                    "product_no" => "12345",
+                    "tracking_number" => "2019061112A1",
+                ],
+                [
+                    "product_no" => "23456",
+                    "tracking_number" => "20190604_A12",
+                ]
+            ]
+        ];
+
+        $response = $this->curl(self::createOrderSimpleUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 8.根据货物重量计算订单费用,并更改订单状态为待付款
+    function stockOut($order)
+    {
+        $request = [
+            'order_id' => 'elg8-785',
+            'weight' => '2.5', // 订单重量(KG)
+        ];
+
+        $response = $this->curl(self::stockOutUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 9.通知物流系统订单不可删除. error包含错误的订单ID.
+    function notifyOrderUndeletable($order)
+    {
+        $request = [
+            'order_ids' => ['elg8-785']
+        ];
+
+        $response = $this->curl(self::notifyOrderUndeletableUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 10.取消订单产品/包裹。它将状态更改为无效,并保留订单产品/包裹.
+    function cancelOrderProduct($order)
+    {
+        $request = [
+            'order_product_ids' => ['15895', '15896', '15897']
+        ];
+
+        $response = $this->curl(self::cancelOrderProductUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 11.从数据库中删除订购产品/包裹.
+    function deleteOrderProduct($order)
+    {
+        $request = [
+            'order_product_ids' => ['15895', '15896', '15897']
+        ];
+
+        $response = $this->curl(self::deleteOrderProductUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 12.取消订单,状态改为无效,保留订单不删除.
+    function cancelOrder($order)
+    {
+        $request = [
+            'order_ids' => ['elg8-787', 'elg8-786']
+        ];
+
+        $response = $this->curl(self::cancelOrderUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 13.从数据库中删除订单.
+    function deleteOrder($order)
+    {
+        $request = [
+            'order_ids' => ['elg8-787', 'elg8-786']
+        ];
+
+        $response = $this->curl(self::deleteOrderUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 13.从数据库中删除订单.
+    function trackOrder($trackNo)
+    {
+        // 运单号
+        $trackNo = 'TBS10182495KJ2430';
+
+        $curl = curl_init();
+        curl_setopt($curl,CURLOPT_TIMEOUT,5000);
+        curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);
+        curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,false);
+        curl_setopt($curl,CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
+        curl_setopt($curl,CURLOPT_URL, self::trackOrderUrl . $trackNo);
+        curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
+        $response = curl_exec($curl);
+        if (!$response) {
+            $error = curl_errno($curl);
+            curl_close($curl);
+            LoggerTool::error($error);
+            return false;
+        }
+        curl_close($curl);
+
+        $result = json_decode($response, true);
+        LoggerTool::info($result);
+
+        return $result['data'];
+    }
+
+    /**
+     * 生成随机数
+     * @param $length
+     * @param int $numeric
+     * @return string
+     */
+    private function _random($length, $numeric = 0) {
+        $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
+        $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed));
+        $hash = '';
+        $max = strlen($seed) - 1;
+        for ($i = 0; $i < $length; $i++) {
+            $hash .= $seed[mt_rand(0, $max)];
+        }
+        return $hash;
+    }
+}

+ 10 - 0
common/helpers/user/Info.php

@@ -73,6 +73,16 @@ class Info {
         return $user ? $user['MOBILE'] : null;
     }
 
+    /**
+     * 获取会员邮箱通过ID
+     * @param $userId
+     * @return null
+     */
+    public static function getUserEmailByUserId($userId) {
+        $user = User::findOneAsArray('ID=:ID AND DELETED=0', [':ID' => $userId], 'EMAIL');
+        return $user ? $user['EMAIL'] : null;
+    }
+
     /**
      * 通过用户ID获取用户的编号和名称
      * @param $userId

+ 97 - 0
common/models/ApproachDecOrder.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "{{%APPROACH_DEC_ORDER}}".
+ *
+ * @property string $ID
+ * @property string $DEC_SN 报单编号
+ * @property string $ORDER_SN 订单编号
+ * @property string $USER_ID 会员ID
+ * @property string $TO_USER_ID 报单对象ID
+ * @property string $TYPE 报单类型
+ * @property int $IS_ADMIN 是否管理员操作
+ * @property string $DEC_AMOUNT 报单金额
+ * @property string $DEC_PV 报单PV
+ * @property string $PAID_WALLET 支付钱包
+ * @property int $IS_BATCH 是否批量报单
+ * @property string $REMARK 备注
+ * @property string $REC_USER_ID 推荐人ID
+ * @property string $CON_USER_ID 接点人ID
+ * @property string $DEC_ID 报单中心ID
+ * @property int $PERIOD_NUM 报单期数
+ * @property string $P_CALC_MONTH 分区结算月
+ * @property int $CALC_MONTH 结算月
+ * @property int $CREATED_AT 创建时间
+ * @property string $UPDATER 操作人
+ * @property string $UPDATED_AT 更新时间
+ * @property int $IS_DEL 是否删除
+ * @property int $DELETED_AT 删除时间
+ * @property int $DETAIL_TYPE 类型
+ * @property int $UPGRADE_TYPE 升级类型
+ * @property string $ORI_LV 升级前级别
+ * @property string $UPGRADE_LV 升级后级别
+ */
+class ApproachDecOrder extends \common\components\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return '{{%APPROACH_DEC_ORDER}}';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['USER_ID', 'TO_USER_ID','REC_USER_ID',/*'CON_USER_ID','DEC_ID', */'TYPE', 'PAID_WALLET', 'PERIOD_NUM', 'P_CALC_MONTH', 'CREATED_AT'], 'required'],
+            [['IS_ADMIN', 'IS_BATCH', 'PERIOD_NUM', 'CALC_MONTH', 'CREATED_AT', 'IS_DEL', 'DELETED_AT'], 'integer'],
+            [['DEC_AMOUNT', 'DEC_PV'], 'number'],
+            [['ID', 'DEC_SN', 'ORDER_SN', 'USER_ID', 'TO_USER_ID','REC_USER_ID','CON_USER_ID','DEC_ID', 'TYPE', 'UPDATER', 'UPDATED_AT'], 'string', 'max' => 32],
+            [['PAID_WALLET'], 'string', 'max' => 48],
+            [['REMARK'], 'string', 'max' => 4000],
+            [['DEC_SN'], 'unique'],
+            [['ID'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'DEC_SN' => '报单编号',
+            'ORDER_SN' => '订单编号',
+            'USER_ID' => '会员ID',
+            'TO_USER_ID' => '报单对象ID',
+            'TYPE' => '报单类型',
+            'IS_ADMIN' => '是否管理员操作',
+            'DEC_AMOUNT' => '报单金额',
+            'DEC_PV' => '报单PV',
+            'PAID_WALLET' => '支付钱包',
+            'STATUS' => '支付状态',
+            'IS_BATCH' => '是否批量报单',
+            'REMARK' => '备注',
+            'REC_USER_ID' => '开拓人编号',
+            'CON_USER_ID' => '上级编号',
+            'DEC_ID' => '上级编号',
+            'PERIOD_NUM' => '报单期数',
+            'P_CALC_MONTH' => '分区结算月',
+            'CALC_MONTH' => '结算月',
+            'CREATED_AT' => '创建时间',
+            'UPDATER' => '操作人',
+            'UPDATED_AT' => '更新时间',
+            'IS_DEL' => '是否删除',
+            'DELETED_AT' => '删除时间',
+        ];
+    }
+}

+ 141 - 0
common/models/ApproachOrder.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "{{%ORDER}}".
+ *
+ * @property string $ID
+ * @property string $SN 订单号
+ * @property string $DEC_SN 报单编号
+ * @property string $USER_ID 用户ID
+ * @property string $USER_NAME 会员编号
+ * @property string $ORDER_TYPE 订货类型
+ * @property string $ORDER_AMOUNT 订单总价格
+ * @property string $PV 订货PV
+ * @property string $PAY_AMOUNT 支付价格
+ * @property string $PAY_PV 实付PV
+ * @property int $PAY_AT 支付时间
+ * @property string $REMAIN_PV
+ * @property string $PAY_TYPE 支付方式
+ * @property string $FREIGHT 运费
+ * @property string $PAY_FREIGHT 实付运费金额
+ * @property int $DELIVERY_STATUS 发货状态
+ * @property int $DELIVERY_PERIOD 发货期数
+ * @property int $DELIVERY_AT 发货时间
+ * @property string $EXPRESS_COMPANY 快递公司
+ * @property string $ORDER_TRACK_NO 快递单号
+ * @property int $EXPRESS_TYPE 发货方式
+ * @property string $FRONT_REMARK 前台备注
+ * @property string $REMARK 后台备注
+ * @property int $PERIOD_NUM 期数
+ * @property int $STATUS 订单状态
+ * @property string $CONSIGNEE 收货人
+ * @property string $MOBILE 收货人手机
+ * @property string $TEL 固定电话
+ * @property int $PROVINCE 省份名称
+ * @property int $CITY 城市名称
+ * @property int $COUNTY 县区
+ * @property string $ADDRESS 详细地址
+ * @property string $P_CALC_MONTH 分区日期
+ * @property int $CREATED_AT 订单创建时间
+ * @property string $CREATE_USER 订单创建人
+ * @property int $UPDATED_AT 修改时间
+ * @property string $UPDATER 修改人
+ * @property int $IS_DELETE 是否删除
+ * @property int $DELETED_AT 删除时间
+ * @property int $WAREHOUSE 发货仓
+ * @property string $EMAIL 邮箱
+ * @property string $NOTE 备注说明
+ * @property string $CONSIGNEE_REAL_NAME 收件人真实姓名
+ * @property string $CONSIGNEE_ID_NO 收件人身份证号码
+ * @property string $ZIP_CODE 收件人邮编
+ */
+class ApproachOrder extends \common\components\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return '{{%APPROACH_ORDER}}';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['USER_ID', 'USER_NAME', 'ORDER_TYPE', 'CREATE_USER'], 'required'],
+            [['ORDER_AMOUNT', 'PV', 'PAY_AMOUNT', 'PAY_PV', 'FREIGHT', 'PAY_FREIGHT'], 'number'],
+            [['PAY_AT', 'DELIVERY_STATUS', 'DELIVERY_PERIOD', 'DELIVERY_AT', 'EXPRESS_TYPE', 'PERIOD_NUM', 'STATUS', 'PROVINCE', 'CITY', 'COUNTY', 'CREATED_AT', 'UPDATED_AT', 'IS_DELETE', 'DELETED_AT'], 'integer'],
+            [['ID','SN', 'DEC_SN', 'USER_ID', 'ORDER_TRACK_NO','PAY_TYPE'], 'string', 'max' => 32],
+            [['USER_NAME', 'TEL', 'CREATE_USER', 'UPDATER', 'WAREHOUSE'], 'string', 'max' => 16],
+            [['ORDER_TYPE'], 'string', 'max' => 12],
+            [['EXPRESS_COMPANY'], 'string', 'max' => 128],
+            [['FRONT_REMARK'], 'string', 'max' => 1000],
+            [['REMARK', 'NOTE'], 'string', 'max' => 4000],
+            [['CONSIGNEE'], 'string', 'max' => 120],
+            [['MOBILE'], 'string', 'max' => 11],
+            [['ADDRESS'], 'string', 'max' => 255],
+            [['SN'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'SN' => '订单号',
+            'DEC_SN' => '报单编号',
+            'USER_ID' => '用户ID',
+            'USER_NAME' => '会员编号',
+            'ORDER_TYPE' => '订货类型',
+            'ORDER_AMOUNT' => '订单总价格',
+            'PV' => '订货PV',
+            'PAY_AMOUNT' => '支付价格',
+            'PAY_PV' => '实付PV',
+            'PAY_AT' => '支付时间',
+            'PAY_TYPE' => '支付方式',
+            'FREIGHT' => '运费',
+            'PAY_FREIGHT' => '实付运费金额',
+            'DELIVERY_STATUS' => '发货状态',
+            'DELIVERY_PERIOD' => '发货期数',
+            'DELIVERY_AT' => '发货时间',
+            'EXPRESS_COMPANY' => '快递公司',
+            'ORDER_TRACK_NO' => '快递单号',
+            'EXPRESS_TYPE' => '发货方式',
+            'FRONT_REMARK' => '前台备注',
+            'REMARK' => '后台备注',
+            'PERIOD_NUM' => '期数',
+            'STATUS' => '订单状态',
+            'CONSIGNEE' => '收货人',
+            'MOBILE' => '收货人手机',
+            'TEL' => '固定电话',
+            'PROVINCE' => '省份名称',
+            'CITY' => '城市名称',
+            'COUNTY' => '县区',
+            'ADDRESS' => '详细地址',
+            'P_CALC_MONTH' => '分区日期',
+            'CREATED_AT' => '订单创建时间',
+            'CREATE_USER' => '订单创建人',
+            'UPDATED_AT' => '修改时间',
+            'UPDATER' => '修改人',
+            'IS_DELETE' => '是否删除',
+            'DELETED_AT' => '删除时间',
+            'WAREHOUSE' => '发货仓',
+            'EMAIL' => 'Email'
+        ];
+    }
+
+    public function getUserByUserId()
+    {
+        return $this->hasOne(User::class, ['ID' => 'USER_ID']);
+    }
+}

+ 71 - 0
common/models/ApproachOrderCall.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace common\models;
+
+use common\components\MongoActiveRecord;
+use Yii;
+
+/**
+ * This is the model class for collection "ar_approach_order_call".
+ *
+ * @property \MongoDB\BSON\ObjectID|string $_id
+ * @property string $sn
+ * @property string $Signature
+ * @property string $TransId
+ * @property string $data
+ */
+class ApproachOrderCall extends MongoActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function collectionName()
+    {
+        return 'ar_approach_order_call';
+    }
+
+    /**
+     * @return \yii\mongodb\Connection the MongoDB connection used by this AR class.
+     * @throws \yii\base\InvalidConfigException
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('dbLog');
+    }
+
+    /**
+     * 获取id
+     * @return string
+     */
+    public function getId() {
+        return (string) $this->_id;
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function attributes()
+    {
+        return [
+            '_id',
+            'sn',
+            'Signature',
+            'TransId',
+            'data',
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            '_id' => 'objectID',
+            'sn' => 'orderSn',
+            'Signature' => 'Signature',
+            'TransId' => 'TransId',
+            'data' => 'data',
+        ];
+    }
+
+}

+ 75 - 0
common/models/ApproachOrderGoods.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "{{%ORDER_GOODS}}".
+ *
+ * @property string $ID
+ * @property string $ORDER_SN 订单ID
+ * @property string $GOODS_ID 商品ID
+ * @property string $GOODS_TITLE 商品名称
+ * @property string $PRICE 价格
+ * @property string $REAL_PRICE 实际价格
+ * @property string $PV 订货PV
+ * @property string $REAL_PV 实际PV
+ * @property string $TAX_RATE 税率
+ * @property string $POINT 兑换积分
+ * @property string $SKU_CODE 商品编码
+ * @property int $BUY_NUMS 购买数量
+ * @property string $P_CALC_MONTH 分区日期
+ * @property int CATEGORY_TYPE 商品分类
+ * @property int PAY_TYPE 支付方式
+ */
+class ApproachOrderGoods extends \common\components\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return '{{%APPROACH_ORDER_GOODS}}';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['ORDER_SN', 'GOODS_ID', 'GOODS_TITLE', 'SKU_CODE', 'CATEGORY_TYPE', 'PAY_TYPE'], 'required'],
+            [['PRICE', 'REAL_PRICE', 'PV', 'REAL_PV', 'POINT', 'CATEGORY_TYPE', 'PAY_TYPE', 'TAX_RATE'], 'number'],
+            [['BUY_NUMS'], 'integer'],
+            [['ID', 'ORDER_SN', 'GOODS_ID'], 'string', 'max' => 32],
+            [['GOODS_TITLE'], 'string', 'max' => 255],
+            [['SKU_CODE'], 'string', 'max' => 16],
+            [['ID'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'ORDER_SN' => '订单ID',
+            'GOODS_ID' => '商品ID',
+            'GOODS_TITLE' => '商品名称',
+            'PRICE' => '价格',
+            'REAL_PRICE' => '实际价格',
+            'PV' => '订货BV',
+            'REAL_PV' => '实际BV',
+            'TAX_RATE' => '税率',
+            'POINT' => '兑换积分',
+            'SKU_CODE' => '商品编码',
+            'BUY_NUMS' => '购买数量',
+            'P_CALC_MONTH' => '分区日期',
+            'CATEGORY_TYPE' => '商品分类',
+            'PAY_TYPE' => '支付方式',
+        ];
+    }
+}

+ 62 - 0
common/models/HighestEmpLevelLog.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace common\models;
+
+/**
+ * This is the model class for table "{{%HIGHEST_EMP_LEVEL_LOG}}".
+ *
+ * @property string $ID
+ * @property string $USER_ID 会员ID
+ * @property string $FROM_ID 变动前的级别
+ * @property string $TO_ID 变动后的级别
+ * @property string $ADMIN_ID admin id
+ * @property int $PERIOD_NUM 变动的期数
+ * @property string $CALC_MONTH 变动的结算月
+ * @property string $REMARK 备注
+ * @property int $STATUS 状态
+ * @property int $CREATED_AT 创建时间
+ * @property int $DEC_TYPE 变动类型: 1管理后台调整级别 2奖金计算调整
+ */
+class HighestEmpLevelLog extends \common\components\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return '{{%HIGHEST_EMP_LEVEL_LOG}}';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['USER_ID', 'FROM_ID', 'TO_ID', 'PERIOD_NUM', 'CALC_MONTH', 'CREATED_AT'], 'required'],
+            [['PERIOD_NUM', 'CALC_MONTH', 'STATUS', 'CREATED_AT'], 'integer'],
+            [['ID', 'USER_ID', 'FROM_ID', 'TO_ID', 'ADMIN_ID'], 'string', 'max' => 32],
+            [['REMARK'], 'string', 'max' => 4000],
+            [['ID'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'USER_ID' => '会员ID',
+            'FROM_ID' => '变动前的级别',
+            'TO_ID' => '变动后的级别',
+            'ADMIN_ID' => '管理员id',
+            'PERIOD_NUM' => '变动的期数',
+            'CALC_MONTH' => '变动的结算月',
+            'REMARK' => '备注',
+            'STATUS' => '状态',
+            'CREATED_AT' => '创建时间',
+        ];
+    }
+}

+ 2 - 0
common/models/ReceiveAddress.php

@@ -16,6 +16,7 @@ use Yii;
  * @property int $CITY 城市名称
  * @property int $COUNTY 县区名称
  * @property string $ADDRESS 详细地址
+ * @property string $ZIP_CODE 邮政编码
  * @property int $IS_DEFAULT 是否默认
  * @property int $CREATED_AT 创建时间
  * @property string $UPDATER 修改人
@@ -64,6 +65,7 @@ class ReceiveAddress extends \common\components\ActiveRecord
             'CITY' => '城市名称',
             'COUNTY' => '县区名称',
             'ADDRESS' => '详细地址',
+            'ZIP_CODE' => '邮编',
             'IS_DEFAULT' => '是否默认',
             'CREATED_AT' => '创建时间',
             'UPDATER' => '修改人',

+ 7 - 1
common/models/ShopGoods.php

@@ -39,6 +39,9 @@ class ShopGoods extends \common\components\ActiveRecord
         ],
         3 => [
             'name' => '积分兑换'
+        ],
+        4 => [
+            'name' => '在线支付'
         ]
     ];
     const GOODS_TYPE = [
@@ -168,7 +171,10 @@ class ShopGoods extends \common\components\ActiveRecord
             ],
             'exchange' => [
                 'name' => '无业绩兑换点数'
-            ]
+            ],
+            'online' => [
+                'name' => '在线支付'
+            ],
         ];
     }
 

+ 101 - 0
common/models/WstOrderCall.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace common\models;
+
+use common\components\MongoActiveRecord;
+use Yii;
+
+/**
+ * This is the model class for collection "ar_wst_order_call".
+ *
+ * @property \MongoDB\BSON\ObjectID|string $_id
+ * @property string $order_id
+ * @property string $order_no
+ * @property string $warehouse_id
+ * @property string $delivery_method_name
+ * @property string $addon_service_name
+ * @property string $country
+ * @property string $state
+ * @property string $city
+ * @property string $post_code
+ * @property string $address
+ * @property string $consignee
+ * @property string $telephone
+ * @property string $comment
+ * @property string $products
+ */
+class WstOrderCall extends MongoActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function collectionName()
+    {
+        return 'ar_wst_order_call';
+    }
+
+    /**
+     * @return \yii\mongodb\Connection the MongoDB connection used by this AR class.
+     * @throws \yii\base\InvalidConfigException
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('dbLog');
+    }
+
+    /**
+     * 获取id
+     * @return string
+     */
+    public function getId() {
+        return (string) $this->_id;
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function attributes()
+    {
+        return [
+            '_id',
+            'order_id',
+            'order_no',
+            'warehouse_id',
+            'delivery_method_name',
+            'addon_service_name',
+            'country',
+            'state',
+            'city',
+            'post_code',
+            'address',
+            'consignee',
+            'telephone',
+            'comment',
+            'products',
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            '_id' => 'objectID',
+            'order_id',
+            'order_no',
+            'warehouse_id',
+            'delivery_method_name',
+            'addon_service_name',
+            'country',
+            'state',
+            'city',
+            'post_code',
+            'address',
+            'consignee',
+            'telephone',
+            'comment',
+            'products',
+        ];
+    }
+
+}

+ 492 - 0
common/models/forms/ApproachOrderForm.php

@@ -0,0 +1,492 @@
+<?php
+namespace common\models\forms;
+
+use common\helpers\Cache;
+use common\helpers\Date;
+use common\components\Model;
+use common\helpers\Form;
+use common\helpers\IPay88;
+use common\helpers\LoggerTool;
+use common\helpers\PayStack;
+use common\helpers\user\Balance;
+use common\helpers\user\Cash;
+use common\helpers\user\Info;
+use common\libs\logging\operate\AdminOperate;
+use common\models\ApproachDecOrder;
+use common\models\ApproachOrder;
+use common\models\ApproachOrderGoods;
+use common\models\BaUser;
+use common\models\DealType;
+use common\models\DecLevelLog;
+use common\models\DecOrder;
+use common\models\Order;
+use common\models\OrderGoods;
+use common\models\Period;
+use common\models\ReceiveAddress;
+use common\models\Region;
+use common\models\ShopGoods;
+use common\models\User;
+use common\models\UserNetwork;
+use common\models\Instalment;
+use Yii;
+use yii\base\Exception;
+
+/**
+ * Login form
+ */
+class ApproachOrderForm extends Model
+{
+    public $sn;
+    public $expressCompany;
+    public $orderTrackNo;
+    public $status;
+    public $remark;
+    public $note;
+
+    public $type;
+    public $addressId;
+    public $payType;
+    public $goodsId;
+    public $goodsNum;
+    public $payPassword;
+
+    public $userName;
+    public $consignee;
+    public $acceptMobile;
+    public $province;
+    public $city;
+    public $county;
+    public $lgaName;
+    public $detailaddress;
+
+    public $consigneeIdNo;
+    public $consigneeRealName;
+
+    private $_address;
+    private $_decAmount;
+    private $_decPv;
+    private $_freight;
+    private $_payAmount;
+    private $_orderGoods;
+    private $_remainPv;
+    private $_realPv;
+
+    /**
+     * @var ApproachOrder
+     */
+    private $_model;
+
+    public function init() {
+        parent::init();
+        $this->adminOperateLogger = new AdminOperate([
+            'fetchClass' => ApproachOrder::class,
+        ]);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function rules()
+    {
+        return [
+            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','cityName','detailaddress', 'consigneeIdNo', 'consigneeRealName'], 'trim'],
+            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','detailaddress', 'consigneeIdNo', 'consigneeRealName'], 'required'],
+            [['status'], 'isStatus'],
+            [['addressId'], 'isAddress'],
+            [['payType'], 'isPayType'],
+        ];
+    }
+
+    public function attributeLabels()
+    {
+        return [
+            'sn' => '订单号',
+            'expressCompany' => '快递公司',
+            'orderTrackNo' => '快递单号',
+            'status' => '状态',
+            'remark' => '备注',
+            'type' => '订单类型',
+            'addressId' => '收货地址',
+            'payType' => '支付方式',
+            'goodsId' => '商品ID',
+            'goodsNum' => '商品数量',
+            'userName' => '复消会员编号',
+            'consignee' => '收货人',
+            'acceptMobile' => '收货电话',
+            'province' => '省',
+            'city' => '市',
+            'county' => '区',
+            'detailaddress' => '收货详细地址',
+        ];
+    }
+
+    /**
+     * 指定校验场景
+     * @return array
+     */
+    public function scenarios()
+    {
+        $parentScenarios =  parent::scenarios();
+        $customScenarios = [
+            // 管理员修改订单状态
+            'adminStatus' => ['sn', 'status'],
+            // 校验订单支付
+            'verifyPay' => ['sn', 'status', 'note'],
+            // 会员下单
+            'userOrder' => ['type','addressId', 'payType','goodsId','goodsNum', 'note', 'consigneeIdNo', 'consigneeRealName'],
+        ];
+        return array_merge($parentScenarios, $customScenarios);
+    }
+
+    /**
+     * 校验之前
+     * @return bool
+     */
+    public function beforeValidate()
+    {
+        $parentValidate = parent::beforeValidate();
+        if ($this->sn) {
+            $this->_model = ApproachOrder::findOne(['SN' => $this->sn]);
+            if (!$this->_model){
+                $this->addError('sn', '订单不存在');
+                return false;
+            }
+        }
+
+        if ($this->scenario == 'verifyIPay88'){
+            if ($this->_model->STATUS != \Yii::$app->params['orderStatus']['notPaid']['value']) {
+                $this->addError('sn', '支付方式错误');
+                return false;
+            }
+        }
+
+        return $parentValidate;
+    }
+
+    /**
+     * 判断收货地址是否存在
+     * @param $attribute
+     */
+    public function isAddress($attribute){
+        if (!$receiveAddress = ReceiveAddress::find()->where(' ID=:ID', [':ID' => $this->addressId])->asArray()->one()) {
+            $this->addError($attribute, '收货地址不存在');
+        } else {
+            $this->_address = $receiveAddress;
+        }
+    }
+
+    /**
+     * 判断支付方式
+     * @param $attribute
+     * @throws Exception
+     */
+    public function isPayType($attribute)
+    {
+        if ($this->payType != 'online'){
+            $this->addError('支付方式错误');
+        }
+
+        // 一个订单只能包含一类商品
+        $goods = ShopGoods::find()->select('ID,CATE_ID')->where(['in', 'ID', $this->goodsId])->andWhere(['STATUS' => 1])->asArray()->all();
+        if (!$goods) {
+            throw new Exception('商品已下架');
+        }
+        $goodsCategoryType = array_unique(array_column($goods, 'CATE_ID'));
+        if (count($goodsCategoryType) > 1) {
+            $this->addError($attribute, '订单不能包含多种商品分类');
+        }
+    }
+
+    /**
+     * 校验类型
+     * @param $attribute
+     */
+    public function isStatus($attribute){
+        if($this->type && !in_array($this->type, \Yii::$app->params['orderStatus'])){
+            $this->addError($attribute, '订单状态类型错误');
+            return ;
+        }
+    }
+
+    /**
+     * 校验iPay88支付,更新订单状态.同步到正式订单.
+     * @throws Exception
+     */
+    public function verifyPayOnline(): ?ApproachOrder
+    {
+        if (!$this->validate()) {
+            return null;
+        }
+
+        LoggerTool::info([$this->sn, $this->note]);
+
+        // 调用iPay88支付校验
+//        LoggerTool::info([$this->note['reference'], $this->note]);
+//        $payload = IPay88::transactionVerify($this->note['reference']);
+//        LoggerTool::info($payload);
+//        if ($payload['status'] !== true) {
+//            throw new Exception(Form::formatErrorsForApi($payload['message']));
+//        }
+//        if ($payload['data']['amount'] != $this->_model->PAY_AMOUNT * 100) {
+//            throw new Exception(Form::formatErrorsForApi('支付金额与订单金额不符'));
+//        }
+
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+        try {
+            // 更新准订单状态为已支付
+            $this->_model->STATUS = $this->status;
+            $this->_model->NOTE = json_encode($this->note);
+            $this->_model->PAY_AT = time();
+//            $this->_model->PAY_AT = Date::utcToTime($this->note['TranDate']);
+            if (!$this->_model->save()) {
+                throw new Exception(Form::formatErrorsForApi($this->_model->getErrors()));
+            }
+
+            // 同步准订单到正式订单
+            Order::insertOne($this->_model->toArray());
+            // 同步准订单商品到正式订单商品
+            $approachOrderGoods = ApproachOrderGoods::findAllAsArray('ORDER_SN = :ORDER_SN', [':ORDER_SN' => $this->sn]);
+            OrderGoods::batchInsert($approachOrderGoods);
+
+            // 删除中间表
+            ApproachOrder::deleteAll('SN = :SN', [':SN' => $this->sn]);
+            ApproachOrderGoods::deleteAll('ORDER_SN = :ORDER_SN', [':ORDER_SN' => $this->sn]);
+
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            $this->addError('edit', $e->getFile() . '  ' . $e->getMessage());
+            return null;
+        }
+
+        return $this->_model;
+    }
+
+    /**
+     * BV分期
+     *
+     *
+     */
+    private function _pvSplit($oPv){
+        $sysConfig = Cache::getSystemConfig();
+        $mesureUpCondition = $sysConfig['monthPcsPvFxCondition']['VALUE'];
+        if ($oPv > $mesureUpCondition) {
+            $currentPv = $oPv % $mesureUpCondition + $mesureUpCondition;
+            $remainPv = $oPv - $currentPv;
+        } else {
+            $currentPv = $oPv;
+            $remainPv = 0;
+        }
+        return [
+            'current' => $currentPv,
+            'remain' => $remainPv
+        ];
+    }
+
+
+    /**
+     * 复销
+     * @throws Exception
+     * @throws \yii\db\Exception
+     */
+    public function add() {
+        if(!$this->validate()){
+            return null;
+        }
+
+        $ids = $this->goodsId;
+        $totalAmount = 0;
+        $totalPv = 0;
+        $totalRealPv = 0;
+        $this->_remainPv = 0;
+        foreach ($this->goodsNum as $k => $v) {
+            if ($v) {
+                $goods = ShopGoods::findOneAsArray('ID=:ID AND STATUS=1',[':ID'=> $ids[$k]]);
+                if (!$goods) {
+                    throw new Exception('商品已下架');
+                }
+                if ($goods['STORE_NUMS'] > 0) {
+                    $discount = $goods['SELL_DISCOUNT'];
+                    $realPrice = $goods['SELL_PRICE'] * $discount;
+                    $realPv = $goods['PRICE_PV'] * $discount;
+                    if ($goods['PV_SPLIT'] == 1) { // 当商品为PV分期时
+                        $pvSplit = $this->_pvSplit($realPv);
+                        $currentPv = $pvSplit['current'];
+                        $remainPv = $pvSplit['remain'];
+                        $totalPv += $currentPv * intval($v);
+                        $totalRealPv += $realPv * intval($v);
+                        $this->_remainPv += $remainPv * intval($v);
+                    } else {
+                        $currentPv = $goods['PRICE_PV'];
+                        $totalPv += $realPv * intval($v);
+                        $totalRealPv += $realPv * intval($v);
+                        $remainPv = 0;
+                        $this->_remainPv += 0;
+                    }
+                    $totalAmount += $realPrice * intval($v);
+
+                    $this->_orderGoods[] = [
+                        'GOODS_ID' => $goods['ID'],
+                        'PRICE' => $goods['SELL_PRICE'],
+                        'PV' => $currentPv,
+                        'REAL_PRICE' => $realPrice,
+                        'REAL_PV' => $realPv,
+                        'REMAIN_PV' => $remainPv,
+                        'POINT' => $goods['POINT'],
+                        'BUY_NUMS' => intval($v),
+                        'SKU_CODE' => $goods['GOODS_NO'],
+                        'GOODS_TITLE' => $goods['GOODS_NAME']
+                    ];
+                }
+            }
+        }
+
+        $this->_decAmount = $totalAmount;
+        $this->_decPv = $totalPv;
+        $this->_realPv = $totalRealPv;
+        $this->_freight = ($totalAmount>=300) ? 0 : 15;
+        $this->_payAmount = $this->_decAmount + $this->_freight;
+
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+
+        // 支付减库存
+        foreach ($this->goodsNum as $k => $v) {
+            if ($v) {
+                $goods = ShopGoods::findOneAsArray('ID=:ID AND STATUS=1', [':ID'=> $ids[$k]]);
+                if (!$goods) {
+                    throw new Exception('商品已下架');
+                }
+                if ($goods['STORE_NUMS'] >= $this->goodsNum[$k]) {
+                    $data = ShopGoods::find()->where(['ID' => $ids[$k]])->one();
+                    $goods_store_nums = $data->STORE_NUMS - $this->goodsNum[$k];
+                    $data->STORE_NUMS = $goods_store_nums;
+                    $data->update();
+                    //下单后库存小于等于0 商品下架
+                    if ($goods_store_nums <= 0) {
+                        $data->STATUS = 0;
+                        $data->UPDATED_AT = Date::nowTime();
+                        $data->update();
+                    }
+                } else {
+                    throw new Exception($goods['GOODS_NAME'].'库存不足,无法购买商品');
+                }
+            }
+        }
+
+        try {
+            // 写入订单
+            if (!$orderResult = $this->addOrder()) {
+                throw new Exception(Form::formatErrorsForApi($orderResult->getErrors()));
+            }
+
+            // TODO: 获取iPay88所需参数
+//            $orderSn = $orderResult->SN;
+
+            $transaction->commit();
+
+            return $orderResult;
+        }catch (\Exception $e){
+            $transaction->rollBack();
+            $this->addError('add', $e->getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 复销订单
+     * @throws Exception
+     */
+    public function addOrder()
+    {
+        $periodObj = Period::instance();
+        $nowPeriodNum = $periodObj->getNowPeriodNum();
+        $nowCalcMonth = $periodObj->getYearMonth($nowPeriodNum);
+
+        $userId = \Yii::$app->user->id;
+
+        $userName = Info::getUserNameByUserId($userId);
+        $userRealName = Info::getUserRealNameByUserId($userId);
+        $userMobile = Info::getUserMobileByUserId($userId);
+        $userEmail = Info::getUserEmailByUserId($userId);
+
+        // 加入订单信息
+        $warehouse = Region::getWarehouseByCode($this->_address['PROVINCE']);//仓库
+        if(!$warehouse){
+            throw new Exception('地区暂时不支持配送,具体联系客服');
+        }
+
+        $ordNo = $this->_generateSn();
+        $orderModel = new ApproachOrder();
+        $orderModel->SN = 'OS' . $ordNo;
+        $orderModel->DEC_SN = 'DS' . $ordNo;
+        $orderModel->ORDER_TYPE = $this->type;
+        $orderModel->USER_ID = $userId;
+        $orderModel->USER_NAME = $userName;
+        $orderModel->ORDER_AMOUNT = $this->_decAmount;
+        $orderModel->PV = $this->_decPv;
+        $orderModel->PAY_AMOUNT = $this->_payAmount;
+        $orderModel->PAY_PV = $this->_decPv;
+        $orderModel->REMAIN_PV = $this->_remainPv;
+        $orderModel->PAY_AT = 0;
+        $orderModel->PAY_TYPE = $this->payType;
+        $orderModel->PERIOD_NUM = $nowPeriodNum;
+        $orderModel->P_CALC_MONTH = Date::ociToDate($nowCalcMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH);
+        $orderModel->FREIGHT = $this->_freight;
+        $orderModel->PAY_FREIGHT = $this->_freight;
+        $orderModel->CONSIGNEE = $this->_address['CONSIGNEE'];
+        $orderModel->MOBILE = $this->_address['MOBILE'];
+        $orderModel->PROVINCE = $this->_address['PROVINCE'];
+        $orderModel->CITY = $this->_address['CITY'];
+        $orderModel->COUNTY = $this->_address['COUNTY'];
+        $orderModel->ADDRESS = $this->_address['ADDRESS'];
+        $orderModel->FRONT_REMARK = $this->remark;
+        $orderModel->WAREHOUSE = $warehouse;
+        $orderModel->STATUS = \Yii::$app->params['orderStatus']['notPaid']['value'];
+        $orderModel->CREATED_AT = Date::nowTime();
+        $orderModel->CREATE_USER = $userName;
+        $orderModel->EMAIL = $userEmail ?: $userName.'@elken.net';
+        $orderModel->CONSIGNEE_ID_NO = $this->consigneeIdNo;
+        $orderModel->CONSIGNEE_REAL_NAME = $this->consigneeRealName;
+        $orderModel->ZIP_CODE = $this->_address['ZIP_CODE'];
+        if(!$orderModel->save()){
+            $this->addErrors($orderModel->getErrors());
+            return false;
+        }
+
+        // 加入商品到订单商品表
+        foreach($this->_orderGoods as $key=>$value) {
+            $this->_orderGoods[$key]['ORDER_SN'] = $orderModel->SN;
+            $this->_orderGoods[$key]['P_CALC_MONTH'] = Date::ociToDate($nowCalcMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH);
+        }
+        ApproachOrderGoods::batchInsert($this->_orderGoods);
+
+        return $orderModel;
+    }
+
+    /**
+     * 生成流水号
+     * @return string
+     */
+    private function _generateSn() {
+        return Date::today('Ymd') . $this->_random(10, 1);
+    }
+
+    /**
+     * 生成随机数
+     * @param $length
+     * @param int $numeric
+     * @return string
+     */
+    private function _random($length, $numeric = 0) {
+        $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
+        $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed));
+        $hash = '';
+        $max = strlen($seed) - 1;
+        for ($i = 0; $i < $length; $i++) {
+            $hash .= $seed[mt_rand(0, $max)];
+        }
+        return $hash;
+    }
+}

+ 146 - 0
common/models/forms/HighestEmpLevelLogForm.php

@@ -0,0 +1,146 @@
+<?php
+namespace common\models\forms;
+
+use common\helpers\Date;
+use common\components\Model;
+use common\helpers\Form;
+use common\helpers\user\Info;
+use common\libs\logging\operate\AdminOperate;
+use common\models\EmployLevel;
+use common\models\HighestEmpLevelLog;
+use common\models\Period;
+use common\models\User;
+use yii\base\Exception;
+
+/**
+ * Login form
+ */
+class HighestEmpLevelLogForm extends Model
+{
+    public $userName;
+    public $levelId;
+    public $periodNum;
+    public $remark;
+    private $_userId;
+    private $_fromId;
+
+    public function init() {
+        parent::init();
+        $this->adminOperateLogger = new AdminOperate([
+            'fetchClass' => User::class,
+        ]);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function rules()
+    {
+        return [
+            [['userName', 'levelId', 'periodNum', 'remark'], 'trim'],
+            [['userName', 'levelId', 'periodNum'], 'required'],
+            [['userName'], 'exist', 'targetClass' => User::class, 'targetAttribute' => 'USER_NAME'],
+            [['levelId'], 'exist', 'targetClass' => EmployLevel::class, 'targetAttribute' => 'ID'],
+            [['userName'], 'isUser'],
+            [['levelId'], 'isLevel'],
+            [['periodNum'], 'integer'],
+        ];
+    }
+
+    public function attributeLabels()
+    {
+        return [
+            'userName' => '会员编号',
+            'levelId' => '级别',
+            'periodNum' => '期数',
+            'remark' => '备注',
+        ];
+    }
+
+    /**
+     * 指定校验场景
+     * @return array
+     */
+    public function scenarios()
+    {
+        $parentScenarios =  parent::scenarios();
+        $customScenarios = [
+            'adminChange' => ['userName', 'levelId', 'remark'],
+        ];
+        return array_merge($parentScenarios, $customScenarios);
+    }
+
+    /**
+     * 赋值UserId并校验会员是否存在
+     * @param $attribute
+     */
+    public function isUser($attribute){
+        $this->_userId = Info::getUserIdByUserName($this->userName);
+        if(!$this->_userId){
+            $this->addError($attribute, '会员不存在');
+        }
+    }
+
+    /**
+     * 查看级别是否有变化
+     * @param $attribute
+     * @throws \yii\db\Exception
+     */
+    public function isLevel($attribute){
+        $this->_fromId = Info::getEmpLv($this->_userId);
+        if ($this->levelId == $this->_fromId) {
+            $this->addError($attribute, '级别没有变化无需调整');
+        }
+    }
+
+    /**
+     * 更改最高聘级.
+     * @return HighestEmpLevelLog|null
+     */
+    public function adminChange()
+    {
+        if (!$this->validate()) {
+            return null;
+        }
+        $this->adminOperateLogger->beforeUpdate($this->_userId,'ID',['select'=>'ID,EMP_LV']);
+
+        $model = new HighestEmpLevelLog();
+
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+        try {
+            $period = Period::instance();
+            // 新增数据
+            $model->USER_ID = $this->_userId;
+            $model->FROM_ID = $this->_fromId;
+            $model->TO_ID = $this->levelId;
+            $model->PERIOD_NUM = $period->getNowPeriodNum();
+            $model->CALC_MONTH = $period->getYearMonth($period->getNowPeriodNum());
+            $model->REMARK = $this->remark ?? '';
+            $model->STATUS = 1;
+            $model->ADMIN_ID = \Yii::$app->user->id;
+            $model->CREATED_AT = Date::nowTime();
+            if (!$model->save()) {
+                throw new Exception(Form::formatErrorsForApi($model->getErrors()));
+            }
+
+            // 修改最高聘级
+            User::updateAll(['EMP_LV' => $this->levelId], 'ID=:USER_ID', [':USER_ID' => $this->_userId]);
+
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            $this->addError('adminChange', $e->getMessage());
+            return null;
+        }
+
+        $this->adminOperateLogger->afterUpdate($this->_userId,'ID',['select'=>'ID,EMP_LV'])->clean()->save([
+            'optType' => '调整会员最高聘级',
+            'userId' => $this->_userId,
+            'userName' => Info::getUserNameByUserId($this->_userId),
+            'remark' => $this->remark
+        ]);
+
+        return $model;
+    }
+}

+ 7 - 3
common/models/forms/ReceiveAddressForm.php

@@ -25,6 +25,7 @@ class ReceiveAddressForm extends Model
     public $city;
     public $county;
     public $address;
+    public $zipCode;
     public $isDefault;
 
     /**
@@ -46,7 +47,7 @@ class ReceiveAddressForm extends Model
     public function rules()
     {
         return [
-            [['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'isDefault'], 'trim'],
+            [['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'zipCode', 'isDefault'], 'trim'],
             [['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address'], 'required'],
             [['mobile'], 'mobile'],
             [['province', 'city', 'county'], 'exist', 'targetClass' => Region::class, 'targetAttribute' => 'REGION_CODE'],
@@ -62,6 +63,7 @@ class ReceiveAddressForm extends Model
             'city' => '市/区',
             'county' => '区/县',
             'address' => '详细地址',
+            'zipCode' => '邮政编码',
         ];
     }
 
@@ -73,8 +75,8 @@ class ReceiveAddressForm extends Model
     {
         $parentScenarios =  parent::scenarios();
         $customScenarios = [
-            'userAdd' => ['consignee', 'mobile', 'province', 'city', 'county', 'address', 'isDefault'],
-            'userEdit' => ['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'isDefault'],
+            'userAdd' => ['consignee', 'mobile', 'province', 'city', 'county', 'address', 'zipCode', 'isDefault'],
+            'userEdit' => ['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'zipCode', 'isDefault'],
             'userIsDefault' => ['id', 'isDefault'],
         ];
         return array_merge($parentScenarios, $customScenarios);
@@ -138,6 +140,7 @@ class ReceiveAddressForm extends Model
                 $this->_model->CITY = $this->city;
                 $this->_model->COUNTY = $this->county;
                 $this->_model->ADDRESS = $this->address;
+                $this->_model->ZIP_CODE = $this->zipCode;
                 $this->_model->IS_DEFAULT = $this->isDefault ? 1 : 0;
                 $this->_model->CREATED_AT = Date::nowTime();
             } elseif($this->scenario == 'userEdit') {
@@ -147,6 +150,7 @@ class ReceiveAddressForm extends Model
                 $this->_model->CITY = $this->city;
                 $this->_model->COUNTY = $this->county;
                 $this->_model->ADDRESS = $this->address;
+                $this->_model->ZIP_CODE = $this->zipCode;
                 $this->_model->IS_DEFAULT = $this->isDefault ? 1 : 0;
                 $this->_model->UPDATED_AT = Date::nowTime();
             } elseif($this->scenario == 'userIsDefault') {

+ 18 - 7
common/models/forms/ShopGoodsForm.php

@@ -51,7 +51,7 @@ class ShopGoodsForm extends Model
     {
         return [
             [['id','sellDiscount','giftType','sellType','goodsNo', 'goodsName', 'unit', 'sellPrice', 'marketPrice', 'pricePv', 'storeNums', 'content', 'sort','status','cover'], 'trim'],
-            [['goodsName','sellDiscount','giftType','goodsNo', 'storeNums','sellPrice','marketPrice','pricePv', 'sort','status','cover'], 'required'],
+            [['goodsName','sellDiscount','giftType','goodsNo', 'storeNums','sellPrice','marketPrice','pricePv', 'sort','status', 'type'], 'required'],
             [['id'], 'required', 'on'=>'edit'],
             [['id'], 'exist', 'targetClass'=>ShopGoods::class, 'targetAttribute'=>'ID'],
             [['sellPrice','marketPrice','pricePv'], 'price'],
@@ -59,7 +59,8 @@ class ShopGoodsForm extends Model
             [['selectedIds'], 'isSelected'],
             [['sort'], 'isSort'],
             [['sellDiscount'], 'isDiscount'],
-            [['statusdate','goodsstatusdate'],'match','pattern'=>'/^[0-1]{1,1}$/']
+            [['statusdate','goodsstatusdate'],'match','pattern'=>'/^[0-1]{1,1}$/'],
+            [['type'], 'isType'],
         ];
     }
 
@@ -82,6 +83,7 @@ class ShopGoodsForm extends Model
             'storeNums' => '库存',
             'content' => '产品详情',
             'listOrder' => '排序',
+            'type' => '商品来源',
         ];
     }
 
@@ -93,8 +95,8 @@ class ShopGoodsForm extends Model
     {
         $parentScenarios =  parent::scenarios();
         $customScenarios = [
-            'add' => ['goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv','storeNums', 'content','sort','cover'],
-            'edit' => ['id','goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv', 'storeNums', 'content','sort','cover','statusdate','goodsstatusdate','goodsdate'],
+            'add' => ['goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv','storeNums', 'content','sort','cover', 'type'],
+            'edit' => ['id','goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv', 'storeNums', 'content','sort','cover','statusdate','goodsstatusdate','goodsdate', 'type'],
             'changeStatus' => ['selectedIds', 'status'],
         ];
         return array_merge($parentScenarios, $customScenarios);
@@ -139,6 +141,14 @@ class ShopGoodsForm extends Model
         }
     }
 
+    /**
+     * 根据商品类型处理支付类型
+     * @param $attributes
+     */
+    public function isType() {
+        $this->sellType = $this->type == '1' ? '1,2,3' : '4';
+    }
+
     /**
      * 添加
      * @return ShopGoods|null
@@ -157,7 +167,7 @@ class ShopGoodsForm extends Model
             $shopGoods->SELL_DISCOUNT = $this->sellDiscount;
             $shopGoods->GIFT_TYPE = implode(',',$this->giftType);
             // $shopGoods->SELL_TYPE = implode(',',$this->sellType);
-            $shopGoods->SELL_TYPE = '1,2,3';
+            $shopGoods->SELL_TYPE = $this->sellType;
             $shopGoods->GOODS_NO = $this->goodsNo;
             $shopGoods->UNIT = $this->unit ? $this->unit : '个';
             $shopGoods->COVER = $this->cover ? $this->cover : '';
@@ -168,7 +178,7 @@ class ShopGoodsForm extends Model
             $shopGoods->CONTENT = $this->content;
             $shopGoods->STORE_NUMS = $this->storeNums;
             $shopGoods->SORT = $this->sort;
-            $shopGoods->CATE_ID = '1';
+            $shopGoods->CATE_ID = $this->type;
             $shopGoods->CREATED_AT = Date::nowTime();
             if (!$shopGoods->save()) {
                 throw new Exception(Form::formatErrorsForApi($shopGoods->getErrors()));
@@ -198,10 +208,11 @@ class ShopGoodsForm extends Model
             $model = $this->_model;
             $model->GOODS_NAME = $this->goodsName;
             $model->TYPE = 0;
+            $model->CATE_ID = $this->type;
             $model->SELL_DISCOUNT = $this->sellDiscount;
             $model->GIFT_TYPE = implode(',',$this->giftType);
             // $model->SELL_TYPE = implode(',',$this->sellType);
-            $model->SELL_TYPE = '1,2,3';
+            $model->SELL_TYPE = $this->sellType;
             $model->GOODS_NO = $this->goodsNo;
             $model->UNIT = $this->unit ? $this->unit : '个';
             $model->COVER = $this->cover ? $this->cover : '';

+ 24 - 2
console/controllers/ToolController.php

@@ -176,7 +176,7 @@ class ToolController extends BaseController
     }
 
     /**
-     * 自动送钉钉提醒
+     * 自动送钉钉提醒(会员端)
      */
     public function actionAutoSendDingTalkFrontend() {
         $ip = 'https://fapi.ekhkad.com';
@@ -198,7 +198,7 @@ class ToolController extends BaseController
     }
 
     /**
-     * 自动送钉钉提醒
+     * 自动送钉钉提醒(管理端)
      */
     public function actionAutoSendDingTalkBackend() {
         $ip = 'https://bapi.ekhkad.com';
@@ -218,4 +218,26 @@ class ToolController extends BaseController
             LoggerTool::error($error);
         }
     }
+
+    /**
+     * 自动推送跨境商品到wst
+     */
+    public function actionAutoLogistics() {
+        $ip = 'https://fapi.ekhkad.com';
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $ip . '/v1/shop/logistics-auto');
+        curl_setopt($curl, CURLOPT_TIMEOUT, 5000);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        $res = curl_exec($curl);
+        if ($res) {
+            curl_close($curl);
+            LoggerTool::info($res);
+        } else {
+            $error = curl_errno($curl);
+            curl_close($curl);
+            LoggerTool::error($error);
+        }
+    }
 }

+ 22 - 2
frontendApi/config/params.php

@@ -1,6 +1,26 @@
 <?php
 return [
     'adminEmail' => 'admin@example.com',
-    'noCheckTokenActions' => ['v1/oauth/login', 'v1/oauth/refresh-access-token', 'v1/oauth/refresh-refresh-token', 'v1/oauth/refresh-token', 'v1/site/days-diff', 'v1/site/page-data', 'v1/site/captcha', 'v1/oauth/is-login-verify','v1/oauth/login-by-backend', 'v1/oauth/no-login-modify-password', 'v1/site/doc', 'v1/site/config', 'v1/site/send-notice',],
-    'noCheckPermissionActions' => [],
+    'noCheckTokenActions' => [
+        'v1/oauth/login',
+        'v1/oauth/refresh-access-token',
+        'v1/oauth/refresh-refresh-token',
+        'v1/oauth/refresh-token',
+        'v1/site/days-diff',
+        'v1/site/page-data',
+        'v1/site/captcha',
+        'v1/oauth/is-login-verify',
+        'v1/oauth/login-by-backend',
+        'v1/oauth/no-login-modify-password',
+        'v1/site/doc',
+        'v1/site/config',
+        'v1/site/send-notice',
+        'v1/shop/verify-approach-order',
+        'v1/shop/logistics',
+        'v1/shop/logistics-auto',
+    ],
+    'noCheckPermissionActions' => [
+        'shop/logistics',
+        'shop/logistics-auto',
+    ],
 ];

+ 6 - 1
frontendApi/config/urlManagerRules.php

@@ -73,7 +73,12 @@ return [
             'GET pay-success' => 'pay-success',
             'GET order-list' => 'order-list',
             'GET dec-order-list' => 'dec-order-list',
-            'GET order-list' => 'order-list',
+            'POST sure-approach-order' => 'sure-approach-order',
+            'POST verify-approach-order' => 'verify-approach-order',
+            'POST delete-approach-order' => 'delete-approach-order',
+            'POST i-pay88' => 'i-pay88',
+            'GET logistics' => 'logistics',
+            'GET logistics-auto' => 'logistics-auto',
         ],
     ],
     [

+ 416 - 0
frontendApi/modules/v1/components/IPay88.php

@@ -0,0 +1,416 @@
+<?php
+
+namespace frontendApi\modules\v1\components;
+
+use common\helpers\LoggerTool;
+use Exception;
+use Yii;
+
+class IPay88 {
+
+    /**
+     * Normal iPay88 payment method
+     */
+    const TRANSACTION_TYPE_PAYMENT = 'payment';
+
+    /**
+     * Normal iPay88 recurring payment subscription
+     */
+    const TRANSACTION_TYPE_RECURRING_SUBSCRIPTION = 'recurring_subscription';
+
+    /**
+     * Normal iPay88 recurring payment termination
+     */
+    const TRANSACTION_TYPE_RECURRING_TERMINATION = 'recurring_termination';
+
+    /**
+     * Merchant code assigned by iPay88
+     */
+    public $merchantCode;
+
+    /**
+     * Merchant Key assigned by iPay88
+     */
+    public $merchantKey;
+
+    /**
+     * Currency Code max length 5
+     */
+    public $currencyCode;
+
+    /**
+     * Merchant code assigned by iPay88
+     */
+    public $responseUrl;
+
+    /*
+     * Response Url or Return Url after payment
+     */
+    public $paymentUrl;
+
+    /*
+     * Backend Url or Notify Url after payment (Send response by iPay88 server)
+     */
+    public $backendUrl;
+
+    /*
+     * Requery from iPay88 server regarding bill details
+     */
+    public $requeryUrl;
+
+    /*
+    * ipay88 Recurring Payment Url
+    */
+    public $recurringUrlSubscription;
+
+    /*
+    * ipay88 Recurring Payment Termination Url
+    */
+    public $recurringUrlTermination;
+
+
+    /*
+     * Details to be sent to IPay88 for payment request.
+     */
+    private $paymentRequest = array(
+        'MerchantCode', // Merchant code assigned by iPay88. (length 20)
+        'PaymentId', // (Optional) (int)
+        'RefNo', // Unique merchant transaction number / Order ID (Retry for same RefNo only valid for 30 mins). (length 20)
+        'Amount', // Payment amount with two decimals.
+        'Currency', // (length 5)
+        'ProdDesc', // Product description. (length 100)
+        'UserName', // Customer name. (length 100)
+        'UserEmail', // Customer email.  (length 100)
+        'UserContact', // Customer contact.  (length 20)
+        'Remark', // (Optional) Merchant remarks. (length 100)
+        'Lang', // (Optional) Encoding type:- ISO-8859-1 (English), UTF-8 (Unicode), GB2312 (Chinese Simplified), GD18030 (Chinese Simplified), BIG5 (Chinese Traditional)
+        'Signature',
+        'ResponseURL',
+        'BackendURL',
+    );
+
+
+    /*
+     * Details to be sent to iPay88 for recurring subscription payment request.
+     */
+    private $recurringSubscriptionRequest = array(
+        'MerchantCode', // Merchant code assigned by iPay88. (length 20)
+        'RefNo', // Unique merchant transaction number / Order ID. (length 20)
+        'FirstPaymentDate', // (ddmmyyyy)
+        'Currency', // MYR only. (length 5)
+        'Amount', // Payment amount with two decimals.
+        'NumberOfPayments', // (int)
+        'Frequency', // Frequency type; 1 - Monthly, 2 - Quarterly, 3 - Half-Yearly, 4 - Yearly. (int)
+        'Desc', // Product description. (length 100)
+        'CC_Name', // Name printed on credit card. (length 100)
+        'CC_PAN', // 16-digit credit card number (Visa/Mastercard). (length 16)
+        'CC_CVC', // 3-digit verification code behind credit card. (length 3)
+        'CC_ExpiryDate', // Credit card expiry date. (mmyyyy)
+        'CC_Country', // Credit card issuing country. (length 100)
+        'CC_Bank', // Credit card issuing bank. (length 100)
+        'CC_Ic', // Credit card holder IC / Passport number. (length 50)
+        'CC_Email', // Credit card holder email address. (length 255)
+        'CC_Phone', // Credit card phone number. (length 100)
+        'CC_Remark', // (Optional) Remarks. (varchar 100)
+        'P_Name', // Subscriber name as printed in IC / Passport. (length 100)
+        'P_Email', // Subscriber email address. (length 255)
+        'P_Phone', // Subscriber phone number. (length 100)
+        'P_Addrl1', // Subscriber address line 1. (length 100)
+        'P_Addrl2', // (Optional) Subscriber address line 2. (length 100)
+        'P_City', // Subscriber city. (length 100)
+        'P_State', // Subscriber state. (length 100)
+        'P_Zip', // Subscriber zip code. (length 100)
+        'P_Country', // Subscriber country. (varchar 100)
+        'BackendURL', // Payment backend response page. (length 255)
+        'Signature', // SHA1 signature. (length 100)
+    );
+
+
+    /*
+     * Get required payment fields
+     */
+    public function getPaymentFields($reqParams = null, $paymentType) {
+        $retnParams = array();
+        try {
+            if (isset($reqParams) && (count($reqParams) > 0)) {
+
+                if (isset($paymentType) && $paymentType != "") {
+                    $paymentType = strtolower(trim($paymentType));
+                    switch ($paymentType) {
+                        case 'payment':
+                            $retnParams = $this->__getPaymentField($reqParams, $paymentType);
+                            break;
+                        case 'recurring_subscription':
+                            $retnParams = $this->__getRecurringSubscriptionField($reqParams, $paymentType);
+                            break;
+                        case 'recurring_termination':
+                            $retnParams = $this->__getRecurringTerminationField($reqParams, $paymentType);
+                            break;
+                    }
+                } else {
+                    throw new Exception("Ipay: Payment method missing");
+                }
+            } else {
+                throw new Exception("Ipay: Required Parameters missing");
+            }
+        } catch (Exception $e) {
+            LoggerTool::error(['iPay88-getPaymentFields', $e->getLine(), $e->getMessage()]);
+        }
+        return $retnParams;
+    }
+
+    /*
+     * Code for hex2bin
+     */
+    public function _hex2bin($hexSource) {
+        $bin = '';
+        for ($i = 0; $i < strlen($hexSource); $i = $i + 2) {
+            $bin .= chr(hexdec(substr($hexSource, $i, 2)));
+        }
+        return $bin;
+    }
+
+    /*
+     * Get payment fields for normal payment fields
+     */
+    public function __getPaymentField($reqParams, $paymentType) {
+        $retnParams = array();
+        foreach ($this->paymentRequest as $pymtKey) {
+            if (isset($reqParams[$pymtKey])) {
+                $retnParams[$pymtKey] = $reqParams[$pymtKey];
+            } else {
+
+                switch ($pymtKey) {
+                    case 'MerchantCode':
+                        $retnParams[$pymtKey] = $this->merchantCode;
+                        break;
+                    case 'Currency':
+                        $retnParams[$pymtKey] = $this->currencyCode;
+                        break;
+                    case 'Lang':
+                        $retnParams[$pymtKey] = 'UTF-8'; //(Optional) Encoding type:- ISO-8859-1 (English), UTF-8 (Unicode), GB2312 (Chinese Simplified), GD18030 (Chinese Simplified), BIG5 (Chinese Traditional)
+                        break;
+                    case 'Signature':
+                        $retnParams[$pymtKey] = $this->__createSignature($retnParams, $paymentType); // SHA1 signature.
+                        break;
+                    case 'ResponseURL':
+                        $retnParams[$pymtKey] = $this->responseUrl; // (Optional) Payment response page.
+                        break;
+                    case 'BackendURL':
+                        $retnParams[$pymtKey] = $this->backendUrl; // (Optional) BackendURL but should security purpose
+                        break;
+                }
+            }
+        }
+
+        return $retnParams;
+    }
+
+    /*
+     * Get payment fields for recurring payment
+     */
+    public function __getRecurringSubscriptionField($reqParams, $paymentType) {
+        $retnParams = array();
+        foreach ($this->recurringSubscriptionRequest as $pymtKey) {
+            if (isset($reqParams[$pymtKey])) {
+                $retnParams[$pymtKey] = $reqParams[$pymtKey];
+            } else {
+
+                switch ($pymtKey) {
+                    case 'MerchantCode':
+                        $retnParams[$pymtKey] = $this->merchantCode;
+                        break;
+                    case 'Currency':
+                        $retnParams[$pymtKey] = $this->currencyCode;
+                        break;
+                    case 'Lang':
+                        $retnParams[$pymtKey] = 'UTF-8'; //(Optional) Encoding type:- ISO-8859-1 (English), UTF-8 (Unicode), GB2312 (Chinese Simplified), GD18030 (Chinese Simplified), BIG5 (Chinese Traditional)
+                        break;
+                    case 'Signature':
+                        $retnParams[$pymtKey] = $this->__createSignature($retnParams, $paymentType); // SHA1 signature.
+                        break;
+                    case 'ResponseURL':
+                        $retnParams[$pymtKey] = $this->responseUrl; // (Optional) Payment response page.
+                        break;
+                    case 'BackendURL':
+                        $retnParams[$pymtKey] = $this->backendUrl; // (Optional) BackendURL but should security purpose
+                        break;
+                }
+            }
+        }
+
+        return $retnParams;
+    }
+
+
+
+    /*
+     * Get payment fields for recurring payment termination
+     */
+    public function __getRecurringTerminationField($reqParams, $paymentType) {
+        $retnParams = array();
+        foreach ($this->recurringSubscriptionRequest as $pymtKey) {
+            if (isset($reqParams[$pymtKey])) {
+                $retnParams[$pymtKey] = $reqParams[$pymtKey];
+            } else {
+
+                switch ($pymtKey) {
+                    case 'MerchantCode':
+                        $retnParams[$pymtKey] = $this->merchantCode;
+                        break;
+                }
+            }
+        }
+
+        return $retnParams;
+    }
+
+    /*
+     * Create signature for payment
+     */
+    public function __createSignature($signatureParams, $paymentType) {
+        $signature = '';
+        if (isset($signatureParams)) {
+            $_signatureParams = array();
+            if ($paymentType == self::TRANSACTION_TYPE_PAYMENT) {
+                $_signatureParams = array('MerchantCode', 'RefNo', 'Amount', 'Currency');
+            } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_SUBSCRIPTION) {
+                $_signatureParams = array('MerchantCode', 'RefNo', 'FirstPaymentDate', 'Currency', 'Amount', 'NumberOfPayments', 'Frequency', 'CC_PAN');
+            } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_TERMINATION) {
+                $_signatureParams = array('MerchantCode', 'RefNo');
+            }
+
+
+            foreach ($_signatureParams as $val) {
+                if (!isset($signatureParams[$val])) {
+                    throw new Exception("Ipay: Missing required parameters for signature.");
+                    return false;
+                }
+            }
+        }
+
+        // Make sure the order is correct.
+        if ($paymentType == self::TRANSACTION_TYPE_PAYMENT) {
+            $signature .= $this->merchantKey;
+            $signature .= $signatureParams['MerchantCode'];
+            //$signature .= $signatureParams['PaymentId'];
+            $signature .= $signatureParams['RefNo'];
+            $signature .= preg_replace("/[^\d]+/", "", $signatureParams['Amount']);
+            $signature .= $signatureParams['Currency'];
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_SUBSCRIPTION) {
+            $signature .= $signatureParams['MerchantCode'];
+            $signature .= $this->merchantKey;
+            $signature .= $signatureParams['RefNo'];
+            $signature .= $signatureParams['FirstPaymentDate'];
+            $signature .= $signatureParams['Currency'];
+            $signature .= $signatureParams['Amount'];
+            $signature .= $signatureParams['NumberOfPayments'];
+            $signature .= $signatureParams['Frequency'];
+            $signature .= $signatureParams['CC_PAN'];
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_TERMINATION) {
+            $signature .= $signatureParams['MerchantCode'];
+            $signature .= $this->merchantKey;
+            $signature .= $signatureParams['RefNo'];
+        }
+        // Hash the signature.
+        //return $signature = base64_encode($this->_hex2bin(sha1($signature)));
+        return $signature = hash('sha256', $signature);
+    }
+
+    /*
+     * Get url for respective payment redirection url
+     */
+    public function getTransactionUrl($paymentType) {
+        if ($paymentType == self::TRANSACTION_TYPE_PAYMENT) {
+            return $this->paymentUrl;
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_SUBSCRIPTION) {
+            return $this->recurringUrlSubscription;
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_TERMINATION) {
+            return $this->recurringUrlTermination;
+        }
+    }
+
+
+    /*
+     * iPay88 payment signature validation
+     */
+    public function checkiPay88Signature($reqParams) {
+        $status = 'fail';
+        try {
+            if (isset($reqParams) && count($reqParams) > 0) {
+                $orginalKey = $this->merchantKey . $this->merchantCode;
+                if (isset($reqParams['RefNo'])) {
+                    $orginalKey .=$reqParams['RefNo'];
+                }
+
+                if (isset($reqParams['Amount'])) {
+                    $orginalKey .=preg_replace("/[^\d]+/", "", $reqParams['Amount']);
+                }
+                $orginalKey .= $this->currencyCode;
+                if (isset($reqParams['Status'])) {
+                    $orginalKey .=$reqParams['Status'];
+                }
+
+                $orginalKeyGen = base64_encode($this->_hex2bin(sha1($orginalKey)));
+                $returnKey = $this->merchantKey;
+                if (isset($reqParams['MerchantCode'])) {
+                    $returnKey .=$reqParams['MerchantCode'];
+                }
+
+
+                if (isset($reqParams['RefNo'])) {
+                    $returnKey .=$reqParams['RefNo'];
+                }
+                if (isset($reqParams['Amount'])) {
+                    $returnKey .=preg_replace("/[^\d]+/", "", $reqParams['Amount']);
+                }
+                if (isset($reqParams['Currency'])) {
+                    $returnKey .=$reqParams['Currency'];
+                }
+                if (isset($reqParams['Status'])) {
+                    $returnKey .=$reqParams['Status'];
+                }
+
+
+                $returnKeyGen = base64_encode($this->_hex2bin(sha1($returnKey)));
+                if ($orginalKeyGen === $returnKeyGen) {
+                    $status = 'success';
+                }
+            } else {
+                throw new Exception("Ipay::checkiPay88Signature: Params missing");
+            }
+        } catch (exception $e) {
+            LoggerTool::error(['iPay88-checkiPay88Signature', $e->getLine(), $e->getMessage()]);
+        }
+
+        return $status;
+    }
+
+    /*
+     * Curl hit to get bill deyails
+     */
+    public function requeryPayment($rawPostData) {
+        try {
+            $result = '';
+            if (is_callable('curl_init')) {
+                if (isset($rawPostData) && $rawPostData != "") {
+                    $ch = curl_init();
+                    $url = $this->requeryUrl . '?' . $rawPostData;
+                    curl_setopt($ch, CURLOPT_URL, $url);
+                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+                    $result = curl_exec($ch);
+                    curl_close($ch);
+                } else {
+                    throw new Exception("Ipay::requeryPayment: No request string");
+                }
+            } else {
+                throw new Exception("Ipay::requeryPayment: Curl not enabled");
+            }
+        } catch (exception $e) {
+            LoggerTool::error(['iPay88-requeryPayment', $e->getLine(), $e->getMessage()]);
+        }
+
+        return $result;
+    }
+
+}

+ 1 - 1
frontendApi/modules/v1/controllers/ConfigController.php

@@ -86,7 +86,7 @@ class ConfigController extends BaseController
         $condition = ' AND USER_ID=:USER_ID';
         $params[':USER_ID'] = \Yii::$app->user->id;
         $data = ReceiveAddress::lists($condition, $params, [
-            'SELECT' => 'ID,CONSIGNEE,MOBILE,PROVINCE,CITY,COUNTY,ADDRESS,IS_DEFAULT',
+            'SELECT' => 'ID,CONSIGNEE,MOBILE,PROVINCE,CITY,COUNTY,ADDRESS,ZIP_CODE,IS_DEFAULT',
             'orderBy' => 'IS_DEFAULT DESC,CREATED_AT DESC',
             'useSlaves' => true,
         ]);

+ 307 - 0
frontendApi/modules/v1/controllers/ShopController.php

@@ -8,10 +8,18 @@
 
 namespace frontendApi\modules\v1\controllers;
 
+use common\helpers\Cache;
 use common\helpers\Date;
+use common\helpers\DingTalk;
 use common\helpers\Form;
+use common\helpers\LoggerTool;
+use common\helpers\Logistics;
+use common\helpers\Tool;
 use common\helpers\user\Info;
+use common\models\ApproachOrder;
+use common\models\ApproachOrderGoods;
 use common\models\DecOrder;
+use common\models\forms\ApproachOrderForm;
 use common\models\forms\DeclarationForm;
 use common\models\forms\OrderForm;
 use common\models\Order;
@@ -22,10 +30,17 @@ use common\models\ShopGoods;
 use common\models\User;
 use common\models\UserBonus;
 use common\models\UserWallet;
+use Yii;
+use yii\data\Pagination;
+use yii\db\Query;
+use yii\web\HttpException;
 
 class ShopController extends BaseController {
+
     public $modelClass = DecOrder::class;
 
+    const TRANSACTION_TYPE_PAYMENT = 'payment';
+
     /**
      * 商品列表
      * @return mixed
@@ -45,6 +60,7 @@ class ShopController extends BaseController {
         ]);
         foreach ($data['list'] as $key => $value) {
             $data['list'][$key]['DISCOUNT'] = $value['SELL_DISCOUNT']*100;
+            $data['list'][$key]['CATE'] = ShopGoods::GOODS_TYPE[$value['CATE_ID']]['name'] ?? '';
         }
         return static::notice($data);
     }
@@ -180,6 +196,60 @@ class ShopController extends BaseController {
         return static::notice($data);
     }
 
+//    /**
+//     * 我的订单
+//     * @return mixed
+//     * @throws \yii\web\HttpException
+//     */
+//    public function actionOrderList() {
+//        $uname = Info::getUserNameByUserId(\Yii::$app->user->id);
+//        $condition = " O.IS_DELETE = 0 AND O.ORDER_TYPE='FX' AND (O.USER_ID=:USER_ID OR O.CREATE_USER='$uname')";
+//        $params[':USER_ID'] = \Yii::$app->user->id;
+//        $orderQuery = Order::find()
+//            ->alias('O')
+//            ->where($condition, $params)
+//            ->select('O.*,U.REAL_NAME,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV,OG.ORDER_SN,OG.GOODS_ID')
+//            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+//            ->join('LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+//            ->orderBy('O.CREATED_AT DESC');
+//
+//        // 订单中间表只查询待支付和支付失败的订单
+//        $params[':NOT_PAID'] = \Yii::$app->params['orderStatus']['notPaid']['value'];   // 待支付
+//        $params[':FAIL_PAID'] = \Yii::$app->params['orderStatus']['failPaid']['value'];   // 支付失败
+//        $orderStandardQuery = ApproachOrder::find()
+//            ->alias('O')
+//            ->where($condition . ' AND (O.STATUS = :NOT_PAID OR O.STATUS = :FAIL_PAID)', $params)
+//            ->select('O.*,U.REAL_NAME,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV,OG.ORDER_SN,OG.GOODS_ID')
+//            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+//            ->join('LEFT JOIN', ApproachOrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+//            ->orderBy('O.CREATED_AT DESC');
+//
+//        $queryAll = $orderQuery->union($orderStandardQuery, true);
+//        $query = (new Query())->from(['Q' => $queryAll])->select('Q.*')->distinct()->orderBy(['CREATED_AT' => SORT_DESC]);
+//
+//        $totalCount = $query->count();
+//        $pagination = new Pagination(['totalCount' => $totalCount, 'pageSize' => \Yii::$app->request->get('pageSize')]);
+//        $lists = $query->offset($pagination->offset)->limit($pagination->limit)->all();
+//
+//        $data = [
+//            'list' => $lists,
+//            'currentPage'=>$pagination->page,
+//            'totalPages'=>$pagination->pageCount,
+//            'startNum' => $pagination->page * $pagination->pageSize + 1,
+//            'totalCount' => $pagination->totalCount,
+//            'pageSize' => $pagination->pageSize,
+//        ];
+//
+//        foreach ($data['list'] as $key => $value) {
+//            $data['list'][$key]['ORDER_TYPE'] = $value['ORDER_TYPE'] == 'ZC' ? '首单' : '复消';
+//            $data['list'][$key]['PAY_AT'] = $value['PAY_AT'] ? Date::convert($value['PAY_AT'],'Y-m-d H:i:s') : '';
+//            $data['list'][$key]['PAY_TYPE'] = ShopGoods::payTypes()[$value['PAY_TYPE']]['name'] ?? '';
+//            $data['list'][$key]['STATUS'] = \Yii::$app->params['orderStatus'][$value['STATUS']]['label'] ?? '';
+//        }
+//
+//        return $data;
+//    }
+
     /**
      * 会员复消
      */
@@ -237,6 +307,243 @@ class ShopController extends BaseController {
                 return static::notice(Form::formatErrorsForApi($formModel->getErrors()),400);
             }
         }
+
+        return static::notice('无效请求');
+    }
+
+    /**
+     * 确认订单
+     */
+    public function actionSureApproachOrder(){
+        if (\Yii::$app->request->isPost) {
+            $formModel = new ApproachOrderForm();
+            $formModel->scenario = 'userOrder';
+            $formModel->remark = '复销备注';
+            $post = \Yii::$app->request->post();
+            $post['type'] = DeclarationForm::TYPE_FX;
+            if ($formModel->load($post, '') && $order = $formModel->add()) {
+                return static::notice($order);
+            } else {
+                return static::notice(Form::formatErrorsForApi($formModel->getErrors()),400);
+            }
+        }
+
+        return static::notice('无效请求');
     }
 
+    /**
+     * iPay88支付成功的webhook.
+     * @throws HttpException
+     * @throws \Exception
+     */
+    public function actionVerifyApproachOrder() {
+        // iPay88支付成功的webhook.
+        $rawPostData = file_get_contents('php://input');
+        LoggerTool::notice(['actionVerifyApproachOrder', $rawPostData]);
+        $data = [];
+        if (strlen($rawPostData) > 0) {
+            $rawPostArray = explode('&', $rawPostData);
+            foreach ($rawPostArray as $raw) {
+                $raw = explode('=', $raw);
+                if (count($raw) == 2)
+                    $data[$raw[0]] = urldecode($raw[1]);
+            }
+        }
+
+        // 支付webhook回调日志
+        Tool::approachOrderCall($data);
+
+        try {
+            // 订单状态
+            $orderStatus = ($data['Status'] == '1') ? \Yii::$app->params['orderStatus']['paid']['value'] : \Yii::$app->params['orderStatus']['failPaid']['value'];
+
+            $oderSn = $data['RefNo'] ?? '';
+
+            $formModel =  new ApproachOrderForm();
+            $formModel->scenario = 'verifyPay';
+            $load = [
+                'sn' => $oderSn,
+                'scenario' => 'verifyPay',
+                'status' => $orderStatus,
+                'note' => [
+                    'MerchantCode' => $data['MerchantCode'],
+                    'PaymentId' => $data['PaymentId'],
+                    'status' => $data['Status'],
+                    'Signature' => $data['Signature'],
+                    'Currency' => $data['Currency'],
+                    'Amount' => $data['Amount'],
+                    'TransId' => $data['TransId'],
+                    'TranDate' => $data['TranDate'],
+                    'BankMID' => $data['BankMID'],
+                    'CCNo' => $data['CCNo'],
+
+                ],
+            ];
+
+            if ($formModel->load($load, '') && $result = $formModel->verifyPayOnline()) {
+                LoggerTool::info($result);
+
+                return http_response_code(200);
+            } else {
+                LoggerTool::error(Form::formatErrorsForApi($formModel->getErrors()));
+                return http_response_code(500);
+            }
+        } catch (\Exception $e) {
+            LoggerTool::error(sprintf('actionVerifyApproachOrderError: File[%s], Line:[%s], Message[%s]', $e->getFile(), $e->getLine(), $e->getMessage()));
+            return http_response_code(500);
+        }
+    }
+
+    /**
+     * 删除准订单
+     */
+    public function actionDeleteApproachOrder()
+    {
+        $orderSn = \Yii::$app->request->post('orderSn');
+        // 删除订单中间表
+        ApproachOrder::deleteAll('SN = :SN', [':SN' => $orderSn]);
+        // 删除订单商品中间表
+        ApproachOrderGoods::deleteAll('ORDER_SN = :ORDER_SN', [':ORDER_SN' => $orderSn]);
+
+        return static::notice('');
+    }
+
+    /**
+     * iPay88支付
+     * @return mixed
+     * @throws HttpException
+     */
+    public function actionIPay88()
+    {
+        // 订单ID
+        $paymentParams['RefNo'] = \Yii::$app->request->post('RefNo');
+        // 订单金额,元=>分
+        $money = \Yii::$app->request->post('Amount');
+        // 马来币汇率
+        $exchangeRateMYR = floatval(Cache::getSystemConfig()['exchangeRateMYR']['VALUE'] ?? 0);
+        // 计算马来币
+        $amount = number_format(round($money * $exchangeRateMYR), 2, '.', '');
+//        $amount = number_format($money, 2, '.', '');
+//        $amount = number_format(1, 2, '.', ''); // TODO: 测试
+        $paymentParams['Amount'] = str_replace('.', '', $amount);
+        // (Optional) (int)
+        $paymentParams['PaymentId'] = '182';   // 2=信用卡 182=银联
+        // Product description. (length 100)
+        $paymentParams['ProdDesc'] = 'Pay for sales';
+        // Customer name. (length 100)
+        $paymentParams['UserName'] = 'MY32';
+        $paymentParams['SignatureType'] = 'SHA256';
+        // Customer email.  (length 100)
+        $paymentParams['UserEmail'] = 'ek_dummy25@elken.com';
+        // Customer contact.  (length 20)
+        $paymentParams['UserContact'] = '60172249692';
+
+        // (Optional) Merchant remarks. (length 100)
+        //$paymentParams['Remark'] = 'Here is the description';
+        //merchantkey + merchantcode+ reference Number + amount in cent + currency_code
+        $paymentFields = \Yii::$app->iPay88->getPaymentFields($paymentParams, self::TRANSACTION_TYPE_PAYMENT);
+
+        $transactionUrl = \Yii::$app->iPay88->getTransactionUrl(self::TRANSACTION_TYPE_PAYMENT);
+        $paymentFields['Amount'] = $amount;
+        $res = [
+            'paymentFields' => $paymentFields,
+            'transactionUrl' => $transactionUrl,
+        ];
+
+        return static::notice($res);
+    }
+
+    /**
+     * 推送订单到wst仓储系统
+     * @throws HttpException
+     * @throws \Exception
+     */
+    public function actionLogistics()
+    {
+        $orderSn = \Yii::$app->request->get('sn');
+        $order = Order::find()
+            ->where('SN=:ORDER_SN', [':ORDER_SN' => $orderSn])
+            ->asArray()
+            ->one();
+
+        if (!$order) {
+            return static::notice('订单【' . $orderSn . '】不存在');
+        }
+        if ($order['SEND_AT'] > 0) {
+            return static::notice('订单【' . $orderSn . '】不可重复推送');
+        }
+
+        $logistics = new Logistics();
+        $response = $logistics->createOrder($order);
+        LoggerTool::info(['actionLogistics', $response]);
+        if ($response['success'] == 1) {
+            // 更新db中订单推送成功状态
+            if (Order::updateAll(['SEND_AT' => time()], 'SN=:SN', [':SN' => $orderSn])) {
+                return static::notice($response);
+            } else {
+                return static::notice($orderSn . ' 推送wst系统成功, 更新状态失败');
+            }
+        }
+
+        return static::notice($orderSn . ' 推送wst系统失败');
+    }
+
+    /**
+     * @throws HttpException
+     * @throws \Exception
+     */
+    public function actionLogisticsAuto()
+    {
+        $createdAtStart = strtotime('yesterday');
+        $createdAtEnd = strtotime(date('Y-m-d')) - 1;
+        // 早0点推送,前一天0-24点的订单
+        $orderList = Order::find()
+            ->where(
+                '(CREATED_AT BETWEEN :CREATED_AT_START AND :CREATED_AT_END) AND STATUS=:STATUS AND SEND_AT=:SEND_AT AND PAY_TYPE=:PAY_TYPE',
+                [
+                    ':CREATED_AT_START' => $createdAtStart,
+                    ':CREATED_AT_END' => $createdAtEnd,
+                    ':STATUS' => \Yii::$app->params['orderStatus']['paid']['value'],
+                    ':SEND_AT' => 0,
+                    'PAY_TYPE' => 'online',
+                ]
+            )
+            ->asArray()
+            ->all();
+
+        if (!$orderList) {
+            LoggerTool::info(['推送wst系统终止,原因:无订单', $createdAtStart, $createdAtEnd]);
+            return static::notice('推送wst系统终止,原因:无订单');
+        }
+
+        $orderSnSuccess = [];
+        $orderSnFailed = [];
+        $logistics = new Logistics();
+        foreach ($orderList as $order) {
+            // 发送wst仓库系统
+            $response = $logistics->createOrder($order);
+            LoggerTool::warning($response);
+            if ($response['success'] == 1) {
+                // 写入mongo
+                Tool::wstOrderCall($response['data']);
+                $orderSnSuccess[] = $order['SN'];
+            } else {
+                $orderSnFailed[] = $order['SN'];
+            }
+
+            $orderSnSuccess[] = $response;
+        }
+
+
+        // 更新db中订单推送成功状态
+        if (count($orderSnSuccess) > 0) {
+            Order::updateAll(['SEND_AT' => time()], 'SN IN (:SN)', [':SN' => implode("', '", $orderSnSuccess)]);
+        }
+        if (count($orderSnFailed) > 0) {
+            // 发送预警通知
+            LoggerTool::error(['自动推送wst失败', $orderSnFailed]);
+        }
+
+        return static::notice(sprintf('wstLogisticsAutoSend success order count{%d}, orderSN[%s]; failed count{%d}, orderSN[%s]', count($orderSnSuccess), implode(', ', $orderSnSuccess), count($orderSnFailed), implode(', ', $orderSnFailed)));
+    }
 }

+ 1 - 1
frontendEle/.gitignore

@@ -8,7 +8,7 @@ yarn-error.log*
 /test/e2e/reports/
 selenium-debug.log
 src/utils/config_development.js
-src/utils/config_production.js
+src/utils/config.js
 config/index.js
 package-lock.json
 

+ 12 - 0
frontendEle/src/router/index.js

@@ -90,6 +90,18 @@ export const constantRouterMap = [
                     ],
                 },
             },
+            {
+              path: '/shop/order-overseas',
+              component: _import('shop/order-overseas'),
+              name: 'order-overseas',
+              meta: {
+                title: '海外商品结算',
+                breadcrumb: [
+                  {title: '首页', path: '/dashboard/index'},
+                  {title: '商城管理', path: '/shop/dec-order-list'}
+                ],
+              },
+            },
             {
                 path: '/shop/goods-detail/:id',
                 component: _import('shop/goods-detail'),

+ 10 - 0
frontendEle/src/views/config/receive-address-edit.vue

@@ -34,6 +34,12 @@
           </template>
           <el-input v-model="form.address"></el-input>
         </el-form-item>
+        <el-form-item>
+          <template slot="label">
+            邮政编码
+          </template>
+          <el-input v-model="form.zipCode"></el-input>
+        </el-form-item>
         <el-form-item>
           <template slot="label">
             是否默认
@@ -73,6 +79,7 @@
           mobile: '',
           areaSelected: [],
           address: '',
+          zipCode: '',
           isDefault: false,
         },
         loading: true,
@@ -90,6 +97,7 @@
           city: this.form.areaSelected[1] ? this.form.areaSelected[1] : '',
           county: this.form.areaSelected[2] ? this.form.areaSelected[2] : '',
           address: this.form.address,
+          zipCode: this.form.zipCode,
           isDefault: this.form.isDefault,
         }
         if (this.$route.name === 'config_receive-address-edit'){
@@ -102,6 +110,7 @@
             city: this.form.areaSelected[1] ? this.form.areaSelected[1] : '',
             county: this.form.areaSelected[2] ? this.form.areaSelected[2] : '',
             address: this.form.address,
+            zipCode: this.form.zipCode,
             isDefault: this.form.isDefault,
           }
         }
@@ -128,6 +137,7 @@
           this.form.areaSelected[1] = response.CITY
           this.form.areaSelected[2] = response.COUNTY
           this.form.address = response.ADDRESS
+          this.form.zipCode = response.ZIP_CODE
           this.form.isDefault = response.IS_DEFAULT === '1' ? true : false
           this.loading = false
         })

+ 2 - 1
frontendEle/src/views/config/receive-address-list.vue

@@ -4,10 +4,11 @@
       <el-table class="table-box" ref="multipleTable" :data="tableData" stripe style="width: 100%;">
         <el-table-column label="收货人" prop="CONSIGNEE" width="150"></el-table-column>
         <el-table-column label="手机号码" prop="MOBILE" width="150"></el-table-column>
+        <el-table-column label="邮编" prop="ZIP_CODE" width="120"></el-table-column>
         <el-table-column label="省/市" prop="PROVINCE_NAME" width="150"></el-table-column>
         <el-table-column label="市/区" prop="CITY_NAME" width="150"></el-table-column>
         <el-table-column label="区/县" prop="COUNTY_NAME" width="150"></el-table-column>
-        <el-table-column label="详细地址" prop="ADDRESS" ></el-table-column>
+        <el-table-column label="详细地址" prop="ADDRESS"></el-table-column>
         <el-table-column label="是否默认" width="100">
           <template slot-scope="scope">
             {{scope.row.IS_DEFAULT === '1' ? '是' : '否'}}

+ 47 - 10
frontendEle/src/views/shop/index.vue

@@ -16,6 +16,9 @@
 
                 </el-table-column>
 
+
+                <el-table-column label="商品来源" prop="CATE"></el-table-column>
+
                 <el-table-column label="图片" >
                      <template slot-scope="scope">
                         <el-image style="width: 100px; height: 100px" :src="tool.getLocaleLink(scope.row.COVER, '/files/')" :preview-src-list="[tool.getLocaleLink(scope.row.COVER, '/files/')]"></el-image>
@@ -45,12 +48,11 @@
 </template>
 
 <script>
-    import network from '@/utils/network'
-    import baseInfo from '@/utils/baseInfo'
-    import store from '@/utils/vuexStore'
-    import tool from '@/utils/tool'
-    import Pagination from '@/components/Pagination'
-    export default {
+import network from '@/utils/network'
+import tool from '@/utils/tool'
+import Pagination from '@/components/Pagination'
+
+export default {
         name: "index",
         components: {Pagination},
         mounted() {
@@ -74,6 +76,7 @@
                 is_go_order:true,
                 numList: [],
                 selectLock:false,
+                cate: [],
             }
         },
         watch: {
@@ -81,11 +84,26 @@
                 this.getData()
             }
         },
+        created() {
+          // network.getData('shop/logistics').then(response => {
+          //   console.log(response);
+          // }).catch(err => {
+          //   console.log(err);
+          // })
+        },
         methods: {
             handleChange(val){
                 console.log(val);
             },
             goToAccounts() {
+                if (this.cate.length > 1) {
+                  this.$message({
+                    message: '海内商品、海外商品只能选择一种',
+                    type: 'warning'
+                  })
+                  return false
+                }
+
                 this.is_go_order=true;
                 // this.multipleSelection.map((item,index)=>{
                 //     item.chose_num = parseInt(item.chose_num);
@@ -97,11 +115,10 @@
                 //         this.is_go_order=false
                 //     }
                 // });
-                console.log('this.multipleSelection',this.multipleSelection);
                 setTimeout(()=>{
                     if(this.is_go_order){
                         sessionStorage.setItem('order_goods',JSON.stringify(this.multipleSelection))
-                        this.$router.push({path: `/shop/order`})
+                        this.$router.push(this.cate[0] == '1' ? {path: `/shop/order`} : {path: `/shop/order-overseas`})
                     }
                 },0)
 
@@ -116,18 +133,38 @@
             handleSelectionChange(val) {
                 if( this.selectLock ) return;
                 let idx = -1,num;
+                this.cate = []
+                let cateList = []
                 for(let i in this.tableData){
                     for(let v in val){
                         if(val[v].ID==this.tableData[i].ID){
                             idx = i;
                             num = this.numList[idx];
                             val[v]["chose_num"] = num;
+                            cateList.push(val[v].CATE_ID)
                             break;
                         }
                     }
                 }
-                this.multipleSelection[this.currentPage] = val;
-                console.log(this.multipleSelection);
+
+                for (let i = 0; i < cateList.length; i++) {
+                    if (cateList.indexOf(cateList[i]) === i) {
+                       this.cate.push(cateList[i])
+                    }
+                }
+
+                if ((this.cate.length > 1)) {
+                    // 海内/海外商品只能选择一种
+                    if (this.cate.length > 1) {
+                        this.$message({
+                          message: '海内商品、海外商品只能选择一种',
+                          type: 'warning'
+                        })
+                        return false
+                    }
+                }
+
+                this.multipleSelection[this.currentPage] = val
             },
             handleInputNumber(val, row){
                 let pageList = this.multipleSelection[this.currentPage];

Разница между файлами не показана из-за своего большого размера
+ 207 - 103
frontendEle/src/views/shop/order-list.vue


+ 462 - 0
frontendEle/src/views/shop/order-overseas.vue

@@ -0,0 +1,462 @@
+<template>
+    <div v-loading="loading">
+        <div class="white-box">
+            <div class="table">
+                <el-table
+                    :data="goods"
+                    style="width: 100%"
+                    show-summary
+                    :summary-method="getSummaries">
+                     <el-table-column label="商品名称" prop="GOODS_NAME">
+                    </el-table-column>
+
+                  <el-table-column label="图片" >
+                    <template slot-scope="scope">
+                      <el-image style="width: 100px; height: 100px" :src="tool.getLocaleLink(scope.row.COVER, '/files/')" :preview-src-list="[tool.getLocaleLink(scope.row.COVER, '/files/')]"></el-image>
+                    </template>
+                  </el-table-column>
+
+                    <el-table-column label="会员价格" prop="member_price">
+                    </el-table-column>
+                    <el-table-column label="会员PV" prop="member_pv">
+                    </el-table-column>
+                    <el-table-column label="数量" prop="chose_num">
+
+                    </el-table-column>
+                    <el-table-column label="合计金额" prop="member_price_plus">
+
+                    </el-table-column>
+                    <el-table-column label="合计PV" prop="member_pv_plus"></el-table-column>
+                </el-table>
+            </div>
+
+            <div class="box address_box">
+                <div style="min-width: 65px;">收件人:</div>
+                <div class="sum" style="margin-top: 5px; margin-bottom: 5px;">
+                  <div class="sum_box">
+                    <div style="min-width: 70px; font-size: 14px; margin-right: 0;">真实姓名<span style="color: red;">*</span></div>
+                    <div><el-input size="small" v-model="consigneeRealName" style="min-width: 150px;"></el-input></div>
+                  </div>
+                  <div class="sum_box" style="margin-top: 5px;">
+                    <div style="min-width: 70px; font-size: 14px; margin-right: 0;">身份证号<span style="color: red;">*</span></div>
+                    <div><el-input size="small" v-model="consigneeIdNo" style="min-width: 150px;"></el-input></div>
+                  </div>
+                </div>
+            </div>
+            <div class="address_box">
+                请选择收货地址:
+                <el-radio-group v-model="addressId" @change='choseAddress'>
+                    <div v-for="(item , index) in all_address" :key='index' class="address">
+                        <el-radio :label="item.ID" >
+                            {{item.PROVINCE_NAME}}省{{item.CITY_NAME}}市{{item.COUNTY_NAME}}县&nbsp;&nbsp;&nbsp;&nbsp;
+                            详细地址:{{item.ADDRESS}}
+                            邮编:{{item.ZIP_CODE}}
+                            手机号码:{{item.MOBILE}}
+                        </el-radio>
+                    </div>
+                </el-radio-group>
+            </div>
+            <div class="address_box">
+                请选择支付方式:
+                <el-radio-group v-model="payType" @change='chosePayType'>
+                    <div v-for="(item , index) in payList" :key='index' class="address">
+                        <el-radio :label="item.type" >
+                            {{item.name}}
+                        </el-radio>
+                    </div>
+                </el-radio-group>
+            </div>
+            <div class="box address_box">
+                合计:
+                <div class="sum">
+                    <div class="sum_box">
+                        <div>实付金额</div>
+                        <div>¥{{ cashSum }}元</div>
+                    </div>
+                </div>
+            </div>
+            <div>
+                <el-button @click="goToAccounts()" :loading="submitButtonStat">去结算</el-button>
+            </div>
+        </div>
+
+      <el-dialog title="支付" v-if="visible" :visible.sync="visible" width="450px" :before-close="handleClose">
+        <div class="app-container">
+          <div v-loading="loading" class="white-box">
+            <form method="post" name="ePayment" :action="actionUrl">
+              <input type="hidden" name="MerchantCode" v-bind:value="form.MerchantCode"></input>
+              <input type="hidden" name="PaymentId" v-bind:value="form.PaymentId"></input>
+              <input type="hidden" name="RefNo" v-bind:value="form.RefNo"></input>
+              <input type="hidden" name="Amount"  v-bind:value="form.Amount"></input>
+              <input type="hidden" name="Currency"  v-bind:value="form.Currency"></input>
+              <input type="hidden" name="ProdDesc"  v-bind:value="form.ProdDesc"></input>
+              <input type="hidden" name="UserName"  v-bind:value="form.UserName"></input>
+              <input type="hidden" name="UserEmail" v-bind:value="form.UserEmail"></input>
+              <input type="hidden" name="UserContact" v-bind:value="form.UserContact"></input>
+              <!-- <input type="hidden" name="Remark"  value="gfdfgd"></input> -->
+              <input type="hidden" name="Lang"  v-bind:value="form.Lang"></input>
+              <input type="hidden" name="SignatureType" value="SHA256"></input>
+              <input type="hidden" name="Signature" v-bind:value="form.Signature"></input>
+              <input type="hidden" name="ResponseURL" v-bind:value="form.ResponseURL"></input>
+              <input type="hidden" name="BackendURL" v-bind:value="form.BackendURL"></input>
+              <span style="display: inline-block; margin: 0 auto;">因汇率波动,实际支付金额可能会有浮动</span><br /><br />
+              <input type="submit" value="支 付" name="Submit" class="payBtn"></input>
+            </form>
+          </div>
+        </div>
+      </el-dialog>
+
+      <el-dialog title="支付成功" :visible.sync="payDialog" :show-close="false" width="320px" :close="handleOrderList">
+        <el-result icon="success" title="支付成功">
+          <template slot="extra">
+            <span style="color: #008efa; font-size: 30px;">{{ countdown }}</span>
+          </template>
+        </el-result>
+      </el-dialog>
+
+      <el-dialog title="提示" :visible.sync="IDCardDialog" width="350px">
+        <el-result icon="warning" title="">
+          <template slot="extra">
+            <span style="font-size: 15px;">购买跨境商品必须上传<span style="color: red;">收件人身份信息</span>,如已上传请忽略</span><br /><br />
+            <el-button type="primary" size="medium" @click="uploadIDCard">去上传</el-button>
+          </template>
+        </el-result>
+      </el-dialog>
+    </div>
+</template>
+
+<script>
+ import network from '@/utils/network'
+ import tool from '@/utils/tool'
+  export default{
+      name: 'order-overseas',
+      data() {
+          return {
+              loading: true,
+              tool: tool,
+              goods:[],
+              payList:[],
+              all_address:[],
+              balance:{},
+              addressId:'',
+              payType:'',
+              pointsSum:'',
+              cashSum:'',
+              freight:'',
+              pointFreight:'',
+              goodsId:'',
+              goodsNum:'',
+              payPassword:'',
+              submitButtonStat: false,
+              visible: false,
+              payLoading: false,
+              payDialog: false,
+              countdown: 5,
+              actionUrl: '',
+              ePaymentShow: false,
+              form: {
+                MerchantCode: '',
+                PaymentId: 2,
+                RefNo: '',
+                Amount: '',
+                Currency: '',
+                ProdDesc: '',
+                UserName: '',
+                UserEmail: '',
+                UserContact: '',
+                Remark: '',
+                Lang: 'UTF-8',
+                SignatureType: 'SHA256',
+                Signature: '',
+                ResponseURL: '',
+                BackendURL: '',
+              },
+              consigneeRealName: '',
+              consigneeIdNo: '',
+              IDCardDialog: false,
+              IDCardWebsite: 'http://t.taoplus.cc/Home/UploadIDCard',
+          }
+      },
+      created() {
+          this.getShowCart();
+          let option= sessionStorage.getItem('order_goods');
+          if(option){
+              let pageGoodsList = JSON.parse(option);
+              let pageList;
+              for( let i in pageGoodsList ) {
+                  pageList =  pageGoodsList[i];
+                  if (!pageList) continue;
+
+                  pageList.map((pageData, index)=>{
+                      if( Number(pageData.chose_num) > 0 ) {
+                        let discount = pageData.DISCOUNT / 100;
+                        pageData.member_price = Math.round(pageData.SELL_PRICE * discount * 100) / 100;
+                        pageData.member_price_plus = Math.round(pageData.SELL_PRICE * Number(pageData.chose_num) * discount * 100) / 100;
+
+                        pageData.member_pv = Math.round(pageData.PRICE_PV * discount * 100) / 100;
+                        pageData.member_pv_plus = Math.round(pageData.PRICE_PV * Number(pageData.chose_num) * discount * 100) / 100;
+
+                        this.goods.push(pageData)
+                      }
+                  })
+              }
+
+              this.getSumMoney();
+          }
+          // 展示上传身份证页面
+          this.IDCardDialog  = true
+      },
+      methods:{
+          getSummaries(param) {
+            const { columns, data } = param;
+            const sums = [];
+            columns.forEach((column, index) => {
+              if (index === 0) {
+                sums[index] = '合计';
+                return;
+              }
+              const values = data.map(item => Number(item[column.property]));
+              if ((!values.every(value => isNaN(value))) && [4, 5, 6].includes(index)) {
+                sums[index] = values.reduce((prev, curr) => {
+                  const value = Number(curr);
+                  if (!isNaN(value)) {
+                    return prev + curr;
+                  } else {
+                    return prev;
+                  }
+                }, 0);
+              }
+            });
+
+            return sums;
+          },
+          goToAccounts(){
+              // 海外商品收货地址中必须包含邮编
+              let address = ''
+              this.all_address.map(item => {
+                if (item.ID === this.addressId) {
+                  address = item
+                }
+              })
+              // if (address.ZIP_CODE.length < 1) {
+              //   this.$message({
+              //     message: '收货地址中没有邮政编码',
+              //     type: 'error'
+              //   })
+              //   return false
+              // }
+
+              // 必须填写真实姓名和身份证号码
+              if (((this.consigneeIdNo.trim()).length <= 0) || ((this.consigneeRealName.trim()).length <= 0)) {
+                  this.$message({
+                    message: '收件人真实姓名和身份证号码必须填写',
+                    type: 'error'
+                  })
+                  return false
+              }
+
+              this.submitButtonStat = true
+              const data = {
+                addressId: this.addressId,
+                payType: this.payType,
+                goodsId: this.goodsId,
+                goodsNum: this.goodsNum,
+                consigneeIdNo: this.consigneeIdNo,
+                consigneeRealName: this.consigneeRealName,
+              }
+              this.$confirm(`确定要购买商品吗?`, '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning',
+                customClass: 'msgbox',
+              }).then(() => {
+                network.postData(`shop/sure-approach-order`, data).then(response => {
+                  this.submitButtonStat = false
+                  // 唤起支付页面
+                  network.postData(`shop/i-pay88`, { Amount: response.ORDER_AMOUNT, RefNo: response.SN }).then(payment => {
+                    this.actionUrl = payment.transactionUrl
+                    this.form = payment.paymentFields
+
+                    this.visible = true
+                    this.payLoading = false
+                  }).catch(err => {
+                    this.$message({
+                      message: err,
+                      type: 'error'
+                    })
+                    this.submitButtonStat = false
+                  })
+                })
+              }).catch(err => {
+                this.$message({
+                  message: err,
+                  type: 'error'
+                })
+                this.submitButtonStat = false
+              })
+          },
+          getSumMoney(){
+              let cash_plus_sum=[];
+              let goodsId=[];
+              let goodsNum=[];
+              let choseNum = 0;
+              this.goods.map((item,index)=>{
+                  choseNum = Number(item.chose_num);
+                  if( choseNum > 0 ) {
+                      cash_plus_sum.push(item.SELL_PRICE * choseNum*(item.DISCOUNT/100));
+                      goodsId.push(item.ID);
+                      goodsNum.push(choseNum);
+                  }
+              })
+              this.goodsNum=goodsNum;
+              this.goodsId=goodsId;
+              //增加运费03-12yuan
+              let payAmount = tool.sum(cash_plus_sum);
+              this.pointFreight = 0
+              // if(payAmount>=300){
+              //   this.pointFreight = this.freight = 0;
+              // }else{
+              //   this.pointFreight = this.freight = 15;
+              // }
+
+            this.pointsSum = this.cashSum = tool.formatPrice(tool.sum(cash_plus_sum) + this.freight) ;
+          },
+          getShowCart(){
+              network.getData('shop/show-cart')
+              .then(response=>{
+                  this.loading=false;
+                  this.all_address=response.allAddress;
+                  this.all_address.map((item,index)=>{
+                      if(item.IS_DEFAULT==1){
+                          this.addressId=item.ID
+                      }
+                  })
+                  // 如果没有默认收货地址,则第一个地址默认选中
+                  if (this.all_address.length > 0 && this.addressId.length < 1) {
+                      this.addressId = this.all_address[0].ID
+                  }
+
+                  for(let key in response.payList){
+                    // 如果是海外商品,则只有在线支付
+                    if (key === 'online') {
+                      this.payList.push({type:key, name:response.payList[key].name})
+                      break;
+                    }
+                  }
+
+                //默认设置为第一项付款方式
+                  this.payType=this.payList[0].type;
+              })
+          },
+          chosePayType(type){
+
+          },
+          choseAddress(addressId){
+          },
+          // 关闭支付回调
+          handleClose () {
+           let _this = this
+           _this.$confirm('确认关闭?').then(() => {
+             return network.postData('shop/delete-approach-order', {orderSn: this.form.RefNo}).then(() => {
+               // 关闭支付模态框
+               _this.visible = false
+               // 关闭购物车页面,返回到订单列表页
+               sessionStorage.setItem('order_goods', null)
+               this.$router.push({path: `/shop/index`})
+             })
+           }).catch(() => {
+             // 关闭支付模态框
+             _this.visible = false
+             // 关闭购物车页面,返回到订单列表页
+             sessionStorage.setItem('order_goods', null)
+             this.$router.push({path: `/shop/index`})
+           })
+          },
+          // 关闭支付回调
+          processClose () {
+           // 关闭支付模态框
+           this.visible = false
+           // 关闭购物车页面,返回到订单列表页
+           sessionStorage.removeItem('order_goods')
+           this.$router.push({path: `/shop/index`})
+          },
+          handleOrderList () {
+           this.$router.push({path: `/shop/order-list`})
+          },
+          // 启动倒计时
+          handleCountdown () {
+           // 创建定时器
+           setInterval(() => {
+             // 每隔1秒把time的值减一,赋值给span标签
+             this.countdown--
+             if (this.countdown === 0) {
+               // 倒计时结束,跳转到订单列表
+               this.$router.push({path: `/shop/order-list`})
+             }
+           }, 1000)
+          },
+          // 上传身份证
+          uploadIDCard() {
+            // window.location.href = this.IDCardWebsite
+            window.open(this.IDCardWebsite, '_blank')
+          },
+      }
+  }
+</script>
+<style scoped>
+.address{
+    /* height: 3rem; */
+    line-height: 3.5rem;
+
+}
+.address_box{
+     border-bottom: 1px solid #e3e3e3;
+}
+.sum{
+    display: inline-block;
+}
+.box{
+    margin: 1rem 0;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    padding-bottom: 1rem;
+}
+.sum_box{
+    display: flex;
+    margin-left: 1rem;
+}
+.sum_box > div{
+    line-height: 2rem;
+}
+.sum_box > div:nth-child(1){
+    margin-right: 1rem;
+}
+.payBtn {
+  display: inline-block;
+  line-height: 1;
+  white-space: nowrap;
+  cursor: pointer;
+  text-align: center;
+  padding: 10px 10px;
+  font-size: 14px;
+  border-radius: 4px;
+  color: #fff;
+  background-color: #409eff;
+  border: 1px solid #DCDFE6;
+  -webkit-appearance: none;
+  box-sizing: border-box;
+  margin-left: 150px;
+}
+@media (max-width: 720px) {
+  .msgbox{
+    width: 320px !important;
+    margin-top: 15px;
+  }
+}
+</style>
+<style>
+.myclass {
+    width:260px;
+}
+</style>

+ 3 - 0
frontendEle/src/views/shop/order.vue

@@ -245,6 +245,9 @@
                         }
                     })
                     for(let key in response.payList){
+                        if (key === 'online') {
+                          continue
+                        }
                         this.payList.push({type:key,name:response.payList[key].name})
                     }
                     // console.log(this.payList)

Некоторые файлы не были показаны из-за большого количества измененных файлов