diff --git a/app/common/controller/NotifyController.php b/app/common/controller/NotifyController.php new file mode 100644 index 0000000..9e4fffb --- /dev/null +++ b/app/common/controller/NotifyController.php @@ -0,0 +1,136 @@ +post(); + + // 解析数据 + // $param = json_decode($param['event'], true); + $orderSn = $param['event']['action']['value']; + if (strpos($orderSn, '"') !== false) { + $orderSn = str_replace('"', '', $orderSn); + } + + // 处理询单&客户线索信息 + $openId = $param['event']['operator']['open_id']; + + // 获取询单 + $order = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('order_no', $orderSn)->first(); + if (!$order) { + return $this->error('未找到询单记录'); + } + + // 获取客户xians + $customer = CrmCustomer::where('sn', $orderSn)->first(); + if (!$customer) { + return $this->error('未找到客户线索'); + } + + // 获取用户 + $uinfo = User::where('open_id', $openId)->first(); + + // 更新客户线索 + $customer->user_id = $uinfo->id; + + // 更新询单数据 + $order->recipient = $uinfo->id; + $order->status = 2; + $order->t_time = time(); + + // 保存 + if (!$order->save() || !$customer->save()) { + return $this->error('跟踪失败'); + } + + // 获取飞书消息模板 + $feishuMessage = new FeishuMessage(); + $content = $feishuMessage->groupUpdateTemplates( + (new InquiryLogService())->qualityName($order->quality), + $order->order_no, + $order->channelInfo ? $order->channelInfo->name : '', + $order->countryInfo ? $order->countryInfo->cn_addr : '', + $order->enter ? $order->enter->name : '', + $order->r_time ? date('Y-m-d H:i:s', $order->r_time) : '', + $order->t_time ? date('Y-m-d H:i:s', $order->t_time) : '' + ); + + // 组装飞书消息模板 + $data = '{ + "toast": { + "type": "success", + "content": "操作成功" + }, + "card": { + "type": "raw", + "data": '.$content.' + } + }'; + + // 响应消息 + return new Response(200, ['Content-Type' => 'application/json'], $data); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function error($msg = '') + { + $data = '{ + "toast": { + "type": "error", + "content": "'.$msg.'" + } + }'; + return new Response(200, ['Content-Type' => 'application/json'], $data); + } + + /** + * 飞书事件 + * @param Request $request + * @return Response + */ + public function feishuEvent(Request $request) + { + try { + $data = $request->post(); + + var_dump($data); + + return json($data); + } catch (\Throwable $e) { + return json($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/controller/PresetController.php b/app/common/controller/PresetController.php index a4b2a81..27f0c7a 100644 --- a/app/common/controller/PresetController.php +++ b/app/common/controller/PresetController.php @@ -6,6 +6,7 @@ use support\Request; use app\common\service\CorpService; use app\common\service\CorpFeishuAppService; use app\common\service\SystemsService; +use app\common\service\UserService; class PresetController extends BaseController { @@ -48,4 +49,15 @@ class PresetController extends BaseController return $this->error($e->getMessage()); } } + + + public function users() + { + try { + $result = (new UserService())->getList(); + return $this->success($result, '获取用户列表'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/common/logic/UserLogic.php b/app/common/logic/UserLogic.php index 4b58247..9f5ae44 100644 --- a/app/common/logic/UserLogic.php +++ b/app/common/logic/UserLogic.php @@ -53,4 +53,19 @@ class UserLogic return $res; } + /** + * 获取用户列表 + */ + public function getList() + { + $list = User::where('status', 1)->orderBy('id', 'desc')->get(); + $arr = []; + foreach ($list->toArray() as $item) { + $arr[] = [ + 'name' => $item['name'], + 'id' => $item['id'], + ]; + } + return $arr; + } } \ No newline at end of file diff --git a/app/common/model/CrmCustomer.php b/app/common/model/CrmCustomer.php new file mode 100644 index 0000000..49446e6 --- /dev/null +++ b/app/common/model/CrmCustomer.php @@ -0,0 +1,49 @@ +hasOne(User::class, 'id', 'recipient'); } - // public function enter() - // { - // return $this->belongsTo(User::class, 'enter_user_id'); - // } + public function channelInfo() + { + return $this->hasOne(InquiryChannel::class, 'id', 'channel'); + } - // public function recipient() - // { - // return $this->belongsTo(User::class, 'recipient'); - // } - - public function country() + public function countryInfo() { return $this->hasOne(Country::class, 'id', 'country'); } diff --git a/app/common/model/User.php b/app/common/model/User.php index 5d1b8f0..446ed46 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -96,4 +96,4 @@ class User extends Model ->orWhere('phone', $account) ->orWhere('email', $account); } -} +} \ No newline at end of file diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 85684d3..25de588 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -5,6 +5,7 @@ namespace app\common\service; use app\common\logic\UserLogic; use app\common\model\Corp; use app\common\model\User; +use app\common\utils\CrudUtil; use app\crm\model\Country; use support\exception\BusinessException; @@ -99,4 +100,12 @@ class UserService $user = $this->logic->savePassword($uid,$data); return $user; } + + /** + * 获取用户列表 + */ + public function getList() + { + return $this->logic->getList(); + } } \ No newline at end of file diff --git a/app/common/traits/crud/CrudLogicTrait.php b/app/common/traits/crud/CrudLogicTrait.php index 4e2b794..9e629f9 100644 --- a/app/common/traits/crud/CrudLogicTrait.php +++ b/app/common/traits/crud/CrudLogicTrait.php @@ -38,14 +38,6 @@ trait CrudLogicTrait return CrudUtil::findOneBy(CrudUtil::buildQuery($config)); } - /** - * 任意条件查询单条(可带 dataLimit) - */ - // public function logicShowBy(array $where, array $with = [], array $dataLimit = []) - // { - // return CrudUtil::findOneBy($this->modelClass, $where, $with, $dataLimit); - // } - /** * 创建(可带 dataLimit) */ @@ -54,14 +46,6 @@ trait CrudLogicTrait return CrudUtil::create($this->modelClass, $data, $dataLimit); } - /** - * 主键更新(可带 dataLimit) - */ - // public function logicUpdateByPk($id, array $data, array $dataLimit = []) - // { - // return CrudUtil::updateByPk($this->modelClass, $id, $data, $dataLimit); - // } - /** * 任意条件更新(可带 dataLimit) */ @@ -70,14 +54,6 @@ trait CrudLogicTrait return CrudUtil::updateBy(CrudUtil::buildQuery($config), $data); } - /** - * 主键删除(批量) - */ - // public function logicDeleteByPk(array $ids) - // { - // return CrudUtil::deleteByPk($this->modelClass, $ids); - // } - /** * 任意条件删除(可带 dataLimit) */ @@ -93,4 +69,12 @@ trait CrudLogicTrait { return CrudUtil::executeComplex($this->modelClass, $callback); } + + /** + * 根据主键查询 + */ + public function findByPk(int $id, array $with = []) + { + return CrudUtil::findByPk($this->modelClass, $id, $with); + } } diff --git a/app/common/traits/crud/CrudServiceTrait.php b/app/common/traits/crud/CrudServiceTrait.php index c042b5f..5ad6dbc 100644 --- a/app/common/traits/crud/CrudServiceTrait.php +++ b/app/common/traits/crud/CrudServiceTrait.php @@ -32,8 +32,8 @@ trait CrudServiceTrait $result = $this->complexQuery($request->get()); } else { $config = $this->buildQueryConfig($request, 'index'); - $limit = (int)$request->get('limit', 15); - $page = (int)$request->get('page', 1); + $limit = (int)$request->get('size', 15); + $page = (int)$request->get('current', 1); $result = $this->logicList($config, $limit, $page); } return $this->postProcess('List', $result); @@ -62,7 +62,7 @@ trait CrudServiceTrait /** 创建 */ public function serviceStore(Request $request, array $data = []) { - // $data = $request->post(); + $data = $this->preProcess('Store', $data, $request); $dl = $this->getDataLimitFromRequest($request); $created = $this->logicCreate($data, $dl); return $this->postProcess('Store', $created); @@ -71,7 +71,7 @@ trait CrudServiceTrait /** 更新 */ public function serviceUpdate(Request $request, array $data = []) { - // $data= $request->post(); + $data = $this->preProcess('Update', $data, $request); $config = $this->buildQueryConfig($request, 'update'); $updated = $this->logicUpdateBy($config, $data); return $this->postProcess('Update', $updated); @@ -88,16 +88,7 @@ trait CrudServiceTrait /** ----------------- 公共辅助方法 ----------------- */ /** - * 构造 CrudUtil 的查询配置1 - * - * 前端可以通过以下参数控制: - * - filter_= → where - * - sort_by=&order= - * - fields=id,name,email → select - * - with=roles,posts → 关联 - * - withFields=roles:id,name;... → 关联字段 - * - dataLimitType=personal|auth - * - dataLimitField=admin_id + * 构造 CrudUtil 查询配置 */ protected function buildQueryConfig(Request $request, string $scene = 'default'): array { @@ -131,37 +122,6 @@ trait CrudServiceTrait } return $config; - - // // 1. where 从 filter_ 前缀自动映射 - // $where = []; - // foreach ($p as $key => $val) { - // if (strpos($key, 'filter_') === 0 && $val !== '') { - // $col = substr($key, 7); - // // 支持 %like% 查询 - // $where[$col] = strpos($val, '%') !== false - // ? ['like', $val] - // : $val; - // } - // } - - // // 2. 其余配置 - // return [ - // 'model' => $this->modelClass, - // 'where' => $where, - // 'order' => [ - // $p['sort_by'] ?? 'id' => $p['order'] ?? 'desc' - // ], - // 'fields' => !empty($p['fields']) - // ? explode(',', $p['fields']) - // : [], - // 'with' => !empty($p['with']) - // ? explode(',', $p['with']) - // : [], - // 'withFields' => $this->parseWithFields( - // $p['withFields'] ?? '' - // ), - // 'dataLimit' => $this->getDataLimitFromRequest($request), - // ]; } /** 解析 withFields 字符串 "rel:field,field;..." */ @@ -191,14 +151,20 @@ trait CrudServiceTrait ]; } - /** - * 处理复杂查询 - * 覆盖此方法,实现复杂查询逻辑 + /** + * $data 前置处理 + * + * @param array $data + * @param Request $request + * @return array */ - // protected function complexQuery(array $params): array - // { - // throw new \LogicException('请在子类实现 complexQuery'); - // } + protected function preProcess(string $op, mixed $data, Request $request): mixed + { + $hook = "beforeService{$op}"; + return method_exists($this, $hook) + ? $this->{$hook}($data, $request) + : $data; + } /** * 根据操作类型,调用对应的后置钩子 @@ -214,4 +180,14 @@ trait CrudServiceTrait ? $this->{$hook}($data) : $data; } + + + /** + * 处理复杂查询 + * 覆盖此方法,实现复杂查询逻辑 + */ + // protected function complexQuery(array $params): array + // { + // throw new \LogicException('请在子类实现 complexQuery'); + // } } \ No newline at end of file diff --git a/app/common/utils/CrudUtil.php b/app/common/utils/CrudUtil.php index 979181f..dec779b 100644 --- a/app/common/utils/CrudUtil.php +++ b/app/common/utils/CrudUtil.php @@ -326,7 +326,7 @@ class CrudUtil } /** - * 真正执行一个条件的 where + * 执行条件 where */ protected static function applySingleWhere(Builder $q, string $col, string $op, $v): void { diff --git a/app/common/utils/FeishuMessage.php b/app/common/utils/FeishuMessage.php new file mode 100644 index 0000000..311ffd9 --- /dev/null +++ b/app/common/utils/FeishuMessage.php @@ -0,0 +1,555 @@ +class = new Feishu($this->app_id, $this->app_secret); + } + + /** + * 飞书消息模板 + * @param string $scene + * @param array $options + */ + public function messageTemplates(string $scene = 'group-send', array $options = []) + { + try { + $template = match ($scene) { + 'inquiry-group-send' => $this->groupNewTemplates( + $options['quality'], + $options['order_no'], + $options['channel'], + $options['country'], + $options['enter_user'], + $options['r_time'] + ), + 'inquiry-group-update' => $this->groupUpdateTemplates( + $options['quality'], + $options['order_no'], + $options['channel'], + $options['country'], + $options['enter_user'], + $options['r_time'], + $options['t_time'] + ), + 'inquiry-person-allocation' => $this->personAllocationTemplates( + $options['quality'], + $options['order_no'], + $options['channel'], + $options['country'], + $options['enter_user'], + $options['r_time'], + $options['t_time'] + ), + 'crm-person-follow-up' => $this->personFollowUpTemplates( + $options['contacts'], + $options['follow_next'], + $options['follow_last'] + ) + }; + } catch (\UnhandledMatchError $e) { + throw new BusinessException($e->getMessage()); + } + return $template; + } + + /** + * 发送飞书消息 + * @param array $options + */ + public function messageSend(string $scene = 'group-send', array $options = []) + { + $template = $this->messageTemplates($scene, $options); + $result = $this->class->sendMessages($options['receive_id'], $template, $options['msg_type'], $options['receive_id_type']); + if (!isset($result['code']) || $result['code'] !== 0) { + throw new BusinessException('发送飞书消息失败'); + } + return $result; + } + + /** + * 线索消息发送 + */ + public function groupNewTemplates($quality = '💛 银牌商机', $order_no = 'SN2025072365640', $channel = '谷歌', $country = '白俄罗斯', $enter_user = '木子李', $r_time = '2025-07-24 09:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": " **'.$quality.'**\n- **编号:** '.$order_no.'\n- **渠道:** '.$channel.'\n- **国家:** '.$country.'\n- **发布人:** '.$enter_user.'\n- **发布时间:** '.$r_time.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "form", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "flex_mode": "stretch", + "horizontal_spacing": "8px", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "auto", + "elements": [ + { + "tag": "button", + "text": { + "tag": "plain_text", + "content": "立即跟踪" + }, + "type": "primary_filled", + "width": "fill", + "size": "medium", + "icon": { + "tag": "standard_icon", + "token": "list-check-bold_outlined" + }, + "confirm": { + "title": { + "tag": "plain_text", + "content": "确认跟踪?" + }, + "text": { + "tag": "plain_text", + "content": "客户线索:'.$order_no.'" + } + }, + "behaviors": [ + { + "type": "callback", + "value": "'.$order_no.'" + } + ], + "form_action_type": "submit", + "name": "Button_m7d0zqa2", + "element_id": "track" + } + ], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top" + }, + { + "tag": "column", + "width": "auto", + "elements": [], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top" + }, + { + "tag": "column", + "width": "auto", + "elements": [], + "padding": "0px 0px 0px 0px", + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "margin": "0px 0px 0px 0px" + } + ], + "margin": "4px 0px 0px 0px" + } + ], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "12px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 0px 0px 0px", + "margin": "0px 0px 0px 0px", + "name": "Form_m7d0zqa1" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "新的【公海】客户线索" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "template": "default", + "icon": { + "tag": "standard_icon", + "token": "ai-common_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } + + /** + * 线索消息更新 + */ + public function groupUpdateTemplates($quality = '💛 银牌商机', $order_no = 'SN2025072365640', $channel = '谷歌', $country = '白俄罗斯', $enter_user = '木子李', $r_time = '2025-07-24 09:41:23', $t_time = '2025-07-24 14:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": " **'.$quality.'**\n- **编号:** '.$order_no.'\n- **渠道:** '.$channel.'\n- **国家:** '.$country.'\n- **发布人:** '.$enter_user.'\n- **发布时间:** '.$r_time.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "div", + "text": { + "tag": "plain_text", + "content": "跟踪时间:'.$t_time.'", + "text_size": "notation", + "text_align": "left", + "text_color": "green" + }, + "icon": { + "tag": "standard_icon", + "token": "chat-done_outlined", + "color": "green" + }, + "element_id": "track_time" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "【公海】客户线索" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "text_tag_list": [ + { + "tag": "text_tag", + "text": { + "tag": "plain_text", + "content": "已跟踪" + }, + "color": "orange" + } + ], + "template": "green", + "icon": { + "tag": "standard_icon", + "token": "ai-common_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } + + /** + * 线索分配消息 + */ + public function personAllocationTemplates($quality = '💛 银牌商机', $order_no = 'SN2025072365640', $channel = '谷歌', $country = '白俄罗斯', $enter_user = '木子李', $r_time = '2025-07-24 09:41:23', $t_time = '2025-07-24 14:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": " **'.$quality.'**\n- **编号:** '.$order_no.'\n- **渠道:** '.$channel.'\n- **国家:** '.$country.'\n- **录单人:** '.$enter_user.'\n- **录入时间:** '.$r_time.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "div", + "text": { + "tag": "plain_text", + "content": "分配时间:'.$t_time.'", + "text_size": "notation", + "text_align": "left", + "text_color": "green" + }, + "icon": { + "tag": "standard_icon", + "token": "chat-done_outlined", + "color": "green" + }, + "element_id": "allocation_time" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "新的客户线索" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "text_tag_list": [ + { + "tag": "text_tag", + "text": { + "tag": "plain_text", + "content": "指定" + }, + "color": "yellow" + } + ], + "template": "blue", + "icon": { + "tag": "standard_icon", + "token": "todo_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } + + /** + * 跟进提醒消息 + */ + public function personFollowUpTemplates($contacts = '木子李', $follow_next = '2025-07-24 14:41:23', $follow_last = '2025-07-24 14:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": "- **客户名称:** '.$contacts.'\n- **跟进时间:** '.$follow_next.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "div", + "text": { + "tag": "plain_text", + "content": "最近跟进:'.$follow_last.'", + "text_size": "notation", + "text_align": "left", + "text_color": "grey" + }, + "icon": { + "tag": "standard_icon", + "token": "chat_outlined", + "color": "grey" + }, + "element_id": "track_time" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "跟进提醒" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "template": "green", + "icon": { + "tag": "standard_icon", + "token": "calendar_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } +} \ No newline at end of file diff --git a/app/common/utils/scaffold/Generate.php b/app/common/utils/scaffold/Generate.php index 6735997..753a9cf 100644 --- a/app/common/utils/scaffold/Generate.php +++ b/app/common/utils/scaffold/Generate.php @@ -24,44 +24,51 @@ class Generate { $fs = new Filesystem(); - // 模板路径 $templateDir = __DIR__ . '/templates/'; - $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".$this->rName($this->options['controller']['name'])."Controller.php"; - $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".$this->rName($this->options['service']['name'])."Service.php"; - $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".$this->rName($this->options['logic']['name'])."Logic.php"; - $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".$this->rName($this->options['model']['name']).".php"; - $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".$this->rName($this->options['validate']['name'])."Validate.php"; - // 创建目录(如果不存在) - $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['model']['module']}/model"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['validate']['module']}/validate"); + $resultStr = ''; - // // 删除已存在的文件并重新生成 - $this->deleteFileIfExists($controllerPath); - $this->deleteFileIfExists($servicePath); - $this->deleteFileIfExists($logicPath); - $this->deleteFileIfExists($modelPath); - $this->deleteFileIfExists($validatePath); + if (!empty($this->options['controller'])) { + $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".$this->rName($this->options['controller']['name'])."Controller.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); + $this->deleteFileIfExists($controllerPath); + $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath, 'controller'); + $resultStr .= $ControllerStub; + } - // 生成控制器文件 - $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath, 'controller'); - - // 生成服务类文件 - $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath, 'service'); - - // 生成逻辑类文件 - $LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath, 'logic'); - - // 生成模型类文件 - $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model'); - - // 生成验证器类文件 - $ValidatorStub = $this->generateFile($templateDir, 'ValidateStub.stub', $validatePath, 'validate'); - - return $ControllerStub . $ServiceStub . $LogicStub . $ModelStub . $ValidatorStub . "文件生成成功!
"; + if (!empty($this->options['service'])) { + $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".$this->rName($this->options['service']['name'])."Service.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service"); + $this->deleteFileIfExists($servicePath); + $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath, 'service'); + $resultStr .= $ServiceStub; + } + + if (!empty($this->options['logic'])) { + $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".$this->rName($this->options['logic']['name'])."Logic.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic"); + $this->deleteFileIfExists($logicPath); + $LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath, 'logic'); + $resultStr .= $LogicStub; + } + + if (!empty($this->options['model'])) { + $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".$this->rName($this->options['model']['name']).".php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['model']['module']}/model"); + $this->deleteFileIfExists($modelPath); + $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model'); + $resultStr .= $ModelStub; + } + + if (!empty($this->options['validate'])) { + $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".$this->rName($this->options['validate']['name'])."Validate.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['validate']['module']}/validate"); + $this->deleteFileIfExists($validatePath); + $ValidatorStub = $this->generateFile($templateDir, 'ValidateStub.stub', $validatePath, 'validate'); + $resultStr .= $ValidatorStub; + } + + return $resultStr ? $resultStr . "文件生成成功!
" : '未生成任何文件'; } /** diff --git a/app/common/validate/InquiryLogValidate.php b/app/common/validate/InquiryLogValidate.php index ca266b1..2a869bb 100644 --- a/app/common/validate/InquiryLogValidate.php +++ b/app/common/validate/InquiryLogValidate.php @@ -3,27 +3,174 @@ namespace app\common\validate; use think\Validate; +use app\common\model\InquiryLog; class InquiryLogValidate extends Validate { // 规则 protected $rule = [ - + 'id' => 'require|integer|gt:0', + 'order_no' => 'require|alphaDash|length:36', + 'enter_user_id'=> 'integer|egt:0', + 'recipient' => 'integer|egt:0', + 'quality' => 'require|in:1,2,3', + 'channel' => 'integer|between:0,99', + 'email' => 'email|max:255|checkEmailContactUnique', + 'tel' => 'max:255|checkTelContactUnique', + 'whatsapp' => 'max:255|checkWhatsappContactUnique', + 'country' => 'integer|egt:0|requireWith:contacts', + 'contacts' => 'require|max:255|requireWithout:email,tel,whatsapp|checkContactUnique', + 'content' => 'string', + 'status' => 'in:0,1,2', + 'r_time' => 'integer|egt:0', + 't_time' => 'integer|egt:0' ]; // 提示 protected $message = [ - + 'id.require' => 'ID不能为空', + 'id.integer' => 'ID必须是整数', + 'id.gt' => 'ID必须大于0', + 'order_no.require' => '订单号不能为空', + 'order_no.alphaDash'=> '订单号只能包含字母、数字和下划线', + 'order_no.length' => '订单号长度必须为36位', + 'enter_user_id.integer' => '录入人ID必须是整数', + 'enter_user_id.egt' => '录入人ID不能为负数', + 'recipient.integer' => '接单人ID必须是整数', + 'recipient.egt' => '接单人ID不能为负数', + 'quality.require' => '询单等级不能为空', + 'quality.in' => '询单等级只能是1、2或3', + 'channel.integer' => '渠道编号必须是整数', + 'channel.between' => '渠道编号范围0-99', + 'email.email' => '邮箱格式不正确', + 'email.max' => '邮箱长度不能超过255个字符', + 'tel.max' => '电话长度不能超过255个字符', + 'whatsapp.max' => 'WhatsApp长度不能超过255个字符', + 'country.integer' => '国家代码必须是整数', + 'country.egt' => '国家代码不能为负数', + 'contacts.require' => '联系人不能为空', + 'contacts.max' => '联系人长度不能超过255个字符', + 'content.string' => '询单内容必须是字符串', + 'status.in' => '状态值只能是0、1或2', + 'r_time.integer' => '发布时间必须是整数', + 'r_time.egt' => '发布时间不能为负数', + 't_time.integer' => '跟踪时间必须是整数', + 't_time.egt' => '跟踪时间不能为负数', + 'email.checkEmailContactUnique' => '邮箱联系方式已存在', + 'tel.checkTelContactUnique' => '电话联系方式已存在', + 'whatsapp.checkWhatsappContactUnique' => 'WhatsApp联系方式已存在', + 'contacts.requireWithout' => '至少需要提供一种联系方式', + 'contacts.checkContactUnique' => '联系人已存在', + 'country.requireWith' => '国家必须与联系人一起提供' ]; // 场景 protected $scene = [ 'store' => [ - + 'enter_user_id', 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content' ], - 'update' => [ - + 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content' + ], + 'release' => [ + 'status', 'r_time' + ], + 'allocation' => [ + 'status', 't_time' ] ]; + + + /** + * 验证联系方式唯一性 + */ + protected function checkContactUnique($value, $rule, $data, $field, $title) + { + // 如果是更新操作且联系人未修改,跳过检查 + if ($this->currentScene == 'update') { + $original = InquiryLog::find($data['id']); + if ($original && $original->contacts == $value && $original->country == ($data['country'] ?? 0)) { + return true; + } + } + + if (empty($data['email']) && empty($data['tel']) && empty($data['whatsapp'])) { + $query = InquiryLog::where('contacts', trim($value)) + ->where('country', $data['country']); + + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + + return !$query->exists(); + } + + return true; + } + + /** + * 验证邮箱联系方式唯一性 + */ + protected function checkEmailContactUnique($value, $rule, $data, $field, $title) + { + if (empty($value)) return true; + + // 如果是更新操作且邮箱未修改,跳过检查 + if ($this->currentScene == 'update' && !empty($data['id'])) { + $original = InquiryLog::find($data['id']); + if ($original && $original->email == $value) { + return true; + } + } + + $query = InquiryLog::where('email', trim($value)); + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + return !$query->exists(); + } + + /** + * 验证电话联系方式唯一性 + */ + protected function checkTelContactUnique($value, $rule, $data, $field, $title) + { + if (empty($value)) return true; + + // 如果是更新操作且电话未修改,跳过检查 + if ($this->currentScene == 'update' && !empty($data['id'])) { + $original = InquiryLog::find($data['id']); + if ($original && $original->tel == $value) { + return true; + } + } + + $query = InquiryLog::where('tel', trim($value)); + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + return !$query->exists(); + } + + /** + * 验证WhatsApp联系方式唯一性 + */ + protected function checkWhatsappContactUnique($value, $rule, $data, $field, $title) + { + if (empty($value)) return true; + + // 如果是更新操作且WhatsApp未修改,跳过检查 + if ($this->currentScene == 'update' && !empty($data['id'])) { + $original = InquiryLog::find($data['id']); + if ($original && $original->whatsapp == $value) { + return true; + } + } + + $query = InquiryLog::where('whatsapp', trim($value)); + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + return !$query->exists(); + } } \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 50a3280..b942887 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -9,13 +9,14 @@ use app\common\utils\scaffold\Generate; use support\Request; use support\Db; use support\Cache; +use app\inquiry\service\InquiryLogService; class IndexController { - protected $noNeedLogin = ['index', 'view', 'perm', 'createResource']; + protected $noNeedLogin = ['index', 'view', 'perm', 'createResource', 'test']; - protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource']; + protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource', 'test']; public function index(Request $request) { @@ -70,30 +71,53 @@ class IndexController public function createResource() { $options = [ - 'controller' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], + // 'controller' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], 'model' => [ - 'module' => 'inquiry', - 'name' => 'country' + 'module' => 'common', + 'name' => 'crm_customer' ], - 'service' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], - 'logic' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], - 'validate' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], - 'table' => 'country' + // 'service' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], + // 'logic' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], + // 'validate' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], + 'table' => 'crm_customer' ]; $result = (new Generate($options))->doGenerate(); return $result; } + + + public function test() + { + // $res = ['id'=>25714]; + // \Webman\RedisQueue\Redis::send('inquiry-to-crm', $res); + + // $res = [ + // 'id'=>25717, + // 'category'=>'inquiry', + // 'scene'=>'inquiry-person-allocation', + // 'receive_type'=>'person' + // ]; + + // $res = [ + // 'id'=>1, + // 'category'=>'crm', + // 'scene'=>'crm-person-follow-up', + // 'receive_type'=>'person' + // ]; + // \Webman\RedisQueue\Redis::send('feishu-message', $res); + return 'success'; + } } \ No newline at end of file diff --git a/app/functions.php b/app/functions.php index 2a05e3f..3fed6a2 100644 --- a/app/functions.php +++ b/app/functions.php @@ -66,6 +66,15 @@ if (!function_exists('getConfig')) { } } +/* + * 询单号生成 + */ +if (!function_exists('orderNum')) { + function orderNum() { + return "SN".date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); + } +} + /* * 调试用 */ diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php index 9b48395..8bc6761 100644 --- a/app/inquiry/controller/InquiryLogController.php +++ b/app/inquiry/controller/InquiryLogController.php @@ -4,6 +4,7 @@ namespace app\inquiry\controller; use app\common\controller\BaseController; use app\common\traits\crud\CrudControllerTrait; use app\inquiry\service\InquiryLogService; +use support\Request; class InquiryLogController extends BaseController { @@ -22,544 +23,56 @@ class InquiryLogController extends BaseController * @param array $data * @return array */ - // protected function indexFormat($data) - // { - // if (isset($data['created_at'])) - // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); - // if (isset($data['updated_at'])) - // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); - // return $data; - // } - - - // 列表 - public function list(Request $request) + protected function indexFormat($data) { - if ($request->method() == 'POST') { - $data = $request->all(); - $limit = isset($data['limit']) && $data['limit'] ? $data['limit'] : 10; - - // 状态 - $roleId = $request->user->role_id; - $where = []; - if (in_array($roleId, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::KEFU_ROLE_ID])) { - if (isset($data["status"]) && $data["status"] != "") { - $where[] = [ - "status", "=", $data["status"] - ]; - } else { - $where[] = [ - "status", ">", -1 - ]; - } - } else { - $where[] = [ - "status", "=", 1 - ]; - } - - // 检索渠道 - if (isset($data["channel"]) && $data["channel"]) { - $where[] = [ - "log_type", "=", $data["channel"] - ]; - } - - // 检索国家 - if (isset($data["country"]) && $data["country"]) { - $where[] = [ - "log_country", "=", $data["country"] - ]; - } - - // 检索时间 - if (isset($data["ld_time"]) && $data["ld_time"]) { - $times = explode(' - ', $data["ld_time"]); - $specificDayStart = trim($times[0]); - $specificDayEnd = trim($times[1]); - $where[] = [ - "c_time", ">", strtotime($specificDayStart) - ]; - $where[] = [ - "c_time", "<=", strtotime($specificDayEnd) - ]; - } - - // 检查渠道(不取缓存中的值) - // $channel = $request->user->channel; - $uinfos = UsersModel::where('id', $request->user->uid)->first(); - $channel = $uinfos->channel; - if ($channel) { - $channel = explode(",", $channel); - $InquiryLogModelModel = InquiryLogModel::where($where)->whereIn("log_type", $channel); - } else { - $InquiryLogModelModel = InquiryLogModel::where($where); - } - - // 关键字 - if (isset($data["kwd"]) && $data["kwd"] != "") { - $InquiryLogModelModel->whereRaw("concat(`log_contact_type`,`log_contact_tel`,`log_contact_whatsapp`,`log_contacts`,`order_no`) like '%".$data['kwd']."%'"); - } - - $list = $InquiryLogModelModel - ->with([ - "eUserInfo"=>function($query) { - $query->where("id", ">", 0); - }, "rUserInfo"=>function($query) { - $query->where("id", ">", 0); - }, "countryInfo" - ]) - ->orderBy("id", "desc") - ->paginate($limit); - - $configsModel = new ConfigsModel(); - $logLevel = $configsModel->getConfig("log_level"); - $countdown = (int)($configsModel->getConfig("score_countdown")) * 60; - $inquiryChannelList = ChannelModel::all(['id', 'name']); - $inquiryChannel = []; - foreach ($inquiryChannelList as $key => $val) { - $inquiryChannel[$val->id] = $val->name; - } - - $arr = []; - foreach ($list->items() as $key => $val) { - $expiry_time = $val->r_time + $countdown; - $is_expiry = 0; - $expiry_ts = 0; - if ($expiry_time <= time()) { - $is_expiry = 1; - } else { - $diff = $expiry_time - (int)time(); - $expiry_ts = $diff * 1000; - } - $rUinfo = ""; - if ($val->rUserInfo) { - $rUinfo = $val->rUserInfo->name; - } - $eUinfo = ""; - if ($val->eUserInfo) { - $eUinfo = $val->eUserInfo->name; - } - - /** - * 显示联系方式条件: - * 1.已跟单并且当前用户是接单人 - * 2.主权限 - * 3.当前用户是录单人 - */ - if ( - ($val->status == 2 && $request->user->uid == $val->recipient) || - ($roleId == RoleRulesModel::ALL_ROLE_ID) || - ($request->user->uid == $val->enter_user_id) - ) { - $log_contact_type = $val->log_contact_type == "" ? " 无 " : $val->log_contact_type; - $log_contact_tel = $val->log_contact_tel == "" ? " 无 " : $val->log_contact_tel; - $log_contact_whatsapp = $val->log_contact_whatsapp == "" ? " 无 " : $val->log_contact_whatsapp; - } else { - $log_contact_type = "******"; - $log_contact_tel = "******"; - $log_contact_whatsapp = "******"; - } - $arr[] = [ - "id" => $val->id, - "order_no" => $val->order_no, - "enter_user_id" => $val->enter_user_id, - "recipient" => $val->recipient, - "log_quality" => $val->log_quality, - "log_quality_desc" => $logLevel[$val->log_quality], - "log_quality_color" => InquiryLogModel::LEVEL_BUTTON_COLOR[$val->log_quality], - "log_type" => $val->log_type, - "log_type_desc" => $inquiryChannel[$val->log_type], - "log_contact_type" => $log_contact_type, - "log_contact_tel" => $log_contact_tel, - "log_contact_whatsapp" => $log_contact_whatsapp, - "log_country" => $val->countryInfo->cn_addr."(".$val->countryInfo->two_letter.")", - "log_country_steering" => $val->countryInfo->steering, - "log_contacts" => $val->log_contacts, - "log_content" => $val->log_content, - "e_time" => $val->status==1?date("Y-m-d H:i:s", $expiry_time):"", - "status" => $val->status, - "status_color" => InquiryLogModel::STATUS_BUTTON_COLOR[$val->status], - "status_desc" => InquiryLogModel::STATUS[$val->status], - "is_expiry" => $is_expiry, - "expiry_ts" => $expiry_ts, - "rUinfo" => $rUinfo, - "eUinfo" => $eUinfo, - "r_time" => $val->r_time?date("Y-m-d H:i:s", $val->r_time):"", - "n_time" => date("Y-m-d H:i:s", time()), - "c_time" => date("Y-m-d H:i:s", strtotime($val->c_time)), - "u_time" => date("Y-m-d H:i:s", strtotime($val->u_time)) - ]; - } - $data = [ - 'lastPage' => $list->lastPage(), - 'currentPage' => $list->currentPage(), - 'hasMorePages' => $list->hasMorePages(), - 'list' => $arr, - 'counts' => $list->total() - ]; - return $this->successResponse($data); + $data['countdown'] = time()-1; + if (isset($data['r_time'])) { + $countdown = 60 * 60; + $data['countdown'] = (time() - $data['r_time']) < $countdown ? $data['r_time'] + (60*60) : time()-1; } - return view('inquiry-log/list'); + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + if (isset($data['r_time'])) + $data['r_time'] = $data['r_time'] > 0 ? date('Y-m-d H:i:s', $data['r_time']) : 0; + if (isset($data['t_time'])) + $data['t_time'] = $data['t_time'] > 0 ? date('Y-m-d H:i:s', $data['t_time']) : 0; + + return $data; } - // 添加 - public function add(Request $request) - { - if ($request->method() == "POST") { - $enterUserId = $request->user->uid; - $data = $request->all(); - - if ($data["log_contact_type"]) { - $check1 = InquiryLogModel::where("log_contact_type", trim($data["log_contact_type"]))->first(); - if ($check1 != NULL) { - return $this->errorResponse('邮箱-联系方式已存在'); - } - } - - if ($data["log_contact_tel"]) { - $check2 = InquiryLogModel::where("log_contact_tel", trim($data["log_contact_tel"]))->first(); - if ($check2 != NULL) { - return $this->errorResponse('电话-联系方式已存在'); - } - } - - if ($data["log_contact_whatsapp"]) { - $check3 = InquiryLogModel::where("log_contact_whatsapp", trim($data["log_contact_whatsapp"]))->first(); - if ($check3 != NULL) { - return $this->errorResponse('whatsapp-联系方式已存在'); - } - } - - if (!$data["log_contact_type"] && !$data["log_contact_tel"] && !$data["log_contact_whatsapp"]) { - $check4 = InquiryLogModel::where("log_contacts", trim($data["log_contacts"]))->where("log_country", $data["country"])->first(); - if ($check4 != NULL) { - return $this->errorResponse('联系人已存在'); - } - } - - $model = new InquiryLogModel(); - $model->order_no = orderNum(); - $model->enter_user_id = $enterUserId; - $model->recipient = 0; - $model->log_type = $data["log_type"]; - $model->log_contact_type = trim($data["log_contact_type"]); - $model->log_contact_tel = trim($data["log_contact_tel"]); - $model->log_contact_whatsapp = trim($data["log_contact_whatsapp"]); - $model->log_country = $data["country"]; - $model->log_contacts = $data["log_contacts"]; - $model->log_content = $data["log_content"]; - $model->log_quality = $data["log_quality"]; - $model->status = 0; - if ($model->save()) { - return $this->successResponse([], '添加成功'); - } else { - return $this->errorResponse('添加失败,请重试'); - } - } - return view('inquiry-log/add'); - } - - // 编辑 - public function edit(Request $request) - { - if ($request->method() == "POST") { - $data = $request->all(); - $model = new InquiryLogModel(); - $info = $model->where("id", $data["id"])->first(); - - if ($data["log_contact_type"]) { - $check1 = InquiryLogModel::where("log_contact_type", trim($data["log_contact_type"]))->first(); - if ($check1 != NULL && $check1->id != $data["id"]) { - return $this->errorResponse('邮箱-联系方式已存在'); - } - } - - if ($data["log_contact_tel"]) { - $check2 = InquiryLogModel::where("log_contact_tel", trim($data["log_contact_tel"]))->first(); - if ($check2 != NULL && $check2->id != $data["id"]) { - return $this->errorResponse('电话-联系方式已存在'); - } - } - - if ($data["log_contact_whatsapp"]) { - $check3 = InquiryLogModel::where("log_contact_whatsapp", trim($data["log_contact_whatsapp"]))->first(); - if ($check3 != NULL && $check3->id != $data["id"]) { - return $this->errorResponse('whatsapp-联系方式已存在'); - } - } - - if (!$data["log_contact_type"] && !$data["log_contact_tel"] && !$data["log_contact_whatsapp"]) { - $check4 = InquiryLogModel::where("log_contacts", trim($data["log_contacts"]))->where("log_country", $data["country"])->first(); - if ($check4 != NULL && $check4->id != $data["id"]) { - return $this->errorResponse('联系人已存在'); - } - } - - $info->log_type = $data["log_type"]; - $info->log_contact_type = $data["log_contact_type"]; - $info->log_contact_tel = $data["log_contact_tel"]; - $info->log_contact_whatsapp = $data["log_contact_whatsapp"]; - $info->log_country = $data["country"]; - $info->log_contacts = $data["log_contacts"]; - $info->log_content = $data["log_content"]; - $info->log_quality = $data["log_quality"]; - if (!$info->save()) { - return $this->errorResponse('操作失败,请重试'); - } - return $this->successResponse([], '操作成功'); - } - return view('inquiry-log/edit'); - } - - // 删除 - public function del(Request $request) - { - if ($request->method() == "POST") { - $id = $request->post("id"); - $rst = InquiryLogModel::where("id", $id)->first(); - if (!$rst) { - return $this->errorResponse('询单未找到'); - } - if ($rst->enter_user_id != $request->user->uid) { - return $this->errorResponse('只能删除自己录入的询单'); - } - $res = InquiryLogModel::where("id", $id)->where("enter_user_id", $request->user->uid)->delete(); - if (!$res) { - return $this->errorResponse('操作失败,请重试'); - } - fileLog('ID:'.$request->user->uid.' - 删除:询单ID:'.$id); - return $this->successResponse([], '操作成功'); - } - return $this->errorResponse('请求方式错误'); - } - - // 单条信息 - public function info(Request $request) - { - if ($request->method() == "POST") { - $id = $request->all("id"); - $info = InquiryLogModel::where("id", $id)->first()->toArray(); - $list = CountryModel::all(["cn_addr", "two_letter", "id"]); - $arr = []; - foreach ($list as $vo) { - $arr[] = [ - "value" => $vo->id, - "name" => $vo->cn_addr."(".$vo->two_letter.")" - ]; - } - $info["guojia"] = $arr; - $info["guojia_id"] = [$info["log_country"]]; - - $configsModel = new ConfigsModel(); - // $data = $configsModel->getConfig("inquiry_channel"); - $inquiryChannelList = ChannelModel::all(['id', 'name']); - $data = []; - foreach ($inquiryChannelList as $key => $val) { - $data[$val->id] = $val->name; - } - - $arr = []; - foreach ($data as $ky => $vo) { - $arr[] = [ - "id" => $ky, - "name" => $vo - ]; - } - $info["qudao"] = $arr; - - $data1 = $configsModel->getConfig("log_level"); - $arr1 = []; - foreach ($data1 as $ky => $vo) { - $arr1[] = [ - "id" => $ky, - "name" => $vo - ]; - } - $info["dengji"] = $arr1; - if (!$info) { - return $this->errorResponse('查询失败,请重试'); - } - $info["log_content"] = strTh($info["log_content"]); - return $this->successResponse($info, 'success'); - } - return $this->errorResponse('请求方式错误'); - } - - // 立即发布 + /** + * 发布 + * + * @param Request $request + * @return mixed + */ public function release(Request $request) { try { - if ($request->method() == "POST") { - $id = $request->post("id"); - if (!in_array($request->user->role_id, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::KEFU_ROLE_ID])) { - return $this->errorResponse('无操作权限'); - } - $info = InquiryLogModel::where("id", $id)->first(); - if (!$info) { - return $this->errorResponse('询单信息未找到'); - } - if ($info->status != 0) { - return $this->errorResponse('状态异常,无法发布'); - } - $info->r_time = time(); - $info->status = 1; - if (!$info->save()) { - return $this->errorResponse('发布失败,请重试'); - } - - // 发送飞书群消息通知 - $data = ["order_id"=>$id]; - RedisQueueRedis::send("release", $data); - // 发送飞书群消息通知 - - return $this->successResponse([], '发布成功'); - } - return $this->errorResponse('电话-请求方式错误'); - } catch (\Throwable $th) { - fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); - return $this->errorResponse($th->getMessage()); + $data = $this->validate($this->service->getValidator(), $request->post(), 'release'); + $res = $this->service->serviceRelease($request, $data); + return $this->success([], '发布成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); } } - // 立即跟单 - public function track(Request $request) - { - // Db::beginTransaction(); - try { - if ($request->method() == "POST") { - $id = $request->post("id"); - $uid = $request->user->uid; - if (!in_array($request->user->role_id, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::YEWU_ROLE_ID])) { - // Db::rollBack(); - return $this->errorResponse('无操作权限'); - } - $info = InquiryLogModel::where("id", $id)->first(); - if (!$info) { - return $this->errorResponse('询单信息未找到'); - } - if ($info->status != 1) { - // Db::rollBack(); - return $this->errorResponse('状态异常,无法跟单'); - } - // 更新询单 - $info->recipient = $uid; - $info->t_time = time(); - $info->status = 2; - // 记录跟单 - $model = new UserOrderModel(); - $model->uid = $request->user->uid; - $model->log_id = $id; - $model->score = (new InquiryLogModel())->score($info->log_quality, $info->r_time); - if (!$info->save() || !$model->save()) { - // Db::rollBack(); - return $this->errorResponse('操作失败,请重试'); - } - // Db::commit(); - - // // 发送飞书群消息通知 - $data1 = ["order_id"=>$id]; - RedisQueueRedis::send("track", $data1); - // // 发送飞书群消息通知 - - // // 同步询单到crm - $data2 = ["order_id"=>$id]; - RedisQueueRedis::send("inquiry-sync", $data2); - // // 同步询单到crm - - return $this->successResponse([], '跟单操作成功'); - } else { - // Db::rollBack(); - return $this->errorResponse('请求方式错误'); - } - } catch (\Throwable $th) { - // Db::rollBack(); - fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); - return $this->errorResponse($th->getMessage()); - } - } - - // 详情 - public function detail(Request $request) - { - if ($request->method() == "POST") { - $id = $request->all("id"); - $res = InquiryLogModel::where("id", $id)->first()->toArray(); - return $this->successResponse(["detail"=>$res["log_content"]], '操作成功'); - - } - return view('inquiry-log/detail'); - } - - // 客服分单给业务 + /** + * 分配 + * + * @param Request $request + * @return mixed + */ public function allocation(Request $request) { - // Db::beginTransaction(); try { - if ($request->method() == "POST") { - $data = $request->all(); - - $userInfo = UsersModel::where("id", $data["gd_user"])->first(); - if (!$userInfo) { - // Db::rollBack(); - return $this->errorResponse('用户未找到'); - } - - $model = new InquiryLogModel(); - $info = $model->where("id", $data["id"])->first(); - if (!$info) { - // Db::rollBack(); - return $this->errorResponse('询单未找到'); - } - - $uomodel = new UserOrderModel(); - $uoInfo = $uomodel->where("log_id", $data["id"])->first(); - if (!$uoInfo) { - // 记录跟单 - $HsUserOrderModel = new UserOrderModel(); - $HsUserOrderModel->uid = $data["gd_user"]; - $HsUserOrderModel->log_id = $data["id"]; - $HsUserOrderModel->score = (new InquiryLogModel())->score($info->log_quality, $info->r_time); - if (!$HsUserOrderModel->save()) { - // Db::rollBack(); - return $this->errorResponse('记录跟单操作失败,请重试'); - } - } else { - $uoInfo->uid = $data["gd_user"]; - if (!$uoInfo->save()) { - // Db::rollBack(); - return $this->errorResponse('操作失败,请重试'); - } - } - - if ($info->status != 2) { - $info->t_time = time(); - $info->status = 2; - } - $info->recipient = $data["gd_user"]; - if (!$info->save()) { - // Db::rollBack(); - return $this->errorResponse('操作失败,请重试'); - } - // Db::commit(); - - // 发送飞书群消息通知 - $data1 = ["order_id"=>$data["id"]]; - RedisQueueRedis::send("allocation", $data1); - // 发送飞书群消息通知 - - // // 同步询单到crm - $data2 = ["order_id"=>$data["id"]]; - RedisQueueRedis::send("inquiry-sync", $data2); - // // 同步询单到crm - - return $this->successResponse([], '操作成功'); - } - return view('inquiry-log/allocation'); - } catch (\Throwable $th) { - // Db::rollBack(); - fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); - return $this->errorResponse($th->getMessage()); + $data = $this->validate($this->service->getValidator(), $request->post(), 'allocation'); + $res = $this->service->serviceAllocation($request, $data); + return $this->success([], '分配成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); } } diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index bc966f4..8f40a0e 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -5,7 +5,9 @@ namespace app\inquiry\service; use app\common\validate\InquiryLogValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\InquiryLog; +use support\exception\BusinessException; use support\Request; +use Webman\RedisQueue\Redis as RedisQueueRedis; class InquiryLogService { @@ -30,7 +32,31 @@ class InquiryLogService return new $this->validator; } + /** + * 发布 + */ + public function serviceRelease(Request $request, array $data = []) + { + $data = $this->preProcess('Release', $data, $request); + $config = $this->buildQueryConfig($request, 'release'); + $updated = $this->logicUpdateBy($config, $data); + return $this->postProcess('Release', $data); + } + /** + * 分配 + */ + public function serviceAllocation(Request $request, array $data = []) + { + $data = $this->preProcess('Allocation', $data, $request); + $config = $this->buildQueryConfig($request, 'allocation'); + $updated = $this->logicUpdateBy($config, $data); + return $this->postProcess('Allocation', $data); + } + + /** + * 构建查询条件 + */ protected function buildQueryConfig(Request $request, string $scene = 'index'): array { $p = $request->all(); @@ -39,37 +65,29 @@ class InquiryLogService $config['model'] = $this->modelClass; switch ($scene) { case 'index': - // 排序 + // ====== ====== 排序 ====== ====== $config['order']['id'] = 'desc'; + // ====== ====== 筛选 ====== ====== // 渠道 if (isset($p['channel']) && $p['channel']) { $config['where']['channel'] = $p['channel']; } - // 国家 if (isset($p['country']) && $p['country']) { $config['where']['country'] = $p['country']; } - // 时间 if (isset($p['time']) && $p['time']) { - // $time = explode(' - ', $p['time']); - // $config['where']['created_at'] = [ - // ['>=', strtotime($time[0])], - // ['<=', strtotime($time[1])], - // ]; + $time = explode(' - ', $p['time']); $config['where']['created_at'] = [ - strtotime(explode(' - ', $p['time'])[0]), - strtotime(explode(' - ', $p['time'])[1]), + 'between', [strtotime($time[0]), strtotime($time[1])] ]; } - // 状态 - if (isset($p['status']) && $p['status']) { + if (isset($p['status']) && in_array($p['status'], [0,1,2])) { $config['where']['status'] = $p['status']; } - // 关键字 if (isset($p['keyword']) && $p['keyword']) { $k = '%' . $p['keyword'] . '%'; @@ -78,27 +96,131 @@ class InquiryLogService } } - // 关联 - $config['with'][] = 'country'; - $config['with']['enter'][] = 'id'; + // ====== ====== 关联 ====== ====== + $config['with']['countryInfo'][] = 'cn_addr'; + $config['with']['countryInfo'][] = 'steering'; $config['with']['enter'][] = 'name'; - $config['with']['recipient'][] = 'id'; $config['with']['recipient'][] = 'name'; + $config['with']['channelInfo'][] = 'name'; break; case 'show': - $config['where']['id'] = $p['id']; - $config['where']['status'] = 1; - break; - case 'store': - break; case 'update': - $config['where']['id'] = $p['id']; - break; case 'destroy': + case 'release': + case 'allocation': $config['where']['id'] = $p['id']; + case 'store': break; } return $config; } + + /** + * 添加前置处理 + */ + public function beforeServiceStore($data, Request $request) + { + $data['order_no'] = orderNum(); + $data['enter_user_id'] = $request->user->uid; + $data['recipient'] = 0; + $data['email'] = trim($data["email"]); + $data['tel'] = trim($data['tel']); + $data['whatsapp'] = trim($data['whatsapp']); + return $data; + } + + /** + * 发布前置处理 + */ + public function beforeServiceRelease($data, Request $request) + { + $info = $this->findByPk($request->post('id')); + if (!$info) { + throw new BusinessException('数据不存在'); + } + if ($info->status != 0) { + throw new BusinessException('询单已被发布'); + } + $data['r_time'] = time(); + $data['status'] = 1; + return $data; + } + + /** + * 分配前置处理 + */ + public function beforeServiceAllocation($data, Request $request) + { + $info = $this->findByPk($request->post('id')); + if (!$info) { + throw new BusinessException('数据不存在'); + } + if ($info->status == 2) { + throw new BusinessException('询单已被分配'); + } + if ($info->status == 0) { + $data['r_time'] = time(); + } + $data['t_time'] = time(); + $data['status'] = 2; + return $data; + } + + /** + * 发布后置处理 + */ + protected function afterServiceRelease($res) + { + RedisQueueRedis::send('inquiry-to-crm', $res); + return $res; + } + + /** + * 分配后置处理 + */ + protected function afterServiceAllocation($res) + { + RedisQueueRedis::send('inquiry-to-crm', $res); + return $res; + } + + + /** + * 获取询单ById + * @param int|string $id + * @param string $orderSn + * @return Model|null + */ + public function getInquiryLogBy(int|string $id) + { + $config = []; + $config['model'] = $this->modelClass; + $config['with']['countryInfo'][] = 'cn_addr'; + $config['with']['countryInfo'][] = 'steering'; + $config['with']['enter'][] = 'name'; + $config['with']['recipient'][] = 'name'; + $config['with']['channelInfo'][] = 'name'; + $config['orWhere'][] = ['id', '=', $id]; + $config['orWhere'][] = ['order_no', '=', $id]; + return $this->logicShow($config); + } + + /** + * 获取等级名称 + * @param $id + * @return string + */ + public function qualityName($id) + { + if (!$id) { + return '🤍 银牌商机'; + } + $quality = [ + 1 => '💛 金牌商机', + 2 => '🤍 银牌商机', + 3 => '🤍 银牌商机' + ]; + return $quality[$id]; + } } \ No newline at end of file diff --git a/app/queue/redis/FeishuMessage.php b/app/queue/redis/FeishuMessage.php new file mode 100644 index 0000000..dacb4fc --- /dev/null +++ b/app/queue/redis/FeishuMessage.php @@ -0,0 +1,127 @@ +where('id', $data['id'])->first(); + + // 跟单人 + $reInfo = User::where('id', $info->user_id)->first(); + + // 飞书应用 + $feishuApp = new CorpFeishuApp(); + $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->first(); + + // 推送数据 + $options = [ + 'contacts' => $info->contacts, + 'follow_last' => $info->follow_last ? date('Y-m-d H:i', $info->follow_last) : '', + 'follow_next' => $info->follow_next ? date('Y-m-d H:i', $info->follow_next) : '', + + 'app_id' => $feishuAppInfo->app_id, + 'app_secret' => $feishuAppInfo->app_secret, + 'msg_type' => 'interactive', + 'receive_id_type' => 'open_id', + 'receive_id' => $reInfo->open_id + ]; + } + + if ($data['category'] === 'inquiry') { + // 询单信息 + $inquiryLogService = new InquiryLogService(); + $info = $inquiryLogService->getInquiryLogBy($data['id']); + + // 接受者处理 + if ($data['receive_type'] == 'group') { + $appId = 'cli_a72f13d6c022500d'; + $appSecret = 'Kxtbq70OQWrEhpltrufRZc76z7kjDqPT'; + $receiveId = 'oc_4f574e6f8bc5b8f3e5175e67ebc5725c'; + $receiveIdType = 'chat_id'; + } else { + // 跟单人 + $reInfo = User::where('id', $info->recipient)->first(); + // 飞书应用 + $feishuApp = new CorpFeishuApp(); + $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->first(); + // 推送信息 + $appId = $feishuAppInfo->app_id; + $appSecret = $feishuAppInfo->app_secret; + $receiveId = $reInfo->open_id; + $receiveIdType = 'open_id'; + } + + // 推送数据 + $options = [ + 'order_no' => $info->order_no, + 'quality' => $inquiryLogService->qualityName($info->quality), + 'channel' => $info->channelInfo ? $info->channelInfo->name : '', + 'country' => $info->countryInfo ? $info->countryInfo->cn_addr : '', + 'enter_user' => $info->enter ? $info->enter->name : '', + 'r_time' => $info->r_time ? date('Y-m-d H:i:s', $info->r_time) : '', + 't_time' => $info->t_time ? date('Y-m-d H:i:s', $info->t_time) : '', + + 'app_id' => $appId, + 'app_secret' => $appSecret, + 'msg_type' => 'interactive', + 'receive_id_type' => $receiveIdType, + 'receive_id' => $receiveId + ]; + } + + $feishuMsg = new FeishuMsg(); + $feishuMsg->messageSend($data['scene'], $options); + } catch (\Throwable $th) { + throw $th; + } + } + + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php new file mode 100644 index 0000000..ecac73e --- /dev/null +++ b/app/queue/redis/InquiryToCrm.php @@ -0,0 +1,101 @@ +where('id', $data['id'])->where('sync_crm', 0)->first(); + if ($info) { + // 获取用户信息 + $recipient = 0; + $corpId = 0; + if ($info->recipient) { + $recipient = $info->recipient; + $uinfo = User::where('id', $info->recipient)->first(); + if ($uinfo) { + $corpId = $uinfo->corp_id; + } + // 消息推送参数 分配 + $scene = 'inquiry-person-allocation'; + $receiveType = 'person'; + } else { + // 消息推送参数 发布 + $scene = 'inquiry-group-send'; + $receiveType = 'group'; + } + // 同步客户数据 + $crmCustomer = new CrmCustomer(); + $crmCustomer->sn = $info->order_no; + $crmCustomer->corp_abbr = $corpId; + $crmCustomer->user_id = $recipient; + $crmCustomer->type = 1; + $crmCustomer->channel_id = $info->channel; + $crmCustomer->country_id = $info->country; + $crmCustomer->magnitude = $info->quality - 1; + $crmCustomer->email = $info->email; + $crmCustomer->phone = $info->tel; + $crmCustomer->WhatsApp = $info->whatsapp; + $crmCustomer->contacts = $info->contacts; + $crmCustomer->desc_collect = $info->content; + $crmCustomer->enter_user_id = $info->enter_user_id; + // 修改同步CRM状态 + $info->sync_crm = 1; + // 保存 + if (!$crmCustomer->save() || !$info->save()) { + throw new BusinessException('同步失败~'); + } + + // 发送飞书消息 + $options = [ + 'id' => $info->id, + 'category' => 'inquiry', + 'scene' => $scene, + 'receive_type' => $receiveType + ]; + \Webman\RedisQueue\Redis::send('feishu-message', $options); + } + } catch (\Throwable $th) { + throw $th; + } + } + + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/process.php b/config/plugin/webman/redis-queue/process.php index 9fac733..8474aee 100644 --- a/config/plugin/webman/redis-queue/process.php +++ b/config/plugin/webman/redis-queue/process.php @@ -5,7 +5,7 @@ return [ 'count' => getenv('REDIS_QUEUE_PROCESS_COUNT'), // 可以设置多进程同时消费 'constructor' => [ // 消费者类目录 - // 'consumer_dir' => app_path() . '/queue/redis' + 'consumer_dir' => app_path() . '/queue/redis', 'consumer_dir' => app_path() . getenv('REDIS_QUEUE_CONSUMER_DIR') ] ] diff --git a/config/route.php b/config/route.php index 01953d0..8d12177 100644 --- a/config/route.php +++ b/config/route.php @@ -29,6 +29,10 @@ use Webman\Route; Route::any('/c', [app\controller\IndexController::class, 'createResource']); Route::any('/perm', [app\controller\IndexController::class, 'perm']); +// 飞书 +Route::any('/feishu/event', [app\common\controller\NotifyController::class, 'feishuEvent']); +// 飞书回调 +Route::any('/feishu/callback', [app\common\controller\NotifyController::class, 'feishuCallback']); // 公共 Route::group('/common', function () { @@ -77,10 +81,12 @@ Route::group('/admin', function () { // 询单 Route::group('/inquiry', function () { Route::any('/list', [app\inquiry\controller\InquiryLogController::class, 'index']); - // Route::any('/show', [app\admin\controller\CorpController::class, 'show']); - // Route::any('/store', [app\admin\controller\CorpController::class, 'store']); - // Route::any('/update', [app\admin\controller\CorpController::class, 'update']); - // Route::any('/destroy', [app\admin\controller\CorpController::class, 'destroy']); + Route::any('/show', [app\inquiry\controller\InquiryLogController::class, 'show']); + Route::any('/store', [app\inquiry\controller\InquiryLogController::class, 'store']); + Route::any('/update', [app\inquiry\controller\InquiryLogController::class, 'update']); + Route::any('/destroy', [app\inquiry\controller\InquiryLogController::class, 'destroy']); + Route::any('/release', [app\inquiry\controller\InquiryLogController::class, 'release']); + Route::any('/allocation', [app\inquiry\controller\InquiryLogController::class, 'allocation']); }); }); diff --git a/config/whitelist.php b/config/whitelist.php deleted file mode 100644 index 1736876..0000000 --- a/config/whitelist.php +++ /dev/null @@ -1,13 +0,0 @@ - [ - '/', // 首页 - '/index/index', // 首页 - '/index/view', // 飞书调试 - '/perm', // 调试-添加权限 - - '/common/preset/corps', // 公司列表 - '/common/preset/feishu/app', // 飞书应用信息 - ] -]; \ No newline at end of file