# Unidade Gestora — Rotas e Controllers

## 1. Rotas do CMS

Arquivo: `v6/routes/basepro.php`. Todas as rotas abaixo vivem dentro do grupo
`Route::group(['prefix' => 'cms', 'before' => 'csrf'], function () {`
iniciado em `v6/routes/basepro.php:35`, portanto o prefixo real na URL é `/cms`.

### 1.1 Rotas geradas por `Route::resource`

`v6/routes/basepro.php:181`:

```php
Route::resource('unidade-gestora', 'Prefecture\ManagementUnitController');
```

| Método | URI | Nome da rota | Controller@action | Existe no controller? |
|---|---|---|---|---|
| GET | `/cms/unidade-gestora` | `unidade-gestora.index` | `index` | sim |
| GET | `/cms/unidade-gestora/create` | `unidade-gestora.create` | `create` | sim |
| POST | `/cms/unidade-gestora` | `unidade-gestora.store` | `store` | sim |
| GET | `/cms/unidade-gestora/{id}` | `unidade-gestora.show` | `show` | **NÃO** |
| GET | `/cms/unidade-gestora/{id}/edit` | `unidade-gestora.edit` | `edit` | sim |
| PUT/PATCH | `/cms/unidade-gestora/{id}` | `unidade-gestora.update` | `update` | sim |
| DELETE | `/cms/unidade-gestora/{id}` | `unidade-gestora.destroy` | `destroy` | sim |

> **Bug U6:** a rota `unidade-gestora.show` (GET `/cms/unidade-gestora/{id}`) é
> gerada pelo `Route::resource`, mas o controller **não tem** o método `show`.
> Acessar a URL resulta em erro ("method does not exist"). A view
> `prefecture/management-units/show.blade.php` existe, mas é órfã e referencia uma
> variável (`$functionality`) que o controller jamais forneceria.

> **Bug U7:** o formulário de criação faz
> `{{ Form::open(['route' => ['unidade-gestora.index'], 'id' => 'form']) }}`
> (`v6/resources/views/prefecture/management-units/create.blade.php:12`). O
> `Form::open` gera `method="POST"`, e `POST /cms/unidade-gestora` é a rota
> `unidade-gestora.store` — funciona **por acaso**, porque o `Route::resource`
> registra `store` na mesma URI. Se o `resource` for substituído por rotas
> explícitas, o formulário quebra.

### 1.2 Rota de exclusão em lote

`v6/routes/basepro.php:150` (dentro do grupo `Route::group(['prefix' => 'ajax'], function () {`,
iniciado em `v6/routes/basepro.php:114`):

```php
Route::post('unidade-gestora/delete-por-lote', 'Prefecture\ManagementUnitController@deleteByLote')
    ->name('ajax.management-unit.deleteByLote');
```

| Método | URI | Nome da rota | Controller@action |
|---|---|---|---|
| POST | `/cms/ajax/unidade-gestora/delete-por-lote` | `ajax.management-unit.deleteByLote` | `deleteByLote` |

Corpo esperado: `{ _token: string, toDelete: array<int> }`
(veja `v6/public/assets/js/custom/utilities/deleteAll.js`).

### 1.3 Rota do DataTable

`v6/routes/basepro.php:407` (dentro do grupo
`Route::group(['prefix' => 'datatable', 'as' => 'datatable.'], function () {`,
iniciado em `v6/routes/basepro.php:395`):

```php
Route::get('/unidade-gestora', 'Prefecture\DatatableController@get_unidade_gestora')->name('unidade-gestora');
```

| Método | URI | Nome da rota | Controller@action |
|---|---|---|---|
| GET | `/cms/datatable/unidade-gestora` | `datatable.unidade-gestora` | `get_unidade_gestora` |

## 2. Middleware

| Camada | Onde | Efeito |
|---|---|---|
| `web` (grupo) | `v6/app/Providers/RouteServiceProvider.php:88-92` | sessão, cookies, CSRF. |
| `csrf` (`'before' => 'csrf'`) | `v6/routes/basepro.php:35` | token CSRF verificado nas requisições POST/PUT/DELETE do CMS. |
| `check.route.permission` | `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php:28` (`$this->middleware('check.route.permission')`) | Exige login Sentinel; `super-admin` passa direto; demais papéis casam o nome da rota com as permissões. |

Não há `check.logged` explícito nas rotas deste módulo — a exigência de login vem
do próprio `check.route.permission`, que redireciona para `login` quando
`Sentinel::check()` é falso.

## 3. Permissões exigidas

O middleware resolve a permissão pelo **nome da rota**
(`v6/app/Http/Middleware/CheckRoutePermission.php:44-95`):

