# Unidades de Atendimento — Modelagem de Dados

> O módulo **não tem tabela própria**. Toda a persistência ocorre em tabelas
> genéricas da plataforma (`contents`, `category_contents`, `archives`, `links`).
> A única estrutura exclusiva do módulo é a **view SQL `unidades_de_atendimento`**.

---

## 1. Tabelas envolvidas

### 1.1 `category_contents` — a categoria do módulo

Origem: `v6/database/migrations/2018_04_01_180916_create_category_contents_table.php`
(+ `2021_02_18_123218_add_status_to_category_contents.php`).

| Coluna | Tipo (PostgreSQL) | Null | Default | Papel |
|---|---|---|---|---|
| `id` | integer | não | `nextval('category_contents_id_seq')` | PK. |
| `name` | varchar(191) | não | — | Nome exibido ("Unidades de Atendimento"). |
| `slug` | varchar(191) | não | — | **Chave lógica do módulo**: `unidades-de-atendimento`. |
| `status_create` | boolean | sim | — | Permite criação de conteúdo na categoria. |
| `quantidade_itens` | integer | sim | — | Não usado pelo módulo. |
| `order` | integer | sim | — | Ordenação no CMS (genérica). |
| `layout` | integer | sim | — | Não usado pelo módulo. |
| `category_contents_id` | integer | sim | — | Auto-referência; `NULL` = categoria raiz. |
| `status` | boolean | sim | `true` | Soft delete lógico na prática. |
| `display_type`, `custom_icon`, `description` | varchar/text | sim | — | Genéricos. |
| `created_at`, `updated_at`, `deleted_at` | timestamp | sim | — | Timestamps. |

FK: `category_contents_category_contents_id_foreign → category_contents(id)`.

Estado no banco (consulta somente-leitura): `id = 44`, `name = 'Unidades de Atendimento'`,
`slug = 'unidades-de-atendimento'`, `status_create = true`, `category_contents_id = NULL`.

### 1.2 `contents` — os registros de unidade

Origem: `v6/database/migrations/2018_04_02_180433_create_contents_table.php`
(+ `2022_04_19_084011_add_secretaria_to_contents_table.php`,
`2022_05_06_103629_add_column_to_contents_table.php`).

| Coluna | Tipo | Null | Papel no módulo |
|---|---|---|---|
| `id` | integer | não (PK) | Identificador (exibido na DataTable). |
| `title` | varchar(191) | sim | Título da unidade (obrigatório, máx. 120). |
| `text` | text | sim | Descrição em HTML (Summernote). Renderizada com `{!! !!}`. |
| `slug` | varchar(191) | sim | Identificador na URL pública `/unidades-de-atendimento/{slug}`. |
| `publication_date` | timestamp | sim | Data/hora exibida; ordena a listagem. |
| `author`, `source` | varchar(191) | sim | Exibidos no detalhe: `source` aparece como "Do(a) {source}" e `author` como "por {author}" (`v6/resources/views/portal/layouts/modelo1/unidades-de-atendimento/show.blade.php:14`). |
| `clicks` | integer | sim | Contador; inicia em 0 no `store`. |
| `highlights` | boolean | sim | Não usado neste módulo. |
| `galery` | boolean | sim | Liga/desliga a galeria (`Ativar Galeria` no formulário). |
| `status` | boolean | sim | Não filtrado pelo portal do módulo. |
| `category_contents_id` | integer | **não** | FK → `category_contents(id)`; aponta para a id 44. |
| `secretaria`, `carousel`, `orgaos_id`, `position_id`, `councilmen_id`, `layout`, `icon`, `referencia`, `acronym`, `linha_fina` | — | sim | Campos genéricos, não preenchidos pelo formulário do módulo. |
| `created_at`, `updated_at`, `deleted_at` | timestamp | sim | `SoftDeletes` no model. |

### 1.3 `archives` — imagens e arquivos

