# Obras — Arquitetura

> Versão analisada: `v5` (Laravel 5.5.50, PHP 7.2.34, PostgreSQL 10 + PostGIS)

---

## 1. Visão geral das camadas

O módulo **não** usa repository pattern. O fluxo é o MVC clássico do Laravel,
com duas entradas independentes:

```
CMS (autenticado)                              Portal (público)
───────────────────                            ───────────────────
routes/basepro.php                             routes/web.php
  prefix /cms                                    grupo check.site.manutention
  grupo 'as' => 'obras.'                        rota 'portal-obras.*'
        │                                              │
        ▼                                              ▼
Prefecture\Works\WorkController             Portal\WorksController
  (extends Prefecture\Controller)           (extends Admin\Controller)
  middleware check.route.permission           sem middleware de permissão
        │                                              │
        ├── SaveRequest (Prefecture\Works)             ├── (validação inline)
        ├── DeleteRequest (Prefecture\Works)           │
        │                                              │
        ▼                                              ▼
App\Prefecture\Works\Work  ◄──────────────────────────┘
App\Prefecture\Works\WorkCategory
App\Prefecture\Works\WorkStatus
App\Prefecture\Works\WorkArchive
        │  (pivot)
        ▼
App\Prefecture\Archive  ──►  tabela `archives` (compartilhada)
        │
        ▼
PostgreSQL: works, work_categories, work_statuses, work_archives, archives
            + views SQL listagem_obras / listagem_categorias_obras
```

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `WorkController` | `v5/app/Http/Controllers/Prefecture/Works/WorkController.php` | CRUD de obras no CMS, upload de arquivos, galeria de fotos, geocodificação. Usa `FileTrait`. |
| `WorkCategoryController` | `v5/app/Http/Controllers/Prefecture/Works/WorkCategoryController.php` | CRUD de categorias de obra no CMS (resource `categorias-obras`). |
| `WorksController` (portal) | `v5/app/Http/Controllers/Portal/WorksController.php` | Listagem pública com filtros, mapa, ficha da obra e exportações. |
| `SaveRequest` | `v5/app/Http/Requests/Prefecture/Works/SaveRequest.php` | Validação e normalização do formulário de obra (datas `d/m/Y` → `Y-m-d`, `gallery` → 0/1). |
| `DeleteRequest` | `v5/app/Http/Requests/Prefecture/Works/DeleteRequest.php` | Valida `id` existente em `works`; retorna 422 em JSON. |
| `SaveRequest` (categorias) | `v5/app/Http/Requests/Prefecture/WorkCategories/SaveRequest.php` | Valida `name` da categoria. |
| `DeleteRequest` (categorias) | `v5/app/Http/Requests/Prefecture/WorkCategories/DeleteRequest.php` | Valida `id` existente em `work_categories`. |
| `Work` | `v5/app/Http/Model/Prefecture/Works/Work.php` | Model principal (soft delete, casts, `belongsTo` categoria/situação, `belongsToMany` arquivos). |
| `WorkCategory` | `v5/app/Http/Model/Prefecture/Works/WorkCategory.php` | Categorias de obra (soft delete, `hasMany` obras). |
| `WorkStatus` | `v5/app/Http/Model/Prefecture/Works/WorkStatus.php` | Situações de obra (soft delete, sem relações). |
| `WorkArchive` | `v5/app/Http/Model/Prefecture/Works/WorkArchive.php` | Pivot `work_archives` (model próprio, soft delete). |
| `ArchiveTypesEnum` | `v5/app/Enum/Prefecture/Works/ArchiveTypesEnum.php` | Constantes `FILE = 'file'` e `GALLERY = 'gallery'` para o pivot. |
| `GeocodingService` | `v5/app/Services/GeocodingService.php` | Consulta o Google Geocoding API e devolve `lat`/`lng`. |
| `FileTrait` | `v5/app/Http/Traits/FileTrait.php` | `getFileName()`, `saveUpload()`, `compress()` — gravação e compressão de arquivos. |
| `WorksExport` | `v5/app/Exports/WorksExport.php` | Export `FromView` do Maatwebsite Excel, renderiza `exports.works`. |
| `DatatableController` | `v5/app/Http/Controllers/Prefecture/DatatableController.php` | Serve as duas listagens DataTable do CMS a partir das views SQL. |
| `ArchiveController` | `v5/app/Http/Controllers/Prefecture/ArchiveController.php` | **Módulo neighbour**: exclusão de arquivo via `/cms/galeria/delete/{id}` (usada pelo JS de obras). |
| `Configuration` | `v5/app/Http/Model/Prefecture/Configuration.php` | Fornece `layout` (template do portal) e `estado` (UF do mapa). |

