'Preparar consultas e cotações', 'purchasing_sourcing.evaluate'=>'Avaliar cotações', 'purchasing_sourcing.propose'=>'Propor adjudicação', 'purchasing_sourcing.confirm'=>'Confirmar adjudicação independente', 'purchasing_requisitions.view' => 'Consultar requisições', 'purchasing_requisitions.prepare' => 'Preparar e submeter requisições', 'purchasing_requisitions.configure' => 'Configurar Compras internas', 'purchasing_requisitions.approve.department_head' => 'Aprovar como responsável de departamento', 'purchasing_requisitions.approve.procurement' => 'Aprovar como responsável de Compras', 'purchasing_requisitions.approve.finance' => 'Aprovar como responsável financeiro', 'purchasing_budget.create' => 'Preparar dotações', 'purchasing_budget.approve' => 'Aprovar dotações', 'purchasing_budget.record' => 'Registar e libertar reservas orçamentais', 'purchasing_budget.consume' => 'Registar consumo orçamental', 'purchasing_budget.view' => 'Consultar orçamento', 'purchasing_budget.reinforcement.propose' => 'Propor reforços orçamentais', 'purchasing_budget.reinforcement.review' => 'Rever reforços orçamentais', ]; public const RIGHTS = [ 'purchasing_sourcing.prepare','purchasing_sourcing.evaluate','purchasing_sourcing.propose','purchasing_sourcing.confirm', 'purchasing_requisitions.view', 'purchasing_requisitions.prepare', 'purchasing_requisitions.configure', 'purchasing_requisitions.approve.department_head', 'purchasing_requisitions.approve.procurement', 'purchasing_requisitions.approve.finance', 'purchasing_budget.create', 'purchasing_budget.approve', 'purchasing_budget.record', 'purchasing_budget.consume', 'purchasing_budget.view', 'purchasing_budget.reinforcement.propose', 'purchasing_budget.reinforcement.review', ]; public static function prepare(string $host): array { $paths = ['app/Modules/Identity/Services/Permissions.php', 'app/Modules/Identity/Services/IdentityWorkflow.php']; $result = []; foreach ($paths as $path) { $source = file_get_contents($host.'/'.$path); $before = hash('sha256', $source); if (str_ends_with($path, '/Permissions.php')) { $source = self::replace($source, "public const UNIT = ['unit.view', 'unit.edit'];", 'public const UNIT = '.var_export(['unit.view', 'unit.edit', ...self::RIGHTS], true).';'); } else { // Expanding a catalogue must never expand creation-time grants. $source = self::replace($source, 'foreach (Permissions::UNIT as $permission)', "foreach (['unit.view', 'unit.edit'] as \$permission)"); $source = self::replace($source, "if (\$unit && \$data['permission'] === 'unit.edit')", "if (\$unit && \$data['permission'] !== 'unit.view')"); $source = self::replace($source, "if (\$this->permissions->has(\$target->id, \$tenant, \$company, 'unit.edit', \$unit)) { \$this->permissions->require(\$actor, \$tenant, \$company, 'unit.edit', \$unit); }", "foreach ((clone \$query)->where('unit_id', \$unit)->whereIn('permission', Permissions::UNIT)->get() as \$existing) { \$this->permissions->require(\$actor, \$tenant, \$company, \$existing->permission, \$unit); }"); } $result[$path] = ['before_sha256' => $before, 'after_sha256' => hash('sha256', $source), 'contents' => $source]; } return $result; } private static function replace(string $source, string $old, string $new): string { if (substr_count($source, $old) !== 1) { throw new \RuntimeException('Host divergiu: reconciliar a alteração de identidade antes de preparar a entrega.'); } return str_replace($old, $new, $source); } }