作者 赵彬吉
@@ -18,6 +18,7 @@ use App\Models\Ai\AiCommand; @@ -18,6 +18,7 @@ use App\Models\Ai\AiCommand;
18 use App\Models\Com\NoticeLog; 18 use App\Models\Com\NoticeLog;
19 use App\Models\Com\V6WeeklyReport; 19 use App\Models\Com\V6WeeklyReport;
20 use App\Models\Product\Category; 20 use App\Models\Product\Category;
  21 +use App\Models\Project\AggregateKeywordAffix;
21 use App\Models\Project\AiBlogTask; 22 use App\Models\Project\AiBlogTask;
22 use App\Models\Project\DeployBuild; 23 use App\Models\Project\DeployBuild;
23 use App\Models\Project\DeployOptimize; 24 use App\Models\Project\DeployOptimize;
@@ -58,7 +59,22 @@ class lyhDemo extends Command @@ -58,7 +59,22 @@ class lyhDemo extends Command
58 protected $description = '更新路由'; 59 protected $description = '更新路由';
59 60
60 public function handle(){ 61 public function handle(){
61 - 62 + $aggregateKeywordAffixModel = new AggregateKeywordAffix();
  63 + $info = $aggregateKeywordAffixModel->read(['project_id'=>3298]);
  64 + $projectModel = new Project();
  65 + $lists = $projectModel->list(['delete_status' => 0,'project_type'=>0,'extend_type'=>0,'type'=>2], 'id', ['id']);
  66 + foreach ($lists as $item){
  67 + if(in_array($item['id'],[467,110,3298])){
  68 + continue;
  69 + }
  70 + $data = [
  71 + 'project_id'=>$item['id'],
  72 + 'prefix'=>$info['prefix'],
  73 + 'suffix'=>$info['suffix']
  74 + ];
  75 + $aggregateKeywordAffixModel->addReturnId($data);
  76 + }
  77 + return true;
62 } 78 }
63 79
64 public function _actionTemplateMain(){ 80 public function _actionTemplateMain(){
@@ -43,7 +43,7 @@ class Temp extends Command @@ -43,7 +43,7 @@ class Temp extends Command
43 43
44 public function handle() 44 public function handle()
45 { 45 {
46 - $this->check_no_cname_projects(); 46 + $this->change_cname_projects();
47 } 47 }
48 48
49 /** 49 /**
@@ -600,15 +600,18 @@ class Temp extends Command @@ -600,15 +600,18 @@ class Temp extends Command
600 600
601 601
602 /** 602 /**
603 - * 240服务器上解析cname的项目迁移 603 + * 服务器上解析cname的项目迁移
604 * @author Akun 604 * @author Akun
605 * @date 2025/02/17 14:21 605 * @date 2025/02/17 14:21
606 */ 606 */
607 - public function change_cname_projects_240() 607 + public function change_cname_projects()
608 { 608 {
  609 + $origin_server_id = 20;//原服务器
  610 + $target_server_id = 27;//目标服务器
  611 +
609 $server_ip_model = new ServersIp(); 612 $server_ip_model = new ServersIp();
610 613
611 - $server_ip_ids = $server_ip_model->where('servers_id', 1)->get()->pluck('id')->toArray(); 614 + $server_ip_ids = $server_ip_model->where('servers_id', $origin_server_id)->get()->pluck('id')->toArray();
612 615
613 $project_list = Project::select(['id', 'serve_id'])->whereIn('serve_id', $server_ip_ids)->get(); 616 $project_list = Project::select(['id', 'serve_id'])->whereIn('serve_id', $server_ip_ids)->get();
614 617
@@ -626,60 +629,70 @@ class Temp extends Command @@ -626,60 +629,70 @@ class Temp extends Command
626 $domain = $domain_info['domain']; 629 $domain = $domain_info['domain'];
627 630
628 //迁移主站 631 //迁移主站
629 - $check = dns_get_record($domain, DNS_A);  
630 - $host = $check[0]['host'] ?? '';  
631 - if ($host == 'cname.globalso.com') {  
632 - //获取主站备份证书  
633 - $ssl_info = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $domain)->first();  
634 - if (!$ssl_info) {  
635 - $this->output('项目id:' . $project_id . ' | 未备份主站证书');  
636 - continue;  
637 - }  
638 -  
639 - //创建主站建站任务  
640 - $task_info = DomainCreateTask::where('type', 1)->where('server_id', 20)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();  
641 - if (!$task_info) {  
642 - $task_model = new DomainCreateTask();  
643 - $task_model->type = 1;  
644 - $task_model->server_id = 20;  
645 - $task_model->project_id = $project_id;  
646 - $task_model->domain_id = $domain_id;  
647 - $task_model->certs = json_encode(['key' => $ssl_info->private_key, 'csr' => $ssl_info->private_cert]);  
648 - $task_model->save();  
649 - }  
650 -  
651 - //创建主站页面生成任务  
652 - $notify_data = [  
653 - 'project_id' => $project_id,  
654 - 'type' => 1,  
655 - 'route' => 1,  
656 - 'server_id' => 20,  
657 - 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]  
658 - ];  
659 - $notify = $notify_model->read($notify_data, ['id']); 632 +// try {
  633 +// $check = dns_get_record($domain, DNS_A);
  634 +// $host = $check[0]['host'] ?? '';
  635 +// } catch (\Exception $e) {
  636 +// $this->output($domain . ' | 获取解析记录失败');
  637 +// continue;
  638 +// }
  639 +//
  640 +// if ($host != 'cname.globalso.com') {
  641 +// $this->output($domain . ' | 未解析cname');
  642 +// continue;
  643 +// }
  644 +
  645 + //获取主站备份证书
  646 + $ssl_info = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $domain)->first();
  647 + if (!$ssl_info) {
  648 + $this->output($domain . ' | 未备份证书');
  649 + continue;
  650 + }
660 651
661 - if (!$notify) {  
662 - $notify_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);  
663 - $notify_data['status'] = Notify::STATUS_INIT;  
664 - $notify_model->add($notify_data);  
665 - } 652 + //创建主站建站任务
  653 + $task_info = DomainCreateTask::where('type', 1)->where('server_id', $target_server_id)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();
  654 + if (!$task_info) {
  655 + $task_model = new DomainCreateTask();
  656 + $task_model->type = 1;
  657 + $task_model->server_id = $target_server_id;
  658 + $task_model->project_id = $project_id;
  659 + $task_model->domain_id = $domain_id;
  660 + $task_model->certs = json_encode(['key' => $ssl_info->private_key, 'csr' => $ssl_info->private_cert]);
  661 + $task_model->save();
  662 + }
666 663
667 - //创建主站关键词页面生成任务  
668 - $notify_keyword_data = [  
669 - 'project_id' => $project_id,  
670 - 'type' => 1,  
671 - 'route' => 4,  
672 - 'server_id' => 20,  
673 - 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]  
674 - ];  
675 - $notify_keyword = $notify_model->read($notify_keyword_data, ['id']); 664 +// //创建主站页面生成任务
  665 +// $notify_data = [
  666 +// 'project_id' => $project_id,
  667 +// 'type' => 1,
  668 +// 'route' => 1,
  669 +// 'server_id' => 20,
  670 +// 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
  671 +// ];
  672 +// $notify = $notify_model->read($notify_data, ['id']);
  673 +//
  674 +// if (!$notify) {
  675 +// $notify_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);
  676 +// $notify_data['status'] = Notify::STATUS_INIT;
  677 +// $notify_model->add($notify_data);
  678 +// }
  679 +//
  680 +// //创建主站关键词页面生成任务
  681 +// $notify_keyword_data = [
  682 +// 'project_id' => $project_id,
  683 +// 'type' => 1,
  684 +// 'route' => 4,
  685 +// 'server_id' => 20,
  686 +// 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
  687 +// ];
  688 +// $notify_keyword = $notify_model->read($notify_keyword_data, ['id']);
  689 +//
  690 +// if (!$notify_keyword) {
  691 +// $notify_keyword_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);
  692 +// $notify_keyword_data['status'] = Notify::STATUS_INIT;
  693 +// $notify_model->add($notify_keyword_data);
  694 +// }
