From cfee4b3c310a82dfad83543e662ea9f2c621c854 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 21 Oct 2021 13:58:10 +0200 Subject: [PATCH] write support for referenceddocument --- .../main/java/org/mustangproject/Item.java | 22 +++ .../mustangproject/ReferencedDocument.java | 38 ++++ .../ZUGFeRD/IReferencedDocument.java | 8 + .../ZUGFeRD/IZUGFeRDExportableItem.java | 8 + .../ZUGFeRD/ZUGFeRD2PullProvider.java | 14 +- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 167 ++++++++++-------- 6 files changed, 184 insertions(+), 73 deletions(-) create mode 100644 library/src/main/java/org/mustangproject/ReferencedDocument.java create mode 100644 library/src/main/java/org/mustangproject/ZUGFeRD/IReferencedDocument.java diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 9d89cc9d..2d8de5b9 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -1,5 +1,6 @@ package org.mustangproject; +import org.mustangproject.ZUGFeRD.IReferencedDocument; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem; @@ -17,6 +18,7 @@ public class Item implements IZUGFeRDExportableItem { protected String referencedLineID=null; protected Product product; protected ArrayList notes = null; + protected ArrayList referencedDocuments = null; protected ArrayList Allowances = new ArrayList(), Charges = new ArrayList(); @@ -189,6 +191,26 @@ public class Item implements IZUGFeRDExportableItem { return this; } + /*** + * adds item level Referenced documents along with their typecodes and issuerassignedIDs + * @param doc the ReferencedDocument to add + * @return fluent setter + */ + public Item addReferencedDocument(ReferencedDocument doc) { + if (referencedDocuments==null) { + referencedDocuments=new ArrayList(); + } + referencedDocuments.add(doc); + return this; + } + + @Override + public IReferencedDocument[] getReferencedDocuments() { + if (referencedDocuments==null) { + return null; + } + return referencedDocuments.toArray(new IReferencedDocument[0]); + } /*** * specify a item level delivery period * (apart from the document level delivery period, and the document level diff --git a/library/src/main/java/org/mustangproject/ReferencedDocument.java b/library/src/main/java/org/mustangproject/ReferencedDocument.java new file mode 100644 index 00000000..ea416dd4 --- /dev/null +++ b/library/src/main/java/org/mustangproject/ReferencedDocument.java @@ -0,0 +1,38 @@ +package org.mustangproject; + +import org.mustangproject.ZUGFeRD.IReferencedDocument; + +public class ReferencedDocument implements IReferencedDocument { + + + String issuerAssignedID; + String typeCode; + String referenceTypeCode; + + public void setIssuerAssignedID(String issuerAssignedID) { + this.issuerAssignedID = issuerAssignedID; + } + + public void setTypeCode(String typeCode) { + this.typeCode = typeCode; + } + + public void setReferenceTypeCode(String referenceTypeCode) { + this.referenceTypeCode = referenceTypeCode; + } + + @Override + public String getIssuerAssignedID() { + return issuerAssignedID; + } + + @Override + public String getTypeCode() { + return typeCode; + } + + @Override + public String getReferenceTypeCode() { + return referenceTypeCode; + } +} diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IReferencedDocument.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IReferencedDocument.java new file mode 100644 index 00000000..a78852c1 --- /dev/null +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IReferencedDocument.java @@ -0,0 +1,8 @@ +package org.mustangproject.ZUGFeRD; + +public interface IReferencedDocument { + String getIssuerAssignedID(); + String getTypeCode(); + String getReferenceTypeCode(); + +} diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 08194198..65318300 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -94,6 +94,14 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ return null; } + + /*** + * allows to specify multiple(!) referenced documents along with e.g. their typecodes + * @return + */ + default IReferencedDocument[] getReferencedDocuments() { + return null; + } /*** * descriptive texts * @return an array of strings of item specific "includedNotes", text values diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 254c870b..978afb76 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -351,9 +351,21 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + " \n" + " \n"; + if (currentItem.getReferencedDocuments() != null) { + for (IReferencedDocument currentReferencedDocument : currentItem.getReferencedDocuments()) { + xml = xml + "\n" + + "" + XMLTools.encodeXML(currentReferencedDocument.getIssuerAssignedID()) + "\n" + + "" + XMLTools.encodeXML(currentReferencedDocument.getTypeCode()) + "\n" + + "" + XMLTools.encodeXML(currentReferencedDocument.getReferenceTypeCode()) + "\n" + + "\n"; + + + } + + } if (currentItem.getBuyerOrderReferencedDocumentLineID() != null) { xml = xml + " \n" - + " "+XMLTools.encodeXML(currentItem.getBuyerOrderReferencedDocumentLineID())+"\n" + + " " + XMLTools.encodeXML(currentItem.getBuyerOrderReferencedDocumentLineID()) + "\n" + " \n"; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index f4acd281..42d4cbd4 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -18,6 +18,8 @@ import java.util.Date; public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDInvoiceImporter.class.getCanonicalName()); // log + private boolean recalcPrice = false; + private boolean ignoreCalculationErrors = false; public ZUGFeRDInvoiceImporter() { super(); @@ -84,7 +86,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { if ((exchangedDocumentChilds.item(documentChildIndex).getLocalName() != null) && (exchangedDocumentChilds.item(documentChildIndex).getLocalName().equals("IssueDateTime"))) { NodeList issueDateTimeChilds = exchangedDocumentChilds.item(documentChildIndex).getChildNodes(); for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) { - if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName()!=null)&&(issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) { + if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null) && (issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) { issueDate = new SimpleDateFormat("yyyyMMdd").parse(issueDateTimeChilds.item(issueDateChildIndex).getTextContent()); } } @@ -160,6 +162,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { String description = ""; String quantity = "0"; String vatPercent = "0"; + String lineTotal = "0"; String unitCode = "0"; //nodes.item(i).getTextContent())) { @@ -169,7 +172,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { if ((itemChilds.item(itemChildIndex).getLocalName() != null) && (itemChilds.item(itemChildIndex).getLocalName().equals("SpecifiedLineTradeAgreement"))) { NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes(); for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds.getLength(); tradeLineChildIndex++) { - if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName()!=null)&&tradeLineChilds.item(tradeLineChildIndex).getLocalName().equals("NetPriceProductTradePrice")) { + if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds.item(tradeLineChildIndex).getLocalName().equals("NetPriceProductTradePrice")) { NodeList netChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes(); for (int netIndex = 0; netIndex < netChilds.getLength(); netIndex++) { if ((netChilds.item(netIndex).getLocalName() != null) && (netChilds.item(netIndex).getLocalName().equals("ChargeAmount"))) { @@ -192,8 +195,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { if ((itemChilds.item(itemChildIndex).getLocalName() != null) && (itemChilds.item(itemChildIndex).getLocalName().equals("SpecifiedTradeProduct"))) { NodeList tradeProductChilds = itemChilds.item(itemChildIndex).getChildNodes(); for (int tradeProductChildIndex = 0; tradeProductChildIndex < tradeProductChilds.getLength(); tradeProductChildIndex++) { - if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null) && (tradeProductChilds.item(tradeProductChildIndex).getLocalName().equals("Name"))) - { + if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null) && (tradeProductChilds.item(tradeProductChildIndex).getLocalName().equals("Name"))) { name = tradeProductChilds.item(tradeProductChildIndex).getTextContent(); } } @@ -201,95 +203,116 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { if ((itemChilds.item(itemChildIndex).getLocalName() != null) && (itemChilds.item(itemChildIndex).getLocalName().equals("SpecifiedLineTradeSettlement"))) { NodeList tradeSettlementChilds = itemChilds.item(itemChildIndex).getChildNodes(); for (int tradeSettlementChildIndex = 0; tradeSettlementChildIndex < tradeSettlementChilds.getLength(); tradeSettlementChildIndex++) { - if ((tradeSettlementChilds.item(tradeSettlementChildIndex).getLocalName() != null) && (tradeSettlementChilds.item(tradeSettlementChildIndex).getLocalName().equals("ApplicableTradeTax"))) { - NodeList taxChilds = tradeSettlementChilds.item(tradeSettlementChildIndex).getChildNodes(); - for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { - if ((taxChilds.item(taxChildIndex).getLocalName() != null) && (taxChilds.item(taxChildIndex).getLocalName().equals("RateApplicablePercent"))) { - vatPercent = taxChilds.item(taxChildIndex).getTextContent(); + + if (tradeSettlementChilds.item(tradeSettlementChildIndex).getLocalName() != null) { + if (tradeSettlementChilds.item(tradeSettlementChildIndex).getLocalName().equals("ApplicableTradeTax")) { + NodeList taxChilds = tradeSettlementChilds.item(tradeSettlementChildIndex).getChildNodes(); + for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { + if ((taxChilds.item(taxChildIndex).getLocalName() != null) && (taxChilds.item(taxChildIndex).getLocalName().equals("RateApplicablePercent"))) { + vatPercent = taxChilds.item(taxChildIndex).getTextContent(); + } + } + } + + if (tradeSettlementChilds.item(tradeSettlementChildIndex).getLocalName().equals("SpecifiedTradeSettlementLineMonetarySummation")) { + NodeList totalChilds = tradeSettlementChilds.item(tradeSettlementChildIndex).getChildNodes(); + for (int totalChildIndex = 0; totalChildIndex < totalChilds.getLength(); totalChildIndex++) { + if ((totalChilds.item(totalChildIndex).getLocalName() != null) && (totalChilds.item(totalChildIndex).getLocalName().equals("LineTotalAmount"))) { + lineTotal = totalChilds.item(totalChildIndex).getTextContent(); + } } } } } } - - } - zpp.addItem(new Item(new Product(name, description, unitCode, new BigDecimal(vatPercent.trim())), new BigDecimal(price.trim()), new BigDecimal(quantity.trim()))); + 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); + } + zpp.addItem(new Item(new Product(name, description, unitCode, new BigDecimal(vatPercent.trim())), prc, qty)); + + } - } + // 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 taxPercent = null; + for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) { + if (chargeNodeChilds.item(chargeChildIndex).getLocalName() != null) { - // 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 taxPercent = null; - for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) { - if (chargeNodeChilds.item(chargeChildIndex).getLocalName() != null) { - - if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("ChargeIndicator")) { - NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); - for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) { - if (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator")) { - isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent().equalsIgnoreCase("true"); + if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("ChargeIndicator")) { + NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); + for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) { + if (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator")) { + isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent().equalsIgnoreCase("true"); + } } - } - } else if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("ActualAmount")) { - chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent(); - } else if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("Reason")) { - reason = chargeNodeChilds.item(chargeChildIndex).getTextContent(); - } else if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("CategoryTradeTax")) { - NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); - for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { - if ((taxChilds.item(taxChildIndex).getLocalName() != null) && (taxChilds.item(taxChildIndex).getLocalName().equals("RateApplicablePercent"))) - { - taxPercent = taxChilds.item(taxChildIndex).getTextContent(); + } else if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("ActualAmount")) { + chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent(); + } else if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("Reason")) { + reason = chargeNodeChilds.item(chargeChildIndex).getTextContent(); + } else if (chargeNodeChilds.item(chargeChildIndex).getLocalName().equals("CategoryTradeTax")) { + NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); + for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { + if ((taxChilds.item(taxChildIndex).getLocalName() != null) && (taxChilds.item(taxChildIndex).getLocalName().equals("RateApplicablePercent"))) { + taxPercent = taxChilds.item(taxChildIndex).getTextContent(); + } } } } } + if (isCharge) { + Charge c = new Charge(new BigDecimal(chargeAmount)); + if (reason != null) { + c.setReason(reason); + } + 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 (taxPercent != null) { + a.setTaxPercent(new BigDecimal(taxPercent)); + } + zpp.addAllowance(a); + } + } - - if (isCharge) { - Charge c = new Charge(new BigDecimal(chargeAmount)); - if (reason != null) { - c.setReason(reason); - } - 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 (taxPercent != null) { - a.setTaxPercent(new BigDecimal(taxPercent)); - } - zpp.addAllowance(a); + TransactionCalculator tc = new TransactionCalculator(zpp); + String expectedStringTotalGross = tc.getGrandTotal().toPlainString(); + if ((!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); } - } - - TransactionCalculator tc = new TransactionCalculator(zpp); - String expectedStringTotalGross = tc.getGrandTotal().toPlainString(); - if (!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2))) { - throw new ParseException("Could not reproduce the invoice, this could mean that it could not be read properly", 0); - } - return zpp; + + } + public void doRecalculateItemPricesFromLineTotals() { + recalcPrice = true; + } + + public void doIgnoreCalculationErrors() { + ignoreCalculationErrors = true; + } }