# Menus do Site — Arquitetura

> Versão analisada: `v6` (Laravel 5.5.50 / PHP 7.2.34 / PostgreSQL 10).
> Slug do módulo: `menus-do-site` (`modulos.id = 31`).

---

## 1. Visão em camadas

O módulo é um CRUD clássico do CMS com um consumo indireto pelo portal. Não há
serviço de domínio, nem API dedicada, nem fila: o "serviço" é o *presenter*
`MenuItemPresenter`, que resolve a URL de cada item.

```
┌──────────────────────────────────────────────────────────────────────┐
│ CMS (prefixo /cms, grupo 'cms' + 'before' => 'csrf' em routes/basepro)│
└──────────────────────────────────────────────────────────────────────┘
   Route::resource('menu', 'Prefecture\MenuController');         v6/routes/basepro.php:170
   Route::resource('menu-item', 'Prefecture\MenuItemController'); v6/routes/basepro.php:171
              │
              ▼
   Middleware 'check.route.permission'  (registrado no construtor)
              │
              ▼
   Controller  App\Http\Controllers\Prefecture\MenuController
              │  App\Http\Controllers\Prefecture\MenuItemController
              │
              ├──► FormRequest  App\Http\Requests\MenuItemRequest
              │      (só MenuItemController; MenuController usa Request cru)
              │
              ├──► Repository   App\Repositories\Eloquent\Prefecture\Menu\MenuRepository
              │                 App\Repositories\Eloquent\Prefecture\MenuItem\MenuItemRepository
              │                 (base: App\Repositories\Repository::upInsert)
              │
              ├──► Model        App\Prefecture\Menu
              │                 App\Prefecture\MenuItem  (PresentableTrait)
              │                 App\Prefecture\MenuOrder
              │
              ▼
   PostgreSQL: menus, menu_items, menu_orders (+ links, static_pages,
                category_contents, contents, modulos, resources)
              │
              ▼
   View        prefecture.menus.*      (index/create/edit/form/show)
               prefecture.menus-itens.* (index/create/edit/form/show)
   JS          assets/js/custom/contents/menus/datatable.js
               jQuery UI Sortable (inline em menus-itens/show.blade.php)

┌──────────────────────────────────────────────────────────────────────┐
│ PORTAL (v6/routes/web.php + view composers)                          │
└──────────────────────────────────────────────────────────────────────┘
   App\Providers\MenuServiceProvider::boot()
       ├─ Evento BuildingMenu (AdminLTE)  → menu lateral do CMS
       ├─ composer portal.layouts.modelo1..6.layout.*  → $menuHeader
       ├─ composer portal.layouts.modelo1..6.*         → $menuRapido
       └─ composer (headers dos 6 modelos)             → $atalhos, $contact_link
   App\Providers\FooterServiceProvider::boot()
       └─ composer portal.layouts.modelo1..5.*, components.* → $menus (rodapé)
```

---

## 2. Fluxo de requisição — listagem de menus (CMS)

1. GET `/cms/menu` → `MenuController@index`
   (`v6/app/Http/Controllers/Prefecture/MenuController.php:51`).
2. O construtor (`MenuController.php:37-44`) executa `menuClicks('menu')`
   (incrementa `menu_orders.clicks` da linha de slug `menu`) e aplica o
   middleware `check.route.permission`.
3. `index()` carrega `Menu::all()` (aplica o *global scope* de `SoftDeletes`) e
   devolve `prefecture.menus.index`.
4. A view `v6/resources/views/prefecture/menus/index.blade.php` desenha apenas o
   `<table id="tabela_menus">` e carrega
   `v6/public/assets/js/custom/contents/menus/datatable.js`.
5. O JS chama `runDatatables('menus', collums)`
   (`v6/public/assets/js/custom/utilities/table.js:3`), que faz
   `GET /cms/datatable/menus`.
6. A rota `v6/routes/basepro.php:445` (grupo `prefix => 'datatable'`, iniciado em
   `basepro.php:396`) chama `DatatableController@get_menus_site`
   (`v6/app/Http/Controllers/Prefecture/DatatableController.php:349-353`), que lê a
   **view SQL** `menus_site` e aplica `struct_views($envios, 'menu')` para anexar
   as flags `edit`, `delete`, `active`, `inactivate` conforme as permissões
   (`DatatableController.php:504-520`).
7. O JS monta os botões "Editar" (`/cms/menu/{id}/edit`) e "Ajustar itens"
   (`/cms/menu-item/{id}`).

> Observação: o passo 3 (`Menu::all()`) é **desnecessário** — a view não usa a
> variável `$menus`; o dado vem da view SQL no passo 6.

---

