# Unidade Gestora — Arquitetura

## 1. Visão geral das camadas

O módulo é um CRUD clássico do CMS AdminLTE. Não há API pública, nem página no
portal, nem Repository/Service com regra de negócio: a "regra" é a validação de
unicidade e a integridade referencial com `biddings`.

```
  OPERADOR (CMS)                     routes/basepro.php  (prefixo "cms")
  /cms/unidade-gestora        ──────► v6/routes/basepro.php:150, 181, 407
                                            │
                                            ▼
                    ┌───────────────────────────────────────────────────┐
                    │ Prefecture\ManagementUnitController               │
                    │ v6/app/Http/Controllers/Prefecture/               │
                    │   ManagementUnitController.php                     │
                    │ middleware: check.route.permission                │
                    └───────┬───────────────────────────┬───────────────┘
                            │                           │
             │ ManagementUnitRequest       │   ┌────────▼──────────────────────────────────┐
             │ ManagementUnitRequest       │   │IManagementUnitRepository                  │
             │ ManagementUnitRequest       │   │ -> ManagementUnitRepositoryDefaultImpl    │
             │ ManagementUnitRequest       │   │ -> Repository (base)                      │
             │ ManagementUnitRequest       │   └────────┬──────────────────────────────────┘
             └──────────────┬─────────────┘             │ destroyAll()
                            │ rules()                   │ (soft delete em lote)
                            ▼                           ▼
             ┌────────────────────────────────────────────────────────┐
             │ App\Prefecture\ManagementUnit  (Eloquent, SoftDeletes) │
             │ v6/app/Http/Model/Prefecture/ManagementUnit.php        │
             │ table: management_units                                  │
             └───────────────┬────────────────────────────────────────┘
                             │ FK biddings.management_units_id (NOT NULL)
                             ▼
             ┌────────────────────────────────────────────────────────┐
             │ biddings  (App\Http\Model\Prefecture\Bidding)          │
             │ uso: select "Unidade Gestora" no formulário de licitação│
             └────────────────────────────────────────────────────────┘

  LISTAGEM (DataTable server-side)
  /cms/datatable/unidade-gestora ──► Prefecture\DatatableController@get_unidade_gestora
                                        └─ DB::table('unidade_gestora')  (view SQL)
```

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `App\Http\Controllers\Prefecture\ManagementUnitController` | `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php` | Controller do CMS; 7 métodos públicos (sem `show`). Aplica `check.route.permission` no construtor. |
| `App\Http\Requests\ManagementUnitRequest` | `v6/app/Http/Requests/ManagementUnitRequest.php` | Form Request com regras de criação e atualização; `authorize()` retorna `true` (a autorização real é feita pelo middleware). |
| `App\Prefecture\ManagementUnit` | `v6/app/Http/Model/Prefecture/ManagementUnit.php` | Eloquent de `management_units`; `SoftDeletes`; `fillable = ['name','code']`; relação `biddings()`. |
| `App\Repositories\Contracts\IManagementUnitRepository` | `v6/app/Repositories/Contracts/IManagementUnitRepository.php` | **Interface vazia** — não declara nenhum método. |
| `App\Repositories\Eloquent\Prefecture\ManagementUnit\ManagementUnitRepositoryDefaultImpl` | `v6/app/Repositories/Eloquent/Prefecture/ManagementUnit/ManagementUnitRepositoryDefaultImpl.php` | Implementação; define `$this->model = new ManagementUnit()` e `structureUpInsert()` retorna `[]` (array vazio). |
| `App\Repositories\Repository` | `v6/app/Repositories/Repository.php` | Classe-base abstrata: `getModel()`, `upInsert()`, `destroy()`, `destroyAll()`, `destroyWithRelation()`. |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | `get_unidade_gestora()` (linha 270) monta o JSON do DataTable a partir da view SQL. |
| `App\Http\Middleware\CheckRoutePermission` | `v6/app/Http/Middleware/CheckRoutePermission.php` | Autorização por nome de rota/permissão. |
| `App\Prefecture\Bidding` | `v6/app/Http/Model/Prefecture/Bidding.php` | Consumidor da unidade: `management_units_id` no `fillable` (linha 32) e relação `managementUnits()` (linha 114). |
| `App\Library\Helper\appHelpers` | `v6/app/Library/Helper/appHelpers.php:22` | Helper `has_access()` usado nas views do CMS. |
| `App\Helpers\functions.php` | `v6/app/Helpers/functions.php:865` | Helper `menuClicks($slug)` — contador de acessos por módulo. |
| `App\Http\Controllers\Admin\Controller` | `v6/app/Http/Controllers/Admin/Controller.php` | Base do controller do CMS. |

