Browse Source

长期合作工程师提交

dongxiaoqin 10 months ago
parent
commit
a69894f0e0

+ 13 - 1
app/adminapi/controller/ConfigController.php

@@ -56,6 +56,18 @@ class ConfigController extends BaseAdminController
         return $this->data($data);
     }
 
-
+    /**
+     * @notes 根据字段类型获取字典配置数据
+     * @return \think\response\Json
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function dictConfig()
+    {
+        $value = $this->request->get('value', '');
+        $data = ConfigLogic::getDictConfig($value);
+        return $this->data($data);
+    }
 
 }

+ 89 - 0
app/adminapi/controller/setting/dict/DictConfigController.php

@@ -0,0 +1,89 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeadmin快速开发前后端分离管理后台(PHP版)
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
+// | github下载:https://github.com/likeshop-github/likeadmin
+// | 访问官网:https://www.likeadmin.cn
+// | likeadmin团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeadminTeam
+// +----------------------------------------------------------------------
+
+namespace app\adminapi\controller\setting\dict;
+
+use app\adminapi\controller\BaseAdminController;
+use app\adminapi\lists\setting\dict\DictConfigLists;
+use app\adminapi\logic\setting\dict\DictConfigLogic;
+use app\adminapi\validate\dict\DictConfigValidate;
+
+
+/**
+ * 字典数据
+ * Class DictConfigController
+ * @package app\adminapi\controller\dictionary
+ */
+class DictConfigController extends BaseAdminController
+{
+
+    /**
+     * @notes 获取字典数据列表
+     * @return \think\response\Json
+     */
+    public function lists()
+    {
+        return $this->dataLists(new DictConfigLists());
+    }
+
+    /**
+     * @notes 添加字典数据
+     * @return \think\response\Json
+     */
+    public function add()
+    {
+        $params = (new DictConfigValidate())->post()->goCheck('add');
+        $result = DictConfigLogic::save($params);
+        if (false === $result) {
+            return $this->fail(DictConfigLogic::getError());
+        }
+        return $this->success('添加成功', [], 1, 1);
+    }
+
+    /**
+     * @notes 编辑字典数据
+     * @return \think\response\Json
+     */
+    public function edit()
+    {
+        $params = (new DictConfigValidate())->post()->goCheck('edit');
+        $result = DictConfigLogic::save($params);
+        if (false === $result) {
+            return $this->fail(DictConfigLogic::getError());
+        }
+        return $this->success('编辑成功', [], 1, 1);
+    }
+
+    /**
+     * @notes 删除字典数据
+     * @return \think\response\Json
+     */
+    public function delete()
+    {
+        $params = (new DictConfigValidate())->post()->goCheck('id');
+        DictConfigLogic::delete($params);
+        return $this->success('删除成功', [], 1, 1);
+    }
+
+    /**
+     * @notes 获取字典详情
+     * @return \think\response\Json
+     */
+    public function detail()
+    {
+        $params = (new DictConfigValidate())->goCheck('id');
+        $result = DictConfigLogic::detail($params);
+        return $this->data($result);
+    }
+}

+ 13 - 0
app/adminapi/controller/works/ServiceWorkController.php

@@ -25,6 +25,7 @@ use app\adminapi\validate\works\ServiceWorkValidate;
 use app\api\logic\ServiceOrderLogic;
 use app\common\logic\ThirdOrderLogic;
 use app\common\model\works\ServiceWork;
