dootask/app/Models/ProjectTask.php

1149 lines
46 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use App\Tasks\PushTask;
use Arr;
use Carbon\Carbon;
use DB;
use Exception;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
use Request;
/**
* App\Models\ProjectTask
*
* @property int $id
* @property int|null $parent_id 父级任务ID
* @property int|null $project_id 项目ID
* @property int|null $column_id 列表ID
* @property int|null $dialog_id 聊天会话ID
* @property int|null $flow_item_id 工作流状态ID
* @property string|null $flow_item_name 工作流状态名称
* @property string|null $name 标题
* @property string|null $color 颜色
* @property string|null $desc 描述
* @property string|null $start_at 计划开始时间
* @property string|null $end_at 计划结束时间
* @property string|null $archived_at 归档时间
* @property int|null $archived_userid 归档会员
* @property int|null $archived_follow 跟随项目归档(项目取消归档时任务也取消归档)
* @property string|null $complete_at 完成时间
* @property int|null $userid 创建人
* @property int|null $p_level 优先级
* @property string|null $p_name 优先级名称
* @property string|null $p_color 优先级颜色
* @property int|null $sort 排序(ASC)
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \App\Models\ProjectTaskContent|null $content
* @property-read int $file_num
* @property-read int $msg_num
* @property-read bool $overdue
* @property-read int $percent
* @property-read int $sub_complete
* @property-read int $sub_num
* @property-read bool $today
* @property-read \App\Models\Project|null $project
* @property-read \App\Models\ProjectColumn|null $projectColumn
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskFile[] $taskFile
* @property-read int|null $task_file_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskTag[] $taskTag
* @property-read int|null $task_tag_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser
* @property-read int|null $task_user_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTask onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedFollow($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereColor($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereColumnId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCompleteAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDesc($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereEndAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePColor($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePLevel($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereParentId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereSort($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereStartAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTask withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTask withoutTrashed()
* @mixin \Eloquent
*/
class ProjectTask extends AbstractModel
{
use SoftDeletes;
protected $appends = [
'file_num',
'msg_num',
'sub_num',
'sub_complete',
'percent',
'today',
'overdue',
];
/**
* 附件数量
* @return int
*/
public function getFileNumAttribute()
{
if (!isset($this->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) {
// 判断版本
if (version_compare(Base::getClientVersion(), '0.6.0', '<')) {
throw new ApiException('当前版本过低');
}
// 主任务
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
// 工作流
if (Arr::exists($data, 'flow_item_id')) {
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 (!in_array(User::userid(), $currentFlowItem->userids)
&& !ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->exists()) {
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;
return;
}
// 标题
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)];
$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();
}
}
});
}
$this->addLog("修改{任务}时间");
}
// 以下紧顶级任务可修改
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;
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("修改{任务}优先级");
}
}
$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 = $this->taskUser->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;
}
/**
* 判断是否负责人(或者是主任务的负责人)
* @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)
{
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 $mustOwner 0|false:不限制, 1|true:限制任务或项目负责人, 2:已有负责人才限制任务或项目负责人(子任务时如果是主任务负责人也可以)
* @param array $with
* @return self
*/
public static function userTask($task_id, $archived = true, $mustOwner = 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 ($mustOwner === 2) {
$mustOwner = $task->hasOwner() ? 1 : 0;
}
if (($mustOwner === 1 || $mustOwner === true) && !$task->isOwner() && !$project->owner) {
throw new ApiException('仅限项目或任务负责人操作');
}
//
return $task;
}
}