This commit is contained in:
2026-11-03 11:51:44 +02:00
parent badaada043
commit e753f7a0a4
309 changed files with 14537 additions and 529 deletions
+29 -3
View File
@@ -16,6 +16,27 @@ class SupplierEvaluationWorkflow
{
public const ROLES = [PurchasingRole::Admin, PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead];
public function canPrepare(User $actor): bool
{
return $actor->tenant_id !== null && $actor->hasFunctions(self::ROLES)
&& ! app(OrganizationAccess::class)->maintenanceOnly($actor)
&& (! $actor->departmentScoped() || $actor->department_id !== null);
}
public function canAct(User $actor, SupplierEvaluation $evaluation, string $action): bool
{
if (! app(SupplierImprovementWorkflow::class)->canParticipate($actor, $evaluation)) {
return false;
}
return match ($action) {
'edit' => in_array($evaluation->status, ['draft', 'returned'], true),
'review' => $evaluation->status === 'review' && ! in_array($actor->id, $evaluation->contributors, true),
'revise' => $evaluation->status === 'approved',
default => false,
};
}
public function reader(User $actor): User
{
abort_unless(auth()->id() === $actor->id && $actor->tenant_id !== null, 403);
@@ -67,7 +88,10 @@ class SupplierEvaluationWorkflow
{
app(ConflictGuard::class)->lockCompany($actor);
return app(CurrentActor::class)->lock($actor, self::ROLES);
$actor = app(CurrentActor::class)->lock($actor, self::ROLES);
abort_if(app(OrganizationAccess::class)->maintenanceOnly($actor), 403);
return $actor;
}
private function participating(User $actor, int $supplierId): void
@@ -92,7 +116,7 @@ class SupplierEvaluationWorkflow
{
return DB::transaction(function () use ($actor, $supplierId, $operation, $payload) {
$actor = $this->actor($actor);
abort_if($actor->departmentScoped() && $actor->department_id === null, 403, 'O perfil de chefia precisa do departamento correspondente.');
abort_unless($this->canPrepare($actor), 403, 'A preparação exige um perfil operacional e o departamento correspondente.');
$supplier = Supplier::query()->findOrFail($supplierId);
$this->participating($actor, $supplier->id);
$payload = $this->normalise($actor, $supplier, $payload);
@@ -122,6 +146,7 @@ class SupplierEvaluationWorkflow
$evaluation = $this->locked($actor, $id, $version);
$this->participating($actor, $evaluation->supplier_id);
abort_unless(in_array($evaluation->status, ['draft', 'returned'], true), 409, 'A avaliação não está aberta para edição.');
abort_unless($this->canAct($actor, $evaluation, 'edit'), 403);
$payload = $this->normalise($actor, Supplier::query()->findOrFail($evaluation->supplier_id), $payload);
if ($payload === $evaluation->payload) {
return $evaluation;
@@ -186,6 +211,7 @@ class SupplierEvaluationWorkflow
$this->normalise($actor, Supplier::query()->findOrFail($evaluation->supplier_id), $evaluation->payload);
if ($action === 'submit') {
abort_unless(in_array($evaluation->status, ['draft', 'returned'], true), 409);
abort_unless($this->canAct($actor, $evaluation, 'edit'), 403);
$this->complete($evaluation->payload, false);
$evaluation->fill(['status' => 'review', 'submitted_by' => $actor->id, 'contributors' => array_values(array_unique([...$evaluation->contributors, $actor->id])), 'reviewed_by' => null, 'reviewed_at' => null, 'review_reason' => null]);
} else {
@@ -193,7 +219,7 @@ class SupplierEvaluationWorkflow
if ($reason === null || mb_strlen(trim($reason)) < 10) {
throw ValidationException::withMessages(['reason' => 'Fundamente a decisão de revisão.']);
}
abort_if(in_array($actor->id, $evaluation->contributors, true), 403, 'A conferência deve ser realizada por outra pessoa que não elaborou esta revisão.');
abort_unless($this->canAct($actor, $evaluation, 'review'), 403, 'A conferência deve ser realizada por outra pessoa que não elaborou esta revisão.');
if ($action === 'approve') {
$this->complete($evaluation->payload, true);
}