# Aprovação WMS inline no painel — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Levar a UX de revisão/aprovação de movimentos WMS (switches, snapshot scoping, "Aprovar missão") para dentro do `designacao_painel/{ID}`, substituindo a tabela de itens para missões WMS e aposentando o modal da lista.

**Architecture:** Reúso máximo — toda a lógica de UI já existe no modal `revisar_movimentos_wms.php` e todo o backend (`RevisarMovimentosLaboratorio`, `aprovarOperacaoWms`) está pronto e testado (13 testes). O trabalho é **realocar**: extrair a seção para um partial parametrizado por estado (read-only vs editável), incluí-lo no painel para missões WMS, redirecionar o botão da lista para o painel, e remover o modal.

**Tech Stack:** PHP (Galaxia V2 / views "burras"), Metronic + JS vanilla + `fetch`, i18n `show()`. Backend não muda.

**Spec:** `PRD/operacao-wms/15-aprovacao-painel-inline-spec.md`

## Global Constraints

- **Sem mudança de backend:** `RevisarMovimentosLaboratorio` (listar/alternar/alternarTodos) e `MissoesAprovacao::aprovarOperacaoWms` (commit + CAS + snapshot scoping) permanecem idênticos.
- **Commits:** NÃO commitar sem o usuário pedir (regra do projeto). Os passos "Commit" existem para granularidade; execute só com aprovação — caso contrário só `git add` e siga.
- **i18n:** toda string visível via `show('texto PT literal')`.
- **WMS = `tipo_missao_id` 14** (`TipoMissao::ALOCACAO_WMS->id()`). `TipoMissao` já está importado na view do painel (linha 40).
- **Não-regressão:** a suíte WMS (13 testes / 159 asserts) deve seguir verde — o backend não muda.
- **`editavel` = só em `aguardando_aprovacao`** (D2). Nos demais estados a seção é read-only.

---

### Task 1: Partial reutilizável `forms/wms_movimentos_secao.php`

**Files:**
- Create: `componentes/Missoes/Logistica/sinais/Designacoes/forms/wms_movimentos_secao.php`

**Interfaces:**
- Consumes (do escopo do include): `$missao` (objeto com `->id` e `->status`), `$galaxia` (GalaxiaRoute, p/ `acaoElemento`).
- Consome backend (inalterado): `GET .../RevisarMovimentosLaboratorio/listar`, `POST .../alternar`, `POST .../alternarTodos`; ação `MissoesAprovacao/aprovarOperacaoWms` (com `snapshot_max_id`).
- Produces: seção HTML+JS de movimentos; `editavel = ($missao->status === 'aguardando_aprovacao')` controla switches acionáveis + botão "Aprovar missão".

- [ ] **Step 1: Criar o partial**

Create `componentes/Missoes/Logistica/sinais/Designacoes/forms/wms_movimentos_secao.php`:

