# e-Vacina — Rotas e Controllers

> Todas as rotas no grupo `cms` de `v6/routes/basepro.php`.

---

## 1. Rotas

Geradas por `Route::resource('e-vacina', 'Prefecture\EvacinaController')`
(`v6/routes/basepro.php:201`).

| Método | URI | Nome | Controller@action | Middleware | Implementada? |
|---|---|---|---|---|---|
| GET | `/cms/e-vacina` | `e-vacina.index` | `Prefecture\EvacinaController@index` | `web` | **sim** |
| GET | `/cms/e-vacina/create` | `e-vacina.create` | `@create` | `web` | **NÃO** -> 500 |
| POST | `/cms/e-vacina` | `e-vacina.store` | `@store` | `web` | **sim** |
| GET | `/cms/e-vacina/{e_vacina}` | `e-vacina.show` | `@show` | `web` | **NÃO** -> 500 |
| GET | `/cms/e-vacina/{e_vacina}/edit` | `e-vacina.edit` | `@edit` | `web` | **NÃO** -> 500 |
| PUT/PATCH | `/cms/e-vacina/{e_vacina}` | `e-vacina.update` | `@update` | `web` | **sim** |
| DELETE | `/cms/e-vacina/{e_vacina}` | `e-vacina.destroy` | `@destroy` | `web` | **NÃO** -> 500 |

### 1.1 Nenhuma autenticação

A rota **não** declara `check.logged`, e o `EvacinaController::__construct()`
não registra `check.route.permission`:

```php
public function __construct()
{
    $this->evacina_repo = app(EvacinaRepository::class);
}
```

Como a classe base `Prefecture\Controller` registra o middleware no
**seu** construtor
(`v6/app/Http/Controllers/Prefecture/Controller.php:22-25`) e a
subclasse não chama `parent::__construct()`, o middleware nunca é
registrado.

**Resultado:** `GET /cms/e-vacina` (com a URL da planilha e a
população) e `POST /cms/e-vacina` (gravar) são acessíveis **a qualquer
visitante**. As escritas exigem apenas o token CSRF do grupo `web`.

> Compare com o módulo `manual-do-sistema`, cujo controller chama
> explicitamente `$this->middleware('check.route.permission')`.

### 1.2 Colisão de rota com o portal

`Route::get('/{slug}', 'Portal\PortalController@staticShow')->name('static.show')`
está no **final** de `v6/routes/basepro.php` (fora do grupo `cms`), e
`GET /cms/e-vacina` está no grupo com prefixo `cms` — não há colisão.

---

## 2. Controller

`v6/app/Http/Controllers/Prefecture/EvacinaController.php` (60 linhas).

| Action | Comportamento | Linha |
|---|---|---|
| `__construct()` | resolve o `EvacinaRepository` pelo container | 25-28 |
| `index()` | `$this->evacina_repo->getModel()->latest()->first()` -> view `prefecture.configurations.evacina.edit` | 35-39 |
| `store(EvacinaRequest $request)` | `$request->merge(["status" => isset($request->status_evacina)])`; `upInsert($request)`; `Artisan::call('reload:evacina')`; `redirect()->back()` | 45-52 |
| `update(EvacinaRequest $request)` | idem, mas com `upInsert($request, $last_evacina->id)` | 58-64 |

### 2.1 `store`

```php
public function store(EvacinaRequest $request): RedirectResponse
{
    $request->merge(["status" => isset($request->status_evacina)]);

    if($this->evacina_repo->upInsert($request))
        Artisan::call('reload:evacina');

    return redirect()->back();
}
```

- **Cria** um novo registro a cada salvamento (o `upInsert` com
  `$id = null` faz `updateOrCreate(['id' => null], ...)` -> INSERT).
  Como o `index` sempre mostra o **mais recente**, a tela funciona como
  um "último valor wins", mas a tabela **cresce a cada gravação**.
- O `Artisan::call` só é executado se o `upInsert` retornou verdadeiro.
- Não há flash de sucesso/erro: o operador recebe um redirect silencioso.
- Não há auditoria (`activity()`).

### 2.2 `update` — ignora o id da URL

```php
public function update(EvacinaRequest $request): RedirectResponse
{
    $request->merge(["status" => isset($request->status_evacina)]);

    if(!($last_evacina = $this->evacina_repo->getModel()->latest()->first()))
        $this->evacina_repo->error("Evacina", 'update');

    if($last_evacina->id && $this->evacina_repo->upInsert($request, $last_evacina->id))
        Artisan::call('reload:evacina');

    return redirect()->back();
}
```

