diff --git a/History.md b/History.md index 36fa2040..90d1cdb4 100644 --- a/History.md +++ b/History.md @@ -3,7 +3,7 @@ 2024- - 435 use invoiceimporter as common technical basis also for zugferdimporter - also import delivery address -- 527 metrics raises errors +- 527 metrics may raise error on some pdf files - 517 read product GlobalID - 380 Added test for input stream validation - 518 corrently validate more XRechnung versions @@ -13,7 +13,9 @@ - 532 support validation warnings! - 534 new signature - 538 Mustang validator always claims PDF is invalid if flavour is PDF/A-3A - +- 555 be able to validate ubl credit notes +- when parsing now distinguishing between the parseExceptions StructureException and ArithmetricException +- Import IncludedNotes on invoice extraction #554 2.14.2 ======= diff --git a/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java b/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java new file mode 100644 index 00000000..de063e81 --- /dev/null +++ b/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java @@ -0,0 +1,13 @@ +package org.mustangproject.Exceptions; + +import java.text.ParseException; + +/*** + * will be thrown if a invoice cant be reproduced numerically + */ +public class ArithmetricException extends ParseException { + public ArithmetricException() { + super( + "Could not reproduce the invoice, this could mean that it could not be read properly", 0); + } +} diff --git a/library/src/main/java/org/mustangproject/Exceptions/StructureException.java b/library/src/main/java/org/mustangproject/Exceptions/StructureException.java new file mode 100644 index 00000000..1859da7a --- /dev/null +++ b/library/src/main/java/org/mustangproject/Exceptions/StructureException.java @@ -0,0 +1,12 @@ +package org.mustangproject.Exceptions; + +import java.text.ParseException; + +/*** + * will be thrown if a invoice cant be read + */ +public class StructureException extends ParseException { + public StructureException(String message, int line) { + super(message, line); + } +} diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index a5408146..a45d57ba 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonInclude(JsonInclude.Include.NON_EMPTY) public class TradeParty implements IZUGFeRDExportableTradeParty { - protected String name, zip, street, location, country; + protected String name, zip, street, location, country, taxScheme; protected String taxID = null, vatID = null; protected String ID = null; protected String description = null; @@ -91,8 +91,46 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } } - // UBL only: formally it can have a name as well but BT27 party name *should* be stored in - // so overwrite if one exists + if (currentTopElementName.equals("PartyTaxScheme")) { + NodeList partyTaxScheme = party.item(partyIndex).getChildNodes(); + for (int partyTaxSchemeIndex = 0; partyTaxSchemeIndex < partyTaxScheme.getLength(); partyTaxSchemeIndex++) { + if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName() != null) { + if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("CompanyID")) { + setTaxID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); + + } + } + } + } + + +// if (currentTopElementName.equals("PartyTaxScheme")) { +// NodeList partyTaxScheme = party.item(partyIndex).getChildNodes(); +// for (int partyTaxSchemeIndex = 0; partyTaxSchemeIndex < partyTaxScheme.getLength(); partyTaxSchemeIndex++) { +// if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName() != null) { +// if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("TaxScheme")) { +// NodeList taxScheme = partyTaxScheme.item(partyTaxSchemeIndex).getChildNodes(); +// for (int taxSchemeIndex = 0 ; taxSchemeIndex < taxScheme.getLength(); taxSchemeIndex++) { +// if (taxScheme.item(taxSchemeIndex).getLocalName() != null) { +// if(taxScheme.item(taxSchemeIndex).getLocalName().equals("ID")){ +// if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("CompanyID")) { +// setTaxID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); +// } else { +// setVATID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); +// } +// } +// } +// } +// } +// +// } +// } +// } + /* + UBL only: formally it can have a name as well but BT27 party name *should* be stored in + so overwrite if one exists + */ + if (currentTopElementName.equals("PartyLegalEntity")) { NodeList legal = party.item(partyIndex).getChildNodes(); for (int legalChildIndex = 0; legalChildIndex < legal.getLength(); legalChildIndex++) { @@ -100,6 +138,9 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) { setName(legal.item(legalChildIndex).getTextContent()); } + if (legal.item(legalChildIndex).getLocalName().equals("CompanyLegalForm")) { + setDescription(legal.item(legalChildIndex).getTextContent()); + } } } } @@ -212,26 +253,27 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } - if (currentUBLChild.equals("SpecifiedTaxRegistration")) { + if (currentUBLChild.equals("PartyTaxScheme")) { NodeList taxChilds = nodes.item(nodeIndex).getChildNodes(); for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { if (taxChilds.item(taxChildIndex).getLocalName() != null) { - if ((taxChilds.item(taxChildIndex).getLocalName().equals("ID"))) { - if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) { - Node firstChild = taxChilds.item(taxChildIndex).getFirstChild(); - if (firstChild != null) { - if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("VA")) { - setVATID(firstChild.getNodeValue()); + if ((taxChilds.item(taxChildIndex).getLocalName().equals("TaxScheme"))) { + if (taxChilds.item(taxChildIndex).getLocalName().equals("CompanyID")) { + if (taxChilds.item(taxChildIndex).getLocalName().equals("ID")) { + if (taxChilds.item(taxChildIndex).getLocalName().equals("CompanyID")) { + setVATID(taxChilds.item(taxChildIndex).getTextContent()); } - if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("FC")) { - setTaxID(firstChild.getNodeValue()); +// setTaxID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); + if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("ID").getNodeValue().equals("FC")) { + if (taxChilds.item(taxChildIndex).getLocalName().equals("CompanyID")) { + setTaxID(taxChilds.item(taxChildIndex).getTextContent()); + } } } } } } } - } } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index e9c5f712..cebeb824 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -54,15 +54,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - /*** - * Wrapper for protected method extractString - * @param xpathStr the xpath expression to be evaluated - * @return the extracted String for the specific path in the document - */ - public String wExtractString(String xpathStr) { - return extractString(xpathStr); - } - //////////////////////////////////// diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 901f1b96..d9bd7e42 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -9,6 +9,8 @@ import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.mustangproject.*; +import org.mustangproject.Exceptions.ArithmetricException; +import org.mustangproject.Exceptions.StructureException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -352,6 +354,34 @@ public class ZUGFeRDInvoiceImporter { } } } + List includedNotes = new ArrayList<>(); + if ((item.getLocalName() != null) && (item.getLocalName().equals("IncludedNote"))) { + String subjectCode = ""; + String content = null; + NodeList includedNodeChilds = item.getChildNodes(); + for (int issueDateChildIndex = 0; issueDateChildIndex < includedNodeChilds.getLength(); issueDateChildIndex++) { + if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null) + && (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("Content"))) { + content = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex)); + } + if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null) + && (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("SubjectCode"))) { + subjectCode = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex)); + } + } + switch (subjectCode){ + case "AAI": includedNotes.add(IncludedNote.generalNote(content)); break; + case "REG": includedNotes.add(IncludedNote.regulatoryNote(content)); break; + case "ABL": includedNotes.add(IncludedNote.legalNote(content)); break; + case "CUS": includedNotes.add(IncludedNote.customsNote(content)); break; + case "SUR": includedNotes.add(IncludedNote.sellerNote(content)); break; + case "TXD": includedNotes.add(IncludedNote.taxNote(content)); break; + case "ACY": includedNotes.add(IncludedNote.introductionNote(content)); break; + case "AAK": includedNotes.add(IncludedNote.discountBonusNote(content)); break; + default: includedNotes.add(IncludedNote.unspecifiedNote(content)); break; + } + } + zpp.addNotes(includedNotes); } } String rootNode = extractString("local-name(/*)"); @@ -440,7 +470,8 @@ public class ZUGFeRDInvoiceImporter { } - String currency = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|*[local-name()=\"DocumentCurrencyCode\"]"); + + String currency = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|//*[local-name()=\"DocumentCurrencyCode\"]") ; zpp.setCurrency(currency); xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]"); @@ -578,6 +609,9 @@ public class ZUGFeRDInvoiceImporter { if (buyerOrderIssuerAssignedID != null) { zpp.setBuyerOrderReferencedDocumentID(buyerOrderIssuerAssignedID); } + else { + zpp.setBuyerOrderReferencedDocumentID(extractString("//*[local-name()=\"OrderReference\"]/*[local-name()=\"ID\"]")); + } if (sellerOrderIssuerAssignedID != null) { zpp.setSellerOrderReferencedDocumentID(sellerOrderIssuerAssignedID); } @@ -697,15 +731,13 @@ public class ZUGFeRDInvoiceImporter { try { whichType = getStandard(); } catch (Exception e) { - throw new ParseException("Could not find out if it's an invoice, order, or delivery advice", 0); - + throw new StructureException("Could not find out if it's an invoice, order, or delivery advice", 0); } if ((whichType != EStandard.despatchadvice) && ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2))) && (!ignoreCalculationErrors))) { - throw new ParseException( - "Could not reproduce the invoice, this could mean that it could not be read properly", 0); + throw new ArithmetricException(); } } return zpp; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java index b2e47e3c..64e14fc3 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java @@ -249,6 +249,12 @@ public class ZUGFeRDVisualizer { EStandard theStandard = findOutStandardFromRootNode(fis); fis = new FileInputStream(xmlFilename);//rewind :-( + return toFOP(fis, theStandard); + } + + protected String toFOP(InputStream is, EStandard theStandard) + throws FileNotFoundException, TransformerException { + try { if (mXsltPDFTemplate == null) { mXsltPDFTemplate = mFactory.newTemplates( @@ -263,11 +269,11 @@ public class ZUGFeRDVisualizer { //zf2 or fx if (theStandard == EStandard.facturx) { - applyZF2XSLT(fis, iaos); + applyZF2XSLT(is, iaos); } else if (theStandard == EStandard.ubl) { - applyUBL2XSLT(fis, iaos); + applyUBL2XSLT(is, iaos); } else if (theStandard == EStandard.ubl_creditnote) { - applyUBLCreditNote2XSLT(fis, iaos); + applyUBLCreditNote2XSLT(is, iaos); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ImporterTest.java deleted file mode 100644 index 08fddcad..00000000 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ImporterTest.java +++ /dev/null @@ -1,431 +0,0 @@ - -/** - * ********************************************************************* - *