```php
<?php
/**
 * Seção de movimentos WMS para o painel da missão (designacao_painel).
 * Reúso da UX de revisão/aprovação por movimento, agora inline no painel.
 *
 * Lê do escopo do include: $missao (->id, ->status) e $galaxia (GalaxiaRoute).
 *
 * editavel = true só em aguardando_aprovacao: switches acionáveis + "Aprovar missão".
 * Nos demais estados a seção é read-only (acompanhamento / histórico).
 */
$missaoId = (int)($missao->id ?? 0);
$statusMissao = (string)($missao->status ?? '');
$editavel = ($statusMissao === 'aguardando_aprovacao');
?>
<div class="wms-mov-secao" data-missao="<?= $missaoId ?>">
    <div style="display:flex; align-items:center; gap:.5rem; margin-bottom:.75rem;">
        <h3 style="margin:0; font-size:1rem; color:var(--admin-text, #fff);"><i class="bi bi-box-seam"></i> <?= show('Movimentos') ?></h3>
        <?php if ($editavel): ?>
        <button type="button" class="btn btn-sm btn-light" id="rmw-all-on"><?= show('Confirmar todos') ?></button>
        <button type="button" class="btn btn-sm btn-light" id="rmw-all-off"><?= show('Descartar todos') ?></button>
        <?php endif; ?>
        <span class="ms-auto text-muted" id="rmw-count"></span>
    </div>

    <div class="table-responsive" style="max-height:60vh; overflow:auto;">
        <table class="table table-sm align-middle">
            <thead>
                <tr>
                    <th><?= show('Produto') ?></th>
                    <th><?= show('Endereço') ?></th>
                    <th class="text-end"><?= show('Qtd') ?></th>
                    <th><?= show('Quando') ?></th>
                    <th class="text-center"><?= show('Aprovar') ?></th>
                </tr>
            </thead>
            <tbody id="rmw-tbody">
                <tr><td colspan="5" class="text-center text-muted"><?= show('Carregando...') ?></td></tr>
            </tbody>
        </table>
    </div>

    <?php if ($editavel): ?>
    <div style="display:flex; justify-content:flex-end; gap:.5rem; margin-top:1rem;">
        <button type="button" class="btn btn-success" id="rmw-enviar"
                <?= $galaxia->setConstelacao('Logistica')->setEstrela('MissaoLogistica')->setSatelite('MissoesAprovacao')->setOpcao('aprovarOperacaoWms')->acaoElemento('missao_id=' . $missaoId . '&snapshot_max_id={snapshot_max_id}'); ?>>
            <?= show('Aprovar missão') ?> <span id="rmw-enviar-n"></span>
        </button>
    </div>
    <?php endif; ?>
</div>

<script>
(function () {
    var missaoId = <?= $missaoId ?>;
    var editavel = <?= $editavel ? 'true' : 'false' ?>;
    var base = '/labs/apis/Logistica/AlocacaoWms/RevisarMovimentosLaboratorio/';
    var tbody = document.getElementById('rmw-tbody');
    var btnEnviar = document.getElementById('rmw-enviar'); // null quando não editável

    function esc(v) {
        return String(v === null || v === undefined ? '' : v).replace(/[&<>"']/g, function (c) {
            return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
        });
    }

    var pendingWrites = 0;
    var carregado = false;
    function atualizarBotaoEnviar() {
        if (!btnEnviar) { return; }
        if (!carregado) { btnEnviar.disabled = true; btnEnviar.title = '<?= show('Carregando…') ?>'; }
        else if (pendingWrites > 0) { btnEnviar.disabled = true; btnEnviar.title = '<?= show('Salvando…') ?>'; }
        else { btnEnviar.disabled = false; btnEnviar.title = ''; }
    }
    atualizarBotaoEnviar();

    function atualizarContagem() {
        var ligados = tbody.querySelectorAll('input.rmw-switch:checked').length;
        var total = tbody.querySelectorAll('input.rmw-switch').length;
        document.getElementById('rmw-count').textContent = ligados + '/' + total;
        var n = document.getElementById('rmw-enviar-n');
        if (n) { n.textContent = '(' + ligados + ')'; }
    }

    function atualizarSnapshotMaxId(movs) {
        if (!btnEnviar) { return; }
        if (!btnEnviar.dataset.acaoTemplate) {
            btnEnviar.dataset.acaoTemplate = btnEnviar.getAttribute('data-acao-dados') || '';
        }
        var maxId = 0;
        movs.forEach(function (m) { if (+m.id > maxId) { maxId = +m.id; } });
        var tpl = btnEnviar.dataset.acaoTemplate;
        if (tpl) {
            btnEnviar.setAttribute('data-acao-dados', tpl.replace(/\{snapshot_max_id\}|%7Bsnapshot_max_id%7D/g, maxId));
        }
    }

    function endereco(m) {
        if (m.origem && m.destino) { return m.origem + ' → ' + m.destino; }
        return m.localizacao || '—';
    }

    function alternar(sw, ligado) {
        pendingWrites++;
        atualizarBotaoEnviar();
        fetch(base + 'alternar', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ movimento_id: sw.dataset.id, ligado: ligado ? 1 : 0 })
        })
        .then(function (r) { if (!r.ok) { throw new Error('HTTP ' + r.status); } return r.json(); })
        .then(function (json) { if (!json || json.ok === false) { carregar(); } })
        .catch(function () { carregar(); })
        .finally(function () { pendingWrites--; atualizarBotaoEnviar(); });
    }

    function render(movs) {
        atualizarSnapshotMaxId(movs);
        if (!movs.length) {
            tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted"><?= show('Nenhum movimento.') ?></td></tr>';
            atualizarContagem();
            return;
        }
        tbody.innerHTML = movs.map(function (m) {
            var prod = esc(m.produto_nome) + (m.produto_codigo ? ' (' + esc(m.produto_codigo) + ')' : '');
            var qtd = (m.quantidade === null || m.quantidade === undefined) ? '—' : esc(m.quantidade);
            var dis = editavel ? '' : ' disabled';
            return '<tr data-id="' + esc(m.id) + '">' +
                '<td>' + prod + '</td>' +
                '<td>' + esc(endereco(m)) + '</td>' +
                '<td class="text-end">' + qtd + '</td>' +
                '<td class="text-muted small">' + esc(m.confirmada_em || '') + '</td>' +
                '<td class="text-center"><div class="form-check form-switch d-inline-block">' +
                '<input class="form-check-input rmw-switch" type="checkbox" data-id="' + esc(m.id) + '"' + (m.ligado ? ' checked' : '') + dis + '></div></td>' +
                '</tr>';
        }).join('');
        if (editavel) {
            tbody.querySelectorAll('input.rmw-switch').forEach(function (sw) {
                sw.addEventListener('change', function () {
                    atualizarContagem();
                    alternar(this, this.checked);
                });
            });
        }
        atualizarContagem();
    }

    function carregar() {
        fetch(base + 'listar?missao_id=' + missaoId)
            .then(function (r) { if (!r.ok) { throw new Error('HTTP ' + r.status); } return r.json(); })
            .then(function (d) {
                render((d && d.movimentos) || []);
                carregado = true;
                atualizarBotaoEnviar();
            })
            .catch(function () {
                tbody.innerHTML = '<tr><td colspan="5" class="text-center text-danger"><?= show('Erro ao carregar.') ?></td></tr>';
            });
    }

    if (editavel) {
        var setAll = function (estado) {
            tbody.querySelectorAll('input.rmw-switch').forEach(function (sw) { sw.checked = estado; });
            atualizarContagem();
            pendingWrites++;
            atualizarBotaoEnviar();
            fetch(base + 'alternarTodos', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ missao_id: missaoId, ligado: estado ? 1 : 0 })
            })
            .then(function (r) { if (!r.ok) { throw new Error('HTTP ' + r.status); } return r.json(); })
            .then(function (json) { if (!json || json.ok === false) { carregar(); } })
            .catch(function () { carregar(); })
            .finally(function () { pendingWrites--; atualizarBotaoEnviar(); });
        };
        document.getElementById('rmw-all-on').addEventListener('click', function () { setAll(true); });
        document.getElementById('rmw-all-off').addEventListener('click', function () { setAll(false); });
    }

    // Exposto p/ o backend recarregar a lista quando bloqueia o "Aprovar missão" (snapshot desatualizado).
    window.rmwRecarregar = function () { carregar(); };

    carregar();
})();
</script>
```

