Merge pull request #639 from marzn/master

Fix invoice calculation if rounding amount is present
This commit is contained in:
Jochen Staerk
2025-01-02 13:48:40 +01:00
committed by GitHub
4 changed files with 345 additions and 11 deletions

View File

@@ -3,11 +3,14 @@ package org.mustangproject.Exceptions;
import java.text.ParseException; import java.text.ParseException;
/*** /***
* will be thrown if a invoice cant be reproduced numerically * will be thrown if an invoice cant be reproduced numerically
*/ */
public class ArithmetricException extends ParseException { public class ArithmetricException extends ParseException {
public ArithmetricException() { public ArithmetricException() {
super( this("");
"Could not reproduce the invoice, this could mean that it could not be read properly", 0); }
public ArithmetricException(String details) {
super("Could not reproduce the invoice. " + details, 0);
} }
} }

View File

@@ -32,6 +32,9 @@ import java.nio.file.StandardOpenOption;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ZUGFeRDInvoiceImporter { public class ZUGFeRDInvoiceImporter {
@@ -472,9 +475,11 @@ public class ZUGFeRDInvoiceImporter {
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"DuePayableAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"PayableAmount\"]"); xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"DuePayableAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"PayableAmount\"]");
NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
BigDecimal duePayableAmount = null;
if (lineDueNodes.getLength() > 0) { if (lineDueNodes.getLength() > 0) {
duePayableAmount = new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0)));
if (zpp instanceof CalculatedInvoice) { if (zpp instanceof CalculatedInvoice) {
((CalculatedInvoice) zpp).setDuePayable(new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0)))); ((CalculatedInvoice) zpp).setDuePayable(duePayableAmount);
} }
} }
@@ -938,8 +943,7 @@ public class ZUGFeRDInvoiceImporter {
TransactionCalculator tc = new TransactionCalculator(zpp); TransactionCalculator tc = new TransactionCalculator(zpp);
String expectedStringTotalGross = tc.getGrandTotal() String calculatedPayableTotal = tc.getDuePayable().toPlainString();
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
EStandard whichType; EStandard whichType;
try { try {
whichType = getStandard(); whichType = getStandard();
@@ -947,10 +951,21 @@ public class ZUGFeRDInvoiceImporter {
throw new StructureException("Could not find out if it's an invoice, order, or delivery advice", 0); throw new StructureException("Could not find out if it's an invoice, order, or delivery advice", 0);
} }
if ((whichType != EStandard.despatchadvice) if (whichType != EStandard.despatchadvice && !ignoreCalculationErrors) {
&& ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2))) // Check calculation if document type allows it and calculation errors should not be ignored
&& (!ignoreCalculationErrors))) {
throw new ArithmetricException(); String payableTotalFromXml = XMLTools.nDigitFormat(Objects.requireNonNullElse(duePayableAmount, expectedGrandTotal), 2);
if (!calculatedPayableTotal.equals(payableTotalFromXml)) {
String moreDetails = "";
try {
moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = "
+ Stream.of(tc.trans.getZFItems())
.map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString())
.collect(Collectors.joining(" + "));
} catch (Exception ignored) {
}
throw new ArithmetricException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
}
} }
} }
return zpp; return zpp;
@@ -1068,5 +1083,4 @@ public class ZUGFeRDInvoiceImporter {
LOGGER.error(e.getMessage(), e); LOGGER.error(e.getMessage(), e);
} }
} }
} }

View File

@@ -466,4 +466,12 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
assertFalse(invoice.getZFItems()[0].getNotes() == null); assertFalse(invoice.getZFItems()[0].getNotes() == null);
assertEquals(1, invoice.getZFItems()[0].getNotes().length); assertEquals(1, invoice.getZFItems()[0].getNotes().length);
} }
@Test
public void testImportXRechnungWithoutCalculationErrors() throws FileNotFoundException, XPathExpressionException, ParseException {
File inputFile = getResourceAsFile("cii/02.03a-INVOICE_uncefact.xml");
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(inputFile));
assertEquals("0", zii.importedInvoice.getDuePayable().toPlainString());
}
} }

File diff suppressed because one or more lines are too long