actorId()) { try { $port->authorize('save', $record); $actions = ['edit', 'submit']; } catch (HttpException $e) { if (! in_array($e->getStatusCode(), [403, 404], true)) { throw $e; } } } elseif (($next = InternalRequisitions::nextStep($record, $port->actorId())) !== null) { try { $port->authorize('step:'.$next['role'], $record); $actions = ['approved']; $s = $port->scope(); if (app(Permissions::class)->has($port->actorId(), $s['tenant_id'], $s['company_id'], 'purchasing_budget.record', $s['unit_id'])) { $actions[] = 'rejected'; } } catch (HttpException $e) { if (! in_array($e->getStatusCode(), [403, 404], true)) { throw $e; } } } return $record + ['allowed_actions' => $actions]; } private function port(Request $request, string $key = ''): Port { $actor = $request->user(); abort_unless($actor instanceof User, 403); return new Port($actor, $key); } public function index(Request $request) { $port = $this->port($request); $data = $port->atomic(function () use ($port) { if (! $port->query('policies')->exists()) { $port->authorize('configure', null); return ['context' => 'procurement.internal', 'scope' => $port->scope(), 'records' => [], 'centres' => [], 'configurationSources' => $port->configurationSources(), 'policy' => null, 'permissions' => ['configure' => true, 'prepare' => false]]; } $port->authorize('view', null); $records = []; foreach ($port->query('requests')->orderByDesc('id')->limit(100)->pluck('id') as $id) { $record = $port->load((int) $id); try { $port->authorize('view', $record); $records[] = $this->project($port, $record); } catch (HttpException $e) { if ($e->getStatusCode() !== 404) { throw $e; } } } $policy = $port->setup(); return ['context' => 'procurement.internal', 'scope' => $port->scope(), 'records' => $records, 'centres' => $port->allowedCentres(), 'configurationSources' => $port->configurationSources(), 'policy' => $port->has('configure') ? $policy : array_intersect_key($policy, array_flip(['revision'])), 'permissions' => array_combine(['prepare', 'configure', 'department_head', 'procurement', 'finance'], array_map(fn ($p) => $port->has($p), ['prepare', 'configure', 'approve.department_head', 'approve.procurement', 'approve.finance']))]; }); return response()->json($data)->header('Cache-Control', 'private, no-store'); } public function show(Request $request, int $id) { $port = $this->port($request); $result = $port->atomic(function () use ($port, $id) { $record = $port->load($id); $port->authorize('view', $record); return ['record' => $this->project($port, $record), 'history' => $port->query('events')->where('request_id', $id)->orderBy('version')->get()]; }); return response()->json($result)->header('Cache-Control', 'private, no-store'); } public function command(Request $request, string $action, ?int $id = null) { $meta = $request->validate(['operation_key' => 'required|uuid', 'version' => ($id === null ? 'nullable' : 'required').'|integer|min:1']); $port = $this->port($request, $meta['operation_key']); $input = $request->except(['operation_key', 'version']); if ($action === 'submit') { abort_unless($input === [], 422, 'Campo protegido.'); } if ($action === 'decide') { $input = Validator::make($input, ['decision' => 'required|in:approved,rejected', 'comment' => 'nullable|required_if:decision,rejected|string|max:2000'])->validate(); } try { $result = (new InternalRequisitions($port))->command($action, $id, $meta['version'] ?? null, $meta['operation_key'], $input); } catch (RequisitionFailure $e) { return response()->json(['message' => $e->getMessage()], $e->status); } return response()->json($result)->header('Cache-Control', 'private, no-store'); } public function configure(Request $request) { $port = $this->port($request); $data = $request->validate(['version' => 'required|integer|min:0', 'reason' => 'required|string|min:10|max:4000', 'enabled' => 'required|boolean', 'budget_required' => 'required|boolean', 'first_limit_minor' => 'required|integer|min:0|max:100000000000000', 'second_limit_minor' => 'required|integer|min:0|max:100000000000000', 'approval_includes_tax' => 'required|boolean', 'centres' => 'required|array|min:1', 'centres.*.id' => 'required|integer|distinct|min:1', 'centres.*.department_ref' => 'required|string|max:100', 'members' => 'required|array|min:1', 'members.*.user_id' => 'required|integer|distinct|min:1', 'members.*.department_ref' => 'required|string|max:100']); abort_if(array_diff(array_keys($request->all()), array_keys($data)) !== [], 422, 'Campo protegido.'); $port->atomic(function () use ($port, $data) { $port->authorize('configure', null); $scope = $port->scope(); $old = $port->query('policies')->lockForUpdate()->first(); abort_unless((int) ($old->version ?? 0) === $data['version'], 409, 'Política alterada.'); abort_unless($data['second_limit_minor'] >= $data['first_limit_minor'], 422, 'Limites inválidos.'); foreach ($data['centres'] as $c) { abort_unless(DB::table('cb_centres')->where($scope)->where('id', $c['id'])->exists(), 403, 'Centro não autorizado.'); } foreach ($data['members'] as $m) { abort_unless(DB::table('company_memberships')->where(['tenant_id' => $scope['tenant_id'], 'company_id' => $scope['company_id'], 'user_id' => $m['user_id']])->exists(), 403, 'Pessoa não autorizada.'); abort_unless(app(Permissions::class)->has($m['user_id'], $scope['tenant_id'], $scope['company_id'], 'unit.view', $scope['unit_id']), 403, 'Unidade não autorizada.'); } $payload = $data; unset($payload['version']); $payload['rules_provisional'] = false; $values = ['version' => $data['version'] + 1, 'actor_id' => $port->actorId(), 'payload' => json_encode($payload, JSON_THROW_ON_ERROR), 'created_at' => now()]; if ($old) { $port->query('policies')->update($values); } else { DB::table('ir_policies')->insert($scope + $values); } DB::table('ir_policy_versions')->insert($scope + $values); }); return response()->json(['configured' => true, 'version' => $data['version'] + 1]); } public function page(Request $request) { $request->headers->set('X-Purchasing-Context', 'procurement.internal'); $port = $this->port($request); $port->atomic(fn () => $port->authorize($port->query('policies')->exists() ? 'view' : 'configure', null)); $selection = $request->session()->get('company_context', []); $scope = $port->scope(); $contract = ['csrf' => csrf_token(), 'token' => $selection['token'] ?? '', 'scope' => $scope, 'context' => 'procurement.internal', 'actor_id' => $port->actorId(), 'company' => DB::table('companies')->where('id', $scope['company_id'])->value('name'), 'unit' => DB::table('units')->where('id', $scope['unit_id'])->value('name')]; ob_start(); require __DIR__.'/screen.php'; $html = ob_get_clean(); return response($html)->header('Cache-Control', 'private, no-store'); } public function script(Request $request) { return response(file_get_contents(__DIR__.'/screen.mjs'))->header('Content-Type', 'text/javascript')->header('Cache-Control', 'private, no-store'); } }