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
This commit is contained in:
@@ -43,6 +43,8 @@ public class Item implements IZUGFeRDExportableItem {
|
|||||||
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
|
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
|
||||||
protected List<IncludedNote> includedNotes = null;
|
protected List<IncludedNote> includedNotes = null;
|
||||||
protected String accountingReference;
|
protected String accountingReference;
|
||||||
|
protected String parentLineID = null;
|
||||||
|
protected String lineStatusReasonCode = null;
|
||||||
//protected HashMap<String, String> attributes = new HashMap<>();
|
//protected HashMap<String, String> attributes = new HashMap<>();
|
||||||
|
|
||||||
/***
|
/***
|
||||||
@@ -91,9 +93,11 @@ public class Item implements IZUGFeRDExportableItem {
|
|||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
itemMap.getAsNodeMap("AssociatedDocumentLineDocument")
|
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(adld -> {
|
||||||
.flatMap(icnm -> icnm.getAsString("LineID"))
|
adld.getAsString("LineID").ifPresent(this::setId);
|
||||||
.ifPresent(this::setId);
|
adld.getAsString("ParentLineID").ifPresent(this::setParentLineID);
|
||||||
|
adld.getAsString("LineStatusReasonCode").ifPresent(this::setLineStatusReasonCode);
|
||||||
|
});
|
||||||
|
|
||||||
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
|
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
|
||||||
// ubl
|
// ubl
|
||||||
@@ -644,4 +648,34 @@ public class Item implements IZUGFeRDExportableItem {
|
|||||||
public String getAccountingReference() {
|
public String getAccountingReference() {
|
||||||
return accountingReference;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,36 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
|||||||
return null;
|
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.
|
* A grouping of business terms to indicate accounting-relevant free texts including a qualification of these.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -155,12 +155,15 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
|||||||
|
|
||||||
/***
|
/***
|
||||||
* returns the total net value of all items, without document level
|
* 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
|
* @return item sum
|
||||||
*/
|
*/
|
||||||
protected BigDecimal getTotal() {
|
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);
|
.map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add);
|
||||||
return dec;
|
return dec;
|
||||||
}
|
}
|
||||||
@@ -190,6 +193,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
|||||||
final String vatDueDateTypeCode = trans.getVATDueDateTypeCode();
|
final String vatDueDateTypeCode = trans.getVATDueDateTypeCode();
|
||||||
|
|
||||||
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
|
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
|
||||||
|
// skip GROUP and INFORMATION lines for sub invoice lines
|
||||||
|
if (!currentItem.isCalculationRelevant()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
BigDecimal percent = null;
|
BigDecimal percent = null;
|
||||||
if (currentItem.getProduct() != null) {
|
if (currentItem.getProduct() != null) {
|
||||||
percent = currentItem.getProduct().getVATPercent();
|
percent = currentItem.getProduct().getVATPercent();
|
||||||
@@ -258,6 +265,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
|||||||
final String vatDueDateTypeCode = this.trans.getVATDueDateTypeCode();
|
final String vatDueDateTypeCode = this.trans.getVATDueDateTypeCode();
|
||||||
for (final IZUGFeRDExportableItem currentItem : this.trans.getZFItems())
|
for (final IZUGFeRDExportableItem currentItem : this.trans.getZFItems())
|
||||||
{
|
{
|
||||||
|
// skip GROUP and INFORMATION lines for sub invoice lines
|
||||||
|
if (!currentItem.isCalculationRelevant()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
BigDecimal percent = null;
|
BigDecimal percent = null;
|
||||||
if (currentItem.getProduct() != null)
|
if (currentItem.getProduct() != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -895,4 +895,117 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
|
|||||||
assertNull(invoice.getDespatchAdviceReferencedDocumentID());
|
assertNull(invoice.getDespatchAdviceReferencedDocumentID());
|
||||||
assertNull(invoice.getInvoiceReferencedDocumentID());
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.io.PrintWriter;
|
|||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -24,6 +25,8 @@ import javax.xml.xpath.XPathFactory;
|
|||||||
|
|
||||||
import org.mustangproject.CalculatedInvoice;
|
import org.mustangproject.CalculatedInvoice;
|
||||||
import org.mustangproject.XMLTools;
|
import org.mustangproject.XMLTools;
|
||||||
|
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
|
||||||
|
import org.mustangproject.ZUGFeRD.LineCalculator;
|
||||||
import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter;
|
import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -454,6 +457,9 @@ public class XMLValidator extends Validator {
|
|||||||
CalculatedInvoice ci=new CalculatedInvoice();
|
CalculatedInvoice ci=new CalculatedInvoice();
|
||||||
zi.extractInto(ci);
|
zi.extractInto(ci);
|
||||||
|
|
||||||
|
// check sub invoice line hierarchy if present
|
||||||
|
checkSubInvoiceLineHierarchy(ci, context);
|
||||||
|
|
||||||
} catch ( ArithmeticException e) {
|
} catch ( ArithmeticException e) {
|
||||||
try {
|
try {
|
||||||
context.addResultItem(new ValidationResultItem(ESeverity.warning, "Arithmetical issue:"+e.getMessage()).setSection(10));
|
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<String, IZUGFeRDExportableItem> 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 {
|
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
|
//Guideline ID=urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 or
|
||||||
|
|||||||
@@ -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 = "<validation>" + xv.getXMLResult() + "</validation>";
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user