## 3. Fluxo de requisição — criação de item (CMS)

1. POST `/cms/menu-item` → `MenuItemController@store`
   (`v6/app/Http/Controllers/Prefecture/MenuItemController.php:91`).
2. `MenuItemRequest::rules()` (`v6/app/Http/Requests/MenuItemRequest.php:31-84`)
   valida conforme `type`:
   - `type = 0`: `menus_id`, `name`, `link` obrigatórios;
   - `type = 1`: `menus_id`, `name`, `static_pages_id` (com `SelectRule`);
   - `type = 2`: `menus_id`, `name`, `category_contents_id` obrigatórios;
   - nos três casos, se já existem **6 ou mais itens raiz** no menu e a rota atual
     é `menu-item.store`, `menus_items_id` passa a ser obrigatório.
3. O controller injeta `is_mega_menu` (checkbox -> `1`/`0`) no request
   (`MenuItemController.php:92`).
4. `MenuItemRepository::structureUpInsert()`
   (`v6/app/Repositories/Eloquent/Prefecture/MenuItem/MenuItemRepository.php:18-44`):
   - normaliza `isBlank` e `is_mega_menu` para `0` quando ausentes;
   - se `type == 0`, **cria uma linha em `links`** (`Link::create`) com
     `title`/`name`/`alt` = nome do item e `link` = link informado, guardando o
     novo id em `links_id`;
   - se `sub_category_contents_id` vier preenchido, sobrescreve
     `category_contents_id`;
   - devolve o array filtrado por `filter_request`.
5. `Repository::upInsert()` (`v6/app/Repositories/Repository.php:52-64`) roda
   `DB::transaction` e chama `updateOrCreate(['id' => $id], $data)`.
6. Grava log de atividade (`activity()->performedOn($result)`), emite `flash()` e
   redireciona para `menu-item.show` com o `menus_id` do request.
7. `MenuItemController@show` (`MenuItemController.php:122-147`) monta a tela:
   itens raiz (`whereNull('menus_items_id')`), todos os itens do menu, páginas
   estáticas, categorias raiz e `order = MenuItem::max('order') + 1`.

---

## 4. Fluxo de requisição — reordenação (CMS)

1. O JS inline em `v6/resources/views/prefecture/menus-itens/show.blade.php`
   (bloco `sortable`) serializa os `id` de todos os `.item-list` e faz
   `POST /cms/ajust-items-lvl-1`.
2. A rota `v6/routes/basepro.php:122` chama `MenuItemController@ajustOrder`
   (`MenuItemController.php:62-85`): para cada id recebido, faz `find`, grava
   `order` renumerado de 1 em 1 e `save()` — **um UPDATE por item** (sem transação).
3. O método registra log de atividade e responde `json(1)`.

---

## 5. Fluxo de requisição — leitura do menu pelo portal

Não há rota específica: a leitura é feita por *view composer*.

1. `App\Providers\MenuServiceProvider::boot()` roda em toda requisição web.
2. Para o cabeçalho, carrega os itens raiz de `menus_id = 1` com `subItens`
   (até 2 níveis) e `SubItensLv2`:
   `v6/app/Providers/MenuServiceProvider.php:428-434`.
3. `view()->composer()` com a lista `portal.layouts.modelo1.layout.*` ate
   `portal.layouts.modelo6.layout.*` injeta `$menuHeader`.
4. As views `portal/layouts/modelo{1..6}/layout/nav*.blade.php` iteram
   `$menuHeader->where('is_active', true)` e chamam
   `$item->present()->getLink` / `->targetLink`.

> Consequência: **toda** página do portal paga 1 consulta para o menu principal
> (mais as eager loads). Não há cache.

---

