diff --git a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchaseHeader.TableExt.al b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchaseHeader.TableExt.al index 1dcdb89d79..5f53d989a3 100644 --- a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchaseHeader.TableExt.al +++ b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchaseHeader.TableExt.al @@ -1,4 +1,11 @@ -#pragma warning disable AA0247 +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Purchases.Document; + +using Microsoft.eServices.EDocument; + tableextension 6169 "E-Doc. Purchase Header" extends "Purchase Header" { diff --git a/src/Apps/W1/EDocument/App/src/Processing/EDocumentProcessing.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/EDocumentProcessing.Codeunit.al index f99890bac4..05d17f0cec 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/EDocumentProcessing.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/EDocumentProcessing.Codeunit.al @@ -5,6 +5,7 @@ namespace Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.Finance.GeneralLedger.Journal; using Microsoft.Finance.GeneralLedger.Ledger; using Microsoft.Foundation.Reporting; @@ -687,6 +688,55 @@ codeunit 6108 "E-Document Processing" exit(RecCaption); end; + internal procedure ErrorIfNotAllowedToLinkToExistingDoc(EDocument: Record "E-Document"; EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + Vendor: Record Vendor; + NoVendorErr: Label 'Cannot link e-document to existing purchase document because vendor number is missing in e-document purchase header.'; + begin + if EDocumentPurchaseHeader."[BC] Vendor No." = '' then + Error(NoVendorErr); + if Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + Vendor.TestField("IC Partner Code"); + end; + + procedure OpenPurchaseDocumentList(EDocumentType: Enum "E-Document Type"; var PurchaseHeader: Record "Purchase Header"): Boolean + var + PurchaseInvoices: Page "Purchase Invoices"; + PurchaseOrders: Page "Purchase Orders"; + PurchaseCreditMemos: Page "Purchase Credit Memos"; + begin + case EDocumentType of + EDocumentType::"Purchase Invoice": + begin + PurchaseInvoices.SetTableView(PurchaseHeader); + PurchaseInvoices.LookupMode := true; + if PurchaseInvoices.RunModal() = Action::LookupOK then begin + PurchaseInvoices.GetRecord(PurchaseHeader); + exit(true); + end; + end; + EDocumentType::"Purchase Credit Memo": + begin + PurchaseCreditMemos.SetTableView(PurchaseHeader); + PurchaseCreditMemos.LookupMode := true; + if PurchaseCreditMemos.RunModal() = Action::LookupOK then begin + PurchaseCreditMemos.GetRecord(PurchaseHeader); + exit(true); + end; + end; + EDocumentType::"Purchase Order": + begin + PurchaseOrders.SetTableView(PurchaseHeader); + PurchaseOrders.LookupMode := true; + if PurchaseOrders.RunModal() = Action::LookupOK then begin + PurchaseOrders.GetRecord(PurchaseHeader); + exit(true); + end; + end; + end; + exit(false); + end; + [IntegrationEvent(false, false)] local procedure OnAfterGetTypeFromSourceDocument(RecordVariant: Variant; var EDocumentType: Enum "E-Document Type") begin diff --git a/src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al index b5d502c026..c992aa71fd 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al @@ -394,6 +394,8 @@ codeunit 6103 "E-Document Subscribers" EDocImportParameters."Step to Run / Desired Status" := EDocImportParameters."Step to Run / Desired Status"::"Desired E-Document Status"; EDocImportParameters."Desired E-Document Status" := "Import E-Doc. Proc. Status"::"Draft Ready"; EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParameters); + + PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No."); end; [EventSubscriber(ObjectType::Codeunit, Codeunit::"Data Classification Eval. Data", 'OnCreateEvaluationDataOnAfterClassifyTablesToNormal', '', false, false)] diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/EDocImportParameters.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/EDocImportParameters.Table.al index c0cdec5f7c..dcfbcf8264 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/EDocImportParameters.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/EDocImportParameters.Table.al @@ -57,5 +57,13 @@ table 6106 "E-Doc. Import Parameters" { } #endregion + + /// + /// Specifies an existing purchase document to link to instead of creating a new one. + /// When set, the ApplyDraftToBC step will link the e-document to this existing document rather than creating a new purchase invoice. + /// + field(10; "Existing Doc. RecordId"; RecordId) + { + } } } \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al index cef186eb24..146435436d 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al @@ -38,6 +38,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, TempPOMatchWarnings: Record "E-Doc PO Match Warning" temporary; EDocPOMatching: Codeunit "E-Doc. PO Matching"; DocumentAttachmentMgt: Codeunit "Document Attachment Mgmt"; + EmptyRecordId: RecordId; IEDocumentFinishPurchaseDraft: Interface IEDocumentCreatePurchaseInvoice; YourMatchedLinesAreNotValidErr: Label 'The purchase invoice cannot be created because one or more of its matched lines are not valid matches. Review if your configuration allows for receiving at invoice.'; SomeLinesNotYetReceivedErr: Label 'Some of the matched purchase order lines have not yet been received, you need to either receive the lines or remove the matches.'; @@ -54,14 +55,17 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, Error(SomeLinesNotYetReceivedErr); IEDocumentFinishPurchaseDraft := EDocImportParameters."Processing Customizations"; - PurchaseHeader := IEDocumentFinishPurchaseDraft.CreatePurchaseInvoice(EDocument); + if EDocImportParameters."Existing Doc. RecordId" <> EmptyRecordId then begin + EDocImpSessionTelemetry.SetBool('LinkedToExisting', true); + PurchaseHeader.Get(EDocImportParameters."Existing Doc. RecordId"); + end else + PurchaseHeader := IEDocumentFinishPurchaseDraft.CreatePurchaseInvoice(EDocument); EDocPOMatching.TransferPOMatchesFromEDocumentToInvoice(EDocument); PurchaseHeader.SetRecFilter(); PurchaseHeader.FindFirst(); PurchaseHeader."Doc. Amount Incl. VAT" := EDocumentPurchaseHeader.Total; PurchaseHeader."Doc. Amount VAT" := EDocumentPurchaseHeader."Total VAT"; - PurchaseHeader.TestField("Document Type", "Purchase Document Type"::Invoice); PurchaseHeader.TestField("No."); PurchaseHeader."E-Document Link" := EDocument.SystemId; PurchaseHeader.Modify(); @@ -85,12 +89,14 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); if not PurchaseHeader.FindFirst() then exit; + EDocPOMatching.TransferPOMatchesFromInvoiceToEDocument(PurchaseHeader); DocumentAttachmentMgt.CopyAttachments(PurchaseHeader, EDocument); DocumentAttachmentMgt.DeleteAttachedDocuments(PurchaseHeader); + PurchaseHeader.TestField("Document Type", "Purchase Document Type"::Invoice); Clear(PurchaseHeader."E-Document Link"); - PurchaseHeader.Delete(true); + PurchaseHeader.Modify(); end; procedure CreatePurchaseInvoice(EDocument: Record "E-Document"): Record "Purchase Header" @@ -264,5 +270,4 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, if PurchaseLine.FindLast() then exit(PurchaseLine."Line No."); end; - } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index 60832a5017..4905732652 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.Foundation.Attachment; +using Microsoft.Purchases.Document; using Microsoft.Purchases.Vendor; using System.Feedback; using System.Telemetry; @@ -282,8 +283,11 @@ page 6181 "E-Document Purchase Draft" Visible = ShowFinalizeDraftAction; trigger OnAction() + var + EDocImportParameters: Record "E-Doc. Import Parameters"; begin - FinalizeEDocument(); + Session.LogMessage('0000PCO', FinalizeDraftInvokedTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All, 'Category', EDocumentPurchaseHeader.FeatureName()); + FinalizeEDocument(EDocImportParameters); end; } action(ResetDraftDocument) @@ -311,6 +315,19 @@ page 6181 "E-Document Purchase Draft" AnalyzeEDocument(); end; } + action(LinkToExistingDocument) + { + ApplicationArea = Basic, Suite; + Caption = 'Link to existing document'; + ToolTip = 'Link this electronic document to an existing purchase document in Business Central. Use this when you have already created the document manually and want to attach the e-document to it.'; + Image = Links; + Visible = false; + + trigger OnAction() + begin + DoLinkToExistingDocument(); + end; + } } group(ViewDocument) { @@ -570,18 +587,15 @@ page 6181 "E-Document Purchase Draft" CurrPage.ErrorMessagesFactBox.Page.Update(false); end; - local procedure FinalizeEDocument() + local procedure FinalizeEDocument(EDocImportParameters: Record "E-Doc. Import Parameters") var TempErrorMessage: Record "Error Message" temporary; ErrorMessage: Record "Error Message"; - EDocImportParameters: Record "E-Doc. Import Parameters"; EDocImport: Codeunit "E-Doc. Import"; EDocImpSessionTelemetry: Codeunit "E-Doc. Imp. Session Telemetry"; Telemetry: Codeunit Telemetry; CustomDimensions: Dictionary of [Text, Text]; begin - Session.LogMessage('0000PCO', FinalizeDraftInvokedTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All, 'Category', EDocumentPurchaseHeader.FeatureName()); - if not GlobalEDocumentHelper.EnsureInboundEDocumentHasService(Rec) then exit; @@ -695,6 +709,29 @@ page 6181 "E-Document Purchase Draft" end; end; + local procedure DoLinkToExistingDocument() + var + PurchaseHeader: Record "Purchase Header"; + EDocImportParameters: Record "E-Doc. Import Parameters"; + ConfirmDialogMgt: Codeunit "Confirm Management"; + LinkToExistingDocumentQst: Label 'Do you want to link this e-document to %1 %2?', Comment = '%1 = Document Type, %2 = Document No.'; + RelinkToExistingDocumentQst: Label 'This e-document is already linked to a document. Linking to %1 %2 will unlink the currently linked document. You will need to manually clean up that document. Do you want to continue?', Comment = '%1 = Document Type, %2 = Document No.'; + ConfirmQst: Text; + begin + EDocumentProcessing.ErrorIfNotAllowedToLinkToExistingDoc(Rec, EDocumentPurchaseHeader); + PurchaseHeader.SetRange("Buy-from Vendor No.", EDocumentPurchaseHeader."[BC] Vendor No."); + PurchaseHeader.SetRange("Doc. Amount Incl. VAT", EDocumentPurchaseHeader.Total); + if not EDocumentProcessing.OpenPurchaseDocumentList(Rec."Document Type", PurchaseHeader) then + exit; + + ConfirmQst := StrSubstNo(Rec.Status = Rec.Status::Processed ? RelinkToExistingDocumentQst : LinkToExistingDocumentQst, PurchaseHeader."Document Type", PurchaseHeader."No."); + if not ConfirmDialogMgt.GetResponseOrDefault(ConfirmQst, Rec.Status <> Rec.Status::Processed) then + exit; + + EDocImportParameters."Existing Doc. RecordId" := PurchaseHeader.RecordId(); + FinalizeEDocument(EDocImportParameters); + end; + var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; EDocumentServiceStatus: Record "E-Document Service Status"; diff --git a/src/Apps/W1/EDocument/Test/src/LibraryEDocument.Codeunit.al b/src/Apps/W1/EDocument/Test/src/LibraryEDocument.Codeunit.al index af223e470b..047fc89157 100644 --- a/src/Apps/W1/EDocument/Test/src/LibraryEDocument.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/LibraryEDocument.Codeunit.al @@ -253,6 +253,8 @@ codeunit 139629 "Library - E-Document" else EntryNo := EDocument."Entry No"; EDocument."Entry No" := EntryNo; + EDocument.Direction := EDocument.Direction::Incoming; + EDocument.Service := EDocService.Code; EDocument.Insert(); EDocumentServiceStatus."E-Document Entry No" := EntryNo; EDocumentServiceStatus."E-Document Service Code" := EDocService.Code; diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocLinkToExistingTest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocLinkToExistingTest.Codeunit.al new file mode 100644 index 0000000000..700fa146e3 --- /dev/null +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocLinkToExistingTest.Codeunit.al @@ -0,0 +1,597 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Test; + +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Integration; +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.Intercompany.Partner; +using Microsoft.Purchases.Document; +using Microsoft.Purchases.Setup; +using Microsoft.Purchases.Vendor; +using System.TestLibraries.Utilities; + + +codeunit 139886 "E-Doc Link To Existing Test" +{ + Subtype = Test; + TestType = IntegrationTest; + + #region Validation Tests + + [Test] + procedure LinkToExisting_ErrorWhenVendorNoIsMissing() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + NoVendorErr: Label 'Cannot link e-document to existing purchase document because vendor number is missing in e-document purchase header.'; + begin + // [SCENARIO] Should error when EDocumentPurchaseHeader."[BC] Vendor No." is empty + Initialize(); + + // [GIVEN] An inbound e-document with draft prepared but no vendor linked + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := ''; + EDocumentPurchaseHeader.Modify(); + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + + // [THEN] An error is thrown indicating vendor number is missing + asserterror EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + Assert.ExpectedError(NoVendorErr); + end; + + [Test] + procedure LinkToExisting_ErrorWhenVendorHasNoICPartnerCode() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + begin + // [SCENARIO] Should error when vendor exists but IC Partner Code is blank + Initialize(); + + // [GIVEN] An inbound e-document with draft prepared and vendor linked, but vendor has no IC Partner Code + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Modify(); + + // [GIVEN] The vendor has no IC Partner Code + Vendor."IC Partner Code" := ''; + Vendor.Modify(); + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + + // [THEN] An error is thrown indicating IC Partner Code must have a value + asserterror EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + Assert.ExpectedErrorCode('TestField'); + Assert.ExpectedError('IC Partner Code'); + end; + + [Test] + [HandlerFunctions('PurchaseInvoicesModalPageHandler,ConfirmHandler,PIModalPageHandler')] + procedure LinkToExisting_SuccessWhenVendorHasICPartnerCode() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + PurchaseHeader: Record "Purchase Header"; + ICPartner: Record "IC Partner"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + begin + // [SCENARIO] Should succeed when vendor has IC Partner Code set + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document with draft prepared and vendor linked + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Total := 1000; + EDocumentPurchaseHeader.Modify(); + + // [GIVEN] An existing purchase invoice from the same vendor + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No."); + PurchaseHeader."Doc. Amount Incl. VAT" := 1000; + PurchaseHeader.Modify(); + LibraryVariableStorage.Enqueue(true); // Signal handler to select + LibraryVariableStorage.Enqueue(PurchaseHeader."No."); // For modal handler + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + + // [THEN] The e-document is linked to the existing purchase document + PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No."); + Assert.AreEqual(EDocument.SystemId, PurchaseHeader."E-Document Link", 'E-Document Link should be set on purchase header'); + + // [THEN] The e-document is marked as processed + EDocument.Get(EDocument."Entry No"); + Assert.AreEqual(Enum::"E-Document Status"::Processed, EDocument.Status, 'E-Document should be marked as processed'); + end; + + + #endregion + + #region Document Selection Tests + + [Test] + [HandlerFunctions('PurchaseInvoicesModalPageHandler')] + procedure LinkToExisting_OpensInvoiceListForPurchaseInvoice() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + ICPartner: Record "IC Partner"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + begin + // [SCENARIO] When e-document type is Purchase Invoice, the Purchase Invoices page opens + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document of type Purchase Invoice + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocument."Document Type" := Enum::"E-Document Type"::"Purchase Invoice"; + EDocument.Modify(); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Modify(); + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + LibraryVariableStorage.Enqueue(false); // Signal handler to cancel (no document to select) + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + + // [THEN] The Purchase Invoices page is opened (verified by handler being called) + // Handler is called - test passes if no error about missing handler + end; + + [Test] + [HandlerFunctions('PurchaseCreditMemosModalPageHandler')] + procedure LinkToExisting_OpensCreditMemoListForPurchaseCreditMemo() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + ICPartner: Record "IC Partner"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + begin + // [SCENARIO] When e-document type is Purchase Credit Memo, the Purchase Credit Memos page opens + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document of type Purchase Credit Memo + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocument."Document Type" := Enum::"E-Document Type"::"Purchase Credit Memo"; + EDocument.Modify(); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Modify(); + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + + // [THEN] The Purchase Credit Memos page is opened (verified by handler being called) + // Handler is called - test passes if no error about missing handler + end; + + [Test] + [HandlerFunctions('PurchaseInvoicesVerifyVendorFilterHandler')] + procedure LinkToExisting_FiltersDocumentsByVendor() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + PurchaseHeaderMatchingVendor: Record "Purchase Header"; + PurchaseHeaderOtherVendor: Record "Purchase Header"; + OtherVendor: Record Vendor; + ICPartner: Record "IC Partner"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + begin + // [SCENARIO] The document list should be pre-filtered by vendor no. + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document with vendor linked + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Total := 1000; + EDocumentPurchaseHeader.Modify(); + + // [GIVEN] A purchase invoice from the matching vendor + LibraryPurchase.CreatePurchHeader(PurchaseHeaderMatchingVendor, PurchaseHeaderMatchingVendor."Document Type"::Invoice, Vendor."No."); + PurchaseHeaderMatchingVendor."Doc. Amount Incl. VAT" := 1000; + PurchaseHeaderMatchingVendor.Modify(); + + // [GIVEN] A purchase invoice from a different vendor + LibraryPurchase.CreateVendor(OtherVendor); + LibraryPurchase.CreatePurchHeader(PurchaseHeaderOtherVendor, PurchaseHeaderOtherVendor."Document Type"::Invoice, OtherVendor."No."); + PurchaseHeaderOtherVendor."Doc. Amount Incl. VAT" := 1000; + PurchaseHeaderOtherVendor.Modify(); + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + LibraryVariableStorage.Enqueue(Vendor."No."); // Expected vendor filter + LibraryVariableStorage.Enqueue(PurchaseHeaderMatchingVendor."No."); // Should be visible + LibraryVariableStorage.Enqueue(PurchaseHeaderOtherVendor."No."); // Should NOT be visible + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + + // [THEN] The handler verifies the vendor filter is applied + end; + + [Test] + [HandlerFunctions('PurchaseInvoicesVerifyAmountFilterHandler')] + procedure LinkToExisting_FiltersDocumentsByAmount() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + PurchaseHeaderMatchingAmount: Record "Purchase Header"; + PurchaseHeaderOtherAmount: Record "Purchase Header"; + ICPartner: Record "IC Partner"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + begin + // [SCENARIO] The document list should be pre-filtered by Doc. Amount Incl. VAT matching e-doc total + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document with total amount set + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Total := 1500; + EDocumentPurchaseHeader.Modify(); + + // [GIVEN] A purchase invoice with matching amount + LibraryPurchase.CreatePurchHeader(PurchaseHeaderMatchingAmount, PurchaseHeaderMatchingAmount."Document Type"::Invoice, Vendor."No."); + PurchaseHeaderMatchingAmount."Doc. Amount Incl. VAT" := 1500; + PurchaseHeaderMatchingAmount.Modify(); + + // [GIVEN] A purchase invoice with different amount + LibraryPurchase.CreatePurchHeader(PurchaseHeaderOtherAmount, PurchaseHeaderOtherAmount."Document Type"::Invoice, Vendor."No."); + PurchaseHeaderOtherAmount."Doc. Amount Incl. VAT" := 2000; + PurchaseHeaderOtherAmount.Modify(); + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + LibraryVariableStorage.Enqueue(1500); // Expected amount filter + LibraryVariableStorage.Enqueue(PurchaseHeaderMatchingAmount."No."); // Should be visible + LibraryVariableStorage.Enqueue(PurchaseHeaderOtherAmount."No."); // Should NOT be visible + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + + // [THEN] The handler verifies the amount filter is applied + end; + + #endregion + + #region Post-Link Behavior Tests + + [Test] + [HandlerFunctions('PurchaseInvoicesModalPageHandler,ConfirmHandler,PIModalPageHandler')] + procedure LinkToExisting_VerifyPostLinkBehavior() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + PurchaseHeader: Record "Purchase Header"; + AllPurchaseHeaders: Record "Purchase Header"; + ICPartner: Record "IC Partner"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + InitialPurchaseHeaderCount: Integer; + begin + // [SCENARIO] Verify all post-link behaviors: + // - E-Document Link is set on purchase document + // - No new document is created + // - E-Document status becomes Processed + // - Doc amounts are transferred to purchase document + // - Created from E-Document is false (since it wasn't created from e-doc) + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document with draft prepared and specific amounts + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Total := 1500; + EDocumentPurchaseHeader."Total VAT" := 300; + EDocumentPurchaseHeader.Modify(); + + // [GIVEN] An existing purchase invoice from the same vendor + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No."); + PurchaseHeader."Doc. Amount Incl. VAT" := 1500; + PurchaseHeader.Modify(); + + // [GIVEN] Count of existing purchase headers + InitialPurchaseHeaderCount := AllPurchaseHeaders.Count(); + + LibraryVariableStorage.Enqueue(true); // Signal handler to select + LibraryVariableStorage.Enqueue(PurchaseHeader."No."); // For modal handler + + // [WHEN] Opening the draft page and clicking Link to Existing Document + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + + // [THEN] E-Document Link is set on purchase document + PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No."); + Assert.AreEqual(EDocument.SystemId, PurchaseHeader."E-Document Link", 'E-Document Link should be set on purchase header'); + + // [THEN] No new purchase document was created + Assert.AreEqual(InitialPurchaseHeaderCount, AllPurchaseHeaders.Count(), 'No new purchase document should be created when linking to existing'); + + // [THEN] E-Document status becomes Processed + EDocument.Get(EDocument."Entry No"); + Assert.AreEqual(Enum::"E-Document Status"::Processed, EDocument.Status, 'E-Document should be marked as processed'); + + // [THEN] Doc amounts are transferred to purchase document + Assert.AreEqual(1500, PurchaseHeader."Doc. Amount Incl. VAT", 'Doc. Amount Incl. VAT should be set from e-document'); + Assert.AreEqual(300, PurchaseHeader."Doc. Amount VAT", 'Doc. Amount VAT should be set from e-document'); + end; + + [Test] + [HandlerFunctions('PurchaseInvoicesModalPageHandler,ConfirmHandler,PIModalPageHandler')] + procedure LinkToExisting_RelinkToAnotherDocumentUnlinksFirst() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + PurchaseHeaderA: Record "Purchase Header"; + PurchaseHeaderB: Record "Purchase Header"; + ICPartner: Record "IC Partner"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + EmptyGuid: Guid; + begin + // [SCENARIO] When relinking an e-document to a different document: + // - The first document (A) is unlinked + // - The second document (B) is linked + // - Neither document is deleted since they weren't created from e-doc + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document with draft prepared + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Total := 1000; + EDocumentPurchaseHeader."Total VAT" := 200; + EDocumentPurchaseHeader.Modify(); + + // [GIVEN] Two existing purchase invoices from the same vendor + LibraryPurchase.CreatePurchHeader(PurchaseHeaderA, PurchaseHeaderA."Document Type"::Invoice, Vendor."No."); + PurchaseHeaderA."Doc. Amount Incl. VAT" := 1000; + PurchaseHeaderA.Modify(); + + LibraryPurchase.CreatePurchHeader(PurchaseHeaderB, PurchaseHeaderB."Document Type"::Invoice, Vendor."No."); + PurchaseHeaderB."Doc. Amount Incl. VAT" := 1000; + PurchaseHeaderB.Modify(); + + // [WHEN] Linking to document A first + LibraryVariableStorage.Enqueue(true); + LibraryVariableStorage.Enqueue(PurchaseHeaderA."No."); + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + EDocPurchaseDraftTestPage.Close(); + + // [THEN] Document A is linked + PurchaseHeaderA.Get(PurchaseHeaderA."Document Type", PurchaseHeaderA."No."); + Assert.AreEqual(EDocument.SystemId, PurchaseHeaderA."E-Document Link", 'Document A should be linked to e-document'); + + // [WHEN] Relinking to document B + LibraryVariableStorage.Enqueue(true); + LibraryVariableStorage.Enqueue(PurchaseHeaderB."No."); + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + EDocPurchaseDraftTestPage.Close(); + + // [THEN] Document A is unlinked (E-Document Link is cleared) + PurchaseHeaderA.Get(PurchaseHeaderA."Document Type", PurchaseHeaderA."No."); + Assert.AreEqual(EmptyGuid, PurchaseHeaderA."E-Document Link", 'Document A should be unlinked after relinking to B'); + + // [THEN] Document B is now linked + PurchaseHeaderB.Get(PurchaseHeaderB."Document Type", PurchaseHeaderB."No."); + Assert.AreEqual(EDocument.SystemId, PurchaseHeaderB."E-Document Link", 'Document B should now be linked to e-document'); + + // [THEN] Both documents still exist (neither was deleted) + Assert.IsTrue(PurchaseHeaderA.Get(PurchaseHeaderA."Document Type", PurchaseHeaderA."No."), 'Document A should still exist'); + Assert.IsTrue(PurchaseHeaderB.Get(PurchaseHeaderB."Document Type", PurchaseHeaderB."No."), 'Document B should still exist'); + end; + + [Test] + [HandlerFunctions('PurchaseInvoicesModalPageHandler,ConfirmHandler,PIModalPageHandler')] + procedure LinkToExisting_UnlinksDocumentCreatedFromEDoc() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + CreatedPurchaseHeader: Record "Purchase Header"; + ExistingPurchaseHeader: Record "Purchase Header"; + ICPartner: Record "IC Partner"; + EDocImportParameters: Record "E-Doc. Import Parameters"; + EDocImport: Codeunit "E-Doc. Import"; + EDocPurchaseDraftTestPage: TestPage "E-Document Purchase Draft"; + CreatedDocNo: Code[20]; + EmptyGuid: Guid; + begin + // [SCENARIO] When linking to an existing document after a PI was already created from e-doc: + // - The originally created PI is unlinked but NOT deleted (user must clean up manually) + // - The existing document is linked + Initialize(); + SetupICPartner(ICPartner); + + // [GIVEN] An inbound e-document with draft prepared + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Total := 2000; + EDocumentPurchaseHeader."Total VAT" := 400; + EDocumentPurchaseHeader.Modify(); + + // [GIVEN] Finalize draft to create a new PI from e-document + EDocImportParameters."Step to Run" := Enum::"Import E-Document Steps"::"Finish draft"; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParameters); + EDocument.Get(EDocument."Entry No"); + + // [GIVEN] Find the created purchase invoice + CreatedPurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); + CreatedPurchaseHeader.FindFirst(); + CreatedDocNo := CreatedPurchaseHeader."No."; + + // [GIVEN] An existing purchase invoice (not created from e-doc) + LibraryPurchase.CreatePurchHeader(ExistingPurchaseHeader, ExistingPurchaseHeader."Document Type"::Invoice, Vendor."No."); + ExistingPurchaseHeader."Doc. Amount Incl. VAT" := 2000; + ExistingPurchaseHeader.Modify(); + + // [WHEN] Linking to the existing document + LibraryVariableStorage.Enqueue(true); + LibraryVariableStorage.Enqueue(ExistingPurchaseHeader."No."); + EDocPurchaseDraftTestPage.OpenEdit(); + EDocPurchaseDraftTestPage.GoToRecord(EDocument); + EDocPurchaseDraftTestPage.LinkToExistingDocument.Invoke(); + EDocPurchaseDraftTestPage.Close(); + + // [THEN] The originally created PI is unlinked but still exists (user must clean up manually) + Assert.IsTrue(CreatedPurchaseHeader.Get(CreatedPurchaseHeader."Document Type"::Invoice, CreatedDocNo), 'The PI created from e-doc should still exist'); + Assert.AreEqual(EmptyGuid, CreatedPurchaseHeader."E-Document Link", 'The PI created from e-doc should be unlinked'); + + // [THEN] The existing document is now linked + ExistingPurchaseHeader.Get(ExistingPurchaseHeader."Document Type", ExistingPurchaseHeader."No."); + Assert.AreEqual(EDocument.SystemId, ExistingPurchaseHeader."E-Document Link", 'Existing document should be linked'); + end; + + #endregion + + [ModalPageHandler] + procedure PurchaseInvoicesModalPageHandler(var PurchaseInvoices: TestPage "Purchase Invoices") + var + PurchaseInvoiceNo: Code[20]; + ShouldSelect: Boolean; + begin + if LibraryVariableStorage.Length() > 0 then begin + ShouldSelect := LibraryVariableStorage.DequeueBoolean(); + if not ShouldSelect then + exit; // Cancel selection by not selecting anything + + PurchaseInvoiceNo := CopyStr(LibraryVariableStorage.DequeueText(), 1, MaxStrLen(PurchaseInvoiceNo)); + PurchaseInvoices.Filter.SetFilter("No.", PurchaseInvoiceNo); + PurchaseInvoices.First(); + PurchaseInvoices.OK().Invoke(); + end; + end; + + [ModalPageHandler] + procedure PurchaseCreditMemosModalPageHandler(var PurchaseCreditMemos: TestPage "Purchase Credit Memos") + begin + // Handler confirms the page opened - no selection needed for this test + end; + + [ModalPageHandler] + procedure PurchaseOrdersModalPageHandler(var PurchaseOrders: TestPage "Purchase Orders") + begin + // Handler confirms the page opened - no selection needed for this test + end; + + [ModalPageHandler] + procedure PurchaseInvoicesVerifyVendorFilterHandler(var PurchaseInvoices: TestPage "Purchase Invoices") + var + VisibleDocNo: Code[20]; + NotVisibleDocNo: Code[20]; + begin + LibraryVariableStorage.DequeueText(); // Discard expected vendor no. (not needed here) + VisibleDocNo := CopyStr(LibraryVariableStorage.DequeueText(), 1, MaxStrLen(VisibleDocNo)); + NotVisibleDocNo := CopyStr(LibraryVariableStorage.DequeueText(), 1, MaxStrLen(NotVisibleDocNo)); + + // Verify the matching vendor document is visible + PurchaseInvoices.Filter.SetFilter("No.", VisibleDocNo); + Assert.IsTrue(PurchaseInvoices.First(), 'Document from matching vendor should be visible'); + + // Verify the other vendor document is NOT visible (filter should exclude it) + PurchaseInvoices.Filter.SetFilter("No.", NotVisibleDocNo); + Assert.IsFalse(PurchaseInvoices.First(), 'Document from different vendor should not be visible due to vendor filter'); + end; + + [ModalPageHandler] + procedure PurchaseInvoicesVerifyAmountFilterHandler(var PurchaseInvoices: TestPage "Purchase Invoices") + var + VisibleDocNo: Code[20]; + NotVisibleDocNo: Code[20]; + begin + LibraryVariableStorage.DequeueDecimal(); + VisibleDocNo := CopyStr(LibraryVariableStorage.DequeueText(), 1, MaxStrLen(VisibleDocNo)); + NotVisibleDocNo := CopyStr(LibraryVariableStorage.DequeueText(), 1, MaxStrLen(NotVisibleDocNo)); + + // Verify the matching amount document is visible + PurchaseInvoices.Filter.SetFilter("No.", VisibleDocNo); + Assert.IsTrue(PurchaseInvoices.First(), 'Document with matching amount should be visible'); + + // Verify the different amount document is NOT visible (filter should exclude it) + PurchaseInvoices.Filter.SetFilter("No.", NotVisibleDocNo); + Assert.IsFalse(PurchaseInvoices.First(), 'Document with different amount should not be visible due to amount filter'); + end; + + [PageHandler] + procedure PIModalPageHandler(var PurchaseInvoice: TestPage "Purchase Invoice") + begin + PurchaseInvoice.Close(); // Opens after finalize draft + end; + + [ConfirmHandler] + procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean) + begin + Reply := true; + end; + + + local procedure Initialize() + begin + LibraryLowerPermission.SetOutsideO365Scope(); + LibraryPurchase.SetOrderNoSeriesInSetup(); + LibraryPurchase.SetPostedNoSeriesInSetup(); + SetInvoiceNoSeriesInSetup(); + + LibraryEDocument.SetupStandardVAT(); + LibraryEDocument.SetupStandardPurchaseScenario(Vendor, EDocumentService, Enum::"E-Document Format"::Mock, Enum::"Service Integration"::Mock, Enum::"E-Document Import Process"::"Version 2.0"); + Commit(); + end; + + local procedure SetInvoiceNoSeriesInSetup() + var + PurchasesPayablesSetup: Record "Purchases & Payables Setup"; + begin + PurchasesPayablesSetup.Get(); + PurchasesPayablesSetup.Validate("Invoice Nos.", LibraryERM.CreateNoSeriesCode()); + PurchasesPayablesSetup.Modify(); + end; + + local procedure SetupICPartner(var ICPartner: Record "IC Partner") + begin + if not ICPartner.Get('ICTEST') then begin + ICPartner.Init(); + ICPartner.Code := 'ICTEST'; + ICPartner.Name := 'IC Partner Test'; + ICPartner.Insert(); + end; + Vendor."IC Partner Code" := ICPartner.Code; + Vendor.Modify(); + end; + + var + EDocumentService: Record "E-Document Service"; + Vendor: Record Vendor; + Assert: Codeunit Assert; + LibraryEDocument: Codeunit "Library - E-Document"; + LibraryLowerPermission: Codeunit "Library - Lower Permissions"; + LibraryPurchase: Codeunit "Library - Purchase"; + LibraryERM: Codeunit "Library - ERM"; + LibraryVariableStorage: Codeunit "Library - Variable Storage"; +} \ No newline at end of file