Skip to content

Commit

Permalink
Merge pull request #253 from stephanstapel/topic-tradeallowancecharge
Browse files Browse the repository at this point in the history
TradeAllowanceCharge corrections
  • Loading branch information
stephanstapel authored May 4, 2024
2 parents 0c6acce + ca903a4 commit 78aa304
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 79 deletions.
4 changes: 2 additions & 2 deletions ZUGFeRD-Test/ZUGFeRD20Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ public void TestWriteAndReadExtended()
Assert.AreEqual(timestamp.AddDays(14), loadedInvoice.BillingPeriodEnd);

//TradeAllowanceCharges
var tradeAllowanceCharge = loadedInvoice.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason for charge");
var tradeAllowanceCharge = loadedInvoice.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason for charge");
Assert.IsNotNull(tradeAllowanceCharge);
Assert.IsTrue(tradeAllowanceCharge.ChargeIndicator);
Assert.AreEqual("Reason for charge", tradeAllowanceCharge.Reason);
Expand Down Expand Up @@ -747,7 +747,7 @@ public void TestWriteAndReadExtended()
//Assert.AreEqual("987654", accountingAccount.TradeAccountID);


var lineItemTradeAllowanceCharge = loadedLineItem.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason: UnitTest");
var lineItemTradeAllowanceCharge = loadedLineItem.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason: UnitTest");
Assert.IsNotNull(lineItemTradeAllowanceCharge);
Assert.IsTrue(lineItemTradeAllowanceCharge.ChargeIndicator);
Assert.AreEqual(10m, lineItemTradeAllowanceCharge.BasisAmount);
Expand Down
61 changes: 53 additions & 8 deletions ZUGFeRD-Test/ZUGFeRD21Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,18 @@ public void TestReferenceExtendedInvoice()
Assert.AreEqual(desc.TradeLineItems.Count, 6);
Assert.AreEqual(desc.LineTotalAmount, 457.20m);

foreach (TradeAllowanceCharge charge in desc.TradeAllowanceCharges)
IList<TradeAllowanceCharge> _tradeAllowanceCharges = desc.GetTradeAllowanceCharges();
foreach (TradeAllowanceCharge charge in _tradeAllowanceCharges)
{
Assert.AreEqual(charge.Tax.TypeCode, TaxTypes.VAT);
Assert.AreEqual(charge.Tax.CategoryCode, TaxCategoryCodes.S);
}

Assert.AreEqual(desc.TradeAllowanceCharges.Count, 4);
Assert.AreEqual(desc.TradeAllowanceCharges[0].Tax.Percent, 19m);
Assert.AreEqual(desc.TradeAllowanceCharges[1].Tax.Percent, 7m);
Assert.AreEqual(desc.TradeAllowanceCharges[2].Tax.Percent, 19m);
Assert.AreEqual(desc.TradeAllowanceCharges[3].Tax.Percent, 7m);
Assert.AreEqual(_tradeAllowanceCharges.Count, 4);
Assert.AreEqual(_tradeAllowanceCharges[0].Tax.Percent, 19m);
Assert.AreEqual(_tradeAllowanceCharges[1].Tax.Percent, 7m);
Assert.AreEqual(_tradeAllowanceCharges[2].Tax.Percent, 19m);
Assert.AreEqual(_tradeAllowanceCharges[3].Tax.Percent, 7m);

Assert.AreEqual(desc.ServiceCharges.Count, 1);
Assert.AreEqual(desc.ServiceCharges[0].Tax.TypeCode, TaxTypes.VAT);
Expand Down Expand Up @@ -1510,7 +1511,7 @@ public void TestWriteAndReadExtended()
Assert.AreEqual(timestamp.AddDays(14), loadedInvoice.BillingPeriodEnd);

//TradeAllowanceCharges
var tradeAllowanceCharge = loadedInvoice.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason for charge");
var tradeAllowanceCharge = loadedInvoice.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason for charge");
Assert.IsNotNull(tradeAllowanceCharge);
Assert.IsTrue(tradeAllowanceCharge.ChargeIndicator);
Assert.AreEqual("Reason for charge", tradeAllowanceCharge.Reason);
Expand Down Expand Up @@ -1602,7 +1603,7 @@ public void TestWriteAndReadExtended()
Assert.AreEqual("987654", accountingAccount.TradeAccountID);


