user('web'); abort_unless($user instanceof User, 403); $actor = $service->administrator($user); $ready = Schema::hasTable('organization_access'); $state = $ready ? DB::table('organization_access')->where('tenant_id', $actor->tenant_id)->first() : null; Inertia::encryptHistory(); $response = Inertia::render('company/parameters', ['ready' => $ready, 'active' => (bool) ($state->active ?? false), 'version' => $state->version ?? 1, 'workflowTasks' => app(WorkflowTasks::class)->catalogue(), 'activationPreview' => app(OrganizationActivationPreview::class)->build($actor), 'companyRules' => $this->companyRules((int) $actor->tenant_id), 'comparisonPolicy' => app(ComparisonPolicy::class)->company(Tenant::query()->findOrFail($actor->tenant_id)), 'intelligencePolicy' => app(IntelligencePolicy::class)->company(Tenant::query()->findOrFail($actor->tenant_id)), 'intelligenceReady' => Schema::hasColumn('tenants', 'intelligence_policy'), 'aiPlan' => app(AiPreparationPlan::class)->company(Tenant::query()->findOrFail($actor->tenant_id)), 'aiDomains' => AiPreparationPlan::DOMAINS, 'aiDataScopes' => AiPreparationPlan::SCOPES, 'permissions' => OrganizationAccess::PERMISSIONS, 'workContextConfiguration' => Schema::hasColumn('tenants', 'work_context_policy') ? app(WorkContext::class)->shared($request) : null, 'enabledWorkContexts' => app(WorkContext::class)->policy($actor)['enabled'] ?? [], 'profiles' => $ready ? DB::table('organization_profiles')->where('tenant_id', $actor->tenant_id)->orderBy('name')->get()->map(fn ($p) => ['id' => $p->id, 'name' => $p->name, 'permissions' => json_decode($p->permissions, true, 32, JSON_THROW_ON_ERROR), 'work_contexts' => json_decode($p->work_contexts ?? '[]', true, 32, JSON_THROW_ON_ERROR)]) : [], 'memberships' => $ready ? DB::table('organization_memberships')->where('tenant_id', $actor->tenant_id)->get(['user_id', 'profile_id', 'maintenance', 'expires_at', 'reason']) : [], 'users' => DB::table('users')->where('tenant_id', $actor->tenant_id)->orderBy('name')->get(['id', 'name', 'email', 'department_id']), 'departments' => DB::table('departments')->where('tenant_id', $actor->tenant_id)->orderBy('name')->get(['id', 'name']), 'costCenters' => DB::table('cost_centers')->where('tenant_id', $actor->tenant_id)->orderBy('code')->get(['id', 'code', 'name']), 'notice' => $request->session()->get('organization_notice')])->toResponse($request); $response->headers->set('Cache-Control', 'private, no-store, max-age=0'); return $response; }); } public function command(Request $request, string $command, OrganizationConfiguration $service): Response { $user = $request->user('web'); abort_unless($user instanceof User, 403); $actor = $service->administrator($user); abort_unless(Schema::hasTable('organization_access'), 503, 'A instalação da parametrização ainda não está concluída.'); $rules = ['version' => 'required|integer|min:1', 'tenant_id' => 'prohibited', 'purchasing_role' => 'prohibited']; if ($command === 'profile') { $rules += ['work_contexts' => 'sometimes|array', 'work_contexts.*' => ['string', 'distinct', Rule::in(array_keys(WorkContext::OPTIONS))]]; $rules += ['id' => 'nullable|integer|min:1', 'name' => ['required', 'string', 'max:120', Rule::unique('organization_profiles')->where('tenant_id', $actor->tenant_id)->ignore($request->integer('id'))], 'permissions' => 'present|array', 'permissions.*' => ['string', 'distinct', Rule::in(array_keys(OrganizationAccess::PERMISSIONS))]]; } elseif ($command === 'work_contexts') { $rules += ['enabled' => 'present|array', 'enabled.*' => ['string', 'distinct', Rule::in(array_keys(WorkContext::OPTIONS))], 'reason' => 'required|string|min:10|max:4000', 'confirm' => 'accepted']; } elseif ($command === 'user') { $rules += ['name' => 'required|string|max:100', 'email' => 'required|email|max:255|unique:users', 'password' => 'required|string|min:12|confirmed', 'department_id' => 'required|integer|min:1', 'profile_ids' => 'present|array', 'profile_ids.*' => 'integer|distinct|min:1', 'reason' => 'required|string|min:10|max:4000']; } elseif ($command === 'assign') { $rules += ['user_id' => 'required|integer|min:1', 'profile_ids' => 'present|array', 'profile_ids.*' => 'integer|distinct|min:1', 'maintenance' => 'required|boolean', 'expires_at' => 'nullable|date|after:now', 'reason' => 'required|string|min:10|max:4000']; } elseif ($command === 'rules') { $rules += ['first_limit' => ['required', 'string', 'regex:/^\d{1,12}(\.\d{1,2})?$/D'], 'second_limit' => ['required', 'string', 'regex:/^\d{1,12}(\.\d{1,2})?$/D'], 'includes_tax' => 'required|boolean', 'reason' => 'required|string|min:10|max:4000']; } elseif ($command === 'comparison') { $rules += ['minimum_proposals' => 'required|integer|between:1,100', 'minimum_evaluators' => 'required|integer|between:1,100', 'weights' => 'required|array:price,quality,delivery,history,payment', 'reason' => 'required|string|min:10|max:4000']; foreach (array_keys(ComparisonPolicy::DEFAULTS['weights']) as $criterion) { $rules['weights.'.$criterion] = 'required|integer|between:0,100'; } } elseif ($command === 'ai_plan') { $rules += app(AiPreparationPlan::class)->rules() + ['reason' => 'required|string|min:10|max:4000']; } elseif ($command === 'intelligence') { $rules += ['procurement_brief' => 'required|boolean', 'people_brief' => 'required|boolean', 'reason' => 'required|string|min:10|max:4000', 'api_key' => 'prohibited', 'external_processing_enabled' => 'prohibited']; } elseif ($command === 'tax') { $rules += ['default_tax_rate' => 'required|numeric|decimal:0,2|between:0,100', 'reason' => 'required|string|min:10|max:4000']; } else { abort_unless($command === 'activate', 404); $rules += ['reason' => 'required|string|min:10|max:4000', 'confirm' => 'required|accepted']; } $service->execute($actor, $command, $request->validate($rules)); return to_route('organization.parameters')->with('organization_notice', 'Parametrização actualizada e registada na auditoria.'); } /** @return array */ private function companyRules(int $tenant): array { $company = Tenant::query()->findOrFail($tenant); return ['first_limit' => (string) BigDecimal::of((string) $company->first_limit_minor)->dividedBy(100, 2), 'second_limit' => (string) BigDecimal::of((string) $company->second_limit_minor)->dividedBy(100, 2), 'includes_tax' => $company->approval_includes_tax, 'provisional' => $company->rules_provisional, 'default_tax_rate' => (string) ($company->default_tax_rate ?? '16.00')]; } }