+use app\adminapi\lists\works\ServiceWorkAppointmentLists;
 
 
 /**
@@ -303,9 +304,21 @@ class ServiceWorkController extends BaseAdminController
         $params['user_info']['worker_number'] = $this->adminId;
         $params['user_info']['real_name'] = $this->adminInfo['name'];
         $result = ServiceWorkLogic::confirmDoor($params);
+    }
+
+    public function appointmentLists()
+    {
+        return $this->dataLists(new ServiceWorkAppointmentLists());
+    }
+
+    public function appointmentAudit()
+    {
+        $params = (new ServiceWorkValidate())->post()->goCheck('detail');
+        $result = ServiceWorkLogic::appointmentAudit($params);
         if (false === $result) {
             return $this->fail(ServiceWorkLogic::getError());
         }
         return $this->success('操作成功', [], 1, 1);
     }
+
 }

+ 2 - 1
app/adminapi/lists/master_worker/MasterWorkerLists.php

@@ -49,7 +49,7 @@ class MasterWorkerLists extends BaseAdminDataLists implements ListsSearchInterfa
     {
         // 派单搜索条件 - 工程师接单状态
         return [
-            '=' => ['mw.sn', 'mw.real_name', 'mw.city', 'mw.account', 'mw.password', 'mw.mobile', 'mw.sex', 'mw.channel', 'mw.is_disable', 'mw.is_new_user', 'mw.create_time', 'mw.update_time', 'mw.accept_order_status', 'mw.cooperation','mw.audit_state', 'mw.work_status'],
+            '=' => ['mw.sn', 'mw.real_name', 'mw.city', 'mw.account', 'mw.password', 'mw.mobile', 'mw.sex', 'mw.channel', 'mw.is_disable', 'mw.is_new_user', 'mw.create_time', 'mw.update_time', 'mw.accept_order_status', 'mw.cooperation','mw.audit_state', 'mw.work_status',"mw.type"],
             //'in' => ['mw.time_period']
         ];
     }
@@ -179,6 +179,7 @@ class MasterWorkerLists extends BaseAdminDataLists implements ListsSearchInterfa
             ->whereRaw($distanceWhereSql)
             ->field($fields)
             ->limit($this->limitOffset, $this->limitLength)
+            ->order("mw.type","desc")
             ->orderRaw('(mws.comprehensive_score + mws.weight_score) desc')
             ->order($orders)
             ->select()->toArray();

+ 3 - 2
app/adminapi/lists/master_worker/MasterWorkerOnlineLists.php

@@ -42,7 +42,7 @@ class MasterWorkerOnlineLists extends BaseAdminDataLists implements ListsSearchI
     {
         // 派单搜索条件 - 工程师接单状态
         return [
-            '=' => ['mw.real_name', 'mw.mobile', 'mw.accept_order_status'],
+            '=' => ['mw.real_name', 'mw.mobile', 'mw.accept_order_status','mw.type'],
             '%like%' => ['sa.service_name'],
         ];
     }
@@ -82,7 +82,7 @@ class MasterWorkerOnlineLists extends BaseAdminDataLists implements ListsSearchI
      */
     public function lists(): array
     {
-        $fields = ['mw.id,mw.avatar,mw.real_name,mw.mobile,mw.work_total,mw.distance,mw.accept_order_status,mw.address,mw.service_area_id,mws.comprehensive_score, mws.weight_score,sa.service_name,mwr.credential_images'];
+        $fields = ['mw.id,mw.avatar,mw.real_name,mw.mobile,mw.work_total,mw.distance,mw.accept_order_status,mw.address,mw.service_area_id,mw.type,mws.comprehensive_score, mws.weight_score,sa.service_name,mwr.credential_images'];
         $orders = ['mw.id' => 'desc'];
         $queryWhere = $this->queryWhere();
         // 根据位置排序
@@ -105,6 +105,7 @@ class MasterWorkerOnlineLists extends BaseAdminDataLists implements ListsSearchI
             ->whereRaw($distanceWhereSql)
             ->field($fields)
             ->limit($this->limitOffset, $this->limitLength)
+            ->order("mw.type","desc")
             ->order($orders)
             ->select()->toArray();
         foreach($list as &$item){

+ 69 - 0
app/adminapi/lists/setting/dict/DictConfigLists.php

@@ -0,0 +1,69 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeadmin快速开发前后端分离管理后台(PHP版)
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
+// | github下载:https://github.com/likeshop-github/likeadmin
+// | 访问官网:https://www.likeadmin.cn
+// | likeadmin团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeadminTeam
+// +----------------------------------------------------------------------
+
+namespace app\adminapi\lists\setting\dict;
+
+use app\adminapi\lists\BaseAdminDataLists;
+use app\common\lists\ListsSearchInterface;
+use app\common\model\dict\DictConfig;
+
+/**
+ * 字典配置数据列表
+ * Class DictCconfigLists
+ * @package app\adminapi\lists\dict
+ */
+class DictConfigLists extends BaseAdminDataLists implements ListsSearchInterface
+{
+
+    /**
+     * @notes 设置搜索条件
+     * @return \string[]
+     */
+    public function setSearch(): array
+    {
+        return [
+            '%like%' => ['name', 'value'],
+            '=' => ['status']
+        ];
+    }
+
+
+    /**
+     * @notes 获取列表
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function lists(): array
+    {
+        return DictConfig::where($this->searchWhere)
+            ->append(['status_desc'])
+            ->limit($this->limitOffset, $this->limitLength)
+            ->order([ 'id' => 'desc'])
+            ->select()
+            ->toArray();
+    }
+
+
+    /**
+     * @notes 获取数量
+     * @return int
+     */
+    public function count(): int
+    {
+        return DictConfig::where($this->searchWhere)->count();
+    }
+
+}

+ 98 - 0
app/adminapi/lists/works/ServiceWorkAppointmentLists.php

@@ -0,0 +1,98 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeadmin快速开发前后端分离管理后台(PHP版)
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
+// | github下载:https://github.com/likeshop-github/likeadmin
+// | 访问官网:https://www.likeadmin.cn
+// | likeadmin团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeadminTeam
+// +----------------------------------------------------------------------
+
+namespace app\adminapi\lists\works;
+
+
+use app\adminapi\lists\BaseAdminDataLists;
+use app\common\model\works\ServiceWorkAppointmentLog;
+use app\common\lists\ListsSearchInterface;
+
+
+/**
+ * ServiceWorkAppointment列表
+ * Class ServiceWorkAppointmentLists
+ * @package app\adminapi\lists
+ */
+class ServiceWorkAppointmentLists extends BaseAdminDataLists implements ListsSearchInterface
+{
+
+
+    /**
+     * @notes 设置搜索条件
+     * @return \string[][]
+     * @author likeadmin
+     * @date 2025/05/04 15:41
+     */
+    public function setSearch(): array
+    {
+        return [
+            '=' => ['work_id'],
+        ];
+    }
+
+    public function queryWhere(){
+        $where = [];
+        if (isset($this->params['status']) ) {
+            if ($this->params['status'] == 0) {
+                $where[] = ["a.status", "=", 0];
+            } else {
+                $where[] = ["a.status", "<>", 1];
+            }
+        }
+        if (!empty($this->params['real_name'])) {
+            $where[] = ["b.real_name", "like", '%' . $this->params['real_name'] . '%'];
+        }
+        return $where;
+    }
+
+    /**
+     * @notes 获取列表
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @author likeadmin
+     * @date 2025/05/04 15:41
+     */
+    public function lists(): array
+    {
+        return ServiceWorkAppointmentLog::alias("a")
+            ->leftJoin("master_worker b","a.worker_id = b.id")
+            ->where($this->searchWhere)
+            ->where($this->queryWhere())
+            ->field(['a.id', 'a.work_id', 'a.worker_id', 'a.last_appointment_time', 'a.this_appointment_time', 'a.status', 'a.create_time','b.real_name'])
+            ->limit($this->limitOffset, $this->limitLength)
+            ->order(['a.id' => 'desc'])
+            ->select()
+            ->toArray();
+    }
+
+
+    /**
+     * @notes 获取数量
+     * @return int
+     * @author likeadmin
+     * @date 2025/05/04 15:41
+     */
+    public function count(): int
+    {
+        return ServiceWorkAppointmentLog::alias("a")
+                ->leftJoin("master_worker b","a.worker_id = b.id")
+                ->where($this->searchWhere)
+                ->where($this->queryWhere())
+                ->count();
+    }
+
+}

+ 30 - 14
app/adminapi/logic/ConfigLogic.php

@@ -14,21 +14,9 @@
 
 namespace app\adminapi\logic;
 
-use app\adminapi\logic\article\ArticleCateLogic;
-use app\adminapi\logic\auth\MenuLogic;
-use app\adminapi\logic\auth\RoleLogic;
-use app\adminapi\logic\dept\DeptLogic;
-use app\adminapi\logic\dept\JobsLogic;
-use app\adminapi\logic\setting\dict\DictTypeLogic;
-use app\common\enum\YesNoEnum;
 use app\common\logic\TableDataLogic;
-use app\common\model\article\ArticleCate;
-use app\common\model\auth\SystemMenu;
-use app\common\model\auth\SystemRole;
-use app\common\model\dept\Dept;
-use app\common\model\dept\Jobs;
 use app\common\model\dict\DictData;
-use app\common\model\dict\DictType;
+use app\common\model\dict\DictConfig;
 use app\common\service\{FileService, ConfigService};
 
 /**
@@ -108,7 +96,35 @@ class ConfigLogic
         }
         return $result;
     }
+    
+    /**
+     * @notes 根据类型获取字典类型
+     * @param $type
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public static function getDictConfig($value)
+    {
+        if (!is_string($value)) {
+            return [];
+        }
+        
+        $value = explode(',', $value);
+        $lists = DictConfig::whereIn('value', $value)->select()->toArray();
+        
+        if (empty($lists)) {
+            return [];
+        }
 
-
+        $result = [];
+        foreach ($lists as $item) {
+            if ($item['content']) {
+                $result[$item['value']] = json_decode($item['content'], true);
+            }
+        }
+        return $result;
+    }
 
 }

+ 11 - 2
app/adminapi/logic/master_worker/MasterWorkerLogic.php

@@ -51,7 +51,7 @@ class MasterWorkerLogic extends BaseLogic
                 self::setError('您所选的位置已超出服务区域!');
                 return false;
             }
-            $masterWorker = MasterWorker::create([
+            $data = [
                 'sn' => $params['sn'],
                 'avatar' => $params['avatar'],
                 'real_avatar' => $params['real_avatar'],
@@ -85,7 +85,13 @@ class MasterWorkerLogic extends BaseLogic
                 'labels' => (isset($params['labels']) && $params['labels'])?implode(',',$params['labels']):'',
                 'remark' => $params['remark']??'',
                 'is_wecall' => $params['is_wecall']??0,
-            ]);
+            ];
+            if (isset($params['type']) && $params['type'] == 2) {
+                $data['type'] = 2;
+                $data['promotion_level'] = 6;   //A1级工程师
+                $data['title_promotion'] = 1;   //普通工程师
+            }
+            $masterWorker = MasterWorker::create($data);
             
             //添加工程师汇总评分数据
             MasterWorkerScore::create([
@@ -184,6 +190,9 @@ class MasterWorkerLogic extends BaseLogic
                 'labels' => (isset($params['labels']) && $params['labels'])?implode(',',$params['labels']):'',
                 'remark' => $params['remark']??'',
                 'is_wecall' => $params['is_wecall']??0,
+                'type'  => $params['type']??1,
+                'promotion_level' => $params['promotion_level']??0,   
+                'title_promotion' => $params['title_promotion']??0,   
             ];
             //'tenant_id' => $params['tenant_id']??0,
             MasterWorker::where('id', $params['id'])->update($update);

+ 85 - 0
app/adminapi/logic/setting/dict/DictConfigLogic.php

@@ -0,0 +1,85 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeadmin快速开发前后端分离管理后台(PHP版)
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
+// | github下载:https://github.com/likeshop-github/likeadmin
+// | 访问官网:https://www.likeadmin.cn
+// | likeadmin团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeadminTeam
+// +----------------------------------------------------------------------
+
+namespace app\adminapi\logic\setting\dict;
+
+use app\common\logic\BaseLogic;
+use app\common\model\dict\DictConfig;
+
+
+/**
+ * 字典数据逻辑
+ * Class DictConfigLogic
+ * @package app\adminapi\logic\DictConfig
+ */
+class DictConfigLogic extends BaseLogic
+{
+
+    /**
+     * @notes 添加编辑
+     * @param array $params
+     * @return DictConfig|\think\Model
+     */
+    public static function save(array $params)
+    {
+        try {
+            $data = [
+                'name' => $params['name'],
+                'value' => $params['value'],
+                'content' => $params['content'] ? json_encode($params['content']) : null,
+                'status' => $params['status'],
+                'remark' => $params['remark'] ?? '',
+            ];
+            //校验字段类型是否重复
+            $id = !empty($params['id'])? $params['id'] : 0;
+            if (DictConfig::where(['value' => $params['value']])->where('id', '<>', $id)->value('id')) {
+                throw new \think\Exception('字段类型已存在'.$params['id']);
+            }
+            if (!empty($params['id'])) {
+                return DictConfig::where(['id' => $params['id']])->update($data);
+            } else {
+                return DictConfig::create($data);
+            }
+        } catch (\Exception $e) {
+            self::setError($e->getMessage());
+            return false;
+        }
+    }
+
+
+    /**
+     * @notes 删除字典数据
+     * @param array $params
+     * @return bool
+     */
+    public static function delete(array $params)
+    {
+        return DictConfig::destroy($params['id']);
+    }
+
+
+    /**
+     * @notes 获取字典数据详情
+     * @param $params
+     * @return array
+     */
+    public static function detail($params): array
+    {
+        $detail = DictConfig::findOrEmpty($params['id'])->toArray();
+        $detail['content'] = $detail['content'] ?  json_decode($detail['content'], true) : [];
+        return $detail;
+    }
+
+
+}

+ 93 - 14
app/adminapi/logic/works/ServiceWorkLogic.php

@@ -55,6 +55,8 @@ use app\common\model\master_worker\MasterWorkerRetentionMoneyLog;
 use app\common\model\master_commission\MasterWorkerCommissionRatio;
 use app\common\model\master_commission\MasterWorkerCommissionConfig;
 use app\common\model\service_area\ServiceArea;
+use app\common\service\call\VirtualCallService;
+use think\facade\Cache;
 
 /**
  * ServiceWork逻辑
@@ -164,22 +166,37 @@ class ServiceWorkLogic extends BaseLogic
                 throw new Exception('请勿重复点击');
             }
 
-            //验证更改的预约时间必须是在领单时间内的半小内修改,否则不允许修改
-            if(strtotime($work->appointment_time) != strtotime($params['appointment_time']) && (time()-strtotime($work->receive_time))>1800){
-                throw new Exception('距离领单时间已超过半小时,无法修改预约时间,请联系客服');
+            //判断是否有待审核的预约时间修改记录
+            $exists = ServiceWorkAppointmentLog::where(['work_id'=>$work->id,'status'=>0])->value('id');
+            if ($exists){
+                throw new Exception('存在待审核的预约时间修改记录,无法修改');
             }
 
-            $work->work_status = 3;//待上门
-            $work->appointment_time = strtotime($params['appointment_time']);
-            $work->save();
+            //验证更改的预约时间必须是在领单时间内的半小内修改,否则不允许修改
+            // if(strtotime($work->appointment_time) != strtotime($params['appointment_time']) && (time()-strtotime($work->receive_time))>1800){
+            //     throw new Exception('距离领单时间已超过半小时,无法修改预约时间,请联系客服');
+            // }
 
-            //添加变更日志
-            $work_log = [
+            //添加预约时间修改待审核记录
+            ServiceWorkAppointmentLog::create([
+                'status'=>0,//待审核
                 'work_id'=>$work->id,
-                'master_worker_id'=>$work->master_worker_id,
-                'opera_log'=>'编号['.$params['user_info']['worker_number'].']'.$params['user_info']['real_name'].'于'.date('y-m-d H:i:s',time()).'联系了客户,确认了于'.$params['appointment_time'].$params['address'].'预约上门',
-            ];
-            ServiceWorkLogLogic::add($work_log);
+                'worker_id'=>$params['user_id'],
+                'last_appointment_time'=>strtotime($work->appointment_time),
+                'this_appointment_time'=>strtotime($params['appointment_time']),
+            ]);
+
+            // $work->work_status = 3;//待上门
+            // $work->appointment_time = strtotime($params['appointment_time']);
+            // $work->save();
+
+            //添加变更日志
+            // $work_log = [
+            //     'work_id'=>$work->id,
+            //     'master_worker_id'=>$work->master_worker_id,
+            //     'opera_log'=>'编号['.$params['user_info']['worker_number'].']'.$params['user_info']['real_name'].'于'.date('y-m-d H:i:s',time()).'联系了客户,确认了于'.$params['appointment_time'].$params['address'].'预约上门',
+            // ];
+            // ServiceWorkLogLogic::add($work_log);
             Db::commit();
         }
         catch (\Exception $e) {
@@ -426,7 +443,7 @@ class ServiceWorkLogic extends BaseLogic
                 throw new Exception('请上传配件图片');
             }
             //修改
-            ServiceWorkSpare::where("id",$params['id'])->where('service_worker_id', $work['id'])->update(['spare_image' => $params['spare_image']]);
+            ServiceWorkSpare::where("id",$params['id'])->where('service_work_id', $work['id'])->update(['spare_image' => $params['spare_image'], 'status' => 0]);
             
             Db::commit();
             return true;
@@ -717,6 +734,14 @@ class ServiceWorkLogic extends BaseLogic
         if($result['third_type']==1){
             $result['meituan_order'] = ThirdOrders::where(['work_id'=>$result['id']])->order('create_time desc')->findOrEmpty();
         }
+        //工程师预约时间修改申请记录
+        if (!empty($params['user_id'])) {
+            $result['appointment_log'] = ServiceWorkAppointmentLog::where('work_id',$result['id'])->where('worker_id',$params['user_id'])->where('status','<>',1)->field('id,work_id,worker_id,last_appointment_time,this_appointment_time,status,create_time')->order('id desc')->findOrEmpty();
+            if ($result['appointment_log']){
+                $result['appointment_log']['last_appointment_time'] = date('Y/m/d H:i:s',$result['appointment_log']['last_appointment_time']);
+                $result['appointment_log']['this_appointment_time'] = date('Y/m/d H:i:s',$result['appointment_log']['this_appointment_time']);
+            }
+        }
         return  $result;
     }
 
@@ -1122,6 +1147,20 @@ class ServiceWorkLogic extends BaseLogic
                 $work->first_contact_time = time();
                 $work->save();
             }
+            
+            if ($middleNumber = Cache::get('WORKER_MIDDLE_NUMBER_'.$work->id)) {
+                return ['middleNumber' => $middleNumber];
+            }
+            $worker_mobile = $params['user_info']['mobile'];
+            $res = VirtualCallService::auth($worker_mobile, $work->mobile, 60);
+            if (isset($res['result']) && $res['result'] == '000000') {
+                Cache::set('WORKER_MIDDLE_NUMBER_'.$work->id, $res['middleNumber'], 60); //缓存60秒
+                return ['middleNumber' => $res['middleNumber']];
+            } else {
+                Log::info('虚拟外呼失败:'.json_encode($res));
+                throw new \Exception('虚拟外呼失败');
+            }
+
             return true;
         }
         catch  (\Exception $e) {
@@ -1272,7 +1311,6 @@ class ServiceWorkLogic extends BaseLogic
         }
     }
 
-
     /**
      * 给用户发券
      */
@@ -1335,4 +1373,45 @@ class ServiceWorkLogic extends BaseLogic
         }
     }
 