var lineItemTradeAllowanceCharge = loadedLineItem.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason: UnitTest");
var lineItemTradeAllowanceCharge = loadedLineItem.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason: UnitTest");
Assert.IsNotNull(lineItemTradeAllowanceCharge);
Assert.IsTrue(lineItemTradeAllowanceCharge.ChargeIndicator);
Assert.AreEqual(10m, lineItemTradeAllowanceCharge.BasisAmount);
Expand Down Expand Up @@ -1781,6 +1782,50 @@ public void TestBasisQuantityMultiple()
Assert.AreEqual("27.50", node.InnerText);
} // !TestBasisQuantityMultiple()


[TestMethod]
public void TestTradeAllowanceChargeWithoutExplicitPercentage()
{
InvoiceDescriptor invoice = InvoiceProvider.CreateInvoice();

// fake values, does not matter for our test case
invoice.AddTradeAllowanceCharge(true, 100, CurrencyCodes.EUR, 10, "", TaxTypes.VAT, TaxCategoryCodes.S, 19);

MemoryStream ms = new MemoryStream();
invoice.Save(ms, ZUGFeRDVersion.Version21, Profile.Extended);
ms.Position = 0;

InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms);
IList<TradeAllowanceCharge> allowanceCharges = loadedInvoice.GetTradeAllowanceCharges();

Assert.IsTrue(allowanceCharges.Count == 1);
Assert.AreEqual(allowanceCharges[0].BasisAmount, 100m);
Assert.AreEqual(allowanceCharges[0].Amount, 10m);
Assert.AreEqual(allowanceCharges[0].ChargePercentage, null);
} // !TestTradeAllowanceChargeWithoutExplicitPercentage()


[TestMethod]
public void TestTradeAllowanceChargeWithExplicitPercentage()
{
InvoiceDescriptor invoice = InvoiceProvider.CreateInvoice();

// fake values, does not matter for our test case
invoice.AddTradeAllowanceCharge(true, 100, CurrencyCodes.EUR, 10, 12, "", TaxTypes.VAT, TaxCategoryCodes.S, 19);

MemoryStream ms = new MemoryStream();
invoice.Save(ms, ZUGFeRDVersion.Version21, Profile.Extended);
ms.Position = 0;
InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms);
IList<TradeAllowanceCharge> allowanceCharges = loadedInvoice.GetTradeAllowanceCharges();

Assert.IsTrue(allowanceCharges.Count == 1);
Assert.AreEqual(allowanceCharges[0].BasisAmount, 100m);
Assert.AreEqual(allowanceCharges[0].Amount, 10m);
Assert.AreEqual(allowanceCharges[0].ChargePercentage, 12);
} // !TestTradeAllowanceChargeWithExplicitPercentage()


