# Servidor — Modelagem de Dados

> **Módulo:** `servidor` (tabela `modulos`, id 35)
> **Banco:** PostgreSQL 10 (`prefeituras_v5`)

---

## 1. Tabela `servidor`

Criada por `v6/database/migrations/2023_01_12_093921_create_servidors_table.php`
(classe `CreateServidorsTable`). O arquivo é **idêntico** ao da v5.

```php
Schema::create('servidor', function (Blueprint $table) {
    $table->increments('id');
    $table->string('cpf');
    $table->string('name');
    $table->string('cargo');
    $table->string('matricula');
    $table->string('identidade');
    $table->integer('secretaria')->unsigned()->nullable();
    $table->string('banco')->nullable();
    $table->string('conta')->nullable();
    $table->string('agencia')->nullable();
    $table->softDeletes();
    $table->timestamps();

    $table->foreign('secretaria')
        ->references('id')
        ->on('contents');
});
```

### Colunas (estado real no banco)

| Coluna | Tipo | Nulo | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` (serial) | não | `servidor_id_seq` | PK |
| `cpf` | `character varying(191)` | não | — | sem índice e sem unicidade |
| `name` | `character varying(191)` | não | — | nome do servidor |
| `cargo` | `character varying(191)` | não | — | função exercida |
| `matricula` | `character varying(191)` | não | — | matrícula funcional |
| `identidade` | `character varying(191)` | não | — | RG |
| `secretaria` | `integer` | sim | — | FK `servidor_secretaria_foreign` → `contents(id)` |
| `banco` | `character varying(191)` | sim | — | **dado bancário em claro** |
| `conta` | `character varying(191)` | sim | — | **dado bancário em claro** |
| `agencia` | `character varying(191)` | sim | — | **dado bancário em claro** |
| `deleted_at` | `timestamp(0) without time zone` | sim | — | soft delete |
| `created_at` | `timestamp(0) without time zone` | sim | — | timestamps |
| `updated_at` | `timestamp(0) without time zone` | sim | — | timestamps |

Índices e constraints (verificados no banco):

- `servidor_pkey` (PK)
- `servidor_secretaria_foreign` — FOREIGN KEY (`secretaria`) REFERENCES `contents(id)`
- **Referenciada por:** `requerimento_diarias.requerimento_diarias_id_servidor_foreign`
  — FOREIGN KEY (`id_servidor`) REFERENCES `servidor(id)`

Observações:

- O nome da migration é `create_servidors_table.php` (plural), mas a tabela criada é
  `servidor` (singular). O model declara `protected $table = 'servidor'` para
  compensar.
- Não há unicidade em `cpf` nem em `matricula`: o mesmo CPF pode ser cadastrado duas vezes.
- `secretaria` é `unsigned` na migration (o Postgres não tem unsigned; a coluna é `integer`).
- A FK para `contents` **não** tem `ON DELETE`: excluir fisicamente uma secretaria
  vinculada a um servidor é bloqueado pelo banco; o soft delete da secretaria deixa o
  servidor apontando para um registro invisível na lista.

## 2. View SQL `servidores`

Criada por `v6/database/migrations/2023_03_27_154732_create_view_to_servidores.php`
(classe `CreateViewToServidores`), idêntica à v5:

```sql
CREATE OR REPLACE VIEW servidores
AS SELECT row_number() OVER (ORDER BY s.created_at ASC) AS value,
    s.name as name,
    s.cpf as cpf,
    s.id as id
