From 20b186a2b7fd8f4fc1bd98dacf53d91c4275e689 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Soares Date: Thu, 13 Aug 2026 23:27:49 -0300 Subject: [PATCH] feat(dps): serializa gReeRepRes no bloco IBSCBS --- README.md | 2 +- src/dps/serializacao.teste.ts | 33 +++++++++++++++++++++ src/dps/serializacao.ts | 56 ++++++++++++++++++++++++++++++++--- src/dps/tipos.ts | 52 ++++++++++++++++++++++++++++++-- 4 files changed, 136 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f3c7509..d285b45 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Em desenvolvimento inicial. A tabela abaixo reflete o estado real de cada módul | --- | --- | --- | | `certificado` | Leitura de certificado A1 (.pfx/.p12) | Feito | | `assinatura` | Assinatura XML (XMLDSig) | Feito | -| `dps` | Tipos e serialização da DPS, incluindo o bloco IBSCBS (XML validado contra o XSD oficial) | Feito; grupo gReeRepRes (reembolso/repasse/ressarcimento de terceiros) ainda não | +| `dps` | Tipos e serialização da DPS, incluindo o bloco IBSCBS completo (XML validado contra o XSD oficial) | Feito | | `cliente` | Cliente HTTP mTLS para o SEFIN Nacional e o ADN (produção e homologação) | Feito | | `danfse` | Geração do DANFSe em PDF (NT 008/2026), incluindo o Canhoto | Feito | diff --git a/src/dps/serializacao.teste.ts b/src/dps/serializacao.teste.ts index 3176ba7..449b354 100644 --- a/src/dps/serializacao.teste.ts +++ b/src/dps/serializacao.teste.ts @@ -112,6 +112,24 @@ test('monta um XML de DPS com bloco IBSCBS valido contra o XSD oficial', () => { }, imovel: { cCIB: 'AB123456' }, valores: { + gReeRepRes: [ + { + dFeNacional: { tipoChaveDFe: '1', chaveDFe: '1'.repeat(50) }, + fornec: { CNPJ: '11222333000181', xNome: 'Fornecedor Exemplo' }, + dtEmiDoc: new Date('2026-06-01T00:00:00Z'), + dtCompDoc: new Date('2026-06-01T00:00:00Z'), + tpReeRepRes: '04', + vlrReeRepRes: 100, + }, + { + docOutro: { nDoc: '123', xDoc: 'Recibo de despesa' }, + dtEmiDoc: new Date('2026-06-02T00:00:00Z'), + dtCompDoc: new Date('2026-06-02T00:00:00Z'), + tpReeRepRes: '99', + xTpReeRepRes: 'Reembolso diverso', + vlrReeRepRes: 50, + }, + ], trib: { gIBSCBS: { CST: '000', @@ -154,3 +172,18 @@ test('rejeita IBSCBS.imovel sem cCIB nem end', () => { }; assert.throws(() => montarXmlDps(dados), ErroValidacaoDps); }); + +test('rejeita documento de gReeRepRes sem dFeNacional, docFiscalOutro ou docOutro', () => { + const dados = dadosDpsExemplo(); + dados.IBSCBS = { + finNFSe: '0', + cIndOp: '000001', + indDest: '0', + valores: { + // @ts-expect-error -- teste propositalmente nao informa nenhuma referencia de documento + gReeRepRes: [{ dtEmiDoc: new Date(), dtCompDoc: new Date(), tpReeRepRes: '01', vlrReeRepRes: 10 }], + trib: { gIBSCBS: { CST: '000', cClassTrib: '000001' } }, + }, + }; + assert.throws(() => montarXmlDps(dados), ErroValidacaoDps); +}); diff --git a/src/dps/serializacao.ts b/src/dps/serializacao.ts index 2699439..a2b215a 100644 --- a/src/dps/serializacao.ts +++ b/src/dps/serializacao.ts @@ -5,8 +5,11 @@ import type { DadosDps, DestinatarioIbscbs, DiferimentoIbscbs, + DocumentoReeRepRes, Endereco, EnderecoImovel, + FornecedorReeRepRes, + IdentificacaoDestinatarioIbscbs, ImovelIbscbs, InfoIbscbs, Pessoa, @@ -164,18 +167,18 @@ function xmlValores(valores: Valores): string { ); } -function xmlIdentificacaoDest(dest: DestinatarioIbscbs): string { +function xmlIdentificacaoDest(dest: IdentificacaoDestinatarioIbscbs, contexto: string): string { if (dest.CNPJ) return tag('CNPJ', dest.CNPJ); if (dest.CPF) return tag('CPF', dest.CPF); if (dest.NIF) return tag('NIF', dest.NIF); if (dest.cNaoNIF) return tag('cNaoNIF', dest.cNaoNIF); - throw new ErroValidacaoDps('IBSCBS.dest precisa informar CNPJ, CPF, NIF ou cNaoNIF.'); + throw new ErroValidacaoDps(`${contexto} precisa informar CNPJ, CPF, NIF ou cNaoNIF.`); } function xmlDest(dest: DestinatarioIbscbs): string { return ( `` + - xmlIdentificacaoDest(dest) + + xmlIdentificacaoDest(dest, 'IBSCBS.dest') + tag('xNome', dest.xNome) + (dest.end ? `${xmlEndereco(dest.end)}` : '') + tag('fone', dest.fone) + @@ -233,7 +236,52 @@ function xmlRefNFSe(refs: string[]): string { return `${refs.map((ref) => tag('refNFSe', ref)).join('')}`; } +function xmlDocumentoReferenciadoReeRepRes(doc: DocumentoReeRepRes): string { + if (doc.dFeNacional) { + const d = doc.dFeNacional; + return `${tag('tipoChaveDFe', d.tipoChaveDFe)}${tag('xTipoChaveDFe', d.xTipoChaveDFe)}${tag('chaveDFe', d.chaveDFe)}`; + } + if (doc.docFiscalOutro) { + const d = doc.docFiscalOutro; + return `${tag('cMunDocFiscal', d.cMunDocFiscal)}${tag('nDocFiscal', d.nDocFiscal)}${tag('xDocFiscal', d.xDocFiscal)}`; + } + if (doc.docOutro) { + const d = doc.docOutro; + return `${tag('nDoc', d.nDoc)}${tag('xDoc', d.xDoc)}`; + } + throw new ErroValidacaoDps( + 'IBSCBS.valores.gReeRepRes: cada documento precisa informar dFeNacional, docFiscalOutro ou docOutro.' + ); +} + +function xmlFornecedorReeRepRes(fornec: FornecedorReeRepRes): string { + return `${xmlIdentificacaoDest(fornec, 'IBSCBS.valores.gReeRepRes.fornec')}${tag('xNome', fornec.xNome)}`; +} + +function xmlDocumentoReeRepRes(doc: DocumentoReeRepRes): string { + return ( + `` + + xmlDocumentoReferenciadoReeRepRes(doc) + + (doc.fornec ? xmlFornecedorReeRepRes(doc.fornec) : '') + + tag('dtEmiDoc', formatarData(doc.dtEmiDoc)) + + tag('dtCompDoc', formatarData(doc.dtCompDoc)) + + tag('tpReeRepRes', doc.tpReeRepRes) + + tag('xTpReeRepRes', doc.xTpReeRepRes) + + tagNum('vlrReeRepRes', doc.vlrReeRepRes) + + `` + ); +} + +function xmlGReeRepRes(documentos: DocumentoReeRepRes[]): string { + return `${documentos.map(xmlDocumentoReeRepRes).join('')}`; +} + function xmlIbscbs(ibscbs: InfoIbscbs): string { + const gReeRepRes = + ibscbs.valores.gReeRepRes && ibscbs.valores.gReeRepRes.length > 0 + ? xmlGReeRepRes(ibscbs.valores.gReeRepRes) + : ''; + return ( `` + tag('finNFSe', ibscbs.finNFSe) + @@ -245,7 +293,7 @@ function xmlIbscbs(ibscbs: InfoIbscbs): string { tag('indDest', ibscbs.indDest) + (ibscbs.dest ? xmlDest(ibscbs.dest) : '') + (ibscbs.imovel ? xmlImovel(ibscbs.imovel) : '') + - `${xmlSituacaoTributariaIbscbs(ibscbs.valores.trib.gIBSCBS)}` + + `${gReeRepRes}${xmlSituacaoTributariaIbscbs(ibscbs.valores.trib.gIBSCBS)}` + `` ); } diff --git a/src/dps/tipos.ts b/src/dps/tipos.ts index 003419e..e9457a1 100644 --- a/src/dps/tipos.ts +++ b/src/dps/tipos.ts @@ -6,8 +6,8 @@ // (sem obra, evento, comercio exterior ou dedução por documentos), valores, // tributacao municipal e federal, e o bloco IBSCBS declarado pelo emitente // (TCRTCInfoIBSCBS: finalidade, destinatario, imovel, situacao/classificacao -// tributaria, tributacao regular e diferimento). O grupo gReeRepRes (valores -// de reembolso/repasse/ressarcimento de terceiros) ainda nao esta tipado. +// tributaria, tributacao regular, diferimento e reembolso/repasse/ressarcimento +// de terceiros via gReeRepRes). export type TipoAmbiente = '1' | '2'; export type TipoEmitenteDps = '1' | '2' | '3'; @@ -186,7 +186,55 @@ export interface SituacaoTributariaIbscbs { gDif?: DiferimentoIbscbs; } +export type TipoChaveDfe = '1' | '2' | '3' | '9'; +export type TipoReembolsoRepasseRessarcimento = '01' | '02' | '03' | '04' | '99'; + +/** Documento fiscal eletrônico do Repositório Nacional (TCRTCListaDocDFe). */ +export interface DocumentoFiscalEletronicoReferenciado { + tipoChaveDFe: TipoChaveDfe; + /** Obrigatório apenas quando tipoChaveDFe = "9" (Outro). */ + xTipoChaveDFe?: string; + chaveDFe: string; +} + +/** Documento fiscal fora do Repositório Nacional (TCRTCListaDocFiscalOutro). */ +export interface DocumentoFiscalOutroReferenciado { + cMunDocFiscal: string; + nDocFiscal: string; + xDocFiscal: string; +} + +/** Documento não fiscal (TCRTCListaDocOutro). */ +export interface DocumentoOutroReferenciado { + nDoc: string; + xDoc: string; +} + +export interface FornecedorReeRepRes extends IdentificacaoDestinatarioIbscbs { + xNome: string; +} + +/** + * Documento referenciado num reembolso, repasse ou ressarcimento de valores + * já tributados por terceiros (TCRTCListaDoc) - exige exatamente uma das três + * formas de identificar o documento. + */ +export interface DocumentoReeRepRes { + dFeNacional?: DocumentoFiscalEletronicoReferenciado; + docFiscalOutro?: DocumentoFiscalOutroReferenciado; + docOutro?: DocumentoOutroReferenciado; + fornec?: FornecedorReeRepRes; + dtEmiDoc: Date; + dtCompDoc: Date; + tpReeRepRes: TipoReembolsoRepasseRessarcimento; + /** Obrigatório apenas quando tpReeRepRes = "99" (Outros). */ + xTpReeRepRes?: string; + vlrReeRepRes: number; +} + export interface ValoresIbscbs { + /** Documentos de reembolso/repasse/ressarcimento (gReeRepRes/documentos), até 1000 ocorrências. */ + gReeRepRes?: DocumentoReeRepRes[]; trib: { gIBSCBS: SituacaoTributariaIbscbs; };