# Menus do Site — Modelagem de Dados

> Schema lido do PostgreSQL da v6 em modo somente leitura:
> `docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 -c "\d menus" -c "\d menu_items" -c "\d menu_orders"`

---

## 1. Tabela `menus`

Agregador de navegação. Uma linha por "grupo" de navegação (Principal, Rodapé,
Acesso Rápido, Atalhos).

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` | não | `nextval('menus_id_seq')` | PK |
| `name` | `varchar(191)` | não | — | Rótulo exibido no CMS |
| `slug` | `varchar(191)` | não | — | Gerado por `str_slug($request['name'])` no `MenuRepository` |
| `status_create` | `boolean` | sim | — | Usado **apenas** para ordenar a view `menus_site` |
| `created_at` | `timestamp(0)` | sim | — | |
| `updated_at` | `timestamp(0)` | sim | — | |
| `deleted_at` | `timestamp(0)` | sim | — | *Soft delete* (o model usa `SoftDeletes`) |

Restrições: apenas a PK `menus_pkey`.

### Dados atuais (SELECT `id, name, slug, status_create, deleted_at FROM menus ORDER BY id`)

| id | name | slug | status_create |
|---|---|---|---|
| 1 | Principal | `principal` | `t` |
| 2 | Rodapé | `rodape` | `t` |
| 3 | Acesso Rápido | `acesso-rapido` | `t` |
| 4 | Atalhos | `atalhos` | `t` |

> **Não existe a linha `id = 5, slug = 'tags'`** na v6. O seeder que a criava
> (`v5/database/seeds/AddMenuTagsToProductionSeeder.php`) **não foi portado** —
> ver `09-TAGS-DE-MENU.md` e `08-GAPS-E-DIVIDAS.md`.
>
> Também não existe a linha `slug = 'abas'` usada como fallback em
> `v6/resources/views/portal/layouts/modelo5/home/transparencia/transparencia-fiscal-grid.blade.php:37`.

---

## 2. Tabela `menu_items`

Item da navegação. É a tabela central do módulo.

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` | não | `nextval('menu_items_id_seq')` | PK |
| `order` | `integer` | não | — | Posição; renumerado a cada drag-and-drop. *Seed* `AlterColumnTableMenuItemsSeeder` deixa a coluna nullable |
| `name` | `varchar(191)` | não | — | Texto do item |
| `created_at` | `timestamp(0)` | sim | — | |
| `updated_at` | `timestamp(0)` | sim | — | |
| `deleted_at` | `timestamp(0)` | sim | — | Coluna herdada do `softDeletes()` original, **sem uso**: o model tem `//use SoftDeletes;` (`MenuItem.php:18`), logo a remoção é física |
| `menus_id` | `integer` | não | — | FK -> `menus.id` |
| `menus_items_id` | `integer` | sim | — | FK -> `menu_items.id` (auto-referência = item pai) |
| `type` | `integer` | sim | — | `0` link, `1` página estática, `2` categoria/conteúdo |
| `links_id` | `integer` | sim | — | FK -> `links.id` (usado quando `type = 0`) |
| `static_pages_id` | `integer` | sim | — | FK -> `static_pages.id` (usado quando `type = 1`) |
| `category_contents_id` | `integer` | sim | — | FK -> `category_contents.id` (usado quando `type = 2`) |
| `contents_id` | `integer` | sim | — | FK -> `contents.id` (conteúdo específico dentro da categoria) |
| `status_create` | `boolean` | sim | — | Sem uso observável no módulo |
| `isBlank` | `boolean` | sim | — | Abre o link em nova aba |
| `icon` | `varchar(191)` | sim | — | Classe CSS do ícone (ionicons) |
| `second_name` | `varchar(191)` | sim | — | "Sobrenome" do item (uso livre; exibido apenas no formulário) |
| `is_active` | `boolean` | **não** | `true` | Controla a exibição no portal |
| `is_mega_menu` | `boolean` | sim | `false` | Item raiz renderizado como mega menu (modelo1 e modelo5) |

Restrições de integridade (FKs):

