-
-
Notifications
You must be signed in to change notification settings - Fork 208
Description
Summary
The ZUGFERD2PullProvider.generateXML() method for an EN16931 - profile document fails to print out the optional document-level charge <ram:CalculationPercent> (BT-101) and <ram:BasisAmount> (BT-100) fields if they have been set in a Charge object that was added to an Invoice using the setZFCharges() method.
Found in Mustang v2.21
Problem
It is not currently possible to get the document-level charge (BG-21) charge rate and basis amount optional fields included in the generated XML. Our visible PDF invoice includes both these charge-related information, so I understand we must then also include them in the embedded factur-x.xml file for the recommended EN16931 profile ZUGFeRD hybrid PDF.
Therefore, I understand, the resulting hybrid PDF does not comply with the ZUGFeRD requirement of equivalence between the visible invoice and the factur-x XML.
Related (see below), I also notice that the code does output the equivalent of these two fields for line - item charges (BT-143 and BT-142) for the EXTENDED profile only. But my copy of the 1_FACTUR-X 1.08 - 2025 12 04 - EN FR - VF file shows that these fields ARE allowed in the EN16931 profile:
(Edit) This may be related to this existing open issue. #925
Affected Code Locations
1. ZUGFERD2PullProvider.generateXML() method
The code within this document-level charges block does not check if the Charge object's percent and basisAmount properties have been set and if they have, include the relevant BT-100 and BT-101 XML elements.
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) {
for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
I notice that both these fields ARE optionally included for line item - related charges in the getItemTotalAllowanceChargeStr() method BUT ONLY if the profile is EXTENDED :
if ((allowance.getPercent() != null) && (profile == Profiles.getByName("Extended"))) {
percentage = "<ram:CalculationPercent>" + vatFormat(allowance.getPercent()) + "</ram:CalculationPercent>";
percentage += "<ram:BasisAmount>" + item.getValue() + "</ram:BasisAmount>";
}
Also I note that the document-level allowances export code has the same problem (missing optional BT-94 and BT-93 for EN16931 and EXTENDED profiles), so should probably be fixed also.
Suggested Fix
1. ZUGFERD2PullProvider.generateXML() method
Print the optional charge rate and basis amounts fields for document-level charges if they have been set for EN16931 and EXTENDED profiles:
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) {
for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>true</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(charge.getTotalAmount(calc)) + "</ram:ActualAmount>";
// ** NEW CODE **
if (charge.getPercent() != null && (profile == Profiles.getByName("EN16931") || (profile == Profiles.getByName("EXTENDED")))) {
xml += "<ram:CalculationPercent>" + vatFormat(charge.getPercent()) + "</ram:CalculationPercent>" +
"<ram:BasisAmount>" + currencyFormat(charge.getBasisAmount()) + "</ram:BasisAmount>";
}
// ** END NEW CODE
if (charge.getReasonCode() != null) {
xml += "<ram:ReasonCode>" + charge.getReasonCode() + "</ram:ReasonCode>";
}
if (charge.getReason() != null) {
xml += "<ram:Reason>" + XMLTools.encodeXML(charge.getReason()) + "</ram:Reason>";
}
xml += "<ram:CategoryTradeTax>" +
"<ram:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + charge.getCategoryCode() + "</ram:CategoryCode>";
if (charge.getTaxPercent() != null && !charge.getCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) {
xml += "<ram:RateApplicablePercent>" + vatFormat(charge.getTaxPercent()) + "</ram:RateApplicablePercent>";
}
xml += "</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
2. ZUGFERD2PullProvider.generateXML() method
Equivalent new code from fix # 1 in the following document-level allowances section:
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
if (profile == Profiles.getByName("XRechnung")) {
for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
...
3. ZUGFERD2PullProvider.getItemTotalAllowanceChargeStr() method
Include optional BT-142 and BT-143 line-item charge & allowance fields if defined:
if ((allowance.getPercent() != null) && (profile == Profiles.getByName("EN16931") || profile == Profiles.getByName("Extended"))) {
percentage = "<ram:CalculationPercent>" + vatFormat(allowance.getPercent()) + "</ram:CalculationPercent>";
percentage += "<ram:BasisAmount>" + item.getValue() + "</ram:BasisAmount>";
}