+    public static function appointmentAudit($params):  bool
+    { 
+        Db::startTrans();
+        try {
+            $work = ServiceWork::where('id',$params['work_id'])->findOrEmpty();
+            if($work->isEmpty()){
+                throw new \Exception('工单不存在');
+            }
+            $log = ServiceWorkAppointmentLog::where('id',$params['id'])->findOrEmpty();
+            if($log->isEmpty()){
+                throw new \Exception('审核记录不存在');
+            }
+            $master = MasterWorker::where('id',$log->worker_id)->findOrEmpty();
+            
+            
+            $log->status = $params['status'];
+            $log->remark = $params['remark'];
+            $log->save();
+
+            //预约时间修改审核通过则更新工单预约时间
+            if ($params['status'] == 2) {
+                //$work->work_status = 3;//待上门
+                $work->appointment_time = $log->this_appointment_time;
+                $work->save();
+
+                //添加变更日志
+                $work_log = [
+                    'work_id'=>$work->id,
+                    'master_worker_id'=>$log->worker_id,
+                    'opera_log'=>'编号['.$master->worker_number.']'.$master->real_name.'于'.$log->create_time.'联系了客户,确认了于'.date('Y-m-d H:i:s',$log->this_appointment_time).'预约上门',
+                ];
+                ServiceWorkLogLogic::add($work_log);
+            }
+            Db::commit();
+            return true;
+        } catch (\Exception $e) {
+            Db::rollback();
+            self::setError('预约时间审核失败');
+            return false;
+        }
+    }
 }

