80 lines
3.7 KiB
PHP
80 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\ExistingBusiness;
|
|
use App\Models\ExistingBusinessObject;
|
|
use App\Models\User;
|
|
use App\Services\Mne\BusinessRepository;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
final class ExistingBusinessVerification
|
|
{
|
|
public const CHECKS = ['identity', 'representation', 'declared_ownership', 'documentary_ownership', 'document_received', 'document_validated', 'independent_valuation', 'land_right_documented', 'license_confirmed', 'validity', 'legal_transfer', 'prior_authorization'];
|
|
|
|
private function contentVersion(string $id): int
|
|
{
|
|
return (int) DB::table('existing_business_revisions')->where('business_id', $id)
|
|
->whereIn('action', ['created', 'updated', 'object_added', 'object_updated'])->max('version_no');
|
|
}
|
|
|
|
/** @param array<string,mixed> $input */
|
|
public function save(User $actor, string $id, array $input): ExistingBusiness
|
|
{
|
|
$data = Validator::make($input, [
|
|
'version_no' => ['required', 'integer', 'min:1'],
|
|
'object_id' => ['nullable', 'uuid'],
|
|
'check' => ['required', 'in:'.implode(',', self::CHECKS)],
|
|
'outcome' => ['required', 'in:confirmed,rejected,inconclusive,revoked'],
|
|
'evidence_reference' => ['required', 'string', 'min:10', 'max:2000'],
|
|
'note' => ['required', 'string', 'min:10', 'max:2000'],
|
|
'valid_until' => ['required_if:outcome,confirmed', 'nullable', 'date_format:Y-m-d', 'after_or_equal:today'],
|
|
])->validate();
|
|
|
|
return DB::transaction(function () use ($actor, $id, $data) {
|
|
$business = app(BusinessRepository::class)->find($id, true);
|
|
Gate::forUser($actor)->authorize('review', $business);
|
|
abort_unless($business->status === 'in_review' && $business->version_no === $data['version_no'], 409, 'Reabra a versão actual em revisão.');
|
|
if (! empty($data['object_id'])) {
|
|
ExistingBusinessObject::where('business_id', $id)->whereKey($data['object_id'])->firstOrFail();
|
|
}
|
|
$data['content_version'] = $this->contentVersion($id);
|
|
$business->version_no++;
|
|
$business->save();
|
|
app(ExistingBusinessDrafts::class)->snapshot($business, $actor, 'verification', $data['note'], $data);
|
|
|
|
return $business;
|
|
});
|
|
}
|
|
|
|
/** Latest finding for each check/subject; complete evidence remains in immutable revisions.
|
|
* @return array<int,array<string,mixed>>
|
|
*/
|
|
public function findings(ExistingBusiness $business): array
|
|
{
|
|
$contentVersion = $this->contentVersion($business->id);
|
|
$findings = [];
|
|
foreach (DB::table('existing_business_revisions')->where('business_id', $business->id)
|
|
->where('action', 'verification')->orderByDesc('version_no')->cursor() as $revision) {
|
|
$data = json_decode(Crypt::decryptString($revision->snapshot), true, 512, JSON_THROW_ON_ERROR)['context'];
|
|
$key = ($data['object_id'] ?? 'business').':'.$data['check'];
|
|
if (isset($findings[$key])) {
|
|
continue;
|
|
}
|
|
$effective = $data['outcome'];
|
|
if ($data['content_version'] !== $contentVersion) {
|
|
$effective = 'stale';
|
|
} elseif (! empty($data['valid_until']) && $data['valid_until'] < today()->toDateString()) {
|
|
$effective = 'expired';
|
|
}
|
|
$findings[$key] = [...$data, 'effective_outcome' => $effective,
|
|
'version_no' => $revision->version_no, 'created_at' => $revision->created_at];
|
|
}
|
|
|
|
return array_values($findings);
|
|
}
|
|
}
|