diff --git a/History.md b/History.md index fd141a14..df5b20b1 100644 --- a/History.md +++ b/History.md @@ -5,7 +5,7 @@ - Support validation of XRechnung (CII) 2.2 - allow to create fx 1 files with command line again - is maven build profile to gen xslt, mvn clean package -P generateXSLTFromSchematron -- OXPullprovider to no longer generate invsalid XML if a duedate is set +- OXPullprovider to no longer generate invalid XML if a duedate is set - Add missing encodeXML to node payment terms description #278 thanks to weclapp-dev 2.5.1 diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index ab45418b..3d21f588 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -12,8 +12,9 @@ import java.math.BigDecimal; public class Product implements IZUGFeRDExportableProduct { protected String unit, name, description, sellerAssignedID, buyerAssignedID; protected BigDecimal VATPercent; - protected boolean isReverseCharge=false; - protected boolean isIntraCommunitySupply=false; + protected boolean isReverseCharge = false; + protected boolean isIntraCommunitySupply = false; + protected SchemedID globalId = null; /*** * default constructor @@ -38,6 +39,29 @@ public class Product implements IZUGFeRDExportableProduct { } + @Override + public String getGlobalID() { + if (globalId == null) { + return null; + } else { + return globalId.getID(); + } + } + + @Override + public String getGlobalIDScheme() { + if (globalId == null) { + return null; + } else { + return globalId.getScheme(); + } + } + + public Product addGlobalID(SchemedID schemedID) { + globalId = schemedID; + return this; + } + public String getSellerAssignedID() { return sellerAssignedID; @@ -82,7 +106,7 @@ public class Product implements IZUGFeRDExportableProduct { * @return fluent setter */ public Product setReverseCharge() { - isReverseCharge=true; + isReverseCharge = true; setVATPercent(BigDecimal.ZERO); return this; } @@ -93,7 +117,7 @@ public class Product implements IZUGFeRDExportableProduct { * @return fluent setter */ public Product setIntraCommunitySupply() { - isIntraCommunitySupply=true; + isIntraCommunitySupply = true; setVATPercent(BigDecimal.ZERO); return this; } @@ -120,6 +144,7 @@ public class Product implements IZUGFeRDExportableProduct { /** * name of the product + * * @param name short name * @return fluent setter */ @@ -135,6 +160,7 @@ public class Product implements IZUGFeRDExportableProduct { /** * description of the product (required) + * * @param description long name * @return fluent setter */ diff --git a/library/src/main/java/org/mustangproject/SchemedID.java b/library/src/main/java/org/mustangproject/SchemedID.java new file mode 100644 index 00000000..d3071850 --- /dev/null +++ b/library/src/main/java/org/mustangproject/SchemedID.java @@ -0,0 +1,34 @@ +package org.mustangproject; + +public class SchemedID { + protected String scheme; + protected String id; + + public String getScheme() { + return scheme; + } + + public SchemedID setScheme(String scheme) { + this.scheme = scheme; + return this; + } + + public String getID() { + return id; + } + + public SchemedID setId(String id) { + this.id = id; + return this; + } + + public SchemedID() { + + } + + public SchemedID(String scheme, String id) { + setScheme(scheme); + setId(id); + } + +} diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index 5b733a4e..21005bec 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -24,6 +24,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { protected List debitDetails = new ArrayList<>(); protected Contact contact = null; protected LegalOrganisation legalOrg = null; + protected SchemedID globalId=null; /** * Default constructor. @@ -150,6 +151,26 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return ID; } + @Override + public String getGlobalID() { + if (globalId!=null) { + return globalId.getID(); + } + return null; + } + + + + @Override + public String getGlobalIDScheme() { + if (globalId!=null) { + return globalId.getScheme(); + } + return null; + + } + + /** * if it's a customer, this can e.g. be the customer ID * @@ -172,6 +193,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return this; } + public TradeParty addGlobalID(SchemedID schemedID) { + globalId=schemedID; + return this; + } + /*** * required (for senders, if payment is not debit): the BIC and IBAN * @param s bank credentials diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java index c0d15e04..3b82786e 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java @@ -128,6 +128,26 @@ public interface IZUGFeRDExportableProduct { } } + + /** + * customer global identification assigned by the seller + * + * @return customer identification + */ + default String getGlobalID() { + return null; + } + /** + * customer global identification scheme + * + * @return customer identification + */ + default String getGlobalIDScheme() { + return null; + } + + + default String getTaxExemptionReason() { if (isIntraCommunitySupply()) { return "Intra-community supply"; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index e8307fc1..85754262 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -359,7 +359,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + "" + ""; - // + " 4012345001235" + if ((currentItem.getProduct().getGlobalIDScheme() != null) && (currentItem.getProduct().getGlobalID() != null)) { + xml += ""+XMLTools.encodeXML(currentItem.getProduct().getGlobalID())+""; + } + if (currentItem.getProduct().getSellerAssignedID() != null) { xml += "" + XMLTools.encodeXML(currentItem.getProduct().getSellerAssignedID()) + ""; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index 2ead1d9b..27f41abb 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -32,6 +32,9 @@ import java.io.FileWriter; import java.io.IOException; 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.util.Date; import static org.xmlunit.assertj.XmlAssert.assertThat; @@ -78,9 +81,16 @@ public class XRTest extends TestCase { String number = "123"; String amountStr = "1.00"; BigDecimal amount = new BigDecimal(amountStr); - byte[] b = {12, 13}; + byte[] b = null; + Path path = Paths.get("c:\\users\\jstaerk\\temp\\ef1710en.pdf"); + try { + b = Files.readAllBytes(path); + + } catch(IOException ex) { + ex.printStackTrace(); + } + FileAttachment fe1=new FileAttachment("one.pdf", "application/pdf", "Alternative", b); - FileAttachment fe2=new FileAttachment("two.pdf", "application/pdf", "Alternative", b); Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) .setSender(new TradeParty(orgname,"teststr","55232","teststadt","DE").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test","+49123456789","test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890","COBADEFXXX"))) .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")) @@ -88,7 +98,7 @@ public class XRTest extends TestCase { // not using any VAT, this is also a test of zero-rated goods: .setNumber(number).setPaymentTermDescription("#SKONTO#TAGE=14#PROZENT=2.25#\n" + "#SKONTO#TAGE=28#PROZENT=1.00#\n").addItem(new Item(new Product("Testprodukt", "", "C62", BigDecimal.ZERO), amount, new BigDecimal(1.0))) - .embedFileInXML(fe1).embedFileInXML(fe2); + .embedFileInXML(fe1); ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 2e3c5ad2..af55657f 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -33,6 +33,11 @@ import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; import junit.framework.TestCase; +import org.xmlunit.builder.Input; +import org.xmlunit.xpath.JAXPXPathEngine; +import org.xmlunit.xpath.XPathEngine; + +import static org.xmlunit.assertj.XmlAssert.assertThat; @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -372,13 +377,15 @@ public class ZF2PushTest extends TestCase { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { + SchemedID gtin=new SchemedID("0160","2001015001325"); + SchemedID gln=new SchemedID("0088","4304171000002"); 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()) .setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurrenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurrenceTo)) .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setID("0009845")) .setDeliveryAddress(new TradeParty("just the other side of the street", "teststr.12a", "55232", "Entenhausen", "DE").addVATID("DE47110")) .setContractReferencedDocument(contractID) - .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")).setAdditionalAddress("Hinterhaus 3")) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)), price, new BigDecimal(1.0)).addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15"))) + .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addGlobalID(gln).addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")).setAdditionalAddress("Hinterhaus 3")) + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)).addGlobalID(gtin), price, new BigDecimal(1.0)).addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15"))) .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))) .setDeliveryDate(sdf.parse("2020-11-02")).setOwnVATID("DE0815").setNumber(number) @@ -399,6 +406,13 @@ public class ZF2PushTest extends TestCase { assertFalse(zi.getUTF8().contains("")); // maybe add a direct debit mandate to the class in the future then this would fail assertTrue(zi.getUTF8().contains("Hinterhaus")); + assertThat(zi.getUTF8()).valueByXPath("//ram:BuyerTradeParty/ram:GlobalID[@schemeID=0088]/text()") + .isEqualTo("4304171000002"); + assertThat(zi.getUTF8()).valueByXPath("/BuyerTradeParty//GlobalID[@schemeID=0160]") + .isEqualTo("2001015001325"); + assertTrue(zi.getUTF8().contains("2001015001325")); + assertTrue(zi.getUTF8().contains("4304171000002")); + assertTrue(zi.getUTF8().contains("0088")); assertTrue(zi.getUTF8().contains("0009845")); assertTrue(zi.getUTF8().contains("0008734")); assertTrue(zi.getUTF8().contains("ram:BuyerOrderReferencedDocument")); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/test.xml b/library/src/test/java/org/mustangproject/ZUGFeRD/test.xml new file mode 100644 index 00000000..b985dc7e --- /dev/null +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/test.xml @@ -0,0 +1,184 @@ + + + + + + + urn:cen.eu:en16931:2017 + + + + 123 + 380 + + 20220725 + + + document level 1/2 + + + document level 2/2 + + + + + + 1 + + item level 1/1 + + + + Testprodukt + + + + + xxx + + + 1.0000 + 1.0000 + + + false + + 0.0200 + + + + 0.9800 + 1.0000 + + + + 1.0000 + + + + VAT + S + 16.00 + + + + 20200113 + + + 20200115 + + + + 0.98 + + + + + + 0009845 + Test company + + 55232 + teststr + teststadt + DE + + + DE0815 + + + 9990815 + + + + 4304171000002 + Franz Müller + + 55232 + teststr.12 + Hinterhaus 3 + Entenhausen + DE + + + DE4711 + + + + 376zreurzu0983 + + + + + just the other side of the street + + 55232 + teststr.12a + Entenhausen + DE + + + + + 20201102 + + + + + 123 + CHF + + 0.20 + VAT + 1.28 + S + 16.00 + + + + 20201001 + + + 20201005 + + + + + true + + 0.50 + quick delivery charge + + VAT + S + 16.00 + + + + + false + + 0.20 + discount + + VAT + S + 16.00 + + + + Zahlbar ohne Abzug bis 25.07.2022 + + + 0.98 + 0.50 + 0.20 + 1.28 + 0.20 + 1.48 + 0.00 + 1.48 + + + +