160 lines
9.1 KiB
PHP
160 lines
9.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\PeopleProcess;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Query\Builder;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
use stdClass;
|
|
|
|
class PeopleRegistry
|
|
{
|
|
public function canRead(User $actor): bool
|
|
{
|
|
return $actor->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<string,mixed> */
|
|
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<string,mixed> */
|
|
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<string,mixed> $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<string,mixed> $input
|
|
* @return array<string,mixed>
|
|
*/
|
|
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;
|
|
}
|
|
}
|