# Requerimentos — Rotas e Controladores

> **Versão analisada:** `v6` — módulo **novo na v6** (nenhuma rota equivalente
> em `v5/routes/web.php` ou `v5/routes/basepro.php`)
> **Slug:** `requerimentos` (`modulos.id = 41`)

---

## 1. Rotas do portal (público)

Arquivo: `v6/routes/web.php`, dentro do grupo
`Route::middleware(['check.site.manutention'])` (linha 8).

| Método | URI | Nome | Controller | Middleware |
|---|---|---|---|---|
| GET | `/requerimentos` | `requerimentos.index` | `Portal\RequerimentoController@index` | `web`, `check.site.manutention` |
| GET | `/requerimentos/{slug}` | `requerimentos.show` | `Portal\RequerimentoController@show` | `web`, `check.site.manutention` |
| GET | `/requerimentos/{slug1}/{slug2}` | `requerimentos.view` | `Portal\RequerimentoController@view` | `web`, `check.site.manutention` |

Trecho real (`v6/routes/web.php:43-46`):

```php
// Publicações
Route::get('/requerimentos', 'Portal\RequerimentoController@index')->name('requerimentos.index');
Route::get('/requerimentos/{slug}', 'Portal\RequerimentoController@show')->name('requerimentos.show');
Route::get('/requerimentos/{slug1}/{slug2}', 'Portal\RequerimentoController@view')->name('requerimentos.view');
```

### Observações de rota

- `requerimentos.show` e `requerimentos.view` **não têm restrição de parâmetro**:
  qualquer slug é aceito. `show` cai na primeira categoria quando o slug não
  pertence ao conjunto; `view` nem sequer verifica a categoria.
- O mesmo controller atende a chamada Ajax: `show()` detecta
  `request()->ajax()` e devolve `Response::json(...)` com o HTML já renderizado
  (`v6/app/Http/Controllers/Portal/RequerimentoController.php:145-168`).
- Não há rota de API (`v6/routes/api.php`) para este módulo.

### Breadcrumbs

`v6/routes/breadcrumbs.php:330-360` define:

| Nome do breadcrumb | Parâmetro | Hierarquia |
|---|---|---|
| `requerimentos` | — | Home › Requerimentos |
| `category-requerimentos` | `$category` | Home › Requerimentos › Categoria (não empilha se a categoria for a própria raiz) |
| `view-requerimentos` | `$content` | Home › Requerimentos › [Categoria?] › Requerimento |

## 2. Rotas do CMS (`/cms`)

