/** ********************************************************************** * * Copyright 2018 Jochen Staerk * * Use is subject to license terms. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. * *********************************************************************** */ package org.mustangproject.ZUGFeRD; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.HashMap; public class ZUGFeRD2PullProvider implements IXMLProvider { private class LineCalc { private BigDecimal totalGross; private BigDecimal itemTotalNetAmount; private BigDecimal itemTotalVATAmount; public LineCalc(IZUGFeRDExportableItem currentItem) { BigDecimal multiplicator = currentItem.getProduct().getVATPercent().divide(new BigDecimal(100)).add(new BigDecimal(1)); // priceGross=currentItem.getPrice().multiply(multiplicator); totalGross = currentItem.getPrice().multiply(multiplicator).multiply(currentItem.getQuantity()); itemTotalNetAmount = currentItem.getQuantity().multiply(currentItem.getPrice()).setScale(2, BigDecimal.ROUND_HALF_UP); itemTotalVATAmount = totalGross.subtract(itemTotalNetAmount); } public BigDecimal getItemTotalNetAmount() { return itemTotalNetAmount; } public BigDecimal getItemTotalVATAmount() { return itemTotalVATAmount; } } //// MAIN CLASS protected byte[] zugferdData; private IZUGFeRDExportableTransaction trans; /** * enables the flag to indicate a test invoice in the XML structure */ public void setTest() {} private String nDigitFormat(BigDecimal value, int scale) { /* * I needed 123,45, locale independent.I tried * NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is * locale specific.I also tried DecimalFormat df = new DecimalFormat( * "0,00" ); df.setDecimalSeparatorAlwaysShown(true); * df.setGroupingUsed(false); DecimalFormatSymbols symbols = new * DecimalFormatSymbols(); symbols.setDecimalSeparator(','); * symbols.setGroupingSeparator(' '); * df.setDecimalFormatSymbols(symbols); * * but that would not switch off grouping. Although I liked very much * the (incomplete) "BNF diagram" in * http://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html * in the end I decided to calculate myself and take eur+sparator+cents * * This function will cut off, i.e. floor() subcent values Tests: * System.err.println(utils.currencyFormat(new BigDecimal(0), * ".")+"\n"+utils.currencyFormat(new BigDecimal("-1.10"), * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.1"), * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.01"), * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3489"), * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3419"), * ",")+"\n"+utils.currencyFormat(new BigDecimal("12"), ",")); * * results 0.00 -1,10 -1,10 -1,01 20000123,34 20000123,34 12,00 */ value = value.setScale(scale, BigDecimal.ROUND_HALF_UP); // first, round so that e.g. 1.189999999999999946709294817992486059665679931640625 becomes 1.19 char[] repeat = new char[scale]; Arrays.fill(repeat, '0'); DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(); otherSymbols.setDecimalSeparator('.'); DecimalFormat dec = new DecimalFormat("0." + new String(repeat), otherSymbols); return dec.format(value); } private String vatFormat(BigDecimal value) { return nDigitFormat(value, 2); } private String currencyFormat(BigDecimal value) { return nDigitFormat(value, 2); } private String priceFormat(BigDecimal value) { return nDigitFormat(value, 4); } private String quantityFormat(BigDecimal value) { return nDigitFormat(value, 4); } @Override public byte[] getXML() { return zugferdData; } private BigDecimal getTotalGross() { BigDecimal res = getTotal(); HashMap VATPercentAmountMap = getVATPercentAmountMap(); for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); res = res.add(amount.getCalculated()); } return res; } private BigDecimal getTotal() { BigDecimal res = new BigDecimal(0); for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { LineCalc lc = new LineCalc(currentItem); res = res.add(lc.getItemTotalNetAmount()); } 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 */ private 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)); } } return hm; } @Override public void generateXML(IZUGFeRDExportableTransaction trans) { this.trans = trans; SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); //$NON-NLS-1$ SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$ String senderReg = ""; if (trans.getOwnOrganisationFullPlaintextInfo() != null) { senderReg = "" + "\n" + " \n" + trans.getOwnOrganisationFullPlaintextInfo() + " \n" + "REG\n" + "\n"; } String xml = "\n" //$NON-NLS-1$ + "\n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ // + " "+testBooleanStr+"\n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:en16931\n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + trans.getNumber() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // + " RECHNUNG\n" //$NON-NLS-1$ + " 380\n" //$NON-NLS-1$ + " " + zugferdDateFormat.format(trans.getIssueDate()) + "\n" //date format was 20130605 //$NON-NLS-1$ //$NON-NLS-2$ + senderReg // + " \n" // + " \n" // + "Rechnung gemäß Bestellung Nr. 2013-471331 vom 01.03.2013.\n" // + "\n" // + " \n" // + " \n" // + " \n" // + " \n" // + "Es bestehen Rabatt- und Bonusvereinbarungen.\n" // + " \n" // + " AAK\n" // + " \n" + " \n" //$NON-NLS-1$ + " \n"; //$NON-NLS-1$ int lineID = 0; for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { lineID++; LineCalc lc = new LineCalc(currentItem); xml = xml + " \n" + //$NON-NLS-1$ " \n" //$NON-NLS-1$ + " " + lineID + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ // + " 4012345001235\n" // + " KR3M\n" // + " 55T01\n" + " " + currentItem.getProduct().getName() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " " + currentItem.getProduct().getDescription() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + priceFormat(currentItem.getPrice()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ //currencyID=\"EUR\" + " 1.0000\n" //$NON-NLS-1$ //$NON-NLS-2$ // + " \n" // + " false\n" // + " 0.6667\n" // + " Rabatt\n" // + " \n" + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + priceFormat(currentItem.getPrice()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // currencyID=\"EUR\" + " 1.0000\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + quantityFormat(currentItem.getQuantity()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " VAT\n" //$NON-NLS-1$ + " S\n" //$NON-NLS-1$ + " " + vatFormat(currentItem.getProduct().getVATPercent()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + currencyFormat(lc.getItemTotalNetAmount()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // currencyID=\"EUR\" + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " \n"; //$NON-NLS-1$ } xml = xml + " \n" //$NON-NLS-1$ // + " AB-312\n" + " \n" //$NON-NLS-1$ // + " 4000001123452\n" + " " + trans.getOwnOrganisationName() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" + " " + trans.getOwnZIP() + "\n" + " " + trans.getOwnStreet() + "\n" + " " + trans.getOwnLocation() + "\n" + " " + trans.getOwnCountry() + "\n" + " \n" + " \n" //$NON-NLS-1$ + " " + trans.getOwnTaxID() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + trans.getOwnVATID() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ // + " GE2020211\n" // + " 4000001987658\n" + " " + trans.getRecipient().getName() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // + " \n" // + " xxx\n" // + " \n" + " \n" //$NON-NLS-1$ + " " + trans.getRecipient().getZIP() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " " + trans.getRecipient().getStreet() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " " + trans.getRecipient().getLocation() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " " + trans.getRecipient().getCountry() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + trans.getRecipient().getVATID() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ // + " \n" // + " 20130301\n" // + " 2013-471331\n" // + " \n" + " \n" //$NON-NLS-1$ + " \n" + " \n" + " " + zugferdDateFormat.format(trans.getDeliveryDate()) + "\n" + " \n" /* + " \n" + " 20130603\n" + " 2013-51112\n" + " \n" */ + " \n" + " \n" //$NON-NLS-1$ + " " + trans.getNumber() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " EUR\n"; //$NON-NLS-1$ for(IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) { xml += " \n" //$NON-NLS-1$ + " 42\n" //$NON-NLS-1$ + " Überweisung\n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + payment.getOwnIBAN() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " " + payment.getOwnKto() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + payment.getOwnBIC() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " " + payment.getOwnBLZ() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // + " "+trans.getOwnBankName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n"; //$NON-NLS-1$ } HashMap VATPercentAmountMap = getVATPercentAmountMap(); for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); if (amount != null) { xml += " \n" //$NON-NLS-1$ + " " + currencyFormat(amount.getCalculated()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ //currencyID=\"EUR\" + " VAT\n" //$NON-NLS-1$ + " " + currencyFormat(amount.getBasis()) + "\n" // currencyID=\"EUR\" + " S\n" //$NON-NLS-1$ + " " + vatFormat(currentTaxPercent) + "\n" //$NON-NLS-1$ + " \n"; //$NON-NLS-1$ } } /* xml+= " + " \n" + " false\n" + " 10\n" + " 1.00\n" + " Sondernachlass\n" + " \n" + " VAT\n" + " S\n" + " 19\n" + " \n" + " \n" + " \n" + " false\n" + " 137.30\n" + " 13.73\n" + " Sondernachlass\n" + " \n" + " VAT\n" + " S\n" + " 7\n" + " \n" + " \n" + " \n" + " Versandkosten\n" + " 5.80\n" + " \n" + " VAT\n" + " S\n" + " 7\n" + " \n" + " \n"*/ xml = xml + " \n" //$NON-NLS-1$ + " Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate()) + "\n" + " " + zugferdDateFormat.format(trans.getDueDate()) + "\n"//20130704 //$NON-NLS-1$ //$NON-NLS-2$ + " \n" //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + " " + currencyFormat(getTotal()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ currencyID=\"EUR\" + " 0.00\n" //$NON-NLS-1$ currencyID=\"EUR\" + " 0.00\n" //$NON-NLS-1$ // currencyID=\"EUR\" // + " 5.80\n" // + " 14.73\n" + " " + currencyFormat(getTotal()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // currencyID=\"EUR\" + " " + currencyFormat(getTotalGross().subtract(getTotal())) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + " " + currencyFormat(getTotalGross()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // currencyID=\"EUR\" // + " 0.00\n" + " " + currencyFormat(getTotalGross()) + "\n" //$NON-NLS-1$ //$NON-NLS-2$ // currencyID=\"EUR\" + " \n" //$NON-NLS-1$ + " \n"; //$NON-NLS-1$ // + " \n" // + " \n" // + " \n" // + " Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. 2013-51112 in Rechnung zu stellen:\n" // + " \n" // + " \n" // + " \n"; xml = xml + " \n" //$NON-NLS-1$ + ""; //$NON-NLS-1$ byte[] zugferdRaw; try { zugferdRaw = xml.getBytes("UTF-8"); if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) { // I don't like BOMs, lets remove it zugferdData = new byte[zugferdRaw.length - 3]; System.arraycopy(zugferdRaw, 3, zugferdData, 0, zugferdRaw.length - 3); } else { zugferdData = zugferdRaw; } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } //$NON-NLS-1$ } }