# Requerimento de Diárias — Modelagem de Dados

> Colunas extraídas com `\d` no PostgreSQL 10 (`prefeituras_v5`, banco
> `v6_6f22e1bd-postgres-1`) e conferidas contra as migrations de `v6`.

---

## 1. `requerimento_diarias`

Migration criadora: `v6/database/migrations/2023_01_13_093828_create_requerimento_diarias_table.php`

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` (serial) | não | `requerimento_diarias_id_seq` | PK |
| `id_servidor` | `integer` | não | — | FK → `servidor(id)` |
| `codigo_diarias` | `integer` | não | — | Era `varchar(191)`; convertido em `2023_01_30_093017_alter_codigo_diarias_to_requerimento_diarias.php` |
| `dados_objetivo` | `text` | não | — | Era `varchar`; convertido em `2023_05_17_091233_update_requerimento_diarias.php` |
| `cidade_destino` | `varchar(191)` | não | — | |
| `estado_destino` | `integer` | não | — | FK → `states(id)` |
| `placa_transporte` | `varchar(191)` | não | — | Texto livre (rótulo do campo diz "placa") |
| `data_saida` | `date` | não | — | |
| `data_entrada` | `date` | não | — | |
| `duracao_viagem` | `varchar(191)` | não | — | Texto livre (ex.: "2 dias") |
| `numero_diarias` | `varchar(191)` | não | — | Quantidade; texto, não inteiro |
| `valor_diarias` | `varchar(191)` | não | — | Valor monetário como texto (máscara `money` no form) |
| `fundamentos_legais` | `varchar(191)` | não | — | |
| `deleted_at` | `timestamp(0)` | sim | — | Soft delete |
| `created_at` | `timestamp(0)` | sim | — | |
| `updated_at` | `timestamp(0)` | sim | — | |

Índices e constraints:

| Nome | Tipo |
|---|---|
| `requerimento_diarias_pkey` | PRIMARY KEY (id) |
| `requerimento_diarias_estado_destino_foreign` | FOREIGN KEY → `states(id)` |
| `requerimento_diarias_id_servidor_foreign` | FOREIGN KEY → `servidor(id)` |
| `archives_requerimento_id_foreign` | `archives.requerimento_id` → `requerimento_diarias(id)` **ON DELETE CASCADE** |
| `requerimento_diarias_configs_requerimento_id_foreign` | `requerimento_diarias_configs.requerimento_id` → `requerimento_diarias(id)` |

> **Coluna inexistente:** o controller e o formulário usam `padrao_arquivo_config`
> (`RequerimentoDiariasController@delete_archives`), mas **essa coluna não
> existe** em `requerimento_diarias` nem está no `$fillable` do model. Ver
> `08-GAPS-E-DIVIDAS.md`.

## 2. `requerimento_diarias_configs`

Migration criadora: `v6/database/migrations/2023_09_14_133731_create_requerimento_diarias_configs_table.php`

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` (serial) | não | `requerimento_diarias_configs_id_seq` | PK. O código trata a linha `id = 1` como singleton |
| `lei_padrao` | `text` | sim | — | Texto da lei padrão (herdado do `fundamentos_legais`) |
| `archive` | `varchar(300)` | sim | — | **Nome original do arquivo** (não o caminho) |
| `requerimento_id` | `integer` | sim | — | FK → `requerimento_diarias(id)`. Não usado pela lógica atual |
| `path` | `varchar(300)` | sim | — | Caminho gravado (`storage/app/public/leis_de_requerimento/...`) |
| `deleted_at` | `timestamp(0)` | sim | — | Soft delete |
| `created_at` | `timestamp(0)` | sim | — | |
| `updated_at` | `timestamp(0)` | sim | — | |

## 3. Tabelas compartilhadas usadas pelo módulo

### `servidor` (origem do requerente)

