Files
Investidor-V2/app/Services/OpportunityReviewConditions.php
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

91 lines
5.1 KiB
PHP

<?php
namespace App\Services;
use App\Models\Opportunity;
use App\Models\User;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
/** Explicit internal review conditions in the existing workflow JSON and audit. */
class OpportunityReviewConditions
{
/** @param array<string,mixed> $data
* @param array<string,mixed> $snapshot
* @param array<string,mixed> $context */
public function review(Opportunity $record, User $actor, array $data, array $snapshot, array &$context): void
{
$gate = $data['gate'];
foreach ($data['resolve_condition_ids'] ?? [] as $id) {
$condition = $context['conditions'][$id] ?? null;
$this->require($condition !== null && $condition['gate'] === $gate && $condition['status'] === 'open', 'resolve_condition_ids', 'Selecione uma condição aberta desta revisão.');
$this->require(($data['gate_decision'] ?? '') === 'approved', 'gate_decision', 'A resolução exige verificação interna aprovada com evidência.');
$documents = [];
foreach ($data['evidence_document_ids'] ?? [] as $documentId) {
$documents[$documentId] = $snapshot['resources']['documents'][$documentId]['sha256'];
}
$this->require($documents !== [], 'evidence_document_ids', 'A resolução exige documento ativo e analisado desta oportunidade.');
$context['conditions'][$id]['status'] = 'resolved';
$context['conditions'][$id]['resolution'] = ['actor_id' => $actor->id, 'at' => now()->toIso8601String(), 'reason' => $data['reason'], 'document_hashes' => $documents, 'content_revision' => $context['content_revision'] ?? 1, 'scope' => 'internal_verification_not_external_decision'];
}
if (! empty($data['condition'])) {
$input = $data['condition'];
$subject = User::query()->whereKey($input['responsible_user_id'])->first();
$access = app(PortalProfileAccess::class);
$this->require($subject !== null && $access->canView($subject, $record)
&& (($access->scoped($subject, 'promoter', $record->organization_id, $record->opportunity_category) && $access->operational($subject, 'edit', $record->id))
|| ($access->scoped($subject, 'validator', $record->organization_id, $record->opportunity_category) && $access->operational($subject, 'validate', $record->id))),
'condition.responsible_user_id', 'Selecione um responsável autorizado neste âmbito; não atribua responsabilidade externa.');
$id = (string) Str::uuid();
$context['conditions'][$id] = ['id' => $id, 'gate' => $gate, 'description' => $input['description'], 'responsible_user_id' => $subject->id,
'responsible_name' => $subject->name, 'due_on' => $input['due_on'] ?? null, 'blocking_action' => $input['blocking_action'],
'status' => 'open', 'created_by' => $actor->id, 'created_at' => now()->toIso8601String(), 'scope' => 'internal_review_condition', 'resolution_history' => []];
}
}
/** @param array<string,mixed> $context
* @param array<string,mixed> $snapshot
* @param list<string> $reopened */
public function reconcile(array &$context, array $snapshot, array $reopened): void
{
foreach ($context['conditions'] ?? [] as $id => $condition) {
if ($condition['status'] !== 'resolved') {
continue;
}
$matches = ! in_array($condition['gate'], $reopened, true);
foreach ($condition['resolution']['document_hashes'] ?? [] as $documentId => $sha) {
$matches = $matches && ($snapshot['resources']['documents'][$documentId]['sha256'] ?? null) === $sha;
}
if (! $matches) {
$context['conditions'][$id]['resolution_history'][] = $condition['resolution'];
unset($context['conditions'][$id]['resolution']);
$context['conditions'][$id]['status'] = 'open';
$context['conditions'][$id]['reopened_at'] = now()->toIso8601String();
}
}
}
/** @param array<string,mixed> $context
* @param array<string,mixed> $snapshot */
public function authorize(array $context, array $snapshot, string $action): void
{
foreach ($context['conditions'] ?? [] as $condition) {
if ($action === 'approve' && $condition['blocking_action'] !== 'approve') {
continue;
}
$resolved = $condition['status'] === 'resolved';
foreach ($condition['resolution']['document_hashes'] ?? [] as $id => $sha) {
$resolved = $resolved && ($snapshot['resources']['documents'][$id]['sha256'] ?? null) === $sha;
}
$this->require($resolved, 'conditions.'.$condition['id'], 'Condição pendente: '.$condition['description'].'. Responsável: '.$condition['responsible_name'].'. Prazo: '.($condition['due_on'] ?? 'não definido').'.');
}
}
private function require(bool $ok, string $field, string $message): void
{
if (! $ok) {
throw ValidationException::withMessages([$field => $message]);
}
}
}