Skip to content

Commit a61a69e

Browse files
Merge pull request #487 from amroel/reading_applicable_taxes
Read AllowanceChargeBasisAmount in Extended Profiles
2 parents 70c276c + f7a9af8 commit a61a69e

File tree

4 files changed

+87
-34
lines changed

4 files changed

+87
-34
lines changed

ZUGFeRD.Test/TestTaxes.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
3+
namespace s2industries.ZUGFeRD.Test;
4+
5+
[TestClass]
6+
public class TestTaxes
7+
{
8+
[TestMethod]
9+
[DataRow(ZUGFeRDVersion.Version1, Profile.Extended)]
10+
[DataRow(ZUGFeRDVersion.Version20, Profile.Extended)]
11+
[DataRow(ZUGFeRDVersion.Version23, Profile.Extended)]
12+
public void SavingThenReadingAppliedTradeTaxesShouldWork(ZUGFeRDVersion version, Profile profile)
13+
{
14+
InvoiceDescriptor expected = InvoiceDescriptor.CreateInvoice("123", new DateTime(2024, 12, 5), CurrencyCodes.EUR);
15+
var lineItem = expected.AddTradeLineItem(name: "Something",
16+
grossUnitPrice: 9.9m,
17+
netUnitPrice: 9.9m,
18+
billedQuantity: 20m,
19+
taxType: TaxTypes.VAT,
20+
categoryCode: TaxCategoryCodes.S,
21+
taxPercent: 19m
22+
);
23+
lineItem.LineTotalAmount = 198m; // 20 * 9.9
24+
expected.AddApplicableTradeTax(
25+
basisAmount: lineItem.LineTotalAmount!.Value,
26+
percent: 19m,
27+
taxAmount: 29.82m, // 19% of 198
28+
typeCode: TaxTypes.VAT,
29+
categoryCode: TaxCategoryCodes.S,
30+
allowanceChargeBasisAmount: -5m
31+
);
32+
expected.LineTotalAmount = 198m;
33+
expected.TaxBasisAmount = 198m;
34+
expected.TaxTotalAmount = 29.82m;
35+
expected.GrandTotalAmount = 198m + 29.82m;
36+
expected.DuePayableAmount = expected.GrandTotalAmount;
37+
38+
using MemoryStream ms = new();
39+
expected.Save(ms, version, profile);
40+
ms.Seek(0, SeekOrigin.Begin);
41+
42+
InvoiceDescriptor actual = InvoiceDescriptor.Load(ms);
43+
44+
Assert.AreEqual(expected.Taxes.Count, actual.Taxes.Count);
45+
Assert.AreEqual(1, actual.Taxes.Count);
46+
Tax actualTax = actual.Taxes[0];
47+
Assert.AreEqual(198m, actualTax.BasisAmount);
48+
Assert.AreEqual(19m, actualTax.Percent);
49+
Assert.AreEqual(29.82m, actualTax.TaxAmount);
50+
Assert.AreEqual(TaxTypes.VAT, actualTax.TypeCode);
51+
Assert.AreEqual(TaxCategoryCodes.S, actualTax.CategoryCode);
52+
Assert.AreEqual(-5m, actualTax.AllowanceChargeBasisAmount);
53+
}
54+
}

ZUGFeRD/InvoiceDescriptor1Reader.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ public override InvoiceDescriptor Load(Stream stream)
196196
XmlUtils.NodeAsDecimal(node, ".//ram:ApplicablePercent", nsmgr, 0).Value,
197197
XmlUtils.NodeAsDecimal(node, ".//ram:CalculatedAmount", nsmgr, 0).Value,
198198
default(TaxTypes).FromString(XmlUtils.NodeAsString(node, ".//ram:TypeCode", nsmgr)),
199-
default(TaxCategoryCodes).FromString(XmlUtils.NodeAsString(node, ".//ram:CategoryCode", nsmgr)));
199+
default(TaxCategoryCodes).FromString(XmlUtils.NodeAsString(node, ".//ram:CategoryCode", nsmgr)),
200+
XmlUtils.NodeAsDecimal(node, ".//ram:AllowanceChargeBasisAmount", nsmgr));
200201
}
201202

202203
foreach (XmlNode node in doc.SelectNodes("//ram:SpecifiedTradeAllowanceCharge", nsmgr))

ZUGFeRD/InvoiceDescriptor20Reader.cs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@
1818
*/
1919
using System;
2020
using System.Collections.Generic;
21+
using System.IO;
2122
using System.Linq;
22-
using System.Text;
2323
using System.Xml;
24-
using System.Xml.XPath;
25-
using System.IO;
2624

2725

