# Citrus — Sistema de Gestão de Diárias
## Spec 4: Fechamento de Card (Holerite Quinzenal) — Documento de Design

| Campo | Valor |
| :---- | :---- |
| Projeto | Citrus Engenharia — Sistema de Gestão de Diárias |
| Spec | 4 de N (Fechamento de Card / holerite quinzenal) |
| Data | 2026-06-19 |
| Stack | Docker + PHP 8.2 + Yii2 + MySQL 8.0 + Bootstrap5 + mPDF |
| Branch | `citrus-card` |
| Requisitos-fonte | `Citrus_Requisitos_v1.0.docx.md` §6 (e §11 tabelas) |
| Depende de | Spec 1 (Profissional, Funcao, Obra, BaseActiveRecord, PermissaoBehavior, ExportHelper), Spec 2 (Efetivo/EfetivoDia), Spec 3 (padrão de listagem/filtros) |

---

## 0. Contexto e escopo

O card é o documento de pagamento quinzenal por profissional, montado a partir do efetivo. Quinzenas
fixas: 1–15 e 16–último dia do mês.

**No escopo:** index de cards por período (pendências), montagem/edição do card (diárias por obra +
bônus/descontos manuais), snapshot ao salvar com recálculo, total líquido, fluxo de status
(rascunho→fechado→pago + reaberturas com motivo), histórico de status, geração de PDF. Acesso só
**Administrativo**.

**Fora do escopo:** dashboard (Spec 5), relatórios dinâmicos, envio do card por WhatsApp (fora do v1.0),
módulo de adiantamentos (adiantamento entra como linha de desconto manual).

Decisões do brainstorming:
- **PDF via mPDF** (renderiza view HTML → PDF).
- **Valor por tipo de função**: tipo `diaria` → `valor_diaria` × nº diárias por obra; tipo `quinzena` → `valor_base_quinzena` da função (linha base fixa).
- **Snapshot ao salvar** com botão **Recalcular** (card fechado/pago não recalcula).
- **Index com filtro de período obrigatório**, tabela por profissional, pendentes primeiro; clicar abre o detalhe sem filtros.
- **Collapse das diárias** no detalhe (mostra cada efetivo: data/entrada/saída/observação) para decisão.
- Bônus do líder (`valor_bonus_mensal`) **não** é automático — lançado como bônus manual.

---

## 1. Modelo de dados

### `card` (cabeçalho — herda BaseActiveRecord)
| Coluna | Tipo | Observações |
| :-- | :-- | :-- |
| id | pk | |
| profissional_id | int FK → profissional | |
| quinzena | string(9) | `AAAA-MM-Q` (Q∈{1,2}) |
| status | ENUM(`rascunho`,`fechado`,`pago`) | default `rascunho` |
| total_diarias / total_bonus / total_desconto / total_liquido | decimal(10,2) | congelados ao salvar |
| pago_em | date null | |
| pago_por | int FK → profissional, null | quem confirmou o pagamento |
| created_at / updated_at / deleted_at | int | |

Único `(profissional_id, quinzena)` entre não-excluídos (garantido por *find-or-create* + checagem no app).

### `card_item` (linhas — herda BaseActiveRecord)
| Coluna | Tipo | Observações |
| :-- | :-- | :-- |
| id | pk | |
| card_id | int FK → card | |
| tipo | ENUM(`diaria`,`bonus`,`desconto`) | |
| obra_id | int FK → obra, null | preenchido só em `diaria` |
| descricao | string(180) | obra (diária base) ou descrição livre (bônus/desconto) |
| quantidade | decimal(6,2) | nº de diárias (1.0 por padrão; permite 0.5 p/ meia diária ajustada) |
| valor_unitario | decimal(10,2) | |
| subtotal | decimal(10,2) | quantidade × valor_unitario (bônus/desconto: subtotal = valor) |
| created_at / updated_at / deleted_at | int | |