| Coluna | Tipo | Null |
|---|---|---|
| `id` | `integer` | não |
| `cpf` | `varchar(191)` | não |
| `name` | `varchar(191)` | não |
| `cargo` | `varchar(191)` | não |
| `matricula` | `varchar(191)` | não |
| `identidade` | `varchar(191)` | não |
| `secretaria` | `integer` | sim (FK → `contents(id)`) |
| `banco` / `conta` / `agencia` | `varchar(191)` | sim |
| `deleted_at` / `created_at` / `updated_at` | `timestamp(0)` | sim |

### `states` (UF de destino)

| Coluna | Tipo | Null |
|---|---|---|
| `id` | `integer` | não |
| `uf_code` | `bigint` | não |
| `name` | `varchar(191)` | não |
| `uf` | `varchar(191)` | não |
| `countries_id` | `integer` | não (FK → `countries`) |
| `deleted_at` / `created_at` / `updated_at` | `timestamp(0)` | sim |

### `archives` (arquivo anexado — colunas usadas)

| Coluna | Tipo | Null | Observação |
|---|---|---|---|
| `id` | `integer` | não | PK |
| `title` | `varchar(300)` | sim | Nome original do arquivo (usado no link do Portal) |
| `name` | `varchar(300)` | sim | Nome gravado |
| `path` | `varchar(300)` | sim | Caminho gravado |
| `requerimento_id` | `bigint` | sim | FK → `requerimento_diarias(id)` ON DELETE CASCADE |
| `deleted_at` / `created_at` / `updated_at` | `timestamp(0)` | sim | Soft delete |

## 4. Relacionamentos Eloquent

| Model | Método | Tipo | Colunas |
|---|---|---|---|
| `RequerimentoDiarias` | `servidor()` | `hasOne` | `id_servidor` → `servidor.id` |
| `RequerimentoDiarias` | `estado()` | `hasOne` | `estado_destino` → `states.id` |
| `RequerimentoDiarias` | `archives()` | `hasOne` | `requerimento_id` → `archives.id` |
| `RequerimentoDiariasConfig` | `requerimento_diarias()` | `hasMany` | `requerimento_id` → `requerimento_diarias.id` |
| `App\Prefecture\Archive` | `requerimento_diarias()` | `hasOne` | `requerimento_id` → `requerimento_diarias.id` |

> **Observações de modelagem:**
> - `servidor()` e `estado()` usam `hasOne` onde o correto seria `belongsTo`
>   (relação 1-1). Funciona porque a FK está na tabela local, mas o modelo de
>   dados fica incorreto.
> - `archives()` é `hasOne` apesar do nome plural: um requerimento pode ter
>   vários arquivos no banco, mas a relação expõe apenas um.
> - `RequerimentoDiariasConfig` importa
>   `Illuminate\Database\Eloquent\Model\RequerimentoDiarias` — import inválido
>   (a classe real está em `App\Http\Model\Prefecture`). O `use` não é usado no
>   corpo da classe, então não quebra em runtime.
> - O model `RequerimentoDiarias` tem `protected $fillable` com `archive`, que
>   **não existe** como coluna na tabela.

## 5. Views SQL

### 5.1 `req_diarias` — view **deste módulo**

Criada em `v6/database/migrations/2023_04_10_100557_create_view_to_requerimentos_diarias.php`
e recriada em `v6/database/migrations/2023_05_17_091233_update_requerimento_diarias.php`
(versão vigente, idêntica à primeira).

SQL efetivo no banco (confirmado com `pg_get_viewdef`):

```sql
SELECT rd.codigo_diarias AS value,
    s.name AS requerente,
    rd.dados_objetivo AS objetivo_viagem,
    rd.fundamentos_legais,
    concat('/cms/requerimento-diarias/', rd.id) AS download_link,
    rd.id
FROM requerimento_diarias rd
  JOIN servidor s ON s.id = rd.id_servidor
 WHERE rd.deleted_at IS NULL
 ORDER BY rd.codigo_diarias DESC;
```