## 6. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `App\Prefecture\Menu` | `v6/app/Http/Model/Prefecture/Menu.php` | Model da tabela `menus`; `SoftDeletes`; `fillable = name, slug, status_create`; relação `menuItems()` (declarada como `belongsTo`, incorreto) |
| `App\Prefecture\MenuItem` | `v6/app/Http/Model/Prefecture/MenuItem.php` | Model da tabela `menu_items`; `PresentableTrait` + `MenuItemPresenter`; `SoftDeletes` comentado; relações `menus()`, `links()`, `staticPages()`, `categoryContent()`, `content()`, `menuBelongs()`, `subItens()`, `menuItemsWithoutCategory()`; `scopeSubItensLv2`; accessor `is_father` |
| `App\Prefecture\MenuOrder` | `v6/app/Http/Model/Prefecture/MenuOrder.php` | Model da tabela `menu_orders`; `SoftDeletes`; `fillable = slug, clicks, modulo_id`; relação `modulo()` -> `App\Http\Model\Admin\Modulos` |
| `App\Http\Model\Admin\Modulos` | `v6/app/Http/Model/Admin/Modulos.php` | Catálogo de módulos do CMS (`modulos`), `fillable = status` |
| `App\Http\Controllers\Prefecture\MenuController` | `v6/app/Http/Controllers/Prefecture/MenuController.php` | CRUD de menus (`index`, `create`, `store`, `show`, `edit`, `update`, `destroy`) |
| `App\Http\Controllers\Prefecture\MenuItemController` | `v6/app/Http/Controllers/Prefecture/MenuItemController.php` | CRUD de itens + `getQtdSubItemsByItems` + `ajustOrder` + `toggle` |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` |Fonte de dados do DataTable de menus (`get_menus_site`) |
| `App\Http\Controllers\Prefecture\LayoutController` | `v6/app/Http/Controllers/Prefecture/LayoutController.php` | Editor de layout: lê/escreve a chave `tags` e `secretarias-grid` da tabela `layouts`; oferece os menus ao seletor de módulo |
| `App\Repositories\Eloquent\Prefecture\Menu\MenuRepository` | `v6/app/Repositories/Eloquent/Prefecture/Menu/MenuRepository.php` | `structureUpInsert` deriva o `slug` via `str_slug($request['name'])` |
| `App\Repositories\Eloquent\Prefecture\MenuItem\MenuItemRepository` | `v6/app/Repositories/Eloquent/Prefecture/MenuItem/MenuItemRepository.php` | `structureUpInsert` normaliza flags e cria o registro em `links` quando `type = 0` |
| `App\Repositories\Repository` | `v6/app/Repositories/Repository.php` | Base com `upInsert`, `destroy`, `destroyAll`, `destroyWithRelation`, `filter_request`, `traitRequest` |
| `App\Repositories\Contracts\IMenuRepository` | `v6/app/Repositories/Contracts/IMenuRepository.php` | Contrato **vazio** (sem métodos declarados) |
| `App\Repositories\Contracts\IMenuItemRepository` | `v6/app/Repositories/Contracts/IMenuItemRepository.php` | Contrato **vazio** (sem métodos declarados) |
| `App\Http\Requests\MenuItemRequest` | `v6/app/Http/Requests/MenuItemRequest.php` | Validação por `type` + regra do limite de 6 itens raiz |
| `App\Http\Presenters\MenuItemPresenter` | `v6/app/Http/Presenters/MenuItemPresenter.php` | Resolve `getLink()`, `getShortLink()` e `targetLink()` do item |
| `App\Http\Presenters\DefaultPresenter` | `v6/app/Http/Presenters/DefaultPresenter.php` | Base dos presenters (`App\Http\Presenters\DefaultPresenter`) |
| `App\Providers\MenuServiceProvider` | `v6/app/Providers/MenuServiceProvider.php` | Monta o menu lateral do CMS (AdminLTE) e injeta `$menuHeader`, `$menuRapido`, `$atalhos`, `$contact_link` nas views do portal |
| `App\Providers\FooterServiceProvider` | `v6/app/Providers/FooterServiceProvider.php` | Injeta `$menus` (rodapé) a partir de `menus_id = 2` e parte de `menus_id = 1` |
| `App\Http\Middleware\CheckRoutePermission` | `v6/app/Http/Middleware/CheckRoutePermission.php` | Autorização por nome de rota; mapeia `menu-item.*` -> `menu.*` |
| `App\Library\Helper\appHelpers` | `v6/app/Library/Helper/appHelpers.php` | `has_access()` (função global, linha 22) usada nas views e no `MenuServiceProvider` |
| `App\Http\Controllers\Portal\SitemapController` | `v6/app/Http/Controllers/Portal/SitemapController.php` | Consumidor colateral: gera o sitemap a partir de `menu_items` com `links_id` |
| `App\Http\Controllers\Prefecture\MenuSecretaryController` | `v6/app/Http/Controllers/Prefecture/MenuSecretaryController.php` | **Homônimo, não relacionado**: menu de categorias das secretarias |

### Dependências externas (composer)

| Pacote | Uso no módulo |
|---|---|
| `laracasts/presenter` (`PresentableTrait`) | `MenuItem` -> `present()` -> `MenuItemPresenter` |
| `yajra/laravel-datatables` (`^1.5`) | `DatatableController::get_menus_site` (DataTables server-side) |
| `cartalyst/sentinel` | `Sentinel::getUser()` nos logs de atividade; `has_access()` |
| `spatie/laravel-activitylog` (`^2.8`) | `activity()->performedOn($model)->log($texto)` |
| `laravelcollective/html` (`^5.5`, helper `Form::`) | Formulários Blade do CMS (`Form::open`, `Form::model`, `Form::select`) |
| `jQuery UI Sortable` (asset) | Reordenação por arrasto na tela de itens |

> Versões confirmadas em `v6/composer.json:19-43`.

---

## 7. Diagrama de fluxo (Mermaid)

```mermaid
flowchart TD
    A[Operador no CMS] --> B[GET /cms/menu]
    B --> C[MenuController@index]
    C --> D[prefecture.menus.index]
    D --> E[datatable.js]
    E --> F[GET /cms/datatable/menus]
    F --> G[DatatableController@get_menus_site]
    G --> H[(view menus_site)]
    G --> I[struct_views: flags de permissao]

    A --> J[GET /cms/menu-item/{id}]
    J --> K[MenuItemController@show]
    K --> L[(menu_items)]
    K --> M[prefecture.menus-itens.show]

    A --> N[POST /cms/menu-item]
    N --> O[MenuItemRequest]
    O --> P[MenuItemRepository::structureUpInsert]
    P --> Q[(links)]
    P --> R[(menu_items)]
    R --> S[menu-item.show]

    A --> T[arrastar item]
    T --> U[POST /cms/ajust-items-lvl-1]
    U --> V[MenuItemController@ajustOrder]
    V --> R

    W[Cidadao no portal] --> X[MenuServiceProvider::boot]
    X --> Y[(menu_items)]
    X --> Z[view composer injeta menuHeader/menuRapido/atalhos]
    Z --> AA[nav.blade.php de cada modelo]
