作者 ZhengBing He

合并分支 'workorder' 到 'master'

Workorder



查看合并请求 !2365
... ... @@ -56,65 +56,99 @@ class FetchTicketProjects extends Command
*/
public function fetchV5()
{
# pm 项目经理 assm 售后服务经理
$response = Http::get('https://www.quanqiusou.cn/extend_api/webs/globalso_all.php');
if ($response->status() == 200) {
$items = $response->json();
foreach ($items as $item) {
# V5: 版本号+postid
$uuid = md5("V5{$item['postid']}");
$project = TicketProject::where('uuid', $uuid)->first();
$assm_id = Manage::where('name', $item['assm'])->value('id') ?? Manage::where('name', '张鸿飞')->value('id') ?? 0; //售后服务经理
$seom_id = Manage::where('name', $item['yhs'])->value('id') ?? Manage::where('name', '陶婵')->value('id') ?? 0; //优化师
// 如果 $item['cate'] 包含”推广“字符,则 $engineer_name = $item['assm']
/**
* 第一负责人逻即说明:
* 优化推广项目:找售后服务经理??鸿飞
* 建站类项目: 找杨长远
*/
$engineer_id = (strpos($item['cate'], '推广') !== false) ? $assm_id : Manage::where('name', '杨长远')->value('id') ?? 0;
$page = 1;
$postids = [];
$fields = [
'post_id' => $item['postid'],
'company_name' => $item['company'],
'title' => $item['title'],
'engineer_id' => $engineer_id, // 第一负责人
'assm_id' => $assm_id,
'seom_id' => $seom_id,
'website' => $item['main_url'] ?? '',
'test_website' => $item['test_url'] ?? '',
'is_del' => 0,
'plan' => $item['plan'] ?? '',
'project_cate' => 1,
];
if (!$project) {
$new = new TicketProject();
$new->uuid = $uuid;
$new->version = 5;
$new->table_id = 0;
foreach ($fields as $k => $v) {
$new->$k = $v;
}
$new->save();
} else {
$changed = false;
foreach ($fields as $k => $v) {
if ($project->$k != $v) {
$project->$k = $v;
$changed = true;
while (true) {
$response = Http::get('https://www.quanqiusou.cn/extend_api/webs/globalso_all.php?page=' . $page);
if ($response->status() == 200) {
$resp_json = $response->json();
$items = $resp_json['data'] ?? [];
if (empty($items))
{
echo now() . " | INFO | V5: not found items on page $page \n";
break;
}
foreach ($items as $item) {
# V5: 版本号+postid
$uuid = md5("V5{$item['postid']}");
$project = TicketProject::where('uuid', $uuid)->first();
// 项目状态, 根据 $item['cate'] 判断,建站中,建站客户,推广
if (strpos($item['cate'], '推广') !== false)
$status=3; // 推广
elseif ($item['cate'] == "建站客户")
$status=2; // 建站客户
elseif ($item['cate'] == "建站中")
$status=1; // 建站中
$assm_id = Manage::where('name', $item['assm'])->value('id') ?? Manage::where('name', '张鸿飞')->value('id') ?? 0; //售后服务经理
$seom_id = Manage::where('name', $item['yhs'])->value('id') ?? Manage::where('name', '陶婵')->value('id') ?? 0; //优化师
$pm_id = Manage::where('name', $item['pm'])->value('id') ?? Manage::where('name', '李洁玉')->value('id') ?? 0; // 项目经理
/**
* 第一负责人逻即说明:
* 优化推广项目:找售后服务经理??鸿飞
* 建站中:项目经理
* 建站完成:杨长远
*/
if ($status == 3)
$engineer_id = $assm_id; // 推广类项目找售后服务经理
elseif ($status == 2)
$engineer_id = Manage::where('name', '杨长远')->value('id') ?? 0; //建站完成
elseif ($status == 1)
$engineer_id = $pm_id; // 建站中找项目经理
$fields = [
'post_id' => $item['postid'],
'company_name' => $item['company'],
'title' => $item['title'] . " - V5",
'engineer_id' => $engineer_id, // 第一负责人
'assm_id' => $assm_id,
'seom_id' => $seom_id,
'website' => $item['main_url'] ?? '',
'test_website' => $item['test_url'] ?? '',
'is_del' => 0,
'plan' => $item['plan'] ?? '',
'project_cate' => 1,
'pm_id' => $pm_id,
'status' => $status, // 项目状态
'wechat_group_id' => $item['wx_id']
];
if (!$project) {
$new = new TicketProject();
$new->uuid = $uuid;
$new->version = 5;
$new->table_id = 0;
foreach ($fields as $k => $v) {
$new->$k = $v;
}
$new->save();
} else {
$changed = false;
foreach ($fields as $k => $v) {
if ($project->$k != $v) {
$project->$k = $v;
$changed = true;
}
}
if ($changed) {
$project->save();
}
}
if ($changed) {
$project->save();
}
echo now() . " | INFO | V5: {$item['postid']} {$item['company']} fetch ok \n";
}
$page++;
$postids = array_merge($postids, collect($items)->pluck('postid')->toArray());
}
$postids = collect($items)->pluck('postid')->toArray();
}
if ($postids)
{
// 软删除 gl_ticket_projects 中不存在的项目
TicketProject::where('version', 5)
->whereNotIn('post_id', $postids)
->update(['is_del' => 1]);
echo date("Y-m-d H:i:s") . " V5: fetch completed, total " . count($items) . " items\n";
}
}
... ... @@ -142,26 +176,35 @@ class FetchTicketProjects extends Command
foreach ($items as $item) {
$uuid = md5("V6{$item->id}");
$project = TicketProject::where('uuid', $uuid)->first();
// 项目状态
if ($item->type == Project::TYPE_ONE)
$status = 1; // 建站中
elseif ($item->type == Project::TYPE_THREE)
$status = 2; // 建站完成
else
$status = 3; // 推广找售后服务经理
// 售后服务经理
$assm_id = collect([
ManageHr::find($item->deploy_optimize->manager_mid)->manage_id ?? 0,
ManageHr::find($item->deploy_optimize->tech_leader)->manage_id ?? 0,
8, //张鸿飞
])->first(fn($v) => $v !== null && $v !== 0, 0);
// 优化师
$optimist_mid = ManageHr::find($item->deploy_optimize->optimist_mid) ? ManageHr::find($item->deploy_optimize->optimist_mid)->manage_id : 0;
$seom_id = $optimist_mid ? $optimist_mid : $assm_id;
/**
* 第一负责人逻辑
* 建站类项目:找杨长远
* 推广类:找售后
*/
if ($item->type == Project::TYPE_THREE) {
$engineer_id = Manage::where('name', '杨长远')->value('id') ?? 0; // 建站类项目找杨长远
}else {
// 其他找售后服务经理
$engineer_id = $assm_id;
}
$seom_id = ManageHr::find($item->deploy_optimize->optimist_mid) ? ManageHr::find($item->deploy_optimize->optimist_mid)->manage_id : 0;
// 项目经理
$pm_id = ManageHr::find($item->deploy_build->manager_mid)->manage_id ?? ManageHr::where('name', '李洁玉')->value('manage_id') ?? 0;
// 第一负责人
if ($status == 1)
$engineer_id = $pm_id; // 建站中找项目经理
elseif ($status == 2)
$engineer_id = Manage::where('name', '杨长远')->value('id') ?? 0; // 建站完成找杨长远
else
$engineer_id = $assm_id; // 推广找售后服务经理
$is_del = (
$item->extend_type == 5
|| $item->type == 8
... ... @@ -171,7 +214,7 @@ class FetchTicketProjects extends Command
$fields = [
'company_name' => $item->company,
'title' => $item->title,
'title' => $item->title . " - V6",
'assm_id' => $assm_id,
'seom_id' => $seom_id,
'engineer_id' => $engineer_id,
... ... @@ -181,10 +224,12 @@ class FetchTicketProjects extends Command
'version' => empty($item->version) ? 7 : $item->version, // 版本号
'plan' => $item->planMap()[$item->deploy_build->plan] ?? '',
'project_cate' => 2,
'wechat_group_id' => ProjectAssociation::where('project_id', $project->table_id)
'wechat_group_id' => ProjectAssociation::where('project_id', $item->id)
->where('status', ProjectAssociation::STATUS_NORMAL)
->where('binding_app', ProjectAssociation::ENTERPRISE_WECHAT)
->value('friend_id')
->value('friend_id'),
'pm_id' => $pm_id,
'status' => $status, // 项目状态
];
if (!$project) {
$project = new TicketProject();
... ...
... ... @@ -8,10 +8,8 @@ use App\Http\Requests\Api\WorkOrder\TicketStoreRequest;
use App\Models\WorkOrder\TicketLog;
use App\Models\WorkOrder\TicketProject;
use App\Models\WorkOrder\Tickets;
use Darabonba\GatewaySpi\Models\InterceptorContext\response;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
class TicketController extends BaseController
{
... ... @@ -29,8 +27,11 @@ class TicketController extends BaseController
$size = (int)$request->input('size', 10);
$tickets = Tickets::with([
'project:*',
'logs.engineer:id,name',
'project.pm',
'project.assm',
'project.seom',
'project.first_engineer',
'logs.engineer',
])
->where('project_id', $project->id)
// ->where('submit_side', 2)
... ... @@ -85,9 +86,7 @@ class TicketController extends BaseController
$ticket->submit_side = 2; // 2 for B-side submission
$ticket->submit_username = $request->input('submit_username');
$ticket->save();
$log = new TicketLog();
$log->engineer_id = $project->engineer_id; // 默认第一负责人
$ticket->logs()->save($log);
$ticket->saveEngineers([$project->engineer_id]);
$project->pushWechatGroupMsg("客户新增了工单(ID:{$ticket->id}),请及时处理!");
return $ticket;
});
... ... @@ -143,10 +142,10 @@ class TicketController extends BaseController
public function projectInfo($project_id)
{
$project = TicketProject::with([
'projectV6:id,company',
'assm:id,name',
'seom:id,name',
'first_engineer:id,name',
'pm',
'assm',
'seom',
'first_engineer',
])
->where('uuid', $project_id)->first();
if (!$project) return $this->response('未找到项目', 404);
... ...
... ... @@ -8,7 +8,7 @@ use App\Http\Requests\Aside\WorkOrder\AsideTicketStoreRequest;
use App\Http\Requests\Aside\WorkOrder\AsideTicketListRequest;
use App\Http\Requests\Aside\WorkOrder\AsideTicketUpdateRequest;
use App\Http\Requests\Aside\WorkOrder\TicketProjectListRequest;
use App\Models\Workchat\MessagePush;
use App\Models\Manage\ManageHr;
use App\Models\WorkOrder\TicketLog;
use App\Models\WorkOrder\TicketProject;
use App\Models\WorkOrder\Tickets;
... ... @@ -26,8 +26,8 @@ class AsideTicketController extends BaseController
{
$validated = $request->validated();
$lists = Tickets::with([
'logs.engineer:id,name',
'project',
'logs.engineer',
'project.pm',
])
->when(!empty($validated['engineer_id']), function ($query) use ($validated) {
// 查 gl_tickets 表 submit_user_id 或 gl_ticket_logs 表 engineer_id
... ... @@ -51,6 +51,10 @@ class AsideTicketController extends BaseController
$status = $request->input('status');
return $query->where('status', $status);
})
->when($request->input('star') !== null, function ($query) use ($request) {
$star = $request->input('star');
return $query->where('star', $star);
})
->when($request->input('search'), function ($query) use ($request) {
// search 查 gl_tickets.title 或 gl_ticket_projects.title 或 gl_ticket_projects.company_name
$search = $request->input('search');
... ... @@ -92,7 +96,31 @@ class AsideTicketController extends BaseController
public function projectList(TicketProjectListRequest $request)
{
$validated = $request->validated();
$dept_id = ManageHr::where('manage_id', $this->manage['id'])
->value('dept_id');
$lists = TicketProject::where('is_del', 0)
->when(($this->manage['role'] != 1 && $dept_id != 5), function ($query) use ($dept_id) {
/**
* 超管看所有项目 $this->manage['role']=1
* 全球搜: 技术部ID 1、售后部ID 2
* 超迹AI: AICC技术部 ID 4
* 域途:域途运营部 ID 17
*/
if (in_array($dept_id, [1, 2])) {
// V5 V6
return $query->whereIn('project_cate', [1, 2]);
}elseif ($dept_id == 4) {
// 超迹AI
return $query->where('project_cate', 3);
}elseif ($dept_id == 17){
// 域途
return $query->where('project_cate', 4);
}else{
// 其他部门,不允许看数据
return $query->where('id', 0); // 返回空结果
}
})
->when(!empty($validated['search']), function ($query) use ($validated) {
// 查找项目名称或公司名称
$search = $validated['search'];
... ... @@ -141,13 +169,12 @@ class AsideTicketController extends BaseController
$ticket->submit_side = 1; // 1 for A-side submission
$ticket->submit_user_id = $this->manage['id'];
$ticket->submit_username = $this->manage['name'];
$ticket->star = $request->input('star', 3);
$ticket->plan_end_at = $request->input('plan_end_at', null);
$ticket->save();
// A 端提工单,都是针对客户提的需求等开发任务;比如翻译,修改页面等。。。
foreach ($request->input('engineer_ids', []) as $engineer_id) {
$log = new TicketLog();
$log->engineer_id = $engineer_id;
$ticket->logs()->save($log);
}
// 分配工单参与人
$ticket->saveEngineers($request->input('engineer_ids', []));
$project->pushWechatGroupMsg("创贸({$ticket->submit_username})新增了工单(ID:{$ticket->id}),请及时处理!");
return $ticket;
});
... ... @@ -186,19 +213,11 @@ class AsideTicketController extends BaseController
// 开始修改
$result = DB::transaction(function () use ($request, $ticket) {
if ($request->input('engineer_ids'))
{
// 有邀请工程师协同处理
foreach ($request->input('engineer_ids') as $engineer_id)
{
try {
// 利用唯一索引去重
$new_log = new TicketLog();
$new_log->engineer_id = $engineer_id;
$ticket->logs()->save($new_log);
}catch (\Exception $exception){}
}
}
$ticket->saveEngineers($request->input('engineer_ids'));
// 其他字段有提交数据才修改,比如star plan_end_at
$ticket->star = $request->input('star', $ticket->star);
$ticket->plan_end_at = $request->input('plan_end_at', $ticket->plan_end_at);
$ticket->reply = $request->input('reply', null);
$ticket->status = $request->input('status', $ticket->status);
if ($ticket->status == Tickets::STATUS_COMPLETED)
... ...
... ... @@ -78,6 +78,7 @@ class AsideTicketLogController extends BaseController
// 是否有未完成的子任务
$pending = $ticket->logs()
->where('status', '<', TicketLog::STATUS_COMPLETED)
->where('is_engineer', 1)
->count();
if ($pending)
{
... ... @@ -89,6 +90,7 @@ class AsideTicketLogController extends BaseController
$ticket->end_at = now();
$project = $ticket->project;
$project->pushWechatGroupMsg("工单(ID:{$ticket->id})已全部完成,请访问查看详情!");
$ticket->pushDing('finish');
}
$ticket->save();
return $log;
... ...
... ... @@ -26,6 +26,7 @@ class AsideTicketListRequest extends FormRequest
return [
'project_id' => 'nullable|string',
'status' => 'nullable|in:0,1,2,3|integer',
'star' => 'nullable|in:1,2,3|integer',
'search' => 'nullable|string', // 搜索关键词
'engineer_id' => 'nullable|integer', // 工程师ID
'page' => 'nullable|integer',
... ...
... ... @@ -29,6 +29,8 @@ class AsideTicketStoreRequest extends FormRequest
'content' => 'required|string',
'files' => 'nullable|array',
'engineer_ids' => 'array',
'star' => 'nullable|in:1,2,3|integer',
'plan_end_at' => 'nullable|date',
];
}
}
... ...
... ... @@ -27,6 +27,8 @@ class AsideTicketUpdateRequest extends FormRequest
'status' => 'nullable|in:0,1,2,3|integer',
'reply' => 'nullable|string',
'engineer_ids' => 'nullable|array',
'star' => 'nullable|in:1,2,3|integer',
'plan_end_at' => 'nullable|date',
];
}
}
... ...
<?php
namespace App\Models\WorkOrder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TicketDing extends Model
{
use HasFactory;
protected $table = 'gl_ticket_dings';
}
... ...
... ... @@ -4,6 +4,7 @@ namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
use App\Models\Manage\ManageHr;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class TicketLog extends Base
... ... @@ -19,8 +20,8 @@ class TicketLog extends Base
public function engineer()
{
return $this->belongsTo(Manage::class, 'engineer_id', 'id')
->select(['id', 'name', 'mobile']);
return $this->belongsTo(ManageHr::class, 'engineer_id', 'manage_id')
->select(['manage_id', 'name', 'nickname']);
}
public function ticket()
... ...
... ... @@ -4,6 +4,7 @@ namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
use App\Models\Manage\ManageHr;
use App\Models\Project\Project;
use App\Models\ProjectAssociation\ProjectAssociation;
use App\Models\Workchat\MessagePush;
... ... @@ -21,18 +22,24 @@ class TicketProject extends Base
->where('version', 6);
}
// 项目经理
public function pm()
{
return $this->hasOne(ManageHr::class, 'manage_id', 'pm_id')->select(['manage_id', 'name', 'nickname']);
}
//售后服务经理
public function assm()
{
return $this->hasOne(Manage::class, 'id', 'assm_id')
->select(['id', 'name']);
return $this->hasOne(ManageHr::class, 'manage_id', 'assm_id')
->select(['manage_id', 'name', 'nickname']);
}
// 优化师
public function seom()
{
return $this->hasOne(Manage::class, 'id', 'seom_id')
->select(['id', 'name']);
return $this->hasOne(ManageHr::class, 'manage_id', 'seom_id')
->select(['manage_id', 'name', 'nickname']);
}
/**
... ... @@ -40,8 +47,8 @@ class TicketProject extends Base
*/
public function first_engineer()
{
return $this->hasOne(Manage::class, 'id', 'engineer_id')
->select(['id', 'name']);
return $this->hasOne(ManageHr::class, 'manage_id', 'engineer_id')
->select(['manage_id', 'name', 'nickname']);
}
/**
... ...
... ... @@ -3,7 +3,9 @@
namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\ManageHr;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Log;
class Tickets extends Base
{
... ... @@ -32,4 +34,90 @@ class Tickets extends Base
{
return $this->belongsTo(TicketProject::class, 'project_id', 'id');
}
/**
* 当前工单,保存参与的人员到 gl_ticket_logs 表
* 逻辑说明:
* 1. 如果当前项目是超迹,要把徐莹和第一负责人加进去,为参与人
* 2. 若是域途项目,把黄小玉和第一负责人加进去,为参与人
*/
public function saveEngineers($engineer_ids = [])
{
$canyu = [
$this->project->engineer_id, // 第一负责人
];
if ($this->project->project_cate == 3)
$canyu[] = 20; // 徐莹
elseif ($this->project->project_cate == 4)
$canyu[] = 85; // 黄小玉
$all_engineer_ids = array_unique(array_merge($canyu, $engineer_ids));
foreach ($all_engineer_ids as $engineer_id)
{
try {
$log = $this->logs()->where('engineer_id', $engineer_id)->first();
if ($log && $log->is_engineer != in_array($engineer_id, $engineer_ids))
{
$log->is_engineer = in_array($engineer_id, $engineer_ids);
$log->save();
}else
{
// 利用唯一索引去重
$log = new TicketLog();
$log->engineer_id = $engineer_id;
$log->is_engineer = in_array($engineer_id, $engineer_ids);
$this->logs()->save($log);
}
}catch (\Exception $exception){
Log::error(" | ERRPR | Ticket saveEngineers {$exception->getMessage()} \n {$exception->getTraceAsString()}");
}
}
// 删除没有参与当前工单的人员(若之前已有)
$this->logs()->whereNotIn('engineer_id', $all_engineer_ids)->delete();
}
/**
* TODO 这个是一个补充功能
* 那些情况需要推送钉钉内部通知?
* 1. 客户提交了工单
* - 通知第一负责人 (gl_ticket_logs 表做了标记)
* 2. 客户补充了工单
* - 通知最近的聊天技术(gl_ticket_chats 表做了标记)
* 3. 技术完成了工单
* - 通知第一负责人 (暂无,所以这里做补充,如果以后要把上面的逻辑也加进来也可以,只是我为了偷懒,暂时只考虑完成工单请款)
*/
public function pushDing($type = 'finish')
{
try {
$ding = new TicketDing();
$ding->msgKey = 'sampleLink';
$ding->table_name = 'gl_tickets';
$ding->table_id = $this->id;
if ($type == 'finish')
{
// 所以技术完成了工单,通知第一负责人 和 工单的提交人
$ding->userIds = [$this->project->engineer_id];
if ($this->submit_side == 1) {
// A 端提的,还要通知提交人
$ding->userIds[] = $this->submit_user_id;
// 去重
$ding->userIds = array_unique($ding->userIds);
}
$ding->msgParam = json_encode([
'text' => "工单(ID: {$this->id}),已经全部完成,请访问查看详情!",
'title' => 'AI协同工单 - ' . $this->project->title,
'picUrl' => 'https://hub.globalso.com/logocm.png',
'messageUrl' => 'https://oa.quanqiusou.cn/afterorder?project_id=' . $this->project->uuid,
], JSON_UNESCAPED_UNICODE);
$ding->save();
}
}catch (\Exception $exception){
Log::error(" | ERRPR | Ticket {$exception->getMessage()} \n {$exception->getTraceAsString()}");
}
}
}
... ...
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketDingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('gl_ticket_dings', function (Blueprint $table) {
$table->id();
$table->json('userIds')->comment('接收人ID列表');
$table->string('msgKey')->default('sampleMarkdown')->comment('消息模板标识');
$table->json('msgParam')->comment('消息内容参数,JSON格式');
$table->smallInteger('status')->default(0)->index()->comment('发送状态:0-未发送,1-已发送,2-发送失败');
$table->string('errorMsg')->nullable()->comment('错误信息,发送失败时记录');
$table->string('table_name', 50)->nullable()->comment('关联表名称');
$table->string('table_id', 50)->nullable()->comment('关联表ID');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('gl_ticket_dings');
}
}
... ...