- O `{e_vacina}` da URL é **ignorado**: o registro atualizado é sempre o
  mais recente.
- Se **não** houver nenhum registro, `$last_evacina` é `null` e a linha
  `$last_evacina->id` gera **erro fatal** ("Call to a member function
  id() on null") — o `error()` do repository apenas dispara um flash e
  **não** interrompe o fluxo.
- `EvacinaRepository::error()` grava um flash; mas o código continua e
  quebra na linha seguinte.

---

## 3. Form Request

`v6/app/Http/Requests/EvacinaRequest.php`:

```php
public function authorize()
{
    return true;
}

public function rules()
{
    return [
        'url'       => 'required',
        'populacao' => 'required',
    ];
}
```

Mensagens: "A url é obrigatória." e "A quantidade populacional é
obrigatória."

Observações:

- `authorize()` retorna `true` — **nenhuma** autorização.
- `url` não é validada como URL (apenas `required`).
- `populacao` não é validada como numérica (apenas `required`).
- Não há validação de `imagem_evacina` (o arquivo é aceito sem
  restrição de tipo ou tamanho).
- Não há `messages()` para o resto.

---

## 4. Repository

`v6/app/Repositories/Eloquent/Prefecture/Evacina/EvacinaRepository.php`:

```php
protected function structureUpInsert($request): array
{
    $fields = ['url', 'populacao', 'status'];
    return $this->filter_request($request, $fields);
}

public function upInsert($request, int $id = null)
{
    $entity = parent::upInsert($request, $id);

    if ($entity && isset($request->imagem_evacina)) {
        $caminho = $request->imagem_evacina->storeAs("evacina/images/$entity->id", 'banner.png');
        $entity->path_imagem = $caminho;
        $entity->save();
    }
    $type = $this->getType($id);
    $entity ? $this->success("Evacina", $type) : $this->error("Evacina", $type);
    return $entity;
}
```

Pontos:

- Só `url`, `populacao` e `status` são filtrados — os 4 contadores e o
  `path_imagem` **não** são gravados por aqui (o `path_imagem` é
  gravado logo abaixo, fora do filtro).
- A imagem é gravada com nome **fixo** (`banner.png`) no diretório
  `evacina/images/{id}`.
- `parent::upInsert` (o `Repository` abstrato) captura `Throwable` e
  chama **`dd($th)`** — a exceção vaza na tela.
- `filter_request` converte `status = false` em **`null`**.

---

## 5. Permissões exigidas

| Recurso | Permissão | Onde |
|---|---|---|
| Item de menu "e-Vacina" | `e-vacina.index` + `$modulos['e-vacina']` | `v6/resources/views/vendor/adminlte/page.blade.php:263` |
| Acesso às rotas | **nenhuma** | — |

O recurso `e-vacina` **existe** em `resources` (id 46) — ao contrário de
`supplier`, `changelog` e `manual-usuario`. Mas, como `modulos.status`
do módulo 5 é `false`, a linha do recurso **não aparece** na matriz de
permissões até que o módulo seja ligado.

---

## 6. API Resources / Presenters

Nenhum.

---

## 7. Comando artisan

`php artisan reload:evacina` (`v6/app/Console/Commands/ReloadEvacina.php`).

Agendado **mensalmente** em `v6/app/Console/Kernel.php:26-28`:

```php
$schedule->command('reload:evacina')
    ->monthly();
```

O comando:

1. `Evacina::latest()->first()` — se não houver registro, o
   `->url` em `null` gera notice e o `explode` falha; o `catch` engole.
2. Extrai `sheet_id` e `gid` da URL com `explode("spreadsheets/d/", ...)`
   e `explode("gid=", ...)`.
3. Baixa o CSV com `file_get_contents` (sem timeout, sem User-Agent).
4. `Storage::put('evacina/evacina.csv', $csv)`.
5. Lê o CSV de `storage_path('app/public/evacina/evacina.csv')` — o
   caminho assume o symlink `public/storage` -> `storage/app/public`.
6. Conta os valores da **coluna de índice 28** de cada linha.
7. Atualiza os 4 contadores **somente se `status` for verdadeiro**.
8. `catch (\Throwable $th) { // Ignorando Erro }` — **todas** as
   falhas são silenciosas.

---

## 8. Rotas do portal público

Nenhuma. Nenhum consumidor de `evacinas` foi identificado no portal da
v6.
