From 1266f030ea448901ec6e8f7e0351a49beacbb941 Mon Sep 17 00:00:00 2001 From: Andrius Andrulevicius Date: Mon, 23 Feb 2026 12:54:19 +0200 Subject: [PATCH 01/12] Adds E-Document support to digital voucher checks Introduces E-Document as a new check type for digital vouchers, enforcing automatic generation and linking for purchase documents. Updates UI logic to ensure consistent behavior and validation. Extends data structures and event handling to support E-Document attachment and integration with related processes. Enables more robust compliance with regulatory requirements for electronic document management. --- Apps/W1/EnforcedDigitalVouchers/app/app.json | 11 +- .../DigitalVoucherEntrySetup.Page.al | 18 +++ .../DigitalVoucherEntrySetup.Table.al | 17 ++ .../DigVoucherIncDocAttach.TableExt.al | 4 + .../DigitalVoucherCheckType.Enum.al | 5 + .../DigitalVoucherImpl.Codeunit.al | 61 +++++++- .../VoucherAttachmentCheck.Codeunit.al | 2 +- .../VoucherEDocumentCheck.Codeunit.al | 148 ++++++++++++++++++ .../digital-voucher.code-workspace | 14 ++ 9 files changed, 276 insertions(+), 4 deletions(-) create mode 100644 Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al create mode 100644 Apps/W1/EnforcedDigitalVouchers/digital-voucher.code-workspace diff --git a/Apps/W1/EnforcedDigitalVouchers/app/app.json b/Apps/W1/EnforcedDigitalVouchers/app/app.json index 8b7b46e2fd..455f6f9937 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/app.json +++ b/Apps/W1/EnforcedDigitalVouchers/app/app.json @@ -3,7 +3,7 @@ "name": "Enforced Digital Vouchers", "publisher": "Microsoft", "brief": "The Digital Vouchers extension makes it easy to generate digital version for every general ledger register.", - "description": "In some countries authorities require to make sure that for every single general ledger register ther is a digital vouchers assigned.", + "description": "In some countries authorities require to make sure that for every single general ledger register there is a digital vouchers assigned.", "version": "28.0.0.0", "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009", "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", @@ -11,7 +11,14 @@ "url": "https://go.microsoft.com/fwlink/?LinkId=724011", "logo": "ExtensionLogo.png", "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2204541", - "dependencies": [], + "dependencies": [ + { + "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b", + "name": "E-Document Core", + "publisher": "Microsoft", + "version": "28.0.0.0" + } + ], "screenshots": [], "platform": "28.0.0.0", "internalsVisibleTo": [ diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al index 32e88c136c..d7fd1aa5b6 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al @@ -23,16 +23,26 @@ page 5579 "Digital Voucher Entry Setup" field("Entry Type"; Rec."Entry Type") { ToolTip = 'Specifies the entry type.'; + trigger OnValidate() + begin + UpdateGenerateAutomaticallyEditable(); + end; } field("Check Type"; Rec."Check Type") { ToolTip = 'Specifies the check type.'; AboutTitle = 'Enter the check type'; AboutText = 'In case of check type None you can post this type of entry without any digital voucher. In case of check type Attachment you need to have an attachment to your entry. In case of check type Attachment or Note you can either have an attachment or a note for your entry.'; + + trigger OnValidate() + begin + UpdateGenerateAutomaticallyEditable(); + end; } field("Generate Automatically"; Rec."Generate Automatically") { ToolTip = 'Specifies if the digital voucher needs to be generated automatically.'; + Editable = GenerateAutomaticallyEditable; } field("Skip If Manually Added"; Rec."Skip If Manually Added") { @@ -89,6 +99,7 @@ page 5579 "Digital Voucher Entry Setup" var OpenedFromWizard: Boolean; + GenerateAutomaticallyEditable: Boolean; VoucherEntryTypeDescription: Text; GeneralJournalEntryDescriptionTxt: Label 'Specifies postings you are doing from the General Journal for all Account Types excluding those related to Customer and Vendor. By choosing one of those options, you will change control of the posting process. If you select the Customer as the Account Type, the system will check your setup related to the Sales Journal. If you select the Vendor as the Account Type, the system will check your setup related to the Purchase Journal.'; SalesJournalEntryDescriptionTxt: Label 'Specifies posting you are doing from the Sales Journal and the General Journal with the Customer selected as the Account Type.'; @@ -107,11 +118,13 @@ page 5579 "Digital Voucher Entry Setup" trigger OnAfterGetRecord() begin SetVoucherEntryTypeDescription(); + UpdateGenerateAutomaticallyEditable(); end; trigger OnAfterGetCurrRecord() begin SetVoucherEntryTypeDescription(); + UpdateGenerateAutomaticallyEditable(); end; procedure SetOpenFromGuide() @@ -142,6 +155,11 @@ page 5579 "Digital Voucher Entry Setup" end; end; + local procedure UpdateGenerateAutomaticallyEditable() + begin + GenerateAutomaticallyEditable := Rec."Check Type" <> Rec."Check Type"::"E-Document"; + end; + [IntegrationEvent(false, false)] local procedure OnBeforeSetVoucherEntryTypeDescription(var NewVoucherEntryTypeDescription: Text; var IsHandled: Boolean) begin diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al index 270499c647..0bc202014b 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al @@ -21,9 +21,26 @@ table 5579 "Digital Voucher Entry Setup" } field(2; "Check Type"; Enum "Digital Voucher Check Type") { + trigger OnValidate() + var + CheckTypeRequiresSalesOrPurchaseErr: Label '%1 %2 requires %3 to be %4 or %5.', Comment = '%1 - check type field caption, %2 - check type value, %3 - entry type field caption, %4 - entry type value, %5 - entry type value'; + begin + if "Check Type" = "Check Type"::"E-Document" then begin + if not ("Entry Type" in ["Entry Type"::"Sales Document", "Entry Type"::"Purchase Document"]) then + Error(CheckTypeRequiresSalesOrPurchaseErr, FieldCaption("Check Type"), "Check Type"::"E-Document", FieldCaption("Entry Type"), "Entry Type"::"Sales Document", "Entry Type"::"Purchase Document"); + "Generate Automatically" := true; + end; + end; } field(3; "Generate Automatically"; Boolean) { + trigger OnValidate() + var + GenerateAutoMustBeEnabledErr: Label 'Generate Automatically must be enabled when %1 is %2.', Comment = '%1 - check type field caption, %2 - check type value'; + begin + if ("Check Type" = "Check Type"::"E-Document") and (not "Generate Automatically") then + Error(GenerateAutoMustBeEnabledErr, FieldCaption("Check Type"), "Check Type"::"E-Document"); + end; } field(4; "Skip If Manually Added"; Boolean) { diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Extensions/DigVoucherIncDocAttach.TableExt.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Extensions/DigVoucherIncDocAttach.TableExt.al index a86976dc86..aef35ff27e 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Extensions/DigVoucherIncDocAttach.TableExt.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Extensions/DigVoucherIncDocAttach.TableExt.al @@ -12,5 +12,9 @@ tableextension 5582 "Dig. Voucher Inc. Doc. Attach." extends "Incoming Document { DataClassification = SystemMetadata; } + field(5583; "Is E-Document"; Boolean) + { + DataClassification = SystemMetadata; + } } } \ No newline at end of file diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherCheckType.Enum.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherCheckType.Enum.al index e522f49d46..528ffcba8c 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherCheckType.Enum.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherCheckType.Enum.al @@ -26,4 +26,9 @@ enum 5580 "Digital Voucher Check Type" implements "Digital Voucher Check" Caption = 'Attachment or Note'; Implementation = "Digital Voucher Check" = "Voucher Attach Or Note Check"; } + value(3; "E-Document") + { + Caption = 'E-Document'; + Implementation = "Digital Voucher Check" = "Voucher E-Document Check"; + } } diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al index 0868541b1e..7bbd4c433a 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al @@ -246,7 +246,7 @@ codeunit 5579 "Digital Voucher Impl." exit; DataTypeManagement.GetRecordRef(RelatedRecord, RecRef); if DigitalVoucherFeature.IsDigitalVoucherEnabledForTableNumber(RecRef.Number) then - error(CannotChangeIncomDocWithEnforcedDigitalVoucherErr); + Error(CannotChangeIncomDocWithEnforcedDigitalVoucherErr); end; procedure GetDigitalVoucherEntrySetup(var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; EntryType: Enum "Digital Voucher Entry Type"): Boolean @@ -266,6 +266,40 @@ codeunit 5579 "Digital Voucher Impl." exit(not IncomingDocumentAttachment.IsEmpty()); end; + /// + /// Attaches the exported E-Document XML file as an Incoming Document Attachment to the source document. + /// Only attaches for Sales Invoice Header when the "Attach Sales E-Document" setting is enabled. + /// + /// The E-Document being exported + /// The source document (e.g., Sales Invoice Header) + /// The blob containing the exported E-Document content + internal procedure AttachIncomingDocumentOnExport(EDocument: Record "E-Document"; SourceDocumentHeader: RecordRef; var TempBlob: Codeunit "Temp Blob") + var + IncomingDocumentAttachment: Record "Incoming Document Attachment"; + ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; + EDocumentHelper: Codeunit "E-Document Processing"; + RecordLinkTxt: Text; + FileNameTok: Label 'E-Document_%1.xml', Locked = true; + begin + if not (EDocument."Document Type" in [ + EDocument."Document Type"::"Sales Invoice", + EDocument."Document Type"::"Sales Credit Memo", + EDocument."Document Type"::"Sales Order", + EDocument."Document Type"::"Sales Quote", + EDocument."Document Type"::"Sales Return Order"]) then + exit; + + RecordLinkTxt := EDocumentHelper.GetRecordLinkText(EDocument); + IncomingDocumentAttachment.SetRange("Document No.", EDocument."Document No."); + IncomingDocumentAttachment.SetRange("Posting Date", EDocument."Posting Date"); + IncomingDocumentAttachment.SetContentFromBlob(TempBlob); + if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, StrSubstNo(FileNameTok, RecordLinkTxt), TempBlob) then + exit; + + IncomingDocumentAttachment."Is E-Document" := true; + IncomingDocumentAttachment.Modify(false); + end; + local procedure FilterIncomingDocumentRecordFromRecordRef(var IncomingDocumentAttachment: Record "Incoming Document Attachment"; var IncomingDocument: Record "Incoming Document"; MainRecordRef: RecordRef): Boolean begin Clear(IncomingDocument); @@ -677,6 +711,7 @@ codeunit 5579 "Digital Voucher Impl." local procedure ExcludeDigitalVouchersOnAttachIncomingDocumentsOnAfterSetFilter(var IncomingDocumentAttachment: Record "Incoming Document Attachment") begin IncomingDocumentAttachment.SetRange("Is Digital Voucher", false); + IncomingDocumentAttachment.SetRange("Is E-Document", false); end; [EventSubscriber(ObjectType::Table, Database::"Digital Voucher Setup", 'OnBeforeDeleteEvent', '', false, false)] @@ -699,6 +734,30 @@ codeunit 5579 "Digital Voucher Impl." CopyDigitalVoucherToCorrectiveDocument("Digital Voucher Entry Type"::"Sales Document", SalesInvoiceHeader, SalesHeader."No.", SalesHeader."Posting Date"); end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Export", OnExportEDocumentAfterCreateEDocument, '', false, false)] + local procedure EDocExportOnExportEDocumentAfterCreateEDocument(EDocument: Record "E-Document"; SourceDocumentHeaderMapped: RecordRef; SourceDocumentLineMapped: RecordRef; var TempBlob: Codeunit "Temp Blob"; Success: Boolean) + begin + if not Success then + exit; + + AttachIncomingDocumentOnExport(EDocument, SourceDocumentHeaderMapped, TempBlob); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Document Subscribers", OnAfterUpdateToPostedPurchaseEDocument, '', false, false)] + local procedure EDocumentSubscribers_OnAfterUpdateToPostedPurchaseEDocument(var EDocument: Record "E-Document"; PostedRecord: Variant; DocumentType: Enum "E-Document Type") + var + PurchInvHeader: Record "Purch. Inv. Header"; + RecRef: RecordRef; + VoucherEDocumentCheck: Codeunit "Voucher E-Document Check"; + begin + if DocumentType <> DocumentType::"Purchase Invoice" then + exit; + + RecRef.GetTable(PostedRecord); + RecRef.SetTable(PurchInvHeader); + VoucherEDocumentCheck.AttachToIncomingDocument(EDocument, PurchInvHeader."No.", PurchInvHeader."Posting Date"); + end; + [IntegrationEvent(false, false)] local procedure OnGenerateDigitalVoucherForDocumentOnCaseElse(RecRef: RecordRef) begin diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherAttachmentCheck.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherAttachmentCheck.Codeunit.al index 575070c823..bdc0daa3e1 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherAttachmentCheck.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherAttachmentCheck.Codeunit.al @@ -22,7 +22,7 @@ codeunit 5580 "Voucher Attachment Check" implements "Digital Voucher Check" if (DigitalVoucherEntryType in [DigitalVoucherEntryType::"General Journal", DigitalVoucherEntryType::"Purchase Journal", DigitalVoucherEntryType::"Sales Journal"]) or (RecRef.Number() = Database::"Service Header") then - error(NotPossibleToPostWithoutVoucherErr); + Error(NotPossibleToPostWithoutVoucherErr); ErrorMessageMgt.LogSimpleErrorMessage(NotPossibleToPostWithoutVoucherErr); end; diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al new file mode 100644 index 0000000000..fe7427d77f --- /dev/null +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al @@ -0,0 +1,148 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.EServices.EDocument; + +using Microsoft.Purchases.History; +using System.IO; +using System.Utilities; + +codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" +{ + Access = Internal; + + internal procedure CheckVoucherIsAttachedToDocument(var ErrorMessageMgt: Codeunit "Error Message Management"; DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef) + var + EDocument: Record "E-Document"; + EDocumentLinkGuid: Guid; + NotPossibleToPostWithoutEDocumentErr: Label 'Not possible to post without linking an E-Document.'; + begin + if DigitalVoucherEntryType = DigitalVoucherEntryType::"Sales Document" then + exit; + + if DigitalVoucherEntryType <> DigitalVoucherEntryType::"Purchase Document" then + exit; + + EDocumentLinkGuid := GetEDocumentLinkFromPurchaseHeader(RecRef); + + if IsNullGuid(EDocumentLinkGuid) then begin + ErrorMessageMgt.LogSimpleErrorMessage(NotPossibleToPostWithoutEDocumentErr); + exit; + end; + + EDocument.SetRange(SystemId, EDocumentLinkGuid); + if EDocument.FindFirst() then + exit; + + ErrorMessageMgt.LogSimpleErrorMessage(NotPossibleToPostWithoutEDocumentErr); + end; + + internal procedure GenerateDigitalVoucherForPostedDocument(DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef) + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + PurchInvHeader: Record "Purch. Inv. Header"; + PurchCrMemoHeader: Record "Purch. Cr. Memo Hdr."; + EDocument: Record "E-Document"; + DigitalVoucherImpl: Codeunit "Digital Voucher Impl."; + DigitalVoucherCheck: Interface "Digital Voucher Check"; + begin + DigitalVoucherImpl.GetDigitalVoucherEntrySetup(DigitalVoucherEntrySetup, DigitalVoucherEntryType); + DigitalVoucherCheck := DigitalVoucherEntrySetup."Check Type"::Attachment; + DigitalVoucherCheck.GenerateDigitalVoucherForPostedDocument(DigitalVoucherEntrySetup."Entry Type", RecRef); + end; + + local procedure GetEDocumentLinkFromPurchaseHeader(RecRef: RecordRef): Guid + var + EDocumentLinkFieldRef: FieldRef; + NullGuid: Guid; + FieldIndex: Integer; + begin + for FieldIndex := 1 to RecRef.FieldCount do begin + EDocumentLinkFieldRef := RecRef.FieldIndex(FieldIndex); + if EDocumentLinkFieldRef.Name = 'E-Document Link' then + exit(EDocumentLinkFieldRef.Value()); + end; + exit(NullGuid); + end; + + /// + /// Attaches the original E-Document file to the Incoming Document of a Purchase Header. + /// Creates an Incoming Document Attachment and links it to the Purchase Header. + /// + /// The E-Document containing the file to attach + /// The document number to attach the incoming document to + /// The posting date of the document + internal procedure AttachToIncomingDocument(EDocument: Record "E-Document"; DocumentNo: Code[20]; PostingDate: Date) + var + EDocDataStorage: Record "E-Doc. Data Storage"; + IncomingDocumentAttachment: Record "Incoming Document Attachment"; + EDocumentService: Record "E-Document Service"; + ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; + TempBlob: Codeunit "Temp Blob"; + FileName: Text; + EDocumentFileNameLbl: Label 'E-Document_%1.%2', Comment = '%1 = E-Document Entry No., %2 = File Format', Locked = true; + begin + if not (EDocument."Document Type" in [ + EDocument."Document Type"::"Purchase Invoice", + EDocument."Document Type"::"Purchase Credit Memo", + EDocument."Document Type"::"Purchase Order", + EDocument."Document Type"::"Purchase Quote", + EDocument."Document Type"::"Purchase Return Order"]) then + exit; + + EDocumentService.Get(EDocument.Service); + if EDocument."Unstructured Data Entry No." = 0 then + exit; + + if not EDocDataStorage.Get(EDocument."Unstructured Data Entry No.") then + exit; + + TempBlob := EDocDataStorage.GetTempBlob(); + if not TempBlob.HasValue() then + exit; + + if EDocument."File Name" <> '' then + FileName := EDocument."File Name" + else + FileName := StrSubstNo(EDocumentFileNameLbl, EDocument."Entry No", EDocDataStorage."File Format"); + + IncomingDocumentAttachment.SetRange("Document No.", DocumentNo); + IncomingDocumentAttachment.SetRange("Posting Date", PostingDate); + IncomingDocumentAttachment.SetContentFromBlob(TempBlob); + + if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, FileName, TempBlob) then + exit; + + IncomingDocumentAttachment."Is E-Document" := true; + IncomingDocumentAttachment.Modify(false); + + if EDocDataStorage."File Format" = EDocDataStorage."File Format"::PDF then begin + FileName := StrSubstNo(EDocumentFileNameLbl, EDocument."Entry No", EDocDataStorage."File Format"::XML); + ExtractXMLFromPDF(TempBlob, FileName, IncomingDocumentAttachment); + end; + end; + + local procedure ExtractXMLFromPDF(var TempBlob: Codeunit System.Utilities."Temp Blob"; FileName: Text; var IncomingDocumentAttachment: Record "Incoming Document Attachment") + var + PDFDocument: Codeunit "PDF Document"; + ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; + ExtractedXmlBlob: Codeunit "Temp Blob"; + PdfInStream: InStream; + begin + TempBlob.CreateInStream(PdfInStream); + if not PDFDocument.GetDocumentAttachmentStream(PdfInStream, ExtractedXmlBlob) then + exit; + + if not ExtractedXmlBlob.HasValue() then + exit; + + IncomingDocumentAttachment.Default := false; + IncomingDocumentAttachment."Main Attachment" := false; + if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, FileName, ExtractedXmlBlob) then + exit; + + IncomingDocumentAttachment."Is E-Document" := true; + IncomingDocumentAttachment.Modify(false); + end; +} diff --git a/Apps/W1/EnforcedDigitalVouchers/digital-voucher.code-workspace b/Apps/W1/EnforcedDigitalVouchers/digital-voucher.code-workspace new file mode 100644 index 0000000000..c3cf87c90e --- /dev/null +++ b/Apps/W1/EnforcedDigitalVouchers/digital-voucher.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "app" + }, + { + "path": "test" + }, + { + "path": "test library" + } + ], + "settings": {} +} \ No newline at end of file From efdcbbeb34737ef15bc2cefa8590ec264a0d5ddf Mon Sep 17 00:00:00 2001 From: Andrius Andrulevicius Date: Tue, 24 Feb 2026 11:35:00 +0200 Subject: [PATCH 02/12] Enforces feature checks for digital voucher attachments Adds feature flag and setup validations before attaching digital vouchers to documents, ensuring attachments only occur when the functionality is enabled and properly configured. Expands handling for purchase credit memos and improves type checks for both sales and purchase documents. --- .../DigitalVoucherImpl.Codeunit.al | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al index 7bbd4c433a..6393ef615d 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al @@ -273,14 +273,22 @@ codeunit 5579 "Digital Voucher Impl." /// The E-Document being exported /// The source document (e.g., Sales Invoice Header) /// The blob containing the exported E-Document content - internal procedure AttachIncomingDocumentOnExport(EDocument: Record "E-Document"; SourceDocumentHeader: RecordRef; var TempBlob: Codeunit "Temp Blob") + internal procedure AttachIncomingEDocument(EDocument: Record "E-Document"; SourceDocumentHeader: RecordRef; var TempBlob: Codeunit "Temp Blob") var IncomingDocumentAttachment: Record "Incoming Document Attachment"; + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; EDocumentHelper: Codeunit "E-Document Processing"; RecordLinkTxt: Text; FileNameTok: Label 'E-Document_%1.xml', Locked = true; begin + if not DigitalVoucherFeature.IsFeatureEnabled() then + exit; + + GetDigitalVoucherEntrySetup(DigitalVoucherEntrySetup, "Digital Voucher Entry Type"::"Sales Document"); + if DigitalVoucherEntrySetup."Check Type" <> DigitalVoucherEntrySetup."Check Type"::"E-Document" then + exit; + if not (EDocument."Document Type" in [ EDocument."Document Type"::"Sales Invoice", EDocument."Document Type"::"Sales Credit Memo", @@ -739,23 +747,47 @@ codeunit 5579 "Digital Voucher Impl." begin if not Success then exit; - - AttachIncomingDocumentOnExport(EDocument, SourceDocumentHeaderMapped, TempBlob); + + AttachIncomingEDocument(EDocument, SourceDocumentHeaderMapped, TempBlob); end; [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Document Subscribers", OnAfterUpdateToPostedPurchaseEDocument, '', false, false)] local procedure EDocumentSubscribers_OnAfterUpdateToPostedPurchaseEDocument(var EDocument: Record "E-Document"; PostedRecord: Variant; DocumentType: Enum "E-Document Type") var PurchInvHeader: Record "Purch. Inv. Header"; + PurchCrMemoHdr: Record "Purch. Cr. Memo Hdr."; + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; RecRef: RecordRef; VoucherEDocumentCheck: Codeunit "Voucher E-Document Check"; begin - if DocumentType <> DocumentType::"Purchase Invoice" then + if not DigitalVoucherFeature.IsFeatureEnabled() then + exit; + + GetDigitalVoucherEntrySetup(DigitalVoucherEntrySetup, "Digital Voucher Entry Type"::"Purchase Document"); + if DigitalVoucherEntrySetup."Check Type" <> DigitalVoucherEntrySetup."Check Type"::"E-Document" then + exit; + + if not (EDocument."Document Type" in [ + EDocument."Document Type"::"Purchase Invoice", + EDocument."Document Type"::"Purchase Credit Memo", + EDocument."Document Type"::"Purchase Order", + EDocument."Document Type"::"Purchase Quote", + EDocument."Document Type"::"Purchase Return Order"]) then exit; RecRef.GetTable(PostedRecord); - RecRef.SetTable(PurchInvHeader); - VoucherEDocumentCheck.AttachToIncomingDocument(EDocument, PurchInvHeader."No.", PurchInvHeader."Posting Date"); + case DocumentType of + DocumentType::"Purchase Invoice": + begin + RecRef.SetTable(PurchInvHeader); + VoucherEDocumentCheck.AttachToIncomingDocument(EDocument, PurchInvHeader."No.", PurchInvHeader."Posting Date"); + end; + DocumentType::"Purchase Credit Memo": + begin + RecRef.SetTable(PurchCrMemoHdr); + VoucherEDocumentCheck.AttachToIncomingDocument(EDocument, PurchCrMemoHdr."No.", PurchCrMemoHdr."Posting Date"); + end; + end; end; [IntegrationEvent(false, false)] From 3722878b201d2dcd7b4448f29bbe89447bc937f1 Mon Sep 17 00:00:00 2001 From: Andrius Andrulevicius Date: Tue, 24 Feb 2026 13:33:49 +0200 Subject: [PATCH 03/12] Refactors E-Document attachment logic for vouchers Streamlines digital voucher validation by generalizing E-Document attachment to support more document types and rely on record references rather than hardcoded field access or document types. Improves maintainability and feature extensibility by extracting document details via a dedicated method and refining permission set object ordering. Enhances code clarity and robustness in preparation for feature expansion. --- .../DigitalVoucherObjects.PermissionSet.al | 17 +++--- .../DigitalVoucherImpl.Codeunit.al | 19 +++---- .../VoucherEDocumentCheck.Codeunit.al | 56 ++++++++++--------- 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/Permissions/DigitalVoucherObjects.PermissionSet.al b/Apps/W1/EnforcedDigitalVouchers/app/Permissions/DigitalVoucherObjects.PermissionSet.al index b9ed6c23e5..c23da3955c 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/Permissions/DigitalVoucherObjects.PermissionSet.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/Permissions/DigitalVoucherObjects.PermissionSet.al @@ -12,15 +12,16 @@ permissionset 5583 "Digital Voucher - Objects" Permissions = table "Digital Voucher Entry Setup" = X, table "Digital Voucher Setup" = X, table "Voucher Entry Source Code" = X, - page "Digital Voucher Entry Setup" = X, - page "Digital Voucher Setup" = X, - page "Digital Voucher Guide" = X, - page "Voucher Entry Source Codes" = X, - codeunit "Voucher Attach Or Note Check" = X, + codeunit "Digital Voucher Entry" = X, + codeunit "Digital Voucher Feature" = X, + codeunit "Digital Voucher Impl." = X, codeunit "Voucher Attachment Check" = X, + codeunit "Voucher Attach Or Note Check" = X, + codeunit "Voucher E-Document Check" = X, codeunit "Voucher No Check" = X, codeunit "Voucher Unknown Check" = X, - codeunit "Digital Voucher Impl." = X, - codeunit "Digital Voucher Feature" = X, - codeunit "Digital Voucher Entry" = X; + page "Digital Voucher Entry Setup" = X, + page "Digital Voucher Guide" = X, + page "Digital Voucher Setup" = X, + page "Voucher Entry Source Codes" = X; } diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al index 6393ef615d..6a32b80c43 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.EServices.EDocument; +using Microsoft.EServices.EDocument; using Microsoft.Finance.GeneralLedger.Journal; using Microsoft.Finance.GeneralLedger.Ledger; using Microsoft.Finance.GeneralLedger.Posting; @@ -759,6 +760,10 @@ codeunit 5579 "Digital Voucher Impl." DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; RecRef: RecordRef; VoucherEDocumentCheck: Codeunit "Voucher E-Document Check"; + DigitalVoucherEntry: Codeunit "Digital Voucher Entry"; + DocType: Text; + DocNo: Code[20]; + PostingDate: Date; begin if not DigitalVoucherFeature.IsFeatureEnabled() then exit; @@ -776,18 +781,8 @@ codeunit 5579 "Digital Voucher Impl." exit; RecRef.GetTable(PostedRecord); - case DocumentType of - DocumentType::"Purchase Invoice": - begin - RecRef.SetTable(PurchInvHeader); - VoucherEDocumentCheck.AttachToIncomingDocument(EDocument, PurchInvHeader."No.", PurchInvHeader."Posting Date"); - end; - DocumentType::"Purchase Credit Memo": - begin - RecRef.SetTable(PurchCrMemoHdr); - VoucherEDocumentCheck.AttachToIncomingDocument(EDocument, PurchCrMemoHdr."No.", PurchCrMemoHdr."Posting Date"); - end; - end; + DigitalVoucherEntry.GetDocNoAndPostingDateFromRecRef(DocType, DocNo, PostingDate, RecRef); + VoucherEDocumentCheck.AttachEDocument(EDocument, DocNo, PostingDate); end; [IntegrationEvent(false, false)] diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al index fe7427d77f..caf580ab59 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.EServices.EDocument; +using Microsoft.Purchases.Document; using Microsoft.Purchases.History; using System.IO; using System.Utilities; @@ -12,32 +13,45 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" { Access = Internal; + /// + /// Validates that an E-Document is attached to the document before posting. + /// Only applies to Purchase Documents when the Digital Voucher feature is enabled and Check Type is set to E-Document. + /// + /// Error message management for logging validation errors + /// The type of digital voucher entry being validated + /// Record reference to the document being validated internal procedure CheckVoucherIsAttachedToDocument(var ErrorMessageMgt: Codeunit "Error Message Management"; DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef) var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; EDocument: Record "E-Document"; - EDocumentLinkGuid: Guid; + PurchaseHeader: Record "Purchase Header"; + DigitalVoucherFeature: Codeunit "Digital Voucher Feature"; + DigitalVoucherImpl: Codeunit "Digital Voucher Impl."; NotPossibleToPostWithoutEDocumentErr: Label 'Not possible to post without linking an E-Document.'; begin - if DigitalVoucherEntryType = DigitalVoucherEntryType::"Sales Document" then + if DigitalVoucherEntryType <> DigitalVoucherEntryType::"Purchase Document" then exit; - if DigitalVoucherEntryType <> DigitalVoucherEntryType::"Purchase Document" then + if not DigitalVoucherFeature.IsFeatureEnabled() then exit; - EDocumentLinkGuid := GetEDocumentLinkFromPurchaseHeader(RecRef); + DigitalVoucherImpl.GetDigitalVoucherEntrySetup(DigitalVoucherEntrySetup, DigitalVoucherEntryType); + if DigitalVoucherEntrySetup."Check Type" <> DigitalVoucherEntrySetup."Check Type"::"E-Document" then + exit; - if IsNullGuid(EDocumentLinkGuid) then begin + EDocument.SetRange("Document Record ID", RecRef.RecordId()); + if not EDocument.FindFirst() then begin ErrorMessageMgt.LogSimpleErrorMessage(NotPossibleToPostWithoutEDocumentErr); exit; end; - - EDocument.SetRange(SystemId, EDocumentLinkGuid); - if EDocument.FindFirst() then - exit; - - ErrorMessageMgt.LogSimpleErrorMessage(NotPossibleToPostWithoutEDocumentErr); end; + /// + /// Generates a digital voucher for a posted document by delegating to the Attachment check type implementation. + /// This procedure retrieves the digital voucher entry setup and invokes the attachment-based voucher generation. + /// + /// The type of digital voucher entry for the posted document + /// Record reference to the posted document internal procedure GenerateDigitalVoucherForPostedDocument(DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef) var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; @@ -52,28 +66,16 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" DigitalVoucherCheck.GenerateDigitalVoucherForPostedDocument(DigitalVoucherEntrySetup."Entry Type", RecRef); end; - local procedure GetEDocumentLinkFromPurchaseHeader(RecRef: RecordRef): Guid - var - EDocumentLinkFieldRef: FieldRef; - NullGuid: Guid; - FieldIndex: Integer; - begin - for FieldIndex := 1 to RecRef.FieldCount do begin - EDocumentLinkFieldRef := RecRef.FieldIndex(FieldIndex); - if EDocumentLinkFieldRef.Name = 'E-Document Link' then - exit(EDocumentLinkFieldRef.Value()); - end; - exit(NullGuid); - end; - /// /// Attaches the original E-Document file to the Incoming Document of a Purchase Header. /// Creates an Incoming Document Attachment and links it to the Purchase Header. + /// If the E-Document is a PDF with embedded XML, also extracts and attaches the XML content. + /// Only processes Purchase Invoice, Credit Memo, Order, Quote, and Return Order document types. /// - /// The E-Document containing the file to attach + /// The E-Document record containing the file to attach /// The document number to attach the incoming document to /// The posting date of the document - internal procedure AttachToIncomingDocument(EDocument: Record "E-Document"; DocumentNo: Code[20]; PostingDate: Date) + internal procedure AttachEDocument(EDocument: Record "E-Document"; DocumentNo: Code[20]; PostingDate: Date) var EDocDataStorage: Record "E-Doc. Data Storage"; IncomingDocumentAttachment: Record "Incoming Document Attachment"; From 7a0e6d46c37ef6386a3e2f38ce0c3315877247b4 Mon Sep 17 00:00:00 2001 From: Andrius Andrulevicius Date: Wed, 25 Feb 2026 14:24:12 +0200 Subject: [PATCH 04/12] Adds E-Document attachment unit tests and refines checks Introduces comprehensive automated tests covering E-Document attachment behavior for sales and purchase documents, ensuring correct enforcement and attachment logic. Updates dependency declarations for better test isolation, removes unused variables, and improves error checking and file name handling for E-Document processing. Enhances reliability and maintainability of digital voucher and E-Document integration. --- Apps/W1/EnforcedDigitalVouchers/app/app.json | 15 +- .../DigitalVoucherImpl.Codeunit.al | 6 +- .../VoucherEDocumentCheck.Codeunit.al | 14 +- Apps/W1/EnforcedDigitalVouchers/test/app.json | 12 +- .../test/src/EDocAttachmentTests.Codeunit.al | 449 ++++++++++++++++++ 5 files changed, 475 insertions(+), 21 deletions(-) create mode 100644 Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al diff --git a/Apps/W1/EnforcedDigitalVouchers/app/app.json b/Apps/W1/EnforcedDigitalVouchers/app/app.json index 455f6f9937..e333206f55 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/app.json +++ b/Apps/W1/EnforcedDigitalVouchers/app/app.json @@ -13,10 +13,10 @@ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2204541", "dependencies": [ { - "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b", - "name": "E-Document Core", - "publisher": "Microsoft", - "version": "28.0.0.0" + "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b", + "name": "E-Document Core", + "publisher": "Microsoft", + "version": "28.0.0.0" } ], "screenshots": [], @@ -27,6 +27,11 @@ "name": "Enforced Digital Vouchers Test Library", "publisher": "Microsoft" }, + { + "id": "f0eb8756-ea72-4ef8-b0de-686d2a44b259", + "name": "Enforced Digital Vouchers Tests", + "publisher": "Microsoft" + }, { "id": "bb837764-d7cc-4b7b-898a-3ea5a1fab62f", "name": "Enforced Digital Vouchers (DK)", @@ -49,4 +54,4 @@ }, "application": "28.0.0.0", "target": "Cloud" -} +} \ No newline at end of file diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al index 6a32b80c43..c083526298 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al @@ -4,7 +4,6 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.EServices.EDocument; -using Microsoft.EServices.EDocument; using Microsoft.Finance.GeneralLedger.Journal; using Microsoft.Finance.GeneralLedger.Ledger; using Microsoft.Finance.GeneralLedger.Posting; @@ -755,12 +754,9 @@ codeunit 5579 "Digital Voucher Impl." [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Document Subscribers", OnAfterUpdateToPostedPurchaseEDocument, '', false, false)] local procedure EDocumentSubscribers_OnAfterUpdateToPostedPurchaseEDocument(var EDocument: Record "E-Document"; PostedRecord: Variant; DocumentType: Enum "E-Document Type") var - PurchInvHeader: Record "Purch. Inv. Header"; - PurchCrMemoHdr: Record "Purch. Cr. Memo Hdr."; DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; - RecRef: RecordRef; VoucherEDocumentCheck: Codeunit "Voucher E-Document Check"; - DigitalVoucherEntry: Codeunit "Digital Voucher Entry"; + RecRef: RecordRef; DocType: Text; DocNo: Code[20]; PostingDate: Date; diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al index caf580ab59..1fc3ae0904 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al @@ -24,7 +24,6 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; EDocument: Record "E-Document"; - PurchaseHeader: Record "Purchase Header"; DigitalVoucherFeature: Codeunit "Digital Voucher Feature"; DigitalVoucherImpl: Codeunit "Digital Voucher Impl."; NotPossibleToPostWithoutEDocumentErr: Label 'Not possible to post without linking an E-Document.'; @@ -40,7 +39,7 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" exit; EDocument.SetRange("Document Record ID", RecRef.RecordId()); - if not EDocument.FindFirst() then begin + if EDocument.IsEmpty() then begin ErrorMessageMgt.LogSimpleErrorMessage(NotPossibleToPostWithoutEDocumentErr); exit; end; @@ -55,9 +54,6 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" internal procedure GenerateDigitalVoucherForPostedDocument(DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef) var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; - PurchInvHeader: Record "Purch. Inv. Header"; - PurchCrMemoHeader: Record "Purch. Cr. Memo Hdr."; - EDocument: Record "E-Document"; DigitalVoucherImpl: Codeunit "Digital Voucher Impl."; DigitalVoucherCheck: Interface "Digital Voucher Check"; begin @@ -79,10 +75,9 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" var EDocDataStorage: Record "E-Doc. Data Storage"; IncomingDocumentAttachment: Record "Incoming Document Attachment"; - EDocumentService: Record "E-Document Service"; ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; TempBlob: Codeunit "Temp Blob"; - FileName: Text; + FileName: Text[250]; EDocumentFileNameLbl: Label 'E-Document_%1.%2', Comment = '%1 = E-Document Entry No., %2 = File Format', Locked = true; begin if not (EDocument."Document Type" in [ @@ -93,7 +88,6 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" EDocument."Document Type"::"Purchase Return Order"]) then exit; - EDocumentService.Get(EDocument.Service); if EDocument."Unstructured Data Entry No." = 0 then exit; @@ -105,7 +99,7 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" exit; if EDocument."File Name" <> '' then - FileName := EDocument."File Name" + FileName := CopyStr(EDocument."File Name",1,MaxStrLen(FileName)) else FileName := StrSubstNo(EDocumentFileNameLbl, EDocument."Entry No", EDocDataStorage."File Format"); @@ -125,7 +119,7 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" end; end; - local procedure ExtractXMLFromPDF(var TempBlob: Codeunit System.Utilities."Temp Blob"; FileName: Text; var IncomingDocumentAttachment: Record "Incoming Document Attachment") + local procedure ExtractXMLFromPDF(var TempBlob: Codeunit System.Utilities."Temp Blob"; FileName: Text[250]; var IncomingDocumentAttachment: Record "Incoming Document Attachment") var PDFDocument: Codeunit "PDF Document"; ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; diff --git a/Apps/W1/EnforcedDigitalVouchers/test/app.json b/Apps/W1/EnforcedDigitalVouchers/test/app.json index 6d1ab6e862..827efd2244 100644 --- a/Apps/W1/EnforcedDigitalVouchers/test/app.json +++ b/Apps/W1/EnforcedDigitalVouchers/test/app.json @@ -19,6 +19,12 @@ "publisher": "Microsoft", "version": "28.0.0.0" }, + { + "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b", + "name": "E-Document Core", + "publisher": "Microsoft", + "version": "28.0.0.0" + }, { "id": "928f7b70-0dbd-431a-beb5-f45c4adbd361", "name": "Enforced Digital Vouchers Test Library", @@ -48,6 +54,10 @@ { "from": 139515, "to": 139516 + }, + { + "from": 139519, + "to": 139519 } ], "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2141039", @@ -60,4 +70,4 @@ "features": [ "TranslationFile" ] -} +} \ No newline at end of file diff --git a/Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al new file mode 100644 index 0000000000..4041dda809 --- /dev/null +++ b/Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al @@ -0,0 +1,449 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Test.EServices.EDocument; + +using Microsoft.eServices.EDocument; +using Microsoft.Foundation.Address; +using Microsoft.Purchases.Document; +using Microsoft.Purchases.History; +using Microsoft.Sales.Customer; +using Microsoft.Sales.Document; +using Microsoft.Sales.History; +using Microsoft.Tests.EServices.EDocument; + +codeunit 139519 "E-Doc. Attachment Tests" +{ + Subtype = Test; + TestPermissions = Disabled; + + var + LibraryTestInitialize: Codeunit "Library - Test Initialize"; + LibraryPurchase: Codeunit "Library - Purchase"; + LibrarySales: Codeunit "Library - Sales"; + LibraryInventory: Codeunit "Library - Inventory"; + LibraryRandom: Codeunit "Library - Random"; + LibraryERM: Codeunit "Library - ERM"; + Assert: Codeunit Assert; + IsInitialized: Boolean; + NotPossibleToPostWithoutEDocumentErr: Label 'Not possible to post without linking an E-Document.'; + DialogErrorCodeTok: Label 'Dialog', Locked = true; + + trigger OnRun() + begin + // [FEATURE] [Digital Voucher] [E-Document] + end; + + [Test] + procedure GenerateAutomaticallyEnabledWhenCheckTypeSetToEDocument() + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + // [SCENARIO] Generate Automatically is auto-enabled when Check Type is set to E-Document + Initialize(); + + // [GIVEN] Setup record for Purchase Document with Generate Automatically = false + DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Purchase Document"; + DigitalVoucherEntrySetup."Generate Automatically" := false; + + // [WHEN] Validate Check Type = E-Document + DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + + // [THEN] Generate Automatically is automatically set to true + DigitalVoucherEntrySetup.TestField("Generate Automatically", true); + end; + + [Test] + procedure GenerateAutomaticallyCannotBeDisabledForEDocument() + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + // [SCENARIO] Generate Automatically cannot be disabled when Check Type = E-Document + Initialize(); + + // [GIVEN] Entry Type = Purchase Document, Check Type = E-Document + DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Purchase Document"; + DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + + // [WHEN] Try to disable Generate Automatically + asserterror DigitalVoucherEntrySetup.Validate("Generate Automatically", false); + + // [THEN] Error: Generate Automatically must be enabled when Check Type is E-Document + Assert.ExpectedError('Generate Automatically must be enabled'); + end; + + [Test] + procedure EDocumentCheckTypeNotAllowedForGeneralJournal() + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + // [SCENARIO] E-Document check type is not allowed for General Journal + Initialize(); + + // [GIVEN] Entry Type = General Journal + DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"General Journal"; + + // [WHEN] Validate Check Type = E-Document + asserterror DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + + // [THEN] Error mentioning E-Document requires Sales Document or Purchase Document + Assert.ExpectedError('E-Document'); + end; + + [Test] + procedure EDocumentCheckTypeNotAllowedForSalesJournal() + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + // [SCENARIO] E-Document check type is not allowed for Sales Journal + Initialize(); + + // [GIVEN] Entry Type = Sales Journal + DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Sales Journal"; + + // [WHEN] Validate Check Type = E-Document + asserterror DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + + // [THEN] Error mentioning E-Document requires Sales Document or Purchase Document + Assert.ExpectedError('E-Document'); + end; + + [Test] + procedure EDocumentCheckTypeNotAllowedForPurchaseJournal() + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + // [SCENARIO] E-Document check type is not allowed for Purchase Journal + Initialize(); + + // [GIVEN] Entry Type = Purchase Journal + DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Purchase Journal"; + + // [WHEN] Validate Check Type = E-Document + asserterror DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + + // [THEN] Error mentioning E-Document requires Sales Document or Purchase Document + Assert.ExpectedError('E-Document'); + end; + + [Test] + procedure EDocumentCheckTypeAllowedForSalesDocument() + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + // [SCENARIO] E-Document check type is allowed for Sales Document + Initialize(); + + // [WHEN] Entry Type = Sales Document and Check Type = E-Document + DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Sales Document"; + DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + + // [THEN] No error, Check Type is set successfully + DigitalVoucherEntrySetup.TestField("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + end; + + [Test] + procedure EDocumentCheckTypeAllowedForPurchaseDocument() + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + // [SCENARIO] E-Document check type is allowed for Purchase Document + Initialize(); + + // [WHEN] Entry Type = Purchase Document and Check Type = E-Document + DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Purchase Document"; + DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + + // [THEN] No error, Check Type is set successfully + DigitalVoucherEntrySetup.TestField("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + end; + + [Test] + procedure PurchaseInvoiceCannotPostWithoutEDocument() + var + PurchaseHeader: Record "Purchase Header"; + DummyEDocument: Record "E-Document"; + DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; + begin + // [SCENARIO] Purchase invoice posting fails when E-Document check is enabled but no e-document linked + Initialize(); + BindSubscription(DigVouchersDisableEnforce); + EnableDigitalVoucherFeature(); + + // [GIVEN] Digital voucher entry setup for purchase document is "E-Document" + InitSetupEDocument("Digital Voucher Entry Type"::"Purchase Document"); + + // [GIVEN] Purchase invoice WITHOUT linked e-document + CreatePurchaseDocument(PurchaseHeader, "Purchase Document Type"::Invoice, DummyEDocument); + + // [WHEN] Post purchase invoice + asserterror LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, true); + + // [THEN] Error: Not possible to post without linking an E-Document + Assert.ExpectedErrorCode(DialogErrorCodeTok); + Assert.ExpectedError(NotPossibleToPostWithoutEDocumentErr); + + UnbindSubscription(DigVouchersDisableEnforce); + end; + + [Test] + procedure PurchaseInvoiceEDocumentAttachedAfterPosting() + var + PurchaseHeader: Record "Purchase Header"; + PurchInvHeader: Record "Purch. Inv. Header"; + EDocument: Record "E-Document"; + DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; + DocNo: Code[20]; + begin + // [SCENARIO] Purchase invoice with e-document creates incoming attachment with Is E-Document = true + Initialize(); + BindSubscription(DigVouchersDisableEnforce); + EnableDigitalVoucherFeature(); + + // [GIVEN] Digital voucher entry setup for purchase document is "E-Document" + InitSetupEDocument("Digital Voucher Entry Type"::"Purchase Document"); + + // [GIVEN] Purchase invoice with linked e-document + CreatePurchaseDocumentWithEDocument(PurchaseHeader, EDocument, "Purchase Document Type"::Invoice); + + // [WHEN] Post purchase invoice (e-document attachment happens automatically) + DocNo := LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, true); + + // [THEN] Incoming attachment exists with "Is E-Document" = true + PurchInvHeader.Get(DocNo); + AssertEDocumentIncomingAttachmentExists(PurchInvHeader."Posting Date", PurchInvHeader."No."); + + UnbindSubscription(DigVouchersDisableEnforce); + end; + + [Test] + procedure PurchaseCreditMemoEDocumentAttachedAfterPosting() + var + PurchaseHeader: Record "Purchase Header"; + PurchCrMemoHdr: Record "Purch. Cr. Memo Hdr."; + EDocument: Record "E-Document"; + DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; + DocNo: Code[20]; + begin + // [SCENARIO] Purchase credit memo with e-document creates incoming attachment with Is E-Document = true + Initialize(); + BindSubscription(DigVouchersDisableEnforce); + EnableDigitalVoucherFeature(); + + // [GIVEN] Digital voucher entry setup for purchase document is "E-Document" + InitSetupEDocument("Digital Voucher Entry Type"::"Purchase Document"); + + // [GIVEN] Purchase credit memo with linked e-document + CreatePurchaseDocumentWithEDocument(PurchaseHeader, EDocument, "Purchase Document Type"::"Credit Memo"); + + // [WHEN] Post purchase credit memo (e-document attachment happens automatically) + DocNo := LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, true); + + // [THEN] Incoming attachment exists with "Is E-Document" = true + PurchCrMemoHdr.Get(DocNo); + AssertEDocumentIncomingAttachmentExists(PurchCrMemoHdr."Posting Date", PurchCrMemoHdr."No."); + + UnbindSubscription(DigVouchersDisableEnforce); + end; + + [Test] + procedure SalesInvoiceEDocumentAttachedAfterExport() + var + SalesHeader: Record "Sales Header"; + SalesInvoiceHeader: Record "Sales Invoice Header"; + DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; + DocNo: Code[20]; + begin + // [SCENARIO] Sales invoice e-document export creates incoming attachment with Is E-Document = true + Initialize(); + BindSubscription(DigVouchersDisableEnforce); + EnableDigitalVoucherFeature(); + + // [GIVEN] Digital voucher entry setup for sales document is "E-Document" + InitSetupEDocument("Digital Voucher Entry Type"::"Sales Document"); + + // [WHEN] Posted sales invoice + DocNo := CreateSalesDocumentWithEDocRequirements(SalesHeader, "Sales Document Type"::Invoice); + SalesInvoiceHeader.Get(DocNo); + + // [THEN] Incoming attachment exists with "Is E-Document" = true + AssertEDocumentIncomingAttachmentExists(SalesInvoiceHeader."Posting Date", SalesInvoiceHeader."No."); + + UnbindSubscription(DigVouchersDisableEnforce); + end; + + [Test] + procedure SalesCreditMemoEDocumentAttachedAfterExport() + var + SalesHeader: Record "Sales Header"; + SalesCrMemoHeader: Record "Sales Cr.Memo Header"; + DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; + DocNo: Code[20]; + begin + // [SCENARIO] Sales credit memo e-document export creates incoming attachment with Is E-Document = true + Initialize(); + BindSubscription(DigVouchersDisableEnforce); + EnableDigitalVoucherFeature(); + + // [GIVEN] Digital voucher entry setup for sales document is "E-Document" + InitSetupEDocument("Digital Voucher Entry Type"::"Sales Document"); + + // [WHEN] Posted sales credit memo + DocNo := CreateSalesDocumentWithEDocRequirements(SalesHeader, "Sales Document Type"::"Credit Memo"); + SalesCrMemoHeader.Get(DocNo); + + // [THEN] Incoming attachment exists with "Is E-Document" = true + AssertEDocumentIncomingAttachmentExists(SalesCrMemoHeader."Posting Date", SalesCrMemoHeader."No."); + + UnbindSubscription(DigVouchersDisableEnforce); + end; + + local procedure Initialize() + begin + LibraryTestInitialize.OnTestInitialize(Codeunit::"E-Doc. Attachment Tests"); + if IsInitialized then + exit; + LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"E-Doc. Attachment Tests"); + + IsInitialized := true; + Commit(); + LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"E-Doc. Attachment Tests"); + end; + + local procedure EnableDigitalVoucherFeature() + var + DigitalVoucherSetup: Record "Digital Voucher Setup"; + begin + DigitalVoucherSetup.DeleteAll(); + DigitalVoucherSetup.Enabled := true; + DigitalVoucherSetup.Insert(); + end; + + local procedure InitSetupEDocument(EntryType: Enum "Digital Voucher Entry Type") + var + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + begin + DigitalVoucherEntrySetup.SetRange("Entry Type", EntryType); + DigitalVoucherEntrySetup.DeleteAll(); + DigitalVoucherEntrySetup."Entry Type" := EntryType; + DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + DigitalVoucherEntrySetup.Insert(); + end; + + local procedure CreatePurchaseDocumentWithEDocument(var PurchaseHeader: Record "Purchase Header"; var EDocument: Record "E-Document"; DocumentType: Enum "Purchase Document Type") + var + EDocumentService: Record "E-Document Service"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocLogHelper: Codeunit "E-Document Log Helper"; + OutStream: OutStream; + EDocType: Enum "E-Document Type"; + XmlRootTag: Text; + FileName: Text; + begin + case DocumentType of + "Purchase Document Type"::Invoice: + begin + EDocType := EDocType::"Purchase Invoice"; + XmlRootTag := 'Invoice'; + FileName := 'test-invoice.xml'; + end; + "Purchase Document Type"::"Credit Memo": + begin + EDocType := EDocType::"Purchase Credit Memo"; + XmlRootTag := 'CreditNote'; + FileName := 'test-credit-memo.xml'; + end; + end; + + CreateService(EDocumentService); + + EDocDataStorage.Init(); + EDocDataStorage."File Format" := EDocDataStorage."File Format"::XML; + EDocDataStorage."Data Storage".CreateOutStream(OutStream); + OutStream.WriteText(StrSubstNo('<%1>%2', XmlRootTag, PurchaseHeader."No.")); + EDocDataStorage.Insert(true); + + EDocument.Init(); + EDocument.Service := EDocumentService.Code; + EDocument.Direction := EDocument.Direction::Incoming; + EDocument."Document Type" := EDocType; + EDocument."Unstructured Data Entry No." := EDocDataStorage."Entry No."; + EDocument."File Name" := FileName; + EDocument.Insert(true); + EDocLogHelper.InsertLog(EDocument, EDocumentService, Enum::"E-Document Service Status"::Created); + CreatePurchaseDocument(PurchaseHeader, DocumentType, EDocument); + end; + + local procedure AssertEDocumentIncomingAttachmentExists(PostingDate: Date; DocNo: Code[20]) + var + IncomingDocument: Record "Incoming Document"; + IncomingDocumentAttachment: Record "Incoming Document Attachment"; + NoIncomingDocumentTxt: Label 'No incoming document found for %1 on %2', Comment = '%1 = Document No., %2 = Posting Date'; + begin + Assert.IsTrue( + IncomingDocument.FindByDocumentNoAndPostingDate(IncomingDocument, DocNo, Format(PostingDate)), + StrSubstNo(NoIncomingDocumentTxt, DocNo, PostingDate)); + + IncomingDocumentAttachment.SetRange("Incoming Document Entry No.", IncomingDocument."Entry No."); + IncomingDocumentAttachment.SetRange("Is E-Document", true); + + Assert.RecordIsNotEmpty(IncomingDocumentAttachment); + end; + + local procedure CreateSalesDocumentWithEDocRequirements(var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type"): Code[20] + var + PostCode: Record "Post Code"; + Customer: Record Customer; + begin + LibrarySales.CreateCustomer(Customer); + LibrarySales.CreateCustomerAddress(Customer); + Customer.Validate(GLN, '1234567890128'); + Customer.Modify(false); + + case DocumentType of + "Sales Document Type"::Invoice: + LibrarySales.CreateSalesInvoiceForCustomerNo(SalesHeader, Customer."No."); + "Sales Document Type"::"Credit Memo": + LibrarySales.CreateSalesCreditMemoForCustomerNo(SalesHeader, Customer."No."); + end; + + SalesHeader.Validate("Bill-to Address", LibraryRandom.RandText(MaxStrLen(SalesHeader."Bill-to Address"))); + LibraryERM.CreatePostCode(PostCode); + SalesHeader.Validate("Bill-to Post Code", PostCode.Code); + SalesHeader.Validate("Your Reference", LibraryRandom.RandText(MaxStrLen(SalesHeader."Your Reference"))); + + exit(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + end; + + local procedure CreateService(var EDocService: Record "E-Document Service") + var + LibraryUtility: Codeunit "Library - Utility"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService.Insert(); + end; + + local procedure CreatePurchaseDocument(var PurchaseHeader: Record "Purchase Header"; DocumentType: Enum "Purchase Document Type"; var EDocument: Record "E-Document") + var + PurchaseLine: Record "Purchase Line"; + begin + LibraryPurchase.CreatePurchHeader(PurchaseHeader, DocumentType, LibraryPurchase.CreateVendorNo()); + LibraryPurchase.CreatePurchaseLine( + PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, + LibraryInventory.CreateItemNo(), LibraryRandom.RandInt(10)); + PurchaseLine.Validate("Direct Unit Cost", LibraryRandom.RandDec(100, 2)); + PurchaseLine.Modify(true); + PurchaseHeader.CalcFields(Amount, "Amount Including VAT"); + PurchaseHeader.Validate("Doc. Amount Incl. VAT", PurchaseHeader."Amount Including VAT"); + PurchaseHeader.Validate("E-Document Link", EDocument.SystemId); + PurchaseHeader.Modify(false); + if EDocument."Entry No" <> 0 then begin + EDocument."Document No." := PurchaseHeader."No."; + EDocument."Posting Date" := PurchaseHeader."Posting Date"; + EDocument."Document Record ID" := PurchaseHeader.RecordId(); + EDocument.Modify(false); + end; + end; +} From 7e36e8043cbdb2309b6b6b570b89e9a0e77d5c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:47:20 +0200 Subject: [PATCH 05/12] Update Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al index 0bc202014b..a197767762 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al @@ -36,7 +36,7 @@ table 5579 "Digital Voucher Entry Setup" { trigger OnValidate() var - GenerateAutoMustBeEnabledErr: Label 'Generate Automatically must be enabled when %1 is %2.', Comment = '%1 - check type field caption, %2 - check type value'; + GenerateAutoMustBeEnabledErr: Label 'Generate Automatically must be enabled when %1 is %2.', Comment = '%1 - Check type field caption, %2 - Check type value'; begin if ("Check Type" = "Check Type"::"E-Document") and (not "Generate Automatically") then Error(GenerateAutoMustBeEnabledErr, FieldCaption("Check Type"), "Check Type"::"E-Document"); From 9d7535bf74a0e9c1e0f10f44be787fe9c3f74772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:47:28 +0200 Subject: [PATCH 06/12] Update Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al index a197767762..c1fa7b4e3a 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al @@ -23,7 +23,7 @@ table 5579 "Digital Voucher Entry Setup" { trigger OnValidate() var - CheckTypeRequiresSalesOrPurchaseErr: Label '%1 %2 requires %3 to be %4 or %5.', Comment = '%1 - check type field caption, %2 - check type value, %3 - entry type field caption, %4 - entry type value, %5 - entry type value'; + CheckTypeEDocErr: Label '%1 %2 is available only for %3 %4 or %5.', Comment = '%1 - Check type field caption, %2 - Check type value, %3 - Entry type field caption, %4 - Entry type value, %5 - Entry type value'; begin if "Check Type" = "Check Type"::"E-Document" then begin if not ("Entry Type" in ["Entry Type"::"Sales Document", "Entry Type"::"Purchase Document"]) then From d935a24f71186c26c2ae5d18a9d9ae5a5a2eca9d Mon Sep 17 00:00:00 2001 From: Andrius Andrulevicius Date: Tue, 3 Mar 2026 12:43:30 +0200 Subject: [PATCH 07/12] Moves E-Document attachment logic to implementation layer Refactors E-Document file attachment for purchase documents by relocating the logic from the check codeunit to the main implementation codeunit, improving code structure and responsibility separation. Updates test object ranges, enhances test reliability, and adjusts error handling to use labels for better localization. Clarifies and simplifies validation around E-Document requirements and streamlines page variable initialization. Improves maintainability and prepares for future extensibility. --- .../DigitalVoucherEntrySetup.Page.al | 53 +++----- .../DigitalVoucherEntrySetup.Table.al | 4 +- .../DigitalVoucherImpl.Codeunit.al | 122 ++++++++++++++---- .../VoucherEDocumentCheck.Codeunit.al | 95 +------------- Apps/W1/EnforcedDigitalVouchers/test/app.json | 4 +- .../test/src/EDocAttachmentTests.Codeunit.al | 96 ++++++-------- 6 files changed, 158 insertions(+), 216 deletions(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al index d7fd1aa5b6..400642595a 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al @@ -6,13 +6,13 @@ namespace Microsoft.EServices.EDocument; page 5579 "Digital Voucher Entry Setup" { - PageType = List; - SourceTable = "Digital Voucher Entry Setup"; - AboutTitle = 'About setup of digital vouchers for each entry type'; AboutText = 'Here you can select the line with a certain entry type and setup the type of the digital voucher''s check you want to perform.'; + AboutTitle = 'About setup of digital vouchers for each entry type'; ApplicationArea = Basic, Suite; - UsageCategory = Administration; DelayedInsert = true; + PageType = List; + SourceTable = "Digital Voucher Entry Setup"; + UsageCategory = Administration; layout { @@ -23,26 +23,16 @@ page 5579 "Digital Voucher Entry Setup" field("Entry Type"; Rec."Entry Type") { ToolTip = 'Specifies the entry type.'; - trigger OnValidate() - begin - UpdateGenerateAutomaticallyEditable(); - end; } field("Check Type"; Rec."Check Type") { - ToolTip = 'Specifies the check type.'; - AboutTitle = 'Enter the check type'; AboutText = 'In case of check type None you can post this type of entry without any digital voucher. In case of check type Attachment you need to have an attachment to your entry. In case of check type Attachment or Note you can either have an attachment or a note for your entry.'; - - trigger OnValidate() - begin - UpdateGenerateAutomaticallyEditable(); - end; + AboutTitle = 'Enter the check type'; + ToolTip = 'Specifies the check type.'; } field("Generate Automatically"; Rec."Generate Automatically") { ToolTip = 'Specifies if the digital voucher needs to be generated automatically.'; - Editable = GenerateAutomaticallyEditable; } field("Skip If Manually Added"; Rec."Skip If Manually Added") { @@ -59,9 +49,9 @@ page 5579 "Digital Voucher Entry Setup" Visible = VoucherEntryTypeDescription <> ''; field(VoucherEntryTypeDescriptionControl; VoucherEntryTypeDescription) { - ShowCaption = false; Editable = false; MultiLine = true; + ShowCaption = false; ToolTip = 'Specifies the description of the voucher entry type.'; } } @@ -74,14 +64,14 @@ page 5579 "Digital Voucher Entry Setup" { action(SourceCodes) { + AboutText = 'If you post a journal line, the connected source code identifies the entry type - general journal, sales journal, purchase journal, etc.'; + AboutTitle = 'About source codes'; Caption = 'Source Codes'; Image = ViewSourceDocumentLine; - Scope = Repeater; - ToolTip = 'Specifies the connected source codes.'; - AboutTitle = 'About source codes'; - AboutText = 'If you post a journal line, the connected source code identifies the entry type - general journal, sales journal, purchase journal, etc.'; RunObject = Page "Voucher Entry Source Codes"; RunPageLink = "Entry Type" = field("Entry Type"); + Scope = Repeater; + ToolTip = 'Specifies the connected source codes.'; } } area(Promoted) @@ -89,23 +79,19 @@ page 5579 "Digital Voucher Entry Setup" group(Category_Process) { Caption = 'Process'; - actionref(SourceCodes_Promoted; SourceCodes) - { - - } + actionref(SourceCodes_Promoted; SourceCodes) { } } } } var OpenedFromWizard: Boolean; - GenerateAutomaticallyEditable: Boolean; - VoucherEntryTypeDescription: Text; GeneralJournalEntryDescriptionTxt: Label 'Specifies postings you are doing from the General Journal for all Account Types excluding those related to Customer and Vendor. By choosing one of those options, you will change control of the posting process. If you select the Customer as the Account Type, the system will check your setup related to the Sales Journal. If you select the Vendor as the Account Type, the system will check your setup related to the Purchase Journal.'; - SalesJournalEntryDescriptionTxt: Label 'Specifies posting you are doing from the Sales Journal and the General Journal with the Customer selected as the Account Type.'; + PurchaseDocumentEntryDescriptionTxt: Label 'Specifies postings you are doing from the purchase documents.'; PurchaseJournalEntryDescriptionTxt: Label 'Specifies posting you are doing from the Purchase Journal and the General Journal with the Vendor selected as the Account Type.'; SalesDocumentEntryDescriptionTxt: Label 'Specifies postings you are doing from the sales documents.'; - PurchaseDocumentEntryDescriptionTxt: Label 'Specifies postings you are doing from the purchase documents.'; + SalesJournalEntryDescriptionTxt: Label 'Specifies posting you are doing from the Sales Journal and the General Journal with the Customer selected as the Account Type.'; + VoucherEntryTypeDescription: Text; trigger OnOpenPage() var @@ -118,13 +104,11 @@ page 5579 "Digital Voucher Entry Setup" trigger OnAfterGetRecord() begin SetVoucherEntryTypeDescription(); - UpdateGenerateAutomaticallyEditable(); end; trigger OnAfterGetCurrRecord() begin SetVoucherEntryTypeDescription(); - UpdateGenerateAutomaticallyEditable(); end; procedure SetOpenFromGuide() @@ -155,13 +139,8 @@ page 5579 "Digital Voucher Entry Setup" end; end; - local procedure UpdateGenerateAutomaticallyEditable() - begin - GenerateAutomaticallyEditable := Rec."Check Type" <> Rec."Check Type"::"E-Document"; - end; - [IntegrationEvent(false, false)] local procedure OnBeforeSetVoucherEntryTypeDescription(var NewVoucherEntryTypeDescription: Text; var IsHandled: Boolean) begin end; -} +} \ No newline at end of file diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al index c1fa7b4e3a..9a530b3fd6 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Table.al @@ -27,7 +27,7 @@ table 5579 "Digital Voucher Entry Setup" begin if "Check Type" = "Check Type"::"E-Document" then begin if not ("Entry Type" in ["Entry Type"::"Sales Document", "Entry Type"::"Purchase Document"]) then - Error(CheckTypeRequiresSalesOrPurchaseErr, FieldCaption("Check Type"), "Check Type"::"E-Document", FieldCaption("Entry Type"), "Entry Type"::"Sales Document", "Entry Type"::"Purchase Document"); + Error(CheckTypeEDocErr, FieldCaption("Check Type"), "Check Type"::"E-Document", FieldCaption("Entry Type"), "Entry Type"::"Sales Document", "Entry Type"::"Purchase Document"); "Generate Automatically" := true; end; end; @@ -64,4 +64,4 @@ table 5579 "Digital Voucher Entry Setup" Clustered = true; } } -} +} \ No newline at end of file diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al index c083526298..c6f67b823d 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al @@ -24,6 +24,7 @@ using Microsoft.Service.History; using Microsoft.Service.Posting; using System.Email; using System.Environment.Configuration; +using System.IO; using System.Media; using System.Reflection; using System.Utilities; @@ -38,13 +39,13 @@ codeunit 5579 "Digital Voucher Impl." Tabledata "Purch. Cr. Memo Hdr." = m; var - DigitalVoucherFeature: Codeunit "Digital Voucher Feature"; DigitalVoucherEntry: Codeunit "Digital Voucher Entry"; - AssistedSetupTxt: Label 'Set up a digital voucher feature'; + DigitalVoucherFeature: Codeunit "Digital Voucher Feature"; AssistedSetupDescriptionTxt: Label 'In some countries authorities require to make sure that for every single general ledger register ther is a digital vouchers assigned.'; AssistedSetupHelpTxt: Label 'https://learn.microsoft.com/en-us/dynamics365/business-central/across-how-setup-digital-vouchers', Locked = true; - CannotRemoveReferenceRecordFromIncDocErr: Label 'Cannot remove the reference record from the incoming document because it is used for the enforced digital voucher functionality'; + AssistedSetupTxt: Label 'Set up a digital voucher feature'; CannotChangeIncomDocWithEnforcedDigitalVoucherErr: Label 'Cannot change incoming document with the enforced digital voucher functionality'; + CannotRemoveReferenceRecordFromIncDocErr: Label 'Cannot remove the reference record from the incoming document because it is used for the enforced digital voucher functionality'; DigitalVoucherFileTxt: Label 'DigitalVoucher_%1_%2_%3.pdf', Comment = '%1 = doc type; %2 = posting date; %3 = doc no.'; procedure HandleDigitalVoucherForDocument(var ErrorMessageMgt: Codeunit "Error Message Management"; EntryType: Enum "Digital Voucher Entry Type"; Record: Variant) @@ -103,13 +104,13 @@ codeunit 5579 "Digital Voucher Impl." [CommitBehavior(CommitBehavior::Ignore)] procedure GenerateDigitalVoucherForDocument(RecRef: RecordRef) var - SalesInvHeader: Record "Sales Invoice Header"; - SalesCrMemoHeader: Record "Sales Cr.Memo Header"; - ServInvHeader: Record "Service Invoice Header"; - ServCrMemoHeader: Record "Service Cr.Memo Header"; - PurchInvHeader: Record "Purch. Inv. Header"; PurchCrMemoHeader: Record "Purch. Cr. Memo Hdr."; + PurchInvHeader: Record "Purch. Inv. Header"; ReportSelections: Record "Report Selections"; + SalesCrMemoHeader: Record "Sales Cr.Memo Header"; + SalesInvHeader: Record "Sales Invoice Header"; + ServCrMemoHeader: Record "Service Cr.Memo Header"; + ServInvHeader: Record "Service Invoice Header"; begin case RecRef.Number of Database::"Sales Invoice Header": @@ -266,21 +267,14 @@ codeunit 5579 "Digital Voucher Impl." exit(not IncomingDocumentAttachment.IsEmpty()); end; - /// - /// Attaches the exported E-Document XML file as an Incoming Document Attachment to the source document. - /// Only attaches for Sales Invoice Header when the "Attach Sales E-Document" setting is enabled. - /// - /// The E-Document being exported - /// The source document (e.g., Sales Invoice Header) - /// The blob containing the exported E-Document content - internal procedure AttachIncomingEDocument(EDocument: Record "E-Document"; SourceDocumentHeader: RecordRef; var TempBlob: Codeunit "Temp Blob") + local procedure AttachIncomingEDocument(EDocument: Record "E-Document"; SourceDocumentHeader: RecordRef; var TempBlob: Codeunit "Temp Blob") var - IncomingDocumentAttachment: Record "Incoming Document Attachment"; DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; - ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; + IncomingDocumentAttachment: Record "Incoming Document Attachment"; EDocumentHelper: Codeunit "E-Document Processing"; - RecordLinkTxt: Text; + ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; FileNameTok: Label 'E-Document_%1.xml', Locked = true; + RecordLinkTxt: Text; begin if not DigitalVoucherFeature.IsFeatureEnabled() then exit; @@ -308,6 +302,77 @@ codeunit 5579 "Digital Voucher Impl." IncomingDocumentAttachment.Modify(false); end; + local procedure AttachPurchaseEDocument(EDocument: Record "E-Document"; DocumentNo: Code[20]; PostingDate: Date) + var + EDocDataStorage: Record "E-Doc. Data Storage"; + IncomingDocumentAttachment: Record "Incoming Document Attachment"; + ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; + TempBlob: Codeunit "Temp Blob"; + EDocumentFileNameLbl: Label 'E-Document_%1.%2', Comment = '%1 = E-Document Entry No., %2 = File Format', Locked = true; + FileName: Text[250]; + begin + if not (EDocument."Document Type" in [ + EDocument."Document Type"::"Purchase Invoice", + EDocument."Document Type"::"Purchase Credit Memo", + EDocument."Document Type"::"Purchase Order", + EDocument."Document Type"::"Purchase Quote", + EDocument."Document Type"::"Purchase Return Order"]) then + exit; + + if EDocument."Unstructured Data Entry No." = 0 then + exit; + + if not EDocDataStorage.Get(EDocument."Unstructured Data Entry No.") then + exit; + + TempBlob := EDocDataStorage.GetTempBlob(); + if not TempBlob.HasValue() then + exit; + + if EDocument."File Name" <> '' then + FileName := CopyStr(EDocument."File Name", 1, MaxStrLen(FileName)) + else + FileName := StrSubstNo(EDocumentFileNameLbl, EDocument."Entry No", EDocDataStorage."File Format"); + + IncomingDocumentAttachment.SetRange("Document No.", DocumentNo); + IncomingDocumentAttachment.SetRange("Posting Date", PostingDate); + IncomingDocumentAttachment.SetContentFromBlob(TempBlob); + + if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, FileName, TempBlob) then + exit; + + IncomingDocumentAttachment."Is E-Document" := true; + IncomingDocumentAttachment.Modify(false); + + if EDocDataStorage."File Format" = EDocDataStorage."File Format"::PDF then begin + FileName := StrSubstNo(EDocumentFileNameLbl, EDocument."Entry No", EDocDataStorage."File Format"::XML); + ExtractXMLFromPDF(TempBlob, FileName, IncomingDocumentAttachment); + end; + end; + + local procedure ExtractXMLFromPDF(var TempBlob: Codeunit System.Utilities."Temp Blob"; FileName: Text[250]; var IncomingDocumentAttachment: Record "Incoming Document Attachment") + var + ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; + PDFDocument: Codeunit "PDF Document"; + ExtractedXmlBlob: Codeunit "Temp Blob"; + PdfInStream: InStream; + begin + TempBlob.CreateInStream(PdfInStream); + if not PDFDocument.GetDocumentAttachmentStream(PdfInStream, ExtractedXmlBlob) then + exit; + + if not ExtractedXmlBlob.HasValue() then + exit; + + IncomingDocumentAttachment.Default := false; + IncomingDocumentAttachment."Main Attachment" := false; + if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, FileName, ExtractedXmlBlob) then + exit; + + IncomingDocumentAttachment."Is E-Document" := true; + IncomingDocumentAttachment.Modify(false); + end; + local procedure FilterIncomingDocumentRecordFromRecordRef(var IncomingDocumentAttachment: Record "Incoming Document Attachment"; var IncomingDocument: Record "Incoming Document"; MainRecordRef: RecordRef): Boolean begin Clear(IncomingDocument); @@ -376,8 +441,8 @@ codeunit 5579 "Digital Voucher Impl." local procedure IsPaymentReconciliationJournal(DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef): Boolean var - SourceCodeSetup: Record "Source Code Setup"; GenJournalLine: Record "Gen. Journal Line"; + SourceCodeSetup: Record "Source Code Setup"; FieldRef: FieldRef; SourceCodeValue: Text; begin @@ -393,8 +458,8 @@ codeunit 5579 "Digital Voucher Impl." local procedure IsGenJnlLineWithIncDocAttachedToAdjLine(DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef): Boolean var - GenJournalLine: Record "Gen. Journal Line"; AdjacentGenJournalLine: Record "Gen. Journal Line"; + GenJournalLine: Record "Gen. Journal Line"; IncomingDocument: Record "Incoming Document"; begin if DigitalVoucherEntryType <> DigitalVoucherEntryType::"General Journal" then @@ -580,8 +645,8 @@ codeunit 5579 "Digital Voucher Impl." [EventSubscriber(ObjectType::Codeunit, Codeunit::"Service-Post", 'OnAfterPostServiceDoc', '', true, true)] local procedure CheckServiceVoucherOnAfterPostServiceDoc(ServInvoiceNo: Code[20]; ServCrMemoNo: Code[20]; PassedInvoice: Boolean) var - ServInvHeader: Record "Service Invoice Header"; ServCrMemoHeader: Record "Service Cr.Memo Header"; + ServInvHeader: Record "Service Invoice Header"; RecVar: Variant; begin if not DigitalVoucherFeature.IsFeatureEnabled() then @@ -681,8 +746,8 @@ codeunit 5579 "Digital Voucher Impl." [EventSubscriber(ObjectType::Table, Database::"Incoming Document", 'OnBeforeCanReplaceMainAttachment', '', false, false)] local procedure CheckVoucherOnBeforeCanReplaceMainAttachment(var CanReplaceMainAttachment: Boolean; IncomingDocument: Record "Incoming Document"; var IsHandled: Boolean) var - RecRef: RecordRef; RelatedRecordID: RecordId; + RecRef: RecordRef; begin RelatedRecordID := IncomingDocument."Related Record ID"; if RelatedRecordID.TableNo = 0 then @@ -695,8 +760,8 @@ codeunit 5579 "Digital Voucher Impl." [EventSubscriber(ObjectType::Table, Database::"Incoming Document", 'OnBeforeRemoveReferencedRecords', '', false, false)] local procedure CheckVoucherOnBeforeRemoveReferencedRecords(IncomingDocument: Record "Incoming Document"; var IsHandled: Boolean) var - RecRef: RecordRef; RelatedRecordID: RecordId; + RecRef: RecordRef; begin if not DigitalVoucherFeature.IsFeatureEnabled() then exit; @@ -753,13 +818,18 @@ codeunit 5579 "Digital Voucher Impl." [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Document Subscribers", OnAfterUpdateToPostedPurchaseEDocument, '', false, false)] local procedure EDocumentSubscribers_OnAfterUpdateToPostedPurchaseEDocument(var EDocument: Record "E-Document"; PostedRecord: Variant; DocumentType: Enum "E-Document Type") + begin + AttachOutgoingDocument(EDocument, PostedRecord); + end; + + local procedure AttachOutgoingDocument(var EDocument: Record "E-Document"; PostedRecord: Variant) var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; VoucherEDocumentCheck: Codeunit "Voucher E-Document Check"; RecRef: RecordRef; - DocType: Text; DocNo: Code[20]; PostingDate: Date; + DocType: Text; begin if not DigitalVoucherFeature.IsFeatureEnabled() then exit; @@ -778,7 +848,7 @@ codeunit 5579 "Digital Voucher Impl." RecRef.GetTable(PostedRecord); DigitalVoucherEntry.GetDocNoAndPostingDateFromRecRef(DocType, DocNo, PostingDate, RecRef); - VoucherEDocumentCheck.AttachEDocument(EDocument, DocNo, PostingDate); + AttachPurchaseEDocument(EDocument, DocNo, PostingDate); end; [IntegrationEvent(false, false)] diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al index 1fc3ae0904..04d0e4b0bc 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al @@ -4,9 +4,6 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.EServices.EDocument; -using Microsoft.Purchases.Document; -using Microsoft.Purchases.History; -using System.IO; using System.Utilities; codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" @@ -17,9 +14,9 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" /// Validates that an E-Document is attached to the document before posting. /// Only applies to Purchase Documents when the Digital Voucher feature is enabled and Check Type is set to E-Document. /// - /// Error message management for logging validation errors - /// The type of digital voucher entry being validated - /// Record reference to the document being validated + /// Error message management for logging validation errors. + /// The type of digital voucher entry being validated. + /// Record reference to the document being validated. internal procedure CheckVoucherIsAttachedToDocument(var ErrorMessageMgt: Codeunit "Error Message Management"; DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef) var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; @@ -49,8 +46,8 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" /// Generates a digital voucher for a posted document by delegating to the Attachment check type implementation. /// This procedure retrieves the digital voucher entry setup and invokes the attachment-based voucher generation. /// - /// The type of digital voucher entry for the posted document - /// Record reference to the posted document + /// The type of digital voucher entry for the posted document. + /// Record reference to the posted document. internal procedure GenerateDigitalVoucherForPostedDocument(DigitalVoucherEntryType: Enum "Digital Voucher Entry Type"; RecRef: RecordRef) var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; @@ -61,84 +58,4 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" DigitalVoucherCheck := DigitalVoucherEntrySetup."Check Type"::Attachment; DigitalVoucherCheck.GenerateDigitalVoucherForPostedDocument(DigitalVoucherEntrySetup."Entry Type", RecRef); end; - - /// - /// Attaches the original E-Document file to the Incoming Document of a Purchase Header. - /// Creates an Incoming Document Attachment and links it to the Purchase Header. - /// If the E-Document is a PDF with embedded XML, also extracts and attaches the XML content. - /// Only processes Purchase Invoice, Credit Memo, Order, Quote, and Return Order document types. - /// - /// The E-Document record containing the file to attach - /// The document number to attach the incoming document to - /// The posting date of the document - internal procedure AttachEDocument(EDocument: Record "E-Document"; DocumentNo: Code[20]; PostingDate: Date) - var - EDocDataStorage: Record "E-Doc. Data Storage"; - IncomingDocumentAttachment: Record "Incoming Document Attachment"; - ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; - TempBlob: Codeunit "Temp Blob"; - FileName: Text[250]; - EDocumentFileNameLbl: Label 'E-Document_%1.%2', Comment = '%1 = E-Document Entry No., %2 = File Format', Locked = true; - begin - if not (EDocument."Document Type" in [ - EDocument."Document Type"::"Purchase Invoice", - EDocument."Document Type"::"Purchase Credit Memo", - EDocument."Document Type"::"Purchase Order", - EDocument."Document Type"::"Purchase Quote", - EDocument."Document Type"::"Purchase Return Order"]) then - exit; - - if EDocument."Unstructured Data Entry No." = 0 then - exit; - - if not EDocDataStorage.Get(EDocument."Unstructured Data Entry No.") then - exit; - - TempBlob := EDocDataStorage.GetTempBlob(); - if not TempBlob.HasValue() then - exit; - - if EDocument."File Name" <> '' then - FileName := CopyStr(EDocument."File Name",1,MaxStrLen(FileName)) - else - FileName := StrSubstNo(EDocumentFileNameLbl, EDocument."Entry No", EDocDataStorage."File Format"); - - IncomingDocumentAttachment.SetRange("Document No.", DocumentNo); - IncomingDocumentAttachment.SetRange("Posting Date", PostingDate); - IncomingDocumentAttachment.SetContentFromBlob(TempBlob); - - if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, FileName, TempBlob) then - exit; - - IncomingDocumentAttachment."Is E-Document" := true; - IncomingDocumentAttachment.Modify(false); - - if EDocDataStorage."File Format" = EDocDataStorage."File Format"::PDF then begin - FileName := StrSubstNo(EDocumentFileNameLbl, EDocument."Entry No", EDocDataStorage."File Format"::XML); - ExtractXMLFromPDF(TempBlob, FileName, IncomingDocumentAttachment); - end; - end; - - local procedure ExtractXMLFromPDF(var TempBlob: Codeunit System.Utilities."Temp Blob"; FileName: Text[250]; var IncomingDocumentAttachment: Record "Incoming Document Attachment") - var - PDFDocument: Codeunit "PDF Document"; - ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; - ExtractedXmlBlob: Codeunit "Temp Blob"; - PdfInStream: InStream; - begin - TempBlob.CreateInStream(PdfInStream); - if not PDFDocument.GetDocumentAttachmentStream(PdfInStream, ExtractedXmlBlob) then - exit; - - if not ExtractedXmlBlob.HasValue() then - exit; - - IncomingDocumentAttachment.Default := false; - IncomingDocumentAttachment."Main Attachment" := false; - if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, FileName, ExtractedXmlBlob) then - exit; - - IncomingDocumentAttachment."Is E-Document" := true; - IncomingDocumentAttachment.Modify(false); - end; -} +} \ No newline at end of file diff --git a/Apps/W1/EnforcedDigitalVouchers/test/app.json b/Apps/W1/EnforcedDigitalVouchers/test/app.json index 5c5bd0e6b9..74f3e406e0 100644 --- a/Apps/W1/EnforcedDigitalVouchers/test/app.json +++ b/Apps/W1/EnforcedDigitalVouchers/test/app.json @@ -56,8 +56,8 @@ "to": 139516 }, { - "from": 139519, - "to": 139519 + "from": 139649, + "to": 139649 } ], "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2141039", diff --git a/Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al index 4041dda809..c717cc1929 100644 --- a/Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/test/src/EDocAttachmentTests.Codeunit.al @@ -13,33 +13,30 @@ using Microsoft.Sales.Document; using Microsoft.Sales.History; using Microsoft.Tests.EServices.EDocument; -codeunit 139519 "E-Doc. Attachment Tests" +codeunit 139649 "E-Doc. Attachment Tests" { Subtype = Test; TestPermissions = Disabled; var - LibraryTestInitialize: Codeunit "Library - Test Initialize"; - LibraryPurchase: Codeunit "Library - Purchase"; - LibrarySales: Codeunit "Library - Sales"; + Assert: Codeunit Assert; + LibraryERM: Codeunit "Library - ERM"; LibraryInventory: Codeunit "Library - Inventory"; + LibraryPurchase: Codeunit "Library - Purchase"; LibraryRandom: Codeunit "Library - Random"; - LibraryERM: Codeunit "Library - ERM"; - Assert: Codeunit Assert; + LibrarySales: Codeunit "Library - Sales"; + LibraryTestInitialize: Codeunit "Library - Test Initialize"; IsInitialized: Boolean; - NotPossibleToPostWithoutEDocumentErr: Label 'Not possible to post without linking an E-Document.'; DialogErrorCodeTok: Label 'Dialog', Locked = true; - - trigger OnRun() - begin - // [FEATURE] [Digital Voucher] [E-Document] - end; + NotPossibleToPostWithoutEDocumentErr: Label 'Not possible to post without linking an E-Document.', Locked = true; + EDocumentTok: Label 'E-Document', Locked = true; [Test] procedure GenerateAutomaticallyEnabledWhenCheckTypeSetToEDocument() var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] Generate Automatically is auto-enabled when Check Type is set to E-Document Initialize(); @@ -58,7 +55,9 @@ codeunit 139519 "E-Doc. Attachment Tests" procedure GenerateAutomaticallyCannotBeDisabledForEDocument() var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + GenerateAutomaticallyErrorTok: Label 'Generate Automatically must be enabled', Locked = true; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] Generate Automatically cannot be disabled when Check Type = E-Document Initialize(); @@ -70,7 +69,7 @@ codeunit 139519 "E-Doc. Attachment Tests" asserterror DigitalVoucherEntrySetup.Validate("Generate Automatically", false); // [THEN] Error: Generate Automatically must be enabled when Check Type is E-Document - Assert.ExpectedError('Generate Automatically must be enabled'); + Assert.ExpectedError(GenerateAutomaticallyErrorTok); end; [Test] @@ -78,6 +77,7 @@ codeunit 139519 "E-Doc. Attachment Tests" var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] E-Document check type is not allowed for General Journal Initialize(); @@ -88,7 +88,7 @@ codeunit 139519 "E-Doc. Attachment Tests" asserterror DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); // [THEN] Error mentioning E-Document requires Sales Document or Purchase Document - Assert.ExpectedError('E-Document'); + Assert.ExpectedError(EDocumentTok); end; [Test] @@ -96,6 +96,7 @@ codeunit 139519 "E-Doc. Attachment Tests" var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] E-Document check type is not allowed for Sales Journal Initialize(); @@ -106,7 +107,7 @@ codeunit 139519 "E-Doc. Attachment Tests" asserterror DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); // [THEN] Error mentioning E-Document requires Sales Document or Purchase Document - Assert.ExpectedError('E-Document'); + Assert.ExpectedError(EDocumentTok); end; [Test] @@ -114,6 +115,7 @@ codeunit 139519 "E-Doc. Attachment Tests" var DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] E-Document check type is not allowed for Purchase Journal Initialize(); @@ -124,48 +126,17 @@ codeunit 139519 "E-Doc. Attachment Tests" asserterror DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); // [THEN] Error mentioning E-Document requires Sales Document or Purchase Document - Assert.ExpectedError('E-Document'); - end; - - [Test] - procedure EDocumentCheckTypeAllowedForSalesDocument() - var - DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; - begin - // [SCENARIO] E-Document check type is allowed for Sales Document - Initialize(); - - // [WHEN] Entry Type = Sales Document and Check Type = E-Document - DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Sales Document"; - DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); - - // [THEN] No error, Check Type is set successfully - DigitalVoucherEntrySetup.TestField("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); - end; - - [Test] - procedure EDocumentCheckTypeAllowedForPurchaseDocument() - var - DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; - begin - // [SCENARIO] E-Document check type is allowed for Purchase Document - Initialize(); - - // [WHEN] Entry Type = Purchase Document and Check Type = E-Document - DigitalVoucherEntrySetup."Entry Type" := DigitalVoucherEntrySetup."Entry Type"::"Purchase Document"; - DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); - - // [THEN] No error, Check Type is set successfully - DigitalVoucherEntrySetup.TestField("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); + Assert.ExpectedError(EDocumentTok); end; [Test] procedure PurchaseInvoiceCannotPostWithoutEDocument() var - PurchaseHeader: Record "Purchase Header"; DummyEDocument: Record "E-Document"; + PurchaseHeader: Record "Purchase Header"; DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] Purchase invoice posting fails when E-Document check is enabled but no e-document linked Initialize(); BindSubscription(DigVouchersDisableEnforce); @@ -190,12 +161,13 @@ codeunit 139519 "E-Doc. Attachment Tests" [Test] procedure PurchaseInvoiceEDocumentAttachedAfterPosting() var - PurchaseHeader: Record "Purchase Header"; - PurchInvHeader: Record "Purch. Inv. Header"; EDocument: Record "E-Document"; + PurchInvHeader: Record "Purch. Inv. Header"; + PurchaseHeader: Record "Purchase Header"; DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; DocNo: Code[20]; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] Purchase invoice with e-document creates incoming attachment with Is E-Document = true Initialize(); BindSubscription(DigVouchersDisableEnforce); @@ -220,12 +192,13 @@ codeunit 139519 "E-Doc. Attachment Tests" [Test] procedure PurchaseCreditMemoEDocumentAttachedAfterPosting() var - PurchaseHeader: Record "Purchase Header"; - PurchCrMemoHdr: Record "Purch. Cr. Memo Hdr."; EDocument: Record "E-Document"; + PurchCrMemoHdr: Record "Purch. Cr. Memo Hdr."; + PurchaseHeader: Record "Purchase Header"; DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; DocNo: Code[20]; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] Purchase credit memo with e-document creates incoming attachment with Is E-Document = true Initialize(); BindSubscription(DigVouchersDisableEnforce); @@ -255,6 +228,7 @@ codeunit 139519 "E-Doc. Attachment Tests" DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; DocNo: Code[20]; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] Sales invoice e-document export creates incoming attachment with Is E-Document = true Initialize(); BindSubscription(DigVouchersDisableEnforce); @@ -276,11 +250,12 @@ codeunit 139519 "E-Doc. Attachment Tests" [Test] procedure SalesCreditMemoEDocumentAttachedAfterExport() var - SalesHeader: Record "Sales Header"; SalesCrMemoHeader: Record "Sales Cr.Memo Header"; + SalesHeader: Record "Sales Header"; DigVouchersDisableEnforce: Codeunit "Dig. Vouchers Disable Enforce"; DocNo: Code[20]; begin + // [FEATURE] [Digital Voucher] [E-Document] // [SCENARIO] Sales credit memo e-document export creates incoming attachment with Is E-Document = true Initialize(); BindSubscription(DigVouchersDisableEnforce); @@ -315,9 +290,10 @@ codeunit 139519 "E-Doc. Attachment Tests" var DigitalVoucherSetup: Record "Digital Voucher Setup"; begin - DigitalVoucherSetup.DeleteAll(); + if DigitalVoucherSetup.Get() then + DigitalVoucherSetup.Delete(false); DigitalVoucherSetup.Enabled := true; - DigitalVoucherSetup.Insert(); + DigitalVoucherSetup.Insert(false); end; local procedure InitSetupEDocument(EntryType: Enum "Digital Voucher Entry Type") @@ -325,10 +301,10 @@ codeunit 139519 "E-Doc. Attachment Tests" DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; begin DigitalVoucherEntrySetup.SetRange("Entry Type", EntryType); - DigitalVoucherEntrySetup.DeleteAll(); + DigitalVoucherEntrySetup.DeleteAll(false); DigitalVoucherEntrySetup."Entry Type" := EntryType; DigitalVoucherEntrySetup.Validate("Check Type", DigitalVoucherEntrySetup."Check Type"::"E-Document"); - DigitalVoucherEntrySetup.Insert(); + DigitalVoucherEntrySetup.Insert(false); end; local procedure CreatePurchaseDocumentWithEDocument(var PurchaseHeader: Record "Purchase Header"; var EDocument: Record "E-Document"; DocumentType: Enum "Purchase Document Type") @@ -393,8 +369,8 @@ codeunit 139519 "E-Doc. Attachment Tests" local procedure CreateSalesDocumentWithEDocRequirements(var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type"): Code[20] var - PostCode: Record "Post Code"; Customer: Record Customer; + PostCode: Record "Post Code"; begin LibrarySales.CreateCustomer(Customer); LibrarySales.CreateCustomerAddress(Customer); @@ -422,7 +398,7 @@ codeunit 139519 "E-Doc. Attachment Tests" begin EDocService.Init(); EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); - EDocService.Insert(); + EDocService.Insert(false); end; local procedure CreatePurchaseDocument(var PurchaseHeader: Record "Purchase Header"; DocumentType: Enum "Purchase Document Type"; var EDocument: Record "E-Document") From 8b9bd7d9841dc9cfee5e5a4265c569b781b1fcca Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute Date: Wed, 4 Mar 2026 15:42:23 +0200 Subject: [PATCH 08/12] Revert Digital Voucher Entry Setup changes --- .../DigitalVoucherEntrySetup.Page.al | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al index 400642595a..d3fe5e4ece 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al @@ -6,13 +6,13 @@ namespace Microsoft.EServices.EDocument; page 5579 "Digital Voucher Entry Setup" { - AboutText = 'Here you can select the line with a certain entry type and setup the type of the digital voucher''s check you want to perform.'; - AboutTitle = 'About setup of digital vouchers for each entry type'; - ApplicationArea = Basic, Suite; - DelayedInsert = true; PageType = List; SourceTable = "Digital Voucher Entry Setup"; + AboutTitle = 'About setup of digital vouchers for each entry type'; + AboutText = 'Here you can select the line with a certain entry type and setup the type of the digital voucher''s check you want to perform.'; + ApplicationArea = Basic, Suite; UsageCategory = Administration; + DelayedInsert = true; layout { @@ -26,9 +26,9 @@ page 5579 "Digital Voucher Entry Setup" } field("Check Type"; Rec."Check Type") { - AboutText = 'In case of check type None you can post this type of entry without any digital voucher. In case of check type Attachment you need to have an attachment to your entry. In case of check type Attachment or Note you can either have an attachment or a note for your entry.'; - AboutTitle = 'Enter the check type'; ToolTip = 'Specifies the check type.'; + AboutTitle = 'Enter the check type'; + AboutText = 'In case of check type None you can post this type of entry without any digital voucher. In case of check type Attachment you need to have an attachment to your entry. In case of check type Attachment or Note you can either have an attachment or a note for your entry.'; } field("Generate Automatically"; Rec."Generate Automatically") { @@ -49,9 +49,9 @@ page 5579 "Digital Voucher Entry Setup" Visible = VoucherEntryTypeDescription <> ''; field(VoucherEntryTypeDescriptionControl; VoucherEntryTypeDescription) { + ShowCaption = false; Editable = false; MultiLine = true; - ShowCaption = false; ToolTip = 'Specifies the description of the voucher entry type.'; } } @@ -64,14 +64,14 @@ page 5579 "Digital Voucher Entry Setup" { action(SourceCodes) { - AboutText = 'If you post a journal line, the connected source code identifies the entry type - general journal, sales journal, purchase journal, etc.'; - AboutTitle = 'About source codes'; Caption = 'Source Codes'; Image = ViewSourceDocumentLine; - RunObject = Page "Voucher Entry Source Codes"; - RunPageLink = "Entry Type" = field("Entry Type"); Scope = Repeater; ToolTip = 'Specifies the connected source codes.'; + AboutTitle = 'About source codes'; + AboutText = 'If you post a journal line, the connected source code identifies the entry type - general journal, sales journal, purchase journal, etc.'; + RunObject = Page "Voucher Entry Source Codes"; + RunPageLink = "Entry Type" = field("Entry Type"); } } area(Promoted) @@ -79,19 +79,22 @@ page 5579 "Digital Voucher Entry Setup" group(Category_Process) { Caption = 'Process'; - actionref(SourceCodes_Promoted; SourceCodes) { } + actionref(SourceCodes_Promoted; SourceCodes) + { + + } } } } var OpenedFromWizard: Boolean; + VoucherEntryTypeDescription: Text; GeneralJournalEntryDescriptionTxt: Label 'Specifies postings you are doing from the General Journal for all Account Types excluding those related to Customer and Vendor. By choosing one of those options, you will change control of the posting process. If you select the Customer as the Account Type, the system will check your setup related to the Sales Journal. If you select the Vendor as the Account Type, the system will check your setup related to the Purchase Journal.'; - PurchaseDocumentEntryDescriptionTxt: Label 'Specifies postings you are doing from the purchase documents.'; + SalesJournalEntryDescriptionTxt: Label 'Specifies posting you are doing from the Sales Journal and the General Journal with the Customer selected as the Account Type.'; PurchaseJournalEntryDescriptionTxt: Label 'Specifies posting you are doing from the Purchase Journal and the General Journal with the Vendor selected as the Account Type.'; SalesDocumentEntryDescriptionTxt: Label 'Specifies postings you are doing from the sales documents.'; - SalesJournalEntryDescriptionTxt: Label 'Specifies posting you are doing from the Sales Journal and the General Journal with the Customer selected as the Account Type.'; - VoucherEntryTypeDescription: Text; + PurchaseDocumentEntryDescriptionTxt: Label 'Specifies postings you are doing from the purchase documents.'; trigger OnOpenPage() var From 5264655c970a27b9275fbef257f26f4ecbd7bc4e Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute Date: Wed, 4 Mar 2026 15:44:29 +0200 Subject: [PATCH 09/12] revert Digital Voucher Entry Setup changes --- .../app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al index d3fe5e4ece..32e88c136c 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/EntrySetup/DigitalVoucherEntrySetup.Page.al @@ -146,4 +146,4 @@ page 5579 "Digital Voucher Entry Setup" local procedure OnBeforeSetVoucherEntryTypeDescription(var NewVoucherEntryTypeDescription: Text; var IsHandled: Boolean) begin end; -} \ No newline at end of file +} From acc7cb2d822cd078e469770b57a807ab11f89c91 Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute Date: Wed, 4 Mar 2026 16:23:11 +0200 Subject: [PATCH 10/12] Structure of codeunit 5579 "Digital Voucher Impl." update --- .../DigitalVoucherImpl.Codeunit.al | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al index c6f67b823d..eb09512c0c 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al @@ -302,15 +302,22 @@ codeunit 5579 "Digital Voucher Impl." IncomingDocumentAttachment.Modify(false); end; - local procedure AttachPurchaseEDocument(EDocument: Record "E-Document"; DocumentNo: Code[20]; PostingDate: Date) + local procedure AttachOutgoingEDocument(var EDocument: Record "E-Document"; PostedRecord: Variant) var - EDocDataStorage: Record "E-Doc. Data Storage"; - IncomingDocumentAttachment: Record "Incoming Document Attachment"; - ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; - TempBlob: Codeunit "Temp Blob"; - EDocumentFileNameLbl: Label 'E-Document_%1.%2', Comment = '%1 = E-Document Entry No., %2 = File Format', Locked = true; - FileName: Text[250]; + DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; + VoucherEDocumentCheck: Codeunit "Voucher E-Document Check"; + RecRef: RecordRef; + DocNo: Code[20]; + PostingDate: Date; + DocType: Text; begin + if not DigitalVoucherFeature.IsFeatureEnabled() then + exit; + + GetDigitalVoucherEntrySetup(DigitalVoucherEntrySetup, "Digital Voucher Entry Type"::"Purchase Document"); + if DigitalVoucherEntrySetup."Check Type" <> DigitalVoucherEntrySetup."Check Type"::"E-Document" then + exit; + if not (EDocument."Document Type" in [ EDocument."Document Type"::"Purchase Invoice", EDocument."Document Type"::"Purchase Credit Memo", @@ -319,6 +326,20 @@ codeunit 5579 "Digital Voucher Impl." EDocument."Document Type"::"Purchase Return Order"]) then exit; + RecRef.GetTable(PostedRecord); + DigitalVoucherEntry.GetDocNoAndPostingDateFromRecRef(DocType, DocNo, PostingDate, RecRef); + AttachPurchaseEDocument(EDocument, DocNo, PostingDate); + end; + + local procedure AttachPurchaseEDocument(EDocument: Record "E-Document"; DocumentNo: Code[20]; PostingDate: Date) + var + EDocDataStorage: Record "E-Doc. Data Storage"; + IncomingDocumentAttachment: Record "Incoming Document Attachment"; + ImportAttachmentIncDoc: Codeunit "Import Attachment - Inc. Doc."; + TempBlob: Codeunit "Temp Blob"; + EDocumentFileNameLbl: Label 'E-Document_%1.%2', Comment = '%1 = E-Document Entry No., %2 = File Format', Locked = true; + FileName: Text[250]; + begin if EDocument."Unstructured Data Entry No." = 0 then exit; @@ -819,36 +840,7 @@ codeunit 5579 "Digital Voucher Impl." [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Document Subscribers", OnAfterUpdateToPostedPurchaseEDocument, '', false, false)] local procedure EDocumentSubscribers_OnAfterUpdateToPostedPurchaseEDocument(var EDocument: Record "E-Document"; PostedRecord: Variant; DocumentType: Enum "E-Document Type") begin - AttachOutgoingDocument(EDocument, PostedRecord); - end; - - local procedure AttachOutgoingDocument(var EDocument: Record "E-Document"; PostedRecord: Variant) - var - DigitalVoucherEntrySetup: Record "Digital Voucher Entry Setup"; - VoucherEDocumentCheck: Codeunit "Voucher E-Document Check"; - RecRef: RecordRef; - DocNo: Code[20]; - PostingDate: Date; - DocType: Text; - begin - if not DigitalVoucherFeature.IsFeatureEnabled() then - exit; - - GetDigitalVoucherEntrySetup(DigitalVoucherEntrySetup, "Digital Voucher Entry Type"::"Purchase Document"); - if DigitalVoucherEntrySetup."Check Type" <> DigitalVoucherEntrySetup."Check Type"::"E-Document" then - exit; - - if not (EDocument."Document Type" in [ - EDocument."Document Type"::"Purchase Invoice", - EDocument."Document Type"::"Purchase Credit Memo", - EDocument."Document Type"::"Purchase Order", - EDocument."Document Type"::"Purchase Quote", - EDocument."Document Type"::"Purchase Return Order"]) then - exit; - - RecRef.GetTable(PostedRecord); - DigitalVoucherEntry.GetDocNoAndPostingDateFromRecRef(DocType, DocNo, PostingDate, RecRef); - AttachPurchaseEDocument(EDocument, DocNo, PostingDate); + AttachOutgoingEDocument(EDocument, PostedRecord); end; [IntegrationEvent(false, false)] From c035881303c5da30fdca42c3e37bfa72f1c0877a Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute Date: Wed, 4 Mar 2026 16:24:56 +0200 Subject: [PATCH 11/12] Removed unnessasary empty line --- .../app/src/Implementation/DigitalVoucherImpl.Codeunit.al | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al index eb09512c0c..e07710c656 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/DigitalVoucherImpl.Codeunit.al @@ -358,7 +358,6 @@ codeunit 5579 "Digital Voucher Impl." IncomingDocumentAttachment.SetRange("Document No.", DocumentNo); IncomingDocumentAttachment.SetRange("Posting Date", PostingDate); IncomingDocumentAttachment.SetContentFromBlob(TempBlob); - if not ImportAttachmentIncDoc.ImportAttachment(IncomingDocumentAttachment, FileName, TempBlob) then exit; From 30dc167f102665b80f3cb28b70308842ecbb2f0a Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute Date: Wed, 4 Mar 2026 16:49:08 +0200 Subject: [PATCH 12/12] Updated sequence of code in codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" --- .../app/src/Implementation/VoucherEDocumentCheck.Codeunit.al | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al index 04d0e4b0bc..613d7e55d5 100644 --- a/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al +++ b/Apps/W1/EnforcedDigitalVouchers/app/src/Implementation/VoucherEDocumentCheck.Codeunit.al @@ -25,10 +25,10 @@ codeunit 5588 "Voucher E-Document Check" implements "Digital Voucher Check" DigitalVoucherImpl: Codeunit "Digital Voucher Impl."; NotPossibleToPostWithoutEDocumentErr: Label 'Not possible to post without linking an E-Document.'; begin - if DigitalVoucherEntryType <> DigitalVoucherEntryType::"Purchase Document" then + if not DigitalVoucherFeature.IsFeatureEnabled() then exit; - if not DigitalVoucherFeature.IsFeatureEnabled() then + if DigitalVoucherEntryType <> DigitalVoucherEntryType::"Purchase Document" then exit; DigitalVoucherImpl.GetDigitalVoucherEntrySetup(DigitalVoucherEntrySetup, DigitalVoucherEntryType);