belongsTo(ProjectTask::class, 'task_id'); } public function relatedTask(): BelongsTo { return $this->belongsTo(ProjectTask::class, 'related_task_id'); } /** * 创建双向任务关联 * * @param int $sourceTaskId 源任务ID * @param int $targetTaskId 目标任务ID * @param int|null $dialogId 来源对话ID * @param int|null $msgId 来源消息ID * @param int|null $userid 操作人 * @param bool $push 是否推送更新 * @return bool 是否创建成功 */ public static function createRelation( int $sourceTaskId, int $targetTaskId, ?int $dialogId = null, ?int $msgId = null, ?int $userid = null, bool $push = true ): bool { if ($sourceTaskId === $targetTaskId) { return false; } $sourceTask = ProjectTask::with('project')->find($sourceTaskId); $targetTask = ProjectTask::with('project')->find($targetTaskId); if (!$sourceTask || !$targetTask) { return false; } if ($sourceTask->deleted_at || $targetTask->deleted_at) { return false; } $existed = static::whereTaskId($sourceTaskId) ->whereRelatedTaskId($targetTaskId) ->exists(); // 创建正向关联:源任务提及目标任务 $mentionRelation = static::updateOrCreate( [ 'task_id' => $sourceTaskId, 'related_task_id' => $targetTaskId, 'direction' => self::DIRECTION_MENTION, ], [ 'dialog_id' => $dialogId, 'msg_id' => $msgId, 'userid' => $userid, ] ); // 创建反向关联:目标任务被源任务提及 $reverseRelation = static::updateOrCreate( [ 'task_id' => $targetTaskId, 'related_task_id' => $sourceTaskId, 'direction' => self::DIRECTION_MENTIONED_BY, ], [ 'dialog_id' => $dialogId, 'msg_id' => $msgId, 'userid' => $userid, ] ); // 记录任务动态(仅首次建立关联时) if (!$existed) { self::addRelationLog($sourceTask, $targetTask, '关联任务', $userid); self::addRelationLog($targetTask, $sourceTask, '关联任务', $userid); } // 推送关联更新 if ($push) { $needPush = $mentionRelation->wasRecentlyCreated || $mentionRelation->wasChanged() || $reverseRelation->wasRecentlyCreated || $reverseRelation->wasChanged(); if ($needPush) { if ($sourceTask->project) { $sourceTask->pushMsg('relation', null, null, false); } if ($targetTask->project) { $targetTask->pushMsg('relation', null, null, false); } } } return true; } /** * 删除双向任务关联 * * @param int $taskId 任务ID * @param int $relatedTaskId 关联任务ID * @param int|null $userid 操作人 * @return bool 是否删除成功 */ public static function deleteRelation(int $taskId, int $relatedTaskId, ?int $userid = null): bool { // 删除正向关联 $deleted1 = static::whereTaskId($taskId) ->whereRelatedTaskId($relatedTaskId) ->delete(); // 删除反向关联 $deleted2 = static::whereTaskId($relatedTaskId) ->whereRelatedTaskId($taskId) ->delete(); if ($deleted1 || $deleted2) { // 推送关联更新 $sourceTask = ProjectTask::with('project')->find($taskId); $targetTask = ProjectTask::with('project')->find($relatedTaskId); // 记录任务动态 if ($sourceTask && $targetTask) { self::addRelationLog($sourceTask, $targetTask, '解除关联任务', $userid); self::addRelationLog($targetTask, $sourceTask, '解除关联任务', $userid); } if ($sourceTask?->project) { $sourceTask->pushMsg('relation', null, null, false); } if ($targetTask?->project) { $targetTask->pushMsg('relation', null, null, false); } return true; } return false; } /** * 记录任务关联变更动态 * * @param ProjectTask $task 记录动态的任务 * @param ProjectTask $relatedTask 关联的任务 * @param string $detail 动态内容 * @param int|null $userid 操作人 */ private static function addRelationLog(ProjectTask $task, ProjectTask $relatedTask, string $detail, ?int $userid): void { $task->addLog($detail, [ 'change' => ['#' . $relatedTask->id . ' ' . $relatedTask->name], ], $userid ?: 0); } public static function recordMentionsFromMessage(WebSocketDialogMsg $msg): void { if ($msg->type !== 'text') { return; } $payload = $msg->msg; if (!is_array($payload)) { $payload = Base::json2array($msg->getRawOriginal('msg')); } $text = $payload['text'] ?? ''; if (!$text || !preg_match_all('/#?(.*?)<\/span>/i', $text, $matches)) { return; } $targetIds = array_values(array_unique(array_filter(array_map('intval', $matches[1] ?? [])))); if (empty($targetIds)) { return; } $sourceTaskIds = ProjectTask::whereDialogId($msg->dialog_id) ->whereNull('deleted_at') ->pluck('id') ->toArray(); if (empty($sourceTaskIds)) { return; } foreach ($sourceTaskIds as $sourceTaskId) { foreach ($targetIds as $targetId) { self::createRelation( $sourceTaskId, $targetId, $msg->dialog_id, $msg->id, $msg->userid ); } } } }