+ 93 - 0
app/adminapi/validate/dict/DictConfigValidate.php

@@ -0,0 +1,93 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeadmin快速开发前后端分离管理后台(PHP版)
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
+// | github下载:https://github.com/likeshop-github/likeadmin
+// | 访问官网:https://www.likeadmin.cn
+// | likeadmin团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeadminTeam
+// +----------------------------------------------------------------------
+
+namespace app\adminapi\validate\dict;
+
+use app\common\model\dict\DictConfig;
+use app\common\validate\BaseValidate;
+
+
+/**
+ * 字典配置数据验证
+ * Class DictConfigValidate
+ * @package app\adminapi\validate\dict
+ */
+class DictConfigValidate extends BaseValidate
+{
+
+    protected $rule = [
+        'id' => 'require|checkDictConfig',
+        'name' => 'require|length:1,255',
+        'value' => 'require',
+        'content' => 'require',
+        'status' => 'require|in:0,1',
+    ];
+
+
+    protected $message = [
+        'id.require' => '参数缺失',
+        'name.require' => '请填写字典数据名称',
+        'name.length' => '字典数据名称长度须在1-255位字符',
+        'value.require' => '请填写字典数据值',
+        'content.require' => '请填写配置数据',
+        'status.require' => '请选择字典数据状态',
+        'status.in' => '字典数据状态参数错误',
+    ];
+
+
+    /**
+     * @notes 添加场景
+     * @return DictDataValidate
+     */
+    public function sceneAdd()
+    {
+        return $this->remove('id', true);
+    }
+
+
+    /**
+     * @notes ID场景
+     * @return DictDataValidate
+     */
+    public function sceneId()
+    {
+        return $this->only(['id']);
+    }
+
+
+    /**
+     * @notes 编辑场景
+     * @return DictDataValidate
+     */
+    public function sceneEdit()
+    {
+        return $this->remove('type_id', true);
+    }
+
+
+    /**
+     * @notes 校验字典数据
+     * @param $value
+     * @return bool|string
+     */
+    protected function checkDictConfig($value)
+    {
+        $article = DictConfig::findOrEmpty($value);
+        if ($article->isEmpty()) {
+            return '字典配置数据不存在';
+        }
+        return true;
+    }
+
+}