> **Nota de autoload:** os models vivem em `v5/app/Http/Model/Prefecture/Works/`
> mas declaram o namespace `App\Prefecture\Works`. O `composer.json` da v5 usa
> `classmap: ["app/"]`, o que faz o autoload funcionar apesar da divergência
> entre caminho e namespace.

## 3. Diagrama do fluxo — criação de obra no CMS

```
Operador (AdminLTE)
   │
   │ GET /cms/obras/create
   ▼
Prefecture\Controller::__construct  →  middleware check.route.permission
   │
   ▼
WorkController@create
   ├── activity()->performedOn(new Work)        (log de atividade)
   ├── WorkCategory::select(DB::raw("id, name")) → $categories
   ├── WorkStatus::select(DB::raw("id, name"))   → $statuses
   └── view('prefecture.works.create')
           │
           ▼
   prefecture.works.create  →  include prefecture.works.form
           │
           ▼
   JS: script.js
     ├── blur no CEP   → XHR https://viacep.com.br/ws/{cep}/json/  ( ViaCEP )
     │                      → preenche state, city, neighborhood, street
     └── blur no número → GET /cms/obras/get-geolocation-data?address=...
                              → WorkController@getGeolocationData
                              → GeocodingService::getCoordinates()  ( Google )
                              → preenche latitude / longitude
           │
           │ POST /cms/obras  (multipart, `files => true`)
           ▼
SaveRequest (Prefecture\Works)
   ├── prepareForValidation(): id, gallery (0/1), start_date/end_date/
   │                             estimated_completion_date (d/m/Y → Y-m-d)
   └── rules(): 29 campos, 16 obrigatórios
           │
           ▼
WorkController@store
   ├── DB::beginTransaction()
   ├── Work::create($this->attributes($request))
   ├── storeArchives($request->archive, $id)      ← grava arquivos em archives
   │     ├── Archive::create([...])
   │     ├── $file->storeAs("obras/{id}/arquivos/", $name)
   │     └── WorkArchive::create([...'type' => FILE])
   ├── DB::commit()
   ├── activity()->log('Cadastrou uma obra')
   ├── flash('Obra cadastrada com sucesso')->success()
   └── redirect → obras.index  (ou obras.edit?goto=galeria se gallery = 1)
```

## 4. Diagrama do fluxo — listagem pública no portal

```
Visitante
   │
   │ GET /obras?dt_inicial=...&status=...&category=...&contract=...&edital=...
   ▼
Route::middleware(['check.site.manutention'])
   ▼
Portal\WorksController@index
   ├── Configuration('estado')            → $estado (default 'PB')
   ├── WorkCategory::select(DB::raw(...))  → $categories
   ├── WorkStatus::select(DB::raw(...))    → $statuses
   ├── $result = Work::query()
   │     ├── when(dt_inicial)  where start_date >= ...
   │     ├── when(dt_final)    where start_date <= ...
   │     ├── when(status)      where status_id = ...
   │     ├── when(contract)    whereRaw(getNonAccentedILikeSearch(...))
   │     ├── when(edital)      whereRaw(getNonAccentedILikeSearch(...))
   │     └── when(category)    where work_category_id = ...
   ├── $forMap = (clone $result)->whereNotNull(latitude)
   │                              ->whereNotNull(longitude)->get()
   │              ← carrega TODAS as obras georreferenciadas em memória
   ├── $result = $result->orderBy(DB::raw('start_date IS NULL, start_date'),'DESC')
   │                     ->paginate(10)
   ├── se Route::currentRouteName() == 'portal-obras.exportacao'
   │        → export($result, $request->type)  (xls | csv | txt)
   ├── se request()->ajax()  → response()->json($result)
   └── view('portal.layouts.{layout}.obras.index')
           ├── include portal.components.obras.filtro   (modelo1/modelo2)
           │   ou layouts.{modelo}.obras.filtro          (modelo3 a modelo6)
           ├── <div id="map">  → assets/js/custom/portal/work/geo.js
           ├── include portal.components.obras.tabela
           └── scripts jspdf + jspdf-autotable + gerar-pdf-obras.js
```

