Skip to content

Commit 78aa304

Browse files
Merge pull request #253 from stephanstapel/topic-tradeallowancecharge
TradeAllowanceCharge corrections
2 parents 0c6acce + ca903a4 commit 78aa304

11 files changed

+247
-79
lines changed

ZUGFeRD-Test/ZUGFeRD20Tests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ public void TestWriteAndReadExtended()
654654
Assert.AreEqual(timestamp.AddDays(14), loadedInvoice.BillingPeriodEnd);
655655

656656
//TradeAllowanceCharges
657-
var tradeAllowanceCharge = loadedInvoice.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason for charge");
657+
var tradeAllowanceCharge = loadedInvoice.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason for charge");
658658
Assert.IsNotNull(tradeAllowanceCharge);
659659
Assert.IsTrue(tradeAllowanceCharge.ChargeIndicator);
660660
Assert.AreEqual("Reason for charge", tradeAllowanceCharge.Reason);
@@ -747,7 +747,7 @@ public void TestWriteAndReadExtended()
747747
//Assert.AreEqual("987654", accountingAccount.TradeAccountID);
748748

749749

750-
var lineItemTradeAllowanceCharge = loadedLineItem.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason: UnitTest");
750+
var lineItemTradeAllowanceCharge = loadedLineItem.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason: UnitTest");
751751
Assert.IsNotNull(lineItemTradeAllowanceCharge);
752752
Assert.IsTrue(lineItemTradeAllowanceCharge.ChargeIndicator);
753753
Assert.AreEqual(10m, lineItemTradeAllowanceCharge.BasisAmount);

ZUGFeRD-Test/ZUGFeRD21Tests.cs

+53-8
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,18 @@ public void TestReferenceExtendedInvoice()
147147
Assert.AreEqual(desc.TradeLineItems.Count, 6);
148148
Assert.AreEqual(desc.LineTotalAmount, 457.20m);
149149

150-
foreach (TradeAllowanceCharge charge in desc.TradeAllowanceCharges)
150+
IList<TradeAllowanceCharge> _tradeAllowanceCharges = desc.GetTradeAllowanceCharges();
151+
foreach (TradeAllowanceCharge charge in _tradeAllowanceCharges)
151152
{
152153
Assert.AreEqual(charge.Tax.TypeCode, TaxTypes.VAT);
153154
Assert.AreEqual(charge.Tax.CategoryCode, TaxCategoryCodes.S);
154155
}
155156

156-
Assert.AreEqual(desc.TradeAllowanceCharges.Count, 4);
157-
Assert.AreEqual(desc.TradeAllowanceCharges[0].Tax.Percent, 19m);
158-
Assert.AreEqual(desc.TradeAllowanceCharges[1].Tax.Percent, 7m);
159-
Assert.AreEqual(desc.TradeAllowanceCharges[2].Tax.Percent, 19m);
160-
Assert.AreEqual(desc.TradeAllowanceCharges[3].Tax.Percent, 7m);
157+
Assert.AreEqual(_tradeAllowanceCharges.Count, 4);
158+
Assert.AreEqual(_tradeAllowanceCharges[0].Tax.Percent, 19m);
159+
Assert.AreEqual(_tradeAllowanceCharges[1].Tax.Percent, 7m);
160+
Assert.AreEqual(_tradeAllowanceCharges[2].Tax.Percent, 19m);
161+
Assert.AreEqual(_tradeAllowanceCharges[3].Tax.Percent, 7m);
161162

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

15121513
//TradeAllowanceCharges
1513-
var tradeAllowanceCharge = loadedInvoice.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason for charge");
1514+
var tradeAllowanceCharge = loadedInvoice.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason for charge");
15141515
Assert.IsNotNull(tradeAllowanceCharge);
15151516
Assert.IsTrue(tradeAllowanceCharge.ChargeIndicator);
15161517
Assert.AreEqual("Reason for charge", tradeAllowanceCharge.Reason);
@@ -1602,7 +1603,7 @@ public void TestWriteAndReadExtended()
16021603
Assert.AreEqual("987654", accountingAccount.TradeAccountID);
16031604

16041605

1605-
var lineItemTradeAllowanceCharge = loadedLineItem.TradeAllowanceCharges.FirstOrDefault(i => i.Reason == "Reason: UnitTest");
1606+
var lineItemTradeAllowanceCharge = loadedLineItem.GetTradeAllowanceCharges().FirstOrDefault(i => i.Reason == "Reason: UnitTest");
16061607
Assert.IsNotNull(lineItemTradeAllowanceCharge);
16071608
Assert.IsTrue(lineItemTradeAllowanceCharge.ChargeIndicator);
16081609
Assert.AreEqual(10m, lineItemTradeAllowanceCharge.BasisAmount);
@@ -1781,6 +1782,50 @@ public void TestBasisQuantityMultiple()
17811782
Assert.AreEqual("27.50", node.InnerText);
17821783
} // !TestBasisQuantityMultiple()
17831784