[TestMethod]
public void TestReferenceXRechnung21UBL()
{
Expand Down
2 changes: 1 addition & 1 deletion ZUGFeRD/IInvoiceDescriptorReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ protected static int _nodeAsInt(XmlNode node, string xpath, XmlNamespaceManager
/// <summary>
/// reads the value from given xpath and interprets the value as decimal
/// </summary>
protected static decimal? _nodeAsDecimal(XmlNode node, string xpath, XmlNamespaceManager nsmgr = null, decimal? defaultValue = null)
protected static decimal? _nodeAsDecimal(XmlNode node, string xpath, XmlNamespaceManager nsmgr = null, decimal? defaultValue = default(decimal?))
{
if (node == null)
{
Expand Down
55 changes: 51 additions & 4 deletions ZUGFeRD/InvoiceDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,10 @@ public class InvoiceDescriptor
public List<ServiceCharge> ServiceCharges { get; set; } = new List<ServiceCharge>();

/// <summary>
/// Detailed information on discounts and charges
/// Detailed information on discounts and charges.
/// This field is marked as private now, please use GetTradeAllowanceCharges() to retrieve all trade allowance charges
/// </summary>
public List<TradeAllowanceCharge> TradeAllowanceCharges { get; set; } = new List<TradeAllowanceCharge>();
private List<TradeAllowanceCharge> _TradeAllowanceCharges { get; set; } = new List<TradeAllowanceCharge>();

/// <summary>
/// Detailed information about payment terms
Expand Down Expand Up @@ -721,16 +722,17 @@ public void AddLogisticsServiceCharge(decimal amount, string description, TaxTyp
/// <param name="taxTypeCode">VAT type code for document level allowance/ charge</param>
/// <param name="taxCategoryCode">VAT type code for document level allowance/ charge</param>
/// <param name="taxPercent">VAT rate for the allowance</param>
public void AddTradeAllowanceCharge(bool isDiscount, decimal basisAmount, CurrencyCodes currency, decimal actualAmount, string reason, TaxTypes taxTypeCode, TaxCategoryCodes taxCategoryCode, decimal taxPercent)
public void AddTradeAllowanceCharge(bool isDiscount, decimal? basisAmount, CurrencyCodes currency, decimal actualAmount, string reason, TaxTypes taxTypeCode, TaxCategoryCodes taxCategoryCode, decimal taxPercent)
{
this.TradeAllowanceCharges.Add(new TradeAllowanceCharge()
this._TradeAllowanceCharges.Add(new TradeAllowanceCharge()
{
ChargeIndicator = !isDiscount,
Reason = reason,
BasisAmount = basisAmount,
ActualAmount = actualAmount,
Currency = currency,
Amount = actualAmount,
ChargePercentage = null,
Tax = new Tax()
{
CategoryCode = taxCategoryCode,
Expand All @@ -741,6 +743,51 @@ public void AddTradeAllowanceCharge(bool isDiscount, decimal basisAmount, Curren
} // !AddTradeAllowanceCharge()


/// <summary>
/// Adds an allowance or charge on document level.
///
/// Allowance represents a discount whereas charge represents a surcharge.
/// </summary>
/// <param name="isDiscount">Marks if the allowance charge is a discount. Please note that in contrary to this function, the xml file indicated a surcharge, not a discount (value will be inverted)</param>
/// <param name="basisAmount">Base amount (basis of allowance)</param>
/// <param name="currency">Curency of the allowance</param>
/// <param name="actualAmount">Actual allowance charge amount</param>
/// <param name="chargePercentage">Actual allowance charge percentage</param>
/// <param name="reason">Reason for the allowance</param>
/// <param name="taxTypeCode">VAT type code for document level allowance/ charge</param>
/// <param name="taxCategoryCode">VAT type code for document level allowance/ charge</param>
/// <param name="taxPercent">VAT rate for the allowance</param>
public void AddTradeAllowanceCharge(bool isDiscount, decimal? basisAmount, CurrencyCodes currency, decimal actualAmount, decimal? chargePercentage, string reason, TaxTypes taxTypeCode, TaxCategoryCodes taxCategoryCode, decimal taxPercent)
{
this._TradeAllowanceCharges.Add(new TradeAllowanceCharge()
{
ChargeIndicator = !isDiscount,
Reason = reason,
BasisAmount = basisAmount,
ActualAmount = actualAmount,
Currency = currency,
Amount = actualAmount,
ChargePercentage = chargePercentage,
Tax = new Tax()
{
CategoryCode = taxCategoryCode,
TypeCode = taxTypeCode,
Percent = taxPercent
}
});
} // !AddTradeAllowanceCharge()


/// <summary>
/// Returns all existing trade allowance charges
/// </summary>
/// <returns></returns>
public IList<TradeAllowanceCharge> GetTradeAllowanceCharges()
{
return this._TradeAllowanceCharges;
} // !GetTradeAllowanceCharges()


public void SetTradePaymentTerms(string description, DateTime? dueDate = null)
{
this.PaymentTerms = new PaymentTerms()
Expand Down
30 changes: 18 additions & 12 deletions ZUGFeRD/InvoiceDescriptor1Writer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,19 +303,22 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)

_writeOptionalTaxes(Writer);

if ((this.Descriptor.TradeAllowanceCharges != null) && (this.Descriptor.TradeAllowanceCharges.Count > 0))
if ((this.Descriptor.GetTradeAllowanceCharges() != null) && (this.Descriptor.GetTradeAllowanceCharges().Count > 0))
{
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.TradeAllowanceCharges)
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.GetTradeAllowanceCharges())
{
Writer.WriteStartElement("ram:SpecifiedTradeAllowanceCharge");
Writer.WriteStartElement("ram:ChargeIndicator", Profile.Comfort | Profile.Extended);
Writer.WriteElementString("udt:Indicator", tradeAllowanceCharge.ChargeIndicator ? "true" : "false");
Writer.WriteEndElement(); // !ram:ChargeIndicator

Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount));
Writer.WriteEndElement();
if (tradeAllowanceCharge.BasisAmount.HasValue)
{
Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value));
Writer.WriteEndElement();
}

Writer.WriteStartElement("ram:ActualAmount", Profile.Comfort | Profile.Extended);
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
Expand Down Expand Up @@ -461,18 +464,21 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)
_writeElementWithAttribute(Writer, "ram:BasisQuantity", "unitCode", tradeLineItem.UnitCode.EnumToString(), _formatDecimal(tradeLineItem.UnitQuantity.Value, 4));
}

foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.TradeAllowanceCharges)
foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.GetTradeAllowanceCharges())
{
Writer.WriteStartElement("ram:AppliedTradeAllowanceCharge");

Writer.WriteStartElement("ram:ChargeIndicator", Profile.Comfort | Profile.Extended);
Writer.WriteElementString("udt:Indicator", tradeAllowanceCharge.ChargeIndicator ? "true" : "false");
Writer.WriteEndElement(); // !ram:ChargeIndicator

Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount, 4));
Writer.WriteEndElement();
if (tradeAllowanceCharge.BasisAmount.HasValue)
{
Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value, 4));
Writer.WriteEndElement();
}
Writer.WriteStartElement("ram:ActualAmount", Profile.Comfort | Profile.Extended);
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.ActualAmount, 4));
Expand Down Expand Up @@ -636,7 +642,7 @@ internal override bool Validate(InvoiceDescriptor descriptor, bool throwExceptio

private void _writeOptionalAmount(ProfileAwareXmlTextWriter writer, string tagName, decimal? value, int numDecimals = 2)
{
if (value.HasValue && (value.Value != decimal.MinValue))
if (value.HasValue)
{
writer.WriteStartElement(tagName);
writer.WriteAttributeString("currencyID", this.Descriptor.Currency.EnumToString());
Expand Down
26 changes: 16 additions & 10 deletions ZUGFeRD/InvoiceDescriptor20Writer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,17 +232,20 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)
_writeElementWithAttribute(Writer, "ram:BasisQuantity", "unitCode", tradeLineItem.UnitCode.EnumToString(), _formatDecimal(tradeLineItem.UnitQuantity.Value, 4));
}

foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.TradeAllowanceCharges)
foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.GetTradeAllowanceCharges())
{
Writer.WriteStartElement("ram:AppliedTradeAllowanceCharge");

Writer.WriteStartElement("ram:ChargeIndicator");
Writer.WriteElementString("udt:Indicator", tradeAllowanceCharge.ChargeIndicator ? "true" : "false");
Writer.WriteEndElement(); // !ram:ChargeIndicator

Writer.WriteStartElement("ram:BasisAmount");
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount, 4));
Writer.WriteEndElement();
if (tradeAllowanceCharge.BasisAmount.HasValue)
{
Writer.WriteStartElement("ram:BasisAmount");
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value, 4));
Writer.WriteEndElement();
}
Writer.WriteStartElement("ram:ActualAmount");
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.ActualAmount, 4));
Writer.WriteEndElement();
Expand Down Expand Up @@ -648,18 +651,21 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)
#endregion

