Files

77 lines
4.7 KiB
PHP

<?php
namespace App\Services;
use App\Models\CrmTask;
use App\Models\Lead;
use App\Models\User;
use Carbon\CarbonImmutable;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
final class CrmTaskCreation
{
private const SOURCE = 'portal-crm-task-creation';
/** @param array<string,mixed> $input
* @param array<int,int> $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<array{key:string,task_id:string,version:int}>,preparation:array<string,mixed>} */
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]);
}
}