diff --git a/ZUGFeRD.Test/XRechnungUBLTests.cs b/ZUGFeRD.Test/XRechnungUBLTests.cs index fbfb2cd8..f9b16b33 100644 --- a/ZUGFeRD.Test/XRechnungUBLTests.cs +++ b/ZUGFeRD.Test/XRechnungUBLTests.cs @@ -1143,7 +1143,7 @@ public void TestReferenceXRechnung21UBL() Assert.AreEqual(desc.OrderNo, "0815-99-1"); Assert.AreEqual(desc.Currency, CurrencyCodes.EUR); - Assert.AreEqual(desc.Buyer.Name, "Rechnungs Roulette GmbH & Co KG"); + Assert.AreEqual(desc.Buyer.Name, ""); Assert.AreEqual(desc.Buyer.City, "Klein Schlappstadt a.d. Lusche"); Assert.AreEqual(desc.Buyer.Postcode, "12345"); Assert.AreEqual(desc.Buyer.Country, CountryCodes.DE); @@ -1154,7 +1154,7 @@ public void TestReferenceXRechnung21UBL() Assert.AreEqual(desc.BuyerContact.EmailAddress, "manfred.mustermann@rr.de"); Assert.AreEqual(desc.BuyerContact.PhoneNo, "012345 98 765 - 44"); - Assert.AreEqual(desc.Seller.Name, "Harry Hirsch Holz- und Trockenbau"); + Assert.AreEqual(desc.Seller.Name, ""); Assert.AreEqual(desc.Seller.City, "Klein Schlappstadt a.d. Lusche"); Assert.AreEqual(desc.Seller.Postcode, "12345"); Assert.AreEqual(desc.Seller.Country, CountryCodes.DE); @@ -1355,6 +1355,21 @@ public void TestNonStandardDateTimeFormat() } // !TestNonStandardDateTimeFormat() + /// + /// Test that InvoicePeriod is not read TradeLineItem -> InvoicePeriod + /// + [TestMethod] + public void TestDontMixInvoicePeriodWithTradeLineItem() + { + string path = @"..\..\..\..\demodata\xRechnung\01.01a-INVOICE_ubl.xml"; + path = _makeSurePathIsCrossPlatformCompatible(path); + + InvoiceDescriptor desc = InvoiceDescriptor.Load(path); + + Assert.IsNull(desc.BillingPeriodStart); + Assert.IsNull(desc.BillingPeriodEnd); + } // !TestDontMixInvoicePeriodWithTradeLineItem() + [TestMethod] public void TestSellerPartyDescription() { diff --git a/ZUGFeRD/InvoiceDescriptor1Reader.cs b/ZUGFeRD/InvoiceDescriptor1Reader.cs index 277b99dd..86a91941 100644 --- a/ZUGFeRD/InvoiceDescriptor1Reader.cs +++ b/ZUGFeRD/InvoiceDescriptor1Reader.cs @@ -169,8 +169,8 @@ public override InvoiceDescriptor Load(Stream stream) }; retval.PaymentMeans = paymentMeans; - retval.BillingPeriodStart = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableHeaderTradeSettlement/ram:BillingSpecifiedPeriod/ram:StartDateTime", nsmgr); - retval.BillingPeriodEnd = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableHeaderTradeSettlement/ram:BillingSpecifiedPeriod/ram:EndDateTime", nsmgr); + retval.BillingPeriodStart = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableSupplyChainTradeSettlement/ram:BillingSpecifiedPeriod/ram:StartDateTime", nsmgr); + retval.BillingPeriodEnd = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableSupplyChainTradeSettlement/ram:BillingSpecifiedPeriod/ram:EndDateTime", nsmgr); XmlNodeList creditorFinancialAccountNodes = doc.SelectNodes("//ram:ApplicableSupplyChainTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount", nsmgr); XmlNodeList creditorFinancialInstitutions = doc.SelectNodes("//ram:ApplicableSupplyChainTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeeSpecifiedCreditorFinancialInstitution", nsmgr); @@ -261,7 +261,7 @@ public override InvoiceDescriptor Load(Stream stream) decimal? penaltyPercent = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:CalculationPercent", nsmgr, null); int? penaltyDueDays = null; // XmlUtils.NodeAsInt(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisPeriodMeasure", nsmgr); decimal? penaltyBaseAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisAmount", nsmgr, null); - decimal? penaltyActualAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentDiscountTerms/ram:ActualPenaltyAmount", nsmgr, null); + decimal? penaltyActualAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:ActualPenaltyAmount", nsmgr, null); PaymentTermsType? paymentTermsType = discountPercent.HasValue ? PaymentTermsType.Skonto : penaltyPercent.HasValue ? PaymentTermsType.Verzug : (PaymentTermsType?)null; @@ -479,7 +479,7 @@ private static Party _nodeAsParty(XmlNode baseNode, string xpath, XmlNamespaceMa if (!String.IsNullOrWhiteSpace(lineTwo)) { retval.ContactName = lineOne; - retval.Street = lineOne; + retval.Street = lineTwo; } else { diff --git a/ZUGFeRD/InvoiceDescriptor20Reader.cs b/ZUGFeRD/InvoiceDescriptor20Reader.cs index e9ebc7bc..1a88bdb2 100644 --- a/ZUGFeRD/InvoiceDescriptor20Reader.cs +++ b/ZUGFeRD/InvoiceDescriptor20Reader.cs @@ -351,7 +351,7 @@ public override InvoiceDescriptor Load(Stream stream) decimal? penaltyPercent = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:CalculationPercent", nsmgr, null); int? penaltyDueDays = null; // XmlUtils.NodeAsInt(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisPeriodMeasure", nsmgr); decimal? penaltyBaseAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisAmount", nsmgr, null); - decimal? penaltyActualAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentDiscountTerms/ram:ActualPenaltyAmount", nsmgr, null); + decimal? penaltyActualAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:ActualPenaltyAmount", nsmgr, null); PaymentTermsType? paymentTermsType = discountPercent.HasValue ? PaymentTermsType.Skonto : penaltyPercent.HasValue ? PaymentTermsType.Verzug : (PaymentTermsType?)null; diff --git a/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs b/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs index 2c6d3866..aa25791c 100644 --- a/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs +++ b/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs @@ -146,7 +146,7 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream, ZUGFeRDFo _Writer.WriteOptionalElementString("cbc", "BuyerReference", this._Descriptor.ReferenceOrderNo); - if (this._Descriptor.BillingPeriodEnd.HasValue || this._Descriptor.BillingPeriodEnd.HasValue) + if (this._Descriptor.BillingPeriodStart.HasValue || this._Descriptor.BillingPeriodEnd.HasValue) { _Writer.WriteStartElement("cac", "InvoicePeriod"); @@ -960,7 +960,7 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes pa if (partyType != PartyTypes.SellerTaxRepresentativeTradeParty) writer.WriteStartElement("cac", "Party", this._Descriptor.Profile); - if (ElectronicAddress != null) + if (!String.IsNullOrWhiteSpace(ElectronicAddress?.Address)) { writer.WriteStartElement("cbc", "EndpointID"); writer.WriteAttributeString("schemeID", ElectronicAddress.ElectronicAddressSchemeID.EnumToString()); diff --git a/ZUGFeRD/InvoiceDescriptor22UblReader.cs b/ZUGFeRD/InvoiceDescriptor22UblReader.cs index f086056d..f1e64f08 100644 --- a/ZUGFeRD/InvoiceDescriptor22UblReader.cs +++ b/ZUGFeRD/InvoiceDescriptor22UblReader.cs @@ -196,8 +196,7 @@ public override InvoiceDescriptor Load(Stream stream) EmailAddress = XmlUtils.NodeAsString(doc.DocumentElement, "//cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail", nsmgr) }; } - - // TaxRepresentativeParty is directly of type PartyType in UBL, no nested cac:Party + retval.SellerTaxRepresentative = _nodeAsParty(doc.DocumentElement, "//cac:TaxRepresentativeParty", nsmgr); //Get all referenced and embedded documents (BG-24) @@ -309,8 +308,8 @@ public override InvoiceDescriptor Load(Stream stream) retval.PaymentMeans = tempPaymentMeans; - retval.BillingPeriodStart = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//cac:InvoicePeriod/cbc:StartDate", nsmgr); - retval.BillingPeriodEnd = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//cac:InvoicePeriod/cbc:EndDate", nsmgr); + retval.BillingPeriodStart = XmlUtils.NodeAsDateTime(doc.DocumentElement, "/*[1]/cac:InvoicePeriod/cbc:StartDate", nsmgr); // do not find InvoicePeriod in + retval.BillingPeriodEnd = XmlUtils.NodeAsDateTime(doc.DocumentElement, "/*[1]/cac:InvoicePeriod/cbc:EndDate", nsmgr); XmlNodeList creditorFinancialAccountNodes = doc.SelectNodes("//cac:PaymentMeans/cac:PayeeFinancialAccount", nsmgr); foreach (XmlNode node in creditorFinancialAccountNodes) @@ -534,7 +533,7 @@ private static List _parseTradeLineItem(XmlNode tradeLineItem, Xm BuyerAssignedID = XmlUtils.NodeAsString(tradeLineItem, "./cac:Item/cac:BuyersItemIdentification/cbc:ID", nsmgr), Name = XmlUtils.NodeAsString(tradeLineItem, "./cac:Item/cbc:Name", nsmgr), Description = XmlUtils.NodeAsString(tradeLineItem, ".//cac:Item/cbc:Description", nsmgr), - NetQuantity = XmlUtils.NodeAsDecimal(tradeLineItem, ".//cac:Price/cbc:BaseQuantity", nsmgr, 1), + NetQuantity = XmlUtils.NodeAsDecimal(tradeLineItem, ".//cac:Price/cbc:BaseQuantity", nsmgr), BilledQuantity = billedQuantity ?? 0, LineTotalAmount = XmlUtils.NodeAsDecimal(tradeLineItem, ".//cbc:LineExtensionAmount", nsmgr, 0), TaxCategoryCode = EnumExtensions.StringToEnum(XmlUtils.NodeAsString(tradeLineItem, ".//cac:Item/cac:ClassifiedTaxCategory/cbc:ID", nsmgr)), @@ -696,8 +695,9 @@ private static List _parseTradeLineItem(XmlNode tradeLineItem, Xm if (!item.UnitCode.HasValue) { - // UnitCode alternativ aus BilledQuantity extrahieren - item.UnitCode = EnumExtensions.StringToNullableEnum(XmlUtils.NodeAsString(tradeLineItem, ".//cbc:InvoicedQuantity/@unitCode", nsmgr)); + // UnitCode alternativ aus InvoicedQuantity oder CreditedQuantity extrahieren + item.UnitCode = EnumExtensions.StringToNullableEnum(XmlUtils.NodeAsString(tradeLineItem, ".//cbc:InvoicedQuantity/@unitCode", nsmgr)) + ?? EnumExtensions.StringToNullableEnum(XmlUtils.NodeAsString(tradeLineItem, ".//cbc:CreditedQuantity/@unitCode", nsmgr)); } // TODO: Find value //if (tradeLineItem.SelectSingleNode(".//ram:SpecifiedLineTradeDelivery/ram:DeliveryNoteReferencedDocument/ram:IssuerAssignedID", nsmgr) != null) @@ -797,11 +797,6 @@ private static Party _nodeAsParty(XmlNode baseNode, string xpath, XmlNamespaceMa retval.Name = XmlUtils.NodeAsString(node, "cac:PartyName/cbc:Name", nsmgr); retval.SpecifiedLegalOrganization = _nodeAsLegalOrganization(node, "cac:PartyLegalEntity", nsmgr); - if (string.IsNullOrWhiteSpace(retval.Name)) - { - retval.Name = XmlUtils.NodeAsString(node, "cac:PartyLegalEntity/cbc:RegistrationName", nsmgr); - } - if (string.IsNullOrWhiteSpace(retval.Description)) { retval.Description = XmlUtils.NodeAsString(node, "cac:PartyLegalEntity/cbc:CompanyLegalForm", nsmgr); diff --git a/ZUGFeRD/InvoiceDescriptor23CIIReader.cs b/ZUGFeRD/InvoiceDescriptor23CIIReader.cs index 82f9e6f8..14d7cafc 100644 --- a/ZUGFeRD/InvoiceDescriptor23CIIReader.cs +++ b/ZUGFeRD/InvoiceDescriptor23CIIReader.cs @@ -432,7 +432,7 @@ public override InvoiceDescriptor Load(Stream stream) penaltyDueDays = XmlUtils.NodeAsInt(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisPeriodMeasure", nsmgr); DateTime? penaltyMaturityDate = XmlUtils.NodeAsDateTime(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisDateTime/udt:DateTimeString", nsmgr); // BT-X-276-0 decimal? penaltyBaseAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:BasisAmount", nsmgr, null); - decimal? penaltyActualAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentDiscountTerms/ram:ActualPenaltyAmount", nsmgr, null); + decimal? penaltyActualAmount = XmlUtils.NodeAsDecimal(node, ".//ram:ApplicableTradePaymentPenaltyTerms/ram:ActualPenaltyAmount", nsmgr, null); PaymentTermsType? paymentTermsType = discountPercent.HasValue ? PaymentTermsType.Skonto : penaltyPercent.HasValue ? PaymentTermsType.Verzug : (PaymentTermsType?)null; diff --git a/ZUGFeRD/InvoiceValidator.cs b/ZUGFeRD/InvoiceValidator.cs index e7231c10..e596c7a5 100644 --- a/ZUGFeRD/InvoiceValidator.cs +++ b/ZUGFeRD/InvoiceValidator.cs @@ -127,7 +127,7 @@ public static ValidationResult Validate(InvoiceDescriptor descriptor, ZUGFeRDVer // TODO // TODO ausgeben: Recalculating tax basis for tax percentages: [Key{percentage=7.00, code=[VAT] Value added tax, category=[S] Standard rate}, Key{percentage=19.00, code=[VAT] Value added tax, category=[S] Standard rate}] - retval.Messages.Add(String.Format("Recalculated tax basis = {0:0.0000}", lineTotal - allowanceTotal)); + retval.Messages.Add(String.Format("Recalculated tax basis = {0:0.0000}", lineTotal - allowanceTotal + chargeTotal)); retval.Messages.Add("Calculating tax total..."); decimal taxTotal = 0.0m;