From 850e2bfc483ccfa69c95b7d9f6ac75ae5d44695e Mon Sep 17 00:00:00 2001 From: Thomas Christlieb Date: Wed, 14 Jan 2026 16:32:41 +0100 Subject: [PATCH 1/2] add DeliveryNoteReferencedDocument per Item --- .../main/java/org/mustangproject/Item.java | 37 +++++++++++++++++++ .../ZUGFeRD/IZUGFeRDExportableItem.java | 20 ++++++++++ .../ZUGFeRD/ZUGFeRD2PullProvider.java | 16 +++++++- .../ZUGFeRD/ZUGFeRDImporter.java | 19 ++++++++++ .../MustangReaderWriterCustomXMLTest.java | 14 +++++++ .../mustangproject/ZUGFeRD/ZF2PushTest.java | 12 +++++- 6 files changed, 115 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 5c9d8976..eb3cc6cc 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -46,6 +46,8 @@ public class Item implements IZUGFeRDExportableItem { protected String parentLineID = null; protected String lineStatusReasonCode = null; protected TradeParty lineSeller; + protected String deliveryNoteReferencedDocumentID = null; + protected Date deliveryNoteReferencedDocumentDate = null; //protected HashMap attributes = new HashMap<>(); /*** @@ -187,6 +189,19 @@ public class Item implements IZUGFeRDExportableItem { } }); + itemMap.getAsNodeMap("SpecifiedLineTradeDelivery").ifPresent(icnm -> { + icnm.getAsNodeMap("DeliveryNoteReferencedDocument").ifPresent(dn -> { + dn.getAsString("IssuerAssignedID") + .ifPresent(this::setDeliveryNoteReferencedDocumentID); + + dn.getAsNodeMap("FormattedIssueDateTime") + .flatMap(fdt -> fdt.getNode("DateTimeString")) + .map(XMLTools::getNodeValue) + .map(XMLTools::tryDate) + .ifPresent(this::setDeliveryNoteReferencedDocumentDate); + }); + }); + itemMap.getAsNodeMap("SpecifiedLineTradeSettlement", "SpecifiedSupplyChainTradeSettlement").ifPresent(icnm -> { icnm.getAsNodeMap("ApplicableTradeTax") .flatMap(cnm -> cnm.getAsBigDecimal("RateApplicablePercent", "ApplicablePercent")) @@ -697,4 +712,26 @@ public class Item implements IZUGFeRDExportableItem { return this.lineSeller; } + @Override + public String getDeliveryNoteReferencedDocumentID() { + return deliveryNoteReferencedDocumentID; + } + + + public Item setDeliveryNoteReferencedDocumentID(String deliveryNoteReferencedDocumentID) { + this.deliveryNoteReferencedDocumentID = deliveryNoteReferencedDocumentID; + return this; + } + + @Override + public Date getDeliveryNoteReferencedDocumentDate() { + return deliveryNoteReferencedDocumentDate; + } + + + public Item setDeliveryNoteReferencedDocumentDate(Date deliveryNoteReferencedDocumentDate) { + this.deliveryNoteReferencedDocumentDate = deliveryNoteReferencedDocumentDate; + return this; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 5d16ab33..3d8ed971 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -247,4 +247,24 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ return null; } + /** + * get delivery note document ID (per Item - ZUGFeRD EXTENDED) + * rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem/ram:SpecifiedLineTradeDelivery/ram:DeliveryNoteReferencedDocument/IssuerAssignedID + * + * @return the ID of the delivery note document + */ + default String getDeliveryNoteReferencedDocumentID() { + return null; + } + + /** + * get delivery note document date (per Item - ZUGFeRD EXTENDED) + * rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem/ram:SpecifiedLineTradeDelivery/ram:DeliveryNoteReferencedDocument/FormattedIssueDateTime + * + * @return the date of the delivery note document + */ + default Date getDeliveryNoteReferencedDocumentDate() { + return null; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 7d8cd0fd..372b2547 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -534,8 +534,20 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += "" + "" + "" - + quantityFormat(currentItem.getQuantity()) + "" - + "" + + quantityFormat(currentItem.getQuantity()) + ""; + + if (currentItem.getDeliveryNoteReferencedDocumentID() != null && !currentItem.getDeliveryNoteReferencedDocumentID().trim().isEmpty()) { + xml += ""; + xml += "" + XMLTools.encodeXML(currentItem.getDeliveryNoteReferencedDocumentID()) + ""; + if (currentItem.getDeliveryNoteReferencedDocumentDate() != null) { + final SimpleDateFormat dateFormat102 = new SimpleDateFormat("yyyyMMdd"); + xml += ""+XMLTools.encodeXML(dateFormat102.format(currentItem.getDeliveryNoteReferencedDocumentDate()))+""; + } + xml += ""; + + } + + xml += "" + "" + "" + "VAT"; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 3f2cd02d..72860003 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -680,6 +680,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { for (int i = 0; i < nl.getLength(); i++) { final Node nn = nl.item(i); Node node = null; + Node subnode = null; if (nn.getLocalName() != null) { switch (nn.getLocalName()) { case "SpecifiedLineTradeAgreement": @@ -738,6 +739,24 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { case "SpecifiedSupplyChainTradeDelivery": node = getNodeByName(nn.getChildNodes(), "BilledQuantity"); lineItem.setQuantity(XMLTools.tryBigDecimal(node)); + + node = getNodeByName(nn.getChildNodes(), "DeliveryNoteReferencedDocument"); + if (node != null) { + subnode = getNodeByName(node.getChildNodes(), "IssuerAssignedID"); + if (subnode != null) { + lineItem.setDeliveryNoteReferencedDocumentID(XMLTools.getNodeValue(subnode)); + } + node = getNodeByName(node.getChildNodes(), "FormattedIssueDateTime"); + if (node != null) { + NodeList FormattedIssueDateTimeChilds = node.getChildNodes(); + for (int dateChildIndex = 0; dateChildIndex < FormattedIssueDateTimeChilds.getLength(); dateChildIndex++) { + if ((FormattedIssueDateTimeChilds.item(dateChildIndex).getLocalName() != null) + && (FormattedIssueDateTimeChilds.item(dateChildIndex).getLocalName().equals("DateTimeString"))) { + lineItem.setDeliveryNoteReferencedDocumentDate(XMLTools.tryDate(FormattedIssueDateTimeChilds.item(dateChildIndex))); + } + } + } + } break; case "SpecifiedLineTradeSettlement": diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java index 4c572aa7..427b434e 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java @@ -28,6 +28,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class MustangReaderWriterCustomXMLTest extends TestCase { @@ -100,6 +102,12 @@ public class MustangReaderWriterCustomXMLTest extends TestCase { "\n" + "\n" + "1.0000\n" + + "\n" + + "deliverynote123\n" + + "\n" + + "20260114\n" + + "\n" + + "\n" + "\n" + "\n" + "\n" + @@ -279,6 +287,12 @@ public class MustangReaderWriterCustomXMLTest extends TestCase { assertEquals(zi.getIBAN(), "DE88 2008 0000 0970 3757 00"); assertEquals(zi.getHolder(), "Bei Spiel GmbH"); assertEquals(zi.getForeignReference(), "RE-20171118/506"); + assertEquals(zi.getLineItemList().get(0).getDeliveryNoteReferencedDocumentID(), "deliverynote123"); + try { + assertEquals(zi.getLineItemList().get(0).getDeliveryNoteReferencedDocumentDate(), new SimpleDateFormat("dd.MM.yyyy").parse("14.01.2026")); + } catch (ParseException e) { + e.printStackTrace(); + } } /** diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 1f81e373..117c5bfa 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -611,7 +611,11 @@ public class ZF2PushTest extends TestCase { .setInvoicer( new TradeParty("Abweichender Rechnungssteller", "Teststr.12", "04711", "Entenhausen", "DE") ) .setInvoicee( new TradeParty("Abweichender Rechnungsempfänger", "Teststr.42", "00815", "Entenhausen", "DE") ) .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).setId("a123"). - addAdditionalReference(dr2).addBuyerOrderReferencedDocumentID("orderId").addBuyerOrderReferencedDocumentLineID("xxx").addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15"))) + addAdditionalReference(dr2).addBuyerOrderReferencedDocumentID("orderId").addBuyerOrderReferencedDocumentLineID("xxx").addReferencedLineID("xxx") + .addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")) + .setDeliveryNoteReferencedDocumentID("deliverynote123") + .setDeliveryNoteReferencedDocumentDate(new SimpleDateFormat("dd.MM.yyyy").parse("14.01.2026")) + ) .addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16))) .addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16))) .addCashDiscount(new CashDiscount(new BigDecimal(2), 14)) @@ -647,6 +651,12 @@ public class ZF2PushTest extends TestCase { assertTrue(zi.getUTF8().contains(occurrenceTo)); assertTrue(zi.getUTF8().contains(contractID)); assertEquals(zi.importedInvoice.getZFItems()[0].getId(), "a123"); + assertEquals(zi.importedInvoice.getZFItems()[0].getDeliveryNoteReferencedDocumentID(), "deliverynote123"); + try { + assertEquals(zi.importedInvoice.getZFItems()[0].getDeliveryNoteReferencedDocumentDate(), new SimpleDateFormat("dd.MM.yyyy").parse("14.01.2026")); + } catch (ParseException e) { + e.printStackTrace(); + } assertTrue(zi.getUTF8().contains("20200113")); // to contain item delivery periods assertTrue(zi.getUTF8().contains("20200115")); // to contain item delivery periods From a41bf0bdd3216481ed5aafceca53eb389e31eec4 Mon Sep 17 00:00:00 2001 From: Thomas Christlieb Date: Mon, 19 Jan 2026 10:23:28 +0100 Subject: [PATCH 2/2] add DeliveryNoteReferencedDocument per Item Line ID --- .../src/main/java/org/mustangproject/Item.java | 15 +++++++++++++++ .../ZUGFeRD/IZUGFeRDExportableItem.java | 10 ++++++++++ .../ZUGFeRD/ZUGFeRD2PullProvider.java | 1 + .../mustangproject/ZUGFeRD/ZUGFeRDImporter.java | 4 ++++ .../ZUGFeRD/MustangReaderWriterCustomXMLTest.java | 2 ++ .../org/mustangproject/ZUGFeRD/ZF2PushTest.java | 2 ++ 6 files changed, 34 insertions(+) diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index eb3cc6cc..65a3bc63 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -48,6 +48,7 @@ public class Item implements IZUGFeRDExportableItem { protected TradeParty lineSeller; protected String deliveryNoteReferencedDocumentID = null; protected Date deliveryNoteReferencedDocumentDate = null; + protected String deliveryNoteReferencedDocumentLineID = null; //protected HashMap attributes = new HashMap<>(); /*** @@ -194,6 +195,9 @@ public class Item implements IZUGFeRDExportableItem { dn.getAsString("IssuerAssignedID") .ifPresent(this::setDeliveryNoteReferencedDocumentID); + dn.getAsString("LineID") + .ifPresent(this::setDeliveryNoteReferencedDocumentLineID); + dn.getAsNodeMap("FormattedIssueDateTime") .flatMap(fdt -> fdt.getNode("DateTimeString")) .map(XMLTools::getNodeValue) @@ -734,4 +738,15 @@ public class Item implements IZUGFeRDExportableItem { return this; } + @Override + public String getDeliveryNoteReferencedDocumentLineID() { + return deliveryNoteReferencedDocumentLineID; + } + + + public Item setDeliveryNoteReferencedDocumentLineID(String deliveryNoteReferencedDocumentLineID) { + this.deliveryNoteReferencedDocumentLineID = deliveryNoteReferencedDocumentLineID; + return this; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 3d8ed971..542ceb7a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -267,4 +267,14 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ return null; } + /** + * get delivery note document LineID (per Item - ZUGFeRD EXTENDED) + * rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem/ram:SpecifiedLineTradeDelivery/ram:DeliveryNoteReferencedDocument/LineID + * + * @return the LineID of the delivery note document item + */ + default String getDeliveryNoteReferencedDocumentLineID() { + return null; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 372b2547..24b804c3 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -539,6 +539,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (currentItem.getDeliveryNoteReferencedDocumentID() != null && !currentItem.getDeliveryNoteReferencedDocumentID().trim().isEmpty()) { xml += ""; xml += "" + XMLTools.encodeXML(currentItem.getDeliveryNoteReferencedDocumentID()) + ""; + xml += "" + XMLTools.encodeXML(currentItem.getDeliveryNoteReferencedDocumentLineID()) + ""; if (currentItem.getDeliveryNoteReferencedDocumentDate() != null) { final SimpleDateFormat dateFormat102 = new SimpleDateFormat("yyyyMMdd"); xml += ""+XMLTools.encodeXML(dateFormat102.format(currentItem.getDeliveryNoteReferencedDocumentDate()))+""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 72860003..e05a8652 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -746,6 +746,10 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { if (subnode != null) { lineItem.setDeliveryNoteReferencedDocumentID(XMLTools.getNodeValue(subnode)); } + subnode = getNodeByName(node.getChildNodes(), "LineID"); + if (subnode != null) { + lineItem.setDeliveryNoteReferencedDocumentLineID(XMLTools.getNodeValue(subnode)); + } node = getNodeByName(node.getChildNodes(), "FormattedIssueDateTime"); if (node != null) { NodeList FormattedIssueDateTimeChilds = node.getChildNodes(); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java index 427b434e..20c9ab42 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterCustomXMLTest.java @@ -104,6 +104,7 @@ public class MustangReaderWriterCustomXMLTest extends TestCase { "1.0000\n" + "\n" + "deliverynote123\n" + + "deliverypos456\n" + "\n" + "20260114\n" + "\n" + @@ -288,6 +289,7 @@ public class MustangReaderWriterCustomXMLTest extends TestCase { assertEquals(zi.getHolder(), "Bei Spiel GmbH"); assertEquals(zi.getForeignReference(), "RE-20171118/506"); assertEquals(zi.getLineItemList().get(0).getDeliveryNoteReferencedDocumentID(), "deliverynote123"); + assertEquals(zi.getLineItemList().get(0).getDeliveryNoteReferencedDocumentLineID(), "deliverypos456"); try { assertEquals(zi.getLineItemList().get(0).getDeliveryNoteReferencedDocumentDate(), new SimpleDateFormat("dd.MM.yyyy").parse("14.01.2026")); } catch (ParseException e) { diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 117c5bfa..ade0ed6d 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -614,6 +614,7 @@ public class ZF2PushTest extends TestCase { addAdditionalReference(dr2).addBuyerOrderReferencedDocumentID("orderId").addBuyerOrderReferencedDocumentLineID("xxx").addReferencedLineID("xxx") .addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")) .setDeliveryNoteReferencedDocumentID("deliverynote123") + .setDeliveryNoteReferencedDocumentLineID("deliverypos456") .setDeliveryNoteReferencedDocumentDate(new SimpleDateFormat("dd.MM.yyyy").parse("14.01.2026")) ) .addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16))) @@ -652,6 +653,7 @@ public class ZF2PushTest extends TestCase { assertTrue(zi.getUTF8().contains(contractID)); assertEquals(zi.importedInvoice.getZFItems()[0].getId(), "a123"); assertEquals(zi.importedInvoice.getZFItems()[0].getDeliveryNoteReferencedDocumentID(), "deliverynote123"); + assertEquals(zi.importedInvoice.getZFItems()[0].getDeliveryNoteReferencedDocumentLineID(), "deliverypos456"); try { assertEquals(zi.importedInvoice.getZFItems()[0].getDeliveryNoteReferencedDocumentDate(), new SimpleDateFormat("dd.MM.yyyy").parse("14.01.2026")); } catch (ParseException e) {