1785+
1786+
[TestMethod]
1787+
public void TestTradeAllowanceChargeWithoutExplicitPercentage()
1788+
{
1789+
InvoiceDescriptor invoice = InvoiceProvider.CreateInvoice();
1790+
1791+
// fake values, does not matter for our test case
1792+
invoice.AddTradeAllowanceCharge(true, 100, CurrencyCodes.EUR, 10, "", TaxTypes.VAT, TaxCategoryCodes.S, 19);
1793+
1794+
MemoryStream ms = new MemoryStream();
1795+
invoice.Save(ms, ZUGFeRDVersion.Version21, Profile.Extended);
1796+
ms.Position = 0;
1797+
1798+
InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms);
1799+
IList<TradeAllowanceCharge> allowanceCharges = loadedInvoice.GetTradeAllowanceCharges();
1800+
1801+
Assert.IsTrue(allowanceCharges.Count == 1);
1802+
Assert.AreEqual(allowanceCharges[0].BasisAmount, 100m);
1803+
Assert.AreEqual(allowanceCharges[0].Amount, 10m);
1804+
Assert.AreEqual(allowanceCharges[0].ChargePercentage, null);
1805+
} // !TestTradeAllowanceChargeWithoutExplicitPercentage()
1806+
1807+
1808+
[TestMethod]
1809+
public void TestTradeAllowanceChargeWithExplicitPercentage()
1810+
{
1811+
InvoiceDescriptor invoice = InvoiceProvider.CreateInvoice();
1812+
1813+
// fake values, does not matter for our test case
1814+
invoice.AddTradeAllowanceCharge(true, 100, CurrencyCodes.EUR, 10, 12, "", TaxTypes.VAT, TaxCategoryCodes.S, 19);
1815+
1816+
MemoryStream ms = new MemoryStream();
1817+
invoice.Save(ms, ZUGFeRDVersion.Version21, Profile.Extended);
1818+
ms.Position = 0;
1819+
InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms);
1820+
IList<TradeAllowanceCharge> allowanceCharges = loadedInvoice.GetTradeAllowanceCharges();
1821+
1822+
Assert.IsTrue(allowanceCharges.Count == 1);
1823+
Assert.AreEqual(allowanceCharges[0].BasisAmount, 100m);
1824+
Assert.AreEqual(allowanceCharges[0].Amount, 10m);
1825+
Assert.AreEqual(allowanceCharges[0].ChargePercentage, 12);
1826+
} // !TestTradeAllowanceChargeWithExplicitPercentage()
1827+
1828+
17841829
[TestMethod]
17851830
public void TestReferenceXRechnung21UBL()
17861831
{

ZUGFeRD/IInvoiceDescriptorReader.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ protected static int _nodeAsInt(XmlNode node, string xpath, XmlNamespaceManager
137137
/// <summary>
138138
/// reads the value from given xpath and interprets the value as decimal
139139
/// </summary>
140-
protected static decimal? _nodeAsDecimal(XmlNode node, string xpath, XmlNamespaceManager nsmgr = null, decimal? defaultValue = null)
140+
protected static decimal? _nodeAsDecimal(XmlNode node, string xpath, XmlNamespaceManager nsmgr = null, decimal? defaultValue = default(decimal?))
141141
{
142142
if (node == null)
143143
{

ZUGFeRD/InvoiceDescriptor.cs

+51-4
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,10 @@ public class InvoiceDescriptor
251251
public List<ServiceCharge> ServiceCharges { get; set; } = new List<ServiceCharge>();
252252

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

258259
/// <summary>
259260
/// Detailed information about payment terms
@@ -721,16 +722,17 @@ public void AddLogisticsServiceCharge(decimal amount, string description, TaxTyp
721722
/// <param name="taxTypeCode">VAT type code for document level allowance/ charge</param>
722723
/// <param name="taxCategoryCode">VAT type code for document level allowance/ charge</param>
723724
/// <param name="taxPercent">VAT rate for the allowance</param>
724-
public void AddTradeAllowanceCharge(bool isDiscount, decimal basisAmount, CurrencyCodes currency, decimal actualAmount, string reason, TaxTypes taxTypeCode, TaxCategoryCodes taxCategoryCode, decimal taxPercent)
725+
public void AddTradeAllowanceCharge(bool isDiscount, decimal? basisAmount, CurrencyCodes currency, decimal actualAmount, string reason, TaxTypes taxTypeCode, TaxCategoryCodes taxCategoryCode, decimal taxPercent)
725726
{
726-
this.TradeAllowanceCharges.Add(new TradeAllowanceCharge()
727+
this._TradeAllowanceCharges.Add(new TradeAllowanceCharge()
727728
{
728729
ChargeIndicator = !isDiscount,
729730
Reason = reason,
730731
BasisAmount = basisAmount,
731732
ActualAmount = actualAmount,
732733
Currency = currency,
733734
Amount = actualAmount,
735+
ChargePercentage = null,
734736
Tax = new Tax()
735737
{
736738
CategoryCode = taxCategoryCode,
@@ -741,6 +743,51 @@ public void AddTradeAllowanceCharge(bool isDiscount, decimal basisAmount, Curren
741743
} // !AddTradeAllowanceCharge()
742744

743745

746+
/// <summary>
747+
/// Adds an allowance or charge on document level.
748+
///
749+
/// Allowance represents a discount whereas charge represents a surcharge.
750+
/// </summary>
751+
/// <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>
752+
/// <param name="basisAmount">Base amount (basis of allowance)</param>
753+
/// <param name="currency">Curency of the allowance</param>
754+
/// <param name="actualAmount">Actual allowance charge amount</param>
755+
/// <param name="chargePercentage">Actual allowance charge percentage</param>
756+
/// <param name="reason">Reason for the allowance</param>
757+
/// <param name="taxTypeCode">VAT type code for document level allowance/ charge</param>
758+
/// <param name="taxCategoryCode">VAT type code for document level allowance/ charge</param>
759+
/// <param name="taxPercent">VAT rate for the allowance</param>
760+
public void AddTradeAllowanceCharge(bool isDiscount, decimal? basisAmount, CurrencyCodes currency, decimal actualAmount, decimal? chargePercentage, string reason, TaxTypes taxTypeCode, TaxCategoryCodes taxCategoryCode, decimal taxPercent)
761+
{
762+
this._TradeAllowanceCharges.Add(new TradeAllowanceCharge()
763+
{
764+
ChargeIndicator = !isDiscount,
765+
Reason = reason,
766+
BasisAmount = basisAmount,
767+
ActualAmount = actualAmount,
768+
Currency = currency,
769+
Amount = actualAmount,
770+
ChargePercentage = chargePercentage,
771+
Tax = new Tax()
772+
{
773+
CategoryCode = taxCategoryCode,
774+
TypeCode = taxTypeCode,
775+
Percent = taxPercent
776+
}
777+
});
778+
} // !AddTradeAllowanceCharge()
779+
780+
781+
/// <summary>
782+
/// Returns all existing trade allowance charges
783+
/// </summary>
784+
/// <returns></returns>
785+
public IList<TradeAllowanceCharge> GetTradeAllowanceCharges()
786+
{
787+
return this._TradeAllowanceCharges;
788+
} // !GetTradeAllowanceCharges()
789+
790+
744791
public void SetTradePaymentTerms(string description, DateTime? dueDate = null)
745792
{
746793
this.PaymentTerms = new PaymentTerms()

ZUGFeRD/InvoiceDescriptor1Writer.cs

+18-12
Original file line numberDiff line numberDiff line change
@@ -303,19 +303,22 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)
303303

304304
_writeOptionalTaxes(Writer);
305305

306-
if ((this.Descriptor.TradeAllowanceCharges != null) && (this.Descriptor.TradeAllowanceCharges.Count > 0))
306+
if ((this.Descriptor.GetTradeAllowanceCharges() != null) && (this.Descriptor.GetTradeAllowanceCharges().Count > 0))
307307
{
308-
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.TradeAllowanceCharges)
308+
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.GetTradeAllowanceCharges())
309309
{
310310
Writer.WriteStartElement("ram:SpecifiedTradeAllowanceCharge");
311311
Writer.WriteStartElement("ram:ChargeIndicator", Profile.Comfort | Profile.Extended);
312312
Writer.WriteElementString("udt:Indicator", tradeAllowanceCharge.ChargeIndicator ? "true" : "false");
313313
Writer.WriteEndElement(); // !ram:ChargeIndicator
314314

315-
Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
316-
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
317-
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount));
318-
Writer.WriteEndElement();
315+
if (tradeAllowanceCharge.BasisAmount.HasValue)
316+
{
317+
Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
318+
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
319+
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value));
320+
Writer.WriteEndElement();
321+
}
319322

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

464-
foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.TradeAllowanceCharges)
467+
foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.GetTradeAllowanceCharges())
465468
{
466469
Writer.WriteStartElement("ram:AppliedTradeAllowanceCharge");
467470

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

472-
Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
473-
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
474-
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount, 4));
475-
Writer.WriteEndElement();
475+
if (tradeAllowanceCharge.BasisAmount.HasValue)
476+
{
477+
Writer.WriteStartElement("ram:BasisAmount", Profile.Extended);
478+
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
479+
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value, 4));
480+
Writer.WriteEndElement();
481+
}
476482
Writer.WriteStartElement("ram:ActualAmount", Profile.Comfort | Profile.Extended);
477483
Writer.WriteAttributeString("currencyID", tradeAllowanceCharge.Currency.EnumToString());
478484
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.ActualAmount, 4));
@@ -636,7 +642,7 @@ internal override bool Validate(InvoiceDescriptor descriptor, bool throwExceptio
636642

637643
private void _writeOptionalAmount(ProfileAwareXmlTextWriter writer, string tagName, decimal? value, int numDecimals = 2)
638644
{
639-
if (value.HasValue && (value.Value != decimal.MinValue))
645+
if (value.HasValue)
640646
{
641647
writer.WriteStartElement(tagName);
642648
writer.WriteAttributeString("currencyID", this.Descriptor.Currency.EnumToString());

ZUGFeRD/InvoiceDescriptor20Writer.cs

+16-10
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,20 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)
232232
_writeElementWithAttribute(Writer, "ram:BasisQuantity", "unitCode", tradeLineItem.UnitCode.EnumToString(), _formatDecimal(tradeLineItem.UnitQuantity.Value, 4));
233233
}
234234

235-
foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.TradeAllowanceCharges)
235+
foreach (TradeAllowanceCharge tradeAllowanceCharge in tradeLineItem.GetTradeAllowanceCharges())
236236
{
237237
Writer.WriteStartElement("ram:AppliedTradeAllowanceCharge");
238238

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

243-
Writer.WriteStartElement("ram:BasisAmount");
244-
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount, 4));
245-
Writer.WriteEndElement();
243+
if (tradeAllowanceCharge.BasisAmount.HasValue)
244+
{
245+
Writer.WriteStartElement("ram:BasisAmount");
246+
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value, 4));
247+
Writer.WriteEndElement();
248+
}
246249
Writer.WriteStartElement("ram:ActualAmount");
247250
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.ActualAmount, 4));
248251
Writer.WriteEndElement();
@@ -648,18 +651,21 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream)
648651
#endregion
649652

