$this->persistDraft($user, $data, $existing))); } /** @param array $data */ public function save(User $user, array $data, ?Requisition $existing = null): Requisition { $key = $data['operation_key'] ?? null; $version = isset($data['process_version']) ? (int) $data['process_version'] : null; unset($data['operation_key'], $data['process_version']); $result = $this->execute($user, 'save', $existing?->id, $version, $key, $data); return Requisition::query()->whereKey($result['id'])->firstOrFail(); } /** * @param array $input * @return array */ private function execute(User $user, string $action, ?int $id, ?int $version, ?string $key, array $input = []): array { try { return $this->process($user)->command($action, $id, $version, $key, $input); } catch (RequisitionFailure $error) { abort_if($error->status !== 422, $error->status, $error->getMessage()); throw ValidationException::withMessages(['requisition' => $error->getMessage()]); } } /** @param array $data */ private function persistDraft(User $user, array $data, ?Requisition $existing = null, ?SupplyRecord $automaticRule = null): Requisition { return DB::transaction(function () use ($user, $data, $existing, $automaticRule) { app(ConflictGuard::class)->lockCompany($user); $user = app(CurrentActor::class)->lock($user); $executor = $user; if ($automaticRule !== null) { abort_unless($existing === null, 403); $user = app(AutomaticPurchases::class)->requester($executor, $automaticRule); } else { abort_unless(app(OrganizationAccess::class)->canRequest($user), 403); } if (! $user->department_id || ! CostCenter::query()->whereKey($data['cost_center_id'])->where('department_id', $user->department_id)->exists()) { throw ValidationException::withMessages(['cost_center_id' => 'O departamento ou centro de custo mudou. Actualize o pedido antes de guardar.']); } $requisition = $existing ? Requisition::query()->lockForUpdate()->findOrFail($existing->id) : new Requisition; if ($existing) { abort_unless($requisition->requester_id === $user->id && $requisition->status === 'draft', 403); app(ConflictGuard::class)->assertParticipation($user, $requisition); } $before = $existing ? $requisition->load('items')->toArray() : null; $items = []; $subtotal = 0; $tax = 0; /** @var list $lines */ $lines = $data['items']; $oldLines = $existing ? $requisition->items()->get()->keyBy('id')->all() : []; foreach ($lines as $line) { $line = app(RequisitionCatalogue::class)->line($user, $line, $oldLines); app(EquipmentWorkflow::class)->line($line, (int) $user->department_id, $data['priority']); $calculation = InternalRequisitions::line($line['quantity'], $line['unit_price'], $line['tax_rate']); $price = $calculation['unit_price_minor']; $lineSubtotal = $calculation['subtotal_minor']; $lineTax = $calculation['tax_minor']; $items[] = [ 'tenant_id' => $user->tenant_id, ...(! empty($line['catalogue_entry_id']) ? ['catalogue_entry_id' => $line['catalogue_entry_id'], 'catalogue_snapshot' => $line['catalogue_snapshot']] : []), 'description' => $line['description'], 'category' => $line['category'], 'purchase_type' => $line['purchase_type'], 'quantity' => $line['quantity'], 'unit' => $line['unit'], 'equipment_id' => $line['equipment_id'] ?? null, 'spare_part_id' => $line['spare_part_id'] ?? null, 'production_area' => $line['production_area'] ?? null, 'downtime_started_at' => $line['downtime_started_at'] ?? null, 'unit_price_minor' => $price, 'tax_rate' => $line['tax_rate'], 'subtotal_minor' => $lineSubtotal, 'tax_minor' => $lineTax, 'total_minor' => $lineSubtotal + $lineTax, ]; $subtotal += $lineSubtotal; $tax += $lineTax; if ($subtotal + $tax > 100000000000000) { throw ValidationException::withMessages(['items' => 'O total da requisição ultrapassa o limite suportado de 1.000.000.000.000 MZN.']); } } $requisition->fill([ 'tenant_id' => $user->tenant_id, 'requester_id' => $user->id, 'department_id' => $user->department_id, 'cost_center_id' => $data['cost_center_id'], 'priority' => $data['priority'], 'needed_by' => $data['needed_by'], 'justification' => $data['justification'], 'subtotal_minor' => $subtotal, 'tax_minor' => $tax, 'total_minor' => $subtotal + $tax, ...($existing ? [] : ['comparison_policy' => app(ComparisonPolicy::class)->company(Tenant::query()->findOrFail($user->tenant_id))]), ...(Schema::hasColumn('requisitions', 'process_version') ? ['process_version' => $existing ? (int) $requisition->getAttribute('process_version') + 1 : 1] : []), ])->save(); if (! $existing) { $requisition->update(['number' => sprintf('REQ-%s-%06d', now()->format('Y'), $requisition->id)]); } $requisition->items()->delete(); $requisition->items()->createMany($items); $requisition->load('items'); $this->audit($executor, $automaticRule !== null ? 'requisition.automatically_created' : ($existing ? 'requisition.updated' : 'requisition.created'), $requisition, $before, $requisition->toArray() + ($automaticRule === null ? [] : ['automatic_rule_id' => $automaticRule->id, 'designated_requester_id' => $user->id])); return $requisition; }); } /** Only an independently activated rule may create a draft on behalf of its designated requester. */ public function automaticDraft(User $executor, SupplyRecord $rule, array $data): Requisition { $process = new InternalRequisitions(new InternalRequisitionPort($executor, fn (array $input, ?Requisition $existing) => $this->persistDraft($executor, $input, $existing, $rule), $rule)); $result = $process->command('save', null, null, null, $data); return Requisition::query()->findOrFail($result['id']); } public function submit(User $user, Requisition $requisition, ?string $operationKey = null, ?int $version = null): void { $this->execute($user, 'submit', $requisition->id, $version, $operationKey); } public function decide(User $user, Requisition $requisition, string $decision, ?string $comment, ?string $operationKey = null, ?int $version = null): void { $this->execute($user, 'decide', $requisition->id, $version, $operationKey, ['decision' => $decision, 'comment' => $comment]); } /** @param array|null $before * @param array $after */ public function audit(User $user, string $action, ?Requisition $requisition, ?array $before, array $after): void { AuditLog::query()->create([ 'tenant_id' => $user->tenant_id, 'user_id' => $user->id, 'requisition_id' => $requisition?->id, 'action' => $action, 'ip_address' => request()->ip(), 'before' => $before, 'after' => $after, ]); } }