From f543cdac57c87af682e862b9161515e5d6806e5f Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 21 Aug 2024 14:16:29 +0200 Subject: [PATCH 01/13] working on invoiceimporter as new zugferdimporter basis --- .../ZUGFeRD/ZUGFeRDImporter.java | 555 +++++++++++++++--- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 419 ------------- 2 files changed, 484 insertions(+), 490 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 2029e854..c15ced7a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -20,6 +20,7 @@ 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.SimpleDateFormat; import java.util.*; @@ -76,6 +77,10 @@ public class ZUGFeRDImporter { */ private Document document; private Integer version; + Invoice importedInvoice=null; + private boolean recalcPrice = false; + private boolean ignoreCalculationErrors = false; + private ArrayList fileAttachments=new ArrayList<>(); protected ZUGFeRDImporter() { @@ -218,6 +223,13 @@ public class ZUGFeRDImporter { 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); + } } @@ -261,21 +273,441 @@ 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 = 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 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. + * That's a workaround for some invoices which just put 0 as item price + */ + public void doRecalculateItemPricesFromLineTotals() { + recalcPrice = true; + } + + /*** + * do not raise ParseExceptions even if the reproduced invoice total does not match the given value + */ + public void doIgnoreCalculationErrors() { + ignoreCalculationErrors = true; + } + + //////////////////////////////////// + /** * @return the reference (purpose) the sender specified for this invoice */ public String getForeignReference() { - String result = extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']/*[local-name() = 'PaymentReference']"); - if (result == null || result.isEmpty()) { - result = extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']/*[local-name() = 'PaymentReference']"); - } - return result; + + return importedInvoice.getNumber(); } /** * @return the ZUGFeRD Profile */ public String getZUGFeRDProfil() { + importedInvoice.getProfile + @todo String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']"); if (guideline.contains("xrechnung")) { return "XRECHNUNG"; @@ -299,21 +731,6 @@ public class ZUGFeRDImporter { } } - /** - * @return the Invoice Currency Code - */ - public String getInvoiceCurrencyCode() { - try { - if (getVersion() == 1) { - return extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']"); - } else { - return extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']"); - } - } catch (final Exception e) { - // Exception was already logged - return ""; - } - } /** * @return the IssuerAssigned ID @@ -336,57 +753,6 @@ public class ZUGFeRDImporter { return extractIssuerAssignedID("ContractReferencedDocument"); } - private String extractIssuerAssignedID(String propertyName) { - try { - if (getVersion() == 1) { - return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']"); - } else { - return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']"); - } - } catch (final Exception e) { - // Exception was already logged - return ""; - } - } - - /** - * @return the BuyerTradeParty ID - */ - public String getBuyerTradePartyID() { - return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'ID']"); - } - - /** - * @return the Issue Date() - */ - public String getIssueDate() { - try { - if (getVersion() == 1) { - return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']"); - } else { - return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']"); - } - } catch (final Exception e) { - // Exception was already logged - return ""; - } - } - - public Date getDetailedDeliveryPeriodFrom() { - final String toParse = extractString( - "//*[local-name() = 'ApplicableHeaderTradeSettlement']" + - "//*[local-name() = 'BillingSpecifiedPeriod']" + - "//*[local-name() = 'StartDateTime']//*[local-name() = 'DateTimeString']"); - return tryDate(toParse); - } - - public Date getDetailedDeliveryPeriodTo() { - final String toParse = extractString( - "//*[local-name() = 'ApplicableHeaderTradeSettlement']" + - "//*[local-name() = 'BillingSpecifiedPeriod']" + - "//*[local-name() = 'EndDateTime']//*[local-name() = 'DateTimeString']"); - return tryDate(toParse); - } /** * @return the TaxBasisTotalAmount @@ -647,6 +1013,53 @@ public class ZUGFeRDImporter { } + //////////////////// + + /** + * @return the Invoice Currency Code + */ + public String getInvoiceCurrencyCode() { + return importedInvoice.getCurrency(); + } + + + private String extractIssuerAssignedID(String propertyName) { + try { + if (getVersion() == 1) { + return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']"); + } else { + return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']"); + } + } catch (final Exception e) { + // Exception was already logged + return ""; + } + } + + /** + * @return the BuyerTradeParty ID + */ + public String getBuyerTradePartyID() { + return importedInvoice.getRecipient().getID(); + } + + /** + * @return the Issue Date() + */ + public String getIssueDate() { + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(importedInvoice.getIssueDate()); + } + + public Date getDetailedDeliveryPeriodFrom() { + return importedInvoice.getDetailedDeliveryPeriodFrom(); + } + + public Date getDetailedDeliveryPeriodTo() { + return importedInvoice.getDetailedDeliveryPeriodTo(); + } + + public HashMap getAdditionalData() { return additionalXMLs; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 4a28fd44..bcc56b3c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -25,9 +25,6 @@ import org.w3c.dom.NodeList; public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDInvoiceImporter.class.getCanonicalName()); // log - private boolean recalcPrice = false; - private boolean ignoreCalculationErrors = false; - private ArrayList fileAttachments=new ArrayList<>(); public ZUGFeRDInvoiceImporter() { super(); @@ -41,420 +38,4 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { super(stream); } - 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 = 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 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. - * That's a workaround for some invoices which just put 0 as item price - */ - public void doRecalculateItemPricesFromLineTotals() { - recalcPrice = true; - } - - /*** - * do not raise ParseExceptions even if the reproduced invoice total does not match the given value - */ - public void doIgnoreCalculationErrors() { - ignoreCalculationErrors = true; - } } From 689d36d7b2d0d26dc13f64659cb281ffb18ecec6 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 5 Sep 2024 09:43:34 +0200 Subject: [PATCH 02/13] shuffeled the methods, replaced getGrandTotal --- .../ZUGFeRD/ZUGFeRDImporter.java | 686 +---------------- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 708 +++++++++++++++++- 2 files changed, 706 insertions(+), 688 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index c15ced7a..7fcea1ee 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -49,219 +49,22 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -public class ZUGFeRDImporter { +public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class); - - /** - * if metadata has been found - */ - protected boolean containsMeta = false; - /** - * map filenames of additional XML files to their contents - */ - private final HashMap additionalXMLs = new HashMap<>(); - /** - * map filenames of all embedded files in the respective PDF - */ - private final ArrayList 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 fileAttachments=new ArrayList<>(); - - - protected ZUGFeRDImporter() { - //constructor for extending classes + public ZUGFeRDImporter() { + super(); } - public ZUGFeRDImporter(String pdfFilename) { - 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 ZUGFeRDImporter(String filename) { + super(filename); } - - public ZUGFeRDImporter(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 getFileAttachmentsPDF() { - return PDFAttachments; + public ZUGFeRDImporter(InputStream stream) { + super(stream); } - /** - * 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 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> kids = etn.getKids(); - if (kids == null) { - return; - } - for (final PDNameTreeNode node : kids) { - final Map namesL = node.getNames(); - extractFiles(namesL); - } - } - } - } else { - // no PDF probably XML - containsMeta = true; - setRawXML(XMLTools.getBytesFromStream(pdfStream)); - - } - } - - - private void extractFiles(Map 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 @@ -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 = 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 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. * 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 */ public String getZUGFeRDProfil() { - importedInvoice.getProfile - @todo + String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']"); if (guideline.contains("xrechnung")) { return "XRECHNUNG"; @@ -994,14 +392,10 @@ public class ZUGFeRDImporter { * @return the total payable amount */ public String getAmount() { - String result = extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']/*[local-name() = 'DuePayableAmount']"); - if (result == null || result.isEmpty()) { - /* fx/zf would be SpecifiedTradeSettlementMonetarySummation - * but ox is SpecifiedTradeSettlementHeaderMonetarySummation...*/ - result = extractString("//*[local-name() = 'GrandTotalAmount']"); - } - return result; + TransactionCalculator ic=new TransactionCalculator(importedInvoice); + + return ic.getGrandTotal().toPlainString(); } @@ -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(" additionalXMLs = new HashMap<>(); + /** + * map filenames of all embedded files in the respective PDF + */ + protected final ArrayList 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 fileAttachments=new ArrayList<>(); + + + protected ZUGFeRDInvoiceImporter() { + //constructor for extending classes } - public ZUGFeRDInvoiceImporter(String filename) { - super(filename); + public ZUGFeRDInvoiceImporter(String pdfFilename) { + 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 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 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> kids = etn.getKids(); + if (kids == null) { + return; + } + for (final PDNameTreeNode node : kids) { + final Map namesL = node.getNames(); + extractFiles(namesL); + } + } + } + } else { + // no PDF probably XML + containsMeta = true; + setRawXML(XMLTools.getBytesFromStream(pdfStream)); + + } + } + + + + + private void extractFiles(Map 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 = 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(" 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); + } } } From 0ef8174bd512e91c20ae96ec3657588b9408f714 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 9 Sep 2024 08:23:28 +0200 Subject: [PATCH 03/13] adding bt-33 closing #463, correcting bt-27 closing #461 in UBL --- .../java/org/mustangproject/TradeParty.java | 62 +++++++++++++++---- .../ZUGFeRD/IZUGFeRDExportableTradeParty.java | 9 ++- .../ZUGFeRD/ZUGFeRD2PullProvider.java | 4 +- .../mustangproject/ZUGFeRD/ZF2PushTest.java | 22 ++++++- 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index 2580fbef..a174dedd 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -23,6 +23,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { protected String name, zip, street, location, country; protected String taxID = null, vatID = null; protected String ID = null; + protected String description = null; protected String additionalAddress = null; protected String additionalAddressExtension = null; protected List bankDetails = new ArrayList<>(); @@ -66,14 +67,14 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { Node currentItemNode = nodes.item(nodeIndex); if (currentItemNode.getLocalName() != null) { - String debcurrentChild = currentItemNode.getLocalName(); - if (debcurrentChild.equals("Party")) { + String currentUBLChild = currentItemNode.getLocalName(); + if (currentUBLChild.equals("Party")) { NodeList party = currentItemNode.getChildNodes(); for (int partyIndex = 0; partyIndex < party.getLength(); partyIndex++) { if (party.item(partyIndex).getLocalName() != null) { - String debCN = party.item(partyIndex).getLocalName(); - if (debCN.equals("PartyName")) { + String currentTopElementName = party.item(partyIndex).getLocalName(); + if (currentTopElementName.equals("PartyName")) { NodeList partyName = party.item(partyIndex).getChildNodes(); for (int partyNameIndex = 0; partyNameIndex < partyName.getLength(); partyNameIndex++) { @@ -86,7 +87,20 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } } } - if (debCN.equals("PostalAddress")) { + + // 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++) { + if (legal.item(legalChildIndex).getLocalName() != null) { + if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) { + setName(legal.item(legalChildIndex).getTextContent()); + } + } + } + } + if (currentTopElementName.equals("PostalAddress")) { NodeList postal = party.item(partyIndex).getChildNodes(); for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) { @@ -144,7 +158,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } } - if (debCN.equals("Contact")) { + if (currentTopElementName.equals("Contact")) { NodeList contact = party.item(partyIndex).getChildNodes(); setContact(new Contact(contact)); @@ -155,19 +169,19 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } - if (debcurrentChild.equals("GlobalID")) { + if (currentUBLChild.equals("GlobalID")) { if (nodes.item(nodeIndex).getAttributes().getNamedItem("schemeID") != null) { SchemedID gid = new SchemedID().setScheme(nodes.item(nodeIndex).getAttributes().getNamedItem("schemeID").getNodeValue()).setId(nodes.item(nodeIndex).getTextContent()); addGlobalID(gid); } } - if (debcurrentChild.equals("DefinedTradeContact")) { + if (currentUBLChild.equals("DefinedTradeContact")) { NodeList contact = nodes.item(nodeIndex).getChildNodes(); setContact(new Contact(contact)); } - if (debcurrentChild.equals("PostalTradeAddress")) { + if (currentUBLChild.equals("PostalTradeAddress")) { NodeList postal = nodes.item(nodeIndex).getChildNodes(); for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) { if (postal.item(postalChildIndex).getLocalName() != null) { @@ -195,7 +209,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } - if (debcurrentChild.equals("SpecifiedTaxRegistration")) { + if (currentUBLChild.equals("SpecifiedTaxRegistration")) { NodeList taxChilds = nodes.item(nodeIndex).getChildNodes(); for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { if (taxChilds.item(taxChildIndex).getLocalName() != null) { @@ -286,8 +300,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) { //nodes.item(i).getTextContent())) { - String debLN = nodes.item(nodeIndex).getLocalName(); - if (debLN.equals("Party")) { + String topElementName = nodes.item(nodeIndex).getLocalName(); + if (topElementName.equals("Party")) { // take one step back and parse from top parseFromUBL(nodes); return; @@ -302,6 +316,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { if (itemChilds.item(itemChildIndex).getLocalName().equals("Name")) { setName(itemChilds.item(itemChildIndex).getTextContent()); } + + if (itemChilds.item(itemChildIndex).getLocalName().equals("Description")) { + setDescription(itemChilds.item(itemChildIndex).getTextContent()); + } if (itemChilds.item(itemChildIndex).getLocalName().equals("GlobalID")) { if (itemChilds.item(itemChildIndex).getAttributes().getNamedItem("schemeID") != null) { SchemedID gid = new SchemedID().setScheme(itemChilds.item(itemChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue()).setId(itemChilds.item(itemChildIndex).getTextContent()); @@ -556,6 +574,26 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } + /*** + * + * @return String the description, e.g. if it's a vat exempt company + */ + public String getDescription() { + return description; + } + + + /*** + * required, usually done in the constructor: the complete name of the organisation + * @param description human readable description + * @return fluent setter + */ + public TradeParty setDescription(String description) { + this.description = description; + return this; + } + + public String getZIP() { return zip; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java index 8af10dbb..a9977f01 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java @@ -112,12 +112,17 @@ public interface IZUGFeRDExportableTradeParty { } /** - * First and last name of the recipient + * e.g. first and last name of the owner * - * @return First and last name of the recipient + * @return full name of the party */ String getName(); + /** + * @return description, e.g. if it's a small company + */ + default String getDescription() { return null; } + /** * Postal code of the recipient * diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 8577e5a0..6f1a9a33 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -138,7 +138,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + XMLTools.encodeXML(party.getGlobalID()) + ""; } xml += "" + XMLTools.encodeXML(party.getName()) + ""; - + if (party.getDescription() != null) { + xml += "" + XMLTools.encodeXML(party.getDescription()) + ""; + } if (party.getLegalOrganisation() != null) { xml += " "; xml += "" + XMLTools.encodeXML(party.getLegalOrganisation().getID()) + ""; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 4235d8df..5156950f 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -60,7 +60,9 @@ public class ZF2PushTest extends TestCase { final String TARGET_REVERSECHARGEPDF = "./target/testout-ZF2PushReverseCharge.pdf"; public void testPushExport() { - + /*** + * This writes to a filename like an official sample, please consider when changing (probably better not?) + */ // the writing part String orgname = "Bei Spiel GmbH"; String number = "RE-20201121/508"; @@ -92,10 +94,14 @@ public class ZF2PushTest extends TestCase { fail("Exception should not be raised"); } + + // now check the contents (like MustangReaderTest) ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF); assertTrue(zi.getUTF8().contains("DE88200800000970375700")); //the iban assertTrue(zi.getUTF8().contains("Max Mustermann")); //account holder + assertTrue(zi.getUTF8().contains("DueDateDateTime")); //account holder + assertTrue(zi.getUTF8().contains("20201212")); //account holder assertTrue(zi.getUTF8().contains(" Date: Fri, 20 Sep 2024 11:51:53 +0200 Subject: [PATCH 04/13] #435 obvious ones migrated --- .../ZUGFeRD/ZUGFeRDImporter.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 7fcea1ee..722167bd 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -234,7 +234,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the BuyerTradeParty SpecifiedTaxRegistration ID */ public String getBuyertradePartySpecifiedTaxRegistrationID() { - return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'SpecifiedTaxRegistration']//*[local-name() = 'ID']"); + return importedInvoice.getRecipient().getLegalOrganisation().getID(); } @@ -258,14 +258,14 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the BuyerTradeParty Name */ public String getBuyerTradePartyName() { - return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'Name']"); + return importedInvoice.getRecipient().getName(); } /** * @return the BuyerTradeParty Name */ public String getDeliveryTradePartyName() { - return extractString("//*[local-name() = 'ShipToTradeParty']//*[local-name() = 'Name']"); + return importedInvoice.getDeliveryAddress().getName(); } @@ -312,16 +312,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the Invoice ID */ public String getInvoiceID() { - try { - if (getVersion() == 1) { - return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'ID']"); - } else { - return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'ID']"); - } - } catch (final Exception e) { - // Exception was already logged - return ""; - } + return importedInvoice.getNumber(); } @@ -379,11 +370,18 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the sender's account IBAN code */ public String getIBAN() { - return extractString("//*[local-name() = 'PayeePartyCreditorFinancialAccount']/*[local-name() = 'IBANID']"); + for (IZUGFeRDTradeSettlement settlement:importedInvoice.getTradeSettlement()) { + if (settlement instanceof IZUGFeRDTradeSettlementDebit) { + return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN(); + } + } + return null; } public String getHolder() { + + return extractString("//*[local-name() = 'SellerTradeParty']/*[local-name() = 'Name']"); } @@ -403,7 +401,8 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return when the payment is due */ public String getDueDate() { - return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']/*[local-name() = 'DueDateDateTime']/*[local-name() = 'DateTimeString']"); + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(importedInvoice.getDueDate()); } @@ -558,7 +557,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { try { if (getVersion() == 1) { - nl = getNodeListByPath("//*[local-name() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']"); + nl = getNodeListByPath("//*[localname() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']"); } else { nl = getNodeListByPath("//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']"); } From f605398484a26db27b63360bf07963ff1fa7b4d5 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 30 Sep 2024 12:03:26 +0200 Subject: [PATCH 05/13] also read invoice currency --- .../java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 7fa57346..cdd8ba1e 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -377,6 +377,8 @@ public class ZUGFeRDInvoiceImporter { } + String currency= extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|*[local-name()=\"DocumentCurrencyCode\"]"); + zpp.setCurrency(currency); xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]"); NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); From b21b095c1a7b2b032025f57d910fe3f16f70627c Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 30 Sep 2024 12:41:47 +0200 Subject: [PATCH 06/13] corrected a test --- .../src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 5156950f..5637261f 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -152,7 +152,7 @@ public class ZF2PushTest extends TestCase { } catch (IOException e) { fail("IOException should not be raised"); } - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PDF); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_ATTACHMENTSPDF); Invoice i= null; try { i = zii.extractInvoice(); From a15267272bc9faef2ca904e25b9ea47133de3901 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 2 Oct 2024 13:54:16 +0200 Subject: [PATCH 07/13] correct date format, make it possible to *not* initially parse docs --- .../ZUGFeRD/ZUGFeRDImporter.java | 5 +- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 57 +++++++++++++------ .../org/mustangproject/ZUGFeRD/XRTest.java | 7 +-- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 722167bd..fec66407 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -374,6 +374,9 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { if (settlement instanceof IZUGFeRDTradeSettlementDebit) { return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN(); } + if (settlement instanceof IZUGFeRDTradeSettlementPayment) { + return ((IZUGFeRDTradeSettlementPayment) settlement).getOwnIBAN(); + } } return null; } @@ -401,7 +404,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return when the payment is due */ public String getDueDate() { - SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd"); return sdf.format(importedInvoice.getDueDate()); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index cdd8ba1e..e46a57f6 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -62,11 +62,15 @@ public class ZUGFeRDInvoiceImporter { * parsed Document */ protected Document document; + /*** + * automatically parse into importedInvoice + */ + protected boolean parseAutomatically = true; protected Integer version; - protected Invoice importedInvoice=null; + protected Invoice importedInvoice = null; protected boolean recalcPrice = false; protected boolean ignoreCalculationErrors = false; - protected ArrayList fileAttachments=new ArrayList<>(); + protected ArrayList fileAttachments = new ArrayList<>(); protected ZUGFeRDInvoiceImporter() { @@ -93,7 +97,6 @@ public class ZUGFeRDInvoiceImporter { } - /*** * return the file names of all files embedded into the PDF * @see for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML @@ -104,7 +107,6 @@ public class ZUGFeRDInvoiceImporter { } - /** * Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling. * @@ -165,8 +167,6 @@ public class ZUGFeRDInvoiceImporter { } - - private void extractFiles(Map names) throws IOException { for (final String alias : names.keySet()) { @@ -199,16 +199,35 @@ public class ZUGFeRDInvoiceImporter { } } - public void setRawXML(byte[] rawXML) throws IOException { + /*** + * set the xml of a CII invoice + * @param rawXML the xml string + * @param doParse automatically parse input for zugferdImporter (not ZUGFeRDInvoiceImporter) + * @throws IOException + */ + public void setRawXML(byte[] rawXML, boolean doParse) throws IOException { this.containsMeta = true; this.rawXML = rawXML; this.version = null; + + parseAutomatically = doParse; + try { setDocument(); } catch (ParserConfigurationException | SAXException e) { LOGGER.error("Failed to parse XML", e); throw new ZUGFeRDExportException(e); } + + } + + /*** + * set the xml of a CII invoice, simple version + * @param rawXML the cii(?) as a string + * @throws IOException + */ + public void setRawXML(byte[] rawXML) throws IOException { + setRawXML(rawXML, true); } private void setDocument() throws ParserConfigurationException, IOException, SAXException { @@ -218,18 +237,19 @@ public class ZUGFeRDInvoiceImporter { 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); + if (parseAutomatically) { + 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 @@ -377,7 +397,7 @@ 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\"]"); @@ -503,7 +523,7 @@ public class ZUGFeRDInvoiceImporter { 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())); + 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" @@ -581,6 +601,7 @@ public class ZUGFeRDInvoiceImporter { } TransactionCalculator tc = new TransactionCalculator(zpp); + String expectedStringTotalGross = tc.getGrandTotal().toPlainString(); EStandard whichType; try { @@ -600,12 +621,12 @@ public class ZUGFeRDInvoiceImporter { 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."); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index 56dfeb5e..0607cd09 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -125,13 +125,12 @@ public class XRTest extends TestCase { Invoice readInvoice = new Invoice(); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); try { - - zii.setRawXML(zf2p.getXML()); + zii.setRawXML(zf2p.getXML(), false); zii.extractInto(readInvoice); } catch (ParseException | XPathExpressionException xp) { - fail("Exception not expected"); + fail("ParseException not expected"); } catch (IOException e) { - throw new RuntimeException(e); + fail("IOException not expected"); } List attachedFiles=zii.getFileAttachmentsXML(); assertNotNull(attachedFiles); From f71b59a11c4c3b296684353c1ab762fe1216fa53 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 7 Oct 2024 10:21:01 +0200 Subject: [PATCH 08/13] be able to parse minimal invoices --- .../org/mustangproject/CalculatedInvoice.java | 30 +++++++++++++++++++ .../ZUGFeRD/ZUGFeRDImporter.java | 4 +-- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 10 +++++-- .../ZUGFeRD/ProfilesMinimumBasicWLTest.java | 2 +- 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 library/src/main/java/org/mustangproject/CalculatedInvoice.java diff --git a/library/src/main/java/org/mustangproject/CalculatedInvoice.java b/library/src/main/java/org/mustangproject/CalculatedInvoice.java new file mode 100644 index 00000000..729d0f9b --- /dev/null +++ b/library/src/main/java/org/mustangproject/CalculatedInvoice.java @@ -0,0 +1,30 @@ +// Copyright (c) 2023 Jochen Stärk, see LICENSE file +package org.mustangproject; + + +import org.mustangproject.ZUGFeRD.TransactionCalculator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.math.BigDecimal; + +public class CalculatedInvoice extends Invoice implements Serializable { + + protected BigDecimal grandTotal=null; + + public void calculate() { + TransactionCalculator tc=new TransactionCalculator(this); + grandTotal=tc.getGrandTotal(); + } + public BigDecimal getGrandTotal() { + if (grandTotal==null) { + calculate(); + } + return grandTotal; + } + public CalculatedInvoice setGrandTotal(BigDecimal grand) { + grandTotal=grand; + return this; + } +} diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index fec66407..53343fc9 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -394,9 +394,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { */ public String getAmount() { - TransactionCalculator ic=new TransactionCalculator(importedInvoice); - - return ic.getGrandTotal().toPlainString(); + return importedInvoice.getGrandTotal().toPlainString(); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index e46a57f6..36fdb57d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -67,7 +67,7 @@ public class ZUGFeRDInvoiceImporter { */ protected boolean parseAutomatically = true; protected Integer version; - protected Invoice importedInvoice = null; + protected CalculatedInvoice importedInvoice = null; protected boolean recalcPrice = false; protected boolean ignoreCalculationErrors = false; protected ArrayList fileAttachments = new ArrayList<>(); @@ -239,7 +239,7 @@ public class ZUGFeRDInvoiceImporter { document = builder.parse(is); if (parseAutomatically) { try { - importedInvoice = new Invoice(); + importedInvoice = new CalculatedInvoice(); extractInto(importedInvoice); } catch (XPathExpressionException e) { throw new RuntimeException(e); @@ -281,6 +281,12 @@ public class ZUGFeRDInvoiceImporter { NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (totalNodes.getLength() > 0) { expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent()); + if (zpp instanceof CalculatedInvoice) { + // usually we would re-calculate the invoice to get expectedGrandTotal + // however, for "minimal" invoices or other invoices without lines + // this will not work + ((CalculatedInvoice) zpp).setGrandTotal(expectedGrandTotal); + } } Date issueDate = null; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ProfilesMinimumBasicWLTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ProfilesMinimumBasicWLTest.java index 016dd9f2..4feab5a4 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ProfilesMinimumBasicWLTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ProfilesMinimumBasicWLTest.java @@ -136,7 +136,7 @@ public class ProfilesMinimumBasicWLTest extends TestCase { ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM); // Reading ZUGFeRD - assertEquals("145.37",zi.getAmount()); + assertEquals("146.37",zi.getAmount()); // assertEquals(zi.getBIC(), ownBIC); // assertEquals(zi.getIBAN(), ownIBAN); assertEquals(ownOrgName, zi.getHolder()); From a9a3dce1bd6be1e57516071df3369ae5cf0459ba Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 7 Oct 2024 11:42:51 +0200 Subject: [PATCH 09/13] corrected document level period start/end --- .../java/org/mustangproject/XMLTools.java | 85 ++++++++++++++++++- .../ZUGFeRD/ZUGFeRDImporter.java | 78 ++++------------- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 30 +++++++ 3 files changed, 127 insertions(+), 66 deletions(-) diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index af44d904..765277c1 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -4,13 +4,12 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.AbstractList; -import java.util.Collections; -import java.util.List; -import java.util.RandomAccess; +import java.text.SimpleDateFormat; +import java.util.*; import org.apache.commons.io.IOUtils; import org.dom4j.io.XMLWriter; +import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -70,7 +69,85 @@ public class XMLTools extends XMLWriter { } + /** + * returns the value of an node + * + * @param node the Node to get the value from + * @return A String or empty String, if no value was found + */ + public static String getNodeValue(Node node) { + if (node != null && node.getFirstChild() != null) { + return node.getFirstChild().getNodeValue(); + } + return ""; + } + + /** + * tries to convert a String to BigDecimal. + * + * @param nodeValue The value as String + * @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs + */ + public static BigDecimal tryBigDecimal(String nodeValue) { + try { + return new BigDecimal(nodeValue); + } catch (final Exception e) { + try { + return BigDecimal.valueOf(Float.valueOf(nodeValue)); + } catch (final Exception ex) { + return new BigDecimal("0.00"); + } + } + } + + + /** + * tries to convert a Node to a BigDecimal. + * + * @param node The value as String + * @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs + */ + public static BigDecimal tryBigDecimal(Node node) { + final String nodeValue = XMLTools.getNodeValue(node); + if (nodeValue.isEmpty()) { + return null; + } + return XMLTools.tryBigDecimal(nodeValue); + } + + /*** + * returns a util.Date from a 102 String yyyymmdd in a node + * @param node the node + * @return a util.Date, or null, if not parseable + */ + public static Date tryDate(Node node) { + final String nodeValue = XMLTools.getNodeValue(node); + if (nodeValue.isEmpty()) { + return null; + } + return tryDate(nodeValue); + } + + /*** + * returns a util.Date from a 102 String yyyymmdd + * @param toParse the string + * @return a util.Date, or null, if not parseable + */ + public static Date tryDate(String toParse) { + final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter(); + try { + return formatter.parse(toParse); + } catch (final Exception e) { + return null; + } + } + + /*** + * relplaces some entities like < , > and & with their escaped pendant like < + * @param s the string + * @return the "safe" string + */ public static String encodeXML(CharSequence s) { if (s == null) { return ""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 53343fc9..2a1ba512 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -34,6 +34,7 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.io.IOUtils; +import org.apache.fop.util.XMLUtil; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; @@ -702,7 +703,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { if (node != null) { final NodeList tradeAgreementChildren = node.getChildNodes(); node = getNodeByName(tradeAgreementChildren, "ChargeAmount"); - lineItem.setPrice(tryBigDecimal(getNodeValue(node))); + lineItem.setPrice(XMLTools.tryBigDecimal(node)); node = getNodeByName(tradeAgreementChildren, "BasisQuantity"); if (node != null && node.getAttributes() != null) { final Node unitCodeAttribute = node.getAttributes().getNamedItem("unitCode"); @@ -715,48 +716,48 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { node = getNodeByName(nn.getChildNodes(), "GrossPriceProductTradePrice"); if (node != null) { node = getNodeByName(node.getChildNodes(), "ChargeAmount"); - lineItem.setGrossPrice(tryBigDecimal(getNodeValue(node))); + lineItem.setGrossPrice(XMLTools.tryBigDecimal(node)); } break; case "AssociatedDocumentLineDocument": node = getNodeByName(nn.getChildNodes(), "LineID"); - lineItem.setId(getNodeValue(node)); + lineItem.setId(XMLTools.getNodeValue(node)); break; case "SpecifiedTradeProduct": node = getNodeByName(nn.getChildNodes(), "SellerAssignedID"); - lineItem.getProduct().setSellerAssignedID(getNodeValue(node)); + lineItem.getProduct().setSellerAssignedID(XMLTools.getNodeValue(node)); node = getNodeByName(nn.getChildNodes(), "BuyerAssignedID"); - lineItem.getProduct().setBuyerAssignedID(getNodeValue(node)); + lineItem.getProduct().setBuyerAssignedID(XMLTools.getNodeValue(node)); node = getNodeByName(nn.getChildNodes(), "Name"); - lineItem.getProduct().setName(getNodeValue(node)); + lineItem.getProduct().setName(XMLTools.getNodeValue(node)); node = getNodeByName(nn.getChildNodes(), "Description"); - lineItem.getProduct().setDescription(getNodeValue(node)); + lineItem.getProduct().setDescription(XMLTools.getNodeValue(node)); break; case "SpecifiedLineTradeDelivery": case "SpecifiedSupplyChainTradeDelivery": node = getNodeByName(nn.getChildNodes(), "BilledQuantity"); - lineItem.setQuantity(tryBigDecimal(getNodeValue(node))); + lineItem.setQuantity(XMLTools.tryBigDecimal(node)); break; case "SpecifiedLineTradeSettlement": node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); if (node != null) { node = getNodeByName(node.getChildNodes(), "RateApplicablePercent"); - lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node))); + lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node)); } node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); if (node != null) { node = getNodeByName(node.getChildNodes(), "CalculatedAmount"); - lineItem.setTax(tryBigDecimal(getNodeValue(node))); + lineItem.setTax(XMLTools.tryBigDecimal(node)); } node = getNodeByName(nn.getChildNodes(), "BillingSpecifiedPeriod"); if (node != null) { @@ -770,13 +771,13 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { if (end != null) { dateTimeEnd = getNodeByName(end.getChildNodes(), "DateTimeString"); } - lineItem.setDetailedDeliveryPeriod(tryDate(dateTimeStart), tryDate(dateTimeEnd)); + lineItem.setDetailedDeliveryPeriod(XMLTools.tryDate(dateTimeStart), XMLTools.tryDate(dateTimeEnd)); } node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementLineMonetarySummation"); if (node != null) { node = getNodeByName(node.getChildNodes(), "LineTotalAmount"); - lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node))); + lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node)); } break; case "SpecifiedSupplyChainTradeSettlement": @@ -785,19 +786,19 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); if (node != null) { node = getNodeByName(node.getChildNodes(), "ApplicablePercent"); - lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node))); + lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node)); } node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); if (node != null) { node = getNodeByName(node.getChildNodes(), "CalculatedAmount"); - lineItem.setTax(tryBigDecimal(getNodeValue(node))); + lineItem.setTax(XMLTools.tryBigDecimal(node)); } node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementMonetarySummation"); if (node != null) { node = getNodeByName(node.getChildNodes(), "LineTotalAmount"); - lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node))); + lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node)); } break; } @@ -872,51 +873,4 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } } - /** - * returns the value of an node - * - * @param node the Node to get the value from - * @return A String or empty String, if no value was found - */ - private String getNodeValue(Node node) { - if (node != null && node.getFirstChild() != null) { - return node.getFirstChild().getNodeValue(); - } - return ""; - } - - /** - * tries to convert an String to BigDecimal. - * - * @param nodeValue The value as String - * @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs - */ - private BigDecimal tryBigDecimal(String nodeValue) { - try { - return new BigDecimal(nodeValue); - } catch (final Exception e) { - try { - return BigDecimal.valueOf(Float.valueOf(nodeValue)); - } catch (final Exception ex) { - return new BigDecimal("0.00"); - } - } - } - - private Date tryDate(Node node) { - final String nodeValue = getNodeValue(node); - if (nodeValue.isEmpty()) { - return null; - } - return tryDate(nodeValue); - } - - private static Date tryDate(String toParse) { - final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter(); - try { - return formatter.parse(toParse); - } catch (final Exception e) { - return null; - } - } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 36fdb57d..cfd580f1 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -261,6 +261,8 @@ public class ZUGFeRDInvoiceImporter { String number = ""; String typeCode = null; + String deliveryPeriodStart = null; + String deliveryPeriodEnd = null; /* * dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate * setSender setRecipient setnumber bspw. due date @@ -460,9 +462,37 @@ public class ZUGFeRDInvoiceImporter { } } } + if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null) + && (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("BillingSpecifiedPeriod"))) { + NodeList periodChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes(); + for (int periodChildIndex = 0; periodChildIndex < periodChilds.getLength(); periodChildIndex++) { + if ((periodChilds.item(periodChildIndex).getLocalName() != null) && (periodChilds.item(periodChildIndex).getLocalName().equals("StartDateTime"))) { + + NodeList startPeriodChilds = periodChilds.item(periodChildIndex).getChildNodes(); + for (int startPeriodIndex = 0; startPeriodIndex < startPeriodChilds.getLength(); startPeriodIndex++) { + if ((startPeriodChilds.item(startPeriodIndex).getLocalName() != null) && (startPeriodChilds.item(startPeriodIndex).getLocalName().equals("DateTimeString"))) {//CII + deliveryPeriodStart = startPeriodChilds.item(startPeriodIndex).getTextContent(); + } + } + } + if ((periodChilds.item(periodChildIndex).getLocalName() != null) && (periodChilds.item(periodChildIndex).getLocalName().equals("EndDateTime"))) { + NodeList endPeriodChilds = periodChilds.item(periodChildIndex).getChildNodes(); + for (int endPeriodIndex = 0; endPeriodIndex < endPeriodChilds.getLength(); endPeriodIndex++) { + if ((endPeriodChilds.item(endPeriodIndex).getLocalName() != null) && (endPeriodChilds.item(endPeriodIndex).getLocalName().equals("DateTimeString"))) {//CII + deliveryPeriodEnd = endPeriodChilds.item(endPeriodIndex).getTextContent(); + } + } + } + } + } } } + if ((deliveryPeriodStart!=null)&&(deliveryPeriodEnd!=null)) { + zpp.setDetailedDeliveryPeriod(XMLTools.tryDate(deliveryPeriodStart), XMLTools.tryDate(deliveryPeriodEnd)); + } else if (deliveryPeriodStart!=null) { + zpp.setDeliveryDate(XMLTools.tryDate(deliveryPeriodStart)); + } xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); From d4e4ed3f2f7fc6ecf802a5cc70a906d616318d84 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 9 Oct 2024 13:16:05 +0200 Subject: [PATCH 10/13] allow zf1 reading of line totals --- .../main/java/org/mustangproject/Item.java | 4 +-- .../ZUGFeRD/ZUGFeRDImporter.java | 7 ---- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 34 +++++++++++++++---- .../ZUGFeRD/MustangReaderWriterTest.java | 5 +-- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 321b4723..0e218223 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -237,7 +237,7 @@ public class Item implements IZUGFeRDExportableItem { } } - if (tradeSettlementName.equals("SpecifiedTradeSettlementLineMonetarySummation")) { + if (tradeSettlementName.equals("SpecifiedTradeSettlementLineMonetarySummation") || tradeSettlementName.equals("SpecifiedTradeSettlementMonetarySummation")) { NodeList totalChilds = tradeSettlementChilds.item(tradeSettlementChildIndex) .getChildNodes(); for (int totalChildIndex = 0; totalChildIndex < totalChilds @@ -255,7 +255,7 @@ public class Item implements IZUGFeRDExportableItem { BigDecimal prc = new BigDecimal(price.trim()); BigDecimal qty = new BigDecimal(quantity.trim()); if ((recalcPrice) && (!qty.equals(BigDecimal.ZERO))) { - prc = new BigDecimal(lineTotal.trim()).divide(qty, 4, RoundingMode.HALF_UP); + prc = new BigDecimal(lineTotal.trim()).divide(qty, 18, RoundingMode.HALF_UP); } Product p = new Product(name, description, unitCode, vatPercent == null ? null : new BigDecimal(vatPercent.trim())); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 2a1ba512..184aefce 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -77,13 +77,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - /*** - * have the item prices be determined from the line total. - * That's a workaround for some invoices which just put 0 as item price - */ - public void doRecalculateItemPricesFromLineTotals() { - recalcPrice = true; - } /*** * do not raise ParseExceptions even if the reproduced invoice total does not match the given value diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index cfd580f1..344184c4 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -78,6 +78,14 @@ public class ZUGFeRDInvoiceImporter { } public ZUGFeRDInvoiceImporter(String pdfFilename) { + setPDFFilename(pdfFilename); + } + + public ZUGFeRDInvoiceImporter(InputStream pdfStream) { + setInputStream(pdfStream); + } + + public void setPDFFilename(String pdfFilename){ try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) { extractLowLevel(bis); } catch (final IOException e) { @@ -86,8 +94,7 @@ public class ZUGFeRDInvoiceImporter { } } - - public ZUGFeRDInvoiceImporter(InputStream pdfStream) { + public void setInputStream(InputStream pdfStream) { try { extractLowLevel(pdfStream); } catch (final IOException e) { @@ -167,6 +174,20 @@ public class ZUGFeRDInvoiceImporter { } + /*** + * have the item prices be determined from the line total. + * That's a workaround for some invoices which just put 0 as item price + */ + public void doRecalculateItemPricesFromLineTotals() { + recalcPrice = true; + } + + + /*** + * sets th pdf attachments, and if a file is recognized (e.g. a factur-x.xml) triggers processing + * @param names the Hashmap of String, PDComplexFileSpecification + * @throws IOException + */ private void extractFiles(Map names) throws IOException { for (final String alias : names.keySet()) { @@ -209,8 +230,7 @@ public class ZUGFeRDInvoiceImporter { this.containsMeta = true; this.rawXML = rawXML; this.version = null; - - parseAutomatically = doParse; + parseAutomatically = doParse; try { setDocument(); @@ -235,7 +255,7 @@ public class ZUGFeRDInvoiceImporter { xmlFact.setNamespaceAware(true); final DocumentBuilder builder = xmlFact.newDocumentBuilder(); final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); - /// is.skip(guessBOMSize(is)); + /// is.skip(guessBOMSize(is)); document = builder.parse(is); if (parseAutomatically) { try { @@ -488,9 +508,9 @@ public class ZUGFeRDInvoiceImporter { } } - if ((deliveryPeriodStart!=null)&&(deliveryPeriodEnd!=null)) { + if ((deliveryPeriodStart != null) && (deliveryPeriodEnd != null)) { zpp.setDetailedDeliveryPeriod(XMLTools.tryDate(deliveryPeriodStart), XMLTools.tryDate(deliveryPeriodEnd)); - } else if (deliveryPeriodStart!=null) { + } else if (deliveryPeriodStart != null) { zpp.setDeliveryDate(XMLTools.tryDate(deliveryPeriodStart)); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java index 3a767c97..5cec1518 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java @@ -214,8 +214,9 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { public void testForeignImport() { InputStream inputStream = this.getClass().getResourceAsStream("/zugferd_invoice.pdf"); - ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream); - + ZUGFeRDImporter zi = new ZUGFeRDImporter(); + zi.doRecalculateItemPricesFromLineTotals(); + zi.setInputStream(inputStream); // Reading ZUGFeRD String amount = zi.getAmount(); From 41693d69095efd82a9dfa1b20c2df3eecad0e0e0 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 9 Oct 2024 13:27:09 +0200 Subject: [PATCH 11/13] "fix" test --- .../java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java | 9 +-------- .../mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java | 8 ++++++++ .../mustangproject/ZUGFeRD/MustangReaderWriterTest.java | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 184aefce..739b313e 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -78,13 +78,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { - /*** - * do not raise ParseExceptions even if the reproduced invoice total does not match the given value - */ - public void doIgnoreCalculationErrors() { - ignoreCalculationErrors = true; - } - //////////////////////////////////// /** @@ -435,7 +428,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the Issue Date() */ public String getIssueDate() { - SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd"); return sdf.format(importedInvoice.getIssueDate()); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 344184c4..c86741b6 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -183,6 +183,14 @@ public class ZUGFeRDInvoiceImporter { } + /*** + * do not raise ParseExceptions even if the reproduced invoice total does not match the given value + */ + public void doIgnoreCalculationErrors() { + ignoreCalculationErrors = true; + } + + /*** * sets th pdf attachments, and if a file is recognized (e.g. a factur-x.xml) triggers processing * @param names the Hashmap of String, PDComplexFileSpecification diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java index 5cec1518..b7fbede6 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java @@ -216,6 +216,7 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { InputStream inputStream = this.getClass().getResourceAsStream("/zugferd_invoice.pdf"); ZUGFeRDImporter zi = new ZUGFeRDImporter(); zi.doRecalculateItemPricesFromLineTotals(); + zi.doIgnoreCalculationErrors(); zi.setInputStream(inputStream); // Reading ZUGFeRD String amount = zi.getAmount(); From 17b6841071fd7a531694b412838723a86c7cc924 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 9 Oct 2024 13:52:55 +0200 Subject: [PATCH 12/13] also import deliveryaddress --- .../ZUGFeRD/ZUGFeRDImporter.java | 29 ++++++--- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 6 ++ .../org/mustangproject/ZUGFeRD/ZF2Test.java | 62 ++++++++++--------- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 739b313e..7807ebce 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -52,6 +52,7 @@ import org.xml.sax.SAXException; public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class); + public ZUGFeRDImporter() { super(); } @@ -65,8 +66,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - - /*** * Wrapper for protected method extractString * @param xpathStr the xpath expression to be evaluated @@ -77,7 +76,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - //////////////////////////////////// /** @@ -221,7 +219,16 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the BuyerTradeParty SpecifiedTaxRegistration ID */ public String getBuyertradePartySpecifiedTaxRegistrationID() { - return importedInvoice.getRecipient().getLegalOrganisation().getID(); + String id = null; + if ((importedInvoice.getRecipient()!=null) && (importedInvoice.getRecipient().getLegalOrganisation()!=null)) { + // this *should* be the official result + id = importedInvoice.getRecipient().getLegalOrganisation().getID(); + } + // but also provide some fallback + if (id == null) { + id = getBuyerTradePartyID(); + } + return id; } @@ -357,7 +364,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the sender's account IBAN code */ public String getIBAN() { - for (IZUGFeRDTradeSettlement settlement:importedInvoice.getTradeSettlement()) { + for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) { if (settlement instanceof IZUGFeRDTradeSettlementDebit) { return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN(); } @@ -389,7 +396,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return when the payment is due */ public String getDueDate() { - SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); return sdf.format(importedInvoice.getDueDate()); } @@ -421,14 +428,19 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the BuyerTradeParty ID */ public String getBuyerTradePartyID() { - return importedInvoice.getRecipient().getID(); + String id = importedInvoice.getRecipient().getID(); + if (id == null) { + // provide some fallback + id = importedInvoice.getRecipient().getVATID(); + } + return id; } /** * @return the Issue Date() */ public String getIssueDate() { - SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); return sdf.format(importedInvoice.getIssueDate()); } @@ -508,7 +520,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - /** * Returns the raw XML data as extracted from the ZUGFeRD PDF file. * diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index c86741b6..441c658f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -300,6 +300,12 @@ public class ZUGFeRDInvoiceImporter { XPath xpath = xpathFact.newXPath(); XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*"); NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + XPathExpression shipEx = xpath.compile("//*[local-name()=\"ShipToTradeParty\"]"); + NodeList deliveryNodes = (NodeList) shipEx.evaluate(getDocument(), XPathConstants.NODESET); + if (deliveryNodes!=null) { + zpp.setDeliveryAddress(new TradeParty(deliveryNodes)); + } + xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*"); NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2Test.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2Test.java index 2ae78e90..d945c770 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2Test.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2Test.java @@ -1,22 +1,24 @@ -/** ********************************************************************** - * +/** + * ********************************************************************* + *

* 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 java.io.IOException; @@ -99,9 +101,9 @@ public class ZF2Test extends MustangReaderTestCase { public IZUGFeRDExportableItem[] getZFItems() { final Item[] allItems = new Item[3]; final Product designProduct = new Product("", "Künstlerische Gestaltung (Stunde): Einer Beispielrechnung", "HUR", - new BigDecimal("7.000000")); + new BigDecimal("7.000000")); final Product balloonProduct = new Product("", "Bestellerweiterung für E&F Umbau", "C62", - new BigDecimal("19.000000"));// test for issue 103 + new BigDecimal("19.000000"));// test for issue 103 final Product airProduct = new Product("", "Heiße Luft pro Liter", "LTR", new BigDecimal("19.000000")); allItems[0] = new Item(new BigDecimal("160"), new BigDecimal("1"), designProduct); @@ -166,12 +168,12 @@ public class ZF2Test extends MustangReaderTestCase { // the writing part try (InputStream SOURCE_PDF = this.getClass() - .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf"); + .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf"); ZUGFeRDExporterFromA3 ze = new ZUGFeRDExporterFromA3().setProducer("My Application") - .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile("EN16931") - .load(SOURCE_PDF)) { - + .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile("EN16931") + .load(SOURCE_PDF)) { + ze.setTransaction(this); final String theXML = new String(ze.getProvider().getXML()); assertTrue(theXML.contains(" Date: Wed, 9 Oct 2024 14:14:50 +0200 Subject: [PATCH 13/13] corrected merge errors --- .../main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index e1cd03f8..c9a4ff15 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -232,7 +232,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { String id = null; if ((importedInvoice.getRecipient()!=null) && (importedInvoice.getRecipient().getLegalOrganisation()!=null)) { // this *should* be the official result - id = importedInvoice.getRecipient().getLegalOrganisation().getID(); + id = importedInvoice.getRecipient().getLegalOrganisation().getSchemedID().getID(); } // but also provide some fallback if (id == null) {