// 13. SpecifiedTradeAllowanceCharge (optional)
if ((this.Descriptor.TradeAllowanceCharges != null) && (this.Descriptor.TradeAllowanceCharges.Count > 0))
if ((this.Descriptor.GetTradeAllowanceCharges() != null) && (this.Descriptor.GetTradeAllowanceCharges().Count > 0))
{
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.TradeAllowanceCharges)
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.GetTradeAllowanceCharges())
{
Writer.WriteStartElement("ram:SpecifiedTradeAllowanceCharge");
Writer.WriteStartElement("ram:ChargeIndicator");
Writer.WriteElementString("udt:Indicator", tradeAllowanceCharge.ChargeIndicator ? "true" : "false");
Writer.WriteEndElement(); // !ram:ChargeIndicator

Writer.WriteStartElement("ram:BasisAmount");
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount));
Writer.WriteEndElement();
if (tradeAllowanceCharge.BasisAmount.HasValue)
{
Writer.WriteStartElement("ram:BasisAmount");
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value));
Writer.WriteEndElement();
}

Writer.WriteStartElement("ram:ActualAmount");
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.ActualAmount));
Expand Down Expand Up @@ -770,7 +776,7 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)

private void _writeOptionalAmount(ProfileAwareXmlTextWriter writer, string tagName, decimal? value, int numDecimals = 2, bool forceCurrency = false, Profile profile = Profile.Unknown)
{
if (value.HasValue && (value.Value != decimal.MinValue))
if (value.HasValue)
{
writer.WriteStartElement(tagName, profile);
if (forceCurrency)
Expand Down
Loading

0 comments on commit 78aa304

Please sign in to comment.