diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index f66eb453..a4be6e48 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -110,7 +110,7 @@ public Item(NodeList itemChilds, boolean recalcPrice) { itemMap.getNode(new String[]{"InvoicedQuantity", "CreditedQuantity"}).ifPresent(icn -> { // ubl - setQuantity(new BigDecimal(icn.getTextContent().trim())); + setQuantity(NodeMap.parse(icn.getTextContent().trim())); product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue()); }); @@ -182,7 +182,7 @@ public Item(NodeList itemChilds, boolean recalcPrice) { itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery") .flatMap(icnm -> icnm.getNode("BilledQuantity", "RequestedQuantity", "DespatchedQuantity")) .ifPresent(bq -> { - setQuantity(new BigDecimal(bq.getTextContent().trim())); + setQuantity(NodeMap.parse(bq.getTextContent().trim())); if (bq.hasAttributes()) { Node unitAttr = bq.getAttributes().getNamedItem("unitCode"); if (unitAttr != null) { @@ -235,13 +235,13 @@ public Item(NodeList itemChilds, boolean recalcPrice) { izac = new Charge(); } if (amountString != null) { - izac.setTotalAmount(new BigDecimal(amountString.trim())); + izac.setTotalAmount(NodeMap.parse(amountString.trim())); } if (basisAmountString != null) { - izac.setBasisAmount(new BigDecimal(basisAmountString.trim())); + izac.setBasisAmount(NodeMap.parse(basisAmountString.trim())); } if (percentString != null) { - izac.setPercent(new BigDecimal(percentString.trim())); + izac.setPercent(NodeMap.parse(percentString.trim())); } if (reason != null) { izac.setReason(reason); @@ -287,10 +287,10 @@ public Item(NodeList itemChilds, boolean recalcPrice) { izac = new Charge(); } if (amountString != null) { - izac.setTotalAmount(new BigDecimal(amountString)); + izac.setTotalAmount(NodeMap.parse(amountString)); } if (percentString != null) { - izac.setPercent(new BigDecimal(percentString)); + izac.setPercent(NodeMap.parse(percentString)); } if (reason != null) { izac.setReason(reason); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 85e66166..0e3f0268 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -507,7 +507,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx BigDecimal expectedGrandTotal = null; NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (totalNodes.getLength() > 0) { - expectedGrandTotal = new BigDecimal(XMLTools.trimOrNull(totalNodes.item(0))); + expectedGrandTotal = NodeMap.parse(XMLTools.trimOrNull(totalNodes.item(0))); if (zpp instanceof CalculatedInvoice) { // usually we would re-calculate the invoice to get expectedGrandTotal // however, for "minimal" invoices or other invoices without lines @@ -520,7 +520,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx BigDecimal expectedTaxBasis = null; NodeList basisNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (basisNodes.getLength() > 0) { - expectedTaxBasis = new BigDecimal(XMLTools.trimOrNull(basisNodes.item(0))); + expectedTaxBasis = NodeMap.parse(XMLTools.trimOrNull(basisNodes.item(0))); if (zpp instanceof CalculatedInvoice) { // usually we would re-calculate the invoice to get expectedGrandTotal // however, for "minimal" invoices or other invoices without lines @@ -532,7 +532,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx xpr = xpath.compile("//*[local-name()=\"TotalPrepaidAmount\"]|//*[local-name()=\"PrepaidAmount\"]"); NodeList prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (prepaidNodes.getLength() > 0) { - zpp.setTotalPrepaidAmount(new BigDecimal(XMLTools.trimOrNull(prepaidNodes.item(0)))); + zpp.setTotalPrepaidAmount(NodeMap.parse(XMLTools.trimOrNull(prepaidNodes.item(0)))); } @@ -540,7 +540,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx NodeList lineTotalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (lineTotalNodes.getLength() > 0) { if (zpp instanceof CalculatedInvoice) { - ((CalculatedInvoice) zpp).setLineTotalAmount(new BigDecimal(XMLTools.trimOrNull(lineTotalNodes.item(0)))); + ((CalculatedInvoice) zpp).setLineTotalAmount(NodeMap.parse(XMLTools.trimOrNull(lineTotalNodes.item(0)))); } } @@ -549,7 +549,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx if (taxTotalNodes.getLength() > 0) { String taxTotalStr=XMLTools.trimOrNull(taxTotalNodes.item(0)); if ((zpp instanceof CalculatedInvoice)&&(taxTotalStr!=null)) { - ((CalculatedInvoice) zpp).setVATtotal(new BigDecimal(taxTotalStr)); + ((CalculatedInvoice) zpp).setVATtotal(NodeMap.parse(taxTotalStr)); } } @@ -557,7 +557,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); BigDecimal duePayableAmount = null; if (lineDueNodes.getLength() > 0) { - duePayableAmount = new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0))); + duePayableAmount = NodeMap.parse(XMLTools.trimOrNull(lineDueNodes.item(0))); if (zpp instanceof CalculatedInvoice) { ((CalculatedInvoice) zpp).setDuePayable(duePayableAmount); } @@ -1120,7 +1120,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx String rounding = extractString("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"RoundingAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"PayableRoundingAmount\"]"); if (!rounding.isEmpty()) { - zpp.setRoundingAmount(new BigDecimal(rounding.trim())); + zpp.setRoundingAmount(NodeMap.parse(rounding.trim())); } xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]"); @@ -1234,7 +1234,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx } if (isCharge) { - Charge c = new Charge(new BigDecimal(chargeAmount)); + Charge c = new Charge(NodeMap.parse(chargeAmount)); if (reason != null) { c.setReason(reason); } @@ -1242,10 +1242,10 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx c.setReasonCode(reasonCode); } if (taxPercent != null) { - c.setTaxPercent(new BigDecimal(taxPercent)); + c.setTaxPercent(NodeMap.parse(taxPercent)); } if (basisAmount != null) { - c.setBasisAmount(new BigDecimal(basisAmount)); + c.setBasisAmount(NodeMap.parse(basisAmount)); } if (taxExemptionReason != null) { c.setTaxExemptionReason(taxExemptionReason); @@ -1258,7 +1258,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx } zpp.addCharge(c); } else { - Allowance a = new Allowance(new BigDecimal(chargeAmount)); + Allowance a = new Allowance(NodeMap.parse(chargeAmount)); if (reason != null) { a.setReason(reason); } @@ -1266,10 +1266,10 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx a.setReasonCode(reasonCode); } if (taxPercent != null) { - a.setTaxPercent(new BigDecimal(taxPercent)); + a.setTaxPercent(NodeMap.parse(taxPercent)); } if (basisAmount != null) { - a.setBasisAmount(new BigDecimal(basisAmount)); + a.setBasisAmount(NodeMap.parse(basisAmount)); } if (taxExemptionReason != null) { a.setTaxExemptionReason(taxExemptionReason); @@ -1309,9 +1309,9 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx //AppliedTradeTax } if (chargeAmount != null) { - Charge c = new Charge(new BigDecimal(chargeAmount)); + Charge c = new Charge(NodeMap.parse(chargeAmount)); if (taxPercent != null) { - c.setTaxPercent(new BigDecimal(taxPercent)); + c.setTaxPercent(NodeMap.parse(taxPercent)); } zpp.addCharge(c); } @@ -1334,7 +1334,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx // in case someone writes 30.00 days (happens!) we still want 30 integer days } } else if (chargeChildName.equals("CalculationPercent")) { - cd.setPercent(new BigDecimal(XMLTools.trimOrNull(currentNode))); + cd.setPercent(NodeMap.parse(XMLTools.trimOrNull(currentNode))); } } //appliedAmount @@ -1378,7 +1378,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx if (daysFound&&percentFound) { cd.setDays(Integer.valueOf(days)); - cd.setPercent(new BigDecimal(percent)); + cd.setPercent(NodeMap.parse(percent)); zpp.addCashDiscount(cd); } //else : could not parse skonto diff --git a/library/src/main/java/org/mustangproject/util/NodeMap.java b/library/src/main/java/org/mustangproject/util/NodeMap.java index 77064d07..555c7e4f 100644 --- a/library/src/main/java/org/mustangproject/util/NodeMap.java +++ b/library/src/main/java/org/mustangproject/util/NodeMap.java @@ -106,14 +106,29 @@ public Optional getAsString(String... localNames) { * @return the text content of the matching node, converted to BigDecimal */ public Optional getAsBigDecimal(String... localNames) { - return getNode(localNames).map(Node::getTextContent).map(s->{ - try { - return new BigDecimal(s.trim()); - } catch (NumberFormatException e) { - return null; - } + return getNode(localNames).map(Node::getTextContent).map(NodeMap::parse); + } - }); + /** + * Leniently parses text into a {@link BigDecimal}. + *

+ * The value is trimmed first and {@code null} is returned - instead of throwing - + * when the input is {@code null}, blank or not a valid decimal. This is the single + * entry point all importer-side decimal parsing should use, so that one malformed + * field degrades to {@code null} rather than aborting the whole invoice import. + * + * @param value the raw text content to parse (may be {@code null}) + * @return the parsed {@code BigDecimal}, or {@code null} if it cannot be parsed + */ + public static BigDecimal parse(String value) { + if (value == null) { + return null; + } + try { + return new BigDecimal(value.trim()); + } catch (NumberFormatException e) { + return null; + } } /**