## 3. Fluxo detalhado

### 3.1 `GET /cms/unidade-gestora` (index)

1. Middleware `web` (grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'], function () {`,
   `v6/routes/basepro.php:35`).
2. `check.route.permission` (aplicado no construtor do controller):
   - exige login Sentinel (redirect para `login` se não autenticado);
   - `super-admin` passa direto;
   - demais papéis: `hasAnyAccess(['unidade-gestora.index', 'unidade-gestora.index'])`
     (o nome da rota já é `unidade-gestora.index`).
3. `index()` chama `menuClicks('licitacao')` e renderiza
   `prefecture.management-units.index` com `['title' => 'Unidades Gestoras']`.
4. A view carrega `assets/js/custom/contents/licitacoes/unidades-gestoras/datatable.js`,
   que dispara `GET /cms/datatable/unidade-gestora`.

### 3.2 `GET /cms/datatable/unidade-gestora`

```php
// v6/app/Http/Controllers/Prefecture/DatatableController.php:270-274
public function get_unidade_gestora()
{
    $unidade_gestora = DB::table('unidade_gestora')->get();
    return DataTables::of($this->struct_views($unidade_gestora, 'unidade-gestora'))->make(true);
}
```

`struct_views($data, 'unidade-gestora')` (linha 504) anexa a cada item:
`edit` (`has_access('unidade-gestora.edit')`), `delete` (`has_access('unidade-gestora.delete')`),
`active` (`has_access('unidade-gestora.restore')`) e `inactivate` (`has_access('unidade-gestora.inactivate')`).

> O grupo de rotas `datatable` tem `as => 'datatable.'`
> (`v6/routes/basepro.php:395`), logo o nome da rota é `datatable.unidade-gestora`.
> O middleware `check.route.permission` considera `datatable` como rota **aberta**
> (`$openRoutes`), ou seja, a listagem só exige login.

### 3.3 `POST /cms/unidade-gestora` (store)

1. `ManagementUnitRequest` valida (`rules()` escolhe o conjunto pelo nome da rota
   corrente: `unidade-gestora.store` -> `create`).
2. `store()` abre uma `DB::transaction` e chama
   `ManagementUnit::create($request->all())` — **diretamente no model**, sem
   passar pelo repository.
3. Flash "Unidade Gestora cadastrada com sucesso." e redirect para
   `unidade-gestora.index`.
4. Em erro `PDOException`: flash de erro e redirect para o índice; em `Exception`
   genérica: flash de erro e redirect para `unidade-gestora.create`.

### 3.4 `PUT /cms/unidade-gestora/{id}` (update)

1. `ManagementUnitRequest` (rota `unidade-gestora.update` -> conjunto `update`).
2. `update()` abre transação, faz `ManagementUnit::find($id)` e `->update($request->all())`.
3. Flash de sucesso e redirect para o índice.
4. Se o `id` não existir, `find()` retorna `null` e `->update()` em `null` gera
   **erro fatal** (bug U5).

### 3.5 `DELETE /cms/unidade-gestora/{id}` (destroy)

1. `destroy()` abre transação, faz `ManagementUnit::find($id)` e `->delete()`
   (soft delete).
2. Devolve `response()->json(['success' => 'Unidade Gestora deletada com sucesso.'], 200)`.
3. Erros devolvem `['erro' => 'Ocorreu um erro ao tentar salvar no banco de dados.']`
   (PDOException) ou `['erro' => 'Ocorreu um erro! Por favor entre em contato com o suporte técnico.']`
   (Exception genérica) com HTTP 500.
