@sinete/nfe 0.0.0 → 0.1.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/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +147 -1
- package/dist/build/build.d.ts +127 -0
- package/dist/build/build.d.ts.map +1 -0
- package/dist/build/destinatario.d.ts +19 -0
- package/dist/build/destinatario.d.ts.map +1 -0
- package/dist/build/ibscbs.d.ts +27 -0
- package/dist/build/ibscbs.d.ts.map +1 -0
- package/dist/build/icms.d.ts +42 -0
- package/dist/build/icms.d.ts.map +1 -0
- package/dist/build/nfce.d.ts +122 -0
- package/dist/build/nfce.d.ts.map +1 -0
- package/dist/build/pl.d.ts +19 -0
- package/dist/build/pl.d.ts.map +1 -0
- package/dist/build/rejeicoes.d.ts +52 -0
- package/dist/build/rejeicoes.d.ts.map +1 -0
- package/dist/build/tributos.d.ts +45 -0
- package/dist/build/tributos.d.ts.map +1 -0
- package/dist/build/values.d.ts +48 -0
- package/dist/build/values.d.ts.map +1 -0
- package/dist/decimal.d.ts +66 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/format.d.ts +48 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/ibs-cbs.d.ts +14 -0
- package/dist/ibs-cbs.d.ts.map +1 -0
- package/dist/ibs-cbs.js +43 -0
- package/dist/ibs-cbs.js.map +10 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4370 -0
- package/dist/index.js.map +33 -0
- package/dist/issues.d.ts +24 -0
- package/dist/issues.d.ts.map +1 -0
- package/dist/model.d.ts +861 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/ports.d.ts +109 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/rotulo.d.ts +12 -0
- package/dist/rotulo.d.ts.map +1 -0
- package/dist/rtc.d.ts +65 -0
- package/dist/rtc.d.ts.map +1 -0
- package/dist/services/client.d.ts +273 -0
- package/dist/services/client.d.ts.map +1 -0
- package/dist/services/gzip.d.ts +7 -0
- package/dist/services/gzip.d.ts.map +1 -0
- package/dist/services/index.d.ts +14 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/outcome.d.ts +15 -0
- package/dist/services/outcome.d.ts.map +1 -0
- package/dist/services/proc.d.ts +41 -0
- package/dist/services/proc.d.ts.map +1 -0
- package/dist/services/recuperar.d.ts +28 -0
- package/dist/services/recuperar.d.ts.map +1 -0
- package/dist/services/resolver.d.ts +88 -0
- package/dist/services/resolver.d.ts.map +1 -0
- package/dist/services/soap.d.ts +43 -0
- package/dist/services/soap.d.ts.map +1 -0
- package/dist/time.d.ts +12 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/versao-gerada.d.ts +3 -0
- package/dist/versao-gerada.d.ts.map +1 -0
- package/package.json +58 -3
- package/src/build/build.ts +1486 -0
- package/src/build/destinatario.ts +212 -0
- package/src/build/ibscbs.ts +131 -0
- package/src/build/icms.ts +645 -0
- package/src/build/nfce.ts +425 -0
- package/src/build/pl.ts +73 -0
- package/src/build/rejeicoes.ts +142 -0
- package/src/build/tributos.ts +217 -0
- package/src/build/values.ts +126 -0
- package/src/data/arredondamento.json +30 -0
- package/src/data/cstat.json +36 -0
- package/src/data/fusos.json +17 -0
- package/src/data/nfce-urls.json +170 -0
- package/src/data/produtor-rural.json +17 -0
- package/src/data/reforma.json +6 -0
- package/src/data/resp-tec.json +13 -0
- package/src/data/servicos.json +44 -0
- package/src/data/suframa.json +7 -0
- package/src/decimal.ts +231 -0
- package/src/format.ts +64 -0
- package/src/ibs-cbs.ts +89 -0
- package/src/index.ts +153 -0
- package/src/issues.ts +80 -0
- package/src/model.ts +1033 -0
- package/src/ports.ts +96 -0
- package/src/rotulo.ts +137 -0
- package/src/rtc.ts +305 -0
- package/src/services/client.ts +1085 -0
- package/src/services/gzip.ts +26 -0
- package/src/services/index.ts +43 -0
- package/src/services/outcome.ts +50 -0
- package/src/services/proc.ts +164 -0
- package/src/services/recuperar.ts +91 -0
- package/src/services/resolver.ts +127 -0
- package/src/services/soap.ts +133 -0
- package/src/time.ts +23 -0
- package/src/versao-gerada.ts +3 -0
package/src/ports.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portas do `@sinete/nfe` para o que pode vir de fora. A montagem só conhece o contrato estreito declarado aqui; a
|
|
3
|
+
* implementação padrão do IBS/CBS é o `ibsCbsCalculator` (`rtc.ts`, sobre o `@sinete/ibs-cbs`), e quem calcula em outro
|
|
4
|
+
* lugar (ou testa com alíquotas fixas) injeta a sua em `BuildNfeOptions.ibsCbs`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Ambiente, Uf, ValidationIssue } from '@sinete/core';
|
|
8
|
+
import type { TTribNFe } from '@sinete/schemas/nfe/PL_010f';
|
|
9
|
+
import type { Decimal } from './decimal.ts';
|
|
10
|
+
import type { Instante } from './time.ts';
|
|
11
|
+
|
|
12
|
+
/** Um item classificado, com os valores que a calculadora pode precisar para a base do IBS/CBS. */
|
|
13
|
+
export interface IbsCbsItemRequest {
|
|
14
|
+
/** Número do item na nota (1 a 990). */
|
|
15
|
+
readonly nItem: number;
|
|
16
|
+
/** CST do IBS/CBS (3 dígitos). */
|
|
17
|
+
readonly CST: string;
|
|
18
|
+
/** Classificação tributária (6 dígitos). */
|
|
19
|
+
readonly cClassTrib: string;
|
|
20
|
+
readonly indDoacao?: '1';
|
|
21
|
+
readonly cCredPres?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Tributação regular (`gTribRegular`, UB68): o CST e o cClassTrib que valeriam sem a suspensão, o diferimento ou a
|
|
24
|
+
* exportação do `cClassTrib` principal, quando ele exige ou permite o grupo (550001, por exemplo).
|
|
25
|
+
*/
|
|
26
|
+
readonly gTribRegular?: { readonly CSTReg: string; readonly cClassTribReg: string };
|
|
27
|
+
/** Base informada por quem emite; ausente, a calculadora determina. */
|
|
28
|
+
readonly vBC?: Decimal;
|
|
29
|
+
readonly NCM: string;
|
|
30
|
+
readonly CFOP: string;
|
|
31
|
+
readonly uTrib: string;
|
|
32
|
+
readonly qTrib: Decimal;
|
|
33
|
+
readonly vProd: Decimal;
|
|
34
|
+
readonly vDesc: Decimal;
|
|
35
|
+
readonly vFrete: Decimal;
|
|
36
|
+
readonly vSeg: Decimal;
|
|
37
|
+
readonly vOutro: Decimal;
|
|
38
|
+
/** Tributos do item já calculados pelo builder (a base do IBS/CBS pode excluí-los, conforme a LC 214/2025). */
|
|
39
|
+
readonly vICMS: Decimal;
|
|
40
|
+
readonly vICMSST: Decimal;
|
|
41
|
+
readonly vFCP: Decimal;
|
|
42
|
+
readonly vFCPST: Decimal;
|
|
43
|
+
readonly vIPI: Decimal;
|
|
44
|
+
readonly vPIS: Decimal;
|
|
45
|
+
readonly vCOFINS: Decimal;
|
|
46
|
+
readonly vII: Decimal;
|
|
47
|
+
readonly vISSQN: Decimal;
|
|
48
|
+
/**
|
|
49
|
+
* ICMS e FCP de partilha do item para a UF de destino (grupo `ICMSUFDest`, DIFAL da EC 87/2015); zero sem o grupo.
|
|
50
|
+
* A base do IBS/CBS pode deduzi-los junto com o ICMS próprio.
|
|
51
|
+
*/
|
|
52
|
+
readonly vICMSUFDest: Decimal;
|
|
53
|
+
readonly vFCPUFDest: Decimal;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Dados da nota que decidem a regra aplicável (local da operação, vigência, compra governamental). */
|
|
57
|
+
export interface IbsCbsNotaRequest {
|
|
58
|
+
/** Instante do fato gerador (relógio `fatoGerador` do `TimeContext`): decide a vigência das alíquotas. */
|
|
59
|
+
readonly fatoGerador: Instante;
|
|
60
|
+
/** Instante da emissão (o do `dhEmi`): decide quais regras de validação da NT já estão implantadas no ambiente. */
|
|
61
|
+
readonly emissao: Instante;
|
|
62
|
+
readonly ambiente: Ambiente;
|
|
63
|
+
readonly mod: '55' | '65';
|
|
64
|
+
readonly tpNF: '0' | '1';
|
|
65
|
+
readonly finNFe: string;
|
|
66
|
+
readonly tpNFDebito?: string;
|
|
67
|
+
readonly tpNFCredito?: string;
|
|
68
|
+
readonly indFinal: '0' | '1';
|
|
69
|
+
readonly indPres: string;
|
|
70
|
+
readonly emitente: { readonly UF: Uf; readonly cMun: string; readonly CRT: string };
|
|
71
|
+
/** Local de destino da operação (entrega, destinatário ou `cMunFGIBS`, nessa ordem de precedência do leiaute). */
|
|
72
|
+
readonly destino?: { readonly UF: Uf | 'EX'; readonly cMun: string };
|
|
73
|
+
readonly cMunFGIBS?: string;
|
|
74
|
+
readonly compraGov?: { readonly tpEnteGov: string; readonly pRedutor: Decimal; readonly tpOperGov: string };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Resultado da calculadora: o grupo `IBSCBS` de cada item pedido, já na forma lexical do leiaute. */
|
|
78
|
+
export interface IbsCbsResponse {
|
|
79
|
+
readonly itens: readonly { readonly nItem: number; readonly IBSCBS: TTribNFe }[];
|
|
80
|
+
/**
|
|
81
|
+
* Problemas de classificação ou de dado (`path` relativo ao item, como `itens[2].impostos.ibsCbs`). A ocorrência sem
|
|
82
|
+
* `origem` entra como `montagem` (ADR 0011); marque `entrada` a que aponta um valor da nota.
|
|
83
|
+
*/
|
|
84
|
+
readonly issues?: readonly ValidationIssue[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Calcula o IBS e a CBS dos itens classificados. O padrão é o `ibsCbsCalculator`, sobre o `@sinete/ibs-cbs/calcular`; nos
|
|
89
|
+
* testes, um dublê com alíquotas fixas. A calculadora não vê o XML nem o resto da nota além do que está no pedido.
|
|
90
|
+
*/
|
|
91
|
+
export interface IbsCbsCalculator {
|
|
92
|
+
calcular(request: {
|
|
93
|
+
readonly nota: IbsCbsNotaRequest;
|
|
94
|
+
readonly itens: readonly IbsCbsItemRequest[];
|
|
95
|
+
}): IbsCbsResponse | Promise<IbsCbsResponse>;
|
|
96
|
+
}
|
package/src/rotulo.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rótulo em português do caminho de uma ocorrência da NF-e (ADR 0011), para mostrar a quem preencheu a nota: `Item 2,
|
|
3
|
+
* Descrição do produto` em vez de `itens[1].produto.xProd`. Aceita os caminhos da entrada (`NfeInput`) e os do
|
|
4
|
+
* documento montado, tanto os das conferências do montador (`infNFe.det[1].prod.xProd`) quanto os do validador de XSD
|
|
5
|
+
* (`/infNFe/det[2]/prod/xProd`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { GrupoDeCaminho } from '@sinete/core';
|
|
9
|
+
import { criarRotuloDoCaminho } from '@sinete/core';
|
|
10
|
+
|
|
11
|
+
const item =
|
|
12
|
+
(prefixo: string) =>
|
|
13
|
+
(n: number): string =>
|
|
14
|
+
`${prefixo} ${n}`;
|
|
15
|
+
|
|
16
|
+
/** Do mais específico ao mais geral. Os índices chegam somados de um. */
|
|
17
|
+
const GRUPOS: readonly GrupoDeCaminho[] = [
|
|
18
|
+
// Entrada (NfeInput)
|
|
19
|
+
{ padrao: /^emitente\b/, rotulo: 'Emitente' },
|
|
20
|
+
{ padrao: /^destinatario\b/, rotulo: 'Destinatário' },
|
|
21
|
+
{ padrao: /^retirada\b/, rotulo: 'Local de retirada' },
|
|
22
|
+
{ padrao: /^entrega\b/, rotulo: 'Local de entrega' },
|
|
23
|
+
{ padrao: /^autXML\b/, rotulo: 'Autorizados a baixar o XML' },
|
|
24
|
+
{ padrao: /^referenciadas\b/, rotulo: 'Nota referenciada' },
|
|
25
|
+
{ padrao: /^itens\[(\d+)\]\.impostos\.icms\b/, rotulo: item('ICMS do item') },
|
|
26
|
+
{ padrao: /^itens\[(\d+)\]\.impostos\.ibsCbs\b/, rotulo: item('IBS/CBS do item') },
|
|
27
|
+
{ padrao: /^itens\[(\d+)\]\.impostos\b/, rotulo: item('Impostos do item') },
|
|
28
|
+
{ padrao: /^itens\[(\d+)\]/, rotulo: item('Item') },
|
|
29
|
+
{ padrao: /^itens\b/, rotulo: 'Itens' },
|
|
30
|
+
{ padrao: /^transporte\b/, rotulo: 'Transporte' },
|
|
31
|
+
{ padrao: /^cobranca\b/, rotulo: 'Cobrança' },
|
|
32
|
+
{ padrao: /^pagamento\b/, rotulo: 'Pagamento' },
|
|
33
|
+
{ padrao: /^informacoesAdicionais\b/, rotulo: 'Informações adicionais' },
|
|
34
|
+
{ padrao: /^agropecuario\b/, rotulo: 'Produtos agropecuários' },
|
|
35
|
+
{ padrao: /^respTec\b/, rotulo: 'Responsável técnico' },
|
|
36
|
+
{ padrao: /^contingencia\b/, rotulo: 'Contingência' },
|
|
37
|
+
{ padrao: /^gCompraGov\b/, rotulo: 'Compra governamental' },
|
|
38
|
+
{ padrao: /^exporta\b/, rotulo: 'Exportação' },
|
|
39
|
+
// Opções da montagem da NFC-e (qrCode, urlQrCode, urlChave) e o infNFeSupl montado
|
|
40
|
+
{ padrao: /^(?:qrCode|urlQrCode|urlChave)\b/, rotulo: 'QR Code da NFC-e' },
|
|
41
|
+
{ padrao: /^(?:NFe\.)?infNFeSupl\b/, rotulo: 'QR Code da NFC-e' },
|
|
42
|
+
// Documento montado (infNFe)
|
|
43
|
+
{ padrao: /^infNFe\.ide\.NFref\b/, rotulo: 'Nota referenciada' },
|
|
44
|
+
{ padrao: /^infNFe\.ide\b/, rotulo: 'Identificação da nota' },
|
|
45
|
+
{ padrao: /^infNFe\.emit\b/, rotulo: 'Emitente' },
|
|
46
|
+
{ padrao: /^infNFe\.dest\b/, rotulo: 'Destinatário' },
|
|
47
|
+
{ padrao: /^infNFe\.retirada\b/, rotulo: 'Local de retirada' },
|
|
48
|
+
{ padrao: /^infNFe\.entrega\b/, rotulo: 'Local de entrega' },
|
|
49
|
+
{ padrao: /^infNFe\.autXML\b/, rotulo: 'Autorizados a baixar o XML' },
|
|
50
|
+
{ padrao: /^infNFe\.det(?:\[(\d+)\])?\.imposto\.ICMS\b/, rotulo: item('ICMS do item') },
|
|
51
|
+
{ padrao: /^infNFe\.det(?:\[(\d+)\])?\.imposto\.IBSCBS\b/, rotulo: item('IBS/CBS do item') },
|
|
52
|
+
{ padrao: /^infNFe\.det(?:\[(\d+)\])?\.imposto\b/, rotulo: item('Impostos do item') },
|
|
53
|
+
{ padrao: /^infNFe\.det(?:\[(\d+)\])?/, rotulo: item('Item') },
|
|
54
|
+
{ padrao: /^infNFe\.total\b/, rotulo: 'Totais da nota' },
|
|
55
|
+
{ padrao: /^infNFe\.transp\b/, rotulo: 'Transporte' },
|
|
56
|
+
{ padrao: /^infNFe\.cobr\b/, rotulo: 'Cobrança' },
|
|
57
|
+
{ padrao: /^infNFe\.pag\b/, rotulo: 'Pagamento' },
|
|
58
|
+
{ padrao: /^infNFe\.infAdic\b/, rotulo: 'Informações adicionais' },
|
|
59
|
+
{ padrao: /^infNFe\.agropecuario\b/, rotulo: 'Produtos agropecuários' },
|
|
60
|
+
{ padrao: /^infNFe\.infRespTec\b/, rotulo: 'Responsável técnico' },
|
|
61
|
+
{ padrao: /^infNFe\.exporta\b/, rotulo: 'Exportação' },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/** Campos pelo nome do leiaute (os mesmos na entrada e no XML). */
|
|
65
|
+
const CAMPOS: Readonly<Record<string, string>> = {
|
|
66
|
+
IE: 'Inscrição estadual',
|
|
67
|
+
IEST: 'Inscrição estadual do substituto tributário',
|
|
68
|
+
IM: 'Inscrição municipal',
|
|
69
|
+
CNPJ: 'CNPJ',
|
|
70
|
+
CPF: 'CPF',
|
|
71
|
+
idEstrangeiro: 'Identificação do estrangeiro',
|
|
72
|
+
xNome: 'Nome',
|
|
73
|
+
xFant: 'Nome fantasia',
|
|
74
|
+
xLgr: 'Logradouro',
|
|
75
|
+
nro: 'Número',
|
|
76
|
+
xCpl: 'Complemento',
|
|
77
|
+
xBairro: 'Bairro',
|
|
78
|
+
cMun: 'Município',
|
|
79
|
+
xMun: 'Município',
|
|
80
|
+
UF: 'UF',
|
|
81
|
+
CEP: 'CEP',
|
|
82
|
+
fone: 'Telefone',
|
|
83
|
+
email: 'E-mail',
|
|
84
|
+
natOp: 'Natureza da operação',
|
|
85
|
+
serie: 'Série',
|
|
86
|
+
nNF: 'Número da nota',
|
|
87
|
+
cProd: 'Código do produto',
|
|
88
|
+
cEAN: 'GTIN',
|
|
89
|
+
cEANTrib: 'GTIN da unidade tributável',
|
|
90
|
+
xProd: 'Descrição do produto',
|
|
91
|
+
NCM: 'NCM',
|
|
92
|
+
CEST: 'CEST',
|
|
93
|
+
cBenef: 'Código de benefício fiscal',
|
|
94
|
+
CFOP: 'CFOP',
|
|
95
|
+
uCom: 'Unidade comercial',
|
|
96
|
+
qCom: 'Quantidade comercial',
|
|
97
|
+
vUnCom: 'Valor unitário comercial',
|
|
98
|
+
uTrib: 'Unidade tributável',
|
|
99
|
+
qTrib: 'Quantidade tributável',
|
|
100
|
+
vUnTrib: 'Valor unitário tributável',
|
|
101
|
+
vProd: 'Valor do produto',
|
|
102
|
+
vDesc: 'Desconto',
|
|
103
|
+
vFrete: 'Frete',
|
|
104
|
+
vSeg: 'Seguro',
|
|
105
|
+
vOutro: 'Outras despesas',
|
|
106
|
+
CST: 'CST',
|
|
107
|
+
CSOSN: 'CSOSN',
|
|
108
|
+
cClassTrib: 'Classificação tributária (cClassTrib)',
|
|
109
|
+
vBC: 'Base de cálculo',
|
|
110
|
+
refNFe: 'Chave da NF-e referenciada',
|
|
111
|
+
refNFP: 'Nota de produtor referenciada',
|
|
112
|
+
AAMM: 'Mês de emissão da nota referenciada',
|
|
113
|
+
modFrete: 'Modalidade do frete',
|
|
114
|
+
placa: 'Placa',
|
|
115
|
+
RNTC: 'RNTC',
|
|
116
|
+
tPag: 'Meio de pagamento',
|
|
117
|
+
vPag: 'Valor do pagamento',
|
|
118
|
+
vTroco: 'Troco',
|
|
119
|
+
card: 'Cartão',
|
|
120
|
+
CSC: 'CSC',
|
|
121
|
+
idCSC: 'Identificador do CSC',
|
|
122
|
+
versao: 'Versão',
|
|
123
|
+
xJust: 'Justificativa',
|
|
124
|
+
dhCont: 'Entrada em contingência',
|
|
125
|
+
infCpl: 'Informações complementares',
|
|
126
|
+
infAdFisco: 'Informações de interesse do fisco',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const rotular = criarRotuloDoCaminho({ grupos: GRUPOS, campos: CAMPOS, padrao: 'Dados da NF-e' });
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Rótulo em português do caminho de uma ocorrência da NF-e: `Grupo, Campo` quando os dois são conhecidos (`Emitente,
|
|
133
|
+
* Inscrição estadual`), só um deles quando falta o outro, e `Dados da NF-e` quando nenhum é.
|
|
134
|
+
*/
|
|
135
|
+
export function rotuloDoCaminho(path: string): string {
|
|
136
|
+
return rotular(path);
|
|
137
|
+
}
|
package/src/rtc.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A calculadora padrão de IBS/CBS do `buildNfe`: a porta `IbsCbsCalculator` implementada sobre o motor do sinete.
|
|
3
|
+
*
|
|
4
|
+
* Para cada item classificado, monta a operação do `@sinete/ibs-cbs/calcular` (CST, cClassTrib, base, local da operação,
|
|
5
|
+
* compra governamental), calcula com o dataset do `@sinete/ibs-cbs-dados` e as alíquotas do `@sinete/ibs-cbs/aliquotas`, confere o
|
|
6
|
+
* resultado pelas regras da NT 2025.002 do `@sinete/ibs-cbs/validar` e devolve o grupo `IBSCBS` de cada item na forma
|
|
7
|
+
* lexical do leiaute. Erro de classificação, regime não suportado, alíquota desconhecida e violação de regra voltam
|
|
8
|
+
* como ocorrências (`ValidationIssue`) no caminho do item: a nota não é montada, e nunca sai valor zerado no lugar.
|
|
9
|
+
*
|
|
10
|
+
* O dataset embarcado (~2 MB de JSON) só é importado na primeira nota com item classificado, por `import()` dinâmico:
|
|
11
|
+
* quem usa o pacote para ler XML, eventos ou Distribuição DF-e não o carrega, e um bundler com code splitting o põe num
|
|
12
|
+
* chunk à parte.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ValidationIssue } from '@sinete/core';
|
|
16
|
+
import { fixedClock, isUf, timeContext, ufByCUf } from '@sinete/core';
|
|
17
|
+
import type { RateProvider } from '@sinete/ibs-cbs/aliquotas';
|
|
18
|
+
import { officialRates, RateUnknownError } from '@sinete/ibs-cbs/aliquotas';
|
|
19
|
+
import type {
|
|
20
|
+
ClassifiedItem,
|
|
21
|
+
ClassifiedOperation,
|
|
22
|
+
IBSCBS,
|
|
23
|
+
OperationPlace,
|
|
24
|
+
Roc,
|
|
25
|
+
TpEnteGov,
|
|
26
|
+
} from '@sinete/ibs-cbs/calcular';
|
|
27
|
+
import { ClassificationError, calculate, UnsupportedRegimeError } from '@sinete/ibs-cbs/calcular';
|
|
28
|
+
import type { Rule } from '@sinete/ibs-cbs/validar';
|
|
29
|
+
import { documentFromRoc, validate } from '@sinete/ibs-cbs/validar';
|
|
30
|
+
import type { IbsCbsDataset } from '@sinete/ibs-cbs-dados';
|
|
31
|
+
import type { IbsCbsCalculator, IbsCbsItemRequest, IbsCbsNotaRequest, IbsCbsResponse } from './ports.ts';
|
|
32
|
+
|
|
33
|
+
/** Grupo `IBSCBS` do item, como o `@sinete/nfe` o recebe. */
|
|
34
|
+
export type GrupoIbsCbs = IbsCbsResponse['itens'][number]['IBSCBS'];
|
|
35
|
+
|
|
36
|
+
export interface IbsCbsCalculatorOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Dataset do IBS/CBS. Padrão: o embarcado no `@sinete/ibs-cbs-dados`, importado sob demanda na primeira nota
|
|
39
|
+
* (`carregarDatasetEmbarcado`). Informe um bundle verificado em runtime (`verifyDataset`) para usar dados de outra
|
|
40
|
+
* origem, ou o `bundledDataset()` já carregado para o cálculo ser síncrono.
|
|
41
|
+
*/
|
|
42
|
+
readonly dataset?: IbsCbsDataset;
|
|
43
|
+
/** Alíquotas. Padrão: `officialRates()` do `@sinete/ibs-cbs/aliquotas`; troque por um provedor com alíquotas informadas. */
|
|
44
|
+
readonly rates?: RateProvider;
|
|
45
|
+
/**
|
|
46
|
+
* Base do IBS/CBS do item que não trouxe `vBC`, em texto com até 2 casas. A composição da base (NT 2025.002, UB16-10)
|
|
47
|
+
* ainda é "implementação futura, aguardando orientação normativa": sem esta função, item sem `vBC` vira ocorrência
|
|
48
|
+
* `ibscbs_base_ausente` em vez de uma base presumida.
|
|
49
|
+
*/
|
|
50
|
+
readonly base?: (item: IbsCbsItemRequest, nota: IbsCbsNotaRequest) => string;
|
|
51
|
+
/**
|
|
52
|
+
* Regras da NT 2025.002 conferidas sobre os grupos produzidos (`@sinete/ibs-cbs/validar`). Padrão: as implantadas na data
|
|
53
|
+
* de emissão e no ambiente. `false` desliga; `{ rules }` troca a lista.
|
|
54
|
+
*/
|
|
55
|
+
readonly regras?: false | { readonly rules?: readonly Rule[]; readonly ignoreActivation?: boolean };
|
|
56
|
+
/** Fuso do local da operação para a data civil do fato gerador, em minutos. Padrão: Brasília (-180). */
|
|
57
|
+
readonly utcOffsetMinutes?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const BASE = /^\d{1,13}(\.\d{1,2})?$/;
|
|
61
|
+
|
|
62
|
+
/** Texto decimal sem zeros à direita, para comparar valores com escalas diferentes (`'0.00'` e `'0'`). */
|
|
63
|
+
function normalizado(v: string): string {
|
|
64
|
+
const [int = '0', frac = ''] = v.split('.');
|
|
65
|
+
const f = frac.replace(/0+$/, '');
|
|
66
|
+
const i = int.replace(/^0+(?=\d)/, '');
|
|
67
|
+
return f === '' ? i : `${i}.${f}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const caminho = (nItem: number): string => `itens[${nItem - 1}].impostos.ibsCbs`;
|
|
71
|
+
|
|
72
|
+
/** UF do município pelo código IBGE (os dois primeiros dígitos são o cUF). */
|
|
73
|
+
function ufDoMunicipio(cMun: string): string | undefined {
|
|
74
|
+
return ufByCUf(cMun.slice(0, 2))?.sigla;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Local da operação para as alíquotas próprias de UF e município: o `cMunFGIBS` informado (campo B12a da NT
|
|
79
|
+
* 2025.002, município de ocorrência do fato gerador do IBS/CBS), senão o destino da mercadoria (entrega ou
|
|
80
|
+
* destinatário, pela LC 214/2025, art. 11, o local da entrega), senão o emitente. Destino no exterior cai no emitente.
|
|
81
|
+
*/
|
|
82
|
+
export function localDaOperacao(nota: IbsCbsNotaRequest): OperationPlace {
|
|
83
|
+
if (nota.cMunFGIBS !== undefined) {
|
|
84
|
+
const uf = ufDoMunicipio(nota.cMunFGIBS);
|
|
85
|
+
if (uf !== undefined) return { uf, cMun: nota.cMunFGIBS };
|
|
86
|
+
}
|
|
87
|
+
const d = nota.destino;
|
|
88
|
+
if (d !== undefined && d.UF !== 'EX' && isUf(d.UF)) return { uf: d.UF, cMun: d.cMun };
|
|
89
|
+
return { uf: nota.emitente.UF, cMun: nota.emitente.cMun };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Converte o grupo do motor para o do leiaute: os códigos numéricos do motor viram texto. */
|
|
93
|
+
function grupoDoLeiaute(g: IBSCBS, indDoacao: '1' | undefined): GrupoIbsCbs {
|
|
94
|
+
const { gCredPresOper, gCredPresIBSZFM, ...resto } = g;
|
|
95
|
+
return {
|
|
96
|
+
...resto,
|
|
97
|
+
...(indDoacao === undefined ? {} : { indDoacao }),
|
|
98
|
+
...(gCredPresOper === undefined
|
|
99
|
+
? {}
|
|
100
|
+
: { gCredPresOper: { ...gCredPresOper, cCredPres: String(gCredPresOper.cCredPres).padStart(2, '0') } }),
|
|
101
|
+
...(gCredPresIBSZFM === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: {
|
|
104
|
+
gCredPresIBSZFM: {
|
|
105
|
+
...gCredPresIBSZFM,
|
|
106
|
+
tpCredPresIBSZFM: String(gCredPresIBSZFM.tpCredPresIBSZFM),
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
} as GrupoIbsCbs;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Erro do motor ou das alíquotas como ocorrência no item (ou na nota, quando o erro não diz o item). */
|
|
113
|
+
function ocorrenciaDoMotor(e: unknown, itens: readonly IbsCbsItemRequest[]): ValidationIssue | undefined {
|
|
114
|
+
if (e instanceof ClassificationError || e instanceof UnsupportedRegimeError) {
|
|
115
|
+
const path = e.item !== undefined ? caminho(e.item) : caminho(itens[0]?.nItem ?? 1);
|
|
116
|
+
return { path, code: e.code, message: e.message, origem: 'entrada' };
|
|
117
|
+
}
|
|
118
|
+
// Alíquota que o sinete não conhece para a data: falta de dado do pacote, não da nota.
|
|
119
|
+
if (e instanceof RateUnknownError)
|
|
120
|
+
return { path: 'impostos.ibsCbs', code: e.code, message: e.message, origem: 'montagem' };
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let embarcado: Promise<IbsCbsDataset> | undefined;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* O dataset embarcado no `@sinete/ibs-cbs-dados`, importado sob demanda (`import()` dinâmico) e carregado uma vez por
|
|
128
|
+
* processo. É o que a calculadora padrão usa quando `dataset` não é informado; chamar antes só adianta a carga. Se o
|
|
129
|
+
* import falhar, a próxima chamada tenta de novo.
|
|
130
|
+
*/
|
|
131
|
+
export function carregarDatasetEmbarcado(): Promise<IbsCbsDataset> {
|
|
132
|
+
embarcado ??= import('@sinete/ibs-cbs-dados/bundled').then(
|
|
133
|
+
(m) => m.bundledDataset(),
|
|
134
|
+
(e: unknown) => {
|
|
135
|
+
embarcado = undefined;
|
|
136
|
+
throw e;
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
return embarcado;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Cria a calculadora de IBS/CBS sobre o motor do sinete. O `buildNfe` usa uma com as opções padrão quando
|
|
144
|
+
* `options.ibsCbs` não é informado; crie a sua para trocar dataset, alíquotas, base ou regras. Com `dataset` informado
|
|
145
|
+
* o cálculo é síncrono; sem ele, a primeira chamada espera o import do dataset embarcado.
|
|
146
|
+
*/
|
|
147
|
+
export function ibsCbsCalculator(options: IbsCbsCalculatorOptions = {}): IbsCbsCalculator {
|
|
148
|
+
const rates = options.rates ?? officialRates();
|
|
149
|
+
const dataset = options.dataset;
|
|
150
|
+
return {
|
|
151
|
+
calcular(request: {
|
|
152
|
+
readonly nota: IbsCbsNotaRequest;
|
|
153
|
+
readonly itens: readonly IbsCbsItemRequest[];
|
|
154
|
+
}): IbsCbsResponse | Promise<IbsCbsResponse> {
|
|
155
|
+
if (dataset !== undefined) return calcularCom(dataset, rates, options, request);
|
|
156
|
+
return carregarDatasetEmbarcado().then((ds) => calcularCom(ds, rates, options, request));
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function calcularCom(
|
|
162
|
+
dataset: IbsCbsDataset,
|
|
163
|
+
rates: RateProvider,
|
|
164
|
+
options: IbsCbsCalculatorOptions,
|
|
165
|
+
{
|
|
166
|
+
nota,
|
|
167
|
+
itens,
|
|
168
|
+
}: {
|
|
169
|
+
readonly nota: IbsCbsNotaRequest;
|
|
170
|
+
readonly itens: readonly IbsCbsItemRequest[];
|
|
171
|
+
},
|
|
172
|
+
): IbsCbsResponse {
|
|
173
|
+
const issues: ValidationIssue[] = [];
|
|
174
|
+
const classificados: ClassifiedItem[] = [];
|
|
175
|
+
for (const it of itens) {
|
|
176
|
+
if (it.cCredPres !== undefined) {
|
|
177
|
+
// O crédito presumido pede os percentuais por tributo (pCredPres), que a porta não traz.
|
|
178
|
+
issues.push({
|
|
179
|
+
path: caminho(it.nItem),
|
|
180
|
+
code: 'ibscbs_nao_suportado',
|
|
181
|
+
message: 'crédito presumido (cCredPres) precisa do grupo gCredPresOper pronto (ibsCbs.grupo)',
|
|
182
|
+
origem: 'entrada',
|
|
183
|
+
});
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const base = it.vBC !== undefined ? it.vBC.toFixed(2) : options.base?.(it, nota);
|
|
187
|
+
if (base === undefined) {
|
|
188
|
+
issues.push({
|
|
189
|
+
path: `${caminho(it.nItem)}.classificacao.vBC`,
|
|
190
|
+
code: 'ibscbs_base_ausente',
|
|
191
|
+
message:
|
|
192
|
+
'informe vBC do IBS/CBS ou IbsCbsCalculatorOptions.base: a composição da base (UB16-10) ainda não tem regra publicada',
|
|
193
|
+
origem: 'entrada',
|
|
194
|
+
});
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!BASE.test(base)) {
|
|
198
|
+
// Da entrada quando é o vBC do item; da montagem quando veio da função `base` das opções.
|
|
199
|
+
issues.push({
|
|
200
|
+
path: `${caminho(it.nItem)}.classificacao.vBC`,
|
|
201
|
+
code: 'decimal_invalido',
|
|
202
|
+
message: base,
|
|
203
|
+
origem: it.vBC !== undefined ? 'entrada' : 'montagem',
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
classificados.push({
|
|
208
|
+
n: it.nItem,
|
|
209
|
+
cst: it.CST,
|
|
210
|
+
cClassTrib: it.cClassTrib,
|
|
211
|
+
base,
|
|
212
|
+
quantity: it.qTrib.toString(),
|
|
213
|
+
unit: it.uTrib,
|
|
214
|
+
...(it.gTribRegular === undefined
|
|
215
|
+
? {}
|
|
216
|
+
: { regular: { cst: it.gTribRegular.CSTReg, cClassTrib: it.gTribRegular.cClassTribReg } }),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (issues.length > 0) return { itens: [], issues };
|
|
220
|
+
|
|
221
|
+
const op: ClassifiedOperation = {
|
|
222
|
+
modelo: Number(nota.mod),
|
|
223
|
+
place: localDaOperacao(nota),
|
|
224
|
+
...(nota.compraGov === undefined
|
|
225
|
+
? {}
|
|
226
|
+
: {
|
|
227
|
+
governmentPurchase: {
|
|
228
|
+
tpEnteGov: Number(nota.compraGov.tpEnteGov) as TpEnteGov,
|
|
229
|
+
...(nota.compraGov.tpOperGov === '1' || nota.compraGov.tpOperGov === '2'
|
|
230
|
+
? { tpOperGov: Number(nota.compraGov.tpOperGov) as 1 | 2 }
|
|
231
|
+
: {}),
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
items: classificados,
|
|
235
|
+
};
|
|
236
|
+
const time = timeContext({ emissao: fixedClock(nota.emissao), fatoGerador: fixedClock(nota.fatoGerador) });
|
|
237
|
+
let roc: Roc;
|
|
238
|
+
try {
|
|
239
|
+
roc = calculate(op, {
|
|
240
|
+
dataset,
|
|
241
|
+
rates,
|
|
242
|
+
time,
|
|
243
|
+
...(options.utcOffsetMinutes === undefined ? {} : { utcOffsetMinutes: options.utcOffsetMinutes }),
|
|
244
|
+
});
|
|
245
|
+
} catch (e) {
|
|
246
|
+
const issue = ocorrenciaDoMotor(e, itens);
|
|
247
|
+
if (issue === undefined) throw e;
|
|
248
|
+
return { itens: [], issues: [issue] };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// O redutor da compra governamental vai no ide.gCompraGov como a nota informou, mas o motor calcula com o do
|
|
252
|
+
// dataset: se diferirem, os valores do item não fechariam com o cabeçalho emitido.
|
|
253
|
+
const redutor = roc.oper?.gCompraGov.pRedutor;
|
|
254
|
+
if (
|
|
255
|
+
nota.compraGov !== undefined &&
|
|
256
|
+
redutor !== undefined &&
|
|
257
|
+
normalizado(nota.compraGov.pRedutor.toString()) !== normalizado(redutor)
|
|
258
|
+
) {
|
|
259
|
+
issues.push({
|
|
260
|
+
path: 'gCompraGov.pRedutor',
|
|
261
|
+
code: 'ibscbs_redutor_divergente',
|
|
262
|
+
message: `pRedutor informado (${nota.compraGov.pRedutor.toString()}) difere do vigente no fato gerador (${redutor})`,
|
|
263
|
+
origem: 'entrada',
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (options.regras !== false) {
|
|
268
|
+
const doc = documentFromRoc(roc, {
|
|
269
|
+
modelo: op.modelo as 55 | 65,
|
|
270
|
+
crt: Number(nota.emitente.CRT) as 1 | 2 | 3 | 4,
|
|
271
|
+
finNFe: Number(nota.finNFe) as 1 | 2 | 3 | 4 | 5 | 6,
|
|
272
|
+
...(nota.tpNFDebito === undefined ? {} : { tpNFDebito: nota.tpNFDebito }),
|
|
273
|
+
...(nota.tpNFCredito === undefined ? {} : { tpNFCredito: nota.tpNFCredito }),
|
|
274
|
+
emitMun: nota.emitente.cMun,
|
|
275
|
+
...(nota.destino === undefined ? {} : { destMun: nota.destino.cMun }),
|
|
276
|
+
items: itens.map((it) => ({ nItem: it.nItem, ncm: it.NCM, vProd: it.vProd.toFixed(2) })),
|
|
277
|
+
});
|
|
278
|
+
const report = validate(doc, {
|
|
279
|
+
dataset,
|
|
280
|
+
time,
|
|
281
|
+
ambiente: nota.ambiente,
|
|
282
|
+
rates,
|
|
283
|
+
...(options.utcOffsetMinutes === undefined ? {} : { utcOffsetMinutes: options.utcOffsetMinutes }),
|
|
284
|
+
...(options.regras?.rules === undefined ? {} : { rules: options.regras.rules }),
|
|
285
|
+
...(options.regras?.ignoreActivation === undefined ? {} : { ignoreActivation: options.regras.ignoreActivation }),
|
|
286
|
+
});
|
|
287
|
+
for (const v of report.violations) {
|
|
288
|
+
issues.push({
|
|
289
|
+
path: v.item === undefined ? 'total.IBSCBSTot' : caminho(v.item),
|
|
290
|
+
code: 'ibscbs_regra_nt',
|
|
291
|
+
message: `${v.rule} (rejeição ${v.cStat}): ${v.message} [${v.source}]`,
|
|
292
|
+
origem: 'montagem',
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const porItem = new Map(itens.map((it) => [it.nItem, it]));
|
|
298
|
+
return {
|
|
299
|
+
itens: roc.items.map((r) => ({
|
|
300
|
+
nItem: r.nItem,
|
|
301
|
+
IBSCBS: grupoDoLeiaute(r.IBSCBS, porItem.get(r.nItem)?.indDoacao),
|
|
302
|
+
})),
|
|
303
|
+
...(issues.length === 0 ? {} : { issues }),
|
|
304
|
+
};
|
|
305
|
+
}
|