| Coluna da view | Origem | Uso |
|---|---|---|
| `value` | `requerimento_diarias.codigo_diarias` | 1ª coluna do DataTable (`runDatatables` injeta `data: 'value'`) |
| `requerente` | `servidor.name` | Coluna "Requerente" |
| `objetivo_viagem` | `requerimento_diarias.dados_objetivo` | Coluna "Objetivo da viagem" |
| `fundamentos_legais` | `requerimento_diarias.fundamentos_legais` | Coluna "Fundamentos Legais" |
| `download_link` | `concat('/cms/requerimento-diarias/', id)` | Botão de download do PDF no CMS |
| `id` | `requerimento_diarias.id` | Rotas de edição/exclusão |

Consumidor único: `v6/app/Http/Controllers/Prefecture/DatatableController.php:181`
(`DB::table('req_diarias')->get()`).

> O `ORDER BY` dentro da view é ignorado pelo PostgreSQL quando a view é
> consultada sem `LIMIT`/`OFFSET` — a ordenação real vem do DataTable.

### 5.2 `requerimentos` — view de **outro módulo** (documentada por exigência do contrato)

As migrations abaixo criam a view `requerimentos`, que **não pertence** a este
módulo: ela projeta `contents` da categoria `category_contents.slug = 'requerimentos'`
(módulo 41 — Publicações/Requérimentos), consumida por
`Prefecture\DatatableController@get_requerimentos` e por
`Portal\RequerimentoController`. O Portal de Diárias **não** a utiliza.

| Migration | Efeito |
|---|---|
| `v6/database/migrations/2024_10_08_005745_create_view_requerimentos.php` | Versão inicial: filtro por `category_contents_id = (SELECT id ... slug='requerimentos')` |
| `v6/database/migrations/2025_08_12_112801_recreate_view_requerimentos.php` | Troca o filtro por `IN (SELECT ... WHERE category_contents.category_contents_id = (pai))` — passa a pegar as **categorias filhas** do slug `requerimentos` |
| `v6/database/migrations/2026_06_09_110000_fix_view_requerimentos_recursive.php` | Correção **recursiva**: CTE `WITH RECURSIVE cat_tree` percorre toda a árvore de `category_contents` a partir do nó raiz `slug='requerimentos'` |
| `v6/database/migrations/2026_06_12_100100_add_requerente_to_view_requerimentos.php` | Versão **vigente**: `DROP VIEW` + recriação com `LEFT JOIN councilmen cm` e a coluna `requerente` |

**SQL final real (arquivo mais recente — `2026_06_12_100100_add_requerente_to_view_requerimentos.php`):**

```sql
CREATE VIEW public.requerimentos AS
WITH RECURSIVE cat_tree AS (
    SELECT id
    FROM category_contents
    WHERE slug = 'requerimentos'
      AND category_contents_id IS NULL
    UNION ALL
    SELECT cc.id
    FROM category_contents cc
    JOIN cat_tree ct ON cc.category_contents_id = ct.id
)
SELECT
    row_number() OVER (ORDER BY c.created_at) AS value,
    c.title AS titulo,
    to_char(c.publication_date::timestamp with time zone, 'DD/MM/YYYY') AS data_publicacao,
    cc.name AS categoria,
    cm.name AS requerente,
    c.id
FROM contents c
JOIN category_contents cc ON cc.id = c.category_contents_id
LEFT JOIN councilmen cm ON cm.id = c.councilmen_id AND cm.deleted_at IS NULL
WHERE c.deleted_at IS NULL
  AND c.category_contents_id IN (
      SELECT id FROM cat_tree
      WHERE id NOT IN (
          SELECT id FROM category_contents
          WHERE slug = 'requerimentos' AND category_contents_id IS NULL
      )
  )
ORDER BY c.created_at DESC;
```

