From dcd9012cd0172b9cc9958fb6ae3cf9a6224dc0cf Mon Sep 17 00:00:00 2001 From: Roland Fredenhagen Date: Tue, 12 May 2026 13:39:44 +0200 Subject: [PATCH 1/3] dont emit empty node for definedTradeContact --- .../ZUGFeRD/ZUGFeRD2PullProvider.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index ad87e52f..38d19c7d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -160,29 +160,31 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } if ((party.getContact() != null) && (isSender || profile == Profiles.getByName("EN16931") || profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) { - xml += ""; + String definedTradeContactXML = ""; if (party.getContact().getName() != null) { - xml += "" + definedTradeContactXML += "" + XMLTools.encodeXML(party.getContact().getName()) + ""; } if (party.getContact().getPhone() != null) { - xml += "" + definedTradeContactXML += "" + XMLTools.encodeXML(party.getContact().getPhone()) + "" + ""; } if ((party.getContact().getFax() != null) && (profile == Profiles.getByName("Extended"))) { - xml += "" + definedTradeContactXML += "" + XMLTools.encodeXML(party.getContact().getFax()) + "" + ""; } if (party.getContact().getEMail() != null) { - xml += "" + definedTradeContactXML += "" + XMLTools.encodeXML(party.getContact().getEMail()) + "" + ""; } - xml += ""; + if (!definedTradeContactXML.isEmpty()) { + xml += "" + definedTradeContactXML + ""; + } } xml += ""; From 04d06b14f0e45f174b46a76ac278d33220cbe2bd Mon Sep 17 00:00:00 2001 From: langfr Date: Wed, 13 May 2026 19:35:35 +0100 Subject: [PATCH 2/3] Support --- .../src/main/java/org/mustangproject/Invoice.java | 12 +++++++++++- .../ZUGFeRD/IExportableTransaction.java | 9 +++++++++ .../mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java | 5 +++++ .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 7 +++++++ .../java/org/mustangproject/ZUGFeRD/ZF2PushTest.java | 2 ++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index cbc00060..f9cb0557 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -42,7 +42,7 @@ public class Invoice implements IExportableTransaction { protected boolean testIndicator; protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null; protected Date issueDate = null, dueDate = null, deliveryDate = null; - protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null, invoicer = null, invoicee = null; + protected TradeParty sender = null, recipient = null, deliveryAddress = null, endCustomerDeliveryAddress = null, payee = null, invoicer = null, invoicee = null; protected ArrayList cashDiscounts = null; @JsonDeserialize(contentAs = Item.class) protected ArrayList ZFItems = null; @@ -790,6 +790,16 @@ public class Invoice implements IExportableTransaction { return this; } + @Override + public TradeParty getEndCustomerDeliveryAddress() { + return endCustomerDeliveryAddress; + } + + public Invoice setEndCustomerDeliveryAddress(TradeParty endCustomerDeliveryAddress) { + this.endCustomerDeliveryAddress = endCustomerDeliveryAddress; + return this; + } + @Override public TradeParty getPayee() { return this.payee; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index f3e87aab..9711c1b1 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -513,6 +513,15 @@ public interface IExportableTransaction { return null; } + /*** + * ultimate delivery address, i.e. ram:UltimateShipToTradeParty (only supported for zf2) + * + * @return the IZUGFeRDExportableTradeParty delivery address + */ + default IZUGFeRDExportableTradeParty getEndCustomerDeliveryAddress() { + return null; + } + /*** * payee / payment receiver, if different from seller, ram:Payee (only supported for zf2) * diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index ad87e52f..997ae462 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -732,6 +732,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { getTradePartyAsXML(this.trans.getDeliveryAddress(), false, true) + ""; } + if (this.trans.getEndCustomerDeliveryAddress() != null) { + xml += "" + + getTradePartyAsXML(this.trans.getEndCustomerDeliveryAddress(), false, true) + + ""; + } if (trans.getDeliveryDate() != null) { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index dfdeac49..b251bdb5 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -396,12 +396,19 @@ public class ZUGFeRDInvoiceImporter { XPath xpath = xpathFact.newXPath(); XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*"); NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + XPathExpression shipEx = xpath.compile("//*[local-name()=\"ShipToTradeParty\"]"); NodeList deliveryNodes = (NodeList) shipEx.evaluate(getDocument(), XPathConstants.NODESET); if (deliveryNodes.getLength() > 0) { zpp.setDeliveryAddress(new TradeParty(deliveryNodes)); } + XPathExpression ultimateShipEx = xpath.compile("//*[local-name()=\"UltimateShipToTradeParty\"]"); + NodeList ultimateDeliveryNodes = (NodeList) ultimateShipEx.evaluate(getDocument(), XPathConstants.NODESET); + if (ultimateDeliveryNodes.getLength() > 0) { + zpp.setEndCustomerDeliveryAddress(new TradeParty(ultimateDeliveryNodes)); + } + List includedNotes = new ArrayList<>(); //UBL... diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 3408b9a0..04fd4c12 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -605,6 +605,7 @@ public class ZF2PushTest extends TestCase { .setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurrenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurrenceTo)) .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setEmail("sender@test.org").setID(orgID).addVATID("DE0815")) .setDeliveryAddress(new TradeParty("just the other side of the street", "teststr.12a", "55232", "Entenhausen", "DE").addVATID("DE47110")) + .setEndCustomerDeliveryAddress(new TradeParty("Max Mustermann", "Glückswinkel 42", "98765", "Musterhausen", "DE")) .setContractReferencedDocument(contractID) .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addGlobalID(gln).setEmail("recipient@test.org").addVATID("DE4711") .setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")).setAdditionalAddress("Hinterhaus 3")) @@ -692,6 +693,7 @@ public class ZF2PushTest extends TestCase { assertNotNull(i.getInvoicer()); assertNotNull(i.getInvoicee()); + assertNotNull(i.getEndCustomerDeliveryAddress()); } catch (XPathExpressionException e) { fail("XPathExpressionException should not be raised"); } catch (ParseException e) { From 3655c1de7ac5c267282a0ac656fa944d98be7b98 Mon Sep 17 00:00:00 2001 From: langfr Date: Wed, 13 May 2026 22:15:02 +0100 Subject: [PATCH 3/3] Support --- library/src/main/java/org/mustangproject/Invoice.java | 10 ++++++++++ .../mustangproject/ZUGFeRD/IExportableTransaction.java | 4 ++++ .../mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java | 7 +++++++ .../mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java | 10 ++++++++++ .../java/org/mustangproject/ZUGFeRD/ZF2PushTest.java | 3 +++ 5 files changed, 34 insertions(+) diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index cbc00060..9f432f08 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -41,6 +41,7 @@ public class Invoice implements IExportableTransaction { protected boolean testIndicator; protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null; + protected String deliveryTypeCode; protected Date issueDate = null, dueDate = null, deliveryDate = null; protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null, invoicer = null, invoicee = null; protected ArrayList cashDiscounts = null; @@ -332,6 +333,15 @@ public class Invoice implements IExportableTransaction { return this; } + public String getDeliveryTypeCode() { + return deliveryTypeCode; + } + + public Invoice setDeliveryTypeCode(String deliveryTypeCode) { + this.deliveryTypeCode = deliveryTypeCode; + return this; + } + @Override public String getBuyerOrderReferencedDocumentID() { return buyerOrderReferencedDocumentID; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index f3e87aab..68937fd5 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -438,6 +438,10 @@ public interface IExportableTransaction { return null; } + default String getDeliveryTypeCode() { + return null; + } + /** * get the ID of the SellerOrderReferencedDocument, which sits in the * ApplicableSupplyChainTradeAgreement/ApplicableHeaderTradeAgreement diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index ad87e52f..65643279 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -661,6 +661,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += "" + getTradePartyAsXML(trans.getSender(), true, false) + ""; xml += "" + getTradePartyAsXML(trans.getRecipient(), false, false) + ""; + if (trans.getDeliveryTypeCode() != null && getProfile() == Profiles.getByName("Extended")) { + xml += "" + + "" + + trans.getDeliveryTypeCode() + + "" + + ""; + } if (trans.getSellerOrderReferencedDocumentID() != null && !trans.getSellerOrderReferencedDocumentID().trim().isEmpty()) { xml += "" + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index dfdeac49..a597b9fb 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -749,6 +749,16 @@ public class ZUGFeRDInvoiceImporter { NodeList headerTradeAgreementChilds = headerTradeAgreementNode.getChildNodes(); for (int agreementChildIndex = 0; agreementChildIndex < headerTradeAgreementChilds.getLength(); agreementChildIndex++) { if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null) { + if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("ApplicableTradeDeliveryTerms")) { + NodeList applicableTradeDeliveryTermsChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes(); + for (int applicableTradeDeliveryTermsChildIndex = 0; applicableTradeDeliveryTermsChildIndex < applicableTradeDeliveryTermsChilds.getLength(); applicableTradeDeliveryTermsChildIndex++) { + if ((applicableTradeDeliveryTermsChilds.item(applicableTradeDeliveryTermsChildIndex).getLocalName() != null) + && (applicableTradeDeliveryTermsChilds.item(applicableTradeDeliveryTermsChildIndex).getLocalName().equals("DeliveryTypeCode"))) { + zpp.setDeliveryTypeCode(XMLTools.trimOrNull(applicableTradeDeliveryTermsChilds.item(applicableTradeDeliveryTermsChildIndex))); + } + } + } + if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) { NodeList buyerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes(); for (int buyerOrderChildIndex = 0; buyerOrderChildIndex < buyerOrderChilds.getLength(); buyerOrderChildIndex++) { diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 3408b9a0..a7a64d57 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -623,6 +623,7 @@ public class ZF2PushTest extends TestCase { .setTenderReferencedDocument(dr1) .setDeliveryDate(sdf.parse("2020-11-02")).setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE) .setInvoiceReferencedDocumentID("abc123").addInvoiceReferencedDocument(new ReferencedDocument("abcd1234")) + .setDeliveryTypeCode("EXW") ); } catch (ParseException e) { e.printStackTrace(); @@ -652,6 +653,7 @@ public class ZF2PushTest extends TestCase { assertTrue(zi.getUTF8().contains(occurrenceFrom)); assertTrue(zi.getUTF8().contains(occurrenceTo)); assertTrue(zi.getUTF8().contains(contractID)); + assertTrue(zi.getUTF8().contains("EXW")); assertEquals(zi.importedInvoice.getZFItems()[0].getId(), "a123"); assertEquals(zi.importedInvoice.getZFItems()[0].getDeliveryNoteReferencedDocumentID(), "deliverynote123"); assertEquals(zi.importedInvoice.getZFItems()[0].getDeliveryNoteReferencedDocumentLineID(), "deliverypos456"); @@ -677,6 +679,7 @@ public class ZF2PushTest extends TestCase { Invoice i = zii.extractInvoice(); assertEquals("abc123", i.getInvoiceReferencedDocumentID()); + assertEquals("EXW", i.getDeliveryTypeCode()); assertEquals(1, i.getInvoiceReferencedDocuments().size()); assertEquals("abcd1234", i.getInvoiceReferencedDocuments().get(0).getIssuerAssignedID()); assertEquals("4304171000002", i.getRecipient().getGlobalID());