diff --git a/.gitignore b/.gitignore index 50af58d..bd7c37c 100644 --- a/.gitignore +++ b/.gitignore @@ -337,3 +337,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ documentation/xRechnung/XRechnung 2.3.1/Schematron/generated/ubl-inv-br-de-20-paymentmeans-code59-test-106-code-urn_cen.eu_en16931_2017_compliant_urn_xoev-de_kosit_standard_xrechnung_2.3_conformant_urn_xoev-de_kosit_extension_xrechnung_2.3.xml +/documentation/Factur-X 1.0.06 2022 03 01 - FINAL EN diff --git a/ZUGFeRD-Test/ZUGFeRD21Tests.cs b/ZUGFeRD-Test/ZUGFeRD21Tests.cs index 62f4c00..a446251 100644 --- a/ZUGFeRD-Test/ZUGFeRD21Tests.cs +++ b/ZUGFeRD-Test/ZUGFeRD21Tests.cs @@ -205,13 +205,22 @@ public void TestElectronicAddress() Assert.AreEqual(loadedInvoice.BuyerElectronicAddress.ElectronicAddressSchemeID, ElectronicAddressSchemeIdentifiers.LuxemburgVatNumber); } // !TestElectronicAddress() + [TestMethod] public void TestMinimumInvoice() { InvoiceDescriptor desc = this.InvoiceProvider.CreateInvoice(); desc.Invoicee = new Party() // this information will not be stored in the output file since it is available in Extended profile only { - Name = "Test" + Name = "Invoicee" + }; + desc.Seller = new Party() + { + Name = "Seller", + SpecifiedLegalOrganization = new LegalOrganization() + { + TradingBusinessName = "Trading business name for seller party" + } }; desc.TaxBasisAmount = 73; // this information will not be stored in the output file since it is available in Extended profile only MemoryStream ms = new MemoryStream(); @@ -221,6 +230,9 @@ public void TestMinimumInvoice() InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms); Assert.AreEqual(loadedInvoice.Invoicee, null); + Assert.AreNotEqual(loadedInvoice.Seller, null); + Assert.AreNotEqual(loadedInvoice.Seller.SpecifiedLegalOrganization, null); + Assert.AreEqual(loadedInvoice.Seller.SpecifiedLegalOrganization.TradingBusinessName, ""); } // !TestMinimumInvoice() diff --git a/ZUGFeRD/InvoiceDescriptor21Writer.cs b/ZUGFeRD/InvoiceDescriptor21Writer.cs index 949ffe3..1b5f018 100644 --- a/ZUGFeRD/InvoiceDescriptor21Writer.cs +++ b/ZUGFeRD/InvoiceDescriptor21Writer.cs @@ -501,12 +501,12 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream) #region SellerTradeParty // BT-31: this.Descriptor.SellerTaxRegistration - _writeOptionalParty(Writer, "ram:SellerTradeParty", this.Descriptor.Seller, this.Descriptor.SellerContact, this.Descriptor.SellerElectronicAddress, this.Descriptor.SellerTaxRegistration, descriptor.Profile, ALL_PROFILES, Profile.Extended); + _writeOptionalParty(Writer, PartyTypes.SellerTradeParty, this.Descriptor.Seller, this.Descriptor.SellerContact, this.Descriptor.SellerElectronicAddress, this.Descriptor.SellerTaxRegistration); #endregion #region BuyerTradeParty // BT-48: this.Descriptor.BuyerTaxRegistration - _writeOptionalParty(Writer, "ram:BuyerTradeParty", this.Descriptor.Buyer, this.Descriptor.BuyerContact, this.Descriptor.BuyerElectronicAddress, this.Descriptor.BuyerTaxRegistration, descriptor.Profile, ALL_PROFILES, Profile.Comfort | Profile.Extended); + _writeOptionalParty(Writer, PartyTypes.BuyerTradeParty, this.Descriptor.Buyer, this.Descriptor.BuyerContact, this.Descriptor.BuyerElectronicAddress, this.Descriptor.BuyerTaxRegistration); #endregion // TODO: implement SellerTaxRepresentativeTradeParty @@ -630,9 +630,9 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream) #region ApplicableHeaderTradeDelivery Writer.WriteStartElement("ram:ApplicableHeaderTradeDelivery"); // Pflichteintrag - _writeOptionalParty(Writer, "ram:ShipToTradeParty", this.Descriptor.ShipTo, profile: Profile.Extended | Profile.XRechnung1 | Profile.XRechnung, legalOrganizationProfile: Profile.Extended, tradingBusinessNameProfile: Profile.Extended); + _writeOptionalParty(Writer, PartyTypes.ShipToTradeParty, this.Descriptor.ShipTo); //ToDo: UltimateShipToTradeParty - _writeOptionalParty(Writer, "ram:ShipFromTradeParty", this.Descriptor.ShipFrom, profile: Profile.Extended, legalOrganizationProfile: Profile.Extended, tradingBusinessNameProfile: Profile.Extended); // ShipFrom shall not be written in XRechnung profiles + _writeOptionalParty(Writer, PartyTypes.ShipFromTradeParty, this.Descriptor.ShipFrom); // ShipFrom shall not be written in XRechnung profiles #region ActualDeliverySupplyChainEvent if (this.Descriptor.ActualDeliveryDate.HasValue) @@ -710,12 +710,10 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream) Writer.WriteElementString("ram:InvoiceCurrencyCode", this.Descriptor.Currency.EnumToString()); // 7. InvoiceeTradeParty (optional) - _writeOptionalParty(Writer, "ram:InvoiceeTradeParty", this.Descriptor.Invoicee, profile: Profile.Extended, - legalOrganizationProfile: Profile.Extended, tradingBusinessNameProfile: Profile.Extended); + _writeOptionalParty(Writer, PartyTypes.InvoiceeTradeParty, this.Descriptor.Invoicee); // 8. PayeeTradeParty (optional) - _writeOptionalParty(Writer, "ram:PayeeTradeParty", this.Descriptor.Payee, profile: ALL_PROFILES ^ Profile.Minimum, - legalOrganizationProfile: ALL_PROFILES, tradingBusinessNameProfile: Profile.BasicWL | Profile.Basic | Profile.Comfort | Profile.Extended); + _writeOptionalParty(Writer, PartyTypes.PayeeTradeParty, this.Descriptor.Payee); #region SpecifiedTradeSettlementPaymentMeans // 10. SpecifiedTradeSettlementPaymentMeans (optional) @@ -1163,12 +1161,11 @@ private void _writeNotes(ProfileAwareXmlTextWriter writer, List notes) } } // !_writeNotes() - private void _writeOptionalLegalOrganization(ProfileAwareXmlTextWriter writer, string legalOrganizationTag, LegalOrganization legalOrganization, - Profile profile = Profile.Unknown, Profile tradingBusinessNameProfile = Profile.Unknown) + private void _writeOptionalLegalOrganization(ProfileAwareXmlTextWriter writer, string legalOrganizationTag, LegalOrganization legalOrganization, PartyTypes partyType = PartyTypes.Unknown) { if (legalOrganization != null) { - writer.WriteStartElement(legalOrganizationTag, profile); + writer.WriteStartElement(legalOrganizationTag, this.Descriptor.Profile); if (legalOrganization.ID != null) { if (!String.IsNullOrEmpty(legalOrganization.ID.ID) && !String.IsNullOrEmpty(legalOrganization.ID.SchemeID.EnumToString())) @@ -1184,19 +1181,65 @@ private void _writeOptionalLegalOrganization(ProfileAwareXmlTextWriter writer, s } if (!String.IsNullOrEmpty(legalOrganization.TradingBusinessName)) { - writer.WriteElementString("ram:TradingBusinessName", legalOrganization.TradingBusinessName, tradingBusinessNameProfile); + // filter according to https://github.com/stephanstapel/ZUGFeRD-csharp/pull/221 + if (((partyType == PartyTypes.SellerTradeParty) && (this.Descriptor.Profile != Profile.Minimum)) || + ((partyType == PartyTypes.PayeeTradeParty) && (this.Descriptor.Profile != Profile.Minimum)) || + ((partyType == PartyTypes.BuyerTradeParty) && (this.Descriptor.Profile == Profile.Comfort)) || + ((partyType == PartyTypes.BuyerTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + (this.Descriptor.Profile == Profile.Extended) /* remaining party types */ + ) + { + writer.WriteElementString("ram:TradingBusinessName", legalOrganization.TradingBusinessName, this.Descriptor.Profile); + } } } writer.WriteEndElement(); } } - private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, string partyTag, Party party, Contact contact = null, ElectronicAddress ElectronicAddress = null, List taxRegistrations = null, - Profile profile = Profile.Unknown, Profile legalOrganizationProfile = Profile.Unknown, Profile tradingBusinessNameProfile = Profile.Unknown) + private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes partyType, Party party, Contact contact = null, ElectronicAddress ElectronicAddress = null, List taxRegistrations = null) { + // filter according to https://github.com/stephanstapel/ZUGFeRD-csharp/pull/221 + if ( ((partyType == PartyTypes.UltimateShipToTradeParty) && (this.Descriptor.Profile != Profile.Extended)) || + ((partyType == PartyTypes.ShipToTradeParty) && (this.Descriptor.Profile != Profile.Extended)) || + ((partyType == PartyTypes.ShipFromTradeParty) && (this.Descriptor.Profile != Profile.Extended)) || + ((partyType == PartyTypes.InvoiceeTradeParty) && (this.Descriptor.Profile != Profile.Extended)) || + ((partyType == PartyTypes.PayeeTradeParty) && (this.Descriptor.Profile == Profile.Minimum)) || + ((partyType == PartyTypes.PayerTradeParty) && (this.Descriptor.Profile != Profile.Extended)) + ) + { + return; + } + if (party != null) { - writer.WriteStartElement(partyTag, profile); + switch (partyType) + { + case PartyTypes.SellerTradeParty: + writer.WriteStartElement("ram:SellerTradeParty", this.Descriptor.Profile); + break; + case PartyTypes.BuyerTradeParty: + writer.WriteStartElement("ram:BuyerTradeParty", this.Descriptor.Profile); + break; + case PartyTypes.ShipToTradeParty: + writer.WriteStartElement("ram:ShipToTradeParty", this.Descriptor.Profile); + break; + case PartyTypes.UltimateShipToTradeParty: + writer.WriteStartElement("ram:UltimateShipToTradeParty", this.Descriptor.Profile); + break; + case PartyTypes.ShipFromTradeParty: + writer.WriteStartElement("ram:ShipFromTradeParty", this.Descriptor.Profile); + break; + case PartyTypes.InvoiceeTradeParty: + writer.WriteStartElement("ram:InvoiceeTradeParty", this.Descriptor.Profile); + break; + case PartyTypes.PayeeTradeParty: + writer.WriteStartElement("ram:PayeeTradeParty", this.Descriptor.Profile); + break; + case PartyTypes.PayerTradeParty: + writer.WriteStartElement("ram:PayerTradeParty", this.Descriptor.Profile); + break; + } if (party.ID != null) { @@ -1226,9 +1269,26 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, string partyT writer.WriteElementString("ram:Name", party.Name); } - if ((party.SpecifiedLegalOrganization != null) && ((profile & legalOrganizationProfile) == profile)) + if (party.SpecifiedLegalOrganization != null) { - _writeOptionalLegalOrganization(writer, "ram:SpecifiedLegalOrganization", party.SpecifiedLegalOrganization, tradingBusinessNameProfile); + // filter according to https://github.com/stephanstapel/ZUGFeRD-csharp/pull/221 + if (((partyType == PartyTypes.ShipToTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.ShipFromTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.SalesAgentTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.UltimateShipToTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.BuyerTaxRepresentativeTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.ProductEndUserTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.BuyerAgentTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.InvoicerTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.InvoiceeTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.PayerTradeParty) && (this.Descriptor.Profile == Profile.Extended)) || + ((partyType == PartyTypes.SellerTradeParty) /* all profiles */ ) || + ((partyType == PartyTypes.BuyerTradeParty) /* all profiles */ ) || + ((partyType == PartyTypes.PayeeTradeParty) /* all profiles */ ) + ) + { + _writeOptionalLegalOrganization(writer, "ram:SpecifiedLegalOrganization", party.SpecifiedLegalOrganization, partyType); + } } if (contact != null) diff --git a/ZUGFeRD/PartyTypes.cs b/ZUGFeRD/PartyTypes.cs index 8d9c37f..5f31ff6 100644 --- a/ZUGFeRD/PartyTypes.cs +++ b/ZUGFeRD/PartyTypes.cs @@ -6,6 +6,19 @@ namespace s2industries.ZUGFeRD { internal enum PartyTypes { - SellerTradeParty + Unknown, + SellerTradeParty, + BuyerTradeParty, + ShipToTradeParty, + UltimateShipToTradeParty, + ShipFromTradeParty, + InvoiceeTradeParty, + PayeeTradeParty, + SalesAgentTradeParty, + BuyerTaxRepresentativeTradeParty, + ProductEndUserTradeParty, + BuyerAgentTradeParty, + InvoicerTradeParty, + PayerTradeParty } }