id.':'.$kind.':'.($record->workflow_context['content_revision'] ?? 1); if (isset($record->workflow_context['review_cycle'])) { $key .= ':cycle:'.$record->workflow_context['review_cycle']; } $existing = DB::table('tasks')->where('source_system', 'opportunity-workflow')->where('external_id', $key)->first(); if ($existing) { return $existing->id; } $id = (string) Str::uuid(); DB::table('tasks')->insert(['id' => $id, 'source_system' => 'opportunity-workflow', 'external_id' => $key, 'correlation_id' => $record->management_creation_key, 'task_type' => $kind, 'title' => $title, 'status' => 'open', 'due_at' => null, 'assignee_user_id' => null, 'data_classification' => 'restricted']); return $id; } public function assign(Opportunity $record, string $taskId, User $subject): void { abort_unless(DB::table('tasks')->where('id', $taskId)->where('source_system', 'opportunity-workflow') ->where('external_id', 'like', 'portal-opportunity:'.$record->id.':%')->whereIn('status', ['open', 'in_progress'])->exists(), 409, 'Tarefa não pertence à versão aberta.'); DB::table('tasks')->where('id', $taskId)->update(['assignee_user_id' => $subject->id, 'status' => 'in_progress', 'version_no' => DB::raw('version_no+1')]); } public function closedReviewBinding(Opportunity $record, ?string $id): bool { return $id !== null && DB::table('tasks')->where('id', $id)->where('source_system', 'opportunity-workflow') ->where('external_id', 'like', 'portal-opportunity:'.$record->id.':opportunity_review:%')->where('status', 'done')->exists(); } public function complete(?string $taskId, string $outcome): void { if ($taskId) { // Existing CRM outcome is a varchar(80) summary. The full reason is // retained in the versioned workflow decision and its audit event. $summary = mb_strlen($outcome) > 80 ? mb_substr($outcome, 0, 79).'…' : $outcome; DB::table('tasks')->where('id', $taskId)->where('source_system', 'opportunity-workflow')->whereIn('status', ['open', 'in_progress']) ->update(['status' => 'done', 'outcome' => $summary, 'completed_at' => now(), 'version_no' => DB::raw('version_no+1')]); } } /** @return list<\stdClass> */ public function listing(Opportunity $record): array { return array_values(DB::table('tasks')->where('source_system', 'opportunity-workflow')->where('external_id', 'like', 'portal-opportunity:'.$record->id.':%') ->get(['id', 'task_type', 'status', 'title', 'assignee_user_id', 'due_at', 'completed_at', 'outcome'])->all()); } }