Origem: `v6/database/migrations/2018_04_02_181807_create_archives_table.php`
(+ `2022_04_12_124253_add_column_to_archives_table.php`,
`2022_11_29_092240_add_viewpdf_to_archives_table.php`,
`2023_07_24_130504_add_column_type_size_id_to_archives_table.php`).

| Coluna | Tipo | Null | Papel no módulo |
|---|---|---|---|
| `id` | integer | não (PK) | — |
| `contents_id` | integer | sim | FK → `contents(id)` (`archives_contents_id_foreign`). |
| `name` | varchar(300) | sim | Nome do arquivo; usado na URL `storage/content/{slug}/{id}/destaque/{name}`. |
| `path` | varchar(300) | sim | Caminho relativo no storage. |
| `title`, `description`, `type`, `hash`, `extension` | varchar/text | sim | Metadados; `extension` filtra a galeria (só imagens). |
| `highlight` | boolean | sim | `true` = imagem de destaque; `null` = item de galeria. |
| `view_pdf` | boolean | sim | Marca PDF (excluído da galeria). |
| `order` | integer | sim | Ordem de exibição na galeria. |
| `type_size_id`, `width`, `height` | bigint/varchar | sim | Dimensões (usadas pelo editor de imagem). |
| `deleted_at` | timestamp | sim | Soft delete. |

### 1.4 `links` — vídeo do YouTube

Criada pelo contêiner genérico; `Content::linksUnique()` (`hasOne(Link::class,'contents_id','id')`).
Guarda o link normalizado do vídeo (`ContentController@store`:
`sanitizeLink(normalizeVideoLink($request->get('link')))`, em
`v6/app/Http/Controllers/Prefecture/ContentController.php:718`).

### 1.5 Tabelas de registry

| Tabela | Registro do módulo |
|---|---|
| `modulos` | `id = 47`, `name = 'Unidades de Atendimento'`, `slug = 'unidades-de-atendimento'`, `status = 0`. |
| `resources` | `id = 66`, `slug = 'unidades-de-atendimento'`, `modulo_id = 47`. |
| `menu_orders` | `slug = 'unidades-de-atendimento'`, `modulo_id = 47`, `clicks = 0`. |
| `configurations` | `key = 'layout'` define em qual layout o portal renderiza o módulo. |

## 2. View SQL `unidades_de_atendimento`

Criada por `v6/database/migrations/2024_11_30_102902_create_view_unidades_de_atendimento.php`:

```sql
CREATE OR REPLACE VIEW unidades_de_atendimento AS
SELECT row_number() OVER (ORDER BY c.publication_date DESC) AS value,
    c.title AS titulo,
    to_char(c.publication_date::timestamp with time zone, 'DD/MM/YYYY às HH24:MI'::text) AS data_e_hora,
    c.id,
    c.galery as galeria,
    c.slug as slug
FROM contents c
WHERE c.deleted_at IS NULL AND c.category_contents_id = (
    SELECT id FROM category_contents WHERE slug = 'unidades-de-atendimento'
);
```

Rollback: `DROP VIEW if exists unidades_de_atendimento;`.

**O que a view abre:**

| Coluna da view | Origem | Uso |
|---|---|---|
| `value` | `row_number()` sobre `publication_date desc` | Índice exibido na 1ª coluna da DataTable (`v6/public/assets/js/custom/utilities/table.js:9-14`). |
| `titulo` | `contents.title` | 2ª coluna da DataTable (`datatable.js:3-9`). |
| `data_e_hora` | `publication_date` formatado | Não exibido na DataTable atual (coluna ausente no JS). |
| `id` | `contents.id` | Usado nos links de editar/excluir. |
| `galeria` | `contents.galery` | Habilita o botão de galeria (`createGaleryButton`). |
| `slug` | `contents.slug` | Não usado pela DataTable atual. |