+ 9 - 15
app/api/controller/SmsController.php

@@ -38,7 +38,7 @@ class SmsController extends BaseApiController
      */
     public function sendCode()
     {
-        $params = (new SendSmsValidate())->post()->goCheck();
+        $params = (new SendSmsValidate())->post()->goCheck('sendCode');
         $result = SmsLogic::sendCode($params);
         if (true === $result) {
             return $this->success('发送成功');
@@ -48,23 +48,17 @@ class SmsController extends BaseApiController
 
     public function verifyCode()
     {
+        $params = (new SendSmsValidate())->get()->goCheck('verifyCode');
         $res = generateCaptcha();
 
-        // 存储验证码到缓存,有效期2分钟(120秒)
-        cache('verifyCode', $res['captcha'], 120);
+        // 存储验证码到缓存,有效期1分钟(60秒)
+        cache('verifyCode_'.$params['mobile'], $res['captcha'], 60);
 
-        $image = $res['image'];
-
-        // 设置响应头
-        header('Content-Type: image/png');
-        header('Cache-Control: no-cache, must-revalidate');
-        header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
-
-        // 输出图像
-        imagepng($image);
-
-        // 释放资源
-        imagedestroy($image);
+        $data = [
+            'image' => $res['image'],
+            'size' => $res['size']
+        ];
+        return $this->success('发送成功', $data);
     }
 
 }

+ 28 - 0
app/api/controller/notify/VirtualCallController.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace app\api\controller\notify;
+
+use app\api\controller\BaseApiController;
+use app\common\service\call\VirtualCallService;
+
+/**
+ * 虚拟呼叫结果回调接口
+ * Class VirtualCallController
+ * @package app\api\controller\notify
+ */
+class VirtualCallController extends BaseApiController
+{
+
+    public array $notNeedLogin = ['notify'];
+
+    public function notify()
+    {
+        $params = $this->request->param();
+        if (!empty($params['dataType']) && $params['dataType'] == 'ROBOT_TASK_STATUS_CHANGE') {
+            VirtualCallService::notify($params);
+        }
+        
+        return $this->data(['resultCode' => "200"]);
+    }
+
+}

+ 36 - 0
app/api/validate/SendSmsValidate.php

@@ -29,11 +29,47 @@ class SendSmsValidate extends BaseValidate
     protected $rule = [
         'mobile' => 'require|mobile',
         'scene' => 'require',
+        //'verifyCode' => 'require|checkVerifyCode',
     ];
 
     protected $message = [
         'mobile.require' => '请输入手机号',
         'mobile.mobile' => '请输入正确手机号',
         'scene.require' => '请输入场景值',
+        //'verifyCode.require' => '请输入图形验证码',
     ];
+
+    /**
+     * @notes 发送短信    
+     */
+    public function sceneSendCode()
+    {
+        //return $this->only(['mobile','scene','verifyCode']);
+        return $this->only(['mobile','scene']);
+    }
+
+    /**
+     * @notes 图形验证码    
+     */
+    public function sceneVerifyCode()
+    {
+        return $this->only(['mobile']);
+    }
+
+    /**
+     * @notes 校验图形验证码
+     */
+    public function checkVerifyCode($scene, $rule, $data)
+    {
+        $captcha = cache('verifyCode_'.$data['mobile']);
+        if (empty($captcha)) {
+            return '图形验证码已失效,请重新获取';
+        }
+        if ($captcha != $data['verifyCode']) {
+            return '图形验证码错误';
+        }
+        // 验证成功后删除验证码
+        cache('verifyCode_'.$data['mobile'],null);
+        return true;
+    }
 }