- [ ] **Step 2: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Designacoes/forms/wms_movimentos_secao.php`
Expected: `No syntax errors detected`.

- [ ] **Step 3: Commit** (só com aprovação)

```bash
git add componentes/Missoes/Logistica/sinais/Designacoes/forms/wms_movimentos_secao.php
git commit -m "feat(operacao-wms): partial de movimentos WMS (read-only vs editável) p/ o painel"
```

---

### Task 2: Painel renderiza a seção p/ missões WMS

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Designacoes/html/designacao_painel.php` (computar `$isWms`; esconder barra de progresso; trocar tabela de itens pelo partial)

**Interfaces:**
- Consumes: partial `forms/wms_movimentos_secao.php` (Task 1); `$missao`, `$isGestorLogistico`, `TipoMissao` (já no escopo da view).
- Produces: para missão WMS, o painel mostra a seção de movimentos no lugar da tabela de itens; a barra de progresso de itens fica oculta.

- [ ] **Step 1: Computar `$isWms` no topo da view**

Em `designacao_painel.php`, logo após o bloco do `$tituloMissao` (~linha 45-49), adicionar:

```php
$isWms = (int)($missao->tipo_missao_id ?? 0) === TipoMissao::ALOCACAO_WMS->id();
```

- [ ] **Step 2: Esconder a barra de progresso (itens) p/ WMS**

