# Legislação — Arquitetura

> Base: `v6` (Laravel 5.5.50 / PHP 7.2.34). Referências de comparação em `v5`.

---

## 1. Visão geral das camadas

O módulo **Legislação** não tem service ou repository próprio. Ele reaproveita
a engrenagem genérica de "conteúdos" do CMS (CRUD orientado por *slug*) e
adiciona três Faces:

| Face | Ponto de entrada | Quem consome |
|---|---|---|
| Portal (público) | `App\Http\Controllers\Portal\LegislationController` | Cidadão |
| CMS (admin) | `App\Http\Controllers\Prefecture\ContentController`, `...CategoryContentController`, `...DatatableController` | Operador |
| API (dados abertos) | `App\Http\Controllers\Swagger\V1\Portal\LegislationController` | Terceiros / SIM |

A única camada transversal do módulo é o trait
`App\Http\Traits\ExportsContentTrait` (exportação CSV/XLS/TXT/PDF).

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `App\Http\Controllers\Portal\LegislationController` | `v6/app/Http/Controllers/Portal/LegislationController.php` | Portal: índice (redirect), listagem por categoria, visualização, filtro POST e exportação. |
| `App\Http\Controllers\Prefecture\ContentController` | `v6/app/Http/Controllers/Prefecture/ContentController.php` | CMS: CRUD genérico de conteúdo dirigido por slug (`genericIndex`, `genericGenerate`, `store`, `update`, `destroy`). |
| `App\Http\Controllers\Prefecture\CategoryContentController` | `v6/app/Http/Controllers/Prefecture/CategoryContentController.php` | CMS: CRUD de categorias por slug + API JSON (`apiStore`, `apiUpdate`, `apiDestroy`, `genericShow`, `ajustOrder`). |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | CMS: grade Yajra; `get_legislacao()` reconstrói em PHP as colunas da view `legislacao`. |
| `App\Http\Controllers\Swagger\V1\Portal\LegislationController` | `v6/app/Http/Controllers/Swagger/V1/Portal/LegislationController.php` | API `/api/v1/legislacoes`. |
| `App\Http\Controllers\Swagger\V1\Portal\Controller` | `v6/app/Http/Controllers/Swagger/V1/Portal/Controller.php` | Base da API: `getContent()`, `getPeriods()`, `caseInsensitiveAccentFreeSearch()`. |
| `App\Http\Controllers\Admin\Controller` | `v6/app/Http/Controllers/Admin/Controller.php` | Controller base do portal (`getTemplateActived()` vem de trait). |
| `App\Http\Requests\ContentRequest` | `v6/app/Http/Requests/ContentRequest.php` | Validação do formulário de conteúdo; `switch ($slug)` com o `case 'legislacao'`. |
| `App\Http\Requests\Swagger\V1\Portal\LegislationRequest` | `v6/app/Http/Requests/Swagger/V1/Portal/LegislationRequest.php` | Validação dos query params da API. |
| `App\Http\Requests\CategoryContent\ApiStoreRequest` / `ApiUpdateRequest` / `ApiDestroyRequest` | `v6/app/Http/Requests/CategoryContent/ApiStoreRequest.php` (idem `ApiUpdateRequest.php`, `ApiDestroyRequest.php`) | Validação da API JSON de categorias. |
| `App\Http\Resources\Swagger\V1\Portal\LegislationResource` | `v6/app/Http/Resources/Swagger/V1/Portal/LegislationResource.php` | Serialização da API (titulo, data_publicacao, descricao, interacoes, slug, arquivos). |
| `App\Http\Resources\CategoryContents\ShowResource` | `v6/app/Http/Resources/CategoryContents/ShowResource.php` | JSON das categorias para o CMS (`id`, `name`, `order`, `parentId`). |
| `App\Prefecture\Content` | `v6/app/Http/Model/Prefecture/Content.php` | Eloquent da tabela `contents` (SoftDeletes, cast `publication_date => datetime`). |
| `App\Prefecture\CategoryContent` | `v6/app/Http/Model/Prefecture/CategoryContent.php` | Eloquent da tabela `category_contents`; `getAllDescendants()`, `scopeAllDescendants()`, `rightWayToGetCategoryContents()`. |
| `App\Prefecture\Archive` | `v6/app/Http/Model/Prefecture/Archive.php` | Anexos (`contents_id`, `view_pdf`, `highlight`). |
| `App\Http\Traits\ExportsContentTrait` | `v6/app/Http/Traits/ExportsContentTrait.php` | `exportContents($data, $type, $filename, $reportTitle)`. |
| `App\Http\Traits\GetInformationsPrefectureTrait` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php` | `getTemplateActived()` (linhas 92-102), `getAllItemsToCategoryContents()`. |
| `App\Exports\ContentsExport` | `v6/app/Exports/ContentsExport.php` | Maatwebsite Excel: headings e `map()`. |
| `App\Http\Presenters\ContentPresenter` | `v6/app/Http/Presenters/ContentPresenter.php` | Presenter do `Content` (usado pelo CMS). |
| `App\Http\Presenters\CategoryContentPresenter` | `v6/app/Http/Presenters/CategoryContentPresenter.php` | Presenter do `CategoryContent`. |
| `App\Services\Dashboard\Widgets\LegislacoesWidget` | `v6/app/Services/Dashboard/Widgets/LegislacoesWidget.php` | Widget do dashboard: conta as normas de **todos os descendentes** da raiz `legislacao` (raiz excluída, sem filtro de status/data). |

### Models e o peculiar namespace

`v6/app/Http/Model/Prefecture/Content.php` e
`v6/app/Http/Model/Prefecture/CategoryContent.php` declaram o namespace
`App\Prefecture` (não `App\Http\Model\Prefecture`). Isso vale para os dois
modelos deste módulo.

## 3. Fluxo de requisição — Portal

```
GET /legislacao                       (route 'legislacao.index')
  └─ middleware 'web' + 'check.site.manutention'
     └─ Portal\LegislationController@getIndex
        └─ CategoryContent::whereHas('categoryContents', slug='legislacao')
                          ->orderBy('order')->first()
        └─ redirect -> route('conteudo.legislacao.list', $categoryFirst->slug)

