@sinete/nfe 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/build/build.d.ts +8 -6
- package/dist/build/build.d.ts.map +1 -1
- package/dist/build/nfce.d.ts +8 -0
- package/dist/build/nfce.d.ts.map +1 -1
- package/dist/build/rejeicoes.d.ts +48 -0
- package/dist/build/rejeicoes.d.ts.map +1 -1
- package/dist/build/textos.d.ts +1 -2
- package/dist/build/textos.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +202 -189
- package/dist/index.js.map +9 -9
- package/dist/services/client.d.ts +27 -2
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/outcome.d.ts +1 -1
- package/dist/services/outcome.d.ts.map +1 -1
- package/dist/versao-gerada.d.ts +1 -1
- package/package.json +7 -7
- package/src/build/build.ts +61 -28
- package/src/build/nfce.ts +41 -20
- package/src/build/rejeicoes.ts +182 -0
- package/src/build/textos.ts +6 -148
- package/src/data/cstat.json +2 -1
- package/src/index.ts +2 -0
- package/src/services/client.ts +66 -8
- package/src/services/outcome.ts +1 -0
- package/src/versao-gerada.ts +1 -1
package/src/build/rejeicoes.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Ocorrencia } from '@sinete/core';
|
|
11
|
+
import { indicadoresCfop } from '@sinete/validators';
|
|
11
12
|
import type { Issues } from '../issues.ts';
|
|
12
13
|
import type { DadosNfe } from '../model.ts';
|
|
13
14
|
import type { Instante } from '../time.ts';
|
|
@@ -140,3 +141,184 @@ export function conferirEmitenteDoCertificado(
|
|
|
140
141
|
}
|
|
141
142
|
return [];
|
|
142
143
|
}
|
|
144
|
+
|
|
145
|
+
/** A RV C17-42 (NFC-e sem IE) "não se aplica a partir de 2033". */
|
|
146
|
+
const NFCE_SEM_IE_ATE = '2033-01-01';
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Contribuinte exclusivo do IBS/CBS: a nota sem `emitente.IE` (NT 2026.007 v1.10). Sai daqui o que a NT decide só com o
|
|
150
|
+
* documento:
|
|
151
|
+
*
|
|
152
|
+
* - NFC-e sem IE, rejeição 156 (RV C17-42), até o fim de 2032; a data é a da emissão e a do fato gerador, e a conferência
|
|
153
|
+
* só recusa quando todas as leituras (local, Brasília e UTC) caem antes de 2033;
|
|
154
|
+
* - NF-e sem IE e sem CNPJ do emitente (emitente CPF), 157 (C17-43);
|
|
155
|
+
* - NF-e sem IE com a IE do substituto tributário, 158 (C18-50);
|
|
156
|
+
* - item com ICMS ou ICMS interestadual (`icmsUfDest`) na NF-e sem IE, 161 (N01-10), menos na devolução (finNFe 4) e na
|
|
157
|
+
* nota de crédito de retorno por recusa ou não localização (tpNFCredito 03), as duas exceções da regra;
|
|
158
|
+
* - item sem `impostos.ibsCbs` (nem o grupo pronto nem a classificação para a calculadora) na NF-e sem IE, 162
|
|
159
|
+
* (UB12-11). O item classificado cujo cálculo falhou já tem a ocorrência da calculadora, de montagem.
|
|
160
|
+
*
|
|
161
|
+
* Ficam para a SEFAZ as regras da mesma NT que dependem de tabela ou cadastro: a 159 (tabela de CFOP do Portal), a 163 e
|
|
162
|
+
* a 164 (CCC), as de local de retirada e entrega (CCC) e as da LCC-RFB (178 a 187). A 166 e a 188 são do roteamento.
|
|
163
|
+
*/
|
|
164
|
+
export function exclusivoIbsCbs(
|
|
165
|
+
input: DadosNfe,
|
|
166
|
+
contexto: { readonly nfce: boolean; readonly dhEmi: string; readonly instantes: readonly Instante[] },
|
|
167
|
+
issues: Issues,
|
|
168
|
+
): void {
|
|
169
|
+
const e = input.emitente;
|
|
170
|
+
if (e.IE !== undefined) return;
|
|
171
|
+
if (contexto.nfce) {
|
|
172
|
+
const datas = [
|
|
173
|
+
contexto.dhEmi.slice(0, 10),
|
|
174
|
+
...contexto.instantes.flatMap((i) => [dataNoFuso(i, OFFSET_BRASILIA), dataNoFuso(i, 0)]),
|
|
175
|
+
];
|
|
176
|
+
if (datas.every((d) => d < NFCE_SEM_IE_ATE)) {
|
|
177
|
+
issues.add(
|
|
178
|
+
'emitente.IE',
|
|
179
|
+
'campo_obrigatorio',
|
|
180
|
+
'a NFC-e exige a IE do emitente: contribuinte exclusivo do IBS/CBS, sem IE, emite só NF-e (C17-42, rejeição 156)',
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (e.CNPJ === undefined) {
|
|
186
|
+
issues.add(
|
|
187
|
+
'emitente.CNPJ',
|
|
188
|
+
'campo_obrigatorio',
|
|
189
|
+
'NF-e sem IE do emitente (contribuinte exclusivo do IBS/CBS) exige o CNPJ do emitente (C17-43, rejeição 157)',
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (e.IEST !== undefined) {
|
|
193
|
+
issues.add(
|
|
194
|
+
'emitente.IEST',
|
|
195
|
+
'combinacao_invalida',
|
|
196
|
+
'NF-e sem IE do emitente (contribuinte exclusivo do IBS/CBS) não leva a IE do substituto tributário (C18-50, rejeição 158)',
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
const icmsLiberado = (input.finNFe ?? '1') === '4' || input.tpNFCredito === '03';
|
|
200
|
+
input.itens.forEach((it, n) => {
|
|
201
|
+
if (!icmsLiberado) {
|
|
202
|
+
for (const [campo, grupo] of [
|
|
203
|
+
['icms', it.impostos.icms],
|
|
204
|
+
['icmsUfDest', it.impostos.icmsUfDest],
|
|
205
|
+
] as const) {
|
|
206
|
+
if (grupo === undefined) continue;
|
|
207
|
+
issues.add(
|
|
208
|
+
`itens[${n}].impostos.${campo}`,
|
|
209
|
+
'grupo_vedado',
|
|
210
|
+
'NF-e sem IE do emitente (contribuinte exclusivo do IBS/CBS) não leva ICMS no item (N01-10, rejeição 161)',
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (it.impostos.ibsCbs === undefined) {
|
|
215
|
+
issues.add(
|
|
216
|
+
`itens[${n}].impostos.ibsCbs`,
|
|
217
|
+
'campo_obrigatorio',
|
|
218
|
+
'NF-e sem IE do emitente (contribuinte exclusivo do IBS/CBS) exige o grupo IBS/CBS em todo item (UB12-11, rejeição 162)',
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Nota de crédito que aceita CFOP de devolução (NT 2025.002 v1.52, RV I08-144, exceção): 03, 04 e 06. */
|
|
225
|
+
const CREDITO_COM_DEVOLUCAO: ReadonlySet<string> = new Set(['03', '04', '06']);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* CFOP de devolução (Tabela CFOP, `indDevol`) em NF-e que não é de devolução nem complementar: rejeição 328 (MOC 7.0
|
|
229
|
+
* Anexo I, RV I08-144, obrigatória no modelo 55; NT 2025.002 v1.52, que exclui a nota de crédito 03, 04 e 06). CFOP
|
|
230
|
+
* fora da tabela versionada não é recusado.
|
|
231
|
+
*/
|
|
232
|
+
export function cfopDeDevolucao(input: DadosNfe, issues: Issues): void {
|
|
233
|
+
const fin = input.finNFe ?? '1';
|
|
234
|
+
if (fin === '2' || fin === '4') return;
|
|
235
|
+
if (fin === '5' && input.tpNFCredito !== undefined && CREDITO_COM_DEVOLUCAO.has(input.tpNFCredito)) return;
|
|
236
|
+
input.itens.forEach((it, n) => {
|
|
237
|
+
if (indicadoresCfop(it.produto.CFOP.replace(/\D/g, ''))?.indDevol !== true) return;
|
|
238
|
+
issues.add(
|
|
239
|
+
`itens[${n}].produto.CFOP`,
|
|
240
|
+
'combinacao_invalida',
|
|
241
|
+
`CFOP ${it.produto.CFOP} é de devolução e a nota não é de devolução (finNFe ${fin}) (I08-144, rejeição 328)`,
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** CST aceitos com destinatário não contribuinte (RV N12-70; o 61 entrou pela NT 2023.001). */
|
|
247
|
+
const CST_NAO_CONTRIBUINTE: ReadonlySet<string> = new Set(['00', '20', '40', '41', '60', '61']);
|
|
248
|
+
|
|
249
|
+
/** Códigos ANP que a exceção 5 da N12-70 deixa de fora (não derivados de petróleo). */
|
|
250
|
+
const ANP_FORA_DA_EXCECAO_5: ReadonlySet<string> = new Set([
|
|
251
|
+
'820101001',
|
|
252
|
+
'820101010',
|
|
253
|
+
'810102001',
|
|
254
|
+
'810102004',
|
|
255
|
+
'810102002',
|
|
256
|
+
'810102003',
|
|
257
|
+
'810101002',
|
|
258
|
+
'810101001',
|
|
259
|
+
'810101003',
|
|
260
|
+
'220101003',
|
|
261
|
+
'220101004',
|
|
262
|
+
'220101002',
|
|
263
|
+
'220101001',
|
|
264
|
+
'220101005',
|
|
265
|
+
'220101006',
|
|
266
|
+
'560101001',
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
/** CFOP da exceção 7 da N12-70 (CST 51 em qualquer operação). */
|
|
270
|
+
const CFOP_CST51: ReadonlySet<string> = new Set(['5123', '5922', '6123', '6922']);
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* CST fora de 00, 20, 40, 41, 60 e 61 com destinatário não contribuinte (indIEDest 9): rejeição 508 (MOC 7.0 Anexo I,
|
|
274
|
+
* RV N12-70, obrigatória no modelo 55, no texto da NT 2023.001 v1.60 e da NT 2023.003 v1.40). Só o CST: a regra não
|
|
275
|
+
* fala do CSOSN. Ficam de fora todas as exceções:
|
|
276
|
+
*
|
|
277
|
+
* 1. NF-e de entrada; 3. nota com veículo novo (`veicProd`) em algum item; 6. CST 50 e 51 na devolução;
|
|
278
|
+
* 2. CST 50 com CFOP de retorno ou remessa (Tabela CFOP, `indRetor`, `indRemes`) ou 5949 e 6949, e com CFOP fora da
|
|
279
|
+
* tabela versionada, que a conferência não sabe classificar;
|
|
280
|
+
* 5. CST 30 interestadual com combustível derivado de petróleo; 9. CST 30 interestadual com energia elétrica (NCM
|
|
281
|
+
* 27160000);
|
|
282
|
+
* 7. CST 51 com CFOP 5123, 5922, 6123 e 6922, e em qualquer operação interna: a NT 2023.001 diz "operações internas" e
|
|
283
|
+
* a NT 2023.003 restringe ao retorno de depósito (5906 e 5907), e a conferência recusa só o que as duas recusam;
|
|
284
|
+
* 8. CST 10 e 02 em operação interna, a critério da UF; e o CST 90 com CFOP 5403 ou 5405 no CE (observação da NT
|
|
285
|
+
* 2023.003), também a critério da UF.
|
|
286
|
+
*
|
|
287
|
+
* A exceção 4 (emissão antes de 01/07/2016) não alcança nota montada agora.
|
|
288
|
+
*/
|
|
289
|
+
export function cstComNaoContribuinte(
|
|
290
|
+
input: DadosNfe,
|
|
291
|
+
contexto: { readonly idDest: string; readonly uf: string },
|
|
292
|
+
issues: Issues,
|
|
293
|
+
): void {
|
|
294
|
+
if (input.destinatario?.indIEDest !== '9' || input.tpNF === '0') return;
|
|
295
|
+
if (input.itens.some((it) => it.produto.especifico !== undefined && 'veicProd' in it.produto.especifico)) return;
|
|
296
|
+
const fin = input.finNFe ?? '1';
|
|
297
|
+
const interna = contexto.idDest === '1';
|
|
298
|
+
const interestadual = contexto.idDest === '2';
|
|
299
|
+
input.itens.forEach((it, n) => {
|
|
300
|
+
const icms = it.impostos.icms;
|
|
301
|
+
if (icms === undefined || !('CST' in icms)) return;
|
|
302
|
+
const cst = icms.CST;
|
|
303
|
+
if (CST_NAO_CONTRIBUINTE.has(cst)) return;
|
|
304
|
+
const cfop = it.produto.CFOP.replace(/\D/g, '');
|
|
305
|
+
const esp = it.produto.especifico;
|
|
306
|
+
if ((cst === '50' || cst === '51') && fin === '4') return;
|
|
307
|
+
if (cst === '50') {
|
|
308
|
+
const ind = indicadoresCfop(cfop);
|
|
309
|
+
if (ind === undefined || ind.indRetor || ind.indRemes || cfop === '5949' || cfop === '6949') return;
|
|
310
|
+
}
|
|
311
|
+
if (cst === '51' && (CFOP_CST51.has(cfop) || interna)) return;
|
|
312
|
+
if ((cst === '10' || cst === '02') && interna) return;
|
|
313
|
+
if (cst === '30' && interestadual) {
|
|
314
|
+
if (esp !== undefined && 'comb' in esp && !ANP_FORA_DA_EXCECAO_5.has(esp.comb.cProdANP)) return;
|
|
315
|
+
if (it.produto.NCM.replace(/\D/g, '') === '27160000') return;
|
|
316
|
+
}
|
|
317
|
+
if (cst === '90' && contexto.uf === 'CE' && (cfop === '5403' || cfop === '5405')) return;
|
|
318
|
+
issues.add(
|
|
319
|
+
`itens[${n}].impostos.icms.CST`,
|
|
320
|
+
'combinacao_invalida',
|
|
321
|
+
`CST ${cst} não se usa com destinatário não contribuinte (indIEDest 9) (N12-70, rejeição 508)`,
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
}
|
package/src/build/textos.ts
CHANGED
|
@@ -11,32 +11,18 @@
|
|
|
11
11
|
* escrito aqui: a tabela abaixo só diz qual elemento do `infNFe` recebe cada campo da entrada.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
14
|
+
import type { CampoDeTexto, ComplexType } from '@sinete/schemas';
|
|
15
|
+
import { conferirTextos } from '@sinete/schemas';
|
|
16
16
|
import type { Issues } from '../issues.ts';
|
|
17
17
|
import type { DadosNfe } from '../model.ts';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
export function textoXmlValido(texto: string): boolean {
|
|
21
|
-
for (const ch of texto) {
|
|
22
|
-
const c = ch.codePointAt(0) ?? 0;
|
|
23
|
-
const ok =
|
|
24
|
-
c === 0x9 ||
|
|
25
|
-
c === 0xa ||
|
|
26
|
-
c === 0xd ||
|
|
27
|
-
(c >= 0x20 && c <= 0xd7ff) ||
|
|
28
|
-
(c >= 0xe000 && c <= 0xfffd) ||
|
|
29
|
-
(c >= 0x10000 && c <= 0x10ffff);
|
|
30
|
-
if (!ok) return false;
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
19
|
+
export { textoXmlValido } from '@sinete/schemas';
|
|
34
20
|
|
|
35
21
|
/**
|
|
36
22
|
* Campo de texto da entrada (com `[]` onde a entrada é uma lista) e o elemento do `infNFe` que o recebe como veio. Só
|
|
37
23
|
* os campos copiados sem transformação: o que a montagem calcula ou formata é conferido por ela.
|
|
38
24
|
*/
|
|
39
|
-
const CAMPOS: readonly
|
|
25
|
+
const CAMPOS: readonly CampoDeTexto[] = [
|
|
40
26
|
['natOp', 'ide.natOp'],
|
|
41
27
|
['emitente.xNome', 'emit.xNome'],
|
|
42
28
|
['emitente.xFant', 'emit.xFant'],
|
|
@@ -82,117 +68,6 @@ const CAMPOS: readonly (readonly [entrada: string, xml: string])[] = [
|
|
|
82
68
|
['compra.xCont', 'compra.xCont'],
|
|
83
69
|
];
|
|
84
70
|
|
|
85
|
-
/** O tipo do elemento `nome` entre as partículas (sequências e escolhas aninhadas). */
|
|
86
|
-
function elemento(p: Particle | undefined, nome: string): ComplexType | SimpleType | undefined {
|
|
87
|
-
if (p === undefined || ehWildcard(p)) return undefined;
|
|
88
|
-
if (ehElementParticle(p)) return p.e === nome ? p.t : undefined;
|
|
89
|
-
for (const i of p.i) {
|
|
90
|
-
const t = elemento(i, nome);
|
|
91
|
-
if (t !== undefined) return t;
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** O tipo simples do elemento em `caminho` (pontos) a partir do `infNFe`; `undefined` se o PL não o tem. */
|
|
97
|
-
function tipoEm(infNFe: ComplexType, caminho: string): SimpleType | undefined {
|
|
98
|
-
let t: ComplexType | SimpleType | undefined = infNFe;
|
|
99
|
-
for (const nome of caminho.split('.')) {
|
|
100
|
-
if (t === undefined || !ehComplexType(t)) return undefined;
|
|
101
|
-
t = elemento(t.c, nome);
|
|
102
|
-
}
|
|
103
|
-
return t === undefined || ehComplexType(t) ? undefined : t;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const tiposPorPl = new WeakMap<ComplexType, ReadonlyMap<string, SimpleType>>();
|
|
107
|
-
|
|
108
|
-
/** Tipo de cada campo da tabela no PL, calculado uma vez por PL. */
|
|
109
|
-
function tiposDoPl(infNFe: ComplexType): ReadonlyMap<string, SimpleType> {
|
|
110
|
-
let m = tiposPorPl.get(infNFe);
|
|
111
|
-
if (m === undefined) {
|
|
112
|
-
const novo = new Map<string, SimpleType>();
|
|
113
|
-
for (const [entrada, xml] of CAMPOS) {
|
|
114
|
-
const t = tipoEm(infNFe, xml);
|
|
115
|
-
if (t !== undefined) novo.set(entrada, t);
|
|
116
|
-
}
|
|
117
|
-
m = novo;
|
|
118
|
-
tiposPorPl.set(infNFe, m);
|
|
119
|
-
}
|
|
120
|
-
return m;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Os valores de `caminho` (com `[]` nas listas) na entrada, com o caminho concreto de cada um. */
|
|
124
|
-
function valoresEm(raiz: unknown, caminho: string): [string, unknown][] {
|
|
125
|
-
let atuais: [string, unknown][] = [['', raiz]];
|
|
126
|
-
for (const parte of caminho.split('.')) {
|
|
127
|
-
const lista = parte.endsWith('[]');
|
|
128
|
-
const chave = lista ? parte.slice(0, -2) : parte;
|
|
129
|
-
const proximos: [string, unknown][] = [];
|
|
130
|
-
for (const [c, v] of atuais) {
|
|
131
|
-
if (typeof v !== 'object' || v === null) continue;
|
|
132
|
-
const filho = (v as Record<string, unknown>)[chave];
|
|
133
|
-
const base = c === '' ? chave : `${c}.${chave}`;
|
|
134
|
-
if (!lista) proximos.push([base, filho]);
|
|
135
|
-
else if (Array.isArray(filho)) for (const [n, f] of filho.entries()) proximos.push([`${base}[${n}]`, f]);
|
|
136
|
-
}
|
|
137
|
-
atuais = proximos;
|
|
138
|
-
}
|
|
139
|
-
return atuais;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Caminhos de todos os textos da entrada com caractere que o XML não representa. */
|
|
143
|
-
function textosForaDoXml(valor: unknown, caminho: string, saida: string[]): void {
|
|
144
|
-
if (typeof valor === 'string') {
|
|
145
|
-
if (!textoXmlValido(valor)) saida.push(caminho);
|
|
146
|
-
} else if (Array.isArray(valor)) {
|
|
147
|
-
for (const [n, v] of valor.entries()) textosForaDoXml(v, `${caminho}[${n}]`, saida);
|
|
148
|
-
} else if (typeof valor === 'object' && valor !== null) {
|
|
149
|
-
for (const [k, v] of Object.entries(valor)) textosForaDoXml(v, caminho === '' ? k : `${caminho}.${k}`, saida);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Mensagem do caractere que não vai ao documento e que a pessoa não consegue ver (controle, invisível). */
|
|
154
|
-
const CARACTERE_INVISIVEL = 'caractere não aceito (símbolo ou caractere de controle)';
|
|
155
|
-
|
|
156
|
-
/** Caractere que a pessoa enxerga: letra, marca, número, pontuação ou símbolo. */
|
|
157
|
-
const VISIVEL = /^[\p{L}\p{M}\p{N}\p{P}\p{S}]$/u;
|
|
158
|
-
|
|
159
|
-
/** O primeiro caractere de `valor` que o tipo não aceita no meio de um texto, ou `undefined` se todos passam. */
|
|
160
|
-
function caractereRecusado(tipo: SimpleType, valor: string): string | undefined {
|
|
161
|
-
for (const ch of new Set(valor)) {
|
|
162
|
-
const saida: OcorrenciaSchema[] = [];
|
|
163
|
-
conferirTipoSimples(tipo, `A${ch}A`, '', saida);
|
|
164
|
-
if (saida.some((o) => o.code === 'padrao')) return ch;
|
|
165
|
-
}
|
|
166
|
-
return undefined;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* As regras do tipo que `valor` viola, em texto para quem preenche o campo. O código do validador decide a regra; a
|
|
171
|
-
* mensagem diz o limite (do próprio tipo) e, no `padrao`, se o problema é o espaço nas pontas ou um caractere.
|
|
172
|
-
*/
|
|
173
|
-
function mensagensDoTexto(tipo: SimpleType, valor: string, violadas: readonly OcorrenciaSchema[]): string[] {
|
|
174
|
-
if (violadas.length === 0) return [];
|
|
175
|
-
if (valor.trim() === '') return ['não pode ficar em branco'];
|
|
176
|
-
const tamanho = [...valor].length;
|
|
177
|
-
const mensagens = new Set<string>();
|
|
178
|
-
for (const o of violadas) {
|
|
179
|
-
if (o.code === 'tamanho_maximo' && tipo.mx !== undefined)
|
|
180
|
-
mensagens.add(`no máximo ${tipo.mx} caracteres (tem ${tamanho})`);
|
|
181
|
-
else if (o.code === 'tamanho_minimo' && tipo.mn !== undefined)
|
|
182
|
-
mensagens.add(`no mínimo ${tipo.mn} caracteres (tem ${tamanho})`);
|
|
183
|
-
else if (o.code === 'tamanho' && tipo.l !== undefined)
|
|
184
|
-
mensagens.add(`exatamente ${tipo.l} caracteres (tem ${tamanho})`);
|
|
185
|
-
else if (o.code === 'padrao') {
|
|
186
|
-
const espaco = valor !== valor.trim();
|
|
187
|
-
const ch = caractereRecusado(tipo, valor);
|
|
188
|
-
if (espaco) mensagens.add('sem espaço no começo nem no fim');
|
|
189
|
-
if (ch !== undefined) mensagens.add(VISIVEL.test(ch) ? `caractere não aceito: “${ch}”` : CARACTERE_INVISIVEL);
|
|
190
|
-
if (!espaco && ch === undefined) mensagens.add('formato não aceito');
|
|
191
|
-
} else mensagens.add('valor não aceito neste campo');
|
|
192
|
-
}
|
|
193
|
-
return [...mensagens];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
71
|
/**
|
|
197
72
|
* Confere os textos da entrada: em qualquer campo, o caractere que o XML não representa; nos campos da tabela, o tipo
|
|
198
73
|
* do elemento no PL (tamanho, espaço nas pontas, caractere fora do conjunto aceito). Tudo sai como `campo_invalido`,
|
|
@@ -206,23 +81,6 @@ export function conferirTextosDaEntrada(
|
|
|
206
81
|
issues: Issues,
|
|
207
82
|
substituidos: ReadonlySet<string> = new Set(),
|
|
208
83
|
): void {
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
textosForaDoXml(entrada, '', foraDoXml);
|
|
212
|
-
for (const c of foraDoXml) {
|
|
213
|
-
if (jaRecusados.has(c) || substituidos.has(c)) continue;
|
|
214
|
-
issues.add(c, 'campo_invalido', CARACTERE_INVISIVEL);
|
|
215
|
-
jaRecusados.add(c);
|
|
216
|
-
}
|
|
217
|
-
const tipos = tiposDoPl(infNFe);
|
|
218
|
-
for (const [campo] of CAMPOS) {
|
|
219
|
-
const tipo = tipos.get(campo);
|
|
220
|
-
if (tipo === undefined) continue;
|
|
221
|
-
for (const [caminho, valor] of valoresEm(entrada, campo)) {
|
|
222
|
-
if (typeof valor !== 'string' || jaRecusados.has(caminho) || substituidos.has(caminho)) continue;
|
|
223
|
-
const saida: OcorrenciaSchema[] = [];
|
|
224
|
-
conferirTipoSimples(tipo, valor, caminho, saida);
|
|
225
|
-
for (const m of mensagensDoTexto(tipo, valor, saida)) issues.add(caminho, 'campo_invalido', m);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
84
|
+
const pular = new Set([...issues.list.map((i) => i.caminho), ...substituidos]);
|
|
85
|
+
for (const t of conferirTextos(entrada, infNFe, CAMPOS, pular)) issues.add(t.caminho, 'campo_invalido', t.mensagem);
|
|
228
86
|
}
|
package/src/data/cstat.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"descricao": "Códigos de status de sucesso e de processamento (MOC 7.0 Anexo I, tabela 4.4.1) e os códigos que o @sinete/nfe trata de forma própria. Rejeições e denegações com mensagem oficial estão no @sinete/rejeicoes; aqui fica só o desfecho de cada código por serviço.",
|
|
3
|
-
"fonte": "MOC 7.0 Anexo I, tabela 4.4.1 (códigos de sucesso) e 4.4.2 (denegação 301, 302, 303); NT 2014.002 (Distribuição DF-e, 137 e 138); NT 2011.004 (lote em processamento, 105); MOC 7.0 Anexo I, consulta protocolo J03 a J06 (561, 562 e 613: a numeração da chave consultada tem outra NF-e)",
|
|
3
|
+
"fonte": "MOC 7.0 Anexo I, tabela 4.4.1 (códigos de sucesso) e 4.4.2 (denegação 301, 302, 303); NT 2014.002 (Distribuição DF-e, 137 e 138); NT 2011.004 (lote em processamento, 105); MOC 7.0 Anexo I, consulta protocolo J03 a J06 (561, 562 e 613: a numeração da chave consultada tem outra NF-e); MOC 7.0 Visão Geral, tabela 5-12, regra I07 (563: a mesma faixa já inutilizada, com o nProt anterior)",
|
|
4
4
|
"atualizadoEm": "2026-09-26",
|
|
5
5
|
"autorizada": ["100", "150"],
|
|
6
6
|
"denegada": ["110", "301", "302", "303"],
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"loteNaoLocalizado": ["106"],
|
|
12
12
|
"servicoEmOperacao": ["107"],
|
|
13
13
|
"inutilizacaoHomologada": ["102"],
|
|
14
|
+
"inutilizacaoJaHomologada": ["563"],
|
|
14
15
|
"eventoRegistrado": ["135", "136", "155"],
|
|
15
16
|
"loteEventoProcessado": ["128"],
|
|
16
17
|
"cadastroEncontrado": ["111", "112"],
|
package/src/index.ts
CHANGED
|
@@ -137,11 +137,13 @@ export type {
|
|
|
137
137
|
Espera,
|
|
138
138
|
EventoRegistrado,
|
|
139
139
|
Inutilizacao,
|
|
140
|
+
InutilizacaoAnterior,
|
|
140
141
|
InutilizacaoPedido,
|
|
141
142
|
ManifestacaoPedido,
|
|
142
143
|
ManifestacaoTipo,
|
|
143
144
|
PoliticaRecibo,
|
|
144
145
|
ProtocoloNfe,
|
|
146
|
+
RecusadoInutilizacao,
|
|
145
147
|
ResultadoAutorizacao,
|
|
146
148
|
ResultadoConsulta,
|
|
147
149
|
ResultadoEvento,
|
package/src/services/client.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* número. Consulte a chave (`resolverEnvioSemResposta`) e, se ela não constar, reenvie exatamente os mesmos bytes.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { Ambiente, Assinador, CUf, Logger, Relogio, ResultadoSefaz, Uf } from '@sinete/core';
|
|
16
|
+
import type { Ambiente, Assinador, CUf, Logger, Recusado, Relogio, ResultadoSefaz, Uf } from '@sinete/core';
|
|
17
17
|
import {
|
|
18
18
|
criarAutorizado,
|
|
19
19
|
criarDenegado,
|
|
@@ -112,6 +112,16 @@ export interface ClienteNfeOpcoes {
|
|
|
112
112
|
readonly endpointNfce?: (servico: NfeServico, uf: Uf) => EndpointResolvido;
|
|
113
113
|
/** Gerador de `idLote` (até 15 dígitos); padrão: os milissegundos do relógio. */
|
|
114
114
|
readonly idLote?: () => string;
|
|
115
|
+
/**
|
|
116
|
+
* O titular emite NF-e sem inscrição estadual, como contribuinte exclusivo do IBS/CBS (NT 2026.007 v1.10). A
|
|
117
|
+
* autorização dessa NF-e é só na SVRS (RV C17-11, rejeição 166), e os eventos de autoria do emitente dela também
|
|
118
|
+
* (RV 1P10-40, rejeição 188), menos na série 890 a 919. Com a NF-e assinada na mão (`autorizar`, `consultar` e
|
|
119
|
+
* `consultarRecibo` com ela) o cliente vê a falta de `emit/IE` sozinho; esta opção é para o que parte só da chave:
|
|
120
|
+
* cancelamento, carta de correção, consulta e recibo sem a nota (o recibo sem a nota vai à SVRS no modelo 55). A série 890 a
|
|
121
|
+
* 919 é exceção só dos eventos. Não vale para a NFC-e, que não admite emitente sem
|
|
122
|
+
* IE (RV C17-42, rejeição 156).
|
|
123
|
+
*/
|
|
124
|
+
readonly contribuinteExclusivoIbsCbs?: boolean;
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
/** Opções de toda chamada que vai à rede. */
|
|
@@ -213,7 +223,24 @@ export interface Inutilizacao {
|
|
|
213
223
|
readonly procInutNFe: string;
|
|
214
224
|
}
|
|
215
225
|
|
|
216
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Protocolo da inutilização que já valia para a mesma faixa, trazido pelo 563 (MOC 7.0 Visão Geral, tabela 5-12,
|
|
228
|
+
* regra I07). Não existe consulta de inutilização na NF-e 4.00: depois de um pedido sem resposta, reenviar a mesma
|
|
229
|
+
* faixa e ler este campo é o único caminho para guardar o `nProt` que valeu. O `procInutNFe` não se monta com ele,
|
|
230
|
+
* porque a resposta 563 não é a homologação.
|
|
231
|
+
*/
|
|
232
|
+
export interface InutilizacaoAnterior {
|
|
233
|
+
readonly nProt: string;
|
|
234
|
+
/** `retInutNFe` do 563 como veio na resposta. */
|
|
235
|
+
readonly retInutNFe: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Recusa da inutilização; no 563 com protocolo, `anterior` traz o protocolo da faixa já inutilizada. */
|
|
239
|
+
export interface RecusadoInutilizacao extends Recusado {
|
|
240
|
+
readonly anterior?: InutilizacaoAnterior;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export type ResultadoInutilizacao = Exclude<ResultadoSefaz<Inutilizacao, never>, Recusado> | RecusadoInutilizacao;
|
|
217
244
|
|
|
218
245
|
export interface Cadastro {
|
|
219
246
|
readonly UF: string;
|
|
@@ -430,6 +457,13 @@ function autorDoEmitente(a: AutorDocumento | undefined, c: ChaveAcesso): { CNPJ:
|
|
|
430
457
|
*/
|
|
431
458
|
const SVC_DO_TPEMIS: Readonly<Record<string, 'SVC-AN' | 'SVC-RS'>> = { '6': 'SVC-AN', '7': 'SVC-RS' };
|
|
432
459
|
|
|
460
|
+
/** A NF-e tem o grupo `emit` e ele não traz `IE`: contribuinte exclusivo do IBS/CBS (NT 2026.007). */
|
|
461
|
+
function emitenteSemIe(a: DocumentoAssinado): boolean {
|
|
462
|
+
const inf = primeiroFilho(a.documento.raiz, 'infNFe', NFE_NS);
|
|
463
|
+
const emit = inf === undefined ? undefined : primeiroFilho(inf, 'emit', NFE_NS);
|
|
464
|
+
return emit !== undefined && primeiroFilho(emit, 'IE', NFE_NS) === undefined;
|
|
465
|
+
}
|
|
466
|
+
|
|
433
467
|
/** Chave do documento assinado, lida para rotear: o emitente é conferido pela SEFAZ, não aqui. */
|
|
434
468
|
function chaveDoDocumento(a: DocumentoAssinado): ChaveAcesso {
|
|
435
469
|
const r = lerChaveAcesso(a.id.slice(3), { caminho: 'infNFe.Id', conferirEmitente: false });
|
|
@@ -592,8 +626,18 @@ export function criarClienteNfe(opcoesDoCliente: ClienteNfeOpcoes): ClienteNfe {
|
|
|
592
626
|
* autorizou a nota. `naUf` força o autorizador da UF (a CC-e não existe no SVC). A NFC-e vai sempre ao autorizador
|
|
593
627
|
* normal da NFC-e.
|
|
594
628
|
*/
|
|
595
|
-
const endpointDaChave = (
|
|
629
|
+
const endpointDaChave = (
|
|
630
|
+
servico: NfeServico,
|
|
631
|
+
c: ChaveAcesso,
|
|
632
|
+
naUf = false,
|
|
633
|
+
semIe = opcoesDoCliente.contribuinteExclusivoIbsCbs === true,
|
|
634
|
+
): EndpointResolvido => {
|
|
596
635
|
if (c.mod === '65') return endpointNfce(servico, c.uf);
|
|
636
|
+
// Contribuinte exclusivo do IBS/CBS (NT 2026.007): autorização, consulta e eventos do emitente só na SVRS (RV
|
|
637
|
+
// C17-11 e 1P10-40). A série 890 a 919 é exceção só dos eventos (exceção 1 da RV 1P10-40).
|
|
638
|
+
const serie = Number(c.serie);
|
|
639
|
+
if (semIe && !(servico === 'RecepcaoEvento' && serie >= 890 && serie <= 919))
|
|
640
|
+
return nfeEndpoint({ ambiente: opcoesDoCliente.ambiente, servico, autorizador: 'SVRS' });
|
|
597
641
|
const svc = naUf || c.tpEmis === undefined ? undefined : SVC_DO_TPEMIS[c.tpEmis];
|
|
598
642
|
return nfeEndpoint({
|
|
599
643
|
ambiente: opcoesDoCliente.ambiente,
|
|
@@ -602,6 +646,10 @@ export function criarClienteNfe(opcoesDoCliente: ClienteNfeOpcoes): ClienteNfe {
|
|
|
602
646
|
});
|
|
603
647
|
};
|
|
604
648
|
|
|
649
|
+
/** A NF-e assinada diz se o emitente tem IE; sem ela, vale a opção `contribuinteExclusivoIbsCbs`. */
|
|
650
|
+
const semIeOuOpcao = (a: DocumentoAssinado | undefined): boolean =>
|
|
651
|
+
a === undefined ? opcoesDoCliente.contribuinteExclusivoIbsCbs === true : emitenteSemIe(a);
|
|
652
|
+
|
|
605
653
|
const call = (
|
|
606
654
|
ep: EndpointResolvido,
|
|
607
655
|
servico: NfeServico,
|
|
@@ -640,7 +688,11 @@ export function criarClienteNfe(opcoesDoCliente: ClienteNfeOpcoes): ClienteNfe {
|
|
|
640
688
|
const msg = envelope('consReciNFe', '4.00', [`<tpAmb>${tpAmb}</tpAmb><nRec>${nRec}</nRec>`]);
|
|
641
689
|
// O recibo é do autorizador que recebeu o lote: com a NF-e, o da chave; sem ela, o das opções.
|
|
642
690
|
const ep =
|
|
643
|
-
c === undefined
|
|
691
|
+
c === undefined
|
|
692
|
+
? (opcoes.mod ?? '55') === '55' && opcoesDoCliente.contribuinteExclusivoIbsCbs === true
|
|
693
|
+
? nfeEndpoint({ ambiente: opcoesDoCliente.ambiente, servico: 'NFeRetAutorizacao', autorizador: 'SVRS' })
|
|
694
|
+
: endpoint('NFeRetAutorizacao', opcoes.mod ?? '55')
|
|
695
|
+
: endpointDaChave('NFeRetAutorizacao', c, false, semIeOuOpcao(a));
|
|
644
696
|
const r = await call(ep, 'NFeRetAutorizacao', msg, 'retConsReciNFe', opcoes.signal);
|
|
645
697
|
const v = decodificar(TRetConsReciNFe, r.ret, r.doc.texto).valor;
|
|
646
698
|
if (v.nRec !== undefined && v.nRec !== nRec) {
|
|
@@ -663,7 +715,7 @@ export function criarClienteNfe(opcoesDoCliente: ClienteNfeOpcoes): ClienteNfe {
|
|
|
663
715
|
const a = nfeAssinada === undefined ? undefined : documentoAssinado(nfeAssinada, 'NFe', 'infNFe');
|
|
664
716
|
if (a && a.id !== `NFe${c.chave}`) throw new ErroDeConfiguracao('a NF-e assinada não é a da chave consultada');
|
|
665
717
|
const msg = serializarRaiz(consSitNFeElement, { versao: '4.00', tpAmb, xServ: 'CONSULTAR', chNFe: c.chave });
|
|
666
|
-
const ep = endpointDaChave('NfeConsultaProtocolo', c);
|
|
718
|
+
const ep = endpointDaChave('NfeConsultaProtocolo', c, false, semIeOuOpcao(a));
|
|
667
719
|
const r = await call(ep, 'NfeConsultaProtocolo', msg, 'retConsSitNFe', opcoes?.signal);
|
|
668
720
|
const v = decodificar(TRetConsSitNFe, r.ret, r.doc.texto).valor;
|
|
669
721
|
const status = { cStat: v.cStat, xMotivo: v.xMotivo };
|
|
@@ -797,7 +849,7 @@ export function criarClienteNfe(opcoesDoCliente: ClienteNfeOpcoes): ClienteNfe {
|
|
|
797
849
|
a.xml,
|
|
798
850
|
]);
|
|
799
851
|
// O autorizador é o do documento: a UF do cUF e, assinada em SVC (tpEmis 6 ou 7), o SVC da chave.
|
|
800
|
-
const ep = endpointDaChave('NFeAutorizacao', chaveDoDocumento(a));
|
|
852
|
+
const ep = endpointDaChave('NFeAutorizacao', chaveDoDocumento(a), false, semIeOuOpcao(a));
|
|
801
853
|
const r = await call(ep, 'NFeAutorizacao', msg, 'retEnviNFe', opcoes.signal);
|
|
802
854
|
const v = decodificar(TRetEnviNFe, r.ret, r.doc.texto).valor;
|
|
803
855
|
const status = { cStat: v.cStat, xMotivo: v.xMotivo };
|
|
@@ -1015,8 +1067,11 @@ export function criarClienteNfe(opcoesDoCliente: ClienteNfeOpcoes): ClienteNfe {
|
|
|
1015
1067
|
const v = decodificar(TRetInutNFe, r.ret, r.doc.texto).valor;
|
|
1016
1068
|
const status = { cStat: v.infInut.cStat, xMotivo: v.infInut.xMotivo };
|
|
1017
1069
|
logger.info('nfe.inutilizacao', { id, cStat: status.cStat });
|
|
1018
|
-
|
|
1019
|
-
//
|
|
1070
|
+
const homologada = cstatEm(status.cStat, 'inutilizacaoHomologada');
|
|
1071
|
+
// `<nProt></nProt>` vazio não é protocolo: sem ele, o 563 fica como a recusa simples.
|
|
1072
|
+
const anterior = cstatEm(status.cStat, 'inutilizacaoJaHomologada') ? v.infInut.nProt || undefined : undefined;
|
|
1073
|
+
if (!homologada && anterior === undefined) return rejeitado(status);
|
|
1074
|
+
// A homologação (ou a anterior, no 563) tem de ser desta faixa: os campos que o retorno trouxer, iguais aos do pedido.
|
|
1020
1075
|
const r0 = v.infInut;
|
|
1021
1076
|
const n = (x: string | undefined): string | undefined => (x === undefined ? undefined : String(Number(x)));
|
|
1022
1077
|
const confere: readonly (readonly [string, string | undefined, string])[] = [
|
|
@@ -1033,6 +1088,9 @@ export function criarClienteNfe(opcoesDoCliente: ClienteNfeOpcoes): ClienteNfe {
|
|
|
1033
1088
|
detalhes: Object.fromEntries(fora.map(([k, v]) => [k, v])),
|
|
1034
1089
|
});
|
|
1035
1090
|
}
|
|
1091
|
+
if (anterior !== undefined) {
|
|
1092
|
+
return { ...rejeitado(status), anterior: { nProt: anterior, retInutNFe: avulso(r.doc, r.ret) } };
|
|
1093
|
+
}
|
|
1036
1094
|
return criarAutorizado(status, {
|
|
1037
1095
|
dhRecbto: v.infInut.dhRecbto,
|
|
1038
1096
|
retInutNFe: avulso(r.doc, r.ret),
|
package/src/services/outcome.ts
CHANGED
package/src/versao-gerada.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// GERADO por scripts/versao-gerada.ts a partir do package.json. Não edite: rode `bun scripts/versao-gerada.ts`.
|
|
2
2
|
/** Versão de `@sinete/nfe`, para o padrão de `verProc`/`verAplic` (`formatarVerProc` do @sinete/core). */
|
|
3
|
-
export const VERSAO_PACOTE = '0.
|
|
3
|
+
export const VERSAO_PACOTE = '0.6.0';
|