2826
namespace s2industries.ZUGFeRD
@@ -158,7 +156,7 @@ public override InvoiceDescriptor Load(Stream stream)
158156
};
159157
var financialCardId = XmlUtils.NodeAsString(doc.DocumentElement, "//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans/ram:ApplicableTradeSettlementFinancialCard/ram:ID", nsmgr);
160158
var financialCardCardholderName = XmlUtils.NodeAsString(doc.DocumentElement, "//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans/ram:ApplicableTradeSettlementFinancialCard/ram:CardholderName", nsmgr);
161-
159+
162160
if (!string.IsNullOrWhiteSpace(financialCardId) || !string.IsNullOrWhiteSpace(financialCardCardholderName))
163161
{
164162
_tempPaymentMeans.FinancialCard = new FinancialCard()
@@ -169,7 +167,7 @@ public override InvoiceDescriptor Load(Stream stream)
169167
}
170168

171169
retval.PaymentMeans = _tempPaymentMeans;
172-
170+
173171
retval.BillingPeriodStart = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableHeaderTradeSettlement/ram:BillingSpecifiedPeriod/ram:StartDateTime", nsmgr);
174172
retval.BillingPeriodEnd = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableHeaderTradeSettlement/ram:BillingSpecifiedPeriod/ram:EndDateTime", nsmgr);
175173

@@ -193,18 +191,18 @@ public override InvoiceDescriptor Load(Stream stream)
193191
retval.CreditorBankAccounts.Add(_account);
194192
} // !for(i)
195193
}
196-
194+
197195
var specifiedTradeSettlementPaymentMeansNodes = doc.SelectNodes("//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans", nsmgr);
198196

199197
foreach (var specifiedTradeSettlementPaymentMeansNode in specifiedTradeSettlementPaymentMeansNodes.OfType<XmlNode>())
200198
{
201199
var payerPartyDebtorFinancialAccountNode = specifiedTradeSettlementPaymentMeansNode.SelectSingleNode("ram:PayerPartyDebtorFinancialAccount", nsmgr);
202-
200+
203201
if (payerPartyDebtorFinancialAccountNode == null)
204202
{
205203
continue;
206204
}
207-
205+
208206
var _account = new BankAccount()
209207
{
210208
ID = XmlUtils.NodeAsString(payerPartyDebtorFinancialAccountNode, ".//ram:ProprietaryID", nsmgr),
@@ -247,7 +245,7 @@ public override InvoiceDescriptor Load(Stream stream)
247245
XmlUtils.NodeAsDecimal(node, ".//ram:CalculatedAmount", nsmgr, 0).Value,
248246
default(TaxTypes).FromString(XmlUtils.NodeAsString(node, ".//ram:TypeCode", nsmgr)),
249247
default(TaxCategoryCodes).FromString(XmlUtils.NodeAsString(node, ".//ram:CategoryCode", nsmgr)),
250-
0,
248+
XmlUtils.NodeAsDecimal(node, ".//ram:AllowanceChargeBasisAmount", nsmgr),
251249
default(TaxExemptionReasonCodes).FromString(XmlUtils.NodeAsString(node, ".//ram:ExemptionReasonCode", nsmgr)),
252250
XmlUtils.NodeAsString(node, ".//ram:ExemptionReason", nsmgr));
253251
}
@@ -304,7 +302,7 @@ public override InvoiceDescriptor Load(Stream stream)
304302
retval.DuePayableAmount = XmlUtils.NodeAsDecimal(doc.DocumentElement, "//ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount", nsmgr, 0).Value;
305303

306304
// in this version we should only have on invoice referenced document but nevertheless...
307-
foreach(XmlNode invoiceReferencedDocumentNodes in doc.DocumentElement.SelectNodes("//ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument", nsmgr))
305+
foreach (XmlNode invoiceReferencedDocumentNodes in doc.DocumentElement.SelectNodes("//ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument", nsmgr))
308306
{
309307
retval.AddInvoiceReferencedDocument(
310308
XmlUtils.NodeAsString(invoiceReferencedDocumentNodes, "./ram:IssuerAssignedID", nsmgr),
@@ -357,7 +355,7 @@ public override bool IsReadableByThisReaderVersion(Stream stream)
357355

358356
return _IsReadableByThisReaderVersion(stream, validURIs);
359357
} // !IsReadableByThisReaderVersion()
360-
358+
361359

362360
private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNamespaceManager nsmgr = null)
363361
{
@@ -367,12 +365,12 @@ private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNames
367365
}
368366

