This commit is contained in:
2026-11-02 20:27:11 +02:00
parent b44378b899
commit badaada043
300 changed files with 17458 additions and 1056 deletions
+36 -4
View File
@@ -34,6 +34,7 @@ class FormalRecommendation
private function process(User $actor, int $id): Requisition
{
abort_if(app(OrganizationAccess::class)->maintenanceOnly($actor), 403);
$request = Requisition::query()->visibleTo($actor)->lockForUpdate()->findOrFail($id);
if ($request->status !== 'approved' || PurchaseAward::query()->where('requisition_id', $id)->exists() || PurchaseOrder::query()->where('requisition_id', $id)->exists()) {
$this->invalid('A recomendação só pode ser preparada num pedido aprovado, antes de adjudicar.');
@@ -152,7 +153,40 @@ class FormalRecommendation
public function eligible(User $actor, Requisition $request, RecommendationAdoption $adoption, RecommendationAttempt $attempt): bool
{
return $actor->tenant_id === $request->tenant_id && $actor->hasFunctions($adoption->snapshot['roles']) && ! in_array($actor->id, [$request->requester_id, $attempt->created_by], true) && (! $actor->departmentScoped() || $actor->department_id === $request->department_id);
return $actor->tenant_id === $request->tenant_id && $adoption->tenant_id === $request->tenant_id && $attempt->tenant_id === $request->tenant_id
&& $adoption->requisition_id === $request->id && ! app(OrganizationAccess::class)->maintenanceOnly($actor)
&& $actor->hasFunctions($adoption->snapshot['roles']) && ! in_array($actor->id, [$request->requester_id, $attempt->created_by], true) && (! $actor->departmentScoped() || $actor->department_id === $request->department_id);
}
public function canParticipate(User $actor, Requisition $request): bool
{
return $actor->tenant_id === $request->tenant_id && $request->status === 'approved'
&& ! app(OrganizationAccess::class)->maintenanceOnly($actor)
&& Requisition::query()->visibleTo($actor)->whereKey($request->id)->exists()
&& ! PurchaseAward::query()->where('requisition_id', $request->id)->exists()
&& ! PurchaseOrder::query()->where('requisition_id', $request->id)->exists()
&& ! app(ConflictGuard::class)->participationBlocked($actor, $request);
}
/** Same immutable basis check used before a decision is recorded. */
public function currentReviewBasis(Requisition $request, RecommendationAdoption $adoption, RecommendationAttempt $attempt): bool
{
if ($adoption->requisition_id !== $request->id || $attempt->recommendation_adoption_id !== $adoption->id) {
return false;
}
$latest = RecommendationAttempt::query()->where('recommendation_adoption_id', $adoption->id)->latest('revision')->first();
$basis = $this->basis($request);
return $latest?->id === $attempt->id && $basis['ready'] && $basis['hash'] === $attempt->basis_hash;
}
public function canReview(User $actor, Requisition $request, RecommendationAttempt $attempt): bool
{
$adoption = $this->adoption($request);
return $adoption !== null && $this->eligible($actor, $request, $adoption, $attempt) && $this->canParticipate($actor, $request)
&& $this->currentReviewBasis($request, $adoption, $attempt) && $this->status($attempt, $adoption) === 'pending'
&& ! RecommendationReview::query()->where('recommendation_attempt_id', $attempt->id)->where('reviewed_by', $actor->id)->exists();
}
public function review(User $actor, RecommendationAttempt $attempt, string $verdict, string $reason): void
@@ -167,9 +201,7 @@ class FormalRecommendation
$request = $this->process($actor, $original->requisition_id);
$adoption = $this->adoption($request);
abort_unless($adoption && $this->eligible($actor, $request, $adoption, $attempt), 403);
$latest = RecommendationAttempt::query()->where('recommendation_adoption_id', $adoption->id)->latest('revision')->first();
$basis = $this->basis($request);
if ($attempt->recommendation_adoption_id !== $adoption->id || $latest?->id !== $attempt->id || ! $basis['ready'] || $basis['hash'] !== $attempt->basis_hash) {
if (! $this->currentReviewBasis($request, $adoption, $attempt)) {
$this->invalid('A base, adopção ou tentativa mudou. Prepare outra recomendação e novas conferências.');
}
$existing = RecommendationReview::query()->where('recommendation_attempt_id', $attempt->id)->where('reviewed_by', $actor->id)->first();