diff --git a/NFe.Classes/Informacoes/Detalhe/Tributacao/IS.cs b/NFe.Classes/Informacoes/Detalhe/Tributacao/IS.cs
index 763fbf60..fa6eb151 100644
--- a/NFe.Classes/Informacoes/Detalhe/Tributacao/IS.cs
+++ b/NFe.Classes/Informacoes/Detalhe/Tributacao/IS.cs
@@ -1,4 +1,6 @@
using System.Xml.Serialization;
+using System;
+using System.ComponentModel;
namespace NFe.Classes.Informacoes.Detalhe.Tributacao
{
@@ -6,7 +8,7 @@ public class IS
{
private decimal _vBcIs;
private decimal _pIs;
- private decimal? _pIsEspec;
+ private decimal? _adRemIS;
private decimal _qTrib;
private decimal _vIs;
private string _cClassTribIS;
@@ -39,32 +41,58 @@ public decimal pIS
set => _pIs = value.Arredondar(4);
}
- // UB07
+ ///
+ /// UB07 - Alíquota do Imposto Seletivo (por valor), conforme NT 2025.002 v1.40.
+ ///
[XmlElement(Order = 5)]
+ public decimal? adRemIS
+ {
+ get => _adRemIS.Arredondar(4);
+ set => _adRemIS = value.Arredondar(4);
+ }
+ public bool ShouldSerializeadRemIS() => adRemIS.HasValue;
+
+ ///
+ /// Nome anterior da alíquota específica, mantido para compatibilidade de código.
+ ///
+ [Obsolete("Utilize adRemIS (NT 2025.002 v1.40).")]
+ [XmlIgnore]
public decimal? pISEspec
{
- get => _pIsEspec.Arredondar(4);
- set => _pIsEspec = value.Arredondar(4);
+ get => adRemIS;
+ set => adRemIS = value;
}
- public bool ShouldSerializepISEspec()
+ public bool ShouldSerializepISEspec() => false;
+
+ ///
+ /// Lê a tag de XMLs antigos; a emissão utiliza exclusivamente adRemIS.
+ /// O XmlSerializer ignora membros Obsolete, por isso a leitura usa este membro separado.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [XmlElement("pISEspec", Order = 6)]
+ public decimal? pISEspecXml
{
- return pISEspec.HasValue;
+ get => adRemIS;
+ set => adRemIS = value;
}
+ public bool ShouldSerializepISEspecXml() => false;
// UB09
- [XmlElement(Order = 6)]
+ [XmlElement(Order = 7)]
public string uTrib { get; set; }
+ public bool ShouldSerializeuTrib() => !string.IsNullOrEmpty(uTrib);
// UB10
- [XmlElement(Order = 7)]
+ [XmlElement(Order = 8)]
public decimal qTrib
{
get => _qTrib.Arredondar(4);
set => _qTrib = value.Arredondar(4);
}
+ public bool ShouldSerializeqTrib() => ShouldSerializeuTrib();
// UB11
- [XmlElement(Order = 8)]
+ [XmlElement(Order = 9)]
public decimal vIS
{
get => _vIs.Arredondar(2);
@@ -88,4 +116,4 @@ public void SetCSTIS(int intValue)
CSTIS = intValue.ToString("D3");
}
}
-}
\ No newline at end of file
+}
diff --git a/NFe.Utils.Testes/ISTestes.cs b/NFe.Utils.Testes/ISTestes.cs
new file mode 100644
index 00000000..88640fd8
--- /dev/null
+++ b/NFe.Utils.Testes/ISTestes.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+using NFe.Classes.Informacoes.Detalhe.Tributacao;
+using Xunit;
+
+namespace NFe.Utils.Testes
+{
+ public class ISTestes
+ {
+ private const string NamespaceNFe = "http://www.portalfiscal.inf.br/nfe";
+ private static readonly XmlSerializer Serializer = new XmlSerializer(
+ typeof(IS), new XmlRootAttribute("IS") { Namespace = NamespaceNFe });
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void Serializar_AdValoremSemUnidade_ValidaNoXsd(string unidade)
+ {
+ var imposto = CriarIS();
+ imposto.uTrib = unidade;
+ var xml = Serializar(imposto);
+
+ AssertValido(xml);
+ Assert.Null(xml.Element(XName.Get("uTrib", NamespaceNFe)));
+ Assert.Null(xml.Element(XName.Get("qTrib", NamespaceNFe)));
+ Assert.Null(xml.Element(XName.Get("adRemIS", NamespaceNFe)));
+ }
+
+ [Fact]
+ public void Serializar_AliquotaEspecifica_ValidaNoXsd()
+ {
+ var imposto = CriarIS();
+ imposto.adRemIS = 1.0000m;
+ imposto.uTrib = "UN";
+ imposto.qTrib = 100m;
+
+ var xml = Serializar(imposto);
+
+ AssertValido(xml);
+ Assert.Equal(1m, (decimal)xml.Element(XName.Get("adRemIS", NamespaceNFe)));
+ Assert.Null(xml.Element(XName.Get("pISEspec", NamespaceNFe)));
+ Assert.Equal(new[] { "CSTIS", "cClassTribIS", "vBCIS", "pIS", "adRemIS", "uTrib", "qTrib", "vIS" },
+ xml.Elements().Select(element => element.Name.LocalName));
+ }
+
+ [Fact]
+ public void Desserializar_PISEspecAntigo_LeAliquotaEEmiteSomenteAdRemIS()
+ {
+ var xmlAntigo = XElement.Parse("" +
+ "00000000110001" +
+ "1.0000UN10010");
+
+ var imposto = Desserializar(xmlAntigo);
+
+ Assert.Equal(1m, imposto.adRemIS);
+ Assert.Equal("UN", imposto.uTrib);
+ Assert.Equal(100m, imposto.qTrib);
+ Assert.Equal(10m, imposto.vIS);
+ var xmlAtual = Serializar(imposto);
+ AssertValido(xmlAtual);
+ Assert.Null(xmlAtual.Element(XName.Get("pISEspec", NamespaceNFe)));
+ Assert.Equal(1m, (decimal)xmlAtual.Element(XName.Get("adRemIS", NamespaceNFe)));
+ }
+
+ [Fact]
+ public void SerializarEDesserializar_PreservaAdRemISEDemaisCampos()
+ {
+ var imposto = CriarIS();
+ imposto.adRemIS = 1.2345m;
+ imposto.uTrib = "UN";
+ imposto.qTrib = 100m;
+ var primeiroXml = Serializar(imposto);
+
+ var restaurado = Desserializar(primeiroXml);
+ var segundoXml = Serializar(restaurado);
+
+ Assert.Equal(1.2345m, restaurado.adRemIS);
+ Assert.True(XNode.DeepEquals(primeiroXml, segundoXml));
+ AssertValido(segundoXml);
+ }
+
+ [Theory]
+ [InlineData(1.23456, 1.2346)]
+ [InlineData(0, 0)]
+ public void AdRemIS_ArredondaQuatroCasasEEmiteZero(decimal valor, decimal esperado)
+ {
+ var imposto = CriarIS();
+ imposto.adRemIS = valor;
+
+ var xml = Serializar(imposto);
+
+ Assert.Equal(esperado, imposto.adRemIS);
+ Assert.Equal(esperado, (decimal)xml.Element(XName.Get("adRemIS", NamespaceNFe)));
+ AssertValido(xml);
+ }
+
+ [Fact]
+ public void PISEspec_AliasDeCodigo_CompartilhaValorComAdRemIS()
+ {
+ var imposto = CriarIS();
+ // A API obsoleta é exercitada intencionalmente para garantir compatibilidade de código.
+#pragma warning disable CS0618
+ imposto.pISEspec = 1.23456m;
+ Assert.Equal(1.2346m, imposto.adRemIS);
+ imposto.adRemIS = 2m;
+ Assert.Equal(2m, imposto.pISEspec);
+ imposto.pISEspec = null;
+#pragma warning restore CS0618
+ Assert.Null(imposto.adRemIS);
+
+ var xml = Serializar(imposto);
+ Assert.Null(xml.Element(XName.Get("pISEspec", NamespaceNFe)));
+ Assert.Null(xml.Element(XName.Get("adRemIS", NamespaceNFe)));
+ AssertValido(xml);
+ }
+
+ private static IS Desserializar(XElement xml)
+ {
+ using (var reader = xml.CreateReader())
+ return (IS)Serializer.Deserialize(reader);
+ }
+
+ private static void AssertValido(XElement xml)
+ {
+ var erros = Validar(xml);
+ Assert.True(erros.Count == 0, string.Join(Environment.NewLine, erros));
+ }
+
+ private static IS CriarIS()
+ {
+ return new IS { CSTIS = "000", cClassTribIS = "000001", vBCIS = 1000m, pIS = 1m, vIS = 10m };
+ }
+
+ private static XElement Serializar(IS imposto)
+ {
+ using (var writer = new StringWriter())
+ {
+ Serializer.Serialize(writer, imposto);
+ return XElement.Parse(writer.ToString());
+ }
+ }
+
+ private static List Validar(XElement xml)
+ {
+ // O wrapper declara somente a raiz; o contrato TIS vem do XSD da biblioteca.
+ var schema = new XmlSchema { TargetNamespace = NamespaceNFe, ElementFormDefault = XmlSchemaForm.Qualified };
+ schema.Includes.Add(new XmlSchemaInclude
+ {
+ SchemaLocation = Path.Combine(AppContext.BaseDirectory, "Schemas", "DFeTiposBasicos_v1.00.xsd")
+ });
+ schema.Items.Add(new XmlSchemaElement { Name = "IS", SchemaTypeName = new XmlQualifiedName("TIS", NamespaceNFe) });
+ var schemas = new XmlSchemaSet { XmlResolver = new XmlUrlResolver() };
+ schemas.Add(schema);
+ schemas.Compile();
+
+ var erros = new List();
+ new XDocument(xml).Validate(schemas, (sender, args) => erros.Add(args.Message));
+ return erros;
+ }
+ }
+}