From 3a3987360e61177a51cb01295f75ebcbb158cd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20Hartvig=20Gr=C3=B8nbech?= Date: Wed, 25 Mar 2026 14:46:30 +0100 Subject: [PATCH] Fix eDocument PEPPOL BIS 3.0 import errors for text-only document references and hierarchical line IDs (#7362) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fix "Please choose a file to attach" error when importing PEPPOL invoices/credit memos with `AdditionalDocumentReference` elements that have no `` child (text-only references). Added `"File Name" <> ''` guard to all 4 attachment insert locations. - Fix "The value '1.1' can't be evaluated into type Integer" error when importing documents with hierarchical line numbering (e.g., 1.1, 1.2). Replaced direct `Evaluate` of XML line ID with auto-incrementing counter (10000, 20000, ...) passed as `var` parameter from caller to parser. - Added test XML fixtures and automated tests in `EDocumentStructuredTests`. [AB#626739](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/626739) --------- Co-authored-by: Magnus Hartvig Grønbech Co-authored-by: Claude Sonnet 4.6 --- .../Format/EDocImportPEPPOLBIS30.Codeunit.al | 26 +-- .../peppol-invoice-hierarchical-lineids.xml | 177 +++++++++++++++++ .../peppol/peppol-invoice-textonly-docref.xml | 185 ++++++++++++++++++ .../src/Processing/EDocE2ETest.Codeunit.al | 53 +++++ .../EDocumentStructuredTests.Codeunit.al | 1 + 5 files changed, 430 insertions(+), 12 deletions(-) create mode 100644 src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-hierarchical-lineids.xml create mode 100644 src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-textonly-docref.xml diff --git a/src/Apps/W1/EDocument/App/src/Format/EDocImportPEPPOLBIS30.Codeunit.al b/src/Apps/W1/EDocument/App/src/Format/EDocImportPEPPOLBIS30.Codeunit.al index 804516ec5f..033258ecec 100644 --- a/src/Apps/W1/EDocument/App/src/Format/EDocImportPEPPOLBIS30.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Format/EDocImportPEPPOLBIS30.Codeunit.al @@ -188,6 +188,7 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" DocumentAttachment: Record "Document Attachment"; DocumentAttachmentData: Codeunit "Temp Blob"; InStream: InStream; + LineNo: Integer; begin PurchaseHeader."Document Type" := PurchaseHeader."Document Type"::Invoice; PurchaseHeader."No." := CopyStr(GetNodeByPath(TempXMLBuffer, '/Invoice/cbc:ID'), 1, MaxStrLen(PurchaseHeader."No.")); @@ -198,11 +199,11 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" TempXMLBuffer.Reset(); if TempXMLBuffer.FindSet() then repeat - ParseInvoice(EDocument, PurchaseHeader, PurchaseLine, DocumentAttachment, DocumentAttachmentData, TempXMLBuffer); + ParseInvoice(EDocument, PurchaseHeader, PurchaseLine, DocumentAttachment, DocumentAttachmentData, TempXMLBuffer, LineNo); until TempXMLBuffer.Next() = 0; // Insert last document attachment - if DocumentAttachment."No." <> '' then begin + if (DocumentAttachment."No." <> '') and (DocumentAttachment."File Name" <> '') then begin DocumentAttachmentData.CreateInStream(InStream, TextEncoding::UTF8); EDocumentAttachmentGen.Insert(EDocument, InStream, DocumentAttachment.FindUniqueFileName(DocumentAttachment."File Name", DocumentAttachment."File Extension")); Clear(DocumentAttachment); @@ -222,6 +223,7 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" DocumentAttachment: Record "Document Attachment"; DocumentAttachmentData: Codeunit "Temp Blob"; InStream: InStream; + LineNo: Integer; begin PurchaseHeader."Document Type" := PurchaseHeader."Document Type"::"Credit Memo"; PurchaseHeader."No." := CopyStr(GetNodeByPath(TempXMLBuffer, '/CreditNote/cbc:ID'), 1, MaxStrLen(PurchaseHeader."No.")); @@ -230,11 +232,11 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" TempXMLBuffer.Reset(); if TempXMLBuffer.FindSet() then repeat - ParseCreditMemo(EDocument, PurchaseHeader, PurchaseLine, DocumentAttachment, DocumentAttachmentData, TempXMLBuffer); + ParseCreditMemo(EDocument, PurchaseHeader, PurchaseLine, DocumentAttachment, DocumentAttachmentData, TempXMLBuffer, LineNo); until TempXMLBuffer.Next() = 0; // Insert last document attachment - if DocumentAttachment."No." <> '' then begin + if (DocumentAttachment."No." <> '') and (DocumentAttachment."File Name" <> '') then begin DocumentAttachmentData.CreateInStream(InStream, TextEncoding::UTF8); EDocumentAttachmentGen.Insert(EDocument, InStream, DocumentAttachment.FindUniqueFileName(DocumentAttachment."File Name", DocumentAttachment."File Extension")); Clear(DocumentAttachment); @@ -311,7 +313,7 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" /// Parses credit memo information line by line from TempXMLBuffer. /// We handle the insert of Purchase Order Line and Document Attachment after the call to this function. /// - local procedure ParseCreditMemo(EDocument: Record "E-Document"; var PurchaseHeader: Record "Purchase Header" temporary; var PurchaseLine: record "Purchase Line" temporary; var DocumentAttachment: Record "Document Attachment"; DocumentAttachmentData: Codeunit "Temp Blob"; var TempXMLBuffer: Record "XML Buffer" temporary) + local procedure ParseCreditMemo(EDocument: Record "E-Document"; var PurchaseHeader: Record "Purchase Header" temporary; var PurchaseLine: record "Purchase Line" temporary; var DocumentAttachment: Record "Document Attachment"; DocumentAttachmentData: Codeunit "Temp Blob"; var TempXMLBuffer: Record "XML Buffer" temporary; var LineNo: Integer) var Base64Convert: Codeunit "Base64 Convert"; OutStream: OutStream; @@ -352,7 +354,7 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" Evaluate(PurchaseHeader.Amount, Value, 9); '/CreditNote/cac:AdditionalDocumentReference/cbc:ID': begin - if DocumentAttachment."No." <> '' then begin + if (DocumentAttachment."No." <> '') and (DocumentAttachment."File Name" <> '') then begin DocumentAttachmentData.CreateInStream(InStream, TextEncoding::UTF8); EDocumentAttachmentGen.Insert(EDocument, InStream, DocumentAttachment.FindUniqueFileName(DocumentAttachment."File Name", DocumentAttachment."File Extension")); Clear(DocumentAttachment); @@ -381,6 +383,8 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" PurchaseLine.Init(); PurchaseLine."Document Type" := PurchaseHeader."Document Type"; PurchaseLine."Document No." := PurchaseHeader."No."; + LineNo += 10000; + PurchaseLine."Line No." := LineNo; end; '/CreditNote/cac:CreditNoteLine/cbc:CreditedQuantity': if Value <> '' then @@ -404,8 +408,6 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" PurchaseLine."Item Reference No." := CopyStr(Value, 1, MaxStrLen(PurchaseLine."Item Reference No.")); '/CreditNote/cac:CreditNoteLine/cac:Item/cac:StandardItemIdentification/cbc:ID': PurchaseLine."No." := CopyStr(Value, 1, MaxStrLen(PurchaseLine."No.")); - '/CreditNote/cac:CreditNoteLine/cbc:ID': - Evaluate(PurchaseLine."Line No.", Value, 9); '/CreditNote/cac:CreditNoteLine/cac:Item/cac:ClassifiedTaxCategory/cbc:Percent': if Value <> '' then Evaluate(PurchaseLine."VAT %", Value, 9); @@ -425,7 +427,7 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" /// Parses invoice information line by line from TempXMLBuffer. /// We handle the insert of Purchase Order Line and Document Attachment after the call to this function. /// - local procedure ParseInvoice(EDocument: Record "E-Document"; var PurchaseHeader: Record "Purchase Header" temporary; var PurchaseLine: Record "Purchase Line" temporary; var DocumentAttachment: Record "Document Attachment"; DocumentAttachmentData: Codeunit "Temp Blob"; var TempXMLBuffer: Record "XML Buffer" temporary) + local procedure ParseInvoice(EDocument: Record "E-Document"; var PurchaseHeader: Record "Purchase Header" temporary; var PurchaseLine: Record "Purchase Line" temporary; var DocumentAttachment: Record "Document Attachment"; DocumentAttachmentData: Codeunit "Temp Blob"; var TempXMLBuffer: Record "XML Buffer" temporary; var LineNo: Integer) var Base64Convert: Codeunit "Base64 Convert"; OutStream: OutStream; @@ -464,7 +466,7 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" Evaluate(PurchaseHeader."Document Date", Value, 9); '/Invoice/cac:AdditionalDocumentReference/cbc:ID': begin - if DocumentAttachment."No." <> '' then begin + if (DocumentAttachment."No." <> '') and (DocumentAttachment."File Name" <> '') then begin DocumentAttachmentData.CreateInStream(InStream, TextEncoding::UTF8); EDocumentAttachmentGen.Insert(EDocument, InStream, DocumentAttachment.FindUniqueFileName(DocumentAttachment."File Name", DocumentAttachment."File Extension")); Clear(DocumentAttachment); @@ -493,6 +495,8 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" PurchaseLine.Init(); PurchaseLine."Document Type" := PurchaseHeader."Document Type"; PurchaseLine."Document No." := PurchaseHeader."No."; + LineNo += 10000; + PurchaseLine."Line No." := LineNo; end; '/Invoice/cac:InvoiceLine/cbc:InvoicedQuantity': if Value <> '' then @@ -517,8 +521,6 @@ codeunit 6166 "EDoc Import PEPPOL BIS 3.0" PurchaseLine."Item Reference No." := CopyStr(Value, 1, MaxStrLen(PurchaseLine."Item Reference No.")); '/Invoice/cac:InvoiceLine/cac:Item/cac:StandardItemIdentification/cbc:ID': PurchaseLine."No." := CopyStr(Value, 1, MaxStrLen(PurchaseLine."No.")); - '/Invoice/cac:InvoiceLine/cbc:ID': - Evaluate(PurchaseLine."Line No.", Value, 9); '/Invoice/cac:InvoiceLine/cac:Item/cac:ClassifiedTaxCategory/cbc:Percent': if Value <> '' then Evaluate(PurchaseLine."VAT %", Value, 9); diff --git a/src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-hierarchical-lineids.xml b/src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-hierarchical-lineids.xml new file mode 100644 index 0000000000..0ed9e1978b --- /dev/null +++ b/src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-hierarchical-lineids.xml @@ -0,0 +1,177 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + 103033 + 2026-01-22 + 2026-02-22 + 380 + XYZ + 1 + + 2 + + + 103033 + + + 103033 + + + dGVzdA== + + + + + 1234567890128 + + CRONUS International + + + Main Street, 14 + Birmingham + B27 4KT + + GB + + + + GB123456789 + + VAT + + + + CRONUS International + 123456789 + + + Jim Olive + JO@contoso.com + + + + + + 789456278 + + 8712345000004 + + + The Cannon Group PLC + + + 192 Market Square + Birmingham + B27 4KT + + GB + + + + GB789456278 + + VAT + + + + The Cannon Group PLC + + + + + + CRONUS International + + + GB123456789 + + + + 30 + 2026-02-22 + + GB12CPBK08929965044991 + + BG99999 + + + + + 1 Month/2% 8 days + + + 1000 + + 4000 + 1000 + + S + 25 + + VAT + + + + + + 14000 + 14000 + 14140 + 0 + 0.00 + 0 + 14140 + + + 1.1 + Item + 1 + 4000 + + Bicycle + + 1000 + + + S + 25 + + VAT + + + + + 4000.00 + 1 + + + + 1.2 + Item + 2 + 10000 + + Bicycle v2 + + 2000 + + + S + 25 + + VAT + + + + + 5000.00 + 2 + + + diff --git a/src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-textonly-docref.xml b/src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-textonly-docref.xml new file mode 100644 index 0000000000..269a1d908c --- /dev/null +++ b/src/Apps/W1/EDocument/Test/.resources/peppol/peppol-invoice-textonly-docref.xml @@ -0,0 +1,185 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + 103033 + 2026-01-22 + 2026-02-22 + 380 + XYZ + 1 + + 2 + + + 103033 + + + DOC-REF-001 + Text-only reference without attachment + + + DOC-REF-002 + Another text-only reference + + + 103033 + + + dGVzdA== + + + + + 1234567890128 + + CRONUS International + + + Main Street, 14 + Birmingham + B27 4KT + + GB + + + + GB123456789 + + VAT + + + + CRONUS International + 123456789 + + + Jim Olive + JO@contoso.com + + + + + + 789456278 + + 8712345000004 + + + The Cannon Group PLC + + + 192 Market Square + Birmingham + B27 4KT + + GB + + + + GB789456278 + + VAT + + + + The Cannon Group PLC + + + + + + CRONUS International + + + GB123456789 + + + + 30 + 2026-02-22 + + GB12CPBK08929965044991 + + BG99999 + + + + + 1 Month/2% 8 days + + + 1000 + + 4000 + 1000 + + S + 25 + + VAT + + + + + + 14000 + 14000 + 14140 + 0 + 0.00 + 0 + 14140 + + + 10000 + Item + 1 + 4000 + + Bicycle + + 1000 + + + S + 25 + + VAT + + + + + 4000.00 + 1 + + + + 20000 + Item + 2 + 10000 + + Bicycle v2 + + 2000 + + + S + 25 + + VAT + + + + + 5000.00 + 2 + + + diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocE2ETest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocE2ETest.Codeunit.al index 70f194ea57..50d05d79ea 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocE2ETest.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocE2ETest.Codeunit.al @@ -1750,6 +1750,59 @@ codeunit 139624 "E-Doc E2E Test" Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be in In Progress status.'); end; + [Test] + procedure ImportPEPPOLInvoiceWithTextOnlyDocumentReferences() + var + EDocument: Record "E-Document"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + EDocImportParams: Record "E-Doc. Import Parameters"; + begin + // [SCENARIO] Import a PEPPOL invoice with AdditionalDocumentReference elements + // that have no child (text-only references). + // Previously this caused "Please choose a file to attach" error. + Initialize(Enum::"Service Integration"::"Mock"); + + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + WorkDate(DMY2Date(1, 1, 2027)); + Assert.IsTrue( + LibraryEDoc.CreateInboundPEPPOLDocumentToState( + EDocument, EDocumentService, 'peppol/peppol-invoice-textonly-docref.xml', EDocImportParams), + 'The e-document should be processed'); + + EDocument.Get(EDocument."Entry No"); + PurchaseHeader.Get(EDocument."Document Record ID"); + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + Assert.AreEqual(2, PurchaseLine.Count(), 'Expected 2 purchase lines to be imported.'); + end; + + [Test] + procedure ImportPEPPOLInvoiceWithHierarchicalLineIds() + var + EDocument: Record "E-Document"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + EDocImportParams: Record "E-Doc. Import Parameters"; + begin + // [SCENARIO] Import a PEPPOL invoice with non-integer line IDs (e.g., "1.1", "1.2"). + // Previously this caused "The value '1.1' can't be evaluated into type Integer" error. + Initialize(Enum::"Service Integration"::"Mock"); + + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + WorkDate(DMY2Date(1, 1, 2027)); + Assert.IsTrue( + LibraryEDoc.CreateInboundPEPPOLDocumentToState( + EDocument, EDocumentService, 'peppol/peppol-invoice-hierarchical-lineids.xml', EDocImportParams), + 'The e-document should be processed'); + + EDocument.Get(EDocument."Entry No"); + PurchaseHeader.Get(EDocument."Document Record ID"); + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + Assert.AreEqual(2, PurchaseLine.Count(), 'Expected 2 purchase lines to be imported.'); + end; + local procedure CheckPDFEmbedToXML(TempBlob: Codeunit "Temp Blob") var TempXMLBuffer: Record "XML Buffer" temporary; diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al index 98e4cd174b..e387307354 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al @@ -97,6 +97,7 @@ codeunit 139891 "E-Document Structured Tests" else Assert.Fail(EDocumentStatusNotUpdatedErr); end; + #endregion local procedure Initialize(Integration: Enum "Service Integration")