tenant_id !== null && ($actor->hasFunctions(['people_registry_read', 'people_registry_manage']) || $this->preparer($actor)); } public function canCreate(User $actor): bool { return ! app(OrganizationAccess::class)->maintenanceOnly($actor) && ($actor->hasFunctions(['people_registry_manage']) || $this->preparer($actor)); } private function preparer(User $actor): bool { return app(PeopleHiring::class)->allowed($actor, 'service', 'prepare') || app(PeopleHiring::class)->allowed($actor, 'staff', 'prepare'); } public function canEdit(User $actor, stdClass $person): bool { return $person->tenant_id === $actor->tenant_id && $this->canCreate($actor) && ($actor->hasFunctions(['people_registry_manage']) || $person->created_by === $actor->id); } public function visible(User $actor): Builder { $query = DB::table('people_registry')->where('tenant_id', $actor->tenant_id); if (! $actor->hasFunctions(['people_registry_read', 'people_registry_manage'])) { $query->where('created_by', $actor->id); } return $query; } /** @return array */ public function item(User $actor, stdClass $person): array { return ['id' => $person->id, 'name' => $person->name, 'reference' => $person->reference, 'profile_summary' => $person->profile_summary, 'active' => (bool) $person->active, 'version' => $person->version, 'canEdit' => $this->canEdit($actor, $person)]; } /** @return array */ public function lookup(User $actor, PeopleProcess $process, string $search): array { $ready = Schema::hasTable('people_registry'); $allowed = $process->status === 'open' && $this->canRead($actor) && app(PeopleHiring::class)->allowed($actor, $process->kind, 'prepare') && app(PeopleAccess::class)->can($actor, $process, 'prepare'); $items = $ready && $allowed && mb_strlen(trim($search)) >= 2 ? $this->visible($actor)->where('active', true) ->where(fn ($q) => $q->whereLike('name', '%'.$search.'%')->orWhereLike('reference', '%'.$search.'%'))->orderBy('name')->orderBy('id')->limit(20)->get() ->map(fn ($person) => $this->item($actor, $person))->all() : []; return ['ready' => $ready && $allowed, 'canCreate' => $allowed && $this->canCreate($actor), 'search' => $search, 'items' => $items]; } /** @param array $input */ public function execute(User $actor, ?int $id, array $input): stdClass { return DB::transaction(function () use ($actor, $id, $input) { app(ConflictGuard::class)->lockCompany($actor); $actor = app(CurrentActor::class)->lock($actor); abort_unless($this->canCreate($actor), 403); $data = Validator::make($input, ['name' => 'required|string|min:3|max:200', 'reference' => 'nullable|string|max:120', 'profile_summary' => 'required|string|min:10|max:5000', 'reason' => 'required|string|min:10|max:4000', 'operation_key' => $id === null ? 'required|uuid' : 'nullable|uuid', 'version' => $id === null ? 'nullable|integer' : 'required|integer|min:1', 'active' => $id === null ? 'nullable|boolean' : 'required|boolean'])->validate(); $reference = trim($data['reference'] ?? ''); if ($reference === '') { if ($id !== null) { throw ValidationException::withMessages(['reference' => 'Conserve ou indique uma referência do cadastro.']); } $reference = 'PESS-'.strtoupper($data['operation_key']); } $key = mb_strtoupper(Str::squish($reference)); $values = ['name' => trim($data['name']), 'reference' => $reference, 'reference_key' => $key, 'profile_summary' => trim($data['profile_summary'])]; $hash = hash('sha256', json_encode($values, JSON_THROW_ON_ERROR)); if ($id === null) { $existing = DB::table('people_registry')->where('tenant_id', $actor->tenant_id)->where('operation_key', $data['operation_key'])->first(); if ($existing) { abort_unless($existing->created_by === $actor->id && $existing->creation_hash === $hash, 409); return $existing; } $before = null; } else { $before = $this->visible($actor)->where('id', $id)->lockForUpdate()->first(); abort_unless($before && $this->canEdit($actor, $before), 404); if ($before->version !== (int) $data['version']) { throw ValidationException::withMessages(['version' => 'O cadastro mudou. Actualize antes de guardar.']); } } if (DB::table('people_registry')->where('tenant_id', $actor->tenant_id)->where('reference_key', $key)->when($id !== null, fn ($q) => $q->where('id', '!=', $id))->exists()) { throw ValidationException::withMessages(['reference' => 'Já existe esta referência na empresa. Reutilize o cadastro autorizado ou peça a sua conferência.']); } if ($id === null) { $id = DB::table('people_registry')->insertGetId([...$values, 'tenant_id' => $actor->tenant_id, 'created_by' => $actor->id, 'operation_key' => $data['operation_key'], 'creation_hash' => $hash, 'active' => true, 'version' => 1, 'created_at' => now(), 'updated_at' => now()]); } else { DB::table('people_registry')->where('id', $id)->update([...$values, 'active' => $data['active'], 'version' => $before->version + 1, 'updated_at' => now()]); } $after = DB::table('people_registry')->where('tenant_id', $actor->tenant_id)->where('id', $id)->first(); abort_unless($after !== null, 500); app(RequisitionWorkflow::class)->audit($actor, $before === null ? 'people_registry.created' : 'people_registry.updated', null, $before === null ? null : (array) $before, [...(array) $after, 'reason' => $data['reason']]); return $after; }); } /** @param array $input * @return array */ public function application(User $actor, array $input): array { $base = Validator::make($input, ['operation_key' => 'required|uuid', 'reference' => 'required|string|max:200', 'received_on' => 'required|date_format:Y-m-d|before_or_equal:today', 'person_id' => 'nullable|integer|min:1', 'save_person' => 'sometimes|boolean'])->validate(); $person = null; if (! empty($base['person_id']) || ($base['save_person'] ?? false)) { abort_unless(Schema::hasTable('people_registry') && Schema::hasColumn('people_applications', 'person_id'), 503, 'Instalação do cadastro de pessoas pendente.'); } if (! empty($base['person_id'])) { abort_unless($this->canRead($actor), 403); $data = Validator::make($input, ['person_version' => 'required|integer|min:1', 'summary' => 'nullable|string|min:10|max:5000'])->validate(); $person = $this->visible($actor)->where('id', $base['person_id'])->lockForUpdate()->first(); abort_unless($person !== null, 404); if (! $person->active || $person->version !== (int) $data['person_version']) { throw ValidationException::withMessages(['person_id' => 'O cadastro foi alterado ou arquivado. Pesquise e seleccione a versão actual.']); } $name = $person->name; $summary = $data['summary'] ?? $person->profile_summary; } else { $data = Validator::make($input, ['name' => 'required|string|min:3|max:200', 'summary' => 'required|string|min:10|max:5000', 'person_reference' => 'nullable|string|max:120'])->validate(); $name = $data['name']; $summary = $data['summary']; if ($base['save_person'] ?? false) { $person = $this->execute($actor, null, ['name' => $name, 'reference' => $data['person_reference'] ?? null, 'profile_summary' => $summary, 'operation_key' => $base['operation_key'], 'reason' => $input['reason']]); } } $application = ['operation_key' => $base['operation_key'], 'reference' => $base['reference'], 'received_on' => $base['received_on'], 'name' => $name, 'summary' => $summary]; if ($person) { $application += ['person_id' => $person->id, 'person_snapshot' => json_encode(['id' => $person->id, 'version' => $person->version, 'reference' => $person->reference, 'name' => $person->name, 'profile_summary' => $person->profile_summary], JSON_THROW_ON_ERROR)]; } return $application; } }