A lista de produtos já suporta ordenação, busca rápida e paginação — mas ainda mostra tudo misturado. Produtos arquivados aparecem junto com ativos, inativos se perdem no meio. As Tabs de Status separam a visualização: cada aba filtra a lista por um status específico, e a aba "Todos" mantém a visão completa pra quando precisar.
Agora mergulhamos no código — 6 arquivos tocados, as mudanças linha por linha.
Esta etapa toca 6 arquivos: 1 novo (banco, a coluna st_produto) e 5 editados (Bean, DAO, Form, List, Manager). A List é onde mora a maior mudança — TabStrip, 4 inner classes de aba, e a infra resetPage() que fecha o débito técnico da Paginação anterior.
ALTER TABLE departamento.produto ADD COLUMN st_produto integer;
UPDATE departamento.produto SET st_produto = 1;
Duas instruções: ALTER cria a coluna nullable; UPDATE faz o backfill marcando todos os produtos existentes como Ativo (1) pra não sumirem da aba "Ativos" no primeiro render.
package br.xt.app.departamento.produto;
import br.jasap.dao.DBInfo;
import br.jasap.util.DomainValue;
import br.jasap.util.JasapList;
import java.io.Serializable;
public class DepartamentoProdutoBean implements Serializable {
public static String TABLE = "departamento.produto";
private Integer id_produto;
private String nome_produto;
private Double vl_produto;
private Integer qtd_produto;
private String obs_produto;
private String qs_produto;
private Integer st_produto;
private Integer insert_chk;
// ... getters/setters de id/nome/vl/qtd/obs/qs (sem alteração)
public Integer getSt_produto() { return st_produto; }
public void setSt_produto(Integer st_produto) { this.st_produto = st_produto; }
// ... getter/setter de insert_chk (sem alteração)
public static class DomStatus {
public static final Integer ATIVO = 1;
public static final Integer INATIVO = 2;
public static final Integer ARQUIVADO = 3;
public static JasapList domain() {
JasapList list = new JasapList();
list.getList().add(new DomainValue(ATIVO, "Ativo"));
list.getList().add(new DomainValue(INATIVO, "Inativo"));
list.getList().add(new DomainValue(ARQUIVADO, "Arquivado"));
return list;
}
}
public static String ID_PRODUTO = "id_produto";
public static String NOME_PRODUTO = "nome_produto";
public static String VL_PRODUTO = "vl_produto";
public static String QTD_PRODUTO = "qtd_produto";
public static String OBS_PRODUTO = "obs_produto";
public static String QS_PRODUTO = "qs_produto";
public static String ST_PRODUTO = "st_produto";
public static String INSERT_CHK = "insert_chk";
}
Mudanças: campo st_produto + getter/setter, inner class DomStatus (constantes + domain()), constante ST_PRODUTO, dois imports novos (DomainValue, JasapList).
public void daoWhere(Object objWhere) throws Exception {
String where = "";
if (objWhere != null) {
DepartamentoProdutoWBean filtro = (DepartamentoProdutoWBean) objWhere;
if (filtro.getSt_produto() != null)
where = SQL.and(where, SQL.equals(SQL.column(DepartamentoProdutoBean.ST_PRODUTO), SQL.value(filtro.getSt_produto())));
if (filtro.getQs_produto() != null && !JasapFunctions.equals(filtro.getQs_produto(), "")) {
String[] cols = { SQL.column(DepartamentoProdutoBean.QS_PRODUTO) };
where = where + qsWhere(filtro.getQs_produto(), cols);
}
filtro.setWhere(where);
}
}
Mudança: 2 linhas antes do filtro de qs_produto. Se filtro.getSt_produto() é null, não adiciona cláusula — é o que permite a aba "Todos" mostrar tudo sem filtro de status.
import br.jasap.gui.form.Radio;
public class DepartamentoProdutoForm extends DepartamentoProdutoAction {
// ... execute(), ShowInsert, Insert, ShowUpdate, Update, Cancelar, render(), window(), br() (sem alteração)
public String form() throws Exception {
Form frm = ui().form();
frm.addHidden(DepartamentoProdutoBean.ID_PRODUTO, proBean().getId_produto());
frm.line().add(nome_produto(), "150");
frm.line().add(vl_produto(), "150");
frm.line().add(qtd_produto(), "150");
frm.line().add(st_produto(), "150");
frm.line().add(obs_produto(), "150");
frm.line().add(insert_chk(), "150");
return frm.getTable().toHtml();
}
// ... nome_produto(), vl_produto(), qtd_produto() (sem alteração)
protected Radio st_produto = null;
public Radio st_produto() throws Exception {
if (st_produto == null) {
Integer valorAtual = proBean().getSt_produto() != null ? proBean().getSt_produto() : DepartamentoProdutoBean.DomStatus.ATIVO;
st_produto = new Radio(getManager(), DepartamentoProdutoBean.ST_PRODUTO)
.setLabel("Status")
.setValue(DepartamentoProdutoBean.DomStatus.domain())
.setRequired(true);
st_produto.getValue().setSelected(valorAtual);
}
return st_produto;
}
// ... obs_produto(), insert_chk(), FORM (sem alteração)
}
Mudanças: import Radio, método st_produto(), e plugar na form() entre qtd_produto e obs_produto. Default ATIVO no insert (quando proBean().getSt_produto() ainda é null).
package br.xt.app.departamento.produto;
import br.jasap.core.Effect;
import br.jasap.effect.Response;
import br.jasap.gui.JasapPage;
import br.jasap.gui.ListColumn;
import br.jasap.gui.ListLine;
import br.jasap.gui.ListView;
import br.jasap.gui.Bar;
import br.jasap.gui.Button;
import br.jasap.gui.TabStrip;
import br.jasap.gui.Table;
import br.jasap.gui.Toast;
import br.jasap.gui.form.Text;
// ... demais imports (sem alteração)
public class DepartamentoProdutoList extends DepartamentoProdutoAction {
@Override
public Effect execute() throws Exception {
if (getSession().getObject(TBL) == null) {
getSession().addStr(TBL, TBL_ATIVOS);
getFiltro().setSt_produto(DepartamentoProdutoBean.DomStatus.ATIVO);
}
render();
return new Response();
}
// ... render() (sem alteração)
public Table window() throws Exception {
Table w = new Table(getManager()).setSize("100%", "100%");
w.rowC("1%", JasapPage.DIV_TABSTRIP, tbs());
w.rowC("99%", JasapPage.DIV_WSPACE, lView());
w.rowC("1%", null, ui().line());
w.rowC("1%", null, qs_produto());
w.rowC("1%", JasapPage.DIV_BOTTOM, br());
w.rowC("1%", null, ui().line());
return w;
}
private TabStrip tbs = null;
public TabStrip tbs() throws Exception {
if (tbs == null) {
tbs = ui().tabStrip().setSelectedKey(getSession().getString(TBL, getInput()));
tbs.createTab(TBL_ATIVOS, "Ativos") .setOnclick(link(TabAtivos.class).ajax());
tbs.createTab(TBL_INATIVOS, "Inativos") .setOnclick(link(TabInativos.class).ajax());
tbs.createTab(TBL_ARQUIVADOS, "Arquivados").setOnclick(link(TabArquivados.class).ajax());
tbs.createTab(TBL_TODOS, "Todos") .setOnclick(link(TabTodos.class).ajax());
}
return tbs;
}
public void resetPage() {
getSession().addInt(RESET_PAGE, 1);
}
// ... qs_produto(), br() (sem alteração)
private ListView lv = null;
public ListView lView() throws Exception {
if (lv == null) {
lv = ui().lView();
// ... setSortAct, setPageAction, setPage, setOrderBy, setSort, ajax() (sem alteração)
lv.setPageSize(50);
if (getSession().isSet(RESET_PAGE)) {
lv.setPage(1);
getSession().remove(RESET_PAGE);
}
lv.setFiltro(getFiltro());
getFactory().departamento().proModel().daoList(lv.getData());
// ... colunas e while (sem alteração)
}
return lv;
}
// ... getFiltro() (sem alteração)
public static class Sort extends DepartamentoProdutoList { /* sem alteração */ }
public static class QuickSearch extends DepartamentoProdutoList {
@Override
public Effect execute() throws Exception {
resetPage();
getFiltro().setQs_produto(getInput().getString(DepartamentoProdutoBean.QS_PRODUTO));
update(lView().getDIV_BODY(), lView().getBody());
update(lView().getDIV_NAVIGATE(), lView().getNavForm());
eval(Js.setFocusTo(DepartamentoProdutoBean.QS_PRODUTO));
return new Response();
}
}
public static class TabAtivos extends DepartamentoProdutoList {
@Override
public Effect execute() throws Exception {
resetPage();
getSession().addStr(TBL, TBL_ATIVOS);
getFiltro().setSt_produto(DepartamentoProdutoBean.DomStatus.ATIVO);
render();
return new Response();
}
}
public static class TabInativos extends DepartamentoProdutoList {
@Override
public Effect execute() throws Exception {
resetPage();
getSession().addStr(TBL, TBL_INATIVOS);
getFiltro().setSt_produto(DepartamentoProdutoBean.DomStatus.INATIVO);
render();
return new Response();
}
}
public static class TabArquivados extends DepartamentoProdutoList {
@Override
public Effect execute() throws Exception {
resetPage();
getSession().addStr(TBL, TBL_ARQUIVADOS);
getFiltro().setSt_produto(DepartamentoProdutoBean.DomStatus.ARQUIVADO);
render();
return new Response();
}
}
public static class TabTodos extends DepartamentoProdutoList {
@Override
public Effect execute() throws Exception {
resetPage();
getSession().addStr(TBL, TBL_TODOS);
getFiltro().setSt_produto(null);
render();
return new Response();
}
}
public static class DeleteFromList extends DepartamentoProdutoList { /* sem alteração */ }
public static final String LIST = ROOT.concat("__LIST/");
public static final String FILTRO = LIST.concat("__FILTRO");
public static final String RESET_PAGE = LIST.concat("__RESET_PAGE");
public static final String TBL = LIST.concat("__TBL");
public static final String TBL_ATIVOS = "__ATIVOS";
public static final String TBL_INATIVOS = "__INATIVOS";
public static final String TBL_ARQUIVADOS = "__ARQUIVADOS";
public static final String TBL_TODOS = "__TODOS";
public static final String CONFIRM_LIST = LIST.concat("__CONFIRM_LIST");
}
Mudanças principais: import TabStrip, default ATIVO no execute(), row do tabstrip no window(), método tbs(), método resetPage(), bloco consumer no lView(), chamada de resetPage() no QuickSearch, 4 inner classes TabAtivos/TabInativos/TabArquivados/TabTodos, e 6 constantes novas (TBL, TBL_ATIVOS/INATIVOS/ARQUIVADOS/TODOS, RESET_PAGE).
regAction(DepartamentoProdutoList.class);
regAction(DepartamentoProdutoList.Sort.class);
regAction(DepartamentoProdutoList.QuickSearch.class);
regAction(DepartamentoProdutoList.TabAtivos.class);
regAction(DepartamentoProdutoList.TabInativos.class);
regAction(DepartamentoProdutoList.TabArquivados.class);
regAction(DepartamentoProdutoList.TabTodos.class);
regAction(DepartamentoProdutoList.DeleteFromList.class);
Mudança: 4 regAction novos — um por Tab inner class. Cada action que vai receber requisição precisa estar registrada no Manager; sem isso, clicar na aba retorna erro "action não encontrada".
Tabs de Status parece uma feature só, mas são 3 mecanismos independentes que se encontram na UI:
| Pilar | Onde mora | Papel |
|---|---|---|
| Filtro por status | Banco + Bean + DAO | Nova coluna st_produto, campo no Bean, cláusula WHERE st_produto = ? no DAO quando o filtro tá setado |
| TabStrip + 4 Tab actions | List + Manager | TabStrip renderiza as abas, cada Tab*.execute() grava o status na sessão e re-renderiza |
| resetPage() | List (infra) | Padrão session flag + consumer — trocar de aba volta pra página 1 (senão o usuário na página 3 dos Ativos vai pro fantasma da página 3 dos Inativos) |
Pode existir 1 sem os outros 2: filtro sozinho funcionaria com URL params, TabStrip sozinho sem filtro seria só decoração, resetPage sozinho é infra sem uso. Juntos, viram a feature.
st_produto + backfill
ALTER TABLE departamento.produto ADD COLUMN st_produto integer;
UPDATE departamento.produto SET st_produto = 1;
Duas instruções, uma única vez (já rodou — 75 produtos marcados como Ativo). A coluna é integer nullable — nullable porque registros antigos poderiam existir sem status definido. O UPDATE backfill é que evita isso: marca todos com st_produto = 1 (ATIVO) pra não sumirem da primeira aba renderizada.
NOT NULL?Três motivos práticos: (1) ALTER TABLE ... ADD COLUMN ... NOT NULL sem DEFAULT falha se a tabela não tá vazia — precisa de 2 instruções separadas; (2) em produção, colunas nullable são mais tolerantes a estado legado; (3) a aba "Todos" filtra com st_produto IS NOT NULL ou sem filtro — nullable permite testes de regressão mais fáceis.
1 no backfill e não outro valor?1 é o código numérico de DomStatus.ATIVO no Bean. O domínio é fechado (1=Ativo, 2=Inativo, 3=Arquivado). Usar 1 aqui e ATIVO no Java garante que os dois lados convergem — se o enum mudar, o backfill roda de novo alinhado.
st_produto + DomStatus
private Integer st_produto;
public Integer getSt_produto() { return st_produto; }
public void setSt_produto(Integer st_produto) { this.st_produto = st_produto; }
public static class DomStatus {
public static final Integer ATIVO = 1;
public static final Integer INATIVO = 2;
public static final Integer ARQUIVADO = 3;
public static JasapList domain() {
JasapList list = new JasapList();
list.getList().add(new DomainValue(ATIVO, "Ativo"));
list.getList().add(new DomainValue(INATIVO, "Inativo"));
list.getList().add(new DomainValue(ARQUIVADO, "Arquivado"));
return list;
}
}
public static String ST_PRODUTO = "st_produto";
O Bean segue a convenção: campo privado + getter/setter padrão + constante pública com o nome da coluna. Nada de @DBInfo no st_produto — não é PK nem serial, o framework trata como coluna comum.
DomStatus como inner classDomínio fechado (3 valores possíveis) encapsulado no Bean dono do campo. Padrão replicado de LabProdutoBean.DomStatus. Três razões pra ficar aqui e não em br.xt.util.DomStatus global:
st_produto está mexendo no Bean de ProdutoDomStatus diferentes (ex: LabPessoaBean.DomStatus tem 5 valores) — não faz sentido globalizarDepartamentoProdutoBean.DomStatus.ATIVO lê como "o status ativo de produto" — inequívocodomain() factoryMétodo estático que monta a JasapList de domínio para componentes visuais (Radio, Combo). Cada invocação cria uma lista nova — não compartilha estado com outras invocações. É o idioma Jasap: o componente visual recebe a lista e gerencia qual DomainValue está selecionado.
br.jasap.util.DomainValue — par chave/rótulo que popula a JasapListbr.jasap.util.JasapList — lista genérica usada por componentes visuais do frameworkRadio st_produto()
protected Radio st_produto = null;
public Radio st_produto() throws Exception {
if (st_produto == null) {
Integer valorAtual = proBean().getSt_produto() != null ? proBean().getSt_produto() : DepartamentoProdutoBean.DomStatus.ATIVO;
st_produto = new Radio(getManager(), DepartamentoProdutoBean.ST_PRODUTO)
.setLabel("Status")
.setValue(DepartamentoProdutoBean.DomStatus.domain())
.setRequired(true);
st_produto.getValue().setSelected(valorAtual);
}
return st_produto;
}
Sem esse método, o usuário não teria como mudar o status de um produto — ele ficaria preso no valor que tá no banco. As tabs viram read-only depois do primeiro insert.
3 opções é o limite psicológico pra Radio: todas visíveis sem scroll, escolha imediata. Acima de 5, Combo economiza espaço. A convenção do XT é Radio pra domínios ≤ 5 valores — confere com LabProdutoForm.st_pro().
Integer valorAtual = proBean().getSt_produto() != null ? proBean().getSt_produto() : DepartamentoProdutoBean.DomStatus.ATIVO;
Lógica ternária: se o Bean já tem status (modo edição), usa o valor existente; se é null (modo insert, primeiro render), usa ATIVO. Isso garante que o Radio sempre tem uma opção pré-selecionada — sem isso, o usuário precisaria clicar mesmo pra "manter" o valor.
setRequired(true)Valida no submit — se por algum motivo o Radio chegar no servidor sem seleção (ex: usuário abriu o DevTools e removeu o atributo checked), o framework barra. Belt + suspenders com o default.
form()Depois de qtd_produto, antes de obs_produto. Ordem: campos básicos → quantitativos → status → texto livre. Status é "coluna de controle" — não é dado do produto, é classificação.
st_produto no daoWhere
if (filtro.getSt_produto() != null)
where = SQL.and(where, SQL.equals(SQL.column(DepartamentoProdutoBean.ST_PRODUTO), SQL.value(filtro.getSt_produto())));
Uma condicional, uma linha de where =. Posicionada antes do filtro de qs_produto — ordem arbitrária pro SQL, mas convencional: status primeiro, busca textual depois.
SQL.and(where, clausula) — concatena com AND só se where não tá vazia (senão retorna a cláusula sem AND pendurado)SQL.equals(coluna, valor) — monta "a.coluna = valor" com aspas corretasSQL.column(nome) — prefixa com alias da tabela principal ("a.st_produto")SQL.value(obj) — serializa o valor pro SQL com escape (string com aspas, número sem, null vira NULL)Sem esses helpers, seria where += " AND a.st_produto = " + filtro.getSt_produto() — funcional, mas vulnerável a SQL injection e sem escape de aspas. O idioma Jasap é sempre via SQL.*.
null = sem filtroO if (filtro.getSt_produto() != null) é o que permite a aba "Todos" funcionar: quando TabTodos.execute() roda getFiltro().setSt_produto(null), o daoWhere pula o bloco — SQL sai sem cláusula de status, retorna tudo.
TabStrip + 4 Tab actions
private TabStrip tbs = null;
public TabStrip tbs() throws Exception {
if (tbs == null) {
tbs = ui().tabStrip().setSelectedKey(getSession().getString(TBL, getInput()));
tbs.createTab(TBL_ATIVOS, "Ativos") .setOnclick(link(TabAtivos.class).ajax());
tbs.createTab(TBL_INATIVOS, "Inativos") .setOnclick(link(TabInativos.class).ajax());
tbs.createTab(TBL_ARQUIVADOS, "Arquivados").setOnclick(link(TabArquivados.class).ajax());
tbs.createTab(TBL_TODOS, "Todos") .setOnclick(link(TabTodos.class).ajax());
}
return tbs;
}
setSelectedKey + constante TBLTBL é a chave de sessão que guarda qual aba tá selecionada no momento. setSelectedKey lê esse valor e o TabStrip marca a aba correspondente como ativa no HTML (classe CSS, estilo visual).
setSelectedKey: todas as abas aparecem inativas — feature quebrada visualmentecreateTab(key, label)Primeiro argumento é a chave interna (TBL_ATIVOS etc) — precisa bater com o que setSelectedKey espera. Segundo é o rótulo visível. O setOnclick(link(TabX.class).ajax()) amarra o clique à action correspondente.
public static class TabAtivos extends DepartamentoProdutoList {
@Override
public Effect execute() throws Exception {
resetPage();
getSession().addStr(TBL, TBL_ATIVOS);
getFiltro().setSt_produto(DepartamentoProdutoBean.DomStatus.ATIVO);
render();
return new Response();
}
}
Cada Tab faz a mesma coreografia, diferindo só no valor gravado:
resetPage() — volta pra página 1 (Mudança 6 explica o mecanismo)getSession().addStr(TBL, TBL_*) — grava qual aba tá ativa (pro setSelectedKey do próximo render ler)getFiltro().setSt_produto(...) — seta o filtro que o daoWhere vai aplicarrender() — re-renderiza a lista completaIdioma Jasap: cada regAction(Class) no Manager precisa ser uma classe nominal. Não existe regAction(Class, params). O framework usa o nome da classe como chave de roteamento no XML — 1 classe por endpoint.
Alternativa rejeitada: uma única TabChange que lê o alvo de um parâmetro TAB_KEY. Funcionaria, mas quebra o registro nominal — seria uma action com comportamentos diferentes dependendo do input, mais difícil de navegar no Manager e mais fácil de bugar.
Custo real: cada classe tem ~8 linhas. 4 × 8 = 32 linhas repetitivas. Para o benefício de clareza e idioma, é um custo aceitável.
TabTodos passa nullgetFiltro().setSt_produto(null) — é o que desliga a cláusula de status no daoWhere (Mudança 4). A aba "Todos" não é um status especial; é a ausência de filtro de status.
execute() da List base@Override
public Effect execute() throws Exception {
if (getSession().getObject(TBL) == null) {
getSession().addStr(TBL, TBL_ATIVOS);
getFiltro().setSt_produto(DepartamentoProdutoBean.DomStatus.ATIVO);
}
render();
return new Response();
}
Primeira visita à lista (sessão ainda sem TBL): seta ATIVO como default. Nas visitas subsequentes, a sessão já tem valor — o if não entra e respeita a escolha prévia do usuário.
resetPage() (fecha o débito da Paginação)
Quando a Paginação foi adicionada, ela resolveu o reset pra busca rápida via .putInteger(PAGE, 0) no onkeyup — uma solução pontual. Agora, com 4 Tab actions que também precisam resetar página, esse padrão não escala: cada uma teria que passar PAGE=0 no link do TabStrip. A solução genérica é um helper.
// Método helper — qualquer action pode chamar
public void resetPage() {
getSession().addInt(RESET_PAGE, 1);
}
// Constante pra chave de sessão
public static final String RESET_PAGE = LIST.concat("__RESET_PAGE");
// Bloco consumer dentro de lView(), logo depois de setPageSize
if (getSession().isSet(RESET_PAGE)) {
lv.setPage(1);
getSession().remove(RESET_PAGE);
}
O padrão é flag de sessão + consumer:
resetPage() grava a flag — não muda nada imediatamentelView(), o consumer detecta a flag, força lv.setPage(1), remove a flagresetPage() antes do próximo render vai aterrissar na página 1QuickSearch.execute() — substituiu o .putInteger(PAGE, 0) que morava no onkeyupTabAtivos.execute(), TabInativos.execute(), TabArquivados.execute(), TabTodos.execute()Todas as 5 actions seguem o mesmo idioma: resetPage() como primeira linha do execute(), depois o resto.
Na Paginação só o QuickSearch precisava resetar — 1 caller. Adicionar 3 peças de infra (método + constante + bloco) pra atender 1 caller é over-engineering. O .putInteger(PAGE, 0) no onkeyup resolvia com 1 linha.
Agora, em Tabs, são 5 callers. A matemática muda: ou o .putInteger(PAGE, 0) se espalha por 4 links do TabStrip + 1 no onkeyup, ou centraliza em 1 helper. O helper ganha.
Na referência do Lab (LabProdutoList), resetPage() existe e é chamado no QuickSearch, mas o bloco consumer não foi adicionado no lView(). Resultado: a flag RESET_PAGE é gravada na sessão e ninguém lê — código morto. Funciona "por acidente" porque o onkeyup também faz .putInteger(PAGE, 0).
Essa etapa (Tabs de Status) foi a primeira a implementar o padrão corretamente — helper + consumer + callers em sintonia. É o padrão de referência daqui pra frente.
Quando vier a feature de filtros (range de valor, período de data, etc), cada "Aplicar Filtros" / "Limpar Filtros" vai chamar resetPage(). Mesmo mecanismo, mais callers. O investimento em infra aqui paga-se lá.
regAction
regAction(DepartamentoProdutoList.TabAtivos.class);
regAction(DepartamentoProdutoList.TabInativos.class);
regAction(DepartamentoProdutoList.TabArquivados.class);
regAction(DepartamentoProdutoList.TabTodos.class);
Cada action precisa ser registrada no Manager. regAction grava um XML interno que o framework consulta em cada requisição pra saber qual classe instanciar. Sem o registro, o link(TabAtivos.class).ajax() renderiza uma URL que, ao ser clicada, resulta em "action não encontrada".
Posicionamento: depois de QuickSearch e antes de DeleteFromList — a ordem não afeta funcionamento, mas segue o fluxo visual da classe (helpers de ação → tabs → delete).
Três coisas aparecem naturalmente quando a gente pensa em "tabs de status", mas ficam fora desta etapa — viram pendência ou são rejeitadas por escopo:
| Item | Faz parte? | Por quê |
|---|---|---|
| Contador de registros por aba (ex: "Ativos (3)") | Não | Exige query de contagem extra por aba em cada render — custo não justifica na etapa mínima. Pode entrar depois como feature de conveniência |
| Filtros avançados (data, range de valor) | Não | Etapa própria. Reusará o resetPage() que nasceu aqui |
Método DomStatus.label(Integer) | Não (removido) | Gerado inicialmente junto com domain(), mas nenhum lugar do Departamento chama — deletado na revisão de escopo |
| Cascade de status (status do pai propaga pros filhos) | Não | Específico de entidades com hierarquia — Produto não tem pai/filho de status |
Sobre o DomStatus.label(): foi implementado na primeira versão seguindo o espelho do LabProdutoBean. A revisão de simplificação (rodada logo depois da implementação Java) identificou que nenhum caller existia no Departamento — dead code. Deletado. O label() só entra quando algum componente visual precisar traduzir Integer → String, o que não acontece nessa etapa.
| Arquivo | Tipo | Edição |
|---|---|---|
| departamento.produto (banco) | novo | Coluna st_produto integer + backfill UPDATE marcando todos como Ativo |
| DepartamentoProdutoBean | editar | Campo st_produto + getter/setter, inner class DomStatus (ATIVO/INATIVO/ARQUIVADO + domain()), constante ST_PRODUTO, 2 imports |
| DepartamentoProdutoDAO | editar | 1 bloco if com 1 linha de where = no daoWhere |
| DepartamentoProdutoForm | editar | Import Radio, método st_produto(), plugado na form() |
| DepartamentoProdutoList | editar | Import TabStrip, default ATIVO no execute(), row tabstrip no window(), método tbs(), método resetPage(), consumer em lView(), resetPage() no QuickSearch, 4 inner classes Tab*, 6 constantes novas |
| DepartamentoManager | editar | 4 regAction novos |
1 arquivo novo (banco), 5 editados. A maior parte da complexidade vive na List. O padrão resetPage() estabelecido aqui vai reaparecer em Filtros avançados — foi investimento, não custo.