support credit notes, automatically switch from XR 1.2.2 to XR 2.0 on 2021-01-01
This commit is contained in:
@@ -6,6 +6,8 @@
|
|||||||
- corrected sample in docs thanks to tweimer PR #204
|
- corrected sample in docs thanks to tweimer PR #204
|
||||||
- don't write "null" as paymentDescription if no Bank account is specified
|
- don't write "null" as paymentDescription if no Bank account is specified
|
||||||
- generic and unitcode/categorycode improvements (thanks to weclapp-dev) PR #207
|
- 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
|
2.0.2
|
||||||
=======
|
=======
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ public class Invoice implements IExportableTransaction {
|
|||||||
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public Invoice setCreditNote() {
|
||||||
|
documentCode = DocumentCodeTypeConstants.CREDITNOTE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getOwnOrganisationFullPlaintextInfo() {
|
public String getOwnOrganisationFullPlaintextInfo() {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.mustangproject.ZUGFeRD;
|
package org.mustangproject.ZUGFeRD;
|
||||||
|
|
||||||
|
import java.time.Year;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
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")},
|
{"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")},
|
{"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")},
|
{"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]));
|
}).collect(Collectors.toMap(data -> (String) data[0], data -> (Profile) data[1]));
|
||||||
static Map<String, Profile> zf1Map = Stream.of(new Object[][]{
|
static Map<String, Profile> zf1Map = Stream.of(new Object[][]{
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import org.mustangproject.FileAttachment;
|
|||||||
import org.mustangproject.XMLTools;
|
import org.mustangproject.XMLTools;
|
||||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||||
|
|
||||||
|
import static org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
||||||
|
|
||||||
public class ZUGFeRD2PullProvider implements IXMLProvider {
|
public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||||
|
|
||||||
//// MAIN CLASS
|
//// MAIN CLASS
|
||||||
@@ -229,7 +231,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
|||||||
paymentTermsDescription = trans.getPaymentTermDescription();
|
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());
|
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;
|
hasDueDate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ package org.mustangproject.ZUGFeRD.model;
|
|||||||
|
|
||||||
public class DocumentCodeTypeConstants {
|
public class DocumentCodeTypeConstants {
|
||||||
public static final String INVOICE = "380";
|
public static final String INVOICE = "380";
|
||||||
|
public static final String CREDITNOTE = "381";
|
||||||
public static final String DEBITNOTE = "84";
|
public static final String DEBITNOTE = "84";
|
||||||
public static final String CORRECTEDINVOICE = "384";
|
public static final String CORRECTEDINVOICE = "384";
|
||||||
public static final String CREDITNOTE = "389";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import junit.framework.TestCase;
|
|||||||
public class ZF2PushTest extends TestCase {
|
public class ZF2PushTest extends TestCase {
|
||||||
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20201121_508.pdf";
|
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20201121_508.pdf";
|
||||||
final String TARGET_ALLOWANCESPDF = "./target/testout-ZF2PushAllowances.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_CORRECTIONPDF = "./target/testout-ZF2PushCorrection.pdf";
|
||||||
final String TARGET_ITEMCHARGESALLOWANCESPDF = "./target/testout-ZF2PushItemChargesAllowances.pdf";
|
final String TARGET_ITEMCHARGESALLOWANCESPDF = "./target/testout-ZF2PushItemChargesAllowances.pdf";
|
||||||
final String TARGET_CHARGESALLOWANCESPDF = "./target/testout-ZF2PushChargesAllowances.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() {
|
public void testCorrectionExport() {
|
||||||
|
|
||||||
String orgname = "Test company";
|
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("<rsm:CrossIndustryInvoice"));
|
||||||
|
ze.export(TARGET_CREDITNOTEPDF);
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("IOException should not be raised in testEdgeExport");
|
||||||
|
}
|
||||||
|
|
||||||
|
// now check the contents (like MustangReaderTest)
|
||||||
|
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_CREDITNOTEPDF);
|
||||||
|
|
||||||
|
assertTrue(zi.getUTF8().contains("EUR"));
|
||||||
|
//totest: typecode 384, BuyerOrderReferencedDocument
|
||||||
|
// Reading ZUGFeRD
|
||||||
|
assertEquals("3.57", zi.getAmount());
|
||||||
|
assertEquals(zi.getHolder(), orgname);
|
||||||
|
assertEquals(zi.getForeignReference(), number);
|
||||||
|
try {
|
||||||
|
assertEquals(zi.getVersion(), 2);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<param name="BR-DE-20"
|
<param name="BR-DE-20"
|
||||||
value="not(ram:TypeCode = '59') or matches(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')), '^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{0,30}$') and xs:integer(string-join(for $cp in string-to-codepoints(concat(substring(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')),5),upper-case(substring(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')),1,2)),substring(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')),3,2))) return (if($cp > 64) then $cp - 55 else $cp - 48),'')) mod 97 = 1" />
|
value="not(ram:TypeCode = '59') or matches(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')), '^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{0,30}$') and xs:integer(string-join(for $cp in string-to-codepoints(concat(substring(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')),5),upper-case(substring(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')),1,2)),substring(normalize-space(replace(ram:PayerPartyDebtorFinancialAccount/ram:IBANID, '([ \n\r\t\s])', '')),3,2))) return (if($cp > 64) then $cp - 55 else $cp - 48),'')) mod 97 = 1" />
|
||||||
<param name="BR-DE-21"
|
<param name="BR-DE-21"
|
||||||
value="ram:GuidelineSpecifiedDocumentContextParameter/ram:ID = 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2'" />
|
value="ram:GuidelineSpecifiedDocumentContextParameter/ram:ID = 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0'" />
|
||||||
|
|
||||||
<param name="INVOICE" value="//rsm:CrossIndustryInvoice" />
|
<param name="INVOICE" value="//rsm:CrossIndustryInvoice" />
|
||||||
<param name="BG-2_PROCESS_CONTROL"
|
<param name="BG-2_PROCESS_CONTROL"
|
||||||
|
|||||||
Reference in New Issue
Block a user