# Unidades de Atendimento — Rotas, Controllers e Validações

---

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

Arquivo: `v6/routes/web.php` — grupo público, **sem** `check.logged`.

| Método | URI | Nome (`->name()`) | Controller@action | Linha |
|---|---|---|---|---|
| GET | `/unidades-de-atendimento` | `unidades-de-atendimento.index` | `Portal\ServiceUnitsController@index` | `v6/routes/web.php:116` |
| GET | `/unidades-de-atendimento/{slug}` | `unidades-de-atendimento.show` | `Portal\ServiceUnitsController@show` | `v6/routes/web.php:117` |

Sem rotas de API (`v6/routes/api.php`) específicas do módulo.

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

Arquivo: `v6/routes/basepro.php` — grupo `prefix => 'cms'`, `before => 'csrf'` (`:35`).

| Método | URI | Nome | Controller@action | Linha |
|---|---|---|---|---|
| GET | `/cms/conteudos/unidades-de-atendimento` | `listar-conteudo` | `Prefecture\ContentController@genericIndex` | `:235` |
| GET | `/cms/conteudos/cadastrar/unidades-de-atendimento` | `cadastrar-conteudo` | `Prefecture\ContentController@genericCreate` | `:236` |
| GET | `/cms/conteudos/editar/unidades-de-atendimento/{id}` | `editar-conteudo` | `Prefecture\ContentController@genericEdit` | `:237` |
| GET | `/cms/datatable/unidades-de-atendimento` | `datatable.unidades-de-atendimento` (prefixo do grupo `as => 'datatable.'`, `:396`) | `Prefecture\DatatableController@get_unidades_de_atendimento` | `:461` |
| POST | `/cms/conteudo` | `conteudo.store` | `Prefecture\ContentController@store` | `:174` (resource) |
| PUT | `/cms/conteudo/{id}` | `conteudo.update` | `Prefecture\ContentController@update` | `:174` (resource) |
| DELETE | `/cms/conteudo/{id}` | `conteudo.destroy` | `Prefecture\ContentController@destroy` | `:174` (resource) |
| DELETE | `/cms/conteudo/delete/{id}` | `generic.conteudo.delete` | `Prefecture\ContentController@genericDestroy` | `:223` |
| POST | `/cms/conteudo/deleteAll` | `ajax.generic.content.deleteAll` | `Prefecture\ContentController@genericDeleteAll` | `:141` |
| POST | `/cms/ajax-upload-files` | `ajax.upload.files` | `Prefecture\ContentController@ajaxUploadFiles` | `:123` |
| POST | `/cms/ajax-upload-content-files` | `ajax.upload.content.files` | `Prefecture\ContentController@ajaxUploadContentFiles` | `:125` |
| POST | `/cms/ajax-load-files` | `ajax.load.files` | `Prefecture\ContentController@ajaxLoadFiles` | `:126` |
| POST | `/cms/ajax-delete-files` | `ajax.delete.files` | `Prefecture\ContentController@ajaxDeleteFiles` | `:128` |
| POST | `/cms/ajax-delete-files-gallery` | `ajax.delete.files.gallery` | `Prefecture\ContentController@ajaxDeleteFilesofGallery` | `:132` |
| POST | `/cms/ajax-update-path` | `ajax.update.path.archive` | `Prefecture\ContentController@pathUpdate` | `:131` |

Rotas do CMS são **genéricas por slug**: a mesma rota atende `noticias`,
`legislacao`, `unidades-de-atendimento`, etc. O controller valida o slug em
`genericIndex()` (`v6/app/Http/Controllers/Prefecture/ContentController.php:91-119`);
slugs desconhecidos caem em `redirect()->route('dashboard.index')`.

## 3. Menu do CMS

`v6/app/Providers/MenuServiceProvider.php:295-297`:

```php
case 'unidades-de-atendimento':
    $event->menu->add(['text' => 'Unidades de Atendimento', 'url' => '/cms/conteudos/unidades-de-atendimento', 'icon' => 'home']);
    break;
```

## 4. Controllers

### 4.1 `Portal\ServiceUnitsController`

Arquivo: `v6/app/Http/Controllers/Portal/ServiceUnitsController.php`
(herda de `App\Http\Controllers\Admin\Controller`, que fornece `getTemplateActived()`).

**`index()`** (`:25-33`):

```php
$category = CategoryContent::where('slug', 'unidades-de-atendimento')->first();
$current_date = date('Y-m-d H:i:s');

$contents = Content::whereNull('deleted_at')
    ->where('category_contents_id', $category->id)->with('archives')
    ->orderBy('publication_date', 'desc')->orderBy('id', 'desc')->paginate(12);

return view('portal.layouts.' . $this->getTemplateActived() . '.unidades-de-atendimento.index', compact('contents', 'category'));
```

Pontos de atenção:
- `$current_date` é calculado e **não utilizado** (código morto).
- `$category->id` sem `firstOrFail()`: se a categoria não existir (seed não
  executado), o erro é `Trying to get property of non-object` (HTTP 500), não 404.
- `paginate(12)` — **página fixa de 12 itens**, sem configuração.

**`show($slug)`** (`:34-52`):

```php
$content = Content::whereHas('categoryContents',  function ($query) use ($slug) {
    return $query->where('slug', 'unidades-de-atendimento');
})->where('slug', $slug)->with('archives')->firstOrFail();

$archives = Archive::where('contents_id', $content->id)->where('highlight', null)->get();

return view('portal.layouts.' . $this->getTemplateActived() . '.unidades-de-atendimento.show', compact('content', 'archives'));
```

