shuffeled the methods, replaced getGrandTotal
This commit is contained in:
@@ -49,219 +49,22 @@ import org.w3c.dom.Node;
|
|||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class ZUGFeRDImporter {
|
public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
|
||||||
|
public ZUGFeRDImporter() {
|
||||||
/**
|
super();
|
||||||
* if metadata has been found
|
|
||||||
*/
|
|
||||||
protected boolean containsMeta = false;
|
|
||||||
/**
|
|
||||||
* map filenames of additional XML files to their contents
|
|
||||||
*/
|
|
||||||
private final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
|
|
||||||
/**
|
|
||||||
* map filenames of all embedded files in the respective PDF
|
|
||||||
*/
|
|
||||||
private final ArrayList<FileAttachment> PDFAttachments = new ArrayList<>();
|
|
||||||
/**
|
|
||||||
* Raw XML form of the extracted data - may be directly obtained.
|
|
||||||
*/
|
|
||||||
private byte[] rawXML = null;
|
|
||||||
/**
|
|
||||||
* XMP metadata
|
|
||||||
*/
|
|
||||||
private String xmpString = null; // XMP metadata
|
|
||||||
/**
|
|
||||||
* parsed Document
|
|
||||||
*/
|
|
||||||
private Document document;
|
|
||||||
private Integer version;
|
|
||||||
Invoice importedInvoice=null;
|
|
||||||
private boolean recalcPrice = false;
|
|
||||||
private boolean ignoreCalculationErrors = false;
|
|
||||||
private ArrayList<FileAttachment> fileAttachments=new ArrayList<>();
|
|
||||||
|
|
||||||
|
|
||||||
protected ZUGFeRDImporter() {
|
|
||||||
//constructor for extending classes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZUGFeRDImporter(String pdfFilename) {
|
public ZUGFeRDImporter(String filename) {
|
||||||
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
|
super(filename);
|
||||||
extractLowLevel(bis);
|
|
||||||
} catch (final IOException e) {
|
|
||||||
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
|
||||||
throw new ZUGFeRDExportException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ZUGFeRDImporter(InputStream stream) {
|
||||||
public ZUGFeRDImporter(InputStream pdfStream) {
|
super(stream);
|
||||||
try {
|
|
||||||
extractLowLevel(pdfStream);
|
|
||||||
} catch (final IOException e) {
|
|
||||||
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
|
||||||
throw new ZUGFeRDExportException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***
|
|
||||||
* return the file names of all files embedded into the PDF
|
|
||||||
* @see for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
|
|
||||||
* @return a ArrayList of FileAttachments, empty if none
|
|
||||||
*/
|
|
||||||
public List<FileAttachment> getFileAttachmentsPDF() {
|
|
||||||
return PDFAttachments;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
|
|
||||||
*
|
|
||||||
* @param inStream a inputstream of a pdf file
|
|
||||||
*/
|
|
||||||
private void extractLowLevel(InputStream inStream) throws IOException {
|
|
||||||
BufferedInputStream pdfStream = new BufferedInputStream(inStream);
|
|
||||||
byte[] pad = new byte[4];
|
|
||||||
pdfStream.mark(0);
|
|
||||||
pdfStream.read(pad);
|
|
||||||
pdfStream.reset();
|
|
||||||
byte[] pdfSignature = {'%', 'P', 'D', 'F'};
|
|
||||||
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
|
|
||||||
|
|
||||||
|
|
||||||
try (PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) {
|
|
||||||
// PDDocumentInformation info = doc.getDocumentInformation();
|
|
||||||
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
|
|
||||||
//start
|
|
||||||
|
|
||||||
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
|
|
||||||
LOGGER.info("no-xmlpart");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
|
|
||||||
xmpString = convertStreamToString(XMP);
|
|
||||||
|
|
||||||
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
|
|
||||||
if (etn == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
|
|
||||||
// String filePath = "/tmp/";
|
|
||||||
|
|
||||||
if (efMap != null) {
|
|
||||||
extractFiles(efMap); // see
|
|
||||||
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
|
|
||||||
} else {
|
|
||||||
|
|
||||||
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
|
|
||||||
if (kids == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
|
|
||||||
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
|
|
||||||
extractFiles(namesL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no PDF probably XML
|
|
||||||
containsMeta = true;
|
|
||||||
setRawXML(XMLTools.getBytesFromStream(pdfStream));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
|
|
||||||
for (final String alias : names.keySet()) {
|
|
||||||
|
|
||||||
final PDComplexFileSpecification fileSpec = names.get(alias);
|
|
||||||
final String filename = fileSpec.getFilename();
|
|
||||||
/**
|
|
||||||
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
|
|
||||||
*/
|
|
||||||
|
|
||||||
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
|
|
||||||
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
|
|
||||||
containsMeta = true;
|
|
||||||
|
|
||||||
// String embeddedFilename = filePath + filename;
|
|
||||||
// File file = new File(filePath + filename);
|
|
||||||
// System.out.println("Writing " + embeddedFilename);
|
|
||||||
// ByteArrayOutputStream fileBytes=new
|
|
||||||
// ByteArrayOutputStream();
|
|
||||||
// FileOutputStream fos = new FileOutputStream(file);
|
|
||||||
|
|
||||||
setRawXML(embeddedFile.toByteArray());
|
|
||||||
|
|
||||||
// fos.write(embeddedFile.getByteArray());
|
|
||||||
// fos.close();
|
|
||||||
}
|
|
||||||
if (filename.startsWith("additional_data")) {
|
|
||||||
additionalXMLs.put(filename, embeddedFile.toByteArray());
|
|
||||||
}
|
|
||||||
PDFAttachments.add(new FileAttachment(filename, embeddedFile.getSubtype(), "Data", embeddedFile.toByteArray()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected Document getDocument() {
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
|
|
||||||
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
|
|
||||||
xmlFact.setNamespaceAware(true);
|
|
||||||
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
|
|
||||||
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
|
|
||||||
/// is.skip(guessBOMSize(is));
|
|
||||||
document = builder.parse(is);
|
|
||||||
try {
|
|
||||||
extractInto(importedInvoice);
|
|
||||||
} catch (XPathExpressionException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setRawXML(byte[] rawXML) throws IOException {
|
|
||||||
this.containsMeta = true;
|
|
||||||
this.rawXML = rawXML;
|
|
||||||
this.version = null;
|
|
||||||
try {
|
|
||||||
setDocument();
|
|
||||||
} catch (ParserConfigurationException | SAXException e) {
|
|
||||||
LOGGER.error("Failed to parse XML", e);
|
|
||||||
throw new ZUGFeRDExportException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected String extractString(String xpathStr) {
|
|
||||||
if (!containsMeta) {
|
|
||||||
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
|
|
||||||
}
|
|
||||||
final String result;
|
|
||||||
try {
|
|
||||||
final Document document = getDocument();
|
|
||||||
final XPathFactory xpathFact = XPathFactory.newInstance();
|
|
||||||
final XPath xpath = xpathFact.newXPath();
|
|
||||||
result = xpath.evaluate(xpathStr, document);
|
|
||||||
} catch (final XPathExpressionException e) {
|
|
||||||
LOGGER.error("Failed to evaluate XPath", e);
|
|
||||||
throw new ZUGFeRDExportException(e);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Wrapper for protected method extractString
|
* Wrapper for protected method extractString
|
||||||
@@ -273,410 +76,6 @@ public class ZUGFeRDImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void fromXML(String XML) {
|
|
||||||
try {
|
|
||||||
containsMeta = true;
|
|
||||||
setRawXML(XML.getBytes(StandardCharsets.UTF_8));
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***
|
|
||||||
* This will parse a XML into the given invoice object
|
|
||||||
* @param zpp the invoice to be altered
|
|
||||||
* @return the parsed invoice object
|
|
||||||
* @throws XPathExpressionException if xpath could not be evaluated
|
|
||||||
* @throws ParseException if the grand total of the parsed invoice could not be replicated with the new invoice
|
|
||||||
*/
|
|
||||||
public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseException {
|
|
||||||
|
|
||||||
String number = "";
|
|
||||||
String typeCode = null;
|
|
||||||
/*
|
|
||||||
* dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate
|
|
||||||
* setSender setRecipient setnumber bspw. due date
|
|
||||||
* //ExchangedDocument//IssueDateTime//DateTimeString : due date optional
|
|
||||||
*/
|
|
||||||
XPathFactory xpathFact = XPathFactory.newInstance();
|
|
||||||
XPath xpath = xpathFact.newXPath();
|
|
||||||
XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*");
|
|
||||||
NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*");
|
|
||||||
NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
|
|
||||||
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"PayableAmount\"]");
|
|
||||||
BigDecimal expectedGrandTotal = null;
|
|
||||||
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
if (totalNodes.getLength() > 0) {
|
|
||||||
expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
Date issueDate = null;
|
|
||||||
Date dueDate = null;
|
|
||||||
Date deliveryDate = null;
|
|
||||||
String despatchAdviceReferencedDocument = null;
|
|
||||||
for (int i = 0; i < ExchangedDocumentNodes.getLength(); i++) {
|
|
||||||
|
|
||||||
// nodes.item(i).getTextContent())) {
|
|
||||||
Node exchangedDocumentNode = ExchangedDocumentNodes.item(i);
|
|
||||||
NodeList exchangedDocumentChilds = exchangedDocumentNode.getChildNodes();
|
|
||||||
for (int documentChildIndex = 0; documentChildIndex < exchangedDocumentChilds.getLength(); documentChildIndex++) {
|
|
||||||
Node item = exchangedDocumentChilds.item(documentChildIndex);
|
|
||||||
if ((item.getLocalName() != null) && (item.getLocalName().equals("ID"))) {
|
|
||||||
number = item.getTextContent();
|
|
||||||
}
|
|
||||||
if ((item.getLocalName() != null) && (item.getLocalName().equals("TypeCode"))) {
|
|
||||||
typeCode = item.getTextContent();
|
|
||||||
}
|
|
||||||
if ((item.getLocalName() != null) && (item.getLocalName().equals("IssueDateTime"))) {
|
|
||||||
NodeList issueDateTimeChilds = item.getChildNodes();
|
|
||||||
for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) {
|
|
||||||
if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null)
|
|
||||||
&& (issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) {
|
|
||||||
issueDate = new SimpleDateFormat("yyyyMMdd").parse(issueDateTimeChilds.item(issueDateChildIndex).getTextContent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String rootNode = extractString("local-name(/*)");
|
|
||||||
if (rootNode.equals("Invoice")) {
|
|
||||||
// UBL...
|
|
||||||
number = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"ID\"]").trim();
|
|
||||||
issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"IssueDate\"]").trim());
|
|
||||||
String dueDt = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"DueDate\"]").trim();
|
|
||||||
if (dueDt.length() > 0) {
|
|
||||||
dueDate = new SimpleDateFormat("yyyy-MM-dd").parse(dueDt);
|
|
||||||
}
|
|
||||||
String deliveryDt = extractString("//*[local-name()=\"Delivery\"]/*[local-name()=\"ActualDeliveryDate\"]").trim();
|
|
||||||
if (deliveryDt.length() > 0) {
|
|
||||||
deliveryDate = new SimpleDateFormat("yyyy-MM-dd").parse(deliveryDt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeDelivery\"]");
|
|
||||||
NodeList headerTradeDeliveryNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
|
|
||||||
for (int i = 0; i < headerTradeDeliveryNodes.getLength(); i++) {
|
|
||||||
// nodes.item(i).getTextContent())) {
|
|
||||||
Node headerTradeDeliveryNode = headerTradeDeliveryNodes.item(i);
|
|
||||||
NodeList headerTradeDeliveryChilds = headerTradeDeliveryNode.getChildNodes();
|
|
||||||
for (int deliveryChildIndex = 0; deliveryChildIndex < headerTradeDeliveryChilds.getLength(); deliveryChildIndex++) {
|
|
||||||
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName() != null) {
|
|
||||||
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName().equals("ActualDeliverySupplyChainEvent")) {
|
|
||||||
NodeList actualDeliveryChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes();
|
|
||||||
for (int actualDeliveryChildIndex = 0; actualDeliveryChildIndex < actualDeliveryChilds.getLength(); actualDeliveryChildIndex++) {
|
|
||||||
if ((actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName() != null)
|
|
||||||
&& (actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName().equals("OccurrenceDateTime"))) {
|
|
||||||
NodeList occurenceChilds = actualDeliveryChilds.item(actualDeliveryChildIndex).getChildNodes();
|
|
||||||
for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds.getLength(); occurenceChildIndex++) {
|
|
||||||
if ((occurenceChilds.item(occurenceChildIndex).getLocalName() != null)
|
|
||||||
&& (occurenceChilds.item(occurenceChildIndex).getLocalName().equals("DateTimeString"))) {
|
|
||||||
deliveryDate = new SimpleDateFormat("yyyyMMdd").parse(occurenceChilds.item(occurenceChildIndex).getTextContent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName().equals("DespatchAdviceReferencedDocument")) {
|
|
||||||
NodeList despatchAdviceChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes();
|
|
||||||
for (int despatchAdviceChildIndex = 0; despatchAdviceChildIndex < despatchAdviceChilds.getLength(); despatchAdviceChildIndex++) {
|
|
||||||
if (despatchAdviceChilds.item(despatchAdviceChildIndex).getLocalName() != null
|
|
||||||
&& despatchAdviceChilds.item(despatchAdviceChildIndex).getLocalName().equals("IssuerAssignedID")) {
|
|
||||||
despatchAdviceReferencedDocument = despatchAdviceChilds.item(despatchAdviceChildIndex).getTextContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeAgreement\"]");
|
|
||||||
NodeList headerTradeAgreementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
String buyerOrderIssuerAssignedID = null;
|
|
||||||
String sellerOrderIssuerAssignedID = null;
|
|
||||||
for (int i = 0; i < headerTradeAgreementNodes.getLength(); i++) {
|
|
||||||
// nodes.item(i).getTextContent())) {
|
|
||||||
Node headerTradeAgreementNode = headerTradeAgreementNodes.item(i);
|
|
||||||
NodeList headerTradeAgreementChilds = headerTradeAgreementNode.getChildNodes();
|
|
||||||
for (int agreementChildIndex = 0; agreementChildIndex < headerTradeAgreementChilds.getLength(); agreementChildIndex++) {
|
|
||||||
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null) {
|
|
||||||
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) {
|
|
||||||
NodeList buyerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
|
|
||||||
for (int buyerOrderChildIndex = 0; buyerOrderChildIndex < buyerOrderChilds.getLength(); buyerOrderChildIndex++) {
|
|
||||||
if ((buyerOrderChilds.item(buyerOrderChildIndex).getLocalName() != null)
|
|
||||||
&& (buyerOrderChilds.item(buyerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
|
|
||||||
buyerOrderIssuerAssignedID = buyerOrderChilds.item(buyerOrderChildIndex).getTextContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("SellerOrderReferencedDocument")) {
|
|
||||||
NodeList sellerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
|
|
||||||
for (int sellerOrderChildIndex = 0; sellerOrderChildIndex < sellerOrderChilds.getLength(); sellerOrderChildIndex++) {
|
|
||||||
if ((sellerOrderChilds.item(sellerOrderChildIndex).getLocalName() != null)
|
|
||||||
&& (sellerOrderChilds.item(sellerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
|
|
||||||
sellerOrderIssuerAssignedID = sellerOrderChilds.item(sellerOrderChildIndex).getTextContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]");
|
|
||||||
NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
List<BankDetails> bankDetails = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) {
|
|
||||||
// nodes.item(i).getTextContent())) {
|
|
||||||
Node headerTradeSettlementNode = headerTradeSettlementNodes.item(i);
|
|
||||||
NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes();
|
|
||||||
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) {
|
|
||||||
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
|
|
||||||
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradePaymentTerms"))) {
|
|
||||||
NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
|
|
||||||
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
|
|
||||||
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DueDateDateTime"))) {
|
|
||||||
NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes();
|
|
||||||
for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) {
|
|
||||||
if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
|
|
||||||
dueDate = new SimpleDateFormat("yyyyMMdd").parse(dueDateChilds.item(dueDateChildIndex).getTextContent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
|
|
||||||
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradeSettlementPaymentMeans"))) {
|
|
||||||
NodeList paymentMeansChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
|
|
||||||
for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds.getLength(); paymentMeansChildIndex++) {
|
|
||||||
String IBAN = null, BIC = null;
|
|
||||||
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount"))) {
|
|
||||||
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
|
|
||||||
for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
|
|
||||||
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
|
|
||||||
IBAN = accountChilds.item(accountChildIndex).getTextContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialInstitution"))) {
|
|
||||||
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
|
|
||||||
for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
|
|
||||||
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("BICID"))) {//CII
|
|
||||||
BIC = accountChilds.item(accountChildIndex).getTextContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (IBAN != null) {
|
|
||||||
BankDetails bd = new BankDetails(IBAN);
|
|
||||||
if (BIC != null) {
|
|
||||||
bd.setBIC(BIC);
|
|
||||||
}
|
|
||||||
bankDetails.add(bd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only
|
|
||||||
NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
|
|
||||||
for (int i = 0; i < paymentMeansNodes.getLength(); i++) {
|
|
||||||
// nodes.item(i).getTextContent())) {
|
|
||||||
Node paymentMeansNode = paymentMeansNodes.item(i);
|
|
||||||
NodeList paymentMeansChilds = paymentMeansNode.getChildNodes();
|
|
||||||
for (int meansChildIndex = 0; meansChildIndex < paymentMeansChilds.getLength(); meansChildIndex++) {
|
|
||||||
if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null)
|
|
||||||
&& (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PayeeFinancialAccount"))) {
|
|
||||||
NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes();
|
|
||||||
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
|
|
||||||
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) {
|
|
||||||
String IBAN = paymentTermChilds.item(paymentTermChildIndex).getTextContent();
|
|
||||||
if (IBAN != null) {
|
|
||||||
BankDetails bd = new BankDetails(IBAN);
|
|
||||||
bankDetails.add(bd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
|
|
||||||
bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
|
|
||||||
|
|
||||||
if (buyerOrderIssuerAssignedID != null) {
|
|
||||||
zpp.setBuyerOrderReferencedDocumentID(buyerOrderIssuerAssignedID);
|
|
||||||
}
|
|
||||||
if (sellerOrderIssuerAssignedID != null) {
|
|
||||||
zpp.setSellerOrderReferencedDocumentID(sellerOrderIssuerAssignedID);
|
|
||||||
}
|
|
||||||
if (despatchAdviceReferencedDocument != null) {
|
|
||||||
zpp.setDespatchAdviceReferencedDocumentID(despatchAdviceReferencedDocument);
|
|
||||||
}
|
|
||||||
|
|
||||||
zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim());
|
|
||||||
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]");
|
|
||||||
String buyerReference = null;
|
|
||||||
totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
if (totalNodes.getLength() > 0) {
|
|
||||||
buyerReference = totalNodes.item(0).getTextContent();
|
|
||||||
}
|
|
||||||
if (buyerReference != null) {
|
|
||||||
zpp.setReferenceNumber(buyerReference);
|
|
||||||
}
|
|
||||||
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]|//*[local-name()=\"InvoiceLine\"]");
|
|
||||||
NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
|
|
||||||
if (nodes.getLength() != 0) {
|
|
||||||
for (int i = 0; i < nodes.getLength(); i++) {
|
|
||||||
|
|
||||||
Node currentItemNode = nodes.item(i);
|
|
||||||
Item it = new Item(currentItemNode.getChildNodes(), recalcPrice);
|
|
||||||
zpp.addItem(it);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// now handling base64 encoded attachments AttachmentBinaryObject=CII, EmbeddedDocumentBinaryObject=UBL
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
|
|
||||||
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
for (int i = 0; i < attachmentNodes.getLength(); i++) {
|
|
||||||
FileAttachment fa=new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(),attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(),"Data", Base64.getDecoder().decode(attachmentNodes.item(i).getTextContent()));
|
|
||||||
fileAttachments.add(fa);
|
|
||||||
// filename = "Aufmass.png" mimeCode = "image/png"
|
|
||||||
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
// item level charges+allowances are not yet handled but a lower item price will
|
|
||||||
// be read,
|
|
||||||
// so the invoice remains arithmetically correct
|
|
||||||
// -> parse document level charges+allowances
|
|
||||||
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]");
|
|
||||||
NodeList chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
|
||||||
for (int i = 0; i < chargeNodes.getLength(); i++) {
|
|
||||||
NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes();
|
|
||||||
boolean isCharge = true;
|
|
||||||
String chargeAmount = null;
|
|
||||||
String reason = null;
|
|
||||||
String reasonCode = null;
|
|
||||||
String taxPercent = null;
|
|
||||||
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
|
|
||||||
String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
|
|
||||||
if (chargeChildName != null) {
|
|
||||||
|
|
||||||
if (chargeChildName.equals("ChargeIndicator")) {
|
|
||||||
NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
|
|
||||||
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) {
|
|
||||||
if ((indicatorChilds.item(indicatorChildIndex).getLocalName() != null)
|
|
||||||
&& (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator"))) {
|
|
||||||
isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent().equalsIgnoreCase("true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (chargeChildName.equals("ActualAmount")) {
|
|
||||||
chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
|
||||||
} else if (chargeChildName.equals("Reason")) {
|
|
||||||
reason = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
|
||||||
} else if (chargeChildName.equals("ReasonCode")) {
|
|
||||||
reasonCode = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
|
||||||
} else if (chargeChildName.equals("CategoryTradeTax")) {
|
|
||||||
NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
|
|
||||||
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
|
|
||||||
String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
|
|
||||||
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") || taxItemName.equals("ApplicablePercent"))) {
|
|
||||||
taxPercent = taxChilds.item(taxChildIndex).getTextContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCharge) {
|
|
||||||
Charge c = new Charge(new BigDecimal(chargeAmount));
|
|
||||||
if (reason != null) {
|
|
||||||
c.setReason(reason);
|
|
||||||
}
|
|
||||||
if (reasonCode != null) {
|
|
||||||
c.setReasonCode(reasonCode);
|
|
||||||
}
|
|
||||||
if (taxPercent != null) {
|
|
||||||
c.setTaxPercent(new BigDecimal(taxPercent));
|
|
||||||
}
|
|
||||||
zpp.addCharge(c);
|
|
||||||
} else {
|
|
||||||
Allowance a = new Allowance(new BigDecimal(chargeAmount));
|
|
||||||
if (reason != null) {
|
|
||||||
a.setReason(reason);
|
|
||||||
}
|
|
||||||
if (reasonCode != null) {
|
|
||||||
a.setReasonCode(reasonCode);
|
|
||||||
}
|
|
||||||
if (taxPercent != null) {
|
|
||||||
a.setTaxPercent(new BigDecimal(taxPercent));
|
|
||||||
}
|
|
||||||
zpp.addAllowance(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TransactionCalculator tc = new TransactionCalculator(zpp);
|
|
||||||
String expectedStringTotalGross = tc.getGrandTotal().toPlainString();
|
|
||||||
EStandard whichType;
|
|
||||||
try {
|
|
||||||
whichType = getStandard();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParseException("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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return zpp;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
*
|
|
||||||
* @return the file attachments embedded in XML (using base64) decoded as byte array,
|
|
||||||
* @see for PDF embedded files in FX use getFileAttachmentsPDF()
|
|
||||||
*/
|
|
||||||
public List<FileAttachment> getFileAttachmentsXML() {
|
|
||||||
return fileAttachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* This will parse a XML into a invoice object
|
|
||||||
*
|
|
||||||
* @return the parsed invoice object
|
|
||||||
* @throws XPathExpressionException if internal xpath expressions were wrong
|
|
||||||
* @throws ParseException if the grand total of the parsed invoice could not be replicated with the new invoice
|
|
||||||
*/
|
|
||||||
public Invoice extractInvoice() throws XPathExpressionException, ParseException {
|
|
||||||
Invoice i = new Invoice();
|
|
||||||
return extractInto(i);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* have the item prices be determined from the line total.
|
* have the item prices be determined from the line total.
|
||||||
* That's a workaround for some invoices which just put 0 as item price
|
* That's a workaround for some invoices which just put 0 as item price
|
||||||
@@ -706,8 +105,7 @@ public class ZUGFeRDImporter {
|
|||||||
* @return the ZUGFeRD Profile
|
* @return the ZUGFeRD Profile
|
||||||
*/
|
*/
|
||||||
public String getZUGFeRDProfil() {
|
public String getZUGFeRDProfil() {
|
||||||
importedInvoice.getProfile
|
|
||||||
@todo
|
|
||||||
String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']");
|
String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']");
|
||||||
if (guideline.contains("xrechnung")) {
|
if (guideline.contains("xrechnung")) {
|
||||||
return "XRECHNUNG";
|
return "XRECHNUNG";
|
||||||
@@ -994,14 +392,10 @@ public class ZUGFeRDImporter {
|
|||||||
* @return the total payable amount
|
* @return the total payable amount
|
||||||
*/
|
*/
|
||||||
public String getAmount() {
|
public String getAmount() {
|
||||||
String result = extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']/*[local-name() = 'DuePayableAmount']");
|
|
||||||
if (result == null || result.isEmpty()) {
|
|
||||||
|
|
||||||
/* fx/zf would be SpecifiedTradeSettlementMonetarySummation
|
TransactionCalculator ic=new TransactionCalculator(importedInvoice);
|
||||||
* but ox is SpecifiedTradeSettlementHeaderMonetarySummation...*/
|
|
||||||
result = extractString("//*[local-name() = 'GrandTotalAmount']");
|
return ic.getGrandTotal().toPlainString();
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1104,28 +498,6 @@ public class ZUGFeRDImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public EStandard getStandard() throws Exception {
|
|
||||||
if (!containsMeta) {
|
|
||||||
throw new Exception("Not yet parsed");
|
|
||||||
}
|
|
||||||
final String head = getUTF8();
|
|
||||||
String rootNode = extractString("local-name(/*)");
|
|
||||||
if (rootNode.equals("CrossIndustryDocument")) {
|
|
||||||
return EStandard.zugferd;
|
|
||||||
} else if (rootNode.equals("Invoice")) {
|
|
||||||
return EStandard.ubl;
|
|
||||||
} else if (rootNode.equals("CrossIndustryInvoice")) {
|
|
||||||
return EStandard.facturx;
|
|
||||||
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
|
|
||||||
return EStandard.despatchadvice;
|
|
||||||
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
|
|
||||||
return EStandard.orderx;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("ZUGFeRD version could not be determined");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVersion() throws Exception {
|
public int getVersion() throws Exception {
|
||||||
if (!containsMeta) {
|
if (!containsMeta) {
|
||||||
throw new Exception("Not yet parsed");
|
throw new Exception("Not yet parsed");
|
||||||
@@ -1149,34 +521,6 @@ public class ZUGFeRDImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return return UTF8 XML (without BOM) of the invoice
|
|
||||||
*/
|
|
||||||
public String getUTF8() {
|
|
||||||
if (rawXML == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (rawXML.length < 3) {
|
|
||||||
return new String(rawXML);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final byte[] bomlessData;
|
|
||||||
|
|
||||||
if ((rawXML[0] == (byte) 0xEF)
|
|
||||||
&& (rawXML[1] == (byte) 0xBB)
|
|
||||||
&& (rawXML[2] == (byte) 0xBF)) {
|
|
||||||
// I don't like BOMs, lets remove it
|
|
||||||
bomlessData = new byte[rawXML.length - 3];
|
|
||||||
System.arraycopy(rawXML, 3, bomlessData, 0,
|
|
||||||
rawXML.length - 3);
|
|
||||||
} else {
|
|
||||||
bomlessData = rawXML;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String(bomlessData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the raw XML data as extracted from the ZUGFeRD PDF file.
|
* Returns the raw XML data as extracted from the ZUGFeRD PDF file.
|
||||||
@@ -1203,14 +547,6 @@ public class ZUGFeRDImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static String convertStreamToString(java.io.InputStream is) {
|
|
||||||
try {
|
|
||||||
return IOUtils.toString(is, StandardCharsets.UTF_8);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns an instance of PostalTradeAddress for SellerTradeParty section
|
* returns an instance of PostalTradeAddress for SellerTradeParty section
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,41 +1,723 @@
|
|||||||
package org.mustangproject.ZUGFeRD;
|
package org.mustangproject.ZUGFeRD;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPath;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
import javax.xml.xpath.XPathExpression;
|
import javax.xml.xpath.XPathExpression;
|
||||||
import javax.xml.xpath.XPathExpressionException;
|
import javax.xml.xpath.XPathExpressionException;
|
||||||
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFactory;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||||
|
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.*;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
public class ZUGFeRDInvoiceImporter {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDInvoiceImporter.class.getCanonicalName()); // log
|
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDInvoiceImporter.class.getCanonicalName()); // log
|
||||||
|
|
||||||
public ZUGFeRDInvoiceImporter() {
|
/**
|
||||||
super();
|
* if metadata has been found
|
||||||
|
*/
|
||||||
|
protected boolean containsMeta = false;
|
||||||
|
/**
|
||||||
|
* map filenames of additional XML files to their contents
|
||||||
|
*/
|
||||||
|
protected final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* map filenames of all embedded files in the respective PDF
|
||||||
|
*/
|
||||||
|
protected final ArrayList<FileAttachment> PDFAttachments = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* Raw XML form of the extracted data - may be directly obtained.
|
||||||
|
*/
|
||||||
|
protected byte[] rawXML = null;
|
||||||
|
/**
|
||||||
|
* XMP metadata
|
||||||
|
*/
|
||||||
|
protected String xmpString = null; // XMP metadata
|
||||||
|
/**
|
||||||
|
* parsed Document
|
||||||
|
*/
|
||||||
|
protected Document document;
|
||||||
|
protected Integer version;
|
||||||
|
protected Invoice importedInvoice=null;
|
||||||
|
protected boolean recalcPrice = false;
|
||||||
|
protected boolean ignoreCalculationErrors = false;
|
||||||
|
protected ArrayList<FileAttachment> fileAttachments=new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
protected ZUGFeRDInvoiceImporter() {
|
||||||
|
//constructor for extending classes
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZUGFeRDInvoiceImporter(String filename) {
|
public ZUGFeRDInvoiceImporter(String pdfFilename) {
|
||||||
super(filename);
|
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
|
||||||
|
extractLowLevel(bis);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
||||||
|
throw new ZUGFeRDExportException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZUGFeRDInvoiceImporter(InputStream stream) {
|
|
||||||
super(stream);
|
public ZUGFeRDInvoiceImporter(InputStream pdfStream) {
|
||||||
|
try {
|
||||||
|
extractLowLevel(pdfStream);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
||||||
|
throw new ZUGFeRDExportException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***
|
||||||
|
* return the file names of all files embedded into the PDF
|
||||||
|
* @see for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
|
||||||
|
* @return a ArrayList of FileAttachments, empty if none
|
||||||
|
*/
|
||||||
|
public List<FileAttachment> getFileAttachmentsPDF() {
|
||||||
|
return PDFAttachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
|
||||||
|
*
|
||||||
|
* @param inStream a inputstream of a pdf file
|
||||||
|
*/
|
||||||
|
private void extractLowLevel(InputStream inStream) throws IOException {
|
||||||
|
BufferedInputStream pdfStream = new BufferedInputStream(inStream);
|
||||||
|
byte[] pad = new byte[4];
|
||||||
|
pdfStream.mark(0);
|
||||||
|
pdfStream.read(pad);
|
||||||
|
pdfStream.reset();
|
||||||
|
byte[] pdfSignature = {'%', 'P', 'D', 'F'};
|
||||||
|
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
|
||||||
|
|
||||||
|
|
||||||
|
try (PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) {
|
||||||
|
// PDDocumentInformation info = doc.getDocumentInformation();
|
||||||
|
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
|
||||||
|
//start
|
||||||
|
|
||||||
|
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
|
||||||
|
LOGGER.info("no-xmlpart");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
|
||||||
|
xmpString = convertStreamToString(XMP);
|
||||||
|
|
||||||
|
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
|
||||||
|
if (etn == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
|
||||||
|
// String filePath = "/tmp/";
|
||||||
|
|
||||||
|
if (efMap != null) {
|
||||||
|
extractFiles(efMap); // see
|
||||||
|
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
|
||||||
|
} else {
|
||||||
|
|
||||||
|
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
|
||||||
|
if (kids == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
|
||||||
|
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
|
||||||
|
extractFiles(namesL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no PDF probably XML
|
||||||
|
containsMeta = true;
|
||||||
|
setRawXML(XMLTools.getBytesFromStream(pdfStream));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
|
||||||
|
for (final String alias : names.keySet()) {
|
||||||
|
|
||||||
|
final PDComplexFileSpecification fileSpec = names.get(alias);
|
||||||
|
final String filename = fileSpec.getFilename();
|
||||||
|
/**
|
||||||
|
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
|
||||||
|
*/
|
||||||
|
|
||||||
|
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
|
||||||
|
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
|
||||||
|
containsMeta = true;
|
||||||
|
|
||||||
|
// String embeddedFilename = filePath + filename;
|
||||||
|
// File file = new File(filePath + filename);
|
||||||
|
// System.out.println("Writing " + embeddedFilename);
|
||||||
|
// ByteArrayOutputStream fileBytes=new
|
||||||
|
// ByteArrayOutputStream();
|
||||||
|
// FileOutputStream fos = new FileOutputStream(file);
|
||||||
|
|
||||||
|
setRawXML(embeddedFile.toByteArray());
|
||||||
|
|
||||||
|
// fos.write(embeddedFile.getByteArray());
|
||||||
|
// fos.close();
|
||||||
|
}
|
||||||
|
if (filename.startsWith("additional_data")) {
|
||||||
|
additionalXMLs.put(filename, embeddedFile.toByteArray());
|
||||||
|
}
|
||||||
|
PDFAttachments.add(new FileAttachment(filename, embeddedFile.getSubtype(), "Data", embeddedFile.toByteArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRawXML(byte[] rawXML) throws IOException {
|
||||||
|
this.containsMeta = true;
|
||||||
|
this.rawXML = rawXML;
|
||||||
|
this.version = null;
|
||||||
|
try {
|
||||||
|
setDocument();
|
||||||
|
} catch (ParserConfigurationException | SAXException e) {
|
||||||
|
LOGGER.error("Failed to parse XML", e);
|
||||||
|
throw new ZUGFeRDExportException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
|
||||||
|
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
|
||||||
|
xmlFact.setNamespaceAware(true);
|
||||||
|
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
|
||||||
|
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
|
||||||
|
/// is.skip(guessBOMSize(is));
|
||||||
|
document = builder.parse(is);
|
||||||
|
try {
|
||||||
|
importedInvoice=new Invoice();
|
||||||
|
extractInto(importedInvoice);
|
||||||
|
} catch (XPathExpressionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***
|
||||||
|
* This will parse a XML into the given invoice object
|
||||||
|
* @param zpp the invoice to be altered
|
||||||
|
* @return the parsed invoice object
|
||||||
|
* @throws XPathExpressionException if xpath could not be evaluated
|
||||||
|
* @throws ParseException if the grand total of the parsed invoice could not be replicated with the new invoice
|
||||||
|
*/
|
||||||
|
public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseException {
|
||||||
|
|
||||||
|
String number = "";
|
||||||
|
String typeCode = null;
|
||||||
|
/*
|
||||||
|
* dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate
|
||||||
|
* setSender setRecipient setnumber bspw. due date
|
||||||
|
* //ExchangedDocument//IssueDateTime//DateTimeString : due date optional
|
||||||
|
*/
|
||||||
|
XPathFactory xpathFact = XPathFactory.newInstance();
|
||||||
|
XPath xpath = xpathFact.newXPath();
|
||||||
|
XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*");
|
||||||
|
NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*");
|
||||||
|
NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
|
||||||
|
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"PayableAmount\"]");
|
||||||
|
BigDecimal expectedGrandTotal = null;
|
||||||
|
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
if (totalNodes.getLength() > 0) {
|
||||||
|
expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
Date issueDate = null;
|
||||||
|
Date dueDate = null;
|
||||||
|
Date deliveryDate = null;
|
||||||
|
String despatchAdviceReferencedDocument = null;
|
||||||
|
for (int i = 0; i < ExchangedDocumentNodes.getLength(); i++) {
|
||||||
|
|
||||||
|
// nodes.item(i).getTextContent())) {
|
||||||
|
Node exchangedDocumentNode = ExchangedDocumentNodes.item(i);
|
||||||
|
NodeList exchangedDocumentChilds = exchangedDocumentNode.getChildNodes();
|
||||||
|
for (int documentChildIndex = 0; documentChildIndex < exchangedDocumentChilds.getLength(); documentChildIndex++) {
|
||||||
|
Node item = exchangedDocumentChilds.item(documentChildIndex);
|
||||||
|
if ((item.getLocalName() != null) && (item.getLocalName().equals("ID"))) {
|
||||||
|
number = item.getTextContent();
|
||||||
|
}
|
||||||
|
if ((item.getLocalName() != null) && (item.getLocalName().equals("TypeCode"))) {
|
||||||
|
typeCode = item.getTextContent();
|
||||||
|
}
|
||||||
|
if ((item.getLocalName() != null) && (item.getLocalName().equals("IssueDateTime"))) {
|
||||||
|
NodeList issueDateTimeChilds = item.getChildNodes();
|
||||||
|
for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) {
|
||||||
|
if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null)
|
||||||
|
&& (issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) {
|
||||||
|
issueDate = new SimpleDateFormat("yyyyMMdd").parse(issueDateTimeChilds.item(issueDateChildIndex).getTextContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String rootNode = extractString("local-name(/*)");
|
||||||
|
if (rootNode.equals("Invoice")) {
|
||||||
|
// UBL...
|
||||||
|
number = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"ID\"]").trim();
|
||||||
|
issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"IssueDate\"]").trim());
|
||||||
|
String dueDt = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"DueDate\"]").trim();
|
||||||
|
if (dueDt.length() > 0) {
|
||||||
|
dueDate = new SimpleDateFormat("yyyy-MM-dd").parse(dueDt);
|
||||||
|
}
|
||||||
|
String deliveryDt = extractString("//*[local-name()=\"Delivery\"]/*[local-name()=\"ActualDeliveryDate\"]").trim();
|
||||||
|
if (deliveryDt.length() > 0) {
|
||||||
|
deliveryDate = new SimpleDateFormat("yyyy-MM-dd").parse(deliveryDt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeDelivery\"]");
|
||||||
|
NodeList headerTradeDeliveryNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
|
||||||
|
for (int i = 0; i < headerTradeDeliveryNodes.getLength(); i++) {
|
||||||
|
// nodes.item(i).getTextContent())) {
|
||||||
|
Node headerTradeDeliveryNode = headerTradeDeliveryNodes.item(i);
|
||||||
|
NodeList headerTradeDeliveryChilds = headerTradeDeliveryNode.getChildNodes();
|
||||||
|
for (int deliveryChildIndex = 0; deliveryChildIndex < headerTradeDeliveryChilds.getLength(); deliveryChildIndex++) {
|
||||||
|
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName() != null) {
|
||||||
|
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName().equals("ActualDeliverySupplyChainEvent")) {
|
||||||
|
NodeList actualDeliveryChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes();
|
||||||
|
for (int actualDeliveryChildIndex = 0; actualDeliveryChildIndex < actualDeliveryChilds.getLength(); actualDeliveryChildIndex++) {
|
||||||
|
if ((actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName() != null)
|
||||||
|
&& (actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName().equals("OccurrenceDateTime"))) {
|
||||||
|
NodeList occurenceChilds = actualDeliveryChilds.item(actualDeliveryChildIndex).getChildNodes();
|
||||||
|
for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds.getLength(); occurenceChildIndex++) {
|
||||||
|
if ((occurenceChilds.item(occurenceChildIndex).getLocalName() != null)
|
||||||
|
&& (occurenceChilds.item(occurenceChildIndex).getLocalName().equals("DateTimeString"))) {
|
||||||
|
deliveryDate = new SimpleDateFormat("yyyyMMdd").parse(occurenceChilds.item(occurenceChildIndex).getTextContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName().equals("DespatchAdviceReferencedDocument")) {
|
||||||
|
NodeList despatchAdviceChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes();
|
||||||
|
for (int despatchAdviceChildIndex = 0; despatchAdviceChildIndex < despatchAdviceChilds.getLength(); despatchAdviceChildIndex++) {
|
||||||
|
if (despatchAdviceChilds.item(despatchAdviceChildIndex).getLocalName() != null
|
||||||
|
&& despatchAdviceChilds.item(despatchAdviceChildIndex).getLocalName().equals("IssuerAssignedID")) {
|
||||||
|
despatchAdviceReferencedDocument = despatchAdviceChilds.item(despatchAdviceChildIndex).getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeAgreement\"]");
|
||||||
|
NodeList headerTradeAgreementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
String buyerOrderIssuerAssignedID = null;
|
||||||
|
String sellerOrderIssuerAssignedID = null;
|
||||||
|
for (int i = 0; i < headerTradeAgreementNodes.getLength(); i++) {
|
||||||
|
// nodes.item(i).getTextContent())) {
|
||||||
|
Node headerTradeAgreementNode = headerTradeAgreementNodes.item(i);
|
||||||
|
NodeList headerTradeAgreementChilds = headerTradeAgreementNode.getChildNodes();
|
||||||
|
for (int agreementChildIndex = 0; agreementChildIndex < headerTradeAgreementChilds.getLength(); agreementChildIndex++) {
|
||||||
|
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null) {
|
||||||
|
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) {
|
||||||
|
NodeList buyerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
|
||||||
|
for (int buyerOrderChildIndex = 0; buyerOrderChildIndex < buyerOrderChilds.getLength(); buyerOrderChildIndex++) {
|
||||||
|
if ((buyerOrderChilds.item(buyerOrderChildIndex).getLocalName() != null)
|
||||||
|
&& (buyerOrderChilds.item(buyerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
|
||||||
|
buyerOrderIssuerAssignedID = buyerOrderChilds.item(buyerOrderChildIndex).getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("SellerOrderReferencedDocument")) {
|
||||||
|
NodeList sellerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
|
||||||
|
for (int sellerOrderChildIndex = 0; sellerOrderChildIndex < sellerOrderChilds.getLength(); sellerOrderChildIndex++) {
|
||||||
|
if ((sellerOrderChilds.item(sellerOrderChildIndex).getLocalName() != null)
|
||||||
|
&& (sellerOrderChilds.item(sellerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
|
||||||
|
sellerOrderIssuerAssignedID = sellerOrderChilds.item(sellerOrderChildIndex).getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]");
|
||||||
|
NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
List<BankDetails> bankDetails = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) {
|
||||||
|
// nodes.item(i).getTextContent())) {
|
||||||
|
Node headerTradeSettlementNode = headerTradeSettlementNodes.item(i);
|
||||||
|
NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes();
|
||||||
|
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) {
|
||||||
|
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
|
||||||
|
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradePaymentTerms"))) {
|
||||||
|
NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
|
||||||
|
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
|
||||||
|
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DueDateDateTime"))) {
|
||||||
|
NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes();
|
||||||
|
for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) {
|
||||||
|
if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
|
||||||
|
dueDate = new SimpleDateFormat("yyyyMMdd").parse(dueDateChilds.item(dueDateChildIndex).getTextContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
|
||||||
|
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradeSettlementPaymentMeans"))) {
|
||||||
|
NodeList paymentMeansChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
|
||||||
|
for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds.getLength(); paymentMeansChildIndex++) {
|
||||||
|
String IBAN = null, BIC = null;
|
||||||
|
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount"))) {
|
||||||
|
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
|
||||||
|
for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
|
||||||
|
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
|
||||||
|
IBAN = accountChilds.item(accountChildIndex).getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialInstitution"))) {
|
||||||
|
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
|
||||||
|
for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
|
||||||
|
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("BICID"))) {//CII
|
||||||
|
BIC = accountChilds.item(accountChildIndex).getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (IBAN != null) {
|
||||||
|
BankDetails bd = new BankDetails(IBAN);
|
||||||
|
if (BIC != null) {
|
||||||
|
bd.setBIC(BIC);
|
||||||
|
}
|
||||||
|
bankDetails.add(bd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only
|
||||||
|
NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
|
||||||
|
for (int i = 0; i < paymentMeansNodes.getLength(); i++) {
|
||||||
|
// nodes.item(i).getTextContent())) {
|
||||||
|
Node paymentMeansNode = paymentMeansNodes.item(i);
|
||||||
|
NodeList paymentMeansChilds = paymentMeansNode.getChildNodes();
|
||||||
|
for (int meansChildIndex = 0; meansChildIndex < paymentMeansChilds.getLength(); meansChildIndex++) {
|
||||||
|
if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null)
|
||||||
|
&& (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PayeeFinancialAccount"))) {
|
||||||
|
NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes();
|
||||||
|
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
|
||||||
|
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) {
|
||||||
|
String IBAN = paymentTermChilds.item(paymentTermChildIndex).getTextContent();
|
||||||
|
if (IBAN != null) {
|
||||||
|
BankDetails bd = new BankDetails(IBAN);
|
||||||
|
bankDetails.add(bd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
|
||||||
|
bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
|
||||||
|
|
||||||
|
if (buyerOrderIssuerAssignedID != null) {
|
||||||
|
zpp.setBuyerOrderReferencedDocumentID(buyerOrderIssuerAssignedID);
|
||||||
|
}
|
||||||
|
if (sellerOrderIssuerAssignedID != null) {
|
||||||
|
zpp.setSellerOrderReferencedDocumentID(sellerOrderIssuerAssignedID);
|
||||||
|
}
|
||||||
|
if (despatchAdviceReferencedDocument != null) {
|
||||||
|
zpp.setDespatchAdviceReferencedDocumentID(despatchAdviceReferencedDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim());
|
||||||
|
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]");
|
||||||
|
String buyerReference = null;
|
||||||
|
totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
if (totalNodes.getLength() > 0) {
|
||||||
|
buyerReference = totalNodes.item(0).getTextContent();
|
||||||
|
}
|
||||||
|
if (buyerReference != null) {
|
||||||
|
zpp.setReferenceNumber(buyerReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]|//*[local-name()=\"InvoiceLine\"]");
|
||||||
|
NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
|
||||||
|
if (nodes.getLength() != 0) {
|
||||||
|
for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
|
||||||
|
Node currentItemNode = nodes.item(i);
|
||||||
|
Item it = new Item(currentItemNode.getChildNodes(), recalcPrice);
|
||||||
|
zpp.addItem(it);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// now handling base64 encoded attachments AttachmentBinaryObject=CII, EmbeddedDocumentBinaryObject=UBL
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
|
||||||
|
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
for (int i = 0; i < attachmentNodes.getLength(); i++) {
|
||||||
|
FileAttachment fa=new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(),attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(),"Data", Base64.getDecoder().decode(attachmentNodes.item(i).getTextContent()));
|
||||||
|
fileAttachments.add(fa);
|
||||||
|
// filename = "Aufmass.png" mimeCode = "image/png"
|
||||||
|
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
// item level charges+allowances are not yet handled but a lower item price will
|
||||||
|
// be read,
|
||||||
|
// so the invoice remains arithmetically correct
|
||||||
|
// -> parse document level charges+allowances
|
||||||
|
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]");
|
||||||
|
NodeList chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
|
for (int i = 0; i < chargeNodes.getLength(); i++) {
|
||||||
|
NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes();
|
||||||
|
boolean isCharge = true;
|
||||||
|
String chargeAmount = null;
|
||||||
|
String reason = null;
|
||||||
|
String reasonCode = null;
|
||||||
|
String taxPercent = null;
|
||||||
|
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
|
||||||
|
String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
|
||||||
|
if (chargeChildName != null) {
|
||||||
|
|
||||||
|
if (chargeChildName.equals("ChargeIndicator")) {
|
||||||
|
NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
|
||||||
|
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) {
|
||||||
|
if ((indicatorChilds.item(indicatorChildIndex).getLocalName() != null)
|
||||||
|
&& (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator"))) {
|
||||||
|
isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent().equalsIgnoreCase("true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (chargeChildName.equals("ActualAmount")) {
|
||||||
|
chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
||||||
|
} else if (chargeChildName.equals("Reason")) {
|
||||||
|
reason = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
||||||
|
} else if (chargeChildName.equals("ReasonCode")) {
|
||||||
|
reasonCode = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
||||||
|
} else if (chargeChildName.equals("CategoryTradeTax")) {
|
||||||
|
NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
|
||||||
|
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
|
||||||
|
String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
|
||||||
|
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") || taxItemName.equals("ApplicablePercent"))) {
|
||||||
|
taxPercent = taxChilds.item(taxChildIndex).getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCharge) {
|
||||||
|
Charge c = new Charge(new BigDecimal(chargeAmount));
|
||||||
|
if (reason != null) {
|
||||||
|
c.setReason(reason);
|
||||||
|
}
|
||||||
|
if (reasonCode != null) {
|
||||||
|
c.setReasonCode(reasonCode);
|
||||||
|
}
|
||||||
|
if (taxPercent != null) {
|
||||||
|
c.setTaxPercent(new BigDecimal(taxPercent));
|
||||||
|
}
|
||||||
|
zpp.addCharge(c);
|
||||||
|
} else {
|
||||||
|
Allowance a = new Allowance(new BigDecimal(chargeAmount));
|
||||||
|
if (reason != null) {
|
||||||
|
a.setReason(reason);
|
||||||
|
}
|
||||||
|
if (reasonCode != null) {
|
||||||
|
a.setReasonCode(reasonCode);
|
||||||
|
}
|
||||||
|
if (taxPercent != null) {
|
||||||
|
a.setTaxPercent(new BigDecimal(taxPercent));
|
||||||
|
}
|
||||||
|
zpp.addAllowance(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionCalculator tc = new TransactionCalculator(zpp);
|
||||||
|
String expectedStringTotalGross = tc.getGrandTotal().toPlainString();
|
||||||
|
EStandard whichType;
|
||||||
|
try {
|
||||||
|
whichType = getStandard();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParseException("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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zpp;
|
||||||
|
|
||||||
|
}
|
||||||
|
protected Document getDocument() {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected String extractString(String xpathStr) {
|
||||||
|
if (!containsMeta) {
|
||||||
|
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
|
||||||
|
}
|
||||||
|
final String result;
|
||||||
|
try {
|
||||||
|
final Document document = getDocument();
|
||||||
|
final XPathFactory xpathFact = XPathFactory.newInstance();
|
||||||
|
final XPath xpath = xpathFact.newXPath();
|
||||||
|
result = xpath.evaluate(xpathStr, document);
|
||||||
|
} catch (final XPathExpressionException e) {
|
||||||
|
LOGGER.error("Failed to evaluate XPath", e);
|
||||||
|
throw new ZUGFeRDExportException(e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EStandard getStandard() throws Exception {
|
||||||
|
if (!containsMeta) {
|
||||||
|
throw new Exception("Not yet parsed");
|
||||||
|
}
|
||||||
|
final String head = getUTF8();
|
||||||
|
String rootNode = extractString("local-name(/*)");
|
||||||
|
if (rootNode.equals("CrossIndustryDocument")) {
|
||||||
|
return EStandard.zugferd;
|
||||||
|
} else if (rootNode.equals("Invoice")) {
|
||||||
|
return EStandard.ubl;
|
||||||
|
} else if (rootNode.equals("CreditNote")) {
|
||||||
|
return EStandard.ubl;
|
||||||
|
} else if (rootNode.equals("CrossIndustryInvoice")) {
|
||||||
|
return EStandard.facturx;
|
||||||
|
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
|
||||||
|
return EStandard.despatchadvice;
|
||||||
|
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
|
||||||
|
return EStandard.orderx;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("ZUGFeRD version could not be determined");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return return UTF8 XML (without BOM) of the invoice
|
||||||
|
*/
|
||||||
|
public String getUTF8() {
|
||||||
|
if (rawXML == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (rawXML.length < 3) {
|
||||||
|
return new String(rawXML);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final byte[] bomlessData;
|
||||||
|
|
||||||
|
if ((rawXML[0] == (byte) 0xEF)
|
||||||
|
&& (rawXML[1] == (byte) 0xBB)
|
||||||
|
&& (rawXML[2] == (byte) 0xBF)) {
|
||||||
|
// I don't like BOMs, lets remove it
|
||||||
|
bomlessData = new byte[rawXML.length - 3];
|
||||||
|
System.arraycopy(rawXML, 3, bomlessData, 0,
|
||||||
|
rawXML.length - 3);
|
||||||
|
} else {
|
||||||
|
bomlessData = rawXML;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bomlessData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static String convertStreamToString(java.io.InputStream is) {
|
||||||
|
try {
|
||||||
|
return IOUtils.toString(is, StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***
|
||||||
|
*
|
||||||
|
* @return the file attachments embedded in XML (using base64) decoded as byte array,
|
||||||
|
* @see for PDF embedded files in FX use getFileAttachmentsPDF()
|
||||||
|
*/
|
||||||
|
public List<FileAttachment> getFileAttachmentsXML() {
|
||||||
|
return fileAttachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* This will parse a XML into a invoice object
|
||||||
|
*
|
||||||
|
* @return the parsed invoice object
|
||||||
|
* @throws XPathExpressionException if internal xpath expressions were wrong
|
||||||
|
* @throws ParseException if the grand total of the parsed invoice could not be replicated with the new invoice
|
||||||
|
*/
|
||||||
|
public Invoice extractInvoice() throws XPathExpressionException, ParseException {
|
||||||
|
Invoice i = new Invoice();
|
||||||
|
return extractInto(i);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***
|
||||||
|
* sets the XML for the importer to parse
|
||||||
|
* @param XML the UBL or CII
|
||||||
|
*/
|
||||||
|
public void fromXML(String XML) {
|
||||||
|
try {
|
||||||
|
containsMeta = true;
|
||||||
|
setRawXML(XML.getBytes(StandardCharsets.UTF_8));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user