112 lines
6.8 KiB
PHP
112 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
/** Country of constitution in the existing identity master, with explicit provenance. */
|
|
class OrganizationCountry
|
|
{
|
|
public function available(): bool
|
|
{
|
|
return config('portal.organization_country_enabled', false)
|
|
&& Schema::hasColumns('organizations', ['incorporation_country_code', 'incorporation_country_provenance']);
|
|
}
|
|
|
|
/** @return list<array<string, mixed>> */
|
|
public function represented(User $actor): array
|
|
{
|
|
if (! $this->available()) {
|
|
return [];
|
|
}
|
|
$readOnly = app(PortalProfileAccess::class)->has($actor, 'auditor');
|
|
|
|
return array_values(DB::table('memberships as m')->join('organizations as o', 'o.id', '=', 'm.organization_id')
|
|
->where('m.user_id', $actor->id)->whereNull('m.deleted_at')->whereNull('o.deleted_at')
|
|
->get(['o.id', 'o.name', 'o.version_no', 'o.incorporation_country_code', 'o.incorporation_country_provenance', 'm.role'])
|
|
->unique('id')->map(fn ($o) => ['id' => $o->id, 'name' => $o->name, 'version' => $o->version_no,
|
|
'country' => $o->incorporation_country_code, 'provenance' => json_decode($o->incorporation_country_provenance ?? 'null', true),
|
|
'canEdit' => ! $readOnly && in_array($o->role, ['admin', 'manager', 'officer'], true)])->all());
|
|
}
|
|
|
|
public function authorize(User $actor, string $id, bool $write = false): void
|
|
{
|
|
abort_unless($this->available(), 409, 'Extensão do país de constituição ainda não instalada.');
|
|
abort_if($write && app(PortalProfileAccess::class)->has($actor, 'auditor'), 403, 'Auditor só consulta o cadastro mestre, mesmo com representação ou outros papéis.');
|
|
$roles = $write ? ['admin', 'manager', 'officer'] : ['admin', 'manager', 'officer', 'viewer'];
|
|
abort_unless(DB::table('memberships')->where('organization_id', $id)->where('user_id', $actor->id)
|
|
->whereNull('deleted_at')->whereIn('role', $roles)->exists()
|
|
&& DB::table('organizations')->where('id', $id)->whereNull('deleted_at')->exists(), 403, 'Representação da organização não autorizada.');
|
|
}
|
|
|
|
/** @param array<string, mixed> $data
|
|
* @return array{version:int, country:string} */
|
|
public function save(User $actor, string $id, array $data): array
|
|
{
|
|
$this->authorize($actor, $id, true);
|
|
$data = Validator::make($data, ['version' => 'required|integer|min:1', 'operation_key' => ['sometimes', 'required', 'uuid'],
|
|
'country' => ['required', 'string', app(CountryCatalogue::class)->rule()],
|
|
'source_reference' => 'required|string|min:5|max:2000'], [
|
|
'country.required' => 'Selecione o país de constituição da organização.',
|
|
'source_reference.required' => 'Indique a fonte ou referência deste país.',
|
|
'source_reference.min' => 'A referência deve ter pelo menos cinco caracteres.',
|
|
])->validate();
|
|
|
|
return DB::transaction(function () use ($actor, $id, $data) {
|
|
DB::table('users')->where('id', $actor->id)->lockForUpdate()->firstOrFail();
|
|
DB::table('memberships')->where('organization_id', $id)->where('user_id', $actor->id)->lockForUpdate()->get();
|
|
$this->authorize($actor, $id, true);
|
|
$row = DB::table('organizations')->where('id', $id)->lockForUpdate()->firstOrFail();
|
|
$this->authorize($actor, $id, true);
|
|
$commands = app(InvestmentProfileCommands::class);
|
|
$command = ['kind' => 'organization_country', 'target_id' => $id, 'version' => (int) $data['version'], 'payload' => ['country' => $data['country'], 'source_reference' => $data['source_reference']]];
|
|
$replay = $commands->replay($actor, $data['operation_key'] ?? null, $command);
|
|
if ($replay !== null) {
|
|
return ['version' => $replay['version'], 'country' => $data['country']];
|
|
}
|
|
abort_unless((int) $row->version_no === (int) $data['version'], 409, 'Organização alterada. Recarregue.');
|
|
$provenance = ['kind' => 'representative_declaration', 'reference' => $data['source_reference'],
|
|
'actor_id' => $actor->id, 'at' => now()->toIso8601String(), 'source_system' => $row->source_system,
|
|
'external_id' => $row->external_id, 'verification' => 'country_declared_not_independently_verified'];
|
|
DB::table('organizations')->where('id', $id)->update(['incorporation_country_code' => $data['country'],
|
|
'incorporation_country_provenance' => json_encode($provenance, JSON_THROW_ON_ERROR), 'version_no' => $row->version_no + 1]);
|
|
DB::table('audit_events')->insert(['source_system' => 'organization-country', 'entity_type' => 'organizations',
|
|
'actor_user_id' => $actor->id, 'action' => 'incorporation_country_saved', 'data_classification' => 'restricted',
|
|
'before_data' => json_encode(['organization_id' => $id, 'snapshot' => Crypt::encryptString(json_encode($row, JSON_THROW_ON_ERROR))]),
|
|
'after_data' => json_encode(['organization_id' => $id, 'version' => $row->version_no + 1,
|
|
'snapshot' => Crypt::encryptString(json_encode(['country' => $data['country'], 'provenance' => $provenance], JSON_THROW_ON_ERROR))])]);
|
|
|
|
$commands->record($actor, $data['operation_key'] ?? null, $command, ['version' => $row->version_no + 1]);
|
|
|
|
return ['version' => $row->version_no + 1, 'country' => $data['country']];
|
|
});
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function snapshot(User $actor, string $id): array
|
|
{
|
|
$snapshot = $this->readableSnapshot($actor, $id);
|
|
abort_unless($snapshot['country'] !== null, 422, 'País de constituição não informado no cadastro mestre.');
|
|
|
|
return $snapshot;
|
|
}
|
|
|
|
/** Caller owns a transaction; authorize again after waiting for the source lock.
|
|
* @return array<string, mixed> */
|
|
public function readableSnapshot(User $actor, string $id): array
|
|
{
|
|
$this->authorize($actor, $id);
|
|
DB::table('memberships')->where('organization_id', $id)->where('user_id', $actor->id)->lockForUpdate()->get();
|
|
$this->authorize($actor, $id);
|
|
$org = DB::table('organizations')->where('id', $id)->lockForUpdate()->firstOrFail();
|
|
$this->authorize($actor, $id);
|
|
|
|
return ['organization_id' => $org->id, 'organization_version' => $org->version_no, 'country' => $org->incorporation_country_code,
|
|
'provenance' => json_decode($org->incorporation_country_provenance, true), 'reused_at' => now()->toIso8601String(), 'reused_by' => $actor->id];
|
|
}
|
|
}
|