676 695
677 - if (!$notify_keyword) {  
678 - $notify_keyword_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);  
679 - $notify_keyword_data['status'] = Notify::STATUS_INIT;  
680 - $notify_model->add($notify_keyword_data);  
681 - }  
682 - }  
683 696
684 if ($domain_info['amp_status'] == 1) { 697 if ($domain_info['amp_status'] == 1) {
685 //迁移amp站 698 //迁移amp站
@@ -693,44 +706,53 @@ class Temp extends Command @@ -693,44 +706,53 @@ class Temp extends Command
693 } 706 }
694 $amp_domain = implode('.', $host_array); 707 $amp_domain = implode('.', $host_array);
695 708
696 - $check_amp = dns_get_record($amp_domain, DNS_A);  
697 - $host_amp = $check_amp[0]['host'] ?? '';  
698 - if ($host_amp == 'cname.globalso.com') {  
699 - //获取amp站备份证书  
700 - $ssl_info_amp = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $amp_domain)->first();  
701 - if (!$ssl_info_amp) {  
702 - $this->output('项目id:' . $project_id . ' | 未备份amp站证书');  
703 - continue;  
704 - }  
705 -  
706 - //创建amp站建站任务  
707 - $task_info_amp = DomainCreateTask::where('type', 2)->where('server_id', 20)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();  
708 - if (!$task_info_amp) {  
709 - $task_model = new DomainCreateTask();  
710 - $task_model->type = 2;  
711 - $task_model->server_id = 20;  
712 - $task_model->project_id = $project_id;  
713 - $task_model->domain_id = $domain_id;  
714 - $task_model->certs = json_encode(['key' => $ssl_info_amp->private_key, 'csr' => $ssl_info_amp->private_cert]);  
715 - $task_model->save();  
716 - } 709 +// try {
  710 +// $check_amp = dns_get_record($amp_domain, DNS_A);
  711 +// $host_amp = $check_amp[0]['host'] ?? '';
  712 +// } catch (\Exception $e) {
  713 +// $this->output($amp_domain . ' | 获取解析记录失败');
  714 +// continue;
  715 +// }
  716 +//
  717 +// if ($host_amp != 'cname.globalso.com') {
  718 +// $this->output($amp_domain . ' | 未解析cname');
  719 +// continue;
  720 +// }
  721 +
  722 + //获取amp站备份证书
  723 + $ssl_info_amp = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $amp_domain)->first();
  724 + if (!$ssl_info_amp) {
  725 + $this->output($amp_domain . ' | 未备份证书');
  726 + continue;
  727 + }
717 728
718 - //创建amp站页面生成任务  
719 - $notify_amp_data = [  
720 - 'project_id' => $project_id,  
721 - 'type' => 3,  
722 - 'route' => 1,  
723 - 'server_id' => 20,  
724 - 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]  
725 - ];  
726 - $notify_amp = $notify_model->read($notify_amp_data, ['id']);  
727 -  
728 - if (!$notify_amp) {  
729 - $notify_amp_data['data'] = Arr::a2s(['domain' => $amp_domain, 'url' => null, 'language' => []]);  
730 - $notify_amp_data['status'] = Notify::STATUS_INIT;  
731 - $notify_model->add($notify_amp_data);  
732 - } 729 + //创建amp站建站任务
  730 + $task_info_amp = DomainCreateTask::where('type', 2)->where('server_id', $target_server_id)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();
  731 + if (!$task_info_amp) {
  732 + $task_model = new DomainCreateTask();
  733 + $task_model->type = 2;
  734 + $task_model->server_id = $target_server_id;
  735 + $task_model->project_id = $project_id;
  736 + $task_model->domain_id = $domain_id;
  737 + $task_model->certs = json_encode(['key' => $ssl_info_amp->private_key, 'csr' => $ssl_info_amp->private_cert]);
  738 + $task_model->save();
733 } 739 }
  740 +
  741 +// //创建amp站页面生成任务
  742 +// $notify_amp_data = [
  743 +// 'project_id' => $project_id,
  744 +// 'type' => 3,
  745 +// 'route' => 1,
  746 +// 'server_id' => 20,
  747 +// 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
  748 +// ];
  749 +// $notify_amp = $notify_model->read($notify_amp_data, ['id']);
  750 +//
  751 +// if (!$notify_amp) {
  752 +// $notify_amp_data['data'] = Arr::a2s(['domain' => $amp_domain, 'url' => null, 'language' => []]);
  753 +// $notify_amp_data['status'] = Notify::STATUS_INIT;
  754 +// $notify_model->add($notify_amp_data);
  755 +// }
