# Citrus — Sistema de Gestão de Diárias
## Spec 3: Controle de Efetivo (Consolidado) — Documento de Design

| Campo | Valor |
| :---- | :---- |
| Projeto | Citrus Engenharia — Sistema de Gestão de Diárias |
| Spec | 3 de N (Controle de Efetivo consolidado) |
| Data | 2026-06-19 |
| Stack | Docker + PHP 8.2 + Yii2 + MySQL 8.0 + Bootstrap5 |
| Branch | `citrus-controle-efetivo` |
| Requisitos-fonte | `Citrus_Requisitos_v1.0.docx.md` §5.2 |
| Depende de | Spec 1 (Profissional, Funcao, Obra, BaseActiveRecord, PermissaoBehavior, ExportHelper, app-shell responsivo) e Spec 2 (Efetivo header/detail: efetivo_dia + efetivo) |

---

## 0. Contexto e escopo

Visão administrativa **consolidada** de todos os registros de efetivo, somente leitura: filtros,
totalizador de diárias e export para Excel. É a contraparte gerencial do registro diário (Spec 2).

**No escopo:** listagem consolidada filtrável (profissional, função, obra, quinzena, data específica),
totalizador de diárias, export Excel. Acesso restrito ao perfil **Administrativo**.

**Fora do escopo:** fechamento de card/holerite (Spec 4), dashboard (Spec 5), relatórios dinâmicos
por SQL. Sem edição/exclusão aqui (isso é no módulo de registro, Spec 2).

Decisões do brainstorming:
- **1 registro de efetivo = 1 diária** (totalizador = contagem de registros do filtro).
- **Acesso só Administrativo** (nova tela de permissão `controle-efetivo`).
- **Período por quinzena fixa (1–15 / 16–último dia) + data específica opcional** que refina.

---

## 1. Acesso e navegação

- Novo controller **`ControleEfetivoController`** → a `tela` de permissão é `controle-efetivo`.
- Migration semeia a permissão `controle-efetivo` com ações `index,export` **apenas para `administrativo`**.
- `PermissaoBehavior` gateia: `index` → permissão `index`; `export` → permissão `export`.
  Não-administrativo recebe 403.
- **Item de menu:** entra em "Mais" (sidebar desktop + sheet mobile) como "Controle de Efetivo",
  visível só para quem tem a permissão. **O `views/layouts/main.php` tem alterações não-commitadas
  do desenvolvedor; este spec NÃO modifica o `main.php`.** O trecho do item de menu é entregue no
  plano para o desenvolvedor inserir manualmente quando desejar. A tela é acessível por URL/permissão
  independentemente do atalho.

---

## 2. Consulta (EfetivoSearch)

Model de busca **`models/EfetivoSearch.php`** (form não persistido). Atributos seguros:
`profissional_id`, `funcao_id`, `obra_id`, `quinzena` (string `AAAA-MM-Q`, Q∈{1,2}), `data` (Y-m-d).

`search(array $params): ActiveDataProvider`:
- Base: `Efetivo::find()` (já filtra `efetivo.deleted_at IS NULL` via `SoftDeleteQuery`).
- `joinWith(['efetivoDia', 'profissional', 'profissional.funcao'])` para acessar `efetivo_dia.data`,
  `efetivo_dia.obra_id`, `profissional.nome`, `profissional.funcao_id`, `funcao.nome`.
- Exclui dias soft-deletados: `andWhere(['efetivo_dia.deleted_at' => null])`.
- Filtros (combinam com E):
  - `andFilterWhere(['efetivo.profissional_id' => $this->profissional_id])`
  - `andFilterWhere(['profissional.funcao_id' => $this->funcao_id])`
  - `andFilterWhere(['efetivo_dia.obra_id' => $this->obra_id])`
  - Quinzena → intervalo `between efetivo_dia.data` `[início, fim]` (ver §3).
  - Data específica → `efetivo_dia.data = $this->data` (refina).
- Ordenação padrão: `efetivo_dia.data DESC, efetivo.id DESC`.
- Paginação: 50 por página.
- Método `totalDiarias(): int` — contagem total dos registros do filtro (sem paginação)
  = `$dataProvider->getTotalCount()`.
