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 */ 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', 'opening.recorded', 'qualification.defined', 'qualification.assessed', 'admission.adopted', 'admission.proposed', 'admission.reviewed', 'recommendation.adopted', 'recommendation.proposed', 'recommendation.reviewed', 'equipment.technical_approved', 'equipment.technical_rejected', 'invoice.created', 'invoice.validated', 'payment.registered', 'payment.confirmed', 'payment.rejected', 'cancellation.requested', 'cancellation.approved', 'cancellation.rejected', ])->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.']); } } }