Localizar (linha ~448):

```php
    <?php if ($isGestorLogistico): ?>
        <div class="progress-card">
```

Trocar a condição para:

```php
    <?php if ($isGestorLogistico && !$isWms): ?>
        <div class="progress-card">
```

- [ ] **Step 3: Trocar a tabela de itens pela seção de movimentos (WMS)**

Localizar a abertura da tabela (linha 681):

```php
    <div class="missions-table">
```

Inserir ANTES dela o branch WMS e abrir o `else`:

```php
    <?php if ($isWms): ?>
        <?php include __DIR__ . '/../forms/wms_movimentos_secao.php'; ?>
    <?php else: ?>
    <div class="missions-table">
```

Depois, localizar o fechamento da tabela (linhas 1298-1305) — o bloco do "Fim da lista de itens":

```php
            <!-- Footer da lista de itens -->
            <div style="padding: 1.5rem; text-align: center; border-top: 1px solid var(--admin-border); background: rgba(0,0,0,0.2);">
                <div style="color: var(--admin-muted); font-size: 0.75rem; font-weight: 600;">
                    <i class="bi bi-check-circle"></i> Fim da lista de itens
                </div>
            </div>
        </div>
    </div>
```

Acrescentar `<?php endif; ?>` logo após (fechando o `if ($isWms)`):

```php
            <!-- Footer da lista de itens -->
            <div style="padding: 1.5rem; text-align: center; border-top: 1px solid var(--admin-border); background: rgba(0,0,0,0.2);">
                <div style="color: var(--admin-muted); font-size: 0.75rem; font-weight: 600;">
                    <i class="bi bi-check-circle"></i> Fim da lista de itens
                </div>
            </div>
        </div>
    </div>
    <?php endif; ?>
```

- [ ] **Step 4: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Designacoes/html/designacao_painel.php`
Expected: `No syntax errors detected`.

- [ ] **Step 5: Smoke no browser (manual)**

1. Abrir `designacao_painel/{ID}` de uma missão WMS em `aguardando_aprovacao`: deve mostrar a seção "Movimentos" com switches editáveis + "Aprovar missão"; sem barra de progresso de itens.
2. Abrir o painel de uma missão WMS em `em_andamento`: seção read-only (switches desabilitados), sem "Aprovar missão".
3. Abrir o painel de uma missão **não-WMS** (carregamento/separação): tabela de itens e barra de progresso intactas.

- [ ] **Step 6: Commit** (só com aprovação)

```bash
git add componentes/Missoes/Logistica/sinais/Designacoes/html/designacao_painel.php
git commit -m "feat(operacao-wms): painel renderiza seção de movimentos p/ missões WMS"
```

---

### Task 3: Botão da lista navega pro painel

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Designacoes/html/designacoes_lista.php:1409-1415` (branch ALOCACAO_WMS)