- Métodos auxiliares para os KPIs secundários: `totalProfissionais()` e `totalObras()`
  (DISTINCT sobre o mesmo filtro).

Cuidado de implementação: como há `joinWith`, qualificar colunas com prefixo de tabela
(`efetivo.`, `efetivo_dia.`, `profissional.`) para evitar ambiguidade.

---

## 3. Filtro de período (quinzena + data)

- **Quinzena**: select com as quinzenas (ex.: rótulo "1ª quinzena · Jun/2026", valor `2026-06-1`).
  Oferecer os últimos ~12 meses (2 quinzenas por mês). Da `quinzena` deriva-se o intervalo:
  - Q=1 → `[AAAA-MM-01, AAAA-MM-15]`
  - Q=2 → `[AAAA-MM-16, AAAA-MM-<último dia do mês>]` (último dia via `t` do PHP `date`,
    cobrindo fevereiro/anos bissextos).
- **Data específica** (input date, opcional): quando preenchida, adiciona `data = X` (refina dentro
  do que sobrou). Quinzena e data podem ser usadas juntas ou isoladamente; ambas opcionais (sem
  filtro de período = todos os registros).
- A lógica de derivação do intervalo fica num helper estático testável
  (`EfetivoSearch::intervaloQuinzena(string $quinzena): array` → `[inicio, fim]`).

---

## 4. Totalizador

No topo da tela, em destaque:
- **Total de diárias** = `totalDiarias()` (contagem de registros no filtro).
- Secundários: **Profissionais** (distintos) e **Obras** (distintas) no resultado.
Reusa o componente visual de KPIs do app-shell (`.kpi-grid`/`.kpi`).

---

## 5. Listagem

Colunas (§5.2): **Data · Obra · Profissional / Função · Entrada · Saída · Observação**.
- Desktop: tabela (`.data-table`); mobile: cards (`.card-list`/`.entity-card`) — mesmo padrão
  responsivo do Spec 1/2.
- `Saída` vazia exibida como "—".
- Filtros: inline no desktop, recolhíveis (toggle "Filtros") no mobile, como nas listagens de cadastro.
- Sem ações por linha (somente leitura).

---

## 6. Export Excel

Botão **Excel** → action `export` que reusa o **mesmo filtro** (instancia `EfetivoSearch`,
busca TODOS os registros, sem paginação) e gera `.xlsx` via `ExportHelper::download` com as colunas:
`Data, Obra, Profissional, Função, Entrada, Saída, Observação`.

---

## 7. Arquivos

- `controllers/ControleEfetivoController.php` — `actionIndex` (busca + render), `actionExport`.
- `models/EfetivoSearch.php` — search + `intervaloQuinzena` + `totalDiarias`/`totalProfissionais`/`totalObras` + lista de quinzenas para o select.
- `views/controle-efetivo/index.php` + `views/controle-efetivo/_search.php`.
- `migrations/m260619_000001_controle_efetivo_permissao.php` — seed da tela `controle-efetivo` (administrativo: `index,export`).
- **Não** altera `main.php` (item de menu entregue como trecho no plano para inserção manual).

---

## 8. Testes (Codeception, contra `citrus_test`)

- **Unit (`EfetivoSearch`):** `intervaloQuinzena` para Q1/Q2 em mês de 30, 31 e fevereiro (28/29);
  `totalDiarias` igual à contagem; filtro por obra/profissional/função restringe corretamente;
  data específica refina; registros de `efetivo_dia` soft-deletado ficam fora.
- **Functional:** administrativo acessa `controle-efetivo/index` (200) e vê o totalizador;
  perfil não-administrativo (ex.: líder) recebe **403**; `export` retorna content-type xlsx;
  o filtro por obra reduz a lista/total.

---

## 9. Considerações

- O totalizador trata 1 registro = 1 diária; registros sem hora de saída ainda contam (presença no dia).
- Performance: índices existentes (`idx-efetivo_dia-obra-data`, `idx-efetivo-dia-prof`) cobrem os
  filtros mais comuns; a paginação evita carregar tudo na tela (o export é o único que varre o filtro inteiro).
- Próximo (Spec 4): fechamento de card quinzenal, que agrupa diárias por obra a partir destes registros.