+ 15 - 1
app/common.php

@@ -52,9 +52,23 @@ function generateCaptcha() {
         $angle = rand(-20, 20); // 随机旋转角度
         imagettftext($image, $font_size, $angle, (int)$x, (int)$y, $text_color, './static/fonts/arial.ttf', $code[$i]);
     }
+
+    // 捕获图像到输出缓冲区
+    ob_start();
+    imagepng($image);
+    $imageData = ob_get_contents();
+    ob_end_clean();
+
+    // 销毁图像资源
+    imagedestroy($image);
+
+    // 转换为Base64编码
+    $base64Image = base64_encode($imageData);
+
     return [
         'captcha' => $code,
-        'image' => $image,
+        'image' => 'data:image/png;base64,' . $base64Image,
+        'size' => strlen($imageData),
     ];
 }
 

+ 45 - 0
app/common/model/dict/DictConfig.php

@@ -0,0 +1,45 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeadmin快速开发前后端分离管理后台(PHP版)
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
+// | github下载:https://github.com/likeshop-github/likeadmin
+// | 访问官网:https://www.likeadmin.cn
+// | likeadmin团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeadminTeam
+// +----------------------------------------------------------------------
+
+namespace app\common\model\dict;
+
+use app\common\model\BaseModel;
+use think\model\concern\SoftDelete;
+
+
+/**
+ * 字典配置数据模型
+ * Class DictConfig
+ * @package app\common\model\dict
+ */
+class DictConfig extends BaseModel
+{
+
+    use SoftDelete;
+
+    protected $deleteTime = 'delete_time';
+
+
+    /**
+     * @notes 状态描述
+     * @param $value
+     * @param $data
+     * @return string
+     */
+    public function getStatusDescAttr($value, $data)
+    {
+        return $data['status'] ? '正常' : '停用';
+    }
+
+}