4. O JS trata a resposta em `deleteAll.js` / no botão `delete_btn`.

### 3.6 `POST /cms/ajax/unidade-gestora/delete-por-lote` (deleteByLote)

1. Log de atividade com `Spatie\Activitylog`:
   `->log('Exclusão de uniade gestora')` e propriedade "Detalhe" com a lista de ids.
2. `$this->managementUnitRepository->destroyAll($request->toDelete)` executa
   `Model::whereIn('id', $array)->delete()` dentro de uma transação
   (`v6/app/Repositories/Repository.php:99-110`) — soft delete em lote.
3. Resposta: `response()->json(true)` ou `response()->json(false)`.
4. Em exceção: flash "Ocorreu um erro! Por favor entre em contato com o suporte técnico."
   e `redirect()->back()`.

## 4. Onde a unidade é consumida

| Consumidor | Caminho | Como usa |
|---|---|---|
| Formulário de licitação (criar/editar) | `v6/app/Http/Controllers/Prefecture/Bidding/BiddingController.php:74` e `:136` | `ManagementUnit::get(['name','id'])` alimenta o `select` em `v6/resources/views/prefecture/biddings/form.blade.php:33-42`. |
| Validação da licitação | `v6/app/Http/Requests/BiddingRequest.php:60,74` | `'management_units_id' => [new SelectRule]`, com mensagens "O campo unidade gestora é obrigatório." e "O campo unidade gestora deve ser um número." |
| Log da licitação | `v6/app/Http/Controllers/Prefecture/Bidding/BiddingController.php:322` | `' Unidade Gestora id:' . $licitacao->management_units_id` no log de exclusão. |
| Portal (licitações) | `v6/app/Http/Controllers/Portal/BiddingsController.php:129` | Carrega a FK para exibir/filtrar por unidade. |

> O módulo **Emendas Parlamentares** tem uma coluna homônima
> `emendas_parlamentares.unidade_gestora_id`, mas ela aponta para
> `emendas_parlamentares_categories` (categoria de unidade), **não** para
> `management_units` (`v6/database/migrations/2026_03_13_102500_create_emendas_parlamentares_table.php:32,47`).
> São domínios distintos com o mesmo nome.

## 5. Dependências externas (composer)

| Pacote | Uso no módulo |
|---|---|
| `laravel/framework` 5.5 | rotas, views, Eloquent, transações, validação |
| `yajra/laravel-datatables-oracle` | `DataTables::of($this->struct_views($unidade_gestora, 'unidade-gestora'))->make(true)` na listagem |
| `laravelcollective/html` | `Form::open`, `Form::model` nos Blades do CMS |
| `spatie/laravel-activitylog` ^2.8 | log da exclusão em lote (`activity()->performedOn($this->managementUnitRepository->getModel())->causedBy(Sentinel::getUser())->withProperty('Detalhe', 'As Ugs com ids: ' . implode("," , $request->toDelete) . ' Foram Excluidas por: ' . Sentinel::getUser()->name)->log('Exclusão de uniade gestora')`) |
| `cartalyst/sentinel` | `Sentinel::getUser()` no log e `Sentinel::check()` no middleware |

## 6. Pontos de atenção arquitetural

| Ponto | Impacto |
|---|---|
| Repository injetado mas praticamente não usado | `store`/`update` ignoram o repository; só `deleteByLote` o usa. |
| `IManagementUnitRepository` vazia | Não há contrato; a "arquitetura em repositórios" do projeto não é aplicada aqui. |
| `structureUpInsert()` retorna `[]` | Se alguém migrar `store`/`update` para `upInsert()`, o registro será criado **sem** `name`/`code`. |
| Telas no plural no Controller, singular no Model | `prefecture.management-units.*` (view) vs `App\Prefecture\ManagementUnit` (model) — convenção inconsistente. |
| Módulo desabilitado no menu | `modulos.status = false`; as telas continuam acessáveis por URL. |
| Sem tela de "show" funcional | A rota `show` do `Route::resource` existe, mas o controller não tem o método. |
