diff --git a/History.md b/History.md index 05ce9552..8571a94b 100644 --- a/History.md +++ b/History.md @@ -3,16 +3,17 @@ - support for ZF 2.1.1, i.e. "Reference profile" Xrechnung - ZF 2.1.1 now default (up to 1.7.8 ZF2 could be set but ZF1 was default) +- integrates validator - removed jaxb #87 - fluent setter api/pushprovider #40 -- new xrechnung profile -- visualization? zugferdvisualizer? +- new xrechnung reference profile +- visualization zugferdvisualizer - migration feature (XSLT upgrade of ZF1 to ZF2) has been improved and moved from the commandline into the library - german bank account numbers can no longer be specified (dropped in favor of IBAN and BIC) - factory for xrechnung #86 - moved to Maven central - complete or discard read into push provider -- integrates validator +- now TransactionCalculator is a dedicated class and no longer mixed up with pullprovider - validator now additionally supports xrechnung - modular project setup - getSellerTradePartyAddress (PR #157 ) thanks to aberndt-hub diff --git a/Release_Notes.md b/Release_Notes.md index b7e8ed83..d617726b 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -84,7 +84,10 @@ changes to ``` IZUGFeRDExporter ze = new ZUGFeRDExporterFromA1().setZUGFeRDVersion(1).setZUGFeRDConformanceLevel(ZUGFeRDConformanceLevel.EN16931).load(SOURCE_PDF)) { -``` +``` + +The old Contact class has been corrected to TradeParty. The TradeParty class +can now refer to a (human) Contact from the new Contact() class. The importer can still be used like @@ -92,7 +95,41 @@ The importer can still be used like ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream); String amount = zi.getAmount(); ``` -but we are also working on an importer to import into the new invoice class. +but there is also the new invoiceImporter +``` + + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF); + + Invoice invoice=null; + try { + invoice=zii.extractInvoice(); + } catch (XPathExpressionException | ParseException e) { +// handle Exceptions + } + assertFalse(hasExceptions); + // Reading ZUGFeRD + assertEquals("Bei Spiel GmbH", invoice.getOwnOrganisationName()); + assertEquals(3, invoice.getZFItems().length); + assertEquals("400.0000", invoice.getZFItems()[1].getQuantity().toString()); + + assertEquals("160.0000", invoice.getZFItems()[0].getPrice().toString()); + assertEquals("Heiße Luft pro Liter", invoice.getZFItems()[2].getProduct().getName()); + assertEquals("LTR", invoice.getZFItems()[2].getProduct().getUnit()); + assertEquals("7.00", invoice.getZFItems()[0].getProduct().getVATPercent().toString()); + assertEquals("RE-20170509/505", invoice.getNumber()); + + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + assertEquals("2017-05-09",sdf.format(invoice.getIssueDate())); + + assertEquals("Bahnstr. 42", invoice.getRecipient().getStreet()); + assertEquals("88802", invoice.getRecipient().getZIP()); + assertEquals("DE", invoice.getRecipient().getCountry()); + assertEquals("Spielkreis", invoice.getRecipient().getLocation()); + + TransactionCalculator tc=new TransactionCalculator(invoice); + assertEquals(new BigDecimal("571.040000"),tc.getTotalGross()); + +``` ### Using the library to create e-invoices From maven central fetch @@ -143,7 +180,33 @@ zf2p.generateXML(i); String theXML = new String(zf2p.getXML()); ``` or can also be used with setTransaction to generate invoice PDFs straight away. +### Embedding ZF1 to ZF2 migration +The functionality is based on the XSLT file in library/src/main/resources/stylesheets/ZF1ToZF2.xsl, +it can be accessed via +``` + XMLUpgrader zmi = new XMLUpgrader(); + String xml = zmi.migrateFromV1ToV2(xmlName); + Files.write(Paths.get(outName), xml.getBytes()); + +``` +### Embedding ZF2 visualization + +In case you don't want to access this functionality over the +commandline you can use +``` + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + xml = zvi.visualize(sourceName); + Files.write(Paths.get("factur-x.xml"), xml.getBytes()); +``` +for the visualizer. The output requires a CSS and a javascript file which are in the + jar's resources as + (/src/main/resources/)xrechnung-viewer.css + (/src/main/respurces/)xrechnung-viewer.js + + ### Embedding the validator +The validator library also contains the functionality to +read/write ZUGFeRD-invoices like the (smaller) library module. ``` diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalc.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java similarity index 89% rename from library/src/main/java/org/mustangproject/ZUGFeRD/LineCalc.java rename to library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java index 39511cb0..5912ff3f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalc.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java @@ -2,7 +2,11 @@ package org.mustangproject.ZUGFeRD; import java.math.BigDecimal; -public class LineCalc { +/*** + * the linecalculator does the math within an item line, and e.g. calculates quantity*price. + * @see TransactionCalculator + */ +public class LineCalculator { private BigDecimal totalGross; private BigDecimal price; private BigDecimal priceGross; @@ -11,7 +15,7 @@ public class LineCalc { private BigDecimal allowance = new BigDecimal(0); private BigDecimal charge = new BigDecimal(0); - public LineCalc(IZUGFeRDExportableItem currentItem) { + public LineCalculator(IZUGFeRDExportableItem currentItem) { if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) { for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java new file mode 100644 index 00000000..909a619a --- /dev/null +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -0,0 +1,134 @@ +package org.mustangproject.ZUGFeRD; + +import java.math.BigDecimal; +import java.util.HashMap; + +/*** + * The Transactioncalculator e.g. adds the line totals and applies VAT on whole invoices + * @see LineCalculator + */ +public class TransactionCalculator implements IAbsoluteValueProvider { + protected IExportableTransaction trans; + + public TransactionCalculator(IExportableTransaction trans) { + this.trans=trans; + } + + protected BigDecimal getTotalPrepaid() { + if (trans.getTotalPrepaidAmount() == null) { + return new BigDecimal(0); + } else { + return trans.getTotalPrepaidAmount(); + } + } + + protected BigDecimal getTotalGross() { + + BigDecimal res = getTaxBasis(); + HashMap VATPercentAmountMap = getVATPercentAmountMap(); + for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { + VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); + res = res.add(amount.getCalculated()); + } + return res; + } + + protected BigDecimal getCharges() { + BigDecimal res = new BigDecimal(0); + IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges(); + if ((charges != null) && (charges.length > 0)) { + for (IZUGFeRDAllowanceCharge currentCharge : charges) { + res = res.add(currentCharge.getTotalAmount(this)); + } + } + return res; + } + + protected BigDecimal getAllowances() { + BigDecimal res = new BigDecimal(0); + IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances(); + if ((allowances != null) && (allowances.length > 0)) { + for (IZUGFeRDAllowanceCharge currentAllowance : allowances) { + res = res.add(currentAllowance.getTotalAmount(this)); + } + } + return res; + } + + protected BigDecimal getTotal() { + BigDecimal res = new BigDecimal(0); + for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { + LineCalculator lc = new LineCalculator(currentItem); + res = res.add(lc.getItemTotalGrossAmount()); + } + return res; + } + + protected BigDecimal getTaxBasis() { + BigDecimal res = getTotal().add(getCharges()).subtract(getAllowances()); + return res; + } + + /** + * which taxes have been used with which amounts in this transaction, empty for + * no taxes, or e.g. 19:190 and 7:14 if 1000 Eur were applicable to 19% VAT + * (=190 EUR VAT) and 200 EUR were applicable to 7% (=14 EUR VAT) 190 Eur + * + * @return which taxes have been used with which amounts in this invoice + */ + protected HashMap getVATPercentAmountMap() { + HashMap hm = new HashMap<>(); + + for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { + BigDecimal percent = currentItem.getProduct().getVATPercent(); + LineCalculator lc = new LineCalculator(currentItem); + VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), + trans.getDocumentCode()); + VATAmount current = hm.get(percent); + if (current == null) { + hm.put(percent, itemVATAmount); + } else { + hm.put(percent, current.add(itemVATAmount)); + } + } + + + IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges(); + if ((charges != null) && (charges.length > 0)) { + for (IZUGFeRDAllowanceCharge currentCharge : charges) { + VATAmount theAmount = hm.get(currentCharge.getTaxPercent()); + if (theAmount == null) { + theAmount = new VATAmount(new BigDecimal(0), new BigDecimal(0), "S"); + } + theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this))); + BigDecimal factor = currentCharge.getTaxPercent().divide(new BigDecimal(100)); + theAmount.setCalculated(theAmount.getBasis().multiply(factor)); + hm.put(currentCharge.getTaxPercent(), theAmount); + } + } + IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances(); + if ((allowances != null) && (allowances.length > 0)) { + for (IZUGFeRDAllowanceCharge currentAllowance : allowances) { + VATAmount theAmount = hm.get(currentAllowance.getTaxPercent()); + if (theAmount == null) { + theAmount = new VATAmount(new BigDecimal(0), new BigDecimal(0), "S"); + } + theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this))); + BigDecimal factor = currentAllowance.getTaxPercent().divide(new BigDecimal(100)); + theAmount.setCalculated(theAmount.getBasis().multiply(factor)); + + hm.put(currentAllowance.getTaxPercent(), theAmount); + } + } + + + return hm; + } + + + @Override + public BigDecimal getValue() { + return getTotal(); + } + +} diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java index deca7fcf..032b6bfc 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java @@ -104,6 +104,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr @Override public void generateXML(IExportableTransaction trans) { this.trans = trans; + this.calc=new TransactionCalculator(trans); boolean hasDueDate=false; String taxCategoryCode=""; @@ -288,7 +289,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } } - HashMap VATPercentAmountMap = getVATPercentAmountMap(); + HashMap VATPercentAmountMap = calc.getVATPercentAmountMap(); for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); if (amount != null) { @@ -329,23 +330,23 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } xml = xml + " \n" - + " " + currencyFormat(getTotal()) + "\n" //$NON-NLS-2$ + + " " + currencyFormat(calc.getTotal()) + "\n" //$NON-NLS-2$ // currencyID=\"EUR\" + " 0.00\n" // currencyID=\"EUR\" + " 0.00\n" // // currencyID=\"EUR\" // + " 5.80\n" // + " 14.73\n" - + " " + currencyFormat(getTotal()) + "\n" //$NON-NLS-2$ + + " " + currencyFormat(calc.getTotal()) + "\n" //$NON-NLS-2$ // // // currencyID=\"EUR\" + " " - + currencyFormat(getTotalGross().subtract(getTotal())) + "\n" - + " " + currencyFormat(getTotalGross()) + "\n" //$NON-NLS-2$ + + currencyFormat(calc.getTotalGross().subtract(calc.getTotal())) + "\n" + + " " + currencyFormat(calc.getTotalGross()) + "\n" //$NON-NLS-2$ // // // currencyID=\"EUR\" - + " " + currencyFormat(getTotalPrepaid()) + "\n" - + " " + currencyFormat(getTotalGross().subtract(getTotalPrepaid())) + "\n" //$NON-NLS-2$ + + " " + currencyFormat(calc.getTotalPrepaid()) + "\n" + + " " + currencyFormat(calc.getTotalGross().subtract(calc.getTotalPrepaid())) + "\n" //$NON-NLS-2$ // // // currencyID=\"EUR\" + " \n" @@ -361,7 +362,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } - LineCalc lc = new LineCalc(currentItem); + LineCalculator lc = new LineCalculator(currentItem); xml = xml + " \n" + " \n" + " " + lineID + "\n" //$NON-NLS-2$ @@ -482,7 +483,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr if (discountTerms != null) { paymentTermsXml += ""; String currency = trans.getCurrency(); - String basisAmount = currencyFormat(getTotalGross()); + String basisAmount = currencyFormat(calc.getTotalGross()); paymentTermsXml += "" + basisAmount + ""; paymentTermsXml += "" + discountTerms.getCalculationPercentage().toString() + ""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 1e6f09c5..9dc9b68d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -29,23 +29,23 @@ import java.util.Date; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.Base64; -import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import org.mustangproject.FileAttachment; -import org.mustangproject.Invoice; import org.mustangproject.XMLTools; -public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvider { +public class ZUGFeRD2PullProvider implements IXMLProvider { //// MAIN CLASS protected SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); protected byte[] zugferdData; protected IExportableTransaction trans; + protected TransactionCalculator calc; private String paymentTermsDescription; protected Profile profile = Profiles.getByName("EN16931"); @@ -99,117 +99,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide } - //move - protected BigDecimal getTotalPrepaid() { - if (trans.getTotalPrepaidAmount() == null) { - return new BigDecimal(0); - } else { - return trans.getTotalPrepaidAmount(); - } - } - - protected BigDecimal getTotalGross() { - - BigDecimal res = getTaxBasis(); - HashMap VATPercentAmountMap = getVATPercentAmountMap(); - for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { - VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); - res = res.add(amount.getCalculated()); - } - return res; - } - - protected BigDecimal getCharges() { - BigDecimal res = new BigDecimal(0); - IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges(); - if ((charges != null) && (charges.length > 0)) { - for (IZUGFeRDAllowanceCharge currentCharge : charges) { - res = res.add(currentCharge.getTotalAmount(this)); - } - } - return res; - } - - protected BigDecimal getAllowances() { - BigDecimal res = new BigDecimal(0); - IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances(); - if ((allowances != null) && (allowances.length > 0)) { - for (IZUGFeRDAllowanceCharge currentAllowance : allowances) { - res = res.add(currentAllowance.getTotalAmount(this)); - } - } - return res; - } - - protected BigDecimal getTotal() { - BigDecimal res = new BigDecimal(0); - for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { - LineCalc lc = new LineCalc(currentItem); - res = res.add(lc.getItemTotalGrossAmount()); - } - return res; - } - - protected BigDecimal getTaxBasis() { - BigDecimal res = getTotal().add(getCharges()).subtract(getAllowances()); - return res; - } - - /** - * which taxes have been used with which amounts in this transaction, empty for - * no taxes, or e.g. 19:190 and 7:14 if 1000 Eur were applicable to 19% VAT - * (=190 EUR VAT) and 200 EUR were applicable to 7% (=14 EUR VAT) 190 Eur - * - * @return which taxes have been used with which amounts in this invoice - */ - protected HashMap getVATPercentAmountMap() { - HashMap hm = new HashMap<>(); - - for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { - BigDecimal percent = currentItem.getProduct().getVATPercent(); - LineCalc lc = new LineCalc(currentItem); - VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), - trans.getDocumentCode()); - VATAmount current = hm.get(percent); - if (current == null) { - hm.put(percent, itemVATAmount); - } else { - hm.put(percent, current.add(itemVATAmount)); - } - } - - - IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges(); - if ((charges != null) && (charges.length > 0)) { - for (IZUGFeRDAllowanceCharge currentCharge : charges) { - VATAmount theAmount = hm.get(currentCharge.getTaxPercent()); - if (theAmount == null) { - theAmount = new VATAmount(new BigDecimal(0), new BigDecimal(0), "S"); - } - theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this))); - BigDecimal factor = currentCharge.getTaxPercent().divide(new BigDecimal(100)); - theAmount.setCalculated(theAmount.getBasis().multiply(factor)); - hm.put(currentCharge.getTaxPercent(), theAmount); - } - } - IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances(); - if ((allowances != null) && (allowances.length > 0)) { - for (IZUGFeRDAllowanceCharge currentAllowance : allowances) { - VATAmount theAmount = hm.get(currentAllowance.getTaxPercent()); - if (theAmount == null) { - theAmount = new VATAmount(new BigDecimal(0), new BigDecimal(0), "S"); - } - theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this))); - BigDecimal factor = currentAllowance.getTaxPercent().divide(new BigDecimal(100)); - theAmount.setCalculated(theAmount.getBasis().multiply(factor)); - - hm.put(currentAllowance.getTaxPercent(), theAmount); - } - } - - - return hm; - } @Override public Profile getProfile() { @@ -304,6 +193,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide @Override public void generateXML(IExportableTransaction trans) { this.trans = trans; + this.calc=new TransactionCalculator(trans); boolean hasDueDate = false; String taxCategoryCode = ""; @@ -386,7 +276,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } - LineCalc lc = new LineCalc(currentItem); + LineCalculator lc = new LineCalculator(currentItem); xml = xml + " \n" + " \n" + " " + lineID + "\n" //$NON-NLS-2$ @@ -498,13 +388,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide // Additional Documents of XRechnung (Rechnungsbegruendende Unterlagen - BG-24 XRechnung) if (trans.getAdditionalReferencedDocuments() != null) { for (FileAttachment f : trans.getAdditionalReferencedDocuments()) { - // final String documentContent = Base64.encodeBase64String(f.getData()); + final String documentContent = new String(Base64.getEncoder().encodeToString(f.getData())); xml = xml + " \n" + " " + f.getFilename() + "\n" + " 916\n" + " " + f.getDescription() + "\n" + " " + documentContent + "\n" + + " filename=\"" + f.getFilename() + ">" + documentContent + "\n" + " \n"; } } @@ -557,7 +447,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide } } - HashMap VATPercentAmountMap = getVATPercentAmountMap(); + HashMap VATPercentAmountMap = calc.getVATPercentAmountMap(); for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); if (amount != null) { @@ -591,7 +481,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide " \n" + " true\n" + " \n" + - " " + currencyFormat(getCharges()) + "\n" + + " " + currencyFormat(calc.getCharges()) + "\n" + " Charge\n" + " \n" + " VAT\n" + @@ -608,7 +498,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide " \n" + " false\n" + " \n" + - " " + currencyFormat(getAllowances()) + "\n" + + " " + currencyFormat(calc.getAllowances()) + "\n" + " Allowance\n" + " \n" + " VAT\n" + @@ -644,24 +534,24 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide } - String allowanceTotalLine = "" + currencyFormat(getAllowances()) + ""; + String allowanceTotalLine = "" + currencyFormat(calc.getAllowances()) + ""; - String chargesTotalLine = "" + currencyFormat(getCharges()) + ""; + String chargesTotalLine = "" + currencyFormat(calc.getCharges()) + ""; xml = xml + " \n" - + " " + currencyFormat(getTotal()) + "\n" //$NON-NLS-2$ + + " " + currencyFormat(calc.getTotal()) + "\n" //$NON-NLS-2$ + chargesTotalLine + allowanceTotalLine - + " " + currencyFormat(getTaxBasis()) + "\n" //$NON-NLS-2$ + + " " + currencyFormat(calc.getTaxBasis()) + "\n" //$NON-NLS-2$ // // // currencyID=\"EUR\" + " " - + currencyFormat(getTotalGross().subtract(getTaxBasis())) + "\n" - + " " + currencyFormat(getTotalGross()) + "\n" //$NON-NLS-2$ + + currencyFormat(calc.getTotalGross().subtract(calc.getTaxBasis())) + "\n" + + " " + currencyFormat(calc.getTotalGross()) + "\n" //$NON-NLS-2$ // // // currencyID=\"EUR\" - + " " + currencyFormat(getTotalPrepaid()) + "\n" - + " " + currencyFormat(getTotalGross().subtract(getTotalPrepaid())) + "\n" //$NON-NLS-2$ + + " " + currencyFormat(calc.getTotalPrepaid()) + "\n" + + " " + currencyFormat(calc.getTotalGross().subtract(calc.getTotalPrepaid())) + "\n" //$NON-NLS-2$ // // // currencyID=\"EUR\" + " \n" @@ -720,7 +610,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide if (discountTerms != null) { paymentTermsXml += ""; String currency = trans.getCurrency(); - String basisAmount = currencyFormat(getTotalGross()); + String basisAmount = currencyFormat(calc.getTotalGross()); paymentTermsXml += "" + basisAmount + ""; paymentTermsXml += "" + discountTerms.getCalculationPercentage().toString() + ""; @@ -742,8 +632,4 @@ public class ZUGFeRD2PullProvider implements IXMLProvider, IAbsoluteValueProvide return paymentTermsXml; } - @Override - public BigDecimal getValue() { - return getTotal(); - } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 4ef4f238..042398f9 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -6,6 +6,8 @@ import org.w3c.dom.NodeList; import javax.xml.xpath.*; import java.math.BigDecimal; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Date; public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { @@ -13,78 +15,185 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { super(filename); } - public Invoice extractInvoice() { + /*** + * This will parse a XML into a invoice object + * @return the parsed invoice object + */ + public Invoice extractInvoice() throws XPathExpressionException, ParseException { - String number = "AB123"; - /** + String number = ""; + /* * 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(); Invoice zpp = null; - try { - XPathExpression xpr = xpath.compile( - "//SellerTradeParty"); - NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); - xpr = xpath.compile( - "//BuyerTradeParty"); - NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + XPathExpression xpr = xpath.compile( + "//*[local-name()=\"SellerTradeParty\"]"); + NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); - zpp= new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number); -//.addItem(new Item(new Product("Testprodukt","","C62",new BigDecimal(0)),amount,new BigDecimal(1.0))) - zpp.setOwnOrganisationName(extractString("//SellerTradeParty/Name")); + xpr = xpath.compile( + "//*[local-name()=\"BuyerTradeParty\"]"); + NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + xpr = xpath.compile( + "//*[local-name()=\"ExchangedDocument\"]"); + NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); - xpr = xpath.compile( - "//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]"); - NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); - if (nodes.getLength() == 0) { - } else { - for (int i = 0; i < nodes.getLength(); i++) { - //nodes.item(i).getTextContent())) { - Node currentItemNode = nodes.item(i); - NodeList itemChilds = currentItemNode.getChildNodes(); - String price = "0"; - String quantity = "0"; - for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) { - if (itemChilds.item(itemChildIndex).getNodeName().equals("ram:SpecifiedLineTradeAgreement")) { - NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes(); - for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds.getLength(); tradeLineChildIndex++) { - if (tradeLineChilds.item(tradeLineChildIndex).getNodeName().equals("ram:NetPriceProductTradePrice")) { - NodeList netChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes(); - for (int netIndex = 0; netIndex < netChilds.getLength(); netIndex++) { - if (netChilds.item(netIndex).getNodeName().equals("ram:ChargeAmount")) { - price = netChilds.item(netIndex).getTextContent();//ram:ChargeAmount + Date issueDate = null; + Date dueDate = null; + Date deliveryDate = 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++) { + if (exchangedDocumentChilds.item(documentChildIndex).getNodeName().equals("ram:ID")) { + number = exchangedDocumentChilds.item(documentChildIndex).getTextContent(); + } + if (exchangedDocumentChilds.item(documentChildIndex).getNodeName().equals("ram:IssueDateTime")) { + NodeList issueDateTimeChilds = exchangedDocumentChilds.item(documentChildIndex).getChildNodes(); + for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) { + if (issueDateTimeChilds.item(issueDateChildIndex).getNodeName().equals("udt:DateTimeString")) { + issueDate = new SimpleDateFormat("yyyyMMdd").parse(issueDateTimeChilds.item(issueDateChildIndex).getTextContent()); } - if (itemChilds.item(itemChildIndex).getNodeName().equals("ram:SpecifiedLineTradeDelivery")) { - NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes(); - for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds.getLength(); tradeLineChildIndex++) { - if (tradeLineChilds.item(tradeLineChildIndex).getNodeName().equals("ram:BilledQuantity")) { - quantity = tradeLineChilds.item(tradeLineChildIndex).getTextContent(); + } + } + } + } + + + 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).getNodeName().equals("ram:ActualDeliverySupplyChainEvent")) { + NodeList actualDeliveryChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes(); + for (int actualDeliveryChildIndex = 0; actualDeliveryChildIndex < actualDeliveryChilds.getLength(); actualDeliveryChildIndex++) { + if (actualDeliveryChilds.item(actualDeliveryChildIndex).getNodeName().equals("ram:OccurrenceDateTime")) { + NodeList occurenceChilds = actualDeliveryChilds.item(actualDeliveryChildIndex).getChildNodes(); + for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds.getLength(); occurenceChildIndex++) { + if (occurenceChilds.item(occurenceChildIndex).getNodeName().equals("udt:DateTimeString")) { + deliveryDate = new SimpleDateFormat("yyyyMMdd").parse(occurenceChilds.item(occurenceChildIndex).getTextContent()); + } } } } -// Logger.getLogger(ZUGFeRDInvoiceImporter.class.getName()).log(Level.INFO, "deb "+price); -// dummywerte sind derzeit noch produkname, beschreibung, unit, VAT percent, menge - zpp.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(0)), new BigDecimal(price), new BigDecimal(quantity))); } + } + } + + xpr = xpath.compile( + "//*[local-name()=\"ApplicableHeaderTradeSettlement\"]"); + NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + + 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).getNodeName().equals("ram:SpecifiedTradePaymentTerms")) { + NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes(); + for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) { + if (paymentTermChilds.item(paymentTermChildIndex).getNodeName().equals("ram:DueDateDateTime")) { + NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes(); + for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) { + if (dueDateChilds.item(dueDateChildIndex).getNodeName().equals("udt:DateTimeString")) { + dueDate = new SimpleDateFormat("yyyyMMdd").parse(dueDateChilds.item(dueDateChildIndex).getTextContent()); + + } + } + } + } + } + } + } + + zpp = new Invoice().setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number); +//.addItem(new Item(new Product("Testprodukt","","C62",new BigDecimal(0)),amount,new BigDecimal(1.0))) + zpp.setOwnOrganisationName(extractString("//SellerTradeParty/Name")); + xpr = xpath.compile( + "//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]"); + NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + + if (nodes.getLength() == 0) { + } else { + for (int i = 0; i < nodes.getLength(); i++) { + + String price = "0"; + String name = ""; + String description = ""; + String quantity = "0"; + String vatPercent = "0"; + String unitCode = "0"; + + //nodes.item(i).getTextContent())) { + Node currentItemNode = nodes.item(i); + NodeList itemChilds = currentItemNode.getChildNodes(); + for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) { + if (itemChilds.item(itemChildIndex).getNodeName().equals("ram:SpecifiedLineTradeAgreement")) { + NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes(); + for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds.getLength(); tradeLineChildIndex++) { + if (tradeLineChilds.item(tradeLineChildIndex).getNodeName().equals("ram:NetPriceProductTradePrice")) { + NodeList netChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes(); + for (int netIndex = 0; netIndex < netChilds.getLength(); netIndex++) { + if (netChilds.item(netIndex).getNodeName().equals("ram:ChargeAmount")) { + price = netChilds.item(netIndex).getTextContent();//ram:ChargeAmount + + } + } + } + } + } + if (itemChilds.item(itemChildIndex).getNodeName().equals("ram:SpecifiedLineTradeDelivery")) { + NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes(); + for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds.getLength(); tradeLineChildIndex++) { + if (tradeLineChilds.item(tradeLineChildIndex).getNodeName().equals("ram:BilledQuantity")) { + quantity = tradeLineChilds.item(tradeLineChildIndex).getTextContent(); + unitCode = tradeLineChilds.item(tradeLineChildIndex).getAttributes().getNamedItem("unitCode").getNodeValue(); + } + } + } + if (itemChilds.item(itemChildIndex).getNodeName().equals("ram:SpecifiedTradeProduct")) { + NodeList tradeProductChilds = itemChilds.item(itemChildIndex).getChildNodes(); + for (int tradeProductChildIndex = 0; tradeProductChildIndex < tradeProductChilds.getLength(); tradeProductChildIndex++) { + if (tradeProductChilds.item(tradeProductChildIndex).getNodeName().equals("ram:Name")) { + name = tradeProductChilds.item(tradeProductChildIndex).getTextContent(); + } + } + } + if (itemChilds.item(itemChildIndex).getNodeName().equals("ram:SpecifiedLineTradeSettlement")) { + NodeList tradeSettlementChilds = itemChilds.item(itemChildIndex).getChildNodes(); + for (int tradeSettlementChildIndex = 0; tradeSettlementChildIndex < tradeSettlementChilds.getLength(); tradeSettlementChildIndex++) { + if (tradeSettlementChilds.item(tradeSettlementChildIndex).getNodeName().equals("ram:ApplicableTradeTax")) { + NodeList taxChilds = tradeSettlementChilds.item(tradeSettlementChildIndex).getChildNodes(); + for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { + if (taxChilds.item(taxChildIndex).getNodeName().equals("ram:RateApplicablePercent")) { + vatPercent = taxChilds.item(taxChildIndex).getTextContent(); + } + } + } + } + } + + + } + zpp.addItem(new Item(new Product(name, description, unitCode, new BigDecimal(vatPercent)), new BigDecimal(price), new BigDecimal(quantity))); } - - } catch (XPathExpressionException e) { - e.printStackTrace(); } + return zpp; } - } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 9d599875..7b9e0f22 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -38,6 +38,7 @@ import junit.framework.TestCase; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class ZF2PushTest extends TestCase { final String TARGET_PDF = "./target/testout-ZF2Push.pdf"; + final String TARGET_ALLOWANCESPDF = "./target/testout-ZF2PushAllowances.pdf"; final String TARGET_CORRECTIONPDF = "./target/testout-ZF2PushCorrection.pdf"; final String TARGET_ITEMCHARGESALLOWANCESPDF = "./target/testout-ZF2PushItemChargesAllowances.pdf"; final String TARGET_CHARGESALLOWANCESPDF = "./target/testout-ZF2PushChargesAllowances.pdf"; @@ -49,9 +50,9 @@ public class ZF2PushTest extends TestCase { // the writing part String orgname = "Test company"; - String number = "123"; - String amountStr = "1.00"; - BigDecimal amount = new BigDecimal(amountStr); + String number = "RE-20170509/505"; + String priceStr = "1.00"; + BigDecimal price = new BigDecimal(priceStr); String occurenceFrom = "20201001"; String occurenceTo = "20201005"; String contractID = "376zreurzu0983"; @@ -63,7 +64,7 @@ public class ZF2PushTest extends TestCase { .load(SOURCE_PDF)) { try { - ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setContractReferencedDocument(contractID).setOccurrencePeriod(new SimpleDateFormat("yyyyMMdd").parse(occurenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurenceTo)).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setAdditionalAddress("Hinterhaus 3").setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)))); + ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setContractReferencedDocument(contractID).setOccurrencePeriod(new SimpleDateFormat("yyyyMMdd").parse(occurenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurenceTo)).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setAdditionalAddress("Hinterhaus 3").setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)))); } catch (ParseException ex) { throw new RuntimeException("Parse exception"); } @@ -102,9 +103,9 @@ public class ZF2PushTest extends TestCase { String orgname = "Test company"; String number = "123"; - String amountStr = "1.00"; + String priceStr = "1.00"; String taxID = "9990815"; - BigDecimal amount = new BigDecimal(amountStr); + BigDecimal price = new BigDecimal(priceStr); try (InputStream SOURCE_PDF = this.getClass() .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf"); @@ -115,7 +116,7 @@ public class ZF2PushTest extends TestCase { byte[] b = {12, 13}; ze.attachFile("one.pdf", b, "Application/PDF", "Alternative"); ze.attachFile("two.pdf", b, "Application/PDF", "Alternative"); - ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID)).setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0))) + ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID)).setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))) ); String theXML = new String(ze.getProvider().getXML()); @@ -238,14 +239,56 @@ public class ZF2PushTest extends TestCase { } + } + + public void testAllowancesExport() { + + String orgname = "Test company"; + String number = "123"; + BigDecimal qty = new BigDecimal("20"); + try (InputStream SOURCE_PDF = this.getClass() + .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf"); + + ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1().setProducer("My Application") + .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("en16931")).ignorePDFAErrors() + .load(SOURCE_PDF)) { + + ze.setTransaction(new Invoice().setDueDate(new Date()).setOccurrenceDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(500.0), qty).addAllowance(new Allowance(new BigDecimal(300)).setTaxPercent(new BigDecimal(19)))) + .addAllowance(new Allowance(new BigDecimal(600)).setTaxPercent(new BigDecimal(19))) + + ); + String theXML = new String(ze.getProvider().getXML()); + assertTrue(theXML.contains("