## 5. Fluxo da listagem do CMS (DataTable sobre view SQL)

```
GET /cms/datatable/listagem_obras
   ▼
Prefecture\DatatableController@get_works_list()
   ├── $worksView = DB::table("listagem_obras")->get()
   ├── struct_views($worksView, 'obras')
   │     ├── edit  = has_access("obras.edit")
   │     ├── delete= has_access("obras.delete")
   │     ├── active   = has_access("obras.restore")
   │     └── inactivate = has_access("obras.inactivate")
   └── DataTables::of(...)->make(true)   (Yajra DataTables, JSON)
           ▲
           │ ajax: GET /cms/datatable/listagem_obras
           │
      assets/js/custom/contents/obras/datatable.js
           ▲
           │ script carregado por resources/views/prefecture/works/index.blade.php
      resources/views/prefecture/works/index.blade.php
```

## 6. Dependências externas

| Pacote | Versão (composer.lock) | Uso no módulo |
|---|---|---|
| `laravel/framework` | v5.5.50 | Base: routing, Eloquent, FormRequest, Blade, Storage. |
| `yajra/laravel-datatables` | v1.5.0 | `DataTables::of()` no `DatatableController` (listagens do CMS). |
| `maatwebsite/excel` | 3.1.25 | `WorksExport` (`FromView`) para xls/csv do portal. |
| `spatie/laravel-activitylog` | 2.8.4 | Helper `activity()` — log de todas as ações do CMS. |
| `laracasts/flash` | 3.0.3 | Helper `flash()` — mensagens de sucesso/erro. |
| `cartalyst/sentinel` | v2.0.18 | `Sentinel::getUser()`, `hasAnyAccess()`, sessão do CMS. |
| `intervention/image` | 2.7.2 | `FileTrait::compress()` — gera `_min`/`_med`/`_larg` das imagens da galeria. |
| `guzzlehttp/guzzle` | 5.3.4 | `GeocodingService` — chamada HTTP ao Google Geocoding. |
| `laravelcollective/html` | ^5.5 | `Form::open()` / `Form::close()` nos formulários do CMS. |
| `jeroennoten/laravel-adminlte` | v1.22.0 | Layout do CMS (`admin.layouts.app`). |
| `diglactic/laravel-breadcrumbs` | 4.2.0 | Breadcrumb do portal (`Breadcrumbs::view(...)`). |

**Assets de terceiros (não composer):** jsPDF (`jspdf.umd.min.js`) +
jspdf-autotable (`jspdf-autotable.min.js`) para o PDF do portal; Leaflet +
LeafletGeo (`leafletgeo-lib.umd.js`) para o mapa; lightGallery para a galeria
de fotos da ficha pública; jQuery Upload File (`jquery.uploadfile.min.js`) para
o upload em lote da galeria no CMS; SweetAlert2/Cropper para edição de imagem
em `v5/public/assets/js/custom/contents/midias/index.js`.

## 7. Onde o módulo encosta em outros módulos

| Módulo | Ponto de contato |
|---|---|
| **archives** (compartilhado) | Tabela `archives` + model `App\Prefecture\Archive`. As obras gravam arquivos e imagens nela; a exclusão de arquivo do JS de obras chama `Prefecture\ArchiveController@deleteArchives` (`/cms/galeria/delete/{id}`), rota do módulo de galeria genérica. |
| **configurations** | `Configuration('layout')` escolhe o template do portal; `Configuration('estado')` define a UF centralizada no mapa; `Configuration('geocoding_api_key')` alimenta o `GeocodingService`. |
| **menu_orders / resources** | A migration cria `menu_orders.slug = 'obras'` (contador de cliques, incrementado por `menuClicks('obras')`) e `resources.slug = 'obras'` (id 13) — de onde saem as permissões `obras.*`. |
| **MenuServiceProvider** | O menu lateral do CMS monta o submenu "Obras" a partir de `MenuOrder.slug = 'obras'` e do caso `case 'obras':` em `v5/app/Providers/MenuServiceProvider.php:362`. |
| **DatatableController** | Compartilhado: `get_works_list()` e `get_works_categories_list()` usam o mesmo `struct_views()` de todos os outros módulos. |
| **Obras Paralisadas** | Compartilham o **mesmo** recurso de permissão `obras` (id 13) e o mesmo item de menu; nenhuma tabela em comum. |
