From 1eee1159283d1973262b5517d09ff6a07be5106a Mon Sep 17 00:00:00 2001 From: Vision-Digital Date: Sat, 17 Oct 2026 17:45:25 -0300 Subject: [PATCH 1/3] =?UTF-8?q?feat(dce):=20n=C3=BAcleo=20DC-e=20modelo=20?= =?UTF-8?q?99=20=E2=80=94=20modelo=20XML,=20XSDs,=20chave,=20assinatura=20?= =?UTF-8?q?e=20valida=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementa o modelo 99 conforme os leiautes da NT da DC-e e XSDs v1.00. Preserva chave, assinatura XML, serialização e validações do documento fiscal. Embarca os XSDs oficiais, com CaminhoSchemas opcional sem fallback silencioso. Os testes compartilhados com serviços entram no próximo commit. --- DCe.Classes/DCe.Classes.csproj | 11 + DCe.Classes/Flags/DescricaoEventoDCe.cs | 11 + DCe.Classes/Flags/ModalidadeTransporteDCe.cs | 17 + DCe.Classes/Flags/ModeloDCe.cs | 11 + DCe.Classes/Flags/NomePaisEmitenteDCe.cs | 14 + DCe.Classes/Flags/PaisEmitenteDCe.cs | 11 + DCe.Classes/Flags/ServicoConsultaDCe.cs | 11 + DCe.Classes/Flags/ServicoStatusDCe.cs | 11 + DCe.Classes/Flags/TCOrgaoIBGE.cs | 101 ++ DCe.Classes/Flags/TCRT.cs | 17 + DCe.Classes/Flags/TCodUfIBGE_EX.cs | 92 ++ DCe.Classes/Flags/TDoc.cs | 23 + DCe.Classes/Flags/TFinDCe.cs | 17 + DCe.Classes/Flags/TProcEmi.cs | 14 + DCe.Classes/Flags/TUf.cs | 92 ++ DCe.Classes/Flags/TUf_sem_EX.cs | 89 ++ DCe.Classes/Flags/TUtilizacao.cs | 29 + DCe.Classes/Flags/TipoEmissaoDCe.cs | 14 + DCe.Classes/Flags/TipoEmitenteDCe.cs | 23 + DCe.Classes/Flags/TipoEventoDCe.cs | 11 + DCe.Classes/Informacoes/DCe.cs | 37 + DCe.Classes/Informacoes/DCeDest.cs | 32 + DCe.Classes/Informacoes/DCeDet.cs | 34 + DCe.Classes/Informacoes/DCeEmit.cs | 32 + DCe.Classes/Informacoes/DCeIde.cs | 102 ++ DCe.Classes/Informacoes/DCeProd.cs | 83 + DCe.Classes/Informacoes/DCeTotal.cs | 37 + DCe.Classes/Informacoes/DCeTransp.cs | 32 + DCe.Classes/Informacoes/ECT.cs | 23 + DCe.Classes/Informacoes/Fisco.cs | 26 + DCe.Classes/Informacoes/Marketplace.cs | 26 + DCe.Classes/Informacoes/Transportadora.cs | 23 + DCe.Classes/Informacoes/autXML.cs | 23 + DCe.Classes/Informacoes/enderDest.cs | 62 + DCe.Classes/Informacoes/enderEmit.cs | 59 + DCe.Classes/Informacoes/infAdic.cs | 41 + DCe.Classes/Informacoes/infDCe.cs | 102 ++ DCe.Classes/Informacoes/infDCeSupl.cs | 23 + DCe.Classes/Informacoes/infDec.cs | 39 + DCe.Classes/Informacoes/infSolicDCe.cs | 20 + DCe.Classes/Informacoes/obsECT.cs | 23 + DCe.Classes/Informacoes/obsEmit.cs | 23 + DCe.Classes/Informacoes/obsFisco.cs | 23 + DCe.Classes/Informacoes/obsMarketplace.cs | 23 + DCe.Classes/Retorno/RetornoBase.cs | 21 + DCe.Classes/Retorno/infEventoRetorno.cs | 112 ++ DCe.Classes/Retorno/infFisco.cs | 34 + DCe.Classes/Retorno/infProt.cs | 130 ++ DCe.Classes/Retorno/procDCe.cs | 65 + DCe.Classes/Retorno/procEventoDCe.cs | 65 + DCe.Classes/Retorno/protDCe.cs | 32 + DCe.Classes/Retorno/retConsSitDCe.cs | 144 ++ DCe.Classes/Retorno/retConsStatServDCe.cs | 160 ++ DCe.Classes/Retorno/retDCe.cs | 128 ++ DCe.Classes/Retorno/retEventoDCe.cs | 66 + .../consulta/consSitDCeTiposBasico_v1.00.xsd | 123 ++ .../Schemas/consulta/consSitDCe_v1.00.xsd | 10 + .../consStatServDCeTiposBasico_v1.00.xsd | 110 ++ .../consulta/consStatServDCe_v1.00.xsd | 10 + .../Schemas/consulta/dceTiposBasico_v1.00.xsd | 1388 ++++++++++++++++ .../Schemas/consulta/retConsSitDCe_v1.00.xsd | 10 + .../consulta/retConsStatServDCe_v1.00.xsd | 10 + .../Schemas/consulta/tiposGeralDCe_v1.00.xsd | 712 +++++++++ .../consulta/xmldsig-core-schema_v1.01.xsd | 98 ++ .../Schemas/dce/dceTiposBasico_v1.00.xsd | 1410 +++++++++++++++++ DCe.Classes/Schemas/dce/dce_v1.00.xsd | 10 + DCe.Classes/Schemas/dce/procDCe_v1.00.xsd | 37 + DCe.Classes/Schemas/dce/retDCe_v1.00.xsd | 9 + .../Schemas/dce/tiposGeralDCe_v1.00.xsd | 712 +++++++++ .../Schemas/dce/xmldsig-core-schema_v1.01.xsd | 98 ++ .../Schemas/evento/evCancDCe_v1.00.xsd | 36 + .../evento/eventoDCeTiposBasico_v1.00.xsd | 283 ++++ .../Schemas/evento/eventoDCe_v1.00.xsd | 9 + .../Schemas/evento/procEventoDCe_v1.00.xsd | 10 + .../Schemas/evento/retEventoDCe_v1.00.xsd | 9 + .../Schemas/evento/tiposGeralDCe_v1.00.xsd | 712 +++++++++ .../evento/xmldsig-core-schema_v1.01.xsd | 98 ++ DCe.Classes/Servicos/Consulta/consSitDCe.cs | 29 + DCe.Classes/Servicos/Evento/detEvento.cs | 23 + DCe.Classes/Servicos/Evento/evCancDCe.cs | 26 + DCe.Classes/Servicos/Evento/eventoDCe.cs | 26 + DCe.Classes/Servicos/Evento/infEvento.cs | 70 + .../Servicos/StatusServico/consStatServDCe.cs | 29 + DCe.Utils/Abstractions/IClock.cs | 16 + DCe.Utils/Abstractions/IRandomSource.cs | 27 + DCe.Utils/DCe.Utils.csproj | 11 + DCe.Utils/DceAssinatura.cs | 26 + DCe.Utils/DceChaveAcesso.cs | 78 + DCe.Utils/DcePrazoCancelamento.cs | 28 + DCe.Utils/DceSchemaLoader.cs | 76 + DCe.Utils/Extensoes/ExtDCe.cs | 114 ++ DCe.Utils/Extensoes/ExtDCeFiscal.cs | 103 ++ DCe.Utils/Ibge/UnidadesFederativas.cs | 26 + DCe.Utils/Validacao/Validador.cs | 26 + ZeusFiscal.sln | 24 + 95 files changed, 9186 insertions(+) create mode 100644 DCe.Classes/DCe.Classes.csproj create mode 100644 DCe.Classes/Flags/DescricaoEventoDCe.cs create mode 100644 DCe.Classes/Flags/ModalidadeTransporteDCe.cs create mode 100644 DCe.Classes/Flags/ModeloDCe.cs create mode 100644 DCe.Classes/Flags/NomePaisEmitenteDCe.cs create mode 100644 DCe.Classes/Flags/PaisEmitenteDCe.cs create mode 100644 DCe.Classes/Flags/ServicoConsultaDCe.cs create mode 100644 DCe.Classes/Flags/ServicoStatusDCe.cs create mode 100644 DCe.Classes/Flags/TCOrgaoIBGE.cs create mode 100644 DCe.Classes/Flags/TCRT.cs create mode 100644 DCe.Classes/Flags/TCodUfIBGE_EX.cs create mode 100644 DCe.Classes/Flags/TDoc.cs create mode 100644 DCe.Classes/Flags/TFinDCe.cs create mode 100644 DCe.Classes/Flags/TProcEmi.cs create mode 100644 DCe.Classes/Flags/TUf.cs create mode 100644 DCe.Classes/Flags/TUf_sem_EX.cs create mode 100644 DCe.Classes/Flags/TUtilizacao.cs create mode 100644 DCe.Classes/Flags/TipoEmissaoDCe.cs create mode 100644 DCe.Classes/Flags/TipoEmitenteDCe.cs create mode 100644 DCe.Classes/Flags/TipoEventoDCe.cs create mode 100644 DCe.Classes/Informacoes/DCe.cs create mode 100644 DCe.Classes/Informacoes/DCeDest.cs create mode 100644 DCe.Classes/Informacoes/DCeDet.cs create mode 100644 DCe.Classes/Informacoes/DCeEmit.cs create mode 100644 DCe.Classes/Informacoes/DCeIde.cs create mode 100644 DCe.Classes/Informacoes/DCeProd.cs create mode 100644 DCe.Classes/Informacoes/DCeTotal.cs create mode 100644 DCe.Classes/Informacoes/DCeTransp.cs create mode 100644 DCe.Classes/Informacoes/ECT.cs create mode 100644 DCe.Classes/Informacoes/Fisco.cs create mode 100644 DCe.Classes/Informacoes/Marketplace.cs create mode 100644 DCe.Classes/Informacoes/Transportadora.cs create mode 100644 DCe.Classes/Informacoes/autXML.cs create mode 100644 DCe.Classes/Informacoes/enderDest.cs create mode 100644 DCe.Classes/Informacoes/enderEmit.cs create mode 100644 DCe.Classes/Informacoes/infAdic.cs create mode 100644 DCe.Classes/Informacoes/infDCe.cs create mode 100644 DCe.Classes/Informacoes/infDCeSupl.cs create mode 100644 DCe.Classes/Informacoes/infDec.cs create mode 100644 DCe.Classes/Informacoes/infSolicDCe.cs create mode 100644 DCe.Classes/Informacoes/obsECT.cs create mode 100644 DCe.Classes/Informacoes/obsEmit.cs create mode 100644 DCe.Classes/Informacoes/obsFisco.cs create mode 100644 DCe.Classes/Informacoes/obsMarketplace.cs create mode 100644 DCe.Classes/Retorno/RetornoBase.cs create mode 100644 DCe.Classes/Retorno/infEventoRetorno.cs create mode 100644 DCe.Classes/Retorno/infFisco.cs create mode 100644 DCe.Classes/Retorno/infProt.cs create mode 100644 DCe.Classes/Retorno/procDCe.cs create mode 100644 DCe.Classes/Retorno/procEventoDCe.cs create mode 100644 DCe.Classes/Retorno/protDCe.cs create mode 100644 DCe.Classes/Retorno/retConsSitDCe.cs create mode 100644 DCe.Classes/Retorno/retConsStatServDCe.cs create mode 100644 DCe.Classes/Retorno/retDCe.cs create mode 100644 DCe.Classes/Retorno/retEventoDCe.cs create mode 100644 DCe.Classes/Schemas/consulta/consSitDCeTiposBasico_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/consSitDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/consStatServDCeTiposBasico_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/consStatServDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/dceTiposBasico_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/retConsSitDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/retConsStatServDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/tiposGeralDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/consulta/xmldsig-core-schema_v1.01.xsd create mode 100644 DCe.Classes/Schemas/dce/dceTiposBasico_v1.00.xsd create mode 100644 DCe.Classes/Schemas/dce/dce_v1.00.xsd create mode 100644 DCe.Classes/Schemas/dce/procDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/dce/retDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/dce/tiposGeralDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/dce/xmldsig-core-schema_v1.01.xsd create mode 100644 DCe.Classes/Schemas/evento/evCancDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/evento/eventoDCeTiposBasico_v1.00.xsd create mode 100644 DCe.Classes/Schemas/evento/eventoDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/evento/procEventoDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/evento/retEventoDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/evento/tiposGeralDCe_v1.00.xsd create mode 100644 DCe.Classes/Schemas/evento/xmldsig-core-schema_v1.01.xsd create mode 100644 DCe.Classes/Servicos/Consulta/consSitDCe.cs create mode 100644 DCe.Classes/Servicos/Evento/detEvento.cs create mode 100644 DCe.Classes/Servicos/Evento/evCancDCe.cs create mode 100644 DCe.Classes/Servicos/Evento/eventoDCe.cs create mode 100644 DCe.Classes/Servicos/Evento/infEvento.cs create mode 100644 DCe.Classes/Servicos/StatusServico/consStatServDCe.cs create mode 100644 DCe.Utils/Abstractions/IClock.cs create mode 100644 DCe.Utils/Abstractions/IRandomSource.cs create mode 100644 DCe.Utils/DCe.Utils.csproj create mode 100644 DCe.Utils/DceAssinatura.cs create mode 100644 DCe.Utils/DceChaveAcesso.cs create mode 100644 DCe.Utils/DcePrazoCancelamento.cs create mode 100644 DCe.Utils/DceSchemaLoader.cs create mode 100644 DCe.Utils/Extensoes/ExtDCe.cs create mode 100644 DCe.Utils/Extensoes/ExtDCeFiscal.cs create mode 100644 DCe.Utils/Ibge/UnidadesFederativas.cs create mode 100644 DCe.Utils/Validacao/Validador.cs diff --git a/DCe.Classes/DCe.Classes.csproj b/DCe.Classes/DCe.Classes.csproj new file mode 100644 index 000000000..e5a741b98 --- /dev/null +++ b/DCe.Classes/DCe.Classes.csproj @@ -0,0 +1,11 @@ + + + net462;netstandard2.0;net8.0;net10.0 + latest + enable + + + + + + diff --git a/DCe.Classes/Flags/DescricaoEventoDCe.cs b/DCe.Classes/Flags/DescricaoEventoDCe.cs new file mode 100644 index 000000000..f50e924bc --- /dev/null +++ b/DCe.Classes/Flags/DescricaoEventoDCe.cs @@ -0,0 +1,11 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML DescricaoEventoDCe do leiaute DC-e. +public enum DescricaoEventoDCe +{ + [XmlEnum("Cancelamento")] + Cancelamento, + +} diff --git a/DCe.Classes/Flags/ModalidadeTransporteDCe.cs b/DCe.Classes/Flags/ModalidadeTransporteDCe.cs new file mode 100644 index 000000000..c772b8778 --- /dev/null +++ b/DCe.Classes/Flags/ModalidadeTransporteDCe.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML ModalidadeTransporteDCe do leiaute DC-e. +public enum ModalidadeTransporteDCe +{ + [XmlEnum("0")] + Correios = 0, + + [XmlEnum("1")] + Proprio = 1, + + [XmlEnum("2")] + Transportadora = 2, + +} diff --git a/DCe.Classes/Flags/ModeloDCe.cs b/DCe.Classes/Flags/ModeloDCe.cs new file mode 100644 index 000000000..f81057f88 --- /dev/null +++ b/DCe.Classes/Flags/ModeloDCe.cs @@ -0,0 +1,11 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML ModeloDCe do leiaute DC-e. +public enum ModeloDCe +{ + [XmlEnum("99")] + DeclaracaoConteudo = 99, + +} diff --git a/DCe.Classes/Flags/NomePaisEmitenteDCe.cs b/DCe.Classes/Flags/NomePaisEmitenteDCe.cs new file mode 100644 index 000000000..6c0acaee2 --- /dev/null +++ b/DCe.Classes/Flags/NomePaisEmitenteDCe.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML NomePaisEmitenteDCe do leiaute DC-e. +public enum NomePaisEmitenteDCe +{ + [XmlEnum("Brasil")] + Brasil, + + [XmlEnum("BRASIL")] + BRASIL, + +} diff --git a/DCe.Classes/Flags/PaisEmitenteDCe.cs b/DCe.Classes/Flags/PaisEmitenteDCe.cs new file mode 100644 index 000000000..e1d0367c7 --- /dev/null +++ b/DCe.Classes/Flags/PaisEmitenteDCe.cs @@ -0,0 +1,11 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML PaisEmitenteDCe do leiaute DC-e. +public enum PaisEmitenteDCe +{ + [XmlEnum("1058")] + Brasil = 1058, + +} diff --git a/DCe.Classes/Flags/ServicoConsultaDCe.cs b/DCe.Classes/Flags/ServicoConsultaDCe.cs new file mode 100644 index 000000000..ff77144bc --- /dev/null +++ b/DCe.Classes/Flags/ServicoConsultaDCe.cs @@ -0,0 +1,11 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML ServicoConsultaDCe do leiaute DC-e. +public enum ServicoConsultaDCe +{ + [XmlEnum("CONSULTAR")] + Consultar, + +} diff --git a/DCe.Classes/Flags/ServicoStatusDCe.cs b/DCe.Classes/Flags/ServicoStatusDCe.cs new file mode 100644 index 000000000..8af3f1808 --- /dev/null +++ b/DCe.Classes/Flags/ServicoStatusDCe.cs @@ -0,0 +1,11 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML ServicoStatusDCe do leiaute DC-e. +public enum ServicoStatusDCe +{ + [XmlEnum("STATUS")] + Status, + +} diff --git a/DCe.Classes/Flags/TCOrgaoIBGE.cs b/DCe.Classes/Flags/TCOrgaoIBGE.cs new file mode 100644 index 000000000..f959e54c0 --- /dev/null +++ b/DCe.Classes/Flags/TCOrgaoIBGE.cs @@ -0,0 +1,101 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TCOrgaoIBGE do leiaute DC-e. +public enum TCOrgaoIBGE +{ + [XmlEnum("11")] + Valor11 = 11, + + [XmlEnum("12")] + Valor12 = 12, + + [XmlEnum("13")] + Valor13 = 13, + + [XmlEnum("14")] + Valor14 = 14, + + [XmlEnum("15")] + Valor15 = 15, + + [XmlEnum("16")] + Valor16 = 16, + + [XmlEnum("17")] + Valor17 = 17, + + [XmlEnum("21")] + Valor21 = 21, + + [XmlEnum("22")] + Valor22 = 22, + + [XmlEnum("23")] + Valor23 = 23, + + [XmlEnum("24")] + Valor24 = 24, + + [XmlEnum("25")] + Valor25 = 25, + + [XmlEnum("26")] + Valor26 = 26, + + [XmlEnum("27")] + Valor27 = 27, + + [XmlEnum("28")] + Valor28 = 28, + + [XmlEnum("29")] + Valor29 = 29, + + [XmlEnum("31")] + Valor31 = 31, + + [XmlEnum("32")] + Valor32 = 32, + + [XmlEnum("33")] + Valor33 = 33, + + [XmlEnum("35")] + Valor35 = 35, + + [XmlEnum("41")] + Valor41 = 41, + + [XmlEnum("42")] + Valor42 = 42, + + [XmlEnum("43")] + Valor43 = 43, + + [XmlEnum("50")] + Valor50 = 50, + + [XmlEnum("51")] + Valor51 = 51, + + [XmlEnum("52")] + Valor52 = 52, + + [XmlEnum("53")] + Valor53 = 53, + + [XmlEnum("90")] + Valor90 = 90, + + [XmlEnum("91")] + Valor91 = 91, + + [XmlEnum("92")] + Valor92 = 92, + + [XmlEnum("93")] + Valor93 = 93, + +} diff --git a/DCe.Classes/Flags/TCRT.cs b/DCe.Classes/Flags/TCRT.cs new file mode 100644 index 000000000..4195d3a80 --- /dev/null +++ b/DCe.Classes/Flags/TCRT.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TCRT do leiaute DC-e. +public enum TCRT +{ + [XmlEnum("1")] + Valor1 = 1, + + [XmlEnum("2")] + Valor2 = 2, + + [XmlEnum("3")] + Valor3 = 3, + +} diff --git a/DCe.Classes/Flags/TCodUfIBGE_EX.cs b/DCe.Classes/Flags/TCodUfIBGE_EX.cs new file mode 100644 index 000000000..0105edc92 --- /dev/null +++ b/DCe.Classes/Flags/TCodUfIBGE_EX.cs @@ -0,0 +1,92 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TCodUfIBGE_EX do leiaute DC-e. +public enum TCodUfIBGE_EX +{ + [XmlEnum("11")] + Valor11 = 11, + + [XmlEnum("12")] + Valor12 = 12, + + [XmlEnum("13")] + Valor13 = 13, + + [XmlEnum("14")] + Valor14 = 14, + + [XmlEnum("15")] + Valor15 = 15, + + [XmlEnum("16")] + Valor16 = 16, + + [XmlEnum("17")] + Valor17 = 17, + + [XmlEnum("21")] + Valor21 = 21, + + [XmlEnum("22")] + Valor22 = 22, + + [XmlEnum("23")] + Valor23 = 23, + + [XmlEnum("24")] + Valor24 = 24, + + [XmlEnum("25")] + Valor25 = 25, + + [XmlEnum("26")] + Valor26 = 26, + + [XmlEnum("27")] + Valor27 = 27, + + [XmlEnum("28")] + Valor28 = 28, + + [XmlEnum("29")] + Valor29 = 29, + + [XmlEnum("31")] + Valor31 = 31, + + [XmlEnum("32")] + Valor32 = 32, + + [XmlEnum("33")] + Valor33 = 33, + + [XmlEnum("35")] + Valor35 = 35, + + [XmlEnum("41")] + Valor41 = 41, + + [XmlEnum("42")] + Valor42 = 42, + + [XmlEnum("43")] + Valor43 = 43, + + [XmlEnum("50")] + Valor50 = 50, + + [XmlEnum("51")] + Valor51 = 51, + + [XmlEnum("52")] + Valor52 = 52, + + [XmlEnum("53")] + Valor53 = 53, + + [XmlEnum("99")] + Valor99 = 99, + +} diff --git a/DCe.Classes/Flags/TDoc.cs b/DCe.Classes/Flags/TDoc.cs new file mode 100644 index 000000000..b2cdcdb45 --- /dev/null +++ b/DCe.Classes/Flags/TDoc.cs @@ -0,0 +1,23 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TDoc do leiaute DC-e. +public enum TDoc +{ + [XmlEnum("1")] + Valor1 = 1, + + [XmlEnum("2")] + Valor2 = 2, + + [XmlEnum("3")] + Valor3 = 3, + + [XmlEnum("4")] + Valor4 = 4, + + [XmlEnum("5")] + Valor5 = 5, + +} diff --git a/DCe.Classes/Flags/TFinDCe.cs b/DCe.Classes/Flags/TFinDCe.cs new file mode 100644 index 000000000..95267c9af --- /dev/null +++ b/DCe.Classes/Flags/TFinDCe.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TFinDCe do leiaute DC-e. +public enum TFinDCe +{ + [XmlEnum("0")] + Valor0 = 0, + + [XmlEnum("3")] + Valor3 = 3, + + [XmlEnum("4")] + Valor4 = 4, + +} diff --git a/DCe.Classes/Flags/TProcEmi.cs b/DCe.Classes/Flags/TProcEmi.cs new file mode 100644 index 000000000..a96b281d0 --- /dev/null +++ b/DCe.Classes/Flags/TProcEmi.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TProcEmi do leiaute DC-e. +public enum TProcEmi +{ + [XmlEnum("0")] + Valor0 = 0, + + [XmlEnum("3")] + Valor3 = 3, + +} diff --git a/DCe.Classes/Flags/TUf.cs b/DCe.Classes/Flags/TUf.cs new file mode 100644 index 000000000..cb331d9e2 --- /dev/null +++ b/DCe.Classes/Flags/TUf.cs @@ -0,0 +1,92 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TUf do leiaute DC-e. +public enum TUf +{ + [XmlEnum("AC")] + AC, + + [XmlEnum("AL")] + AL, + + [XmlEnum("AM")] + AM, + + [XmlEnum("AP")] + AP, + + [XmlEnum("BA")] + BA, + + [XmlEnum("CE")] + CE, + + [XmlEnum("DF")] + DF, + + [XmlEnum("ES")] + ES, + + [XmlEnum("GO")] + GO, + + [XmlEnum("MA")] + MA, + + [XmlEnum("MG")] + MG, + + [XmlEnum("MS")] + MS, + + [XmlEnum("MT")] + MT, + + [XmlEnum("PA")] + PA, + + [XmlEnum("PB")] + PB, + + [XmlEnum("PE")] + PE, + + [XmlEnum("PI")] + PI, + + [XmlEnum("PR")] + PR, + + [XmlEnum("RJ")] + RJ, + + [XmlEnum("RN")] + RN, + + [XmlEnum("RO")] + RO, + + [XmlEnum("RR")] + RR, + + [XmlEnum("RS")] + RS, + + [XmlEnum("SC")] + SC, + + [XmlEnum("SE")] + SE, + + [XmlEnum("SP")] + SP, + + [XmlEnum("TO")] + TO, + + [XmlEnum("EX")] + EX, + +} diff --git a/DCe.Classes/Flags/TUf_sem_EX.cs b/DCe.Classes/Flags/TUf_sem_EX.cs new file mode 100644 index 000000000..708287a81 --- /dev/null +++ b/DCe.Classes/Flags/TUf_sem_EX.cs @@ -0,0 +1,89 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TUf_sem_EX do leiaute DC-e. +public enum TUf_sem_EX +{ + [XmlEnum("AC")] + AC, + + [XmlEnum("AL")] + AL, + + [XmlEnum("AM")] + AM, + + [XmlEnum("AP")] + AP, + + [XmlEnum("BA")] + BA, + + [XmlEnum("CE")] + CE, + + [XmlEnum("DF")] + DF, + + [XmlEnum("ES")] + ES, + + [XmlEnum("GO")] + GO, + + [XmlEnum("MA")] + MA, + + [XmlEnum("MG")] + MG, + + [XmlEnum("MS")] + MS, + + [XmlEnum("MT")] + MT, + + [XmlEnum("PA")] + PA, + + [XmlEnum("PB")] + PB, + + [XmlEnum("PE")] + PE, + + [XmlEnum("PI")] + PI, + + [XmlEnum("PR")] + PR, + + [XmlEnum("RJ")] + RJ, + + [XmlEnum("RN")] + RN, + + [XmlEnum("RO")] + RO, + + [XmlEnum("RR")] + RR, + + [XmlEnum("RS")] + RS, + + [XmlEnum("SC")] + SC, + + [XmlEnum("SE")] + SE, + + [XmlEnum("SP")] + SP, + + [XmlEnum("TO")] + TO, + +} diff --git a/DCe.Classes/Flags/TUtilizacao.cs b/DCe.Classes/Flags/TUtilizacao.cs new file mode 100644 index 000000000..bc4c90ccd --- /dev/null +++ b/DCe.Classes/Flags/TUtilizacao.cs @@ -0,0 +1,29 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TUtilizacao do leiaute DC-e. +public enum TUtilizacao +{ + [XmlEnum("1")] + Valor1 = 1, + + [XmlEnum("2")] + Valor2 = 2, + + [XmlEnum("3")] + Valor3 = 3, + + [XmlEnum("4")] + Valor4 = 4, + + [XmlEnum("5")] + Valor5 = 5, + + [XmlEnum("6")] + Valor6 = 6, + + [XmlEnum("7")] + Valor7 = 7, + +} diff --git a/DCe.Classes/Flags/TipoEmissaoDCe.cs b/DCe.Classes/Flags/TipoEmissaoDCe.cs new file mode 100644 index 000000000..ca95e8bfa --- /dev/null +++ b/DCe.Classes/Flags/TipoEmissaoDCe.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TipoEmissaoDCe do leiaute DC-e. +public enum TipoEmissaoDCe +{ + [XmlEnum("1")] + Normal = 1, + + [XmlEnum("2")] + Contingencia = 2, + +} diff --git a/DCe.Classes/Flags/TipoEmitenteDCe.cs b/DCe.Classes/Flags/TipoEmitenteDCe.cs new file mode 100644 index 000000000..22c142940 --- /dev/null +++ b/DCe.Classes/Flags/TipoEmitenteDCe.cs @@ -0,0 +1,23 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TipoEmitenteDCe do leiaute DC-e. +public enum TipoEmitenteDCe +{ + [XmlEnum("0")] + Fisco = 0, + + [XmlEnum("1")] + Marketplace = 1, + + [XmlEnum("2")] + Proprio = 2, + + [XmlEnum("3")] + Transportadora = 3, + + [XmlEnum("4")] + ECT = 4, + +} diff --git a/DCe.Classes/Flags/TipoEventoDCe.cs b/DCe.Classes/Flags/TipoEventoDCe.cs new file mode 100644 index 000000000..6097e9c8e --- /dev/null +++ b/DCe.Classes/Flags/TipoEventoDCe.cs @@ -0,0 +1,11 @@ +using System.Xml.Serialization; + +namespace DCe.Classes.Flags; + +/// Domínio XML TipoEventoDCe do leiaute DC-e. +public enum TipoEventoDCe +{ + [XmlEnum("110111")] + Cancelamento = 110111, + +} diff --git a/DCe.Classes/Informacoes/DCe.cs b/DCe.Classes/Informacoes/DCe.cs new file mode 100644 index 000000000..079d53197 --- /dev/null +++ b/DCe.Classes/Informacoes/DCe.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("DCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCe +{ + private infDCe _infDCe = new infDCe(); + [XmlElement("infDCe", Order = 0)] + public infDCe infDCe + { + get => _infDCe; + set { _infDCe = value; infDCeSpecified = true; } + } + + [XmlElement("infSolicDCe", Order = 1)] + public infSolicDCe infSolicDCe { get; set; } = new infSolicDCe(); + + [XmlElement("infDCeSupl", Order = 2)] + public infDCeSupl? infDCeSupl { get; set; } + + [XmlElement("Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#", Order = 3)] + public Signature? Signature { get; set; } + + [XmlIgnore] + public bool infDCeSpecified { get; set; } + +} diff --git a/DCe.Classes/Informacoes/DCeDest.cs b/DCe.Classes/Informacoes/DCeDest.cs new file mode 100644 index 000000000..f6b55bc1e --- /dev/null +++ b/DCe.Classes/Informacoes/DCeDest.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("dest", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCeDest +{ + [XmlElement("CNPJ", Order = 0)] + public string? CNPJ { get; set; } + + [XmlElement("CPF", Order = 1)] + public string? CPF { get; set; } + + [XmlElement("idOutros", Order = 2)] + public string? idOutros { get; set; } + + [XmlElement("xNome", Order = 3)] + public string xNome { get; set; } = string.Empty; + + [XmlElement("enderDest", Order = 4)] + public enderDest enderDest { get; set; } = new enderDest(); + +} diff --git a/DCe.Classes/Informacoes/DCeDet.cs b/DCe.Classes/Informacoes/DCeDet.cs new file mode 100644 index 000000000..8cd5c7adf --- /dev/null +++ b/DCe.Classes/Informacoes/DCeDet.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("det", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCeDet +{ + private int _nItem = default; + [XmlAttribute("nItem")] + public int nItem + { + get => _nItem; + set { _nItem = value; nItemSpecified = true; } + } + + [XmlElement("prod", Order = 0)] + public DCeProd prod { get; set; } = new DCeProd(); + + [XmlElement("infAdProd", Order = 1)] + public string? infAdProd { get; set; } + + [XmlIgnore] + public bool nItemSpecified { get; set; } + +} diff --git a/DCe.Classes/Informacoes/DCeEmit.cs b/DCe.Classes/Informacoes/DCeEmit.cs new file mode 100644 index 000000000..8c5ee1d83 --- /dev/null +++ b/DCe.Classes/Informacoes/DCeEmit.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("emit", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCeEmit +{ + [XmlElement("CNPJ", Order = 0)] + public string? CNPJ { get; set; } + + [XmlElement("CPF", Order = 1)] + public string? CPF { get; set; } + + [XmlElement("idOutros", Order = 2)] + public string? idOutros { get; set; } + + [XmlElement("xNome", Order = 3)] + public string xNome { get; set; } = string.Empty; + + [XmlElement("enderEmit", Order = 4)] + public enderEmit enderEmit { get; set; } = new enderEmit(); + +} diff --git a/DCe.Classes/Informacoes/DCeIde.cs b/DCe.Classes/Informacoes/DCeIde.cs new file mode 100644 index 000000000..b32fa29c0 --- /dev/null +++ b/DCe.Classes/Informacoes/DCeIde.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("ide", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCeIde +{ + [XmlElement("cUF", Order = 0)] + public Estado cUF { get; set; } + + [XmlElement("cDC", Order = 1)] + public string cDC { get; set; } = string.Empty; + + [XmlElement("mod", Order = 2)] + public ModeloDCe mod { get; set; } = ModeloDCe.DeclaracaoConteudo; + + private int _serie = default; + [XmlElement("serie", Order = 3)] + public int serie + { + get => _serie; + set { _serie = value; serieSpecified = true; } + } + + private int _nDC = default; + [XmlElement("nDC", Order = 4)] + public int nDC + { + get => _nDC; + set { _nDC = value; nDCSpecified = true; } + } + + private DateTimeOffset _dhEmi; + private string? _dhEmiXml; + + [XmlIgnore] + public DateTimeOffset dhEmi + { + get => _dhEmi; + set { _dhEmi = value; _dhEmiXml = null; dhEmiProxySpecified = true; } + } + + [XmlElement("dhEmi", Order = 5)] + public string dhEmiProxy + { + get => _dhEmiXml ?? _dhEmi.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set { _dhEmi = DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.None); _dhEmiXml = value; dhEmiProxySpecified = true; } + } + + private TipoEmissaoDCe _tpEmis = TipoEmissaoDCe.Normal; + [XmlElement("tpEmis", Order = 6)] + public TipoEmissaoDCe tpEmis + { + get => _tpEmis; + set { _tpEmis = value; tpEmisSpecified = true; } + } + + [XmlElement("tpEmit", Order = 7)] + public TipoEmitenteDCe tpEmit { get; set; } + + [XmlElement("nSiteAutoriz", Order = 8)] + public int nSiteAutoriz { get; set; } + + [XmlElement("cDV", Order = 9)] + public int cDV { get; set; } + + private TipoAmbiente _tpAmb = default; + [XmlElement("tpAmb", Order = 10)] + public TipoAmbiente tpAmb + { + get => _tpAmb; + set { _tpAmb = value; tpAmbSpecified = true; } + } + + [XmlElement("verProc", Order = 11)] + public string verProc { get; set; } = string.Empty; + + [XmlIgnore] + public bool serieSpecified { get; set; } + + [XmlIgnore] + public bool nDCSpecified { get; set; } + + [XmlIgnore] + public bool tpEmisSpecified { get; set; } + + [XmlIgnore] + public bool tpAmbSpecified { get; set; } + + [XmlIgnore] + public bool dhEmiProxySpecified { get; set; } + +} diff --git a/DCe.Classes/Informacoes/DCeProd.cs b/DCe.Classes/Informacoes/DCeProd.cs new file mode 100644 index 000000000..8ded6940a --- /dev/null +++ b/DCe.Classes/Informacoes/DCeProd.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("prod", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCeProd +{ + [XmlElement("xProd", Order = 0)] + public string xProd { get; set; } = string.Empty; + + [XmlElement("NCM", Order = 1)] + public string NCM { get; set; } = string.Empty; + + private decimal _qCom; + private string? _qComXml; + + [XmlIgnore] + public decimal qCom + { + get => _qCom; + set { _qCom = value; _qComXml = null; qComProxySpecified = true; } + } + + [XmlElement("qCom", Order = 2)] + public string qComProxy + { + get => _qComXml ?? _qCom.ToString("F4", CultureInfo.InvariantCulture); + set { _qCom = decimal.Parse(value, CultureInfo.InvariantCulture); _qComXml = value; qComProxySpecified = true; } + } + + private decimal _vUnCom; + private string? _vUnComXml; + + [XmlIgnore] + public decimal vUnCom + { + get => _vUnCom; + set { _vUnCom = value; _vUnComXml = null; vUnComProxySpecified = true; } + } + + [XmlElement("vUnCom", Order = 3)] + public string vUnComProxy + { + get => _vUnComXml ?? Math.Round(_vUnCom, 8, MidpointRounding.AwayFromZero).ToString("0.00########", CultureInfo.InvariantCulture); + set { _vUnCom = decimal.Parse(value, CultureInfo.InvariantCulture); _vUnComXml = value; vUnComProxySpecified = true; } + } + + private decimal _vProd; + private string? _vProdXml; + + [XmlIgnore] + public decimal vProd + { + get => _vProd; + set { _vProd = value; _vProdXml = null; vProdProxySpecified = true; } + } + + [XmlElement("vProd", Order = 4)] + public string vProdProxy + { + get => _vProdXml ?? Math.Round(_vProd, 8, MidpointRounding.AwayFromZero).ToString("0.00########", CultureInfo.InvariantCulture); + set { _vProd = decimal.Parse(value, CultureInfo.InvariantCulture); _vProdXml = value; vProdProxySpecified = true; } + } + + [XmlIgnore] + public bool qComProxySpecified { get; set; } + + [XmlIgnore] + public bool vUnComProxySpecified { get; set; } + + [XmlIgnore] + public bool vProdProxySpecified { get; set; } + +} diff --git a/DCe.Classes/Informacoes/DCeTotal.cs b/DCe.Classes/Informacoes/DCeTotal.cs new file mode 100644 index 000000000..3fbdc4a1e --- /dev/null +++ b/DCe.Classes/Informacoes/DCeTotal.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("total", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCeTotal +{ + private decimal _vDC; + private string? _vDCXml; + + [XmlIgnore] + public decimal vDC + { + get => _vDC; + set { _vDC = value; _vDCXml = null; vDCProxySpecified = true; } + } + + [XmlElement("vDC", Order = 0)] + public string vDCProxy + { + get => _vDCXml ?? _vDC.ToString("F2", CultureInfo.InvariantCulture); + set { _vDC = decimal.Parse(value, CultureInfo.InvariantCulture); _vDCXml = value; vDCProxySpecified = true; } + } + + [XmlIgnore] + public bool vDCProxySpecified { get; set; } + +} diff --git a/DCe.Classes/Informacoes/DCeTransp.cs b/DCe.Classes/Informacoes/DCeTransp.cs new file mode 100644 index 000000000..18bede142 --- /dev/null +++ b/DCe.Classes/Informacoes/DCeTransp.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("transp", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class DCeTransp +{ + [XmlElement("modTrans", Order = 0)] + public ModalidadeTransporteDCe modTrans + { + get => _modTrans; + set { _modTrans = value; modTransSpecified = true; } + } + + private ModalidadeTransporteDCe _modTrans; + + [XmlIgnore] + public bool modTransSpecified { get; set; } + + [XmlElement("CNPJTransp", Order = 1)] + public string CNPJTransp { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/ECT.cs b/DCe.Classes/Informacoes/ECT.cs new file mode 100644 index 000000000..e167dd306 --- /dev/null +++ b/DCe.Classes/Informacoes/ECT.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("ECT", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class ECT +{ + [XmlElement("CNPJ", Order = 0)] + public string CNPJ { get; set; } = string.Empty; + + [XmlElement("xNome", Order = 1)] + public string xNome { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/Fisco.cs b/DCe.Classes/Informacoes/Fisco.cs new file mode 100644 index 000000000..c5f36287a --- /dev/null +++ b/DCe.Classes/Informacoes/Fisco.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("Fisco", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class Fisco +{ + [XmlElement("CNPJ", Order = 0)] + public string CNPJ { get; set; } = string.Empty; + + [XmlElement("xOrgao", Order = 1)] + public string xOrgao { get; set; } = string.Empty; + + [XmlElement("UF", Order = 2)] + public TUf UF { get; set; } + +} diff --git a/DCe.Classes/Informacoes/Marketplace.cs b/DCe.Classes/Informacoes/Marketplace.cs new file mode 100644 index 000000000..07d44b343 --- /dev/null +++ b/DCe.Classes/Informacoes/Marketplace.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("Marketplace", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class Marketplace +{ + [XmlElement("CNPJ", Order = 0)] + public string CNPJ { get; set; } = string.Empty; + + [XmlElement("xNome", Order = 1)] + public string xNome { get; set; } = string.Empty; + + [XmlElement("Site", Order = 2)] + public string Site { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/Transportadora.cs b/DCe.Classes/Informacoes/Transportadora.cs new file mode 100644 index 000000000..b544455aa --- /dev/null +++ b/DCe.Classes/Informacoes/Transportadora.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("Transportadora", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class Transportadora +{ + [XmlElement("CNPJ", Order = 0)] + public string CNPJ { get; set; } = string.Empty; + + [XmlElement("xNome", Order = 1)] + public string xNome { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/autXML.cs b/DCe.Classes/Informacoes/autXML.cs new file mode 100644 index 000000000..af39db7be --- /dev/null +++ b/DCe.Classes/Informacoes/autXML.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("autXML", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class autXML +{ + [XmlElement("CNPJ", Order = 0)] + public string? CNPJ { get; set; } + + [XmlElement("CPF", Order = 1)] + public string? CPF { get; set; } + +} diff --git a/DCe.Classes/Informacoes/enderDest.cs b/DCe.Classes/Informacoes/enderDest.cs new file mode 100644 index 000000000..09ca1bdf9 --- /dev/null +++ b/DCe.Classes/Informacoes/enderDest.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("enderDest", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class enderDest +{ + [XmlElement("xLgr", Order = 0)] + public string xLgr { get; set; } = string.Empty; + + [XmlElement("nro", Order = 1)] + public string nro { get; set; } = string.Empty; + + [XmlElement("xCpl", Order = 2)] + public string? xCpl { get; set; } + + [XmlElement("xBairro", Order = 3)] + public string xBairro { get; set; } = string.Empty; + + [XmlElement("cMun", Order = 4)] + public int cMun { get; set; } + + [XmlElement("xMun", Order = 5)] + public string xMun { get; set; } = string.Empty; + + [XmlElement("UF", Order = 6)] + public TUf UF + { + get => _UF; + set { _UF = value; UFSpecified = true; } + } + + private TUf _UF; + + [XmlIgnore] + public bool UFSpecified { get; set; } + + [XmlElement("CEP", Order = 7)] + public string CEP { get; set; } = string.Empty; + + [XmlElement("cPais", Order = 8)] + public int cPais { get; set; } = 1058; + + [XmlElement("xPais", Order = 9)] + public string xPais { get; set; } = "BRASIL"; + + [XmlElement("fone", Order = 10)] + public string? fone { get; set; } + + [XmlElement("email", Order = 11)] + public string? email { get; set; } + +} diff --git a/DCe.Classes/Informacoes/enderEmit.cs b/DCe.Classes/Informacoes/enderEmit.cs new file mode 100644 index 000000000..51578fb06 --- /dev/null +++ b/DCe.Classes/Informacoes/enderEmit.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("enderEmit", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class enderEmit +{ + [XmlElement("xLgr", Order = 0)] + public string xLgr { get; set; } = string.Empty; + + [XmlElement("nro", Order = 1)] + public string nro { get; set; } = string.Empty; + + [XmlElement("xCpl", Order = 2)] + public string? xCpl { get; set; } + + [XmlElement("xBairro", Order = 3)] + public string xBairro { get; set; } = string.Empty; + + [XmlElement("cMun", Order = 4)] + public int cMun { get; set; } + + [XmlElement("xMun", Order = 5)] + public string xMun { get; set; } = string.Empty; + + [XmlElement("UF", Order = 6)] + public TUf UF + { + get => _UF; + set { _UF = value; UFSpecified = true; } + } + + private TUf _UF; + + [XmlIgnore] + public bool UFSpecified { get; set; } + + [XmlElement("CEP", Order = 7)] + public string CEP { get; set; } = string.Empty; + + [XmlElement("cPais", Order = 8)] + public PaisEmitenteDCe cPais { get; set; } = PaisEmitenteDCe.Brasil; + + [XmlElement("xPais", Order = 9)] + public NomePaisEmitenteDCe xPais { get; set; } = NomePaisEmitenteDCe.BRASIL; + + [XmlElement("fone", Order = 10)] + public string? fone { get; set; } + +} diff --git a/DCe.Classes/Informacoes/infAdic.cs b/DCe.Classes/Informacoes/infAdic.cs new file mode 100644 index 000000000..0d315f191 --- /dev/null +++ b/DCe.Classes/Informacoes/infAdic.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infAdic", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infAdic +{ + [XmlElement("infAdFisco", Order = 0)] + public string? infAdFisco { get; set; } + + [XmlElement("infCpl", Order = 1)] + public string? infCpl { get; set; } + + [XmlElement("infAdMarketplace", Order = 2)] + public string? infAdMarketplace { get; set; } + + [XmlElement("infAdECT", Order = 3)] + public string? infAdECT { get; set; } + + [XmlElement("obsEmit", Order = 4)] + public List obsEmit { get; set; } = new List(); + + [XmlElement("obsFisco", Order = 5)] + public List obsFisco { get; set; } = new List(); + + [XmlElement("obsMarketplace", Order = 6)] + public List obsMarketplace { get; set; } = new List(); + + [XmlElement("obsECT", Order = 7)] + public List obsECT { get; set; } = new List(); + +} diff --git a/DCe.Classes/Informacoes/infDCe.cs b/DCe.Classes/Informacoes/infDCe.cs new file mode 100644 index 000000000..0a659b61b --- /dev/null +++ b/DCe.Classes/Informacoes/infDCe.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infDCe +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlAttribute("Id")] + public string Id { get; set; } = string.Empty; + + private DCeIde _ide = new DCeIde(); + [XmlElement("ide", Order = 0)] + public DCeIde ide + { + get => _ide; + set { _ide = value; ideSpecified = true; } + } + + private DCeEmit _emit = new DCeEmit(); + [XmlElement("emit", Order = 1)] + public DCeEmit emit + { + get => _emit; + set { _emit = value; emitSpecified = true; } + } + + [XmlElement("Fisco", Order = 2)] + public Fisco? Fisco { get; set; } + + [XmlElement("Marketplace", Order = 3)] + public Marketplace? Marketplace { get; set; } + + [XmlElement("Transportadora", Order = 4)] + public Transportadora? Transportadora { get; set; } + + [XmlElement("ECT", Order = 5)] + public ECT? ECT { get; set; } + + private DCeDest _dest = new DCeDest(); + [XmlElement("dest", Order = 6)] + public DCeDest dest + { + get => _dest; + set { _dest = value; destSpecified = true; } + } + + [XmlElement("autXML", Order = 7)] + public List autXML { get; set; } = new List(); + + [XmlElement("det", Order = 8)] + public List det { get; set; } = new List(); + + [XmlElement("total", Order = 9)] + public DCeTotal total { get; set; } = new DCeTotal(); + + private DCeTransp _transp = new DCeTransp(); + [XmlElement("transp", Order = 10)] + public DCeTransp transp + { + get => _transp; + set { _transp = value; transpSpecified = true; } + } + + [XmlElement("infAdic", Order = 11)] + public infAdic? infAdic { get; set; } + + private infDec _infDec = new infDec(); + [XmlElement("infDec", Order = 12)] + public infDec infDec + { + get => _infDec; + set { _infDec = value; infDecSpecified = true; } + } + + [XmlIgnore] + public bool ideSpecified { get; set; } + + [XmlIgnore] + public bool emitSpecified { get; set; } + + [XmlIgnore] + public bool destSpecified { get; set; } + + [XmlIgnore] + public bool infDecSpecified { get; set; } + + [XmlIgnore] + public bool transpSpecified { get; set; } + +} diff --git a/DCe.Classes/Informacoes/infDCeSupl.cs b/DCe.Classes/Informacoes/infDCeSupl.cs new file mode 100644 index 000000000..067c5eaec --- /dev/null +++ b/DCe.Classes/Informacoes/infDCeSupl.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infDCeSupl", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infDCeSupl +{ + [XmlElement("qrCodDCe", Order = 0)] + public string qrCodDCe { get; set; } = string.Empty; + + [XmlElement("urlChave", Order = 1)] + public string urlChave { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/infDec.cs b/DCe.Classes/Informacoes/infDec.cs new file mode 100644 index 000000000..ad0840a37 --- /dev/null +++ b/DCe.Classes/Informacoes/infDec.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infDec", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infDec +{ + private string _xObs1 = "É contribuinte de ICMS qualquer pessoa física ou jurídica, que realize, com habitualidade ou em volume que caracterize intuito comercial, operações de circulação de mercadoria ou prestações de serviços de transportes interestadual e intermunicipal e de comunicação, ainda que as operações e prestações de iniciem no exterior (Lei Complementar nº 87/96, Art. 4º)"; + [XmlElement("xObs1", Order = 0)] + public string xObs1 + { + get => _xObs1; + set { _xObs1 = value; xObs1Specified = true; } + } + + private string _xObs2 = "Constitui crime contra a ordem tributária suprimir ou reduzir tributo, ou contribuição social e qualquer acessório: quando negar ou deixar de fornecer, quando obrigatório, nota fiscal ou documento equivalente, relativa a venda de mercadoria ou prestação de serviço, efetivamente realizada ou fornece-la em desacordo com a legislação. Sob pena de reclusão de 2 (dois) e 5 (cinco) anos, e multa (Lei 8.137/90, Art 1ª, V)"; + [XmlElement("xObs2", Order = 1)] + public string xObs2 + { + get => _xObs2; + set { _xObs2 = value; xObs2Specified = true; } + } + + [XmlIgnore] + public bool xObs1Specified { get; set; } + + [XmlIgnore] + public bool xObs2Specified { get; set; } + +} diff --git a/DCe.Classes/Informacoes/infSolicDCe.cs b/DCe.Classes/Informacoes/infSolicDCe.cs new file mode 100644 index 000000000..d47aefdad --- /dev/null +++ b/DCe.Classes/Informacoes/infSolicDCe.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infSolicDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infSolicDCe +{ + [XmlElement("xSolic", Order = 0)] + public string xSolic { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/obsECT.cs b/DCe.Classes/Informacoes/obsECT.cs new file mode 100644 index 000000000..d47ccdf82 --- /dev/null +++ b/DCe.Classes/Informacoes/obsECT.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("obsECT", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class obsECT +{ + [XmlAttribute("xCampo")] + public string xCampo { get; set; } = string.Empty; + + [XmlElement("xTexto", Order = 0)] + public string xTexto { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/obsEmit.cs b/DCe.Classes/Informacoes/obsEmit.cs new file mode 100644 index 000000000..6b57f5a10 --- /dev/null +++ b/DCe.Classes/Informacoes/obsEmit.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("obsEmit", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class obsEmit +{ + [XmlAttribute("xCampo")] + public string xCampo { get; set; } = string.Empty; + + [XmlElement("xTexto", Order = 0)] + public string xTexto { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/obsFisco.cs b/DCe.Classes/Informacoes/obsFisco.cs new file mode 100644 index 000000000..621e74707 --- /dev/null +++ b/DCe.Classes/Informacoes/obsFisco.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("obsFisco", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class obsFisco +{ + [XmlAttribute("xCampo")] + public string xCampo { get; set; } = string.Empty; + + [XmlElement("xTexto", Order = 0)] + public string xTexto { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Informacoes/obsMarketplace.cs b/DCe.Classes/Informacoes/obsMarketplace.cs new file mode 100644 index 000000000..b42d4ab99 --- /dev/null +++ b/DCe.Classes/Informacoes/obsMarketplace.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Informacoes; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("obsMarketplace", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class obsMarketplace +{ + [XmlAttribute("xCampo")] + public string xCampo { get; set; } = string.Empty; + + [XmlElement("xTexto", Order = 0)] + public string xTexto { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Retorno/RetornoBase.cs b/DCe.Classes/Retorno/RetornoBase.cs new file mode 100644 index 000000000..bcf7fc7c0 --- /dev/null +++ b/DCe.Classes/Retorno/RetornoBase.cs @@ -0,0 +1,21 @@ +using System; +using System.Xml.Serialization; + +namespace DCe.Classes.Retorno; + +/// XMLs da comunicação, disponíveis junto ao retorno fiscal tipado. +[Serializable] +public abstract class RetornoBase +{ + /// Mensagem fiscal exatamente como inserida no Body, sem declaração XML. + [XmlIgnore] + public string EnvioStr { get; set; } = string.Empty; + + /// Mensagem fiscal original recebida, sem reserialização. + [XmlIgnore] + public string RetornoStr { get; set; } = string.Empty; + + /// Resposta integral recebida do transporte. + [XmlIgnore] + public string RetornoCompletoStr { get; set; } = string.Empty; +} diff --git a/DCe.Classes/Retorno/infEventoRetorno.cs b/DCe.Classes/Retorno/infEventoRetorno.cs new file mode 100644 index 000000000..099b20ed4 --- /dev/null +++ b/DCe.Classes/Retorno/infEventoRetorno.cs @@ -0,0 +1,112 @@ +using System; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Flags; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infEventoRetorno", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infEventoRetorno +{ + [XmlAttribute("Id")] + public string? Id { get; set; } + + [XmlElement("tpAmb", Order = 0)] + public TipoAmbiente tpAmb + { + get => _tpAmb; + set { _tpAmb = value; tpAmbSpecified = true; } + } + + [XmlIgnore] + public bool tpAmbSpecified { get; set; } + + private TipoAmbiente _tpAmb; + + [XmlElement("verAplic", Order = 1)] + public string verAplic + { + get => _verAplic; + set { _verAplic = value; verAplicSpecified = true; } + } + + [XmlIgnore] + public bool verAplicSpecified { get; set; } + + private string _verAplic = string.Empty; + + [XmlElement("cOrgao", Order = 2)] + public TCOrgaoIBGE cOrgao + { + get => _cOrgao; + set { _cOrgao = value; cOrgaoSpecified = true; } + } + + [XmlIgnore] + public bool cOrgaoSpecified { get; set; } + + private TCOrgaoIBGE _cOrgao; + + [XmlElement("cStat", Order = 3)] + public int cStat + { + get => _cStat; + set { _cStat = value; cStatSpecified = true; } + } + + [XmlIgnore] + public bool cStatSpecified { get; set; } + + private int _cStat; + + [XmlElement("xMotivo", Order = 4)] + public string xMotivo + { + get => _xMotivo; + set { _xMotivo = value; xMotivoSpecified = true; } + } + + [XmlIgnore] + public bool xMotivoSpecified { get; set; } + + private string _xMotivo = string.Empty; + + [XmlElement("chDCe", Order = 5)] + public string? chDCe { get; set; } + + [XmlElement("tpEvento", Order = 6)] + public string? tpEvento { get; set; } + + [XmlElement("xEvento", Order = 7)] + public string? xEvento { get; set; } + + [XmlElement("nSeqEvento", Order = 8)] + public string? nSeqEvento { get; set; } + + private DateTimeOffset? _dhRegEvento; + private string? _dhRegEventoXml; + + [XmlIgnore] + public DateTimeOffset? dhRegEvento + { + get => _dhRegEvento; + set { _dhRegEvento = value; _dhRegEventoXml = null; } + } + + [XmlElement("dhRegEvento", Order = 9)] + public string? dhRegEventoProxy + { + get => _dhRegEventoXml ?? _dhRegEvento?.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhRegEvento = value == null ? (DateTimeOffset?)null : DateTimeOffset.Parse(value!, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhRegEventoXml = value; + } + } + + [XmlElement("nProt", Order = 10)] + public string? nProt { get; set; } +} diff --git a/DCe.Classes/Retorno/infFisco.cs b/DCe.Classes/Retorno/infFisco.cs new file mode 100644 index 000000000..652bc7334 --- /dev/null +++ b/DCe.Classes/Retorno/infFisco.cs @@ -0,0 +1,34 @@ +using System; +using System.Xml.Serialization; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infFisco", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infFisco +{ + [XmlElement("cMsg", Order = 0)] + public int cMsg + { + get => _cMsg; + set { _cMsg = value; cMsgSpecified = true; } + } + + [XmlIgnore] + public bool cMsgSpecified { get; set; } + + private int _cMsg; + + [XmlElement("xMsg", Order = 1)] + public string xMsg + { + get => _xMsg; + set { _xMsg = value; xMsgSpecified = true; } + } + + [XmlIgnore] + public bool xMsgSpecified { get; set; } + + private string _xMsg = string.Empty; +} diff --git a/DCe.Classes/Retorno/infProt.cs b/DCe.Classes/Retorno/infProt.cs new file mode 100644 index 000000000..0f452f60d --- /dev/null +++ b/DCe.Classes/Retorno/infProt.cs @@ -0,0 +1,130 @@ +using System; +using System.Globalization; +using System.Xml.Serialization; +using DFe.Classes.Flags; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infProt", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infProt +{ + [XmlAttribute("Id")] + public string? Id { get; set; } + + [XmlElement("tpAmb", Order = 0)] + public TipoAmbiente tpAmb + { + get => _tpAmb; + set { _tpAmb = value; tpAmbSpecified = true; } + } + + private TipoAmbiente _tpAmb; + + [XmlIgnore] + public bool tpAmbSpecified { get; set; } + + [XmlElement("verAplic", Order = 1)] + public string verAplic + { + get => _verAplic; + set { _verAplic = value; verAplicSpecified = true; } + } + + [XmlIgnore] + public bool verAplicSpecified { get; set; } + + private string _verAplic = string.Empty; + + [XmlElement("chDCe", Order = 2)] + public string chDCe + { + get => _chDCe; + set { _chDCe = value; chDCeSpecified = true; } + } + + [XmlIgnore] + public bool chDCeSpecified { get; set; } + + private string _chDCe = string.Empty; + + [XmlIgnore] + public bool dhRecbtoProxySpecified { get; set; } + + private DateTimeOffset _dhRecbto; + private string? _dhRecbtoXml; + + [XmlIgnore] + public DateTimeOffset dhRecbto + { + get => _dhRecbto; + set { _dhRecbto = value; _dhRecbtoXml = null; dhRecbtoProxySpecified = true; } + } + + [XmlElement("dhRecbto", Order = 3)] + public string dhRecbtoProxy + { + get => _dhRecbtoXml ?? _dhRecbto.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhRecbto = DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhRecbtoXml = value; + dhRecbtoProxySpecified = true; + } + } + + [XmlElement("nProt", Order = 4)] + public string? nProt { get; set; } + + [XmlElement("digVal", Order = 5)] + public string? digVal { get; set; } + + [XmlElement("cStat", Order = 6)] + public int cStat + { + get => _cStat; + set { _cStat = value; cStatSpecified = true; } + } + + [XmlIgnore] + public bool cStatSpecified { get; set; } + + private int _cStat; + + [XmlElement("xMotivo", Order = 7)] + public string xMotivo + { + get => _xMotivo; + set { _xMotivo = value; xMotivoSpecified = true; } + } + + [XmlIgnore] + public bool xMotivoSpecified { get; set; } + + private string _xMotivo = string.Empty; + + [XmlIgnore] + public string Ambiente => tpAmbSpecified ? ((int)tpAmb).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string VersaoAplicativo => verAplic; + + [XmlIgnore] + public string ChaveAcesso => chDCe; + + [XmlIgnore] + public DateTimeOffset? DataRecebimento => dhRecbtoProxySpecified ? dhRecbto : (DateTimeOffset?)null; + + [XmlIgnore] + public string? NumeroProtocolo => nProt; + + [XmlIgnore] + public string? DigestValue => digVal; + + [XmlIgnore] + public string CodigoStatus => cStatSpecified ? cStat.ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string Motivo => xMotivo; +} diff --git a/DCe.Classes/Retorno/procDCe.cs b/DCe.Classes/Retorno/procDCe.cs new file mode 100644 index 000000000..e34d780ae --- /dev/null +++ b/DCe.Classes/Retorno/procDCe.cs @@ -0,0 +1,65 @@ +using System; +using System.Globalization; +using System.Xml.Serialization; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("dceProc", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class procDCe +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlAttribute("ipTransmissor")] + public string? ipTransmissor { get; set; } + + [XmlAttribute("nPortaCon")] + public string? nPortaCon { get; set; } + + private DateTimeOffset? _dhConexao; + private string? _dhConexaoXml; + + [XmlIgnore] + public DateTimeOffset? dhConexao + { + get => _dhConexao; + set { _dhConexao = value; _dhConexaoXml = null; } + } + + [XmlAttribute("dhConexao")] + public string? dhConexaoProxy + { + get => _dhConexaoXml ?? _dhConexao?.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhConexao = value == null ? (DateTimeOffset?)null : DateTimeOffset.Parse(value!, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhConexaoXml = value; + } + } + + [XmlElement("DCe", Order = 0)] + public global::DCe.Classes.Informacoes.DCe? DCe + { + get => _DCe; + set { _DCe = value; DCeSpecified = true; } + } + + [XmlIgnore] + public bool DCeSpecified { get; set; } + + private global::DCe.Classes.Informacoes.DCe? _DCe; + + [XmlElement("protDCe", Order = 1)] + public protDCe? protDCe + { + get => _protDCe; + set { _protDCe = value; protDCeSpecified = true; } + } + + [XmlIgnore] + public bool protDCeSpecified { get; set; } + + private protDCe? _protDCe; +} diff --git a/DCe.Classes/Retorno/procEventoDCe.cs b/DCe.Classes/Retorno/procEventoDCe.cs new file mode 100644 index 000000000..e46ef9df2 --- /dev/null +++ b/DCe.Classes/Retorno/procEventoDCe.cs @@ -0,0 +1,65 @@ +using System; +using System.Globalization; +using System.Xml.Serialization; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("procEventoDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class procEventoDCe +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlAttribute("ipTransmissor")] + public string? ipTransmissor { get; set; } + + [XmlAttribute("nPortaCon")] + public string? nPortaCon { get; set; } + + private DateTimeOffset? _dhConexao; + private string? _dhConexaoXml; + + [XmlIgnore] + public DateTimeOffset? dhConexao + { + get => _dhConexao; + set { _dhConexao = value; _dhConexaoXml = null; } + } + + [XmlAttribute("dhConexao")] + public string? dhConexaoProxy + { + get => _dhConexaoXml ?? _dhConexao?.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhConexao = value == null ? (DateTimeOffset?)null : DateTimeOffset.Parse(value!, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhConexaoXml = value; + } + } + + [XmlElement("eventoDCe", Order = 0)] + public global::DCe.Classes.Servicos.Evento.eventoDCe? eventoDCe + { + get => _eventoDCe; + set { _eventoDCe = value; eventoDCeSpecified = true; } + } + + [XmlIgnore] + public bool eventoDCeSpecified { get; set; } + + private global::DCe.Classes.Servicos.Evento.eventoDCe? _eventoDCe; + + [XmlElement("retEventoDCe", Order = 1)] + public retEventoDCe? retEventoDCe + { + get => _retEventoDCe; + set { _retEventoDCe = value; retEventoDCeSpecified = true; } + } + + [XmlIgnore] + public bool retEventoDCeSpecified { get; set; } + + private retEventoDCe? _retEventoDCe; +} diff --git a/DCe.Classes/Retorno/protDCe.cs b/DCe.Classes/Retorno/protDCe.cs new file mode 100644 index 000000000..c2259aa36 --- /dev/null +++ b/DCe.Classes/Retorno/protDCe.cs @@ -0,0 +1,32 @@ +using System; +using System.Xml.Serialization; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("protDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class protDCe +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("infProt", Order = 0)] + public infProt? infProt + { + get => _infProt; + set { _infProt = value; infProtSpecified = true; } + } + + [XmlIgnore] + public bool infProtSpecified { get; set; } + + private infProt? _infProt; + + [XmlElement("infFisco", Order = 1)] + public infFisco? infFisco { get; set; } + + [XmlElement("Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#", Order = 2)] + public Signature? Signature { get; set; } +} diff --git a/DCe.Classes/Retorno/retConsSitDCe.cs b/DCe.Classes/Retorno/retConsSitDCe.cs new file mode 100644 index 000000000..96be5d08b --- /dev/null +++ b/DCe.Classes/Retorno/retConsSitDCe.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("retConsSitDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class retConsSitDCe : RetornoBase +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("tpAmb", Order = 0)] + public TipoAmbiente tpAmb + { + get => _tpAmb; + set { _tpAmb = value; tpAmbSpecified = true; } + } + + [XmlIgnore] + public bool tpAmbSpecified { get; set; } + + private TipoAmbiente _tpAmb; + + [XmlElement("verAplic", Order = 1)] + public string verAplic + { + get => _verAplic; + set { _verAplic = value; verAplicSpecified = true; } + } + + [XmlIgnore] + public bool verAplicSpecified { get; set; } + + private string _verAplic = string.Empty; + + [XmlElement("cStat", Order = 2)] + public int cStat + { + get => _cStat; + set { _cStat = value; cStatSpecified = true; } + } + + [XmlIgnore] + public bool cStatSpecified { get; set; } + + private int _cStat; + + [XmlElement("xMotivo", Order = 3)] + public string xMotivo + { + get => _xMotivo; + set { _xMotivo = value; xMotivoSpecified = true; } + } + + [XmlIgnore] + public bool xMotivoSpecified { get; set; } + + private string _xMotivo = string.Empty; + + [XmlElement("cUF", Order = 4)] + public Estado cUF + { + get => _cUF; + set { _cUF = value; cUFSpecified = true; } + } + + [XmlIgnore] + public bool cUFSpecified { get; set; } + + private Estado _cUF; + + [XmlIgnore] + public bool dhRecbtoProxySpecified { get; set; } + + private DateTimeOffset _dhRecbto; + private string? _dhRecbtoXml; + + [XmlIgnore] + public DateTimeOffset dhRecbto + { + get => _dhRecbto; + set { _dhRecbto = value; _dhRecbtoXml = null; dhRecbtoProxySpecified = true; } + } + + [XmlElement("dhRecbto", Order = 5)] + public string dhRecbtoProxy + { + get => _dhRecbtoXml ?? _dhRecbto.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhRecbto = DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhRecbtoXml = value; + dhRecbtoProxySpecified = true; + } + } + + [XmlElement("chDCe", Order = 6)] + public string chDCe + { + get => _chDCe; + set { _chDCe = value; chDCeSpecified = true; } + } + + [XmlIgnore] + public bool chDCeSpecified { get; set; } + + private string _chDCe = string.Empty; + + [XmlElement("protDCe", Order = 7)] + public protDCe? protDCe { get; set; } + + [XmlElement("procEventoDCe", Order = 8)] + public List procEventoDCe { get; set; } = new List(); + + [XmlIgnore] + public string Ambiente => tpAmbSpecified ? ((int)tpAmb).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string VersaoAplicativo => verAplic; + + [XmlIgnore] + public string CodigoStatus => cStatSpecified ? cStat.ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string Motivo => xMotivo; + + [XmlIgnore] + public string CodigoUf => cUFSpecified ? ((int)cUF).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public DateTimeOffset? DataRecebimento => dhRecbtoProxySpecified ? dhRecbto : (DateTimeOffset?)null; + + [XmlIgnore] + public string ChaveAcesso => chDCe; + + [XmlIgnore] + public infProt? Protocolo => protDCe?.infProt; +} diff --git a/DCe.Classes/Retorno/retConsStatServDCe.cs b/DCe.Classes/Retorno/retConsStatServDCe.cs new file mode 100644 index 000000000..22e8d31a3 --- /dev/null +++ b/DCe.Classes/Retorno/retConsStatServDCe.cs @@ -0,0 +1,160 @@ +using System; +using System.Globalization; +using System.Xml.Serialization; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("retConsStatServDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class retConsStatServDCe : RetornoBase +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("tpAmb", Order = 0)] + public TipoAmbiente tpAmb + { + get => _tpAmb; + set { _tpAmb = value; tpAmbSpecified = true; } + } + + [XmlIgnore] + public bool tpAmbSpecified { get; set; } + + private TipoAmbiente _tpAmb; + + [XmlElement("verAplic", Order = 1)] + public string verAplic + { + get => _verAplic; + set { _verAplic = value; verAplicSpecified = true; } + } + + [XmlIgnore] + public bool verAplicSpecified { get; set; } + + private string _verAplic = string.Empty; + + [XmlElement("cStat", Order = 2)] + public int cStat + { + get => _cStat; + set { _cStat = value; cStatSpecified = true; } + } + + [XmlIgnore] + public bool cStatSpecified { get; set; } + + private int _cStat; + + [XmlElement("xMotivo", Order = 3)] + public string xMotivo + { + get => _xMotivo; + set { _xMotivo = value; xMotivoSpecified = true; } + } + + [XmlIgnore] + public bool xMotivoSpecified { get; set; } + + private string _xMotivo = string.Empty; + + [XmlElement("cUF", Order = 4)] + public Estado cUF + { + get => _cUF; + set { _cUF = value; cUFSpecified = true; } + } + + [XmlIgnore] + public bool cUFSpecified { get; set; } + + private Estado _cUF; + + [XmlIgnore] + public bool dhRecbtoProxySpecified { get; set; } + + private DateTimeOffset _dhRecbto; + private string? _dhRecbtoXml; + + [XmlIgnore] + public DateTimeOffset dhRecbto + { + get => _dhRecbto; + set { _dhRecbto = value; _dhRecbtoXml = null; dhRecbtoProxySpecified = true; } + } + + [XmlElement("dhRecbto", Order = 5)] + public string dhRecbtoProxy + { + get => _dhRecbtoXml ?? _dhRecbto.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhRecbto = DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhRecbtoXml = value; + dhRecbtoProxySpecified = true; + } + } + + [XmlElement("tMed", Order = 6)] + public int? tMed { get; set; } + + public bool ShouldSerializetMed() => tMed.HasValue; + + private DateTimeOffset? _dhRetorno; + private string? _dhRetornoXml; + + [XmlIgnore] + public DateTimeOffset? dhRetorno + { + get => _dhRetorno; + set { _dhRetorno = value; _dhRetornoXml = null; } + } + + [XmlElement("dhRetorno", Order = 7)] + public string? dhRetornoProxy + { + get => _dhRetornoXml ?? _dhRetorno?.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhRetorno = value == null ? (DateTimeOffset?)null : DateTimeOffset.Parse(value!, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhRetornoXml = value; + } + } + + [XmlElement("xObs", Order = 8)] + public string? xObs { get; set; } + + [XmlIgnore] + public string Ambiente => tpAmbSpecified ? ((int)tpAmb).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string VersaoAplicativo => verAplic; + + [XmlIgnore] + public string CodigoStatus => cStatSpecified ? cStat.ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string Motivo => xMotivo; + + [XmlIgnore] + public string CodigoUf => cUFSpecified ? ((int)cUF).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public DateTimeOffset? DataRecebimento => dhRecbtoProxySpecified ? dhRecbto : (DateTimeOffset?)null; + + [XmlIgnore] + public bool EmOperacao => cStat == 107; + + [XmlIgnore] + public int? TempoMedioResposta => tMed; + + [XmlIgnore] + public DateTimeOffset? PrevisaoRetorno => dhRetorno; + + [XmlIgnore] + public string? Observacao => xObs; +} diff --git a/DCe.Classes/Retorno/retDCe.cs b/DCe.Classes/Retorno/retDCe.cs new file mode 100644 index 000000000..f2ba1535b --- /dev/null +++ b/DCe.Classes/Retorno/retDCe.cs @@ -0,0 +1,128 @@ +using System; +using System.Globalization; +using System.Xml.Serialization; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("retDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class retDCe : RetornoBase +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("tpAmb", Order = 0)] + public TipoAmbiente tpAmb + { + get => _tpAmb; + set { _tpAmb = value; tpAmbSpecified = true; } + } + + [XmlIgnore] + public bool tpAmbSpecified { get; set; } + + private TipoAmbiente _tpAmb; + + [XmlElement("verAplic", Order = 1)] + public string verAplic + { + get => _verAplic; + set { _verAplic = value; verAplicSpecified = true; } + } + + [XmlIgnore] + public bool verAplicSpecified { get; set; } + + private string _verAplic = string.Empty; + + [XmlElement("cStat", Order = 2)] + public int cStat + { + get => _cStat; + set { _cStat = value; cStatSpecified = true; } + } + + [XmlIgnore] + public bool cStatSpecified { get; set; } + + private int _cStat; + + [XmlElement("xMotivo", Order = 3)] + public string xMotivo + { + get => _xMotivo; + set { _xMotivo = value; xMotivoSpecified = true; } + } + + [XmlIgnore] + public bool xMotivoSpecified { get; set; } + + private string _xMotivo = string.Empty; + + [XmlElement("cUF", Order = 4)] + public Estado cUF + { + get => _cUF; + set { _cUF = value; cUFSpecified = true; } + } + + [XmlIgnore] + public bool cUFSpecified { get; set; } + + private Estado _cUF; + + [XmlIgnore] + public bool dhRecbtoProxySpecified { get; set; } + + private DateTimeOffset _dhRecbto; + private string? _dhRecbtoXml; + + [XmlIgnore] + public DateTimeOffset dhRecbto + { + get => _dhRecbto; + set { _dhRecbto = value; _dhRecbtoXml = null; dhRecbtoProxySpecified = true; } + } + + [XmlElement("dhRecbto", Order = 5)] + public string dhRecbtoProxy + { + get => _dhRecbtoXml ?? _dhRecbto.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set + { + _dhRecbto = DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.None); + _dhRecbtoXml = value; + dhRecbtoProxySpecified = true; + } + } + + [XmlElement("protDCe", Order = 6)] + public protDCe? protDCe { get; set; } + + [XmlIgnore] + public string Ambiente => tpAmbSpecified ? ((int)tpAmb).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string VersaoAplicativo => verAplic; + + [XmlIgnore] + public string CodigoStatus => cStatSpecified ? cStat.ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string Motivo => xMotivo; + + [XmlIgnore] + public string CodigoUf => cUFSpecified ? ((int)cUF).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public DateTimeOffset? DataRecebimento => dhRecbtoProxySpecified ? dhRecbto : (DateTimeOffset?)null; + + [XmlIgnore] + public infProt? Protocolo => protDCe?.infProt; + + [XmlIgnore] + public bool Autorizada => cStat == 100; +} diff --git a/DCe.Classes/Retorno/retEventoDCe.cs b/DCe.Classes/Retorno/retEventoDCe.cs new file mode 100644 index 000000000..9fb289dfe --- /dev/null +++ b/DCe.Classes/Retorno/retEventoDCe.cs @@ -0,0 +1,66 @@ +using System; +using System.Globalization; +using System.Xml.Serialization; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Retorno; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("retEventoDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class retEventoDCe : RetornoBase +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("infEvento", Order = 0)] + public infEventoRetorno? infEvento + { + get => _infEvento; + set { _infEvento = value; infEventoSpecified = true; } + } + + [XmlIgnore] + public bool infEventoSpecified { get; set; } + + private infEventoRetorno? _infEvento; + + [XmlElement("Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#", Order = 1)] + public Signature? Signature { get; set; } + + [XmlIgnore] + public string Ambiente => infEvento == null ? string.Empty : ((int)infEvento.tpAmb).ToString(CultureInfo.InvariantCulture); + + [XmlIgnore] + public string VersaoAplicativo => infEvento?.verAplic ?? string.Empty; + + [XmlIgnore] + public string CodigoOrgao => infEvento?.cOrgaoSpecified == true ? ((int)infEvento.cOrgao).ToString(CultureInfo.InvariantCulture) : string.Empty; + + [XmlIgnore] + public string CodigoStatus => infEvento?.cStat.ToString(CultureInfo.InvariantCulture) ?? string.Empty; + + [XmlIgnore] + public string Motivo => infEvento?.xMotivo ?? string.Empty; + + [XmlIgnore] + public string? ChaveAcesso => infEvento?.chDCe; + + [XmlIgnore] + public string? TipoEvento => infEvento?.tpEvento; + + [XmlIgnore] + public string? DescricaoEvento => infEvento?.xEvento; + + [XmlIgnore] + public string? SequenciaEvento => infEvento?.nSeqEvento; + + [XmlIgnore] + public DateTimeOffset? DataRegistro => infEvento?.dhRegEvento; + + [XmlIgnore] + public string? NumeroProtocolo => infEvento?.nProt; + + [XmlIgnore] + public bool Registrado => infEvento?.cStat == 135; +} diff --git a/DCe.Classes/Schemas/consulta/consSitDCeTiposBasico_v1.00.xsd b/DCe.Classes/Schemas/consulta/consSitDCeTiposBasico_v1.00.xsd new file mode 100644 index 000000000..5a8347ba6 --- /dev/null +++ b/DCe.Classes/Schemas/consulta/consSitDCeTiposBasico_v1.00.xsd @@ -0,0 +1,123 @@ + + + + + + + + Tipo Pedido de Consulta da Situação Atual da DCe + + + + + Identificação do Ambiente: +1 - Produção; +2 - Homologação; + + + + + Serviço Solicitado + + + + + Chaves de acesso do DCe + + + + + + + + Tipo Retorno de Pedido de Consulta da Situação Atual de DCe + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Versão do Aplicativo que processou o DCe + + + + + Código do status da mensagem enviada. + + + + + Descrição literal do status do serviço solicitado. + + + + + código da UF de atendimento + + + + + Preenchido com a data e hora do processamento (informado também no caso de rejeição). +Formato: “AAAA-MM-DDThh:mm:ssTZD” (UTC – Universal Coordinated Time). + + + + + Chaves de acesso do DCe + + + + + + + + Retornar protDCe da versão correspondente da DCe autorizada + + + + + + + + + + + + + + + + + + + Retornar procEventoDCe da versão correspondente do evento DCe autorizado + + + + + + + + + + + + + + + + + + + Tipo Versão do Consulta situação de DCe - 1.00 + + + + + + diff --git a/DCe.Classes/Schemas/consulta/consSitDCe_v1.00.xsd b/DCe.Classes/Schemas/consulta/consSitDCe_v1.00.xsd new file mode 100644 index 000000000..102f31b50 --- /dev/null +++ b/DCe.Classes/Schemas/consulta/consSitDCe_v1.00.xsd @@ -0,0 +1,10 @@ + + + + + + + Schema de validação XML do Pedido de Consulta da Situação Atual da DCe + + + diff --git a/DCe.Classes/Schemas/consulta/consStatServDCeTiposBasico_v1.00.xsd b/DCe.Classes/Schemas/consulta/consStatServDCeTiposBasico_v1.00.xsd new file mode 100644 index 000000000..375b4aa38 --- /dev/null +++ b/DCe.Classes/Schemas/consulta/consStatServDCeTiposBasico_v1.00.xsd @@ -0,0 +1,110 @@ + + + + + + + Tipo Pedido de Consulta do Status do Serviço DCe + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + código da UF de atendimento + + + + + Serviço Solicitado + + + + + + + + Tipo Resultado da Consulta do Status do Serviço DCe + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Versão do Aplicativo que processou a DCe + + + + + + + + + + Código do status da mensagem enviada. + + + + + Descrição literal do status do serviço solicitado. + + + + + Código da UF responsável pelo serviço + + + + + AAAA-MM-DDTHH:MM:SS TZD + + + + + Tempo médio de resposta do serviço (em segundos) dos últimos 5 minutos + + + + + + + + + + AAAA-MM-DDTHH:MM:SS TZD. Deve ser preenchida com data e hora previstas para o retorno dos serviços prestados. + + + + + Campo observação utilizado para incluir informações ao contribuinte + + + + + + + + + + + + + + + Tipo Versão do Consulta do Status do Serviço DCe + + + + + + diff --git a/DCe.Classes/Schemas/consulta/consStatServDCe_v1.00.xsd b/DCe.Classes/Schemas/consulta/consStatServDCe_v1.00.xsd new file mode 100644 index 000000000..5fe84bc0b --- /dev/null +++ b/DCe.Classes/Schemas/consulta/consStatServDCe_v1.00.xsd @@ -0,0 +1,10 @@ + + + + + + + Schema XML de validação do Pedido de Consulta do Status do Serviço DCe + + + diff --git a/DCe.Classes/Schemas/consulta/dceTiposBasico_v1.00.xsd b/DCe.Classes/Schemas/consulta/dceTiposBasico_v1.00.xsd new file mode 100644 index 000000000..b2bb16cc1 --- /dev/null +++ b/DCe.Classes/Schemas/consulta/dceTiposBasico_v1.00.xsd @@ -0,0 +1,1388 @@ + + + + + + + + Tipo Declaração de Conteúdo Eletrônica + + + + + Informações da DCe + + + + + + Identificação da DCe + + + + + + Código da UF de emissão e autorização da DCe + Código da UF de emissão e autorização do Documento Fiscal. Utilizar a +Tabela do IBGE de código de unidades da federação + + + + + Código numérico que compõe a Chave de Acesso. + Código aleatório gerado pelo emitente, com o objetivo de evitar acessos indevidos ao documento + + + + + + + + + + + Modelo da DCe + Utilizar o código 99 para identificação da DCe + + + + + Série do documento fiscal + Informar a série do documento fiscal (informar zero para série única) + + + + + Número do documento fiscal + Número que identifica o documento fiscal 1 a 999999999 + + + + + Data e hora de emissão do documento fiscal + Formato UTC (AAAA-MM-DDThh:mm:ssTZD, onde TZD = +hh:mm ou -hh:mm) + + + + + Forma de emissão do Documento Fiscal + 1 - Normal (não contingência); 9 - Contingência off-line da DCe + + + + + Tipo do Emitente da DCe + 0 - App Fisco +1 - Marketplace +2 - Emissor próprio +3 - Transportadora ou ECT + + + + + + + + + + + + + + Identificação do número do Site do Autorizador de recepção da DCe + Se o autorizador da DCe possuir apenas um site deverá ser informado com Zero (0), em caso de Autorizador trabalhar com múltiplos sites indicar o número do site para qual foi endereçada a DCE (1 a 9). + +Observação: o ambiente autorizador que trabalhar com mais de um Site deverá divulgar para cada endereço de site qual número correspondente de nSiteAutoriz o contribuinte pode usar + + + + + + + + + + + Digito verificador da chave de acesso + Informar o dígito de controle da chave de acesso documento fiscal, que deve ser calculado com a aplicação do algoritmo módulo 11 (base 2,9) da chave de acesso + + + + + + + + + + + Tipo do Ambiente + 1 - Produção +2 - Homologação + + + + + Versão do processo de emissão + Informar a versão do aplicativo emissor de DCe + + + + + + + + + + + + + + Identificação da DCe + + + + + + + Número do CNPJ do emitente + + + + + Número do CPF do emitente + Informar os zeros não significativos + + + + + Identificação do destinatário outros + Identificação do emitente não obrigado a inscrição do CPF ou CNPJ tais como estrangeiro, indígena e quilombola. Observação: Campo aceita algarismos, letras (maiúsculas e minúsculas) e os caracteres do conjunto que segue: [:.+-/()] + + + + + + + + + + + + + + Razão social ou Nome do emitente + + + + + + + + + + + Endereço do emitente + + + + + + + + + Identificação do Fisco (uso exclusivo fisco) + + + + + + Número do CNPJ do órgão + + + + + Nome do órgão + + + + + + + + + + + Sigla da UF + + + + + + + + Identificação do Marketplace (uso exclusivo Marketplace) + + + + + + Número do CNPJ do Marketplace + + + + + Razão Social ou Nome do Marketplace + + + + + + + + + + + Endereço ou site do Marketplace + + + + + + + + + + + + + + Identificação da Transportadora (uso exclusivo Transportadora) + + + + + + Número do CNPJ da Transportadora + + + + + Razão Social ou Nome da Transportadora + + + + + + + + + + + + + + Identificação dos correios + + + + + + Número do CNPJ dos correios + + + + + Razão Social ou Nome dos correios + + + + + + + + + + + + + + + Identificação do destinatário da DCe + + + + + + + Número do CNPJ + Em caso de empresa não estabelecida no Brasil, será informado o CNPJ com zeros. + Informar os zeros não significativos + + + + + Número do CPF + Informar os zeros não significativos + + + + + Identificação do destinatário outros + Identificação do destinatário não obrigado a inscrição do CPF tais como estrangeiro, indígena e quilombola; +Em caso de não contar CPF do assinante, informar o RG + + + + + + + + + + + + + + Razão social ou Nome do destinatário + + + + + + + + + + + Endereço do destinatário / assinante + + + + + + + + Autorizados para acessar o XML da DCe + Informar CNPJ ou CPF. Preencher os zeros não significativos + + + + + + + CNPJ do autorizado + Informar zeros não significativos + + + + + CPF do autorizado + Informar zeros não significativos + + + + + + + + + Detalhamento de itens da DCe + + + + + + Dados do Produto ou Serviço + + + + + + Descrição do produto, bem ou mercadoria + + + + + + + + + + + Código NCM + Capítulo do Código NCM com 2 dígitos ou NCM completo + + + + + + + + + + + Quantidade + + + + + Valor unitário do item + + + + + + + + Valor total brutp do item + + + + + + + + + + + Informações adicionais do item + + + + + + + + + + + + Número do item da DCe + + + + + + + + + + + + + Dados dos totais da DCe + + + + + + Valor Total da DCe + + + + + + + + Dados dos transportes da DCe + + + + + + Modalidade do transporte + 0 - Transporte pelos correios; +1 - Transporte por contra própria; +2 - Transporte por empresa transportadora + + + + + + + + + + + + + + Número do CNPJ da Transportadora + + + + + + + + Informações Adicionais + + + + + Sequencia XML + + + + Informações adicionais de interesse do Fisco + Norma referenciada, informações complementares, etc + + + + + + + + + + + Informações complementares de interesse do Emitente + + + + + + + + + + + Informações Adicionais do MarketPlace + + + + + + + + + + + Informações Adicionais do Correio + + + + + + + + + + + Grupo Campo de uso livre do Emitente + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + Grupo Campo de uso livre do Fisco + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + Grupo Campo de uso livre do MarketPlace + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + Grupo Campo de uso livre dos Correios + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + + + + Grupo para Declaraçao do usuário emitente + + + + + Sequencia XML + + + + Observação 1 ao usuário emitente + Deverá conter o texto fixo “É contribuinte de ICMS qualquer pessoa física ou jurídica, que realize, com habitualidade ou em volume que caracterize intuito comercial, operações de circulação de mercadoria ou prestações de serviços de transportes interestadual e intermunicipal e de comunicação, ainda que as operações e prestações de iniciem no exterior (Lei Complementar nº 87/96, Art. 4º)” + + + + + + + + + + + Observação 2 ao usuário emitente + Deverá conter o texto fixo “Constitui crime contra a ordem tributária suprimir ou reduzir tributo, ou contribuição social e qualquer acessório: quando negar ou deixar de fornecer, quando obrigatório, nota fiscal ou documento equivalente, relativa a venda de mercadoria ou prestação de serviço, efetivamente realizada ou fornece-la em desacordo com a legislação. Sob pena de reclusão de 2 (dois) e 5 (cinco) anos, e multa (Lei 8.137/90, Art 1ª, V)”. + + + + + + + + + + + + + + + Versão do leiaute + + + + + Identificador da tag a ser assinada + Informar a chave de acesso precedida do literal "DCe" + + + + + + + + + + + + + + + + Informações suplementares da DCe + + + + + + Texto com o QR-Code impresso da DCe + + + + + + + + + + + + + URL para consulta chave de acesso + + + + + + + + + + + + + + + + + + Tipo Dados do Endereço + + + + + Logradouro + + + + + + + + + + + Número + + + + + + + + + + + Complemento + + + + + + + + + + + Bairro + + + + + + + + + + + Código do município (utilizar a tabela do IBGE) + + + + + Nome do município + + + + + + + + + + + Sigla da UF + + + + + CEP + Informar zeros não significativos + + + + + + + + + + + Código do País + 1058=Brasil + + + + + + + + + + Nome do País exemplo: Brasil ou BRASIL + + + + + + + + + + + Telefone + + + + + + + + + + + + + Tipo Dados do Endereço Destinatario + + + + + Logradouro + + + + + + + + + + + Número + + + + + + + + + + + Complemento + + + + + + + + + + + Bairro + + + + + + + + + + + Código do município (utilizar a tabela do IBGE) + + + + + Nome do município + + + + + + + + + + + Sigla da UF + + + + + CEP + Informar zeros não significativos + + + + + + + + + + + Código do país + Utilizar a tabela do BACEN + + + + + + + + + + + Nome do país + + + + + + + + + + + Telefone + + + + + + + + + + + Endereço de E-mail + + + + + + + Tipo Dados do Endereço + + + + + Logradouro + + + + + + + + + + + Número + + + + + + + + + + + Complemento + + + + + + + + + + + Bairro + + + + + + + + + + + Código do município (utilizar a tabela do IBGE), informar 9999999 para operações com o exterior + + + + + Nome do município, informar EXTERIOR para operações com o exterior + + + + + + + + + + + CEP + Informar os zeros não significativos + + + + + + + + + + + Sigla da UF, informar EX para operações com o exterior + + + + + Código do país + Utilizar a tabela do BACEN + + + + + + + + + + + Nome do país + + + + + + + + + + + Telefone + + + + + + + + + + + Endereço de E-mail + + + + + + + Tipo Protocolo de status resultado do processamento da DCe (Modelo 99) + + + + + Dados do protocolo de status + + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Versão do Aplicativo que processou a DCe + + + + + Chave de acesso da DCe + + + + + Data e hora de processamento, no formato AAAA-MM-DDTHH:MM:SS TZD + + + + + Número do Protocolo de Status da DCe + + + + + Digest Value da DCe processado. Utilizado para conferir a integridade da DCe original + + + + + Código do status da DCe + + + + + + + + Descrição literal do status da DCe + + + + + + + + + Mensagem do Fisco + + + + + + Código do status da mensagem do fisco + + + + + + + + Mensagem do Fisco + + + + + + + + + + + + Tipo Retorno do Pedido de Autorização de DCe + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Versão do Aplicativo que processou a DCe + + + + + código do status da resposta + + + + + Descrição literal do status do do retorno da consulta + + + + + Identificação da UF + + + + + Preenchido com a data e hora do processamento (informado também no caso de rejeição). +Formato: “AAAA-MM-DDThh:mm:ssTZD” (UTC – Universal Coordinated Time) + + + + + Resposta ao processamento da DCe + + + + + + + + Tipo Dados do Imposto DCe + + + + + Tipo processo de emissão do DCe + + + + + + + + + + Tipo Versão da DCe + + + + + + + + + Tipo hora + + + + + + + + + Tipo Email + + + + + + + + + + + Finalidade da DCe + + + + + + + + + + + + + + Tipo de Documento de Identificação + + + + + + + + + + + + + Tipo Código Regime Tributário + + + + + + + + + + + + + Tipo de emissão da DCe + + + + + + + + + + + + Tipo utilização + + + + + + + + + + + + + + + + + Tipo Competência + + + + + + + + + diff --git a/DCe.Classes/Schemas/consulta/retConsSitDCe_v1.00.xsd b/DCe.Classes/Schemas/consulta/retConsSitDCe_v1.00.xsd new file mode 100644 index 000000000..483fcbc37 --- /dev/null +++ b/DCe.Classes/Schemas/consulta/retConsSitDCe_v1.00.xsd @@ -0,0 +1,10 @@ + + + + + + + Schema XML de validação do retorno da consulta da situação atual da DCe + + + diff --git a/DCe.Classes/Schemas/consulta/retConsStatServDCe_v1.00.xsd b/DCe.Classes/Schemas/consulta/retConsStatServDCe_v1.00.xsd new file mode 100644 index 000000000..271d95dac --- /dev/null +++ b/DCe.Classes/Schemas/consulta/retConsStatServDCe_v1.00.xsd @@ -0,0 +1,10 @@ + + + + + + + Schema XML de validação do Resultado da Consulta do Status do Serviço de DCe + + + diff --git a/DCe.Classes/Schemas/consulta/tiposGeralDCe_v1.00.xsd b/DCe.Classes/Schemas/consulta/tiposGeralDCe_v1.00.xsd new file mode 100644 index 000000000..e33dd4f37 --- /dev/null +++ b/DCe.Classes/Schemas/consulta/tiposGeralDCe_v1.00.xsd @@ -0,0 +1,712 @@ + + + + + + Data e Hora, formato UTC (AAAA-MM-DDThh:mm:ssTZD, onde TZD = +hh:mm ou -hh:mm) + + + + + + + + + Tipo Código da UF da tabela do IBGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Código do Município da tabela do IBGE + + + + + + + + + Tipo Código de orgão (UF da tabela do IBGE + 90 SUFRAMA + 91 RFB + 92 BRId) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Código da UF da tabela do IBGE + 99 para Exterior + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Chave de Documento Fiscal Eletrônico + + + + + + + + + + Tipo Número do Protocolo de Status + + + + + + + + + Tipo Número do Recibo do envio de lote de DF-e + + + + + + + + + Tipo Código da Mensagem enviada + + + + + + + + + Tipo Número do CNPJ + + + + + + + + + Tipo Número do CNPJ tamanho varíavel (3-14) + + + + + + + + + Tipo Número do CNPJ Opcional + + + + + + + + + Tipo Número do CPF + + + + + + + + + Tipo Número do CPF de tamanho variável (3-11) + + + + + + + + + Tipo Decimal com 5 dígitos, sendo 3 inteiros e 2 decimais + + + + + + + + + Tipo Decimal com 5 dígitos, sendo 3 inteiros e 2 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 6 dígitos, sendo 3 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 6 dígitos, sendo 3 inteiros e 3 decimais usado em tags opcionais + + + + + + + + + Tipo Decimal com 6 ou 5 dígitos, sendo 3 inteiros e 3 ou 2 decimais + + + + + + + + + Tipo Decimal com até 3 dígitos inteiros, podendo ter de 2 até 4 decimais + + + + + + + + + Tipo Decimal com até 3 dígitos inteiros e 2 até 4 decimais. Utilizados em TAGs opcionais, não aceita valor zero + + + + + + + + + Tipo Decimal com 3 inteiros (no máximo 100), com até 4 decimais + + + + + + + + + Tipo Decimal com 4 dígitos inteiros e 2 a 8 casas decimais + + + + + + + + + Tipo Decimal com 8 dígitos, sendo 5 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 11 dígitos, sendo 8 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 11 dígitos, sendo 8 inteiros e 3 decimais utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 12 dígitos, sendo 8 inteiros e 4 decimais + + + + + + + + + Tipo Decimal com 12 dígitos, sendo 8 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 6 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 6 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 8 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 8 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 11 inteiros e 4 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 11 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 11 dígitos inteiros e 0 a 4 casas decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 12 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 12 inteiros e 3 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 16 dígitos, sendo 12 de corpo e 4 decimais + + + + + + + + + Tipo Decimal com 16 dígitos, sendo 12 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 13 inteiros e 2 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 13 inteiros e 2 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 6 casas decimais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 8 casas decimais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 4 casas decimais + + + + + + + + + Tipo Inscrição Estadual do Destinatário + + + + + + + + + Tipo Modelo DCe + + + + + + + + + Tipo Inscrição Estadual do Emitente + + + + + + + + + Tipo Número do Documento Fiscal + + + + + + + + + Tipo Série do Documento Fiscal + + + + + + + + + Tipo Sigla da UF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Sigla da UF - Sem Exterior + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Ambiente + + + + + + + + + + Tipo Versão do Aplicativo + + + + + + + + + Tipo Motivo + + + + + + + + + Tipo Justificativa + + + + + + + + + Tipo Serviço solicitado + + + + + + Tipo ano + + + + + + + + + Tipo temp médio em segundos + + + + + + + + + Tipo string genérico + + + + + + + + + Tipo data AAAA-MM-DD + + + + + + + + + Coordenada geográfica Latitude + + + + + + + + Coordenada geográfica Longitude + + + + + + + + Tipo número sequencial único do AN + + + + + + + + Tipo IP versão 4 + + + + + + + diff --git a/DCe.Classes/Schemas/consulta/xmldsig-core-schema_v1.01.xsd b/DCe.Classes/Schemas/consulta/xmldsig-core-schema_v1.01.xsd new file mode 100644 index 000000000..76b74b38d --- /dev/null +++ b/DCe.Classes/Schemas/consulta/xmldsig-core-schema_v1.01.xsd @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DCe.Classes/Schemas/dce/dceTiposBasico_v1.00.xsd b/DCe.Classes/Schemas/dce/dceTiposBasico_v1.00.xsd new file mode 100644 index 000000000..ae386bb9f --- /dev/null +++ b/DCe.Classes/Schemas/dce/dceTiposBasico_v1.00.xsd @@ -0,0 +1,1410 @@ + + + + + + + + Tipo Declaração de Conteúdo Eletrônica + + + + + Informações da DCe + + + + + + Identificação da DCe + + + + + + Código da UF de emissão e autorização da DCe + Código da UF de emissão e autorização do Documento Fiscal. Utilizar a +Tabela do IBGE de código de unidades da federação + + + + + Código numérico que compõe a Chave de Acesso. + Código aleatório gerado pelo emitente, com o objetivo de evitar acessos indevidos ao documento + + + + + + + + + + + Modelo da DCe + Utilizar o código 99 para identificação da DCe + + + + + Série do documento fiscal + Informar a série do documento fiscal (informar zero para série única) + + + + + Número do documento fiscal + Número que identifica o documento fiscal 1 a 999999999 + + + + + Data e hora de emissão do documento fiscal + Formato UTC (AAAA-MM-DDThh:mm:ssTZD, onde TZD = +hh:mm ou -hh:mm) + + + + + Forma de emissão do Documento Fiscal + 1 - Normal (não contingência); 9 - Contingência off-line da DCe + + + + + Tipo do Emitente da DCe + 0 - App Fisco +1 - Marketplace +2 - Emissor próprio +3 - Transportadora +4 - ECT + + + + + + + + + + + + + + Identificação do número do Site do Autorizador de recepção da DCe + Se o autorizador da DCe possuir apenas um site deverá ser informado com Zero (0), em caso de Autorizador trabalhar com múltiplos sites indicar o número do site para qual foi endereçada a DCE (1 a 9). + +Observação: o ambiente autorizador que trabalhar com mais de um Site deverá divulgar para cada endereço de site qual número correspondente de nSiteAutoriz o contribuinte pode usar + + + + + + + + + + + Digito verificador da chave de acesso + Informar o dígito de controle da chave de acesso documento fiscal, que deve ser calculado com a aplicação do algoritmo módulo 11 (base 2,9) da chave de acesso + + + + + + + + + + + Tipo do Ambiente + 1 - Produção +2 - Homologação + + + + + Versão do processo de emissão + Informar a versão do aplicativo emissor de DCe + + + + + + + + + + + + + + Identificação da DCe + + + + + + + Número do CNPJ do emitente + + + + + Número do CPF do emitente + Informar os zeros não significativos + + + + + Identificação do destinatário outros + Identificação do emitente não obrigado a inscrição do CPF ou CNPJ tais como estrangeiro, indígena e quilombola. Observação: Campo aceita algarismos, letras (maiúsculas e minúsculas) e os caracteres do conjunto que segue: [:.+-/()] + + + + + + + + + + + + + + Razão social ou Nome do emitente + + + + + + + + + + + Endereço do emitente + + + + + + + + + Identificação do Fisco (uso exclusivo fisco) + + + + + + Número do CNPJ do órgão + + + + + Nome do órgão + + + + + + + + + + + Sigla da UF + + + + + + + + Identificação do Marketplace (uso exclusivo Marketplace) + + + + + + Número do CNPJ do Marketplace + + + + + Razão Social ou Nome do Marketplace + + + + + + + + + + + Endereço ou site do Marketplace + + + + + + + + + + + + + + Identificação da Transportadora (uso exclusivo Transportadora) + + + + + + Número do CNPJ da Transportadora + + + + + Razão Social ou Nome da Transportadora + + + + + + + + + + + + + + Identificação dos correios + + + + + + Número do CNPJ dos correios + + + + + Razão Social ou Nome dos correios + + + + + + + + + + + + + + + Identificação do destinatário da DCe + + + + + + + Número do CNPJ + Em caso de empresa não estabelecida no Brasil, será informado o CNPJ com zeros. + Informar os zeros não significativos + + + + + Número do CPF + Informar os zeros não significativos + + + + + Identificação do destinatário outros + Identificação do destinatário não obrigado a inscrição do CPF tais como estrangeiro, indígena e quilombola; +Em caso de não contar CPF do assinante, informar o RG + + + + + + + + + + + + + + Razão social ou Nome do destinatário + + + + + + + + + + + Endereço do destinatário / assinante + + + + + + + + Autorizados para acessar o XML da DCe + Informar CNPJ ou CPF. Preencher os zeros não significativos + + + + + + + CNPJ do autorizado + Informar zeros não significativos + + + + + CPF do autorizado + Informar zeros não significativos + + + + + + + + + Detalhamento de itens da DCe + + + + + + Dados do Produto ou Serviço + + + + + + Descrição do produto, bem ou mercadoria + + + + + + + + + + + Código NCM + Capítulo do Código NCM com 2 dígitos ou NCM completo + + + + + + + + + + + Quantidade + + + + + Valor unitário do item + + + + + + + + Valor total brutp do item + + + + + + + + + + + Informações adicionais do item + + + + + + + + + + + + Número do item da DCe + + + + + + + + + + + + + Dados dos totais da DCe + + + + + + Valor Total da DCe + + + + + + + + Dados dos transportes da DCe + + + + + + Modalidade do transporte + 0 - Transporte pelos correios; +1 - Transporte por contra própria; +2 - Transporte por empresa transportadora + + + + + + + + + + + + + + Número do CNPJ da Transportadora + + + + + + + + Informações Adicionais + + + + + Sequencia XML + + + + Informações adicionais de interesse do Fisco + Norma referenciada, informações complementares, etc + + + + + + + + + + + Informações complementares de interesse do Emitente + + + + + + + + + + + Informações Adicionais do MarketPlace + + + + + + + + + + + Informações Adicionais do Correio + + + + + + + + + + + Grupo Campo de uso livre do Emitente + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + Grupo Campo de uso livre do Fisco + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + Grupo Campo de uso livre do MarketPlace + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + Grupo Campo de uso livre dos Correios + Informar o nome do campo no atributo xCampo e o conteúdo do campo no xTexto + + + + + Sequencia XML + + + + Conteúdo do Campo + + + + + + + + + + + + + + + + + + Grupo para Declaraçao do usuário emitente + + + + + Sequencia XML + + + + Observação 1 ao usuário emitente + Deverá conter o texto fixo “É contribuinte de ICMS qualquer pessoa física ou jurídica, que realize, com habitualidade ou em volume que caracterize intuito comercial, operações de circulação de mercadoria ou prestações de serviços de transportes interestadual e intermunicipal e de comunicação, ainda que as operações e prestações de iniciem no exterior (Lei Complementar nº 87/96, Art. 4º)” + + + + + + + + + + + Observação 2 ao usuário emitente + Deverá conter o texto fixo “Constitui crime contra a ordem tributária suprimir ou reduzir tributo, ou contribuição social e qualquer acessório: quando negar ou deixar de fornecer, quando obrigatório, nota fiscal ou documento equivalente, relativa a venda de mercadoria ou prestação de serviço, efetivamente realizada ou fornece-la em desacordo com a legislação. Sob pena de reclusão de 2 (dois) e 5 (cinco) anos, e multa (Lei 8.137/90, Art 1ª, V)”. + + + + + + + + + + + + + + + Versão do leiaute + + + + + Identificador da tag a ser assinada + Informar a chave de acesso precedida do literal "DCe" + + + + + + + + + + + + + + + + Informações de solicitação da DCe pelo Fisco + + + + + + Solicitação do pedido de emissão da DCe + + + + + + + + + + + + + + + Informações suplementares da DCe + + + + + + Texto com o QR-Code impresso da DCe + + + + + + + + + + + + + URL para consulta chave de acesso + + + + + + + + + + + + + + + + + + Tipo Dados do Endereço + + + + + Logradouro + + + + + + + + + + + Número + + + + + + + + + + + Complemento + + + + + + + + + + + Bairro + + + + + + + + + + + Código do município (utilizar a tabela do IBGE) + + + + + Nome do município + + + + + + + + + + + Sigla da UF + + + + + CEP + Informar zeros não significativos + + + + + + + + + + + Código do País + 1058=Brasil + + + + + + + + + + Nome do País exemplo: Brasil ou BRASIL + + + + + + + + + + + Telefone + + + + + + + + + + + + + Tipo Dados do Endereço Destinatario + + + + + Logradouro + + + + + + + + + + + Número + + + + + + + + + + + Complemento + + + + + + + + + + + Bairro + + + + + + + + + + + Código do município (utilizar a tabela do IBGE) + + + + + Nome do município + + + + + + + + + + + Sigla da UF + + + + + CEP + Informar zeros não significativos + + + + + + + + + + + Código do país + Utilizar a tabela do BACEN + + + + + + + + + + + Nome do país + + + + + + + + + + + Telefone + + + + + + + + + + + Endereço de E-mail + + + + + + + Tipo Dados do Endereço + + + + + Logradouro + + + + + + + + + + + Número + + + + + + + + + + + Complemento + + + + + + + + + + + Bairro + + + + + + + + + + + Código do município (utilizar a tabela do IBGE), informar 9999999 para operações com o exterior + + + + + Nome do município, informar EXTERIOR para operações com o exterior + + + + + + + + + + + CEP + Informar os zeros não significativos + + + + + + + + + + + Sigla da UF, informar EX para operações com o exterior + + + + + Código do país + Utilizar a tabela do BACEN + + + + + + + + + + + Nome do país + + + + + + + + + + + Telefone + + + + + + + + + + + Endereço de E-mail + + + + + + + Tipo Protocolo de status resultado do processamento da DCe (Modelo 99) + + + + + Dados do protocolo de status + + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Versão do Aplicativo que processou a DCe + + + + + Chave de acesso da DCe + + + + + Data e hora de processamento, no formato AAAA-MM-DDTHH:MM:SS TZD + + + + + Número do Protocolo de Status da DCe + + + + + Digest Value da DCe processado. Utilizado para conferir a integridade da DCe original + + + + + Código do status da DCe + + + + + + + + Descrição literal do status da DCe + + + + + + + + + Mensagem do Fisco + + + + + + Código do status da mensagem do fisco + + + + + + + + Mensagem do Fisco + + + + + + + + + + + + Tipo Retorno do Pedido de Autorização de DCe + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Versão do Aplicativo que processou a DCe + + + + + código do status da resposta + + + + + Descrição literal do status do do retorno da consulta + + + + + Identificação da UF + + + + + Preenchido com a data e hora do processamento (informado também no caso de rejeição). +Formato: “AAAA-MM-DDThh:mm:ssTZD” (UTC – Universal Coordinated Time) + + + + + Resposta ao processamento da DCe + + + + + + + + Tipo Dados do Imposto DCe + + + + + Tipo processo de emissão do DCe + + + + + + + + + + Tipo Versão da DCe + + + + + + + + + Tipo hora + + + + + + + + + Tipo Email + + + + + + + + + + + Finalidade da DCe + + + + + + + + + + + + + + Tipo de Documento de Identificação + + + + + + + + + + + + + Tipo Código Regime Tributário + + + + + + + + + + + + + Tipo de emissão da DCe + + + + + + + + + + + + Tipo utilização + + + + + + + + + + + + + + + + + Tipo Competência + + + + + + + + + \ No newline at end of file diff --git a/DCe.Classes/Schemas/dce/dce_v1.00.xsd b/DCe.Classes/Schemas/dce/dce_v1.00.xsd new file mode 100644 index 000000000..0d2d205f0 --- /dev/null +++ b/DCe.Classes/Schemas/dce/dce_v1.00.xsd @@ -0,0 +1,10 @@ + + + + + + + Declaração de Conteúdo Eletrônica + + + diff --git a/DCe.Classes/Schemas/dce/procDCe_v1.00.xsd b/DCe.Classes/Schemas/dce/procDCe_v1.00.xsd new file mode 100644 index 000000000..35ab33206 --- /dev/null +++ b/DCe.Classes/Schemas/dce/procDCe_v1.00.xsd @@ -0,0 +1,37 @@ + + + + + + + DCe processada + + + + + + + + + + IP do transmissor do documento fiscal para o ambiente autorizador + + + + + Porta de origem utilizada na conexão (De 0 a 65535) + + + + + + + + + + Data e Hora da Conexão de Origem + + + + + diff --git a/DCe.Classes/Schemas/dce/retDCe_v1.00.xsd b/DCe.Classes/Schemas/dce/retDCe_v1.00.xsd new file mode 100644 index 000000000..1e4a17a57 --- /dev/null +++ b/DCe.Classes/Schemas/dce/retDCe_v1.00.xsd @@ -0,0 +1,9 @@ + + + + + + Schema XML de validação do retorno da DCe + + + diff --git a/DCe.Classes/Schemas/dce/tiposGeralDCe_v1.00.xsd b/DCe.Classes/Schemas/dce/tiposGeralDCe_v1.00.xsd new file mode 100644 index 000000000..e33dd4f37 --- /dev/null +++ b/DCe.Classes/Schemas/dce/tiposGeralDCe_v1.00.xsd @@ -0,0 +1,712 @@ + + + + + + Data e Hora, formato UTC (AAAA-MM-DDThh:mm:ssTZD, onde TZD = +hh:mm ou -hh:mm) + + + + + + + + + Tipo Código da UF da tabela do IBGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Código do Município da tabela do IBGE + + + + + + + + + Tipo Código de orgão (UF da tabela do IBGE + 90 SUFRAMA + 91 RFB + 92 BRId) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Código da UF da tabela do IBGE + 99 para Exterior + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Chave de Documento Fiscal Eletrônico + + + + + + + + + + Tipo Número do Protocolo de Status + + + + + + + + + Tipo Número do Recibo do envio de lote de DF-e + + + + + + + + + Tipo Código da Mensagem enviada + + + + + + + + + Tipo Número do CNPJ + + + + + + + + + Tipo Número do CNPJ tamanho varíavel (3-14) + + + + + + + + + Tipo Número do CNPJ Opcional + + + + + + + + + Tipo Número do CPF + + + + + + + + + Tipo Número do CPF de tamanho variável (3-11) + + + + + + + + + Tipo Decimal com 5 dígitos, sendo 3 inteiros e 2 decimais + + + + + + + + + Tipo Decimal com 5 dígitos, sendo 3 inteiros e 2 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 6 dígitos, sendo 3 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 6 dígitos, sendo 3 inteiros e 3 decimais usado em tags opcionais + + + + + + + + + Tipo Decimal com 6 ou 5 dígitos, sendo 3 inteiros e 3 ou 2 decimais + + + + + + + + + Tipo Decimal com até 3 dígitos inteiros, podendo ter de 2 até 4 decimais + + + + + + + + + Tipo Decimal com até 3 dígitos inteiros e 2 até 4 decimais. Utilizados em TAGs opcionais, não aceita valor zero + + + + + + + + + Tipo Decimal com 3 inteiros (no máximo 100), com até 4 decimais + + + + + + + + + Tipo Decimal com 4 dígitos inteiros e 2 a 8 casas decimais + + + + + + + + + Tipo Decimal com 8 dígitos, sendo 5 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 11 dígitos, sendo 8 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 11 dígitos, sendo 8 inteiros e 3 decimais utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 12 dígitos, sendo 8 inteiros e 4 decimais + + + + + + + + + Tipo Decimal com 12 dígitos, sendo 8 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 6 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 6 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 8 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 8 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 11 inteiros e 4 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 11 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 11 dígitos inteiros e 0 a 4 casas decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 12 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 12 inteiros e 3 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 16 dígitos, sendo 12 de corpo e 4 decimais + + + + + + + + + Tipo Decimal com 16 dígitos, sendo 12 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 13 inteiros e 2 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 13 inteiros e 2 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 6 casas decimais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 8 casas decimais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 4 casas decimais + + + + + + + + + Tipo Inscrição Estadual do Destinatário + + + + + + + + + Tipo Modelo DCe + + + + + + + + + Tipo Inscrição Estadual do Emitente + + + + + + + + + Tipo Número do Documento Fiscal + + + + + + + + + Tipo Série do Documento Fiscal + + + + + + + + + Tipo Sigla da UF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Sigla da UF - Sem Exterior + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Ambiente + + + + + + + + + + Tipo Versão do Aplicativo + + + + + + + + + Tipo Motivo + + + + + + + + + Tipo Justificativa + + + + + + + + + Tipo Serviço solicitado + + + + + + Tipo ano + + + + + + + + + Tipo temp médio em segundos + + + + + + + + + Tipo string genérico + + + + + + + + + Tipo data AAAA-MM-DD + + + + + + + + + Coordenada geográfica Latitude + + + + + + + + Coordenada geográfica Longitude + + + + + + + + Tipo número sequencial único do AN + + + + + + + + Tipo IP versão 4 + + + + + + + diff --git a/DCe.Classes/Schemas/dce/xmldsig-core-schema_v1.01.xsd b/DCe.Classes/Schemas/dce/xmldsig-core-schema_v1.01.xsd new file mode 100644 index 000000000..76b74b38d --- /dev/null +++ b/DCe.Classes/Schemas/dce/xmldsig-core-schema_v1.01.xsd @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DCe.Classes/Schemas/evento/evCancDCe_v1.00.xsd b/DCe.Classes/Schemas/evento/evCancDCe_v1.00.xsd new file mode 100644 index 000000000..5011d9a9b --- /dev/null +++ b/DCe.Classes/Schemas/evento/evCancDCe_v1.00.xsd @@ -0,0 +1,36 @@ + + + + + + + Schema XML de validação do evento do cancelamento +110111 + + + + + + Descrição do Evento - “Cancelamento” + + + + + + + + + + + Número do Protocolo de Status da DCe + + + + + Justificativa do Cancelamento + + + + + + diff --git a/DCe.Classes/Schemas/evento/eventoDCeTiposBasico_v1.00.xsd b/DCe.Classes/Schemas/evento/eventoDCeTiposBasico_v1.00.xsd new file mode 100644 index 000000000..9e2e78de0 --- /dev/null +++ b/DCe.Classes/Schemas/evento/eventoDCeTiposBasico_v1.00.xsd @@ -0,0 +1,283 @@ + + + + + + + + Tipo Evento + + + + + + + + Código do órgão de recepção do Evento + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Tipo do Emitente da DCe + 0=App Fisco / 1=Marketplace / 2=Emissor próprio / 3=Transportadora ou ECT + + + + + + + + + + + + + CNPJ da empresa, Fisco, Marketplace ou Transportadora emitente do evento deve ser o mesmo CNPJ informado do Grupo D. Identificação da Empresa, Fisco, Marketplace ou Transportador emitente da DCe + + + + + + CNPJ do Usuário Emitente da DCe que o evento estará +vinculado +Deve ser o mesmo informado no campo C02 da DCe + + + + + + CPF do Usuário Emitente da DCe que o evento estará +vinculado +Deve ser o mesmo informao no campo C02a da DCe + + + + + Identificação do usuário emitente não obrigado a CPF +ou CNPJ que o evento estará vinculado +Deve ser o mesmo informado no campo C02b da DCe + + + + + + + Chave de Acesso da DCe vinculada ao evento + + + + + Data e Hora do Evento, formato UTC (AAAA-MM-DDThh:mm:ssTZD) + + + + + Tipo do Evento + + + + + + + + + + + Seqüencial do evento para o mesmo tipo de evento. Para maioria dos eventos será 1, nos casos em que possa existir mais de um evento o autor do evento deve numerar de forma seqüencial + + + + + + + + + + + Detalhamento do evento específico + + + + + + XML do evento +Insira neste local o XML específico do tipo de evento (cancelamento) + + + + + + + + + + + + + + + + + Identificador da TAG a ser assinada, a regra de formação do Id é: +“ID” + tpEvento[6] + ChaveAcesso da DCe[44] + nSeqEvento[3] (preencher com zeros a esquerda para compor 3 digitos) + + + + + + + + + + + + + + + + Tipo retorno do Evento + + + + + + + + Identificação do Ambiente: +1 - Produção +2 - Homologação + + + + + Versão do Aplicativo que recebeu o Evento + + + + + Código do órgão de recepção do Evento. Utilizar a Tabela do IBGE extendida, utilizar 90 para identificar SUFRAMA + + + + + Código do status da registro do Evento + + + + + Descrição literal do status do registro do Evento + + + + + Chave de Acesso DCe vinculada + + + + + Tipo do Evento vinculado + + + + + + + + + + + Descrição do Evento + + + + + + + + + + + Seqüencial do evento + + + + + + + + + + + Data e Hora de do recebimento do evento ou do registro do evento formato AAAA-MM-DDThh:mm:ssTZD + + + + + Número do protocolo de registro do evento + + + + + + + + + + + + + + + + + + + + + + + Tipo procEvento + + + + + + + + + IP do transmissor do documento fiscal para o ambiente autorizador + + + + + Porta de origem utilizada na conexão (De 0 a 65535) + + + + + + + + + + Data e Hora da Conexão de Origem + + + + + + Tipo Versão do Evento + + + + + + + \ No newline at end of file diff --git a/DCe.Classes/Schemas/evento/eventoDCe_v1.00.xsd b/DCe.Classes/Schemas/evento/eventoDCe_v1.00.xsd new file mode 100644 index 000000000..79b3593b9 --- /dev/null +++ b/DCe.Classes/Schemas/evento/eventoDCe_v1.00.xsd @@ -0,0 +1,9 @@ + + + + + + Schema XML de validação do Pedido de Registro de Evento da DCe + + + diff --git a/DCe.Classes/Schemas/evento/procEventoDCe_v1.00.xsd b/DCe.Classes/Schemas/evento/procEventoDCe_v1.00.xsd new file mode 100644 index 000000000..593307012 --- /dev/null +++ b/DCe.Classes/Schemas/evento/procEventoDCe_v1.00.xsd @@ -0,0 +1,10 @@ + + + + + + + Pedido de Registro de Evento de DCe processado + + + diff --git a/DCe.Classes/Schemas/evento/retEventoDCe_v1.00.xsd b/DCe.Classes/Schemas/evento/retEventoDCe_v1.00.xsd new file mode 100644 index 000000000..db7d18a7e --- /dev/null +++ b/DCe.Classes/Schemas/evento/retEventoDCe_v1.00.xsd @@ -0,0 +1,9 @@ + + + + + + Schema XML de validação do retorno Pedido de Evento da DCe + + + diff --git a/DCe.Classes/Schemas/evento/tiposGeralDCe_v1.00.xsd b/DCe.Classes/Schemas/evento/tiposGeralDCe_v1.00.xsd new file mode 100644 index 000000000..e33dd4f37 --- /dev/null +++ b/DCe.Classes/Schemas/evento/tiposGeralDCe_v1.00.xsd @@ -0,0 +1,712 @@ + + + + + + Data e Hora, formato UTC (AAAA-MM-DDThh:mm:ssTZD, onde TZD = +hh:mm ou -hh:mm) + + + + + + + + + Tipo Código da UF da tabela do IBGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Código do Município da tabela do IBGE + + + + + + + + + Tipo Código de orgão (UF da tabela do IBGE + 90 SUFRAMA + 91 RFB + 92 BRId) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Código da UF da tabela do IBGE + 99 para Exterior + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Chave de Documento Fiscal Eletrônico + + + + + + + + + + Tipo Número do Protocolo de Status + + + + + + + + + Tipo Número do Recibo do envio de lote de DF-e + + + + + + + + + Tipo Código da Mensagem enviada + + + + + + + + + Tipo Número do CNPJ + + + + + + + + + Tipo Número do CNPJ tamanho varíavel (3-14) + + + + + + + + + Tipo Número do CNPJ Opcional + + + + + + + + + Tipo Número do CPF + + + + + + + + + Tipo Número do CPF de tamanho variável (3-11) + + + + + + + + + Tipo Decimal com 5 dígitos, sendo 3 inteiros e 2 decimais + + + + + + + + + Tipo Decimal com 5 dígitos, sendo 3 inteiros e 2 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 6 dígitos, sendo 3 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 6 dígitos, sendo 3 inteiros e 3 decimais usado em tags opcionais + + + + + + + + + Tipo Decimal com 6 ou 5 dígitos, sendo 3 inteiros e 3 ou 2 decimais + + + + + + + + + Tipo Decimal com até 3 dígitos inteiros, podendo ter de 2 até 4 decimais + + + + + + + + + Tipo Decimal com até 3 dígitos inteiros e 2 até 4 decimais. Utilizados em TAGs opcionais, não aceita valor zero + + + + + + + + + Tipo Decimal com 3 inteiros (no máximo 100), com até 4 decimais + + + + + + + + + Tipo Decimal com 4 dígitos inteiros e 2 a 8 casas decimais + + + + + + + + + Tipo Decimal com 8 dígitos, sendo 5 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 11 dígitos, sendo 8 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 11 dígitos, sendo 8 inteiros e 3 decimais utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 12 dígitos, sendo 8 inteiros e 4 decimais + + + + + + + + + Tipo Decimal com 12 dígitos, sendo 8 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 6 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 6 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 8 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 9 inteiros e 8 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 11 inteiros e 4 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 11 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 11 dígitos inteiros e 0 a 4 casas decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 12 inteiros e 3 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 12 inteiros e 3 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 16 dígitos, sendo 12 de corpo e 4 decimais + + + + + + + + + Tipo Decimal com 16 dígitos, sendo 12 inteiros e 4 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 13 inteiros e 2 decimais + + + + + + + + + Tipo Decimal com 15 dígitos, sendo 13 inteiros e 2 decimais, utilizado em tags opcionais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 6 casas decimais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 8 casas decimais + + + + + + + + + Tipo Decimal com 13 dígitos inteiros e 2 a 4 casas decimais + + + + + + + + + Tipo Inscrição Estadual do Destinatário + + + + + + + + + Tipo Modelo DCe + + + + + + + + + Tipo Inscrição Estadual do Emitente + + + + + + + + + Tipo Número do Documento Fiscal + + + + + + + + + Tipo Série do Documento Fiscal + + + + + + + + + Tipo Sigla da UF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Sigla da UF - Sem Exterior + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo Ambiente + + + + + + + + + + Tipo Versão do Aplicativo + + + + + + + + + Tipo Motivo + + + + + + + + + Tipo Justificativa + + + + + + + + + Tipo Serviço solicitado + + + + + + Tipo ano + + + + + + + + + Tipo temp médio em segundos + + + + + + + + + Tipo string genérico + + + + + + + + + Tipo data AAAA-MM-DD + + + + + + + + + Coordenada geográfica Latitude + + + + + + + + Coordenada geográfica Longitude + + + + + + + + Tipo número sequencial único do AN + + + + + + + + Tipo IP versão 4 + + + + + + + diff --git a/DCe.Classes/Schemas/evento/xmldsig-core-schema_v1.01.xsd b/DCe.Classes/Schemas/evento/xmldsig-core-schema_v1.01.xsd new file mode 100644 index 000000000..76b74b38d --- /dev/null +++ b/DCe.Classes/Schemas/evento/xmldsig-core-schema_v1.01.xsd @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DCe.Classes/Servicos/Consulta/consSitDCe.cs b/DCe.Classes/Servicos/Consulta/consSitDCe.cs new file mode 100644 index 000000000..6d40ad9b7 --- /dev/null +++ b/DCe.Classes/Servicos/Consulta/consSitDCe.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Servicos.Consulta; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("consSitDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class consSitDCe +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("tpAmb", Order = 0)] + public TipoAmbiente tpAmb { get; set; } + + [XmlElement("xServ", Order = 1)] + public ServicoConsultaDCe xServ { get; set; } = ServicoConsultaDCe.Consultar; + + [XmlElement("chDCe", Order = 2)] + public string chDCe { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Servicos/Evento/detEvento.cs b/DCe.Classes/Servicos/Evento/detEvento.cs new file mode 100644 index 000000000..a9f119e86 --- /dev/null +++ b/DCe.Classes/Servicos/Evento/detEvento.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Servicos.Evento; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("detEvento", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class detEvento +{ + [XmlAttribute("versaoEvento")] + public string versaoEvento { get; set; } = "1.00"; + + [XmlElement("evCancDCe", Order = 0)] + public evCancDCe evCancDCe { get; set; } = new evCancDCe(); + +} diff --git a/DCe.Classes/Servicos/Evento/evCancDCe.cs b/DCe.Classes/Servicos/Evento/evCancDCe.cs new file mode 100644 index 000000000..c121ad1b2 --- /dev/null +++ b/DCe.Classes/Servicos/Evento/evCancDCe.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Servicos.Evento; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("evCancDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class evCancDCe +{ + [XmlElement("descEvento", Order = 0)] + public DescricaoEventoDCe descEvento { get; set; } = DescricaoEventoDCe.Cancelamento; + + [XmlElement("nProt", Order = 1)] + public string nProt { get; set; } = string.Empty; + + [XmlElement("xJust", Order = 2)] + public string xJust { get; set; } = string.Empty; + +} diff --git a/DCe.Classes/Servicos/Evento/eventoDCe.cs b/DCe.Classes/Servicos/Evento/eventoDCe.cs new file mode 100644 index 000000000..10b5a94df --- /dev/null +++ b/DCe.Classes/Servicos/Evento/eventoDCe.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Servicos.Evento; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("eventoDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class eventoDCe +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("infEvento", Order = 0)] + public infEvento infEvento { get; set; } = new infEvento(); + + [XmlElement("Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#", Order = 1)] + public Signature? Signature { get; set; } + +} diff --git a/DCe.Classes/Servicos/Evento/infEvento.cs b/DCe.Classes/Servicos/Evento/infEvento.cs new file mode 100644 index 000000000..17f2d034d --- /dev/null +++ b/DCe.Classes/Servicos/Evento/infEvento.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Servicos.Evento; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("infEvento", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class infEvento +{ + [XmlAttribute("Id")] + public string Id { get; set; } = string.Empty; + + [XmlElement("cOrgao", Order = 0)] + public TCOrgaoIBGE cOrgao { get; set; } + + [XmlElement("tpAmb", Order = 1)] + public TipoAmbiente tpAmb { get; set; } + + [XmlElement("tpEmit", Order = 2)] + public TipoEmitenteDCe tpEmit { get; set; } + + [XmlElement("CNPJAutor", Order = 3)] + public string CNPJAutor { get; set; } = string.Empty; + + [XmlElement("CNPJUsEmit", Order = 4)] + public string? CNPJUsEmit { get; set; } + + [XmlElement("CPFUsEmit", Order = 5)] + public string? CPFUsEmit { get; set; } + + [XmlElement("IdOutrosUsEmit", Order = 6)] + public string? IdOutrosUsEmit { get; set; } + + [XmlElement("chDCe", Order = 7)] + public string chDCe { get; set; } = string.Empty; + + private DateTimeOffset _dhEvento; + private string? _dhEventoXml; + + [XmlIgnore] + public DateTimeOffset dhEvento + { + get => _dhEvento; + set { _dhEvento = value; _dhEventoXml = null; } + } + + [XmlElement("dhEvento", Order = 8)] + public string dhEventoProxy + { + get => _dhEventoXml ?? _dhEvento.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); + set { _dhEvento = DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.None); _dhEventoXml = value; } + } + + [XmlElement("tpEvento", Order = 9)] + public TipoEventoDCe tpEvento { get; set; } = TipoEventoDCe.Cancelamento; + + [XmlElement("nSeqEvento", Order = 10)] + public int nSeqEvento { get; set; } = 1; + + [XmlElement("detEvento", Order = 11)] + public detEvento detEvento { get; set; } = new detEvento(); + +} diff --git a/DCe.Classes/Servicos/StatusServico/consStatServDCe.cs b/DCe.Classes/Servicos/StatusServico/consStatServDCe.cs new file mode 100644 index 000000000..8f09bb362 --- /dev/null +++ b/DCe.Classes/Servicos/StatusServico/consStatServDCe.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Serialization; +using DCe.Classes.Flags; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Classes.Assinatura; + +namespace DCe.Classes.Servicos.StatusServico; + +[Serializable] +[XmlType(Namespace = "http://www.portalfiscal.inf.br/dce")] +[XmlRoot("consStatServDCe", Namespace = "http://www.portalfiscal.inf.br/dce")] +public class consStatServDCe +{ + [XmlAttribute("versao")] + public string versao { get; set; } = "1.00"; + + [XmlElement("tpAmb", Order = 0)] + public TipoAmbiente tpAmb { get; set; } + + [XmlElement("cUF", Order = 1)] + public Estado cUF { get; set; } + + [XmlElement("xServ", Order = 2)] + public ServicoStatusDCe xServ { get; set; } = ServicoStatusDCe.Status; + +} diff --git a/DCe.Utils/Abstractions/IClock.cs b/DCe.Utils/Abstractions/IClock.cs new file mode 100644 index 000000000..96236546c --- /dev/null +++ b/DCe.Utils/Abstractions/IClock.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +namespace DCe.Utils.Abstractions; + +/// Relógio injetável. Existe para que o XML gerado seja determinístico em teste. +public interface IClock +{ + DateTime UtcNow { get; } +} + +public sealed class SystemClock : IClock +{ + public DateTime UtcNow => DateTime.UtcNow; +} diff --git a/DCe.Utils/Abstractions/IRandomSource.cs b/DCe.Utils/Abstractions/IRandomSource.cs new file mode 100644 index 000000000..2aa42f41c --- /dev/null +++ b/DCe.Utils/Abstractions/IRandomSource.cs @@ -0,0 +1,27 @@ +using System; +namespace DCe.Utils.Abstractions; + +/// +/// Fonte do código numérico aleatório que compõe a chave de acesso. O XSD exige 6 dígitos +/// aleatórios gerados pelo emitente; injetar permite congelar o valor em teste. +/// +public interface IRandomSource +{ + int ProximoCodigoDocumento(); +} + +public sealed class SecureRandomSource : IRandomSource +{ + public int ProximoCodigoDocumento() + { + // Amostragem por rejeição evita viés e funciona também no .NET Framework. + using (var gerador = System.Security.Cryptography.RandomNumberGenerator.Create()) + { + var bytes = new byte[4]; + uint valor; + do { gerador.GetBytes(bytes); valor = BitConverter.ToUInt32(bytes, 0); } + while (valor >= uint.MaxValue - (uint.MaxValue % 1_000_000)); + return (int)(valor % 1_000_000); + } + } +} diff --git a/DCe.Utils/DCe.Utils.csproj b/DCe.Utils/DCe.Utils.csproj new file mode 100644 index 000000000..e85b92675 --- /dev/null +++ b/DCe.Utils/DCe.Utils.csproj @@ -0,0 +1,11 @@ + + + net462;netstandard2.0;net8.0;net10.0 + latest + enable + + + + + + diff --git a/DCe.Utils/DceAssinatura.cs b/DCe.Utils/DceAssinatura.cs new file mode 100644 index 000000000..10a0ec52c --- /dev/null +++ b/DCe.Utils/DceAssinatura.cs @@ -0,0 +1,26 @@ +using System; +using System.Security.Cryptography.X509Certificates; +using System.Xml; +using DCe.Classes.Servicos.Evento; +using DCe.Utils.Extensoes; +using DCeDocumento = DCe.Classes.Informacoes.DCe; + +namespace DCe.Utils; + +/// Assinatura de XML usando os modelos fiscais serializáveis. +public static class DceAssinatura +{ + public static string Assinar(string xml, X509Certificate2 certificado) + { + var documento = new XmlDocument { PreserveWhitespace = true, XmlResolver = null }; + documento.LoadXml(xml); + var raiz = documento.DocumentElement!; + if (raiz.NamespaceURI != "http://www.portalfiscal.inf.br/dce") + throw new ArgumentException("Esperado XML de DC-e ou eventoDCe.", nameof(xml)); + if (raiz.LocalName == "DCe") + return new DCeDocumento().CarregarDeXmlString(xml).Assina(certificado).ObterXmlString(); + if (raiz.LocalName == "eventoDCe") + return new eventoDCe().CarregarDeXmlString(xml).Assina(certificado).ObterXmlString(); + throw new ArgumentException("Esperado XML de DC-e ou eventoDCe.", nameof(xml)); + } +} diff --git a/DCe.Utils/DceChaveAcesso.cs b/DCe.Utils/DceChaveAcesso.cs new file mode 100644 index 000000000..5dbebb396 --- /dev/null +++ b/DCe.Utils/DceChaveAcesso.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using System.Globalization; +namespace DCe.Utils; + +/// +/// Chave de acesso da DC-e: 44 dígitos, conforme Tabela 2-1 do Manual — Visão Geral. +/// cUF(2) AAMM(4) CNPJ(14) mod(2) serie(3) nDC(9) tpEmis(1) tpEmit(1) nSiteAutoriz(1) +/// cDC(6) cDV(1). +/// +public static class DceChaveAcesso +{ + /// + /// DV por módulo 11, pesos 2..9 ciclando da direita para a esquerda. + /// Resto 0 ou 1 → DV 0 (regra explícita do manual). + /// + public static int CalcularDv(string chave43) + { + var resto = SomarPonderacoes(chave43) % 11; + return resto is 0 or 1 ? 0 : 11 - resto; + } + + /// + /// Somatória dos dígitos ponderados, conforme item 2.2.4.1 do Manual — Visão Geral. + /// Exposta separadamente para permitir a conferência do cálculo do DV. + /// + public static int SomarPonderacoes(string chave43) + { + if (chave43 is null || chave43.Length != 43 || !chave43.All(char.IsDigit)) + throw new ArgumentException("A chave sem DV precisa ter exatamente 43 dígitos.", nameof(chave43)); + + var soma = 0; + var peso = 2; + + for (var i = chave43.Length - 1; i >= 0; i--) + { + soma += (chave43[i] - '0') * peso; + peso = peso == 9 ? 2 : peso + 1; + } + + return soma; + } + + public const int ModeloDce = 99; + + /// + /// Fuso do emitente. O AAMM da chave tem de sair do mesmo instante local que + /// vai no dhEmi do XML, não do UTC: a regra A03-10 do Anexo I rejeita com + /// cStat 502 quando a chave "difere da concatenação dos campos correspondentes". + /// Derivar de UTC produz uma chave de agosto num documento datado de julho sempre que a + /// emissão cai entre 00:00 e 03:00 UTC do dia 1º, para o offset -03:00. + /// + public static string Montar( + int cUF, DateTime dhEmi, string cnpjSignatario, int serie, int nDC, + int tpEmis, int tpEmit, int nSiteAutoriz, int cDC, TimeSpan offsetHorario) + { + var cnpj = new string((cnpjSignatario ?? string.Empty).Where(char.IsDigit).ToArray()); + if (cnpj.Length != 14) + throw new ArgumentException("CNPJ do signatário precisa ter 14 dígitos.", nameof(cnpjSignatario)); + + var utc = dhEmi.Kind == DateTimeKind.Utc ? dhEmi : dhEmi.ToUniversalTime(); + var local = utc + offsetHorario; + + var sem = string.Concat( + cUF.ToString("D2", CultureInfo.InvariantCulture), + local.ToString("yyMM", CultureInfo.InvariantCulture), + cnpj, + ModeloDce.ToString("D2", CultureInfo.InvariantCulture), + serie.ToString("D3", CultureInfo.InvariantCulture), + nDC.ToString("D9", CultureInfo.InvariantCulture), + tpEmis.ToString("D1", CultureInfo.InvariantCulture), + tpEmit.ToString("D1", CultureInfo.InvariantCulture), + nSiteAutoriz.ToString("D1", CultureInfo.InvariantCulture), + cDC.ToString("D6", CultureInfo.InvariantCulture)); + + return sem + CalcularDv(sem); + } +} diff --git a/DCe.Utils/DcePrazoCancelamento.cs b/DCe.Utils/DcePrazoCancelamento.cs new file mode 100644 index 000000000..9a48ecaa4 --- /dev/null +++ b/DCe.Utils/DcePrazoCancelamento.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +namespace DCe.Utils; + +/// +/// Prazo de cancelamento da DC-e: 24 horas da autorização, conforme a Cláusula 11ª do +/// Ajuste SINIEF 05/2021. Lógica pura, sem relógio próprio — quem chama passa o "agora". +/// +public static class DcePrazoCancelamento +{ + public static readonly TimeSpan Janela = TimeSpan.FromHours(24); + + /// + /// Verdadeiro enquanto a janela não fechou. + /// + /// Decorrido negativo conta como dentro do prazo para tolerar deriva entre relógios. + /// autorizadaEm corresponde ao dhRecbto do protocolo da SEFAZ, enquanto + /// agora é informado pelo chamador. Exigir decorrido não negativo poderia impedir + /// o cancelamento de uma DC-e recém-autorizada quando o relógio local estiver atrasado. + /// + /// A SEFAZ é a autoridade sobre o prazo e rejeita cancelamentos fora da janela com + /// cStat 501. + /// + public static bool DentroDoPrazo(DateTimeOffset autorizadaEm, DateTimeOffset agora) + => agora - autorizadaEm < Janela; +} diff --git a/DCe.Utils/DceSchemaLoader.cs b/DCe.Utils/DceSchemaLoader.cs new file mode 100644 index 000000000..de1a8cc7c --- /dev/null +++ b/DCe.Utils/DceSchemaLoader.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Schema; + +namespace DCe.Utils; + +/// +/// Carrega os XSD da DC-e dos recursos embutidos ou de uma pasta, resolvendo os xs:include. +/// Sem o resolver, o include falha em silêncio no .NET Core e a validação vira decorativa. +/// +public static class DceSchemaLoader +{ + private const string Prefixo = "DCe.Classes.Schemas."; + private const string Ns = "http://www.portalfiscal.inf.br/dce"; + + /// "dce", "consulta" ou "evento" — a subpasta do pacote oficial. + /// Nome do XSD raiz dentro dessa pasta. + /// Pasta dos schemas com suas subpastas oficiais; nulo usa os embutidos. + public static XmlSchemaSet Carregar(string pasta, string arquivoRaiz, string? caminhoSchemas = null) + { + if (!string.IsNullOrWhiteSpace(caminhoSchemas)) + { + if (!Directory.Exists(caminhoSchemas)) + throw new DirectoryNotFoundException("Diretório de Schemas não encontrado: " + caminhoSchemas); + var externos = new XmlSchemaSet { XmlResolver = new XmlUrlResolver() }; + // O caminho completo mantém os includes relativos na subpasta correta. + var arquivo = Path.GetFullPath(Path.Combine(caminhoSchemas, pasta, arquivoRaiz)); + using var leitor = XmlReader.Create(arquivo, new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null + }); + externos.Add(Ns, leitor); + externos.Compile(); + return externos; + } + + var resolver = new RecursoEmbutidoResolver(pasta); + var set = new XmlSchemaSet { XmlResolver = resolver }; + + using var stream = Abrir(pasta, arquivoRaiz); + using var reader = XmlReader.Create(stream, new XmlReaderSettings { XmlResolver = resolver }); + set.Add(Ns, reader); + set.Compile(); + + return set; + } + + internal static Stream Abrir(string pasta, string arquivo) + { + var assembly = typeof(DCe.Classes.Informacoes.DCe).Assembly; + var nome = $"{Prefixo}{pasta}.{arquivo}"; + return assembly.GetManifestResourceStream(nome) + ?? throw new FileNotFoundException($"Schema XSD embutido não encontrado: {nome}"); + } + + /// + /// Resolve os xs:include dentro da MESMA subpasta do schema raiz. A pasta é parte da + /// identidade: dceTiposBasico_v1.00.xsd existe em dce/ e em consulta/ com conteúdos + /// diferentes, e resolver só pelo nome do arquivo colidiria os dois. + /// + private sealed class RecursoEmbutidoResolver : XmlUrlResolver + { + private readonly string _pasta; + + public RecursoEmbutidoResolver(string pasta) => _pasta = pasta; + + public override object GetEntity(Uri absoluteUri, string? role, Type? ofObjectToReturn) + => Abrir(_pasta, Path.GetFileName(absoluteUri.LocalPath)); + + public override Uri ResolveUri(Uri? baseUri, string? relativeUri) + => new Uri("file:///" + (relativeUri ?? string.Empty)); + } +} diff --git a/DCe.Utils/Extensoes/ExtDCe.cs b/DCe.Utils/Extensoes/ExtDCe.cs new file mode 100644 index 000000000..90f5a6a00 --- /dev/null +++ b/DCe.Utils/Extensoes/ExtDCe.cs @@ -0,0 +1,114 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using DCe.Classes.Retorno; +using DCe.Classes.Servicos.Evento; +using DCe.Utils.Validacao; +using DFe.Utils; +using DFe.Utils.Assinatura; +using DCeDocumento = DCe.Classes.Informacoes.DCe; + +namespace DCe.Utils.Extensoes; + +/// Leitura, escrita, assinatura e validação dos objetos fiscais DC-e. +public static class ExtDCe +{ + // A declaração XML da DC-e usa UTF-8 em maiúsculas e não tem quebra de linha. + public static string ObterXmlString(this T documento) where T : class => + "" + FuncoesXml.ClasseParaXmlString(documento); + + public static T CarregarDeXmlString(this T documento, string xml) where T : class + { + if (string.IsNullOrWhiteSpace(xml)) throw new ArgumentException("XML vazio.", nameof(xml)); + var resultado = FuncoesXml.XmlStringParaClasse(xml); + ValidarRetornoLido(resultado); + return resultado; + } + + public static T CarregarDeArquivoXml(this T documento, string arquivo) where T : class => + documento.CarregarDeXmlString(File.ReadAllText(arquivo)); + + public static void SalvarXmlEmDisco(this T documento, string arquivo) where T : class => + File.WriteAllText(arquivo, documento.ObterXmlString(), new UTF8Encoding(false)); + + public static DCeDocumento Assina(this DCeDocumento documento, X509Certificate2 certificado) + { + if (documento == null) throw new ArgumentNullException(nameof(documento)); + if (documento.Signature != null) throw new ArgumentException("O XML já contém assinatura.", nameof(documento)); + if (string.IsNullOrWhiteSpace(documento.infDCe.Id)) throw new ArgumentException("O XML deve conter a informação fiscal com atributo Id.", nameof(documento)); + documento.Signature = AssinaturaDigital.Assina(documento, documento.infDCe.Id, certificado); + return documento; + } + + public static eventoDCe Assina(this eventoDCe evento, X509Certificate2 certificado) + { + if (evento == null) throw new ArgumentNullException(nameof(evento)); + if (evento.Signature != null) throw new ArgumentException("O XML já contém assinatura.", nameof(evento)); + if (string.IsNullOrWhiteSpace(evento.infEvento.Id)) throw new ArgumentException("O XML deve conter a informação fiscal com atributo Id.", nameof(evento)); + evento.Signature = AssinaturaDigital.Assina(evento, evento.infEvento.Id, certificado); + return evento; + } + + public static void Valida(this DCeDocumento documento, string? caminhoSchemas = null) + { + documento.ValidarDadosLocais(); + Validador.Valida(documento.ObterXmlString(), "dce", "dce_v1.00.xsd", caminhoSchemas); + } + + public static void Valida(this eventoDCe evento, string? caminhoSchemas = null) + { + evento.ValidarDadosLocais(); + // O schema do envelope tem processContents=skip: conferir também o detalhe 110111. + Validador.Valida(evento.infEvento.detEvento.evCancDCe.ObterXmlString(), "evento", "evCancDCe_v1.00.xsd", caminhoSchemas); + Validador.Valida(evento.ObterXmlString(), "evento", "eventoDCe_v1.00.xsd", caminhoSchemas); + } + // Recusa retornos incompletos sem exigir dados opcionais + // nem classificar cStat por uma lista fechada. + private static void Obrigatorio(bool presente, string elemento, string pai) + { + if (!presente) throw new InvalidOperationException($"Elemento obrigatório '{elemento}' ausente em '{pai}'."); + } + + private static void Cabecalho(bool ambiente, bool versao, bool status, bool motivo, bool uf, string pai, string nomeUf = "cUF") + { + Obrigatorio(ambiente, "tpAmb", pai); + Obrigatorio(versao, "verAplic", pai); + Obrigatorio(status, "cStat", pai); + Obrigatorio(motivo, "xMotivo", pai); + Obrigatorio(uf, nomeUf, pai); + } + + private static void Protocolo(infProt? protocolo) + { + if (protocolo == null) return; + Obrigatorio(protocolo.tpAmbSpecified, "tpAmb", "infProt"); + Obrigatorio(protocolo.verAplicSpecified, "verAplic", "infProt"); + Obrigatorio(protocolo.chDCeSpecified, "chDCe", "infProt"); + Obrigatorio(protocolo.cStatSpecified, "cStat", "infProt"); + Obrigatorio(protocolo.xMotivoSpecified, "xMotivo", "infProt"); + } + + private static void ValidarRetornoLido(T documento) where T : class + { + switch (documento) + { + case retDCe r: + Cabecalho(r.tpAmbSpecified, r.verAplicSpecified, r.cStatSpecified, r.xMotivoSpecified, r.cUFSpecified, "retDCe"); + Protocolo(r.protDCe?.infProt); + break; + case retConsSitDCe r: + Cabecalho(r.tpAmbSpecified, r.verAplicSpecified, r.cStatSpecified, r.xMotivoSpecified, r.cUFSpecified, "retConsSitDCe"); + Obrigatorio(r.chDCeSpecified, "chDCe", "retConsSitDCe"); + Protocolo(r.protDCe?.infProt); + break; + case retConsStatServDCe r: + Cabecalho(r.tpAmbSpecified, r.verAplicSpecified, r.cStatSpecified, r.xMotivoSpecified, r.cUFSpecified, "retConsStatServDCe"); + break; + case retEventoDCe r: + var inf = r.infEvento ?? throw new InvalidOperationException("retEventoDCe sem infEvento."); + Cabecalho(inf.tpAmbSpecified, inf.verAplicSpecified, inf.cStatSpecified, inf.xMotivoSpecified, inf.cOrgaoSpecified, "infEvento", "cOrgao"); + break; + } + } +} diff --git a/DCe.Utils/Extensoes/ExtDCeFiscal.cs b/DCe.Utils/Extensoes/ExtDCeFiscal.cs new file mode 100644 index 000000000..346a89d67 --- /dev/null +++ b/DCe.Utils/Extensoes/ExtDCeFiscal.cs @@ -0,0 +1,103 @@ +using System; +using System.Globalization; +using System.Linq; +using DCe.Classes.Flags; +using DCe.Classes.Servicos.Evento; +using DCe.Utils.Ibge; +using DCeDocumento = DCe.Classes.Informacoes.DCe; + +namespace DCe.Utils.Extensoes; + +/// Coerência fiscal e preparação da chave a partir dos campos do modelo XML. +public static class ExtDCeFiscal +{ + /// Atualiza Id, DV e QR Code usando o offset de dhEmi e o CNPJ de quem assina. + public static DCeDocumento Preparar(this DCeDocumento documento, string cnpjSignatario) + { + documento.ValidarDadosLocais(); + var inf = documento.infDCe; + // Textos normativos obrigatórios do leiaute. + if (!inf.infDecSpecified) + { + inf.infDec = new Classes.Informacoes.infDec + { + xObs1Specified = true, + xObs2Specified = true + }; + } + var ide = inf.ide; + var chave = DceChaveAcesso.Montar((int)ide.cUF, ide.dhEmi.UtcDateTime, cnpjSignatario, + ide.serie, ide.nDC, (int)ide.tpEmis, (int)ide.tpEmit, ide.nSiteAutoriz, + int.Parse(ide.cDC, CultureInfo.InvariantCulture), ide.dhEmi.Offset); + documento.infDCe.Id = "DCe" + chave; + ide.cDV = chave[43] - '0'; + const string url = "https://www.fazenda.pr.gov.br/dce/qrcode"; + documento.infDCeSupl = new Classes.Informacoes.infDCeSupl + { + qrCodDCe = url + "?chDCe=" + chave + "&tpAmb=" + ((int)ide.tpAmb).ToString(CultureInfo.InvariantCulture), + urlChave = url + }; + return documento; + } + + public static eventoDCe Preparar(this eventoDCe evento) + { + evento.ValidarDadosLocais(); + var inf = evento.infEvento; + inf.Id = "ID" + ((int)inf.tpEvento).ToString(CultureInfo.InvariantCulture) + inf.chDCe + + inf.nSeqEvento.ToString("D3", CultureInfo.InvariantCulture); + return evento; + } + + public static void ValidarDadosLocais(this DCeDocumento documento) + { + if (documento == null) throw new ArgumentNullException(nameof(documento)); + var inf = documento.infDCe; + var origem = inf.emit.enderEmit; + var destino = inf.dest.enderDest; + var codigoUf = UnidadesFederativas.CodigoDe(origem.UF.ToString()); + if (codigoUf == null) + throw new ArgumentException("UF do emitente inválida: \"" + origem.UF + "\". O emitente da DC-e é sempre nacional — informe uma das 27 siglas de unidade federativa brasileira.", nameof(documento)); + if ((int)inf.ide.cUF != codigoUf) + throw new ArgumentException($"cUF {(int)inf.ide.cUF} não corresponde à UF do emitente ({origem.UF} = {codigoUf}). A SEFAZ rejeita com cStat 220.", nameof(documento)); + ValidarMunicipio(origem.cMun, origem.UF.ToString(), "emitente", 273, false); + ValidarMunicipio(destino.cMun, destino.UF.ToString(), "destinatário", 275, true); + ValidarIdentificacao(inf.emit.CNPJ, inf.emit.CPF, inf.emit.idOutros, "emitente/destinatário"); + ValidarIdentificacao(inf.dest.CNPJ, inf.dest.CPF, inf.dest.idOutros, "emitente/destinatário"); + } + + private static void ValidarMunicipio(int municipio, string uf, string papel, int cStat, bool permiteExterior) + { + if (permiteExterior && uf == "EX") + { + if (municipio != 9999999) + throw new ArgumentException($"Município do {papel} no exterior (UF \"EX\") deve ser 9999999, e não {municipio}. A SEFAZ rejeita com cStat 509."); + return; + } + var codigo = UnidadesFederativas.CodigoDe(uf); + if (codigo == null) throw new ArgumentException($"UF do {papel} inválida: \"{uf}\"."); + var prefixo = municipio / 100000; + if (prefixo != codigo) + throw new ArgumentException($"Município {municipio} do {papel} não pertence a {uf} — as duas primeiras posições do código IBGE são {prefixo:D2}, e {uf} é {codigo}. A SEFAZ rejeita com cStat {cStat}."); + } + + public static void ValidarDadosLocais(this eventoDCe evento) + { + if (evento == null) throw new ArgumentNullException(nameof(evento)); + var inf = evento.infEvento; + var chave = inf.chDCe ?? string.Empty; + if (chave.Length != 44 || !chave.All(char.IsDigit)) + throw new ArgumentException($"Chave de acesso da DC-e inválida: esperados 44 dígitos, veio \"{chave}\".", nameof(evento)); + var uf = int.Parse(chave.Substring(0, 2), CultureInfo.InvariantCulture); + if ((int)inf.cOrgao != uf) + throw new ArgumentException($"cOrgao {(int)inf.cOrgao} não corresponde ao cUF da chave ({uf}). O cOrgao do evento é a UF da própria DC-e, e não a do autorizador.", nameof(evento)); + ValidarIdentificacao(inf.CNPJUsEmit, inf.CPFUsEmit, inf.IdOutrosUsEmit, "usuário emitente do evento"); + } + + private static void ValidarIdentificacao(string? cnpj, string? cpf, string? outros, string descricao) + { + var quantidade = new[] { cnpj, cpf, outros }.Count(v => !string.IsNullOrWhiteSpace(v)); + if (quantidade != 1) + throw new InvalidOperationException($"A identificação de {descricao} exige exatamente um entre CNPJ, CPF e idOutros (informados: {quantidade})."); + } +} diff --git a/DCe.Utils/Ibge/UnidadesFederativas.cs b/DCe.Utils/Ibge/UnidadesFederativas.cs new file mode 100644 index 000000000..4fe687240 --- /dev/null +++ b/DCe.Utils/Ibge/UnidadesFederativas.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using DFe.Classes.Entidades; + +namespace DCe.Utils.Ibge; + +/// Consulta os códigos IBGE do enum comum do Zeus, excluindo autorizadores e exterior. +public static class UnidadesFederativas +{ + public static int? CodigoDe(string? sigla) + { + if (string.IsNullOrWhiteSpace(sigla)) return null; + var nome = sigla!.Trim(); + return TodasAsSiglas().Any(s => string.Equals(s, nome, StringComparison.OrdinalIgnoreCase)) + ? (int?)(Estado)Enum.Parse(typeof(Estado), nome, true) : null; + } + + public static string? SiglaDe(int codigo) => + codigo >= 11 && codigo <= 53 && Enum.IsDefined(typeof(Estado), codigo) + ? ((Estado)codigo).ToString() : null; + + public static IReadOnlyCollection TodasAsSiglas() => + Enum.GetValues(typeof(Estado)).Cast() + .Where(uf => (int)uf >= 11 && (int)uf <= 53).Select(uf => uf.ToString()).ToList(); +} diff --git a/DCe.Utils/Validacao/Validador.cs b/DCe.Utils/Validacao/Validador.cs new file mode 100644 index 000000000..98532721f --- /dev/null +++ b/DCe.Utils/Validacao/Validador.cs @@ -0,0 +1,26 @@ +using System.IO; +using System.Xml; +using System.Xml.Schema; + +namespace DCe.Utils.Validacao; + +/// Validação de XML pelos XSDs oficiais embutidos ou externos da DC-e. +public static class Validador +{ + public static void Valida(string xml, string pasta, string schema, string? caminhoSchemas = null) + { + var configuracao = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Prohibit, + XmlResolver = null, + ValidationType = ValidationType.Schema, + Schemas = DceSchemaLoader.Carregar(pasta, schema, caminhoSchemas), + ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings + }; + configuracao.ValidationEventHandler += (_, evento) => + throw new XmlSchemaValidationException("Erro de validação da DC-e: " + evento.Message, evento.Exception); + using (var texto = new StringReader(xml)) + using (var leitor = XmlReader.Create(texto, configuracao)) + while (leitor.Read()) { } + } +} diff --git a/ZeusFiscal.sln b/ZeusFiscal.sln index 43394ef38..b7958d14b 100644 --- a/ZeusFiscal.sln +++ b/ZeusFiscal.sln @@ -126,6 +126,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NFe.Danfe.PdfClown", "NFe.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NFe.Danfe.QuestPdf", "NFe.Danfe.QuestPdf\NFe.Danfe.QuestPdf.csproj", "{D868005D-1F35-43E0-BE57-7914D5BA1583}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DCe", "DCe", "{F62DD288-5ECA-74B7-3843-0A2E69FB470A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Classes", "DCe.Classes\DCe.Classes.csproj", "{1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Utils", "DCe.Utils\DCe.Utils.csproj", "{A388E674-202C-4FEC-81A7-99DEA23642AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -502,6 +508,22 @@ Global {D868005D-1F35-43E0-BE57-7914D5BA1583}.Release|Any CPU.Build.0 = Release|Any CPU {D868005D-1F35-43E0-BE57-7914D5BA1583}.Release|x86.ActiveCfg = Release|Any CPU {D868005D-1F35-43E0-BE57-7914D5BA1583}.Release|x86.Build.0 = Release|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Debug|x86.ActiveCfg = Debug|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Debug|x86.Build.0 = Debug|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Release|Any CPU.Build.0 = Release|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Release|x86.ActiveCfg = Release|Any CPU + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F}.Release|x86.Build.0 = Release|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Debug|x86.ActiveCfg = Debug|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Debug|x86.Build.0 = Debug|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Release|Any CPU.Build.0 = Release|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Release|x86.ActiveCfg = Release|Any CPU + {A388E674-202C-4FEC-81A7-99DEA23642AE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -561,6 +583,8 @@ Global {18429710-19E2-480F-8B3A-39FB6FC7AF19} = {3AC1A3D4-91D0-471A-AF9C-5EF8442F3F27} {1A057DFD-016C-4308-AE11-1E90AD809621} = {003B008B-F388-4FB5-A081-12C0FBA84B57} {D868005D-1F35-43E0-BE57-7914D5BA1583} = {003B008B-F388-4FB5-A081-12C0FBA84B57} + {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} + {A388E674-202C-4FEC-81A7-99DEA23642AE} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C8C4D9F7-EF86-49A2-83AF-13FA5D4E8DBB} From 795ab10480e3ff0d1c31fb25176df432be8423b1 Mon Sep 17 00:00:00 2001 From: Vision-Digital Date: Sat, 17 Oct 2026 17:45:26 -0300 Subject: [PATCH 2/3] =?UTF-8?q?feat(dce):=20servi=C3=A7os=20SOAP=20da=20DC?= =?UTF-8?q?-e=20=E2=80=94=20DCe.Wsdl=20e=20DCe.Servicos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementa autorização, consulta, status e eventos conforme NT e XSDs v1.00 da DC-e. Usa SOAP 1.2 com action no Content-Type e transporte próprio sem bypass de certificado. Mantém o autorizador nacional SEFAZ-PR e os XMLs brutos nos retornos. Inclui toda a suíte DCe.Testes por compartilhar projeto e referências de serviços. Permite CaminhoSchemas opcional sobre os XSDs embutidos. --- DCe.Servicos/DCe.Servicos.csproj | 11 + DCe.Servicos/DCeConfiguracao.cs | 23 ++ DCe.Servicos/DceEndpoints.cs | 23 ++ DCe.Servicos/DceService.cs | 142 +++++++ DCe.Servicos/IDceService.cs | 19 + DCe.Testes/DCe.Testes.csproj | 19 + DCe.Testes/DceCancelamentoTest.cs | 235 +++++++++++ DCe.Testes/DceChaveAcessoTest.cs | 147 +++++++ DCe.Testes/DceCoerenciaUfTest.cs | 182 +++++++++ DCe.Testes/DceDocumentoModeloTest.cs | 101 +++++ DCe.Testes/DceIntegracaoXmlTest.cs | 156 ++++++++ DCe.Testes/DceLeituraModeloTest.cs | 123 ++++++ DCe.Testes/DceRetornosModeloTest.cs | 222 +++++++++++ DCe.Testes/DceSchemasTest.cs | 163 ++++++++ DCe.Testes/DceSerializacaoTest.cs | 250 ++++++++++++ DCe.Testes/DceServiceTest.cs | 373 ++++++++++++++++++ DCe.Testes/DceSoapClientTest.cs | 121 ++++++ DCe.Testes/Fixtures/Cancelamento.xml | 1 + DCe.Testes/Fixtures/DceFixture.cs | 77 ++++ DCe.Testes/Fixtures/Declaracao.xml | 1 + .../Fixtures/Entradas/CancelamentoDce.cs | 38 ++ .../Fixtures/Entradas/DeclaracaoConteudo.cs | 60 +++ DCe.Testes/Fixtures/Entradas/EnderecoDce.cs | 20 + DCe.Testes/Fixtures/Entradas/ItemDce.cs | 14 + DCe.Testes/Fixtures/Entradas/PessoaDce.cs | 16 + DCe.Testes/Fixtures/Entradas/SignatarioDce.cs | 14 + DCe.Testes/Fixtures/Entradas/TransporteDce.cs | 12 + DCe.Testes/Fixtures/EntradasModelo.cs | 24 ++ DCe.Testes/Fixtures/FabricaModeloDCe.cs | 88 +++++ DCe.Wsdl/DCe.Wsdl.csproj | 11 + DCe.Wsdl/Soap/ISoapClient.cs | 11 + DCe.Wsdl/Soap/SoapClient.cs | 58 +++ DCe.Wsdl/Soap/SoapEnvelope.cs | 112 ++++++ ZeusFiscal.sln | 37 ++ 34 files changed, 2904 insertions(+) create mode 100644 DCe.Servicos/DCe.Servicos.csproj create mode 100644 DCe.Servicos/DCeConfiguracao.cs create mode 100644 DCe.Servicos/DceEndpoints.cs create mode 100644 DCe.Servicos/DceService.cs create mode 100644 DCe.Servicos/IDceService.cs create mode 100644 DCe.Testes/DCe.Testes.csproj create mode 100644 DCe.Testes/DceCancelamentoTest.cs create mode 100644 DCe.Testes/DceChaveAcessoTest.cs create mode 100644 DCe.Testes/DceCoerenciaUfTest.cs create mode 100644 DCe.Testes/DceDocumentoModeloTest.cs create mode 100644 DCe.Testes/DceIntegracaoXmlTest.cs create mode 100644 DCe.Testes/DceLeituraModeloTest.cs create mode 100644 DCe.Testes/DceRetornosModeloTest.cs create mode 100644 DCe.Testes/DceSchemasTest.cs create mode 100644 DCe.Testes/DceSerializacaoTest.cs create mode 100644 DCe.Testes/DceServiceTest.cs create mode 100644 DCe.Testes/DceSoapClientTest.cs create mode 100644 DCe.Testes/Fixtures/Cancelamento.xml create mode 100644 DCe.Testes/Fixtures/DceFixture.cs create mode 100644 DCe.Testes/Fixtures/Declaracao.xml create mode 100644 DCe.Testes/Fixtures/Entradas/CancelamentoDce.cs create mode 100644 DCe.Testes/Fixtures/Entradas/DeclaracaoConteudo.cs create mode 100644 DCe.Testes/Fixtures/Entradas/EnderecoDce.cs create mode 100644 DCe.Testes/Fixtures/Entradas/ItemDce.cs create mode 100644 DCe.Testes/Fixtures/Entradas/PessoaDce.cs create mode 100644 DCe.Testes/Fixtures/Entradas/SignatarioDce.cs create mode 100644 DCe.Testes/Fixtures/Entradas/TransporteDce.cs create mode 100644 DCe.Testes/Fixtures/EntradasModelo.cs create mode 100644 DCe.Testes/Fixtures/FabricaModeloDCe.cs create mode 100644 DCe.Wsdl/DCe.Wsdl.csproj create mode 100644 DCe.Wsdl/Soap/ISoapClient.cs create mode 100644 DCe.Wsdl/Soap/SoapClient.cs create mode 100644 DCe.Wsdl/Soap/SoapEnvelope.cs diff --git a/DCe.Servicos/DCe.Servicos.csproj b/DCe.Servicos/DCe.Servicos.csproj new file mode 100644 index 000000000..8b3be97ec --- /dev/null +++ b/DCe.Servicos/DCe.Servicos.csproj @@ -0,0 +1,11 @@ + + + net462;netstandard2.0;net8.0;net10.0 + latest + enable + + + + + + diff --git a/DCe.Servicos/DCeConfiguracao.cs b/DCe.Servicos/DCeConfiguracao.cs new file mode 100644 index 000000000..134303849 --- /dev/null +++ b/DCe.Servicos/DCeConfiguracao.cs @@ -0,0 +1,23 @@ +using System.Security.Cryptography.X509Certificates; +using DFe.Classes.Flags; +using DFe.Utils; + +namespace DCe.Servicos; + +/// Configuração por instância dos serviços nacionais da DC-e. +public sealed class DCeConfiguracao +{ + public TipoAmbiente TipoAmbiente { get; set; } = TipoAmbiente.Homologacao; + public ConfiguracaoCertificado ConfiguracaoCertificado { get; set; } = new ConfiguracaoCertificado(); + + /// Certificado já carregado, opcional. Seu ciclo de vida pertence ao chamador. + public X509Certificate2? Certificado { get; set; } + + /// Tempo limite de comunicação em milissegundos, como nas demais famílias Zeus. + public int TimeOut { get; set; } = 60000; + /// Pasta dos XSDs, contendo dce/, consulta/ e evento/. Nulo usa os recursos embutidos. + public string? CaminhoSchemas { get; set; } + + public bool IsSalvarXml { get; set; } + public string CaminhoSalvarXml { get; set; } = string.Empty; +} diff --git a/DCe.Servicos/DceEndpoints.cs b/DCe.Servicos/DceEndpoints.cs new file mode 100644 index 000000000..06ac8037f --- /dev/null +++ b/DCe.Servicos/DceEndpoints.cs @@ -0,0 +1,23 @@ +using System; +using DFe.Classes.Flags; + +namespace DCe.Servicos; + +/// Autorizador nacional SEFAZ-PR, sem seleção por UF. +public static class DceEndpoints +{ + public static string Base(TipoAmbiente ambiente) + { + switch (ambiente) + { + case TipoAmbiente.Producao: return "https://dce.fazenda.pr.gov.br/dce"; + case TipoAmbiente.Homologacao: return "https://homologacao.dce.fazenda.pr.gov.br/dce"; + default: throw new ArgumentOutOfRangeException(nameof(ambiente), "Ambiente da DC-e inválido."); + } + } + + public static string Autorizacao(TipoAmbiente ambiente) => Base(ambiente) + "/DCeAutorizacao"; + public static string Consulta(TipoAmbiente ambiente) => Base(ambiente) + "/DCeConsulta"; + public static string RecepcaoEvento(TipoAmbiente ambiente) => Base(ambiente) + "/DCeRecepcaoEvento"; + public static string StatusServico(TipoAmbiente ambiente) => Base(ambiente) + "/DCeStatusServico"; +} diff --git a/DCe.Servicos/DceService.cs b/DCe.Servicos/DceService.cs new file mode 100644 index 000000000..d10569153 --- /dev/null +++ b/DCe.Servicos/DceService.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using DCe.Classes.Servicos.Evento; +using DCe.Classes.Servicos.Consulta; +using DCe.Classes.Servicos.StatusServico; +using DCe.Utils.Extensoes; +using DCeDocumento = DCe.Classes.Informacoes.DCe; +using DCe.Classes.Retorno; +using DCe.Wsdl.Soap; +using DCe.Utils; +using DCe.Utils.Validacao; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Utils.Assinatura; + +namespace DCe.Servicos; + +/// Autorização, consulta, status e evento 110111 no autorizador nacional. +public sealed class DceService : IDceService +{ + private readonly DCeConfiguracao _configuracao; + private readonly ISoapClient _soapClient; + + public DceService(DCeConfiguracao configuracao, ISoapClient? soapClient = null) + { + _configuracao = configuracao ?? throw new ArgumentNullException(nameof(configuracao)); + _soapClient = soapClient ?? new SoapClient(); + } + + public Task AutorizarAsync(DCeDocumento declaracao) + { + if (declaracao == null) throw new ArgumentNullException(nameof(declaracao)); + ValidarAmbiente(declaracao.infDCe.ide.tpAmb); + declaracao.ValidarDadosLocais(); + var xml = declaracao.ObterXmlString(); + return ComCertificadoAsync(async certificado => + { + var assinado = new DCeDocumento().CarregarDeXmlString(xml).Assina(certificado); + assinado.Valida(_configuracao.CaminhoSchemas); + return await EnviarAsync(assinado.ObterXmlString(), "DCeAutorizacao", "dceAutorizacao", + DceEndpoints.Autorizacao(declaracao.infDCe.ide.tpAmb), certificado).ConfigureAwait(false); + }); + } + + public Task ConsultarAsync(string chaveAcesso) + { + if (string.IsNullOrWhiteSpace(chaveAcesso)) throw new ArgumentException("A chave de acesso não pode ser vazia.", nameof(chaveAcesso)); + return ConsultarAsync(new consSitDCe { tpAmb = _configuracao.TipoAmbiente, chDCe = chaveAcesso }); + } + + public Task ConsultarAsync(consSitDCe consulta) + { + if (consulta == null) throw new ArgumentNullException(nameof(consulta)); + ValidarAmbiente(consulta.tpAmb); + var endpoint = DceEndpoints.Consulta(consulta.tpAmb); + var xml = consulta.ObterXmlString(); + Validador.Valida(xml, "consulta", "consSitDCe_v1.00.xsd", _configuracao.CaminhoSchemas); + return ComCertificadoAsync(async certificado => await EnviarAsync(xml, + "DCeConsulta", "dceConsulta", endpoint, certificado).ConfigureAwait(false)); + } + + public Task StatusServicoAsync(Estado codigoUf) + { + return StatusServicoAsync(new consStatServDCe { tpAmb = _configuracao.TipoAmbiente, cUF = codigoUf }); + } + + public Task StatusServicoAsync(consStatServDCe consulta) + { + if (consulta == null) throw new ArgumentNullException(nameof(consulta)); + ValidarAmbiente(consulta.tpAmb); + var endpoint = DceEndpoints.StatusServico(consulta.tpAmb); + var xml = consulta.ObterXmlString(); + Validador.Valida(xml, "consulta", "consStatServDCe_v1.00.xsd", _configuracao.CaminhoSchemas); + return ComCertificadoAsync(async certificado => await EnviarAsync(xml, + "DCeStatusServico", "dceStatusServico", endpoint, certificado).ConfigureAwait(false)); + } + + public Task CancelarAsync(eventoDCe pedido) + { + if (pedido == null) throw new ArgumentNullException(nameof(pedido)); + ValidarAmbiente(pedido.infEvento.tpAmb); + pedido.ValidarDadosLocais(); + var xml = pedido.ObterXmlString(); + // O schema geral usa processContents="skip" no detalhe; validar também o evento 110111. + var detalhe = pedido.infEvento.detEvento.evCancDCe; + Validador.Valida(detalhe.ObterXmlString(), "evento", "evCancDCe_v1.00.xsd", _configuracao.CaminhoSchemas); + return ComCertificadoAsync(async certificado => + { + var assinado = new eventoDCe().CarregarDeXmlString(xml).Assina(certificado); + assinado.Valida(_configuracao.CaminhoSchemas); + return await EnviarAsync(assinado.ObterXmlString(), "DCeRecepcaoEvento", "dceRecepcaoEvento", + DceEndpoints.RecepcaoEvento(pedido.infEvento.tpAmb), certificado).ConfigureAwait(false); + }); + } + + private void ValidarAmbiente(TipoAmbiente ambiente) + { + DceEndpoints.Base(ambiente); + if (ambiente != _configuracao.TipoAmbiente) + throw new ArgumentException("O ambiente do documento difere da configuração do serviço.", nameof(ambiente)); + } + + private async Task ComCertificadoAsync(Func> executar) + { + if (_configuracao.TimeOut <= 0) throw new ArgumentOutOfRangeException(nameof(_configuracao.TimeOut), "O timeout deve ser positivo."); + var certificado = _configuracao.Certificado ?? CertificadoDigital.ObterCertificado(_configuracao.ConfiguracaoCertificado); + var liberar = _configuracao.Certificado == null && !_configuracao.ConfiguracaoCertificado.ManterDadosEmCache; + try { return await executar(certificado).ConfigureAwait(false); } + finally { if (liberar) certificado.Dispose(); } + } + + private async Task EnviarAsync(string xml, string servico, string operacao, string endpoint, X509Certificate2 certificado) + where T : RetornoBase, new() + { + var nsServico = SoapEnvelope.NamespaceWsdl + servico; + var envelope = SoapEnvelope.Criar(xml, servico); + var identificador = Guid.NewGuid().ToString("N"); + SalvarXml(xml, identificador + "-" + servico + "-env.xml"); + var resposta = await _soapClient.SendAsync(endpoint, nsServico + "/" + operacao, + envelope, certificado, _configuracao.TimeOut).ConfigureAwait(false); + var retorno = SoapEnvelope.ExtrairXmlFiscal(resposta); + SalvarXml(retorno, identificador + "-" + servico + "-ret.xml"); + var tipado = new T().CarregarDeXmlString(SoapEnvelope.Desembrulhar(resposta)); + tipado.EnvioStr = SoapEnvelope.XmlFiscal(xml); + tipado.RetornoStr = retorno; + tipado.RetornoCompletoStr = resposta; + return tipado; + } + + private void SalvarXml(string xml, string arquivo) + { + if (!_configuracao.IsSalvarXml) return; + if (string.IsNullOrWhiteSpace(_configuracao.CaminhoSalvarXml)) + throw new InvalidOperationException("Informe CaminhoSalvarXml quando IsSalvarXml estiver habilitado."); + Directory.CreateDirectory(_configuracao.CaminhoSalvarXml); + File.WriteAllText(Path.Combine(_configuracao.CaminhoSalvarXml, arquivo), xml, new UTF8Encoding(false)); + } + +} diff --git a/DCe.Servicos/IDceService.cs b/DCe.Servicos/IDceService.cs new file mode 100644 index 000000000..56a631afb --- /dev/null +++ b/DCe.Servicos/IDceService.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using DCe.Classes.Servicos.Evento; +using DCe.Classes.Servicos.Consulta; +using DCe.Classes.Servicos.StatusServico; +using DCeDocumento = DCe.Classes.Informacoes.DCe; +using DCe.Classes.Retorno; +using DFe.Classes.Entidades; + +namespace DCe.Servicos; + +public interface IDceService +{ + Task AutorizarAsync(DCeDocumento declaracao); + Task ConsultarAsync(string chaveAcesso); + Task ConsultarAsync(consSitDCe consulta); + Task StatusServicoAsync(Estado codigoUf); + Task StatusServicoAsync(consStatServDCe consulta); + Task CancelarAsync(eventoDCe pedido); +} diff --git a/DCe.Testes/DCe.Testes.csproj b/DCe.Testes/DCe.Testes.csproj new file mode 100644 index 000000000..b098756f2 --- /dev/null +++ b/DCe.Testes/DCe.Testes.csproj @@ -0,0 +1,19 @@ + + + net10.0 + false + enable + enable + latest + + + + + + + + + + + + diff --git a/DCe.Testes/DceCancelamentoTest.cs b/DCe.Testes/DceCancelamentoTest.cs new file mode 100644 index 000000000..07e58bec8 --- /dev/null +++ b/DCe.Testes/DceCancelamentoTest.cs @@ -0,0 +1,235 @@ +using DCe.Testes.Fixtures; +using DCe.Utils.Extensoes; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System.Xml; +using System.Xml.Linq; +using DCe.Utils.Abstractions; +using DCe.Testes.Fixtures.Entradas; +using DCe.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceCancelamentoTest +{ + private static readonly XNamespace Ns = "http://www.portalfiscal.inf.br/dce"; + private const string Chave = "26260899999999000191990010000000011300123455"; + + private sealed class ClockFixo : IClock + { + public DateTime UtcNow => new(2026, 8, 22, 15, 0, 0, DateTimeKind.Utc); + } + + private sealed class RandomFixo : IRandomSource + { + public int ProximoCodigoDocumento() => 123456; + } + + private static CancelamentoDce Pedido() => new() + { + Ambiente = TipoAmbiente.Homologacao, + ChaveAcesso = Chave, + CodigoOrgao = (Estado)26, + TipoEmitente = 3, + CnpjAutor = "99999999000191", + CpfUsuarioEmitente = "12345678909", + NumeroProtocolo = "126260000000001", + Justificativa = "TRANSPORTE CANCELADO A PEDIDO DO DECLARANTE", + SequenciaEvento = 1 + }; + + private static string Construir() => + new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(Pedido()).ObterXmlString(); + + /// + /// A regra de formação do Id está no XSD: "ID" + tpEvento(6) + chave(44) + nSeqEvento(3 + /// com zeros à esquerda) = ID + 53 dígitos. É a tag que será assinada; errar aqui invalida + /// a assinatura inteira. + /// + [TestMethod] + public void ConstruirEventoCancelamento_MontaOIdComARegraDoXsd() + { + var id = XDocument.Parse(Construir()).Root! + .Element(Ns + "infEvento")!.Attribute("Id")!.Value; + + Assert.AreEqual("ID" + "110111" + Chave + "001", id); + Assert.AreEqual(55, id.Length); + } + + [TestMethod] + public void ConstruirEventoCancelamento_UsaOTipoDeEvento110111() + { + var inf = XDocument.Parse(Construir()).Root!.Element(Ns + "infEvento")!; + + Assert.AreEqual("110111", inf.Element(Ns + "tpEvento")!.Value); + Assert.AreEqual("1", inf.Element(Ns + "nSeqEvento")!.Value); + } + + [TestMethod] + public void ConstruirEventoCancelamento_LevaProtocoloEJustificativa() + { + var ev = XDocument.Parse(Construir()).Root! + .Element(Ns + "infEvento")!.Element(Ns + "detEvento")!.Element(Ns + "evCancDCe")!; + + Assert.AreEqual("126260000000001", ev.Element(Ns + "nProt")!.Value); + Assert.AreEqual("TRANSPORTE CANCELADO A PEDIDO DO DECLARANTE", ev.Element(Ns + "xJust")!.Value); + } + + /// + /// O usuário emitente é um choice entre CNPJ, CPF e IdOutros, igual à emissão. Emitir + /// dois, ou um vazio, reprova no schema. + /// + [TestMethod] + public void ConstruirEventoCancelamento_EmiteUmSoIdentificadorDoUsuarioEmitente() + { + var inf = XDocument.Parse(Construir()).Root!.Element(Ns + "infEvento")!; + var presentes = new[] { "CNPJUsEmit", "CPFUsEmit", "IdOutrosUsEmit" } + .Count(n => inf.Element(Ns + n) is not null); + + Assert.AreEqual(1, presentes); + Assert.AreEqual("12345678909", inf.Element(Ns + "CPFUsEmit")!.Value); + } + + [TestMethod] + public void ConstruirEventoCancelamento_NaoEmiteAssinatura() + { + XNamespace ds = "http://www.w3.org/2000/09/xmldsig#"; + + Assert.AreEqual(0, XDocument.Parse(Construir()).Descendants(ds + "Signature").Count()); + } + + [TestMethod] + public void ConstruirEventoCancelamento_DeclaraEncodingUtf8() + { + Assert.IsTrue(Construir().StartsWith("", StringComparison.Ordinal)); + } + + /// + /// O schema exige ds:Signature. O placeholder verifica apenas a estrutura XML; + /// a validade criptográfica é coberta pelos testes de assinatura. + /// + [TestMethod] + public void ConstruirEventoCancelamento_ValidaContraOSchemaOficialDepoisDeAssinado() + { + XNamespace ds = "http://www.w3.org/2000/09/xmldsig#"; + var raiz = XDocument.Parse(Construir()); + var id = raiz.Root!.Element(Ns + "infEvento")!.Attribute("Id")!.Value; + raiz.Root.Add(AssinaturaPlaceholder(ds, id)); + + var doc = new XmlDocument { Schemas = DceSchemaLoader.Carregar("evento", "eventoDCe_v1.00.xsd") }; + doc.LoadXml(raiz.ToString(SaveOptions.DisableFormatting)); + + var erros = new List(); + doc.Validate((_, e) => erros.Add(e.Message)); + + Assert.IsTrue(erros.Count == 0, string.Join(" | ", erros)); + } + + private static XElement AssinaturaPlaceholder(XNamespace ds, string idAssinado) => + new(ds + "Signature", + new XElement(ds + "SignedInfo", + new XElement(ds + "CanonicalizationMethod", + new XAttribute("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315")), + new XElement(ds + "SignatureMethod", + new XAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1")), + new XElement(ds + "Reference", + new XAttribute("URI", "#" + idAssinado), + new XElement(ds + "Transforms", + new XElement(ds + "Transform", + new XAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature")), + new XElement(ds + "Transform", + new XAttribute("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"))), + new XElement(ds + "DigestMethod", + new XAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1")), + new XElement(ds + "DigestValue", "AAAA"))), + new XElement(ds + "SignatureValue", "AAAA"), + new XElement(ds + "KeyInfo", + new XElement(ds + "X509Data", + new XElement(ds + "X509Certificate", "AAAA")))); + + /// + /// Assim como dhEmi (regra B07-10, cStat 703), dhEvento precisa sair no offset local + /// do emitente, não em "+00:00". Instante fixo do ClockFixo é 2026-08-22T15:00:00Z, offset + /// default -3h. + /// + [TestMethod] + public void ConstruirEventoCancelamento_EmiteDhEventoNoOffsetLocalPadrao() + { + var inf = XDocument.Parse(Construir()).Root!.Element(Ns + "infEvento")!; + + Assert.AreEqual("2026-08-22T12:00:00-03:00", inf.Element(Ns + "dhEvento")!.Value); + } + + [TestMethod] + public void ConstruirEventoCancelamento_RespeitaOffsetCustomizado() + { + var pedido = Pedido(); + pedido.OffsetHorario = TimeSpan.FromHours(-4); + + var xml = new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(pedido).ObterXmlString(); + var inf = XDocument.Parse(xml).Root!.Element(Ns + "infEvento")!; + + Assert.AreEqual("2026-08-22T11:00:00-04:00", inf.Element(Ns + "dhEvento")!.Value); + } + + // ---- prazo ---- + + /// + /// A Cláusula 11ª do Ajuste SINIEF 05/2021 prevê 24 horas da autorização. + /// A verificação local tolera deriva entre o relógio do chamador e o dhRecbto da SEFAZ: + /// decorrido negativo não impede o cancelamento de um documento recém-autorizado. + /// A SEFAZ verifica o prazo e pode rejeitar o evento com cStat 501. + /// + [DataTestMethod] + [DataRow(-1)] // relógio do chamador 1h atrás do da SEFAZ + [DataRow(-24)] + public void DentroDoPrazo_ComDerivaDeRelogioParaTras_ContinuaLiberado(int horas) + { + var autorizada = new DateTimeOffset(2026, 8, 22, 12, 0, 0, TimeSpan.Zero); + + Assert.IsTrue(DcePrazoCancelamento.DentroDoPrazo(autorizada, autorizada.AddHours(horas))); + } + + [DataTestMethod] + [DataRow(0, true)] + [DataRow(23, true)] + [DataRow(24, false)] + [DataRow(25, false)] + public void DentroDoPrazo_AplicaAJanelaDe24Horas(int horasDepois, bool esperado) + { + var autorizada = new DateTimeOffset(2026, 8, 22, 12, 0, 0, TimeSpan.Zero); + + Assert.AreEqual(esperado, + DcePrazoCancelamento.DentroDoPrazo(autorizada, autorizada.AddHours(horasDepois))); + } + + /// + /// O cOrgao do evento é o cUF da própria chave, não o do autorizador (41). + /// Confirmado contra a SEFAZ em homologação: cOrgao corresponde aos dois primeiros + /// dígitos da chave e pode ser conferido localmente. + /// + [TestMethod] + public void CodigoOrgao_DivergindoDoCufDaChave_Recusa() + { + var pedido = Pedido(); + pedido.CodigoOrgao = (Estado)41; // PR, o autorizador — chave é de PE (26) + + var erro = Assert.ThrowsException( + () => new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(pedido).ObterXmlString()); + + Assert.IsTrue(erro.Message.IndexOf("41", StringComparison.Ordinal) >= 0); + Assert.IsTrue(erro.Message.IndexOf("26", StringComparison.Ordinal) >= 0); + } + + [TestMethod] + public void ChaveAcesso_ForaDoFormato_Recusa() + { + var pedido = Pedido(); + pedido.ChaveAcesso = "2626089999999900019199001"; + + Assert.ThrowsException( + () => new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(pedido).ObterXmlString()); + } +} diff --git a/DCe.Testes/DceChaveAcessoTest.cs b/DCe.Testes/DceChaveAcessoTest.cs new file mode 100644 index 000000000..68243aa1a --- /dev/null +++ b/DCe.Testes/DceChaveAcessoTest.cs @@ -0,0 +1,147 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DCe.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceChaveAcessoTest +{ + [DataTestMethod] + [DataRow("0")] + [DataRow("6")] + public void CalcularDv_RestoZeroOuUm_DevolveZero(string ultimoDigito) + { + var chave = new string('0', 42) + ultimoDigito; + Assert.AreEqual(0, DceChaveAcesso.CalcularDv(chave)); + } + + [TestMethod] + public void Montar_CulturaComOutroCalendario_MantemAammGregoriano() + { + var anterior = System.Globalization.CultureInfo.CurrentCulture; + try + { + System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo("ar-SA"); + var chave = DceChaveAcesso.Montar(26, new DateTime(2026, 9, 1, 1, 0, 0, DateTimeKind.Utc), + "99999999000191", 1, 42, 1, 3, 0, 123456, TimeSpan.FromHours(-3)); + Assert.AreEqual("2608", chave.Substring(2, 4)); + } + finally { System.Globalization.CultureInfo.CurrentCulture = anterior; } + } + + /// Chave sintética sem DV, com CNPJ de teste e soma ponderada 644. + private const string ChaveSintetica = "1101019999999900019199001000000001130000029"; + + /// + /// Aplica o módulo 11 do item 2.2.4.1 do Manual DC-e — Visão Geral a uma chave sintética. + /// A soma 644 deixa resto 6 e, portanto, DV 5. + /// + [TestMethod] + public void CalcularDv_ChaveSintetica_DevolveDvEsperado() + { + Assert.AreEqual(5, DceChaveAcesso.CalcularDv(ChaveSintetica)); + } + + /// + /// Confere a soma ponderada separadamente do DV para isolar as etapas do cálculo. + /// A chave sintética tem soma conhecida de 644. + /// + [TestMethod] + public void SomatoriaDasPonderacoes_ChaveSintetica_DevolveSomaEsperada() + { + Assert.AreEqual(644, DceChaveAcesso.SomarPonderacoes(ChaveSintetica)); + } + + /// + /// Módulo 11: quando o resto da divisão é 0 ou 1, o DV é 0 — não 11 nem 10. + /// É o caso de borda que quebra implementações ingênuas. + /// + [DataTestMethod] + [DataRow("1101019999999900019199001000000001130000029", 5)] + public void CalcularDv_NuncaDevolveValorForaDeZeroANove(string chave43, int esperado) + { + var dv = DceChaveAcesso.CalcularDv(chave43); + + Assert.IsTrue(dv >= 0 && dv <= 9); + Assert.AreEqual(esperado, dv); + } + + [TestMethod] + public void Montar_ProduzChaveDe44DigitosComOsCamposNaOrdemDaTabela() + { + // CNPJ sintético dos fixtures: base 999999990001, DVs 9 e 1 por módulo 11. + const string cnpjFicticio = "99999999000191"; + var pesosPrimeiroDv = new[] { 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 }; + var pesosSegundoDv = new[] { 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 }; + var somaPrimeiroDv = pesosPrimeiroDv.Select((peso, i) => peso * (cnpjFicticio[i] - '0')).Sum(); + var somaSegundoDv = pesosSegundoDv.Select((peso, i) => peso * (cnpjFicticio[i] - '0')).Sum(); + Assert.AreEqual(398, somaPrimeiroDv); + Assert.AreEqual(417, somaSegundoDv); + Assert.AreEqual(cnpjFicticio[12] - '0', 11 - somaPrimeiroDv % 11); + Assert.AreEqual(cnpjFicticio[13] - '0', 11 - somaSegundoDv % 11); + + var chave = DceChaveAcesso.Montar( + cUF: 26, + dhEmi: new DateTime(2026, 8, 22, 10, 0, 0, DateTimeKind.Utc), + cnpjSignatario: cnpjFicticio, + serie: 1, + nDC: 42, + tpEmis: 1, + tpEmit: 3, + nSiteAutoriz: 0, + cDC: 123456, + offsetHorario: TimeSpan.FromHours(-3)); + + Assert.AreEqual(44, chave.Length); + Assert.IsTrue(chave.All(char.IsDigit)); + + Assert.AreEqual("26", chave.Substring(0, 2)); // cUF + Assert.AreEqual("2608", chave.Substring(2, 4)); // AAMM + Assert.AreEqual("99999999000191", chave.Substring(6, 14)); + Assert.AreEqual("99", chave.Substring(20, 2)); // mod — sempre 99 + Assert.AreEqual("001", chave.Substring(22, 3)); // serie + Assert.AreEqual("000000042", chave.Substring(25, 9)); // nDC + Assert.AreEqual("1", chave.Substring(34, 1)); // tpEmis + Assert.AreEqual("3", chave.Substring(35, 1)); // tpEmit — Transportadora + Assert.AreEqual("0", chave.Substring(36, 1)); // nSiteAutoriz + Assert.AreEqual("123456", chave.Substring(37, 6)); // cDC + Assert.AreEqual(DceChaveAcesso.CalcularDv(chave.Substring(0, 43)).ToString(), chave.Substring(43, 1)); + } + + /// + /// O AAMM da chave sai do mesmo instante local que vai no dhEmi — não do UTC. Sem isto, + /// toda emissão entre 00:00 e 03:00 UTC do dia 1º produziria uma chave do mês novo num + /// documento datado do mês anterior, e a SEFAZ rejeitaria com cStat 502 (regra A03-10: + /// "Chave de Acesso do campo Id difere da concatenação dos campos correspondentes"). + /// Três horas por mês em que nada emitiria — e o relógio fixo ao meio-dia dos demais + /// testes nunca chega perto da virada. + /// + [TestMethod] + public void Montar_DerivaAammDoInstanteLocal_NaoDoUtc() + { + // 01/09/2026 01:00 UTC = 31/08/2026 22:00 em -03:00 — o documento é de AGOSTO + var viradaDeMes = new DateTime(2026, 9, 1, 1, 0, 0, DateTimeKind.Utc); + + var chave = DceChaveAcesso.Montar( + cUF: 26, dhEmi: viradaDeMes, cnpjSignatario: "99999999000191", serie: 1, nDC: 42, + tpEmis: 1, tpEmit: 3, nSiteAutoriz: 0, cDC: 123456, + offsetHorario: TimeSpan.FromHours(-3)); + + Assert.AreEqual("2608", chave.Substring(2, 4)); + } + + /// Fora da virada, local e UTC coincidem — a correção não pode mexer no caso comum. + [TestMethod] + public void Montar_ForaDaViradaDeMes_MantemOAamm() + { + var chave = DceChaveAcesso.Montar( + cUF: 26, dhEmi: new DateTime(2026, 8, 22, 12, 0, 0, DateTimeKind.Utc), + cnpjSignatario: "99999999000191", serie: 1, nDC: 42, + tpEmis: 1, tpEmit: 3, nSiteAutoriz: 0, cDC: 123456, + offsetHorario: TimeSpan.FromHours(-3)); + + Assert.AreEqual("2608", chave.Substring(2, 4)); + } +} diff --git a/DCe.Testes/DceCoerenciaUfTest.cs b/DCe.Testes/DceCoerenciaUfTest.cs new file mode 100644 index 000000000..df287a33c --- /dev/null +++ b/DCe.Testes/DceCoerenciaUfTest.cs @@ -0,0 +1,182 @@ +using DCe.Utils.Extensoes; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System.Xml.Linq; +using DCe.Utils.Abstractions; +using DCe.Utils.Ibge; +using DCe.Testes.Fixtures.Entradas; +using DCe.Utils; +using DCe.Testes.Fixtures; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +/// +/// Verifica localmente divergências entre UF e município que a SEFAZ rejeita, +/// identificando os campos incoerentes antes da transmissão. +/// +/// Confirmado contra a SEFAZ em homologação: cUF diferente da UF do emitente gera +/// cStat 220 ("Codigo da UF da DC-e difere da UF do emitente"). O cUF da chave +/// corresponde à UF do emitente; a UF do destinatário e a do autorizador são independentes. +/// +[TestClass] +public class DceCoerenciaUfTest +{ + private static readonly XNamespace Ns = "http://www.portalfiscal.inf.br/dce"; + + private sealed class ClockFixo : IClock + { + public DateTime UtcNow => new(2026, 8, 22, 12, 0, 0, DateTimeKind.Utc); + } + + private sealed class RandomFixo : IRandomSource + { + public int ProximoCodigoDocumento() => 123456; + } + + private static string Construir(Action ajustar) + { + var dc = DceFixture.Padrao(); + ajustar(dc); + return new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(dc).ObterXmlString(); + } + + [TestMethod] + public void CodigoUf_DivergindoDaUfDoEmitente_Recusa() + { + // cUF = 29 (BA) com emitente em PE: divergência correspondente ao cStat 220. + var erro = Assert.ThrowsException(() => Construir(dc => dc.CodigoUf = (Estado)29)); + + Assert.IsTrue(erro.Message.IndexOf("cUF", StringComparison.Ordinal) >= 0); + Assert.IsTrue(erro.Message.IndexOf("29", StringComparison.Ordinal) >= 0); + Assert.IsTrue(erro.Message.IndexOf("PE", StringComparison.Ordinal) >= 0); + Assert.IsTrue(erro.Message.IndexOf("220", StringComparison.Ordinal) >= 0); + } + + [TestMethod] + public void CodigoUf_CoerenteComOEmitente_Constroi() + { + // A fixture já é coerente (cUF 26, emitente em PE): a guarda não pode barrar o caminho feliz + var xml = Construir(_ => { }); + + Assert.IsTrue(xml.IndexOf("26", StringComparison.Ordinal) >= 0); + } + + /// Emitente na BA com cUF 29 também passa — a guarda compara, não fixa uma UF. + [TestMethod] + public void CodigoUf_CoerenteComOutraUf_Constroi() + { + var xml = Construir(dc => + { + dc.CodigoUf = (Estado)29; + dc.Emitente.Endereco.Uf = "BA"; + dc.Emitente.Endereco.CodigoMunicipio = 2918407; // Juazeiro/BA + dc.Emitente.Endereco.Municipio = "JUAZEIRO"; + }); + + Assert.IsTrue(xml.IndexOf("29", StringComparison.Ordinal) >= 0); + } + + [TestMethod] + public void UfDoEmitente_Desconhecida_Recusa() + { + var erro = Assert.ThrowsException(() => Construir(dc => dc.Emitente.Endereco.Uf = "XX")); + + Assert.IsTrue(erro.Message.IndexOf("XX", StringComparison.Ordinal) >= 0); + } + + /// Regra C09-20 do Anexo I — rejeição 273. + [TestMethod] + public void MunicipioDoEmitente_DeOutraUf_Recusa() + { + var erro = Assert.ThrowsException( + () => Construir(dc => dc.Emitente.Endereco.CodigoMunicipio = 2918407)); // Juazeiro/BA num emitente PE + + Assert.IsTrue(erro.Message.IndexOf("2918407", StringComparison.Ordinal) >= 0); + Assert.IsTrue(erro.Message.IndexOf("273", StringComparison.Ordinal) >= 0); + } + + /// Regra E10-20 do Anexo I — rejeição 275. + [TestMethod] + public void MunicipioDoDestinatario_DeOutraUf_Recusa() + { + var erro = Assert.ThrowsException( + () => Construir(dc => dc.Destinatario.Endereco.CodigoMunicipio = 2918407)); // Juazeiro/BA num dest PE + + Assert.IsTrue(erro.Message.IndexOf("275", StringComparison.Ordinal) >= 0); + } + + /// + /// Destinatário no exterior é a exceção que a própria E10-20 abre: UF "EX" e município + /// 9999999. A guarda não pode confundir isso com incoerência. + /// + [TestMethod] + public void DestinatarioNoExterior_NaoEhBarrado() + { + var xml = Construir(dc => + { + dc.Destinatario.Endereco.Uf = "EX"; + dc.Destinatario.Endereco.CodigoMunicipio = 9999999; + dc.Destinatario.Endereco.CodigoPais = 2496; // BACEN — Uruguai + dc.Destinatario.Endereco.Pais = "URUGUAI"; + }); + + var ender = XDocument.Parse(xml).Root! + .Element(Ns + "infDCe")!.Element(Ns + "dest")!.Element(Ns + "enderDest")!; + + Assert.AreEqual("EX", ender.Element(Ns + "UF")!.Value); + Assert.AreEqual("9999999", ender.Element(Ns + "cMun")!.Value); + } + + /// + /// O tipo TUf de tiposGeralDCe_v1.00.xsd enumera EX junto das + /// 27 siglas. O endereço enderDest usa TEndeDest, cujo campo UF + /// tem tipo TUf. O teste confere essa permissão no schema embutido. + /// + [TestMethod] + public void SchemaAdmiteUfExterior() + { + using var schema = typeof(global::DCe.Classes.Informacoes.DCe).Assembly + .GetManifestResourceStream("DCe.Classes.Schemas.dce.tiposGeralDCe_v1.00.xsd")!; + var texto = new StreamReader(schema).ReadToEnd(); + var tUf = texto.Substring(texto.IndexOf("name=\"TUf\"", StringComparison.Ordinal)); + tUf = tUf.Substring(0, tUf.IndexOf("", StringComparison.Ordinal)); + + Assert.IsTrue(tUf.IndexOf("", StringComparison.Ordinal) >= 0); + } + + /// + /// O emitente é sempre nacional — a DC-e acoberta transporte dentro do país. "EX" no + /// emitente é erro, não exceção. + /// + [TestMethod] + public void EmitenteNoExterior_Recusa() + { + Assert.ThrowsException(() => Construir(dc => dc.Emitente.Endereco.Uf = "EX")); + } + + [DataTestMethod] + [DataRow("PE", 26)] + [DataRow("BA", 29)] + [DataRow("pe", 26)] + [DataRow("SP", 35)] + [DataRow("DF", 53)] + public void TabelaDeUf_MapeiaSiglaParaCodigoIbge(string sigla, int esperado) + { + Assert.AreEqual(esperado, UnidadesFederativas.CodigoDe(sigla)); + } + + [TestMethod] + public void TabelaDeUf_TemAsVinteESeteUnidades() + { + Assert.AreEqual(27, Enumerable.Range(11, 43).Count(c => UnidadesFederativas.SiglaDe(c) is not null)); + } + + [TestMethod] + public void TabelaDeUf_SiglaDesconhecida_DevolveNulo() + { + Assert.IsNull(UnidadesFederativas.CodigoDe("XX")); + Assert.IsNull(UnidadesFederativas.CodigoDe(null)); + Assert.IsNull(UnidadesFederativas.SiglaDe(99)); + } +} diff --git a/DCe.Testes/DceDocumentoModeloTest.cs b/DCe.Testes/DceDocumentoModeloTest.cs new file mode 100644 index 000000000..632ca7079 --- /dev/null +++ b/DCe.Testes/DceDocumentoModeloTest.cs @@ -0,0 +1,101 @@ +using System.Globalization; +using System.Security.Cryptography.Xml; +using System.Xml; +using DCe.Classes.Servicos.Evento; +using DCe.Utils.Extensoes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using DCeDocumento = DCe.Classes.Informacoes.DCe; + +namespace DCe.Testes; + +[TestClass] +public class DceDocumentoModeloTest +{ + private static string Fixture(bool evento) => File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", evento ? "Cancelamento.xml" : "Declaracao.xml")); + + [DataTestMethod] + [DataRow(false)] + [DataRow(true)] + public void Modelo_LeEReproduzFixtureLiteralmente(bool evento) + { + var xml = Fixture(evento); + var saida = evento ? new eventoDCe().CarregarDeXmlString(xml).ObterXmlString() : new DCeDocumento().CarregarDeXmlString(xml).ObterXmlString(); + Assert.AreEqual(xml, saida); + } + + [DataTestMethod] + [DataRow(false)] + [DataRow(true)] + public void ModeloAssinado_IdaVoltaPreservaXmlSchemaECriptografia(bool evento) + { + using var certificado = DceServiceTest.Autoassinado(); + var xml = evento + ? new eventoDCe().CarregarDeXmlString(Fixture(true)).Assina(certificado).ObterXmlString() + : new DCeDocumento().CarregarDeXmlString(Fixture(false)).Assina(certificado).ObterXmlString(); + string saida; + if (evento) + { + var modelo = new eventoDCe().CarregarDeXmlString(xml); + modelo.Valida(); + saida = modelo.ObterXmlString(); + } + else + { + var modelo = new DCeDocumento().CarregarDeXmlString(xml); + modelo.Valida(); + saida = modelo.ObterXmlString(); + } + Assert.AreEqual(xml, saida); + var doc = new XmlDocument { PreserveWhitespace = true }; + doc.LoadXml(saida); + var assinatura = new SignedXml(doc); + assinatura.LoadXml((XmlElement)doc.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0]!); + Assert.IsTrue(assinatura.CheckSignature(certificado, true)); + } + + [DataTestMethod] + [DataRow("pt-BR", -3)] + [DataRow("en-US", -4)] + [DataRow("ar-SA", -5)] + public void PrepararModelo_ChaveUsaOffsetDaEmissaoECnpjDoSignatario(string cultura, int offset) + { + var anterior = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cultura); + var dce = new DCeDocumento().CarregarDeXmlString(Fixture(false)); + dce.infDCe.ide.dhEmi = new DateTimeOffset(2026, 9, 1, 1, 0, 0, TimeSpan.Zero).ToOffset(TimeSpan.FromHours(offset)); + dce.Preparar("99999999000191"); + Assert.AreEqual("2608", dce.infDCe.Id.Substring(5, 4)); + Assert.AreEqual("99999999000191", dce.infDCe.Id.Substring(9, 14)); + var volta = new DCeDocumento().CarregarDeXmlString(dce.ObterXmlString()); + Assert.AreEqual(TimeSpan.FromHours(offset), volta.infDCe.ide.dhEmi.Offset); + Assert.AreEqual(dce.ObterXmlString(), volta.ObterXmlString()); + } + finally { CultureInfo.CurrentCulture = anterior; } + } + + [DataTestMethod] + [DataRow(false)] + [DataRow(true)] + public void Modelo_ArquivoIdaVoltaPreservaConteudo(bool evento) + { + var caminho = Path.Combine(Path.GetTempPath(), "dce-modelo-" + Guid.NewGuid().ToString("N") + ".xml"); + try + { + if (evento) + { + var modelo = new eventoDCe().CarregarDeXmlString(Fixture(true)); + modelo.SalvarXmlEmDisco(caminho); + Assert.AreEqual(Fixture(true), new eventoDCe().CarregarDeArquivoXml(caminho).ObterXmlString()); + } + else + { + var modelo = new DCeDocumento().CarregarDeXmlString(Fixture(false)); + modelo.SalvarXmlEmDisco(caminho); + Assert.AreEqual(Fixture(false), new DCeDocumento().CarregarDeArquivoXml(caminho).ObterXmlString()); + } + } + finally { File.Delete(caminho); } + } +} diff --git a/DCe.Testes/DceIntegracaoXmlTest.cs b/DCe.Testes/DceIntegracaoXmlTest.cs new file mode 100644 index 000000000..375c15586 --- /dev/null +++ b/DCe.Testes/DceIntegracaoXmlTest.cs @@ -0,0 +1,156 @@ +using DCe.Utils.Extensoes; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Xml; +using System.Xml; +using System.Xml.Linq; +using DCe.Testes.Fixtures.Entradas; +using DCe.Testes.Fixtures; +using DCe.Utils; +using DCe.Utils.Abstractions; +using DCe.Utils.Validacao; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceIntegracaoXmlTest +{ + private static readonly XNamespace Ns = "http://www.portalfiscal.inf.br/dce"; + + private sealed class RelogioVirada : IClock + { + public DateTime UtcNow => new(2026, 9, 1, 1, 0, 0, DateTimeKind.Utc); + } + + private sealed class CodigoFixo : IRandomSource + { + public int ProximoCodigoDocumento() => 123456; + } + + private sealed class RelogioFixo(int hora) : IClock + { + public DateTime UtcNow => new(2026, 8, 22, hora, 0, 0, DateTimeKind.Utc); + } + + [DataTestMethod] + [DataRow(false)] + [DataRow(true)] + public void XmlGerado_CorrespondeIntegralmenteAFixture(bool evento) + { + var builder = new FabricaModeloDCe(new RelogioFixo(evento ? 15 : 12), new CodigoFixo()); + var xml = evento ? builder.Criar(new CancelamentoDce + { + ChaveAcesso = "26260899999999000191990010000000011300123455", + CodigoOrgao = DFe.Classes.Entidades.Estado.PE, + CnpjAutor = "99999999000191", + CpfUsuarioEmitente = "12345678909", + NumeroProtocolo = "1262600000000001", + Justificativa = "TRANSPORTE CANCELADO A PEDIDO DO DECLARANTE" + }).ObterXmlString() : builder.Criar(DceFixture.Padrao()).ObterXmlString(); + var referencia = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", evento ? "Cancelamento.xml" : "Declaracao.xml")); + Assert.AreEqual(referencia, xml); + } + + [DataTestMethod] + [DataRow(-3, "2026-08-31T22:00:00-03:00")] + [DataRow(-4, "2026-08-31T21:00:00-04:00")] + [DataRow(-5, "2026-08-31T20:00:00-05:00")] + public void ChaveEDhEmi_UsamOMesmoMesLocal(int horas, string dataEsperada) + { + var dc = DceFixture.Padrao(); + dc.OffsetHorario = TimeSpan.FromHours(horas); + var inf = XDocument.Parse(new FabricaModeloDCe(new RelogioVirada(), new CodigoFixo()) + .Criar(dc).ObterXmlString()).Root!.Element(Ns + "infDCe")!; + Assert.AreEqual("2608", inf.Attribute("Id")!.Value.Substring(5, 4)); + Assert.AreEqual(dataEsperada, inf.Element(Ns + "ide")!.Element(Ns + "dhEmi")!.Value); + } + +#if NET8_0_OR_GREATER + // CertificateRequest permite testar com chave efêmera sem distribuir certificados. + [DataTestMethod] + [DataRow(false)] + [DataRow(true)] + public void AssinaturaZeus_ValidaCriptografiaESchema(bool evento) + { + var builder = new FabricaModeloDCe(new RelogioVirada(), new CodigoFixo()); + var dc = DceFixture.Padrao(); + var xml = builder.Criar(dc).ObterXmlString(); + if (evento) + { + var chave = XDocument.Parse(xml).Root!.Element(Ns + "infDCe")!.Attribute("Id")!.Value.Substring(3); + xml = builder.Criar(new CancelamentoDce + { + ChaveAcesso = chave, + CodigoOrgao = dc.CodigoUf, + CnpjAutor = dc.Signatario.Cnpj, + CpfUsuarioEmitente = dc.Emitente.Cpf, + NumeroProtocolo = "1262600000000001", + Justificativa = "TRANSPORTE CANCELADO A PEDIDO DO DECLARANTE" + }).ObterXmlString(); + } + + using var chaveRsa = RSA.Create(2048); + var pedido = new CertificateRequest("CN=Teste DCe", chaveRsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + using var certificado = pedido.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1)); + var assinado = DceAssinatura.Assinar(xml, certificado); + Validador.Valida(assinado, evento ? "evento" : "dce", evento ? "eventoDCe_v1.00.xsd" : "dce_v1.00.xsd"); + + var documento = new XmlDocument { PreserveWhitespace = true }; + documento.LoadXml(assinado); + var assinatura = new SignedXml(documento); + assinatura.LoadXml((XmlElement)documento.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0]!); + Assert.AreEqual(SignedXml.XmlDsigRSASHA1Url, assinatura.SignedInfo!.SignatureMethod); + Assert.AreEqual(SignedXml.XmlDsigC14NTransformUrl, assinatura.SignedInfo.CanonicalizationMethod); + Assert.IsTrue(assinatura.CheckSignature(certificado, true)); + Assert.ThrowsException(() => DceAssinatura.Assinar(assinado, certificado)); + + // Alterar um campo assinado deve invalidar a verificação criptográfica. + documento.GetElementsByTagName(evento ? "xJust" : "xNome", Ns.NamespaceName)[0]!.InnerText = "ALTERADO"; + var adulterada = new SignedXml(documento); + adulterada.LoadXml((XmlElement)documento.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0]!); + Assert.IsFalse(adulterada.CheckSignature(certificado, true)); + } +#endif + + [TestMethod] + public void LerConsulta_ExtraiProtocoloEOffset() + { + const string chave = "26260899999999000191990010000000011300123455"; + var xml = $""" + + 2PR-1.0100 + Autorizado o uso da DCe26 + 2026-08-22T12:00:00-03:00{chave} + 2PR-1.0 + {chave}2026-08-22T12:00:00-03:00 + 1262600000000001SGVsbG8= + 100Autorizado o uso da DCe + + """; + Validador.Valida(xml, "consulta", "retConsSitDCe_v1.00.xsd"); + var retorno = new global::DCe.Classes.Retorno.retConsSitDCe().CarregarDeXmlString(xml); + Assert.AreEqual(chave, retorno.ChaveAcesso); + Assert.AreEqual("1262600000000001", retorno.Protocolo!.NumeroProtocolo); + Assert.AreEqual(TimeSpan.FromHours(-3), retorno.Protocolo.DataRecebimento!.Value.Offset); + Assert.AreEqual("100", retorno.CodigoStatus); + } + + [TestMethod] + public void DestinatarioExterior_ComMunicipioNacional_RecusaCom509() + { + var dc = DceFixture.Padrao(); + dc.Destinatario.Endereco.Uf = "EX"; + var erro = Assert.ThrowsException(() => new FabricaModeloDCe().Criar(dc).ObterXmlString()); + StringAssert.Contains(erro.Message, "509"); + } + + [TestMethod] + public void Validador_RecusaXmlInvalidoERecursosExternos() + { + Assert.ThrowsException(() => + Validador.Valida("", "dce", "dce_v1.00.xsd")); + Assert.ThrowsException(() => Validador.Valida( + "]>&externo;", "dce", "dce_v1.00.xsd")); + } +} diff --git a/DCe.Testes/DceLeituraModeloTest.cs b/DCe.Testes/DceLeituraModeloTest.cs new file mode 100644 index 000000000..3790e4843 --- /dev/null +++ b/DCe.Testes/DceLeituraModeloTest.cs @@ -0,0 +1,123 @@ +using DCe.Utils.Extensoes; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System.Xml; +using DCe.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceLeituraModeloTest +{ + private const string Ns = "http://www.portalfiscal.inf.br/dce"; + private const string Chave = "26260899999999000191990010000000011300123455"; + + /// + /// Valida o fixture contra o schema oficial ANTES de entregá-lo ao parser. Fixture que + /// o XSD recusaria testaria o parser contra algo que a SEFAZ nunca mandaria. + /// + private static string Valido(string xml, string pasta, string arquivo) + { + var doc = new XmlDocument { Schemas = DceSchemaLoader.Carregar(pasta, arquivo) }; + doc.LoadXml(xml); + + var erros = new List(); + doc.Validate((_, e) => erros.Add(e.Message)); + Assert.IsTrue(erros.Count == 0, $"fixture inválido contra {arquivo}: {string.Join(" | ", erros)}"); + + return xml; + } + + private static string RetDceAutorizado() => Valido($""" + 2PR-1.0 + 100Autorizado o uso da DCe26 + 2026-08-22T12:00:00-03:00 + 2PR-1.0 + {Chave}2026-08-22T12:00:00-03:00 + 1262600000000001SGVsbG8= + 100Autorizado o uso da DCe + """.Replace("\r\n", "").Replace("\n", ""), "dce", "retDCe_v1.00.xsd"); + + [TestMethod] + public void LerAutorizacao_ExtraiStatusEProtocolo() + { + var r = new global::DCe.Classes.Retorno.retDCe().CarregarDeXmlString(RetDceAutorizado()); + + Assert.AreEqual("100", r.CodigoStatus); + Assert.IsTrue(r.Autorizada); + Assert.AreEqual("Autorizado o uso da DCe", r.Motivo); + Assert.AreEqual("2", r.Ambiente); + Assert.IsNotNull(r.Protocolo); + Assert.AreEqual(Chave, r.Protocolo!.ChaveAcesso); + Assert.AreEqual("1262600000000001", r.Protocolo.NumeroProtocolo); + } + + /// + /// Rejeição não traz protDCe. O parser tem que devolver o motivo e Protocolo nulo, sem + /// lançar — é o caminho mais comum em homologação. + /// + [TestMethod] + public void LerAutorizacao_TrataRejeicaoSemProtocolo() + { + var xml = Valido($""" + 2PR-1.0 + 203Rejeicao: Emissor nao habilitado para emissao de DCe + 262026-08-22T12:00:00-03:00 + """.Replace("\r\n", "").Replace("\n", ""), "dce", "retDCe_v1.00.xsd"); + + var r = new global::DCe.Classes.Retorno.retDCe().CarregarDeXmlString(xml); + + Assert.AreEqual("203", r.CodigoStatus); + Assert.IsFalse(r.Autorizada); + Assert.IsNull(r.Protocolo); + Assert.IsTrue(r.Motivo.IndexOf("nao habilitado", StringComparison.OrdinalIgnoreCase) >= 0); + } + + [TestMethod] + public void LerStatusServico_ExtraiOsCamposOpcionais() + { + var xml = Valido($""" + 2PR-1.0 + 107Servico em Operacao26 + 2026-08-22T12:00:00-03:001 + """.Replace("\r\n", "").Replace("\n", ""), "consulta", "retConsStatServDCe_v1.00.xsd"); + + var r = new global::DCe.Classes.Retorno.retConsStatServDCe().CarregarDeXmlString(xml); + + Assert.IsTrue(r.EmOperacao); + Assert.AreEqual(1, r.TempoMedioResposta); + Assert.IsNull(r.PrevisaoRetorno); + } + + [TestMethod] + public void LerEvento_ExtraiORegistroDoCancelamento() + { + var xml = Valido($""" + 2 + PR-1.026135 + Evento registrado e vinculado a DCe{Chave} + 110111Cancelamento1 + 2026-08-22T13:00:00-03:00 + 1262600000000002 + """.Replace("\r\n", "").Replace("\n", ""), "evento", "retEventoDCe_v1.00.xsd"); + + var r = new global::DCe.Classes.Retorno.retEventoDCe().CarregarDeXmlString(xml); + + Assert.IsTrue(r.Registrado); + Assert.AreEqual("110111", r.TipoEvento); + Assert.AreEqual("1262600000000002", r.NumeroProtocolo); + Assert.AreEqual(Chave, r.ChaveAcesso); + } + + [DataTestMethod] + [DataRow("")] + [DataRow("")] + public void LerAutorizacao_LancaComEntradaQueNaoEhResposta(string entrada) + { + if (entrada.Length == 0) + Assert.ThrowsException(() => new global::DCe.Classes.Retorno.retDCe().CarregarDeXmlString(entrada)); + else + Assert.ThrowsException(() => new global::DCe.Classes.Retorno.retDCe().CarregarDeXmlString(entrada)); + } +} diff --git a/DCe.Testes/DceRetornosModeloTest.cs b/DCe.Testes/DceRetornosModeloTest.cs new file mode 100644 index 000000000..2c940e6dc --- /dev/null +++ b/DCe.Testes/DceRetornosModeloTest.cs @@ -0,0 +1,222 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Xml.Linq; +using DCe.Classes.Retorno; +using DCe.Classes.Servicos.Evento; +using DCe.Utils; +using DCe.Utils.Extensoes; +using DCe.Utils.Validacao; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceRetornosModeloTest +{ + private const string Namespace = "http://www.portalfiscal.inf.br/dce"; + private const string Chave = "26260899999999000191990010000000011300123455"; + private const string Data = "2026-08-31T23:59:59-04:00"; + private static readonly XNamespace Ns = Namespace; + + private static string Cabecalho(int status) => + $"2PR-1.0{status}" + + $"Resposta do autorizador26{Data}"; + + private static string Protocolo(bool opcionais = true) => + "" + + $"2PR-1.0{Chave}{Data}" + + (opcionais ? "1262600000000001SGVsbG8=" : "") + + "100Autorizado o uso da DCe" + + (opcionais ? "123Mensagem do Fisco" : "") + ""; + + private static string RetEvento(bool opcionais = true) => + "" + + "2PR-1.026135" + + "Evento registrado e vinculado a DCe" + + (opcionais ? $"{Chave}110111Cancelamento" + + $"001{Data}1262600000000002" : "") + + ""; + + private static string Raiz(string nome, string conteudo, string atributos = "") => + $"<{nome} xmlns=\"{Namespace}\" versao=\"1.00\"{atributos}>{conteudo}"; + + private static XElement ConteudoXml(string xml) + { + var raiz = XElement.Parse(xml); + // Declarações de namespace e ordem dos atributos não alteram o conteúdo fiscal. + foreach (var elemento in raiz.DescendantsAndSelf()) + { + var atributos = elemento.Attributes().Where(a => !a.IsNamespaceDeclaration) + .OrderBy(a => a.Name.ToString(), StringComparer.Ordinal).ToList(); + elemento.RemoveAttributes(); + elemento.Add(atributos); + } + return raiz; + } + + private static T IdaEVolta(T modelo, string xml, string pasta, string schema) where T : class + { + Validador.Valida(xml, pasta, schema); + var primeiro = modelo.CarregarDeXmlString(xml); + var serializado = primeiro.ObterXmlString(); + Validador.Valida(serializado, pasta, schema); + Assert.IsTrue(XNode.DeepEquals(ConteudoXml(xml), ConteudoXml(serializado)), "Nenhum campo do XML original pode ser perdido."); + var relido = modelo.CarregarDeXmlString(serializado); + Assert.AreEqual(serializado, relido.ObterXmlString(), "O XML do modelo deve ser idêntico após leitura e nova escrita."); + return relido; + } + + [TestMethod] + public void Autorizacao_ProtocoloCompletoPreservaCamposEOffset() + { + var retorno = IdaEVolta(new retDCe(), Raiz("retDCe", Cabecalho(100) + Protocolo()), "dce", "retDCe_v1.00.xsd"); + Assert.AreEqual(TipoAmbiente.Homologacao, retorno.tpAmb); + Assert.AreEqual(Estado.PE, retorno.cUF); + Assert.AreEqual(100, retorno.cStat); + Assert.AreEqual(Data, retorno.dhRecbtoProxy); + Assert.AreEqual(TimeSpan.FromHours(-4), retorno.dhRecbto.Offset); + Assert.AreEqual("ID1262600000000001", retorno.protDCe!.infProt!.Id); + Assert.AreEqual(Chave, retorno.protDCe.infProt.chDCe); + Assert.AreEqual("1262600000000001", retorno.protDCe.infProt.nProt); + Assert.AreEqual("SGVsbG8=", retorno.protDCe.infProt.digVal); + Assert.AreEqual(Data, retorno.protDCe.infProt.dhRecbtoProxy); + Assert.AreEqual(123, retorno.protDCe.infFisco!.cMsg); + Assert.AreEqual("Mensagem do Fisco", retorno.protDCe.infFisco.xMsg); + } + + [TestMethod] + public void Rejeicao_OmiteProtocoloAusente() + { + var retorno = IdaEVolta(new retDCe(), Raiz("retDCe", Cabecalho(203)), "dce", "retDCe_v1.00.xsd"); + Assert.AreEqual(203, retorno.cStat); + Assert.IsNull(retorno.protDCe); + Assert.IsFalse(retorno.Autorizada); + Assert.IsFalse(XDocument.Parse(retorno.ObterXmlString()).Descendants(Ns + "protDCe").Any()); + } + + [TestMethod] + public void Protocolo_OmiteCamposOpcionaisAusentes() + { + var retorno = IdaEVolta(new retDCe(), Raiz("retDCe", Cabecalho(100) + Protocolo(false)), "dce", "retDCe_v1.00.xsd"); + var protocolo = retorno.protDCe!; + Assert.IsNull(protocolo.infProt!.nProt); + Assert.IsNull(protocolo.infProt.digVal); + Assert.IsNull(protocolo.infFisco); + Assert.IsNull(protocolo.Signature); + var nomes = XDocument.Parse(protocolo.ObterXmlString()).Descendants().Select(e => e.Name.LocalName).ToList(); + foreach (var nome in new[] { "nProt", "digVal", "infFisco", "Signature" }) Assert.IsFalse(nomes.Contains(nome)); + } + + [TestMethod] + public void StatusServico_PreservaOpcionaisEOffsetDeRetorno() + { + const string previsao = "2026-09-01T01:30:00+05:00"; + var retorno = IdaEVolta(new retConsStatServDCe(), Raiz("retConsStatServDCe", Cabecalho(108) + + $"0{previsao}Manutencao programada"), "consulta", "retConsStatServDCe_v1.00.xsd"); + Assert.AreEqual(0, retorno.tMed); + Assert.AreEqual(previsao, retorno.dhRetornoProxy); + Assert.AreEqual(TimeSpan.FromHours(5), retorno.dhRetorno!.Value.Offset); + Assert.AreEqual("Manutencao programada", retorno.xObs); + retorno.dhRetorno = new DateTimeOffset(2026, 9, 1, 2, 0, 0, TimeSpan.FromHours(-3)); + Assert.AreEqual("2026-09-01T02:00:00-03:00", retorno.dhRetornoProxy); + } + + [TestMethod] + public void StatusServico_OmiteOpcionaisAusentes() + { + var retorno = IdaEVolta(new retConsStatServDCe(), Raiz("retConsStatServDCe", Cabecalho(107)), "consulta", "retConsStatServDCe_v1.00.xsd"); + Assert.IsNull(retorno.tMed); + Assert.IsNull(retorno.dhRetorno); + Assert.IsNull(retorno.xObs); + var nomes = XDocument.Parse(retorno.ObterXmlString()).Descendants().Select(e => e.Name.LocalName).ToList(); + foreach (var nome in new[] { "tMed", "dhRetorno", "xObs" }) Assert.IsFalse(nomes.Contains(nome)); + } + + [TestMethod] + public void Evento_PreservaTodosCamposEOpcionalAusente() + { + var xml = XElement.Parse(RetEvento()); + xml.Name = Ns + "retEventoDCe"; + foreach (var e in xml.Descendants()) e.Name = Ns + e.Name.LocalName; + var retorno = IdaEVolta(new retEventoDCe(), xml.ToString(), "evento", "retEventoDCe_v1.00.xsd"); + Assert.AreEqual("ID1262600000000002", retorno.infEvento!.Id); + Assert.AreEqual(26, (int)retorno.infEvento.cOrgao); + Assert.AreEqual(135, retorno.infEvento.cStat); + Assert.AreEqual(Chave, retorno.infEvento.chDCe); + Assert.AreEqual("110111", retorno.infEvento.tpEvento); + Assert.AreEqual("Cancelamento", retorno.infEvento.xEvento); + Assert.AreEqual("001", retorno.infEvento.nSeqEvento); + Assert.AreEqual(Data, retorno.infEvento.dhRegEventoProxy); + Assert.AreEqual(TimeSpan.FromHours(-4), retorno.infEvento.dhRegEvento!.Value.Offset); + Assert.AreEqual("1262600000000002", retorno.infEvento.nProt); + var minimo = RetEvento(false).Replace("{Chave}" + Protocolo(false)), "consulta", "retConsSitDCe_v1.00.xsd"); + Assert.AreEqual(Chave, retorno.chDCe); + Assert.AreEqual(Chave, retorno.protDCe!.infProt!.chDCe); + Assert.AreEqual(0, retorno.procEventoDCe.Count); + Assert.IsFalse(XDocument.Parse(retorno.ObterXmlString()).Descendants(Ns + "procEventoDCe").Any()); + } + + [TestMethod] + public void Processados_PreservamRaizesModelosAssinaturasEAtributosDeConexao() + { + using var rsa = RSA.Create(2048); + var pedido = new CertificateRequest("CN=Teste retornos DCe", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + using var certificado = pedido.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1)); + var documento = XElement.Parse(DceAssinatura.Assinar(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "Declaracao.xml")), certificado)); + var modeloEvento = new eventoDCe().CarregarDeXmlString(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "Cancelamento.xml"))); + Validador.Valida(modeloEvento.infEvento.detEvento.evCancDCe.ObterXmlString(), "evento", "evCancDCe_v1.00.xsd"); + var evento = XElement.Parse(modeloEvento.Assina(certificado).ObterXmlString()); + var atributos = $" ipTransmissor=\"192.0.2.1\" nPortaCon=\"0\" dhConexao=\"{Data}\""; + var processado = IdaEVolta(new procDCe(), Raiz("dceProc", documento + Protocolo(), atributos), "dce", "procDCe_v1.00.xsd"); + Assert.IsNotNull(processado.DCe!.Signature); + Assert.AreEqual("192.0.2.1", processado.ipTransmissor); + Assert.AreEqual("0", processado.nPortaCon); + Assert.AreEqual(Data, processado.dhConexaoProxy); + Assert.AreEqual(TimeSpan.FromHours(-4), processado.dhConexao!.Value.Offset); + Assert.AreEqual(documento.Element(Ns + "infDCe")!.Attribute("Id")!.Value, processado.DCe.infDCe.Id); + Assert.AreEqual("dceProc", XDocument.Parse(processado.ObterXmlString()).Root!.Name.LocalName); + var eventoProcessado = IdaEVolta(new procEventoDCe(), Raiz("procEventoDCe", evento + RetEvento(), atributos), "evento", "procEventoDCe_v1.00.xsd"); + Assert.IsNotNull(eventoProcessado.eventoDCe!.Signature); + Assert.AreEqual("1262600000000002", eventoProcessado.retEventoDCe!.infEvento!.nProt); + Assert.AreEqual(Data, eventoProcessado.dhConexaoProxy); + Assert.AreEqual("procEventoDCe", XDocument.Parse(eventoProcessado.ObterXmlString()).Root!.Name.LocalName); + processado.protDCe!.Signature = processado.DCe.Signature; + eventoProcessado.retEventoDCe.Signature = eventoProcessado.eventoDCe.Signature; + Validador.Valida(processado.ObterXmlString(), "dce", "procDCe_v1.00.xsd"); + Validador.Valida(eventoProcessado.ObterXmlString(), "evento", "procEventoDCe_v1.00.xsd"); + var protocoloRelido = new procDCe().CarregarDeXmlString(processado.ObterXmlString()).protDCe!; + var retornoEventoRelido = new procEventoDCe().CarregarDeXmlString(eventoProcessado.ObterXmlString()).retEventoDCe!; + Assert.AreEqual(processado.DCe.Signature!.SignatureValue, protocoloRelido.Signature!.SignatureValue); + Assert.AreEqual(eventoProcessado.eventoDCe.Signature!.SignatureValue, retornoEventoRelido.Signature!.SignatureValue); + var consulta = new retConsSitDCe().CarregarDeXmlString(Raiz("retConsSitDCe", Cabecalho(100) + $"{Chave}" + Protocolo(false))); + consulta.procEventoDCe.AddRange(new[] { eventoProcessado, eventoProcessado }); + // O XSD da consulta usa xs:any singular, apesar de cada procEventoDCe ter dois filhos. + // A coleção é conferida por ida/volta; o processado completo foi validado acima. + var consultaRelida = new retConsSitDCe().CarregarDeXmlString(consulta.ObterXmlString()); + Assert.AreEqual(2, consultaRelida.procEventoDCe.Count); + Assert.AreEqual(eventoProcessado.eventoDCe.infEvento.Id, consultaRelida.procEventoDCe[1].eventoDCe!.infEvento.Id); + Assert.AreEqual("1262600000000002", consultaRelida.procEventoDCe[0].retEventoDCe!.infEvento!.nProt); + processado.dhConexao = null; + processado.ipTransmissor = null; + processado.nPortaCon = null; + var raiz = XDocument.Parse(processado.ObterXmlString()).Root!; + foreach (var nome in new[] { "dhConexao", "ipTransmissor", "nPortaCon" }) Assert.IsNull(raiz.Attribute(nome)); + } +} diff --git a/DCe.Testes/DceSchemasTest.cs b/DCe.Testes/DceSchemasTest.cs new file mode 100644 index 000000000..41a6b0a0a --- /dev/null +++ b/DCe.Testes/DceSchemasTest.cs @@ -0,0 +1,163 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System.Xml; +using System.Xml.Schema; +using DCe.Utils; +using DCe.Utils.Validacao; +using DCe.Utils.Extensoes; +using DCe.Classes.Servicos.StatusServico; +using DCe.Testes.Fixtures; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +/// +/// O teste de include é o mais importante deste módulo. No .NET Core, XmlResolver nulo faz +/// xs:include falhar EM SILÊNCIO: o schema carrega, a validação roda, e os tipos do arquivo +/// incluído não são verificados. Como dce_v1.00.xsd é um wrapper de 10 linhas que inclui +/// dceTiposBasico_v1.00.xsd, include quebrado significa validar NADA. +/// +[TestClass] +public class DceSchemasTest +{ + private const string Ns = "http://www.portalfiscal.inf.br/dce"; + + private static Stream Recurso(string pasta, string nome) + { + var completo = $"DCe.Classes.Schemas.{pasta}.{nome}"; + var stream = typeof(global::DCe.Classes.Informacoes.DCe).Assembly.GetManifestResourceStream(completo); + Assert.IsTrue(stream is not null, $"recurso embutido não encontrado: {completo}"); + return stream!; + } + + [DataTestMethod] + [DataRow("dce", "dce_v1.00.xsd")] + [DataRow("dce", "dceTiposBasico_v1.00.xsd")] + [DataRow("dce", "retDCe_v1.00.xsd")] + [DataRow("dce", "procDCe_v1.00.xsd")] + [DataRow("consulta", "consSitDCe_v1.00.xsd")] + [DataRow("consulta", "retConsSitDCe_v1.00.xsd")] + [DataRow("consulta", "consStatServDCe_v1.00.xsd")] + [DataRow("consulta", "retConsStatServDCe_v1.00.xsd")] + [DataRow("consulta", "dceTiposBasico_v1.00.xsd")] + [DataRow("evento", "eventoDCe_v1.00.xsd")] + [DataRow("evento", "retEventoDCe_v1.00.xsd")] + [DataRow("evento", "evCancDCe_v1.00.xsd")] + [DataRow("evento", "procEventoDCe_v1.00.xsd")] + [DataRow("evento", "eventoDCeTiposBasico_v1.00.xsd")] + public void CadaSchemaEstaEmbutido(string pasta, string nome) + { + using var stream = Recurso(pasta, nome); + Assert.IsTrue(stream.Length > 0); + Assert.IsTrue(DceSchemaLoader.Carregar(pasta, nome).IsCompiled); + } + + /// + /// Os dois dceTiposBasico_v1.00.xsd são recursos DISTINTOS e com conteúdo diferente: o de + /// consulta/ é a versão pré-NT 2024.001 (tpEmit vai só até 3). O pacote oficial é + /// inconsistente e espelhamos isso de propósito — unificar pelo mais novo faria a + /// consulta validar contra schema que a SEFAZ não usa para ela. + /// + [TestMethod] + public void OsDoisDceTiposBasico_SaoRecursosDistintosEDiferentes() + { + using var deDce = new StreamReader(Recurso("dce", "dceTiposBasico_v1.00.xsd")); + using var deConsulta = new StreamReader(Recurso("consulta", "dceTiposBasico_v1.00.xsd")); + + Assert.AreNotEqual(deDce.ReadToEnd(), deConsulta.ReadToEnd()); + } + + /// + /// Prova positiva de que o include resolveu: TDCe é declarado em dceTiposBasico, não em + /// dce_v1.00.xsd. Se o include falhasse, o SchemaSet compilaria com zero tipos globais + /// além do elemento raiz — e validaria tudo como válido. + /// + [TestMethod] + public void IncludeResolve_TiposDoArquivoIncluidoFicamDisponiveis() + { + var set = DceSchemaLoader.Carregar("dce", "dce_v1.00.xsd"); + + Assert.IsTrue(set.GlobalTypes.Names.Cast().Any( + n => n.Name == "TDCe" && n.Namespace == Ns)); + } + + [TestMethod] + public void SchemaRejeitaXmlQueNaoEhDCe() + { + var set = DceSchemaLoader.Carregar("dce", "dce_v1.00.xsd"); + var doc = new XmlDocument { Schemas = set }; + doc.LoadXml($""); + + var erros = new List(); + doc.Validate((_, e) => erros.Add(e.Message)); + + Assert.IsTrue(erros.Count > 0); + } + [TestMethod] + public void CaminhoNulo_ValidaComSchemasEmbutidos() + { + var consulta = new consStatServDCe { tpAmb = TipoAmbiente.Homologacao, cUF = Estado.PE }; + Validador.Valida(consulta.ObterXmlString(), "consulta", "consStatServDCe_v1.00.xsd", null); + } + + [TestMethod] + public void PastaInexistente_ProduzErroComOCaminho() + { + var pasta = Path.Combine(Path.GetTempPath(), "dce-ausente-" + Guid.NewGuid().ToString("N")); + var erro = Assert.ThrowsException(() => + DceSchemaLoader.Carregar("dce", "dce_v1.00.xsd", pasta)); + StringAssert.Contains(erro.Message, "Diretório de Schemas não encontrado"); + StringAssert.Contains(erro.Message, pasta); + } + + [TestMethod] + public void PastaExterna_ResolveIncludesEHomonimosSemFallbackEmbutido() + { + var pasta = Path.Combine(Path.GetTempPath(), "dce-schemas-" + Guid.NewGuid().ToString("N")); + var assembly = typeof(global::DCe.Classes.Informacoes.DCe).Assembly; + const string prefixo = "DCe.Classes.Schemas."; + try + { + var recursos = assembly.GetManifestResourceNames().Where(n => n.StartsWith(prefixo, StringComparison.Ordinal)).ToArray(); + Assert.AreEqual(22, recursos.Length); + foreach (var recurso in recursos) + { + var relativo = recurso.Substring(prefixo.Length); + var ponto = relativo.IndexOf('.'); + var destino = Path.Combine(pasta, relativo.Substring(0, ponto), relativo.Substring(ponto + 1)); + Directory.CreateDirectory(Path.GetDirectoryName(destino)!); + using var origem = assembly.GetManifestResourceStream(recurso)!; + using var arquivo = File.Create(destino); + origem.CopyTo(arquivo); + } + foreach (var (subpasta, raiz) in new[] { ("dce", "dce_v1.00.xsd"), ("consulta", "consStatServDCe_v1.00.xsd"), ("evento", "evCancDCe_v1.00.xsd") }) + Assert.IsTrue(DceSchemaLoader.Carregar(subpasta, raiz, pasta).IsCompiled); + var consulta = new consStatServDCe { tpAmb = TipoAmbiente.Homologacao, cUF = Estado.PE }; + Validador.Valida(consulta.ObterXmlString(), "consulta", "consStatServDCe_v1.00.xsd", pasta); + + // Marcadores distintos nos arquivos homonimos provam qual include foi resolvido. + foreach (var (subpasta, marcador) in new[] { ("dce", "OrigemDeclaracao"), ("consulta", "OrigemConsulta") }) + { + var homonimo = Path.Combine(pasta, subpasta, "dceTiposBasico_v1.00.xsd"); + var conteudo = File.ReadAllText(homonimo); + File.WriteAllText(homonimo, conteudo.Replace("", + $"")); + } + var schemaDce = DceSchemaLoader.Carregar("dce", "dce_v1.00.xsd", pasta); + var schemaConsulta = DceSchemaLoader.Carregar("consulta", "dceTiposBasico_v1.00.xsd", pasta); + Assert.IsTrue(schemaDce.GlobalTypes.Contains(new XmlQualifiedName("OrigemDeclaracao", Ns))); + Assert.IsFalse(schemaDce.GlobalTypes.Contains(new XmlQualifiedName("OrigemConsulta", Ns))); + Assert.IsTrue(schemaConsulta.GlobalTypes.Contains(new XmlQualifiedName("OrigemConsulta", Ns))); + Assert.IsFalse(schemaConsulta.GlobalTypes.Contains(new XmlQualifiedName("OrigemDeclaracao", Ns))); + var arquivoStatus = Path.Combine(pasta, "consulta", "consStatServDCeTiposBasico_v1.00.xsd"); + File.WriteAllText(arquivoStatus, File.ReadAllText(arquivoStatus).Replace("STATUS", "OUTRO")); + Assert.ThrowsException(() => + Validador.Valida(consulta.ObterXmlString(), "consulta", "consStatServDCe_v1.00.xsd", pasta)); + File.Delete(Path.Combine(pasta, "evento", "evCancDCe_v1.00.xsd")); + Assert.ThrowsException(() => + DceSchemaLoader.Carregar("evento", "evCancDCe_v1.00.xsd", pasta)); + } + finally { if (Directory.Exists(pasta)) Directory.Delete(pasta, true); } + } + +} diff --git a/DCe.Testes/DceSerializacaoTest.cs b/DCe.Testes/DceSerializacaoTest.cs new file mode 100644 index 000000000..55e75b203 --- /dev/null +++ b/DCe.Testes/DceSerializacaoTest.cs @@ -0,0 +1,250 @@ +using DCe.Utils.Extensoes; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System.IO; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; +using DCe.Utils.Abstractions; +using DCe.Utils; +using DCe.Testes.Fixtures; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceSerializacaoTest +{ + private static readonly XNamespace Dce = "http://www.portalfiscal.inf.br/dce"; + private static readonly XNamespace Ds = "http://www.w3.org/2000/09/xmldsig#"; + + private sealed class ClockFixo : IClock + { + public DateTime UtcNow => new(2026, 8, 22, 12, 0, 0, DateTimeKind.Utc); + } + + private sealed class RandomFixo : IRandomSource + { + public int ProximoCodigoDocumento() => 123456; + } + + private static string Construir() => + new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(DceFixture.Padrao()).ObterXmlString(); + + [TestMethod] + public void ConstruirDce_DeclaraEncodingUtf8() + { + Assert.IsTrue(Construir().StartsWith("", StringComparison.Ordinal)); + } + + /// O manual proíbe prefixo de namespace, e exige uma única declaração na raiz. + [TestMethod] + public void ConstruirDce_UsaNamespaceSemPrefixo() + { + var raiz = XDocument.Parse(Construir()).Root!; + + Assert.AreEqual("DCe", raiz.Name.LocalName); + Assert.AreEqual(Dce.NamespaceName, raiz.Name.NamespaceName); + Assert.IsTrue(Construir().IndexOf("xmlns:", StringComparison.Ordinal) < 0); + } + + /// Id é o literal "DCe" seguido dos 44 dígitos da chave — é a tag que será assinada. + [TestMethod] + public void ConstruirDce_MontaOIdComOLiteralEAChave() + { + var infDCe = XDocument.Parse(Construir()).Root!.Element(Dce + "infDCe")!; + var id = infDCe.Attribute("Id")!.Value; + + Assert.IsTrue(id.StartsWith("DCe", StringComparison.Ordinal)); + Assert.AreEqual(47, id.Length); + Assert.IsTrue(id.Substring(3).All(char.IsDigit)); + } + + /// O cDC injetado tem que aparecer na chave e no ide — se não aparecer, algo gerou outro. + [TestMethod] + public void ConstruirDce_UsaOCodigoAleatorioInjetado() + { + var ide = XDocument.Parse(Construir()).Root!.Element(Dce + "infDCe")!.Element(Dce + "ide")!; + + Assert.AreEqual("123456", ide.Element(Dce + "cDC")!.Value); + Assert.AreEqual("99", ide.Element(Dce + "mod")!.Value); + } + + /// + /// Nesta fixture, o usuário emitente é não contribuinte e o signatário é informado + /// no grupo Transportadora, separado do grupo emit. + /// + [TestMethod] + public void ConstruirDce_SeparaUsuarioEmitenteDeQuemAssina() + { + var inf = XDocument.Parse(Construir()).Root!.Element(Dce + "infDCe")!; + + Assert.AreEqual("USUARIO NAO CONTRIBUINTE", inf.Element(Dce + "emit")!.Element(Dce + "xNome")!.Value); + Assert.AreEqual("12345678909", inf.Element(Dce + "emit")!.Element(Dce + "CPF")!.Value); + + var transportadora = inf.Element(Dce + "Transportadora")!; + Assert.AreEqual("99999999000191", transportadora.Element(Dce + "CNPJ")!.Value); + Assert.AreEqual("TRANSPORTADORA EXEMPLO LTDA", transportadora.Element(Dce + "xNome")!.Value); + } + + /// Otimização exigida pelo manual: sem tag vazia, sem espaço nem quebra entre tags. + [TestMethod] + public void ConstruirDce_NaoEmiteTagVaziaNemFormatacao() + { + var xml = Construir(); + + Assert.IsTrue(xml.Replace(" <", StringComparison.Ordinal) < 0); + } + + [TestMethod] + public void ConstruirDce_ValidaContraOSchemaOficial() + { + var xdoc = XDocument.Parse(Construir()); + var id = xdoc.Root!.Element(Dce + "infDCe")!.Attribute("Id")!.Value; + xdoc.Root!.Add(ConstruirAssinaturaPlaceholder(id)); + + var doc = new XmlDocument { Schemas = DceSchemaLoader.Carregar("dce", "dce_v1.00.xsd") }; + doc.LoadXml(xdoc.ToString(SaveOptions.DisableFormatting)); + + var erros = new List(); + doc.Validate((_, e) => erros.Add(e.Message)); + + Assert.IsTrue(erros.Count == 0, string.Join(" | ", erros)); + } + + /// + /// Nó ds:Signature estruturalmente válido para o XSD (SignedInfo/Reference/Transforms + /// conforme o manual — enveloped-signature + c14n, SHA-1 fixo pelo próprio + /// xmldsig-core-schema_v1.01.xsd) com DigestValue/SignatureValue/X509Certificate + /// placeholder. Existe só para fechar a validação de schema neste teste; não tem + /// significado criptográfico e não faz parte do XML devolvido por Construir. + /// + private static XElement ConstruirAssinaturaPlaceholder(string idAssinado) => + new(Ds + "Signature", + new XElement(Ds + "SignedInfo", + new XElement(Ds + "CanonicalizationMethod", + new XAttribute("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315")), + new XElement(Ds + "SignatureMethod", + new XAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1")), + new XElement(Ds + "Reference", + new XAttribute("URI", "#" + idAssinado), + new XElement(Ds + "Transforms", + new XElement(Ds + "Transform", + new XAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature")), + new XElement(Ds + "Transform", + new XAttribute("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"))), + new XElement(Ds + "DigestMethod", + new XAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1")), + new XElement(Ds + "DigestValue", "AAAA"))), + new XElement(Ds + "SignatureValue", "AAAA"), + new XElement(Ds + "KeyInfo", + new XElement(Ds + "X509Data", + new XElement(Ds + "X509Certificate", "AAAA")))); + + [TestMethod] + public void ConstruirDce_EhDeterministico() + { + Assert.AreEqual(Construir(), Construir()); + } + + /// + /// A SEFAZ compara o LITERAL de dhEmi contra o relógio local dela (cStat 703, regra + /// B07-10) — mandar em "+00:00" faz um horário local aparecer 3h no futuro mesmo + /// representando o mesmo instante. O default de + /// é -3h; o instante fixo do ClockFixo é 2026-08-22T12:00:00Z. + /// + [TestMethod] + public void ConstruirDce_EmiteDhEmiNoOffsetLocalPadrao() + { + var ide = XDocument.Parse(Construir()).Root!.Element(Dce + "infDCe")!.Element(Dce + "ide")!; + + Assert.AreEqual("2026-08-22T09:00:00-03:00", ide.Element(Dce + "dhEmi")!.Value); + } + + /// Offset customizado (ex.: AM/RO/RR/AC, -04:00) precisa ser respeitado. + [TestMethod] + public void ConstruirDce_RespeitaOffsetCustomizado() + { + var dc = DceFixture.Padrao(); + dc.OffsetHorario = TimeSpan.FromHours(-4); + + var xml = new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(dc).ObterXmlString(); + var ide = XDocument.Parse(xml).Root!.Element(Dce + "infDCe")!.Element(Dce + "ide")!; + + Assert.AreEqual("2026-08-22T08:00:00-04:00", ide.Element(Dce + "dhEmi")!.Value); + } + + [DataTestMethod] + [DataRow("xObs1")] + [DataRow("xObs2")] + public void ConstruirDce_TextoFixoDeInfDec_BateComADocumentacaoDoXsd(string elemento) + { + var infDec = XDocument.Parse(Construir()).Root!.Element(Dce + "infDCe")!.Element(Dce + "infDec")!; + var textoNoXml = infDec.Element(Dce + elemento)!.Value; + + var esperado = TextoFixoNoXsd(elemento); + + Assert.AreEqual(esperado, textoNoXml); + } + + private static string TextoFixoNoXsd(string elemento) + { + using var stream = typeof(global::DCe.Classes.Informacoes.DCe).Assembly + .GetManifestResourceStream("DCe.Classes.Schemas.dce.dceTiposBasico_v1.00.xsd")!; + using var reader = new StreamReader(stream); + var xsd = reader.ReadToEnd(); + + var blocoElemento = Regex.Match( + xsd, $@".*?", RegexOptions.Singleline); + Assert.IsTrue(blocoElemento.Success, $"bloco do elemento {elemento} não encontrado no XSD"); + + var textoFixo = Regex.Match(blocoElemento.Value, "Deverá conter o texto fixo “(.*?)”", RegexOptions.Singleline); + Assert.IsTrue(textoFixo.Success, $"texto fixo não encontrado na documentação de {elemento}"); + + return textoFixo.Groups[1].Value; + } + + /// + /// vUnCom/vProd são TDec_1302_1308: o schema aceita de 2 a 8 decimais. + /// A serialização preserva a precisão informada, com o mínimo de duas casas decimais. + /// + [DataTestMethod] + [DataRow("117000.12345678", "117000.12345678")] + [DataRow("0.12345678", "0.12345678")] + [DataRow("100", "100.00")] // minimo do padrao: 2 casas, nunca "100" nem "100.0" + [DataRow("100.5", "100.50")] + [DataRow("100.123", "100.123")] + public void ConstruirDce_PreservaAPrecisaoDosValoresDeItem(string entrada, string esperado) + { + var dc = DceFixture.Padrao(); + var valor = decimal.Parse(entrada, System.Globalization.CultureInfo.InvariantCulture); + dc.Itens[0].ValorUnitario = valor; + dc.Itens[0].ValorTotal = valor; + + var xml = new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(dc).ObterXmlString(); + var prod = XDocument.Parse(xml).Root! + .Element(Dce + "infDCe")!.Element(Dce + "det")!.Element(Dce + "prod")!; + + Assert.AreEqual(esperado, prod.Element(Dce + "vUnCom")!.Value); + Assert.AreEqual(esperado, prod.Element(Dce + "vProd")!.Value); + } + + /// Prova de que a precisao ampliada continua casando com o padrao do XSD. + [TestMethod] + public void ConstruirDce_ComOitoCasasDecimais_ContinuaValidoContraOSchema() + { + var dc = DceFixture.Padrao(); + dc.Itens[0].ValorUnitario = 117000.12345678m; + dc.Itens[0].ValorTotal = 117000.12345678m; + + var xml = new FabricaModeloDCe(new ClockFixo(), new RandomFixo()).Criar(dc).ObterXmlString(); + var padrao = new System.Text.RegularExpressions.Regex(@"^[0-9]{1,13}(\.[0-9]{2,8})?$"); + var prod = XDocument.Parse(xml).Root! + .Element(Dce + "infDCe")!.Element(Dce + "det")!.Element(Dce + "prod")!; + + Assert.IsTrue(padrao.IsMatch(prod.Element(Dce + "vUnCom")!.Value)); + Assert.IsTrue(padrao.IsMatch(prod.Element(Dce + "vProd")!.Value)); + } +} diff --git a/DCe.Testes/DceServiceTest.cs b/DCe.Testes/DceServiceTest.cs new file mode 100644 index 000000000..ecc400c44 --- /dev/null +++ b/DCe.Testes/DceServiceTest.cs @@ -0,0 +1,373 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Xml; +using System.Xml; +using System.Xml.Linq; +using System.Xml.Schema; +using DCe.Testes.Fixtures.Entradas; +using DCe.Servicos; +using DCe.Classes.Retorno; +using DCe.Utils.Extensoes; +using DCe.Wsdl.Soap; +using DCe.Testes.Fixtures; +using DCe.Utils; +using DCe.Utils.Abstractions; +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DFe.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceServiceTest +{ + private const string Ns = "http://www.portalfiscal.inf.br/dce"; + private const string SoapNs = "http://www.w3.org/2003/05/soap-envelope"; + private const string Chave = "26260899999999000191990010000000011300123455"; + + private sealed class SoapEspiao : ISoapClient + { + public string? Endpoint { get; private set; } + public string? Action { get; private set; } + public string? Envelope { get; private set; } + public X509Certificate2? Certificado { get; private set; } + public int TimeOut { get; private set; } + public string Resposta { get; set; } = string.Empty; + public int Chamadas { get; private set; } + + public Task SendAsync(string endpoint, string soapAction, string soapEnvelope, X509Certificate2 certificado, int timeOut) + { + (Endpoint, Action, Envelope, Certificado, TimeOut) = (endpoint, soapAction, soapEnvelope, certificado, timeOut); + Chamadas++; + return Task.FromResult(Resposta); + } + } + + private sealed class RelogioFixo : IClock + { + public DateTime UtcNow => new(2026, 8, 22, 12, 0, 0, DateTimeKind.Utc); + } + + private sealed class CodigoFixo : IRandomSource + { + public int ProximoCodigoDocumento() => 123456; + } + + internal static X509Certificate2 Autoassinado() + { + using var rsa = RSA.Create(2048); + var pedido = new CertificateRequest("CN=Teste DCe", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + return pedido.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1)); + } + + private static DCeConfiguracao Configuracao(X509Certificate2 certificado, TipoAmbiente ambiente = TipoAmbiente.Homologacao) + => new() { Certificado = certificado, TipoAmbiente = ambiente, TimeOut = 12345 }; + + private static DceService Servico(DCeConfiguracao configuracao, ISoapClient transporte) + => new(configuracao, transporte); + + private static CancelamentoDce Cancelamento(TipoAmbiente ambiente = TipoAmbiente.Homologacao) => new() + { + Ambiente = ambiente, ChaveAcesso = Chave, CodigoOrgao = Estado.PE, + CnpjAutor = "99999999000191", CpfUsuarioEmitente = "12345678909", + NumeroProtocolo = "1262600000000001", Justificativa = "TRANSPORTE CANCELADO A PEDIDO DO DECLARANTE" + }; + + private static string Retorno(string servico) => servico switch + { + "Autorizacao" => $"""2PR-1.0100Autorizado o uso da DCe262026-08-22T12:00:00-03:00""", + "Consulta" => $"""2PR-1.0100Autorizado o uso da DCe262026-08-22T12:00:00-03:00{Chave}""", + "StatusServico" => $"""2PR-1.0107Servico em Operacao262026-08-22T12:00:00-03:00""", + "RecepcaoEvento" => $"""2PR-1.026135Evento registrado e vinculado a DCe{Chave}11011111262600000000002""", + _ => throw new ArgumentException("Serviço de teste desconhecido.", nameof(servico)) + }; + + private static async Task Executar(DceService servico, string operacao, TipoAmbiente ambiente) + { + switch (operacao) + { + case "Autorizacao": + var dc = DceFixture.Padrao(); + dc.Ambiente = ambiente; + var autorizacao = await servico.AutorizarAsync(dc); + Assert.IsTrue(autorizacao.Autorizada); + Assert.AreEqual("100", autorizacao.CodigoStatus); + return autorizacao; + case "Consulta": + var consulta = await servico.ConsultarAsync(Chave); + Assert.AreEqual(Chave, consulta.ChaveAcesso); + Assert.AreEqual("100", consulta.CodigoStatus); + return consulta; + case "StatusServico": + var status = await servico.StatusServicoAsync(Estado.PE); + Assert.IsTrue(status.EmOperacao); + Assert.AreEqual("107", status.CodigoStatus); + return status; + case "RecepcaoEvento": + var evento = await servico.CancelarAsync(Cancelamento(ambiente)); + Assert.IsTrue(evento.Registrado); + Assert.AreEqual("1262600000000002", evento.NumeroProtocolo); + return evento; + default: throw new ArgumentException("Operação de teste desconhecida.", nameof(operacao)); + } + } + + [DataTestMethod] + [DataRow("Autorizacao", TipoAmbiente.Homologacao, "DCe")] + [DataRow("Autorizacao", TipoAmbiente.Producao, "DCe")] + [DataRow("Consulta", TipoAmbiente.Homologacao, "consSitDCe")] + [DataRow("Consulta", TipoAmbiente.Producao, "consSitDCe")] + [DataRow("StatusServico", TipoAmbiente.Homologacao, "consStatServDCe")] + [DataRow("StatusServico", TipoAmbiente.Producao, "consStatServDCe")] + [DataRow("RecepcaoEvento", TipoAmbiente.Homologacao, "eventoDCe")] + [DataRow("RecepcaoEvento", TipoAmbiente.Producao, "eventoDCe")] + public async Task FluxoReal_SelecionaEndpointActionEnvelopeEParseia(string operacao, TipoAmbiente ambiente, string raiz) + { + using var cert = Autoassinado(); + var espiao = new SoapEspiao { Resposta = Retorno(operacao) }; + await Executar(Servico(Configuracao(cert, ambiente), espiao), operacao, ambiente); + + var dominio = ambiente == TipoAmbiente.Producao ? "dce.fazenda.pr.gov.br" : "homologacao.dce.fazenda.pr.gov.br"; + Assert.AreEqual($"https://{dominio}/dce/DCe{operacao}", espiao.Endpoint); + Assert.AreEqual($"http://www.portalfiscal.inf.br/dce/wsdl/DCe{operacao}/dce{operacao}", espiao.Action); + Assert.AreSame(cert, espiao.Certificado); + Assert.AreEqual(12345, espiao.TimeOut); + var envelope = XDocument.Parse(espiao.Envelope!).Root!; + Assert.AreEqual(XName.Get("Envelope", SoapNs), envelope.Name); + var dados = envelope.Element(XName.Get("Body", SoapNs))!.Elements().Single(); + Assert.AreEqual(XName.Get("dceDadosMsg", $"http://www.portalfiscal.inf.br/dce/wsdl/DCe{operacao}"), dados.Name); + var mensagem = dados.Elements().Single(); + Assert.AreEqual(XName.Get(raiz, Ns), mensagem.Name); + Assert.AreEqual(((int)ambiente).ToString(), mensagem.Descendants(XName.Get("tpAmb", Ns)).Single().Value); + if (operacao == "Consulta") + { + Assert.AreEqual("CONSULTAR", mensagem.Element(XName.Get("xServ", Ns))!.Value); + Assert.AreEqual(Chave, mensagem.Element(XName.Get("chDCe", Ns))!.Value); + } + if (operacao == "StatusServico") + Assert.AreEqual("STATUS", mensagem.Element(XName.Get("xServ", Ns))!.Value); + if (operacao == "RecepcaoEvento") + { + Assert.AreEqual("110111", mensagem.Descendants(XName.Get("tpEvento", Ns)).Single().Value); + Assert.AreEqual("12345678909", mensagem.Descendants(XName.Get("CPFUsEmit", Ns)).Single().Value); + Assert.AreEqual("26", mensagem.Descendants(XName.Get("cOrgao", Ns)).Single().Value); + } + if (operacao is "Autorizacao" or "RecepcaoEvento") + { + // Verifica a assinatura no envelope efetivamente enviado, incluindo namespaces herdados. + var documento = new XmlDocument { PreserveWhitespace = true }; + documento.LoadXml(espiao.Envelope!); + var assinatura = new SignedXml(documento); + assinatura.LoadXml((XmlElement)documento.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0]!); + Assert.IsTrue(assinatura.CheckSignature(cert, true), "A assinatura deve permanecer válida dentro do SOAP."); + } + } + + [DataTestMethod] + [DataRow("Autorizacao", 0)] + [DataRow("Autorizacao", 1)] + [DataRow("Autorizacao", 2)] + [DataRow("Autorizacao", 3)] + [DataRow("Consulta", 0)] + [DataRow("Consulta", 1)] + [DataRow("Consulta", 2)] + [DataRow("Consulta", 3)] + [DataRow("StatusServico", 0)] + [DataRow("StatusServico", 1)] + [DataRow("StatusServico", 2)] + [DataRow("StatusServico", 3)] + [DataRow("RecepcaoEvento", 0)] + [DataRow("RecepcaoEvento", 1)] + [DataRow("RecepcaoEvento", 2)] + [DataRow("RecepcaoEvento", 3)] + public async Task Retornos_ConservamXmlLiteralEnviadoRecebidoEEnvelope(string operacao, int formato) + { + using var cert = Autoassinado(); + var fiscal = Retorno(operacao).Replace("versao=\"1.00\"", "versao='1.00'") + .Replace("><", ">\r\n <").Replace("PR-1.0", "PR-1.0"); + var resposta = formato switch + { + 0 => "" + fiscal, + 1 => $"\r\n{fiscal}\r\n", + 2 => $"{fiscal}", + _ => $"metadados{fiscal}" + }; + var espiao = new SoapEspiao { Resposta = resposta }; + var resultado = await Executar(Servico(Configuracao(cert), espiao), operacao, TipoAmbiente.Homologacao); + var inicioDados = espiao.Envelope!.IndexOf("', inicioDados) + 1; + var fimFiscal = espiao.Envelope.IndexOf("", inicioFiscal, StringComparison.Ordinal); + Assert.AreEqual(espiao.Envelope.Substring(inicioFiscal, fimFiscal - inicioFiscal), resultado.EnvioStr); + Assert.AreEqual(formato == 0 ? resposta : fiscal, resultado.RetornoStr); + Assert.AreEqual(resposta, resultado.RetornoCompletoStr); + using var serializado = new StringWriter(); + new System.Xml.Serialization.XmlSerializer(resultado.GetType()).Serialize(serializado, resultado); + Assert.IsFalse(serializado.ToString().Contains("EnvioStr", StringComparison.Ordinal)); + Assert.IsFalse(serializado.ToString().Contains("RetornoStr", StringComparison.Ordinal)); + Assert.IsFalse(serializado.ToString().Contains("RetornoCompletoStr", StringComparison.Ordinal)); + } + + [DataTestMethod] + [DataRow("Autorizacao", false)] + [DataRow("Autorizacao", true)] + [DataRow("Consulta", false)] + [DataRow("Consulta", true)] + [DataRow("StatusServico", false)] + [DataRow("StatusServico", true)] + [DataRow("RecepcaoEvento", false)] + [DataRow("RecepcaoEvento", true)] + public async Task FluxoReal_ExtraiRespostaDoBodyComOuSemWrapper(string operacao, bool wrapper) + { + using var cert = Autoassinado(); + var retorno = wrapper ? $"{Retorno(operacao)}" : Retorno(operacao); + var espiao = new SoapEspiao { Resposta = $"{retorno}" }; + await Executar(Servico(Configuracao(cert), espiao), operacao, TipoAmbiente.Homologacao); + } + + [DataTestMethod] + [DataRow("Autorizacao")] + [DataRow("Consulta")] + [DataRow("StatusServico")] + [DataRow("RecepcaoEvento")] + public async Task SchemaInvalido_ImpedeQualquerTransmissao(string operacao) + { + using var cert = Autoassinado(); + var espiao = new SoapEspiao(); + var servico = Servico(Configuracao(cert), espiao); + await Assert.ThrowsExceptionAsync(async () => + { + switch (operacao) + { + case "Autorizacao": + var dc = DceFixture.Padrao(); + dc.VersaoAplicativo = new string('X', 100); + await servico.AutorizarAsync(dc); + break; + case "Consulta": await servico.ConsultarAsync("123"); break; + case "StatusServico": await servico.StatusServicoAsync((Estado)99); break; + case "RecepcaoEvento": + var evento = Cancelamento(); + evento.Justificativa = "curta"; + await servico.CancelarAsync(evento); + break; + } + }); + Assert.AreEqual(0, espiao.Chamadas); + } + + [DataTestMethod] + [DataRow("", "sem elemento Body")] + [DataRow("", "está vazio")] + [DataRow("", "sem mensagem")] + [DataRow("Rejeitado", "Rejeitado")] + public async Task RespostaSoapInvalida_ProduzErroClaro(string resposta, string mensagem) + { + using var cert = Autoassinado(); + var servico = Servico(Configuracao(cert), new SoapEspiao { Resposta = resposta }); + var erro = await Assert.ThrowsExceptionAsync(() => servico.StatusServicoAsync(Estado.PE)); + StringAssert.Contains(erro.Message, mensagem); + } + + [TestMethod] + public async Task ConfiguracaoInconsistente_ImpedeEnvioSemAlterarDocumento() + { + using var cert = Autoassinado(); + var espiao = new SoapEspiao(); + Assert.ThrowsException(() => new DceService(null!)); + var config = Configuracao(cert, TipoAmbiente.Producao); + var servico = Servico(config, espiao); + var dc = DceFixture.Padrao(); + Assert.ThrowsException(() => servico.AutorizarAsync(dc)); + Assert.ThrowsException(() => servico.CancelarAsync(Cancelamento())); + Assert.AreEqual(TipoAmbiente.Homologacao, dc.Ambiente); + Assert.ThrowsException(() => servico.AutorizarAsync(null!)); + Assert.ThrowsException(() => servico.CancelarAsync(null!)); + config.TipoAmbiente = (TipoAmbiente)99; + Assert.ThrowsException(() => servico.ConsultarAsync(Chave)); + config.TipoAmbiente = TipoAmbiente.Homologacao; + config.TimeOut = 0; + await Assert.ThrowsExceptionAsync(() => servico.StatusServicoAsync(Estado.PE)); + Assert.AreEqual(0, espiao.Chamadas); + } + + [TestMethod] + public async Task CertificadoDaConfiguracaoZeus_UsadoNaAssinaturaENoMtls() + { + using var cert = Autoassinado(); + var config = new DCeConfiguracao + { + ConfiguracaoCertificado = new ConfiguracaoCertificado + { + TipoCertificado = TipoCertificado.A1ByteArray, + ArrayBytesArquivo = cert.Export(X509ContentType.Pfx, "teste"), Senha = "teste", + KeyStorageFlags = X509KeyStorageFlags.Exportable + } + }; + var espiao = new SoapEspiao { Resposta = Retorno("Autorizacao") }; + await Servico(config, espiao).AutorizarAsync(DceFixture.Padrao()); + Assert.IsNotNull(espiao.Certificado); + // O certificado carregado sem cache é liberado ao concluir a chamada. + Assert.AreEqual(IntPtr.Zero, espiao.Certificado.Handle); + var doc = new XmlDocument { PreserveWhitespace = true }; + doc.LoadXml(espiao.Envelope!); + var assinatura = new SignedXml(doc); + assinatura.LoadXml((XmlElement)doc.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0]!); + Assert.IsTrue(assinatura.CheckSignature(cert, true)); + } + + [DataTestMethod] + [DataRow(false)] + [DataRow(true)] + public async Task SalvarXml_RespeitaOpcaoEPreservaEnvioRetorno(bool salvar) + { + using var cert = Autoassinado(); + var pasta = Path.Combine(Path.GetTempPath(), "dce-servicos-" + Guid.NewGuid().ToString("N")); + var config = Configuracao(cert); + config.IsSalvarXml = salvar; + config.CaminhoSalvarXml = pasta; + var espiao = new SoapEspiao { Resposta = Retorno("Autorizacao") }; + try + { + await Servico(config, espiao).AutorizarAsync(DceFixture.Padrao()); + Assert.AreEqual(salvar, Directory.Exists(pasta)); + if (!salvar) return; + var arquivos = Directory.GetFiles(pasta); + Assert.AreEqual(2, arquivos.Length); + var envio = File.ReadAllText(arquivos.Single(p => p.EndsWith("-env.xml", StringComparison.Ordinal))); + var mensagem = XDocument.Parse(espiao.Envelope!).Descendants(XName.Get("DCe", Ns)).Single(); + Assert.IsTrue(XNode.DeepEquals(XElement.Parse(envio), mensagem)); + var retorno = File.ReadAllText(arquivos.Single(p => p.EndsWith("-ret.xml", StringComparison.Ordinal))); + Assert.IsTrue(XNode.DeepEquals(XElement.Parse(Retorno("Autorizacao")), XElement.Parse(retorno))); + } + finally { if (Directory.Exists(pasta)) Directory.Delete(pasta, true); } + } + + [DataTestMethod] + [DataRow("Autorizacao")] + [DataRow("Consulta")] + [DataRow("StatusServico")] + [DataRow("RecepcaoEvento")] + public async Task CaminhoSchemasInexistente_ImpedeEnvioNosQuatroServicos(string operacao) + { + using var cert = Autoassinado(); + var config = Configuracao(cert); + config.CaminhoSchemas = Path.Combine(Path.GetTempPath(), "dce-ausente-" + Guid.NewGuid().ToString("N")); + var espiao = new SoapEspiao(); + var erro = await Assert.ThrowsExceptionAsync(() => + Executar(Servico(config, espiao), operacao, TipoAmbiente.Homologacao)); + StringAssert.Contains(erro.Message, config.CaminhoSchemas); + Assert.AreEqual(0, espiao.Chamadas); + } + + [TestMethod] + public async Task SalvarXmlSemPasta_ImpedeEnvio() + { + using var cert = Autoassinado(); + var config = Configuracao(cert); + config.IsSalvarXml = true; + var espiao = new SoapEspiao(); + await Assert.ThrowsExceptionAsync(() => Servico(config, espiao).StatusServicoAsync(Estado.PE)); + Assert.AreEqual(0, espiao.Chamadas); + } +} diff --git a/DCe.Testes/DceSoapClientTest.cs b/DCe.Testes/DceSoapClientTest.cs new file mode 100644 index 000000000..4aeba2c17 --- /dev/null +++ b/DCe.Testes/DceSoapClientTest.cs @@ -0,0 +1,121 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using DCe.Wsdl.Soap; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DCe.Testes; + +[TestClass] +public class DceSoapClientTest +{ + private const string Endpoint = "https://homologacao.dce.fazenda.pr.gov.br/dce/DCeStatusServico"; + private const string Action = "http://www.portalfiscal.inf.br/dce/wsdl/DCeStatusServico/dceStatusServico"; + private const string Envelope = ""; + + private sealed class HandlerEspiao : HttpMessageHandler + { + public HttpStatusCode Status { get; set; } = HttpStatusCode.OK; + public Exception? Excecao { get; set; } + public Uri? Uri { get; private set; } + public HttpMethod? Metodo { get; private set; } + public MediaTypeHeaderValue? ContentType { get; private set; } + public string? Conteudo { get; private set; } + public string? Accept { get; private set; } + public bool TemSoapAction { get; private set; } + public bool Liberado { get; private set; } + public bool AguardarCancelamento { get; set; } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (Excecao != null) throw Excecao; + if (AguardarCancelamento) await Task.Delay(Timeout.Infinite, cancellationToken); + Uri = request.RequestUri; + Metodo = request.Method; + ContentType = request.Content!.Headers.ContentType; + Conteudo = await request.Content.ReadAsStringAsync(); + Accept = request.Headers.Accept.Single().MediaType; + TemSoapAction = request.Headers.Contains("SOAPAction"); + return new HttpResponseMessage(Status) { Content = new StringContent("SEFAZ") }; + } + + protected override void Dispose(bool disposing) + { + Liberado = true; + base.Dispose(disposing); + } + } + + [TestMethod] + public async Task TransporteReal_EnviaPostUtf8Soap12ComActionNoContentType() + { + using var cert = DceServiceTest.Autoassinado(); + var handler = new HandlerEspiao(); + X509Certificate2? recebido = null; + var client = new SoapClient(c => { recebido = c; return handler; }); + var resposta = await client.SendAsync(Endpoint, Action, Envelope, cert, 12345); + Assert.AreSame(cert, recebido); + Assert.AreEqual(Endpoint, handler.Uri!.AbsoluteUri); + Assert.AreEqual(HttpMethod.Post, handler.Metodo); + Assert.AreEqual(Envelope, handler.Conteudo); + Assert.AreEqual("application/soap+xml", handler.ContentType!.MediaType); + Assert.AreEqual("utf-8", handler.ContentType.CharSet); + Assert.AreEqual("\"" + Action + "\"", handler.ContentType.Parameters.Single(p => p.Name == "action").Value); + Assert.AreEqual("application/soap+xml", handler.Accept); + Assert.IsFalse(handler.TemSoapAction); + Assert.AreEqual("SEFAZ", resposta); + Assert.IsTrue(handler.Liberado); + Assert.IsTrue(cert.HasPrivateKey, "O transporte não deve liberar o certificado do chamador."); + } + + [TestMethod] + public void HandlerReal_ConfiguraCertificadoMtlsTls12SemIgnorarValidacaoDoServidor() + { + using var cert = DceServiceTest.Autoassinado(); + using var handler = SoapClient.CriarHandler(cert); + Assert.AreEqual(ClientCertificateOption.Manual, handler.ClientCertificateOptions); + Assert.AreEqual(1, handler.ClientCertificates.Count); + Assert.AreSame(cert, handler.ClientCertificates[0]); + Assert.AreEqual(SslProtocols.Tls12, handler.SslProtocols); + Assert.IsNull(handler.ServerCertificateCustomValidationCallback); + } + + [TestMethod] + public async Task ErroHttp_PreservaStatusEndpointEResposta() + { + using var cert = DceServiceTest.Autoassinado(); + var handler = new HandlerEspiao { Status = HttpStatusCode.InternalServerError }; + var client = new SoapClient(_ => handler); + var erro = await Assert.ThrowsExceptionAsync(() => client.SendAsync(Endpoint, Action, Envelope, cert, 1000)); + StringAssert.Contains(erro.Message, "500"); + StringAssert.Contains(erro.Message, Endpoint); + StringAssert.Contains(erro.Message, "SEFAZ"); + Assert.IsTrue(handler.Liberado); + } + + [TestMethod] + public async Task TimeoutConfigurado_CancelaRequisicaoELiberaHandler() + { + using var cert = DceServiceTest.Autoassinado(); + var handler = new HandlerEspiao { AguardarCancelamento = true }; + var client = new SoapClient(_ => handler); + await Assert.ThrowsExceptionAsync(() => client.SendAsync(Endpoint, Action, Envelope, cert, 100)); + Assert.IsTrue(handler.Liberado); + } + + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task ErroDeTransporte_PropagaExcecaoELiberaRecursos(bool timeout) + { + using var cert = DceServiceTest.Autoassinado(); + var handler = new HandlerEspiao { Excecao = timeout ? new TaskCanceledException("Tempo excedido") : new HttpRequestException("Falha de conexão") }; + var client = new SoapClient(_ => handler); + if (timeout) + await Assert.ThrowsExceptionAsync(() => client.SendAsync(Endpoint, Action, Envelope, cert, 1000)); + else + await Assert.ThrowsExceptionAsync(() => client.SendAsync(Endpoint, Action, Envelope, cert, 1000)); + Assert.IsTrue(handler.Liberado); + } +} diff --git a/DCe.Testes/Fixtures/Cancelamento.xml b/DCe.Testes/Fixtures/Cancelamento.xml new file mode 100644 index 000000000..121aa1d6e --- /dev/null +++ b/DCe.Testes/Fixtures/Cancelamento.xml @@ -0,0 +1 @@ +26239999999900019112345678909262608999999990001919900100000000113001234552026-08-22T12:00:00-03:001101111Cancelamento1262600000000001TRANSPORTE CANCELADO A PEDIDO DO DECLARANTE \ No newline at end of file diff --git a/DCe.Testes/Fixtures/DceFixture.cs b/DCe.Testes/Fixtures/DceFixture.cs new file mode 100644 index 000000000..3689ae704 --- /dev/null +++ b/DCe.Testes/Fixtures/DceFixture.cs @@ -0,0 +1,77 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using DCe.Testes.Fixtures.Entradas; + +namespace DCe.Testes.Fixtures; + +/// +/// DC-e canônica dos testes. Tudo fixo e determinístico: datas com DateTimeKind.Utc e +/// nenhuma dependência de relógio. O cDC (aleatório por especificação) NÃO mora aqui — +/// entra pelo IRandomSource injetado, senão nenhum teste de XML seria estável. +/// +internal static class DceFixture +{ + public static DeclaracaoConteudo Padrao() => new() + { + Ambiente = TipoAmbiente.Homologacao, + CodigoUf = (Estado)26, + Serie = 1, + Numero = 1, + TipoEmissao = 1, + TipoEmitente = 3, + SiteAutorizador = 0, + VersaoAplicativo = "ZeusFiscal 1.0", + Emitente = new PessoaDce + { + Cpf = "12345678909", + Nome = "USUARIO NAO CONTRIBUINTE", + Endereco = new EnderecoDce + { + Logradouro = "RUA EXEMPLO", + Numero = "100", + Bairro = "CENTRO", + CodigoMunicipio = 2611101, + Municipio = "PETROLINA", + Uf = "PE", + Cep = "56300000" + } + }, + Signatario = new SignatarioDce + { + Cnpj = "99999999000191", + Nome = "TRANSPORTADORA EXEMPLO LTDA" + }, + Destinatario = new PessoaDce + { + Cpf = "12345678909", + Nome = "USUARIO NAO CONTRIBUINTE", + Endereco = new EnderecoDce + { + Logradouro = "AVENIDA EXEMPLO", + Numero = "200", + Bairro = "BAIRRO EXEMPLO", + CodigoMunicipio = 2611606, + Municipio = "RECIFE", + Uf = "PE", + Cep = "51020000" + } + }, + Itens = new List + { + new() + { + Descricao = "VEICULO USADO DE TESTE", + Ncm = "87032310", + Quantidade = 1m, + ValorUnitario = 210342m, + ValorTotal = 210342m + } + }, + Transporte = new TransporteDce + { + Modalidade = "2", // 2 = transporte por empresa transportadora + CnpjTransportador = "99999999000191" + }, + Solicitacao = "TRANSPORTE DE VEICULO USADO PARTICULAR" + }; +} diff --git a/DCe.Testes/Fixtures/Declaracao.xml b/DCe.Testes/Fixtures/Declaracao.xml new file mode 100644 index 000000000..3eb4a6d8e --- /dev/null +++ b/DCe.Testes/Fixtures/Declaracao.xml @@ -0,0 +1 @@ +2612345699112026-08-22T09:00:00-03:0013002ZeusFiscal 1.012345678909USUARIO NAO CONTRIBUINTERUA EXEMPLO100CENTRO2611101PETROLINAPE563000001058BRASIL99999999000191TRANSPORTADORA EXEMPLO LTDA12345678909USUARIO NAO CONTRIBUINTEAVENIDA EXEMPLO200BAIRRO EXEMPLO2611606RECIFEPE510200001058BRASILVEICULO USADO DE TESTE870323101.0000210342.00210342.00210342.00299999999000191É contribuinte de ICMS qualquer pessoa física ou jurídica, que realize, com habitualidade ou em volume que caracterize intuito comercial, operações de circulação de mercadoria ou prestações de serviços de transportes interestadual e intermunicipal e de comunicação, ainda que as operações e prestações de iniciem no exterior (Lei Complementar nº 87/96, Art. 4º)Constitui crime contra a ordem tributária suprimir ou reduzir tributo, ou contribuição social e qualquer acessório: quando negar ou deixar de fornecer, quando obrigatório, nota fiscal ou documento equivalente, relativa a venda de mercadoria ou prestação de serviço, efetivamente realizada ou fornece-la em desacordo com a legislação. Sob pena de reclusão de 2 (dois) e 5 (cinco) anos, e multa (Lei 8.137/90, Art 1ª, V)TRANSPORTE DE VEICULO USADO PARTICULARhttps://www.fazenda.pr.gov.br/dce/qrcode?chDCe=26260899999999000191990010000000011301234560&tpAmb=2https://www.fazenda.pr.gov.br/dce/qrcode \ No newline at end of file diff --git a/DCe.Testes/Fixtures/Entradas/CancelamentoDce.cs b/DCe.Testes/Fixtures/Entradas/CancelamentoDce.cs new file mode 100644 index 000000000..736c7e508 --- /dev/null +++ b/DCe.Testes/Fixtures/Entradas/CancelamentoDce.cs @@ -0,0 +1,38 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System; +namespace DCe.Testes.Fixtures.Entradas; + +/// +/// Evento de cancelamento da DC-e, tpEvento 110111 — o único evento que a DC-e tem. +/// +internal class CancelamentoDce +{ + public TipoAmbiente Ambiente { get; set; } = TipoAmbiente.Homologacao; + public string ChaveAcesso { get; set; } = string.Empty; + public Estado CodigoOrgao { get; set; } + + /// Mesmo domínio de tpEmit da emissão: 3 = Transportadora. + public int TipoEmitente { get; set; } = 3; + + /// CNPJ de quem assina o evento — tem que ser o mesmo emissor da DC-e. + public string CnpjAutor { get; set; } = string.Empty; + + /// Identificação do usuário emitente da DC-e original: um destes três. + public string? CnpjUsuarioEmitente { get; set; } + public string? CpfUsuarioEmitente { get; set; } + public string? IdOutrosUsuarioEmitente { get; set; } + + public string NumeroProtocolo { get; set; } = string.Empty; + public string Justificativa { get; set; } = string.Empty; + + /// 1 no primeiro cancelamento. Até 3 dígitos. + public int SequenciaEvento { get; set; } = 1; + + /// + /// Offset local do emitente usado para formatar dhEvento. Mesma razão de + /// : a SEFAZ compara o literal contra o + /// relógio local dela (cStat 703, regra B07-10). Default -3h; ajustar para AM/RO/RR/AC. + /// + public TimeSpan OffsetHorario { get; set; } = TimeSpan.FromHours(-3); +} diff --git a/DCe.Testes/Fixtures/Entradas/DeclaracaoConteudo.cs b/DCe.Testes/Fixtures/Entradas/DeclaracaoConteudo.cs new file mode 100644 index 000000000..1405f5c0f --- /dev/null +++ b/DCe.Testes/Fixtures/Entradas/DeclaracaoConteudo.cs @@ -0,0 +1,60 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System; +using System.Collections.Generic; +namespace DCe.Testes.Fixtures.Entradas; + +/// +/// Uma DC-e. O manual permite no máximo UMA por chamada ao DCeAutorizacao — não há lote. +/// +internal class DeclaracaoConteudo +{ + /// "1" = Produção, "2" = Homologação. + public TipoAmbiente Ambiente { get; set; } = TipoAmbiente.Homologacao; + + /// Código IBGE da UF do emitente (2 dígitos). + public Estado CodigoUf { get; set; } + + public int Serie { get; set; } = 1; + public int Numero { get; set; } + + /// 1 = normal, 2 = contingência off-line. A NT corrigiu de 9 para 2. + public int TipoEmissao { get; set; } = 1; + + /// 0 Fisco · 1 Marketplace · 2 Emissor próprio · 3 Transportadora · 4 Correios. + public int TipoEmitente { get; set; } = 3; + + public int SiteAutorizador { get; set; } + + public string VersaoAplicativo { get; set; } = string.Empty; + + /// O usuário emitente — o não contribuinte dono dos bens. + public PessoaDce Emitente { get; set; } = new(); + + /// Quem assina. Para a modalidade Transportadora, é a transportadora. + public SignatarioDce Signatario { get; set; } = new(); + + public PessoaDce Destinatario { get; set; } = new(); + + public List Itens { get; set; } = new(); + + public TransporteDce Transporte { get; set; } = new(); + + /// Texto do grupo infSolicDCe. + public string Solicitacao { get; set; } = string.Empty; + + public string? InformacoesComplementares { get; set; } + + /// + /// Offset local do emitente usado para formatar dhEmi. Existe porque a SEFAZ compara + /// o LITERAL de dhEmi contra o relógio local dela, e não o instante UTC equivalente — + /// mandar em +00:00 (ainda que representando o mesmo instante que a hora local) faz o + /// literal parecer 3h no futuro e a SEFAZ rejeita com cStat 703 ("Data-Hora de Emissao + /// posterior ao horario de recebimento", regra B07-10 do Anexo I, tolerância de 5 minutos). + /// O XSD (TDateTimeUTC) aceita offsets de -00:00 a -11:00/+12:00; + /// a validade estrutural não substitui a verificação fiscal do horário. + /// Default -3h cobre a maior parte do país, inclusive PE e PR (autorizador), mas + /// AM/RO/RR/AC são -04:00/-05:00: setar explicitamente para emitentes desses estados. + /// + public TimeSpan OffsetHorario { get; set; } = TimeSpan.FromHours(-3); +} diff --git a/DCe.Testes/Fixtures/Entradas/EnderecoDce.cs b/DCe.Testes/Fixtures/Entradas/EnderecoDce.cs new file mode 100644 index 000000000..809b98d95 --- /dev/null +++ b/DCe.Testes/Fixtures/Entradas/EnderecoDce.cs @@ -0,0 +1,20 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System; +namespace DCe.Testes.Fixtures.Entradas; + +internal class EnderecoDce +{ + public string Logradouro { get; set; } = string.Empty; + public string Numero { get; set; } = string.Empty; + public string? Complemento { get; set; } + public string Bairro { get; set; } = string.Empty; + public int CodigoMunicipio { get; set; } + public string Municipio { get; set; } = string.Empty; + public string Uf { get; set; } = string.Empty; + public string Cep { get; set; } = string.Empty; + public int CodigoPais { get; set; } = 1058; + public string Pais { get; set; } = "BRASIL"; + public string? Telefone { get; set; } + public string? Email { get; set; } +} diff --git a/DCe.Testes/Fixtures/Entradas/ItemDce.cs b/DCe.Testes/Fixtures/Entradas/ItemDce.cs new file mode 100644 index 000000000..d7d80dc76 --- /dev/null +++ b/DCe.Testes/Fixtures/Entradas/ItemDce.cs @@ -0,0 +1,14 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System; +namespace DCe.Testes.Fixtures.Entradas; + +internal class ItemDce +{ + public string Descricao { get; set; } = string.Empty; + public string Ncm { get; set; } = string.Empty; + public decimal Quantidade { get; set; } + public decimal ValorUnitario { get; set; } + public decimal ValorTotal { get; set; } + public string? InformacoesAdicionais { get; set; } +} diff --git a/DCe.Testes/Fixtures/Entradas/PessoaDce.cs b/DCe.Testes/Fixtures/Entradas/PessoaDce.cs new file mode 100644 index 000000000..e96872f3a --- /dev/null +++ b/DCe.Testes/Fixtures/Entradas/PessoaDce.cs @@ -0,0 +1,16 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System; +namespace DCe.Testes.Fixtures.Entradas; + +internal class PessoaDce +{ + public string? Cnpj { get; set; } + public string? Cpf { get; set; } + + /// Identificação de estrangeiro, quando não há CNPJ nem CPF. + public string? IdOutros { get; set; } + + public string Nome { get; set; } = string.Empty; + public EnderecoDce Endereco { get; set; } = new(); +} diff --git a/DCe.Testes/Fixtures/Entradas/SignatarioDce.cs b/DCe.Testes/Fixtures/Entradas/SignatarioDce.cs new file mode 100644 index 000000000..adf069dcf --- /dev/null +++ b/DCe.Testes/Fixtures/Entradas/SignatarioDce.cs @@ -0,0 +1,14 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System; +namespace DCe.Testes.Fixtures.Entradas; + +/// +/// Quem assina a DC-e. No XSD é um choice entre Fisco, Marketplace, Transportadora e ECT — +/// bloco separado do emit, que é o usuário emitente. Esta fixture usa Transportadora. +/// +internal class SignatarioDce +{ + public string Cnpj { get; set; } = string.Empty; + public string Nome { get; set; } = string.Empty; +} diff --git a/DCe.Testes/Fixtures/Entradas/TransporteDce.cs b/DCe.Testes/Fixtures/Entradas/TransporteDce.cs new file mode 100644 index 000000000..25190f7b1 --- /dev/null +++ b/DCe.Testes/Fixtures/Entradas/TransporteDce.cs @@ -0,0 +1,12 @@ +using DFe.Classes.Entidades; +using DFe.Classes.Flags; +using System; +namespace DCe.Testes.Fixtures.Entradas; + +internal class TransporteDce +{ + /// Modalidade do transporte, conforme domínio de modTrans no XSD. + public string Modalidade { get; set; } = string.Empty; + + public string CnpjTransportador { get; set; } = string.Empty; +} diff --git a/DCe.Testes/Fixtures/EntradasModelo.cs b/DCe.Testes/Fixtures/EntradasModelo.cs new file mode 100644 index 000000000..b6dabe671 --- /dev/null +++ b/DCe.Testes/Fixtures/EntradasModelo.cs @@ -0,0 +1,24 @@ +using DCe.Testes.Fixtures; +using System; +using System.Threading.Tasks; +using DCe.Testes.Fixtures.Entradas; +using DCe.Classes.Retorno; +using DCe.Classes.Servicos.Evento; +using DCe.Servicos; +using DCe.Utils; +using DCe.Utils.Abstractions; +using DCeDocumento = DCe.Classes.Informacoes.DCe; + +namespace DCe.Testes; + +// Converte as entradas das fixtures para os modelos fiscais usados pelos serviços. +internal static class EntradasModelo +{ + private sealed class Relogio : IClock { public DateTime UtcNow => new(2026, 8, 22, 12, 0, 0, DateTimeKind.Utc); } + private sealed class Codigo : IRandomSource { public int ProximoCodigoDocumento() => 123456; } + private static FabricaModeloDCe Ponte() => new(new Relogio(), new Codigo()); + public static Task AutorizarAsync(this DceService s, DeclaracaoConteudo d) => + s.AutorizarAsync(d == null ? null! : Ponte().Criar(d)); + public static Task CancelarAsync(this DceService s, CancelamentoDce d) => + s.CancelarAsync(d == null ? null! : Ponte().Criar(d)); +} diff --git a/DCe.Testes/Fixtures/FabricaModeloDCe.cs b/DCe.Testes/Fixtures/FabricaModeloDCe.cs new file mode 100644 index 000000000..2764f6367 --- /dev/null +++ b/DCe.Testes/Fixtures/FabricaModeloDCe.cs @@ -0,0 +1,88 @@ +using System; +using System.Globalization; +using System.Linq; +using DCe.Classes.Flags; +using DCe.Classes.Informacoes; +using DCe.Testes.Fixtures.Entradas; +using DCe.Classes.Servicos.Evento; +using DCe.Utils; +using DCe.Utils.Abstractions; +using DCe.Utils.Extensoes; +using DCeDocumento = DCe.Classes.Informacoes.DCe; + +namespace DCe.Testes.Fixtures; + +/// Fábrica dos dados de entrada dos testes, sem serialização própria. +internal sealed class FabricaModeloDCe +{ + private readonly IClock _clock; + private readonly IRandomSource _random; + public FabricaModeloDCe() : this(new SystemClock(), new SecureRandomSource()) { } + public FabricaModeloDCe(IClock clock, IRandomSource randomSource) + { + _clock = clock ?? throw new ArgumentNullException(nameof(clock)); + _random = randomSource ?? throw new ArgumentNullException(nameof(randomSource)); + } + private static string Digitos(string? s) => new((s ?? string.Empty).Where(char.IsDigit).ToArray()); + private static string? Documento(string? s) => string.IsNullOrWhiteSpace(s) ? null : Digitos(s); + private static string? Texto(string? s) => string.IsNullOrWhiteSpace(s) ? null : s; + private static TUf Uf(string s) => (TUf)Enum.Parse(typeof(TUf), s.Trim().ToUpperInvariant()); + private DateTimeOffset Instante(TimeSpan offset) => new DateTimeOffset(DateTime.SpecifyKind(_clock.UtcNow, DateTimeKind.Utc)).ToOffset(offset); + + public DCeDocumento Criar(DeclaracaoConteudo dc) + { + var e = dc.Emitente.Endereco; + var d = dc.Destinatario.Endereco; + var documento = new DCeDocumento + { + infDCe = new infDCe + { + ide = new DCeIde + { + cUF = dc.CodigoUf, cDC = _random.ProximoCodigoDocumento().ToString("D6", CultureInfo.InvariantCulture), + serie = dc.Serie, nDC = dc.Numero, dhEmi = Instante(dc.OffsetHorario), + tpEmis = (TipoEmissaoDCe)dc.TipoEmissao, tpEmit = (TipoEmitenteDCe)dc.TipoEmitente, + nSiteAutoriz = dc.SiteAutorizador, tpAmb = dc.Ambiente, verProc = dc.VersaoAplicativo + }, + emit = new DCeEmit + { + CNPJ = Documento(dc.Emitente.Cnpj), CPF = Documento(dc.Emitente.Cpf), idOutros = Texto(dc.Emitente.IdOutros)?.Trim(), xNome = dc.Emitente.Nome, + enderEmit = new enderEmit + { + xLgr = e.Logradouro, nro = e.Numero, xCpl = Texto(e.Complemento), xBairro = e.Bairro, + cMun = e.CodigoMunicipio, xMun = e.Municipio, UF = Uf(e.Uf), CEP = Digitos(e.Cep).PadLeft(8, '0'), + cPais = (PaisEmitenteDCe)e.CodigoPais, xPais = (NomePaisEmitenteDCe)Enum.Parse(typeof(NomePaisEmitenteDCe),e.Pais), fone = Documento(e.Telefone) + } + }, + Transportadora = dc.TipoEmitente == 3 ? new Transportadora { CNPJ = Digitos(dc.Signatario.Cnpj), xNome = dc.Signatario.Nome } : null, + dest = new DCeDest + { + CNPJ = Documento(dc.Destinatario.Cnpj), CPF = Documento(dc.Destinatario.Cpf), idOutros = Texto(dc.Destinatario.IdOutros)?.Trim(), xNome = dc.Destinatario.Nome, + enderDest = new enderDest + { + xLgr = d.Logradouro, nro = d.Numero, xCpl = Texto(d.Complemento), xBairro = d.Bairro, + cMun = d.CodigoMunicipio, xMun = d.Municipio, UF = Uf(d.Uf), CEP = Digitos(d.Cep).PadLeft(8,'0'), + cPais = d.CodigoPais, xPais = d.Pais, fone = Documento(d.Telefone), email = Texto(d.Email) + } + }, + det = dc.Itens.Select((i,n) => new DCeDet { nItem = n+1, prod = new DCeProd { xProd=i.Descricao, NCM=i.Ncm, qCom=i.Quantidade, vUnCom=i.ValorUnitario, vProd=i.ValorTotal }, infAdProd=Texto(i.InformacoesAdicionais) }).ToList(), + total = new DCeTotal { vDC = dc.Itens.Sum(i => i.ValorTotal) }, + transp = new DCeTransp { modTrans = (ModalidadeTransporteDCe)int.Parse(dc.Transporte.Modalidade, CultureInfo.InvariantCulture), CNPJTransp = Digitos(dc.Transporte.CnpjTransportador) }, + infAdic = string.IsNullOrWhiteSpace(dc.InformacoesComplementares) ? null : new infAdic { infCpl=dc.InformacoesComplementares } + }, + infSolicDCe = new infSolicDCe { xSolic = dc.Solicitacao ?? string.Empty } + }; + return documento.Preparar(dc.Signatario.Cnpj); + } + + public eventoDCe Criar(CancelamentoDce p) => new eventoDCe + { + infEvento = new infEvento + { + cOrgao = (TCOrgaoIBGE)(int)p.CodigoOrgao, tpAmb=p.Ambiente, tpEmit=(TipoEmitenteDCe)p.TipoEmitente, + CNPJAutor=Digitos(p.CnpjAutor), CNPJUsEmit=Documento(p.CnpjUsuarioEmitente), CPFUsEmit=Documento(p.CpfUsuarioEmitente), + IdOutrosUsEmit=Texto(p.IdOutrosUsuarioEmitente)?.Trim(), chDCe=p.ChaveAcesso, dhEvento=Instante(p.OffsetHorario), nSeqEvento=p.SequenciaEvento, + detEvento = new detEvento { evCancDCe = new evCancDCe { nProt=p.NumeroProtocolo, xJust=p.Justificativa } } + } + }.Preparar(); +} diff --git a/DCe.Wsdl/DCe.Wsdl.csproj b/DCe.Wsdl/DCe.Wsdl.csproj new file mode 100644 index 000000000..99c01b5a1 --- /dev/null +++ b/DCe.Wsdl/DCe.Wsdl.csproj @@ -0,0 +1,11 @@ + + + net462;netstandard2.0;net8.0;net10.0 + latest + enable + + + + + + diff --git a/DCe.Wsdl/Soap/ISoapClient.cs b/DCe.Wsdl/Soap/ISoapClient.cs new file mode 100644 index 000000000..18d7226e0 --- /dev/null +++ b/DCe.Wsdl/Soap/ISoapClient.cs @@ -0,0 +1,11 @@ +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace DCe.Wsdl.Soap; + +/// Transporte substituível; não constrói nem interpreta mensagens fiscais. +public interface ISoapClient +{ + Task SendAsync(string endpoint, string soapAction, string soapEnvelope, + X509Certificate2 certificado, int timeOut); +} diff --git a/DCe.Wsdl/Soap/SoapClient.cs b/DCe.Wsdl/Soap/SoapClient.cs new file mode 100644 index 000000000..48a45770e --- /dev/null +++ b/DCe.Wsdl/Soap/SoapClient.cs @@ -0,0 +1,58 @@ +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace DCe.Wsdl.Soap; + +/// SOAP 1.2 sobre HTTPS com certificado cliente e validação TLS da plataforma. +public sealed class SoapClient : ISoapClient +{ + private readonly Func _criarHandler; + + public SoapClient() : this(CriarHandler) { } + + internal SoapClient(Func criarHandler) + => _criarHandler = criarHandler ?? throw new ArgumentNullException(nameof(criarHandler)); + + internal static HttpClientHandler CriarHandler(X509Certificate2 certificado) + { + if (certificado == null) throw new ArgumentNullException(nameof(certificado)); + var handler = new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Manual }; +#if NET462 + System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12; +#else + handler.SslProtocols = SslProtocols.Tls12; +#endif + handler.ClientCertificates.Add(certificado); + return handler; + } + + public async Task SendAsync(string endpoint, string soapAction, string soapEnvelope, + X509Certificate2 certificado, int timeOut) + { + if (string.IsNullOrWhiteSpace(endpoint)) throw new ArgumentException("O endpoint não pode ser vazio.", nameof(endpoint)); + if (string.IsNullOrWhiteSpace(soapAction)) throw new ArgumentException("A ação SOAP não pode ser vazia.", nameof(soapAction)); + if (string.IsNullOrWhiteSpace(soapEnvelope)) throw new ArgumentException("O envelope SOAP não pode ser vazio.", nameof(soapEnvelope)); + if (certificado == null) throw new ArgumentNullException(nameof(certificado)); + if (timeOut <= 0) throw new ArgumentOutOfRangeException(nameof(timeOut), "O timeout deve ser positivo."); + + using (var client = new HttpClient(_criarHandler(certificado))) + using (var content = new StringContent(soapEnvelope, Encoding.UTF8, "application/soap+xml")) + { + client.Timeout = TimeSpan.FromMilliseconds(timeOut); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/soap+xml")); + content.Headers.ContentType!.Parameters.Add(new NameValueHeaderValue("action", "\"" + soapAction + "\"")); + using (var response = await client.PostAsync(endpoint, content).ConfigureAwait(false)) + { + var resposta = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + if (!response.IsSuccessStatusCode) + throw new HttpRequestException($"Erro HTTP {(int)response.StatusCode} ({response.StatusCode}) ao comunicar com SEFAZ. Endpoint: {endpoint}. Resposta: {resposta}"); + return resposta; + } + } + } +} diff --git a/DCe.Wsdl/Soap/SoapEnvelope.cs b/DCe.Wsdl/Soap/SoapEnvelope.cs new file mode 100644 index 000000000..d4832854d --- /dev/null +++ b/DCe.Wsdl/Soap/SoapEnvelope.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +namespace DCe.Wsdl.Soap; + +/// Envelope SOAP 1.2 e extração da mensagem fiscal, sem alterar seu conteúdo. +public static class SoapEnvelope +{ + private static readonly XNamespace DceNs = "http://www.portalfiscal.inf.br/dce"; + private static readonly XNamespace SoapNs = "http://www.w3.org/2003/05/soap-envelope"; + public const string NamespaceWsdl = "http://www.portalfiscal.inf.br/dce/wsdl/"; + + public static string XmlFiscal(string xml) + { + // A declaração XML pertence ao documento, não pode aparecer dentro do Body. + if (xml.StartsWith("", StringComparison.Ordinal) + 2); + return xml; + } + + public static string Criar(string xmlFiscal, string servico) => + "" + + XmlFiscal(xmlFiscal) + ""; + + /// Recorta o elemento fiscal original, preservando espaços, entidades e aspas. + public static string ExtrairXmlFiscal(string resposta) + { + if (string.IsNullOrWhiteSpace(resposta)) return resposta; + var raiz = XElement.Parse(resposta); + if (raiz.Name.Namespace != SoapNs) return resposta; + // Valida Fault/Body/wrapper com os mesmos critérios da projeção tipada. + Desembrulhar(resposta); + using var texto = new StringReader(resposta); + using var reader = XmlReader.Create(texto, new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null }); + var profundidadeBody = -1; + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1 && + reader.NamespaceURI == SoapNs.NamespaceName && reader.LocalName == "Body") + profundidadeBody = reader.Depth; + if (profundidadeBody < 0 || reader.Depth <= profundidadeBody || + reader.NodeType != XmlNodeType.Element || reader.NamespaceURI != DceNs.NamespaceName) continue; + var inicio = Posicao(resposta, (IXmlLineInfo)reader) - 1; + if (reader.IsEmptyElement) + return resposta.Substring(inicio, FimTag(resposta, inicio) - inicio); + var profundidade = reader.Depth; + while (reader.Read()) + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == profundidade) + { + var fim = FimTag(resposta, Posicao(resposta, (IXmlLineInfo)reader)); + return resposta.Substring(inicio, fim - inicio); + } + } + throw new InvalidOperationException("Envelope SOAP sem mensagem da DC-e."); + } + + private static int Posicao(string xml, IXmlLineInfo info) + { + var indice = 0; + for (var linha = 1; linha < info.LineNumber; linha++) + { + while (xml[indice] != '\r' && xml[indice] != '\n') indice++; + if (xml[indice++] == '\r' && indice < xml.Length && xml[indice] == '\n') indice++; + } + return indice + info.LinePosition - 1; + } + + private static int FimTag(string xml, int inicio) + { + char aspas = '\0'; + for (var i = inicio; i < xml.Length; i++) + { + var c = xml[i]; + if (aspas != '\0') { if (c == aspas) aspas = '\0'; } + else if (c == '\'' || c == '"') aspas = c; + else if (c == '>') return i + 1; + } + throw new XmlException("Tag XML incompleta."); + } + + public static string Desembrulhar(string resposta) + { + if (string.IsNullOrWhiteSpace(resposta)) return resposta; + var raiz = XElement.Parse(resposta, LoadOptions.PreserveWhitespace); + if (raiz.Name.Namespace != SoapNs) return resposta; + var body = raiz.Element(SoapNs + "Body") ?? throw new InvalidOperationException("Envelope SOAP sem elemento Body."); + var conteudo = body.Elements().FirstOrDefault() ?? throw new InvalidOperationException("Body do envelope SOAP está vazio."); + if (conteudo.Name == SoapNs + "Fault") throw new InvalidOperationException("Falha SOAP retornada pela SEFAZ: " + conteudo.Value); + if (conteudo.Name.Namespace != DceNs) + conteudo = conteudo.Elements().FirstOrDefault(e => e.Name.Namespace == DceNs) + ?? throw new InvalidOperationException("Wrapper de retorno sem mensagem da DC-e."); + return Serializar(conteudo); + } + + private static string Serializar(XElement raiz) + { + using (var stream = new MemoryStream()) + { + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings + { + Encoding = new UTF8Encoding(false), Indent = false, OmitXmlDeclaration = false, + NewLineHandling = NewLineHandling.None + })) raiz.Save(writer); + return Encoding.UTF8.GetString(stream.ToArray()); + } + } +} diff --git a/ZeusFiscal.sln b/ZeusFiscal.sln index b7958d14b..2403709ec 100644 --- a/ZeusFiscal.sln +++ b/ZeusFiscal.sln @@ -132,6 +132,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Classes", "DCe.Classes\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Utils", "DCe.Utils\DCe.Utils.csproj", "{A388E674-202C-4FEC-81A7-99DEA23642AE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Testes", "DCe.Testes\DCe.Testes.csproj", "{BA922E6B-B2B0-4812-A31C-9308917A2C01}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Servicos", "DCe.Servicos\DCe.Servicos.csproj", "{AB236674-B734-4783-ABA6-D94B8ADC51A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Wsdl", "DCe.Wsdl\DCe.Wsdl.csproj", "{FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +146,14 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Debug|x86.Build.0 = Debug|Any CPU + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Release|Any CPU.Build.0 = Release|Any CPU + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Release|x86.ActiveCfg = Release|Any CPU + {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Release|x86.Build.0 = Release|Any CPU {BB030128-9609-46D5-941C-1F53D95157E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BB030128-9609-46D5-941C-1F53D95157E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB030128-9609-46D5-941C-1F53D95157E2}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -524,11 +538,32 @@ Global {A388E674-202C-4FEC-81A7-99DEA23642AE}.Release|Any CPU.Build.0 = Release|Any CPU {A388E674-202C-4FEC-81A7-99DEA23642AE}.Release|x86.ActiveCfg = Release|Any CPU {A388E674-202C-4FEC-81A7-99DEA23642AE}.Release|x86.Build.0 = Release|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Debug|x86.ActiveCfg = Debug|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Debug|x86.Build.0 = Debug|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Release|Any CPU.Build.0 = Release|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Release|x86.ActiveCfg = Release|Any CPU + {BA922E6B-B2B0-4812-A31C-9308917A2C01}.Release|x86.Build.0 = Release|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Debug|x86.ActiveCfg = Debug|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Debug|x86.Build.0 = Debug|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Debug|x64.ActiveCfg = Debug|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Debug|x64.Build.0 = Debug|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Release|Any CPU.Build.0 = Release|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Release|x86.ActiveCfg = Release|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Release|x86.Build.0 = Release|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Release|x64.ActiveCfg = Release|Any CPU + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {AB236674-B734-4783-ABA6-D94B8ADC51A7} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} {BB030128-9609-46D5-941C-1F53D95157E2} = {3960070E-93C9-4852-8A53-F4918D602782} {DD441D1C-1F4A-4631-AA72-015C4FA735D2} = {F981A8AC-E660-492B-8BF3-CBF70B66E392} {8994DDA1-E36B-40E5-9F98-C0C75CC61971} = {AFB3AAC0-E3CE-4DF9-8D52-D958FFADF126} @@ -585,6 +620,8 @@ Global {D868005D-1F35-43E0-BE57-7914D5BA1583} = {003B008B-F388-4FB5-A081-12C0FBA84B57} {1B307DCC-F565-47AB-BCBE-75FC5F8ADA1F} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} {A388E674-202C-4FEC-81A7-99DEA23642AE} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} + {BA922E6B-B2B0-4812-A31C-9308917A2C01} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} + {FED02CAF-4511-4F14-AD3A-8BD85DAF45F8} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C8C4D9F7-EF86-49A2-83AF-13FA5D4E8DBB} From 166705a4eb4c2249dddf69d833c07fcd66b4771b Mon Sep 17 00:00:00 2001 From: Vision-Digital Date: Sat, 17 Oct 2026 17:45:26 -0300 Subject: [PATCH 3/3] feat(dce): DACE em QuestPDF, pacotes NuGet, CI e README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementa DACE completa e resumida conforme Anexo II e leiautes da NT da DC-e. Consome o modelo XML tipado validado pelos XSDs v1.00, sem recalcular valores fiscais. Publica os pacotes com dependência explícita da impressão no núcleo Hercules.NET.DCe. Inclui CI, testes de impressão e catálogo no README. Preserva SOAP 1.2 com action no Content-Type, SEFAZ-PR e CaminhoSchemas opcional. --- .github/workflows/ZeusFiscal_build.yml | 20 ++ .../DCe.Dace.QuestPdf.Testes.csproj | 25 ++ DCe.Dace.QuestPdf.Testes/DaceDocumentTests.cs | 258 ++++++++++++++++++ DCe.Dace.QuestPdf/DCe.Dace.QuestPdf.csproj | 18 ++ DCe.Dace.QuestPdf/DaceDocument.cs | 213 +++++++++++++++ DCe.Dace.QuestPdf/DaceViewModel.cs | 179 ++++++++++++ .../Hercules.NET.DCe/Hercules.NET.DCe.csproj | 10 + .../Hercules.NET.DCe/Hercules.NET.DCe.nuspec | 63 +++++ ...Hercules.NET.Impressao.DCe.QuestPdf.csproj | 10 + ...Hercules.NET.Impressao.DCe.QuestPdf.nuspec | 40 +++ README.md | 13 +- ZeusFiscal.sln | 22 ++ 12 files changed, 870 insertions(+), 1 deletion(-) create mode 100644 DCe.Dace.QuestPdf.Testes/DCe.Dace.QuestPdf.Testes.csproj create mode 100644 DCe.Dace.QuestPdf.Testes/DaceDocumentTests.cs create mode 100644 DCe.Dace.QuestPdf/DCe.Dace.QuestPdf.csproj create mode 100644 DCe.Dace.QuestPdf/DaceDocument.cs create mode 100644 DCe.Dace.QuestPdf/DaceViewModel.cs create mode 100644 NuGet/Hercules.NET.DCe/Hercules.NET.DCe.csproj create mode 100644 NuGet/Hercules.NET.DCe/Hercules.NET.DCe.nuspec create mode 100644 NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.csproj create mode 100644 NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.nuspec diff --git a/.github/workflows/ZeusFiscal_build.yml b/.github/workflows/ZeusFiscal_build.yml index cb2c4a262..768bb1c38 100644 --- a/.github/workflows/ZeusFiscal_build.yml +++ b/.github/workflows/ZeusFiscal_build.yml @@ -78,6 +78,20 @@ jobs: dotnet build -p:Version=${{ steps.date.outputs.date }} ".\CTe.Utils\CTe.Utils.csproj" -c Release dotnet build -p:Version=${{ steps.date.outputs.date }} ".\CTe.Wsdl\CTe.Wsdl.csproj" -c Release + - name: Build/Restore DCe + run: | + foreach ($projeto in @('DCe.Classes', 'DCe.Utils', 'DCe.Wsdl', 'DCe.Servicos', 'DCe.Dace.QuestPdf')) { + dotnet build -p:Version=${{ steps.date.outputs.date }} "$projeto/$projeto.csproj" -c Release + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + + - name: Testes unitários DCe e DACE + run: | + foreach ($projeto in @('DCe.Testes', 'DCe.Dace.QuestPdf.Testes')) { + dotnet test "$projeto/$projeto.csproj" -p:Version=${{ steps.date.outputs.date }} -c Release -f net10.0 --logger trx --results-directory TestResults/DCe + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + - name: Build Impressão QuestPDF run: | dotnet build -p:Version=${{ steps.date.outputs.date }} ".\NFe.Danfe.QuestPdf\NFe.Danfe.QuestPdf.csproj" -c Release @@ -91,6 +105,10 @@ jobs: dotnet pack -o ${{ github.workspace }} -v minimal -c Release -p:NuspecProperties=version=${{ steps.date.outputs.date }} -p:PackageVersion=${{ steps.date.outputs.date }} NuGet\Hercules.NET.MDFe\Hercules.NET.MDFe.csproj dotnet pack -o ${{ github.workspace }} -v minimal -c Release -p:NuspecProperties=version=${{ steps.date.outputs.date }} -p:PackageVersion=${{ steps.date.outputs.date }} NuGet\Hercules.NET.CTe\Hercules.NET.CTe.csproj dotnet pack -o ${{ github.workspace }} -v minimal -c Release -p:NuspecProperties=version=${{ steps.date.outputs.date }} -p:PackageVersion=${{ steps.date.outputs.date }} NuGet\Hercules.NET.Impressao.NFCe.QuestPdf\Hercules.NET.Impressao.NFCe.QuestPdf.csproj + dotnet pack -o ${{ github.workspace }} -v minimal -c Release -p:NuspecProperties=version=${{ steps.date.outputs.date }} -p:PackageVersion=${{ steps.date.outputs.date }} NuGet\Hercules.NET.DCe\Hercules.NET.DCe.csproj + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + dotnet pack -o ${{ github.workspace }} -v minimal -c Release -p:NuspecProperties=version=${{ steps.date.outputs.date }} -p:PackageVersion=${{ steps.date.outputs.date }} NuGet\Hercules.NET.Impressao.DCe.QuestPdf\Hercules.NET.Impressao.DCe.QuestPdf.csproj + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Upload Artifacts uses: actions/upload-artifact@v4 @@ -154,6 +172,8 @@ jobs: - Hercules.NET.MDFe - Hercules.NET.CTe - Hercules.NET.Impressao.NFCe.QuestPdf + - Hercules.NET.DCe + - Hercules.NET.Impressao.DCe.QuestPdf files: | ${{ github.workspace }}/*.nupkg diff --git a/DCe.Dace.QuestPdf.Testes/DCe.Dace.QuestPdf.Testes.csproj b/DCe.Dace.QuestPdf.Testes/DCe.Dace.QuestPdf.Testes.csproj new file mode 100644 index 000000000..87a2fc740 --- /dev/null +++ b/DCe.Dace.QuestPdf.Testes/DCe.Dace.QuestPdf.Testes.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + false + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/DCe.Dace.QuestPdf.Testes/DaceDocumentTests.cs b/DCe.Dace.QuestPdf.Testes/DaceDocumentTests.cs new file mode 100644 index 000000000..672e90f79 --- /dev/null +++ b/DCe.Dace.QuestPdf.Testes/DaceDocumentTests.cs @@ -0,0 +1,258 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Xml.Linq; +using DCe.Classes.Retorno; +using DCe.Testes.Fixtures; +using DCe.Utils; +using DCe.Utils.Abstractions; +using DCe.Utils.Extensoes; +using DCe.Utils.Validacao; +using DFe.Classes.Flags; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; +using UglyToad.PdfPig; + +namespace DCe.Dace.QuestPdf.Testes; + +[TestClass] +public class DaceDocumentTests +{ + private static readonly XNamespace Ns = "http://www.portalfiscal.inf.br/dce"; + private static readonly XNamespace Ds = "http://www.w3.org/2000/09/xmldsig#"; + private static readonly Dictionary<(int, int), string> Documentos = new(); + + [ClassInitialize] + public static void Preparar(TestContext _) + { + QuestPDF.Settings.License = LicenseType.Community; + foreach (var ambiente in new[] { 1, 2 }) + foreach (var emissao in new[] { 1, 2 }) + Documentos[(ambiente, emissao)] = CriarProcDce(ambiente, emissao); + } + + [TestMethod] + public void Parse_ExtraiConteudoFiscalSemCalcularValores() + { + var xml = Documentos[(2, 1)]; + var dados = DaceXmlParser.Parse(xml); + var doc = XDocument.Parse(xml); + Assert.AreEqual(doc.Descendants(Ns + "chDCe").Single().Value, dados.ChaveAcesso); + Assert.AreEqual(1, dados.Numero); + Assert.AreEqual(1, dados.Serie); + Assert.AreEqual(new DateTimeOffset(2026, 8, 22, 9, 0, 0, TimeSpan.FromHours(-3)), dados.DataEmissao); + Assert.AreEqual("PETROLINA/PE", dados.Remetente.CidadeUf); + StringAssert.Contains(dados.Remetente.Endereco, "56300-000"); + Assert.AreEqual("RECIFE/PE", dados.Destinatario.CidadeUf); + Assert.AreEqual("Transportadora", dados.ParteAdicionalTipo); + Assert.AreEqual("99999999000191", dados.ParteAdicional!.Documento); + Assert.AreEqual("TRANSPORTADORA EXEMPLO LTDA", dados.ParteAdicional.Nome); + Assert.AreEqual(1, dados.Itens.Count); + Assert.AreEqual(210342m, dados.ValorTotal); + Assert.AreEqual(1m, dados.Itens[0].Quantidade); + Assert.AreEqual(210342m, dados.Itens[0].ValorUnitario); + Assert.AreEqual("INFORMAÇÕES DO DECLARANTE", dados.InformacoesComplementares); + Assert.AreEqual("INFORMAÇÕES DO FISCO", dados.InformacoesAdicionaisFisco); + Assert.AreEqual(doc.Descendants(Ns + "xObs1").Single().Value, dados.ObservacaoLegal1); + Assert.AreEqual(doc.Descendants(Ns + "xObs2").Single().Value, dados.ObservacaoLegal2); + Assert.AreEqual(doc.Descendants(Ns + "qrCodDCe").Single().Value, dados.UrlQrCode); + Assert.AreEqual(11, dados.ChaveFormatada.Split(' ').Length); + Assert.IsTrue(dados.ChaveFormatada.Split(' ').All(grupo => grupo.Length == 4)); + Assert.AreEqual(dados.ChaveAcesso, dados.ChaveFormatada.Replace(" ", "")); + // Mesmo com itens divergentes, o total vem exclusivamente do XML. + doc.Descendants(Ns + "vDC").Single().Value = "99.00"; + Assert.AreEqual(99m, DaceXmlParser.Parse(doc.ToString()).ValorTotal); + } + + [DataTestMethod] + [DataRow(DaceModelo.Completa, 1, 1)] + [DataRow(DaceModelo.Resumida, 1, 1)] + [DataRow(DaceModelo.Completa, 2, 1)] + [DataRow(DaceModelo.Resumida, 2, 1)] + [DataRow(DaceModelo.Completa, 1, 2)] + [DataRow(DaceModelo.Resumida, 1, 2)] + [DataRow(DaceModelo.Completa, 2, 2)] + [DataRow(DaceModelo.Resumida, 2, 2)] + public void GerarPdf_ConteudoFiscalAmbienteEContingencia(DaceModelo modelo, int ambiente, int emissao) + { + var xml = Documentos[(ambiente, emissao)]; + var dados = DaceXmlParser.Parse(xml); + var bytes = new DaceDocument(xml, modelo).GeneratePdf(); + using var pdf = PdfDocument.Open(bytes); + var pagina = pdf.GetPage(1); + var texto = pagina.Text; + Assert.AreEqual(1, pdf.NumberOfPages); + Assert.AreEqual(595.28, (double)pagina.Width, 1); + Assert.AreEqual(841.89, (double)pagina.Height, 1); + Assert.AreEqual(ambiente == 2, texto.Contains("AMBIENTE DE HOMOLOGAÇÃO")); + Assert.AreEqual(emissao == 2, texto.Contains("EMITIDA EM CONTINGÊNCIA")); + Assert.AreEqual(emissao == 1, texto.Contains("1262600000000001")); + Assert.AreEqual(modelo == DaceModelo.Completa, texto.Contains(dados.Itens[0].Descricao)); + foreach (var valor in new[] { dados.Remetente.Nome, dados.Remetente.Endereco, + dados.Destinatario.Endereco, dados.ParteAdicional!.Nome, + dados.InformacoesComplementares!, dados.InformacoesAdicionaisFisco!, + dados.ObservacaoLegal1, dados.ObservacaoLegal2, dados.ChaveFormatada }) + StringAssert.Contains(SemEspacos(texto), SemEspacos(valor)); + Assert.AreEqual(2, pagina.NumberOfImages); // QR do XML e CODE-128 da chave. + // Exportação opcional das próprias fixtures verificadas, fora do repositório. + var pasta = Environment.GetEnvironmentVariable("DACE_REFERENCE_OUTPUT"); + if (!string.IsNullOrWhiteSpace(pasta)) + { + Directory.CreateDirectory(pasta); + var nome = $"dace-{modelo.ToString().ToLowerInvariant()}-{(ambiente == 1 ? "producao" : "homologacao")}{(emissao == 2 ? "-contingencia" : "")}"; + File.WriteAllBytes(Path.Combine(pasta, nome + ".pdf"), bytes); + File.WriteAllText(Path.Combine(pasta, nome + ".xml"), xml); + } + } + + [DataTestMethod] + [DataRow(DaceModelo.Completa)] + [DataRow(DaceModelo.Resumida)] + public void SemQrNoXml_NaoInventaUrlNemImagem(DaceModelo modelo) + { + var doc = XDocument.Parse(Documentos[(2, 1)]); + doc.Descendants(Ns + "infDCeSupl").Remove(); + var dados = DaceXmlParser.Parse(doc.ToString()); + Assert.IsNull(dados.UrlQrCode); + using var pdf = PdfDocument.Open(new DaceDocument(dados, modelo).GeneratePdf()); + Assert.AreEqual(1, pdf.GetPage(1).NumberOfImages); + } + + [DataTestMethod] + [DataRow("Fisco", "xOrgao")] + [DataRow("Marketplace", "xNome")] + [DataRow("Transportadora", "xNome")] + [DataRow("ECT", "xNome")] + public void Parse_IdentificaTodasAsPartesAdicionais(string tipo, string nome) + { + var doc = XDocument.Parse(Documentos[(2, 1)]); + var parte = doc.Descendants(Ns + "Transportadora").Single(); + parte.Name = Ns + tipo; + parte.Element(Ns + "xNome")!.Name = Ns + nome; + if (tipo == "Fisco") parte.Add(new XElement(Ns + "UF", "PE")); + if (tipo == "Marketplace") parte.Add(new XElement(Ns + "Site", "https://marketplace.exemplo.test")); + Validador.Valida(doc.ToString(), "dce", "procDCe_v1.00.xsd"); + var dados = DaceXmlParser.Parse(doc.ToString()); + Assert.AreEqual(tipo, dados.ParteAdicionalTipo); + Assert.AreEqual("TRANSPORTADORA EXEMPLO LTDA", dados.ParteAdicional!.Nome); + } + + [TestMethod] + public void Parse_ExigeDocumentoProcessadoENamespaceCorreto() + { + var dce = XDocument.Parse(Documentos[(2, 1)]).Root!.Element(Ns + "DCe")!; + Assert.ThrowsException(() => DaceXmlParser.Parse(dce.ToString())); + Assert.ThrowsException(() => DaceXmlParser.Parse("")); + Assert.ThrowsException(() => DaceXmlParser.Parse(" ")); + } + + [TestMethod] + public void Parse_QrPreservadoLiteralmenteEChaveDoIdQuandoSemProtocolo() + { + var doc = XDocument.Parse(Documentos[(2, 1)]); + var chave = doc.Descendants(Ns + "chDCe").Single().Value; + doc.Root!.Element(Ns + "protDCe")!.Remove(); + const string url = "https://consulta.exemplo.test/dce?parametro=original&tpAmb=2"; + doc.Descendants(Ns + "qrCodDCe").Single().Value = url; + var dados = DaceXmlParser.Parse(doc.ToString()); + Assert.AreEqual(url, dados.UrlQrCode); + Assert.AreEqual(chave, dados.ChaveAcesso); + Assert.IsNull(dados.Protocolo); + } + + [DataTestMethod] + [DataRow("dhEmi")] + [DataRow("nDC")] + [DataRow("vDC")] + public void Parse_CampoObrigatorioAusenteNaoProduzValorInventado(string campo) + { + var doc = XDocument.Parse(Documentos[(2, 1)]); + doc.Descendants(Ns + campo).Single().Remove(); + Assert.ThrowsException(() => DaceXmlParser.Parse(doc.ToString())); + } + + private static string SemEspacos(string valor) => new(valor.Where(c => !char.IsWhiteSpace(c)).ToArray()); + + [TestMethod] + public void Parse_ModeloProcessadoPreservaDadosDaFachadaXml() + { + var xml = Documentos[(2, 1)]; + var processado = new procDCe().CarregarDeXmlString(xml); + var porXml = DaceXmlParser.Parse(xml); + var porModelo = DaceXmlParser.Parse(processado); + Assert.AreEqual(porXml.ChaveAcesso, porModelo.ChaveAcesso); + Assert.AreEqual(porXml.DataEmissao, porModelo.DataEmissao); + Assert.AreEqual(porXml.Remetente, porModelo.Remetente); + Assert.AreEqual(porXml.Destinatario, porModelo.Destinatario); + Assert.AreEqual(porXml.ValorTotal, porModelo.ValorTotal); + Assert.AreEqual(porXml.Protocolo, porModelo.Protocolo); + Assert.AreEqual(porXml.UrlQrCode, porModelo.UrlQrCode); + CollectionAssert.AreEqual(porXml.Itens.ToArray(), porModelo.Itens.ToArray()); + Assert.ThrowsException(() => DaceXmlParser.Parse((procDCe)null!)); + } + + [DataTestMethod] + [DataRow("infDec")] + [DataRow("xObs1")] + [DataRow("xObs2")] + [DataRow("transp")] + [DataRow("modTrans")] + public void Parse_CampoAusenteNaoImprimePadraoDoModelo(string campo) + { + var doc = XDocument.Parse(Documentos[(2, 1)]); + doc.Descendants(Ns + campo).Single().Remove(); + var dados = DaceXmlParser.Parse(doc.ToString()); + if (campo is "infDec" or "xObs1") Assert.AreEqual(string.Empty, dados.ObservacaoLegal1); + if (campo is "infDec" or "xObs2") Assert.AreEqual(string.Empty, dados.ObservacaoLegal2); + if (campo is "transp" or "modTrans") Assert.AreEqual(string.Empty, dados.ModalidadeTransporte); + } + + [DataTestMethod] + [DataRow("emit", "enderEmit")] + [DataRow("dest", "enderDest")] + public void Parse_UfAusenteNaoImprimePrimeiraUfDoEnum(string pessoa, string endereco) + { + var doc = XDocument.Parse(Documentos[(2, 1)]); + doc.Descendants(Ns + pessoa).Single().Element(Ns + endereco)!.Element(Ns + "UF")!.Remove(); + var dados = DaceXmlParser.Parse(doc.ToString()); + var impressa = pessoa == "emit" ? dados.Remetente : dados.Destinatario; + Assert.IsTrue(impressa.CidadeUf.EndsWith("/", StringComparison.Ordinal)); + Assert.IsFalse(impressa.CidadeUf.EndsWith("/AC", StringComparison.Ordinal)); + } + + private static string CriarProcDce(int ambiente, int emissao) + { + var declaracao = DceFixture.Padrao(); + declaracao.Ambiente = (TipoAmbiente)ambiente; + declaracao.TipoEmissao = emissao; + declaracao.VersaoAplicativo = "ZeusFiscal 1.0"; + declaracao.InformacoesComplementares = "INFORMAÇÕES DO DECLARANTE"; + var dce = XElement.Parse(new FabricaModeloDCe(new Relogio(), new Codigo()).Criar(declaracao).ObterXmlString()); + dce.Descendants(Ns + "infAdic").Single().AddFirst(new XElement(Ns + "infAdFisco", "INFORMAÇÕES DO FISCO")); + using var rsa = RSA.Create(2048); + var pedido = new CertificateRequest("CN=Teste DACE", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + using var certificado = pedido.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1)); + dce = XElement.Parse(DceAssinatura.Assinar(dce.ToString(SaveOptions.DisableFormatting), certificado)); + var chave = dce.Element(Ns + "infDCe")!.Attribute("Id")!.Value[3..]; + var protocolo = new XElement(Ns + "protDCe", new XAttribute("versao", "1.00"), + new XElement(Ns + "infProt", + new XElement(Ns + "tpAmb", ambiente), new XElement(Ns + "verAplic", "ZeusFiscal 1.0"), + new XElement(Ns + "chDCe", chave), new XElement(Ns + "dhRecbto", "2026-08-22T09:00:01-03:00"), + new XElement(Ns + "nProt", "1262600000000001"), + new XElement(Ns + "digVal", dce.Descendants(Ds + "DigestValue").Single().Value), + new XElement(Ns + "cStat", "100"), new XElement(Ns + "xMotivo", "Autorizado o uso da DC-e"))); + var xml = new XElement(Ns + "dceProc", new XAttribute("versao", "1.00"), dce, protocolo).ToString(SaveOptions.DisableFormatting); + Validador.Valida(xml, "dce", "procDCe_v1.00.xsd"); + return xml; + } + + private sealed class Relogio : IClock + { + public DateTime UtcNow => new(2026, 8, 22, 12, 0, 0, DateTimeKind.Utc); + } + + private sealed class Codigo : IRandomSource + { + public int ProximoCodigoDocumento() => 123456; + } +} diff --git a/DCe.Dace.QuestPdf/DCe.Dace.QuestPdf.csproj b/DCe.Dace.QuestPdf/DCe.Dace.QuestPdf.csproj new file mode 100644 index 000000000..9ef5253a2 --- /dev/null +++ b/DCe.Dace.QuestPdf/DCe.Dace.QuestPdf.csproj @@ -0,0 +1,18 @@ + + + + net8.0;net10.0 + enable + enable + + + + + + + + + + + + diff --git a/DCe.Dace.QuestPdf/DaceDocument.cs b/DCe.Dace.QuestPdf/DaceDocument.cs new file mode 100644 index 000000000..377bf4b81 --- /dev/null +++ b/DCe.Dace.QuestPdf/DaceDocument.cs @@ -0,0 +1,213 @@ +using System.Globalization; +using DCe.Classes.Retorno; +using BarcodeStandard; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using SkiaSharp; +using SkiaSharp.QrCode.Image; + +namespace DCe.Dace.QuestPdf; + +/// Modelo de impressão previsto pelo Anexo II da DC-e. +public enum DaceModelo { Completa, Resumida } + +/// +/// Gera a Declaração Auxiliar de Conteúdo eletrônica a partir de um procDCe autorizado. +/// A versão resumida omite apenas a relação de itens; nenhuma informação é calculada fora do XML. +/// +public sealed class DaceDocument : IDocument +{ + private static readonly CultureInfo Brasil = CultureInfo.GetCultureInfo("pt-BR"); + private readonly DaceViewModel _dados; + private readonly DaceModelo _modelo; + + public DaceDocument(string procDCeXml, DaceModelo modelo = DaceModelo.Completa) + : this(DaceXmlParser.Parse(procDCeXml), modelo) { } + + public DaceDocument(procDCe processado, DaceModelo modelo = DaceModelo.Completa) + : this(DaceXmlParser.Parse(processado), modelo) { } + + public DaceDocument(DaceViewModel dados, DaceModelo modelo = DaceModelo.Completa) + { + _dados = dados ?? throw new ArgumentNullException(nameof(dados)); + _modelo = modelo; + } + + public DocumentMetadata GetMetadata() => new() + { + Title = "DACE " + _dados.ChaveAcesso, + Author = "ZeusFiscal", + Subject = "Declaração Auxiliar de Conteúdo eletrônica" + }; + + public DocumentSettings GetSettings() => DocumentSettings.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(1, Unit.Centimetre); + page.DefaultTextStyle(style => style.FontFamily("Arial").FontSize(8)); + page.Content().Column(column => + { + column.Item().Element(Cabecalho); + column.Item().PaddingTop(4).Element(Identificacao); + column.Item().PaddingTop(4).Element(RemDest); + if (_dados.ParteAdicional is not null) + column.Item().PaddingTop(4).Element(ParteAdicional); + if (_modelo == DaceModelo.Completa) + column.Item().PaddingTop(4).Element(Itens); + column.Item().PaddingTop(4).Element(DadosAdicionais); + column.Item().PaddingTop(8).Element(Rodape); + if (_modelo == DaceModelo.Resumida) + column.Item().PaddingTop(4).Element(Chave); + }); + }); + } + + private void Cabecalho(IContainer container) + { + container.Border(0.5f).Row(row => + { + row.RelativeItem(_modelo == DaceModelo.Completa ? 0.42f : 1).Padding(5).AlignCenter().Column(column => + { + column.Item().Text("DACE").Bold().FontSize(16); + column.Item().Text("DECLARAÇÃO AUXILIAR DE CONTEÚDO ELETRÔNICA").Bold().FontSize(6); + column.Item().PaddingTop(3).Text("Nº " + _dados.Numero.ToString(@"000\.000\.000")).Bold(); + column.Item().Text("SÉRIE " + _dados.Serie.ToString("000")); + if (_dados.EmHomologacao) + { + column.Item().PaddingTop(3).Text("SEM VALOR FISCAL").FontColor(Colors.Red.Medium).Bold().FontSize(7); + column.Item().Text("AMBIENTE DE HOMOLOGAÇÃO").FontColor(Colors.Red.Medium).Bold().FontSize(7); + } + }); + if (_modelo == DaceModelo.Completa) + row.RelativeItem().Padding(5).Element(Chave); + }); + } + + private void Chave(IContainer container) => container.AlignCenter().Column(column => + { + column.Item().Height(35).Image(CodigoBarras()).FitArea(); + column.Item().PaddingTop(2).Text(_dados.ChaveFormatada).FontSize(9); + }); + + private void ParteAdicional(IContainer container) => container.Column(column => + { + column.Item().Element(c => Titulo(c, "IDENTIFICAÇÃO DO FISCO, MARKETPLACE, TRANSPORTADORA OU ECT")); + column.Item().Row(row => + { + row.RelativeItem(2).Element(c => Campo(c, "CNPJ (" + _dados.ParteAdicionalTipo + ")", _dados.ParteAdicional!.Documento)); + row.RelativeItem(5).Element(c => Campo(c, "NOME / ÓRGÃO", _dados.ParteAdicional!.Nome)); + }); + }); + + private void DadosAdicionais(IContainer container) => container.Column(column => + { + column.Item().Element(c => Titulo(c, "DADOS ADICIONAIS")); + column.Item().Row(row => + { + row.RelativeItem().Element(c => Campo(c, "INFORMAÇÕES COMPLEMENTARES", _dados.InformacoesComplementares ?? string.Empty)); + row.RelativeItem().Element(c => Campo(c, "INFORMAÇÕES ADICIONAIS DO FISCO", _dados.InformacoesAdicionaisFisco ?? string.Empty)); + }); + }); + + private void Identificacao(IContainer container) + { + container.Column(column => + { + column.Item().Row(row => + { + row.RelativeItem().Element(c => Campo(c, "DATA DE EMISSÃO", _dados.DataEmissao.ToString("dd/MM/yyyy HH:mm:ss", Brasil))); + row.RelativeItem().Element(c => Campo(c, "MODALIDADE DO TRANSPORTE", _dados.ModalidadeTransporte)); + }); + column.Item().Element(c => Campo(c, "PROTOCOLO DE AUTORIZAÇÃO", _dados.CampoProtocolo)); + }); + } + + private void RemDest(IContainer container) + { + container.Column(column => + { + column.Item().Element(c => Pessoa(c, "REMETENTE (USUÁRIO EMITENTE)", _dados.Remetente)); + column.Item().PaddingTop(4).Element(c => Pessoa(c, "DESTINATÁRIO", _dados.Destinatario)); + }); + } + + private void Pessoa(IContainer container, string titulo, DacePessoa pessoa) + { + container.Column(column => + { + column.Item().Element(c => Titulo(c, "IDENTIFICAÇÃO DO " + titulo)); + column.Item().Row(row => + { + row.RelativeItem(2).Element(c => Campo(c, "CNPJ/CPF", pessoa.Documento)); + row.RelativeItem(5).Element(c => Campo(c, "NOME", pessoa.Nome)); + }); + column.Item().Row(row => + { + row.RelativeItem(2).Element(c => Campo(c, "CIDADE-UF", pessoa.CidadeUf)); + row.RelativeItem(5).Element(c => Campo(c, "ENDEREÇO", pessoa.Endereco)); + }); + }); + } + + private void Itens(IContainer container) + { + container.Column(column => + { + column.Item().Element(c => Titulo(c, "IDENTIFICAÇÃO DOS BENS OU MERCADORIAS")); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => { columns.ConstantColumn(28); columns.RelativeColumn(5); columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); }); + table.Header(header => { Header(header.Cell(), "ITEM"); Header(header.Cell(), "DESCRIÇÃO"); Header(header.Cell(), "QUANTIDADE"); Header(header.Cell(), "VALOR UNIT."); Header(header.Cell(), "VALOR TOTAL"); }); + foreach (var item in _dados.Itens) + { + Cell(table.Cell(), item.Numero.ToString()); Cell(table.Cell(), item.Descricao); Cell(table.Cell(), item.Quantidade.ToString("N4", Brasil)); + Cell(table.Cell(), item.ValorUnitario.ToString("C2", Brasil)); Cell(table.Cell(), item.ValorTotal.ToString("C2", Brasil)); + } + }); + column.Item().AlignRight().Border(0.5f).Padding(3).Text("VALOR TOTAL: " + _dados.ValorTotal.ToString("C2", Brasil)).Bold(); + }); + } + + private void Rodape(IContainer container) + { + container.Border(0.5f).Column(column => + { + column.Item().Row(row => { row.RelativeItem().Element(c => Titulo(c, "QR-CODE")); row.RelativeItem(2).Element(c => Titulo(c, "OBSERVAÇÕES")); }); + column.Item().Row(row => + { + row.RelativeItem().Padding(4).AlignCenter().Height(90).Element(c => + { + if (!string.IsNullOrWhiteSpace(_dados.UrlQrCode)) + c.Image(QrCode()).FitArea(); + }); + row.RelativeItem(2).Padding(4).Column(obs => { obs.Item().Text(_dados.ObservacaoLegal1).FontSize(6.5f); obs.Item().PaddingTop(2).Text(_dados.ObservacaoLegal2).FontSize(6.5f); }); + }); + }); + } + + private static void Campo(IContainer container, string titulo, string valor) => container.Border(0.5f).Padding(3).Column(column => { column.Item().Text(titulo).FontSize(6).Bold(); column.Item().Text(valor); }); + private static void Titulo(IContainer container, string valor) => container.Border(0.5f).Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text(valor).FontSize(7).Bold(); + private static void Header(IContainer container, string valor) => container.Border(0.5f).Background(Colors.Grey.Lighten3).Padding(2).Text(valor).FontSize(7).Bold(); + private static void Cell(IContainer container, string valor) => container.Border(0.5f).Padding(2).Text(valor).FontSize(7); + + private byte[] QrCode() + { + using var stream = new MemoryStream(); + new QrCode(_dados.UrlQrCode!, new Vector2Slim(256, 256), SKEncodedImageFormat.Png).GenerateImage(stream); + return stream.ToArray(); + } + + private byte[] CodigoBarras() + { + var barcode = new Barcode(); + using var image = barcode.Encode(BarcodeStandard.Type.Code128, _dados.ChaveAcesso, 1200, 140); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + return data.ToArray(); + } + +} diff --git a/DCe.Dace.QuestPdf/DaceViewModel.cs b/DCe.Dace.QuestPdf/DaceViewModel.cs new file mode 100644 index 000000000..2d23cd3e5 --- /dev/null +++ b/DCe.Dace.QuestPdf/DaceViewModel.cs @@ -0,0 +1,179 @@ +using System.Globalization; +using System.Xml; +using DCe.Classes.Informacoes; +using DCe.Classes.Retorno; +using DCe.Utils.Extensoes; + +namespace DCe.Dace.QuestPdf; + +/// Dados de uma pessoa exatamente como aparecem no XML autorizado. +public sealed record DacePessoa(string Documento, string Nome, string Endereco, string CidadeUf); + +/// Um bem ou mercadoria declarado em det/prod. +public sealed record DaceItem(int Numero, string Descricao, decimal Quantidade, decimal ValorUnitario, decimal ValorTotal); + +/// +/// Modelo independente do desenho da DACE. A DACE deve ser criada de um procDCe para que +/// todo conteúdo impresso tenha origem no documento fiscal autorizado. +/// +public sealed record DaceViewModel( + string ChaveAcesso, + int Numero, + int Serie, + DateTimeOffset DataEmissao, + string ModalidadeTransporte, + int TipoEmissao, + string Ambiente, + string? Protocolo, + DacePessoa Remetente, + DacePessoa Destinatario, + IReadOnlyList Itens, + decimal ValorTotal, + string? InformacoesComplementares, + string ObservacaoLegal1, + string ObservacaoLegal2, + string? UrlQrCode) +{ + public string? ParteAdicionalTipo { get; init; } + public DacePessoa? ParteAdicional { get; init; } + public string? InformacoesAdicionaisFisco { get; init; } + public string ChaveFormatada => string.Join(" ", Enumerable.Range(0, 11).Select(i => ChaveAcesso.Substring(i * 4, 4))); + public bool EmHomologacao => Ambiente == "2"; + public string CampoProtocolo => TipoEmissao == 2 ? "EMITIDA EM CONTINGÊNCIA" : Protocolo ?? string.Empty; + public DaceViewModel SemItens() => this with { Itens = Array.Empty() }; +} + +/// Converte o XML processado da DC-e no modelo que a DACE pode imprimir. +public static class DaceXmlParser +{ + public static DaceViewModel Parse(string procDCeXml) + { + if (string.IsNullOrWhiteSpace(procDCeXml)) + throw new ArgumentException("XML do procDCe vazio.", nameof(procDCeXml)); + + // A verificação da raiz mantém o contrato da fachada; os dados fiscais são + // desserializados exclusivamente pelo modelo XML compartilhado da DC-e. + using (var reader = XmlReader.Create(new StringReader(procDCeXml))) + { + reader.MoveToContent(); + if (reader.LocalName != "dceProc" || reader.NamespaceURI != "http://www.portalfiscal.inf.br/dce") + throw new InvalidOperationException("Esperado dceProc no namespace da DC-e."); + } + + return Parse(new procDCe().CarregarDeXmlString(procDCeXml)); + } + + public static DaceViewModel Parse(procDCe processado) + { + if (processado is null) throw new ArgumentNullException(nameof(processado)); + var dce = processado.DCe; + var infDce = dce?.infDCe; + if (dce is null || !dce.infDCeSpecified || infDce is null) + throw new InvalidOperationException("Nó infDCe não encontrado no procDCe."); + if (!infDce.ideSpecified || infDce.ide is null) + throw new InvalidOperationException("Nó ide não encontrado no procDCe."); + var ide = infDce.ide; + var protocolo = processado.protDCe?.infProt; + var chave = protocolo?.chDCe; + if (string.IsNullOrWhiteSpace(chave)) + { + var id = infDce.Id ?? string.Empty; + chave = id.StartsWith("DCe", StringComparison.OrdinalIgnoreCase) ? id.Substring(3) : id; + } + if (chave.Length != 44 || !chave.All(c => c >= '0' && c <= '9')) + throw new InvalidOperationException("Chave de acesso ausente ou inválida no procDCe."); + + var ambiente = protocolo is not null && protocolo.tpAmbSpecified + ? ((int)protocolo.tpAmb).ToString(CultureInfo.InvariantCulture) + : ide.tpAmbSpecified ? ((int)ide.tpAmb).ToString(CultureInfo.InvariantCulture) + : throw new InvalidOperationException("Ambiente ausente no procDCe."); + var qrCode = dce.infDCeSupl?.qrCodDCe; + var parte = ParseParteAdicional(infDce); + + return new DaceViewModel( + chave, + Obrigatorio(ide.nDC, ide.nDCSpecified, "Inteiro"), + Obrigatorio(ide.serie, ide.serieSpecified, "Inteiro"), + Obrigatorio(ide.dhEmi, ide.dhEmiProxySpecified, "Data"), + Modalidade(!infDce.transpSpecified || infDce.transp?.modTransSpecified != true ? null : ((int)infDce.transp.modTrans).ToString(CultureInfo.InvariantCulture)), + Obrigatorio((int)ide.tpEmis, ide.tpEmisSpecified, "Inteiro"), + ambiente, + protocolo?.nProt, + infDce.emitSpecified ? ParsePessoa(infDce.emit) : PessoaVazia(), + infDce.destSpecified ? ParsePessoa(infDce.dest) : PessoaVazia(), + infDce.det.Select(ParseItem).ToList(), + Obrigatorio(infDce.total.vDC, infDce.total.vDCProxySpecified, "Decimal"), + infDce.infAdic?.infCpl, + infDce.infDecSpecified && infDce.infDec?.xObs1Specified == true ? infDce.infDec.xObs1 : string.Empty, + infDce.infDecSpecified && infDce.infDec?.xObs2Specified == true ? infDce.infDec.xObs2 : string.Empty, + string.IsNullOrWhiteSpace(qrCode) ? null : qrCode) + { + ParteAdicionalTipo = parte.Tipo, + ParteAdicional = parte.Pessoa, + InformacoesAdicionaisFisco = infDce.infAdic?.infAdFisco + }; + } + + private static (string? Tipo, DacePessoa? Pessoa) ParseParteAdicional(infDCe documento) + { + if (documento.Fisco is { } fisco) + return ("Fisco", Parte(fisco.CNPJ, fisco.xOrgao)); + if (documento.Marketplace is { } marketplace) + return ("Marketplace", Parte(marketplace.CNPJ, marketplace.xNome)); + if (documento.Transportadora is { } transportadora) + return ("Transportadora", Parte(transportadora.CNPJ, transportadora.xNome)); + if (documento.ECT is { } ect) + return ("ECT", Parte(ect.CNPJ, ect.xNome)); + return (null, null); + } + + private static DacePessoa Parte(string documento, string nome) => new(documento, nome, string.Empty, string.Empty); + private static DacePessoa PessoaVazia() => Parte(string.Empty, string.Empty); + + private static DaceItem ParseItem(DCeDet item) => new( + Obrigatorio(item.nItem, item.nItemSpecified, "Inteiro"), + item.prod.xProd, + Obrigatorio(item.prod.qCom, item.prod.qComProxySpecified, "Decimal"), + Obrigatorio(item.prod.vUnCom, item.prod.vUnComProxySpecified, "Decimal"), + Obrigatorio(item.prod.vProd, item.prod.vProdProxySpecified, "Decimal")); + + private static DacePessoa ParsePessoa(DCeEmit? pessoa) + { + if (pessoa is null) return PessoaVazia(); + var endereco = pessoa.enderEmit; + return Pessoa(pessoa.CNPJ ?? pessoa.CPF ?? pessoa.idOutros ?? string.Empty, pessoa.xNome, + endereco?.xLgr, endereco?.nro, endereco?.xCpl, endereco?.xBairro, endereco?.CEP, + endereco?.xMun, endereco?.UFSpecified == true ? endereco.UF.ToString() : null); + } + + private static DacePessoa ParsePessoa(DCeDest? pessoa) + { + if (pessoa is null) return PessoaVazia(); + var endereco = pessoa.enderDest; + return Pessoa(pessoa.CNPJ ?? pessoa.CPF ?? pessoa.idOutros ?? string.Empty, pessoa.xNome, + endereco?.xLgr, endereco?.nro, endereco?.xCpl, endereco?.xBairro, endereco?.CEP, + endereco?.xMun, endereco?.UFSpecified == true ? endereco.UF.ToString() : null); + } + + private static DacePessoa Pessoa(string documento, string nome, string? logradouro, string? numero, + string? complemento, string? bairro, string? cep, string? cidade, string? uf) + { + var endereco = (logradouro ?? string.Empty) + ", " + (numero ?? string.Empty) + + (string.IsNullOrWhiteSpace(complemento) ? string.Empty : " - " + complemento) + + (string.IsNullOrWhiteSpace(bairro) ? string.Empty : " - " + bairro) + + (cep is { Length: > 0 } ? " - " + (cep.Length == 8 ? cep.Insert(5, "-") : cep) : string.Empty); + return new DacePessoa(documento, nome, endereco, (cidade ?? string.Empty) + "/" + (uf ?? string.Empty)); + } + + private static T Obrigatorio(T valor, bool presente, string tipo) => presente ? valor : + throw new InvalidOperationException(tipo == "Data" ? "Data obrigatória ausente ou inválida no procDCe." : + tipo + " obrigatório ausente ou inválido no procDCe."); + + private static string Modalidade(string? codigo) => codigo switch + { + "0" => "0 - TRANSPORTE PELOS CORREIOS", + "1" => "1 - TRANSPORTE POR CONTA PRÓPRIA", + "2" => "2 - TRANSPORTE POR EMPRESA TRANSPORTADORA", + _ => codigo ?? string.Empty + }; +} diff --git a/NuGet/Hercules.NET.DCe/Hercules.NET.DCe.csproj b/NuGet/Hercules.NET.DCe/Hercules.NET.DCe.csproj new file mode 100644 index 000000000..e73b32693 --- /dev/null +++ b/NuGet/Hercules.NET.DCe/Hercules.NET.DCe.csproj @@ -0,0 +1,10 @@ + + + net462;netstandard2.0;net8.0;net10.0 + AnyCpu + Release + true + false + Hercules.NET.DCe.nuspec + + diff --git a/NuGet/Hercules.NET.DCe/Hercules.NET.DCe.nuspec b/NuGet/Hercules.NET.DCe/Hercules.NET.DCe.nuspec new file mode 100644 index 000000000..eec20ee62 --- /dev/null +++ b/NuGet/Hercules.NET.DCe/Hercules.NET.DCe.nuspec @@ -0,0 +1,63 @@ + + + + Hercules.NET.DCe + $version$ + Hercules NET DCe + Hercules.NET + Hercules.NET + https://github.com/Hercules-NET/ZeusFiscal + https://avatars.githubusercontent.com/u/165974473 + false + Biblioteca para geração e serviços da DC-e (modelo 99). + + Hercules.NET + pt-BR + df-e dfe dce dc-e + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.csproj b/NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.csproj new file mode 100644 index 000000000..126ac8c8f --- /dev/null +++ b/NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.csproj @@ -0,0 +1,10 @@ + + + net8.0;net10.0 + AnyCpu + Release + true + false + Hercules.NET.Impressao.DCe.QuestPdf.nuspec + + diff --git a/NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.nuspec b/NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.nuspec new file mode 100644 index 000000000..c5dfd4de4 --- /dev/null +++ b/NuGet/Hercules.NET.Impressao.DCe.QuestPdf/Hercules.NET.Impressao.DCe.QuestPdf.nuspec @@ -0,0 +1,40 @@ + + + + Hercules.NET.Impressao.DCe.QuestPdf + $version$ + Hercules NET Impressao DCe QuestPdf + Hercules.NET + Hercules.NET + https://github.com/Hercules-NET/ZeusFiscal + https://avatars.githubusercontent.com/u/165974473 + false + Biblioteca para impressão da DACE completa e resumida com QuestPDF. + + Hercules.NET + pt-BR + df-e dfe dce dc-e dace questpdf + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 6ac5bb103..4279a74a0 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Hercules-NET / ZeusFiscal -### A Principal Biblioteca em C# para Emissão e Impressão de NFe, NFCe, MDF-e e CT-e +### A Principal Biblioteca em C# para Emissão e Impressão de NFe, NFCe, MDF-e, CT-e e DC-e **![(Este é o Fork e Continuação do DFe.NET ("Zeus") SAIBA MAIS SOBRE A 'DECLARAÇÃO OFICIAL A COMUNIDADE "ZEUS"' NESSE LINK!)](https://github.com/Hercules-NET/ZeusFiscal/issues/1)** **Entre no nosso **Discord** https://discord.gg/EE4TGKAkkG** @@ -12,6 +12,8 @@ A biblioteca é desenvolvida em **C#** com o SDK do **.NET 10** (Visual Studio 2 | Escopos | Frameworks Suportados | | ------------------- | ------------------- | | NFe, NFCe, CTe, MDFe | .NET Framework 4.6.2+, .NetStandard 2.0, .NET 8, .NET 10 | +| DC-e (modelo 99) | .NET Framework 4.6.2+, .NET Standard 2.0, .NET 8 e .NET 10 | +| DACE completa e resumida (QuestPDF) | .NET 8 e .NET 10 | | Impressões com FastReport OpenSource (NFe, NFCe, CTe, MDFe) | .NET Framework 4.6.2+, .NetStandard 2.0 (windows+linux), .NET 8 e .NET 10 (windows apenas) | | Impressões com FastReport (Versão PAGA) (NFe, NFCe, CTe, MDFe) | .NET Framework 4.6.2+ | | Impressões com FastReport.Skia (Versão PAGA SkiaSharp) (NFe, NFCe, CTe, MDFe) | .NET 8 e .NET 10 (windows+linux+mobile) | @@ -44,6 +46,15 @@ A melhor maneira de você ter a última versão do ZeusFiscal em seu projeto é [![Nuget count](https://img.shields.io/nuget/v/Hercules.NET.CTe.svg)](http://www.nuget.org/packages/Hercules.NET.CTe/) Hercules.NET.CTe + +[![Nuget downloads](https://img.shields.io/nuget/dt/Hercules.NET.DCe.svg)](http://www.nuget.org/packages/Hercules.NET.DCe/) +[![Nuget count](https://img.shields.io/nuget/v/Hercules.NET.DCe.svg)](http://www.nuget.org/packages/Hercules.NET.DCe/) + Hercules.NET.DCe + +[![Nuget downloads](https://img.shields.io/nuget/dt/Hercules.NET.Impressao.DCe.QuestPdf.svg)](http://www.nuget.org/packages/Hercules.NET.Impressao.DCe.QuestPdf/) +[![Nuget count](https://img.shields.io/nuget/v/Hercules.NET.Impressao.DCe.QuestPdf.svg)](http://www.nuget.org/packages/Hercules.NET.Impressao.DCe.QuestPdf/) + Hercules.NET.Impressao.DCe.QuestPdf + ## O que a biblioteca faz: O projeto traz classes construídas de forma manual que extraem a complexidade dos XSDs. Com isso é possível preencher objetos nativos em .NET e gerar o XML na estrutura exigida para seu DFe, assim como o processo inverso de ler um XML de um DFe e obter objetos nativos em .NET. diff --git a/ZeusFiscal.sln b/ZeusFiscal.sln index 2403709ec..6d7e87895 100644 --- a/ZeusFiscal.sln +++ b/ZeusFiscal.sln @@ -136,6 +136,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Testes", "DCe.Testes\DC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Servicos", "DCe.Servicos\DCe.Servicos.csproj", "{AB236674-B734-4783-ABA6-D94B8ADC51A7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Dace.QuestPdf", "DCe.Dace.QuestPdf\DCe.Dace.QuestPdf.csproj", "{C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Dace.QuestPdf.Testes", "DCe.Dace.QuestPdf.Testes\DCe.Dace.QuestPdf.Testes.csproj", "{494C6972-1B78-4519-9878-FCA3EE359FA4}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DCe.Wsdl", "DCe.Wsdl\DCe.Wsdl.csproj", "{FED02CAF-4511-4F14-AD3A-8BD85DAF45F8}" EndProject Global @@ -146,6 +150,22 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Debug|x86.Build.0 = Debug|Any CPU + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Release|Any CPU.Build.0 = Release|Any CPU + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Release|x86.ActiveCfg = Release|Any CPU + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF}.Release|x86.Build.0 = Release|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Debug|x86.Build.0 = Debug|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Release|Any CPU.Build.0 = Release|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Release|x86.ActiveCfg = Release|Any CPU + {494C6972-1B78-4519-9878-FCA3EE359FA4}.Release|x86.Build.0 = Release|Any CPU {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {AB236674-B734-4783-ABA6-D94B8ADC51A7}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -563,6 +583,8 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {C06C11A8-4FDE-4FE2-9A88-ED47F86572EF} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} + {494C6972-1B78-4519-9878-FCA3EE359FA4} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} {AB236674-B734-4783-ABA6-D94B8ADC51A7} = {F62DD288-5ECA-74B7-3843-0A2E69FB470A} {BB030128-9609-46D5-941C-1F53D95157E2} = {3960070E-93C9-4852-8A53-F4918D602782} {DD441D1C-1F4A-4631-AA72-015C4FA735D2} = {F981A8AC-E660-492B-8BF3-CBF70B66E392}