$input * @param array $assignees * @return array{task_id:string,version:int,replayed:bool} */ public function create(User $actor, Lead $lead, array $input, array $assignees): array { abort_unless(Gate::forUser($actor)->allows('manage-portal'), 403); $data = Validator::make($input, ['client_key' => 'nullable|uuid', 'title' => 'required|string|max:200', 'due_at' => 'required|date', 'assignee_user_id' => 'required|integer'])->validate(); $key = is_string($data['client_key'] ?? null) ? strtolower($data['client_key']) : null; $values = ['title' => trim($data['title']), 'due_at' => CarbonImmutable::parse($data['due_at'])->utc()->format('Y-m-d\TH:i:s.u\Z'), 'assignee_user_id' => (int) $data['assignee_user_id']]; $hash = hash('sha256', json_encode($values, JSON_THROW_ON_ERROR)); return DB::transaction(function () use ($actor, $lead, $values, $key, $hash, $assignees) { $lead = Lead::lockForUpdate()->findOrFail($lead->id); if ($key !== null) { $event = $this->query($actor, $lead)->whereRaw("after_data->'task_creation'->>'key' = ?", [$key])->selectRaw("entity_id,after_data->'task_creation'->>'hash' AS hash,after_data->'task_creation'->'version' AS version")->first(); if ($event) { abort_unless($event->hash === $hash, 409, 'Esta tentativa já foi usada com outros dados. Compare as tarefas antes de continuar.'); $version = is_string($event->version) ? json_decode($event->version, true, 512, JSON_THROW_ON_ERROR) : $event->version; abort_unless(is_string($event->entity_id) && Str::isUuid($event->entity_id) && is_int($version) && $version > 0, 409, 'A criação anterior não foi identificada com segurança.'); return ['task_id' => $event->entity_id, 'version' => $version, 'replayed' => true]; } } Validator::make($values, ['title' => 'required|string|max:200', 'due_at' => 'required|date|after:now', 'assignee_user_id' => ['required', 'integer', Rule::in($assignees)]])->validate(); $task = new CrmTask; PortalAudit::save($task, [...$values, 'lead_id' => $lead->id, 'status' => 'open'], $actor->id); if ($key !== null) { DB::table('audit_events')->insert(['source_system' => self::SOURCE, 'actor_user_id' => $actor->id, 'entity_type' => 'tasks', 'entity_id' => $task->id, 'action' => 'created', 'data_classification' => 'internal', 'after_data' => json_encode(['task_creation' => ['lead_id' => $lead->id, 'key' => $key, 'hash' => $hash, 'version' => (int) $task->getAttribute('version_no')]], JSON_THROW_ON_ERROR)]); } return ['task_id' => $task->id, 'version' => (int) $task->getAttribute('version_no'), 'replayed' => false]; }); } /** @return array{lead_id:string,actor_id:int,commands:list,preparation:array} */ public function context(User $actor, Lead $lead): array { abort_unless(Gate::forUser($actor)->allows('manage-portal'), 403); $commands = []; foreach ($this->query($actor, $lead)->orderBy('occurred_at')->selectRaw("entity_id,after_data->'task_creation'->>'key' AS key,after_data->'task_creation'->'version' AS version")->get() as $event) { $version = is_string($event->version) ? json_decode($event->version, true, 512, JSON_THROW_ON_ERROR) : $event->version; if (is_string($event->key) && Str::isUuid($event->key) && is_string($event->entity_id) && Str::isUuid($event->entity_id) && is_int($version) && $version > 0) { $commands[] = ['key' => $event->key, 'task_id' => $event->entity_id, 'version' => $version]; } } return ['lead_id' => $lead->id, 'actor_id' => $actor->id, 'commands' => $commands, 'preparation' => app(CrmTaskPreparations::class)->view($actor, $lead)]; } private function query(User $actor, Lead $lead): Builder { return DB::table('audit_events')->where('source_system', self::SOURCE)->where('actor_user_id', $actor->id)->where('entity_type', 'tasks')->where('action', 'created') ->whereRaw("after_data->'task_creation'->>'lead_id' = ?", [$lead->id]); } }