### `card_status_historico` (auditoria de status — ActiveRecord simples)
| Coluna | Tipo |
| :-- | :-- |
| id | pk |
| card_id | int FK → card |
| de_status | string(20) null |
| para_status | string(20) |
| usuario_id | int null |
| motivo | string(255) null |
| created_at | int |

Relações: `Card hasMany CardItem`; `CardItem belongsTo Card/Obra`; `Card hasMany CardStatusHistorico`; `Card belongsTo Profissional` (e `pagoPor`).

---

## 2. Helper `Quinzena`

Classe `models/Quinzena.php`:
- `intervalo(string $quinzena): array` → `[inicio, fim]` Y-m-d (1–15 / 16–último dia; fevereiro correto via `date('t')`).
- `disponiveis(): array` → `[valor => rótulo]` últimos ~12 meses × 2 quinzenas (ex.: `2026-06-1 => "1ª quinzena · Jun/2026"`).
- `rotulo(string $quinzena): string` → "01–15/06/2026" para exibição/PDF.

*(Reintroduz a lógica de quinzena que saiu do EfetivoSearch no Spec 3, agora como classe dedicada e reutilizável.)*

---

## 3. Index (`card/index`)

- **Filtro de período obrigatório** (`quinzena`, select de `Quinzena::disponiveis()`). Sem período → tela pede para selecionar (não lista nada).
- Conjunto de linhas = profissionais que têm efetivo no intervalo da quinzena **OU** já têm card na quinzena **OU** são de função tipo `quinzena` (pagos independ, ativos). Uma linha por profissional.
- Colunas: Profissional / Função · nº de diárias (no período) · Total líquido (do card, se existir) · Status.
- **Status efetivo**: `Pendente` (sem card) · Rascunho · Fechado · Pago. **Ordenação: Pendente → Rascunho → Fechado → Pago**, depois por nome.
- Clicar na linha → `card/fechar?profissional_id=X&quinzena=Y`.
- Desktop = tabela; mobile = cards (padrão Spec 1/2/3).

---

## 4. Detalhe (`card/fechar?profissional_id=&quinzena=`)

- Sem seletores (contexto vem da URL). Se não existir card para (profissional, quinzena), monta um **rascunho em memória** computado do efetivo (salva ao "Salvar rascunho").
- Seções: Cabeçalho (nome, CPF/CNPJ, função, quinzena via `Quinzena::rotulo`), Diárias por obra, Bônus, Descontos, Total líquido, Histórico de status.
- **Diárias por obra**: cada obra com nº diárias clicável → **collapse** com os registros de efetivo daquele profissional+obra+período (Data · Entrada · Saída · Observação), lido ao vivo do efetivo (informativo, mesmo em card fechado). Ajustes (ex.: meia diária) são feitos como **desconto manual** ou editando a `quantidade` do item enquanto rascunho.
- **Bônus / Descontos**: linhas manuais (descrição + valor); adicionar/remover enquanto rascunho.
- Ações: **Salvar rascunho**, **Recalcular** (repuxa diárias do efetivo, preserva bônus/descontos), **Gerar PDF**, e botões de status conforme estado.

---

## 5. Cálculo, snapshot e recálculo

- **Recalcular / montar**: 
  - Função tipo `diaria`: agrupa efetivo (profissional, intervalo da quinzena) por obra → um `card_item` `diaria` por obra (`quantidade`=nº diárias, `valor_unitario`=`profissional.valor_diaria`, `subtotal`).
  - Função tipo `quinzena`: um `card_item` base com `valor_unitario`=`funcao.valor_base_quinzena`, `quantidade`=1.
- **Snapshot**: ao **Salvar**, os itens (diárias + bônus + descontos) e os totais (`total_diarias`, `total_bonus`, `total_desconto`, `total_liquido = diarias + bonus − desconto`) são persistidos. Recalcular só está disponível em **rascunho** e só repuxa as diárias (bônus/descontos preservados). Card **fechado/pago não recalcula nem edita itens** (só após reabrir).