650653
// 13. SpecifiedTradeAllowanceCharge (optional)
651-
if ((this.Descriptor.TradeAllowanceCharges != null) && (this.Descriptor.TradeAllowanceCharges.Count > 0))
654+
if ((this.Descriptor.GetTradeAllowanceCharges() != null) && (this.Descriptor.GetTradeAllowanceCharges().Count > 0))
652655
{
653-
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.TradeAllowanceCharges)
656+
foreach (TradeAllowanceCharge tradeAllowanceCharge in this.Descriptor.GetTradeAllowanceCharges())
654657
{
655658
Writer.WriteStartElement("ram:SpecifiedTradeAllowanceCharge");
656659
Writer.WriteStartElement("ram:ChargeIndicator");
657660
Writer.WriteElementString("udt:Indicator", tradeAllowanceCharge.ChargeIndicator ? "true" : "false");
658661
Writer.WriteEndElement(); // !ram:ChargeIndicator
659662

660-
Writer.WriteStartElement("ram:BasisAmount");
661-
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount));
662-
Writer.WriteEndElement();
663+
if (tradeAllowanceCharge.BasisAmount.HasValue)
664+
{
665+
Writer.WriteStartElement("ram:BasisAmount");
666+
Writer.WriteValue(_formatDecimal(tradeAllowanceCharge.BasisAmount.Value));
667+
Writer.WriteEndElement();
668+
}
663669

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

771777
private void _writeOptionalAmount(ProfileAwareXmlTextWriter writer, string tagName, decimal? value, int numDecimals = 2, bool forceCurrency = false, Profile profile = Profile.Unknown)
772778
{
773-
if (value.HasValue && (value.Value != decimal.MinValue))
779+
if (value.HasValue)
774780
{
775781
writer.WriteStartElement(tagName, profile);
776782
if (forceCurrency)

0 commit comments

Comments
 (0)