@@ -3,10 +3,13 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\PurchasingRole;
|
||||
use App\Models\ProposalEvaluation;
|
||||
use App\Models\PurchaseAward;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quotation;
|
||||
use App\Models\QuotationItem;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -112,6 +115,11 @@ class PurchaseWorkflow
|
||||
...$source->only(['unit', 'category', 'purchase_type']), 'tenant_id' => $user->tenant_id,
|
||||
];
|
||||
}
|
||||
$validUntil = $quote->attributesToArray()['valid_until'] ?? null;
|
||||
$deliveryDate = $award->attributesToArray()['delivery_date'] ?? null;
|
||||
if (! is_string($validUntil) || ! is_string($deliveryDate)) {
|
||||
throw ValidationException::withMessages(['delivery_date' => 'As datas da proposta e da entrega devem estar completas antes da emissão.']);
|
||||
}
|
||||
$snapshot = [
|
||||
'development_only' => true, 'budget_checked' => false,
|
||||
'company' => Tenant::query()->findOrFail($user->tenant_id)->only(['id', 'name']),
|
||||
@@ -120,12 +128,12 @@ class PurchaseWorkflow
|
||||
'cost_center' => $requisition->costCenter->only(['id', 'code', 'name']),
|
||||
'requester' => $requisition->requester?->only(['id', 'name']),
|
||||
'supplier' => $supplier->only(['id', 'name', 'nuit', 'contact', 'email', 'phone', 'address']),
|
||||
'quotation' => $quote->only(['id', 'reference', 'currency', 'payment_terms', 'delivery_days', 'valid_until']),
|
||||
'award' => $award->only(['id', 'justification', 'delivery_date', 'delivery_location']),
|
||||
'quotation' => [...$quote->only(['id', 'reference', 'currency', 'payment_terms', 'delivery_days']), 'valid_until' => $validUntil],
|
||||
'award' => [...$award->only(['id', 'justification', 'delivery_location']), 'delivery_date' => $deliveryDate],
|
||||
'issuer' => $user->only(['id', 'name']), 'approval_rules' => $award->approval_snapshot,
|
||||
'estimate_approvals' => $requisition->steps()->get()->toArray(),
|
||||
'final_approvals' => $award->approvals()->get()->toArray(),
|
||||
'decision_matrix' => $award->decision_matrix, 'items' => $lines,
|
||||
'decision_matrix' => $this->comparison->inspect($requisition)['rows'], 'items' => $lines,
|
||||
];
|
||||
$order = PurchaseOrder::query()->create([
|
||||
'tenant_id' => $user->tenant_id, 'requisition_id' => $requisition->id, 'purchase_award_id' => $award->id,
|
||||
@@ -161,6 +169,13 @@ class PurchaseWorkflow
|
||||
/** @return array{rows: list<array<string, mixed>>, ready: bool, top_ids: list<int>, fingerprint: string} */
|
||||
private function checkComparison(User $user, Requisition $requisition, Quotation $quote): array
|
||||
{
|
||||
// Also coordinate with supplier reviews, which lock supplier rows without the company mutex.
|
||||
$quotes = Quotation::query()->where('requisition_id', $requisition->id)->orderBy('id')->lockForUpdate()->get(['id', 'supplier_id']);
|
||||
Supplier::query()->whereIn('id', $quotes->pluck('supplier_id'))->orderBy('id')->lockForUpdate()->get();
|
||||
QuotationItem::query()->whereIn('quotation_id', $quotes->pluck('id'))->orderBy('id')->lockForUpdate()->get();
|
||||
ProposalEvaluation::query()->whereIn('quotation_id', $quotes->pluck('id'))->orderBy('id')->lockForUpdate()->get();
|
||||
User::query()->where('tenant_id', $user->tenant_id)->orderBy('id')->lockForUpdate()->get();
|
||||
$requisition->items()->orderBy('id')->lockForUpdate()->get();
|
||||
$this->guard->assertParticipation($user, $requisition);
|
||||
$this->guard->assertProcess($requisition, $quote->supplier_id, $user);
|
||||
$result = $this->comparison->inspect($requisition);
|
||||
@@ -213,7 +228,7 @@ class PurchaseWorkflow
|
||||
throw ValidationException::withMessages(['approvals' => 'A requisição deve ter todas as aprovações obrigatórias da estimativa, preservadas no histórico.']);
|
||||
}
|
||||
foreach ($steps as $index => $step) {
|
||||
if ($step->decision !== 'approved' || $step->role !== $roles[$index] || $step->approver_id === null || in_array($step->approver_id, $seen, true) ||
|
||||
if ($step->decision !== 'approved' || $step->decided_at === null || $step->role !== $roles[$index] || $step->approver_id === null || in_array($step->approver_id, $seen, true) ||
|
||||
! User::query()->where('tenant_id', $requisition->tenant_id)->whereKey($step->approver_id)->exists()) {
|
||||
throw ValidationException::withMessages(['approvals' => 'O histórico de aprovação da estimativa está incompleto ou inconsistente.']);
|
||||
}
|
||||
@@ -232,7 +247,7 @@ class PurchaseWorkflow
|
||||
}
|
||||
foreach ($steps as $index => $step) {
|
||||
$user = User::query()->where('tenant_id', $requisition->tenant_id)->find($step->approver_id);
|
||||
if (! $user || $step->decision !== 'approved' || $step->role !== $roles[$index] || $user->purchasing_role->value !== $step->role ||
|
||||
if (! $user || $step->decision !== 'approved' || $step->decided_at === null || $step->role !== $roles[$index] || $user->purchasing_role->value !== $step->role ||
|
||||
($step->role === 'department_head' && $user->department_id !== $requisition->department_id) || in_array($user->id, $seen, true)) {
|
||||
throw ValidationException::withMessages(['approvals' => 'As aprovações do valor final devem continuar elegíveis e ser de pessoas distintas.']);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user