**Limitações da view:** (a) o `row_number()` é reescrito a cada consulta, logo a
numeração exibida não é estável entre páginas/requisições; (b) o filtro de
`deleted_at IS NULL` não considera o `SoftDeletes` do model, apenas o valor
bruto; (c) a ordenação do `row_number()` **não** inclui o desempate por `id`
usado no portal, podendo divergir da ordem exibida no portal.

Estado atual no banco: view existe e retorna **0 linhas** (nenhum conteúdo
cadastrado na categoria 44).

## 3. Relacionamentos Eloquent

```
CategoryContent (category_contents)
        ▲
        │ belongsTo (category_contents_id)          hasMany (category_contents_id)
        │                                              ▼
   Content (contents) ──────────────── hasMany ──► Archive (archives)
        │
        └── hasOne (contents_id) ──────────────────► Link (links)
```

| Relação | Model | Definição |
|---|---|---|
| `Content::categoryContents()` | `belongsTo(CategoryContent::class,'category_contents_id','id')` | `v6/app/Http/Model/Prefecture/Content.php:107-110` |
| `Content::archives()` | `hasMany(Archive::class,'contents_id','id')` | `v6/app/Http/Model/Prefecture/Content.php:63-66` |
| `Content::linksUnique()` | `hasOne(Link::class,'contents_id','id')` | `v6/app/Http/Model/Prefecture/Content.php:116-119` |
| `CategoryContent::categoryContents()` | `hasMany(CategoryContent::class,'id','category_contents_id')` | `v6/app/Http/Model/Prefecture/CategoryContent.php:44-47` |
| `Archive::content()` | `belongsTo(Content::class,'contents_id','id')` | `v6/app/Http/Model/Prefecture/Archive.php:47-50` |

## 4. Migrations relevantes

| Migration | Efeito |
|---|---|
| `v6/database/migrations/2018_04_01_180916_create_category_contents_table.php` | Cria `category_contents`. |
| `v6/database/migrations/2018_04_02_180433_create_contents_table.php` | Cria `contents`. |
| `v6/database/migrations/2018_04_02_181807_create_archives_table.php` | Cria `archives`. |
| `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` | Insere o módulo (id 47, slug `unidades-de-atendimento`). |
| `v6/database/migrations/2024_11_30_102902_create_view_unidades_de_atendimento.php` | Cria a view SQL do CMS. |

Nenhuma migration cria tabela própria do módulo.

## 5. Seeds

| Seed | Efeito |
|---|---|
| `v6/database/seeds/CategoryContentTableSeeder.php:243-247` | `CategoryContent::updateOrCreate(['slug'=>'unidades-de-atendimento'], ['name'=>'Unidades de Atendimento','status_create'=>1])` (sem pai). |
| `v6/database/seeds/ResourcesTableSeeder.php:576-582` | `Resource::updateOrCreate(['slug' => 'unidades-de-atendimento'], ['name' => 'Unidades de Atendimento', 'description' => 'Unidades de Atendimento', 'modulo_id' => 47, 'created_at' => date("Y-m-d H:i:s")])`. |
| `v6/database/seeds/MenuOrderSeeder.php:332-338` | `MenuOrder::updateOrCreate(['slug'=>'unidades-de-atendimento'], ['clicks'=>0,'modulo_id'=>47])`. |
| `v6/database/migrations/2024_11_01_150206_create_modulos_table.php:305-310` | Registro em `modulos` (executado como migration, não seed). |

## 6. Regras de integridade observadas

- `contents.category_contents_id` é **NOT NULL** → todo registro do módulo
  aponta obrigatoriamente para uma categoria.
- Não há índice único em `contents.slug` (apenas `contents_pkey`); a
  desambiguação é feita em runtime pelo sufixo `-<max(id)+1>`
  (`v6/app/Http/Controllers/Prefecture/ContentController.php:665-669`).
- `archives.contents_id` é FK com cascade apenas em tabelas de destaque
  (`highlight_id`); para `contents` não há `ON DELETE CASCADE`, o que exige
  remoção manual/soft delete dos arquivos antes do conteúdo.
