diff --git a/History.md b/History.md index 0a863541..a1a0f24d 100644 --- a/History.md +++ b/History.md @@ -6,6 +6,8 @@ - corrected sample in docs thanks to tweimer PR #204 - don't write "null" as paymentDescription if no Bank account is specified - generic and unitcode/categorycode improvements (thanks to weclapp-dev) PR #207 +- programmatically switch to XRechnung 2.0 if invoked next year +- support Credit Notes (additionally to corrected invoices) 2.0.2 ======= diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 1d0c121f..7562f41a 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -119,6 +119,10 @@ public class Invoice implements IExportableTransaction { documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE; return this; } + public Invoice setCreditNote() { + documentCode = DocumentCodeTypeConstants.CREDITNOTE; + return this; + } @Override public String getOwnOrganisationFullPlaintextInfo() { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java index 2496e801..515e9505 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java @@ -20,6 +20,7 @@ */ package org.mustangproject.ZUGFeRD; +import java.time.Year; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -31,7 +32,7 @@ public class Profiles { {"BASIC", new Profile("BASIC", "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic")}, {"EN16931", new Profile("EN16931", "urn:cen.eu:en16931:2017")}, {"EXTENDED", new Profile("EXTENDED", "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended")}, - {"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0")} + {"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_"+((Year.now().getValue()<=2020)?"1.2":"2.0"))} //intentionally switch to XRechnung 2.0 on 01.01.2021 }).collect(Collectors.toMap(data -> (String) data[0], data -> (Profile) data[1])); static Map zf1Map = Stream.of(new Object[][]{ diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 3129943e..5980cbf9 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -40,6 +40,8 @@ import org.mustangproject.FileAttachment; import org.mustangproject.XMLTools; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; +import static org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE; + public class ZUGFeRD2PullProvider implements IXMLProvider { //// MAIN CLASS @@ -229,7 +231,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { paymentTermsDescription = trans.getPaymentTermDescription(); } - if ((paymentTermsDescription == null) && (trans.getDocumentCode() != org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE)) { + if ((paymentTermsDescription == null) && (trans.getDocumentCode() != CORRECTEDINVOICE)/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) { paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate()); } @@ -494,7 +496,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } } } - if (trans.getDocumentCode() == DocumentCodeTypeConstants.CORRECTEDINVOICE) { + if ((trans.getDocumentCode() == CORRECTEDINVOICE)/*||(trans.getDocumentCode() == DocumentCodeTypeConstants.CREDITNOTE)*/) { hasDueDate = false; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java b/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java index 96855ea4..bbc2a7ac 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java @@ -20,7 +20,7 @@ package org.mustangproject.ZUGFeRD.model; public class DocumentCodeTypeConstants { public static final String INVOICE = "380"; + public static final String CREDITNOTE = "381"; public static final String DEBITNOTE = "84"; public static final String CORRECTEDINVOICE = "384"; - public static final String CREDITNOTE = "389"; } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index c19cf0c7..ea0daf27 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -39,6 +39,7 @@ import junit.framework.TestCase; public class ZF2PushTest extends TestCase { final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20201121_508.pdf"; final String TARGET_ALLOWANCESPDF = "./target/testout-ZF2PushAllowances.pdf"; + final String TARGET_CREDITNOTEPDF = "./target/testout-ZF2PushCreditNote.pdf"; final String TARGET_CORRECTIONPDF = "./target/testout-ZF2PushCorrection.pdf"; final String TARGET_ITEMCHARGESALLOWANCESPDF = "./target/testout-ZF2PushItemChargesAllowances.pdf"; final String TARGET_CHARGESALLOWANCESPDF = "./target/testout-ZF2PushChargesAllowances.pdf"; @@ -501,6 +502,11 @@ public class ZF2PushTest extends TestCase { } + /*** + * German Stornorechnung/Rechnungskorrektur: + * quantities have to be negative! + * official example: zugferd_2p1_EXTENDED_Rechnungskorrektur.pdf + */ public void testCorrectionExport() { String orgname = "Test company"; @@ -545,5 +551,54 @@ public class ZF2PushTest extends TestCase { } + /*** + * UNTDID 1001 says 381 as typecode for credit notes and + * the Factur-X 1.0.50 has examples like Avoir_FR_type381_EN16931.pdf + * along with a documentation in chapter 7.1.6 (where they also tackle + * negative TypeCode 380 invoices) + */ + public void testCreditNoteExport() { + + String orgname = "Test company"; + String number = "123"; + String priceStr = "1.00"; + BigDecimal price = new BigDecimal(priceStr); + BigDecimal qty = new BigDecimal(1.0); + 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)) { + Invoice i = new Invoice().setIssueDate(new Date()).setDueDate(new Date()).setDetailedDeliveryPeriod(new Date(), new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815").addBankDetails(new BankDetails("DE88200800000970375700", "COBADEFFXXX"))).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)).setCreditNote(); + ze.setTransaction(i); + String theXML = new String(ze.getProvider().getXML()); + assertTrue(theXML.contains(" + value="ram:GuidelineSpecifiedDocumentContextParameter/ram:ID = 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0'" />