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.Test/ZUGFeRD22Tests.cs b/ZUGFeRD.Test/ZUGFeRD22Tests.cs index e3842964..dac8819c 100644 --- a/ZUGFeRD.Test/ZUGFeRD22Tests.cs +++ b/ZUGFeRD.Test/ZUGFeRD22Tests.cs @@ -162,6 +162,142 @@ public void TestExtendedInvoiceWithIncludedItems() } // !TestExtendedInvoiceWithIncludedItems() + /// + /// Roundtrip test for TradeLineItem product identification fields added in PR #863: + /// IndustryAssignedID, ModelID, BatchID, BrandName, ModelName + /// These fields are Extended profile only (CII 2.3). + /// + [TestMethod] + public void TestTradeLineItemProductFieldsRoundtrip() + { + string path = @"..\..\..\..\demodata\zugferd21\zugferd_2p1_EXTENDED_Warenrechnung-factur-x.xml"; + path = _makeSurePathIsCrossPlatformCompatible(path); + + Stream s = File.Open(path, FileMode.Open); + InvoiceDescriptor desc = InvoiceDescriptor.Load(s); + s.Close(); + + desc.TradeLineItems.Clear(); + + var lineItem = desc.AddTradeLineItem( + lineID: "1", + name: "Test Product", + billedQuantity: 10m, + unitCode: QuantityCodes.H87, + netUnitPrice: 5.0m, + grossUnitPrice: 5.0m, + categoryCode: TaxCategoryCodes.S, + taxPercent: 19.0m, + taxType: TaxTypes.VAT); + + lineItem.SellerAssignedID = "SELLER-001"; + lineItem.BuyerAssignedID = "BUYER-001"; + lineItem.IndustryAssignedID = "IND-12345"; + lineItem.ModelID = "MDL-67890"; + lineItem.BatchID = "BATCH-2025-001"; + lineItem.BrandName = "TestBrand"; + lineItem.ModelName = "TestModel Pro"; + + MemoryStream ms = new MemoryStream(); + desc.Save(ms, ZUGFeRDVersion.Version23, Profile.Extended); + ms.Seek(0, SeekOrigin.Begin); + + InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms); + + Assert.AreEqual(1, loadedInvoice.TradeLineItems.Count); + var loadedLineItem = loadedInvoice.TradeLineItems[0]; + Assert.AreEqual("Test Product", loadedLineItem.Name); + Assert.AreEqual("SELLER-001", loadedLineItem.SellerAssignedID); + Assert.AreEqual("BUYER-001", loadedLineItem.BuyerAssignedID); + Assert.AreEqual("IND-12345", loadedLineItem.IndustryAssignedID); + Assert.AreEqual("MDL-67890", loadedLineItem.ModelID); + Assert.AreEqual("BATCH-2025-001", loadedLineItem.BatchID); + Assert.AreEqual("TestBrand", loadedLineItem.BrandName); + Assert.AreEqual("TestModel Pro", loadedLineItem.ModelName); + } // !TestTradeLineItemProductFieldsRoundtrip() + + + /// + /// Roundtrip test for IncludedReferencedProduct fields added in PR #863: + /// GlobalID, SellerAssignedID, BuyerAssignedID, IndustryAssignedID, Description + /// + [TestMethod] + public void TestIncludedReferencedProductFieldsRoundtrip() + { + string path = @"..\..\..\..\demodata\zugferd21\zugferd_2p1_EXTENDED_Warenrechnung-factur-x.xml"; + path = _makeSurePathIsCrossPlatformCompatible(path); + + Stream s = File.Open(path, FileMode.Open); + InvoiceDescriptor desc = InvoiceDescriptor.Load(s); + s.Close(); + + desc.TradeLineItems.Clear(); + + var lineItem = desc.AddTradeLineItem( + lineID: "1", + name: "Main Product", + billedQuantity: 5m, + unitCode: QuantityCodes.H87, + netUnitPrice: 20.0m, + grossUnitPrice: 20.0m, + categoryCode: TaxCategoryCodes.S, + taxPercent: 19.0m, + taxType: TaxTypes.VAT); + + // Add included product with all new fields set + lineItem.IncludedReferencedProducts.Add(new IncludedReferencedProduct() + { + GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4012345999999"), + SellerAssignedID = "SEL-REF-001", + BuyerAssignedID = "BUY-REF-001", + IndustryAssignedID = "IND-REF-001", + Name = "Included Part A", + Description = "Description of included part A", + UnitQuantity = 2, + UnitCode = QuantityCodes.C62 + }); + + // Add a second included product with minimal fields + lineItem.IncludedReferencedProducts.Add(new IncludedReferencedProduct() + { + Name = "Included Part B", + Description = "Description of included part B" + }); + + MemoryStream ms = new MemoryStream(); + desc.Save(ms, ZUGFeRDVersion.Version23, Profile.Extended); + ms.Seek(0, SeekOrigin.Begin); + + InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms); + + Assert.AreEqual(1, loadedInvoice.TradeLineItems.Count); + var loadedLineItem = loadedInvoice.TradeLineItems[0]; + Assert.AreEqual(2, loadedLineItem.IncludedReferencedProducts.Count); + + // Verify first included product (all fields) + var product1 = loadedLineItem.IncludedReferencedProducts[0]; + Assert.AreEqual(GlobalIDSchemeIdentifiers.EAN, product1.GlobalID.SchemeID); + Assert.AreEqual("4012345999999", product1.GlobalID.ID); + Assert.AreEqual("SEL-REF-001", product1.SellerAssignedID); + Assert.AreEqual("BUY-REF-001", product1.BuyerAssignedID); + Assert.AreEqual("IND-REF-001", product1.IndustryAssignedID); + Assert.AreEqual("Included Part A", product1.Name); + Assert.AreEqual("Description of included part A", product1.Description); + Assert.AreEqual(2m, product1.UnitQuantity); + Assert.AreEqual(QuantityCodes.C62, product1.UnitCode); + + // Verify second included product (minimal fields) + var product2 = loadedLineItem.IncludedReferencedProducts[1]; + Assert.AreEqual("Included Part B", product2.Name); + Assert.AreEqual("Description of included part B", product2.Description); + Assert.AreEqual("", product2.SellerAssignedID); + Assert.AreEqual("", product2.BuyerAssignedID); + Assert.AreEqual("", product2.IndustryAssignedID); + Assert.AreEqual(false, product2.UnitQuantity.HasValue); + Assert.IsNull(product2.UnitCode); + } // !TestIncludedReferencedProductFieldsRoundtrip() + + [TestMethod] public void TestReferenceEReportingFacturXInvoice() { diff --git a/ZUGFeRD/InvoiceDescriptor1Reader.cs b/ZUGFeRD/InvoiceDescriptor1Reader.cs index 277b99dd..77b5a8c6 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; @@ -478,12 +478,14 @@ private static Party _nodeAsParty(XmlNode baseNode, string xpath, XmlNamespaceMa if (!String.IsNullOrWhiteSpace(lineTwo)) { + retval.Street2 = lineOne; retval.ContactName = lineOne; - retval.Street = lineOne; + retval.Street = lineTwo; } else { retval.Street = lineOne; + retval.Street2 = null; retval.ContactName = null; } diff --git a/ZUGFeRD/InvoiceDescriptor1Writer.cs b/ZUGFeRD/InvoiceDescriptor1Writer.cs index d17c2300..b8560e95 100644 --- a/ZUGFeRD/InvoiceDescriptor1Writer.cs +++ b/ZUGFeRD/InvoiceDescriptor1Writer.cs @@ -952,8 +952,10 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, string prefix _writeOptionalContact(writer, "ram", "DefinedTradeContact", Contact); writer.WriteStartElement("ram", "PostalTradeAddress"); writer.WriteOptionalElementString("ram", "PostcodeCode", Party.Postcode); - writer.WriteOptionalElementString("ram", "LineOne", string.IsNullOrWhiteSpace(Party.ContactName) ? Party.Street : Party.ContactName); - if (!string.IsNullOrWhiteSpace(Party.ContactName)) { writer.WriteOptionalElementString("ram", "LineTwo", Party.Street); } + string lineOneValue = !string.IsNullOrWhiteSpace(Party.Street2) ? Party.Street2 : (!string.IsNullOrWhiteSpace(Party.ContactName) ? Party.ContactName : Party.Street); + string lineTwoValue = (!string.IsNullOrWhiteSpace(Party.Street2) || !string.IsNullOrWhiteSpace(Party.ContactName)) ? Party.Street : null; + writer.WriteOptionalElementString("ram", "LineOne", lineOneValue); + writer.WriteOptionalElementString("ram", "LineTwo", lineTwoValue); writer.WriteOptionalElementString("ram", "CityName", Party.City); if (Party.Country.HasValue) diff --git a/ZUGFeRD/InvoiceDescriptor20Reader.cs b/ZUGFeRD/InvoiceDescriptor20Reader.cs index e9ebc7bc..01061efe 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; @@ -644,13 +644,14 @@ private static Party _nodeAsParty(XmlNode baseNode, string xpath, XmlNamespaceMa if (!String.IsNullOrWhiteSpace(lineTwo)) { + retval.Street2 = lineOne; retval.ContactName = lineOne; retval.Street = lineTwo; } else { retval.Street = lineOne; - retval.ContactName = null; + retval.Street2 = null; } retval.AddressLine3 = XmlUtils.NodeAsString(node, "ram:PostalTradeAddress/ram:LineThree", nsmgr); diff --git a/ZUGFeRD/InvoiceDescriptor20Writer.cs b/ZUGFeRD/InvoiceDescriptor20Writer.cs index 16d823f7..948cfa2c 100644 --- a/ZUGFeRD/InvoiceDescriptor20Writer.cs +++ b/ZUGFeRD/InvoiceDescriptor20Writer.cs @@ -1247,8 +1247,10 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, string prefix writer.WriteStartElement("ram", "PostalTradeAddress"); writer.WriteOptionalElementString("ram", "PostcodeCode", party.Postcode); - writer.WriteOptionalElementString("ram", "LineOne", string.IsNullOrWhiteSpace(party.ContactName) ? party.Street : party.ContactName); - if (!string.IsNullOrWhiteSpace(party.ContactName)) { writer.WriteOptionalElementString("ram", "LineTwo", party.Street); } + string lineOneValue = !string.IsNullOrWhiteSpace(party.Street2) ? party.Street2 : (!string.IsNullOrWhiteSpace(party.ContactName) ? party.ContactName : party.Street); + string lineTwoValue = (!string.IsNullOrWhiteSpace(party.Street2) || !string.IsNullOrWhiteSpace(party.ContactName)) ? party.Street : null; + writer.WriteOptionalElementString("ram", "LineOne", lineOneValue); + writer.WriteOptionalElementString("ram", "LineTwo", lineTwoValue); writer.WriteOptionalElementString("ram", "LineThree", party.AddressLine3); // BT-163 diff --git a/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs b/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs index 2c6d3866..1f4c6993 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()); @@ -1037,7 +1037,13 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes pa writer.WriteStartElement("cac", "PostalAddress"); _Writer.WriteOptionalElementString("cbc", "StreetName", party.Street); - _Writer.WriteOptionalElementString("cbc", "AdditionalStreetName", party.AddressLine3); + _Writer.WriteOptionalElementString("cbc", "AdditionalStreetName", party.Street2); + if (!string.IsNullOrWhiteSpace(party.AddressLine3)) + { + writer.WriteStartElement("cac", "AddressLine"); + _Writer.WriteOptionalElementString("cbc", "Line", party.AddressLine3); + writer.WriteEndElement(); //!AddressLine + } _Writer.WriteElementString("cbc", "CityName", party.City); _Writer.WriteElementString("cbc", "PostalZone", party.Postcode); _Writer.WriteOptionalElementString("cbc", "CountrySubentity", party.CountrySubdivisionName); diff --git a/ZUGFeRD/InvoiceDescriptor22UblReader.cs b/ZUGFeRD/InvoiceDescriptor22UblReader.cs index f086056d..a0032f97 100644 --- a/ZUGFeRD/InvoiceDescriptor22UblReader.cs +++ b/ZUGFeRD/InvoiceDescriptor22UblReader.cs @@ -309,8 +309,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 +534,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 +696,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 +798,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); @@ -855,24 +851,13 @@ private static Party _nodeAsAddressParty(XmlNode baseNode, string xpath, XmlName Party retval = new Party() { Street = XmlUtils.NodeAsString(node, "cbc:StreetName", nsmgr), - AddressLine3 = XmlUtils.NodeAsString(node, "cbc:AdditionalStreetName", nsmgr), + Street2 = XmlUtils.NodeAsString(node, "cbc:AdditionalStreetName", nsmgr), + AddressLine3 = XmlUtils.NodeAsString(node, "cac:AddressLine/cbc:Line", nsmgr), City = XmlUtils.NodeAsString(node, "cbc:CityName", nsmgr), Postcode = XmlUtils.NodeAsString(node, "cbc:PostalZone", nsmgr), CountrySubdivisionName = XmlUtils.NodeAsString(node, "cbc:CountrySubentity", nsmgr), Country = EnumExtensions.StringToNullableEnum(XmlUtils.NodeAsString(node, "cac:Country/cbc:IdentificationCode", nsmgr)), }; - string addressLine2 = XmlUtils.NodeAsString(node, "cac:AddressLine/cbc:Line", nsmgr); - if (!string.IsNullOrWhiteSpace(addressLine2)) - { - if (string.IsNullOrWhiteSpace(retval.AddressLine3)) - { - retval.AddressLine3 = addressLine2; - } - else if (!string.IsNullOrWhiteSpace(addressLine2) && string.IsNullOrWhiteSpace(retval.ContactName)) - { - retval.ContactName = addressLine2; - } - } return retval; } // !_nodeAsAddressParty() diff --git a/ZUGFeRD/InvoiceDescriptor23CIIReader.cs b/ZUGFeRD/InvoiceDescriptor23CIIReader.cs index 82f9e6f8..ba4314af 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; @@ -865,12 +865,14 @@ private static Party _nodeAsParty(XmlNode baseNode, string xpath, XmlNamespaceMa if (!String.IsNullOrWhiteSpace(lineTwo)) { + retval.Street2 = lineOne; retval.ContactName = lineOne; retval.Street = lineTwo; } else { retval.Street = lineOne; + retval.Street2 = null; retval.ContactName = null; } diff --git a/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs b/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs index b75a9de2..30f6fd33 100644 --- a/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs +++ b/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs @@ -1817,11 +1817,10 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes pa { writer.WriteStartElement("ram", "PostalTradeAddress"); writer.WriteOptionalElementString("ram", "PostcodeCode", party.Postcode); // buyer: BT-53 - writer.WriteOptionalElementString("ram", "LineOne", string.IsNullOrWhiteSpace(party.ContactName) ? party.Street : party.ContactName); // buyer: BT-50 - if (!string.IsNullOrWhiteSpace(party.ContactName)) - { - writer.WriteOptionalElementString("ram", "LineTwo", party.Street); // buyer: BT-51 - } + string lineOneValue = !string.IsNullOrWhiteSpace(party.Street2) ? party.Street2 : (!string.IsNullOrWhiteSpace(party.ContactName) ? party.ContactName : party.Street); + string lineTwoValue = (!string.IsNullOrWhiteSpace(party.Street2) || !string.IsNullOrWhiteSpace(party.ContactName)) ? party.Street : null; + writer.WriteOptionalElementString("ram", "LineOne", lineOneValue); // buyer: BT-50 + writer.WriteOptionalElementString("ram", "LineTwo", lineTwoValue); // buyer: BT-51 writer.WriteOptionalElementString("ram", "LineThree", party.AddressLine3); // buyer: BT-163 writer.WriteOptionalElementString("ram", "CityName", party.City); // buyer: BT-52 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; diff --git a/ZUGFeRD/Party.cs b/ZUGFeRD/Party.cs index dcc40bdf..64757f7c 100644 --- a/ZUGFeRD/Party.cs +++ b/ZUGFeRD/Party.cs @@ -60,9 +60,15 @@ public class Party /// /// Street name and number - /// e.g. used for BT-35 + /// e.g. used for BT-35, BT-50 /// public string Street { get; set; } + + /// + /// Additional address line (LineTwo in CII, AddressLine2 in UBL) + /// e.g. used for BT-36, BT-51 + /// + public string Street2 { get; set; } /// /// Global identifier ///