@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\ConflictDeclaration;
|
||||
use App\Models\ProposalEvaluation;
|
||||
use App\Models\PurchaseAward;
|
||||
use App\Models\PurchaseAwardApproval;
|
||||
use App\Models\Quotation;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ConflictGuard
|
||||
{
|
||||
/** Call first inside a transaction, before locking a requisition or supplier. */
|
||||
public function lockCompany(User $user): void
|
||||
{
|
||||
Tenant::query()->whereKey($user->tenant_id)->lockForUpdate()->firstOrFail();
|
||||
}
|
||||
|
||||
public function hasConflict(int $userId, ?int $supplierId, ?int $requisitionId): bool
|
||||
{
|
||||
return ConflictDeclaration::query()->where('user_id', $userId)->where('has_conflict', true)
|
||||
->where(function ($scopes) use ($supplierId, $requisitionId) {
|
||||
// Supplier-wide statements apply only once that supplier is known.
|
||||
$scopes->where(function ($supplierScope) use ($supplierId) {
|
||||
$supplierScope->whereNull('requisition_id')->where('supplier_id', $supplierId ?? 0);
|
||||
});
|
||||
if ($requisitionId !== null) {
|
||||
$scopes->orWhere(function ($processScope) use ($supplierId, $requisitionId) {
|
||||
$processScope->where('requisition_id', $requisitionId)->where(function ($supplier) use ($supplierId) {
|
||||
$supplier->whereNull('supplier_id');
|
||||
if ($supplierId !== null) {
|
||||
$supplier->orWhere('supplier_id', $supplierId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
})->exists();
|
||||
}
|
||||
|
||||
public function participationBlocked(User $user, Requisition $requisition): bool
|
||||
{
|
||||
if ($this->hasConflict($user->id, null, $requisition->id)) {
|
||||
return true;
|
||||
}
|
||||
foreach ($this->participants($requisition) as $userId) {
|
||||
if ($this->hasConflict($userId, null, $requisition->id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
foreach (Quotation::query()->where('requisition_id', $requisition->id)->pluck('supplier_id') as $supplierId) {
|
||||
if ($this->processBlocked($requisition, $supplierId, $user)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function assertParticipation(User $user, Requisition $requisition): void
|
||||
{
|
||||
if ($this->participationBlocked($user, $requisition)) {
|
||||
throw ValidationException::withMessages(['conflict' => 'Existe um conflito de interesses activo que impede a sua participação neste processo.']);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
private function participants(Requisition $requisition): array
|
||||
{
|
||||
$quotes = Quotation::query()->where('requisition_id', $requisition->id);
|
||||
$awards = PurchaseAward::query()->where('requisition_id', $requisition->id);
|
||||
|
||||
$participants = collect([$requisition->requester_id])
|
||||
->merge($requisition->steps()->whereNotNull('approver_id')->pluck('approver_id'))
|
||||
->merge((clone $quotes)->pluck('created_by'))
|
||||
->merge(ProposalEvaluation::query()->whereIn('quotation_id', (clone $quotes)->select('id'))->pluck('evaluator_id'))
|
||||
->merge((clone $awards)->pluck('created_by'))
|
||||
->merge(PurchaseAwardApproval::query()->whereIn('purchase_award_id', (clone $awards)->select('id'))->whereNotNull('approver_id')->pluck('approver_id'))
|
||||
->merge(AuditLog::query()->where('requisition_id', $requisition->id)->whereIn('action', [
|
||||
'requisition.created', 'requisition.updated', 'requisition.submitted',
|
||||
'approval.approved', 'approval.rejected', 'quotation.created', 'quotation.evaluated',
|
||||
'award.created', 'award.approved', 'award.rejected', 'purchase_order.issued',
|
||||
])->pluck('user_id'))
|
||||
->unique()->values()->all();
|
||||
$ids = [];
|
||||
foreach ($participants as $participant) {
|
||||
$id = filter_var($participant, FILTER_VALIDATE_INT);
|
||||
if ($id === false) {
|
||||
throw new \LogicException('Identificador inválido no histórico de intervenientes.');
|
||||
}
|
||||
$ids[] = $id;
|
||||
}
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
public function processBlocked(Requisition $requisition, int $supplierId, ?User $actor = null): bool
|
||||
{
|
||||
$participants = $this->participants($requisition);
|
||||
if ($actor !== null) {
|
||||
$participants[] = $actor->id;
|
||||
}
|
||||
foreach (array_unique($participants) as $userId) {
|
||||
if ($this->hasConflict($userId, $supplierId, $requisition->id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function assertProcess(Requisition $requisition, int $supplierId, User $actor): void
|
||||
{
|
||||
if ($this->processBlocked($requisition, $supplierId, $actor)) {
|
||||
throw ValidationException::withMessages(['conflict' => 'Existe um conflito de interesses activo de um interveniente neste processo com este fornecedor. A operação está impedida, mesmo com declaração de ausência de conflito.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user