---

## 6. Status e auditoria

- Transições válidas:
  - `rascunho → fechado`
  - `fechado → pago` (registra `pago_em` = hoje, `pago_por` = usuário logado)
  - `fechado → rascunho` (reabrir) — **exige motivo**
  - `pago → fechado` (reabrir) — **exige motivo**
- Cada transição grava em `card_status_historico` (de/para, usuario_id, motivo, created_at) **e** dispara a auditoria global (via update do card). Transições inválidas são rejeitadas.
- Edição de itens só em `rascunho`.

---

## 7. PDF (mPDF)

- `composer require mpdf/mpdf`.
- Action `card/pdf?id=` renderiza a view `card/_pdf` (HTML: logo Citrus em `web/img/logo.png`, cabeçalho com nome/CPF-CNPJ/função/quinzena, tabela de diárias por obra, bônus, descontos, total líquido) e gera o `.pdf` via mPDF → download inline. Gateado pela permissão `card` (ação `view`).

---

## 8. Acesso / navegação

- `PermissaoBehavior` na tela `card` (já semeada para administrativo no Spec 1: `index,view,create,update,delete,export`). Mapear ações extras (`fechar`→`update`/`view`, `recalcular`→`update`, `mudar-status`→`update`, `pdf`→`view`) via `acaoMap`. Não-admin → 403.
- **`main.php` NÃO é alterado** por este spec (tem trabalho não-commitado do desenvolvedor). O item "Cards" do menu já aponta para `card/index`; se faltar, o trecho é entregue no plano.

---

## 9. Arquivos

- Models: `models/Card.php`, `models/CardItem.php`, `models/CardStatusHistorico.php`, `models/Quinzena.php`.
- Migrations: `create_card`, `create_card_item`, `create_card_status_historico`.
- Controller: `controllers/CardController.php` (substitui stub): `actionIndex`, `actionFechar`, `actionRecalcular`, `actionAdicionarItem`/`actionRemoverItem` (bônus/desconto) ou via salvar em lote, `actionSalvar`, `actionMudarStatus`, `actionPdf`.
- Views: `views/card/index.php` (+ `_search.php`), `views/card/fechar.php` (+ parciais `_diarias.php`, `_itens.php`, `_historico.php`), `views/card/_pdf.php`.
- Assets/CSS: estilos do card + collapse em `web/css/app.css`; JS leve para o collapse das diárias (`web/js/card.js`).
- Dependência: `mpdf/mpdf` no composer.

---

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

- **Unit:** `Quinzena::intervalo` (Q1/Q2; meses 30/31; fevereiro 28/29); montagem das diárias por obra a partir do efetivo (contagem correta, agrupamento); valor por tipo de função (diária × nº vs valor_base_quinzena); `total_liquido = diarias + bonus − desconto`; snapshot não muda após fechar (alterar efetivo depois não muda o card fechado); unicidade `(profissional, quinzena)`; transições de status (válidas aplicam + gravam histórico; inválidas rejeitadas; reabrir exige motivo); pago grava `pago_em`/`pago_por`.
- **Functional:** admin monta um card a partir do efetivo, salva rascunho, fecha, paga, reabre (com motivo); a edição de itens é bloqueada em card fechado; o index exige período e ordena pendentes primeiro; não-admin recebe 403; `pdf` retorna content-type `application/pdf`.

---

## 11. Considerações

- O collapse de diárias lê o efetivo ao vivo (informativo); o valor pago é sempre o snapshot do card.
- Ajuste de meia diária: editar a `quantidade` do item de diária (ex.: 0.5) enquanto rascunho, ou lançar um desconto manual — ambos suportados pelo modelo.
- Próximo (Spec 5): Dashboard (KPIs, gráficos, painel de alertas) e Relatórios dinâmicos.