| Rota | Segmento calculado | Permissão |
|---|---|---|
| `unidade-gestora.index` | `unidade-gestora.index` | `unidade-gestora.index` |
| `unidade-gestora.create` | `unidade-gestora.create` | `unidade-gestora.create` |
| `unidade-gestora.store` | — | **aberta** (ver bug U8) |
| `unidade-gestora.show` | `unidade-gestora.show` | `unidade-gestora.show` |
| `unidade-gestora.edit` | — | **aberta** (ver bug U8) |
| `unidade-gestora.update` | — | **aberta** (ver bug U8) |
| `unidade-gestora.destroy` | — | **aberta** (ver bug U8) |
| `datatable.unidade-gestora` | — | **aberta** (`datatable` está em `$openRoutes`) |
| `ajax.management-unit.deleteByLote` | — | **aberta** (`ajax` está em `$openRoutes`) |

> **Bug U8 (risco de segurança):** em `hasAccess()`, o array `$resourceRoutes`
> (`['edit' => 'update', 'create' => 'store', 'delete' => 'destroy']`) é usado
> com `in_array($value, $resourceRoutes)`, que compara o **valor** do array
> (`update`, `store`, `destroy`) contra os segmentos do nome da rota. Como
> `explode('.', 'unidade-gestora.store')` produz `['unidade-gestora','store']`, o
> segmento `store` casa e a rota é liberada. O mesmo vale para `create`, `update`
> e `destroy`. **Resultado:** qualquer usuário autenticado no CMS (não apenas
> `super-admin`) consegue criar, editar e excluir unidades gestoras, mesmo sem
> as permissões `unidade-gestora.create/edit/delete`. Apenas a **listagem**
> (`index`) é efetivamente protegida.
>
> A correção na v7 é direta: usar Policies do Laravel em vez da heurística por
> nome de rota.

### 3.1 Permissões no papel `administrador`

`v6/database/seeds/RolesTableSeeder.php:62` (papel `administrador`, criado na
linha 21):

```json
"unidade-gestora.index":true,"unidade-gestora.create":true,"unidade-gestora.edit":true,
"unidade-gestora.delete":true,"unidade-gestora.inactive":true,"unidade-gestora.inactivate":true,
"unidade-gestora.restore":true,"unidade-gestora.show":true
```

Como o módulo está com `modulos.status = false`, a matriz de permissões do editor
de perfis **não exibe** a linha "Unidades Gestoras"
(`v6/resources/views/admin/role/form.blade.php:45`), ou seja, não é possível
conceder/revogar essas permissões pela interface.

## 4. Controller — API pública

`v6/app/Http/Controllers/Prefecture/ManagementUnitController.php`:

| Método | Assinatura | Retorno | Observações |
|---|---|---|---|
| `__construct` | `(IManagementUnitRepository $managementUnitRepository)` | — | Aplica `check.route.permission`; guarda o repository em `$this->managementUnitRepository`. |
| `index` | `()` | `View` | `menuClicks('licitacao')`; view `prefecture.management-units.index`, `title = 'Unidades Gestoras'`. |
| `create` | `()` | `View` | `menuClicks('licitacao')`; view `prefecture.management-units.create`. |
| `store` | `(ManagementUnitRequest $request)` | `RedirectResponse` | Transação + `ManagementUnit::create($request->all())`; flash e redirect. |
| `edit` | `(int $id)` | `View` | `ManagementUnit::findOrFail($id)` (404 se não existir); view `prefecture.management-units.edit`. |
| `update` | `(ManagementUnitRequest $request, int $id)` | `RedirectResponse` | Transação + `ManagementUnit::find($id)` + `->update($request->all())`; flash e redirect. |
| `destroy` | `(int $id)` | `JsonResponse` | Transação + `ManagementUnit::find($id)` + `->delete()`; JSON `{"success": "Unidade Gestora deletada com sucesso."}` 200 ou `{"erro": "Ocorreu um erro ao tentar salvar no banco de dados."}` / `{"erro": "Ocorreu um erro! Por favor entre em contato com o suporte técnico."}` 500. |
| `deleteByLote` | `(Request $request)` | `JsonResponse` | Log de atividade + `managementUnitRepository->destroyAll($request->toDelete)`. |
| `show` | — | — | **Não existe** (bug U6). |

### 4.1 Trechos relevantes

```php
public function store(ManagementUnitRequest $request): RedirectResponse
{
    try {
        DB::transaction(function () use ($request) {
            ManagementUnit::create($request->all());
        });
        flash('Unidade Gestora cadastrada com sucesso.')->success();
        return redirect()->route("unidade-gestora.index");
    } catch (PDOException $PDOException) {
        flash('Ocorreu um erro ao tentar salvar no banco de dados.')->error();
        return redirect()->route("unidade-gestora.index");
    } catch (Exception $e) {
        flash('Ocorreu um erro! Por favor entre em contato com o suporte técnico.')->error();
        return redirect()->route("unidade-gestora.create");
    }
}
```