Definição no banco (via `pg_get_viewdef`) confere com o SQL acima. O `down()`
dessa migration recria a versão **sem** `requerente` (idêntica à de
`2026_06_09`).

> **Armadilha de rollback:** o `down()` de `2026_06_12` e o `up()` de
> `2026_06_09` contêm o mesmo SQL sem `requerente`, mas a migration de
> `2026_06_12` faz `DROP VIEW` antes — o que **falha** se a view não existir
> (PostgreSQL exige `DROP VIEW IF EXISTS`).

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

| Data/hora | Arquivo | Efeito |
|---|---|---|
| 2023-01-13 09:38:28 | `2023_01_13_093828_create_requerimento_diarias_table.php` | Cria `requerimento_diarias` |
| 2023-01-30 09:30:17 | `2023_01_30_093017_alter_codigo_diarias_to_requerimento_diarias.php` | `codigo_diarias`: varchar → int |
| 2023-04-10 10:05:57 | `2023_04_10_100557_create_view_to_requerimentos_diarias.php` | Cria a view `req_diarias` |
| 2023-05-17 09:12:33 | `2023_05_17_091233_update_requerimento_diarias.php` | `dados_objetivo`: varchar → text; recria `req_diarias` |
| 2023-09-14 13:37:31 | `2023_09_14_133731_create_requerimento_diarias_configs_table.php` | Cria `requerimento_diarias_configs` |
| 2023-09-19 16:48:31 | `2023_09_19_164831_add_requerimento_id_to_archives.php` | `archives.requerimento_id` + FK ON DELETE CASCADE |
| 2024-11-01 15:02:06 | `2024_11_01_150206_create_modulos_table.php` | Registra o módulo `requerimento-de-diarias` (id 36) e `requerimentos` (id 41) |

## 7. Seeds

| Seed | Versão | Conteúdo |
|---|---|---|
| `v5/database/seeds/RequerimentoDiariasSeeder.php` | **só v5** | Insere `menu_orders` (`servidor`, `requerimento-diarias`) e `resources` (`servidor`, `requerimento-diarias`, `certificado`) — **sem** `modulo_id` |
| `v6/database/seeds/ResourcesTableSeeder.php:414-420` | v6 | `Resource::updateOrCreate(['slug' => 'requerimento-diarias'], [... 'modulo_id' => 36])` |
| `v6/database/seeds/MenuOrderSeeder.php:235-240` | v6 | `MenuOrder::updateOrCreate(['slug' => 'requerimento-diarias'], ['clicks' => 0, 'modulo_id' => 36])` |

O `RequerimentoDiariasSeeder` **não existe na v6** — foi substituído pelos
seeds genéricos com `modulo_id`.

### Permissões derivadas

`v6/resources/views/admin/role/form.blade.php:55` monta a chave de permissão
como `"{$resource->slug}.{$permission->slug}"`. Para este módulo, as
permissões possíveis são:

| Chave | Uso no código |
|---|---|
| `requerimento-diarias.index` | Acesso à listagem (via `check.route.permission`) |
| `requerimento-diarias.create` | `has_access(['requerimento-diarias.create'])` no botão "Cadastrar" (`index.blade.php`) |
| `requerimento-diarias.delete` | `has_access(['requerimento-diarias.delete'])` nos botões de exclusão e exclusão em lote |
| `requerimento-diarias.edit` | Injetado em `struct_views()` como `edit` |
| `requerimento-diarias.show` / `.restore` / `.inactivate` | Injetados em `struct_views()` |

As tabelas `resources` (slug `requerimento-diarias`, id 48) e `permissions`
(slugs `index`, `create`, `edit`, `delete`, `inactive`, `inactivate`, `restore`,
`show`, `publish`) fornecem a base; a atribuição por papel é feita pelo
`Admin\RoleController` (`v6/app/Http/Controllers/Admin/RoleController.php:72`,
`$role->addPermission($permission)`).
