Add configurable BT-23 business process ID for CII export
This commit is contained in:
@@ -75,7 +75,7 @@ public class Invoice implements IExportableTransaction {
|
||||
protected String creditorReferenceID; // required when direct debit is used.
|
||||
private BigDecimal roundingAmount=null;
|
||||
private String paymentReference; // Remittance information / Verwendungszweck, BT-83
|
||||
|
||||
private String businessProcessId;
|
||||
public Invoice() {
|
||||
ZFItems = new ArrayList<>();
|
||||
cashDiscounts = new ArrayList<>();
|
||||
@@ -1156,4 +1156,14 @@ public class Invoice implements IExportableTransaction {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Invoice setBusinessProcessId(String id) {
|
||||
this.businessProcessId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBusinessProcessId() {
|
||||
return businessProcessId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -626,4 +626,16 @@ public interface IExportableTransaction {
|
||||
default String getCreditorReferenceID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* BT-23 Business process identifier
|
||||
* /rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/
|
||||
* ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID
|
||||
*
|
||||
* @return business process ID (e.g. "B1" or a URN) or null if not provided
|
||||
*/
|
||||
default String getBusinessProcessId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -388,7 +388,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
// <ram:TestIndicator><udt:Indicator>"+testBooleanStr+"</udt:Indicator></ram:TestIndicator>"
|
||||
//
|
||||
|
||||
if (getProfile() == Profiles.getByName("XRechnung")) {
|
||||
String businessProcessId = trans.getBusinessProcessId();
|
||||
if (businessProcessId != null && !businessProcessId.isBlank()) {
|
||||
xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
|
||||
+ "<ram:ID>" + XMLTools.encodeXML(businessProcessId) + "</ram:ID>\n"
|
||||
+ "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
|
||||
}else if (getProfile() == Profiles.getByName("XRechnung")) {
|
||||
xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
|
||||
+ "<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>\n"
|
||||
+ "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mustangproject.ZUGFeRD.Profiles;
|
||||
import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for BT-23 (BusinessProcessSpecifiedDocumentContextParameter/ID) emission in CII output.
|
||||
*
|
||||
* Assumptions / prerequisites for the proposed fix:
|
||||
* - IExportableTransaction#getBusinessProcessId() exists (as a default method returning null)
|
||||
* - Invoice#setBusinessProcessId(String) exists and Invoice overrides getBusinessProcessId()
|
||||
* - ZUGFeRD2PullProvider writes BT-23 if a custom businessProcessId is provided,
|
||||
* otherwise keeps the current XRechnung default behavior.
|
||||
*/
|
||||
public class BusinessProcessIdCiiTest {
|
||||
|
||||
@Test
|
||||
void xrechnung_nonRegression_defaultBusinessProcessId_isStillEmitted() throws Exception {
|
||||
// ===== GIVEN: A minimal invoice (as provided) WITHOUT setting businessProcessId =====
|
||||
|
||||
TradeParty buyer = new TradeParty("Client X", "3 rue C", "33000", "Bordeaux", "FR");
|
||||
TradeParty seller = new TradeParty("Mairie A", "1 rue A", "75001", "Paris", "FR");
|
||||
|
||||
Invoice invoice = new Invoice()
|
||||
.setNumber("INV-CALC-001") // BT-1: Invoice number -> /.../ExchangedDocument/ram:ID
|
||||
.setIssueDate(new Date()) // BT-2: Issue date
|
||||
.setDeliveryDate(new Date()) // Delivery date (depending on profile/exporter mapping)
|
||||
.setDueDate(new Date()) // BT-9: Payment due date
|
||||
.setCurrency("EUR") // BT-5: Invoice currency code
|
||||
.setSender(buyer)
|
||||
.setRecipient(seller);
|
||||
|
||||
Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00"));
|
||||
invoice.addItem(new Item(service, BigDecimal.ONE, BigDecimal.TEN).setTax(BigDecimal.valueOf(20)));
|
||||
|
||||
// ===== WHEN: Generating CII XML using the XRechnung profile =====
|
||||
|
||||
ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider();
|
||||
provider.setProfile(Profiles.getByName("XRechnung"));
|
||||
provider.generateXML(invoice);
|
||||
|
||||
String xml = new String(provider.getXML(), StandardCharsets.UTF_8);
|
||||
|
||||
// ===== THEN: Non-regression check =====
|
||||
// XRechnung currently emits BT-23 with a default fixed URN.
|
||||
// This behavior must remain unchanged when businessProcessId is NOT explicitly provided by the user.
|
||||
|
||||
assertTrue(
|
||||
xml.contains("<ram:BusinessProcessSpecifiedDocumentContextParameter>"),
|
||||
"XRechnung should still emit BusinessProcessSpecifiedDocumentContextParameter (BT-23)."
|
||||
);
|
||||
|
||||
assertTrue(
|
||||
xml.contains("<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>"),
|
||||
"XRechnung default BT-23 URN must remain unchanged when businessProcessId is not set."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBusinessProcessIdIsSet_itIsEmitted_inExtendedProfile() throws Exception {
|
||||
// ===== GIVEN: The same minimal invoice, but WITH an explicit businessProcessId =====
|
||||
|
||||
TradeParty buyer = new TradeParty("Client X", "3 rue C", "33000", "Bordeaux", "FR");
|
||||
TradeParty seller = new TradeParty("Mairie A", "1 rue A", "75001", "Paris", "FR");
|
||||
|
||||
Invoice invoice = new Invoice()
|
||||
.setNumber("INV-CALC-001")
|
||||
.setIssueDate(new Date())
|
||||
.setDeliveryDate(new Date())
|
||||
.setDueDate(new Date())
|
||||
.setCurrency("EUR")
|
||||
.setSender(buyer)
|
||||
.setRecipient(seller);
|
||||
|
||||
Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00"));
|
||||
invoice.addItem(new Item(service, BigDecimal.ONE, BigDecimal.TEN).setTax(BigDecimal.valueOf(20)));
|
||||
|
||||
// Proposed new API: user-provided BT-23 value
|
||||
invoice.setBusinessProcessId("B1");
|
||||
// BT-23 path:
|
||||
// /rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/
|
||||
// ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID
|
||||
|
||||
// ===== WHEN: Generating CII XML using the EXTENDED profile =====
|
||||
|
||||
ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider();
|
||||
provider.setProfile(Profiles.getByName("EXTENDED"));
|
||||
provider.generateXML(invoice);
|
||||
|
||||
String xml = new String(provider.getXML(), StandardCharsets.UTF_8);
|
||||
|
||||
// ===== THEN: BT-23 must be emitted with the user-provided value =====
|
||||
|
||||
assertTrue(
|
||||
xml.contains("<ram:BusinessProcessSpecifiedDocumentContextParameter>"),
|
||||
"When businessProcessId is set, BT-23 must be emitted even for EXTENDED."
|
||||
);
|
||||
|
||||
assertTrue(
|
||||
xml.contains("<ram:ID>B1</ram:ID>"),
|
||||
"BT-23 must contain the user-provided businessProcessId."
|
||||
);
|
||||
|
||||
// Extra sanity check: the guideline/specification identifier (BT-24) should still be present
|
||||
assertTrue(
|
||||
xml.contains("<ram:GuidelineSpecifiedDocumentContextParameter>"),
|
||||
"BT-24 (GuidelineSpecifiedDocumentContextParameter) must be present."
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user