GET /legislacao/{slug}                (route 'conteudo.legislacao.list')
  └─ Portal\LegislationController@contentShow($slug, Request)
     ├─ CategoryContent::where('slug','legislacao')->first()->getAllDescendants()
     ├─ CategoryContent::allDescendants()->whereHas('categoryContents', ...)->get()
     ├─ DB::select("select extract(year from publication_date) ... ")   ← SQL cru
     ├─ Content::...->paginate(5)        ← $contents (NÃO usado por modelo1..6)
     ├─ if ($request->all()) → refaz a query com whereYear/whereMonth/whereRaw
     ├─ if (request()->ajax()) → Response::json(view(...legislacao.list)->render())
     └─ view portal.layouts.<layout>.legislacao.show

GET /legislacao/{slug1}/{slug2}       (route 'conteudo.legislacao.view')
  └─ Portal\LegislationController@contentView
     ├─ Content::where('slug',$slug2)->first()
     ├─ if publication_date > now → redirect getIndex()
     ├─ Archive::where('contents_id',...)->where('highlight', null)->get()
     ├─ $content->update(['clicks' => $content->clicks + 1])
     └─ view portal.layouts.<layout>.legislacao.view

POST /legislacao/filtro               (route 'conteudo.legislacao.fill')
  └─ Portal\LegislationController@filtroGET
     ├─ getAllDescendants() → pluck('id') da subcategoria
     ├─ Content::where('category_contents_id', $ids)->get([...])   ← SEM LIMITE
     ├─ filtros de data aplicados em memória (sobrescrevem $result)
     ├─ Content::whereIn('id', array_unique($result->all()))->get() ← SEM LIMITE
     └─ Collection serializada em JSON (o JS pagina em blocos de 10)

GET /legislacao/export                (route 'legislacao.export')   [só v6]
  └─ Portal\LegislationController@export
     ├─ valida sub_category contra getAllDescendants()
     ├─ Content::where('category_contents_id',...)->...->get()  ← SEM LIMITE
     └─ ExportsContentTrait::exportContents($data, $type, $filename, $title)
```

## 4. Fluxo de requisição — CMS

```
GET /cms/conteudos/legislacao         (route 'listar-conteudo')
  └─ check.route.permission (CheckRoutePermission::hasAccessSegment)
     └─ Prefecture\ContentController@genericIndex('legislacao')
        ├─ returnIndex('Legislação','legislacao') → view prefecture.contents.legislacao.index
        └─ DataTable AJAX: GET /cms/datatable/legislacao
             └─ Prefecture\DatatableController@get_legislacao()
                ├─ CategoryContent::where('slug','legislacao')->first()->getAllDescendants()->pluck('id')
                ├─ Content::whereIn('category_contents_id', $ids)->orderByDesc('publication_date')->get()
                ├─ CategoryContent::find($item->category_contents_id) por item  ← N+1
                └─ struct_views($data,'legislacao') → colunas edit/delete/active/inactivate

GET /cms/conteudos/cadastrar/legislacao   (route 'cadastrar-conteudo')
  └─ ContentController@genericCreate → genericGenerate → returnData(..., 'legislacao', ...)
     └─ view prefecture.contents.legislacao.create → include .../form.blade.php

POST /cms/conteudo                       (route 'conteudo.store')
  └─ ContentRequest (case 'legislacao') → ContentController@store
     ├─ generateSlug(title) + colisão "-<maxid+1>"
     ├─ publication_date: str_replace('/','-') + strtotime
     └─ redirect route('listar-conteudo', $slug)

GET /cms/categorias/legislacao        (route 'listar-categorias')
  └─ CategoryContentController@genericIndex('legislacao')  [case 'legislacao', ~linha 91]
     └─ view prefecture.category-contents.legislacao.index (arrastar-e-soltar)