| Constraint | Coluna | Referência |
|---|---|---|
| `menu_items_menus_id_foreign` | `menus_id` | `menus(id)` |
| `menu_items_menus_items_id_foreign` | `menus_items_id` | `menu_items(id)` |
| `menu_items_links_id_foreign` | `links_id` | `links(id)` |
| `menu_items_static_pages_id_foreign` | `static_pages_id` | `static_pages(id)` |
| `menu_items_category_contents_id_foreign` | `category_contents_id` | `category_contents(id)` |
| `menu_items_contents_id_foreign` | `contents_id` | `contents(id)` |

> A coluna `link` **não existe** na tabela `menu_items` na v6, apesar de o seeder
> `v6/database/seeds/AddLinkColumnMenuItemsTableSeeder.php` criá-la e de o
> `v5/app/Http/Model/Prefecture/MenuItem.php` listar `link` no `$fillable`.
> A view `v6/resources/views/prefecture/menus-itens/form.blade.php` ainda usa
> `$menuItem->link`, que resolve para `null` (atributo inexistente).

---

## 3. Tabela `menu_orders`

Ordem dos **módulos do CMS** no menu lateral e contador de acessos. Não é a
tabela de itens de menu do portal — é a tabela que define a ordem e a
visibilidade dos módulos do painel.

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` | não | `nextval('menu_orders_id_seq')` | PK |
| `slug` | `varchar(191)` | sim | — | Slug do módulo (ex.: `menu`, `noticias`, `lai`) |
| `clicks` | `integer` | não | — | Contador de acessos ao módulo |
| `created_at` | `timestamp(0)` | sim | — | |
| `updated_at` | `timestamp(0)` | sim | — | |
| `deleted_at` | `timestamp(0)` | sim | — | *Soft delete* |
| `modulo_id` | `integer` | sim | — | FK -> `modulos.id` |

Constraint: `menu_orders_modulo_id_foreign` -> `modulos(id)`.

### Vínculo deste módulo

`menu_orders.id = 24` tem `slug = 'menu'` e `modulo_id = 31`
(`modulos.slug = 'menus-do-site'`, `name = 'Menus do site'`, `status = t`).
Esse par é o que faz o `MenuServiceProvider` renderizar a entrada
*"Menus do site"* no menu lateral do CMS
(`v6/app/Providers/MenuServiceProvider.php:332-334`, `case 'menu'`).

O `resources` correspondente é `resources.id = 35`, `slug = 'menus-do-site'`,
`modulo_id = 31`.

---

## 4. Views SQL

| View | Criada por | Consulta | Usada em |
|---|---|---|---|
| `menus_site` | `v6/database/migrations/2023_04_13_151009_create_view_to_menus.php` | `SELECT row_number() OVER (ORDER BY (m.status_create is true) DESC) AS value, m.id, m.name, m.slug FROM menus m WHERE m.deleted_at IS NULL ORDER BY (m.status_create is true) DESC` | `DatatableController@get_menus_site` (`v6/app/Http/Controllers/Prefecture/DatatableController.php:349-353`) |

Pontos de atenção:

- A view expõe uma coluna `value` (numeração sequencial) usada pelo DataTable
  como primeira coluna (`v6/public/assets/js/custom/utilities/table.js:8-14`).
- A ordenação depende de `status_create`: menus "criados" (`true`) vêm primeiro.
- A view **não** expõe `status_create`, `created_at` nem `deleted_at` — o
  DataTable só enxerga `value`, `id`, `name`, `slug`.
- O `down()` da migration usa `Drop view if exists public.menus_site` — DDL
  case-insensitive, funciona no PostgreSQL.

---

## 5. Relacionamentos Eloquent

### `App\Prefecture\Menu` (`v6/app/Http/Model/Prefecture/Menu.php`)

| Relação | Tipo declarado | FKS | Avaliação |
|---|---|---|---|
| `menuItems()` | `belongsTo(MenuItem::class, 'menus_id', 'id')` | `menus_id` | **Errado**: uma linha de `menus` tem *vários* `menu_items`. Deveria ser `hasMany(MenuItem::class, 'menus_id', 'id')`. Nenhum código usa a relação. |

Propriedades: `$table = 'menus'`, `$fillable = ['name', 'slug', 'status_create']`,
`$primaryKey = 'id'`, `use SoftDeletes`.

### `App\Prefecture\MenuItem` (`v6/app/Http/Model/Prefecture/MenuItem.php`)

| Relação / escopo | Linha | Tipo | Observação |
|---|---|---|---|
| `menus()` | `:43` | `hasMany(Menu::class, 'id', 'menus_id')` | Chaves invertidas em relação ao padrão do Laravel; funcional por acidente (PK x FK trocadas) |
| `links()` | `:51` | `hasOne(Link::class, 'id', 'links_id')` | Idem: PK/FK trocadas |
| `staticPages()` | `:59` | `hasOne(StaticPage::class, 'id', 'static_pages_id')` | Idem |
| `categoryContent()` | `:67` | `hasOne(CategoryContent::class, 'id', 'category_contents_id')` | Idem |
| `content()` | `:75` | `hasOne(Content::class, 'id', 'contents_id')` | Idem |
| `menuBelongs()` | `:83` | `belongsTo(MenuItem::class, 'menus_items_id')` | Relação com o item pai |
| `subItens()` | `:91` | `hasMany(MenuItem::class, 'menus_items_id', 'id')->orderBy('order')` | Filhos de 1o nivel, ja ordenados |
| `menuItemsWithoutCategory()` | `:115` | `hasMany(MenuItem::class, 'menus_items_id', 'id')->whereNotNull('category_contents_id')->orderBy('order')` | Usado no rodapé (`FooterServiceProvider`) |
| `scopeSubItensLv2()` | `:126` | local scope | Eager loads `staticPages`, `content`, `categoryContent`, `links` e `subItens` (2 niveis) |
| `isFather()` | `:99` | metodo | `is_null($this->attributes['menus_items_id'] ?? null)` — **nome invertido**: retorna `true` quando o item e RAIZ (nao tem pai) |
| `getIsFatherAttribute()` | `:110` | accessor (`$appends = ['is_father']`) | Mesma logica do metodo acima |

> O `use SoftDeletes;` esta comentado em `MenuItem.php:18`, apesar de a tabela ter
> `deleted_at`. A importacao de `SoftDeletes` permanece no topo do arquivo.

### `App\Prefecture\MenuOrder` (`v6/app/Http/Model/Prefecture/MenuOrder.php`)

| Relação | Tipo | Observação |
|---|---|---|
| `modulo()` | `belongsTo(Modulos::class, 'modulo_id')` | Usada no `MenuServiceProvider` para checar `$menu->modulo->status` |

Propriedades: `$fillable = ['slug', 'clicks', 'modulo_id']`, `use SoftDeletes`.

---

## 6. Chaves e indices

| Objeto | Tipo | Colunas |
|---|---|---|
| `menus_pkey` | PK | `id` |
| `menu_items_pkey` | PK | `id` |
| `menu_orders_pkey` | PK | `id` |

Indices secundarios: **nenhum**. Nao ha indice por `menus_id`, `menus_items_id`
nem por `order`, embora as consultas do portal filtrem exatamente por esses
campos — relevante para a reconstrucao (ver `06-MIGRACAO-MODERNA.md`).

---

## 7. Seeds

### v6 (`v6/database/seeds/`)

| Seeder | O que faz | Registrado em `DatabaseSeeder.php` |
|---|---|---|
| `MenusTableSeeder.php` | `updateOrCreate` dos menus `principal`, `rodape`, `acesso-rapido`, `atalhos` com `status_create = '1'` | sim (linha 37) |
| `MenuItemsTableSeeder.php` (1128 linhas) | Cria a arvore padrao de itens (cabecalho, rodape, acesso rapido) referenciando `contents`, `category_contents` e `links` por slug/nome | sim (linha 39) |
| `MenuOrderSeeder.php` (372 linhas) | `updateOrCreate` das linhas de `menu_orders` com `modulo_id` fixo por slug; a linha deste modulo e `slug = 'menu'` com `modulo_id = 31` (`MenuOrderSeeder.php:187-193`) | sim (linha 67) |
| `UpdateMenuItemsSeeder.php` | Ajusta `type`/`category_contents_id` dos itens "Secretarias" (cat. 7) e "Orgaos" (cat. 6) | nao |
| `AddLinkColumnMenuItemsTableSeeder.php` | Cria a coluna `link` em `menu_items` se nao existir (a coluna **nao existe** no banco da v6) | nao |
| `AlterColumnTableMenuItemsSeeder.php` | Torna `menu_items.order` nullable | nao |
| `MenuLinkContatoSeeder.php` | Cria/atualiza paginas estaticas "Contato"/"Ouvidoria", os `links` correspondentes e os itens do menu `menus_id = 4` | nao |
| `UpdateModuloSelectOptions.php` | Reescreve o bloco `#modulos-select` dentro do HTML da chave `tags` da tabela `layouts` (idempotente, contagem de divs) | nao |
| `AddToLayoutsNewTag.php` | `updateOrInsert` da chave `tags` na tabela `layouts` (lista de tags do editor de layout) | nao |
| `RolesTableSeeder.php:86` | Define as permissoes `menu.index/create/edit/delete/inactive/inactivate/restore/show` (todas `false` no papel base) | sim |

