This commit is contained in:
2026-11-03 11:51:44 +02:00
parent badaada043
commit e753f7a0a4
309 changed files with 14537 additions and 529 deletions
+42 -5
View File
@@ -21,6 +21,46 @@ use Illuminate\Validation\ValidationException;
class SupplySuggestions
{
private function independentReviewer(User $actor, SupplyRecord $record): bool
{
return ! in_array($actor->id, $record->snapshot['contributors'], true);
}
/** Participation only: approval/conversion still revalidate the frozen sources. */
public function canAct(User $actor, SupplyRecord $record, string $action): bool
{
$permission = match ($action) {
'approve', 'return', 'close' => 'supply_suggest_review',
'convert' => 'supply_suggest_convert',
'cancel' => 'supply_suggest_prepare',
default => null,
};
if ($record->kind !== 'suggestion' || $actor->tenant_id !== $record->tenant_id || $permission === null
|| ! app(SupplyPlanning::class)->allows($actor, $permission)) {
return false;
}
$latest = app(SupplyPlanning::class)->latest('suggestion')->where('record_key', $record->record_key)->first();
if ($latest?->id !== $record->id || (in_array($action, ['approve', 'return', 'close'], true) && ! $this->independentReviewer($actor, $record))) {
return false;
}
return match ($action) {
'approve', 'cancel' => in_array($record->state, ['draft', 'returned'], true),
'return' => in_array($record->state, ['draft', 'approved'], true),
'convert' => $record->state === 'approved' && ($record->snapshot['reviewed_by'] ?? null) !== $actor->id && app(OrganizationAccess::class)->canRequest($actor),
'close' => $record->state === 'converted' && $this->completionReady($record),
};
}
private function completionReady(SupplyRecord $record): bool
{
$request = Requisition::query()->whereKey($record->data['requisition_id'])->firstOrFail();
$orders = PurchaseOrder::query()->where('requisition_id', $request->id)->get();
$complete = $orders->isNotEmpty() && $orders->every(fn ($order) => app(ReceiptWorkflow::class)->progress($order)['state'] === 'complete');
return $request->status === 'rejected' || $complete;
}
/** @param array<string,mixed> $input */
public function save(User $actor, array $input): SupplyRecord
{
@@ -90,7 +130,7 @@ class SupplySuggestions
$snapshot = $old->snapshot;
$state = $old->state;
if (in_array($v['action'], ['approve', 'return', 'close'], true)) {
$this->require(! in_array($actor->id, $snapshot['contributors'], true), 'action', 'A conferência exige independência de todos os preparadores.');
$this->require($this->independentReviewer($actor, $old), 'action', 'A conferência exige independência de todos os preparadores.');
}
if ($v['action'] === 'approve') {
$this->require(in_array($state, ['draft', 'returned'], true), 'action', 'A sugestão não está disponível para decisão.');
@@ -128,10 +168,7 @@ class SupplySuggestions
$state = 'converted';
} elseif ($v['action'] === 'close') {
$this->require($state === 'converted', 'action', 'Apenas uma sugestão convertida pode encerrar acompanhamento.');
$request = Requisition::query()->whereKey($data['requisition_id'])->firstOrFail();
$orders = PurchaseOrder::query()->where('requisition_id', $request->id)->get();
$complete = $orders->isNotEmpty() && $orders->every(fn ($order) => app(ReceiptWorkflow::class)->progress($order)['state'] === 'complete');
$this->require($request->status === 'rejected' || $complete, 'action', 'A necessidade continua em acompanhamento. Aguarde rejeição da requisição ou recepção completa da ordem.');
$this->require($this->completionReady($old), 'action', 'A necessidade continua em acompanhamento. Aguarde rejeição da requisição ou recepção completa da ordem.');
$state = 'closed';
}
if ($v['action'] === 'approve') {