**Interfaces:**
- Produces: o botão "Revisar movimentos" passa a navegar para `designacao_painel/{ID}` (a rota `missao/logistica/designacoes/designacao_painel/{id}` já é usada na lista, var `$rotaPainel`).

- [ ] **Step 1: Trocar o botão de modal por navegação**

Localizar (linhas 1409-1415):

```php
                                            <?php elseif ($tipoId === TipoMissao::ALOCACAO_WMS->id()): ?>
                                                <!-- ALOCACAO_WMS: abre modal de revisão de movimentos WMS -->
                                                <button class="btn-icon success"
                                                    <?= $galaxia->abrirModal('revisarMovimentosWms', ['sendData' => ['missao_id' => $m->id], 'modalSize' => 'modal-xl modal-dialog-centered'])['html']; ?>
                                                    title="<?= show('Revisar movimentos') ?>">
                                                    <i class="bi bi-list-check"></i>
                                                </button>
```

Trocar por (link pro painel — a aprovação agora vive lá):

```php
                                            <?php elseif ($tipoId === TipoMissao::ALOCACAO_WMS->id()): ?>
                                                <!-- ALOCACAO_WMS: revisão/aprovação agora vive no painel -->
                                                <a class="btn-icon success" href="<?= systemUrl ?>/missao/logistica/designacoes/designacao_painel/<?= $m->id ?>"
                                                    title="<?= show('Revisar movimentos') ?>">
                                                    <i class="bi bi-list-check"></i>
                                                </a>
```

> **Nota de execução:** confirme a constante de base de URL usada na lista (`systemUrl`, `SYSTEM_URL_PATH` ou similar) olhando outros links/href no mesmo arquivo; use a mesma. Se a lista abre o painel por JS (não `<a href>`), replique esse mecanismo em vez do link direto.

- [ ] **Step 2: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Designacoes/html/designacoes_lista.php`
Expected: `No syntax errors detected`.

- [ ] **Step 3: Smoke no browser (manual)**

Na lista de designações, clicar no ícone de movimentos (`list-check`) de uma missão WMS em `aguardando_aprovacao` → abre o `designacao_painel/{ID}` com a seção de movimentos.

- [ ] **Step 4: Commit** (só com aprovação)

```bash
git add componentes/Missoes/Logistica/sinais/Designacoes/html/designacoes_lista.php
git commit -m "feat(operacao-wms): botão WMS da lista navega pro painel (revisão inline)"
```

---

### Task 4: Aposentar o modal de revisão (cleanup)

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Designacoes/Designacoes.php` (remover rota `revisarMovimentosWms` ~234-235 e o método ~1602-1614)
- Delete: `componentes/Missoes/Logistica/sinais/Designacoes/forms/revisar_movimentos_wms.php`

**Interfaces:**
- Produces: o modal deixa de existir; a única superfície de revisão/aprovação WMS é o painel.

- [ ] **Step 1: Confirmar que não há mais referências ao modal**

Run: `grep -rn "revisarMovimentosWms\|revisar_movimentos_wms" componentes/ | grep -v "Designacoes.php:\|forms/revisar_movimentos_wms.php:"`
Expected: nenhuma linha (após a Task 3, o único uso na lista já foi removido).

- [ ] **Step 2: Remover a rota no dispatcher**

Em `Designacoes.php`, remover o branch (linhas ~234-235):

```php
        } elseif ($rota === 'revisarMovimentosWms') {
            return $this->revisarMovimentosWms($data);
```

(Mantendo a cadeia `if/elseif` íntegra — junte os `elseif` vizinhos corretamente.)

- [ ] **Step 3: Remover o método**

Em `Designacoes.php`, remover o método inteiro `public function revisarMovimentosWms(?array $data = [])` (~linhas 1602-1614).

- [ ] **Step 4: Apagar o arquivo do modal**