> O `MenuOrderSeeder.php` cria a linha `slug = 'menu'` com `modulo_id = 31`
> (`v6/database/seeds/MenuOrderSeeder.php:187-193`), o que casa com a linha
> existente no banco (`menu_orders.id = 24`).

### v5 (existentes na v5, ausentes na v6)

| Seeder v5 | Situação na v6 |
|---|---|
| `v5/database/seeds/AddMenuTagsToProductionSeeder.php` | **Ausente na v6.** Insere `menus` com `id = 5`, `name = 'Tags'`, `slug = 'tags'`, `status_create = '1'` |
| `v5/database/seeds/AddMenuTagsWidgetSeeder.php` | **Ausente na v6.** Insere a chave `menu-tags-widget` em `layouts`, apontando para o path `portal.layouts.modelo5.layout.menus.menu_tags` |
| `v5/database/seeds/MenuAcessoRapidoSeeder.php` | Ausente na v6 (coberto por `MenuItemsTableSeeder`) |
| `v5/database/seeds/MenuOrderTableSeeder.php` | Renomeado para `MenuOrderSeeder.php` na v6 |
| `v5/database/seeds/OmbudsmanMenuOrderSeeder.php` | Ausente na v6 |
| `v5/database/seeds/AddItemsToMenuOrderTable.php` | Ausente na v6 |
| `v5/database/seeds/AddToLayoutsNewTagWithMenu.php` | Ausente na v6 (existe `AddToLayoutsNewTag.php`) |