+ 126 - 9
app/common/service/call/VirtualCallService.php

@@ -13,20 +13,137 @@
 // +----------------------------------------------------------------------
 
 namespace app\common\service\call;
+use think\facade\Cache;
 
 class VirtualCallService
 {
+    public static $host = 'https://101.37.133.245:11008/';
+    public static $appId = '989460';
+    public static $accessToken = 'edcd07d7216446b6ae549a2e621eb42b';
+
+    public static function timestamp(){
+        $time = explode (" ", microtime () );
+        return $time[1] . "000";
+    }
+    /**
+     * @notes 鉴权
+     */
+    public static function auth($bindNumberA, $bindNumberB, $maxBindingTime = 60)
+    {
+        $timestamp = self::timestamp();
+        $sig = md5(self::$appId.self::$accessToken.$timestamp);
+        $authorization = base64_encode(self::$appId.":".$timestamp);
+        $url = self::$host . 'voice/1.0.0/middleNumberAXB/'.self::$appId.'/'.$sig;
+        $header = [
+            "Accept:application/json",
+            "Content-Type:application/json;charset=utf-8",
+            "Authorization:$authorization"
+        ];
+        $params = [
+            //"middleNumber" => "13003426180",
+            "bindNumberA" => $bindNumberA,
+            "bindNumberB" => $bindNumberB,
+            "maxBindingTime" => $maxBindingTime,
+            "callbackUrl" => "",
+        ];
+        $params = json_encode($params);
+        $response = http_request($url, $params, $header);
+        return $response;
+    }
+
     /**
-     * @notes 设置配置值
-     * @param $type
-     * @param $name
-     * @param $value
-     * @return mixed
-     * @author 段誉
-     * @date 2021/12/27 15:00
+     * @notes 虚拟外呼回调通知
      */
-    public static function set(string $type, string $name, $value)
+    public static function notify($params)
+    {
+        if (empty($params['appId']) || $params['appId'] != self::$appId) {
+            return ['resultCode' => "200"];
+        }
+        return ['resultCode' => "200"];
+    }
+
+    //////////////////优音云平台//////////////////////////
+    //外显固话
+    public static function addHiddedPhone($bindNumberA, $bindNumberB){
+        $url = 'https://xtapi.uincall.com/api/call/addHiddedPhone.action';
+        $header = [
+            "Content-Type:application/x-www-form-urlencoded;charset=UTF-8"
+        ];
+        $data = json_encode([['phone1' => $bindNumberA, 'phone2' => $bindNumberB]]);
+        //echo $data;die;
+        $params = [
+            'appver' => '1',
+            'timestamp' => date('YmdHis'),
+            'user' => '075533318487_dev',
+            'account' => '075533318487',
+            'data' => ($data),
+        ];
+        $token = self::getToken();
+        $params['secret'] = self::getSecret($params, $token);
+        $params = http_build_query($params, '=', '&');
+        $response = http_request($url, $params, $header);
+        return $response;
+    }
+
+    //外显小号
+    public static function bind($bindNumberA, $bindNumberB, $maxBindingTime = 60){
+        $url = 'https://xtapi.uincall.com/api/call/bindHiddedPhone.action';
+        $header = [
+            "Content-Type:application/x-www-form-urlencoded;charset=UTF-8"
+        ];
+        $params = [
+            'appver' => '1',
+            'timestamp' => date('YmdHis'),
+            'user' => '075533318487_dev',
+            'account' => '075533318487',
+            'callingId' => $bindNumberA,
+            'transferNum' => $bindNumberB,
+            'expiration' => $maxBindingTime,
+        ];
+        $token = self::getToken();
+        $params['secret'] = self::getSecret($params, $token);
+        $params = http_build_query($params, '=', '&');
+        $response = http_request($url, $params, $header);
+        return $response;
+    }
+
+    public static function getToken()
+    { 
+        if ($token = Cache::get('uincall_token')) {
+            return $token;
+        }
+        $url = 'https://xtapi.uincall.com/api/call/union400Login.action';
+        $header = [
+            "Content-Type:application/x-www-form-urlencoded;charset=UTF-8"
+        ];
+        $params = [
+            'appver' => '1',
+            'timestamp' => date('YmdHis'),
+            'user' => '075533318487_dev',
+        ];
+        $params['secret'] = self::getSecret($params, 'da7fd311e57a46af88eb972d47ebc954');
+        $params = http_build_query($params, '=', '&');
+        $response = http_request($url, $params, $header);
+        if (isset($response['errcode']) && $response['errcode'] == 0) {
+            $exp = strtotime($response['data']['expiredtime']) - time();
+            Cache::set('uincall_token', $response['data']['token'], $exp);
+            return $response['data']['token'];
+        }
+        return "";
+    }
+
+    public static function getSecret($params,$token)
     {
-        return '';
+        //按键值名排序
+        ksort($params);
+        $str = '';
+        foreach ($params as $key => $value) {
+            if ($value !== '' && $value !== null) {
+                $str .= $key . $value;
+            }
+        }
+        $str .= $token;
+        $sign = md5($str);
+        return $sign;
     }
 }

