diff --git a/ZUGFeRD-Test/XRechnungUBLTests.cs b/ZUGFeRD-Test/XRechnungUBLTests.cs index 9d5b14b..9f18623 100644 --- a/ZUGFeRD-Test/XRechnungUBLTests.cs +++ b/ZUGFeRD-Test/XRechnungUBLTests.cs @@ -184,8 +184,10 @@ public void TestInvoiceWithAttachment() } } // !TestInvoiceWithAttachment() + + [TestMethod] - public void TestActualDeliveryDate() + public void TestActualDeliveryDateWithoutDeliveryAddress() { DateTime timestamp = new DateTime(2024,08,11); InvoiceDescriptor desc = this.InvoiceProvider.CreateInvoice(); @@ -200,7 +202,87 @@ public void TestActualDeliveryDate() // test the ActualDeliveryDate Assert.AreEqual(timestamp, loadedInvoice.ActualDeliveryDate); - } // !TestActualDeliveryDate() + Assert.IsNull(loadedInvoice.ShipTo); + } // !TestActualDeliveryDateWithoutDeliveryAddress() + + + + [TestMethod] + public void TestActualDeliveryDateWithDeliveryAddress() + { + DateTime timestamp = new DateTime(2024, 08, 11); + InvoiceDescriptor desc = this.InvoiceProvider.CreateInvoice(); + MemoryStream ms = new MemoryStream(); + + desc.ActualDeliveryDate = timestamp; + + string shipToID = "1234"; + string shipToName = "Test ShipTo Name"; + CountryCodes shipToCountry = CountryCodes.DE; + + desc.ShipTo = new Party() + { + ID = new GlobalID() + { + ID = shipToID + }, + Name = shipToName, + Country = shipToCountry + }; + + desc.Save(ms, ZUGFeRDVersion.Version23, Profile.XRechnung, ZUGFeRDFormats.UBL); + ms.Seek(0, SeekOrigin.Begin); + + InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms); + + // test the ActualDeliveryDate + Assert.AreEqual(timestamp, loadedInvoice.ActualDeliveryDate); + Assert.IsNotNull(loadedInvoice.ShipTo); + Assert.IsNotNull(loadedInvoice.ShipTo.ID); + Assert.AreEqual(loadedInvoice.ShipTo.ID.ID, shipToID); + Assert.AreEqual(loadedInvoice.ShipTo.Name, shipToName); + Assert.AreEqual(loadedInvoice.ShipTo.Country, shipToCountry); + } // !TestActualDeliveryDateWithDeliveryAddress() + + + + [TestMethod] + public void TestActualDeliveryAddressWithoutDeliveryDate() + { + InvoiceDescriptor desc = this.InvoiceProvider.CreateInvoice(); + MemoryStream ms = new MemoryStream(); + + // ActualDeliveryDate is set by the InvoiceProvider, we are resetting it to the default value + desc.ActualDeliveryDate = null; + + string shipToID = "1234"; + string shipToName = "Test ShipTo Name"; + CountryCodes shipToCountry = CountryCodes.DE; + + desc.ShipTo = new Party() + { + ID = new GlobalID() + { + ID = shipToID + }, + Name = shipToName, + Country = shipToCountry + }; + + desc.Save(ms, ZUGFeRDVersion.Version23, Profile.XRechnung, ZUGFeRDFormats.UBL); + ms.Seek(0, SeekOrigin.Begin); + + InvoiceDescriptor loadedInvoice = InvoiceDescriptor.Load(ms); + + // test the ActualDeliveryDate + Assert.IsNull(loadedInvoice.ActualDeliveryDate); + Assert.IsNotNull(loadedInvoice.ShipTo); + Assert.IsNotNull(loadedInvoice.ShipTo.ID); + Assert.AreEqual(loadedInvoice.ShipTo.ID.ID, shipToID); + Assert.AreEqual(loadedInvoice.ShipTo.Name, shipToName); + Assert.AreEqual(loadedInvoice.ShipTo.Country, shipToCountry); + } // !TestActualDeliveryAddressWithoutDeliveryDate() + [TestMethod] public void TestTaxTypes() diff --git a/ZUGFeRD/InvoiceDescriptor.cs b/ZUGFeRD/InvoiceDescriptor.cs index 1d9705b..a888f40 100644 --- a/ZUGFeRD/InvoiceDescriptor.cs +++ b/ZUGFeRD/InvoiceDescriptor.cs @@ -118,6 +118,7 @@ public class InvoiceDescriptor /// /// Information about the buyer + /// BG-7 /// public Party Buyer { get; set; } @@ -128,10 +129,24 @@ public class InvoiceDescriptor /// public Contact BuyerContact { get; set; } public List BuyerTaxRegistration { get; set; } = new List(); + + /// + /// Buyer electronic address + /// BT-49 + /// public ElectronicAddress BuyerElectronicAddress { get; set; } + + /// + /// BG-4 + /// public Party Seller { get; set; } public Contact SellerContact { get; set; } public List SellerTaxRegistration { get; set; } = new List(); + + /// + /// Seller electronic address + /// BT-34 + /// public ElectronicAddress SellerElectronicAddress { get; set; } /// diff --git a/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs b/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs index 824e9a6..2a69b64 100644 --- a/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs +++ b/ZUGFeRD/InvoiceDescriptor22UBLWriter.cs @@ -194,15 +194,66 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream, ZUGFeRDFo #region SellerTradeParty - //AccountingSupplierParty + + // AccountingSupplierParty = PartyTypes.SellerTradeParty _writeOptionalParty(Writer, PartyTypes.SellerTradeParty, this.Descriptor.Seller, this.Descriptor.SellerContact, this.Descriptor.SellerElectronicAddress, this.Descriptor.SellerTaxRegistration); #endregion #region BuyerTradeParty - //AccountingCustomerParty + //AccountingCustomerParty = PartyTypes.BuyerTradeParty _writeOptionalParty(Writer, PartyTypes.BuyerTradeParty, this.Descriptor.Buyer, this.Descriptor.BuyerContact, this.Descriptor.BuyerElectronicAddress, this.Descriptor.BuyerTaxRegistration); #endregion - + + + // Deliery = ShipToTradeParty + if ((this.Descriptor.ShipTo != null) || (this.Descriptor.ActualDeliveryDate.HasValue)) + { + Writer.WriteStartElement("cac", "Delivery"); + + if (this.Descriptor.ActualDeliveryDate.HasValue) + { + Writer.WriteStartElement("cbc", "ActualDeliveryDate"); + Writer.WriteValue(_formatDate(this.Descriptor.ActualDeliveryDate.Value, false, true)); + Writer.WriteEndElement(); // !ActualDeliveryDate + } + + if (this.Descriptor.ShipTo != null) + { + Writer.WriteStartElement("cac", "DeliveryLocation"); + + if (this.Descriptor.ShipTo.ID != null) // despite this is a mandatory field, the component should not throw an exception if this is not the case + { + Writer.WriteOptionalElementString("cbc", "ID", this.Descriptor.ShipTo.ID.ID); + } + Writer.WriteStartElement("cac", "Address"); + Writer.WriteOptionalElementString("cbc", "StreetName", this.Descriptor.ShipTo.Street); + Writer.WriteOptionalElementString("cbc", "AdditionalStreetName", this.Descriptor.ShipTo.AddressLine3); + Writer.WriteOptionalElementString("cbc", "CityName", this.Descriptor.ShipTo.City); + Writer.WriteOptionalElementString("cbc", "PostalZone", this.Descriptor.ShipTo.Postcode); + Writer.WriteOptionalElementString("cbc", "CountrySubentity", this.Descriptor.ShipTo.CountrySubdivisionName); + Writer.WriteStartElement("cac", "Country"); + if (this.Descriptor.ShipTo.Country != CountryCodes.Unknown) + { + Writer.WriteElementString("cbc", "IdentificationCode", this.Descriptor.ShipTo.Country.ToString()); + } + Writer.WriteEndElement(); //!Country + Writer.WriteEndElement(); // !Address + Writer.WriteEndElement(); // !DeliveryLocation + + if (!string.IsNullOrWhiteSpace(this.Descriptor.ShipTo.Name)) + { + Writer.WriteStartElement("cac", "DeliveryParty"); + Writer.WriteStartElement("cac", "PartyName"); + Writer.WriteStartElement("cbc", "Name"); + Writer.WriteValue(this.Descriptor.ShipTo.Name); + Writer.WriteEndElement(); // !Name + Writer.WriteEndElement(); // !PartyName + Writer.WriteEndElement(); // !DeliveryParty + } + } + } + + #region AllowanceCharge foreach (TradeAllowanceCharge tradeAllowanceCharge in descriptor.GetTradeAllowanceCharges()) { @@ -243,16 +294,7 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream, ZUGFeRDFo Writer.WriteEndElement(); // !AllowanceCharge() } - #endregion - - if (this.Descriptor.ActualDeliveryDate.HasValue) - { - Writer.WriteStartElement("cac", "Delivery"); - Writer.WriteStartElement("cbc", "ActualDeliveryDate"); - Writer.WriteValue(_formatDate(this.Descriptor.ActualDeliveryDate.Value, false, true)); - Writer.WriteEndElement(); // !ActualDeliveryDate - Writer.WriteEndElement(); // !Delivery - } + #endregion // PaymentMeans if (this.Descriptor.PaymentMeans != null) @@ -546,39 +588,8 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes pa break; case PartyTypes.ShipFromTradeParty: return; - //case PartyTypes.ShipToTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.UltimateShipToTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.ShipFromTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.InvoiceeTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.PayeeTradeParty: - // if (this.Descriptor.Profile == Profile.Minimum) { return; } // party is written for all profiles but minimum - // break; - //case PartyTypes.SalesAgentTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.BuyerTaxRepresentativeTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.ProductEndUserTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.BuyerAgentTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.InvoicerTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; - //case PartyTypes.PayerTradeParty: - // if ((this.Descriptor.Profile != Profile.Extended) && (this.Descriptor.Profile != Profile.XRechnung1) && (this.Descriptor.Profile != Profile.XRechnung)) { return; } // extended, XRechnung1, XRechnung profile only - // break; + case PartyTypes.ShipToTradeParty: // ship to trade party/ cac:Delivery has very minimized information, thus generic _writeOptionalParty() cannot be used + return; default: return; } diff --git a/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs b/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs index 8de3947..effd905 100644 --- a/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs +++ b/ZUGFeRD/InvoiceDescriptor23CIIWriter.cs @@ -1414,7 +1414,7 @@ private void _writeOptionalLegalOrganization(ProfileAwareXmlTextWriter writer, s } // !_writeOptionalLegalOrganization() - private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes partyType, Party party, Profile profile, Contact contact = null, ElectronicAddress ElectronicAddress = null, List taxRegistrations = null) + private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes partyType, Party party, Profile profile, Contact contact = null, ElectronicAddress electronicAddress = null, List taxRegistrations = null) { if (party == null) { @@ -1512,14 +1512,14 @@ private void _writeOptionalParty(ProfileAwareXmlTextWriter writer, PartyTypes pa writer.WriteEndElement(); // !PostalTradeAddress } - if (ElectronicAddress != null) + if (electronicAddress != null) { - if (!String.IsNullOrWhiteSpace(ElectronicAddress.Address)) + if (!String.IsNullOrWhiteSpace(electronicAddress.Address)) { writer.WriteStartElement("ram", "URIUniversalCommunication"); writer.WriteStartElement("ram", "URIID"); - writer.WriteAttributeString("schemeID", ElectronicAddress.ElectronicAddressSchemeID.EnumToString()); - writer.WriteValue(ElectronicAddress.Address); + writer.WriteAttributeString("schemeID", electronicAddress.ElectronicAddressSchemeID.EnumToString()); + writer.WriteValue(electronicAddress.Address); writer.WriteEndElement(); writer.WriteEndElement(); }