Conceitos, Padrões e Convenções do Projeto
Este documento serve como referência para entender a arquitetura, padrões e convenções do projeto RegistreTudo.
O projeto RegistreTudo é composto por múltiplos repositórios e bibliotecas. É importante entender quais são editáveis e quais são apenas para uso.
Estes são os únicos projetos que devem ser modificados durante o desenvolvimento:
@registretudo-api (Backend - Java)
C:/dotum/projetos/registretudo/registretudo-api@registretudo-web (Frontend - Next.js)
C:/dotum/projetos/dot/apps/registretudo-webEstes projetos são bibliotecas internas que podem ser usadas, mas não devem ser editados:
@dot (Monorepo Root)
C:/dotum/projetos/dot@dot/core e @dot/ui@dot/ui (Biblioteca de Componentes)
C:/dotum/projetos/dot/packages/ui@dot/core (Framework Core)
C:/dotum/projetos/dot/packages/core@dotumcore (Framework Backend)
C:/dotum/projetos/registretudo/dotumcore@registretudo-api@dotumcommons (Biblioteca de Conexões com APIs Externas)
C:/dotum/projetos/registretudo/dotumcommons@registretudo-api e outros projetos@dotumfiscal (Biblioteca Fiscal)
C:/dotum/projetos/registretudo/dotumfiscal@registretudo-api@registretudo-api)src/main/java/dotumcloud/
├── api/ # Controllers REST (endpoints da API)
│ └── rt001commons/ # Endpoints de autocomplete (padrão novo)
├── bo/ # Business Objects (lógica de negócio)
├── model/
│ ├── bean/ # Entidades do Framework (tipo Bean próprio)
│ ├── dd/ # Default Data (tabelas com dados padrões)
│ └── json/ # DTOs JSON
├── screen###/ # Módulos de tela (screen005 = unidade, screen100 = venda, etc.)
│ └── [modulo]/
│ ├── dto/ # Data Transfer Objects (usam Record)
│ └── *.java # Actions/Controllers
└── service/ # Classes para selects utilizados com frequência
AbstractAction@UrlPath e @Menu@Endpoint expõem endpoints REST[Modulo][Numero][Acao]
Unidade055Ocorrencia, Compra015CancelardoDataTableOcorrencia, doDataAlterarUnidadedoProcessAlterarUnidade, doProcessLiberarAteRecord do JavaDoProcess[Modulo][Acao]Request
DoProcessAlterarUnidadeRequestDoData[Modulo][Acao]Response
DoDataAlterarUnidadeResponseBean diretamente)Exemplo de DTO usando Record:
// screen100/venda/dto/DoProcessCancelarVendaRequest.java
public record DoProcessCancelarVendaRequest(String motivo) {
}
// api/rt001commons/dto/DoDataCommonUnidadeResponse.java
public record DoDataCommonUnidadeResponse(Integer id, String descricao) {
public static DoDataCommonUnidadeResponse convert(UnidadeBean uniBean) {
return new DoDataCommonUnidadeResponse(uniBean.getId(), uniBean.getDescricao());
}
}
@UrlPath("/unidade/${id}/ocorrencia")
@Menu(label="Ocorrência", icon=CssIconEnum.COMMENT)
public class Unidade055Ocorrencia extends AbstractAction {
@Endpoint
public List<Bean> doDataTableOcorrencia(Integer id) throws Exception {
// Retorna dados para tabela
}
@Endpoint(method = MethodHttp.POST)
public void doProcess(Integer id) throws Exception {
// Processa ação
}
}
@UrlPath("/unidade/${id}/ocorrencia")
@Menu(label="Ocorrência", icon=CssIconEnum.COMMENT)
public class Unidade055Ocorrencia extends AbstractAction {
@Endpoint
public List<DoDataTableOcorrenciaResponse> doDataTableOcorrencia(@PathParam Integer id) throws Exception {
// Retorna dados para tabela
}
@Endpoint(method = MethodHttp.POST)
public void doProcess(@PathParam Integer id, @RequestBody DoProcessRequest body) throws Exception {
// Processa ação
}
}
@PathParam e @RequestBody para novos endpoints.
Bean é próprio do Framework DotumCoremodel/bean/@TableDB(acronym="...") do Beanmodel/dd/[Acrônimo]DD (exemplo: SitDD = Situação DD)@TableDB(acronym="...") do Bean correspondenteSitDD contém constantes de situação (PENDENTE, CONCLUIDO, CANCELADO, etc.)A classe UserContext é do framework DotumCore e resolve o problema "Who User" - identifica quem está fazendo a requisição. Ela contém todas as informações do usuário autenticado e é fundamental para segurança, auditoria e filtragem de dados.
// Em classes que estendem AbstractAction
UserContext userContext = super.getUserContext();
O UserContext contém informações sobre:
UserContext userContext = super.getUserContext();
// Obter ID da unidade do usuário logado
Integer unidadeId = userContext.getUnidade().getId();
// Obter ID da empresa do usuário logado
Integer empresaId = userContext.getEmpresa().getId();
// Obter ID da pessoa do usuário logado
Integer pessoaId = userContext.getPessoa().getId();
// Obter informações do usuário
UsuarioBean user = userContext.getUser();
String nomeUsuario = user.getNome();
String emailUsuario = user.getEmail();
// Obter ID do desenvolvedor (quando aplicável)
Integer devId = userContext.getUserDeveloperId();
@Endpoint
public List<DoDataTableOcorrenciaResponse> doDataTableOcorrencia(@PathParam Integer id) throws Exception {
// ✅ CORRETO: UserContext ANTES de TransactionDB
UserContext userContext = super.getUserContext();
TransactionDB trans = super.getTransaction();
// ✅ CORRETO: Vincular tratamento de dados a variáveis
Integer empresaId = userContext.getEmpresa().getId();
Integer unidadeId = userContext.getUnidade().getId();
WhereDB where = new WhereDB();
// ✅ CORRETO: Ordem de filtros - empresa primeiro, depois unidade, depois outros
where.add("empresa.id", Condition.EQUALS, empresaId);
where.add("unidade.id", Condition.EQUALS, unidadeId);
where.add("pessoa.id", Condition.EQUALS, id);
OrderDB order = new OrderDB();
order.add("data", Orientation.DESC);
List<OcorrenciaBean> beanList = trans.select(OcorrenciaBean.class, where, order);
return DoDataTableOcorrenciaResponse.convert(beanList);
}
@Endpoint
public List<DoDataCommonUnidadeResponse> doDataCommonUnidade(
@QueryParam String term,
@QueryParam Integer defaultValue
) throws Exception {
// ✅ CORRETO: UserContext ANTES de TransactionDB
UserContext userContext = super.getUserContext();
TransactionDB trans = super.getTransaction();
// ✅ CORRETO: Vincular tratamento de dados a variáveis
Integer empId = userContext.getEmpresa().getId();
WhereDB where = new WhereDB();
// ✅ CORRETO: Ordem de filtros - empresa primeiro
where.add("empresa.id", Condition.EQUALS, empId);
// ✅ CORRETO: Para null pointer, pode usar != null diretamente
if (term != null) {
where.add("descricao", Condition.LIKE, term);
}
List<UnidadeBean> uniList = trans.select()
.from("unidade")
.where(where)
.limit(FWConstants.JSON_LIMIT)
.list();
return DoDataCommonUnidadeResponse.convert(uniList);
}
@Endpoint(method = MethodHttp.POST)
public void doProcess(@PathParam Integer id, @RequestBody DoProcessRequest body) throws Exception {
UserContext userContext = super.getUserContext();
TransactionDB trans = super.getTransaction();
// ✅ CORRETO: Chamar BO para fazer o insert
OcorrenciaBO ocorrenciaBO = new OcorrenciaBO(userContext, trans);
ocorrenciaBO.criarOcorrencia(id, body.descricao());
}
public static void liberarUnidade(
UserContext userContext,
TransactionDB trans,
UnidadeBean uniBean,
Date liberadoAte
) throws Exception {
// ✅ CORRETO: Vincular tratamento de dados a variáveis
String primeiroNome = StringUtils.getFirstWord(userContext.getUser().getNome());
String nomeUsuario = StringUtils.toUpperCaseFirstLetter(primeiroNome);
String dataFormatada = FormatUtils.formatDate(liberadoAte);
String descricaoUnidade = uniBean.getDescricao();
// ✅ CORRETO: Usar variáveis já tratadas na concatenação
String descricao = "Liberado o sistema da unidade \"" + descricaoUnidade
+ "\" até " + dataFormatada
+ " pelo usuario " + nomeUsuario;
OcorrenciaBO ocoBO = new OcorrenciaBO(userContext, trans);
ocoBO.gerarOcorenciaPessoa(
uniBean.getId(),
uniBean.getPessoaCliente().getId(),
OcotDD.SISTEMA,
trans.getDataAtual(),
trans.getHoraAtual(),
"Liberação Sistema",
descricao);
}
// Business Objects geralmente recebem UserContext no construtor
public class UnidadeBO extends AbstractBO {
public UnidadeBO(UserContext userContext, TransactionDB trans) {
super(userContext, trans);
}
public void informarDadosCadastro(String nome, String email) throws Exception {
// UserContext já está disponível através do super
// Pode ser usado para validações e regras de negócio
}
}
userContext.getUnidade().getId() ao invés de receber unidadeId como parâmetro// ❌ ERRADO: Aceitar unidadeId como parâmetro sem validar
@Endpoint
public void doProcess(@PathParam Integer unidadeId) throws Exception {
// Usuário poderia passar ID de outra unidade!
UnidadeBean uniBean = trans.selectById(UnidadeBean.class, unidadeId);
// ...
}
// ✅ CORRETO: Usar UserContext para garantir segurança
@Endpoint
public void doProcess(@PathParam Integer id) throws Exception {
UserContext userContext = super.getUserContext();
// Sempre usar a unidade do usuário logado
Integer empresaId = userContext.getEmpresa().getId();
WhereDB where = new WhereDB();
where.add("empresa.id", Condition.EQUALS, empresaId); // Garantir que é da empresa do usuário
where.add("id", Condition.EQUALS, id);
UnidadeBean uniBean = trans.select().from("unidade").where(where).one();
// ...
}
A classe TransactionDB é do framework DotumCore e representa uma transação no banco de dados. É obtida através de super.getTransaction() em classes que estendem AbstractAction.
TransactionDB trans = super.getTransaction();
// ⚠️ NÃO RECOMENDADO: Este padrão é mais pesado e traz objetos completos com todos os joins aninhados
// Busca um registro único pelo ID
PessoaBean pesBean = trans.selectById(PessoaBean.class, id);
UnidadeBean uniBean = trans.selectById(UnidadeBean.class, unidadeId);
// ⚠️ NÃO RECOMENDADO: Este padrão é mais pesado e traz objetos completos com todos os joins aninhados
WhereDB where = new WhereDB();
where.add("pessoa.id", Condition.EQUALS, id);
where.add("unidade.id", Condition.EQUALS, unidadeId);
OrderDB order = new OrderDB();
order.add("data", Orientation.DESC);
order.add("hora", Orientation.DESC);
List<OcorrenciaBean> beanList = trans.select(OcorrenciaBean.class, where, order);
// Select com where e limit
List<UnidadeBean> uniList = trans.select()
.from("unidade")
.where(where)
.limit(FWConstants.JSON_LIMIT)
.list();
// Select único (one)
PessoaBean pesBean = trans.select()
.from("pessoa")
.where(where)
.one();
// Select com addWhere (encadeado)
CodigoPromocionalBean codpBean = trans.select()
.from("codigoPromocional")
.addWhere("codigo", Condition.EQUALS, codIndicacao)
.one();
// Select com join() para trazer objetos aninhados completos
// Por padrão, o builder pattern só traz IDs dos objetos aninhados
// Use join() quando precisar do objeto completo
List<OcorrenciaBean> ocoList = trans.select()
.from("ocorrencia")
.join("pessoa") // Traz o objeto PessoaBean completo
.join("unidade") // Traz o objeto UnidadeBean completo
.where(where)
.list();
join() apenas quando realmente precisar do objeto completo// ⚠️ IMPORTANTE: Use StringBuilder ao invés de String para queries complexas (mais rápido)
// Mantenha a indentação para facilitar copiar o select para debug
StringBuilder sb = new StringBuilder();
sb.append(" select uni.uni_id, ");
sb.append(" uni.emp_id, ");
sb.append(" uni.uni_liberadoate, ");
sb.append(" uni.uni_diavencimento, ");
sb.append(" uni.uni_descricao, ");
sb.append(" pes.pes_id, ");
sb.append(" pes.pes_nomerazao ");
sb.append(" from pessoa pes ");
sb.append(" join unidade uni on (uni.uni_id = pes.uni_id) ");
sb.append(" where pes.pes_principal = " + FWConstants.SIM + " ");
sb.append(" and uni.uni_ativo = " + FWConstants.ATIVO + " ");
sb.append(" and uni.uni_diavencimento is not null ");
// Mapeamento ORM para campos customizados
// ⚠️ IMPORTANTE: Mantenha a indentação alinhada (observe o padrão nos services)
// As vírgulas ficam no final da linha anterior, não indentadas
ORM orm = new ORM();
orm.add(Integer.class, "empresa.id", "emp_id");
orm.add(Integer.class, "id", "uni_id");
orm.add(Integer.class, "pessoaCliente.id", "pes_id");
orm.add(Date.class, "liberadoAte", "uni_liberadoate");
orm.add(String.class, "descricao", "uni_descricao");
orm.add(Integer.class, "diaVencimento", "uni_diavencimento");
orm.add(String.class, "pessoaCliente.nomeRazao", "pes_nomerazao");
WhereDB where = new WhereDB();
where.add("liberadoAte", Condition.GREATOREQUALS, dataLimite);
where.add("empresa.id", Condition.EQUALS, empresaId);
List<UnidadeBean> uniList = trans.select(
UnidadeBean.class,
orm,
sb.toString(), // StringBuilder convertido para String
where
);
StringBuilder ao invés de String - é mais rápido para concatenação// Inserir um Bean
trans.insert(pesBean);
trans.insert(uniBean);
// Inserir uma Lista
trans.insert(pesList);
// Atualizar um Bean (após modificar seus atributos)
uniBean.setDescricao("Novo nome");
trans.update(uniBean);
// Deletar um Bean
trans.delete(pesBean);
// Obter data atual do banco
Date dataAtual = trans.getDataAtual();
// Obter hora atual do banco
String horaAtual = trans.getHoraAtual();
Exemplo 1: Select simples com where e Constants - CORRETO
@Endpoint
public List<DoDataTableOcorrenciaResponse> doDataTableOcorrencia(@PathParam Integer id) throws Exception {
// ✅ CORRETO: UserContext ANTES de TransactionDB
UserContext userContext = super.getUserContext();
TransactionDB trans = super.getTransaction();
Integer empresaId = userContext.getEmpresa().getId();
Integer unidadeId = userContext.getUnidade().getId();
WhereDB where = new WhereDB();
// ✅ CORRETO: Ordem de filtros - empresa primeiro, depois unidade, depois outros
where.add("empresa.id", Condition.EQUALS, empresaId);
where.add("unidade.id", Condition.EQUALS, unidadeId);
where.add("pessoa.id", Condition.EQUALS, id);
// Usando DD para filtrar situações
where.add("situacao.id", Condition.NOTEQUALS, SitDD.CANCELADO);
OrderDB order = new OrderDB();
order.add("data", Orientation.DESC);
order.add("hora", Orientation.DESC);
List<OcorrenciaBean> beanList = trans.select(OcorrenciaBean.class, where, order);
return DoDataTableOcorrenciaResponse.convert(beanList);
}
Exemplo 2: Select com builder pattern e Constants (padrão novo) - CORRETO
value != null, NÃO precisa ser redundante - pode ser apenas value != null== true ou == false SOMENTE quando já tem um boolean tratado (variável boolean) ou em chamada de função que retorna booleanBoolean (wrapper) - sempre use o primitivo boolean para evitar risco de null pointer@Endpoint
public List<DoDataCommonUnidadeResponse> doDataCommonUnidade(
@QueryParam String term,
@QueryParam Integer defaultValue
) throws Exception {
// ✅ CORRETO: UserContext ANTES de TransactionDB
UserContext userContext = super.getUserContext();
TransactionDB trans = super.getTransaction();
Integer empId = userContext.getEmpresa().getId();
WhereDB where = new WhereDB();
// ✅ CORRETO: Ordem de filtros - empresa primeiro
where.add("empresa.id", Condition.EQUALS, empId);
// Usando FWConstants para filtrar apenas ativos
where.add("ativo", Condition.EQUALS, FWConstants.ATIVO);
// ✅ CORRETO: Para null pointer, pode usar != null diretamente
if (term != null) {
where.add("descricao", Condition.LIKE, term);
}
if (defaultValue != null) {
where.add("id", Condition.EQUALS, defaultValue);
}
List<UnidadeBean> uniList = trans.select()
.from("unidade")
.where(where)
.limit(FWConstants.JSON_LIMIT) // Usando FWConstants para limite
.list();
return DoDataCommonUnidadeResponse.convert(uniList);
}
Exemplo 3: Select único (one) com Constants
WhereDB where = new WhereDB();
where.add("id", Condition.EQUALS, id);
// Usando FWConstants para garantir que está ativo
where.add("ativo", Condition.EQUALS, FWConstants.ATIVO);
PessoaBean pesBean = trans.select()
.from("pessoa")
.where(where)
.one();
Exemplo 4: Insert e Update com Constants - CORRETO (em BOs, não em Controllers)
// ✅ CORRETO: Em um Business Object (BO)
public class OcorrenciaBO extends AbstractBO {
public void criarOcorrencia(Integer uniId, String descricao) throws Exception {
UserContext userContext = super.getUserContext();
TransactionDB trans = super.getTransaction();
// ✅ CORRETO: Vincular tratamento de dados a variáveis
Date dataAtual = trans.getDataAtual();
Time horaAtual = trans.getHoraAtual();
// Criar novo registro usando Constants
OcorrenciaBean ocoBean = new OcorrenciaBean();
ocoBean.getUnidade().setId(uniId);
ocoBean.setDescricao(descricao);
ocoBean.setData(dataAtual);
ocoBean.setHora(horaAtual);
// Usando DD para situação
ocoBean.getSituacao().setId(SitDD.PENDENTE);
// Usando DD para tipo de ocorrência
ocoBean.getOcorrenciaTipo().setId(OcotDD.SISTEMA);
trans.insert(ocoBean);
}
public void atualizarOcorrencia(Integer id, String novaDescricao) throws Exception {
TransactionDB trans = super.getTransaction();
// Atualizar registro existente
OcorrenciaBean ocoBean = trans.selectById(OcorrenciaBean.class, id);
ocoBean.setDescricao(novaDescricao);
// Usando DD para marcar como concluído
ocoBean.getSituacao().setId(SitDD.CONCLUIDO);
trans.update(ocoBean);
}
}
WhereDB. Alguns exemplos estão usando operações incorretas. Sempre verifique a documentação ou exemplos corretos no código.
value != null diretamente (não precisa ser redundante)== true ou == false SOMENTE quando já tem um boolean tratado (variável boolean) ou em chamada de função que retorna booleanBoolean (wrapper) - sempre use o primitivo boolean para evitar risco de null pointerWhereDB where = new WhereDB();
// ✅ CORRETO: Ordem de filtros - empresa primeiro, depois unidade, depois outros
where.add("empresa.id", Condition.EQUALS, empresaId);
where.add("unidade.id", Condition.EQUALS, unidadeId);
where.add("id", Condition.EQUALS, id);
// Igualdade
where.add("id", Condition.EQUALS, id);
// Diferente
where.add("situacao.id", Condition.NOTEQUALS, SitDD.CANCELADO);
// LIKE (busca parcial)
where.add("descricao", Condition.LIKE, term);
// Exemplo de condicionais corretas:
// ✅ CORRETO: Para null pointer, pode usar != null diretamente
if (term != null) {
where.add("descricao", Condition.LIKE, term);
}
// ✅ CORRETO: Quando já tem um boolean tratado, use == true ou == false
boolean isAtivo = uniBean.getAtivo() == FWConstants.SIM;
if (isAtivo == true) {
where.add("ativo", Condition.EQUALS, FWConstants.ATIVO);
}
// ✅ CORRETO: Em chamada de função que retorna boolean, use == true ou == false
if (uniBean.isLiberado() == true) {
where.add("liberado", Condition.EQUALS, FWConstants.SIM);
}
// ❌ ERRADO: Usar classe Boolean (wrapper)
Boolean isAtivo = uniBean.getAtivo(); // NUNCA faça isso
// ❌ ERRADO: Usar negação em boolean tratado
if (!isAtivo) { // NUNCA faça isso, use isAtivo == false
// Maior ou igual / Menor ou igual
where.add("data", Condition.GREATOREQUALS, dataInicio);
where.add("valor", Condition.LESSOREQUALS, valorMaximo);
// IN (lista de valores)
where.add("id", Condition.IN, listaIds);
// IS NULL / IS NOT NULL
where.add("dataCancelamento", Condition.ISNULL, null);
where.add("ativo", Condition.ISNOTNULL, null);
SEMPRE use Constants ao invés de valores literais quando elas existirem. Isso melhora a manutenibilidade e evita erros.
WhereDB where = new WhereDB();
// ✅ CORRETO: Usar FWConstants
where.add("principal", Condition.EQUALS, FWConstants.SIM);
where.add("ativo", Condition.EQUALS, FWConstants.ATIVO);
where.add("pagouMensalidade", Condition.EQUALS, FWConstants.NAO);
// ❌ ERRADO: Usar valores literais
where.add("principal", Condition.EQUALS, 1); // Evitar!
where.add("ativo", Condition.EQUALS, 1); // Evitar!
FWConstants comuns:
FWConstants.SIM / FWConstants.NAO - Valores booleanos (Sim/Não)FWConstants.ATIVO / FWConstants.INATIVO - Status ativo/inativoFWConstants.SELECT_LIMIT - Limite padrão para selectsFWConstants.JSON_LIMIT - Limite padrão para JSONWhereDB where = new WhereDB();
// ✅ CORRETO: Usar ParametroConstants
where.add("unidade.id", Condition.EQUALS, ParametroConstants.UNIDADE_DOTUM);
where.add("pessoa.id", Condition.EQUALS, ParametroConstants.PESSOA_PRINCIPAL_DOTUM);
// Usar em cálculos de datas
Date dataLimite = DateUtils.addDay(dataAtual, (ParametroConstants.CARENCIA_USO * -1));
where.add("liberadoAte", Condition.GREATOREQUALS, dataLimite);
// ❌ ERRADO: Usar valores literais
where.add("unidade.id", Condition.EQUALS, 1); // Evitar!
ParametroConstants comuns:
ParametroConstants.UNIDADE_DOTUM - ID da unidade DotumParametroConstants.PESSOA_PRINCIPAL_DOTUM - ID da pessoa principal DotumParametroConstants.USUARIO_PRINCIPAL_DOTUM - ID do usuário principal DotumParametroConstants.DIAS_GRATIS - Dias grátis para novos cadastrosParametroConstants.CARENCIA_USO - Carência de uso do sistemaParametroConstants.DIA_LIBERADO_CARTAO - Dia de liberação para cartãoWhereDB where = new WhereDB();
// ✅ CORRETO: Usar DDs para situações e tipos
where.add("situacao.id", Condition.EQUALS, SitDD.ATIVO);
where.add("situacao.id", Condition.EQUALS, SitDD.CONCLUIDO);
where.add("situacao.id", Condition.NOTEQUALS, SitDD.CANCELADO);
where.add("situacao.id", Condition.EQUALS, SitDD.PENDENTE);
// Para tipos de ocorrência
where.add("ocorrenciaTipo.id", Condition.EQUALS, OcotDD.SISTEMA);
where.add("ocorrenciaTipo.id", Condition.EQUALS, OcotDD.FEEDBACK_DOTUM);
// Para planos de sistema
PlanoSistemaBean psisBean = trans.selectById(PlanoSistemaBean.class, PsiDD.BRONZE);
// ❌ ERRADO: Usar valores literais
where.add("situacao.id", Condition.EQUALS, 90); // Evitar!
where.add("situacao.id", Condition.EQUALS, 91); // Evitar!
DDs comuns:
SitDD.ATIVO / SitDD.INATIVO - Situações ativo/inativoSitDD.PENDENTE / SitDD.CONCLUIDO / SitDD.CANCELADO - Situações de processosSitDD.ORCAMENTO - Situação de orçamentoOcotDD.SISTEMA / OcotDD.FEEDBACK_DOTUM - Tipos de ocorrênciaPsiDD.BRONZE / PsiDD.SILVER / PsiDD.GOLD - Planos de sistemaWhereDB where = new WhereDB();
// Usando FWConstants
where.add("principal", Condition.EQUALS, FWConstants.SIM);
where.add("ativo", Condition.EQUALS, FWConstants.ATIVO);
// Usando DDs
where.add("situacao.id", Condition.EQUALS, SitDD.CONCLUIDO);
// Usando ParametroConstants
where.add("unidade.id", Condition.EQUALS, ParametroConstants.UNIDADE_DOTUM);
// Combinando com outras condições
where.add("data", Condition.GREATOREQUALS, DateUtils.addDay(
trans.getDataAtual(),
(ParametroConstants.CARENCIA_USO * -1)
));
model/dd/) e Constants (FWConstants, ParametroConstants) para ver quais valores estão disponíveisOrderDB order = new OrderDB();
// Ordenação ascendente
order.add("nome", Orientation.ASC);
// Ordenação descendente
order.add("data", Orientation.DESC);
order.add("hora", Orientation.DESC); // Múltiplas ordenações
super.getTransaction() para obter a transação em classes que estendem AbstractActiontrans.select().from().where()) para novos códigostrans.select(Class, where, order)) - é mais pesado e traz objetos completos com joinsjoin() quando precisar do objeto completoselectById quando precisar buscar apenas por IDone() quando espera um único resultadolist() quando espera múltiplos resultadoslimit() para limitar a quantidade de resultados (importante para performance)StringBuilder ao invés de String (mais rápido)FWConstants, ParametroConstants, DDs) ao invés de valores literais nos filtros❌ Padrão Tradicional (Evitar):
// Traz objeto completo com TODOS os joins aninhados carregados
List<OcorrenciaBean> list = trans.select(OcorrenciaBean.class, where, order);
// OcorrenciaBean.pessoa está completo, OcorrenciaBean.unidade está completo, etc.
✅ Builder Pattern (Preferencial):
// Traz apenas IDs dos objetos aninhados (mais leve)
List<OcorrenciaBean> list = trans.select()
.from("ocorrencia")
.where(where)
.list();
// OcorrenciaBean.pessoa.id está preenchido, mas pessoa completa não está carregada
// Use join() apenas quando precisar do objeto completo
List<OcorrenciaBean> list = trans.select()
.from("ocorrencia")
.join("pessoa") // Agora pessoa está completo
.where(where)
.list();
O DotumCore fornece diversas classes utilitárias no pacote br.com.dotum.jedi.util que facilitam operações comuns. SEMPRE prefira usar essas classes ao invés de implementar lógica manual, pois elas são testadas, otimizadas e seguem padrões do framework.
SEMPRE busque e utilize as classes Utils do DotumCore antes de implementar lógica manual. Elas são:
Classe para operações com datas: cálculos, formatação e parsing.
Import:
import br.com.dotum.jedi.util.DateUtils;
Métodos Principais:
// Adicionar dias a uma data
Date dataFutura = DateUtils.addDay(dataAtual, 30);
Date dataPassada = DateUtils.addDay(dataAtual, -10); // Subtrair dias
// Exemplo: Calcular data de vencimento
Date primeiroVencimento = DateUtils.addDay(trans.getDataAtual(), 30);
// Exemplo: Calcular data limite com carência
Date dataLimite = DateUtils.addDay(dataAtual, (ParametroConstants.CARENCIA_USO * -1));
// Adicionar meses a uma data
Date proximoMes = DateUtils.addMonth(dataAtual, 1);
Date proximos3Meses = DateUtils.addMonth(dataAtual, 3);
// Exemplo: Calcular data de liberação
Date liberadoAte = DateUtils.addMonth(trans.getDataAtual(), qtdMesLiberado);
if (qtdMesLiberado == null) {
liberadoAte = DateUtils.addMonth(trans.getDataAtual(), 1);
}
// Calcular dias entre duas datas
Integer diasEntre = DateUtils.daysBetween(dataInicio, dataFim);
// Exemplo: Verificar quantos dias faltam para vencimento
Integer diff = DateUtils.daysBetween(dataAtual, liberadoAte);
if (diff > 0 && diff <= 3) {
// Avisar que está próximo do vencimento
}
// Formatar data para string
String dataFormatada = DateUtils.formatDate(dataAtual, "dd/MM/yyyy");
String mesAno = DateUtils.formatDate(dataAtual, "MM/yyyy");
// Exemplo: Formatar data para exibição
String mesAno = DateUtils.formatDate(liberadoTemp, "MM/yyyy");
// Converter string para Date
Date data = DateUtils.parseDate("31/12/2024");
Date dataCompleta = DateUtils.parseDate(diaVencimento + "/" + mesAno);
// Exemplo: Parse de data com dia e mês/ano
String mesAno = DateUtils.formatDate(liberadoTemp, "MM/yyyy");
Date dataCalculada = DateUtils.parseDate(diaVencimento + "/" + mesAno);
Casos de Uso Comuns:
Classe para operações com strings: validação, transformação e tratamento.
Import:
import br.com.dotum.jedi.util.StringUtils;
Métodos Principais:
// Verificar se string não é null, vazia ou apenas espaços
if (StringUtils.hasValue(senha) == true) {
// String tem valor
}
if (StringUtils.hasValue(pessoaNomeRazao) == false) {
// String está vazia ou null
}
// Exemplo: Validar senha antes de processar
if (StringUtils.hasValue(senha) == true) {
// Processar alteração de senha
}
// Retorna o primeiro valor não nulo/vazio, ou valor padrão
String observacao = StringUtils.coalesce(observacao, "");
String nome = StringUtils.coalesce(nome, "Não informado");
// Exemplo: Garantir que observação nunca seja null
observacao = StringUtils.coalesce(observacao, "");
String observacaoTemp = "Cliente: " + pessoaNomeRazao + " Obs: " + observacao;
// Transformar primeira letra em maiúscula
String nomeFormatado = StringUtils.toUpperCaseFirstLetter("joão");
// Retorna: "João"
// Exemplo: Formatar nome do usuário
String nomeUsuario = StringUtils.toUpperCaseFirstLetter(
StringUtils.getFirstWord(userContext.getUser().getNome())
);
// Obter primeira palavra de uma string
String primeiraPalavra = StringUtils.getFirstWord("João Silva Santos");
// Retorna: "João"
// Exemplo: Usar apenas primeiro nome
String nomeUsuario = StringUtils.toUpperCaseFirstLetter(
StringUtils.getFirstWord(userContext.getUser().getNome())
);
Casos de Uso Comuns:
Classe para formatação de valores: datas, números, moedas, etc.
Import:
import br.com.dotum.jedi.util.FormatUtils;
Métodos Principais:
// Formatar data para exibição
String dataFormatada = FormatUtils.formatDate(liberadoAte);
// Retorna data formatada no padrão brasileiro (dd/MM/yyyy)
// Exemplo: Incluir data formatada em mensagem
String descricao = "Liberado o sistema da unidade \""
+ pesClienteBean.getNomeRazao()
+ "\" até " + FormatUtils.formatDate(liberadoAte)
+ " pelo usuario " + nomeUsuario;
Casos de Uso Comuns:
Classe para operações matemáticas seguras: multiplicação, divisão, comparações, etc.
Import:
import br.com.dotum.jedi.util.MathUtils;
Métodos Principais:
// Multiplicação que trata nulls e valores inválidos
BigDecimal resultado = MathUtils.multiply(valorBase, qtdMesLiberado);
// Exemplo: Calcular valor total
if (qtdMesLiberado != null) {
valorTotal = MathUtils.multiply(valorBase, qtdMesLiberado);
}
// Verificar se BigDecimal é maior que zero
if (MathUtils.greatZero(valorFrete) == true) {
// Processar frete
}
// Exemplo: Validar valores antes de processar
if (MathUtils.greatZero(valorFrete) == true) {
// Adicionar frete ao total
}
Casos de Uso Comuns:
Exemplo 1: Calcular Data de Liberação com Validações
Date liberadoAte = null;
Integer diaVencimento = uniBean.getDiaVencimento();
if (diaVencimento != null) {
Date liberadoTemp = trans.getDataAtual();
String mesAno = DateUtils.formatDate(liberadoTemp, "MM/yyyy");
liberadoTemp = DateUtils.parseDate(diaVencimento + "/" + mesAno);
Integer qtdMesLiberado = psisBean.getQuantidadeMesLiberado();
if (qtdMesLiberado != null) {
liberadoAte = DateUtils.addMonth(liberadoTemp, qtdMesLiberado);
} else {
liberadoAte = DateUtils.addMonth(liberadoTemp, 1);
}
} else {
liberadoAte = DateUtils.addMonth(trans.getDataAtual(), 1);
}
Exemplo 2: Formatar Mensagem com Dados do Usuário
String nomeUsuario = StringUtils.toUpperCaseFirstLetter(
StringUtils.getFirstWord(userContext.getUser().getNome())
);
String descricao = "Liberado o sistema da unidade \""
+ pesClienteBean.getNomeRazao()
+ "\" até " + FormatUtils.formatDate(liberadoAte)
+ " pelo usuario " + nomeUsuario;
Exemplo 3: Validar e Processar Dados de Entrada
// Validar senha
if (StringUtils.hasValue(senha) == true) {
// Processar alteração de senha
pesBean.setSenha(senha);
}
// Garantir que observação não seja null
observacao = StringUtils.coalesce(observacao, "");
// Validar valor de frete
if (MathUtils.greatZero(valorFrete) == true) {
// Adicionar frete ao cálculo
}
Exemplo 4: Calcular Diferença de Dias para Alertas
Date dataAtual = trans.getDataAtual();
Date liberadoAte = uniBean.getLiberadoAte();
// Calcular dias até vencimento
Integer diff = DateUtils.daysBetween(dataAtual, liberadoAte);
if (diff > 0 && diff <= 3) {
String titulo = "Pagamento não identificado";
String mensagem = "Não identificamos o pagamento, seu plano vence em "
+ diff + " dias. Evite o bloqueio do sistema";
// Enviar notificação
ntfBO.informaDados(
pesBean.getUsuario(),
titulo,
mensagem,
null,
FwnType.WARNING,
DateUtils.addDay(dataAtual, 1)
);
}
As principais classes Utils do DotumCore são:
DateUtils - Manipulação de datas (br.com.dotum.jedi.util.DateUtils)StringUtils - Manipulação de strings (br.com.dotum.jedi.util.StringUtils)FormatUtils - Formatação de valores (br.com.dotum.jedi.util.FormatUtils)MathUtils - Operações matemáticas (br.com.dotum.jedi.util.MathUtils)br.com.dotum.jedi.util para ver todas as classes disponíveis.
service/bo/ (Business Objects)No padrão novo, os dados de autocompletes são oriundos de endpoints específicos:
api.rt001commons
UnidadeApi030, Pessoa005Api, ProdutoApi010Record)@QueryParam para receber term (busca) e defaultValue (valor padrão)components/field-helper/
unidade-field.tsx, pessoa-field.tsx, produto-field.tsxAutocompleteField do @dot/uiCommonsApi (serviço que chama api/rt001commons)Backend:
// api/rt001commons/UnidadeApi030.java
@UrlPath("/commons/unidade")
public class UnidadeApi030 extends AbstractAction {
@Endpoint
public List<DoDataCommonUnidadeResponse> doDataCommonUnidade(
@QueryParam String term,
@QueryParam Integer defaultValue
) throws Exception {
// Lógica de busca...
return DoDataCommonUnidadeResponse.convert(uniList);
}
}
// api/rt001commons/dto/DoDataCommonUnidadeResponse.java
public record DoDataCommonUnidadeResponse(Integer id, String descricao) {
public static List<DoDataCommonUnidadeResponse> convert(List<UnidadeBean> uniList) {
return uniList.stream().map(DoDataCommonUnidadeResponse::convert).toList();
}
}
Frontend:
// components/field-helper/unidade-field.tsx
export default function UnidadeField({...}: Props) {
const autoComplete = useAutocomplete({
name,
queryFn: async ({ term, defaultValue }) => {
const res = await CommonsApi.doDataCommonUnidade(term, defaultValue as number);
return res.data;
},
});
return <AutocompleteField {...autoComplete} />;
}
O backend é a fonte de verdade para exemplos de implementação. Para encontrar exemplos:
screen###/screen###/[modulo]/dto/ (todos usam Record)bo/ para lógica de negócioservice/ para selects frequentes (não para regras de negócio)api/rt001commons/@registretudo-web)src/
├── app/
│ ├── (private)/ # Rotas privadas (requerem autenticação)
│ │ └── [modulo]/ # Módulos da aplicação
│ │ ├── page.tsx # Página principal do módulo
│ │ └── components/ # Componentes específicos do módulo
│ ├── (Public)/ # Rotas públicas
│ └── layout.tsx # Layouts
├── components/ # Componentes globais
│ └── field-helper/ # Componentes de autocomplete reutilizáveis (padrão novo)
├── services/ # Serviços de API
│ ├── commons/ # Serviços para api/rt001commons (autocompletes)
│ └── [modulo]/
│ ├── index.ts # Funções de API
│ └── schemas/ # Tipos TypeScript (schemas)
├── hooks/ # React Hooks customizados
├── constants/ # Constantes e enums
└── utils/ # Funções utilitárias
src/services/[modulo]/index.tsdoData*: Funções GETdoProcess*: Funções POST[Modulo]Api
UnidadeApi, ProdutoApisrc/services/[modulo]/schemas/DoProcessAlterarUnidadeRequest.tsExemplo de Schema com Zod:
// services/unidade/schemas/DoProcessAlterarUnidadeRequest.ts
import { z } from "zod";
export const doProcessAlterarUnidadeSchema = z.object({
descricao: z.string(),
diaVencimento: z.number().optional(),
liberadoAte: z.string().optional(),
});
export type DoProcessAlterarUnidadeRequest = z.infer<
typeof doProcessAlterarUnidadeSchema
>;
// services/unidade/schemas/DoDataTableOcorrenciaResponse.ts
import { z } from "zod";
export const doDataTableOcorrenciaSchema = z.object({
id: z.number(),
descricao: z.string(),
data: z.date(),
hora: z.string(),
situacao: z.string(),
});
export type DoDataTableOcorrenciaResponse = z.infer<
typeof doDataTableOcorrenciaSchema
>;
page.tsxcomponents/[nome].tsx[modulo]-form.tsx[modulo]-table.tsx[modulo]-window.tsx// src/services/unidade/index.ts
const URL_PATH = "/unidade";
async function doDataTableOcorrencia(id: number) {
const res = await api.get<DotumResponse<DoDataTableOcorrenciaResponse>>(
`${URL_PATH}/${id}/info/ocorrencia`
);
return res.data.data;
}
async function doProcessAlterarUnidade(
id: number,
body: DoProcessAlterarUnidadeRequest
) {
const res = await api.post<DotumResponse>(
`${URL_PATH}/${id}`,
body
);
return res.data;
}
export const UnidadeApi = {
doDataTableOcorrencia,
doProcessAlterarUnidade,
};
@dot/uiA biblioteca @dot/ui fornece componentes baseados em shadcn/ui:
@dot/ui/form - Formulários com react-hook-form@dot/ui/field - Campos de formulário (TextField, ComboField, etc.)@dot/ui/window - Janelas modais/sidebars@dot/ui/table - Tabelas de dados@dot/ui/button - Botões@dot/ui/dialog - Diálogos modaisimport { Form } from "@dot/ui/form";
import { TextField, ComboField } from "@dot/ui/field";
import { Window } from "@dot/ui/window";
import { useWindow } from "@dot/ui/window";
O frontend está em migração e contém componentes feitos às pressas. SEMPRE:
Sinais de componentes mal feitos:
screen###/[modulo]/dto/
Record do Javaconvertsrc/services/[modulo]/schemas/
src/services/[modulo]/index.ts
doData* para GETdoProcess* para POSTuseQuery para dados (GET)useMutation para ações (POST)O queryFn do useQuery SEMPRE deve ser assíncrono e retornar res.data:
❌ ERRADO:
const { data } = useQuery({
queryKey: ["ocorrencia", id],
queryFn: () => UnidadeApi.doDataTableOcorrencia(id),
});
✅ CORRETO:
const { data } = useQuery({
queryKey: ["ocorrencia", id],
queryFn: async () => {
const res = await UnidadeApi.doDataTableOcorrencia(id);
return res.data;
},
});
async na queryFn e retorne res.data explicitamente.
Backend (Padrão Novo):
// Unidade055Ocorrencia.java
@Endpoint
public List<DoDataTableOcorrenciaResponse> doDataTableOcorrencia(@PathParam Integer id) throws Exception {
// ...
}
Frontend Schema:
// services/unidade/schemas/DoDataTableOcorrenciaResponse.ts
export interface DoDataTableOcorrenciaResponse {
id: number;
data: string;
descricao: string;
// ...
}
Frontend Service:
// services/unidade/index.ts
async function doDataTableOcorrencia(id: number) {
const res = await api.get<DotumResponse<DoDataTableOcorrenciaResponse>>(
`${URL_PATH}/${id}/info/ocorrencia`
);
return res.data.data;
}
Frontend Component:
// components/ocorrencia-table.tsx
const { data } = useQuery({
queryKey: ["ocorrencia", id],
queryFn: async () => {
const res = await UnidadeApi.doDataTableOcorrencia(id);
return res.data;
},
});
screen###/screen###/[modulo]/dto/bo/service/@dot/ui como basescreen005/unidade/ - Módulo de Unidadesscreen100/venda/ - Módulo de Vendasscreen020/pessoa/ - Módulo de PessoasUnidade055Ocorrencia.java)ocorrencia-table.tsx)doDataTableOcorrencia)doDataTableOcorrencia)dto/ ou schemas/components/DotumResponse para respostas padronizadasErrorException do @dot/coresonner)UserContext sempre ANTES de TransactionDB== true ou == false SOMENTE quando já tem um boolean tratado (variável boolean) ou em chamada de função. Para null pointer checks, pode usar != null diretamente. NUNCA use a classe Boolean (wrapper), sempre use o primitivo booleanWhereDB - Verifique exemplos corretos no código antes de usarAbstractAction para controllers@Endpoint para expor métodos REST@PathParam e @RequestBody no padrão novo (não use parâmetros diretos)Record do JavaBean diretamente)api/rt001commons com @QueryParamBO classes (não em Services)FWConstants, ParametroConstants, DDs) ao invés de valores literaisTransactionDB para selectsStringBuilder para queries complexasUserContext para segurança e auditoriaasync na queryFn do useQuery e retorne res.data quando apropriadocomponents/field-helper/ (padrão novo)@dot/ui e field-helperAntes de começar uma nova feature:
Record)@PathParam e @RequestBody no backend (padrão novo)api/rt001commons e componentes em field-helperasync na queryFn do useQuery e retorne res.data quando apropriado@dot/ui e field-helper quando possívelFWConstants, ParametroConstants, DDs) ao invés de valores literaisUserContext para segurança e auditoriaC:/dotum/projetos/registretudo/registretudo-apiC:/dotum/projetos/dot/apps/registretudo-webC:/dotum/projetos/dot/packages/uiC:/dotum/projetos/dot/packages/coreC:/dotum/projetos/registretudo/dotumcoreC:/dotum/projetos/registretudo/dotumcommonsC:/dotum/projetos/registretudo/dotumfiscalÚltima atualização: Este documento deve ser atualizado conforme o projeto evolui e novos padrões são estabelecidos.