```

---

## 8. Onde o módulo é consumido (dependências de outros módulos)

| Consumidor | Caminho | Uso |
|---|---|---|
| Portal — menu principal | `v6/app/Providers/MenuServiceProvider.php:428-452` | `$menuHeader` (menus_id = 1) |
| Portal — acesso rápido | `v6/app/Providers/MenuServiceProvider.php:454-469` | `$menuRapido` (menus_id = 3, só ativos) |
| Portal — atalhos do topo | `v6/app/Providers/MenuServiceProvider.php:471-484` | `$atalhos` (menus_id = 4) |
| Portal — rodapé | `v6/app/Providers/FooterServiceProvider.php:64-80` | `$menus` (menus_id = 2 + parte do 1) |
| Portal — grid "Serviços" (modelo5) | `v6/resources/views/portal/layouts/modelo5/home/transparencia/transparencia-fiscal-grid.blade.php:34-49` | lê `menus` e `menu_items` quando o módulo escolhido no editor é `menus` |
| Portal — mega menu | `v6/resources/views/portal/components/mega-menu-col.blade.php` | colunas do mega menu (`is_mega_menu`) |
| CMS — menu lateral | `v6/app/Providers/MenuServiceProvider.php:332-334` | `case 'menu'` -> `/cms/menu` |
| CMS — exclusão em cascata de conteúdo | `v6/app/Http/Controllers/Prefecture/CategoryContentController.php:709`, `:767`, `:969` | remove/verifica `menu_items` da categoria |
| CMS — exclusão de conteúdo | `v6/app/Http/Controllers/Prefecture/ContentController.php:1334`, `:1377` | idem para `contents_id` |
| CMS — exclusão de página estática | `v6/app/Http/Controllers/Prefecture/StaticPageController.php:368` | idem para `static_pages_id` |
| Portal — sitemap | `v6/app/Http/Controllers/Portal/SitemapController.php:29` | URLs a partir de `menu_items` com `links_id` |
| Modelo `Link` | `v6/app/Http/Model/Prefecture/Link.php:35`, `:62` | `belongsToMany(MenuItem::class)` |
| Modelo `StaticPage` | `v6/app/Http/Model/Prefecture/StaticPage.php:50` | `belongsTo(MenuItem::class, 'static_pages_id', 'id')` |

---

## 9. Diagrama de sequência — leitura do menu no portal

```mermaid
sequenceDiagram
    participant U as Cidadão
    participant P as Portal (Controller)
    participant MSP as MenuServiceProvider
    participant DB as PostgreSQL
    participant V as nav.blade.php
    participant PR as MenuItemPresenter

    U->>P: GET /alguma-pagina
    P->>MSP: boot() (view composer)
    MSP->>DB: SELECT menu_items WHERE menus_id=1 AND menus_items_id IS NULL ORDER BY order
    MSP->>DB: eager load subItens (2 níveis)
    MSP->>V: with(menuHeader)
    V->>PR: present()->getLink()
    PR->>DB: links / static_pages / content / categoryContent
    PR-->>V: URL absoluta
    V-->>U: HTML do menu
```