Run: `git rm componentes/Missoes/Logistica/sinais/Designacoes/forms/revisar_movimentos_wms.php`
(ou `rm` se ainda não estava versionado.)

- [ ] **Step 5: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Designacoes/Designacoes.php`
Expected: `No syntax errors detected`.

- [ ] **Step 6: Commit** (só com aprovação)

```bash
git add componentes/Missoes/Logistica/sinais/Designacoes/Designacoes.php
git commit -m "chore(operacao-wms): aposentar modal de revisão WMS (substituído pela seção do painel)"
```

---

### Task 5: Regressão + verificação final

**Files:** nenhum (só execução).

- [ ] **Step 1: Regressão do backend WMS (deve seguir verde — backend não mudou)**

Run, conferindo 0 FAIL em cada:
```bash
php componentes/Missoes/Logistica/sinais/AlocacaoWms/Testes/Laboratorios/RevisarMovimentosLaboratorioTddTest.php
php componentes/Missoes/Logistica/sinais/AlocacaoWms/Testes/Laboratorios/AprovarOperacaoWmsTddTest.php
php componentes/Missoes/Logistica/sinais/AlocacaoWms/Testes/Laboratorios/SnapshotScopingTddTest.php
php componentes/Missoes/Logistica/sinais/AlocacaoWms/Testes/Laboratorios/AprovacaoRobustezTddTest.php
php componentes/Missoes/Logistica/sinais/AlocacaoWms/Testes/Laboratorios/ForcarConclusaoWmsGuardTddTest.php
php componentes/Missoes/Logistica/sinais/AlocacaoWms/Testes/Sondas/WmsMovimentoSondaRevisaoTddTest.php
```
Expected: todos `TODOS OS TESTES PASSARAM`.

- [ ] **Step 2: Lint das views/controller tocados**

Run:
```bash
php -l componentes/Missoes/Logistica/sinais/Designacoes/forms/wms_movimentos_secao.php
php -l componentes/Missoes/Logistica/sinais/Designacoes/html/designacao_painel.php
php -l componentes/Missoes/Logistica/sinais/Designacoes/html/designacoes_lista.php
php -l componentes/Missoes/Logistica/sinais/Designacoes/Designacoes.php
```
Expected: `No syntax errors detected` em todos.

- [ ] **Step 3: Smoke E2E no browser (manual)**

Fluxo completo: missão WMS com "Requer Aprovação" → operador registra movimentos → finaliza (vai p/ `aguardando_aprovacao`) → na lista, clicar movimentos abre o painel → seção mostra switches (default on) → desligar 1 → "Aprovar missão" conclui a missão e os aprovados sobem no cron; os desligados viram `cancelada`. Conferir também o caso de snapshot desatualizado (chegou movimento novo → bloqueia e recarrega).

- [ ] **Step 4: i18n harvest**

Conferir que as novas strings (`Movimentos`, `Aprovar missão`, `Nenhum movimento.`, `Carregando…`) foram capturadas em `linguagens/{pt-br,en,es}.json` (auto-harvest) e revisar tradução no browser.

## Self-Review (preenchido pelo autor do plano)

- **Cobertura da spec:** §3 (comportamento por estado)→T1 (editavel) + T2 (render no painel); §4 (arquitetura/reúso)→T1+T2+T4; §5 (read-only vs editável)→T1; §6 (não-regressão)→T5; D5 (botão da lista)→T3; D6 (esconder progresso)→T2 Step 2. ✅ sem lacunas.
- **Placeholders:** nenhum — o partial vem completo na T1; demais tasks têm anchors exatos. "Notas de execução" (constante de URL na T3) são verificações no código real, não lacunas.
- **Consistência:** o partial reusa exatamente as rotas/ação do backend existente (`listar`/`alternar`/`alternarTodos`/`aprovarOperacaoWms` + `snapshot_max_id`), idênticas às já testadas; `rmwRecarregar` mantém o mesmo nome esperado pelo `chamarFuncao` do backend. ✅