FROM servidor s
WHERE s.deleted_at IS NULL
ORDER by s.created_at desc;
```

| Coluna da view | Origem | Uso |
|---|---|---|
| `value` | `row_number()` sobre `created_at ASC` | coluna "ID" exibida na DataTable (numeração, não o id real) |
| `name` | `s.name` | coluna "Servidor" |
| `cpf` | `s.cpf` | coluna "CPF" |
| `id` | `s.id` | chave das ações (editar/excluir) |

Pontos importantes:

- A view expõe **apenas 4 colunas**: cargo, matrícula, identidade, secretaria e os dados
  bancários **não** aparecem na listagem do CMS. Isso é positivo para a exposição de
  dados, mas significa que a listagem e o formulário leem objetos diferentes.
- A view já aplica `WHERE deleted_at IS NULL`; o model Eloquent também filtra por
  soft delete, de modo que as duas fontes concordam quanto aos registros apagados.
- A ordenação é inconsistente dentro da própria view: o `row_number()` usa
  `created_at ASC` e o `ORDER BY` final usa `created_at DESC`. Na prática quem ordena
  é o DataTables (`runDatatables('servidor', $colunas, true, 'ASC', null, true)`).

## 3. Tabelas de apoio

### `menu_orders` (linha do módulo)

| Coluna | Valor |
|---|---|
| `id` | 29 |
| `slug` | `servidor` |
| `clicks` | contador incrementado por `menuClicks('servidor')` |
| `modulo_id` | 35 → `modulos.id` |

### `modulos` (registro do módulo)

| id | name | slug | status inicial |
|---|---|---|---|
| 35 | Servidor | `servidor` | 0 (desligado) |

A migration `2024_11_01_150206_create_modulos_table.php` **não existe na v5**: o registro
de módulos é novidade da v6.

### `resources` (linha do módulo)

| id | slug | name | modulo_id |
|---|---|---|---|
| 47 | `servidor` | Servidor | 35 |

A tela de Perfis (`v6/resources/views/admin/role/form.blade.php:44-70`) monta as chaves
de permissão como `{resources.slug}.{permissions.slug}`, ou seja
`servidor.index`, `servidor.create`, `servidor.edit`, `servidor.delete`,
`servidor.inactive`, `servidor.inactivate`, `servidor.restore`, `servidor.show`,
`servidor.publish`. É essa linha em `resources` que torna o módulo administrável por
perfis.

## 4. Relacionamentos

| Origem | Destino | Tipo | Onde |
|---|---|---|---|
| `servidor.secretaria` | `contents.id` | FK física | migration `2023_01_12_093921` |
| `requerimento_diarias.id_servidor` | `servidor.id` | FK física | migration `2023_01_13_093828_create_requerimento_diarias_table.php` |
| `Servidor::secretarias()` | `App\Prefecture\Content` (`id` ← `secretaria`) | `hasOne` (deveria ser `belongsTo`) | `v6/app/Http/Model/Prefecture/Servidor.php` |
| `RequerimentoDiarias::servidor()` | `App\Http\Model\Prefecture\Servidor` (`id` ← `id_servidor`) | `hasOne` (deveria ser `belongsTo`) | `v6/app/Http/Model/Prefecture/RequerimentoDiarias.php` |
| `menu_orders.modulo_id` | `modulos.id` | `belongsTo` | `v6/app/Http/Model/Prefecture/MenuOrder.php` |
| `resources.modulo_id` | `modulos.id` | `belongsTo` | `v6/app/Http/Model/Admin/Resource.php` |

> A relação `secretarias()` do model `Servidor` **não é usada** em nenhuma view nem
> controller: o formulário usa a coleção `$secretarias` carregada pelo controller.

## 5. Migrations do módulo (v6)

| Data | Arquivo | Efeito |
|---|---|---|
| 2023-01-12 | `v6/database/migrations/2023_01_12_093921_create_servidors_table.php` | cria a tabela `servidor` + FK para `contents` |
| 2023-03-27 | `v6/database/migrations/2023_03_27_154732_create_view_to_servidores.php` | cria a view `servidores` |
| 2023-01-13 | `v6/database/migrations/2023_01_13_093828_create_requerimento_diarias_table.php` | cria `requerimento_diarias` com FK para `servidor` (módulo acoplado) |
| 2023-02-01 | `v6/database/migrations/2023_02_01_083626_add_itens_to_archives.php` | insere o "Manual de Servidor" e suas 3 áreas |
| 2024-11-01 | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` | cria `modulos` e insere o registro id 35 (`servidor`) |

## 6. Seeds do módulo (v6)

Não existe seed de dados para o módulo `servidor`. O cadastro é alimentado
exclusivamente pelo operador do CMS. O que existe:

| Arquivo | Efeito |
|---|---|
| `v6/database/migrations/2023_02_01_083626_add_itens_to_archives.php` | "Manual de Servidor" + áreas "Campos Obrigatórios", "Secretarias", "Dados bancários" (documentação interna, não dados) |
| `v6/database/seeds/ChangeLogTableSeeder.php` | entrada de changelog (versão 5.0) descrevendo o módulo |

## 7. Model Eloquent

`v6/app/Http/Model/Prefecture/Servidor.php`:

```php
namespace App\Http\Model\Prefecture;

use App\Prefecture\Content as Info;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Servidor extends Model
{
    use SoftDeletes;

    protected $table = 'servidor';

    protected $fillable = [
        'cpf', 'name', 'cargo', 'banco', 'conta', 'agencia',
        'matricula', 'identidade', 'secretaria',
    ];

    public function secretarias()
    {
        return $this->hasOne(Info::class, 'id', 'secretaria');
    }
}
```

Pontos de atenção:

- `protected $table = 'servidor'` — explícito e obrigatório, já que o singular não é
  dedutível pelo pluralizador.
- `use App\Prefecture\Content as Info;` — alias que mascara o significado de "secretaria":
  a relação aponta para a tabela `contents`, não para uma tabela de secretarias.
- A relação é `hasOne` onde o correto seria `belongsTo` (o servidor **pertence** a uma
  secretaria). Funciona para leitura, mas quebra para relações inversas e para
  eager loading sem chave estrangeira explícita.
- Sem casts: `cpf` é gravado como texto (com ou sem máscara, dependendo do que o
  navegador enviar) e não há normalização (dígitos vs. `123.456.789-09`).
- Sem `$hidden`: `banco`, `conta`, `agencia`, `cpf` e `identidade` são serializados
  normalmente caso o model seja devolvido em alguma resposta JSON.
- Não há trait de auditoria: nenhuma alteração no cadastro é registrada em log de
  atividade (ao contrário de outros fluxos do CMS, que usam `activity()`).