```php
public function deleteByLote(Request $request) {
    try{
        activity()->performedOn($this->managementUnitRepository->getModel())
            ->causedBy(Sentinel::getUser())
            ->withProperty('Detalhe', 'As Ugs com ids: '.implode("," ,$request->toDelete). ' Foram Excluidas por: '. Sentinel::getUser()->name)
            ->log('Exclusão de uniade gestora');

        return $this->managementUnitRepository->destroyAll($request->toDelete);
    }catch(Exception $e){
        flash('Ocorreu um erro! Por favor entre em contato com o suporte técnico.')->error();
        return redirect()->back();
    }
}
```

> `implode(",", $request->toDelete)` falha com `TypeError` se `toDelete` não for
> um array (a requisição pode ser forjada). O `catch (Exception $e)` **não**
> captura `TypeError` (que é `Error`, não `Exception`) em PHP 7.2 — a requisição
> resultaria em erro fatal (bug U9).

## 5. Form Request — `ManagementUnitRequest`

`v6/app/Http/Requests/ManagementUnitRequest.php` (idêntico na v5 e na v6):

```php
public function authorize()
{
    return true;
}

public function rules(Request $request)
{
    $codigo =  Request::segment(3);//Id da unidade gestora
    $rules = [
        'create' => [
            'code' => 'required|max:120|unique:management_units',
            'name' => 'required|max:120|unique:management_units',
      ],
        'update' => [
            'code' => 'sometimes|required|unique:persons,cpf,'.$codigo,
            'name' => 'sometimes|required|unique:persons,cpf,'.$codigo,
          ]
    ];

    $index = (Route::getCurrentRoute()->getName() == 'unidade-gestora.store') ? 'create' : 'update';

    return $rules[$index];
}
```

| Campo | `create` | `update` | Correto? |
|---|---|---|---|
| `code` | `required\|max:120\|unique:management_units` | `sometimes\|required\|unique:persons,cpf,{id}` | **não** na atualização |
| `name` | `required\|max:120\|unique:management_units` | `sometimes\|required\|unique:persons,cpf,{id}` | **não** na atualização |

> **Bug U1 (confirmado):** na atualização, a regra `unique:persons,cpf,{id}`
> verifica a tabela **`persons`** e a coluna **`cpf`** — nada a ver com
> `management_units`. Consequências:
> 1. A unicidade de `name`/`code` **não é verificada** ao editar -> é possível
>    salvar duas unidades com o mesmo nome ou código.
> 2. A validação depende de a tabela `persons` existir e ter a coluna `cpf`
>    (ela existe, mas o ID passado é o da unidade gestora, não de uma pessoa).
> 3. A coluna de comparação `cpf` é `nullable`/única em `persons`; o
>    comportamento com `null` no PostgreSQL faz a regra `unique` **passar
>    silenciosamente** (NULLs não conflitam em índices unique).
>
> O `$codigo` é obtido de `Request::segment(3)`. Na rota
> `PUT /cms/unidade-gestora/{id}`, os segmentos são `1=cms`, `2=unidade-gestora`,
> `3={id}` — a extração está correta.

Mensagens (`messages()`):

| Chave | Texto |
|---|---|
| `code.required` | "O campo código é obrigatório." |
| `code.unique` | "O código já está em uso." |
| `name.required` | "O campo nome é obrigatório." |
| `name.unique` | "O nome já está em uso." |
| `name.max` | "O campo nome atingiu o limite máximo de caracteres." |
| `code.max` | "O campo código atingiu o limite máximo de caracteres." |

> Não há mensagem para `code.numeric` nem para `persons.cpf` — se a regra de
> atualização falhar por causa da tabela errada, a mensagem exibida será a
> genérica do Laravel ("The persons.cpf has already been taken."), em inglês.

> `authorize()` retorna `true`: a autorização real depende exclusivamente do
> middleware `check.route.permission` (com a falha U8).

## 6. API Resources / presenters

**Não há API Resource nem presenter** para o módulo. O controller do CMS devolve
HTML (AdminLTE) ou JSON simples (`{"success": "Unidade Gestora deletada com sucesso."}` /
`{"erro": "Ocorreu um erro ao tentar salvar no banco de dados."}` /
`true` / `false`).

O `DatatableController` devolve o JSON padrão do yajra:

```json
{
  "draw": 1,
  "recordsTotal": 2,
  "recordsFiltered": 2,
  "data": [
    { "value": 1, "nome": "Prefeitura Municipal", "id": 2,
      "edit": true, "delete": true, "active": true, "inactivate": true }
  ]
}
```

> A view SQL `unidade_gestora` **não expõe `code`** — o código da unidade não
> aparece na listagem do CMS.

## 7. Ausência de API REST

Não há rota em `v6/routes/api.php` para o módulo, nem página no portal. O módulo
é 100% interno ao CMS.
