# Licitações — Rotas e Controllers

> Rotas CMS: `v6/routes/basepro.php` (grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)`
> aberto em `v6/routes/basepro.php:35`).
> Rotas Portal: `v6/routes/web.php` (grupo `check.site.manutention`).
> Registro: `v6/app/Providers/RouteServiceProvider.php` — `mapWebRoutes()` roda
> **antes** de `mapBaseproRoutes()`, portanto, em caso de nome de rota repetido,
> prevalece a última registrada (a do CMS).

---

## 1. Rotas CMS — Licitações

| Método | URI | Nome | Controller@action | Middleware |
|---|---|---|---|---|
| GET | `/cms/licitacao` | `licitacao.index` | `Prefecture\Bidding\BiddingController@index` | `web`, `csrf`, `check.route.permission` |
| GET | `/cms/licitacao/create` | `licitacao.create` | `Prefecture\Bidding\BiddingController@create` | idem |
| POST | `/cms/licitacao` | `licitacao.store` | `Prefecture\Bidding\BiddingController@store` | idem |
| GET | `/cms/licitacao/{licitacao}` | `licitacao.show` | `Prefecture\Bidding\BiddingController@show` | idem — **método inexistente** (erro 500) |
| GET | `/cms/licitacao/{licitacao}/edit` | `licitacao.edit` | `Prefecture\Bidding\BiddingController@edit` | idem |
| PUT/PATCH | `/cms/licitacao/{licitacao}` | `licitacao.update` | `Prefecture\Bidding\BiddingController@update` | idem |
| DELETE | `/cms/licitacao/{licitacao}` | `licitacao.destroy` | `Prefecture\Bidding\BiddingController@destroy` | idem |

Fonte: `v6/routes/basepro.php:184` (`Route::resource('licitacao', 'Prefecture\Bidding\BiddingController')`).
O middleware `check.route.permission` é aplicado **no construtor** do controller
(`v6/app/Http/Controllers/Prefecture/Bidding/BiddingController.php:41`), não na rota.

## 2. Rotas CMS — Tipos e Modalidades

| Método | URI | Nome | Controller@action | Middleware |
|---|---|---|---|---|
| GET | `/cms/tipo-licitacao` | `tipo-licitacao.index` | `Prefecture\Bidding\BiddingTypeController@index` | `web`, `csrf` (+ `has_access(['licitacoes.index'])` no código) |
| GET | `/cms/tipo-licitacao/create` | `tipo-licitacao.create` | `...@create` | idem (`has_access(['licitacoes.create'])`) |
| POST | `/cms/tipo-licitacao` | `tipo-licitacao.store` | `...@store` | idem (`BiddingTypeRequest`) |
| GET | `/cms/tipo-licitacao/{tipo_licitacao}/edit` | `tipo-licitacao.edit` | `...@edit` | idem (`has_access(['licitacoes.edit'])`) |
| PUT/PATCH | `/cms/tipo-licitacao/{tipo_licitacao}` | `tipo-licitacao.update` | `...@update` | idem (`BiddingTypeRequest`) |
| DELETE | `/cms/tipo-licitacao/{tipo_licitacao}` | `tipo-licitacao.destroy` | `...@destroy` | idem |
| GET | `/cms/modalidade-licitacao` | `modalidade-licitacao.index` | `Prefecture\Bidding\BiddingModalityController@index` | `web`, `csrf` (`has_access(['licitacoes.index'])`) |
| GET | `/cms/modalidade-licitacao/create` | `modalidade-licitacao.create` | `...@create` | idem (`has_access(['licitacoes.create'])`) |
| POST | `/cms/modalidade-licitacao` | `modalidade-licitacao.store` | `...@store` | idem (`BiddingModalityRequest`) |
| GET | `/cms/modalidade-licitacao/{modalidade_licitacao}/edit` | `modalidade-licitacao.edit` | `...@edit` | idem (`has_access(['licitacoes.edit'])`) |
| PUT/PATCH | `/cms/modalidade-licitacao/{modalidade_licitacao}` | `modalidade-licitacao.update` | `...@update` | idem (`BiddingModalityRequest`) |
| DELETE | `/cms/modalidade-licitacao/{modalidade_licitacao}` | `modalidade-licitacao.destroy` | `...@destroy` | idem |

Fonte: `v6/routes/basepro.php:182-183`.

> **Atenção:** as permissões usadas aqui são `licitacoes.*` (plural). Na tabela
> `resources` só existem `licitacao` (id 20) e `licitacao-anterior` (id 19); o
> `RolesTableSeeder` concede `licitacao.*` e `licitacao-anterior.*`. Logo, para
> qualquer usuário que não seja `super-admin`, `has_access(['licitacoes.index'])`
> retorna `false` e o controller redireciona para `dashboard.index`.

## 3. Rotas CMS — Ajax e DataTable

| Método | URI | Nome | Controller@action | Middleware |
|---|---|---|---|---|
| POST | `/cms/ajax/ajax-delete-files-licitacoes` | `ajax.delete.files-licitacoes` | `Prefecture\Bidding\BiddingController@ajaxDeleteFiles` | `web`, `csrf` |
| POST | `/cms/ajax/ajax-delete-files-licitacoes` | `ajax.delete.files-licitacoes` (duplicada) | idem | idem |
| POST | `/cms/ajax/licitacao/delete-por-lote` | `ajax.bidding.deleteByLote` | `Prefecture\Bidding\BiddingController@deleteByLote` | `web`, `csrf` |
| GET | `/cms/datatable/licitacao` | `datatable.licitacao` | `Prefecture\DatatableController@get_licitacao` | `web`, `csrf` |
| GET | `/cms/datatable/contrato` | `datatable.contrato` | `Prefecture\DatatableController@get_contratos` | `web`, `csrf` |

Fontes: `v6/routes/basepro.php:129-130` (duplicata literal), `:154`,
`:406` (dentro do grupo `['prefix' => 'datatable', 'as' => 'datatable.']`,
`v6/routes/basepro.php:396`) e `:412`.

## 4. Rotas CMS — Contratos (módulo adjacente, mesmo fluxo)

| Método | URI | Nome | Controller@action |
|---|---|---|---|
| GET | `/cms/contrato` | `contrato.index` | `Prefecture\ContractController@index` |
| GET | `/cms/contrato/create` | `contrato.create` | `...@create` |
| POST | `/cms/contrato` | `contrato.store` | `...@store` |
| GET | `/cms/contrato/{contrato}` | `contrato.show` | `...@show` |
| GET | `/cms/contrato/{contrato}/edit` | `contrato.edit` | `...@edit` |
| PUT/PATCH | `/cms/contrato/{contrato}` | `contrato.update` | `...@update` |
| DELETE | `/cms/contrato/{contrato}` | `contrato.destroy` | `...@destroy` |
| POST | `/cms/ajax/contrato/delete-por-lote` | `ajax.contract.deleteByLote` | `Prefecture\ContractController@deleteByLote` |

Fonte: `v6/routes/basepro.php:185` e `:153`.

## 5. Rotas Portal (públicas)

| Método | URI | Nome | Controller@action | Middleware |
|---|---|---|---|---|
| ANY | `/licitacoes` | `licitacao.index` | `Portal\BiddingsController@index` | `web`, `check.site.manutention` |
| ANY | `/licitacao-ajax-detalhe/{id}` | `licitacao.showDetail` | `Portal\BiddingsController@ajaxShowDetail` | idem — **método inexistente** (erro 500) |
| ANY | `/licitacao/{id}` | `licitacoes.show` | `Portal\BiddingsController@indexShow` | idem |
| ANY | `/contratos` | `contratos` | `Portal\ContractController@index` | idem |
| GET | `/contrato/{contractId}` | `contratos.show` | `Portal\ContractController@show` | idem |
| ANY | `/contratos/{contractId}` | `contratos.showByBidding` | `Portal\ContractController@showContractsByBidding` | idem |
| ANY | `/exportacao` | `exportacao` | `Portal\BiddingsController@index` | idem |

Fontes: `v6/routes/web.php:146-148`, `:183-185`, `:188`.

Rotas do módulo “licitações antigas” (módulo `licitacoes-antigas`, id 20):

| Método | URI | Nome | Controller@action |
|---|---|---|---|
| ANY | `/licitacoes-ate-2018` | `licitacoes.old.index` | `Portal\BiddingOldController@index` |
| ANY | `/licitacoes-ate-2018/{id}` | `licitacoes.old.show` | `Portal\BiddingOldController@show` |

Fonte: `v6/routes/web.php:179-180`. O link para essa seção só aparece quando
`Configuration::where('key','licitacoes_old')` está com valor `1`
(o registro não existe na base de desenvolvimento).

## 6. Colisão de nomes de rota

`licitacao.index` é usada **tanto** pelo portal (`v6/routes/web.php:146`) quanto
pelo resource do CMS (`v6/routes/basepro.php:184`). Como `mapBaseproRoutes()` é
registrado depois de `mapWebRoutes()`, o helper `route('licitacao.index')` resolve
para `/cms/licitacao` — que é o comportamento desejado nos `redirect()` do
controller do CMS. Já `licitacao.showDetail` (portal) e `licitacao.show` (CMS)
têm nomes distintos e não colidem.

## 7. Form Requests e regras de validação

### 7.1 `BiddingRequest` — `v6/app/Http/Requests/BiddingRequest.php`

`authorize()` retorna `true` (a autorização real é feita pelo middleware
`check.route.permission` e pelo `has_access()`).

Regras idênticas para `create` e `update` (o bloco `update` é cópia do `create`):

| Campo | Regra | Efeito |
|---|---|---|
| `statuses_id` | `new SelectRule` | reprova `'0'`; sem `required` (ver 7.4) |
| `number` | `required|max:15` | obrigatório, até 15 caracteres |
| `management_units_id` | `new SelectRule` | reprova `'0'`; sem `required` |
| `bidding_types_id` | `new SelectRule` | reprova `'0'`; sem `required` |
| `bidding_modalities_id` | `new SelectRule` | reprova `'0'`; sem `required` |
| `jurisdicionado` | `required` | ao menos uma secretaria |
| `process_number` | `''` (sem regra) | livre |
| `object` | `required` | obrigatório |
| `show_value` | `required` | obrigatório (Sim/Não) |
| `value` | `required` | obrigatório |
| `publication_edital_date` | comentada | **opcional** (regra desativada) |
| `competition_date` | comentada | **opcional** (regra desativada) |

Mensagens customizadas: “O campo status é obrigatório.”, “O campo número da
licitação é obrigatório.”, “O campo publicar valor estimado é obrigatório.”,
“O Número da Licitação digitado já está em uso.” (para `number.unique`).

Bloqueios notáveis no arquivo:

- O bloco `Rule::unique('biddings','number')` (condicionado a
  `bidding_modalities_id` e `deleted_at IS NULL`) está **comentado**;
  a variável `$uniqueNumber` recebe `''`.
- O bloco que tornava `publication_edital_date` e `competition_date`
  obrigatórios para modalidades diferentes de `1, 6, 7, 8` também está
  comentado.
- `$codigo = Request::segment(3);` é atribuído e nunca usado.
- `$index` é escolhido por `Route::getCurrentRoute()->getName() == 'licitacao.store'`.
- `redirect()` retorna `redirect()->back()`.

### 7.2 `BiddingTypeRequest` / `BiddingModalityRequest`

| Campo | Regra |
|---|---|
| `name` | `required|max:120` |

Mensagens: “O campo nome é obrigatório.” e “O campo nome atingiu o limite máximo de caracteres.”

### 7.3 `ContractRequest` — `v6/app/Http/Requests/ContractRequest.php`

| Campo | Regra |
|---|---|
| `number` | `required` |
| `signature_date` | `required` |
| `publication_date` | `required` |
| `proposal` | `nullable|exists:proposals,id` |
| `bidding_id` | `required|exists:biddings,id` |
| `value` | `required` |

A regra de mime type dos arquivos está comentada.

### 7.4 `SelectRule` — `v6/app/Rules/SelectRule.php`

Reprova o valor `'0'` e aprova qualquer outro valor. Como as regras do
`BiddingRequest` usam apenas `[new SelectRule]` (sem `required`), um select
vazio passa pela validação e a violação de `NOT NULL` só aparece no banco —
capturada pelo `try/catch` do controller, que exibe a mensagem genérica de
erro.

## 8. Repositórios e contratos

| Interface (caminho) | Implementação (caminho) | Observações |
|---|---|---|
| `v6/app/Repositories/Contracts/IBiddingRepository.php` | `v6/app/Repositories/Eloquent/Prefecture/Bidding/BiddingRepositoryDefaultImpl.php` | Interface **vazia**; o contrato real é a classe base `v6/app/Repositories/Repository.php`. |
| `v6/app/Repositories/Contracts/IContractRepository.php` | `v6/app/Repositories/Eloquent/Prefecture/Contract/ContractRepositoryDefaultImpl.php` | `structureUpInsert()` devolve `[]` — o `ContractController` grava direto via `DB::transaction`. |
| `v6/app/Repositories/Contracts/IArchiveRepository.php` | (injetada em `BiddingController`) | Usada em `extractedToArchive()`. |

Métodos relevantes da base `Repository`:

| Método | Comportamento |
|---|---|
| `upInsert($request, $id = null)` | `DB::transaction(structureUpInsert(dataTrait($request)))` e depois `updateOrCreate(['id' => $id], $data)`. Em erro faz `dd($th)` (dump + morte). |
| `dataTrait($request)` | Usa `$request->request->all()` (corpo do POST, **não** o payload JSON). |
| `destroy($id, $column = 'id')` | Deleta o registro. |
| `destroyAll($array, $column = 'id')` | `whereIn(...)->delete()`. |
| `destroyWithRelation($id, $relation, $column)` | Apaga o registro e o registro relacionado. |

`BiddingRepositoryDefaultImpl::destroy()` sobrescreve o da base: antes de
excluir, executa `$element->contents()->detach()` para remover as secretarias
jurisdicionadas (a exclusão em lote **não** faz isso).

`BiddingRepositoryDefaultImpl::structureUpInsert()` converte
`publication_edital_date` (`d/m/Y` → `Y-m-d`), `competition_date`
(`d/m/Y H:i` → `Y-m-d H:i`), `homologation_date` (`d/m/Y` → `Y-m-d`) e aplica
`strip_tags()` em `local`.

## 9. API Resources / presenters

Não há API Resource JSON no módulo (o `api.php` não referencia licitações).
A serialização é feita por:

- **Portal JSON**: `if (request()->ajax()) return response()->json($result);`
  em `Portal\BiddingsController@index` — devolve a coleção Eloquent completa
  (com relações), serializada pelo `toArray()` do Laravel.
- **Presenters**: `BiddingPresenter` (`getStyleHasProposal()`,
  `getStyleColorTd()`, `getStatusWinner()`, `dateFormat()`) e
  `ContractPresenter` (`getSignatureDate()`, `getPublicationDate()`,
  `getValidityDate()`, `getValue()`).

## 10. Permissões exigidas

| Chave | Onde é usada | Origem |
|---|---|---|
| `licitacao.create` | `prefecture/biddings/index.blade.php` | `resources.slug = 'licitacao'` |
| `licitacao.delete` | `prefecture/biddings/index.blade.php` | idem |
| `licitacao.edit` / `licitacao.restore` / `licitacao.inactivate` | `DatatableController::struct_views()` | idem |
| `licitacoes.index` / `.create` / `.edit` / `.delete` | `BiddingTypeController`, `BiddingModalityController` e suas views | **inexistente** na tabela `resources` |

O `check.route.permission`
(`v6/app/Http/Middleware/CheckRoutePermission.php`) permite a rota quando o
segmento da URL contém `create`, `store`, `edit`, `update`, `destroy` ou
`delete` — nesses casos ele **não** checa a permissão específica, apenas exige
login e papel diferente de usuário sem acesso.

## 11. Log de auditoria

`BiddingController@log($licitacao, $descricao)` usa
`spatie/laravel-activitylog` (`activity()->performedOn(...)->causedBy(Sentinel::getUser())`)
e grava os campos: id, número, data de publicação do edital, data do certame,
objeto, número do processo, valor, modalidade, unidade gestora, tipo e autor.
Também há logs em `index()` (“Listou Licitações”), `create()`
(“Acessou criação de licitação”), `edit()` (“Acessou a edição de Licitação”),
`store()`, `update()`, `destroy()`, `deleteByLote()` e na criação de cada arquivo.
