Skip to content

ZUGFeRDInvoiceImporter does not properly handle UBL CreditNote documents #998

@burak-inan

Description

@burak-inan

Summary

The ZUGFeRDInvoiceImporter class fails to correctly parse UBL CreditNote documents because it uses Invoice-specific element names when querying CreditNote XML structures.

Problem

UBL uses different element names for Invoice and CreditNote documents:

Invoice Element CreditNote Element
InvoiceTypeCode CreditNoteTypeCode
InvoiceLine CreditNoteLine
InvoicedQuantity CreditedQuantity
DueDate PaymentMeans/PaymentDueDate

Note: According to the Peppol BIS Billing 3.0 CreditNote specification, there is no direct DueDate element for CreditNote. The due date is located at CreditNote/PaymentMeans/PaymentDueDate.

Currently, the extractInto() method only uses Invoice element names, causing CreditNote parsing to fail.

Affected Code Locations

1. ZUGFeRDInvoiceImporter.extractInto() - Type code

Type code extraction only looks for InvoiceTypeCode:

typeCode = this.extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim();

2. ZUGFeRDInvoiceImporter.extractInto() - Due date

Due date extraction doesn't account for CreditNote's different structure:

dueDate = this.extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"]").trim();

3. ZUGFeRDInvoiceImporter.extractInto() - Line items

Line item extraction only looks for InvoiceLine:

xpr = xpath.compile("//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]|//*[local-name()=\"InvoiceLine\"]");

4. Item constructor - Quantity

The Item class constructor uses InvoicedQuantity:

itemMap.getNode(new String[]{"InvoicedQuantity"}).ifPresent((icn) -> {
    this.setQuantity(new BigDecimal(icn.getTextContent().trim()));
    this.product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
});

Suggested Fix

1. Type code extraction

typeCode = this.extractString("/*[local-name()=\"Invoice\"]/*[local-name()=\"InvoiceTypeCode\"] | /*[local-name()=\"CreditNote\"]/*[local-name()=\"CreditNoteTypeCode\"]").trim();

2. Due date extraction

dueDate = this.extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"] | /*[local-name()=\"CreditNote\"]/*[local-name()=\"PaymentMeans\"]/*[local-name()=\"PaymentDueDate\"]").trim();

3. Line item extraction

xpr = xpath.compile("//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]|//*[local-name()=\"InvoiceLine\"]|//*[local-name()=\"CreditNoteLine\"]");

4. Item constructor quantity extraction

itemMap.getNode(new String[]{"InvoicedQuantity", "CreditedQuantity"}).ifPresent((icn) -> {
    this.setQuantity(new BigDecimal(icn.getTextContent().trim()));
    this.product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
});

5. Removed due date restriction in Zugferd2PullProvider.generateXML() method

Before:

if (trans.getDocumentCode() != null 
    && Arrays.asList(DocumentCodeTypeConstants.CORRECTEDINVOICE, DocumentCodeTypeConstants.CREDITNOTE).contains(trans.getDocumentCode())) {
    hasDueDate = false;
}

After: This restriction has been removed entirely, allowing due dates for corrected invoices and credit notes.

6. Added description null check in Zugferd2PullProvider.buildPaymentTermsXml() method

if(pt.getDescription() != null) {
    paymentTermsXml += "" + pt.getDescription() + "";
}

Environment

  • Mustang Version: 2.21.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions