Files
compras/app/Services/QuotationParticipation.php
2026-11-02 20:27:11 +02:00

94 lines
3.6 KiB
PHP

<?php
namespace App\Services;
use App\Enums\PurchasingRole;
use App\Models\PurchaseAward;
use App\Models\Quotation;
use App\Models\Requisition;
use App\Models\User;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
/** Shared read-only authority for quotation forms, tasks and commands. */
class QuotationParticipation
{
private function assertActor(User $actor, Requisition $request, bool $evaluation): void
{
$roles = $evaluation ? [PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead] : [PurchasingRole::Procurement];
abort_unless($actor->tenant_id === $request->tenant_id && $request->status === 'approved' && $actor->hasFunctions($roles)
&& ! app(OrganizationAccess::class)->maintenanceOnly($actor)
&& (! $evaluation || $actor->id !== $request->requester_id)
&& Requisition::query()->visibleTo($actor)->whereKey($request->id)->exists(), 403);
}
private function assertOpen(Requisition $request, bool $evaluation): void
{
if (PurchaseAward::query()->where('requisition_id', $request->id)->exists()) {
throw ValidationException::withMessages(['award' => $evaluation
? 'As avaliações estão fechadas pela adjudicação deste processo.'
: 'As propostas estão fechadas pela adjudicação deste processo.']);
}
}
public function assertPrepare(User $actor, Requisition $request): void
{
$this->assertActor($actor, $request, false);
$this->assertOpen($request, false);
app(ConflictGuard::class)->assertParticipation($actor, $request);
}
public function assertEvaluateProcess(User $actor, Requisition $request): void
{
$this->assertActor($actor, $request, true);
$this->assertOpen($request, true);
app(ConflictGuard::class)->assertParticipation($actor, $request);
}
public function assertEvaluate(User $actor, Requisition $request, Quotation $quote): void
{
$this->assertEvaluateProcess($actor, $request);
abort_unless($quote->tenant_id === $request->tenant_id && $quote->requisition_id === $request->id, 403);
app(ConflictGuard::class)->assertProcess($request, $quote->supplier_id, $actor);
if ($quote->valid_until < today() || ! $quote->supplier?->eligible()) {
throw ValidationException::withMessages(['evaluation' => 'Proposta expirada ou fornecedor não elegível.']);
}
if ($quote->evaluations()->where('evaluator_id', $actor->id)->exists()) {
throw ValidationException::withMessages(['evaluation' => 'Já avaliou esta proposta.']);
}
}
/** @param callable():void $check */
private function eligible(callable $check): bool
{
try {
$check();
return true;
} catch (ValidationException) {
return false;
} catch (HttpException $error) {
if ($error->getStatusCode() !== 403) {
throw $error;
}
return false;
}
}
public function canPrepare(User $actor, Requisition $request): bool
{
return $this->eligible(fn () => $this->assertPrepare($actor, $request));
}
public function canEvaluateProcess(User $actor, Requisition $request): bool
{
return $this->eligible(fn () => $this->assertEvaluateProcess($actor, $request));
}
public function canEvaluate(User $actor, Requisition $request, Quotation $quote): bool
{
return $this->eligible(fn () => $this->assertEvaluate($actor, $request, $quote));
}
}