Cria, revisa e padroniza listeners de persistência Sankhya com `@Listener` (classe estende `PersistenceEventAdapter`) — reage a eventos CRUD (before/after insert, update, delete) de qualquer entidade JAPE, inclusive instâncias nativas. Use ao criar, alterar, revisar, auditar ou padronizar reação a gravação/exclusão de registros, ao validar ou preencher campos automaticamente no insert/update, ao implementar auditoria de alterações, ao reagir a mudança de status de um registro, ao bloquear operações com exceção em `before*`, ao trabalhar com classes `*Listener` de persistência, ou ao tocar em código com `@Listener`/`PersistenceEventAdapter`/`PersistenceEvent`. NÃO usar para interceptar busca/carregamento/leitura de entidades (`FinderListener`, filtro em query) — isso é `@BeforeLoadListener`, skill `before-load-listener`.
インストール
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Cria, revisa e padroniza listeners de persistência Sankhya com `@Listener` (classe estende `PersistenceEventAdapter`) — reage a eventos CRUD (before/after insert, update, delete) de qualquer entidade JAPE, inclusive instâncias nativas. Use ao criar, alterar, revisar, auditar ou padronizar reação a gravação/exclusão de registros, ao validar ou preencher campos automaticamente no insert/update, ao implementar auditoria de alterações, ao reagir a mudança de status de um registro, ao bloquear operações com exceção em `before*`, ao trabalhar com classes `*Listener` de persistência, ou ao tocar em código com `@Listener`/`PersistenceEventAdapter`/`PersistenceEvent`. NÃO usar para interceptar busca/carregamento/leitura de entidades (`FinderListener`, filtro em query) — isso é `@BeforeLoadListener`, skill `before-load-listener`.
Listener de Persistência (@Listener) — Addon Studio 2.0
@Listener executa lógica personalizada em eventos de persistência (CRUD) de uma entidade JAPE — antes/depois de insert, update e delete. Funciona em entidades do próprio add-on e em instâncias nativas do sistema (CabecalhoNota, Parceiro, Produto, etc.). A anotação registra o listener automaticamente — sem edição manual de XML.
⚠️ @Listener ≠ @BeforeLoadListener. São mecanismos distintos, com anotação, interface, evento e escopo diferentes: @Listener reage a escrita (insert/update/delete, via PersistenceEventAdapter); @BeforeLoadListener intercepta leitura (busca/carregamento no Finder, via FinderListener) — e só funciona em instâncias do próprio add-on. Tarefa de leitura/filtro de busca → skill before-load-listener.
1. Quando usar — @Listener vs @BeforeLoadListener vs @BusinessRule vs @ActionButton
Mecanismo
Escopo
Quando usar
@Listener
CRUD (insert/update/delete) de qualquer entidade
Validar/preencher campos ao gravar/excluir; auditoria; reagir a mudança de dados.
@BeforeLoadListener
Toda busca/carregamento de UMA entidade (só do add-on)
Filtro transversal em leitura (segurança, multi-tenant, soft-delete).
@BusinessRule
Confirmação/faturamento de notas
Regras transacionais comerciais com barramento de regras.
@ActionButton
Ação disparada pelo usuário na tela
Processamento sob demanda, não automático por evento.
Regra rápida: reagir a gravação/exclusão de registro (automático, sem clique)? @Listener. Filtrar leitura? @BeforeLoadListener. Regra de nota no barramento comercial? @BusinessRule.
Diferente do @BeforeLoadListener, o @Listenerpode escutar instâncias nativas do sistema (CabecalhoNota, Financeiro, Parceiro, etc.).
Classe estendebr.com.sankhya.jape.event.PersistenceEventAdapter e sobrescreve só os eventos de interesse.
Listener é entrypoint fino: filtra o evento, manipula o DynamicVO e delega a regra de negócio a service/use case injetado.
3. Atributo da anotação @Listener
Atributo
Tipo
Obrigatório
Descrição
instanceNames
String[]
Sim
Nome(s) da(s) instância(s) a escutar — o mesmo valor de @JapeEntity(entity = "..."), de <instance name="..."> no XML, ou o nome da instância nativa. Não é o nome da tabela.
@Listener(instanceNames = "TdcXyzPedido")// uma instancia@Listener(instanceNames = {"CabecalhoNota", "Financeiro"})// varias instancias (nativas)
Gotcha:instanceNames é o nome lógico da entidade, não a tabela. Para @JapeEntity(entity = "TdcXyzPedido", table = "TDCXYZPED"), usa-se @Listener(instanceNames = "TdcXyzPedido").
4. Eventos disponíveis (PersistenceEventAdapter)
Método
Momento
Uso típico
beforeInsert(PersistenceEvent)
Antes de inserir
Preencher/validar campos; exceção bloqueia a inserção.
afterInsert(PersistenceEvent)
Depois de inserir
Auditoria, enfileirar processamento, propagar para outra entidade.
beforeUpdate(PersistenceEvent)
Antes de atualizar
Recalcular campos derivados; validar transição de status.
afterUpdate(PersistenceEvent)
Depois de atualizar
Reagir a mudança efetivada (ex.: status mudou para "Enviado").
beforeDelete(PersistenceEvent)
Antes de excluir
Impedir exclusão (exceção bloqueia); limpeza de dependências.
afterDelete(PersistenceEvent)
Depois de excluir
Auditoria de exclusão, propagação.
Todos declaram throws Exception. Em before*, exceção lançada cancela a operação e propaga a mensagem ao usuário — use exceção tipada com mensagem de negócio.
Alterações no DynamicVO feitas em before* são persistidas junto com a operação — não chame save.
Em after* o registro já foi gravado — alterar o VO ali não persiste nada.
O adapter também expõe hooks avançados (afterLoadValueObject, afterRetrieveValueObject, updateRequired, transferData) — raramente necessários.
5. API do PersistenceEvent
Método
Retorno
Uso
getVo()
EntityVO — cast para DynamicVO
Dados atuais do registro (ler/alterar campos).
getOldVO()
EntityVO
Dados antes da modificação (updates).
getModifingFields()
ModifingFields
Quais campos estão sendo alterados (updates) — ver §6.
getJdbcWrapper()
br.com.sankhya.jape.dao.JdbcWrapper
JDBC dentro da transação corrente. Nunca feche a conexão.
getEntity()
EntityMetaData
Metadados da entidade (getEntity().getName(), etc.).
Gotcha de pacote:JdbcWrapper do evento é br.com.sankhya.jape.dao.JdbcWrapper — nãobr.com.sankhya.jape.util.
Métodos úteis do DynamicVO
Método
Uso
getProperty("CAMPO")
Lê valor cru (Object, null se ausente).
setProperty("CAMPO", valor)
Altera campo — em before*, persiste junto com a operação.
asBigDecimalOrZero("CAMPO")
BigDecimal (zero se nulo) — ideal para cálculo.
asBigDecimal / asString / asInt
Conversões tipadas.
Do VO à entidade tipada — EntityMapper.fromVO
Para trabalhar com a entidade @JapeEntity em vez de strings de campo:
import br.com.sankhya.sdk.data.repository.impl.EntityMapper;
TdcXyzPedidopedido= EntityMapper.fromVO(event.getVo(), TdcXyzPedido.class);
if (!pedido.deveProcessar()) return; // regra de dominio na entidade, nao no listener
6. ModifingFields — filtrar updates por campo alterado
beforeUpdate/afterUpdate disparam em qualquer update da instância — e o evento de update só traz os campos alterados no VO. Filtre pelo que mudou:
Método
Uso
isModifing("CAMPO")
true se o campo está sendo alterado neste update.
isModifingAny("C1,C2")
true se qualquer um dos campos está sendo alterado.
getOldValue("CAMPO")
Valor anterior do campo.
getNewValue("CAMPO")
Valor novo do campo.
@OverridepublicvoidbeforeUpdate(PersistenceEvent event)throws Exception {
DynamicVOvo= (DynamicVO) event.getVo();
// Preenche vendedor preferencial so quando CODPARC muda sem CODVEND explicitoif (event.getModifingFields().isModifing("CODPARC")
&& !event.getModifingFields().isModifing("CODVEND")) {
preencherVendedorDoParceiro(vo);
}
}
Gotcha: no update, vo.getProperty("CAMPO") de campo não alterado pode vir null — o VO do evento só carrega o delta. Precisa do registro completo em after*? Leia a PK do VO e recarregue via repository/use case.
7. Injeção de dependência
@Listenersuporta @Inject (Guice) — não consta na documentação oficial, mas é suportado pelo SDK. Delegue a regra de negócio a services/use cases injetados via construtor:
@Log@Listener(instanceNames = "TdcXyzPedido")publicclassTdcXyzPedidoListenerextendsPersistenceEventAdapter {
privatefinal LimiteCreditoService limiteService;
@InjectpublicTdcXyzPedidoListener(LimiteCreditoService limiteService) {
this.limiteService = limiteService;
}
@OverridepublicvoidbeforeInsert(PersistenceEvent event)throws Exception {
DynamicVOvo= (DynamicVO) event.getVo();
// Excecao tipada do service bloqueia a gravacao com mensagem de negocio
limiteService.validar(vo.asBigDecimalOrZero("CODPARC"), vo.asBigDecimalOrZero("VLRTOT"));
}
}
@Inject de com.google.inject.Inject — nunca javax.inject.Inject.
Dependências private final, injetadas via construtor. Nunca new em dependência gerenciada.
Services/repositories registrados no módulo Guice do projeto (ver dependency-injection).
8. Transação, loops e chamadas externas
O listener roda dentro da transação da operação:
Exceção em before* → operação cancelada (rollback) e mensagem propagada ao usuário.
Acesso a banco na mesma transação: use event.getJdbcWrapper() ou repositories — nunca abra/feche conexão própria.
Chamada externa síncrona (API HTTP) dentro do listener é PROIBIDA — segura a transação e derruba o tempo de resposta da gravação. Padrão correto: gravar numa tabela-fila e processar via @Job ou worker pool assíncrono (fire-and-forget).
Loop de eventos
Listener que grava na própria instância que escuta (direta ou indiretamente via service) dispara os eventos de novo → loop infinito. Sempre proteja com guard clause de estado:
@OverridepublicvoidafterUpdate(PersistenceEvent event)throws Exception {
DynamicVOvo= (DynamicVO) event.getVo();
// O service muda o STATUS do proprio registro — sem este guard,// o update do service dispararia este afterUpdate de novo (loop)if (!"P".equals(vo.asString("STATUS"))) return;
processamentoService.processar(vo.asBigDecimal("NUPED"));
}
9. Anti-Patterns (PROIBIDO)
Anti-Pattern
Correção
Regra de negócio dentro do listener
Listener filtra evento e delega a service/use case injetado
Usar getVo() sem cast
(DynamicVO) event.getVo()
Update sem filtrar por getModifingFields().isModifing(...)
Filtrar campo alterado — listener dispara em qualquer update
Ler campo não alterado do VO em update esperando valor
VO de update só traz o delta — recarregar pela PK se precisar do todo
Chamada HTTP/API externa síncrona no listener
Tabela-fila + @Job/worker assíncrono
Gravar na própria instância sem guard clause de estado
Guard clause anti-loop (ver §8)
Abrir/fechar conexão JDBC própria
event.getJdbcWrapper() — nunca fechar
Alterar VO em after* esperando persistir
Alterações persistem só em before*
throw new RuntimeException(...) cru para bloquear operação