$data * @param array $snapshot * @param array $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 $context * @param array $snapshot * @param list $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 $context * @param array $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]); } } }