workorderConfig = get_addon_config('workorder'); $this->workorderConfig['notice_mail'] = isset($this->workorderConfig['notice_mail']) ? explode(',', $this->workorderConfig['notice_mail']) : []; $this->workorderConfig['notice_sms'] = isset($this->workorderConfig['notice_sms']) ? explode(',', $this->workorderConfig['notice_sms']) : []; Lang::load(ROOT_PATH . 'addons' . DS . 'workorder' . DS . 'lang' . DS . \think\Request::instance()->langset() . '.php'); } public static function instance($options = []) { if (is_null(self::$instance)) { self::$instance = new static($options); } return self::$instance; } /** * 计算一个工程师的平均回复时间 * 通常在有新的回复时调用 * @param int $engineerId 工程师ID * @return float * @throws \think\Exception * @throws \think\exception\PDOException */ public static function calcEngineerAvgResTime($engineerId) { $workorderTimeStatistics = Db::name('workorder_time_statistics') ->alias('s') ->join('workorder_orders o', 'o.id=s.order_id') ->where('o.deletetime', null) ->where('s.type', 'in', '1,2') ->where('s.engineer_id', $engineerId) ->where('s.time_consum', '>', '0') ->avg('s.time_consum'); Db::name('workorder_engineers')->where('id', $engineerId)->update([ 'avg_response_time' => ceil($workorderTimeStatistics) ]); return $workorderTimeStatistics; } public static function orderNumberChangeCalcEngineerStatistics($orderId, $engineer = false, $type = 'del') { if ($engineer) { self::calcEngineerAvgResTime($engineer); } // 沟通记录 $records = Records::where('order_id', $orderId) ->order('createtime asc') ->select(); // 共有哪些工程师 $engineers = []; if ($engineer) { $engineers[$engineer] = $engineer; } foreach ($records as $index => $record) { if ($record->engineer_id) { $engineers[$record->engineer_id] = $record->engineer_id; } } $engineers = Engineer::select($engineers); Db::startTrans(); try { if ($type == 'del') { foreach ($engineers as $item) { $item->work_order_quantity--; $item->save(); } } elseif ($type == 'restore') { foreach ($engineers as $item) { $item->work_order_quantity++; $item->save(); } } Db::commit(); } catch (PDOException | Exception $e) { Db::rollback(); return false; } return true; } public function autoClose() { if ($this->workorderConfig['auto_close'] == 0) { return true; } $nowTime = time(); $orders = Orders::all(['status' => '3']); foreach ($orders as $index => $order) { $userLastRecordTime = Records::where('order_id', $order->id) ->where('user_id', '>', 0) ->order('createtime desc') ->value('createtime'); $autoCloseTime = $userLastRecordTime + (int)($this->workorderConfig['auto_close'] * 3600); if ($autoCloseTime <= time()) { $order->status = '4'; $order->save(); $record = [ 'order_id' => $order->id, 'engineer_id' => $order->engineer_id, 'message_type' => 3, 'message' => __('The job has been closed automatically!') ]; Records::create($record); // 记录结单耗时 $timeStatistics = Db::name('workorder_time_statistics') ->where('order_id', $order->id) ->where('type', 0) ->where('time_consum', null) ->find(); if ($timeStatistics) { Db::name('workorder_time_statistics')->where('id', $timeStatistics['id'])->update([ 'endtime' => $nowTime, 'time_consum' => $nowTime - $timeStatistics['starttime'], 'engineer_id' => $order->engineer_id,// 防转移,冲正为当前工程师 ]); } } } return true; } /** * 发送邮件通知 * @param object $row 工单 * @param string $event 事件 * @return bool * @throws \think\exception\DbException */ public function mailNotice($row, $event, $subject, $message) { if (!in_array($event, $this->workorderConfig['notice_mail'])) { return false; } if ($event == 'user_order_handle' || $event == 'user_got_reply') { if (!isset($row->email) || !isset($row->remind) || !$row->email || ($row->remind != 2)) { return false; } } else { if (!$row->engineer_id) { return false; } $row = \addons\workorder\model\Engineer::get($row->engineer_id); $row->email = $row->user->email; } if (!Validate::is($row->email, "email")) { return false; } $email = new Email; $result = $email->to($row->email) ->subject($subject) ->message('
' . $message . '
') ->send(); if ($result) { return true; } else { //$email->getError(); return false; } } /** * 发送短信通知 * @param object $row 工单 * @param string $event 事件 * @return bool * @throws \think\exception\DbException */ public function smsNotice($row, $event) { if (!in_array($event, $this->workorderConfig['notice_sms'])) { return false; } // 最多12个字符 $msg = mb_strlen($row->title) > 12 ? mb_substr($row->title, 0, 9) . '...' : $row->title; // 兼容短信宝和创蓝 $smsbao = get_addon_info('smsbao'); $clsms = get_addon_info('clsms'); if (($smsbao && $smsbao['state'] == 1) || ($clsms && $clsms['state'] == 1)) { switch ($event) { case 'user_order_handle': $msg = __('The engineer has viewed the work order you submitted:%s and is processing it.', [$msg]); break; case 'user_got_reply': $msg = __('The engineer has replied to your work order:%s, please check the feedback in time.', [$msg]); break; case 'engineer_new_order': $msg = __('Dear engineer, you have received a new work order:%s, please handle it in time.', [$msg]); break; case 'engineer_got_reply': $msg = __('Dear engineer, work order:%s, user feedback has been received, please handle it in time.', [$msg]); break; case 'engineer_urging': $msg = __('Dear engineer, work order:%s, the user hopes you will reply as soon as possible.', [$msg]); break; default: $msg = __('Sending scenario not recognized by the work order system'); break; } } if ($event == 'user_order_handle' || $event == 'user_got_reply') { if (!isset($row->mobile) || !isset($row->remind) || !$row->mobile || ($row->remind != 1)) { return false; } } else { if (!$row->engineer_id) { return false; } $row = \addons\workorder\model\Engineer::get($row->engineer_id); $row->mobile = $row->user->mobile; } if (!$row->mobile || !Validate::regex($row->mobile, "^1\d{10}$")) { return false; } if (!\think\Hook::get('sms_send')) { return false; } // 兼容创蓝 if ($clsms && $clsms['state'] == 1) { $clsms = new \addons\clsms\library\Clsms(); $result = $clsms->smstype(0)->mobile($row->mobile)->msg($msg)->send(); if ($result) { return true; } else { return false; } } // 兼容阿里云短信 $alisms = get_addon_info('alisms'); if ($alisms && $alisms['state'] == 1) { $params = [ 'mobile' => $row->mobile, 'msg' => [ 'title' => $msg ], 'event' => $event ]; $ret = \think\Hook::listen('sms_notice', $params, null, true); } else { $ret = Smslib::notice($row->mobile, $msg, $event); } if ($ret) { return true; } else { return false; } } /** * 获取文件后缀 * @param string $filename 文件路径/名称 * @return string */ public static function getFileExtension($filename) { $filename = explode('.', $filename); return end($filename); } /** * 生成文件后缀图片 * @param string $suffix 后缀 * @param null $background * @return string */ public static function buildSuffixImage($suffix, $background = null) { $suffix = mb_substr(strtoupper($suffix), 0, 4); $total = unpack('L', hash('adler32', $suffix, true))[1]; $hue = $total % 360; list($r, $g, $b) = hsv2rgb($hue / 360, 0.3, 0.9); $background = $background ? $background : "rgb({$r},{$g},{$b})"; $icon = << {$suffix} EOT; return $icon; } /** * 清理xss * @param string $val * @return string */ public static function removeXss($val) { if (!$val) { return ''; } if (function_exists('xss_clean')) { return xss_clean($val); } $val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val); $search = 'abcdefghijklmnopqrstuvwxyz'; $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $search .= '1234567890!@#$%^&*()'; $search .= '~`";:?+/={}[]-_|\'\\'; for ($i = 0; $i < strlen($search); $i++) { $val = preg_replace('/(&#[xX]0{0,8}' . dechex(ord($search[$i])) . ';?)/i', $search[$i], $val); // with a ; $val = preg_replace('/(�{0,8}' . ord($search[$i]) . ';?)/', $search[$i], $val); // with a ; } $ra1 = [ 'javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base' ]; $ra2 = [ 'onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload' ]; $ra = array_merge($ra1, $ra2); $found = true; while ($found == true) { $val_before = $val; for ($i = 0; $i < sizeof($ra); $i++) { $pattern = '/'; for ($j = 0; $j < strlen($ra[$i]); $j++) { if ($j > 0) { $pattern .= '('; $pattern .= '(&#[xX]0{0,8}([9ab]);)'; $pattern .= '|'; $pattern .= '|(�{0,8}([9|10|13]);)'; $pattern .= ')*'; } $pattern .= $ra[$i][$j]; } $pattern .= '/i'; $replacement = substr($ra[$i], 0, 2) . '' . substr($ra[$i], 2); $val = preg_replace($pattern, $replacement, $val); if ($val_before == $val) { $found = false; } } } return $val; } public static function createOrder($row, $userId) { $fields = Orders::getFields(null, 0); $records = []; // 要入沟通记录的字段-所有文件自动入沟通记录 $otherField = ''; $recordField = [ 'describe' => '0',// 描述-富文本 'confidential' => '4',// 机密信息-机密信息 ]; foreach ($fields as $index => $field) { if ($field['isrequire'] && $field['type_list'] != 'text' && (!isset($row[$field['name']]) || $row[$field['name']] == '')) { return ['code' => 0, 'msg' => __('%s can not be empty!', $field['title'])]; } if (isset($row[$field['name']]) && is_array($row[$field['name']])) { $row[$field['name']] = implode(',', $row[$field['name']]); } if ($field['type_list'] == 'editor') { $fieldOrigin = \think\Request::instance()->param($field['name'], '', 'trim'); if (!$fieldOrigin) { $fieldOrigin = \think\Request::instance()->param('row/a', '', 'trim'); $fieldOrigin = isset($fieldOrigin[$field['name']]) ? $fieldOrigin[$field['name']] : ''; } $row[$field['name']] = self::removeXss($fieldOrigin); } // 将uniapp端的城市名与数据库中的进行兼容 if ($field['type_list'] == 'city' && isset($row[$field['name']]) && $row[$field['name']]) { $city = explode('/', $row[$field['name']]); if ($city && mb_strpos($city[0], '市') !== false) { $city[1] = $city[0]; $city[0] = mb_substr($city[0], 0, mb_strlen($city[0]) - 1); $row[$field['name']] = implode('/', $city); } } if (array_key_exists($field['name'], $recordField) && isset($row[$field['name']]) && $row[$field['name']]) { $records[] = [ 'user_id' => $userId, 'message_type' => $recordField[$field['name']], 'message' => $row[$field['name']] ]; } if (in_array($field['type_list'], self::$fileField) && isset($row[$field['name']]) && $row[$field['name']]) { $attachment = explode(',', trim($row[$field['name']], ',')); $message_type = ($field['type_list'] == 'image' || $field['type_list'] == 'images') ? 1 : 2; foreach ($attachment as $item) { $itemInfo = pathinfo($item); if (in_array($itemInfo['extension'], self::$imgArr)) { $message_type = 1; } $records[] = [ 'user_id' => $userId, 'message_type' => $message_type, 'message' => $item ]; } } // 非聊天记录字段且非文件字段且非基本信息字段,对值进行格式化后写入聊天记录 if (!array_key_exists($field['name'], $recordField) && !in_array($field['type_list'], self::$fileField) && !$field['isbasicinfo']) { $fieldValue = self::fieldValue($field, $row[$field['name']]); if ($fieldValue) { $otherField .= $field['title'] . ':' . $fieldValue . '
'; } } } if ($otherField) { $records[] = [ 'user_id' => $userId, 'message_type' => 0, 'message' => $otherField ]; } $row['user_id'] = $userId; $row['status'] = 0; $row['createtime'] = time(); $row['updatetime'] = time(); try { $order = Orders::create($row); if ($records) { foreach ($records as $index => $record) { $records[$index]['batch'] = 1; $records[$index]['order_id'] = $order->id; } $recordsModel = new Records; $recordsModel->saveAll($records); } // 分派工单 $General = General::instance(); $engineerId = $General->distribution($order, $userId); // 准备记录结单和首回耗时 $timeStatistics[0] = [ 'type' => 0, 'order_id' => $order->id, 'engineer_id' => $engineerId, 'starttime' => time() ]; $timeStatistics[1] = $timeStatistics[0]; $timeStatistics[1]['type'] = 2; Db::name('workorder_time_statistics')->insertAll($timeStatistics); } catch (Exception $e) { return ['code' => 0, 'msg' => 'error:' . $e->getMessage()]; } return [ 'code' => 1, 'data' => ['id' => $order->id] ]; } public static function formatFileSize($num) { $p = 0; $format = 'bytes'; if ($num > 0 && $num < 1024) { $p = 0; return number_format($num) . ' ' . $format; } if ($num >= 1024 && $num < pow(1024, 2)) { $p = 1; $format = 'KB'; } if ($num >= pow(1024, 2) && $num < pow(1024, 3)) { $p = 2; $format = 'MB'; } if ($num >= pow(1024, 3) && $num < pow(1024, 4)) { $p = 3; $format = 'GB'; } if ($num >= pow(1024, 4) && $num < pow(1024, 5)) { $p = 3; $format = 'TB'; } $num /= pow(1024, $p); return number_format($num, 2) . ' ' . $format; } public function createEvaluate($order, $row, $userId) { $nowTime = time(); if (!$row['stars']) { return ['code' => 0, 'msg' => __('Please select the overall evaluation~')]; } elseif (!isset($row['solved'])) { return ['code' => 0, 'msg' => __('Please select whether the problem has been solved~')]; } $row['order_id'] = $order->id; $row['category_id'] = $order->category_id; $row['user_id'] = $userId; $row['createtime'] = $nowTime; if (Db::name('workorder_evaluate')->insert($row)) { $order->status = 5; $order->save(); // 记录结单耗时 $timeStatistics = Db::name('workorder_time_statistics') ->where('order_id', $order->id) ->where('type', 0) ->where('time_consum', null) ->find(); if ($timeStatistics) { Db::name('workorder_time_statistics')->where('id', $timeStatistics['id'])->update([ 'endtime' => $nowTime, 'time_consum' => $nowTime - $timeStatistics['starttime'], 'engineer_id' => $order->engineer_id,// 防转移,冲正为当前工程师 ]); } return ['code' => 1, 'msg' => __('Evaluation submitted successfully~')]; } else { return ['code' => 0, 'msg' => __('Evaluation failed, please try again!')]; } } /** * 回复工单 * @param object $order 工单数据 * @param object $user 用户数据 * @param object $engineer 工程师数据 * @param array $row 回复数据 * @var $order Orders 实例 */ public function createReply($order, $user, $engineer, $row) { $nowTime = time(); $replyField = $order->getFields(null, $user ? 1 : 2); // 要入沟通记录的字段-所有文件自动入沟通记录 $recordField = [ 'reply_describe' => '0',// 描述-富文本 'reply_confidential' => '4',// 机密信息-机密信息 ]; $records = []; $otherField = ''; foreach ($replyField as $index => $field) { if ($field['isrequire'] && (!isset($row[$field['name']]) || $row[$field['name']] == '')) { return ['code' => 0, 'msg' => __('%s can not be empty!', $field['title'])]; } if ($field['type_list'] == 'editor') { $fieldOrigin = \think\Request::instance()->param($field['name'], '', 'trim'); if (!$fieldOrigin) { $fieldOrigin = \think\Request::instance()->param('row/a', '', 'trim'); $fieldOrigin = $fieldOrigin[$field['name']] ?? ''; } $row[$field['name']] = self::removeXss($fieldOrigin); } if (array_key_exists($field['name'], $recordField) && isset($row[$field['name']]) && $row[$field['name']]) { $records[] = [ 'message_type' => $recordField[$field['name']], 'message' => $row[$field['name']] ]; } if (in_array($field['type_list'], self::$fileField) && isset($row[$field['name']]) && $row[$field['name']]) { $attachment = explode(',', trim($row[$field['name']], ',')); $message_type = ($field['type_list'] == 'image' || $field['type_list'] == 'images') ? 1 : 2; foreach ($attachment as $item) { $itemInfo = pathinfo($item); if (in_array($itemInfo['extension'], self::$imgArr)) { $message_type = 1; } $records[] = [ 'message_type' => $message_type, 'message' => $item ]; } } // 非回复字段且非文件字段,对值进行格式化后写入聊天记录 if (!array_key_exists($field['name'], $recordField) && !in_array($field['type_list'], self::$fileField)) { $fieldValue = self::fieldValue($field, $row[$field['name']]); if ($fieldValue) { $otherField .= $field['title'] . ':' . $fieldValue . '
'; } } } if ($otherField) { $records[] = [ 'message_type' => 0, 'message' => $otherField ]; } $batch = Records::where('order_id', $order->id)->max('batch'); foreach ($records as $index => $record) { $records[$index]['order_id'] = $order->id; $records[$index]['batch'] = $batch + 1; if ($user) { $records[$index]['sender'] = 'user'; $records[$index]['user_id'] = $user->id; $records[$index]['nickname'] = $user->nickname; $records[$index]['avatar'] = cdnurl($user->avatar, true); } elseif ($engineer) { $records[$index]['sender'] = 'engineer'; $records[$index]['engineer_id'] = $engineer->id; $records[$index]['title'] = $engineer->title; $records[$index]['avatar'] = ($engineer->user && $engineer->user->avatar) ? cdnurl($engineer->user->avatar, true) : (function_exists('letter_avatar') ? letter_avatar($engineer->title) : cdnurl('/assets/img/avatar.png', true)); } } $recordsModel = new Records; $records = $recordsModel->allowField(true)->saveAll($records); if ($records) { $timeStatistics = Db::name('workorder_time_statistics') ->where('order_id', $order->id) ->where('type', 'in', '1,2') ->where('time_consum', null) ->find(); $order->title = $order->title ?? __('Untitled'); if ($engineer) { if ($order->status == 2) { $order->status = 3; $this->mailNotice($order, 'user_got_reply', __('[new reply received for work order]') . $order->title, __('Work order:%s the engineer has replied to your question. Please check / feed back in time.', [$order->title])); $this->smsNotice($order, 'user_got_reply'); } if ($order->lasturgingtime) { $order->lasturgingtime = null; } // 计算回复时间 if ($timeStatistics) { Db::name('workorder_time_statistics')->where('id', $timeStatistics['id'])->update([ 'endtime' => $nowTime, 'time_consum' => $nowTime - $timeStatistics['starttime'], 'engineer_id' => $order->engineer_id,// 防转移,冲正为当前工程师 ]); $this->calcEngineerAvgResTime($order->engineer_id); } } else { if ($order->status == 3) { $order->status = 2; $this->mailNotice($order, 'engineer_got_reply', __('[work order receives new feedback]') . $order->title, __('Work order:%s the user has fed back new information, please check / reply in time.', [$order->title])); $this->smsNotice($order, 'engineer_got_reply'); } // 准备记录回复时间 if (!$timeStatistics) { $timeStatistics = [ 'type' => 1, 'order_id' => $order->id, 'engineer_id' => $order->engineer_id, 'starttime' => time() ]; Db::name('workorder_time_statistics')->insert($timeStatistics); } } $order->allowField(true)->save(); return [ 'code' => 1, 'msg' => __('Reply Success~'), 'data' => [ 'records' => $records ] ]; } return ['code' => 0, 'msg' => __('Nothing happened~')]; } public function transfer($order, $transferEngineer) { $engineer = Engineer::get($transferEngineer); if (!$engineer->user_id) { return ['code' => 0, 'msg' => __('The engineer has not bound users!')]; } if ($engineer->user_id == $order->user_id) { return ['code' => 0, 'msg' => __('Engineer and issuer cannot be the same person!')]; } if ($engineer && $engineer->status == '1') { $order->engineer_id = $transferEngineer; $order->save(); $engineer->lastreceivetime = time(); $engineer->work_order_quantity++; $engineer->save(); $record = [ 'order_id' => $order->id, 'engineer_id' => $transferEngineer, 'message_type' => 3, 'message' => __('The work order has been transferred to:%s', $engineer->title) ]; Records::create($record); return ['code' => 1, 'msg' => __('Work order transferred~')]; } return ['code' => 0, 'msg' => __('Nothing happened~')]; } public function distribution($order, $userId) { if (isset($order->engineer_id) && $order->engineer_id) { return false; } elseif (!$order->category_id) { return false; } $distribution_engineer = null; $where['status'] = '1'; $where['user_id'] = ['<>', $userId]; if ($this->workorderConfig['distribution_type'] == 2) { // 技能分派-循环 $category_engineer = Db::name('workorder_category')->where('id', $order->category_id)->value('we_ids'); if (!$category_engineer) { return false; } $where['id'] = ['in', $category_engineer]; $this->workorderConfig['distribution_type'] = 0; } elseif ($this->workorderConfig['distribution_type'] == 3) { // 技能分派-负载 $category_engineer = Db::name('workorder_category')->where('id', $order->category_id)->value('we_ids'); if (!$category_engineer) { return false; } $where['id'] = ['in', $category_engineer]; $this->workorderConfig['distribution_type'] = 1; } if ($this->workorderConfig['distribution_type'] == 0) { $distribution_engineer = Engineer::where($where)->order('lastreceivetime asc')->find(); } elseif ($this->workorderConfig['distribution_type'] == 1) { $engineer = Engineer::where($where)->select(); foreach ($engineer as $index => $item) { $orderNumber = Orders::where('engineer_id', $item->id)->count(); if ($orderNumber == 0) { $distribution_engineer = $item; break; } else { if (isset($minOrder)) { if ($orderNumber < $minOrder) { $minOrder = $orderNumber; } } else { $distribution_engineer = $item; $minOrder = $orderNumber; } } } } if (!$distribution_engineer) { return false; } $distribution_engineer->lastreceivetime = time(); $distribution_engineer->work_order_quantity++; $distribution_engineer->save(); $order->engineer_id = $distribution_engineer->id; $order->status = 1; $order->save(); // 发送通知 if (isset($order->title)) { $this->mailNotice($order, 'engineer_new_order', __('[new work order]') . $order->title, __('The user submitted a new job:%s please process it as soon as possible.', [$order->title])); $this->smsNotice($order, 'engineer_new_order'); } return $distribution_engineer->id; } public static function handleUrl($url, $category) { if (preg_match('/^https?:\/\//i', $url)) { if (strpos($url, '?') === false) { $url .= '?category=' . $category; } else { $url .= '&category=' . $category; } return $url; } else { return url($url, ['category' => $category]); } } /** * 推荐知识点 */ public static function recKbs($id, $category = false, $limit = 20) { $recKbs = []; if ($category) { $kbsIds = Db::name('workorder_category') ->where('id', $category) ->where('status', '1') ->where('deletetime', null) ->value('kbs_ids'); if ($kbsIds) { $recKbs = Db::name('workorder_kbs') ->field('id,title,url') ->where('id', 'in', $kbsIds) ->where('status', '1') ->where('deletetime', null) ->where('id', '<>', $id) ->order('weigh desc') ->limit($limit) ->select(); } } if (!$recKbs) { $recKbs = Db::name('workorder_kbs') ->field('id,title') ->where('status', '1') ->where('deletetime', null) ->where('id', '<>', $id) ->order('weigh desc,views desc') ->limit($limit) ->select(); } return $recKbs; } public function engineerViewed($order) { if ($order->status == 1) { $order->status = 2; $order->allowField(true)->save(); $record = [ 'order_id' => $order->id, 'engineer_id' => $order->engineer_id, 'message_type' => 3, 'message' => __('The engineer has viewed your submitted questions') ]; Records::create($record); // 发送通知 $this->mailNotice($order, 'user_order_handle', __('[work order is already being processed]') . $order->title, __('Work order:%s the engineer has reviewed the problem you submitted and is processing it', [$order->title])); $this->smsNotice($order, 'user_order_handle'); return true; } return false; } /** * 工单详情处理 * @var Orders $order */ public function orderInfoHandle($order, $isUser, $isCurrentEngineer) { $nowTime = time(); // 下次可催办时间 $urging_rate = (int)($this->workorderConfig['urging_rate'] * 60); $nextUrgingTime = (int)$order->lasturgingtime + $urging_rate; $order->title = $order->title ?? __('Untitled'); // 标记工程师处理中 if ($isCurrentEngineer) { $this->engineerViewed($order); } $order->urging = false; $order->close = false; if ($isUser && $order->status <= 2 && $nextUrgingTime <= $nowTime && $order->engineer_id && $this->workorderConfig['user_urging']) { $order->urging = true; } if ($order->status <= 3) { $order->close = true; } $order->status = $this->handleStatus($order->status, $isCurrentEngineer); // 处理用户头像 if ($order->user) { $order->user->avatar = $order->user->avatar ? cdnurl($order->user->avatar, true) : (function_exists('letter_avatar') ? letter_avatar($order->user->nickname) : cdnurl('/assets/img/avatar.png', true)); } $basicField = [];// 工单基本信息字段 $allField = $order->getFields($order, 0); foreach ($allField as $index => $field) { if ($field['isbasicinfo']) { $field['value'] = self::fieldValue($field, $field['value']); $basicField[] = $field; } } if ($isCurrentEngineer) { $basicField[] = [ 'title' => __('Submit user'), 'value' => $order->user->nickname ]; if (!$this->workorderConfig['engineer_close']) { $order->close = false; } } return [ 'order' => $order, 'basicField' => $basicField ]; } public static function fieldValue($field, $value) { if ($field['type_list'] == 'switch') { return $value ? __('open') : __('close'); } if ($value == '') { return ''; } $listField = ['select', 'selects', 'checkbox', 'radio']; if (in_array($field['type_list'], $listField)) { if (is_array($field['values_list'])) { $valueTmp = ''; if (!is_array($value)) { $value = explode(',', $value); } foreach ($value as $key => $item) { $valueTmp .= isset($field['values_list'][$item]) ? $field['values_list'][$item] . ',' : ''; } return trim($valueTmp, ','); } } return $value; } /** * 关闭工单 * @return array * @var Orders $order */ public function closeOrder($order, $isCurrentEngineer) { $nowTime = time(); if ($order->status == 4 || $order->status == 5) { return ['code' => 0, 'msg' => __('The work order has been closed!')]; } if ($isCurrentEngineer && ($this->workorderConfig['engineer_close'] == 0)) { return ['code' => 0, 'msg' => __('The engineer was not allowed to close the work order~')]; } $order->status = 4; $order->save(); $timeStatistics = Db::name('workorder_time_statistics') ->where('order_id', $order->id) ->where('type', 0) ->where('time_consum', null) ->find(); if ($timeStatistics) { // 记录结单耗时 Db::name('workorder_time_statistics') ->where('id', $timeStatistics['id']) ->update([ 'endtime' => $nowTime, 'time_consum' => $nowTime - $timeStatistics['starttime'], 'engineer_id' => $order->engineer_id,// 防转移,冲正为当前工程师 ]); } return ['code' => 1, 'msg' => __('Work order closed successfully~')]; } /** * 催单 * @return array * @var Orders $order */ public function urgingOrder($order) { $nowTime = time(); if (!$this->workorderConfig['user_urging']) { return ['code' => 0, 'msg' => __('Reminder function not enabled~')]; } // 下次可催办时间-在isPost内有使用 $urging_rate = (int)($this->workorderConfig['urging_rate'] * 60); $nextUrgingTime = (int)$order->lasturgingtime + $urging_rate; if ($nextUrgingTime <= $nowTime) { $order->lasturgingtime = $nowTime; $order->save(); $order->title = $order->title ?? __('Untitled'); $this->mailNotice($order, 'engineer_urging', __('[work order reminder]') . $order->title, __('Dear engineer, work order:%s, the user hopes you will reply as soon as possible.', [$order->title])); $this->smsNotice($order, 'engineer_urging'); return ['code' => 1, 'msg' => __('Reminder message sent successfully~')]; } else { return ['code' => 0, 'msg' => __('Urge message sent frequently!')]; } } /** * 获取一个工单的聊天记录和所有工程师 * @param int $id 工单ID * @param int $engineer 工单当前工程师 * @return array */ public function orderRecords($id, $engineer = 0) { // 沟通记录 $records = Records::where('order_id', $id) ->order('createtime asc') ->select(); // 共有哪些工程师 $engineers = []; if ($engineer) { $engineers[$engineer] = $engineer; } foreach ($records as $index => $record) { if ($record->engineer_id) { $engineers[$record->engineer_id] = $record->engineer_id; } if ($record->engineer && $record->engineer->user) { $record->engineer->user->avatar = $record->engineer->user->avatar ? cdnurl($record->engineer->user->avatar, true) : (function_exists('letter_avatar') ? letter_avatar($record->engineer->title) : cdnurl('/assets/img/avatar.png', true)); } } $engineers = Engineer::select($engineers); $engineerInfoConfig = Engineer::getEngineerInfoConfig(); foreach ($engineers as $index => $engineer) { if (!$engineer->introduce) { $engineer->introduce = ($engineer->user && $engineer->user->bio) ? $engineer->user->bio : ''; } $engineer->all_order_number = in_array('all_order_number', $engineerInfoConfig) ? $engineer->all_order_number : false; $engineer->wx = in_array('wx', $engineerInfoConfig) ? $engineer->wx : false; $engineer->qq = in_array('qq', $engineerInfoConfig) ? $engineer->qq : false; if ($engineer->user) { $engineer->user->mobile = in_array('mobile', $engineerInfoConfig) ? $engineer->user->mobile : false; $engineer->user->email = in_array('email', $engineerInfoConfig) ? $engineer->user->email : false; $engineer->user->avatar = $engineer->user->avatar ? cdnurl($engineer->user->avatar, true) : (function_exists('letter_avatar') ? letter_avatar($engineer->title) : cdnurl('/assets/img/avatar.png', true)); } } return [ 'records' => $records, 'engineers' => $engineers ]; } public function handleStatus($status, $isEngineer = false) { $colors = [ 'info', 'info', 'warning', 'danger', 'danger', 'success' ]; $statusLang = __('Status ' . $status); if ($isEngineer) { switch ($status) { case 3: $statusLang = __('Waiting for user feedback'); break; case 4: $statusLang = __('To be evaluated by users'); break; case 2: $statusLang = __('Waiting for your reply'); break; } } return [ 'pc' => '' . $statusLang . '', 'h5' => '[' . $statusLang . ']', 'original' => $statusLang, 'number' => $status ]; } }