734 } 756 }
735 } 757 }
736 } 758 }
  1 +<?php
  2 +
  3 +namespace App\Console\Commands\WorkOrder;
  4 +
  5 +use App\Models\Manage\Manage;
  6 +use App\Models\Project\Project;
  7 +use App\Models\WorkOrder\TicketProject;
  8 +use Illuminate\Console\Command;
  9 +use Illuminate\Support\Facades\Http;
  10 +
  11 +class FetchTicketProjects extends Command
  12 +{
  13 + /**
  14 + * The name and signature of the console command.
  15 + *
  16 + * @var string
  17 + */
  18 + protected $signature = 'workorder:fetch-ticket-projects {version}';
  19 +
  20 + /**
  21 + * The console command description.
  22 + *
  23 + * @var string
  24 + */
  25 + protected $description = '同步V5,V6的项目到 gl_ticket_projects 表';
  26 +
  27 + /**
  28 + * Create a new command instance.
  29 + *
  30 + * @return void
  31 + */
  32 + public function __construct()
  33 + {
  34 + parent::__construct();
  35 + }
  36 +
  37 + /**
  38 + * Execute the console command.
  39 + *
  40 + * @return int
  41 + */
  42 + public function handle()
  43 + {
  44 + $version = $this->argument('version');
  45 + if ($version == 'v5') {
  46 + $this->fetch_v5();
  47 + } elseif ($version == 'v6') {
  48 + $this->fetch_v6();
  49 + } else {
  50 + $this->error('Invalid action. Use "v5" or "v6".');
  51 + return 1;
  52 + }
  53 + return 0;
  54 + }
  55 +
  56 +
  57 + /**
  58 + * @return void
  59 + * 请求:https://www.quanqiusou.cn/extend_api/webs/globalso_all.php
  60 + */
  61 + public function fetch_v5()
  62 + {
  63 + # pm 项目经理 assm 售后服务经理
  64 + $response = Http::get('https://www.quanqiusou.cn/extend_api/webs/globalso_all.php');
  65 + if ($response->status() == 200) {
  66 + $items = $response->json();
  67 + foreach ($items as $item) {
  68 + # V5: 版本号+postid
  69 + $uuid = md5("V5{$item['postid']}");
  70 + $project = TicketProject::where('uuid', $uuid)->first();
  71 +
  72 + $item['pm'] = $item['pm'] == '未安排' ? '杨长远' : $item['pm'];
  73 + $item['assm'] = $item['assm'] == '未安排' ? '杨长远' : $item['assm'];
  74 + $item['yhs'] = $item['yhs'] == '未安排' ? '杨长远' : $item['yhs'];
  75 +
  76 + // 如果 $item['cate'] 包含”推广“字符,则$engineer_name = $item['assm']
  77 + $engineer_name = (strpos($item['cate'], '推广') !== false) ? $item['yhs'] : $item['assm'];
  78 +
  79 + $fields = [
  80 + 'post_id' => $item['postid'],
  81 + 'company_name' => $item['company'],
  82 + 'title' => $item['title'],
  83 + 'engineer_id' => Manage::where('name', $engineer_name)->value('id') ?? 0, // 第一负责人
  84 + 'assm_id' => Manage::where('name', $item['assm'])->value('id') ?? 0, //售后服务经理
  85 + 'seom_id' => Manage::where('name', $item['yhs'])->value('id') ?? 0, //售后服务经理
  86 + 'website' => $item['main_url'] ?? '',
  87 + ];
  88 + if (!$project) {
  89 + $new = new TicketProject();
  90 + $new->uuid = $uuid;
  91 + $new->version = 5;
  92 + $new->table_id = 0;
  93 + foreach ($fields as $k => $v) {
  94 + $new->$k = $v;
  95 + }
  96 + $new->save();
  97 + } else {
  98 + $changed = false;
  99 + foreach ($fields as $k => $v) {
  100 + if ($project->$k != $v) {
  101 + $project->$k = $v;
  102 + $changed = true;
  103 + }
  104 + }
  105 + if ($changed) {
  106 + $project->save();
  107 + }
  108 + }
  109 + echo "V5: {$item['postid']} - {$item['title']} - {$item['company']} - {$item['main_url']}\n";
  110 + }
  111 + }
  112 + }
  113 +
  114 + /**
  115 + * @return void
  116 + * 1. 按照ID升序查询 gl_project 表 limit 10
  117 + * 2。同步到 TicketProject 后,redis 缓存 ID
  118 + */
  119 + public function fetch_v6()
  120 + {
  121 + $lastid = 0;
  122 + while (true) {
  123 + try {
  124 + $items = Project::where('id', '>', intval($lastid))
  125 + ->orderBy('id', 'asc')
  126 + ->limit(10)
  127 + ->get();
  128 + if ($items->isEmpty()) {
  129 + echo "not found items \n";
  130 + break;
  131 + }
  132 + foreach ($items as $item) {
  133 + $uuid = md5("V5{$item->id}");
  134 + $project = TicketProject::where('uuid', $uuid)->first();
  135 + $fields = [
  136 + 'company_name' => $item->company,
  137 + 'title' => $item->title,
  138 + 'assm_id' => $item->type ==3 ? $item->deploy_optimize->manager_mid ?? 0 : $item->deploy_build->manager_mid ?? 0, // 售后服务经理
  139 + 'seom_id' => $item->deploy_optimize->optimist_mid ?? $item->deploy_optimize->manager_mid ?? $item->deploy_optimize->tech_leader ?? 0, // 优化推广负责人
  140 + 'engineer_id' => $item->type == 3 ?
  141 + $item->deploy_optimize->optimist_mid ?? $item->deploy_optimize->manager_mid ?? $item->deploy_optimize->tech_leader ?? 0
  142 + : $item->deploy_build->manager_mid ?? $item->deploy_build->leader_mid ?? 0, // 技术组长
  143 + ];
  144 + if (!$project) {
  145 + $project = new TicketProject();
  146 + $project->uuid = $uuid;
  147 + $project->post_id = $item->post_id;
  148 + $project->version = 6;
  149 + $project->table_id = $item->id;
  150 + foreach ($fields as $k => $v) {
  151 + $project->$k = $v;
  152 + }
  153 + $project->save();
  154 + }else{
  155 + $changed = false;
  156 + foreach ($fields as $k => $v) {
  157 + if ($project->$k != $v) {
  158 + $project->$k = $v;
  159 + $changed = true;
  160 + }
  161 + }
  162 + if ($changed) {
  163 + $project->save();
  164 + }
  165 + }
  166 + $lastid = $item->id;
  167 + echo date('Y-m-d H:i:s') . " V6: $item->id {$item->company} fetch ok \n";
  168 + }
  169 + }catch (\Exception $exception) {
  170 + echo $exception;
  171 + break;
  172 + }
  173 + }
  174 + }
  175 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Controllers\Api\WorkOrder;
  4 +
  5 +use App\Http\Controllers\Api\BaseController;
  6 +use App\Http\Requests\Api\WorkOrder\TicketStoreRequest;
  7 +use App\Models\WorkOrder\TicketLog;
  8 +use App\Models\WorkOrder\TicketProject;
  9 +use App\Models\WorkOrder\Tickets;
  10 +use Illuminate\Http\Request;
  11 +use Illuminate\Support\Facades\DB;
  12 +
  13 +class TicketController extends BaseController
  14 +{
  15 + /**
  16 + * Display a listing of the resource.
  17 + *
  18 + * @return \Illuminate\Http\Response
  19 + */
  20 + public function index($project_id, Request $request)
  21 + {
  22 + $project = TicketProject::where('uuid', $project_id)->first();
  23 + if (!$project) return $this->error('未找到项目', 404);
  24 + $page = (int)$request->input('page', 1);
  25 + $size = (int)$request->input('size', 10);
  26 +
  27 + $tickets = Tickets::with([
  28 + 'project.projectV6:id,company,version',
  29 + 'logs.engineer:id,name',
  30 + ])
  31 + ->where('project_id', $project->id)
  32 + ->orderBy('id', 'desc')
  33 + ->paginate($size, ['*'], 'page', $page);
  34 + return response()->json(['data' => $tickets]);
  35 + }
  36 +
  37 + /**
  38 + * Store a newly created resource in storage.
  39 + *
  40 + * @param \Illuminate\Http\Request $request
  41 + * @return \Illuminate\Http\Response
  42 + * B端用户在企微群里提交工单
  43 + */
  44 + public function store($project_id, TicketStoreRequest $request)
  45 + {
  46 + $request->validated();
  47 + $project = TicketProject::where('uuid', $project_id)->first();
  48 + if (!$project) return $this->error('未找到项目', 404);
  49 + if ($project->version == 6){
  50 + if ($project->project->projectV6->delete_status) return $this->error('该项目已被删除', 400);
  51 + if ($project->project->projectV6->extend_type == 5) return $this->error('未续费', 400);
  52 + if ($project->project->projectV6->type == 8) return $this->error('项目已归档', 400);
  53 + if ($project->project->projectV6->site_status == 1) return $this->error('站点已关闭', 400);
  54 + }
  55 + $result = DB::transaction(function () use ($request, $project) {
  56 + $ticket = new Tickets();
  57 + $ticket->project_id = $project->id;
  58 + $ticket->title = $request->input('title');
  59 + $ticket->content = $request->input('content');
  60 + // $files = [NULL]
  61 + $files = $request->input('files');
  62 + if (empty($files) || (is_array($files) && count(array_filter($files, function($v){ return !is_null($v); })) === 0)) {
  63 + $ticket->files = null;
  64 + } else {
  65 + $ticket->files = json_encode($files);
  66 + }
  67 + $ticket->submit_side = 2; // 2 for B-side submission
  68 + $ticket->submit_username = $request->input('submit_username');
  69 + $ticket->save();
  70 + $log = new TicketLog();
  71 + if ($project->version == 5){
  72 + # V5
  73 + $log->engineer_id = $project->engineer_id;
  74 + }else{
  75 + # V6 的项目
  76 + if ($project->projectV6->type == 3){
  77 + // 项目类型是优化推广,项目负责人找优化
  78 + $seo = $project->projectV6->deploy_optimize;
  79 + $log->engineer_id = $seo->manager_mid ?? $seo->optimist_mid ?? 0;
  80 + }else{
  81 + // 非优化推广项目,项目负责人找技术组长
  82 + $build = $project->projectV6->deploy_build;
  83 + $log->engineer_id = $build->leader_mid ?? 0;
  84 + }
  85 + }
  86 + $ticket->logs()->save($log);
  87 + return $ticket;
  88 + });
  89 + return response()->json(['data' => $result]);
  90 + }
  91 +
  92 + /**
  93 + * Display the specified resource.
  94 + *
  95 + * @param int $id
  96 + * @return \Illuminate\Http\Response
  97 + */
  98 + public function show($project_id, $id)
  99 + {
  100 + $ticket = Tickets::with([
  101 + 'logs.engineer:id,name',
  102 + 'project.projectV6:id,company',
  103 + ])
  104 + ->find($id);
  105 +
  106 + if (!$ticket) return $this->error('工单未找到', 404);
  107 +
  108 + if ($ticket->project->uuid !== $project_id) return $this->error('无权限查看该工单', 403);
  109 +
  110 + if ($ticket->project->version == 6){
  111 + if ($ticket->project->projectV6->delete_status) return $this->error('该项目已被删除', 400);
  112 + if ($ticket->project->projectV6->extend_type == 5) return $this->error('未续费', 400);
  113 + if ($ticket->project->projectV6->type == 8) return $this->error('项目已归档', 400);
  114 + if ($ticket->project->projectV6->site_status == 1) return $this->error('站点已关闭', 400);
  115 + }
  116 + return response()->json(['data' => $ticket]);
  117 + }
  118 +
  119 + /**
  120 + * Update the specified resource in storage.
  121 + *
  122 + * @param \Illuminate\Http\Request $request
  123 + * @param int $id
  124 + * @return \Illuminate\Http\Response
  125 + */
  126 + public function update(Request $request, $id)
  127 + {
  128 + //
  129 + }
  130 +
  131 + /**
  132 + * Remove the specified resource from storage.
  133 + *
  134 + * @param int $id
  135 + * @return \Illuminate\Http\Response
  136 + */
  137 + public function destroy($id)
  138 + {
  139 + //
  140 + }
  141 +
  142 + public function projectInfo($project_id)
  143 + {
  144 + $project = TicketProject::with([
  145 + 'projectV6:id,company',
  146 + 'assm:id,name',
  147 + 'seom:id,name',
  148 + 'first_engineer:id,name',
  149 + ])
  150 + ->where('uuid', $project_id)->first();
  151 + if (!$project) return $this->error('未找到项目', 404);
  152 + return response()->json(['data' => $project]);
  153 + }
  154 +}
@@ -12,10 +12,23 @@ namespace App\Http\Controllers\Aside\Com; @@ -12,10 +12,23 @@ namespace App\Http\Controllers\Aside\Com;
12 12
13 use App\Enums\Common\Code; 13 use App\Enums\Common\Code;
14 use App\Http\Controllers\Aside\BaseController; 14 use App\Http\Controllers\Aside\BaseController;
  15 +use App\Models\Blog\Blog;
  16 +use App\Models\Blog\BlogCategory;
  17 +use App\Models\CustomModule\CustomModuleCategory;
  18 +use App\Models\CustomModule\CustomModuleContent;
15 use App\Models\Domain\DomainInfo; 19 use App\Models\Domain\DomainInfo;
  20 +use App\Models\News\News;
  21 +use App\Models\News\NewsCategory;
  22 +use App\Models\Product\Category;
  23 +use App\Models\Product\CategoryRelated;
  24 +use App\Models\Product\Keyword;
  25 +use App\Models\Product\Product;
16 use App\Models\Project\Country as CountryModel; 26 use App\Models\Project\Country as CountryModel;
17 use App\Models\Project\DeployBuild; 27 use App\Models\Project\DeployBuild;
  28 +use App\Models\WebSetting\SettingNum;
  29 +use App\Models\WebSetting\TranslateBigProject;
18 use App\Models\WebSetting\WebLanguage; 30 use App\Models\WebSetting\WebLanguage;
  31 +use App\Services\ProjectServer;
19 use Illuminate\Http\Request; 32 use Illuminate\Http\Request;
20 use Illuminate\Support\Facades\DB; 33 use Illuminate\Support\Facades\DB;
21 34
@@ -30,6 +43,69 @@ use Illuminate\Support\Facades\DB; @@ -30,6 +43,69 @@ use Illuminate\Support\Facades\DB;
30 class CNoticeController extends BaseController 43 class CNoticeController extends BaseController
31 { 44 {
32 /** 45 /**
  46 + * @remark :计算页面数量
  47 + * @name :countLanguagePage
  48 + * @author :lyh
  49 + * @method :post
  50 + * @time :2025/6/19 17:14
  51 + */
  52 + public function countLanguagePage(){
  53 + $this->request->validate([
  54 + 'language'=>'required',
  55 + 'project_id'=>'required',
  56 + ],[
  57 + 'language.required' => 'language不能为空',
  58 + 'project_id.required' => 'project_id不能为空',
  59 + ]);
  60 + $bigProjectModel = new TranslateBigProject();
  61 + $project_id_arr = $bigProjectModel->selectField(['status'=>1],'project_id') ?? [];
  62 + if(in_array($this->param['project_id'],$project_id_arr)){
  63 + $this->response('success');
  64 + }
  65 + $lang_num = count($this->param['language']);
  66 + ProjectServer::useProject($this->param['project_id']);
  67 + $keyword_num = (new Keyword())->counts(['route'=>['!=',null]]);
  68 + $data_num = $this->productNum() + $this->customNum() + $this->newsNum() + $this->blogNum();
  69 + DB::disconnect('custom_mysql');
  70 + $number = $keyword_num * 18 + $lang_num * $data_num;
  71 + if($number >= 450000){
  72 + $this->response('success',Code::SUCCESS,['msg'=>'翻译数量过多, 大概页面数量:'.$number.', 磁盘空间占用可能会超过40G,请联系管理员操作!']);
  73 + }
  74 + $this->response('success');
  75 + }
  76 +
  77 + /**
  78 + * @remark :产品分类页面数量
  79 + * @name :productCateNum
  80 + * @author :lyh
  81 + * @method :post
  82 + * @time :2025/1/4 10:43
  83 + */
  84 + public function productNum(){
  85 + $number = (new Product())->counts(['status'=>1]);
  86 + $settingModel = new SettingNum();
  87 + $info = $settingModel->read(['type'=>1]);
  88 + if($info === false){
  89 + $product_page_number = 15;
  90 + }else{
  91 + $product_page_number = $info['num'];
  92 + }
  93 + $productCateModel = new Category();
  94 + $productCateList = $productCateModel->list(['status'=>1]);
  95 + if(!empty($productCateList)){
  96 + $cateReModel = new CategoryRelated();
  97 + foreach ($productCateList as $v){
  98 + $cate_num = $cateReModel->counts(['cate_id'=>$v['id']]);
  99 + if($cate_num == 0){
  100 + $number += 1;
  101 + }else{
  102 + $number += ceil($cate_num / $product_page_number);
  103 + }
  104 + }
  105 + }
  106 + return $number;
  107 + }
  108 + /**
33 * 更新通知C端 109 * 更新通知C端
34 * @param Request $request 110 * @param Request $request
35 * @return \Illuminate\Http\JsonResponse 111 * @return \Illuminate\Http\JsonResponse
@@ -58,8 +134,102 @@ class CNoticeController extends BaseController @@ -58,8 +134,102 @@ class CNoticeController extends BaseController
58 http_post($url, json_encode($param)); 134 http_post($url, json_encode($param));
59 $this->response('更新中请稍后, 更新完成将会发送站内信通知更新结果!'); 135 $this->response('更新中请稍后, 更新完成将会发送站内信通知更新结果!');
60 } 136 }
  137 + /**
  138 + * @remark :新闻数量
  139 + * @name :newsNum
  140 + * @author :lyh
  141 + * @method :post
  142 + * @time :2025/1/4 11:21
  143 + */
  144 + public function newsNum(){
  145 + $newsModel = new News();
  146 + $number = $newsModel->counts(['status'=>1]);
  147 + $settingModel = new SettingNum();
  148 + $info = $settingModel->read(['type'=>2]);
  149 + if($info === false){
  150 + $news_page_number = 10;
  151 + }else{
  152 + $news_page_number = $info['num'];
  153 + }
  154 + $newsCateModel = new NewsCategory();
  155 + $newsCateList = $newsCateModel->list(['status'=>0]);
  156 + if(!empty($newsCateList)){
  157 + foreach ($newsCateList as $v){
  158 + $cate_num = $newsModel->counts(['category_id'=>['like',','.$v['id'].',']]);
  159 + if($cate_num == 0){
  160 + $number += 1;
  161 + }else{
  162 + $number += ceil($cate_num / $news_page_number);
  163 + }
  164 + }
  165 + }
  166 + return $number;
  167 + }
  168 +
  169 + /**
  170 + * @remark :博客数量
  171 + * @name :blogNum
  172 + * @author :lyh
  173 + * @method :post
  174 + * @time :2025/1/4 11:21
  175 + */
  176 + public function blogNum(){
  177 + $blogModel = new Blog();
  178 + $number = $blogModel->counts(['status'=>1]);
  179 + $settingModel = new SettingNum();
  180 + $info = $settingModel->read(['type'=>3]);
  181 + if($info === false){
  182 + $news_page_number = 10;
  183 + }else{
  184 + $news_page_number = $info['num'];
  185 + }
  186 + $blogCateModel = new BlogCategory();
  187 + $blogCateList = $blogCateModel->list(['status'=>0]);
  188 + if(!empty($blogCateList)){
  189 + foreach ($blogCateList as $v){
  190 + $cate_num = $blogModel->counts(['category_id'=>['like',','.$v['id'].',']]);
  191 + if($cate_num == 0){
  192 + $number += 1;
  193 + }else{
  194 + $number += ceil($cate_num / $news_page_number);
  195 + }
  196 + }
  197 + }
  198 + return $number;
  199 + }
61 200
62 /** 201 /**
  202 + * @remark :扩展模块数量
  203 + * @name :blogNum
  204 + * @author :lyh
  205 + * @method :post
  206 + * @time :2025/1/4 11:21
  207 + */
  208 + public function customNum(){
  209 + $contentModel = new CustomModuleContent();
  210 + $number = $contentModel->counts(['status'=>0]);
  211 + $settingModel = new SettingNum();
  212 + $info = $settingModel->read(['type'=>2]);
  213 + if($info === false){
  214 + $news_page_number = 10;
  215 + }else{
  216 + $news_page_number = $info['num'];
  217 + }
  218 + $cateModel = new CustomModuleCategory();
  219 + $cateList = $cateModel->list(['status'=>0]);
  220 + if(!empty($cateList)){
  221 + foreach ($cateList as $v){
  222 + $cate_num = $contentModel->counts(['category_id'=>['like',','.$v['id'].',']]);
  223 + if($cate_num == 0){
  224 + $number += 1;
  225 + }else{
  226 + $number += ceil($cate_num / $news_page_number);
  227 + }
  228 + }
  229 + }
  230 + return $number;
  231 + }
  232 + /**
63 * @remark :获取当前项目选中的语种 233 * @remark :获取当前项目选中的语种
64 * @name :getCountry 234 * @name :getCountry
65 * @author :lyh 235 * @author :lyh
  1 +<?php
  2 +
  3 +namespace App\Http\Controllers\Aside\WorkOrder;
  4 +
  5 +use App\Enums\Common\Code;
  6 +use App\Http\Controllers\Aside\BaseController;
  7 +use App\Http\Requests\Aside\WorkOrder\AsideTicketStoreRequest;
  8 +use App\Http\Requests\Aside\WorkOrder\AsideTicketListRequest;
  9 +use App\Http\Requests\Aside\WorkOrder\AsideTicketUpdateRequest;
  10 +use App\Models\WorkOrder\TicketLog;
  11 +use App\Models\WorkOrder\TicketProject;
  12 +use App\Models\WorkOrder\Tickets;
  13 +use Illuminate\Support\Facades\DB;
  14 +
  15 +class AsideTicketController extends BaseController
  16 +{
  17 + /**
  18 + * Display a listing of the resource.
  19 + *
  20 + * @return \Illuminate\Http\Response
  21 + */
  22 + public function index(AsideTicketListRequest $request)
  23 + {
  24 + /**
  25 + * 1. 超管看所有工单
  26 + * 2. 其他查看和自己有关的工单
  27 + */
  28 + $lists = TicketLog::with([
  29 + 'ticket.project.projectV6:id,company,title',
  30 + 'ticket.logs.engineer:id,name',
  31 + ])
  32 + ->when($this, function ($query) {
  33 + $role = $this->manage['role'];
  34 + // 超管 role = 1
  35 + if ($role == 1) {
  36 + return $query;
  37 + }
  38 + // 其他角色查自己参与的工单
  39 + return $query->where('engineer_id', $this->manage['id']);
  40 + })
  41 + ->when($request->input('project_id') !== null, function ($query) use ($request) {
  42 + // project_id 查 gl_ticket_projects.uuid
  43 + return $query->whereHas('ticket.project', function ($q) use ($request) {
  44 + $q->where('uuid', $request->input('project_id'));
  45 + });
  46 + })
  47 + ->when($request->input('status') !== null, function ($query) use ($request) {
  48 + // status 查 gl_tickets.status
  49 + return $query->whereHas('ticket', function ($q) use ($request) {
  50 + $q->where('status', $request->input('status'));
  51 + });
  52 + })
  53 + ->when($request->input('search'), function ($query) use ($request) {
  54 + // search 查 gl_tickets.title 或 gl_ticket_projects.title 或 gl_ticket_projects.company_name
  55 + $search = $request->input('search');
  56 + $query->where(function ($q) use ($search) {
  57 + $q->whereHas('ticket', function ($q1) use ($search) {
  58 + $q1->where('title', 'like', '%' . $search . '%');
  59 + })
  60 + ->orWhereHas('ticket.project', function ($q2) use ($search) {
  61 + $q2->where('title', 'like', '%' . $search . '%')
  62 + ->orWhere('company_name', 'like', '%' . $search . '%');
  63 + });
  64 + });
  65 + })
  66 + ->orderBy('id', 'desc')
  67 + ->paginate($this->row, ['*'], 'page', $this->page);
  68 + $this->response('success', Code::SUCCESS, $lists);
  69 + }
  70 +
  71 + public function getProjects($search)
  72 + {
  73 + $projects = TicketProject::with([
  74 + 'projectV6:id,company,title',
  75 + ])
  76 + ->where(function ($query) use ($search) {
  77 + $query->where('title', 'like', '%' . $search . '%')
  78 + ->orWhere('company_name', 'like', '%' . $search . '%')
  79 + ->orWhereHas('projectV6', function ($q) use ($search) {
  80 + $q->where('company', 'like', '%' . $search . '%')
  81 + ->orWhere('title', 'like', '%' . $search . '%');
  82 + });
  83 + })
  84 + ->get();
  85 + $this->response('success', Code::SUCCESS, $projects);
  86 + }
  87 +
  88 + /**
  89 + * Store a newly created resource in storage.
  90 + *
  91 + * @param \Illuminate\Http\Request $request
  92 + * @return \Illuminate\Http\Response
  93 + */
  94 + public function store(AsideTicketStoreRequest $request)
  95 + {
  96 + $request->validated();
  97 + $project = TicketProject::where('uuid', $request->input('project_id'))->first();
  98 + if ($project->version == 6){
  99 + if ($project->projectV6->delete_status) $this->response('该项目已被删除', Code::USER_MODEL_NOTFOUND_ERROE);
  100 + if ($project->projectV6->extend_type == 5) $this->response('未续费', Code::USER_MODEL_NOTFOUND_ERROE);
  101 + if ($project->projectV6->type == 8) $this->response('项目已归档', Code::USER_MODEL_NOTFOUND_ERROE);
  102 + if ($project->projectV6->site_status == 1) $this->response('站点已关闭', Code::USER_MODEL_NOTFOUND_ERROE);
  103 + }
  104 + $result = DB::transaction(function () use ($request, $project) {
  105 + $ticket = new Tickets();
  106 + $ticket->project_id = $project->id;
  107 + $ticket->title = $request->input('title');
  108 + $ticket->content = $request->input('content');
  109 + // $files = [NULL]
  110 + $files = $request->input('files');
  111 + if (empty($files) || (is_array($files) && count(array_filter($files, function($v){ return !is_null($v); })) === 0)) {
  112 + $ticket->files = null;
  113 + } else {
  114 + $ticket->files = json_encode($files);
  115 + }
  116 + $ticket->submit_side = 1; // 1 for A-side submission
  117 + $ticket->submit_user_id = $this->manage['id'];
  118 + $ticket->submit_username = $this->manage['name'];
  119 + $ticket->save();
  120 + // A 端提工单,都是针对客户提的需求等开发任务;比如翻译,修改页面等。。。
  121 + foreach ($request->input('engineer_ids', []) as $engineer_id) {
  122 + $log = new TicketLog();
  123 + $log->engineer_id = $engineer_id;
  124 + $ticket->logs()->save($log);
  125 + }
  126 + return $ticket;
  127 + });
  128 + $this->response('success', Code::SUCCESS, $result->toArray());
  129 + }
  130 +
  131 + /**
  132 + * Display the specified resource.
  133 + *
  134 + * @param int $id
  135 + * @return \Illuminate\Http\Response
  136 + */
  137 + public function show($id)
  138 + {
  139 + $ticket = Tickets::with([
  140 + 'logs.engineer',
  141 + 'project.projectV6:id,company,title',
  142 + ])->find($id);
  143 +
  144 + if (!$ticket) $this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
  145 +
  146 + if ($this->manage['role'] != 1
  147 + && $ticket->submit_user_id != $this->manage['id']
  148 + && $ticket->logs()->where('engineer_id', $this->manage['id'])->count() == 0)
  149 + // 只能查看自己的工单
  150 + $this->response('没有权限查看该工单', Code::USER_PERMISSION_ERROE);
  151 +
  152 + // TODO 判断是否有查看工单详情权限,待添加
  153 + $this->response('success', Code::SUCCESS, $ticket->toArray());
  154 + }
  155 +
  156 + /**
  157 + * A端修改工单
  158 + * 1. 邀请协同的同事
  159 + * 2. 审核工单
  160 + */
  161 + public function update(AsideTicketUpdateRequest $request, $id)
  162 + {
  163 + $request->validated();
  164 + $ticket = Tickets::find($id);
  165 + if (!$ticket) $this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
  166 + // 检测修改权限
  167 + if ($ticket->submit_side == 1 && $ticket->submit_user_id != $this->manage['id']) {
  168 + // A端提交的工单,只有提交人可以修改
  169 + $this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
  170 + } elseif ($ticket->submit_side == 2)
  171 + {
  172 + // B端提交的工单,只有第一对接人可以修改
  173 + $log = $ticket->logs()->first();
  174 + if ($log->engineer_id != $this->manage['id'])
  175 + $this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
  176 + }
  177 +
  178 + // 开始修改
  179 + $result = DB::transaction(function () use ($request, $ticket) {
  180 + if ($request->input('engineer_ids'))
  181 + {
  182 + // 有邀请工程师协同处理
  183 + foreach ($request->input('engineer_ids') as $engineer_id)
  184 + {
  185 + try {
  186 + // 利用唯一索引去重
  187 + $new_log = new TicketLog();
  188 + $new_log->engineer_id = $engineer_id;
  189 + $ticket->logs()->save($new_log);
  190 + }catch (\Exception $exception){}
  191 + }
  192 + }
  193 +
  194 + $ticket->reply = $request->input('reply', null);
  195 + $ticket->status = $request->input('status', $ticket->status);
  196 + $ticket->save();
  197 + if ($ticket->status == Tickets::STATUS_COMPLETED)
  198 + {
  199 + // 完成工单,把子任务里面未完成的工单改为完成
  200 + $ticket->end_at = now();
  201 + $ticket->logs()->where('status', '<', TicketLog::STATUS_COMPLETED)
  202 + ->update(['status' => TicketLog::STATUS_COMPLETED, 'end_at' => now()]);
  203 + }
  204 + return $ticket;
  205 + });
  206 + $this->response('success', Code::SUCCESS, $result->toArray());
  207 + }
  208 +
  209 + /**
  210 + * Remove the specified resource from storage.
  211 + *
  212 + * @param int $id
  213 + * @return \Illuminate\Http\Response
  214 + */
  215 + public function destroy($id)
  216 + {
  217 + //
  218 + }
  219 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Controllers\Aside\WorkOrder;
  4 +
  5 +use App\Enums\Common\Code;
  6 +use App\Http\Controllers\Aside\BaseController;
  7 +use App\Http\Requests\Aside\WorkOrder\AsideTicketLogUpdateRequest;
  8 +use App\Models\WorkOrder\TicketLog;
  9 +use App\Models\WorkOrder\Tickets;
  10 +use Illuminate\Http\Request;
  11 +use Illuminate\Support\Facades\DB;
  12 +
  13 +class AsideTicketLogController extends BaseController
  14 +{
  15 + /**
  16 + * Display a listing of the resource.
  17 + *
  18 + * @return \Illuminate\Http\Response
  19 + */
  20 + public function index()
  21 + {
  22 + //
  23 + }
  24 +
  25 + /**
  26 + * Store a newly created resource in storage.
  27 + *
  28 + * @param \Illuminate\Http\Request $request
  29 + * @return \Illuminate\Http\Response
  30 + */
  31 + public function store(Request $request)
  32 + {
  33 + //
  34 + }
  35 +
  36 + /**
  37 + * Display the specified resource.
  38 + *
  39 + * @param int $id
  40 + * @return \Illuminate\Http\Response
  41 + */
  42 + public function show($id)
  43 + {
  44 + //
  45 + }
  46 +
  47 + /**
  48 + * A 端完结工单任务
  49 + */
  50 + public function update(AsideTicketLogUpdateRequest $request, $id)
  51 + {
  52 + $log = TicketLog::find($id); // 拆分的子工单
  53 + if (!$log) {
  54 + $this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
  55 + }
  56 + if ($log->engineer_id != $this->manage['id']) {
  57 + // 只能操作自己的工单
  58 + $this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
  59 + }
  60 + if ($log->status >= TicketLog::STATUS_COMPLETED) {
  61 + // 已经完成的工单不能再操作
  62 + $this->response('工单已完成,不能再操作', Code::USER_PERMISSION_ERROE);
  63 + }
  64 + $ticket = $log->ticket;
  65 + $result = DB::transaction(function () use ($request, $ticket, $log) {
  66 + if ($request->input('status') !== null)
  67 + {
  68 + $log->status = $request->input('status');
  69 + if ($log->status >= TicketLog::STATUS_COMPLETED)
  70 + {
  71 + // 我的工单标记为已完成
  72 + $log->status = $request->input('status');
  73 + $log->end_at = now();
  74 + }
  75 + }
  76 + $log->save();
  77 + // 是否有未完成的子任务
  78 + $pending = $ticket->logs()
  79 + ->where('status', '<', TicketLog::STATUS_COMPLETED)
  80 + ->count();
  81 + if ($pending)
  82 + {
  83 + $ticket->status = Tickets::STATUS_PROCESSING;
  84 + }else
  85 + {
  86 + $ticket->status = Tickets::STATUS_COMPLETED;
  87 + // 如果所有子任务都完成了,则将工单状态改为已完成
  88 + $ticket->end_at = now();
  89 + }
  90 + $ticket->save();
  91 + return $log;
  92 + });
  93 + $this->response('success', Code::SUCCESS, $result->toArray());
  94 +
  95 + }
  96 +
  97 + /**
  98 + * Remove the specified resource from storage.
  99 + *
  100 + * @param int $id
  101 + * @return \Illuminate\Http\Response
  102 + */
  103 + public function destroy($id)
  104 + {
  105 + //
  106 + }
  107 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Requests\Api\WorkOrder;
  4 +
  5 +use Illuminate\Foundation\Http\FormRequest;
  6 +
  7 +class TicketStoreRequest extends FormRequest
  8 +{
  9 + /**
  10 + * Determine if the user is authorized to make this request.
  11 + *
  12 + * @return bool
  13 + */
  14 + public function authorize()
  15 + {
  16 + return true;
  17 + }
  18 +
  19 + /**
  20 + * Get the validation rules that apply to the request.
  21 + *
  22 + * @return array
  23 + */
  24 + public function rules()
  25 + {
  26 + return [
  27 + 'title' => 'required|string',
  28 + 'content' => 'required|string',
  29 + 'files' => 'nullable|array',
  30 + 'submit_username' => 'required|string',
  31 + ];
  32 + }
  33 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Requests\Aside\WorkOrder;
  4 +
  5 +use Illuminate\Foundation\Http\FormRequest;
  6 +
  7 +class AsideTicketListRequest extends FormRequest
  8 +{
  9 + /**
  10 + * Determine if the user is authorized to make this request.
  11 + *
  12 + * @return bool
  13 + */
  14 + public function authorize()
  15 + {
  16 + return true;
  17 + }
  18 +
  19 + /**
  20 + * Get the validation rules that apply to the request.
  21 + *
  22 + * @return array
  23 + */
  24 + public function rules()
  25 + {
  26 + return [
  27 + 'project_id' => 'nullable|string', // 产品ID
  28 + 'status' => 'nullable|in:0,1,2,3|integer',
  29 + 'search' => 'nullable|string', // 搜索关键词
  30 + 'page' => 'nullable|integer',
  31 + 'size' => 'nullable|integer',
  32 + ];
  33 + }
  34 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Requests\Aside\WorkOrder;
  4 +
  5 +use Illuminate\Foundation\Http\FormRequest;
  6 +
  7 +class AsideTicketLogUpdateRequest extends FormRequest
  8 +{
  9 + /**
  10 + * Determine if the user is authorized to make this request.
  11 + *
  12 + * @return bool
  13 + */
  14 + public function authorize()
  15 + {
  16 + return true;
  17 + }
  18 +
  19 + /**
  20 + * Get the validation rules that apply to the request.
  21 + *
  22 + * @return array
  23 + */
  24 + public function rules()
  25 + {
  26 + return [
  27 + 'status' => 'required|in:0,1,2,3|integer',
  28 + 'reply' => 'nullable|string',
  29 + ];
  30 + }
  31 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Requests\Aside\WorkOrder;
  4 +
  5 +use Illuminate\Foundation\Http\FormRequest;
  6 +
  7 +class AsideTicketStoreRequest extends FormRequest
  8 +{
  9 + /**
  10 + * Determine if the user is authorized to make this request.
  11 + *
  12 + * @return bool
  13 + */
  14 + public function authorize()
  15 + {
  16 + return true;
  17 + }
  18 +
  19 + /**
  20 + * Get the validation rules that apply to the request.
  21 + *
  22 + * @return array
  23 + */
  24 + public function rules()
  25 + {
  26 + return [
  27 + 'project_id' => 'required|exists:gl_ticket_projects,uuid',
  28 + 'title' => 'required|string',
  29 + 'content' => 'required|string',
  30 + 'files' => 'nullable|array',
  31 + 'engineer_ids' => 'array',
  32 + ];
  33 + }
  34 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Requests\Aside\WorkOrder;
  4 +
  5 +use Illuminate\Foundation\Http\FormRequest;
  6 +
  7 +class AsideTicketUpdateRequest extends FormRequest
  8 +{
  9 + /**
  10 + * Determine if the user is authorized to make this request.
  11 + *
  12 + * @return bool
  13 + */
  14 + public function authorize()
  15 + {
  16 + return true;
  17 + }
  18 +
  19 + /**
  20 + * Get the validation rules that apply to the request.
  21 + *
  22 + * @return array
  23 + */
  24 + public function rules()
  25 + {
  26 + return [
  27 + 'status' => 'nullable|in:0,1,2,3|integer',
  28 + 'reply' => 'nullable|string',
  29 + 'engineer_ids' => 'nullable|array',
  30 + ];
  31 + }
  32 +}
  1 +<?php
  2 +
  3 +namespace App\Models\WorkOrder;
  4 +
  5 +use App\Models\Base;
  6 +use App\Models\Manage\Manage;
  7 +use Illuminate\Database\Eloquent\Factories\HasFactory;
  8 +
  9 +class TicketLog extends Base
  10 +{
  11 + use HasFactory;
  12 +
  13 + protected $table = 'gl_ticket_logs';
  14 +
  15 + const STATUS_PEDDING = 0; // 待处理
  16 + const STATUS_PROCESSING = 1; // 处理中
  17 + const STATUS_COMPLETED = 2; // 已完成
  18 + const STATUS_CLOSED = 3; // 已关闭
  19 +
  20 + public function engineer()
  21 + {
  22 + return $this->belongsTo(Manage::class, 'engineer_id', 'id')
  23 + ->select(['id', 'name']);
  24 + }
  25 +
  26 + public function ticket()
  27 + {
  28 + return $this->belongsTo(Tickets::class, 'ticket_id', 'id');
  29 + }
  30 +
  31 +}
  1 +<?php
  2 +
  3 +namespace App\Models\WorkOrder;
  4 +
  5 +use App\Models\Base;
  6 +use App\Models\Manage\Manage;
  7 +use App\Models\Project\Project;
  8 +use Illuminate\Database\Eloquent\Factories\HasFactory;
  9 +
  10 +class TicketProject extends Base
  11 +{
  12 + use HasFactory;
  13 +
  14 + protected $table = 'gl_ticket_projects';
  15 +
  16 + public function projectV6()
  17 + {
  18 + return $this->hasOne(Project::class, 'id', 'table_id')
  19 + ->where('version', 6);
  20 + }
  21 +
  22 + //售后服务经理
  23 + public function assm()
  24 + {
  25 + return $this->hasOne(Manage::class, 'id', 'assm_id')
  26 + ->select(['id', 'name']);
  27 + }
  28 +
  29 + // 优化师
  30 + public function seom()
  31 + {
  32 + return $this->hasOne(Manage::class, 'id', 'seom_id')
  33 + ->select(['id', 'name']);
  34 + }
  35 +
  36 + /**
  37 + * 第一负责人
  38 + */
  39 + public function first_engineer()
  40 + {
  41 + return $this->hasOne(Manage::class, 'id', 'engineer_id')
  42 + ->select(['id', 'name']);
  43 + }
  44 +}
  1 +<?php
  2 +
  3 +namespace App\Models\WorkOrder;
  4 +
  5 +use App\Models\Base;
  6 +use Illuminate\Database\Eloquent\Factories\HasFactory;
  7 +
  8 +class Tickets extends Base
  9 +{
  10 + use HasFactory;
  11 +
  12 + protected $table = 'gl_tickets';
  13 +
  14 + const STATUS_PEDDING = 0; // 待处理
  15 + const STATUS_PROCESSING = 1; // 处理中
  16 + const STATUS_COMPLETED = 2; // 已完成
  17 + const STATUS_CLOSED = 3; // 已关闭
  18 +
  19 + /**
  20 + * @return void
  21 + * 关联的工单日志
  22 + */
  23 + public function logs()
  24 + {
  25 + return $this->hasMany(TicketLog::class, 'ticket_id', 'id');
  26 + }
  27 +
  28 + /**
  29 + * 关联项目
  30 + */
  31 + public function project()
  32 + {
  33 + return $this->belongsTo(TicketProject::class, 'project_id', 'id');
  34 + }
  35 +}
  1 +<?php
  2 +
  3 +use Illuminate\Database\Migrations\Migration;
  4 +use Illuminate\Database\Schema\Blueprint;
  5 +use Illuminate\Support\Facades\Schema;
  6 +
  7 +class CreateTicketProjectsTable extends Migration
  8 +{
  9 + /**
  10 + * Run the migrations.
  11 + *
  12 + * @return void
  13 + */
  14 + public function up()
  15 + {
  16 + Schema::create('gl_ticket_projects', function (Blueprint $table) {
  17 + $table->id();
  18 + $table->integer('post_id')->index();
  19 + $table->integer('version')->default(5)->comment('版本号: 5, 6');
  20 + $table->integer('table_id')->index()->comment('来源表ID');
  21 + $table->string('uuid', 32)->unique()->comment('项目链接唯一标识符');
  22 + $table->string('company_name')->nullable()->index()->comment('公司名称');
  23 + $table->string('title')->nullable()->index()->comment('项目标题');
  24 + $table->bigInteger('engineer_id')->default(0)->index()->comment('V5项目的第一负责人 gl_manage 表ID');
  25 + $table->string('website')->nullable()->index()->comment('站点域名');
  26 + $table->timestamps();
  27 + });
  28 + \Illuminate\Support\Facades\DB::statement("ALTER TABLE `gl_ticket_projects` comment '售后工单项目整合表V5,V6'");
  29 + }
  30 +
  31 + /**
  32 + * Reverse the migrations.
  33 + *
  34 + * @return void
  35 + */
  36 + public function down()
  37 + {
  38 + Schema::dropIfExists('gl_ticket_projects');
  39 + }
  40 +}
  1 +<?php
  2 +
  3 +use Illuminate\Database\Migrations\Migration;
  4 +use Illuminate\Database\Schema\Blueprint;
  5 +use Illuminate\Support\Facades\Schema;
  6 +
  7 +class CreateTicketsTable extends Migration
  8 +{
  9 + /**
  10 + * Run the migrations.
  11 + *
  12 + * @return void
  13 + */
  14 + public function up()
  15 + {
  16 + Schema::create('gl_tickets', function (Blueprint $table) {
  17 + $table->id();
  18 + $table->foreignId('project_id')->constrained('gl_ticket_projects')->onDelete('cascade')->comment('项目ID');
  19 + $table->string('title')->nullable()->comment('工单类型,工单标题');
  20 + $table->longText('content')->comment('工单图文描述');
  21 + $table->json('files')->nullable()->comment('附件');
  22 + $table->integer('status')->index()->default(0)->comment('工单状态,0:待处理, 1:处理中,2:已完成, 3:已关闭');
  23 + $table->integer('submit_side')->default(1)->index()->comment('提交方,1: A端, 2: B端');
  24 + $table->string('submit_username')->nullable()->comment('提交人姓名,B端在企微群提交时,留个姓名即可');
  25 + $table->integer('submit_user_id')->default(0)->index()->comment('A端提交时,提交人 gl_manage ID');
  26 + $table->timestamp('end_at')->nullable()->comment('结束时间,工单状态为已完成或已关闭时有值');
  27 + $table->longText('reply')->nullable()->comment('回复内容,审核意见,可以为空');
  28 + $table->timestamps();
  29 + });
  30 + \Illuminate\Support\Facades\DB::statement("ALTER TABLE `gl_tickets` comment '工单表,注意:B端,A端都会提工单'");
  31 + }
  32 +
  33 + /**
  34 + * Reverse the migrations.
  35 + *
  36 + * @return void
  37 + */
  38 + public function down()
  39 + {
  40 + Schema::dropIfExists('gl_tickets');
  41 + }
  42 +}
  1 +<?php
  2 +
  3 +use Illuminate\Database\Migrations\Migration;
  4 +use Illuminate\Database\Schema\Blueprint;
  5 +use Illuminate\Support\Facades\Schema;
  6 +
  7 +class CreateTicketLogsTable extends Migration
  8 +{
  9 + /**
  10 + * Run the migrations.
  11 + *
  12 + * @return void
  13 + */
  14 + public function up()
  15 + {
  16 + Schema::create('gl_ticket_logs', function (Blueprint $table) {
  17 + $table->id();
  18 + $table->foreignId('ticket_id')->constrained('gl_tickets')->onDelete('cascade')->comment('工单ID');
  19 + $table->unsignedInteger('engineer_id')->comment('处理人 gl_manage ID');
  20 + $table->foreign('engineer_id')->references('id')->on('gl_manage')->onDelete('cascade');
  21 + $table->longText('reply')->nullable()->comment('回复内容可以为空');
  22 + $table->integer('status')->default(0)->index()->comment('工单状态,0:待处理, 1:处理中,2:已完成, 3:已关闭');
  23 + $table->boolean('ding')->default(false)->index()->comment('钉钉通知');
  24 + $table->timestamp('end_at')->nullable()->comment('结束时间,工单状态为已完成或已关闭时有值');
  25 + $table->unique(['ticket_id', 'engineer_id'], 'ticket_engineer_unique'); # 唯一索引,防止同一工单被同一人多次操作,同一工单,可以多人协作
  26 + $table->timestamps();
  27 + });
  28 + \Illuminate\Support\Facades\DB::statement("ALTER TABLE `gl_ticket_logs` comment '工单操作日志表,记录工单的分配处理任务等'");
  29 + }
  30 +
  31 + /**
  32 + * Reverse the migrations.
  33 + *
  34 + * @return void
  35 + */
  36 + public function down()
  37 + {
  38 + Schema::dropIfExists('gl_ticket_logs');
  39 + }
  40 +}
@@ -75,4 +75,12 @@ Route::post('/inquiry_relate_domain', [\App\Http\Controllers\Api\PrivateControll @@ -75,4 +75,12 @@ Route::post('/inquiry_relate_domain', [\App\Http\Controllers\Api\PrivateControll
75 Route::get('/get_manage_by_domain', [\App\Http\Controllers\Api\PrivateController::class, 'getProjectManageByDomain']); 75 Route::get('/get_manage_by_domain', [\App\Http\Controllers\Api\PrivateController::class, 'getProjectManageByDomain']);
76 76
77 // 获取信息通过商户号 77 // 获取信息通过商户号
78 -Route::any('/get_project_by_mch_id', [\App\Http\Controllers\Api\PrivateController::class, 'getProjectByMchId']);  
  78 +Route::any('/get_project_by_mch_id', [\App\Http\Controllers\Api\PrivateController::class, 'getProjectByMchId']);
  79 +
  80 +// B端,渠道在企微群操作-售后工单
  81 +Route::prefix('tickets')->group(function () {
  82 + Route::get('/{project_id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'index'])->summary('B端,渠道-工单列表')->name('tickets.list');
  83 + Route::get('/projectInfo/{project_id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'projectInfo'])->summary('B端,渠道-项目信息')->name('tickets.projectInfo');
  84 + Route::post('/{project_id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'store'])->summary('B端,渠道-提工单')->name('tickets.store');
  85 + Route::get('/{project_id}/{id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'show'])->summary('B端,渠道-工单详情')->name('tickets.show');
  86 +});
@@ -15,6 +15,7 @@ Route::middleware(['aloginauth'])->group(function () { @@ -15,6 +15,7 @@ Route::middleware(['aloginauth'])->group(function () {
15 Route::any('/logout', [Aside\LoginController::class, 'logout'])->name('admin.logout.white'); 15 Route::any('/logout', [Aside\LoginController::class, 'logout'])->name('admin.logout.white');
16 Route::any('/getAccessAddress', [Aside\LoginController::class, 'getAccessAddress'])->name('admin.getAccessAddress');//获取B端地址 16 Route::any('/getAccessAddress', [Aside\LoginController::class, 'getAccessAddress'])->name('admin.getAccessAddress');//获取B端地址
17 Route::any('/sendNotify', [Aside\Com\CNoticeController::class, 'sendNotify'])->name('admin.sendNotify'); 17 Route::any('/sendNotify', [Aside\Com\CNoticeController::class, 'sendNotify'])->name('admin.sendNotify');
  18 + Route::any('/countLanguagePage', [Aside\Com\CNoticeController::class, 'countLanguagePage'])->name('admin.countLanguagePage');//统计页面数量
18 Route::any('/getCountry', [Aside\Com\CNoticeController::class, 'getCountry'])->name('admin.getCountry'); 19 Route::any('/getCountry', [Aside\Com\CNoticeController::class, 'getCountry'])->name('admin.getCountry');
19 Route::any('/getDynamicPassword', [Aside\Com\IndexController::class, 'getDynamicPassword'])->name('admin.getDynamicPassword'); 20 Route::any('/getDynamicPassword', [Aside\Com\IndexController::class, 'getDynamicPassword'])->name('admin.getDynamicPassword');
20 Route::any('/notAiHumanizer', [Aside\Com\IndexController::class, 'notAiHumanizer'])->name('admin.notAiHumanizer'); 21 Route::any('/notAiHumanizer', [Aside\Com\IndexController::class, 'notAiHumanizer'])->name('admin.notAiHumanizer');
@@ -255,6 +256,16 @@ Route::middleware(['aloginauth'])->group(function () { @@ -255,6 +256,16 @@ Route::middleware(['aloginauth'])->group(function () {
255 Route::get('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'show'])->name('admin.workorder.show')->summary('A端工单详情'); 256 Route::get('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'show'])->name('admin.workorder.show')->summary('A端工单详情');
256 Route::post('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'update'])->name('admin.workorder.update')->summary('A端更新工单'); 257 Route::post('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'update'])->name('admin.workorder.update')->summary('A端更新工单');
257 }); 258 });
  259 + // 售后工单改版
  260 + Route::prefix('tickets')->group(function () {
  261 + Route::get('/', [Aside\WorkOrder\AsideTicketController::class, 'index'])->name('admin.tickets.index')->summary('A端工单列表');
  262 + Route::post('/', [Aside\WorkOrder\AsideTicketController::class, 'store'])->name('admin.tickets.store')->summary('A端创建工单');
  263 + Route::get('/{id}', [Aside\WorkOrder\AsideTicketController::class, 'show'])->name('admin.tickets.show')->summary('A端工单详情');
  264 + Route::post('/{id}', [Aside\WorkOrder\AsideTicketController::class, 'update'])->name('admin.tickets.update')->summary('A端更新工单,审核,邀请同事');
  265 + Route::get('/projects/{search}', [Aside\WorkOrder\AsideTicketController::class, 'getProjects'])->name('admin.tickets.projects')->summary('A端V5V6项目列表');
  266 + Route::post('/log/{id}', [Aside\WorkOrder\AsideTicketLogController::class, 'update'])->name('admin.tickets.log.update')->summary('A端工单操作日志更新,完成工单');
  267 + });
  268 +
258 //服务器配置 269 //服务器配置
259 Route::prefix('devops')->group(function () { 270 Route::prefix('devops')->group(function () {
260 Route::any('/', [Aside\Devops\ServerConfigController::class, 'lists'])->name('admin.devops.lists'); 271 Route::any('/', [Aside\Devops\ServerConfigController::class, 'lists'])->name('admin.devops.lists');