Pontos de atenção:
- O código comentado (`:43-48`) é a versão anterior da mesma consulta —
  resquício de refactor.
- `$archives` (sem `highlight`) é consumido por
  `v6/resources/views/portal/components/galerias/index.blade.php`.
- `firstOrFail()` gera 404 para slug inexistente **ou** existente em outra
  categoria — comportamento correto para o módulo.

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

| Método | Linha | Papel no módulo |
|---|---|---|
| `genericIndex($slug)` | `:91` | Despacha `unidades-de-atendimento` → `returnIndex('Unidades de Atendimento', $slug)` (`:115`). |
| `returnIndex($title,$slug)` | `:121` | Log de atividade (`generateLog`), `menuClicks($slug)`, render da view de listagem. |
| `genericCreate` / `genericEdit` / `genericGenerate` | `:140`, `:158`, `:163` | Resolvem a view de create/edit (`:250-254` carrega a `categoryContent` raiz). |
| `returnData($title,$slug,$self,$extra,$id)` | `:275` | Monta `content`, `archives`, `gallery`, `destaque` (imagem de destaque) para o formulário. |
| `store(ContentRequest)` | `:616` | Persistência: slug, `publication_date`, `category_contents_id`, logs, `Link`, `Archive` de destaque. |
| `update(ContentRequest,$id)` | `:937` | Idem para edição. |
| `destroy` / `genericDestroy` | `:1320`, `:1369` | Exclusão (individual e em lote via repository). |

O construtor (`:58-67`) aplica o middleware `check.route.permission` (`:60`) e
injeta `Content` + `IContentRepository`.

### 4.3 `Prefecture\DatatableController`

`get_unidades_de_atendimento()` (`:474-478`):

```php
$unidades = DB::table("unidades_de_atendimento")->get();
return DataTables::of($this->struct_views($unidades, 'unidades_de_atendimento'))->make(true);
```

`struct_views()` (`:504-520`) anexa a cada linha os flags de permissão
`edit`, `delete`, `active`, `inactivate` via `has_access()`.

## 5. Form Requests e validação

`v6/app/Http/Requests/ContentRequest.php` — Form Request único para todos os
tipos de conteúdo. O slug da categoria é deduzido do request (`:31-39`):
`category_slug` ou `CategoryContent::find($request->category_contents_id)->slug`.

Não existe `case 'unidades-de-atendimento'` no `switch` (`:59-315`); o módulo cai
no **`default`** (`:308-314`):

```php
default:
    $rules = [
        "title" => "required|max:120",
        "category_contents_id" => "required|numeric",
        "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",
        "archive_highlight" => "max:20000|mimes:jpg,jpeg,png,gif,webp",
    ];
```

Mensagens relacionadas (`:317+`): `title.required`, `title.max`,
`category_contents_id.required`, `archive_highlight.max` (20 MB),
`archive.*.mimes`.

Validação adicional fora do Form Request: extensões de upload restritas a
`rar, zip, doc, docx, pdf, odt, xls, xlsx, xlsb, xslm, ppt, pptx`
(`v6/app/Http/Requests/ContentRequest.php:49-56`).

## 6. API Resources / Presenters

- Não há `App\Http\Resources` para o módulo.
- A resposta do CMS é **JSON do DataTables (Yajra)**, com as colunas da view
  SQL mais os flags `edit`/`delete`/`active`/`inactivate`.
- A resposta do portal é **HTML** (Blade).
- `ContentPresenter` (`v6/app/Http/Presenters/ContentPresenter.php:89`) expõe
  `getArchiveContentHighlightOrThumbnail()`, usado nas views `modelo3`/`modelo4`
  para escolher a imagem de destaque/thumbnail.

## 7. Permissões exigidas

| Contexto | Permissão | Verificação |
|---|---|---|
| CMS — botão "Cadastrar Unidade" | `unidades-de-atendimento.create` | `has_access()` em `prefecture/contents/unidades-de-atendimento/index.blade.php:11` |
| CMS — exclusão em lote | `unidades-de-atendimento.delete` | `index.blade.php:16` |
| CMS — colunas da DataTable | `unidades_de_atendimento.edit` / `.delete` / `.restore` / `.inactivate` | `DatatableController::struct_views()` (`:507-516`) |
| CMS — rotas | `check.route.permission` (nome da rota × permissões do papel Sentinel) | `v6/app/Http/Middleware/CheckRoutePermission.php` |
| Portal | nenhuma | rota pública |

`has_access()` (`v6/app/Library/Helper/appHelpers.php:22-43`) retorna `true`
para o papel `super-admin`; demais papéis usam `Sentinel::check()->hasAccess()`.

> Observação: as permissões do módulo são **strings conventionais**, não linhas
> da tabela `permissions` (na base v6 não há registro com slug
> `unidades-de-atendimento%`); elas são resolvidas pelo Sentinel.

## 8. Fluxo de autorização (CMS)

```
GET /cms/conteudos/unidades-de-atendimento
  → check.logged (exige sessão Sentinel)
  → check.route.permission (nome da rota 'listar-conteudo' vs permissões do papel)
  → ContentController::genericIndex
```

O botão "Cadastrar Unidade" só aparece com `unidades-de-atendimento.create`; o
acesso às ações dentro da DataTable depende de `unidades_de_atendimento.edit`
e `.delete` — nomes **com underscore**, distintos dos usados na view Blade
( kebab-case). Inconsistência real do legado.
