From faf9974b41372c2d6c7f9267039696d2ce62c599 Mon Sep 17 00:00:00 2001 From: langfr Date: Sat, 1 Nov 2025 15:26:18 +0100 Subject: [PATCH 1/5] Adding a tax ExemptionReasonCode to Product. --- .../src/main/java/org/mustangproject/Item.java | 3 +++ .../main/java/org/mustangproject/Product.java | 18 +++++++++++++++++- .../ZUGFeRD/IZUGFeRDExportableProduct.java | 4 ++++ .../ZUGFeRD/ZUGFeRD2PullProvider.java | 3 +++ .../mustangproject/ZUGFeRD/ZF2PushTest.java | 3 ++- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index eff70131..5bfc9994 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -189,6 +189,9 @@ public class Item implements IZUGFeRDExportableItem { icnm.getAsNodeMap("ApplicableTradeTax") .flatMap(cnm -> cnm.getAsString("ExemptionReason")) .ifPresent(product::setTaxExemptionReason); + icnm.getAsNodeMap("ApplicableTradeTax") + .flatMap(cnm -> cnm.getAsString("ExemptionReasonCode")) + .ifPresent(product::setTaxExemptionReasonCode); icnm.getAllNodes("SpecifiedTradeAllowanceCharge").map(NodeMap::new).forEach(stac -> { stac.getAsNodeMap("ChargeIndicator").ifPresent(ci -> { diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 6aca773a..a7609cbf 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -23,6 +23,7 @@ public class Product implements IZUGFeRDExportableProduct { protected String unit, name, sellerAssignedID, buyerAssignedID; protected String description = ""; protected String taxExemptionReason = null; + protected String taxExemptionReasonCode = null; protected String taxCategoryCode = null; protected BigDecimal VATPercent; protected boolean isReverseCharge = false; @@ -157,7 +158,22 @@ public class Product implements IZUGFeRDExportableProduct { * @return fluent setter */ public Product setTaxExemptionReason(String taxExemptionReasonText) { - taxExemptionReason = taxExemptionReasonText; + this.taxExemptionReason = taxExemptionReasonText; + return this; + } + + @Override + public String getTaxExemptionReasonCode() { + return taxExemptionReasonCode; + } + + /*** + * + * @param taxExemptionReasonCode, https://docs.peppol.eu/poacc/billing/3.0/codelist/vatex/ + * @return fluent setter + */ + public Product setTaxExemptionReasonCode(String taxExemptionReasonCode) { + this.taxExemptionReasonCode = taxExemptionReasonCode; return this; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java index 6ee5f22e..7c1d53fd 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java @@ -158,6 +158,10 @@ public interface IZUGFeRDExportableProduct { return null; } + default String getTaxExemptionReasonCode() { + return null; + } + default String getCountryOfOrigin() { 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 51da4a63..d3b42a41 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -537,6 +537,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (currentItem.getProduct().getTaxExemptionReason() != null) { xml += "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } + if (currentItem.getProduct().getTaxExemptionReasonCode() != null) { + xml += "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReasonCode()) + ""; + } xml += "" + currentItem.getProduct().getTaxCategoryCode() + ""; if (!currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) { xml += "" + vatFormat(currentItem.getProduct().getVATPercent()) + ""; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index c8a4c3bd..d8462ab7 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -160,7 +160,7 @@ public class ZF2PushTest extends TestCase { .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711") .setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))) .setNumber(number) - .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(0)).setTaxExemptionReason("Kleinunternehmer gemäß §19 UStG").setTaxCategoryCode("E"), price, new BigDecimal(1.0)).addNote(theNote)) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(0)).setTaxExemptionReason("Kleinunternehmer gemäß §19 UStG").setTaxCategoryCode("E").setTaxExemptionReasonCode( "VATEX-EU-I" ), price, new BigDecimal(1.0)).addNote(theNote)) ); String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8); assertTrue(theXML.contains(" Date: Mon, 3 Nov 2025 13:56:28 +0100 Subject: [PATCH 2/5] Remove duplicate paymentTerms --- .../org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 51da4a63..4f3ae0dc 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -991,11 +991,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { ArrayList paymentTerms = new ArrayList(Arrays.asList(trans.getExtendedPaymentTerms())); - IZUGFeRDPaymentTerms izpt= trans.getPaymentTerms(); - if (izpt!=null) { - paymentTerms.add(izpt); - } - String paymentTermsXml = ""; if (paymentTerms.size() == 0) { return ""; From b3e739ff35736a9c13f1d7428fe9383c357c0abf Mon Sep 17 00:00:00 2001 From: Erras IT GmbH Date: Thu, 4 Dec 2025 07:45:56 +0100 Subject: [PATCH 3/5] Update History.md Version 2.16.2 is not fully backward compatible. Mention this in the history. --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index b3f108d8..c9ea32bf 100644 --- a/History.md +++ b/History.md @@ -147,7 +147,7 @@ corrections - #707 invoiceimporter may fail if certain values are not set - #708 embedded files cannot be determined - #709 ZUGFeRDInvoiceImporter ignored "first" embedded file in list of pdf attachments -- #607 Enable flexible PaymentReference and a DocumentName. +- #607 Enable flexible PaymentReference and a DocumentName. **Important note:** The library’s behavior for generating ZUGFeRD 2 documents is not fully backward compatible. Except for the “Minimum” profile, BT‑83 was automatically populated with the document number in earlier versions. From this release onward, applications must explicitly set BT‑83. - #649 Reuse toPDF method to work without any dependencies to the file system - #650 Add net.sf.offo:fop-hyph - #665 Fix #632: Return ubl_creditnote as Standard for CreditNotes From 0be3e04a2ad443d1ee38a2e76046ac2eba3b7b86 Mon Sep 17 00:00:00 2001 From: Andreas Reichmann Date: Mon, 8 Dec 2025 20:43:14 +0100 Subject: [PATCH 4/5] Add SubInvoiceLine support for ZUGFeRD Extended profile - Parse ParentLineID and LineStatusReasonCode from XML - Filter GROUP/INFORMATION lines from calculation (only DETAIL counts) - Add hierarchy validation for GROUP line totals - Add tests for sub invoice line import and validation --- .../main/java/org/mustangproject/Item.java | 40 ++++++- .../ZUGFeRD/IZUGFeRDExportableItem.java | 30 +++++ .../ZUGFeRD/TransactionCalculator.java | 15 ++- .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 113 ++++++++++++++++++ .../validator/XMLValidator.java | 77 ++++++++++++ .../validator/XMLValidatorTest.java | 24 ++++ 6 files changed, 294 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index eff70131..7abd5ab8 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -43,6 +43,8 @@ public class Item implements IZUGFeRDExportableItem { protected ArrayList Charges = new ArrayList<>(); protected List includedNotes = null; protected String accountingReference; + protected String parentLineID = null; + protected String lineStatusReasonCode = null; //protected HashMap attributes = new HashMap<>(); /*** @@ -91,9 +93,11 @@ public class Item implements IZUGFeRDExportableItem { }); - itemMap.getAsNodeMap("AssociatedDocumentLineDocument") - .flatMap(icnm -> icnm.getAsString("LineID")) - .ifPresent(this::setId); + itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(adld -> { + adld.getAsString("LineID").ifPresent(this::setId); + adld.getAsString("ParentLineID").ifPresent(this::setParentLineID); + adld.getAsString("LineStatusReasonCode").ifPresent(this::setLineStatusReasonCode); + }); itemMap.getAsNodeMap("Price").ifPresent(icnm -> { // ubl @@ -644,4 +648,34 @@ public class Item implements IZUGFeRDExportableItem { public String getAccountingReference() { return accountingReference; } + + @Override + public String getParentLineID() { + return parentLineID; + } + + /*** + * for sub invoice lines: set the parent line ID + * @param parentLineID the line ID of the parent line + * @return fluent setter + */ + public Item setParentLineID(String parentLineID) { + this.parentLineID = parentLineID; + return this; + } + + @Override + public String getLineStatusReasonCode() { + return lineStatusReasonCode; + } + + /*** + * for sub invoice lines: set the status reason code (DETAIL, GROUP, INFORMATION) + * @param lineStatusReasonCode the status reason code + * @return fluent setter + */ + public Item setLineStatusReasonCode(String lineStatusReasonCode) { + this.lineStatusReasonCode = lineStatusReasonCode; + 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 05f4d437..7b48ab5f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -192,6 +192,36 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ return null; } + /*** + * for sub invoice lines in ZUGFeRD Extended: the line ID of the parent line + * @return the parent line ID or null if this is a top-level line + */ + default String getParentLineID() { + return null; + } + + /*** + * for sub invoice lines in ZUGFeRD Extended: the status reason code + * determines if a line is relevant for calculation + * @return DETAIL, GROUP, INFORMATION or null for standard lines + */ + default String getLineStatusReasonCode() { + return null; + } + + /*** + * checks if this line should be included in sum calculation. + * GROUP and INFORMATION lines are not calculation-relevant, + * only DETAIL lines (or lines without status code) are. + * @return true if the line should be included in calculation + */ + @com.fasterxml.jackson.annotation.JsonIgnore + default boolean isCalculationRelevant() { + String status = getLineStatusReasonCode(); + // null means standard line (backwards compatible), DETAIL is explicitly relevant + return status == null || "DETAIL".equals(status); + } + /** * A grouping of business terms to indicate accounting-relevant free texts including a qualification of these. * diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index f8c80127..daf5b0e0 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -155,12 +155,15 @@ public class TransactionCalculator implements IAbsoluteValueProvider { /*** * returns the total net value of all items, without document level - * charges/allowances + * charges/allowances. For sub invoice lines only DETAIL lines are summed, + * GROUP and INFORMATION lines are ignored. * * @return item sum */ protected BigDecimal getTotal() { - BigDecimal dec = Stream.of(trans.getZFItems()).map(LineCalculator::new) + BigDecimal dec = Stream.of(trans.getZFItems()) + .filter(IZUGFeRDExportableItem::isCalculationRelevant) + .map(LineCalculator::new) .map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add); return dec; } @@ -190,6 +193,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider { final String vatDueDateTypeCode = trans.getVATDueDateTypeCode(); for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { + // skip GROUP and INFORMATION lines for sub invoice lines + if (!currentItem.isCalculationRelevant()) { + continue; + } BigDecimal percent = null; if (currentItem.getProduct() != null) { percent = currentItem.getProduct().getVATPercent(); @@ -258,6 +265,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider { final String vatDueDateTypeCode = this.trans.getVATDueDateTypeCode(); for (final IZUGFeRDExportableItem currentItem : this.trans.getZFItems()) { + // skip GROUP and INFORMATION lines for sub invoice lines + if (!currentItem.isCalculationRelevant()) { + continue; + } BigDecimal percent = null; if (currentItem.getProduct() != null) { diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 8873bb0c..cdfbf6d0 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -895,4 +895,117 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { assertNull(invoice.getDespatchAdviceReferencedDocumentID()); assertNull(invoice.getInvoiceReferencedDocumentID()); } + + @Test + public void testSubInvoiceLinesImport() throws FileNotFoundException, XPathExpressionException, ParseException { + // test import of sub invoice lines with GROUP and DETAIL lines + File inputFile = getResourceAsFile("subinvoicelines/Extended_SubInvoiceLines_Hardware_Bsp2.xml"); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + zii.setInputStream(new FileInputStream(inputFile)); + + Invoice invoice = zii.extractInvoice(); + assertEquals(6, invoice.getZFItems().length); + + // find GROUP and DETAIL lines and verify LineStatusReasonCode and ParentLineID + Item group01 = findItemById(invoice, "01"); + assertNotNull(group01); + assertEquals("GROUP", group01.getLineStatusReasonCode()); + assertNull(group01.getParentLineID()); + assertFalse(group01.isCalculationRelevant()); + + Item detail0101 = findItemById(invoice, "0101"); + assertNotNull(detail0101); + assertEquals("DETAIL", detail0101.getLineStatusReasonCode()); + assertEquals("01", detail0101.getParentLineID()); + assertTrue(detail0101.isCalculationRelevant()); + + Item detail0102 = findItemById(invoice, "0102"); + assertNotNull(detail0102); + assertEquals("DETAIL", detail0102.getLineStatusReasonCode()); + assertEquals("01", detail0102.getParentLineID()); + assertTrue(detail0102.isCalculationRelevant()); + + // verify that only DETAIL lines are summed (not GROUP lines) + // DETAIL lines: 0101=600, 0102=450, 0201=360, 0202=90 = 1500 + // GROUP lines should NOT be added: 01=1050, 02=450 + TransactionCalculator tc = new TransactionCalculator(invoice); + assertEquals(new BigDecimal("1500.00"), tc.getTotal().setScale(2)); + assertEquals(new BigDecimal("1785.00"), tc.getGrandTotal().setScale(2)); + } + + @Test + public void testSubInvoiceLinesNestedImport() throws FileNotFoundException, XPathExpressionException, ParseException { + // test import of nested sub invoice lines (GROUP containing GROUP containing DETAIL) + File inputFile = getResourceAsFile("subinvoicelines/Extended___SubInvoiceLines_Kaffee_Bundle_Set_Bsp4__.xml"); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + zii.setInputStream(new FileInputStream(inputFile)); + + Invoice invoice = zii.extractInvoice(); + + // find nested structure: 1 (GROUP) -> 1.3 (GROUP) -> 1.3.1 (DETAIL) + Item group1 = findItemById(invoice, "1"); + assertNotNull(group1); + assertEquals("GROUP", group1.getLineStatusReasonCode()); + + Item group13 = findItemById(invoice, "1.3"); + assertNotNull(group13); + assertEquals("GROUP", group13.getLineStatusReasonCode()); + assertEquals("1", group13.getParentLineID()); + + Item detail131 = findItemById(invoice, "1.3.1"); + assertNotNull(detail131); + assertEquals("DETAIL", detail131.getLineStatusReasonCode()); + assertEquals("1.3", detail131.getParentLineID()); + + // verify calculation only includes DETAIL lines + // DETAIL: 1.1=30, 1.2=60, 1.3.1=90, 1.3.2=36 = 216 + TransactionCalculator tc = new TransactionCalculator(invoice); + assertEquals(new BigDecimal("216.00"), tc.getTotal().setScale(2)); + } + + @Test + public void testSubInvoiceLinesWithDiscounts() throws FileNotFoundException, XPathExpressionException, ParseException { + // test sub invoice lines with negative amounts (discounts) + File inputFile = getResourceAsFile("subinvoicelines/Extended___SubInvoiceLines_Buero_Material_Bsp3__.xml"); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + zii.setInputStream(new FileInputStream(inputFile)); + + Invoice invoice = zii.extractInvoice(); + + // verify calculation handles negative DETAIL lines correctly + // GROUP 01: 600 + 450 - 50 = 1000 + // GROUP 02: 360 + 90 - 45 = 405 + // Total DETAIL: 1405 + TransactionCalculator tc = new TransactionCalculator(invoice); + assertEquals(new BigDecimal("1405.00"), tc.getTotal().setScale(2)); + } + + @Test + public void testSubInvoiceLinesInformation() throws FileNotFoundException, XPathExpressionException, ParseException { + // test INFORMATION lines (should have price 0 and not affect calculation) + File inputFile = getResourceAsFile("subinvoicelines/Extended_Fallschutz-Set_SubInvoiceLine_Bsp5.xml"); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + zii.setInputStream(new FileInputStream(inputFile)); + + Invoice invoice = zii.extractInvoice(); + + // find INFORMATION lines + Item info0101 = findItemById(invoice, "01.01"); + assertNotNull(info0101); + assertEquals("INFORMATION", info0101.getLineStatusReasonCode()); + assertFalse(info0101.isCalculationRelevant()); + + // DETAIL line 01 = 45000 + TransactionCalculator tc = new TransactionCalculator(invoice); + assertEquals(new BigDecimal("45000.00"), tc.getTotal().setScale(2)); + } + + private Item findItemById(Invoice invoice, String id) { + for (IZUGFeRDExportableItem item : invoice.getZFItems()) { + if (item instanceof Item && id.equals(((Item) item).getId())) { + return (Item) item; + } + } + return null; + } } diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 5ca50de4..61565f61 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -5,6 +5,7 @@ import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.io.UncheckedIOException; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -24,6 +25,8 @@ import javax.xml.xpath.XPathFactory; import org.mustangproject.CalculatedInvoice; import org.mustangproject.XMLTools; +import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem; +import org.mustangproject.ZUGFeRD.LineCalculator; import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -454,6 +457,9 @@ public class XMLValidator extends Validator { CalculatedInvoice ci=new CalculatedInvoice(); zi.extractInto(ci); + // check sub invoice line hierarchy if present + checkSubInvoiceLineHierarchy(ci, context); + } catch ( ArithmeticException e) { try { context.addResultItem(new ValidationResultItem(ESeverity.warning, "Arithmetical issue:"+e.getMessage()).setSection(10)); @@ -469,6 +475,77 @@ public class XMLValidator extends Validator { } + /*** + * validates that GROUP line totals match the sum of their DETAIL child lines + */ + private void checkSubInvoiceLineHierarchy(CalculatedInvoice invoice, ValidationContext context) { + IZUGFeRDExportableItem[] items = invoice.getZFItems(); + if (items == null || items.length == 0) { + return; + } + + // check if we have any sub invoice lines at all + boolean hasSubInvoiceLines = false; + for (IZUGFeRDExportableItem item : items) { + if (item.getLineStatusReasonCode() != null) { + hasSubInvoiceLines = true; + break; + } + } + if (!hasSubInvoiceLines) { + return; + } + + // build a map of line ID to item for quick lookup + java.util.HashMap itemMap = new java.util.HashMap<>(); + for (IZUGFeRDExportableItem item : items) { + if (item.getId() != null) { + itemMap.put(item.getId(), item); + } + } + + // for each GROUP line, sum up the DETAIL children and compare + for (IZUGFeRDExportableItem item : items) { + if ("GROUP".equals(item.getLineStatusReasonCode())) { + String groupId = item.getId(); + if (groupId == null) { + continue; + } + + // sum up direct DETAIL children + BigDecimal childSum = BigDecimal.ZERO; + for (IZUGFeRDExportableItem child : items) { + if (groupId.equals(child.getParentLineID()) && "DETAIL".equals(child.getLineStatusReasonCode())) { + LineCalculator lc = child.getCalculation(); + childSum = childSum.add(lc.getItemTotalNetAmount()); + } + } + + // also sum up nested GROUP children (their totals should already include their DETAIL children) + for (IZUGFeRDExportableItem child : items) { + if (groupId.equals(child.getParentLineID()) && "GROUP".equals(child.getLineStatusReasonCode())) { + LineCalculator lc = child.getCalculation(); + childSum = childSum.add(lc.getItemTotalNetAmount()); + } + } + + // compare with GROUP total + LineCalculator groupLc = item.getCalculation(); + BigDecimal groupTotal = groupLc.getItemTotalNetAmount(); + if (childSum.compareTo(groupTotal) != 0) { + try { + context.addResultItem(new ValidationResultItem(ESeverity.warning, + "Sub invoice line hierarchy mismatch: GROUP line " + groupId + + " has total " + groupTotal + " but sum of child lines is " + childSum) + .setSection(10)); + } catch (IrrecoverableValidationError ie) { + LOGGER.error(ie.getMessage(), ie); + } + } + } + } + } + public void validateXR(String xml, ESeverity errorImpact) throws IrrecoverableValidationError { //Guideline ID=urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 or diff --git a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java index 7ed18b8f..47989968 100644 --- a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java @@ -373,4 +373,28 @@ public class XMLValidatorTest extends ResourceCase { } + public void testSubInvoiceLineHierarchy() { + final ValidationContext ctx = new ValidationContext(null); + final XMLValidator xv = new XMLValidator(ctx); + final XPathEngine xpath = new JAXPXPathEngine(); + + // test invalid hierarchy: GROUP sum does not match DETAIL children sum + // GROUP 01 has LineTotalAmount=999, but children sum to 1050 (600+450) + File tempFile = getResourceAsFile("invalidSubInvoiceLineHierarchy.xml"); + try { + xv.setFilename(tempFile.getAbsolutePath()); + xv.validate(); + + String s = "" + xv.getXMLResult() + ""; + // hierarchy mismatch should produce at least one warning + assertThat(s).valueByXPath("count(//warning)") + .asInt() + .isGreaterThanOrEqualTo(1); + + } catch (final IrrecoverableValidationError e) { + // ignore, will be in XML output anyway + } + + } + } From 589a39006915573ed1799db729318ea305a11fcd Mon Sep 17 00:00:00 2001 From: Andreas Reichmann Date: Mon, 8 Dec 2025 20:44:28 +0100 Subject: [PATCH 5/5] Add SubInvoiceLine test resources Extended profile examples with GROUP/DETAIL hierarchy, includes invalid hierarchy file for negative testing --- ...ded_Fallschutz-Set_SubInvoiceLine_Bsp5.xml | 408 ++++++++++++++ ...Extended_SubInvoiceLines_Hardware_Bsp2.xml | 443 +++++++++++++++ ..._SubInvoiceLines_Buero_Material_Bsp3__.xml | 507 ++++++++++++++++++ ...bInvoiceLines_Kaffee_Bundle_Set_Bsp4__.xml | 458 ++++++++++++++++ ...chlagsrechnung_SubInvoiceLine_u_LV_Nr_.xml | 473 ++++++++++++++++ .../invalidSubInvoiceLineHierarchy.xml | 456 ++++++++++++++++ .../invalidSubInvoiceLineHierarchy.xml | 456 ++++++++++++++++ 7 files changed, 3201 insertions(+) create mode 100644 library/src/test/resources/subinvoicelines/Extended_Fallschutz-Set_SubInvoiceLine_Bsp5.xml create mode 100644 library/src/test/resources/subinvoicelines/Extended_SubInvoiceLines_Hardware_Bsp2.xml create mode 100644 library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Buero_Material_Bsp3__.xml create mode 100644 library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Kaffee_Bundle_Set_Bsp4__.xml create mode 100644 library/src/test/resources/subinvoicelines/ZUGFeRD_Extended__Abschlagsrechnung_SubInvoiceLine_u_LV_Nr_.xml create mode 100644 library/src/test/resources/subinvoicelines/invalidSubInvoiceLineHierarchy.xml create mode 100644 validator/src/test/resources/invalidSubInvoiceLineHierarchy.xml diff --git a/library/src/test/resources/subinvoicelines/Extended_Fallschutz-Set_SubInvoiceLine_Bsp5.xml b/library/src/test/resources/subinvoicelines/Extended_Fallschutz-Set_SubInvoiceLine_Bsp5.xml new file mode 100644 index 00000000..9f799e1c --- /dev/null +++ b/library/src/test/resources/subinvoicelines/Extended_Fallschutz-Set_SubInvoiceLine_Bsp5.xml @@ -0,0 +1,408 @@ + + + + + + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 9099999999 + RECHNUNG + 380 + + 20261031 + + + Geschäftsführer: Herr Geschäftsführer , Muster Maschinenbau GmbH etc. + REG + + + Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können. + AAI + + + ZUGFeRD vers 2.4.0 (Extended) + ACB + + + Dies ist eine Beispiel-Rechnung zur empfohlenen Darstellung von Unterpositionen für ein Verkaufsset + ACB + + + + + + 01 + DETAIL + + + 99999999999 + 99999000000090 1 + INDIVIDUELLES Fallschutzset + Kundenindividuelles Sortiment Fallschutz + + + + 4500.00 + 1.0000 + + + 4500.00 + 1.0000 + + + + 10.00 + + 0009999999 + Muster Bau und Fassaden GmbH + + 76543 + Muster Str. 19 + Musterhausen + DE + + + + + + VAT + S + 19.00 + + + + 20261006 + + + 20261021 + + + + 45000.00 + 0.00 + + + 2133367983 + 130 + VN + + + + + + 01.01 + 01 + INFORMATION + + + 9947376819504 + 1239932943961 1 + RUCKSACK-Fallschutz + + + + 0.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 0.00 + + + + + + 01.02 + 01 + INFORMATION + + + 9961975355683 + 1234300430961 1 + Gehörschutz + + + + 0.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 0.00 + + + + + + 01.03 + 01 + INFORMATION + + + 9945727365021 + 1234102250061 1 + Schutzbrille-KLAR + + + + 0.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 0.00 + + + + + + 01.04 + 01 + INFORMATION + + + 9953479245027 + 1234200243961 1 + Schutzhelm-6PUNKT-WEISS + + + + 0.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 0.00 + + + + + + GLN4000000000 + Lieferant GmbH & Co. KG + + Georg Verkäufer + + 0160 123456789 + + + georg.Verkäufer@lieferant.de + + + + 98765 + Lieferanten-Str.12-17 + Lieferstadt + DE + + + info@lieferant.de + + + 78910/12345 + + + DE12435679 + + + + 21212121 + Muster Maschinenbau GmbH + + 87654 + Käufer-Str. 12 + Käuferstadt + DE + + + + 555555555 + + 20261031 + + + + 456789123 + + + + + 0009999999 + Muster Bau und Fassaden GmbH + + 76543 + Muster Str. 19 + Musterhausen + DE + + + + + 20261031 + + + + 8876543219 + + 20261031 + + + + + Kundennummer:. 8888855555 Rechnungsnummer:. 6069999999 + EUR + + 58 + Bezahlung per SEPA Überweisung + + DE75512108001245126199 + + + SOLADESTXYZ + + + + 8550.00 + VAT + 45000.00 + S + 19.00 + + + Bis zum 29.11.2026 ohne Abzug + + 20261129 + + + + 45000.00 + 0.00 + 45000.00 + 8550.00 + 53550.00 + 53550.00 + + + + diff --git a/library/src/test/resources/subinvoicelines/Extended_SubInvoiceLines_Hardware_Bsp2.xml b/library/src/test/resources/subinvoicelines/Extended_SubInvoiceLines_Hardware_Bsp2.xml new file mode 100644 index 00000000..a3c1a947 --- /dev/null +++ b/library/src/test/resources/subinvoicelines/Extended_SubInvoiceLines_Hardware_Bsp2.xml @@ -0,0 +1,443 @@ + + + + + + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 99877 + 380 + + 20260530 + + + Geschäftsführer: Herr Geschäftsführer , Muster Bau GmbH etc. + REG + + + Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können. + AAI + + + ZUGFeRD vers 2.4.0 Extended + ACB + + + Dies ist eine Beispiel-Rechnung zur empfohlenen Darstellung von Unterpositionen + ACB + + + + + + 0101 + 01 + DETAIL + + + 88888886349852 + 123456789 + 987654321 + Laser printer B/W + Schwarzweiß Laserdrucker + + + + 300.00 + + + + 2.00 + + + + VAT + S + 19.00 + + + 600.00 + + + + + + 0102 + 01 + DETAIL + + + 77777776349852 + 2345678910 + 876543219 + Ink printer color + Farbdrucker + + + + 150.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 450.00 + + + + + + 01 + GROUP + + + 6666656349852 + 345678912 + 765432198 + Subtotal hardware + Hardware Gesamt + + + + 1050.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + 1050.00 + + + + + + 0201 + 02 + DETAIL + + + 55555556349852 + 456789123 + 654321987 + Toner + Toner + + + + 120.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 360.00 + + + + + + 0202 + 02 + DETAIL + + + 5555556349852 + 567891234 + 543219876 + PAPER + Kopierpapier + + + + 9.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 90.00 + + + + + + 02 + GROUP + + + 2222256349852 + 9345678912 + 9765432198 + Subtotal Accessories + Zubehör Gesamt + + + + 450.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + 450.00 + + + + + Kundenref. BT-10 + + 998877 + Musterbetrieb Systemhaus AG + + HRA 45678 + + + Kontaktperson + + 5578 + + + absender@musterberieb.de + + + + 37079 + August-Müller-Strasse 222 + Göttingen + DE + + + DE09687654321 + + + + 330145 + Auftraggeber Firmenkunde GmbH + + Herr Thomas Auftraggeber + + +49 321 456789 + + + thomas.auftraggeber@Firmenkunde.de + + + + 37073 + Musterstraße 1212 + Göttingen + DE + + + DE1234567890 + + + + G12042-1-01 + + + BT-13 + + + Vertragsnr. BT-12 + + + Vergabenr. BT-17 + 50 + + + Projektnr. BT-11 + Project reference + + + + + Auftraggeber Firmenkunde GmbH + + 37073 + Musterstraße 1212 + Göttingen + DE + + + + + 20260530 + + + + + EUR + + 58 + + DE75512108001245126199 + Musterbetrieb Kontoname + + + PBNKDEFF + + + + 285.00 + VAT + 1500.00 + S + 19.00 + + + Bei Zahlung bis zum 06.06.2026 zahlen Sie mit 2,00 % Skonto € 1749,30 € + + 20260606 + + + 1785.00 + 2.00 + + + + Bis zum zum 13.06.2026 ohne Abzug + + 20260613 + + + + 1500.00 + 0.00 + 0.00 + 1500.00 + 285.00 + 1785.00 + 0.00 + 1785.00 + + + Kostenstelle BT-19 + + + + diff --git a/library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Buero_Material_Bsp3__.xml b/library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Buero_Material_Bsp3__.xml new file mode 100644 index 00000000..0556e885 --- /dev/null +++ b/library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Buero_Material_Bsp3__.xml @@ -0,0 +1,507 @@ + + + + + + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 99877 + 380 + + 20260530 + + + Geschäftsführer: Herr Geschäftsführer , Muster Bau GmbH etc. + REG + + + Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können. + AAI + + + ZUGFeRD vers 2.4.0 Extended + ACB + + + Dies ist eine Beispiel-Rechnung zur empfohlenen Darstellung von Unterpositionen + ACB + + + + + + 0101 + 01 + DETAIL + + + 88888886349852 + 123456789 + 987654321 + Laser printer B/W + Schwarzweiß Laserdrucker + + + + 300.00 + + + + 2.00 + + + + VAT + S + 19.00 + + + 600.00 + + + + + + 0102 + 01 + DETAIL + + + 77777776349852 + 2345678910 + 876543219 + Ink printer color + Farbdrucker + + + + 150.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 450.00 + + + + + + 0103 + 01 + DETAIL + + + 0000006349852 + 99992345678910 + 88888876543219 + Allowance + Abschlagsposition + + + + 50.00 + + + + -1.00 + + + + VAT + S + 19.00 + + + -50.00 + + + + + + 01 + GROUP + + + 6666656349852 + 345678912 + 765432198 + Subtotal hardware + Hardware Gesamt + + + + 1000.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + 1000.00 + + + + + + 0201 + 02 + DETAIL + + + 55555556349852 + 456789123 + 654321987 + Toner + Toner + + + + 120.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 360.00 + + + + + + 0202 + 02 + DETAIL + + + 5555556349852 + 567891234 + 543219876 + PAPER + Kopierpapier + + + + 9.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 90.00 + + + + + + 0203 + 02 + DETAIL + + + 0000006349852 + 99992345678910 + 88888876543219 + Allowance + Abschlagsposition 10% von 450,- + + + + 45.00 + + + + -1.00 + + + + VAT + S + 19.00 + + + -45.00 + + + + + + 02 + GROUP + + + 2222256349852 + 9345678912 + 9765432198 + Subtotal Accessories + Zubehör Gesamt + + + + 405.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + 405.00 + + + + + Kundenref. BT-10 + + 998877 + Musterbetrieb Systemhaus AG + + HRA 45678 + + + Kontaktperson + + 5578 + + + absender@musterberieb.de + + + + 37079 + August-Müller-Strasse 222 + Göttingen + DE + + + DE09687654321 + + + + 330145 + Auftraggeber Firmenkunde GmbH + + Herr Thomas Auftraggeber + + +49 321 456789 + + + thomas.auftraggeber@Firmenkunde.de + + + + 37073 + Musterstraße 1212 + Göttingen + DE + + + DE1234567890 + + + + G12042-1-01 + + + BT-13 + + + Vertragsnr. BT-12 + + + Vergabenr. BT-17 + 50 + + + Projektnr. BT-11 + Project reference + + + + + Auftraggeber Firmenkunde GmbH + + 37073 + Musterstraße 1212 + Göttingen + DE + + + + + 20260530 + + + + + EUR + + 58 + + DE75512108001245126199 + Musterbetrieb Kontoname + + + PBNKDEFF + + + + 266.95 + VAT + 1405.00 + S + 19.00 + + + Bei Zahlung bis zum 06.06.2026 zahlen Sie mit 2,00 % Skonto € 1638,51 € + + 20260606 + + + 1671.95 + 2.00 + + + + Bis zum zum 13.06.2026 ohne Abzug + + 20260613 + + + + 1405.00 + 0.00 + 0.00 + 1405.00 + 266.95 + 1671.95 + 0.00 + 1671.95 + + + Kostenstelle BT-19 + + + + diff --git a/library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Kaffee_Bundle_Set_Bsp4__.xml b/library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Kaffee_Bundle_Set_Bsp4__.xml new file mode 100644 index 00000000..1a5f86d2 --- /dev/null +++ b/library/src/test/resources/subinvoicelines/Extended___SubInvoiceLines_Kaffee_Bundle_Set_Bsp4__.xml @@ -0,0 +1,458 @@ + + + + + + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 50099877 + 380 + + 20260530 + + + Geschäftsführer: Herr Geschäftsführer , Muster GmbH etc. + REG + + + Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können. + AAI + + + ZUGFeRD vers 2.4.0 Extended + ACB + + + Dies ist eine Beispiel-Rechnung zur empfohlenen Darstellung von Unterpositionen im Bundle mit verschiedenen Steuersätzen + ACB + + + + + + 1 + GROUP + + Positionswert nicht Rechnungs- bzw. Steuerrelevant - Zusammenfassung der steuerrelevanten Unterpositionen + + + + 88888886349852 + 123456789 + 987654321 + Kaffee-Display "Delicous" + Kaffee-Display / Bundle/Set bestehend aus folgenden Positionen + + + + 108.00 + + + + 2.00 + 1.00 + + + + VAT + S + 0.00 + + + 216.00 + + + + + + 1.1 + 1 + DETAIL + + + 77777776349852 + 2345678910 + 876543219 + Kenia Röstung + feinste Röstung + + + + 5.00 + + + + 6.00 + + + + VAT + S + 7.00 + + + 30.00 + + + + + + 1.2 + 1 + DETAIL + + + 0000006349852 + 99992345678910 + 88888876543219 + Dunkle Röstung + feinste Röstung dunkel + + + + 5.00 + + + + 12.00 + + + + VAT + S + 7.00 + + + 60.00 + + + + + + 1.3 + 1 + GROUP + + Positionswert nicht Rechnungs- bzw. Steuerrelevant - Zusammenfassung der steuerrelevanten Unterpositionen + + + + 6666656349852 + 345678912 + 765432198 + Colombia Bundle + Bundle/Set kolumbianische Röstung + + + + 21.00 + + + + 6.00 + + + + VAT + S + 0.00 + + + 126.00 + + + + + + 1.3.1 + 1.3 + DETAIL + + + 55555556349852 + 456789123 + 654321987 + Colombia Roast + kolumbianische Röstung + + + + 5.00 + + + + 18.00 + + + + VAT + S + 7.00 + + + 90.00 + + + + + + 1.3.2 + 1.3 + DETAIL + + + 5555556349852 + 567891234 + 543219876 + Becher + Kaffeebecher Sonderanfertigung + + + + 2.00 + + + + 18.00 + + + + VAT + S + 19.00 + + + 36.00 + + + + + Kundenref. BT-10 + + 998877 + Musterbetrieb Kaffee AG + + HRA 45678 + + + Kontaktperson + + 5578 + + + absender@musterberieb.de + + + + 37079 + August-Müller-Strasse 222 + Göttingen + DE + + + DE09687654321 + + + + 330145 + Auftraggeber Kaffeehaus GmbH + + Herr Thomas Auftraggeber + + +49 321 456789 + + + thomas.auftraggeber@Firmenkunde.de + + + + 37073 + Musterstraße 1212 + Göttingen + DE + + + DE1234567890 + + + + G12042-1-01 + + + BT-13 + + + Vertragsnr. BT-12 + + + Vergabenr. BT-17 + 50 + + + Projektnr. BT-11 + Project reference + + + + + Auftraggeber Kaffeehaus GmbH + + 37073 + Musterstraße 1212 + Göttingen + DE + + + + + 20260530 + + + + + EUR + + 58 + + DE75512108001245126199 + Musterbetrieb Kontoname + + + PBNKDEFF + + + + 12.60 + VAT + 180.00 + S + 7.00 + + + 6.84 + VAT + 36.00 + S + 19.00 + + + Bei Zahlung bis zum 06.06.2026 zahlen Sie mit 2,00 % Skonto € 230,73 € + + 20260606 + + + 235.44 + 2.00 + + + + Bis zum zum 13.06.2026 ohne Abzug + + 20260613 + + + + 216.00 + 0.00 + 0.00 + 216.00 + 19.44 + 235.44 + 0.00 + 235.44 + + + Kostenstelle BT-19 + + + + diff --git a/library/src/test/resources/subinvoicelines/ZUGFeRD_Extended__Abschlagsrechnung_SubInvoiceLine_u_LV_Nr_.xml b/library/src/test/resources/subinvoicelines/ZUGFeRD_Extended__Abschlagsrechnung_SubInvoiceLine_u_LV_Nr_.xml new file mode 100644 index 00000000..244250cd --- /dev/null +++ b/library/src/test/resources/subinvoicelines/ZUGFeRD_Extended__Abschlagsrechnung_SubInvoiceLine_u_LV_Nr_.xml @@ -0,0 +1,473 @@ + + + + + + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 210111 mit LV + 875 + + 20260530 + + + 1. Abschlagsrechnung + ACB + + + Geschäftsführer: Herr Geschäftsführer , Muster Bau GmbH etc. + REG + + + Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können. + AAI + + + ZUGFeRD vers 2.4.0 Extended + ACB + + + Dies ist eine Beispiel-Rechnung zur Darstellung einer Bau-Abschlags-Rechnung mit Sub-Invoice-Lines und Leistungsverzeichnis-Bezug je Position + ACB + + + Betreff zum LV für eine Kurzinformation zum Bauvorhaben BT-22 + ACB + + + Kopftext für zusätzliche Beschreibungen zur Rechnung. Z.B. als Anschreiben für die Rechnung BT-22 + ACB + + + ergänzneder Fußtext für die Rechnung mit zusätzlichen Angaben. Z.B: Ist kein gesondertes Lieferdatum angegeben, entspricht das Rechnungsdatum dem Datum der Lieferung und Leistung + ACB + + + freier Text zur Rechnung BT-22 + ACB + + + + + + 01.01 + 01 + GROUP + + + Baugelände abräumen Anfallender Schutt, Pflanzenreste und Müll entsorgen + + + + 7.00 + + + + 300.00 + + + + VAT + S + 19.00 + + + 2100.00 + + + + + + 01.01.01 + 01.01 + DETAIL + + + Baugelände abräumen + + + + LV 1.1.1.1.1. + LV000001.1 + 130 + Leistungsverzeichnis + BD + + + 7.00 + + + + 100.00 + + + + VAT + S + 19.00 + + + 700.00 + + + + + + 01.01.02 + 01.01 + DETAIL + + + Anfallender Pflanzenreste entsorgen + + + + LV 1.1.1.1.1. + LV000001.2 + 130 + Leistungsverzeichnis + BD + + + 7.00 + + + + 100.00 + + + + VAT + S + 19.00 + + + 700.00 + + + + + + 01.01.03 + 01.01 + DETAIL + + + Müll entsorgen + + + + LV 1.1.1.1.1. + LV000001.3 + 130 + Leistungsverzeichnis + BD + + + 7.00 + + + + 100.00 + + + + VAT + S + 19.00 + + + 700.00 + + + + + + 01.02 + 01 + DETAIL + + + Pflasterfläche vorbereiten, Planum herstellen und verdichten + + + + 6.00 + + + + 250.00 + + + + VAT + S + 19.00 + + + 1500.00 + + + + + + 01 + GROUP + + + Summe 01 Bauabschnitt 1 - Vorarbeiten + + + + 000001 + + + 3600 + 1.0000 + + + + 1.00 + + + + VAT + S + 19.00 + + + 3600.00 + + + + + Kundenref. BT-10 + + 998877 + Musterbetrieb AG Demodaten + + HRA 45678 + + + Kontaktperson + + 5578 + + + absender@musterberieb.de + + + + 37079 + August-Spindler-Strasse 222 + Göttingen + DE + + + DE09687654321 + + + + 330145 + Auftraggeber Firmenkunde GmbH + + Herr Thomas Auftraggeber + + +49 321 456789 + + + thomas.auftraggeber@Firmenkunde.de + + + + 37073 + Gartenstraße 1212 + Göttingen + DE + + + DE1234567890 + + + + G12042-1-01 + + + BT-13 + + + Vertragsnr. BT-12 + + + Vergabenr. BT-17 + 50 + + + Projektnr. BT-11 + Project reference + + + + + Auftraggeber Firmenkunde GmbH + + 37073 + Gartenstraße 1212 + Göttingen + DE + + + + + 20260530 + + + + + EUR + + 58 + + DE75512108001245126199 + Musterbetrieb Kontoname + + + PBNKDEFF + + + + 684.00 + VAT + 3600.00 + S + 19.00 + + + + 20260513 + + + 20260530 + + + + Bei Zahlung bis zum 06.06.2026 zahlen Sie mit 2,50 % Skonto € 4.176,90 + + 20260606 + + + 4284.00 + 2.5 + + + + Bis zum zum 13.06.2026 ohne Abzug + + 20260613 + + + + 3600.00 + 0.00 + 0.00 + 3600.00 + 684.00 + 4284.00 + 0.00 + 4284.00 + + + Kostenstelle BT-19 + + + + diff --git a/library/src/test/resources/subinvoicelines/invalidSubInvoiceLineHierarchy.xml b/library/src/test/resources/subinvoicelines/invalidSubInvoiceLineHierarchy.xml new file mode 100644 index 00000000..89b6cd30 --- /dev/null +++ b/library/src/test/resources/subinvoicelines/invalidSubInvoiceLineHierarchy.xml @@ -0,0 +1,456 @@ + + + + + + + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 99877 + 380 + + 20260530 + + + Geschäftsführer: Herr Geschäftsführer , Muster Bau GmbH etc. + REG + + + Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können. + AAI + + + ZUGFeRD vers 2.4.0 Extended + ACB + + + Dies ist eine Beispiel-Rechnung zur empfohlenen Darstellung von Unterpositionen + ACB + + + + + + 0101 + 01 + DETAIL + + + 88888886349852 + 123456789 + 987654321 + Laser printer B/W + Schwarzweiß Laserdrucker + + + + 300.00 + + + + 2.00 + + + + VAT + S + 19.00 + + + 600.00 + + + + + + 0102 + 01 + DETAIL + + + 77777776349852 + 2345678910 + 876543219 + Ink printer color + Farbdrucker + + + + 150.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 450.00 + + + + + + 01 + GROUP + + + 6666656349852 + 345678912 + 765432198 + Subtotal hardware + Hardware Gesamt + + + + + 999.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + + 999.00 + + + + + + 0201 + 02 + DETAIL + + + 55555556349852 + 456789123 + 654321987 + Toner + Toner + + + + 120.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 360.00 + + + + + + 0202 + 02 + DETAIL + + + 5555556349852 + 567891234 + 543219876 + PAPER + Kopierpapier + + + + 9.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 90.00 + + + + + + 02 + GROUP + + + 2222256349852 + 9345678912 + 9765432198 + Subtotal Accessories + Zubehör Gesamt + + + + 450.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + 450.00 + + + + + Kundenref. BT-10 + + 998877 + Musterbetrieb Systemhaus AG + + HRA 45678 + + + Kontaktperson + + 5578 + + + absender@musterberieb.de + + + + 37079 + August-Müller-Strasse 222 + Göttingen + DE + + + DE09687654321 + + + + 330145 + Auftraggeber Firmenkunde GmbH + + Herr Thomas Auftraggeber + + +49 321 456789 + + + thomas.auftraggeber@Firmenkunde.de + + + + 37073 + Musterstraße 1212 + Göttingen + DE + + + DE1234567890 + + + + G12042-1-01 + + + BT-13 + + + Vertragsnr. BT-12 + + + Vergabenr. BT-17 + 50 + + + Projektnr. BT-11 + Project reference + + + + + Auftraggeber Firmenkunde GmbH + + 37073 + Musterstraße 1212 + Göttingen + DE + + + + + 20260530 + + + + + EUR + + 58 + + DE75512108001245126199 + Musterbetrieb Kontoname + + + PBNKDEFF + + + + 285.00 + VAT + 1500.00 + S + 19.00 + + + Bei Zahlung bis zum 06.06.2026 zahlen Sie mit 2,00 % Skonto € 1749,30 € + + 20260606 + + + 1785.00 + 2.00 + + + + Bis zum zum 13.06.2026 ohne Abzug + + 20260613 + + + + 1500.00 + 0.00 + 0.00 + 1500.00 + 285.00 + 1785.00 + 0.00 + 1785.00 + + + Kostenstelle BT-19 + + + + diff --git a/validator/src/test/resources/invalidSubInvoiceLineHierarchy.xml b/validator/src/test/resources/invalidSubInvoiceLineHierarchy.xml new file mode 100644 index 00000000..89b6cd30 --- /dev/null +++ b/validator/src/test/resources/invalidSubInvoiceLineHierarchy.xml @@ -0,0 +1,456 @@ + + + + + + + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 99877 + 380 + + 20260530 + + + Geschäftsführer: Herr Geschäftsführer , Muster Bau GmbH etc. + REG + + + Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können. + AAI + + + ZUGFeRD vers 2.4.0 Extended + ACB + + + Dies ist eine Beispiel-Rechnung zur empfohlenen Darstellung von Unterpositionen + ACB + + + + + + 0101 + 01 + DETAIL + + + 88888886349852 + 123456789 + 987654321 + Laser printer B/W + Schwarzweiß Laserdrucker + + + + 300.00 + + + + 2.00 + + + + VAT + S + 19.00 + + + 600.00 + + + + + + 0102 + 01 + DETAIL + + + 77777776349852 + 2345678910 + 876543219 + Ink printer color + Farbdrucker + + + + 150.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 450.00 + + + + + + 01 + GROUP + + + 6666656349852 + 345678912 + 765432198 + Subtotal hardware + Hardware Gesamt + + + + + 999.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + + 999.00 + + + + + + 0201 + 02 + DETAIL + + + 55555556349852 + 456789123 + 654321987 + Toner + Toner + + + + 120.00 + + + + 3.00 + + + + VAT + S + 19.00 + + + 360.00 + + + + + + 0202 + 02 + DETAIL + + + 5555556349852 + 567891234 + 543219876 + PAPER + Kopierpapier + + + + 9.00 + + + + 10.00 + + + + VAT + S + 19.00 + + + 90.00 + + + + + + 02 + GROUP + + + 2222256349852 + 9345678912 + 9765432198 + Subtotal Accessories + Zubehör Gesamt + + + + 450.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + 450.00 + + + + + Kundenref. BT-10 + + 998877 + Musterbetrieb Systemhaus AG + + HRA 45678 + + + Kontaktperson + + 5578 + + + absender@musterberieb.de + + + + 37079 + August-Müller-Strasse 222 + Göttingen + DE + + + DE09687654321 + + + + 330145 + Auftraggeber Firmenkunde GmbH + + Herr Thomas Auftraggeber + + +49 321 456789 + + + thomas.auftraggeber@Firmenkunde.de + + + + 37073 + Musterstraße 1212 + Göttingen + DE + + + DE1234567890 + + + + G12042-1-01 + + + BT-13 + + + Vertragsnr. BT-12 + + + Vergabenr. BT-17 + 50 + + + Projektnr. BT-11 + Project reference + + + + + Auftraggeber Firmenkunde GmbH + + 37073 + Musterstraße 1212 + Göttingen + DE + + + + + 20260530 + + + + + EUR + + 58 + + DE75512108001245126199 + Musterbetrieb Kontoname + + + PBNKDEFF + + + + 285.00 + VAT + 1500.00 + S + 19.00 + + + Bei Zahlung bis zum 06.06.2026 zahlen Sie mit 2,00 % Skonto € 1749,30 € + + 20260606 + + + 1785.00 + 2.00 + + + + Bis zum zum 13.06.2026 ohne Abzug + + 20260613 + + + + 1500.00 + 0.00 + 0.00 + 1500.00 + 285.00 + 1785.00 + 0.00 + 1785.00 + + + Kostenstelle BT-19 + + + +