Arquivo: `v6/routes/basepro.php`, grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)`
(linha 35). Não há `Route::resource` exclusivo do módulo: as rotas são as do
**CRUD genérico de conteúdo**, parametrizadas pelo slug `requerimentos`.

### 2.1 Conteúdo (requerimentos)

| Método | URI | Nome | Controller | Formulário que usa |
|---|---|---|---|---|
| GET | `/cms/conteudos/{slug}` | `listar-conteudo` | `Prefecture\ContentController@genericIndex` | — |
| GET | `/cms/conteudos/cadastrar/{slug}` | `cadastrar-conteudo` | `Prefecture\ContentController@genericCreate` | `conteudos/requerimentos/create.blade.php` |
| GET | `/cms/conteudos/editar/{slug}/{id}` | `editar-conteudo` | `Prefecture\ContentController@genericEdit` | `conteudos/requerimentos/edit.blade.php` |
| POST | `/cms/conteudo` | `conteudo.store` | `Prefecture\ContentController@store` | `Form::open(['route' => ['conteudo.index']])` |
| PUT | `/cms/conteudo/{id}` | `conteudo.update` | `Prefecture\ContentController@update` | `Form::model(..., ['route' => ['conteudo.update', $content->id], 'method' => 'PUT'])` |
| DELETE | `/cms/conteudo/delete/{id}` | `generic.conteudo.delete` | `Prefecture\ContentController@genericDestroy` | botão de ação do DataTable |
| POST | `/cms/conteudo/deleteAll` | `ajax.generic.content.deleteAll` | `Prefecture\ContentController@genericDeleteAll` | `prefecture.components.deleteByLoteSubmitButton` |
| GET | `/cms/datatable/requerimentos` | `datatable.requerimentos` | `Prefecture\DatatableController@get_requerimentos` | `assets/js/custom/contents/requerimentos/datatables.js` |

Observação: os links do painel são **relativos** (`cadastrar/requerimentos`,
`editar/requerimentos/{id}`) e resolvem, a partir de `/cms/conteudos/requerimentos`,
para `/cms/conteudos/cadastrar/requerimentos` e
`/cms/conteudos/editar/requerimentos/{id}`.

### 2.2 Categorias (subcategorias de requerimento)

| Método | URI | Nome | Controller |
|---|---|---|---|
| GET | `/cms/categorias/{slug}` | `listar-categorias` | `Prefecture\CategoryContentController@genericIndex` |
| GET | `/cms/categorias/cadastrar/{slug}` | `cadastrar-categorias` | `Prefecture\CategoryContentController@genericCreate` |
| GET | `/cms/categorias/editar/{slug}/{id}` | `editar-categorias` | `Prefecture\CategoryContentController@genericEdit` |
| POST | `/cms/categoria-conteudo` | `categoria-conteudo.store` | `Prefecture\CategoryContentController@store` |
| PUT | `/cms/categoria-conteudo/{id}` | `categoria-conteudo.update` | `Prefecture\CategoryContentController@update` |
| DELETE | `/cms/categoria/delete/{id}` | `generic.category.delete` | `Prefecture\CategoryContentController@genericDestroy` |
| POST | `/cms/ajust-categorias` | `ajust.categorias` | `Prefecture\CategoryContentController@ajustOrder` (reordenação por Ajax) |

O botão “Categorias de Requerimento” da listagem aponta para
`route('cadastrar-categorias', 'requerimentos')`
(`v6/resources/views/prefecture/contents/requerimentos/index.blade.php:11-15`).

### 2.3 Apoio

| Método | URI | Nome | Controller |
|---|---|---|---|
| POST | `/cms/ajax-upload-content-files` | `ajax.upload.content.files` | `Prefecture\ContentController@ajaxUploadContentFiles` |
| POST | `/cms/summernote-files` | `ajax.summernote.files` | `Prefecture\ContentController@summernoteFiles` |
| POST | `/cms/set-status` | `set.status` | `Prefecture\ContentController@setStatus` |
| GET | `/cms/modulos` | `modulos.index` | `Admin\ModulosController@index` (liga/desliga o módulo) |

## 3. Controladores

### 3.1 `Portal\RequerimentoController`

Arquivo: `v6/app/Http/Controllers/Portal/RequerimentoController.php`.

| Método | Linhas | Responsabilidade |
|---|---|---|
| `index()` | 26-45 | Redireciona para a primeira subcategoria (fallback: raiz; sem raiz: `/` com `error`) |
| `show(string $slug, Request $request)` | 54-172 | Lista unificada, filtros, paginação, resposta Ajax ou HTML |
| `view($slug1, $slug2)` | 183-201 | Detalhe, anexos, incremento de `clicks` |

Herda de `App\Http\Controllers\Admin\Controller`, de onde vem
`getTemplateActived()` (`v6/app/Http/Controllers/Admin/Controller.php:39-49`),
usado para montar o nome da view:
`portal.layouts.{template}.requerimentos.{show|list|view}`.

### 3.2 `Prefecture\ContentController` (CMS)

| Trecho | Função |
|---|---|
| `v6/app/Http/Controllers/Prefecture/ContentController.php:111` | `if ($slug === 'requerimentos') return $this->returnIndex('Requerimentos', $slug);` — abre a listagem e chama `menuClicks('requerimentos')` |
| `v6/app/Http/Controllers/Prefecture/ContentController.php:203-224` | Monta o formulário: `councilmen` (vereadores com `status = true`, ordenados por nome), `categoryContent` (raiz) e `categoryContentSelect` (subcategorias) |
| `v6/app/Http/Controllers/Prefecture/ContentController.php:657` | `store()` grava `councilmen_id` (ou `null` se `"0"`/vazio) |
| `v6/app/Http/Controllers/Prefecture/ContentController.php:968` | `update()` normaliza `councilmen_id` idem |
| `v6/app/Http/Controllers/Prefecture/ContentController.php:1369` | `genericDestroy()` — exclusão individual |

Detalhe importante do `returnData` (`ContentController.php:203-214`): se o
requerimento já salvo estiver vinculado a um vereador que **não** está mais na
lista de ativos, o vereador é readicionado ao `$select` para que a edição não
perca o vínculo.

### 3.3 `Prefecture\CategoryContentController` (CMS)

| Trecho | Função |
|---|---|
| `v6/app/Http/Controllers/Prefecture/CategoryContentController.php:149-162` | `genericIndex` — lista as subcategorias (filhas da raiz) e registra a auditoria “Listagem de categoria de requerimentos” |
| `v6/app/Http/Controllers/Prefecture/CategoryContentController.php:259-274` | `genericCreate` — formulário de nova subcategoria |
| `v6/app/Http/Controllers/Prefecture/CategoryContentController.php:372-385` | `genericEdit` — edição de subcategoria |
| `v6/app/Http/Controllers/Prefecture/CategoryContentController.php:820` | `ajustOrder` — reordenação das subcategorias |

### 3.4 `Prefecture\DatatableController`

| Trecho | Função |
|---|---|
| `v6/app/Http/Controllers/Prefecture/DatatableController.php:46-50` | `get_requerimentos()` → `DB::table('requerimentos')->get()` → `DataTables::of($this->struct_views($requerimentos, 'requerimentos'))` |
| `v6/app/Http/Controllers/Prefecture/DatatableController.php:504-519` | `struct_views()` anexa `edit`, `delete`, `active`, `inactivate` conforme `has_access()` |

> A listagem do CMS lê a **view SQL**, não a tabela `contents`. Isso explica por
> que a view precisa ser recriada a cada mudança de layout de dados.

## 4. Validação (Form Requests)

### 4.1 `ContentRequest` — slug `requerimentos`

`v6/app/Http/Requests/ContentRequest.php:240-262`:

```php
case 'requerimentos' :

    $rules = [
        "title" => "required|max:120",
        "publication_date" => "required",
        "category_contents_id" => "required|numeric",
        "councilmen_id" => [
            function ($attribute, $value, $fail) {
                if ($value === null || $value === '' || $value == '0') {
                    return;
                }

                if (!is_numeric($value) || !\App\Http\Model\Prefecture\CityCouncilor::where('id', $value)->exists()) {
                    $fail('O requerente selecionado é inválido.');
                }
            },
        ],
        "text" => "required",
        "archive.*.0" => "mimes:jpg,jpeg,png,bmp,gif,rar,zip,doc,docx,pdf,odt,xls,xlsx,xlsb,xslm,ppt,pptx,ods,wav,mp3,wma,mp4,mpg4,mkv,flv",
    ];
    break;
