77 lines
4.7 KiB
PHP
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]);
|
|
}
|
|
}
|