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
@@ -24,6 +24,7 @@ class DocumentaryQualificationWorkflow
/** Call after the company/actor locks; coordinates with award and quote transactions. */
private function process(User $actor, int $requisitionId): Requisition
{
abort_if(app(OrganizationAccess::class)->maintenanceOnly($actor), 403);
$request = Requisition::query()->visibleTo($actor)->lockForUpdate()->findOrFail($requisitionId);
if ($request->status !== 'approved' || PurchaseAward::query()->where('requisition_id', $request->id)->exists()) {
$this->invalid('qualification', 'Registe a análise num pedido aprovado, antes da adjudicação. As versões anteriores continuam disponíveis.');
@@ -36,6 +37,36 @@ class DocumentaryQualificationWorkflow
return $request;
}
private 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()
&& ! app(ConflictGuard::class)->participationBlocked($actor, $request);
}
public function canDefine(User $actor, Requisition $request): bool
{
return $actor->hasFunctions([PurchasingRole::Procurement]) && $this->canParticipate($actor, $request)
&& Quotation::query()->where('requisition_id', $request->id)->exists();
}
public function currentSet(Requisition $request, QualificationSet $set): bool
{
$latest = QualificationSet::query()->where('requisition_id', $request->id)->latest('revision')->first();
$opening = app(ProposalOpeningWorkflow::class);
return $set->tenant_id === $request->tenant_id && $set->requisition_id === $request->id && $latest?->id === $set->id
&& $set->basis_hash === $opening->basis($opening->proposals($request));
}
public function canAssess(User $actor, Requisition $request, QualificationSet $set): bool
{
return $actor->id !== $request->requester_id && app(ProposalComparison::class)->eligibleRole($actor, $request)
&& $this->canParticipate($actor, $request) && $this->currentSet($request, $set);
}
/** @param array<string,mixed> $data */
public function define(User $actor, Requisition $request, array $data): QualificationSet
{
@@ -80,11 +111,10 @@ class DocumentaryQualificationWorkflow
app(ConflictGuard::class)->lockCompany($actor);
$actor = app(CurrentActor::class)->lock($actor, [PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]);
$request = $this->process($actor, $set->requisition_id);
abort_unless(app(ProposalComparison::class)->eligibleRole($actor, $request), 403);
abort_if($actor->id === $request->requester_id, 403);
$set = QualificationSet::query()->lockForUpdate()->findOrFail($set->id);
$latestSet = QualificationSet::query()->where('requisition_id', $request->id)->latest('revision')->firstOrFail();
$opening = app(ProposalOpeningWorkflow::class);
if ($set->id !== $latestSet->id || $set->basis_hash !== $opening->basis($opening->proposals($request))) {
if (! $this->currentSet($request, $set)) {
$this->invalid('qualification', 'Os requisitos ou a base mudaram. As análises anteriores são históricas; registe uma versão actual dos requisitos.');
}
$quote = Quotation::query()->where('requisition_id', $request->id)->whereKey($data['quotation_id'])->firstOrFail();