diff --git a/History.md b/History.md index 8571a94b..4bfc257a 100644 --- a/History.md +++ b/History.md @@ -38,6 +38,8 @@ switch - new tradeparty class, Contact getOwnContact superseded by TradeParty getSender - new invoicecorrection class - order-x xml read support +- support included notes on document and item level +- occurence periods setOccurrencePeriod(Date start, Date end) - automated tests zuv/verapdf validate created library test files - trans.getTradeSettlementPayment() removed in favor of trans.getTradeSettlement() - commandline option for no notices @@ -58,13 +60,7 @@ switch - *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 -- confirm correct generation from pdf/a3 files zf2edgetest -- visualization tests -- migration tests -- migration documentation -- *correctiontest exports zf1+zf2? -- *USt 0% wird aus invoice-klasse noch nicht sauber abgefangen -- finalize invoiceimporter +- finalize invoiceimporter Alpha3 2020-10-24 Alpha2 2020-09-15 Alpha1 2020-08-06 @@ -106,6 +102,10 @@ Validator 0.9.0 - prevent accidental CEN EN16931 application on extended profile - solved issues when XML structures start with comments instead of root node - added Symtrax signature +- paymenttermsdescription skonto xrechnung ? +- occurencedate vs deliverydate +- exception on invoice import if wrong + Mustangproject 1.7.6 ===== diff --git a/Release_Notes.md b/Release_Notes.md index d617726b..f8c53e82 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -3,11 +3,21 @@ On it's official website you can [download](https://www.mustangproject.org/files/Mustang-CLI-2.0.0-alpha3.jar) a alpha release of Mustang 2. -It integrates the successor of the ZUGFeRD [Validator ZUV](https://github.com/ZUGFeRD/ZUV/) in it's command line tool and can be used as library. +* Factur-X/ZUGFeRD 2 is now the default. In Mustangproject 1.x one had to select ZUGFeRD version 2 if that was desired, in Mustangproject 2 one now has to select ZUGFeRD version 1 if version 2 is not appropriate. +* Mustang is now available via Maven Central, which makes it even easier to use the Mustang library. Apart from making it easier in Maven because there is no longer the need to mention the custom repository, it also makes it possible to use Mustang in Gradle projects +* ZUGFeRD 2.1.1 is now supported with it's XRechnung "reference profile". The previous stable version, Mustang 1.7.8, only supported ZUGFeRD 2.1. +* There is now an experimental visualization feature and an improved experimental upgrade functionality from ZF1 XML to ZF2 XML. +* Mustang now supports percentual and absolute charges and allowances on both item and document level +* Mustang can now embed additional invoice-accompanying files like plans or worklists into the PDFs +* Additionally to the usual interface-based architecture there are now also classes for invoice, tradeparty etc., which makes it easier to develop in the Mustang library. Please feel free to have a look at it's tests on https://github.com/ZUGFeRD/mustangproject/blob/master/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java. +* Mustang now includes a validator, which can check the syntactical correctness of an e-invoice. +The validator component has previously been developed under the name ZUGFeRD and VeraPDF "ZUV". +It is now mature enought to be merged into the Mustang mainstream project. +ZUV already was the validator behind https://www.zugferd-community.net/de/open_community/validation with around 1,000 validations/month alone, +supports ZUGFeRD 1 and ZUGFeRD 2 and can be used via commandline. +Additionally, the Mustang validator now also supports XRechnung 2, can now also be used as library and supports validation of whole directory trees. -Additionally, the default changed from ZUGFeRD 1 to ZUGFeRD 2.1.1 (previously you had to enable that, now you have to specify that you want to use ZF1 if that's the case), it's now available via Maven Central and additionally to the old Interface-style pullProvider requiring you to implement methods there is now also a "normal", class-orientierted, halfway fluent "Push-Provider". It's describe below, if you are impatient please feel free to have a look at it's tests on https://github.com/ZUGFeRD/mustangproject/blob/master/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java. -This is a preview release, please do not (yet) use it in production. ### Use on command line `java -jar Mustang-CLI-2.0.0-alpha3.jar --action=combine` embedds a XML into a PDF (A-1) file and exports as PDF/A-3 @@ -68,10 +78,14 @@ as artifact ID. "validator" includes the library functionality but is >20 MB bigger due to it's dependencies. -### Update from 1.x to 2.0 +### Update from Mustang 1.x to 2.0 ZF2 was possible with Mustang 1 but it is default in Mustang 2, so you will need to `.setZUGFeRDVersion(1)` if you don't want ZUGFeRD 2 files. + +In the commandline, all actions will have to be invoked via --action=, so +--combine changes to --action=combine. + `PDFattachZugferdFile` is now called `setTransaction` and instead of a `ZUGFeRDExporterFromA1Factory` the `ZUGFeRDExporterFromA1` will now return a a class implementing `IZUGFeRDExporter` instead of a `ZUGFeRDExporter`. @@ -86,6 +100,10 @@ changes to ``` +Instead of `Profile.EXTENDED` use `Profiles.getByName("Extended")`. +If you want to use Profiles from older versions, please specify the version like + `Profiles.getByName("Extended")` for an Extended profile of ZF1. + The old Contact class has been corrected to TradeParty. The TradeParty class can now refer to a (human) Contact from the new Contact() class. diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 2be259e8..78b8b856 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -27,6 +27,10 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; +/*** + * An invoice, with fluent setters + * @see IExportableTransaction if you want to implement an interface instead + */ public class Invoice implements IExportableTransaction { protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null; @@ -34,6 +38,7 @@ public class Invoice implements IExportableTransaction { protected BigDecimal totalPrepaidAmount = null; protected TradeParty sender=null, recipient = null, deliveryAddress = null; protected ArrayList ZFItems = null; + protected ArrayList notes = null; protected String contractReferencedDocument = null; protected Date occurrenceDateFrom = null; @@ -266,6 +271,14 @@ public class Invoice implements IExportableTransaction { } + @Override + public String[] getNotes() { + if (notes==null) { + return null; + } + return notes.toArray(new String[0]); + } + @Override public String getCurrency() { return currency; @@ -492,4 +505,13 @@ public class Invoice implements IExportableTransaction { } } + + public Invoice addNote(String text) { + if (notes==null) { + notes=new ArrayList(); + } + notes.add(text); + return this; + } + } diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index e14c8d78..90f7bec9 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -10,6 +10,7 @@ public class Item implements IZUGFeRDExportableItem { protected BigDecimal price, quantity, tax, grossPrice, lineTotalAmount; protected String id; protected Product product; + protected ArrayList notes = null; protected ArrayList Allowances = new ArrayList(), Charges = new ArrayList(); @@ -98,6 +99,16 @@ public class Item implements IZUGFeRDExportableItem { return Charges.toArray(new IZUGFeRDAllowanceCharge[0]); } + + + @Override + public String[] getNotes() { + if (notes==null) { + return null; + } + return notes.toArray(new String[0]); + } + public Item setProduct(Product product) { this.product = product; return this; @@ -108,8 +119,19 @@ public class Item implements IZUGFeRDExportableItem { Charges.add(izac); return this; } + public Item addAllowance(IZUGFeRDAllowanceCharge izac) { Allowances.add(izac); return this; } + + public Item addNote(String text) { + if (notes==null) { + notes=new ArrayList(); + } + notes.add(text); + return this; + } + + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index 16ad9eb7..60a1e6c5 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -33,6 +33,11 @@ import java.util.Date; import org.mustangproject.FileAttachment; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; +/*** + * the interface of an transaction, e.g. an invoice, you want to create xml (potentially to be added to a PDF) + * for + * @see Invoice if you want to use an object rather than an interface + */ public interface IExportableTransaction { /** @@ -426,13 +431,26 @@ public interface IExportableTransaction { return null; } + /*** + * specify delivery date + * @return the delivery date + */ default Date getOccurrenceDate() { return null; } + /*** + * specify delivery period + * @return the beginning of the delivery period + */ default Date getOccurrencePeriodFrom() { return null; } + + /*** + * specify delivery period + * @return the end of the delivery period + */ default Date getOccurrencePeriodTo() { return null; } @@ -446,4 +464,13 @@ public interface IExportableTransaction { default FileAttachment[] getAdditionalReferencedDocuments() { return null; } + + + /*** + * additional text description + * @return an array of strings of document wide "includedNotes" (descriptive text values) + */ + default String[] getNotes() { + return null; + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 9f7cbcf7..0522b2c7 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -34,8 +34,16 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ IZUGFeRDExportableProduct getProduct(); + /** + * item level discounts + * @return array of the discounts on a single item + */ IZUGFeRDAllowanceCharge[] getItemAllowances(); + /** + * item level price additions + * @return array of the additional charges on the item + */ IZUGFeRDAllowanceCharge[] getItemCharges(); @@ -69,9 +77,21 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ default String getCategoryCode() { return TaxCategoryCodeTypeConstants.STANDARDRATE; } - + + /*** + * the ID of an additionally referenced document for this item + * @return the id as string + */ default String getAdditionalReferencedDocumentID() { return null; } + /*** + * descriptive texts + * @return an array of strings of item specific "includedNotes", text values + */ + default String[] getNotes() { + return null; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 909a619a..839c9b27 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -33,23 +33,86 @@ public class TransactionCalculator implements IAbsoluteValueProvider { return res; } - protected BigDecimal getCharges() { + /*** + * returns total of charges for this tax rate + * @param percent a specific rate, or null for any rate + * @return the total amount + */ + protected BigDecimal getChargesForPercent(BigDecimal percent) { 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)); + if ((percent==null)||(currentCharge.getTaxPercent().equals(percent))) { + res = res.add(currentCharge.getTotalAmount(this)); + } } } return res; } - protected BigDecimal getAllowances() { + /*** + * returns a (potentially concatenated) string of charge reasons, or "Charges" if none are defined + * @param percent a specific rate, or null for any rate + * @return the space separated String + */ + protected String getChargeReasonForPercent(BigDecimal percent) { + String res = " "; + IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges(); + if ((charges != null) && (charges.length > 0)) { + for (IZUGFeRDAllowanceCharge currentCharge : charges) { + if ((percent==null)||(currentCharge.getTaxPercent().equals(percent))) { + if (currentCharge.getReason()!=null) { + res = res+currentCharge.getReason()+" "; + } + } + } + } + res=res.substring(0,res.length()-1); + if (res.equals("")) { + res="Charges"; + } + return res; + } + + /*** + * returns a (potentially concatenated) string of allowance reasons, or "Allowances", if none are defined + * @param percent a specific rate, or null for any rate + * @return the space separated String + */ + protected String getAllowanceReasonForPercent(BigDecimal percent) { + String res = " "; + IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances(); + if ((allowances != null) && (allowances.length > 0)) { + for (IZUGFeRDAllowanceCharge currentAllowance : allowances) { + if ((percent==null)||(currentAllowance.getTaxPercent().equals(percent))) { + if (currentAllowance.getReason()!=null) { + res = res+currentAllowance.getReason()+" "; + } + } + } + } + res=res.substring(0,res.length()-1); + if (res.equals("")) { + res="Allowances"; + } + return res; + } + + + /*** + * returns total of allowances for this tax rate + * @param percent a specific rate, or null for any rate + * @return the total amount + */ + protected BigDecimal getAllowancesForPercent(BigDecimal percent) { 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)); + if ((percent==null)||(currentAllowance.getTaxPercent().equals(percent))) { + res = res.add(currentAllowance.getTotalAmount(this)); + } } } return res; @@ -65,7 +128,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } protected BigDecimal getTaxBasis() { - BigDecimal res = getTotal().add(getCharges()).subtract(getAllowances()); + BigDecimal res = getTotal().add(getChargesForPercent(null)).subtract(getAllowancesForPercent(null)); return res; } @@ -83,7 +146,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { BigDecimal percent = currentItem.getProduct().getVATPercent(); LineCalculator lc = new LineCalculator(currentItem); VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), - trans.getDocumentCode()); + currentItem.getCategoryCode()); VATAmount current = hm.get(percent); if (current == null) { hm.put(percent, itemVATAmount); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java b/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java index ff733e02..0537c868 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java @@ -15,7 +15,7 @@ import java.io.*; public class XMLUpgrader { static final ClassLoader CLASS_LOADER = XMLUpgrader.class.getClassLoader(); - private static final String RESOURCE_PATH = ""; //$NON-NLS-1$ + private static final String RESOURCE_PATH = ""; private TransformerFactory mFactory = null; private Templates mXsltTemplate = null; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java index 032b6bfc..9f954256 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java @@ -107,7 +107,6 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr this.calc=new TransactionCalculator(trans); boolean hasDueDate=false; - String taxCategoryCode=""; SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); String exemptionReason=""; @@ -182,51 +181,13 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } xml = xml + " \n"; - if (trans.getOwnForeignOrganisationID()!=null) { - xml = xml + " " + XMLTools.encodeXML(trans.getOwnForeignOrganisationID()) + "\n"; - } - - if ((trans.getSender()!=null)&&(trans.getSender().getGlobalID()!=null)&&(trans.getSender().getGlobalIDScheme()!=null)) { - xml = xml + " " - + XMLTools.encodeXML(trans.getSender().getGlobalID()) + "\n"; - } - xml = xml + " " + XMLTools.encodeXML(trans.getOwnOrganisationName()) + "\n"; //$NON-NLS-2$ - - if (trans.getSender().getContact() != null) { - xml = xml + "\n" + " " + XMLTools.encodeXML(trans.getSender().getContact().getName()) - + "\n"; - if (trans.getSender().getContact().getPhone() != null) { - - xml = xml + " \n" + " " - + XMLTools.encodeXML(trans.getSender().getContact().getPhone()) + "\n" - + " \n"; - } - if (trans.getSender().getContact().getEMail() != null) { - - xml = xml + " \n" + " " - + XMLTools.encodeXML(trans.getSender().getContact().getEMail()) + "\n" - + " \n"; - } - xml = xml + " "; - - } - xml = xml + " \n" + " " - + XMLTools.encodeXML(trans.getOwnZIP()) + "\n" + " " - + XMLTools.encodeXML(trans.getOwnStreet()) + "\n" + " " + XMLTools.encodeXML(trans.getOwnLocation()) - + "\n" + " " + XMLTools.encodeXML(trans.getOwnCountry()) - + "\n" + " \n" - + " \n" - + " " + XMLTools.encodeXML(trans.getOwnTaxID()) + "\n" //$NON-NLS-2$ - + " \n" - + " \n" - + " " + XMLTools.encodeXML(trans.getOwnVATID()) + "\n" //$NON-NLS-2$ - + " \n" - + " \n" + xml+= getTradePartyAsXML(trans.getSender(), true); + xml+= " \n" + " \n"; // + " GE2020211\n" // + " 4000001987658\n" - xml+= getTradePartyAsXML(trans.getRecipient()); + xml+= getTradePartyAsXML(trans.getRecipient(), false); if ((trans.getOwnVATID()!=null)&&(trans.getOwnOrganisationName()!=null)) { xml = xml + " \n" + " " + XMLTools.encodeXML(trans.getOwnVATID()) + "\n" @@ -245,7 +206,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " \n" ; if (this.trans.getDeliveryAddress()!=null) { xml += ""+ - getTradePartyAsXML(this.trans.getDeliveryAddress())+ + getTradePartyAsXML(this.trans.getDeliveryAddress(), false)+ ""; } @@ -299,7 +260,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " VAT\n" + exemptionReason + " " + currencyFormat(amount.getBasis()) + "\n" // currencyID=\"EUR\" - + " "+taxCategoryCode+"\n" + + " "+amount.getCategoryCode()+"\n" + " " + vatFormat(currentTaxPercent) + "\n" + " \n"; //$NON-NLS-2$ @@ -356,7 +317,6 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr int lineID = 0; for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { lineID++; - taxCategoryCode=currentItem.getProduct().getTaxCategoryCode(); if (currentItem.getProduct().getTaxExemptionReason() != null) { exemptionReason="" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } @@ -454,7 +414,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } } catch (UnsupportedEncodingException e) { Logger.getLogger(ZUGFeRD1PullProvider.class.getName()).log(Level.SEVERE, null, e); - } // $NON-NLS-1$ + } } @Override diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 9dc9b68d..4895122f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -39,7 +39,7 @@ import org.dom4j.io.XMLWriter; import org.mustangproject.FileAttachment; import org.mustangproject.XMLTools; -public class ZUGFeRD2PullProvider implements IXMLProvider { +public class ZUGFeRD2PullProvider implements IXMLProvider { //// MAIN CLASS protected SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); @@ -105,7 +105,14 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { return profile; } - protected String getTradePartyAsXML(IZUGFeRDExportableTradeParty party) { + /*** + * returns the UN/CEFACT CII XML for companies(tradeparties), which is actually + * the same for ZF1 (v 2013b) and ZF2 (v 2016b) + * @param party + * @param isSender some attributes are allowed only for senders in certain profiles + * @return + */ + protected String getTradePartyAsXML(IZUGFeRDExportableTradeParty party, boolean isSender) { String xml = ""; // According EN16931 either GlobalID or seller assigned ID might be present for BuyerTradeParty // and ShipToTradeParty, but not both. Prefer seller assigned ID for now. @@ -117,7 +124,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } xml += " " + XMLTools.encodeXML(party.getName()) + "\n"; //$NON-NLS-2$ - if (party.getContact() != null) { + if ((party.getContact() != null)&&(isSender||profile==Profiles.getByName("Extended"))) { xml = xml + "\n" + " " + XMLTools.encodeXML(party.getContact().getName()) + "\n"; if (party.getContact().getPhone() != null) { @@ -175,28 +182,42 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } + /*** + * returns the XML for a charge or allowance on item level + * @param allowance + * @param item + * @return + */ protected String getAllowanceChargeStr(IZUGFeRDAllowanceCharge allowance, IAbsoluteValueProvider item) { String percentage = ""; String chargeIndicator = "false"; - if (allowance.getPercent() != null) { - percentage = "" + allowance.getPercent() + ""; + if ((allowance.getPercent() != null)&&(profile==Profiles.getByName("Extended"))) { + percentage = "" + vatFormat(allowance.getPercent()) + ""; percentage += "" + item.getValue() + ""; } if (allowance.isCharge()) { chargeIndicator = "true"; } - String allowanceChargeStr = "" + chargeIndicator + "" + percentage + "" + priceFormat(allowance.getTotalAmount(item)) + ""; + String reason=""; + if ((allowance.getReason()!=null)&&(profile==Profiles.getByName("Extended"))) { + // only in extended profile + reason=""+XMLTools.encodeXML(allowance.getReason())+""; + } + String allowanceChargeStr = "" + + chargeIndicator + "" + percentage + + "" + priceFormat(allowance.getTotalAmount(item)) + "" + + reason+ + ""; return allowanceChargeStr; } @Override public void generateXML(IExportableTransaction trans) { this.trans = trans; - this.calc=new TransactionCalculator(trans); + this.calc = new TransactionCalculator(trans); boolean hasDueDate = false; - String taxCategoryCode = ""; SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); String exemptionReason = ""; @@ -236,6 +257,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (trans.getDocumentCode() != null) { typecode = trans.getDocumentCode(); } + String notes = ""; + if (trans.getNotes() != null) { + for (String currentNote : trans.getNotes()) { + notes = notes + "" + XMLTools.encodeXML(currentNote) + ""; + + } + } String xml = "\n" + "" + typecode + "\n" + " " + zugferdDateFormat.format(trans.getIssueDate()) + "\n" // date - // format - // was - // 20130605 + + notes + subjectNote + rebateAgreement + senderReg @@ -271,16 +297,23 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { int lineID = 0; for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { lineID++; - taxCategoryCode = currentItem.getProduct().getTaxCategoryCode(); if (currentItem.getProduct().getTaxExemptionReason() != null) { exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } + notes = ""; + if (currentItem.getNotes() != null) { + for (String currentNote : currentItem.getNotes()) { + notes = notes + "" + XMLTools.encodeXML(currentNote) + ""; + } + } LineCalculator lc = new LineCalculator(currentItem); xml = xml + " \n" + " \n" + " " + lineID + "\n" //$NON-NLS-2$ + + notes + " \n" + + " \n"; // + " 4012345001235\n" if (currentItem.getProduct().getSellerAssignedID() != null) { @@ -363,13 +396,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } xml = xml + " \n" - + getTradePartyAsXML(trans.getSender()) + + getTradePartyAsXML(trans.getSender(), true) + " \n" + " \n"; // + " GE2020211\n" // + " 4000001987658\n" - xml += getTradePartyAsXML(trans.getRecipient()); + xml += getTradePartyAsXML(trans.getRecipient(), false); xml += " \n"; if (trans.getBuyerOrderReferencedDocumentID() != null) { @@ -403,7 +436,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + " \n"; if (this.trans.getDeliveryAddress() != null) { xml += "" + - getTradePartyAsXML(this.trans.getDeliveryAddress()) + + getTradePartyAsXML(this.trans.getDeliveryAddress(), false) + ""; } @@ -457,7 +490,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + " VAT\n" + exemptionReason + " " + currencyFormat(amount.getBasis()) + "\n" // currencyID=\"EUR\" - + " " + taxCategoryCode + "\n" + + " " + amount.getCategoryCode() + "\n" + " " + vatFormat(currentTaxPercent) + "\n" + " \n"; //$NON-NLS-2$ @@ -477,36 +510,46 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) { - xml = xml + " \n" + - " \n" + - " true\n" + - " \n" + - " " + currencyFormat(calc.getCharges()) + "\n" + - " Charge\n" + - " \n" + - " VAT\n" + - " S\n" + - " 19.00\n" + - " \n" + - " \n"; + for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { + if (!calc.getChargesForPercent(currentTaxPercent).equals(new BigDecimal(0))) { + + + xml = xml + " \n" + + " \n" + + " true\n" + + " \n" + + " " + currencyFormat(calc.getChargesForPercent(currentTaxPercent)) + "\n" + + " "+XMLTools.encodeXML(calc.getChargeReasonForPercent(currentTaxPercent))+"\n" + + " \n" + + " VAT\n" + + " S\n" + + " " + vatFormat(currentTaxPercent) + "\n" + + " \n" + + " \n"; + + } + } } if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) { - xml = xml + " \n" + - " \n" + - " false\n" + - " \n" + - " " + currencyFormat(calc.getAllowances()) + "\n" + - " Allowance\n" + - " \n" + - " VAT\n" + - " S\n" + - " 19.00\n" + - " \n" + - " \n"; - + for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { + if (!calc.getAllowancesForPercent(currentTaxPercent).equals(new BigDecimal(0))) { + xml = xml + " \n" + + " \n" + + " false\n" + + " \n" + + " " + currencyFormat(calc.getAllowancesForPercent(currentTaxPercent)) + "\n" + + " "+XMLTools.encodeXML(calc.getAllowanceReasonForPercent(currentTaxPercent))+"\n" + + " \n" + + " VAT\n" + + " S\n" + + " " + vatFormat(currentTaxPercent) + "\n" + + " \n" + + " \n"; + } + } } @@ -534,9 +577,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } - String allowanceTotalLine = "" + currencyFormat(calc.getAllowances()) + ""; + String allowanceTotalLine = "" + currencyFormat(calc.getAllowancesForPercent(null)) + ""; - String chargesTotalLine = "" + currencyFormat(calc.getCharges()) + ""; + String chargesTotalLine = "" + currencyFormat(calc.getChargesForPercent(null)) + ""; xml = xml + " \n" + " " + currencyFormat(calc.getTotal()) + "\n" //$NON-NLS-2$ @@ -581,7 +624,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } } catch (UnsupportedEncodingException e) { Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e); - } // $NON-NLS-1$ + } } @Override diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index 810411e4..1bbd6607 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -490,7 +490,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte pdf.setProducer(fullProducer); if (ensurePDFisUpgraded) { try { - pdfaid.setConformance(conformanceLevel.getLetter());// $NON-NLS-1$ + pdfaid.setConformance(conformanceLevel.getLetter()); } catch (BadFieldValueException ex) { // This should be impossible, because it would occur only if an illegal // conformance level is supplied, diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/BackwardCompatibilityTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/BackwardCompatibilityTest.java index 6732c354..d07325a5 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/BackwardCompatibilityTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/BackwardCompatibilityTest.java @@ -56,7 +56,7 @@ public class BackwardCompatibilityTest extends TestCase implements IExportableTr try (InputStream SOURCE_PDF = this.getClass() .getResourceAsStream("/MustangGnuaccountingBeispielRE-20190610_507blanko.pdf"); - IZUGFeRDExporter ze = new ZUGFeRDExporterFromA1().setZUGFeRDVersion(1).setProfile("EN16931").load(SOURCE_PDF)) { + IZUGFeRDExporter ze = new ZUGFeRDExporterFromA1().setZUGFeRDVersion(1).setProfile(Profiles.getByName("Extended",1)).load(SOURCE_PDF)) { ze.setTransaction(this); ze.disableAutoClose(true); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 7b9e0f22..6ff582e6 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -44,6 +44,7 @@ public class ZF2PushTest extends TestCase { final String TARGET_CHARGESALLOWANCESPDF = "./target/testout-ZF2PushChargesAllowances.pdf"; final String TARGET_RELATIVECHARGESALLOWANCESPDF = "./target/testout-ZF2PushRelativeChargesAllowances.pdf"; final String TARGET_ATTACHMENTSPDF = "./target/testout-ZF2PushAttachments.pdf"; + final String TARGET_PUSHEDGE = "./target/testout-ZF2PushEdge.pdf"; public void testPushExport() { @@ -241,6 +242,48 @@ public class ZF2PushTest extends TestCase { } + public void testPushEdge() { + + String orgname = "Test company"; + String number = "123"; + String priceStr = "1.00"; + String taxID = "9990815"; + BigDecimal price = new BigDecimal(priceStr); + 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).ignorePDFAErrors() + .load(SOURCE_PDF)) { + + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + try { + ze.setTransaction(new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").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(16)), price, new BigDecimal(1.0)).addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16)))) + .addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16))) + .addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16))) + .setOccurrenceDate(sdf.parse("2020-01-01")) + .setDeliveryDate(sdf.parse("2020-02-02")) + ); + } catch (ParseException e) { + e.printStackTrace(); + } + + String theXML = new String(ze.getProvider().getXML()); + assertTrue(theXML.contains("