appendattrs['file_num'])) { $this->appendattrs['file_num'] = $this->parent_id > 0 ? 0 : ProjectTaskFile::whereTaskId($this->id)->count(); } return $this->appendattrs['file_num']; } /** * 消息数量 * @return int */ public function getMsgNumAttribute() { if (!isset($this->appendattrs['msg_num'])) { $this->appendattrs['msg_num'] = $this->dialog_id ? WebSocketDialogMsg::whereDialogId($this->dialog_id)->count() : 0; } return $this->appendattrs['msg_num']; } /** * 生成子任务数据 */ private function generateSubTaskData() { if ($this->parent_id > 0) { $this->appendattrs['sub_num'] = 0; $this->appendattrs['sub_complete'] = 0; $this->appendattrs['percent'] = $this->complete_at ? 100 : 0; return; } if (!isset($this->appendattrs['sub_num'])) { $builder = self::whereParentId($this->id)->whereNull('archived_at'); $this->appendattrs['sub_num'] = $builder->count(); $this->appendattrs['sub_complete'] = $builder->whereNotNull('complete_at')->count(); // if ($this->complete_at) { $this->appendattrs['percent'] = 100; } elseif ($this->appendattrs['sub_complete'] == 0) { $this->appendattrs['percent'] = 0; } else { $this->appendattrs['percent'] = intval($this->appendattrs['sub_complete'] / $this->appendattrs['sub_num'] * 100); } } } /** * 子任务数量 * @return int */ public function getSubNumAttribute() { $this->generateSubTaskData(); return $this->appendattrs['sub_num']; } /** * 子任务已完成数量 * @return int */ public function getSubCompleteAttribute() { $this->generateSubTaskData(); return $this->appendattrs['sub_complete']; } /** * 进度(0-100) * @return int */ public function getPercentAttribute() { $this->generateSubTaskData(); return $this->appendattrs['percent']; } /** * 是否今日任务 * @return bool */ public function getTodayAttribute() { if ($this->end_at) { $end_at = Carbon::parse($this->end_at); if ($end_at->toDateString() == Carbon::now()->toDateString()) { return true; } } return false; } /** * 是否过期 * @return bool */ public function getOverdueAttribute() { if ($this->end_at) { if (Carbon::parse($this->end_at)->lt(Carbon::now())) { return true; } } return false; } /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function project(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(Project::class, 'id', 'project_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function projectColumn(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(ProjectColumn::class, 'id', 'column_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function content(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(ProjectTaskContent::class, 'task_id', 'id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function taskFile(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(ProjectTaskFile::class, 'task_id', 'id')->orderBy('id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function taskUser(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(ProjectTaskUser::class, 'task_id', 'id')->orderByDesc('owner')->orderByDesc('id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function taskTag(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(ProjectTaskTag::class, 'task_id', 'id')->orderBy('id'); } /** * 查询所有任务(与正常查询多返回owner字段) * @param self $query * @param null $userid * @return self */ public function scopeAllData($query, $userid = null) { $userid = $userid ?: User::userid(); $query ->select([ 'project_tasks.*', 'project_task_users.owner' ]) ->leftJoin('project_task_users', function ($leftJoin) use ($userid) { $leftJoin ->on('project_task_users.userid', '=', DB::raw($userid)) ->on('project_tasks.id', '=', 'project_task_users.task_id'); }); return $query; } /** * 查询自己负责或参与的任务 * @param self $query * @param null $userid * @param null $owner * @return self */ public function scopeAuthData($query, $userid = null, $owner = null) { $userid = $userid ?: User::userid(); $query ->select([ 'project_tasks.*', 'project_task_users.owner' ]) ->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id') ->where('project_task_users.userid', $userid); if ($owner !== null) { $query->where('project_task_users.owner', $owner); } return $query; } /** * 指定范围内的任务 * @param $query * @param $start * @param $end * @return mixed */ public function scopeBetweenTime($query, $start, $end) { $query->where(function ($q1) use ($start, $end) { $q1->where(function ($q2) use ($start) { $q2->where('project_tasks.start_at', '<=', $start)->where('project_tasks.end_at', '>=', $start); })->orWhere(function ($q2) use ($end) { $q2->where('project_tasks.start_at', '<=', $end)->where('project_tasks.end_at', '>=', $end); })->orWhere(function ($q2) use ($start, $end) { $q2->where('project_tasks.start_at', '>', $start)->where('project_tasks.end_at', '<', $end); }); }); return $query; } /** * 添加任务 * @param $data * @return self */ public static function addTask($data) { $parent_id = intval($data['parent_id']); $project_id = intval($data['project_id']); $column_id = intval($data['column_id']); $name = $data['name']; $content = $data['content']; $times = $data['times']; $owner = $data['owner']; $add_assist = intval($data['add_assist']); $subtasks = $data['subtasks']; $p_level = intval($data['p_level']); $p_name = $data['p_name']; $p_color = $data['p_color']; $top = intval($data['top']); $userid = User::userid(); // if (ProjectTask::whereProjectId($project_id) ->whereNull('project_tasks.complete_at') ->whereNull('project_tasks.archived_at') ->count() > 2000) { throw new ApiException('项目内未完成任务最多不能超过2000个'); } if (ProjectTask::whereColumnId($column_id) ->whereNull('project_tasks.complete_at') ->whereNull('project_tasks.archived_at') ->count() > 500) { throw new ApiException('单个列表未完成任务最多不能超过500个'); } if ($parent_id > 0 && ProjectTask::whereParentId($parent_id) ->whereNull('project_tasks.complete_at') ->whereNull('project_tasks.archived_at') ->count() > 50) { throw new ApiException('每个任务的子任务最多不能超过50个'); } // $retPre = $parent_id ? '子任务' : '任务'; $task = self::createInstance([ 'parent_id' => $parent_id, 'project_id' => $project_id, 'column_id' => $column_id, 'p_level' => $p_level, 'p_name' => $p_name, 'p_color' => $p_color, ]); if ($content) { $task->desc = Base::getHtml($content, 100); } // 标题 if (empty($name)) { throw new ApiException($retPre . '描述不能为空'); } elseif (mb_strlen($name) > 255) { throw new ApiException($retPre . '描述最多只能设置255个字'); } $task->name = $name; // 时间 if ($times) { list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []); if (Base::isDate($start) && Base::isDate($end) && $start != $end) { $task->start_at = Carbon::parse($start); $task->end_at = Carbon::parse($end); } } // 负责人 $owner = is_array($owner) ? $owner : [$owner]; $tmpArray = []; foreach ($owner as $uid) { if (intval($uid) == 0) continue; if (!ProjectUser::whereProjectId($project_id)->whereUserid($uid)->exists()) { throw new ApiException($retPre . '负责人填写错误'); } if (ProjectTask::authData($uid) ->whereNull('project_tasks.complete_at') ->whereNull('project_tasks.archived_at') ->count() > 500) { throw new ApiException(User::userid2nickname($uid) . '负责或参与的未完成任务最多不能超过500个'); } $tmpArray[] = $uid; } $owner = $tmpArray; // 协助人员 $assist = []; if (!in_array($userid, $owner) && $add_assist) { $assist = [$userid]; } // 创建人 $task->userid = $userid; // 排序位置 if ($top) { $task->sort = intval(self::whereColumnId($task->column_id)->orderBy('sort')->value('sort')) - 1; } else { $task->sort = intval(self::whereColumnId($task->column_id)->orderByDesc('sort')->value('sort')) + 1; } // 工作流 $projectFlow = ProjectFlow::whereProjectId($project_id)->orderByDesc('id')->first(); if ($projectFlow) { $projectFlowItem = ProjectFlowItem::whereFlowId($projectFlow->id)->orderBy('sort')->get(); // 赋一个开始状态 foreach ($projectFlowItem as $item) { if ($item->status == 'start') { $task->flow_item_id = $item->id; $task->flow_item_name = $item->status . "|" . $item->name; $owner = array_merge($owner, $item->userids); break; } } } // return AbstractModel::transaction(function() use ($assist, $times, $subtasks, $content, $owner, $task) { $task->save(); $owner = array_values(array_unique($owner)); foreach ($owner as $uid) { ProjectTaskUser::createInstance([ 'project_id' => $task->project_id, 'task_id' => $task->id, 'task_pid' => $task->parent_id ?: $task->id, 'userid' => $uid, 'owner' => 1, ])->save(); } $assist = array_values(array_unique(array_diff($assist, $owner))); foreach ($assist as $uid) { ProjectTaskUser::createInstance([ 'project_id' => $task->project_id, 'task_id' => $task->id, 'task_pid' => $task->parent_id ?: $task->id, 'userid' => $uid, 'owner' => 0, ])->save(); } if ($content) { ProjectTaskContent::createInstance([ 'project_id' => $task->project_id, 'task_id' => $task->id, 'content' => $content, ])->save(); } if ($task->parent_id == 0 && $subtasks && is_array($subtasks)) { foreach ($subtasks as $subtask) { list($start, $end) = is_string($subtask['times']) ? explode(",", $subtask['times']) : (is_array($subtask['times']) ? $subtask['times'] : []); if (Base::isDate($start) && Base::isDate($end) && $start != $end) { if (Carbon::parse($start)->lt($task->start_at)) { throw new ApiException('子任务开始时间不能小于主任务开始时间'); } if (Carbon::parse($end)->gt($task->end_at)) { throw new ApiException('子任务结束时间不能大于主任务结束时间'); } } else { $subtask['times'] = $times; } $subtask['parent_id'] = $task->id; $subtask['project_id'] = $task->project_id; $subtask['column_id'] = $task->column_id; self::addTask($subtask); } } $task->addLog("创建{任务}"); return $task; }); } /** * 修改任务 * @param $data * @param array $updateMarking 更新的标记 * - is_update_project 是否更新项目数据(项目统计) * - is_update_content 是否更新任务详情 * - is_update_maintask 是否更新主任务 * - is_update_subtask 是否更新子任务 * @return bool */ public function updateTask($data, &$updateMarking = []) { AbstractModel::transaction(function () use ($data, &$updateMarking) { // 判断版本 Base::checkClientVersion('0.6.0'); // 主任务 $mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null; // 工作流 if (Arr::exists($data, 'flow_item_id')) { $isProjectOwner = $this->useridInTheProject(User::userid()) === 2; if (!$isProjectOwner && !$this->isOwner()) { throw new ApiException('仅限项目或任务负责人修改任务状态'); } if ($this->flow_item_id == $data['flow_item_id']) { throw new ApiException('任务状态未发生改变'); } $flowData = [ 'flow_item_id' => $this->flow_item_id, 'flow_item_name' => $this->flow_item_name, ]; $currentFlowItem = null; $newFlowItem = ProjectFlowItem::whereProjectId($this->project_id)->find(intval($data['flow_item_id'])); if (empty($newFlowItem) || empty($newFlowItem->projectFlow)) { throw new ApiException('任务状态不存在'); } if ($this->flow_item_id) { // 判断符合流转 $currentFlowItem = ProjectFlowItem::find($this->flow_item_id); if ($currentFlowItem) { if (!in_array($newFlowItem->id, $currentFlowItem->turns)) { throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]"); } if ($currentFlowItem->userlimit) { if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) { throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改"); } } } } if ($newFlowItem->status == 'end') { // 判断自动完成 if (!$this->complete_at) { $flowData['complete_at'] = $this->complete_at; $data['complete_at'] = date("Y-m-d H:i"); } } else { // 判断自动打开 if ($this->complete_at) { $flowData['complete_at'] = $this->complete_at; $data['complete_at'] = false; } } if ($newFlowItem->userids) { // 判断自动添加负责人 $flowData['owner'] = $data['owner'] = $this->taskUser->where('owner', 1)->pluck('userid')->toArray(); if (in_array($newFlowItem->usertype, ["replace", "merge"])) { // 流转模式、剔除模式 if ($this->parent_id === 0) { $flowData['assist'] = $data['assist'] = $this->taskUser->where('owner', 0)->pluck('userid')->toArray(); $data['assist'] = array_merge($data['assist'], $data['owner']); } $data['owner'] = $newFlowItem->userids; // 判断剔除模式:保留操作状态的人员 if ($newFlowItem->usertype == "merge") { $data['owner'][] = User::userid(); } } else { // 添加模式 $data['owner'] = array_merge($data['owner'], $newFlowItem->userids); } $data['owner'] = array_values(array_unique($data['owner'])); if (isset($data['assist'])) { $data['assist'] = array_values(array_unique(array_diff($data['assist'], $data['owner']))); } } $this->flow_item_id = $newFlowItem->id; $this->flow_item_name = $newFlowItem->status . "|" . $newFlowItem->name; $this->addLog("修改{任务}状态", [ 'flow' => $flowData, 'change' => [$currentFlowItem?->name, $newFlowItem->name] ]); } // 状态 if (Arr::exists($data, 'complete_at')) { // 子任务:主任务已完成时无法修改 if ($mainTask?->complete_at) { throw new ApiException('主任务已完成,无法修改子任务状态'); } if (Base::isDate($data['complete_at'])) { // 标记已完成 if ($this->complete_at) { throw new ApiException('任务已完成'); } $this->completeTask(Carbon::now()); } else { // 标记未完成 if (!$this->complete_at) { throw new ApiException('未完成任务'); } $this->completeTask(null); } $updateMarking['is_update_project'] = true; } // 标题 if (Arr::exists($data, 'name') && $this->name != $data['name']) { if (empty($data['name'])) { throw new ApiException('任务描述不能为空'); } elseif (mb_strlen($data['name']) > 255) { throw new ApiException('任务描述最多只能设置255个字'); } $this->addLog("修改{任务}标题", [ 'change' => [$this->name, $data['name']] ]); $this->name = $data['name']; } // 负责人 if (Arr::exists($data, 'owner')) { $count = $this->taskUser->where('owner', 1)->count(); $array = []; $owner = is_array($data['owner']) ? $data['owner'] : [$data['owner']]; if (count($owner) > 10) { throw new ApiException('任务负责人最多不能超过10个'); } foreach ($owner as $uid) { if (intval($uid) == 0) continue; if (!$this->project->useridInTheProject($uid)) continue; // ProjectTaskUser::updateInsert([ 'task_id' => $this->id, 'userid' => $uid, ], [ 'project_id' => $this->project_id, 'task_pid' => $this->parent_id ?: $this->id, 'owner' => 1, ]); $array[] = $uid; if ($this->parent_id) { break; // 子任务只能是一个负责人 } } if ($array) { if ($count == 0 && count($array) == 1 && $array[0] == User::userid()) { $this->addLog("认领{任务}"); } else { $this->addLog("修改{任务}负责人", ['userid' => $array]); } } $rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->whereNotIn('userid', $array)->get(); if ($rows->isNotEmpty()) { $this->addLog("删除{任务}负责人", ['userid' => $rows->implode('userid', ',')]); foreach ($rows as $row) { $row->delete(); } } $updateMarking['is_update_project'] = true; $this->syncDialogUser(); } // 计划时间(原则:子任务时间在主任务时间内) if (Arr::exists($data, 'times')) { $oldAt = [Carbon::parse($this->start_at), Carbon::parse($this->end_at)]; $oldStringAt = $this->start_at ? ($oldAt[0]->toDateTimeString() . '~' . $oldAt[1]->toDateTimeString()) : ''; $this->start_at = null; $this->end_at = null; $times = $data['times']; list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []); if (Base::isDate($start) && Base::isDate($end) && $start != $end) { $start_at = Carbon::parse($start); $end_at = Carbon::parse($end); if ($this->parent_id > 0) { // 判断同步主任务时间(子任务时间 超出 主任务) if ($mainTask) { $isUp = false; if ($start_at->lt(Carbon::parse($mainTask->start_at))) { $mainTask->start_at = $start_at; $isUp = true; } if ($end_at->gt(Carbon::parse($mainTask->end_at))) { $mainTask->end_at = $end_at; $isUp = true; } if ($isUp) { $updateMarking['is_update_maintask'] = true; $mainTask->addLog("同步修改{任务}时间"); $mainTask->save(); } } } $this->start_at = $start_at; $this->end_at = $end_at; } else { if ($this->parent_id > 0) { // 清空子任务时间(子任务时间等于主任务时间) $this->start_at = $mainTask->start_at; $this->end_at = $mainTask->end_at; } } if ($this->parent_id == 0) { // 判断同步子任务时间(主任务时间 不在 子任务时间 之外) self::whereParentId($this->id)->chunk(100, function($list) use ($oldAt, &$updateMarking) { /** @var self $subTask */ foreach ($list as $subTask) { $start_at = Carbon::parse($subTask->start_at); $end_at = Carbon::parse($subTask->end_at); $isUp = false; if (empty($subTask->start_at) || $start_at->eq($oldAt[0]) || $start_at->lt(Carbon::parse($this->start_at))) { $subTask->start_at = $this->start_at; $isUp = true; } if (empty($subTask->end_at) || $end_at->eq($oldAt[1]) || $end_at->gt(Carbon::parse($this->end_at))) { $subTask->end_at = $this->end_at; $isUp = true; } if ($subTask->start_at && Carbon::parse($subTask->start_at)->gt($subTask->end_at)) { $subTask->start_at = $this->start_at; $isUp = true; } if ($isUp) { $updateMarking['is_update_subtask'] = true; $subTask->addLog("同步修改{任务}时间"); $subTask->save(); } } }); } $newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : ''; $this->addLog("修改{任务}时间", [ 'change' => [$oldStringAt, $newStringAt] ]); } // 以下紧顶级任务可修改 if ($this->parent_id === 0) { // 协助人员 if (Arr::exists($data, 'assist')) { $array = []; $assist = is_array($data['assist']) ? $data['assist'] : [$data['assist']]; if (count($assist) > 10) { throw new ApiException('任务协助人员最多不能超过10个'); } foreach ($assist as $uid) { if (intval($uid) == 0) continue; if (!$this->project->useridInTheProject($uid)) continue; // ProjectTaskUser::updateInsert([ 'task_id' => $this->id, 'userid' => $uid, ], [ 'project_id' => $this->project_id, 'task_pid' => $this->id, 'owner' => 0, ]); $array[] = $uid; } if ($array) { $this->addLog("修改{任务}协助人员", ['userid' => $array]); } $rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(0)->whereNotIn('userid', $array)->get(); if ($rows->isNotEmpty()) { $this->addLog("删除{任务}协助人员", ['userid' => $rows->implode('userid', ',')]); foreach ($rows as $row) { $row->delete(); } } $this->syncDialogUser(); } // 背景色 if (Arr::exists($data, 'color') && $this->color != $data['color']) { $this->addLog("修改{任务}背景色", [ 'change' => [$this->color, $data['color']] ]); $this->color = $data['color']; } // 列表 if (Arr::exists($data, 'column_id')) { $oldName = ProjectColumn::whereProjectId($this->project_id)->whereId($this->column_id)->value('name'); $column = ProjectColumn::whereProjectId($this->project_id)->whereId($data['column_id'])->first(); if (empty($column)) { throw new ApiException('请选择正确的列表'); } $this->addLog("修改{任务}列表", [ 'change' => [$oldName, $column->name] ]); $this->column_id = $column->id; } // 内容 if (Arr::exists($data, 'content')) { ProjectTaskContent::updateInsert([ 'project_id' => $this->project_id, 'task_id' => $this->id, ], [ 'content' => $data['content'], ]); $this->desc = Base::getHtml($data['content'], 100); $this->addLog("修改{任务}详细描述"); $updateMarking['is_update_content'] = true; } // 优先级 $p = false; $oldPName = $this->p_name; if (Arr::exists($data, 'p_level') && $this->p_level != $data['p_level']) { $this->p_level = intval($data['p_level']); $p = true; } if (Arr::exists($data, 'p_name') && $this->p_name != $data['p_name']) { $this->p_name = trim($data['p_name']); $p = true; } if (Arr::exists($data, 'p_color') && $this->p_color != $data['p_color']) { $this->p_color = trim($data['p_color']); $p = true; } if ($p) { $this->addLog("修改{任务}优先级", [ 'change' => [$oldPName, $this->p_name] ]); } } $this->save(); if ($this->start_at instanceof \DateTimeInterface) $this->start_at = $this->start_at->format('Y-m-d H:i:s'); if ($this->end_at instanceof \DateTimeInterface) $this->end_at = $this->end_at->format('Y-m-d H:i:s'); }); return true; } /** * 同步项目成员至聊天室 */ public function syncDialogUser() { if ($this->parent_id > 0) { $task = self::find($this->parent_id); $task?->syncDialogUser(); return; } if (empty($this->dialog_id)) { return; } AbstractModel::transaction(function() { $userids = $this->relationUserids(); foreach ($userids as $userid) { WebSocketDialogUser::updateInsert([ 'dialog_id' => $this->dialog_id, 'userid' => $userid, ]); } WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->delete(); }); } /** * 获取任务所有人员(负责人、协助人员、子任务负责人) * @return array */ public function relationUserids() { $userids = ProjectTaskUser::whereTaskId($this->id)->orderByDesc('owner')->orderByDesc('id')->pluck('userid')->toArray(); $items = ProjectTask::with(['taskUser'])->where('parent_id', $this->id)->whereNull('archived_at')->get(); foreach ($items as $item) { $userids = array_merge($userids, $item->taskUser->pluck('userid')->toArray()); } return array_values(array_filter(array_unique($userids))); } /** * 会员id是否在项目里 * @param int $userid * @return int 0:不存在、1存在、2存在且是管理员 */ public function useridInTheProject($userid) { $user = ProjectUser::whereProjectId($this->project_id)->whereUserid(intval($userid))->first(); if (empty($user)) { return 0; } return $user->owner ? 2 : 1; } /** * 会员id是否在任务里 * @param int $userid * @return int 0:不存在、1存在、2存在且是管理员 */ public function useridInTheTask($userid) { $user = ProjectTaskUser::whereTaskId($this->id)->whereUserid(intval($userid))->first(); if (empty($user)) { return 0; } return $user->owner ? 2 : 1; } /** * 权限版本 * @param int $level 1-负责人,2-协助人/负责人,3-创建人/协助人/负责人 * @return bool */ public function permission($level = 1) { if ($level >= 3 && $this->isCreater()) { return true; } if ($level >= 2 && $this->isAssister()) { return true; } return $this->isOwner(); } /** * 判断是否创建者 * @return bool */ public function isCreater() { return $this->userid == User::userid(); } /** * 判断是否协助人员 * @return bool */ public function isAssister() { $row = $this; while ($row->parent_id > 0) { $row = self::find($row->parent_id); } return ProjectTaskUser::whereTaskId($row->id)->whereUserid(User::userid())->whereOwner(0)->exists(); } /** * 判断是否负责人(或者是主任务的负责人) * @return bool */ public function isOwner() { if ($this->owner) { return true; } if ($this->parent_id > 0) { $mainTask = self::allData()->find($this->parent_id); if ($mainTask->owner) { return true; } } return false; } /** * 是否有负责人 * @return bool */ public function hasOwner() { if (!isset($this->appendattrs['has_owner'])) { $this->appendattrs['has_owner'] = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->exists(); } return $this->appendattrs['has_owner']; } /** * 标记已完成、未完成 * @param Carbon|null $complete_at 完成时间 * @return bool */ public function completeTask($complete_at) { AbstractModel::transaction(function () use ($complete_at) { if ($complete_at === null) { // 标记未完成 $this->complete_at = null; $this->addLog("标记{任务}未完成"); } else { // 标记已完成 if ($this->parent_id == 0) { if (self::whereParentId($this->id)->whereCompleteAt(null)->exists()) { throw new ApiException('子任务未完成'); } } if (!$this->hasOwner()) { throw new ApiException('请先领取任务'); } $this->complete_at = $complete_at; $this->addLog("标记{任务}已完成"); } $this->save(); }); return true; } /** * 归档任务、取消归档 * @param Carbon|null $archived_at 归档时间 * @return bool */ public function archivedTask($archived_at, $isAuto = false) { if (!$this->complete_at) { $flowItems = ProjectFlowItem::whereProjectId($this->project_id)->whereStatus('end')->pluck('name'); if ($flowItems) { $flowItems = implode(",", array_values(array_unique($flowItems->toArray()))); } if (empty($flowItems)) { $flowItems = "已完成"; } throw new ApiException('仅限【' . $flowItems . '】状态的任务归档'); } AbstractModel::transaction(function () use ($isAuto, $archived_at) { if ($archived_at === null) { // 取消归档 $this->archived_at = null; $this->archived_userid = User::userid(); $this->archived_follow = 0; $this->addLog("任务取消归档"); } else { // 归档任务 if ($isAuto === true) { $logText = "自动任务归档"; $userid = 0; } else { $logText = "任务归档"; $userid = User::userid(); } $this->archived_at = $archived_at; $this->archived_userid = $userid; $this->archived_follow = 0; $this->addLog($logText, [], $userid); } $this->pushMsg('update', [ 'id' => $this->id, 'archived_at' => $this->archived_at, 'archived_userid' => $this->archived_userid, ]); self::whereParentId($this->id)->update([ 'archived_at' => $this->archived_at, 'archived_userid' => $this->archived_userid, 'archived_follow' => $this->archived_follow, ]); $this->save(); }); return true; } /** * 删除任务 * @param bool $pushMsg 是否推送 * @return bool */ public function deleteTask($pushMsg = true) { AbstractModel::transaction(function () { if ($this->dialog_id) { $dialog = WebSocketDialog::find($this->dialog_id); $dialog?->deleteDialog(); } self::whereParentId($this->id)->delete(); $this->addLog("删除{任务}"); $this->delete(); }); if ($pushMsg) { $this->pushMsg('delete'); } return true; } /** * 添加任务日志 * @param string $detail * @param array $record * @param int $userid * @return ProjectLog */ public function addLog($detail, $record = [], $userid = 0) { $detail = str_replace("{任务}", $this->parent_id ? "子任务" : "任务", $detail); $array = [ 'project_id' => $this->project_id, 'column_id' => $this->column_id, 'task_id' => $this->parent_id ?: $this->id, 'userid' => $userid ?: User::userid(), 'detail' => $detail, ]; if ($this->parent_id) { $record['subtitle'] = $this->name; } if ($record) { $array['record'] = $record; } $log = ProjectLog::createInstance($array); $log->save(); return $log; } /** * 推送消息 * @param string $action * @param array|self $data 发送内容,默认为[id, parent_id, project_id, column_id, dialog_id] * @param array $userid 指定会员,默认为项目所有成员 */ public function pushMsg($action, $data = null, $userid = null) { if (!$this->project) { return; } if ($data === null) { $data = [ 'id' => $this->id, 'parent_id' => $this->parent_id, 'project_id' => $this->project_id, 'column_id' => $this->column_id, 'dialog_id' => $this->dialog_id, ]; } elseif ($data instanceof self) { $data = $data->toArray(); } // $array = [$userid, []]; if ($userid === null) { $array[0] = $this->project->relationUserids(); } elseif (!is_array($userid)) { $array[0] = [$userid]; } // if (isset($data['owner'])) { $owners = ProjectTaskUser::whereTaskId($data['id'])->whereOwner(1)->pluck('userid')->toArray(); $array = [array_intersect($array[0], $owners), array_diff($array[0], $owners)]; } foreach ($array as $index => $item) { if ($index > 0) { $data['owner'] = 0; } $params = [ 'ignoreFd' => Request::header('fd'), 'userid' => array_values($item), 'msg' => [ 'type' => 'projectTask', 'action' => $action, 'data' => $data, ] ]; $task = new PushTask($params, false); Task::deliver($task); } } /** * 获取任务 * @param $task_id * @return ProjectTask|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null */ public static function oneTask($task_id) { return self::with(['taskUser', 'taskTag'])->allData()->where("project_tasks.id", intval($task_id))->first(); } /** * 获取任务(会员有任务权限 或 会员存在项目内) * @param int $task_id * @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制 * @param int|bool $permission 0|false:不限制, 1|true:限制项目负责人、任务负责人、协助人员及任务创建者, 2:已有负责人才限制true (子任务时如果是主任务负责人也可以) * @param array $with * @return self */ public static function userTask($task_id, $archived = true, $permission = 0, $with = []) { $task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first(); // if (empty($task)) { throw new ApiException('任务不存在', [ 'task_id' => $task_id ], -4002); } if ($archived === true && $task->archived_at != null) { throw new ApiException('任务已归档', [ 'task_id' => $task_id ]); } if ($archived === false && $task->archived_at == null) { throw new ApiException('任务未归档', [ 'task_id' => $task_id ]); } // try { $project = Project::userProject($task->project_id); } catch (Exception $e) { if ($task->owner === null) { throw new ApiException($e->getMessage(), [ 'task_id' => $task_id ], -4002); } $project = Project::find($task->project_id); if (empty($project)) { throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002); } } // if ($permission === 2) { $permission = $task->hasOwner() ? 1 : 0; } if (($permission === 1 || $permission === true) && !$project->owner && !$task->permission(3)) { throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作'); } // return $task; } }