• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wmixvideo / nfe / #9462

23 Jul 2026 08:39PM UTC coverage: 50.977% (-0.8%) from 51.794%
#9462

push

web-flow
Refatoração de chaves para suportar CNPJ alfanumérico (NT 2026.004) (#1181)

* Javadoc Warnings invalid input: '<' e '&' literais

* Javadoc Warnings invalid input: '<' e '&' literais

* Refatoração de chaves para suportar CNPJ alfanumérico (NT 2026.004)

* Normaliza char para maiusculo na chave

O cálculo do DV usa valores[i - 1] - '0'. Se a chave vier com letras minúsculas (o que é aceito por alguns validadores de CNPJ na base), o DV calculado muda (ex.: 'a' - '0' != 'A' - '0'), gerando falsos negativos/positivos. Normalize o caractere para maiúsculo antes de converter para o valor numérico.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Diego Fincatto <58352+fincatto@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

12 of 14 new or added lines in 9 files covered. (85.71%)

757 existing lines in 44 files now uncovered.

15132 of 29684 relevant lines covered (50.98%)

0.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.86
/src/main/java/com/fincatto/documentofiscal/nfe400/NotaFiscalChaveParser.java
1
package com.fincatto.documentofiscal.nfe400;
2

3
import com.fincatto.documentofiscal.DFModelo;
4
import com.fincatto.documentofiscal.DFUnidadeFederativa;
5
import com.fincatto.documentofiscal.nfe.NFTipoEmissao;
6
import com.fincatto.documentofiscal.utils.DFUtils;
7
import org.apache.commons.lang3.StringUtils;
8

9
import java.time.LocalDate;
10

11
public class NotaFiscalChaveParser {
12

13
    private final String chave;
14

15
    public NotaFiscalChaveParser(final String chave) {
1✔
16
        this.chave = StringUtils.stripToEmpty(chave).replaceAll("[^A-Za-z0-9]", "").toUpperCase();
1✔
17
        if (this.chave.length() != 44) {
1✔
18
            throw new IllegalArgumentException(String.format("A chave deve ter exatos 44 caracteres alfanumericos: %s", chave));
1✔
19
        }
20
    }
1✔
21

22
    public String getChave() {
23
        return this.chave;
1✔
24
    }
25

26
    public DFUnidadeFederativa getNFUnidadeFederativa() {
27
        return DFUnidadeFederativa.valueOfCodigo(this.chave.substring(0, 2));
1✔
28
    }
29

30
    public LocalDate getDataEmissao() {
31
        return LocalDate.of(this.getDataEmissaoAno(), this.getDataEmissaoMes(), 1);
1✔
32
    }
33

34
    private int getDataEmissaoMes() {
35
        return Integer.parseInt(this.chave.substring(4, 6));
1✔
36
    }
37

38
    private int getDataEmissaoAno() {
39
        return 2000 + Integer.parseInt(this.chave.substring(2, 4));
1✔
40
    }
41

42
    public DFModelo getModelo() {
43
        return DFModelo.valueOfCodigo(this.chave.substring(20, 22));
×
44
    }
45

46
    public String getSerie() {
47
        return this.chave.substring(22, 25);
1✔
48
    }
49

50
    public String getNumero() {
51
        return this.chave.substring(25, 34);
1✔
52
    }
53

54
    public NFTipoEmissao getFormaEmissao() {
55
        return NFTipoEmissao.valueOfCodigo(this.chave.substring(34, 35));
1✔
56
    }
57

58
    public String getCodigoNumerico() {
59
        return this.chave.substring(35, 43);
1✔
60
    }
61

62
    public String getDV() {
63
        return this.chave.substring(43, 44);
1✔
64
    }
65

66
    public boolean isEmitidaContingenciaSCAN() {
67
        return this.getSerie().matches("9[0-9]{2}");
1✔
68
    }
69

70
    /**
71
     * Indica se a nota foi emitida em contingencia SVC-AN.<br>
72
     * Comparado via {@link #getFormaEmissao()} (e nao mais por regex sobre a chave inteira),
73
     * pois o CNPJ alfanumerico (NT 2026.004) pode inserir letras nas posicoes 6-19 da chave,
74
     * o que impediria um regex do tipo "\\d{34}..." de casar.
75
     */
76
    public boolean isEmitidaContingenciaSCVAN() {
77
        return getFormaEmissao() == NFTipoEmissao.CONTINGENCIA_SVCAN;
1✔
78
    }
79

80
    /**
81
     * Indica se a nota foi emitida em contingencia SVC-RS.<br>
82
     * Ver observacao de {@link #isEmitidaContingenciaSCVAN()} sobre CNPJ alfanumerico.
83
     */
84
    public boolean isEmitidaContingenciaSCVRS() {
85
        return getFormaEmissao() == NFTipoEmissao.CONTINGENCIA_SVCRS;
1✔
86
    }
87

88
    public String getFormatado() {
89
        return this.chave.replaceFirst("([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})([A-Z0-9]{4})", "$1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11");
1✔
90
    }
91

92
    /**
93
     * Indica se o emitente da chave eh pessoa fisica.
94
     * @return Se chave foi emitida por pessoa fisica.
95
     */
96
    public boolean isEmitentePessoaFisica() {
97
        return DFUtils.isCpfValido(this.chave.substring(9, 20)) && this.isSerieReservadaPessoaFisica();
1✔
98
    }
99

100
    /**
101
     * Indica se o emitente da chave eh pessoa juridica.
102
     * @return Se chave foi emitida por pessoa juridica.
103
     */
104
    public boolean isEmitentePessoaJuridica() {
105
        return DFUtils.isCnpjValido(this.chave.substring(6, 20)) && !this.isSerieReservadaPessoaFisica();
1✔
106
    }
107

108
    /**
109
     * Returna o CNPJ do emitente da chave.<br>
110
     * Se nao for um CNPJ valido, retorna nulo.
111
     *
112
     * @return CNPJ do emitente ou nulo.
113
     */
114
    public String getCnpjEmitente() {
115
        return isEmitentePessoaJuridica() ? this.chave.substring(6, 20) : null;
1✔
116
    }
117

118
    /**
119
     * Returna o CPF do emitente da chave.<br>
120
     * Se nao for um CPF valido, retorna nulo.
121
     *
122
     * @return CPF do emitente ou nulo.
123
     */
124
    public String getCpfEmitente() {
125
        return isEmitentePessoaFisica() ? this.chave.substring(9, 20) : null;
1✔
126
    }
127

128
    /**
129
     * Indica se a chave é de uma série destinada a pessoa física.<br>
130
     * De acordo com a documentação: "O CPF deverá constar na Chave de Acesso, precedido por zeros, completando 14 posições; Deverá utilizar a série reservada [920-969]"<br>
131
     * <a href="https://www.nfe.fazenda.gov.br/portal/exibirArquivo.aspx?conteudo=4m9xb5EYa/w=">...</a>
132
     *
133
     * @return se a serie é destinada a pessoa física.
134
     */
135
    public boolean isSerieReservadaPessoaFisica() {
136
        if (DFUtils.isNumerico(this.getSerie())) {
1✔
137
            final int serie = Integer.parseInt(this.getSerie());
1✔
138
            return serie >= 920 && serie <= 969;
1✔
139
        }
UNCOV
140
        return false;
×
141
    }
142
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc