Consolida recepcoes, stock, equipamentos e identidade SGC

This commit is contained in:
2026-10-28 13:54:53 +02:00
parent eb1c8b8074
commit 75ffdb395c
76 changed files with 6933 additions and 152 deletions
+14 -4
View File
@@ -3,12 +3,14 @@
namespace App\Services;
use App\Enums\PurchasingRole;
use App\Models\Equipment;
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\SparePart;
use App\Models\Supplier;
use App\Models\Tenant;
use App\Models\User;
@@ -30,8 +32,8 @@ class PurchaseWorkflow
throw ValidationException::withMessages(['award' => 'Este processo já tem uma adjudicação. Não é permitida uma segunda selecção.']);
}
$rules = $this->rules($requisition);
$this->sourceApproved($requisition, $rules);
$this->supportedItems($requisition);
$this->sourceApproved($requisition, $rules);
$quote = Quotation::query()->where('requisition_id', $requisition->id)->lockForUpdate()->findOrFail($data['quotation_id']);
$result = $this->checkComparison($user, $requisition, $quote);
$award = PurchaseAward::query()->create([
@@ -113,6 +115,7 @@ class PurchaseWorkflow
$lines[] = [
...$item->only(['requisition_item_id', 'description', 'quantity', 'unit_price_minor', 'tax_rate', 'subtotal_minor', 'tax_minor', 'total_minor']),
...$source->only(['unit', 'category', 'purchase_type']), 'tenant_id' => $user->tenant_id,
'equipment_id' => $source->equipment_id, 'spare_part_id' => $source->spare_part_id,
];
}
$validUntil = $quote->attributesToArray()['valid_until'] ?? null;
@@ -134,6 +137,7 @@ class PurchaseWorkflow
'estimate_approvals' => $requisition->steps()->get()->toArray(),
'final_approvals' => $award->approvals()->get()->toArray(),
'decision_matrix' => $this->comparison->inspect($requisition)['rows'], 'items' => $lines,
'equipment_context' => $requisition->items()->whereNotNull('equipment_id')->get()->map(fn ($line) => ['requisition_item_id' => $line->id, 'equipment' => Equipment::query()->findOrFail($line->equipment_id)->toArray(), 'spare_part' => SparePart::query()->findOrFail($line->spare_part_id)->toArray(), 'downtime_started_at' => $line->downtime_started_at])->all(),
];
$order = PurchaseOrder::query()->create([
'tenant_id' => $user->tenant_id, 'requisition_id' => $requisition->id, 'purchase_award_id' => $award->id,
@@ -154,8 +158,8 @@ class PurchaseWorkflow
private function currentBasis(User $user, PurchaseAward $award, Requisition $requisition): Quotation
{
$rules = $this->rules($requisition);
$this->sourceApproved($requisition, $rules);
$this->supportedItems($requisition);
$this->sourceApproved($requisition, $rules);
$quote = Quotation::query()->where('requisition_id', $requisition->id)->lockForUpdate()->findOrFail($award->quotation_id);
$result = $this->checkComparison($user, $requisition, $quote);
if ($result['fingerprint'] !== $award->comparison_hash || $award->approval_snapshot !== $rules ||
@@ -258,8 +262,14 @@ class PurchaseWorkflow
private function supportedItems(Requisition $requisition): void
{
if ($requisition->items()->whereNotIn('purchase_type', ['consumable', 'service', 'other'])->exists()) {
throw ValidationException::withMessages(['purchase_type' => 'Equipamento e peças exigem regras adicionais ainda não suportadas. A emissão para estes tipos está bloqueada.']);
if ($requisition->items()->where('purchase_type', 'equipment')->exists()) {
throw ValidationException::withMessages(['purchase_type' => 'O limite próprio de investimento e a sua regra de aplicação ainda não foram definidos pela empresa. A validação técnica e Finanças não dispensam essa decisão; adjudicação e emissão de equipamento bloqueadas.']);
}
foreach ($requisition->items()->get() as $line) {
if (! in_array($line->purchase_type, ['consumable', 'service', 'other', 'spare_part']) || ($line->purchase_type === 'spare_part' && (! $line->equipment_id || ! $line->spare_part_id))) {
throw ValidationException::withMessages(['purchase_type' => 'Tipo não suportado ou peça sem equipamento/artigo associado. Complete o cadastro antes da compra.']);
}
app(EquipmentWorkflow::class)->line($line->toArray(), $requisition->department_id, $requisition->priority);
}
}
}