+ 1 - 1
app/workerapi/controller/SaleController.php

@@ -32,7 +32,7 @@ use app\workerapi\lists\GroupActivityLists;
 class SaleController extends BaseApiController
 {
 
-    public array $notNeedLogin = ['register', 'account','getTenantList','getTenantDetail','getWorkerList','getWorkerDetail','getPropertyList','getPropertyDetail'];
+    public array $notNeedLogin = ['register', 'account','getTenantList','getTenantDetail','getWorkerList','getWorkerDetail','getPropertyList','getPropertyDetail','getGroupActivityList','getGroupActivityDetail'];
 
 
     /**

+ 15 - 1
app/workerapi/controller/SmsController.php

@@ -14,7 +14,7 @@ use app\api\validate\SendSmsValidate;
 class SmsController extends \app\workerapi\controller\BaseApiController
 {
 
-    public array $notNeedLogin = ['sendCode'];
+    public array $notNeedLogin = ['sendCode','verifyCode'];
 
 
     /**
@@ -33,4 +33,18 @@ class SmsController extends \app\workerapi\controller\BaseApiController
         return $this->fail(SmsLogic::getError());
     }
 
+    public function verifyCode()
+    {
+        $params = (new SendSmsValidate())->get()->goCheck('verifyCode');
+        $res = generateCaptcha();
+
+        // 存储验证码到缓存,有效期1分钟(60秒)
+        cache('verifyCode_'.$params['mobile'], $res['captcha'], 60);
+
+        $data = [
+            'image' => $res['image'],
+            'size' => $res['size']
+        ];
+        return $this->success('发送成功', $data);
+    }
 }

+ 5 - 6
app/workerapi/controller/WorksController.php

@@ -338,7 +338,7 @@ class WorksController extends BaseApiController
 
     
     /**
-     * 第一次电话联系客户
+     * 电话联系客户(虚拟拨号)
      * @return \think\response\Json
      */
     public function contactCustomer()
@@ -351,8 +351,7 @@ class WorksController extends BaseApiController
         if (false === $result) {
             return $this->fail(ServiceWorkLogic::getError());
         }
-        
-        return $this->success('成功', [], 1, 1);
+        return $this->data($result);
     }
 
     /**
@@ -435,13 +434,13 @@ class WorksController extends BaseApiController
      */
     public function selfSparePart()
     {
-        $params = (new ServiceWorkValidate())->post()->goCheck('sparepart');
+        $params = (new ServiceWorkValidate())->post()->goCheck('sparepart', [
+            'user_id' => $this->userId,
+        ]);
         $result = ServiceWorkLogic::selfSparePart($params);
         if (false === $result) {
             return $this->fail(ServiceWorkLogic::getError());
         }
         return $this->success('修改成功', [], 1, 1);
     }
-
-
 }

+ 1 - 1
app/workerapi/lists/GroupServiceWorkLists.php

@@ -52,7 +52,7 @@ class GroupServiceWorkLists  extends BaseWorkerDataLists implements ListsSearchI
                 break;
         }
         $list = GroupServiceWork::where($where)
-            ->field(['id', 'work_sn', 'address', 'title', 'work_status', 'service_status','appointment_time'])
+            ->field(['id', 'work_sn', 'address', 'title', 'work_status', 'service_status','appointment_time','remark'])
             ->append(['work_status_text','service_status_text'])
             ->limit($this->limitOffset, $this->limitLength)
             ->order(['appointment_time' => 'asc'])//上门时间排序