diff --git a/History.md b/History.md index 4bfc257a..3927a8af 100644 --- a/History.md +++ b/History.md @@ -54,13 +54,11 @@ switch ### 2.0 still todo - dont show empty tax number field -- new sample invoice -- *visualizer to work with Extended profile -- *visualizer tests +- fail when no bankverbindung? +- fail when xr attrs missing? +- build xr skonto??? - *validator not to XR error on ZF files (only notices) -- *from A3 does not seem tow ork see mustangreaderwriteredgetest:testedgeexport - xmp errors may not show correctly in log -- finalize invoiceimporter Alpha3 2020-10-24 Alpha2 2020-09-15 Alpha1 2020-08-06 diff --git a/Release_Notes.md b/Release_Notes.md index f8c53e82..705bba7c 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -145,7 +145,7 @@ but there is also the new invoiceImporter assertEquals("Spielkreis", invoice.getRecipient().getLocation()); TransactionCalculator tc=new TransactionCalculator(invoice); - assertEquals(new BigDecimal("571.040000"),tc.getTotalGross()); + assertEquals(new BigDecimal("571.04"),tc.getTotalGross()); ``` diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 839c9b27..084b0156 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -1,6 +1,7 @@ package org.mustangproject.ZUGFeRD; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.HashMap; /*** @@ -30,7 +31,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); res = res.add(amount.getCalculated()); } - return res; + return res.setScale(2, RoundingMode.HALF_UP); } /*** diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 042398f9..2285bd8a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -40,6 +40,13 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { "//*[local-name()=\"ExchangedDocument\"]"); NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + xpr = xpath.compile( + "//*[local-name()=\"GrandTotalAmount\"]"); + 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; @@ -192,6 +199,70 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { } + // 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).getNodeName().equals("ram:ChargeIndicator")) { + NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); + for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) { + if (indicatorChilds.item(indicatorChildIndex).getNodeName().equals("udt:Indicator")) { + isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent().equalsIgnoreCase("true"); + } + } + } else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:ActualAmount")) { + chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent(); + } else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:Reason")) { + reason = chargeNodeChilds.item(chargeChildIndex).getTextContent(); + } else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:CategoryTradeTax")) { + NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); + for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { + if (taxChilds.item(taxChildIndex).getNodeName().equals("ram: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); + } + + } + + TransactionCalculator tc = new TransactionCalculator(zpp); + String expectedStringTotalGross = tc.getTotalGross().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; } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 01adc049..a56f637b 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -41,11 +41,10 @@ import java.util.GregorianCalendar; * used for this import, testout-ZF2New.pdf */ public class ZF2ZInvoiceImporterTest extends TestCase { - final String TARGET_PDF = "./target/testout-ZF2new.pdf"; public void testInvoiceImport() { - ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF); + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2new.pdf"); boolean hasExceptions=false; Invoice invoice=null; @@ -82,7 +81,44 @@ public class ZF2ZInvoiceImporterTest extends TestCase { assertEquals("Stadthausen", invoice.getSender().getLocation()); TransactionCalculator tc=new TransactionCalculator(invoice); - assertEquals(new BigDecimal("571.040000"),tc.getTotalGross()); + assertEquals(new BigDecimal("571.04"),tc.getTotalGross()); + + + // name street location zip country, contact name phone email, total amount + + + + } + public void testItemAllowancesChargesImport() { + + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushItemChargesAllowances.pdf"); + + boolean hasExceptions=false; + Invoice invoice=null; + try { + invoice=zii.extractInvoice(); + } catch (XPathExpressionException | ParseException e) { + hasExceptions=true; + } + assertFalse(hasExceptions); + TransactionCalculator tc=new TransactionCalculator(invoice); + assertEquals(new BigDecimal("18.33"),tc.getTotalGross()); + } + + public void testAllowancesChargesImport() { + + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushChargesAllowances.pdf"); + + boolean hasExceptions=false; + Invoice invoice=null; + try { + invoice=zii.extractInvoice(); + } catch (XPathExpressionException | ParseException e) { + hasExceptions=true; + } + assertFalse(hasExceptions); + TransactionCalculator tc=new TransactionCalculator(invoice); + assertEquals(new BigDecimal("11.07"),tc.getTotalGross()); // name street location zip country, contact name phone email, total amount @@ -91,4 +127,5 @@ public class ZF2ZInvoiceImporterTest extends TestCase { } + }