> Nenhum desses seeds e chamado pelo `DatabaseSeeder` da v5 — sao executados
> manualmente.

---

## 8. Consultas de leitura mais importantes

| Consulta | Quem faz | Frequencia |
|---|---|---|
| `menu_items WHERE menus_id = 1 AND menus_items_id IS NULL ORDER BY order` (+ eager loads de 2 niveis) | `MenuServiceProvider` | **Toda** requisicao do portal |
| `menu_items WHERE menus_id = 3 AND is_active = true AND menus_items_id IS NULL ORDER BY order` | `MenuServiceProvider` | Toda requisicao do portal |
| `menu_items WHERE menus_id = 4` (colunas `id`, `name`, `links_id`) | `MenuServiceProvider` | Toda requisicao do portal |
| `menu_items WHERE menus_id = 2 AND menus_items_id IS NULL ORDER BY order LIMIT 1` | `FooterServiceProvider` | Toda requisicao do portal |
| `menu_items WHERE menus_id = 1 AND category_contents_id IS NULL OR category_contents_id = 2 ORDER BY order` | `FooterServiceProvider` | Toda requisicao do portal (note a precedencia do `OR`) |
| `SELECT * FROM menus_site` | `DatatableController@get_menus_site` | Carga da tela `/cms/menu` |
| `menu_items WHERE menus_items_id = ?` | Blade `menus-itens/show.blade.php` (3 niveis, dentro de `@foreach`) | **N+1** na tela de itens |
