diff --git a/History.md b/History.md index 1efe1d53..f2a968b9 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,17 @@ +- added JSONIgnore for Products intra community supply, reverse charge and invoice's isValid (which rather means isComplete, by the way) +- #917 +- #915 +- #921 +- #926 +- upgrade apache fop 2.10 to 2.11 +- #931 +- #932 +- #933, #413, #557, #765 +- make Line Calculation, e.g. total line net amount, accessible via JSON using getCalculation +- #940 +- #939 +- #692 + 2.19.0 ======= 2025-08-12 diff --git a/library/src/main/java/org/mustangproject/Allowance.java b/library/src/main/java/org/mustangproject/Allowance.java index cca2033a..ab8bc8e9 100644 --- a/library/src/main/java/org/mustangproject/Allowance.java +++ b/library/src/main/java/org/mustangproject/Allowance.java @@ -39,9 +39,8 @@ public class Allowance extends Charge { return totalAmount; } else if (percent!=null) { BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); -// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice); - return singlePriceDiff; + return singlePriceDiff.multiply(currentItem.getQuantity()); } else { throw new RuntimeException("percent must be set"); } diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java index 3d647757..524c8ce0 100644 --- a/library/src/main/java/org/mustangproject/Charge.java +++ b/library/src/main/java/org/mustangproject/Charge.java @@ -145,9 +145,10 @@ public class Charge implements IZUGFeRDAllowanceCharge { if(totalAmount != null) { return totalAmount; } else if (percent!=null) { - BigDecimal factor=getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP); - BigDecimal singlePrice=currentItem.getValue().multiply(factor); - return singlePrice; + BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP))); + BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice); + return singlePriceDiff.multiply(currentItem.getQuantity()); + } else { throw new RuntimeException("percent must be set"); } diff --git a/library/src/main/java/org/mustangproject/FileAttachment.java b/library/src/main/java/org/mustangproject/FileAttachment.java index 99a60419..791db6d4 100644 --- a/library/src/main/java/org/mustangproject/FileAttachment.java +++ b/library/src/main/java/org/mustangproject/FileAttachment.java @@ -10,7 +10,7 @@ public class FileAttachment { protected String filename; protected String mimetype; protected String relation = "Unspecified"; - protected String description; + protected String description = "Additional file attachment"; protected byte[] data; @@ -21,19 +21,25 @@ public class FileAttachment { } + public FileAttachment(String filename, String mimetype, String relation, byte[] data, String description) { + this.filename = filename; + this.mimetype = mimetype; + this.relation = relation; + this.data = data; + this.description = description; + } + public FileAttachment(String filename, String mimetype, String relation, byte[] data) { this.filename = filename; this.mimetype = mimetype; this.relation = relation; this.data = data; - this.description = "Additional file attachment"; } public FileAttachment(String filename, String mimetype, byte[] data) { this.filename = filename; this.mimetype = mimetype; this.data = data; - this.description = "Additional file attachment"; } public String getDescription() { diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 72d71a6c..d1a31537 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -23,6 +23,7 @@ package org.mustangproject; import java.math.BigDecimal; import java.util.*; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.*; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; @@ -393,24 +394,29 @@ public class Invoice implements IExportableTransaction { return this; } + + @JsonIgnore @Override public String getOwnStreet() { return sender.getStreet(); } + @JsonIgnore @Override public String getOwnZIP() { return sender.getZIP(); } + @JsonIgnore @Override public String getOwnLocation() { return sender.getLocation(); } + @JsonIgnore @Override public String getOwnCountry() { return sender.getCountry(); @@ -754,6 +760,7 @@ public class Invoice implements IExportableTransaction { * checks if all required items are set in order to be able to export it * @return true if all required items are set */ + @JsonIgnore public boolean isValid() { return (dueDate != null) && (sender != null) && (sender.getTaxID() != null) && (sender.getVATID() != null) && (recipient != null); //contact diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index b8afa6be..5a3853a0 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IReferencedDocument; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem; +import org.mustangproject.ZUGFeRD.LineCalculator; import org.mustangproject.util.NodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -203,9 +204,6 @@ public class Item implements IZUGFeRDExportableItem { } if (amountString != null) { izac.setTotalAmount(new BigDecimal(amountString)); - if (percentString != null && (!percentString.equals("0"))) { - izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity())); - } } if (basisAmountString != null) { izac.setBasisAmount(new BigDecimal(basisAmountString)); diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 467fbb60..6aca773a 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -214,11 +214,14 @@ public class Product implements IZUGFeRDExportableProduct { return this; } + @JsonIgnore @Override public boolean isReverseCharge() { return isReverseCharge; } + + @JsonIgnore @Override public boolean isIntraCommunitySupply() { return isIntraCommunitySupply; diff --git a/library/src/main/java/org/mustangproject/SubjectCode.java b/library/src/main/java/org/mustangproject/SubjectCode.java index 808c09d4..539314d1 100644 --- a/library/src/main/java/org/mustangproject/SubjectCode.java +++ b/library/src/main/java/org/mustangproject/SubjectCode.java @@ -40,5 +40,9 @@ public enum SubjectCode { /** * Vehicle licence number */ - ABZ + ABZ, + /** + * Payment information + */ + PMT } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java index 9bac1b07..7caa3e52 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java @@ -79,7 +79,7 @@ public class DAPullProvider extends ZUGFeRD2PullProvider { if (currentItem.getProduct().getTaxExemptionReason() != null) { // exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); xml += "" + "" + "" + lineID + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java index 3c4be8b5..cf3e50dc 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java @@ -24,4 +24,7 @@ public interface IAbsoluteValueProvider { public BigDecimal getValue(); + default BigDecimal getQuantity() { + return BigDecimal.ONE; + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index 9ce55ef1..c7a6d903 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.mustangproject.FileAttachment; import org.mustangproject.IncludedNote; import org.mustangproject.ReferencedDocument; @@ -181,6 +182,7 @@ public interface IExportableTransaction { * * @return Tax ID (not VAT ID) of the sender */ + @JsonIgnore default String getOwnTaxID() { if (getSender() != null) { return getSender().getTaxID(); @@ -194,6 +196,7 @@ public interface IExportableTransaction { * * @return VAT ID (Umsatzsteueridentifikationsnummer) of the sender */ + @JsonIgnore default String getOwnVATID() { if (getSender() != null) { return getSender().getVATID(); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 04cadc22..05d86170 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -205,4 +205,6 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ default String getAccountingReference() { return null; } + + default LineCalculator getCalculation() {return new LineCalculator(this); }; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java index 50ea54ae..33f883f4 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java @@ -24,26 +24,17 @@ public class LineCalculator { if (currentItem.getItemAllowances() != null) { for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { - BigDecimal factor=BigDecimal.ONE; BigDecimal singleAllowance=allowance.getTotalAmount(currentItem); addItemAllowance(singleAllowance); - - if ((allowance.getPercent()!=null)&&(allowance.getPercent().compareTo(BigDecimal.ZERO)!=0)) { - factor=currentItem.getQuantity(); - } - addAllowanceItemTotal(singleAllowance.multiply(factor)); + addAllowanceItemTotal(singleAllowance); } } if (currentItem.getItemCharges() != null) { for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) { - BigDecimal factor=BigDecimal.ONE; BigDecimal singleCharge=charge.getTotalAmount(currentItem); addItemCharge(singleCharge); - if ((charge.getPercent()!=null)&&(charge.getPercent().compareTo(BigDecimal.ZERO)!=0)) { - factor=currentItem.getQuantity(); - } - subtractAllowanceItemTotal(singleCharge.multiply(factor)); + subtractAllowanceItemTotal(singleCharge); } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java index f2a032f2..7659b526 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java @@ -107,7 +107,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider { // exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); xml += "" + "" + "" + lineID + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 02ae965d..f8c80127 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -195,7 +195,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { percent = currentItem.getProduct().getVATPercent(); } if (percent != null) { - LineCalculator lc = new LineCalculator(currentItem); + LineCalculator lc = currentItem.getCalculation(); VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode); String reasonText = currentItem.getProduct().getTaxExemptionReason(); @@ -265,7 +265,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } if (percent != null) { - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); final VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode, percent); final String reasonText = currentItem.getProduct().getTaxExemptionReason(); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java index 87e6f479..8c74e361 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java @@ -344,7 +344,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider { } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); xml += "" + "" + "" + lineID + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 87ffb274..c51cb60c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -415,7 +415,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (currentItem.getId() != null) { lineIDStr = currentItem.getId(); } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) { xml += "" + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index fce06f01..4442443a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -8,21 +8,8 @@ import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; -import org.mustangproject.Allowance; -import org.mustangproject.BankDetails; -import org.mustangproject.CalculatedInvoice; -import org.mustangproject.Charge; -import org.mustangproject.DirectDebit; -import org.mustangproject.EStandard; +import org.mustangproject.*; import org.mustangproject.Exceptions.StructureException; -import org.mustangproject.FileAttachment; -import org.mustangproject.IncludedNote; -import org.mustangproject.Invoice; -import org.mustangproject.Item; -import org.mustangproject.ReferencedDocument; -import org.mustangproject.SchemedID; -import org.mustangproject.TradeParty; -import org.mustangproject.XMLTools; import org.mustangproject.util.NodeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +48,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -606,10 +595,12 @@ public class ZUGFeRDInvoiceImporter { } zpp.addNotes(includedNotes); String rootNode = extractString("local-name(/*)"); + String potentialCashDiscountTerms=null; if (rootNode != null && Set.of("Invoice", "CreditNote").contains(rootNode)) { // UBL... // //*[local-name()="Invoice" or local-name()="CreditNote"] number = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"ID\"]").trim(); + potentialCashDiscountTerms = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"PaymentTerms\"]/*[local-name()=\"Note\"]").trim(); typeCode = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim(); String issueDateStr = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"IssueDate\"]").trim(); if (!issueDateStr.isEmpty()) { @@ -623,6 +614,10 @@ public class ZUGFeRDInvoiceImporter { if (!deliveryDt.isEmpty()) { deliveryDate = parseDate(deliveryDt, "yyyy-MM-dd"); } + } else { + //CII + potentialCashDiscountTerms = extractString("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"Description\"]").trim(); + } String creditorReferenceID = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"CreditorReferenceID\"]").trim();//BT-90 @@ -994,6 +989,12 @@ public class ZUGFeRDInvoiceImporter { NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); for (int i = 0; i < attachmentNodes.getLength(); i++) { FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getMimeDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i)))); + NodeList nl = attachmentNodes.item(i).getParentNode().getChildNodes(); + for (int j = 0; j < nl.getLength(); j++) { + if (nl.item(j).getLocalName() != null && nl.item(j).getLocalName().equals("Name")) { + fa.setDescription(nl.item(j).getTextContent()); + } + } zpp.embedFileInXML(fa); // filename = "Aufmass.png" mimeCode = "image/png" //EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png" @@ -1121,6 +1122,63 @@ public class ZUGFeRDInvoiceImporter { } } + xpr = xpath.compile("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"ApplicableTradePaymentDiscountTerms\"]");// cash discounts, UBL unknown + NodeList cashdiscountNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + for (int i = 0; i < cashdiscountNodes.getLength(); i++) { + NodeList cashDiscountNodeChilds = cashdiscountNodes.item(i).getChildNodes(); + String chargeAmount = null; + String taxPercent = null; + CashDiscount cd=new CashDiscount(); + for (int cashDiscountChildIndex = 0; cashDiscountChildIndex < cashDiscountNodeChilds.getLength(); cashDiscountChildIndex++) { + Node currentNode=cashDiscountNodeChilds.item(cashDiscountChildIndex); + String chargeChildName = currentNode.getLocalName(); + if (chargeChildName != null) { + if (chargeChildName.equals("BasisPeriodMeasure")) { + if (currentNode.getAttributes().getNamedItem("unitCode").getNodeValue().equals("DAY")) { + cd.setDays(Integer.valueOf(XMLTools.trimOrNull(currentNode))); + } + } else if (chargeChildName.equals("CalculationPercent")) { + cd.setPercent(new BigDecimal(XMLTools.trimOrNull(currentNode))); + } + } + //appliedAmount + //AppliedTradeTax + } + if ((cd.getPercent() != null)&&(cd.getDays() != null)) { + zpp.addCashDiscount(cd); + } + } + if ((potentialCashDiscountTerms!=null&&potentialCashDiscountTerms.length()>3)) { + for (String currentLine:potentialCashDiscountTerms.split("\\n")) { + if (currentLine.startsWith("#SKONTO#")) { + CashDiscount cd=new CashDiscount(); + Pattern pattern = Pattern.compile("#TAGE=(.*?)#", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(currentLine); + boolean daysFound = matcher.find(); + String days=matcher.group(1); + pattern = Pattern.compile("#PROZENT=(.*?)#", Pattern.CASE_INSENSITIVE); + matcher = pattern.matcher(currentLine); + boolean percentFound = matcher.find(); + String percent=matcher.group(1); + + if (daysFound&&percentFound) { + cd.setDays(Integer.valueOf(days)); + cd.setPercent(new BigDecimal(percent)); + zpp.addCashDiscount(cd); + } //else : could not parse skonto + + + + +/* + String percent=; + + cd.setDays() + cd.setPercent()*/ + + } + } + } TransactionCalculator tc = new TransactionCalculator(zpp); String calculatedPayableTotal = tc.getDuePayable().toPlainString(); @@ -1140,7 +1198,7 @@ public class ZUGFeRDInvoiceImporter { try { moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = " + Stream.of(tc.trans.getZFItems()) - .map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString()) + .map(item -> item.getCalculation().getItemTotalNetAmount().toPlainString()) .collect(Collectors.joining(" + ")); } catch (Exception ignored) { } diff --git a/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl b/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl index de3c25aa..5255ab52 100644 --- a/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl +++ b/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl @@ -194,7 +194,12 @@ - + + + + ; + + diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index f386d655..d7fd83af 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -5,12 +5,15 @@ import static java.math.BigDecimal.valueOf; import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.xmlunit.assertj.XmlAssert.assertThat; import org.junit.Test; import org.mustangproject.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xmlunit.builder.Input; +import javax.xml.transform.Source; import javax.xml.xpath.XPathExpressionException; import java.io.*; import java.math.BigDecimal; @@ -32,7 +35,7 @@ public class CalculationTest extends ResourceCase { .setQuantity(TEN) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(valueOf(100).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); assertEquals(valueOf(1000).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); @@ -51,7 +54,7 @@ public class CalculationTest extends ResourceCase { .setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance}) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); assertEquals(valueOf(1769.89).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); @@ -71,7 +74,7 @@ public class CalculationTest extends ResourceCase { .setItemCharges(new IZUGFeRDAllowanceCharge[]{charge}) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); assertEquals(valueOf(1799.63).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); @@ -110,13 +113,13 @@ public class CalculationTest extends ResourceCase { product.addAllowance(new Allowance(new BigDecimal(1))); item = new Item(product, new BigDecimal("9.50"), new BigDecimal(25)); item.addCharge(new Charge(new BigDecimal(10)).setReasonCode("ZZZ").setReason("Zuschlag")); - LineCalculator lc = new LineCalculator(item); + LineCalculator lc = item.getCalculation(); assertEquals(new BigDecimal("222.50"), lc.getItemTotalNetAmount()); invoice.addItem(item); product = new Product("Paper", "", "H87", new BigDecimal(25)); item = new Item(product, new BigDecimal("4.50"), new BigDecimal(15)); item.addAllowance(new Allowance().setPercent(new BigDecimal(5)).setReasonCode("ZZZ").setReason("Zuschlag")); - lc = new LineCalculator(item); + lc = item.getCalculation(); assertEquals(new BigDecimal("64.12"), lc.getItemTotalNetAmount()); invoice.addItem(item); invoice.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(new BigDecimal(25)).setReasonCode("ZZZ").setReason("Mengenrabatt")); @@ -162,6 +165,40 @@ public class CalculationTest extends ResourceCase { } +/* @Test + public void testRounding() { +/*** xml of official fx sample with allowances and charges + * 10x100 with 10% and 50€ item discount =850€ + * +8,75 charges on document level=858,75, +19%VAT=1021,91 + * prepaid 500->due payable=521,91 + * + File inputCII = getResourceAsFile("EN16931_1_Teilrechnung_corrected.xml"); + + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + Invoice invoice = null; + zii.doIgnoreCalculationErrors(); + boolean hasExceptions = false; + try { + zii.setInputStream(new FileInputStream(inputCII)); + + invoice = zii.extractInvoice(); + } catch (XPathExpressionException | ParseException e) { +// handle Exceptions + hasExceptions = true; + } catch (FileNotFoundException e) { + hasExceptions = true; + } + assertFalse(hasExceptions); + // Reading ZUGFeRD + + final TransactionCalculator calculator = new TransactionCalculator(invoice); + + assertEquals(valueOf(147.59).stripTrailingZeros(), calculator.getDuePayable().stripTrailingZeros()); + + + } +*/ + @Test public void testTotalCalculatorGrandTotalRounding() { SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd"); @@ -283,10 +320,76 @@ public class CalculationTest extends ResourceCase { item.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO)); invoice.addItem(item); + + ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); + zf2p.setProfile(Profiles.getByName("XRechnung")); + zf2p.generateXML(invoice); + + + String theXML = new String(zf2p.getXML()); + assertThat(theXML).valueByXPath("//*[local-name()='ActualAmount']") + .asString() + .isEqualTo("0.55");// test for issue #917 + + TransactionCalculator calculator = new TransactionCalculator(invoice); assertEquals(new BigDecimal("4.95"), calculator.getGrandTotal().stripTrailingZeros()); } + public void testSimpleItemPercentCharge() { + /*** + * a product with net 1.10 and qty 5 and relative item allowance of 10% should return 5 as line and grand total + */ + SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd"); + + Invoice invoice = new Invoice(); + invoice.setDocumentName("Rechnung"); + invoice.setNumber("777777"); + try { + invoice.setIssueDate(sqlDate.parse("2020-12-31")); + invoice.setDetailedDeliveryPeriod(sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[0]), sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[1])); + invoice.setDeliveryDate(sqlDate.parse("2020-12-31")); + invoice.setDueDate(sqlDate.parse("2021-01-15")); + } catch (Exception e) { + LOGGER.error("Failed to set dates", e); + + } + TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE"); + sender.addVATID("DE2222222222"); + invoice.setSender(sender); + + /* trade party (recipient) */ + TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE"); + recipient.setID("111111"); + recipient.addVATID("DE111111111"); + invoice.setRecipient(recipient); + + /* item */ + Product product; + Item item; + + product = new Product("AAA", "", "H87", BigDecimal.ZERO); + item = new Item(product, new BigDecimal("1.10"), new BigDecimal(5.00)); + + item.addCharge(new Charge().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO)); + invoice.addItem(item); + + + ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); + zf2p.setProfile(Profiles.getByName("XRechnung")); + zf2p.generateXML(invoice); + + + String theXML = new String(zf2p.getXML()); + assertThat(theXML).valueByXPath("//*[local-name()='ActualAmount']") + .asString() + .isEqualTo("0.55");// test for issue #917 + + + TransactionCalculator calculator = new TransactionCalculator(invoice); + assertEquals(new BigDecimal("6.05"), calculator.getGrandTotal().stripTrailingZeros()); + } + public void testSimpleDocumentPercentCharge() { String orgname = "Test company"; @@ -390,7 +493,7 @@ public class CalculationTest extends ResourceCase { .setQuantity(BigDecimal.valueOf(31)) .setBasisQuantity(BigDecimal.valueOf(366)) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(BigDecimal.valueOf(32.74), calculator.getItemTotalNetAmount()); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index 6f0a910c..8c8dbd7d 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -479,6 +479,20 @@ public class DeSerializationTest extends ResourceCase { assertEquals("sender@test.org", fromJSON.getSender().getEmail()); } + public void testItemAbsoluteChargeFromJSON() throws JsonProcessingException { + String globalID = "4000001123452"; + String globalIDScheme = "0088"; + + String json="{\"number\":\"471102\",\"currency\":\"EUR\",\"issueDate\":\"2018-03-04T00:00:00.000+01:00\",\"dueDate\":\"2018-03-04T00:00:00.000+01:00\",\"deliveryDate\":\"2018-03-04T00:00:00.000+01:00\",\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"globalID\":\"4000001123452\",\"globalIDScheme\":\"0088\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\"},\"zfitems\":[{\"price\":9.9,\"quantity\":20,\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"description\":\"\",\"vatpercent\":19,\"taxCategoryCode\":\"S\"},\"itemCharges\":[{\"totalAmount\":1,\"taxPercent\":19,\"reason\":\"Invoice line charge reason\",\"categoryCode\":\"S\"}]},{\"price\":5.5,\"quantity\":50,\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"description\":\"\",\"vatpercent\":7,\"taxCategoryCode\":\"S\"}}]}"; + + ObjectMapper mapper = new ObjectMapper(); + Invoice fromJSON = mapper.readValue(json, Invoice.class); + assertEquals(globalID, fromJSON.getSender().getGlobalID()); + assertEquals(globalIDScheme, fromJSON.getSender().getGlobalIDScheme()); + TransactionCalculator tc=new TransactionCalculator(fromJSON); + assertEquals(new BigDecimal("531.06"), tc.getDuePayable()); + } + public void testGrossFromJSON() throws JsonProcessingException { String json="{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \"Please remit until 28.07.2025\", \"issueDate\": 1753653600000, \"dueDate\": 1753653600000, \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"lineTotalAmount\": 29.00, \"duePayable\": 34.51, \"grandTotal\": 34.51, \"taxBasis\": 29.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 10.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"allowances\": [ { \"totalAmount\": 0.1000, \"categoryCode\": \"S\" } ], \"vatpercent\": 19.00, \"intraCommunitySupply\": false, \"reverseCharge\": false }, \"value\": 3.0000 } ], \"ownVATID\": \"DE0815\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\", \"ownCountry\": \"DE\", \"ownStreet\": \"teststr\"}"; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index 42fd653c..095bafa0 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -102,7 +102,7 @@ public class XRTest extends TestCase { BigDecimal amount = new BigDecimal(amountStr); byte[] b = {12, 13}; - FileAttachment fe1 = new FileAttachment("one.pdf", "application/pdf", "Alternative", b); + FileAttachment fe1 = new FileAttachment("one.pdf", "application/pdf", "Alternative", b,"Beschreibung"); Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").setEmail("sender@example.com").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890", "COBADEFXXX").setAccountName("kontoInhaber"))) .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setEmail("recipient@sample.org")) @@ -158,7 +158,7 @@ public class XRTest extends TestCase { assertEquals(attachedFiles.length, 1); assertTrue(Arrays.equals(attachedFiles[0].getData(), b)); - + assertEquals("Beschreibung",attachedFiles[0].getDescription()); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 47ac24cb..b99f7108 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -35,7 +35,6 @@ import java.io.InputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -235,9 +234,9 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { } - public void testSpecifiedLogisticsChargeImport() { + public void testSpecifiedLogisticsChargeCashDiscountImport() { ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); - File expectedResult = getResourceAsFile("cii/extended_warenrechnung.xml"); + File expectedResult = getResourceAsFile("cii/extended_warenrechnung_based_doublecashdiscount.xml"); boolean hasExceptions = false; @@ -249,9 +248,11 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { hasExceptions = true; } assertFalse(hasExceptions); + assertEquals(invoice.getCashDiscounts().length,2); TransactionCalculator tc = new TransactionCalculator(invoice); assertEquals(new BigDecimal("518.99"), tc.getGrandTotal()); + } public void testItemAllowancesChargesImport() { @@ -347,10 +348,10 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { TransactionCalculator tc = new TransactionCalculator(invoice); assertEquals(new BigDecimal("1.00"), tc.getGrandTotal()); - + assertEquals(invoice.getCashDiscounts().length,2); assertEquals(version,2); assertTrue(new BigDecimal("1").compareTo(invoice.getZFItems()[0].getQuantity()) == 0); - LineCalculator lc=new LineCalculator(invoice.getZFItems()[0]); + LineCalculator lc=invoice.getZFItems()[0].getCalculation(); assertTrue(new BigDecimal("1").compareTo(lc.getItemTotalNetAmount()) == 0); assertEquals("Z", invoice.getZFItems()[0].getProduct().getTaxCategoryCode()); @@ -407,7 +408,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { ObjectMapper mapper = new ObjectMapper(); String jsonArray = mapper.writeValueAsString(i); - JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520121600000,\"deliveryDate\":1520121600000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"valid\":false,\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"ownTaxID\":\"201/113/40209\",\"ownZIP\":\"80333\",\"ownCountry\":\"DE\",\"ownVATID\":\"DE123456789\",\"ownLocation\":\"München\",\"ownStreet\":\"Lieferantenstraße 20\"}",jsonArray,false); + JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520121600000,\"deliveryDate\":1520121600000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}]}",jsonArray,false); } catch (IOException e) { fail("IOException not expected"); @@ -439,7 +440,153 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { String expectedIssueDate= String.valueOf(morning.toInstant().getEpochSecond() *1000); String expectedPaymentTermDesciption="Please remit until "+german.format(now); - JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1.5, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true); + JSONAssert.assertEquals("{\n" + + " \"documentCode\" : \"380\",\n" + + " \"number\" : \"123\",\n" + + " \"currency\" : \"EUR\",\n" + + " \"paymentTermDescription\" : "+expectedPaymentTermDesciption+",\n" + + " \"issueDate\" : "+expectedIssueDate+",\n" + + " \"dueDate\" : "+expectedDueDate+",\n" + + " \"sender\" : {\n" + + " \"name\" : \"Test company\",\n" + + " \"zip\" : \"55232\",\n" + + " \"street\" : \"teststr\",\n" + + " \"location\" : \"teststadt\",\n" + + " \"country\" : \"DE\",\n" + + " \"taxID\" : \"4711\",\n" + + " \"vatID\" : \"DE0815\",\n" + + " \"vatid\" : \"DE0815\"\n" + + " },\n" + + " \"recipient\" : {\n" + + " \"name\" : \"Franz Müller\",\n" + + " \"zip\" : \"55232\",\n" + + " \"street\" : \"teststr.12\",\n" + + " \"location\" : \"Entenhausen\",\n" + + " \"country\" : \"DE\",\n" + + " \"contact\" : {\n" + + " \"name\" : \"contact testname\",\n" + + " \"phone\" : \"123456\",\n" + + " \"email\" : \"contact.testemail@example.org\",\n" + + " \"fax\" : \"0911623562\"\n" + + " }\n" + + " },\n" + + " \"totalPrepaidAmount\" : 0.0,\n" + + " \"zfitems\" : [ {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 1.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"1\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemAllowances\" : [ {\n" + + " \"totalAmount\" : 0.1,\n" + + " \"taxPercent\" : 0,\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 2.9,\n" + + " \"itemTotalVATAmount\" : 0.551,\n" + + " \"itemTotalGrossAmount\" : 2.9\n" + + " }\n" + + " }, {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 1.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"2\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemAllowances\" : [ {\n" + + " \"percent\" : 50.0,\n" + + " \"totalAmount\" : 1.5,\n" + + " \"basisAmount\" : 3.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"In love with salesperson\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 1.5,\n" + + " \"itemTotalVATAmount\" : 0.285,\n" + + " \"itemTotalGrossAmount\" : 1.5\n" + + " }\n" + + " }, {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 2.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"3\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemCharges\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"AnotherReason\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 7.0,\n" + + " \"itemTotalVATAmount\" : 1.33,\n" + + " \"itemTotalGrossAmount\" : 7.0\n" + + " }\n" + + " }, {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 1.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"4\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemAllowances\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"Something completely strange\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"itemCharges\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"Yet another reason\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 3.0,\n" + + " \"itemTotalVATAmount\" : 0.57,\n" + + " \"itemTotalGrossAmount\" : 3.0\n" + + " }\n" + + " } ],\n" + + " \"zfcharges\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 19.0,\n" + + " \"reason\" : \"AReason\",\n" + + " \"reasonCode\" : \"ABK\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ]\n" + + "}",jsonArray,true); } catch (IOException e) { fail("IOException not expected"); } catch (XPathExpressionException e) { diff --git a/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml b/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml new file mode 100644 index 00000000..8ffdde89 --- /dev/null +++ b/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml @@ -0,0 +1,379 @@ + + + + + + + + + + + urn:cen.eu:en16931:2017 + + + + 471102 + 380 + + 20180605 + + + Rechnung gemäß Bestellung Nr. 2018-471331 vom 01.03.2018. + + + Es bestehen Rabatt- und Bonusvereinbarungen. + AAK + + + Lieferant GmbH +Lieferantenstraße 20 +80333 München +Deutschland +Geschäftsführer: Hans Muster +Handelsregisternummer: H A 123 + + REG + + + + + + 1 + + Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. 2018-51112 in Rechnung zu stellen: + + + + 4012345001235 + KR3M + + Kunstrasen grün 3m breit + 300cm x 100 cm + + + + 4.0000 + + + false + + 0.6667 + + + + 3.3333 + + + + 3.0000 + + + + VAT + S + 19.00 + + + 10.00 + + + + + + 2 + + Bestellt wurden 5 kg Schweinesteak. Mit dieser Rechnung werden nur die bereits gelieferten Steaks berechnet. Die noch offenen 4 kg Schweinesteak werden separat geliefert und berechnet. + + + + 4000050986428 + SFK5 + + Schweinesteak + Schweinesteak aus Deutschland + + + + 5.5000 + + + 5.5000 + + + + 1.0000 + + + + VAT + S + 7.00 + + + 5.50 + + + + + + 3 + + + 4000001234561 + GTRWA5 + + Mineralwasser Medium +12 x 1,0l PET + + + + + 5.4900 + + + 5.4900 + + + + 20.0000 + + + + VAT + S + 7.00 + + + 109.80 + + + + + + 4 + + + 4000001234578 + PFA5 + + Pfand + + + + 2.7700 + + + 2.7700 + + + + 20.0000 + + + + VAT + S + 19.00 + + + 55.46 + + + + + + 549910 + 4000001123452 + Lieferant GmbH + + 80333 + Lieferantenstraße 20 + München + DE + + + 201/113/40209 + + + DE123456789 + + + + GE2020211 + Kunden AG Mitte + + 69876 + Kundenstraße 15 + Frankfurt + DE + + + + 2018-471331 + + + + + + 20180603 + + + + + EUR + + 7.91 + VAT + 113.03 + S + 7.00 + + + 12.25 + VAT + 64.46 + S + 19.00 + + + + false + + 10.00 + 1.00 + Sondernachlass + + VAT + S + 19.00 + + + + + false + + 115.30 + 8.07 + Sondernachlass + + VAT + S + 7.00 + + + + + true + + 115.30 + 5.80 + Versandkosten + + VAT + S + 7.00 + + + + Zahlbar innerhalb 30 Tagen netto bis 04.07.2018, 3% Skonto innerhalb 10 Tagen bis 15.06.2018 + + + 180.70 + 5.80 + 9.07 + 177.43 + 20.16 + 197.59 + 50.00 + 147.59 + + + + diff --git a/library/src/test/resources/cii/extended_warenrechnung.xml b/library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml similarity index 78% rename from library/src/test/resources/cii/extended_warenrechnung.xml rename to library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml index 46665e1a..b425194a 100644 --- a/library/src/test/resources/cii/extended_warenrechnung.xml +++ b/library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml @@ -1,90 +1,4 @@ - - - - - @@ -545,13 +459,20 @@ WEEE-Reg-Nr.: DE87654321 19.00 - - Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto. - - 14 - 2.00 - - + + Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto. + + 14 + 2.00 + + + + Bei Zahlung innerhalb 7 Tagen gewähren wir 1,0% Skonto. + + 7 + 1.00 + + 457.20 3.00 @@ -564,4 +485,4 @@ WEEE-Reg-Nr.: DE87654321 - \ No newline at end of file + diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java index 4c5edb88..ba939eaa 100644 --- a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java +++ b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java @@ -14,6 +14,7 @@ public class ValidationContext { private String profile = null; private String signature = null; private boolean isValid = true; + private boolean hasPDF = false; protected Logger logger; private String filename; @@ -22,6 +23,14 @@ public class ValidationContext { results = new Vector<>(); } + public void setHasPDF() { + hasPDF=true; + } + + public boolean hasPDF() { + return hasPDF; + } + public void addResultItem(ValidationResultItem vr) throws IrrecoverableValidationError { results.add(vr); diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 5e8ce312..8d827418 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -374,6 +374,14 @@ public class XMLValidator extends Validator { addUnsupportedProfileResultItem(); } } + } else { + // no CII -> has to be UBL + if (context.hasPDF()) { + final ValidationResultItem vri = new ValidationResultItem(ESeverity.error, "Factur-X/ZUGFeRD and Order-X are always strictly CII only, no UBL allowed.").setSection(17) + .setPart(EPart.fx); + context.addResultItem(vri); + + } } if (xsltFilename != null) { @@ -536,12 +544,15 @@ public class XMLValidator extends Validator { } ESeverity severity; + Node failNode = currentFailNode.getAttributes().getNamedItem("flag"); + String failVal = failNode == null ? null : failNode.getNodeValue(); if (defaultSeverity == ESeverity.notice) { severity = defaultSeverity; - } else if (currentFailNode.getAttributes().getNamedItem("flag") != null - && "warning".equals(currentFailNode.getAttributes().getNamedItem("flag").getNodeValue())) { + } else if ("warning".equals(failVal)) { // the XR issues warnings with flag=warning severity = ESeverity.warning; + } else if ("information".equals(failVal)) { + severity = ESeverity.notice; } else { severity = ESeverity.error; } diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index bb5e9576..e162edd9 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -121,6 +121,7 @@ public class ZUGFeRDValidator { // Avoid reading again from file pdfv.setFilenameAndContents(contextFilename, content); + context.setHasPDF(); optionsRecognized = true; finalStringResult.append(""); try { diff --git a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java index 4adb787b..5e28bdec 100644 --- a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java @@ -202,7 +202,7 @@ public class ZUGFeRDValidatorTest extends ResourceCase { assertThat(res).valueByXPath("count(//notice)") .asInt() - .isEqualTo(0); + .isEqualTo(1); assertThat(res).valueByXPath("/validation/summary/@status") .asString() .isEqualTo("valid");// expect to be valid because XR notices are, well, only notices @@ -216,14 +216,14 @@ public class ZUGFeRDValidatorTest extends ResourceCase { assertThat(res).valueByXPath("count(//error)") .asInt() - .isEqualTo(5); + .isEqualTo(4); assertThat(res).valueByXPath("count(//warning)") .asInt() .isEqualTo(1); assertThat(res).valueByXPath("count(//notice)") .asInt() - .isEqualTo(0); // 12 notices RE XRechnung 3.0 + .isEqualTo(1); // 12 notices RE XRechnung 3.0 assertThat(res).valueByXPath("/validation/summary/@status") .asString() .isEqualTo("invalid");// expect to be valid diff --git a/validator/src/test/resources/validXRV30.xml b/validator/src/test/resources/validXRV30.xml index adffb992..93caadfb 100644 --- a/validator/src/test/resources/validXRV30.xml +++ b/validator/src/test/resources/validXRV30.xml @@ -102,6 +102,32 @@ + + + Zeitlose Dienstleistung + + + Zeitlose Dienstleistung + + + + 0.00 + + + + 1 + + + + VAT + S + 7 + + + 0.00 + + + 04011000-12345-03