```

| Campo | Regra | Efeito observável |
|---|---|---|
| `title` | `required|max:120` | Título obrigatório, até 120 caracteres (o formulário sugere 150 — divergência) |
| `publication_date` | `required` | Data/hora de publicação obrigatória |
| `category_contents_id` | `required|numeric` | Deve ser uma **subcategoria** da raiz `requerimentos` (o select só oferece as filhas) |
| `councilmen_id` | closure | Vazio/`0` é aceito (requerimento sem autor); caso contrário, o id precisa existir em `councilmen` |
| `text` | `required` | Texto obrigatório |
| `archive.*.0` | `mimes:...` | Anexos limitados a 24 extensões (documentos, planilhas, imagens, áudio e vídeo) |

### 4.2 `CategoryContentRequest` (subcategorias)

`v6/app/Http/Requests/CategoryContentRequest.php`:

| Campo | Regra | Observação |
|---|---|---|
| `category_contents_id` | `required` | Pai da categoria (a raiz `requerimentos`); dispensável apenas quando `generic` está presente |
| `name` | `required|max:190` | Nome da subcategoria |

Não há regras específicas para `order`, `quantidade_itens` ou `layout` no slug
`requerimentos` (só para `noticias`).

## 5. Permissões

| Recurso | Slug | Onde é verificado |
|---|---|---|
| Criar | `requerimentos.create` | `v6/resources/views/prefecture/contents/requerimentos/index.blade.php:11` (botões “Categorias de Requerimento” e “Cadastrar Requerimento”) |
| Excluir | `requerimentos.delete` | `index.blade.php:22-27` (exclusão em lote) e `DatatableController@struct_views` |
| Editar | `requerimentos.edit` | `DatatableController@struct_views` (flag `edit` da linha) |
| Reativar / inativar | `requerimentos.restore` / `requerimentos.inactivate` | `DatatableController@struct_views` |

Mecânica: helper `has_access()` em `v6/app/Library/Helper/appHelpers.php:22`
usa `Sentinel::check()->hasAnyAccess([...])` e **libera tudo** para o papel
`super-admin`.

Estado verificado no banco (`roles.permissions`): o papel `administrador`
**não possui** as chaves `requerimentos.create`, `requerimentos.edit` nem
`requerimentos.delete`; a tabela `permissions` do Sentinel contém apenas 9
permissões genéricas (`index`, `create`, `edit`, `delete`, `inactive`,
`inactivate`, `restore`, `show`, `publish`). Na prática, **apenas
`super-admin` opera o módulo** no ambiente analisado. Detalhamento em
`08-GAPS-E-DIVIDAS.md`.

## 6. Saída JSON / API Resources

- O portal **não** expõe JSON estruturado: o Ajax do filtro devolve o HTML
  renderizado (`Response::json(view(...)->render())`).
- O CMS usa o **DataTable server-side** (Yajra): a resposta é JSON no formato
  DataTables (`data`, `recordsTotal`, `recordsFiltered`, `draw`).
- Não há `API Resource`/`Presenter` específico para requerimentos. Os models
  `Content` e `CategoryContent` usam `PresentableTrait`
  (`v6/app/Http/Presenters/ContentPresenter.php`,
  `v6/app/Http/Presenters/CategoryContentPresenter.php`), mas o DataTable do
  módulo não passa por eles (usa a view SQL crua).
