Merge remote-tracking branch 'remotes/origin/master' into master

This commit is contained in:
Sebastian Sieber
2020-11-20 11:45:38 +01:00
6 changed files with 72 additions and 15 deletions

View File

@@ -1,6 +1,5 @@
### 2.0.1 todo
- confirm that VAT category code switches from S to Z on 0%VAT
- 2.1 support kleinunternehmer, reverse charge?
- dont show empty tax number field
- fail when no bankverbindung?
@@ -8,8 +7,14 @@
- build xr skonto???
- *validator not to XR error on ZF files (only notices)
- xmp errors may not show correctly in log
### 2.0.1 done
- do not list tax numbers for shiptotradeparties
- do not expect dueDate for corrected invoices
- XR test now includes guideline ID #172
- support zero-rated goods
- BigDecimal specific refactoring PR #192 Thanks to weclapp-dev
- Preserving metadata PR #193 Thanks to mr-stephan
- support zero-rated goods: confirm that VAT category code switches from S to Z on 0%VAT
2.0.0
=====
2020-11-12

View File

@@ -1,21 +1,45 @@
package org.mustangproject.ZUGFeRD;
/***
* specifies a kind of a XML completeness levels,
* e.g. Factur-X has 6 (Minimum, Basic-WL, Basic, EN16931, Extended, and XRechnung),
* ZUGFeRD 1 had and Order-X will have three (Basic, Comfort, Extended)
* and the XRechnung has two (Standard, Extension)
* For the XRechnung at the time being please use Factur-X's Xrechnung
*/
public class Profile {
protected String name, id;
/***
* Contruct
* @param name human readable name of the profile, also used as basis to detemine the XMP Name
* @param ID XML Guideline ID
*/
public Profile(String name, String ID) {
this.name = name;
this.id = ID;
}
/***
* gets the name
* @return the name of the profile
*/
public String getName() {
return name;
}
/***
* get guideline id
* @return the XML Guideline ID of the profile
*/
public String getID() {
return id;
}
/***
* if the profile is embedded in PDF we need RDF metadata
* @return the XMP name string of the profile
*/
public String getXMPName() {
if (name.equals("BASICWL")) {
return "BASIC WL";

View File

@@ -184,13 +184,13 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
}
xml = xml + " <ram:SellerTradeParty>\n";
xml += getTradePartyAsXML(trans.getSender(), true);
xml += getTradePartyAsXML(trans.getSender(), true, false);
xml += " </ram:SellerTradeParty>\n"
+ " <ram:BuyerTradeParty>\n";
// + " <ID>GE2020211</ID>\n"
// + " <GlobalID schemeID=\"0088\">4000001987658</GlobalID>\n"
xml += getTradePartyAsXML(trans.getRecipient(), false);
xml += getTradePartyAsXML(trans.getRecipient(), false, false);
if ((trans.getOwnVATID() != null) && (trans.getOwnOrganisationName() != null)) {
xml = xml + " <ram:SpecifiedTaxRegistration>\n" + " <ram:ID schemeID=\"VA\">"
+ XMLTools.encodeXML(trans.getOwnVATID()) + "</ram:ID>\n"
@@ -209,7 +209,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ " <ram:ApplicableSupplyChainTradeDelivery>\n";
if (this.trans.getDeliveryAddress() != null) {
xml += "<ram:ShipToTradeParty>" +
getTradePartyAsXML(this.trans.getDeliveryAddress(), false) +
getTradePartyAsXML(this.trans.getDeliveryAddress(), false, true) +
"</ram:ShipToTradeParty>";
}

View File

@@ -38,6 +38,7 @@ import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.mustangproject.FileAttachment;
import org.mustangproject.XMLTools;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
public class ZUGFeRD2PullProvider implements IXMLProvider {
@@ -105,14 +106,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
return profile;
}
// @todo check if the two boolean args can be refactored
/***
* 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
* @param isShipToTradeParty some attributes are allowed only for senders or recipients
* @return
*/
protected String getTradePartyAsXML(IZUGFeRDExportableTradeParty party, boolean isSender) {
protected String getTradePartyAsXML(IZUGFeRDExportableTradeParty party, boolean isSender, boolean isShipToTradeParty) {
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.
@@ -164,13 +167,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ " <ram:CountryID>" + XMLTools.encodeXML(party.getCountry())
+ "</ram:CountryID>\n"
+ " </ram:PostalTradeAddress>\n";
if (party.getVATID() != null) {
if ((party.getVATID() != null)&&(!isShipToTradeParty)) {
xml += " <ram:SpecifiedTaxRegistration>\n"
+ " <ram:ID schemeID=\"VA\">" + XMLTools.encodeXML(party.getVATID())
+ "</ram:ID>\n"
+ " </ram:SpecifiedTaxRegistration>\n";
}
if (party.getTaxID() != null) {
if ((party.getTaxID() != null)&&(!isShipToTradeParty)) {
xml += " <ram:SpecifiedTaxRegistration>\n"
+ " <ram:ID schemeID=\"FC\">" + XMLTools.encodeXML(party.getTaxID())
+ "</ram:ID>\n"
@@ -226,7 +229,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
paymentTermsDescription = trans.getPaymentTermDescription();
}
if (paymentTermsDescription == null) {
if ((paymentTermsDescription == null)&&(trans.getDocumentCode()!= org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE)) {
paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());
}
@@ -396,13 +399,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
xml = xml + " <ram:SellerTradeParty>\n"
+ getTradePartyAsXML(trans.getSender(), true)
+ getTradePartyAsXML(trans.getSender(), true, false)
+ " </ram:SellerTradeParty>\n"
+ " <ram:BuyerTradeParty>\n";
// + " <ID>GE2020211</ID>\n"
// + " <GlobalID schemeID=\"0088\">4000001987658</GlobalID>\n"
xml += getTradePartyAsXML(trans.getRecipient(), false);
xml += getTradePartyAsXML(trans.getRecipient(), false, false);
xml += " </ram:BuyerTradeParty>\n";
if (trans.getBuyerOrderReferencedDocumentID() != null) {
@@ -436,7 +439,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ " <ram:ApplicableHeaderTradeDelivery>\n";
if (this.trans.getDeliveryAddress() != null) {
xml += "<ram:ShipToTradeParty>" +
getTradePartyAsXML(this.trans.getDeliveryAddress(), false) +
getTradePartyAsXML(this.trans.getDeliveryAddress(), false, true) +
"</ram:ShipToTradeParty>";
}
@@ -479,6 +482,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
}
}
if (trans.getDocumentCode()== DocumentCodeTypeConstants.CORRECTEDINVOICE) {
hasDueDate=false;
}
HashMap<BigDecimal, VATAmount> VATPercentAmountMap = calc.getVATPercentAmountMap();
for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {

View File

@@ -260,7 +260,8 @@ public class ZF2PushTest extends TestCase {
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).setID("0009845"))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setID("0008734").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE")))
.setDeliveryAddress(new TradeParty("just the other side of the street", "teststr.12a", "55232", "Entenhausen", "DE").addVATID("DE47110"))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setID("0008734").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")))
.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)))
@@ -284,7 +285,10 @@ public class ZF2PushTest extends TestCase {
assertTrue(zi.getUTF8().contains("0009845"));
assertTrue(zi.getUTF8().contains("0008734"));
assertTrue(zi.getUTF8().contains("item level 1/1"));
assertTrue(zi.getUTF8().contains("DE4711")); // the VAT ID should be there...
assertFalse(zi.getUTF8().contains("DE47110")); // but not the VAT ID of the shiptotradeparty
assertTrue(zi.getUTF8().contains("document level 2/2"));
assertFalse(zi.getUTF8().contains("++49555123456")); // in profile EN16931 contact fax number is not allowed
}
@@ -389,8 +393,8 @@ public class ZF2PushTest extends TestCase {
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1().setProducer("My Application")
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).ignorePDFAErrors()
.load(SOURCE_PDF)) {
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDetailedDeliveryPeriod(new Date(), new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")).setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0815")).setNumber(number)
// no due date, since we are not expecting money
Invoice i = new Invoice().setIssueDate(new Date()).setDetailedDeliveryPeriod(new Date(), new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")).setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0815")).setNumber(number)
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty)).setCorrection("0815");

View File

@@ -135,6 +135,24 @@ public class LibraryTest extends ResourceCase {
assertThat(res).valueByXPath("/validation/summary/@status")
.isEqualTo("valid");
}
public void testPDFA3Exporter() {
// testout-MustangGnuaccountingBeispielRE-20170509_505newEdge.pdf was a A3 file
// already in import (MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf),
// so it's important to check that we did not screw anythig up in that scenario
File tempFile = new File("../library/target/testout-MustangGnuaccountingBeispielRE-20170509_505newEdge.pdf");
assertTrue(tempFile.exists());
ZUGFeRDValidator zfv = new ZUGFeRDValidator();
String res = zfv.validate(tempFile.getAbsolutePath());
assertThat(res).valueByXPath("/validation/pdf/summary/@status")
.isEqualTo("valid");
assertThat(res).valueByXPath("/validation/xml/summary/@status")
.isEqualTo("valid");
assertThat(res).valueByXPath("/validation/summary/@status")
.isEqualTo("valid");
}
/**
* automatically test the xrechnung
*/