- * Copyright 2019 Jochen Staerk - *

- * Use is subject to license terms. - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0. - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - *

- * See the License for the specific language governing permissions and - * limitations under the License. - *

- * ********************************************************************** - */ -package org.mustangproject.ZUGFeRD; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.mustangproject.*; - -import javax.xml.xpath.XPathExpressionException; -import java.io.*; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; - - -/*** - * Classname ZF2ZInvoiceImporterTest is alphabetical behind the tests which will create the file - * used for this import, testout-ZF2New.pdf - */ -public class ZF2ZInvoiceImporterTest extends ResourceCase { - - - public void testInvoiceImport() { - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2new.pdf"); - - boolean hasExceptions = false; - Invoice invoice = null; - try { - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - // Reading ZUGFeRD - assertEquals("Bei Spiel GmbH", invoice.getOwnOrganisationName()); - assertEquals(3, invoice.getZFItems().length); - assertEquals("400.0000", invoice.getZFItems()[1].getQuantity().toString()); - - assertEquals("AB321", invoice.getReferenceNumber()); - assertEquals("160.0000", invoice.getZFItems()[0].getPrice().toString()); - assertEquals("Heiße Luft pro Liter", invoice.getZFItems()[2].getProduct().getName()); - assertEquals("LTR", invoice.getZFItems()[2].getProduct().getUnit()); - assertEquals("7.00", invoice.getZFItems()[0].getProduct().getVATPercent().toString()); - assertEquals("RE-20170509/505", invoice.getNumber()); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("2017-05-09", sdf.format(invoice.getIssueDate())); - assertEquals("2017-05-07", sdf.format(invoice.getDeliveryDate())); - assertEquals("2017-05-30", sdf.format(invoice.getDueDate())); - - assertEquals("Bahnstr. 42", invoice.getRecipient().getStreet()); - assertEquals("Hinterhaus", invoice.getRecipient().getAdditionalAddress()); - assertEquals("Zweiter Stock", invoice.getRecipient().getAdditionalAddressExtension()); - assertEquals("88802", invoice.getRecipient().getZIP()); - assertEquals("DE", invoice.getRecipient().getCountry()); - assertEquals("Spielkreis", invoice.getRecipient().getLocation()); - - assertEquals("Ecke 12", invoice.getSender().getStreet()); - assertEquals("12345", invoice.getSender().getZIP()); - assertEquals("DE", invoice.getSender().getCountry()); - assertEquals("Stadthausen", invoice.getSender().getLocation()); - - TransactionCalculator tc = new TransactionCalculator(invoice); - assertEquals(new BigDecimal("571.04"), tc.getGrandTotal()); - - - // name street location zip country, contact name phone email, total amount - - } - - public void testInvoiceImportUBL() { - - - boolean hasExceptions = false; - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); - File expectedResult = getResourceAsFile("testout-ZF2new.ubl.xml"); - - Invoice invoice = null; - try { - String xml = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8).replace("\r", "").replace("\n", ""); - - zii.fromXML(xml); - - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException | IOException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - // Reading ZUGFeRD - assertEquals("Bei Spiel GmbH", invoice.getOwnOrganisationName()); - assertEquals(3, invoice.getZFItems().length); - assertEquals("400", invoice.getZFItems()[1].getQuantity().toString()); - - assertEquals("AB321", invoice.getReferenceNumber()); - assertEquals("160", invoice.getZFItems()[0].getPrice().toString()); - assertEquals("Heiße Luft pro Liter", invoice.getZFItems()[2].getProduct().getName()); - assertEquals("LTR", invoice.getZFItems()[2].getProduct().getUnit()); - assertEquals("7", invoice.getZFItems()[0].getProduct().getVATPercent().toString()); - assertEquals("RE-20170509/505", invoice.getNumber()); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("2017-05-09", sdf.format(invoice.getIssueDate())); - assertEquals("2017-05-07", sdf.format(invoice.getDeliveryDate())); - assertEquals("2017-05-30", sdf.format(invoice.getDueDate())); - - assertEquals("Bahnstr. 42", invoice.getRecipient().getStreet()); - assertEquals("Hinterhaus", invoice.getRecipient().getAdditionalAddress()); - assertEquals("Zweiter Stock", invoice.getRecipient().getAdditionalAddressExtension()); - assertEquals("88802", invoice.getRecipient().getZIP()); - assertEquals("DE", invoice.getRecipient().getCountry()); - assertEquals("Spielkreis", invoice.getRecipient().getLocation()); - - assertEquals("Ecke 12", invoice.getSender().getStreet()); - assertEquals("12345", invoice.getSender().getZIP()); - assertEquals("DE", invoice.getSender().getCountry()); - assertEquals("Stadthausen", invoice.getSender().getLocation()); - - assertTrue(invoice.getPayee() != null); - assertEquals("VR Factoring GmbH", invoice.getPayee().getName()); - - TransactionCalculator tc = new TransactionCalculator(invoice); - assertEquals(new BigDecimal("571.04"), tc.getGrandTotal()); - - - // name street location zip country, contact name phone email, total amount - - } - - public void testEdgeInvoiceImport() { - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushEdge.pdf"); - - boolean hasExceptions = false; - Invoice invoice = null; - try { - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - // Reading ZUGFeRD - assertEquals("4711", invoice.getZFItems()[0].getProduct().getSellerAssignedID()); - assertEquals("9384", invoice.getSellerOrderReferencedDocumentID()); - assertEquals("sender@test.org", invoice.getSender().getEmail()); - assertEquals("recipient@test.org", invoice.getRecipient().getEmail()); - assertEquals("28934", invoice.getBuyerOrderReferencedDocumentID()); - - } - - - public void testZF1Import() { - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-MustangGnuaccountingBeispielRE-20171118_506zf1.pdf"); - - boolean hasExceptions = false; - Invoice invoice = null; - try { - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - // Reading ZUGFeRD - assertEquals("Bei Spiel GmbH", invoice.getOwnOrganisationName()); - assertEquals(3, invoice.getZFItems().length); - assertEquals("400.0000", invoice.getZFItems()[1].getQuantity().toString()); - - assertEquals("160.0000", invoice.getZFItems()[0].getPrice().toString()); - assertEquals("Hot air „heiße Luft“ (litres)", invoice.getZFItems()[2].getProduct().getName()); - assertEquals("LTR", invoice.getZFItems()[2].getProduct().getUnit()); - assertEquals("7.00", invoice.getZFItems()[0].getProduct().getVATPercent().toString()); - assertEquals("RE-20190610/507", invoice.getNumber()); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("2019-06-10", sdf.format(invoice.getIssueDate())); - assertEquals("2019-07-01", sdf.format(invoice.getDueDate())); - - assertEquals("street", invoice.getRecipient().getStreet()); - assertEquals("zip", invoice.getRecipient().getZIP()); - assertEquals("DE", invoice.getRecipient().getCountry()); - assertEquals("city", invoice.getRecipient().getLocation()); - - assertEquals("street", invoice.getSender().getStreet()); - assertEquals("zip", invoice.getSender().getZIP()); - assertEquals("DE", invoice.getSender().getCountry()); - assertEquals("city", invoice.getSender().getLocation()); - - TransactionCalculator tc = new TransactionCalculator(invoice); - assertEquals(new BigDecimal("571.04"), tc.getGrandTotal()); - - - // name street location zip country, contact name phone email, total amount - - - } - - public void testItemAllowancesChargesImport() { - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushItemChargesAllowances.pdf"); - - boolean hasExceptions = false; - Invoice invoice = null; - try { - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - TransactionCalculator tc = new TransactionCalculator(invoice); - assertEquals(new BigDecimal("18.33"), tc.getGrandTotal()); - } - - public void testBasisQuantityImport() { - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2newEdge.pdf"); - - boolean hasExceptions = false; - Invoice invoice = null; - try { - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - TransactionCalculator tc = new TransactionCalculator(invoice); - assertEquals(new BigDecimal("337.60"), tc.getGrandTotal()); - } - - public void testAllowancesChargesImport() { - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushChargesAllowances.pdf"); - - boolean hasExceptions = false; - Invoice invoice = null; - try { - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - TransactionCalculator tc = new TransactionCalculator(invoice); - assertEquals(new BigDecimal("11.07"), tc.getGrandTotal()); - - } - - public void testXRImport() { - boolean hasExceptions = false; - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); - try { - zii.fromXML(new String(Files.readAllBytes(Paths.get("./target/testout-XR-Edge.xml")), StandardCharsets.UTF_8)); - - } catch (IOException e) { - hasExceptions = true; - } - - Invoice invoice = null; - try { - invoice = zii.extractInvoice(); - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } - assertFalse(hasExceptions); - TransactionCalculator tc = new TransactionCalculator(invoice); - assertEquals(new BigDecimal("1.00"), tc.getGrandTotal()); - assertTrue(invoice.getTradeSettlement().length==1); - assertTrue(invoice.getTradeSettlement()[0] instanceof IZUGFeRDTradeSettlementPayment); - IZUGFeRDTradeSettlementPayment paym=(IZUGFeRDTradeSettlementPayment)invoice.getTradeSettlement()[0]; - assertEquals("DE12500105170648489890", paym.getOwnIBAN()); - assertEquals("COBADEFXXX", paym.getOwnBIC()); - - - assertTrue(invoice.getPayee() != null); - assertEquals("VR Factoring GmbH", invoice.getPayee().getName()); - } - - /** - * testing if other files embedded in pdf additionally to the invoice can be read correctly - * */ - public void testDetach() { - boolean hasExceptions = false; - - byte[] fileA=null; - byte[] fileB=null; - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushAttachments.pdf"); - for (FileAttachment fa:zii.getFileAttachmentsPDF()) { - if (fa.getFilename().equals("one.pdf")) { - fileA=fa.getData(); - } else if (fa.getFilename().equals("two.pdf")) { - fileB=fa.getData(); - } - } - byte[] b = {12, 13}; // the sample data that was used to write the files - - assertTrue(Arrays.equals(fileA, b)); - assertEquals(fileA.length, 2); - assertTrue(Arrays.equals(fileB, b)); - assertEquals(fileB.length, 2); - } - - - - public void testImportDebit() { - File CIIinputFile = getResourceAsFile("cii/minimalDebit.xml"); - try { - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(CIIinputFile)); - Invoice i=zii.extractInvoice(); - - assertEquals("DE21860000000086001055", i.getSender().getBankDetails().get(0).getIBAN()); - ObjectMapper mapper = new ObjectMapper(); - - String jsonArray = mapper.writeValueAsString(i); - - // assertEquals("",jsonArray); - - } catch (IOException e) { - fail("IOException not expected"); - } catch (XPathExpressionException e) { - throw new RuntimeException(e); - } catch (ParseException e) { - throw new RuntimeException(e); - } - - - } - - - - - - public void testImportMinimum() { - File CIIinputFile = getResourceAsFile("cii/facturFrMinimum.xml"); - try { - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(CIIinputFile)); - - - CalculatedInvoice i=new CalculatedInvoice(); - zii.extractInto(i); - assertEquals("671.15", i.getGrandTotal().toString()); - - } catch (IOException e) { - fail("IOException not expected"); - } catch (XPathExpressionException e) { - throw new RuntimeException(e); - } catch (ParseException e) { - throw new RuntimeException(e); - } - - - } - - - public void testEEISI_300_cii_Import() throws XPathExpressionException, ParseException { - boolean hasExceptions = false; - File inputCII = getResourceAsFile("not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml"); - File inputUBL = getResourceAsFile("not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.ubl.xml"); - - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); - try { - zii.fromXML(new String(Files.readAllBytes(inputCII.toPath()), StandardCharsets.UTF_8)); - - } catch (IOException e) { - hasExceptions = true; - } - - Invoice invoiceUBL = null; - invoiceUBL = zii.extractInvoice(); - - try { - zii.fromXML(new String(Files.readAllBytes(inputUBL.toPath()), StandardCharsets.UTF_8)); - - } catch (IOException e) { - hasExceptions = true; - } - - Invoice invoiceCII = null; - try { - invoiceCII = zii.extractInvoice(); - ObjectMapper mapper = new ObjectMapper(); - String ubl=mapper.writeValueAsString(invoiceUBL); - String cii=mapper.writeValueAsString(invoiceCII); - - assertEquals(cii,ubl); - - - /* - Seller contact point - +41 345 654455 - seller@contact.de);*/ - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - assertFalse(hasExceptions); - - TransactionCalculator tc = new TransactionCalculator(invoiceCII); - assertEquals(new BigDecimal("205.00"), tc.getGrandTotal()); - - } - - -} diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index a060b474..894fcab8 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -22,9 +22,8 @@ package org.mustangproject.ZUGFeRD; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.mustangproject.CalculatedInvoice; -import org.mustangproject.FileAttachment; -import org.mustangproject.Invoice; +import org.junit.jupiter.api.Test; +import org.mustangproject.*; import javax.xml.xpath.XPathExpressionException; import java.io.File; @@ -37,6 +36,10 @@ import java.nio.file.Paths; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; /*** @@ -352,8 +355,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { } - - public void testImportMinimum() { + public void testImportMinimum() { File CIIinputFile = getResourceAsFile("cii/facturFrMinimum.xml"); try { ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(CIIinputFile)); @@ -373,59 +375,25 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { } -/* -this would test if for all elements/attributes - */ - public void testEEISI_300_cii_Import() throws XPathExpressionException, ParseException { - boolean hasExceptions = false; - File inputCII = getResourceAsFile("not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml"); - File inputUBL = getResourceAsFile("not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.ubl.xml"); - - - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); - try { - zii.fromXML(new String(Files.readAllBytes(inputCII.toPath()), StandardCharsets.UTF_8)); - - } catch (IOException e) { - hasExceptions = true; - } - - Invoice invoiceUBL = null; - invoiceUBL = zii.extractInvoice(); - - try { - zii.fromXML(new String(Files.readAllBytes(inputUBL.toPath()), StandardCharsets.UTF_8)); - - } catch (IOException e) { - hasExceptions = true; - } - - Invoice invoiceCII = null; - try { - invoiceCII = zii.extractInvoice(); - ObjectMapper mapper = new ObjectMapper(); - String ubl = mapper.writeValueAsString(invoiceUBL); - String cii = mapper.writeValueAsString(invoiceCII); - - //assertEquals(cii,ubl); - - - /* - Seller contact point - +41 345 654455 - seller@contact.de);* - } catch (XPathExpressionException | ParseException e) { - hasExceptions = true; - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - assertFalse(hasExceptions); - - TransactionCalculator tc = new TransactionCalculator(invoiceCII); - assertEquals(new BigDecimal("205.00"), tc.getGrandTotal()); + @Test + public void testImportIncludedNotes() throws XPathExpressionException, ParseException { + InputStream inputStream = this.getClass() + .getResourceAsStream("/EN16931_Einfach.pdf"); + ZUGFeRDInvoiceImporter importer = new ZUGFeRDInvoiceImporter(inputStream); + Invoice invoice = importer.extractInvoice(); + List notesWithSubjectCode = invoice.getNotesWithSubjectCode(); + assertThat(notesWithSubjectCode).hasSize(2); + assertThat(notesWithSubjectCode.get(0).getSubjectCode()).isNull(); + assertThat(notesWithSubjectCode.get(0).getContent()).isEqualTo("Rechnung gemäß Bestellung vom 01.11.2024."); + assertThat(notesWithSubjectCode.get(1).getSubjectCode()).isEqualTo(SubjectCode.REG); + assertThat(notesWithSubjectCode.get(1).getContent()).isEqualTo("Lieferant GmbH\t\t\t\t\n" + + "Lieferantenstraße 20\t\t\t\t\n" + + "80333 München\t\t\t\t\n" + + "Deutschland\t\t\t\t\n" + + "Geschäftsführer: Hans Muster\n" + + "Handelsregisternummer: H A 123"); } -*/ } diff --git a/library/src/test/resources/EN16931_Einfach.pdf b/library/src/test/resources/EN16931_Einfach.pdf new file mode 100755 index 00000000..802b7282 Binary files /dev/null and b/library/src/test/resources/EN16931_Einfach.pdf differ diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 4392448e..c2124b87 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -269,13 +269,13 @@ public class XMLValidator extends Validator { // saxon java net.sf.saxon.Transform -o tcdl2.0.tsdtf.sch.tmp.xsl -s // tcdl2.0.tsdtf.sch iso_svrl.xsl - } else if (root.getLocalName().equalsIgnoreCase("Invoice")) { + } else if (root.getLocalName().equalsIgnoreCase("Invoice") || root.getLocalName().equalsIgnoreCase("CreditNote") ) { context.setGeneration("2"); context.setFormat("UBL"); isXRechnung = context.getProfile().contains("xrechnung"); // UBL LOGGER.debug("UBL"); - validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "UBL_21/maindoc/UBL-Invoice-2.1.xsd", 18, EPart.fx); + validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "UBL_21/maindoc/UBL-"+root.getLocalName()+"-2.1.xsd", 18, EPart.fx); xsltFilename = "/xslt/en16931schematron/EN16931-UBL-validation.xslt"; mainSchematronSectionErrorTypeCode=24; diff --git a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java index 8b6022f5..2fda10ef 100644 --- a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java @@ -204,6 +204,36 @@ public class XMLValidatorTest extends ResourceCase { } + public void testXRCIIPeppolFailureValidation() { + final ValidationContext ctx = new ValidationContext(null); + final XMLValidator xv = new XMLValidator(ctx); + final XPathEngine xpath = new JAXPXPathEngine(); + + // GIVEN XRechnung CII with Peppol rule violation + File file = getResourceAsFile("CII_XRechnung_with_Peppol_violation.xml"); + + boolean noExceptions = true; + try { + xv.setFilename(file.getAbsolutePath()); + + // WHEN validated + xv.validate(); + + Source source = Input.fromString("" + xv.getXMLResult() + "").build(); + + // THEN validation returns only warning message + boolean onlyWarnings = Boolean.parseBoolean(xpath.evaluate("not(//messages/*[not(self::warning)])", source)); + assertTrue(onlyWarnings); + + // THEN validation returns summary status valid + String status = xpath.evaluate("/validation/summary/@status", source); + assertEquals("valid", status); + } catch (IrrecoverableValidationError e) { + noExceptions = false; + } + assertTrue(noExceptions); + } + public void testXRValidation() { final ValidationContext ctx = new ValidationContext(null); final XMLValidator xv = new XMLValidator(ctx); @@ -298,6 +328,22 @@ public class XMLValidatorTest extends ResourceCase { noExceptions = false; } assertTrue(noExceptions); + tempFile = getResourceAsFile("ubl-tc434-creditnote1.xml"); + try { + xv.setFilename(tempFile.getAbsolutePath()); + xv.validate(); + + Source source = Input.fromString("" + xv.getXMLResult() + "").build(); + String content = xpath.evaluate("/validation/summary/@status", source); + assertEquals("valid", content); + + + } catch (IrrecoverableValidationError e) { + + noExceptions = false; + } + assertTrue(noExceptions); + } diff --git a/validator/src/test/resources/CII_XRechnung_with_Peppol_violation.xml b/validator/src/test/resources/CII_XRechnung_with_Peppol_violation.xml new file mode 100644 index 00000000..8e023db5 --- /dev/null +++ b/validator/src/test/resources/CII_XRechnung_with_Peppol_violation.xml @@ -0,0 +1,144 @@ + + + + + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + + + urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 + + + + RE0021 + 380 + + 20241002 + + + Rechnung + AFM + + + Unsere Lieferungen/Leistungen stellen wir Ihnen wie folgt in Rechnung. + AAI + + + Vielen Dank für die gute Zusammenarbeit. + SUR + + + + + + 1 + + + 1 + + + + 140.0000 + 1.0000 + + + + 1.0000 + + + + VAT + S + 19.00 + + + 140.00 + + + + + 992-90009-96 + + 231132 + Max Muster + + Max Muster + + 0192435345 + + + muster@example.com + + + + 12345 + Straße + Stadt + DE + + + muster@example.com + + + DE99999/99999 + + + DE325845615 + + + + KUnde + + 12345 + Straße + Stadt + DE + + + + + + + 20241002 + + + + + RE0021 + EUR + + 1 + Überweisung + + DE50110101002129646573 + Max Muster + + + BEVODEBBXXX + + + + 26.60 + VAT + 140.00 + S + 19.00 + + + Zahlbar sofort, rein netto + + 20241002 + + + + 140.00 + 0.00 + 0.00 + 140.00 + 26.60 + 166.60 + 0.00 + 166.60 + + + + diff --git a/validator/src/test/resources/ubl-tc434-creditnote1.xml b/validator/src/test/resources/ubl-tc434-creditnote1.xml new file mode 100644 index 00000000..b9380fb3 --- /dev/null +++ b/validator/src/test/resources/ubl-tc434-creditnote1.xml @@ -0,0 +1,136 @@ + + + + urn:cen.eu:en16931:2017 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + 018304 / 28865 + 2019-09-23 + 381 + EUR + 018304 / 28865 + + 2019-02-01 + 2019-02-28 + + + + 0000000196 + + My Supplier Company N.V. + + + De Grote Meir 22 + ANTWERPEN + 2000 + + BE + + + + BE0000000196 + + VAT + + + + My Supplier Company + 0000000196 + + + john.doole@mysuppliercompany.be + + + + + + 0000000295 + + My Customer Company S.A. + + + Boulevard Sint Michel 53 + BRUXELLES + 1000 + + BE + + + + BE0000000295 + + VAT + + + + My Customer Company + 0000000295 + + + pete.smith@mycustomercompany.be + + + + + 1 + 010676609538 + + BE91000000143476 + + BPOTBEB1 + + + + + 0.00 + + 100.11 + 0.00 + + E + 0.00 + Taxes are not applicable + + VAT + + + + + + 100.11 + 100.11 + 100.11 + 100.11 + + + 1 + 1.00 + 100.11 + + Exonération du versement du PP + Exonération du versement du PP + + V55 + + + E + 0.00 + + VAT + + + + 2 + Contributions - précompte professionnel + + + + 100.11 + + +