369367
string _lineId = XmlUtils.NodeAsString(tradeLineItem, ".//ram:AssociatedDocumentLineDocument/ram:LineID", nsmgr, String.Empty);
370-
368+
371369
LineStatusCodes? _lineStatusCode = default(LineStatusCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:AssociatedDocumentLineDocument/ram:LineStatusCode", nsmgr, null));
372370
LineStatusReasonCodes? _lineStatusReasonCode = default(LineStatusReasonCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:AssociatedDocumentLineDocument/ram:LineStatusReasonCode", nsmgr, null));
373371

374372
TradeLineItem item = new TradeLineItem(_lineId)
375-
{
373+
{
376374
GlobalID = new GlobalID(default(GlobalIDSchemeIdentifiers).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedTradeProduct/ram:GlobalID/@schemeID", nsmgr)),
377375
XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedTradeProduct/ram:GlobalID", nsmgr)),
378376
SellerAssignedID = XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedTradeProduct/ram:SellerAssignedID", nsmgr),
@@ -392,7 +390,7 @@ private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNames
392390
BillingPeriodEnd = XmlUtils.NodeAsDateTime(tradeLineItem, ".//ram:BillingSpecifiedPeriod/ram:EndDateTime/udt:DateTimeString", nsmgr),
393391
};
394392

395-
if(_lineStatusCode.HasValue && _lineStatusReasonCode.HasValue)
393+
if (_lineStatusCode.HasValue && _lineStatusReasonCode.HasValue)
396394
{
397395
item.SetLineStatus(_lineStatusCode.Value, _lineStatusReasonCode.Value);
398396
}
@@ -422,7 +420,7 @@ private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNames
422420
}
423421

424422
if (tradeLineItem.SelectSingleNode(".//ram:AssociatedDocumentLineDocument", nsmgr) != null)
425-
{
423+
{
426424
XmlNodeList noteNodes = tradeLineItem.SelectNodes(".//ram:AssociatedDocumentLineDocument/ram:IncludedNote", nsmgr);
427425
foreach (XmlNode noteNode in noteNodes)
428426
{
@@ -557,7 +555,7 @@ private static AdditionalReferencedDocument _getAdditionalReferencedDocument(Xml
557555
return new AdditionalReferencedDocument
558556
{
559557
ID = XmlUtils.NodeAsString(node, "ram:IssuerAssignedID", nsmgr),
560-
TypeCode = default(AdditionalReferencedDocumentTypeCode).FromString(XmlUtils.NodeAsString(node,"ram:TypeCode", nsmgr)),
558+
TypeCode = default(AdditionalReferencedDocumentTypeCode).FromString(XmlUtils.NodeAsString(node, "ram:TypeCode", nsmgr)),
561559
Name = XmlUtils.NodeAsString(node, "ram:Name", nsmgr),
562560
IssueDateTime = DataTypeReader.ReadFormattedIssueDateTime(node, "ram:FormattedIssueDateTime", nsmgr),
563561
AttachmentBinaryObject = !string.IsNullOrWhiteSpace(strBase64BinaryData) ? Convert.FromBase64String(strBase64BinaryData) : null,

ZUGFeRD/InvoiceDescriptor23CIIReader.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public override InvoiceDescriptor Load(Stream stream)
181181

182182
retval.ShipTo = _nodeAsParty(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ShipToTradeParty", nsmgr);
183183
retval.UltimateShipTo = _nodeAsParty(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:UltimateShipToTradeParty", nsmgr);
184-
retval.ShipFrom = _nodeAsParty(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ShipFromTradeParty", nsmgr);
184+
retval.ShipFrom = _nodeAsParty(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ShipFromTradeParty", nsmgr);
185185
retval.ActualDeliveryDate = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString", nsmgr);
186186

187187
string _despatchAdviceNo = XmlUtils.NodeAsString(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:DespatchAdviceReferencedDocument/ram:IssuerAssignedID", nsmgr);
@@ -315,7 +315,7 @@ public override InvoiceDescriptor Load(Stream stream)
315315
XmlUtils.NodeAsDecimal(node, ".//ram:CalculatedAmount", nsmgr, 0).Value,
316316
default(TaxTypes).FromString(XmlUtils.NodeAsString(node, ".//ram:TypeCode", nsmgr)),
317317
default(TaxCategoryCodes).FromString(XmlUtils.NodeAsString(node, ".//ram:CategoryCode", nsmgr)),
318-
0,
318+
XmlUtils.NodeAsDecimal(node, ".//ram:AllowanceChargeBasisAmount", nsmgr),
319319
default(TaxExemptionReasonCodes).FromString(XmlUtils.NodeAsString(node, ".//ram:ExemptionReasonCode", nsmgr)),
320320
XmlUtils.NodeAsString(node, ".//ram:ExemptionReason", nsmgr));
321321
}
@@ -359,7 +359,7 @@ public override InvoiceDescriptor Load(Stream stream)
359359
int? penaltyDueDays = null; // XmlUtils.NodeAsInt(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisPeriodMeasure", nsmgr);
360360
decimal? penaltyAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisAmount", nsmgr, null);
361361
PaymentTermsType? paymentTermsType = discountPercent.HasValue ? PaymentTermsType.Skonto :
362-
penaltyPercent.HasValue ? PaymentTermsType.Verzug :
362+
penaltyPercent.HasValue ? PaymentTermsType.Verzug :
363363
(PaymentTermsType?)null;
364364

365365
retval.AddTradePaymentTerms(XmlUtils.NodeAsString(node, ".//ram:Description", nsmgr),
@@ -463,7 +463,7 @@ private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNames
463463

464464
LineStatusCodes? _lineStatusCode = default(LineStatusCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:AssociatedDocumentLineDocument/ram:LineStatusCode", nsmgr, null));
465465
LineStatusReasonCodes? _lineStatusReasonCode = default(LineStatusReasonCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:AssociatedDocumentLineDocument/ram:LineStatusReasonCode", nsmgr, null));
466-
466+
467467
TradeLineItem item = new TradeLineItem(_lineId)
468468
{
469469
GlobalID = new GlobalID(default(GlobalIDSchemeIdentifiers).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedTradeProduct/ram:GlobalID/@schemeID", nsmgr)),
@@ -519,20 +519,20 @@ private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNames
519519
Name = XmlUtils.NodeAsString(includedItem, ".//ram:Name", nsmgr),
520520
UnitQuantity = XmlUtils.NodeAsDecimal(includedItem, ".//ram:UnitQuantity", nsmgr, null),
521521
UnitCode = unitCode != null ? (QuantityCodes?)default(QuantityCodes).FromString(unitCode) : null
522-
});
522+
});
523523
}
524524

525-
if (tradeLineItem.SelectSingleNode(".//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument", nsmgr) != null)
526-
{
527-
item.BuyerOrderReferencedDocument = new BuyerOrderReferencedDocument()
528-
{
529-
ID = XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument/ram:IssuerAssignedID", nsmgr),
530-
IssueDateTime = DataTypeReader.ReadFormattedIssueDateTime(tradeLineItem, "//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument/ram:FormattedIssueDateTime", nsmgr),
531-
LineID = XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument/ram:LineID", nsmgr)
532-
};
533-
}
525+
if (tradeLineItem.SelectSingleNode(".//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument", nsmgr) != null)
526+
{
527+
item.BuyerOrderReferencedDocument = new BuyerOrderReferencedDocument()
528+
{
529+
ID = XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument/ram:IssuerAssignedID", nsmgr),
530+
IssueDateTime = DataTypeReader.ReadFormattedIssueDateTime(tradeLineItem, "//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument/ram:FormattedIssueDateTime", nsmgr),
531+
LineID = XmlUtils.NodeAsString(tradeLineItem, ".//ram:SpecifiedLineTradeAgreement/ram:BuyerOrderReferencedDocument/ram:LineID", nsmgr)
532+
};
533+
}
534534

535-
if (tradeLineItem.SelectSingleNode(".//ram:SpecifiedLineTradeAgreement/ram:ContractReferencedDocument", nsmgr) != null)
535+
if (tradeLineItem.SelectSingleNode(".//ram:SpecifiedLineTradeAgreement/ram:ContractReferencedDocument", nsmgr) != null)
536536
{
537537
item.ContractReferencedDocument = new ContractReferencedDocument()
538538
{
@@ -659,8 +659,8 @@ private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNames
659659
{
660660
string className = XmlUtils.NodeAsString(designatedProductClassificationNode, "./ram:ClassName", nsmgr);
661661
string classCode = XmlUtils.NodeAsString(designatedProductClassificationNode, "./ram:ClassCode", nsmgr);
662-
DesignatedProductClassificationClassCodes listID = default(DesignatedProductClassificationClassCodes).FromString(XmlUtils.NodeAsString(designatedProductClassificationNode, "./ram:ClassCode/@listID", nsmgr));
663-
string listVersionID = XmlUtils.NodeAsString(designatedProductClassificationNode, "./ram:ClassCode/@listVersionID", nsmgr);
662+
DesignatedProductClassificationClassCodes listID = default(DesignatedProductClassificationClassCodes).FromString(XmlUtils.NodeAsString(designatedProductClassificationNode, "./ram:ClassCode/@listID", nsmgr));
663+
string listVersionID = XmlUtils.NodeAsString(designatedProductClassificationNode, "./ram:ClassCode/@listVersionID", nsmgr);
664664

665665
item.AddDesignatedProductClassification(listID, listVersionID, classCode, className);
666666
} // !foreach(designatedProductClassificationNode))

0 commit comments

Comments
 (0)