Versão: 4.1.1 (bug também presente no master atual)
Classe: br.com.swconsultoria.nfe.util.IbsCbsUtil
Severidade: alta — gera NF-e/NFC-e autorizada com tributação de IBS/CBS incorreta, sem erro nem aviso
Resumo
IbsCbsUtil mantém a classificação tributária do último item processado em campos de instância
(cstIbsCbs, classTribIbsCbs). Quando montaImpostosDet é chamado com um cClassTrib nulo, vazio ou
inexistente na tabela, esses campos não são limpos — e a validação verifica o campo, não o argumento.
O resultado é que, a partir do segundo item de um documento, um cClassTrib inválido não lança exceção:
o item recebe silenciosamente o CST e o cClassTrib do item anterior, e o documento é autorizado
com o imposto errado naquele item.
O comportamento depende da posição do item no documento: o mesmo cClassTrib inválido lança exceção se
estiver no primeiro item e passa despercebido em qualquer outra posição.
Reprodução
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<CstDTO> tabela = mapper.readValue(
getClass().getResourceAsStream("/classificacoes-tributarias.json"),
new TypeReference<List<CstDTO>>() {});
// Uma IbsCbsUtil para o documento inteiro (é como ela precisa ser usada:
// mapTotais acumula por item e alimenta preencheTotaisIbsCsb()).
IbsCbsUtil util = new IbsCbsUtil(tabela, DocumentoEnum.NFCE);
TTribNFe item1 = util.montaImpostosDet("000001", det("10.00")); // válido
TTribNFe item2 = util.montaImpostosDet(null, det("10.00")); // INVÁLIDO — deveria lançar
TTribNFe item3 = util.montaImpostosDet("200013", det("10.00")); // válido
// helper
static TNFe.InfNFe.Det det(String vProd) {
TNFe.InfNFe.Det d = new TNFe.InfNFe.Det();
TNFe.InfNFe.Det.Prod p = new TNFe.InfNFe.Det.Prod();
p.setVProd(vProd);
p.setQCom("1.0000");
d.setProd(p);
d.setImposto(new TNFe.InfNFe.Det.Imposto());
return d;
}
Resultado obtido
item1 [000001] -> CST=000 cClassTrib=000001 vBC=10.00 vCBS=0.09
item2 [null] -> CST=000 cClassTrib=000001 vBC=10.00 vCBS=0.09 <-- herdou do item1, sem exceção
item3 [200013] -> CST=200 cClassTrib=200013 vBC=10.00 vCBS=0.00
TOTAIS: vBCIBSCBS=30.00 vCBS=0.18
O item 2 não tem classificação tributária, mas saiu no XML como CST 000 / cClassTrib 000001
(tributação integral) — a classificação do item 1 — e foi somado normalmente aos totalizadores.
Resultado esperado
NfeException: CClassTrib inválido ou não encontrado: null, independentemente da posição do item.
Contraprova
Com uma IbsCbsUtil nova por chamada, a exceção é lançada corretamente. O bug só se manifesta quando a
instância é reutilizada entre itens — que é exatamente o uso previsto, já que mapTotais acumula por item
para alimentar preencheTotaisIbsCsb().
Causa
Dois defeitos que se compõem.
1. filtraCClasstrib não limpa o estado quando a busca falha (IbsCbsUtil.java:356-368):
private void filtraCClasstrib(String cclassTrib, String cclassTribRegular) {
buscarCstEClassificacao(cclassTrib).ifPresent(entry -> {
cstIbsCbs = entry.getKey();
classTribIbsCbs = entry.getValue();
}); // <-- ifPresent sem else: campos mantêm o valor anterior
if (cclassTribRegular != null && !cclassTribRegular.isEmpty()) {
buscarCstEClassificacao(cclassTribRegular).ifPresent(entry -> {
cstIbsCbsTribRegular = entry.getKey();
classTribIbsCbsTribRegular = entry.getValue();
}); // <-- mesmo problema
}
}
2. validaClassTrib valida o campo, não o argumento (IbsCbsUtil.java:378-381):
private void validaClassTrib(String cclassTrib) throws NfeException {
if (classTribIbsCbs == null) { // <-- campo, que pode ser do item anterior
throw new NfeException("CClassTrib inválido ou não encontrado: " + cclassTrib);
}
...
}
Com a instância recém-construída, classTribIbsCbs é null e a exceção sai. Depois de um item válido, o
campo carrega a classificação daquele item e satisfaz a validação de qualquer item subsequente, válido ou não.
montaImpostosDet (IbsCbsUtil.java:207-214) então monta a tag a partir dos campos:
filtraCClasstrib(cclassTrib, cclassTribRegular);
validaClassTrib(cclassTrib);
calcularBaseCalculoIBSCBS(det);
TTribNFe ibsCbs = new TTribNFe();
ibsCbs.setCST(cstIbsCbs.getCst()); // <-- do item anterior
ibsCbs.setCClassTrib(classTribIbsCbs.getCClassTrib()); // <-- do item anterior
Correção sugerida
Fazer filtraCClasstrib atribuir ou limpar, em vez de só atribuir quando encontra:
private void filtraCClasstrib(String cclassTrib, String cclassTribRegular) {
Optional<AbstractMap.SimpleEntry<CstDTO, ClassificacaoTributariaDTO>> achado =
buscarCstEClassificacao(cclassTrib);
cstIbsCbs = achado.map(AbstractMap.SimpleEntry::getKey).orElse(null);
classTribIbsCbs = achado.map(AbstractMap.SimpleEntry::getValue).orElse(null);
cstIbsCbsTribRegular = null;
classTribIbsCbsTribRegular = null;
if (cclassTribRegular != null && !cclassTribRegular.isEmpty()) {
Optional<AbstractMap.SimpleEntry<CstDTO, ClassificacaoTributariaDTO>> achadoRegular =
buscarCstEClassificacao(cclassTribRegular);
cstIbsCbsTribRegular = achadoRegular.map(AbstractMap.SimpleEntry::getKey).orElse(null);
classTribIbsCbsTribRegular = achadoRegular.map(AbstractMap.SimpleEntry::getValue).orElse(null);
}
}
Isso preserva a validação existente (classTribIbsCbs == null passa a significar "o cClassTrib desta
chamada não foi encontrado") e mantém coerente a checagem de IndTribRegular em validaClassTrib, que hoje
também pode ser satisfeita por um classTribIbsCbsTribRegular remanescente de um item anterior.
Nota: buscarCstEClassificacao (IbsCbsUtil.java:370-376) faz
entry.getValue().getCClassTrib().equals(cclassTrib), então um cClassTrib nulo simplesmente não casa e o
Optional volta vazio — não há NPE. A correção acima é suficiente para nulo, vazio e código inexistente.
Impacto
Em automação comercial (PDV/supermercado), é comum um SKU estar sem cClassTrib no cadastro durante a
transição da Reforma Tributária. Com este bug, esse item não gera rejeição: ele sai no XML com a
classificação do item anterior do cupom e a nota é autorizada com tributação errada.
Se o item anterior for de cesta básica (alíquota zero), o produto sem cadastro sai desonerado; se for
tributado integralmente, sai com imposto cheio. O erro é silencioso, depende da ordem dos itens no documento,
e — a partir do Split Payment — se traduz em retenção financeira incorreta na liquidação.
Versão: 4.1.1 (bug também presente no
masteratual)Classe:
br.com.swconsultoria.nfe.util.IbsCbsUtilSeveridade: alta — gera NF-e/NFC-e autorizada com tributação de IBS/CBS incorreta, sem erro nem aviso
Resumo
IbsCbsUtilmantém a classificação tributária do último item processado em campos de instância(
cstIbsCbs,classTribIbsCbs). QuandomontaImpostosDeté chamado com umcClassTribnulo, vazio ouinexistente na tabela, esses campos não são limpos — e a validação verifica o campo, não o argumento.
O resultado é que, a partir do segundo item de um documento, um
cClassTribinválido não lança exceção:o item recebe silenciosamente o
CSTe ocClassTribdo item anterior, e o documento é autorizadocom o imposto errado naquele item.
O comportamento depende da posição do item no documento: o mesmo
cClassTribinválido lança exceção seestiver no primeiro item e passa despercebido em qualquer outra posição.
Reprodução
Resultado obtido
O item 2 não tem classificação tributária, mas saiu no XML como
CST 000 / cClassTrib 000001(tributação integral) — a classificação do item 1 — e foi somado normalmente aos totalizadores.
Resultado esperado
NfeException: CClassTrib inválido ou não encontrado: null, independentemente da posição do item.Contraprova
Com uma
IbsCbsUtilnova por chamada, a exceção é lançada corretamente. O bug só se manifesta quando ainstância é reutilizada entre itens — que é exatamente o uso previsto, já que
mapTotaisacumula por itempara alimentar
preencheTotaisIbsCsb().Causa
Dois defeitos que se compõem.
1.
filtraCClasstribnão limpa o estado quando a busca falha (IbsCbsUtil.java:356-368):2.
validaClassTribvalida o campo, não o argumento (IbsCbsUtil.java:378-381):Com a instância recém-construída,
classTribIbsCbsénulle a exceção sai. Depois de um item válido, ocampo carrega a classificação daquele item e satisfaz a validação de qualquer item subsequente, válido ou não.
montaImpostosDet(IbsCbsUtil.java:207-214) então monta a tag a partir dos campos:Correção sugerida
Fazer
filtraCClasstribatribuir ou limpar, em vez de só atribuir quando encontra:Isso preserva a validação existente (
classTribIbsCbs == nullpassa a significar "ocClassTribdestachamada não foi encontrado") e mantém coerente a checagem de
IndTribRegularemvalidaClassTrib, que hojetambém pode ser satisfeita por um
classTribIbsCbsTribRegularremanescente de um item anterior.Nota:
buscarCstEClassificacao(IbsCbsUtil.java:370-376) fazentry.getValue().getCClassTrib().equals(cclassTrib), então umcClassTribnulo simplesmente não casa e oOptionalvolta vazio — não há NPE. A correção acima é suficiente para nulo, vazio e código inexistente.Impacto
Em automação comercial (PDV/supermercado), é comum um SKU estar sem
cClassTribno cadastro durante atransição da Reforma Tributária. Com este bug, esse item não gera rejeição: ele sai no XML com a
classificação do item anterior do cupom e a nota é autorizada com tributação errada.
Se o item anterior for de cesta básica (alíquota zero), o produto sem cadastro sai desonerado; se for
tributado integralmente, sai com imposto cheio. O erro é silencioso, depende da ordem dos itens no documento,
e — a partir do Split Payment — se traduz em retenção financeira incorreta na liquidação.