GET /cms/categories/legislacao         (route 'categories.show')
  └─ CategoryContentController@genericShow → ShowResource::collection(...)
```

## 5. Fluxo de requisição — API

```
GET /api/v1/legislacoes                (route 'v1.legislacoes.index')
  └─ middleware 'api'
     └─ Swagger\V1\Portal\LegislationController@index(LegislationRequest)
        ├─ $this->category = 'legislacao'
        ├─ Controller::getContent()   → Content::query()->with('archives')
        │     + whereHas('categoryContents', slug='legislacao')
        │     + whereHas('categoryContents.categoryContents', slug='legislacao')
        ├─ getPeriods($period, $start, $end)   ← 'dd/mm/yyyy' ou 'dd/mm/yyyy_dd/mm/yyyy'
        ├─ whereRaw(caseInsensitiveAccentFreeSearch(...))   ← SQL cru
        └─ LegislationResource::collection($contents)
```

## 6. Diagrama de dependências

```
                       ┌──────────────────────────────────────────┐
                       │  category_contents (árvore)              │
                       │  raiz id=2 'legislacao'                  │
                       │  filhos 22 / 23 / 24                     │
                       └───────────────┬──────────────────────────┘
                                       │ category_contents_id
                                       ▼
  ┌────────────────────────────┐   ┌───────────────────────────────┐
  │ Portal\LegislationController│   │ Swagger\V1\Portal\Legislation │
  │  (getIndex/contentShow/    │   │  Controller (API v1)         │
  │   contentView/filtroGET/   │   └───────────────┬───────────────┘
  │   export)                  │                   │
  └──────┬─────────────────────┘                   │
         │                                           │
         ▼                                           ▼
  ┌────────────────────────────────────────────────────────────┐
  │ App\Prefecture\Content  (tabela contents, SoftDeletes)     │
  │  publication_date, slug, clicks, text, category_contents_id│
  └──────┬─────────────────────────────────────────────────────┘
         │ contents_id
         ▼
  ┌────────────────────────────────────────────────────────────┐
  │ App\Prefecture\Archive (anexos, view_pdf, highlight)       │
  └────────────────────────────────────────────────────────────┘

  CMS: ContentController / CategoryContentController / DatatableController
       → mesmos models → mesmas tabelas
  Legado morto: view SQL `legislacao` (sem nenhum consumidor PHP)
```

## 7. Dependências externas (composer)

| Pacote | Uso no módulo |
|---|---|
| `yajra/laravel-datatables` | `DatatableController@get_legislacao` → `DataTables::of($this->struct_views(...))->make(true)`. |
| `maatwebsite/excel` | `ContentsExport` (CSV/XLS/TXT) via `\Excel::download()`. |
| `barryvdh/laravel-dompdf` | PDF de exportação via `\PDF::loadView('exports.contents', ...)`. |
| `laracasts/presenter` | `PresentableTrait` em `Content` e `CategoryContent`. |
| `cartalyst/sentinel` | Autenticação e permissões (`has_access()`, `Sentinel::inRole('super-admin')`). |
| `diglactic/laravel-eloquent-search` (indireto) | `Collection::where()` / `pluck()` usados como "busca" em `filtroGET`. |
| `laravel/framework` 5.5 | `SoftDeletes`, `Request::__isset` (ver `08-GAPS-E-DIVIDAS.md`). |

## 8. Onde o módulo se acopla ao resto do sistema

| Acoplamento | Ponto | Efeito |
|---|---|---|
| `Content` / `CategoryContent` | Tabelas e models compartilhados com Notícias, Publicações, A Cidade, A Instituição etc. | Uma alteração em `contents` afeta todos os módulos (ver `../noticias/`, `../publicacoes/`). |
| `getTemplateActived()` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php:92-102` | Escolhe `modelo1..modelo6`; `default` e `newsbit` são **inalcançáveis** (não têm `home/index.blade.php`). |
| `MenuServiceProvider` | `v6/app/Providers/MenuServiceProvider.php:237-239` | Entrada "Legislação" no menu do CMS, condicionada a `has_access('legislacao.index')` e `modulos.status`. |
| `ModulosServiceProvider` | `v6/app/Providers/ModulosServiceProvider.php` | Cache `Cache::rememberForever('modulos')`; `modulos.status = true` para `legislacao` (id=19). |
| `menu_items` | `ContentController@destroy` | Remove itens de menu que apontam para o conteúdo excluído. |
| `Archive` / storage | `storage/content/{slug}/{id}/...` | Caminho de upload montado pelo `ContentController` a partir do slug da categoria. |
| `LegislacoesWidget` | `v6/app/Services/Dashboard/Widgets/LegislacoesWidget.php` | Consumidor **não óbvio**: o dashboard conta as normas com a mesma regra da grade do CMS (todos os descendentes da raiz, raiz excluída, sem filtro de `status`/`publication_date`). Testado em `v6/tests/Feature/DashboardLegislacoesWidgetTest.php`. |
