diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/ExtensionLogo.png b/Apps/W1/EDocumentConnectors/SignUp/app/ExtensionLogo.png
new file mode 100644
index 000000000..4d2c9a626
Binary files /dev/null and b/Apps/W1/EDocumentConnectors/SignUp/app/ExtensionLogo.png differ
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/app.json b/Apps/W1/EDocumentConnectors/SignUp/app/app.json
new file mode 100644
index 000000000..3e80be610
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/app.json
@@ -0,0 +1,41 @@
+{
+ "id": "b56171bd-9a8e-47ad-a527-99f476d5af83",
+ "name": "E-Document Connector - SignUp",
+ "publisher": "Microsoft",
+ "brief": "E-Document Connector - SignUp",
+ "description": "E-Document Connector - SignUp",
+ "version": "26.0.0.0",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2204541",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "logo": "ExtensionLogo.png",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2206603",
+ "dependencies": [
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b",
+ "name": "E-Document Core",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ }
+ ],
+ "internalsVisibleTo": [],
+ "screenshots": [],
+ "platform": "26.0.0.0",
+ "idRanges": [
+ {
+ "from": 6380,
+ "to": 6389
+ }
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "allowDownloadingSource": true,
+ "includeSourceInSymbolFile": true
+ },
+ "application": "26.0.0.0",
+ "target": "Cloud",
+ "features": [
+ "TranslationFile"
+ ]
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpCompanyInformationExt.PageExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpCompanyInformationExt.PageExt.al
new file mode 100644
index 000000000..ea4cb5b24
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpCompanyInformationExt.PageExt.al
@@ -0,0 +1,38 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+using Microsoft.Foundation.Company;
+using Microsoft.eServices.EDocument;
+
+pageextension 6381 SignUpCompanyInformationExt extends "Company Information"
+{
+ layout
+ {
+ addafter(General)
+ {
+ group(ExFlowEInvoicing)
+ {
+ Caption = 'ExFlow E-Invoicing';
+ Visible = ExFlowEInvoicingVisible;
+
+ field("SignUp Service Participant Id"; Rec."SignUp Service Participant Id")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ }
+ }
+ }
+
+ var
+ ExFlowEInvoicingVisible: Boolean;
+
+ trigger OnAfterGetRecord()
+ var
+ EDocumentService: Record "E-Document Service";
+ begin
+ EDocumentService.SetRange("Service Integration V2", EDocumentService."Service Integration V2"::"ExFlow E-Invoicing");
+ ExFlowEInvoicingVisible := not EDocumentService.IsEmpty();
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpCompanyInformationExt.TableExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpCompanyInformationExt.TableExt.al
new file mode 100644
index 000000000..84fa797c5
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpCompanyInformationExt.TableExt.al
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+using Microsoft.Foundation.Company;
+
+tableextension 6381 SignUpCompanyInformationExt extends "Company Information"
+{
+ fields
+ {
+ field(6381; "SignUp Service Participant Id"; Text[100])
+ {
+ Caption = 'Service Participant Id';
+ ToolTip = 'ExFlow E-Invoicing Service Participant Id.';
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocSvcSupTypeExtPageExt.PageExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocSvcSupTypeExtPageExt.PageExt.al
new file mode 100644
index 000000000..5e15aa9ff
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocSvcSupTypeExtPageExt.PageExt.al
@@ -0,0 +1,64 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.eServices.EDocument;
+
+pageextension 6383 SignUpEDocSvcSupTypeExtPageExt extends "E-Doc Service Supported Types"
+{
+
+ layout
+ {
+ addlast(General)
+ {
+ field("Profile Id"; Rec."Profile Id")
+ {
+ ApplicationArea = All;
+ Visible = ExFlowEInvoicingVisible;
+ }
+ field("Profile Name"; Rec."Profile Name")
+ {
+ ApplicationArea = All;
+ Visible = ExFlowEInvoicingVisible;
+ }
+ }
+
+ }
+
+ actions
+ {
+ addlast(Processing)
+ {
+ action(PopulateMetaData)
+ {
+ ApplicationArea = All;
+ Caption = 'Retreieve Metadata Profiles';
+ ToolTip = 'Retreieves Metadata Profiles from service';
+ Promoted = true;
+ PromotedCategory = Process;
+ Visible = ExFlowEInvoicingVisible;
+ Image = Refresh;
+
+ trigger OnAction()
+ var
+ SignUpConnection: Codeunit SignUpConnection;
+ begin
+ SignUpConnection.UpdateMetadataProfile();
+ end;
+ }
+ }
+ }
+
+ trigger OnOpenPage()
+ var
+ SignUpHelpers: Codeunit SignUpHelpers;
+ begin
+ ExFlowEInvoicingVisible := SignUpHelpers.IsExFlowEInvoicing(Rec.GetFilter("E-Document Service Code"));
+ end;
+
+ var
+ ExFlowEInvoicingVisible: Boolean;
+
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocSvcSupportedTypeExt.TableExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocSvcSupportedTypeExt.TableExt.al
new file mode 100644
index 000000000..8377a775c
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocSvcSupportedTypeExt.TableExt.al
@@ -0,0 +1,35 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.eServices.EDocument;
+
+tableextension 6383 SignUpEDocSvcSupportedTypeExt extends "E-Doc. Service Supported Type"
+{
+ fields
+ {
+ field(6381; "Profile Id"; Integer)
+ {
+ Caption = 'Profile Id';
+ ToolTip = 'The unique identifier for the metadata profile.';
+ DataClassification = CustomerContent;
+ TableRelation = SignUpMetadataProfile;
+ BlankZero = true;
+
+ trigger OnValidate()
+ begin
+ Rec.CalcFields("Profile Name");
+ end;
+ }
+ field(6382; "Profile Name"; Text[250])
+ {
+ Caption = 'Profile Name';
+ ToolTip = 'The common name of the metadata profile.';
+ FieldClass = FlowField;
+ Editable = false;
+ CalcFormula = lookup(SignUpMetadataProfile."Profile Name" where("Profile ID" = field("Profile Id")));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocumentExt.TableExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocumentExt.TableExt.al
new file mode 100644
index 000000000..45f308d81
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpEDocumentExt.TableExt.al
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.eServices.EDocument;
+
+tableextension 6382 SignUpEDocumentExt extends "E-Document"
+{
+ fields
+ {
+ field(6381; "SignUp Document Id"; Text[50])
+ {
+ Caption = 'SignUp Document ID';
+ ToolTip = 'This value is used by ExFlow E-Invoicing';
+ }
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpIntegrationEnumExt.EnumExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpIntegrationEnumExt.EnumExt.al
new file mode 100644
index 000000000..cd2778e9c
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpIntegrationEnumExt.EnumExt.al
@@ -0,0 +1,16 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+using Microsoft.eServices.EDocument.Integration;
+using Microsoft.eServices.EDocument.Integration.Interfaces;
+
+enumextension 6380 SignUpIntegrationEnumExt extends "Service Integration"
+{
+ value(6380; "ExFlow E-Invoicing")
+ {
+ Caption = 'ExFlow E-Invoicing';
+ Implementation = IDocumentSender = SignUpIntegrationImpl, IDocumentReceiver = SignUpIntegrationImpl;
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpIntegrationImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpIntegrationImpl.Codeunit.al
new file mode 100644
index 000000000..a5e5ad50c
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/SignUpIntegrationImpl.Codeunit.al
@@ -0,0 +1,68 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using System.Utilities;
+using Microsoft.eServices.EDocument;
+using Microsoft.eServices.EDocument.Integration.Interfaces;
+using Microsoft.eServices.EDocument.Integration.Send;
+using Microsoft.eServices.EDocument.Integration.Receive;
+
+
+codeunit 6386 SignUpIntegrationImpl implements IDocumentSender, IDocumentReceiver, IDocumentResponseHandler, IReceivedDocumentMarker
+{
+ Access = Internal;
+
+ var
+ SignUpProcessing: Codeunit SignUpProcessing;
+
+ #region IDocumentSender
+ procedure Send(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; SendContext: Codeunit SendContext);
+ var
+ begin
+ this.SignUpProcessing.Send(EDocument, EDocumentService, SendContext);
+ end;
+ #endregion
+
+ #region IDocumentResponseHandler
+ procedure GetResponse(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; SendContext: Codeunit SendContext): Boolean;
+ begin
+ exit(this.SignUpProcessing.GetResponse(EDocument, EDocumentService, SendContext));
+ end;
+ #endregion
+
+ #region IDocumentReceiver
+ procedure ReceiveDocuments(var EDocumentService: Record "E-Document Service"; DocumentsMetadataTempBlobList: Codeunit "Temp Blob List"; ReceiveContext: Codeunit ReceiveContext)
+ begin
+ this.SignUpProcessing.ReceiveDocuments(EDocumentService, DocumentsMetadataTempBlobList, ReceiveContext);
+ end;
+
+ procedure DownloadDocument(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; DocumentMetadataTempBlob: Codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ begin
+ this.SignUpProcessing.DownloadDocument(EDocument, EDocumentService, DocumentMetadataTempBlob, ReceiveContext);
+ end;
+ #endregion
+
+
+ #region IReceivedDocumentMarker
+ procedure MarkFetched(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var DocumentTempBlob: Codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ begin
+ this.SignUpProcessing.MarkFetched(EDocument, EDocumentService, DocumentTempBlob, ReceiveContext);
+ end;
+ #endregion
+
+
+ [EventSubscriber(ObjectType::Page, Page::"E-Document Service", OnBeforeOpenServiceIntegrationSetupPage, '', false, false)]
+ local procedure OnBeforeOpenServiceIntegrationSetupPage(EDocumentService: Record "E-Document Service"; var IsServiceIntegrationSetupRun: Boolean)
+ var
+ SignUpConnectionSetupCard: Page SignUpConnectionSetupCard;
+ begin
+ if EDocumentService."Service Integration V2" <> EDocumentService."Service Integration V2"::"ExFlow E-Invoicing" then
+ exit;
+
+ SignUpConnectionSetupCard.RunModal();
+ IsServiceIntegrationSetupRun := true;
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCOEdit.PermissionSet.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCOEdit.PermissionSet.al
new file mode 100644
index 000000000..8197ae8d4
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCOEdit.PermissionSet.al
@@ -0,0 +1,16 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+permissionset 6382 SignUpEDCOEdit
+{
+ Access = Internal;
+ Assignable = false;
+ Caption = 'SignUp E-Doc. Connector - Edit', MaxLength = 30;
+ IncludedPermissionSets = SignUpEDCORead;
+ Permissions = tabledata SignUpConnectionSetup = IMD,
+ tabledata SignUpMetadataProfile = IMD;
+
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCOObjects.PermissionSet.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCOObjects.PermissionSet.al
new file mode 100644
index 000000000..d59f0bfbc
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCOObjects.PermissionSet.al
@@ -0,0 +1,26 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.eServices.EDocument;
+
+permissionset 6380 SignUpEDCOObjects
+{
+ Access = Internal;
+ Assignable = false;
+ Caption = 'SignUp E-Doc. Connector - Obj.', MaxLength = 30;
+
+ Permissions = table SignUpConnectionSetup = X,
+ table SignUpMetadataProfile = X,
+ table "E-Document Integration Log" = X,
+ page SignUpConnectionSetupCard = X,
+ page SignUpMetadataProfiles = X,
+ codeunit SignUpAPIRequests = X,
+ codeunit SignUpAuthentication = X,
+ codeunit SignUpConnection = X,
+ codeunit SignUpHelpers = X,
+ codeunit SignUpIntegrationImpl = X,
+ codeunit SignUpProcessing = X;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCORead.PermissionSet.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCORead.PermissionSet.al
new file mode 100644
index 000000000..1c0a7c576
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDCORead.PermissionSet.al
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.eServices.EDocument;
+
+permissionset 6381 SignUpEDCORead
+{
+ Access = Internal;
+ Assignable = false;
+ Caption = 'SignUp E-Doc. Connector - Read', MaxLength = 30;
+ IncludedPermissionSets = SignUpEDCOObjects;
+ Permissions = tabledata SignUpConnectionSetup = R,
+ tabledata SignUpMetadataProfile = R,
+ tabledata "E-Document Integration Log" = rim;
+
+
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpConnectionSetup.Table.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpConnectionSetup.Table.al
new file mode 100644
index 000000000..719e99132
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpConnectionSetup.Table.al
@@ -0,0 +1,99 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+table 6381 SignUpConnectionSetup
+{
+ Access = Internal;
+ DataPerCompany = false;
+ DataClassification = CustomerContent;
+
+ fields
+ {
+ field(1; PK; Code[10])
+ {
+ Caption = 'PK', Locked = true;
+ ToolTip = 'PK', Locked = true;
+ }
+ field(2; "Authentication URL"; Text[2048])
+ {
+ Caption = 'Authentication URL';
+ ToolTip = 'Specifies the URL to connect Microsoft Entra.';
+ }
+ field(3; "Client ID"; Guid)
+ {
+ Caption = 'Client ID';
+ ToolTip = 'Specifies the client ID.';
+ }
+ field(4; "Client Secret"; Guid)
+ {
+ Caption = 'Client Secret';
+ ToolTip = 'Specifies the client secret.';
+ }
+ field(5; "Environment Type"; Enum SignUpEnvironmentType)
+ {
+ Caption = 'Environment Type';
+ ToolTip = 'Specifies the environment type.';
+ }
+ field(6; "Service URL"; Text[2048])
+ {
+ Caption = 'Service URL';
+ ToolTip = 'Specifies the service URL.';
+
+ trigger OnValidate()
+ begin
+ if Rec."Service URL" <> '' then
+ Rec."Service URL" := CopyStr(Rec."Service URL".TrimEnd('/'), 1, MaxStrLen(Rec."Service URL"));
+ end;
+ }
+ field(7; "Root App ID"; Guid)
+ {
+ Caption = 'Root App ID';
+ ToolTip = 'Specifies the root app ID.';
+ }
+ field(8; "Root Secret"; Guid)
+ {
+ Caption = 'Root App Secret';
+ ToolTip = 'Specifies the root application secret.';
+ }
+ field(9; "Root Tenant"; Guid)
+ {
+ Caption = 'Root App Tenant';
+ ToolTip = 'Specifies the root application tenant.';
+ }
+ field(10; "Root Market URL"; Text[2048])
+ {
+ Caption = 'Root Market URL';
+ ToolTip = 'Specifies the root market URL.';
+
+ trigger OnValidate()
+ begin
+ if Rec."Root Market URL" <> '' then
+ Rec."Root Market URL" := CopyStr(Rec."Root Market URL".TrimEnd('/'), 1, MaxStrLen("Root Market URL"));
+ end;
+ }
+ field(11; "Client Tenant"; Guid)
+ {
+ Caption = 'Client App Tenant';
+ ToolTip = 'Specifies the client application tenant.';
+ }
+ }
+
+ keys
+ {
+ key(Key1; PK)
+ {
+ Clustered = true;
+ }
+ }
+
+ procedure GetSetup(): Boolean
+ begin
+ if not IsNullGuid(Rec.SystemId) then
+ exit(true);
+
+ exit(Rec.Get());
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpConnectionSetupCard.Page.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpConnectionSetupCard.Page.al
new file mode 100644
index 000000000..39f055038
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpConnectionSetupCard.Page.al
@@ -0,0 +1,199 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using System.Telemetry;
+using Microsoft.Foundation.Company;
+using System.Environment;
+
+page 6380 SignUpConnectionSetupCard
+{
+ AdditionalSearchTerms = 'SignUp,electronic document,e-invoice,e-document,external,connection,connector';
+ PageType = Card;
+ SourceTable = SignUpConnectionSetup;
+ ApplicationArea = Basic, Suite;
+ UsageCategory = None;
+ Caption = 'E-Document External Connection Setup';
+ Extensible = false;
+
+
+ layout
+ {
+ area(Content)
+ {
+ group(General)
+ {
+ field(ClientID; this.ClientID)
+ {
+ Caption = 'Client ID';
+ ToolTip = 'Specifies the client ID token.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ ShowMandatory = true;
+
+ trigger OnValidate()
+ begin
+ this.SignUpAuthentication.StorageSet(Rec."Client ID", this.ClientID);
+ end;
+ }
+ field(ClientSecret; this.ClientSecret)
+ {
+ Caption = 'Client Secret';
+ ToolTip = 'Specifies the client secret token.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ ShowMandatory = true;
+
+ trigger OnValidate()
+ begin
+ this.SaveSecret(Rec."Client Secret", this.ClientSecret);
+ end;
+ }
+ field(ClientTenant; this.ClientTenant)
+ {
+ Caption = 'Client Tenant';
+ ToolTip = 'Specifies the client tenant.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ Visible = this.FieldsVisible;
+ ShowMandatory = true;
+
+ trigger OnValidate()
+ begin
+ this.SignUpAuthentication.StorageSet(Rec."Client Tenant", this.ClientTenant);
+ end;
+ }
+ field(RootID; this.RootID)
+ {
+ Caption = 'Root App ID';
+ ToolTip = 'Specifies the root app id token.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ Visible = this.FieldsVisible;
+
+ trigger OnValidate()
+ begin
+ this.SignUpAuthentication.StorageSet(Rec."Root App ID", this.RootID);
+ end;
+ }
+ field(RootSecret; this.RootSecret)
+ {
+ Caption = 'Root Secret';
+ ToolTip = 'Specifies the root secret token.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ Visible = this.FieldsVisible;
+
+ trigger OnValidate()
+ begin
+ this.SaveSecret(Rec."Root Secret", this.RootSecret)
+ end;
+ }
+ field(RootTenant; this.RootTenant)
+ {
+ Caption = 'Root Tenant ID';
+ ToolTip = 'Specifies the root tenant id token.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ Visible = this.FieldsVisible;
+
+ trigger OnValidate()
+ begin
+ this.SignUpAuthentication.StorageSet(Rec."Root Tenant", this.RootTenant);
+ end;
+ }
+ field(RootUrl; Rec."Root Market URL")
+ {
+ Caption = 'Root URL';
+ ToolTip = 'Specifies the root url token.';
+ ApplicationArea = Basic, Suite;
+ Visible = this.FieldsVisible;
+ }
+ field("Authentication URL"; Rec."Authentication URL")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field(ServiceURL; Rec."Service URL")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field("Environment Type"; Rec."Environment Type")
+ {
+ ApplicationArea = Basic, Suite;
+ ShowMandatory = true;
+ }
+ }
+ }
+ }
+
+ actions
+ {
+ area(processing)
+ {
+ action(InitOnboarding01)
+ {
+ ApplicationArea = Basic, Suite;
+ Caption = 'Open Onboarding';
+ Image = Setup;
+ Promoted = true;
+ PromotedCategory = Process;
+ PromotedOnly = true;
+ ToolTip = 'Create client credentials and open the onboarding process in a web browser.';
+
+ trigger OnAction()
+ begin
+ this.SignUpAuthentication.CreateClientCredentials();
+ CurrPage.Update();
+ this.SetPageVariables();
+ Hyperlink(this.SignUpAuthentication.GetRootOnboardingUrl());
+ this.FeatureTelemetry.LogUptake('', this.ExternalServiceTok, Enum::"Feature Uptake Status"::"Set up");
+ end;
+ }
+ }
+ }
+
+ trigger OnOpenPage()
+ var
+ EnvironmentInformation: Codeunit "Environment Information";
+ begin
+ this.FieldsVisible := not EnvironmentInformation.IsSaaSInfrastructure();
+ this.SignUpAuthentication.InitConnectionSetup();
+ if Rec.Get() then
+ ;
+ this.SetPageVariables();
+ this.FeatureTelemetry.LogUptake('', this.ExternalServiceTok, Enum::"Feature Uptake Status"::Discovered);
+ end;
+
+ local procedure SetPageVariables()
+ begin
+ if not IsNullGuid(Rec."Client ID") then
+ this.ClientID := this.MaskTxt;
+ if not IsNullGuid(Rec."Client Secret") then
+ this.ClientSecret := this.MaskTxt;
+ if not IsNullGuid(Rec."Client Tenant") then
+ this.ClientTenant := this.MaskTxt;
+ if not IsNullGuid(Rec."Root App ID") then
+ this.RootID := this.MaskTxt;
+ if not IsNullGuid(Rec."Root Secret") then
+ this.RootSecret := this.MaskTxt;
+ if not IsNullGuid(Rec."Root Tenant") then
+ this.RootTenant := this.MaskTxt;
+ end;
+
+ [NonDebuggable]
+ local procedure SaveSecret(var TokenKey: Guid; Value: SecretText)
+ begin
+ this.SignUpAuthentication.StorageSet(TokenKey, Value);
+ end;
+
+ var
+ SignUpAuthentication: Codeunit SignUpAuthentication;
+ FeatureTelemetry: Codeunit "Feature Telemetry";
+ [NonDebuggable]
+ ClientID, ClientSecret, ClientTenant, RootID, RootSecret, RootTenant : Text;
+ FieldsVisible: Boolean;
+ ExternalServiceTok: Label 'E-Document - SignUp', Locked = true;
+ MaskTxt: Label '*', Locked = true;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpEnvironmentType.Enum.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpEnvironmentType.Enum.al
new file mode 100644
index 000000000..a4383312f
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/SignUpEnvironmentType.Enum.al
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+enum 6380 SignUpEnvironmentType
+{
+ Access = Internal;
+ Caption = 'Environment Type', Locked = true;
+
+ value(0; Production)
+ {
+ Caption = 'Production', Locked = true;
+ }
+ value(1; Test)
+ {
+ Caption = 'Test', Locked = true;
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpAPIRequests.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpAPIRequests.Codeunit.al
new file mode 100644
index 000000000..23f7eaac6
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpAPIRequests.Codeunit.al
@@ -0,0 +1,418 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.EServices.EDocument;
+using Microsoft.EServices.EDocument.Service.Participant;
+using Microsoft.Foundation.Company;
+using System.Security.Authentication;
+using System.Text;
+using System.Utilities;
+using System.Xml;
+using System.Environment;
+
+codeunit 6389 SignUpAPIRequests
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ #region variables
+
+ var
+ CompanyId: Text[100];
+ MissingSetupErr: Label 'Connection Setup is missing';
+ MissingSetupMessageErr: Label 'You must set up service integration in the e-document service card.';
+ MissingSetupNavigationActionErr: Label 'Show E-Document Services';
+ MissingSetupCompanyIdErr: Label '%1 in %2 is missing', Comment = '%1 = Field Name, %2 = Table Name';
+ MissingSetupCompanyIdMessageErr: Label 'You must set up %1 in %2.', Comment = '%1 = Field Name, %2 = Table Name';
+ MissingSetupCompanyIdActionErr: Label 'Show %1', Comment = '%1 = Table Name';
+ UnSupportedDocumentTypeTxt: Label 'Document %1 is not supported.', Comment = '%1 = EDocument Type', Locked = true;
+ UnSupportedDocumentTypeProfileMissingTxt: Label 'Document %1 is not supported since %2 is missing in %3 %4', Comment = '%1 = EDocument Type; %2 = Profile Id; %3 = E-Document Service; %4 = Supported Document Types', Locked = true;
+ SupportedDocumentTypesTxt: Label 'Supported Document Types';
+ SenderReceiverPrefixTxt: Label 'iso6523-actorid-upis::', Locked = true;
+ ContentTypeTxt: Label 'Content-Type', Locked = true;
+ ApplicationJsonTxt: Label 'application/json', Locked = true;
+ AuthorizationTxt: Label 'Authorization', Locked = true;
+ AcceptTxt: Label 'Accept', Locked = true;
+ AllTxt: Label '*/*', Locked = true;
+ ApplicationResponseTxt: Label 'ApplicationResponse', Locked = true;
+ InvoiceTxt: Label 'Invoice', Locked = true;
+ CrMemoTxt: Label 'CreditNote', Locked = true;
+ PaymentReminderTxt: Label 'PaymentReminder', Locked = true;
+ DocumentTypeTxt: Label 'documentType', Locked = true;
+ ReceiverTxt: Label 'receiver', Locked = true;
+ SenderTxt: Label 'sender', Locked = true;
+ SenderCountryCodeTxt: Label 'senderCountryCode', Locked = true;
+ DocumentIdTxt: Label 'documentId', Locked = true;
+ DocumentIdSchemeTxt: Label 'documentIdScheme', Locked = true;
+ ProcessIdTxt: Label 'processId', Locked = true;
+ ProcessIdSchemeTxt: Label 'processIdScheme', Locked = true;
+ SendModeTxt: Label 'sendMode', Locked = true;
+ DocumentTxt: Label 'document', Locked = true;
+
+
+ #endregion
+
+ #region public methods
+
+ ///
+ /// The method sends a file to the API.
+ /// https://[BASEURL]/api/v2/Peppol/outbox/transactions
+ ///
+ /// TempBlob
+ /// EDocument table
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure SendFilePostRequest(var TempBlob: Codeunit "Temp Blob"; EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ HttpContent: HttpContent;
+ Payload: Text;
+ begin
+ Payload := this.XmlToTxt(TempBlob);
+ if Payload = '' then
+ exit;
+
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ SignUpConnectionSetup.SetLoadFields("Environment Type", "Service URL");
+ this.GetSetup(SignUpConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::POST, SignUpConnectionSetup."Service URL" + '/api/v2/Peppol/outbox/transactions');
+ this.PrepareContent(HttpContent, Payload, EDocument, SignUpConnectionSetup);
+ HttpRequestMessage.Content(HttpContent);
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method checks the status of the sent document.
+ /// https://[BASE URL]/api/v2/Peppol/outbox/transactions/{transactionId}/status
+ ///
+ /// EDocument table
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure GetSentDocumentStatus(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ SignUpConnectionSetup.SetLoadFields("Service URL");
+ this.GetSetup(SignUpConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::GET, SignUpConnectionSetup."Service URL" + '/api/v2/Peppol/outbox/transactions/' + EDocument."SignUp Document Id" + '/status');
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method modifies the document.
+ /// https://[BASE URL]/api/v2/Peppol/outbox/transactions/{transactionId}/acknowledge
+ ///
+ /// EDocument table
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure PatchDocument(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ SignUpConnectionSetup.SetLoadFields("Service URL");
+ this.GetSetup(SignUpConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::PATCH, SignUpConnectionSetup."Service URL" + '/api/v2/Peppol/outbox/transactions/' + EDocument."SignUp Document Id" + '/acknowledge');
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets the received document request.
+ /// https://[BASE URL]/api/v2/Peppol/inbox/transactions
+ ///
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure GetReceivedDocumentsRequest(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ SignUpConnectionSetup.SetLoadFields("Service URL");
+ this.GetSetup(SignUpConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::GET, SignUpConnectionSetup."Service URL" + '/api/v2/Peppol/inbox/transactions?partyId=' + this.SenderReceiverPrefixTxt + this.GetCompanyId());
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets the target document request.
+ /// https://[BASE URL]/api/v2/Peppol/inbox/transactions/{transactionId}
+ ///
+ /// Document ID
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure GetTargetDocumentRequest(DocumentId: Text; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ SignUpConnectionSetup.SetLoadFields("Service URL");
+ this.GetSetup(SignUpConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::GET, SignUpConnectionSetup."Service URL" + '/api/v2/Peppol/inbox/transactions/' + DocumentId);
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method modifies the received document.
+ /// https://[BASE URL]/api/v2/Peppol/inbox/transactions/{transactionId}/acknowledge
+ ///
+ /// EDocument table
+ /// Http Request Message
+ /// Http Response Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure PatchReceivedDocument(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ SignUpConnectionSetup.SetLoadFields("Service URL");
+ this.GetSetup(SignUpConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::PATCH, SignUpConnectionSetup."Service URL" + '/api/v2/Peppol/inbox/transactions/' + EDocument."SignUp Document Id" + '/acknowledge');
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method fetches metadata profiles.
+ /// https://[BASE URL]/api/v2/Peppol/metadataprofile
+ ///
+ /// The HTTP request message to be sent.
+ /// The HTTP response message received.
+ /// Returns true if the metadata profiles were successfully fetched, otherwise false.
+ procedure FetchMetaDataProfiles(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ SignUpConnectionSetup.SetLoadFields("Service URL");
+ this.GetSetup(SignUpConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::GET, SignUpConnectionSetup."Service URL" + '/api/v2/Peppol/metadataprofile');
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage, false));
+ end;
+
+
+ ///
+ /// The method gets the marketplace credentials.
+ ///
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure GetMarketPlaceCredentials(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpAuthentication: Codeunit SignUpAuthentication;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::POST, SignUpAuthentication.GetRootUrl() + '/api/Registration/init?EntraTenantId=' + SignUpAuthentication.GetBCInstanceIdentifier() + '&EnvironmentName=' + this.GetEnvironmentName());
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage, true));
+ end;
+
+ #endregion
+
+ #region local methods
+ local procedure GetEnvironmentName(): Text
+ var
+ EnvironmentInformation: Codeunit "Environment Information";
+ begin
+ if EnvironmentInformation.IsSaaSInfrastructure() then
+ exit(EnvironmentInformation.GetEnvironmentName())
+ else
+ exit(EnvironmentInformation.GetEnvironmentName()); //Change if OnPrem should be supported
+ end;
+
+ local procedure InitRequest(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ begin
+ Clear(HttpRequestMessage);
+ Clear(HttpResponseMessage);
+ end;
+
+ local procedure GetSetup(var SignUpConnectionSetup: Record SignUpConnectionSetup)
+ var
+ MissingSetupErrorInfo: ErrorInfo;
+ begin
+ if not IsNullGuid(SignUpConnectionSetup.SystemId) then
+ exit;
+
+ if not SignUpConnectionSetup.Get() then begin
+ MissingSetupErrorInfo.Title := this.MissingSetupErr;
+ MissingSetupErrorInfo.Message := this.MissingSetupMessageErr;
+ MissingSetupErrorInfo.PageNo := Page::"E-Document Services";
+ MissingSetupErrorInfo.AddNavigationAction(this.MissingSetupNavigationActionErr);
+ Error(MissingSetupErrorInfo);
+ end;
+ end;
+
+ local procedure GetCompanyId(): Text[100]
+ var
+ CompanyInformation: Record "Company Information";
+ MissingSetupErrorInfo: ErrorInfo;
+ begin
+ if this.CompanyId <> '' then
+ exit(this.CompanyId);
+
+ CompanyInformation.SetLoadFields("SignUp Service Participant Id");
+ if not CompanyInformation.Get() or (CompanyInformation."SignUp Service Participant Id" = '') then begin
+ MissingSetupErrorInfo.Title := StrSubstNo(this.MissingSetupCompanyIdErr, CompanyInformation.FieldName("SignUp Service Participant Id"), CompanyInformation.TableName);
+ MissingSetupErrorInfo.Message := StrSubstNo(this.MissingSetupCompanyIdMessageErr, CompanyInformation.FieldName("SignUp Service Participant Id"), CompanyInformation.TableName);
+ MissingSetupErrorInfo.PageNo := Page::"Company Information";
+ MissingSetupErrorInfo.AddNavigationAction(StrSubstNo(this.MissingSetupCompanyIdActionErr, CompanyInformation.TableName));
+ Error(MissingSetupErrorInfo);
+ end;
+ this.CompanyId := CompanyInformation."SignUp Service Participant Id";
+ exit(this.CompanyId);
+ end;
+
+ local procedure PrepareContent(var HttpContent: HttpContent; Payload: Text; EDocument: Record "E-Document"; SignUpConnectionSetup: Record SignUpConnectionSetup)
+ var
+ ContentText: Text;
+ HttpHeaders: HttpHeaders;
+ begin
+ Clear(HttpContent);
+ ContentText := this.PrepareContentForSend(EDocument, this.GetCompanyId(), this.GetSenderCountryCode(), Payload, SignUpConnectionSetup."Environment Type");
+ HttpContent.WriteFrom(ContentText);
+ HttpContent.GetHeaders(HttpHeaders);
+ if HttpHeaders.Contains(this.ContentTypeTxt) then
+ HttpHeaders.Remove(this.ContentTypeTxt);
+ HttpHeaders.Add(this.ContentTypeTxt, this.ApplicationJsonTxt);
+ end;
+
+ local procedure SendRequest(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage, false));
+ end;
+
+ local procedure SendRequest(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage; RootRequest: Boolean): Boolean
+ var
+ SignUpAuthentication: Codeunit SignUpAuthentication;
+ HttpClient: HttpClient;
+ HttpHeaders: HttpHeaders;
+ begin
+ HttpRequestMessage.GetHeaders(HttpHeaders);
+ if RootRequest then
+ HttpHeaders.Add(this.AuthorizationTxt, SignUpAuthentication.GetRootBearerAuthToken())
+ else
+ HttpHeaders.Add(this.AuthorizationTxt, SignUpAuthentication.GetBearerAuthToken());
+ exit(HttpClient.Send(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ local procedure PrepareRequestMsg(HttpRequestType: Enum "Http Request Type"; Uri: Text) RequestMessage: HttpRequestMessage
+ var
+ HttpHeaders: HttpHeaders;
+ begin
+ RequestMessage.Method(Format(HttpRequestType));
+ RequestMessage.SetRequestUri(Uri);
+ RequestMessage.GetHeaders(HttpHeaders);
+ HttpHeaders.Add(this.AcceptTxt, this.AllTxt);
+ end;
+
+ local procedure XmlToTxt(var TempBlob: Codeunit "Temp Blob"): Text
+ var
+ XMLDOMManagement: Codeunit "XML DOM Management";
+ Content: Text;
+ begin
+ XMLDOMManagement.TryGetXMLAsText(TempBlob.CreateInStream(TextEncoding::UTF8), Content);
+ exit(Content);
+ end;
+
+ local procedure GetDocumentType(EDocument: Record "E-Document"): Text
+ begin
+ if EDocument.Direction = EDocument.Direction::Incoming then
+ exit(this.ApplicationResponseTxt);
+
+ case EDocument."Document Type" of
+ "E-Document Type"::"Sales Invoice":
+ exit(this.InvoiceTxt);
+ "E-Document Type"::"Sales Credit Memo":
+ exit(this.CrMemoTxt);
+ "E-Document Type"::"Issued Finance Charge Memo",
+ "E-Document Type"::"Issued Reminder":
+ exit(this.PaymentReminderTxt);
+ else
+ Error(this.UnSupportedDocumentTypeTxt, EDocument."Document Type");
+ end;
+ end;
+
+ local procedure GetCustomerID(EDocument: Record "E-Document") Return: Text[50]
+ var
+ ServiceParticipant: Record "Service Participant";
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ begin
+ EDocumentServiceStatus.SetLoadFields("E-Document Service Code");
+ EDocumentServiceStatus.SetRange("E-Document Entry No", EDocument."Entry No");
+ EDocumentServiceStatus.FindFirst();
+ ServiceParticipant.SetLoadFields("Participant Identifier");
+ ServiceParticipant.Get(EDocumentServiceStatus."E-Document Service Code", ServiceParticipant."Participant Type"::Customer, EDocument."Bill-to/Pay-to No.");
+ Return := CopyStr(ServiceParticipant."Participant Identifier", 1, MaxStrLen(Return));
+ end;
+
+ local procedure GetSenderCountryCode(): Text
+ var
+ CompanyInformation: Record "Company Information";
+ begin
+ CompanyInformation.SetLoadFields("Country/Region Code");
+ CompanyInformation.Get();
+ CompanyInformation.TestField("Country/Region Code");
+ exit(CompanyInformation."Country/Region Code");
+ end;
+
+ local procedure PrepareContentForSend(EDocument: Record "E-Document"; SendingCompanyID: Text; SenderCountryCode: Text; Payload: Text; SendMode: Enum SignUpEnvironmentType): Text
+ var
+ EDocumentService: Record "E-Document Service";
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ EDocServiceSupportedType: Record "E-Doc. Service Supported Type";
+ SignUpMetadataProfile: Record SignUpMetadataProfile;
+ EDocumentHelper: Codeunit "E-Document Helper";
+ Base64Convert: Codeunit "Base64 Convert";
+ JsonObject: JsonObject;
+ ContentText: Text;
+ begin
+ EDocumentHelper.GetEdocumentService(EDocument, EDocumentService);
+
+ if EDocumentService.Code = '' then begin
+ EDocumentServiceStatus.SetRange("E-Document Entry No", Edocument."Entry No");
+ if EDocumentServiceStatus.FindLast() then
+ EDocumentService.Get(EDocumentServiceStatus."E-Document Service Code");
+ end;
+
+ EDocServiceSupportedType.SetRange("E-Document Service Code", EDocumentService.Code);
+ EDocServiceSupportedType.SetRange("Source Document Type", EDocument."Document Type");
+ if not EDocServiceSupportedType.FindFirst() then
+ Error(this.UnSupportedDocumentTypeProfileMissingTxt, EDocument."Document Type", EDocServiceSupportedType.FieldCaption("Profile Id"), EDocumentService.TableCaption, this.SupportedDocumentTypesTxt);
+ if (EDocServiceSupportedType."Profile Id" = 0) or (not SignUpMetadataProfile.Get(EDocServiceSupportedType."Profile Id")) then
+ Error(this.UnSupportedDocumentTypeProfileMissingTxt, EDocument."Document Type", EDocServiceSupportedType.FieldCaption("Profile Id"), EDocumentService.TableCaption, this.SupportedDocumentTypesTxt);
+
+ JsonObject.Add(this.DocumentTypeTxt, this.GetDocumentType(EDocument));
+ JsonObject.Add(this.ReceiverTxt, this.SenderReceiverPrefixTxt + this.GetCustomerID(EDocument));
+ JsonObject.Add(this.SenderTxt, this.SenderReceiverPrefixTxt + SendingCompanyID);
+ JsonObject.Add(this.SenderCountryCodeTxt, SenderCountryCode);
+ if EDocument.Direction = EDocument.Direction::Incoming then
+ JsonObject.Add(this.DocumentIdTxt, this.ApplicationResponseTxt)
+ else
+ JsonObject.Add(this.DocumentIdTxt, SignUpMetadataProfile."Document Identifier Value");
+ JsonObject.Add(this.DocumentIdSchemeTxt, SignUpMetadataProfile."Document Identifier Scheme");
+ JsonObject.Add(this.ProcessIdTxt, SignUpMetadataProfile."Process Identifier Value");
+ JsonObject.Add(this.ProcessIdSchemeTxt, SignUpMetadataProfile."Process Identifier Scheme");
+ JsonObject.Add(this.SendModeTxt, Format(SendMode));
+ JsonObject.Add(this.DocumentTxt, Base64Convert.ToBase64(Payload));
+ JsonObject.WriteTo(ContentText);
+ exit(ContentText);
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpAuthentication.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpAuthentication.Codeunit.al
new file mode 100644
index 000000000..af28d075a
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpAuthentication.Codeunit.al
@@ -0,0 +1,471 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using System.Azure.Identity;
+using System.Reflection;
+using System.Security.Authentication;
+using System.Azure.KeyVault;
+using System.Environment;
+
+codeunit 6390 SignUpAuthentication
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ #region variables
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ SignUpHelpersImpl: Codeunit SignUpHelpers;
+ BearerTxt: Label 'Bearer %1', Comment = '%1 = text value', Locked = true;
+ AuthURLTxt: Label 'https://login.microsoftonline.com/%1/oauth2/token', Comment = '%1 Entra Tenant Id', Locked = true;
+ AuthTemplateTxt: Label 'grant_type=client_credentials&client_id=%1&client_secret=%2&resource=%3', Locked = true;
+ ProdRootTenantIdTxt: Label '0d725623-dc26-484f-a090-b09d2003d092', Locked = true;
+ ProdClientTenantIdTxt: Label 'eef4ab2c-2b10-4380-bf4b-214157971162', Locked = true;
+ ProdServiceAPITxt: Label 'https://edoc.exflow.io', Locked = true;
+ ErrorTokenLbl: Label 'Unable to fetch a root token.';
+ ErrorUnableToCreateClientCredentialsLbl: Label 'Unable to create client credentials.';
+ ClientIdTxt: Label 'clientId', Locked = true;
+ ClientSecretTxt: Label 'clientSecret', Locked = true;
+ SignupRootUrlTxt: Label 'signup-root-url', Locked = true;
+ SignUpRootIdTxt: Label 'signup-root-id', Locked = true;
+ SignUpRootSecretTxt: Label 'signup-root-secret', Locked = true;
+ SignUpRootTenantTxt: Label 'signup-root-tenant', Locked = true;
+ SignUpClientTenantTxt: Label 'signup-client-tenant', Locked = true;
+ SignUpServiceAPITxt: Label 'signup-service-api', Locked = true;
+ SignUpAccessTokenKeyTxt: Label '{E45BB975-E67B-4A87-AC24-D409A5EF8301}', Locked = true;
+ ContentTypeTxt: Label 'Content-Type', Locked = true;
+ FormUrlEncodedTxt: Label 'application/x-www-form-urlencoded', Locked = true;
+ AccessTokenTxt: Label 'access_token', Locked = true;
+
+ #endregion
+
+ #region public methods
+
+ ///
+ /// The method initializes the connection setup.
+ ///
+ procedure InitConnectionSetup()
+ begin
+ if this.SignUpConnectionSetup.Get() then
+ exit;
+
+ this.SignUpConnectionSetup."Authentication URL" := this.AuthURLTxt;
+ this.SignUpConnectionSetup."Service URL" := this.GetServiceApi();
+ this.StorageSet(this.SignUpConnectionSetup."Root Tenant", this.GetRootTenant());
+ this.StorageSet(this.SignUpConnectionSetup."Client Tenant", this.GetClientTenant());
+ this.SignUpConnectionSetup.Insert();
+ end;
+
+ ///
+ /// The method returns the onboarding URL.
+ ///
+ /// Onboarding URL
+ procedure GetRootOnboardingUrl(): Text
+ begin
+ exit(this.GetRootUrl() + '/supm/landingpage?EntraTenantId=' + this.GetBCInstanceIdentifier());
+ end;
+
+ ///
+ /// The method creates the client credentials.
+ ///
+ [NonDebuggable]
+ procedure CreateClientCredentials()
+ var
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ ClientId, Response : Text;
+ ClientSecret: SecretText;
+ begin
+ if not this.GetClientCredentials(HttpRequestMessage, HttpResponseMessage) then
+ this.ShowErrorMessage(HttpResponseMessage);
+
+ if not HttpResponseMessage.Content.ReadAs(Response) then
+ exit;
+
+ ClientId := this.SignUpHelpersImpl.GetJsonValueFromText(Response, this.ClientIdTxt);
+ ClientSecret := this.SignUpHelpersImpl.GetJsonValueFromText(Response, this.ClientSecretTxt);
+
+ if (ClientId <> '') and (not ClientSecret.IsEmpty()) then
+ this.SaveClientCredentials(ClientId, ClientSecret);
+ end;
+
+ ///
+ /// The method returns the bearer authentication text.
+ ///
+ /// Bearer authentication token
+ procedure GetBearerAuthToken(): SecretText;
+ begin
+ exit(SecretStrSubstNo(this.BearerTxt, this.GetAuthToken()));
+ end;
+
+ ///
+ /// The method returns the root bearer authentication token.
+ ///
+ /// Root bearer authentication token
+ procedure GetRootBearerAuthToken(): SecretText;
+ begin
+ exit(SecretStrSubstNo(this.BearerTxt, this.GetRootAuthToken()));
+ end;
+
+ ///
+ /// The mehod saves the token to the storage.
+ ///
+ /// Token Key
+ /// Token
+ [NonDebuggable]
+ procedure StorageSet(var TokenKey: Guid; Value: Text): Boolean
+ var
+ ModuleDataScope: DataScope;
+ begin
+ ModuleDataScope := ModuleDataScope::Module;
+ this.ValidateValueKey(TokenKey);
+
+ if Value = '' then begin
+ if IsolatedStorage.Contains(TokenKey, ModuleDataScope) then
+ exit(IsolatedStorage.Delete(TokenKey, ModuleDataScope))
+ end else
+ exit(IsolatedStorage.Set(TokenKey, Value, ModuleDataScope));
+ end;
+
+ ///
+ /// The mehod saves the token to the storage.
+ ///
+ /// Token Key
+ /// Token
+ [NonDebuggable]
+ procedure StorageSet(var TokenKey: Guid; Value: SecretText): Boolean
+ begin
+ exit(this.StorageSet(TokenKey, Value, DataScope::Module));
+ end;
+
+ ///
+ /// The method returns BC instance identifier.
+ ///
+ /// Identifier
+ procedure GetBCInstanceIdentifier() Identifier: Text
+ var
+ AADTenantID, AADDomainName : Text;
+ begin
+ Identifier := '10000000-d8ef-4dfb-b761-ffb073057794'; // Hardcoded fake during testing only
+
+ if this.GetAADTenantInformation(AADTenantID, AADDomainName) then
+ Identifier := AADTenantID;
+ end;
+
+ ///
+ /// The method returns the root URL.
+ ///
+ ///
+ [NonDebuggable]
+ procedure GetRootUrl() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.SignupRootUrlTxt, ReturnValue) then
+ exit;
+
+ if not this.SignUpConnectionSetup.GetSetup() then
+ exit;
+
+ this.SignUpConnectionSetup.TestField("Root Market URL");
+ ReturnValue := this.SignUpConnectionSetup."Root Market URL";
+ end;
+
+ #endregion
+
+ #region local methods
+
+ local procedure GetAuthToken() AccessToken: SecretText;
+ var
+ HttpError: Text;
+ begin
+ AccessToken := this.StorageGet(this.SignUpAccessTokenKeyTxt, DataScope::Company);
+
+ if this.SignUpHelpersImpl.IsTokenValid(AccessToken) then
+ exit;
+
+ if not this.RefreshAccessToken(HttpError) then
+ Error(HttpError);
+
+ exit(this.StorageGet(this.SignUpAccessTokenKeyTxt, DataScope::Company));
+ end;
+
+ local procedure GetRootAuthToken() ReturnValue: SecretText;
+ begin
+ if not this.GetRootAccessToken(ReturnValue) then
+ Error(this.ErrorTokenLbl);
+ end;
+
+ local procedure SaveClientCredentials(ClientId: Text; ClientSecret: SecretText)
+ begin
+ Clear(this.SignUpConnectionSetup);
+
+ this.SignUpConnectionSetup.GetSetup();
+ this.StorageSet(this.SignUpConnectionSetup."Client ID", ClientId);
+ this.StorageSet(this.SignUpConnectionSetup."Client Secret", ClientSecret);
+ this.SignUpConnectionSetup.Modify();
+
+ Clear(this.SignUpConnectionSetup);
+ end;
+
+ [NonDebuggable]
+ local procedure GetClientCredentials(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ SignUpAPIRequests: Codeunit SignUpAPIRequests;
+ begin
+ SignUpAPIRequests.GetMarketPlaceCredentials(HttpRequestMessage, HttpResponseMessage);
+
+ if not HttpResponseMessage.IsSuccessStatusCode() then
+ exit;
+
+ exit(this.SignUpHelpersImpl.ParseJsonString(HttpResponseMessage.Content) <> '');
+ end;
+
+ [NonDebuggable]
+ local procedure RefreshAccessToken(var HttpError: Text): Boolean;
+ var
+ SecretToken: SecretText;
+ begin
+ if not this.GetClientAccessToken(SecretToken) then begin
+ HttpError := GetLastErrorText();
+ exit;
+ end;
+
+ exit(this.SaveSignUpAccessToken(DataScope::Company, SecretToken));
+ end;
+
+ [NonDebuggable]
+ local procedure GetRootAccessToken(var AccessToken: SecretText): Boolean
+ var
+ ModuleDataScope: DataScope;
+ begin
+ ModuleDataScope := ModuleDataScope::Module;
+ this.SignUpConnectionSetup.GetSetup();
+
+ exit(this.GetAccessToken(AccessToken, this.GetRootId(),
+ this.GetRootSecret(),
+ this.StorageGetText(this.SignUpConnectionSetup."Root Tenant", ModuleDataScope)));
+ end;
+
+ [NonDebuggable]
+ local procedure GetClientAccessToken(var AccessToken: SecretText): Boolean
+ var
+ ModuleDataScope: DataScope;
+ begin
+ ModuleDataScope := ModuleDataScope::Module;
+ this.SignUpConnectionSetup.GetSetup();
+
+ exit(this.GetAccessToken(AccessToken, this.StorageGetText(this.SignUpConnectionSetup."Client ID", ModuleDataScope),
+ this.StorageGet(this.SignUpConnectionSetup."Client Secret", ModuleDataScope),
+ this.StorageGetText(this.SignUpConnectionSetup."Client Tenant", ModuleDataScope)));
+ end;
+
+ [NonDebuggable]
+ local procedure GetAccessToken(var AccessToken: SecretText; ClientId: Text; ClientSecret: SecretText; ClientTenant: Text): Boolean
+ var
+ TypeHelper: Codeunit "Type Helper";
+ HttpRequestMessage: HttpRequestMessage;
+ Response: Text;
+ begin
+ Clear(AccessToken);
+ this.SignUpConnectionSetup.GetSetup();
+ this.SignUpConnectionSetup.TestField("Authentication URL");
+
+ HttpRequestMessage := this.PrepareRequest(SecretStrSubstNo(this.AuthTemplateTxt, TypeHelper.UriEscapeDataString(ClientId), ClientSecret, TypeHelper.UriEscapeDataString(ClientId)),
+ StrSubstNo(this.SignUpConnectionSetup."Authentication URL", ClientTenant));
+
+ if not this.SendRequest(HttpRequestMessage, Response) then
+ exit;
+
+ AccessToken := this.SignUpHelpersImpl.GetJsonValueFromText(Response, this.AccessTokenTxt);
+ exit(not AccessToken.IsEmpty());
+ end;
+
+
+ local procedure PrepareRequest(Content: SecretText; Url: text) HttpRequestMessage: HttpRequestMessage
+ var
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ begin
+ HttpContent.WriteFrom(Content);
+ HttpContent.GetHeaders(HttpHeaders);
+
+ HttpHeaders.Remove(this.ContentTypeTxt);
+ HttpHeaders.Add(this.ContentTypeTxt, this.FormUrlEncodedTxt);
+
+ HttpRequestMessage.Method := Format(Enum::"Http Request Type"::POST);
+ HttpRequestMessage.SetRequestUri(Url);
+ HttpRequestMessage.Content(HttpContent);
+ end;
+
+ [NonDebuggable]
+ local procedure SendRequest(HttpRequestMessage: HttpRequestMessage; var Response: Text): Boolean
+ var
+ HttpClient: HttpClient;
+ HttpResponseMessage: HttpResponseMessage;
+ begin
+ if not HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then
+ exit;
+ if not HttpResponseMessage.IsSuccessStatusCode() then
+ exit;
+
+ exit(HttpResponseMessage.Content.ReadAs(Response));
+ end;
+
+ local procedure StorageSet(var TokenKey: Guid; Value: SecretText; TokenDataScope: DataScope): Boolean
+ begin
+ this.ValidateValueKey(TokenKey);
+
+ if Value.IsEmpty() then begin
+ if IsolatedStorage.Contains(TokenKey, TokenDataScope) then
+ exit(IsolatedStorage.Delete(TokenKey, TokenDataScope))
+ end else
+ exit(IsolatedStorage.Set(TokenKey, Value, TokenDataScope));
+ end;
+
+ local procedure StorageGet(TokenKey: Text; TokenDataScope: DataScope) TokenValueAsSecret: SecretText
+ begin
+ if not this.StorageContains(TokenKey, TokenDataScope) then
+ exit(TokenValueAsSecret);
+
+ IsolatedStorage.Get(TokenKey, TokenDataScope, TokenValueAsSecret);
+ end;
+
+ [NonDebuggable]
+ local procedure StorageGetText(TokenKey: Text; TokenDataScope: DataScope) TokenValue: Text
+ begin
+ if not this.StorageContains(TokenKey, TokenDataScope) then
+ exit(TokenValue);
+
+ IsolatedStorage.Get(TokenKey, TokenDataScope, TokenValue);
+ end;
+
+ local procedure SaveSignUpAccessToken(TokenDataScope: DataScope; AccessToken: SecretText): Boolean
+ var
+ SignUpAccessTokenKey: Guid;
+ begin
+ SignUpAccessTokenKey := this.GetSignUpAccessTokenKey();
+ exit(this.StorageSet(SignUpAccessTokenKey, AccessToken, TokenDataScope));
+ end;
+
+ local procedure StorageContains(TokenKey: Text; TokenDataScope: DataScope): Boolean
+ begin
+ exit(IsolatedStorage.Contains(TokenKey, TokenDataScope));
+ end;
+
+ local procedure ValidateValueKey(var ValueKey: Guid)
+ begin
+ if IsNullGuid(ValueKey) then
+ ValueKey := CreateGuid();
+ end;
+
+ local procedure GetSignUpAccessTokenKey() SignUpAccessTokenKey: Guid
+ begin
+ Evaluate(SignUpAccessTokenKey, this.SignUpAccessTokenKeyTxt);
+ end;
+
+ local procedure ShowErrorMessage(HttpResponseMessage: HttpResponseMessage)
+ var
+ UnsuccessfulResponseErr: Label 'There was an error sending the request. Response code: %1 and error message: %2', Comment = '%1 - http response status code, e.g. 400, %2- error message';
+ begin
+ if HttpResponseMessage.ReasonPhrase() <> '' then
+ Error(UnsuccessfulResponseErr, HttpResponseMessage.HttpStatusCode, HttpResponseMessage.ReasonPhrase());
+
+ Error(this.ErrorUnableToCreateClientCredentialsLbl);
+ end;
+
+ [NonDebuggable]
+ local procedure GetRootId() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.SignUpRootIdTxt, ReturnValue) then
+ exit;
+
+ if not this.SignUpConnectionSetup.GetSetup() then
+ exit;
+
+ this.SignUpConnectionSetup.TestField("Root App ID");
+ ReturnValue := this.StorageGetText(this.SignUpConnectionSetup."Root App ID", DataScope::Module);
+ end;
+
+ local procedure GetRootSecret() ReturnValue: SecretText
+ begin
+ if this.FetchSecretFromKeyVault(this.SignUpRootSecretTxt, ReturnValue) then
+ exit;
+
+ if not this.SignUpConnectionSetup.GetSetup() then
+ exit;
+
+ this.SignUpConnectionSetup.TestField("Root Secret");
+ ReturnValue := this.StorageGet(this.SignUpConnectionSetup."Root Secret", DataScope::Module);
+ end;
+
+ [NonDebuggable]
+ local procedure GetRootTenant() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.SignUpRootTenantTxt, ReturnValue) then
+ exit;
+ ReturnValue := this.ProdRootTenantIdTxt;
+ end;
+
+ local procedure GetClientTenant() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.SignUpClientTenantTxt, ReturnValue) then
+ exit;
+ ReturnValue := this.ProdClientTenantIdTxt;
+ end;
+
+ local procedure GetServiceApi() ReturnValue: Text[2048]
+ var
+ KeyVaultReturn: Text;
+ begin
+ if this.FetchSecretFromKeyVault(this.SignUpServiceAPITxt, KeyVaultReturn) then begin
+ ReturnValue := CopyStr(KeyVaultReturn, 1, MaxStrLen(ReturnValue));
+ exit;
+ end;
+ ReturnValue := this.ProdServiceAPITxt;
+ end;
+
+ local procedure FetchSecretFromKeyVault(KeyName: Text; var KeyValue: SecretText): Boolean
+ var
+ AzureKeyVault: Codeunit "Azure Key Vault";
+ EnvironmentInformation: Codeunit "Environment Information";
+ begin
+ if EnvironmentInformation.IsSaaSInfrastructure() then
+ exit(AzureKeyVault.GetAzureKeyVaultSecret(KeyName, KeyValue));
+ end;
+
+ [NonDebuggable]
+ local procedure FetchSecretFromKeyVault(KeyName: Text; var KeyValue: Text): Boolean
+ var
+ AzureKeyVault: Codeunit "Azure Key Vault";
+ EnvironmentInformation: Codeunit "Environment Information";
+ begin
+ if EnvironmentInformation.IsSaaSInfrastructure() then
+ exit(AzureKeyVault.GetAzureKeyVaultSecret(KeyName, KeyValue));
+ end;
+
+ local procedure GetAADTenantInformation(var AADTenantID: Text; var AADDomainName: Text): Boolean
+ begin
+ exit(this.GetAADTenantID(AADTenantID) and this.GetAADDomainName(AADDomainName));
+ end;
+
+ [TryFunction]
+ local procedure GetAADTenantID(var AADTenantID: Text)
+ var
+ AzureADTenant: Codeunit "Azure AD Tenant";
+ begin
+ AADTenantID := AzureADTenant.GetAadTenantId();
+ end;
+
+ [TryFunction]
+ local procedure GetAADDomainName(var AADDomainName: Text)
+ var
+ AzureADTenant: Codeunit "Azure AD Tenant";
+ begin
+ AADDomainName := AzureADTenant.GetAadTenantDomainName()
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpConnection.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpConnection.Codeunit.al
new file mode 100644
index 000000000..a43e30175
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpConnection.Codeunit.al
@@ -0,0 +1,251 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.EServices.EDocument;
+using System.Utilities;
+
+codeunit 6391 SignUpConnection
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+ Permissions = tabledata "E-Document" = m;
+
+ #region variables
+
+ var
+ SignUpAPIRequests: Codeunit SignUpAPIRequests;
+ SignUpHelpersImpl: Codeunit SignUpHelpers;
+ UnsuccessfulResponseErr: Label 'There was an error sending the request. Response code: %1 and error message: %2', Comment = '%1 - http response status code, e.g. 400, %2- error message';
+ EnvironmentBlocksErr: Label 'The request to send documents has been blocked. To resolve the problem, enable outgoing HTTP requests for the E-Document apps on the Extension Management page.';
+ FourZeroThreeErr: Label 'You do not have a valid subscription.';
+ MetadataProfileLbl: Label 'metadataProfile', Locked = true;
+ ProfileIdLbl: Label 'profileId', Locked = true;
+ CommonNameLbl: Label 'commonName', Locked = true;
+ ProcessIdentifierLbl: Label 'processIdentifier', Locked = true;
+ SchemeLbl: Label 'scheme', Locked = true;
+ ValueLbl: Label 'value', Locked = true;
+ DocumentIdentifierLbl: Label 'documentIdentifier', Locked = true;
+
+
+ #endregion
+
+ #region public methods
+
+ ///
+ /// The methods sends a file to the API.
+ ///
+ /// Content
+ /// E-Document record
+ /// Http Request Message
+ /// Http Response Message
+ /// True - if completed successfully
+ procedure SendFilePostRequest(var TempBlob: Codeunit "Temp Blob"; var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.SignUpAPIRequests.SendFilePostRequest(TempBlob, EDocument, HttpRequestMessage, HttpResponseMessage);
+ exit(this.CheckIfSuccessfulRequest(EDocument, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method checks the status of the document.
+ ///
+ /// E-Document record
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure CheckDocumentStatus(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.SignUpAPIRequests.GetSentDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage);
+ exit(this.CheckIfSuccessfulRequest(EDocument, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets received documents.
+ ///
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure GetReceivedDocuments(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ if not this.SignUpAPIRequests.GetReceivedDocumentsRequest(HttpRequestMessage, HttpResponseMessage) then
+ exit;
+
+ if not HttpResponseMessage.IsSuccessStatusCode() then
+ if HttpResponseMessage.HttpStatusCode = 403 then
+ Error(this.FourZeroThreeErr)
+ else
+ exit;
+
+ exit(this.SignUpHelpersImpl.ParseJsonString(HttpResponseMessage.Content) <> '');
+ end;
+
+ ///
+ /// The method gets the target document.
+ ///
+ /// DocumentId
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure GetTargetDocumentRequest(DocumentId: Text; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.SignUpAPIRequests.GetTargetDocumentRequest(DocumentId, HttpRequestMessage, HttpResponseMessage);
+ exit(HttpResponseMessage.IsSuccessStatusCode());
+ end;
+
+ ///
+ /// The method removes the document from received.
+ ///
+ /// E-Document record
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure RemoveDocumentFromReceived(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.SignUpAPIRequests.PatchReceivedDocument(EDocument, HttpRequestMessage, HttpResponseMessage);
+ exit(HttpResponseMessage.IsSuccessStatusCode());
+ end;
+
+ ///
+ /// Updates the Metadata Profile table.
+ /// If the data is Fectched, the current Metadata Profile table will be deleted and the new data will be inserted.
+ /// If any Metadata Profiles have been removed, references to them will be set to 0.
+ ///
+ ///
+ /// This procedure retrieves and updates the metadata profile information from an external service.
+ ///
+ procedure UpdateMetadataProfile()
+ var
+ SignUpMetadataProfile: Record SignUpMetadataProfile;
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ MetadataProfileContent: Text;
+ begin
+ this.SignUpAPIRequests.FetchMetaDataProfiles(HttpRequestMessage, HttpResponseMessage);
+ if not HttpResponseMessage.IsSuccessStatusCode() then begin
+ if HttpResponseMessage.HttpStatusCode = 403 then
+ Message(this.FourZeroThreeErr)
+ else
+ Message(HttpResponseMessage.ReasonPhrase);
+ exit;
+ end;
+
+ if not HttpResponseMessage.Content.ReadAs(MetadataProfileContent) then
+ exit;
+
+ SignUpMetadataProfile.Reset();
+ SignUpMetadataProfile.DeleteAll();
+
+ if this.MetadataProfileJsonToTable(MetadataProfileContent, SignUpMetadataProfile) then
+ this.DeleteUnusedMetadataProfileReferenses(SignUpMetadataProfile);
+ end;
+ #endregion
+
+ #region local methods
+ local procedure CheckIfSuccessfulRequest(EDocument: Record "E-Document"; HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ EDocumentErrorHelper: Codeunit "E-Document Error Helper";
+ begin
+ if HttpResponseMessage.IsSuccessStatusCode() then
+ exit(true);
+
+ if HttpResponseMessage.IsBlockedByEnvironment() then
+ EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.EnvironmentBlocksErr)
+ else
+ if HttpResponseMessage.HttpStatusCode = 403 then
+ EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.FourZeroThreeErr)
+ else
+ EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, StrSubstNo(this.UnsuccessfulResponseErr, HttpResponseMessage.HttpStatusCode, HttpResponseMessage.ReasonPhrase));
+ end;
+
+ local procedure MetadataProfileJsonToTable(JsonText: Text; var SignUpMetadataProfile: Record SignUpMetadataProfile): Boolean
+ var
+ JsonObject, ProfileJsonObject, ProcessIdentifierJsonObject, DocumentIdentifierJsonObject : JsonObject;
+ JsonArray: JsonArray;
+ JsonToken: JsonToken;
+ begin
+ if JsonObject.ReadFrom(JsonText) then
+ if JsonObject.Get(this.MetadataProfileLbl, JsonToken) then
+ if JsonToken.IsArray() then begin
+ JsonArray := JsonToken.AsArray();
+ foreach JsonToken in JsonArray do
+ if JsonToken.IsObject() then begin
+ ProfileJsonObject := JsonToken.AsObject();
+ SignUpMetadataProfile.Init();
+
+ if ProfileJsonObject.SelectToken(this.ProfileIdLbl, JsonToken) then
+ SignUpMetadataProfile."Profile ID" := this.GetJsonValueAsInteger(JsonToken.AsValue());
+
+ if ProfileJsonObject.SelectToken(this.CommonNameLbl, JsonToken) then
+ SignUpMetadataProfile."Profile Name" := CopyStr(this.GetJsonValueAsText(JsonToken.AsValue()), 1, MaxStrLen(SignUpMetadataProfile."Profile Name"));
+
+ if ProfileJsonObject.SelectToken(this.ProcessIdentifierLbl, JsonToken) then begin
+ ProcessIdentifierJsonObject := JsonToken.AsObject();
+
+ if ProcessIdentifierJsonObject.SelectToken(this.SchemeLbl, JsonToken) then
+ SignUpMetadataProfile."Process Identifier Scheme" := CopyStr(this.GetJsonValueAsText(JsonToken.AsValue()), 1, MaxStrLen(SignUpMetadataProfile."Process Identifier Scheme"));
+
+ if ProcessIdentifierJsonObject.SelectToken(this.ValueLbl, JsonToken) then
+ SignUpMetadataProfile."Process Identifier Value" := CopyStr(this.GetJsonValueAsText(JsonToken.AsValue()), 1, MaxStrLen(SignUpMetadataProfile."Process Identifier Value"));
+ end;
+
+ if ProfileJsonObject.SelectToken(this.DocumentIdentifierLbl, JsonToken) then begin
+ DocumentIdentifierJsonObject := JsonToken.AsObject();
+
+ if DocumentIdentifierJsonObject.SelectToken(this.SchemeLbl, JsonToken) then
+ SignUpMetadataProfile."Document Identifier Scheme" := CopyStr(this.GetJsonValueAsText(JsonToken.AsValue()), 1, MaxStrLen(SignUpMetadataProfile."Document Identifier Scheme"));
+
+ if DocumentIdentifierJsonObject.SelectToken(this.ValueLbl, JsonToken) then
+ SignUpMetadataProfile."Document Identifier Value" := CopyStr(this.GetJsonValueAsText(JsonToken.AsValue()), 1, MaxStrLen(SignUpMetadataProfile."Document Identifier Value"));
+ end;
+
+ SignUpMetadataProfile.Insert();
+ end;
+ end;
+ exit(not SignUpMetadataProfile.IsEmpty());
+ end;
+
+ local procedure DeleteUnusedMetadataProfileReferenses(var SignUpMetadataProfile: Record SignUpMetadataProfile)
+ var
+ EDocumentService: Record "E-Document Service";
+ EDocServiceSupportedType: Record "E-Doc. Service Supported Type";
+ begin
+ EDocumentService.SetLoadFields("Service Integration V2");
+ EDocumentService.Reset();
+ EDocumentService.SetRange("Service Integration V2", EDocumentService."Service Integration V2"::"ExFlow E-Invoicing");
+ if EDocumentService.FindSet() then
+ repeat
+ EDocServiceSupportedType.Reset();
+ EDocServiceSupportedType.SetRange("E-Document Service Code", EDocumentService.Code);
+ if not EDocServiceSupportedType.FindSet() then
+ repeat
+ if EDocServiceSupportedType."Profile Id" <> 0 then
+ if not SignUpMetadataProfile.Get(EDocServiceSupportedType."Profile Id") then begin
+ EDocServiceSupportedType."Profile Id" := 0;
+ EDocServiceSupportedType.Modify();
+ end;
+ until EDocServiceSupportedType.Next() = 0;
+ until EDocumentService.Next() = 0;
+ end;
+
+ local procedure GetJsonValueAsInteger(JValue: JsonValue): Integer
+ begin
+ if JValue.IsNull then
+ exit(0);
+ if JValue.IsUndefined then
+ exit(0);
+ exit(JValue.AsInteger());
+ end;
+
+ local procedure GetJsonValueAsText(JValue: JsonValue): Text
+ begin
+ if JValue.IsNull then
+ exit('');
+ if JValue.IsUndefined then
+ exit('');
+ exit(JValue.AsText());
+ end;
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpHelpers.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpHelpers.Codeunit.al
new file mode 100644
index 000000000..0b6a4feef
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpHelpers.Codeunit.al
@@ -0,0 +1,106 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using System.Reflection;
+using Microsoft.Utilities;
+using System.Integration;
+using Microsoft.eServices.EDocument;
+
+codeunit 6385 SignUpHelpers
+{
+ Access = Internal;
+
+ var
+ ClaimTypeTxt: Label 'exp', Locked = true;
+
+ #region public methods
+
+ [NonDebuggable]
+ procedure ParseJsonString(HttpContent: HttpContent): Text
+ var
+ JsonObject: JsonObject;
+ Content: Text;
+ begin
+ if not HttpContent.ReadAs(Content) then
+ exit;
+
+ if JsonObject.ReadFrom(Content) then
+ exit(Content);
+ end;
+
+ [NonDebuggable]
+ procedure GetJsonValueFromText(JsonText: Text; Path: Text): Text
+ var
+ JsonObject: JsonObject;
+ JsonToken: JsonToken;
+ begin
+ if JsonObject.ReadFrom(JsonText) then
+ if JsonObject.SelectToken(Path, JsonToken) then
+ exit(this.GetJsonValue(JsonToken.AsValue()));
+ end;
+
+ procedure IsTokenValid(InToken: SecretText): Boolean
+ begin
+ exit(this.GetTokenDateTimeValue(InToken, this.ClaimTypeTxt) > CurrentDateTime());
+ end;
+
+ procedure IsExFlowEInvoicing(EDocumentServiceCodeFilter: Text): Boolean
+ var
+ EDocumentService: Record "E-Document Service";
+ begin
+ if EDocumentServiceCodeFilter = '' then
+ exit;
+
+
+ EDocumentService.SetFilter(Code, EDocumentServiceCodeFilter);
+ EDocumentService.SetRange("Service Integration V2", EDocumentService."Service Integration V2"::"ExFlow E-Invoicing");
+ exit(not EDocumentService.IsEmpty());
+ end;
+
+ #endregion
+
+ #region local methods
+
+ local procedure GetTokenDateTimeValue(InToken: SecretText; ClaimType: Text): DateTime
+ var
+ TypeHelper: Codeunit "Type Helper";
+ Timestamp: Decimal;
+ begin
+ if Evaluate(Timestamp, this.GetValueFromToken(InToken, ClaimType)) then
+ exit(TypeHelper.EvaluateUnixTimestamp(Timestamp));
+ end;
+
+ [NonDebuggable]
+ local procedure GetValueFromToken(InToken: SecretText; ClaimType: Text): Text
+ var
+ TempNameValueBuffer: Record "Name/Value Buffer" temporary;
+ SOAPWebServiceRequestMgt: Codeunit "SOAP Web Service Request Mgt.";
+ begin
+ if InToken.IsEmpty() then
+ exit;
+
+ TempNameValueBuffer.DeleteAll();
+ SOAPWebServiceRequestMgt.GetTokenDetailsAsNameBuffer(InToken, TempNameValueBuffer);
+ TempNameValueBuffer.Reset();
+ TempNameValueBuffer.SetRange(Name, ClaimType);
+ if TempNameValueBuffer.FindFirst() then
+ exit(TempNameValueBuffer.Value);
+ end;
+
+ [NonDebuggable]
+ local procedure GetJsonValue(JsonValue: JsonValue): Text
+ begin
+ if JsonValue.IsNull() then
+ exit;
+
+ if JsonValue.IsUndefined() then
+ exit;
+
+ exit(JsonValue.AsText());
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpMetadataProfile.Table.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpMetadataProfile.Table.al
new file mode 100644
index 000000000..2d9627ae2
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpMetadataProfile.Table.al
@@ -0,0 +1,62 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+table 6382 SignUpMetadataProfile
+{
+ Caption = 'MetadataProfile';
+ Access = Internal;
+ DataClassification = CustomerContent;
+ LookupPageId = 6381;
+
+ fields
+ {
+ field(1; "Profile ID"; Integer)
+ {
+ Caption = 'Profile ID';
+ Tooltip = 'The unique identifier for the metadata profile.';
+ }
+ field(2; "Profile Name"; Text[250])
+ {
+ Caption = 'Profile Name';
+ Tooltip = 'The common name of the metadata profile.';
+ }
+ field(3; "Process Identifier Scheme"; Text[250])
+ {
+ Caption = 'Process Identifier Scheme';
+ Tooltip = 'The scheme of the process identifier.';
+ }
+ field(4; "Process Identifier Value"; Text[2048])
+ {
+ Caption = 'Process Identifier Value';
+ Tooltip = 'The value of the process identifier.';
+ }
+ field(5; "Document Identifier Scheme"; Text[250])
+ {
+ Caption = 'Document Identifier Scheme';
+ Tooltip = 'The scheme of the document identifier.';
+ }
+ field(6; "Document Identifier Value"; Text[2048])
+ {
+ Caption = 'Document Identifier Value';
+ Tooltip = 'The value of the document identifier.';
+ }
+ }
+
+ keys
+ {
+ key(PK; "Profile ID")
+ {
+ Clustered = true;
+ }
+ }
+
+ fieldgroups
+ {
+ fieldgroup(DropDown; "Profile Id", "Profile Name")
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpMetadataProfiles.Page.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpMetadataProfiles.Page.al
new file mode 100644
index 000000000..2fcd34382
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpMetadataProfiles.Page.al
@@ -0,0 +1,47 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+page 6381 SignUpMetadataProfiles
+{
+ PageType = List;
+ SourceTable = SignUpMetadataProfile;
+ ApplicationArea = All;
+ UsageCategory = None;
+
+ layout
+ {
+ area(content)
+ {
+ repeater(Group)
+ {
+ field("Profile ID"; Rec."Profile ID")
+ {
+ ApplicationArea = All;
+ }
+ field(Name; Rec."Profile Name")
+ {
+ ApplicationArea = All;
+ }
+ field("Process Identifier Scheme"; Rec."Process Identifier Scheme")
+ {
+ ApplicationArea = All;
+ }
+ field("Process Identifier Value"; Rec."Process Identifier Value")
+ {
+ ApplicationArea = All;
+ }
+ field("Document Identifier Scheme"; Rec."Document Identifier Scheme")
+ {
+ ApplicationArea = All;
+ }
+ field("Document Identifier Value"; Rec."Document Identifier Value")
+ {
+ ApplicationArea = All;
+ }
+ }
+ }
+ }
+}
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpProcessing.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpProcessing.Codeunit.al
new file mode 100644
index 000000000..0de205560
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/SignUpProcessing.Codeunit.al
@@ -0,0 +1,474 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+using Microsoft.EServices.EDocument;
+using System.Text;
+using System.Utilities;
+using Microsoft.eServices.EDocument.Integration.Send;
+using Microsoft.eServices.EDocument.Integration.Receive;
+
+codeunit 6383 SignUpProcessing
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+ Permissions = tabledata "E-Document" = rim,
+ tabledata "E-Document Service Status" = rm,
+ tabledata "E-Document Service" = r,
+ tabledata "E-Document Integration Log" = rim,
+ tabledata "E-Document Log" = ri;
+
+ #region variables
+ var
+ SignUpConnection: Codeunit SignUpConnection;
+ SignUpHelpersImpl: Codeunit SignUpHelpers;
+ EDocumentErrorHelper: Codeunit "E-Document Error Helper";
+ CouldNotRetrieveDocumentErr: Label 'Could not retrieve document with id: %1 from the service', Comment = '%1 - Document ID';
+ CouldNotSendPatchErr: Label 'Could not Send Patch for document with id: %1', Comment = '%1 - Document ID';
+ CouldNotRetrieveStatusFromResponseLbl: Label 'Could not retrieve status from response';
+ DocumentIdNotFoundErr: Label 'Document ID not found in response';
+ ErrorMessageMissingErr: Label 'Error message is missing or could not be parsed in the response';
+ InboxTxt: Label 'inbox', Locked = true;
+ InstanceIdTxt: Label 'instanceId', Locked = true;
+ TransactionIdTxt: Label 'transactionId', Locked = true;
+ StatusTxt: Label 'status', Locked = true;
+ SentTxt: Label 'sent', Locked = true;
+ ProcessingTxt: Label 'processing', Locked = true;
+ FailedTxt: Label 'failed', Locked = true;
+ DescriptionTxt: Label 'description', Locked = true;
+ ResponseErrorTxt: Label 'ERROR', Locked = true;
+ LevelTxt: Label 'level', Locked = true;
+ EventsTxt: Label 'events', Locked = true;
+ ReasonTxt: Label 'Reason: ', Locked = true;
+ NewTxt: Label 'new', Locked = true;
+ DocumentTxt: Label 'document', Locked = true;
+
+ #endregion
+
+ #region public methods
+
+ ///
+ /// The method sends the E-Document to the API.
+ ///
+ /// The E-Document record to be sent.
+ /// The E-Document Service record associated with the E-Document.
+ /// The context in which the document is being sent, encapsulated in a SendContext codeunit.
+ procedure Send(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; SendContext: Codeunit SendContext);
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ TempBlob: Codeunit "Temp Blob";
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ begin
+ TempBlob := SendContext.GetTempBlob();
+
+ EDocumentServiceStatus.Get(EDocument."Entry No", EDocumentService.Code);
+
+ case EDocumentServiceStatus.Status of
+ EDocumentServiceStatus.Status::Exported:
+ this.SendEDocument(EDocument, TempBlob, HttpRequestMessage, HttpResponseMessage);
+ EDocumentServiceStatus.Status::"Sending Error":
+ if EDocument."SignUp Document Id" = '' then
+ this.SendEDocument(EDocument, TempBlob, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ SendContext.SetTempBlob(TempBlob);
+ SendContext.Http().SetHttpRequestMessage(HttpRequestMessage);
+ SendContext.Http().SetHttpResponseMessage(HttpResponseMessage);
+
+ end;
+
+ ///
+ /// The method retrieves the response for the sent E-Document from the API.
+ ///
+ /// The E-Document record for which the response is being retrieved.
+ /// The E-Document Service record associated with the E-Document.
+ /// The context in which the document was sent, encapsulated in a SendContext codeunit.
+ /// Returns true if the response was successfully retrieved, otherwise false.
+ procedure GetResponse(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; SendContext: Codeunit SendContext): Boolean;
+ var
+ Status, ErrorDescription : Text;
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ begin
+ if EDocument."SignUp Document Id" = '' then
+ exit;
+
+ if not this.SignUpConnection.CheckDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage) then
+ exit;
+
+ SendContext.Http().SetHttpRequestMessage(HttpRequestMessage);
+ SendContext.Http().SetHttpResponseMessage(HttpResponseMessage);
+
+ if not this.ParseDocumentResponse(HttpResponseMessage.Content, Status, ErrorDescription) then begin
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.CouldNotRetrieveStatusFromResponseLbl);
+ exit;
+ end;
+
+
+ case Status of
+ this.SentTxt:
+ exit(this.SendAcknowledgePatch(EDocument, EDocumentService));
+ this.ProcessingTxt:
+ exit(false);
+ this.FailedTxt:
+ begin
+ if ErrorDescription <> '' then
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.ReasonTxt + ErrorDescription)
+ else
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.ErrorMessageMissingErr);
+ exit(this.SendAcknowledgePatch(EDocument, EDocumentService));
+ end;
+ end;
+
+ end;
+
+ ///
+ /// The method receives documents from the API.
+ ///
+ /// The E-Document Service record associated with the documents being received.
+ /// A codeunit containing metadata for the received documents.
+ /// The context in which the documents are being received, encapsulated in a ReceiveContext codeunit.
+ procedure ReceiveDocuments(var EDocumentService: Record "E-Document Service"; DocumentsMetadataTempBlobList: Codeunit "Temp Blob List"; ReceiveContext: Codeunit ReceiveContext)
+ var
+ TempBlob: Codeunit "Temp Blob";
+ JSONManagement: Codeunit "JSON Management";
+ ContentData: Text;
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ JsonArray: JsonArray;
+ JsonToken: JsonToken;
+ ReceiveSucced: Boolean;
+ begin
+ ReceiveSucced := this.SignUpConnection.GetReceivedDocuments(HttpRequestMessage, HttpResponseMessage);
+ ReceiveContext.Http().SetHttpRequestMessage(HttpRequestMessage);
+ ReceiveContext.Http().SetHttpResponseMessage(HttpResponseMessage);
+ if not ReceiveSucced then
+ exit;
+
+ if not HttpResponseMessage.Content.ReadAs(ContentData) then
+ exit;
+
+ if not JsonManagement.InitializeFromString(ContentData) then
+ exit;
+
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.InboxTxt, ContentData);
+ JsonArray.ReadFrom(ContentData);
+
+ foreach JsonToken in JsonArray do begin
+ Clear(TempBlob);
+ JsonToken.WriteTo(TempBlob.CreateOutStream(TextEncoding::UTF8));
+ DocumentsMetadataTempBlobList.Add(TempBlob);
+ end;
+ end;
+
+ procedure GetDocumentCountInBatch(var TempBlob: Codeunit "Temp Blob"): Integer
+ var
+ ResponseTxt: Text;
+ begin
+ TempBlob.CreateInStream().ReadText(ResponseTxt);
+ exit(this.GetNumberOfReceivedDocuments(ResponseTxt));
+ end;
+
+ procedure DownloadDocument(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; DocumentMetadataTempBlob: Codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ var
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ ContentData, DocumentId : Text;
+ begin
+ if EDocumentService."Service Integration V2" <> EDocumentService."Service Integration V2"::"ExFlow E-Invoicing" then
+ exit;
+
+ DocumentMetadataTempBlob.CreateInStream(TextEncoding::UTF8).ReadText(ContentData);
+
+ ContentData := this.LeaveJustNewLine(ContentData);
+
+ if not this.ParseReceivedDocument(ContentData, DocumentId) then begin
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.DocumentIdNotFoundErr);
+ exit;
+ end;
+
+ EDocument."SignUp Document Id" := CopyStr(DocumentId, 1, MaxStrLen(EDocument."SignUp Document Id"));
+ EDocument.Modify();
+
+ Clear(ContentData);
+ this.OnBeforeGetTargetDocumentRequest();
+
+ this.SignUpConnection.GetTargetDocumentRequest(EDocument."SignUp Document Id", HttpRequestMessage, HttpResponseMessage);
+ ReceiveContext.Http().SetHttpRequestMessage(HttpRequestMessage);
+ ReceiveContext.Http().SetHttpResponseMessage(HttpResponseMessage);
+
+ if not HttpResponseMessage.Content.ReadAs(ContentData) then
+ exit;
+
+ if not this.ParseContentData(ContentData) then
+ ContentData := '';
+
+ if ContentData = '' then
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, StrSubstNo(this.CouldNotRetrieveDocumentErr, DocumentId))
+ else
+ ReceiveContext.GetTempBlob().CreateOutStream(TextEncoding::UTF8).WriteText(ContentData);
+ end;
+
+ procedure MarkFetched(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var TempBlob: Codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ var
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ begin
+ this.OnBeforeMarkFetched();
+ this.SignUpConnection.RemoveDocumentFromReceived(EDocument, HttpRequestMessage, HttpResponseMessage);
+
+ ReceiveContext.Http().SetHttpRequestMessage(HttpRequestMessage);
+ ReceiveContext.Http().SetHttpResponseMessage(HttpResponseMessage);
+
+ end;
+ #endregion
+
+ #region local methods
+
+ local procedure InsertIntegrationLog(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; HttpRequestMessage: HttpRequestMessage; HttpResponseMessage: HttpResponseMessage)
+ var
+ EDocumentIntegrationLog: Record "E-Document Integration Log";
+ EDocIntegrationLogRecordRef: RecordRef;
+ RequestTxt: Text;
+ begin
+ if EDocumentService."Service Integration V2" = EDocumentService."Service Integration V2"::"No Integration" then
+ exit;
+
+ EDocumentIntegrationLog.Validate("E-Doc. Entry No", EDocument."Entry No");
+ EDocumentIntegrationLog.Validate("Service Code", EDocumentService.Code);
+ EDocumentIntegrationLog.Validate("Response Status", HttpResponseMessage.HttpStatusCode());
+ EDocumentIntegrationLog.Validate("Request URL", HttpRequestMessage.GetRequestUri());
+ EDocumentIntegrationLog.Validate(Method, HttpRequestMessage.Method());
+ EDocumentIntegrationLog.Insert();
+
+ EDocIntegrationLogRecordRef.GetTable(EDocumentIntegrationLog);
+
+ if HttpRequestMessage.Content.ReadAs(RequestTxt) then begin
+ this.InsertIntegrationBlob(EDocIntegrationLogRecordRef, RequestTxt, EDocumentIntegrationLog.FieldNo(EDocumentIntegrationLog."Request Blob"));
+ EDocIntegrationLogRecordRef.Modify();
+ end;
+
+ if HttpResponseMessage.Content.ReadAs(RequestTxt) then begin
+ this.InsertIntegrationBlob(EDocIntegrationLogRecordRef, RequestTxt, EDocumentIntegrationLog.FieldNo(EDocumentIntegrationLog."Response Blob"));
+ EDocIntegrationLogRecordRef.Modify();
+ end;
+ end;
+
+ local procedure ParseDocumentResponse(HttpContentResponse: HttpContent; var Status: Text; var StatusDescription: Text): Boolean
+ var
+ JsonManagement: Codeunit "JSON Management";
+ Result: Text;
+ begin
+ Status := '';
+ StatusDescription := '';
+
+ Result := this.SignUpHelpersImpl.ParseJsonString(HttpContentResponse);
+ if Result = '' then
+ exit;
+
+ if not JsonManagement.InitializeFromString(Result) then
+ exit;
+
+ if not this.GetStatus(JsonManagement, Status) then
+ exit;
+
+ case Status of
+ this.FailedTxt:
+ StatusDescription := this.GetErrorDescriptionFromJson(Result);
+ end;
+
+ exit(true);
+ end;
+
+ local procedure GetErrorDescriptionFromJson(JsonText: Text): Text
+ var
+ JsonObject: JsonObject;
+ JsonArray: JsonArray;
+ JsonToken: JsonToken;
+ EventObject: JsonObject;
+ ErrorDescription: Text;
+ begin
+ if JsonObject.ReadFrom(JsonText) then
+ if JsonObject.Get(this.EventsTxt, JsonToken) then
+ if JsonToken.IsArray() then begin
+ JsonArray := JsonToken.AsArray();
+ foreach JsonToken in JsonArray do
+ if JsonToken.IsObject then begin
+ EventObject := JsonToken.AsObject();
+ if EventObject.Get(this.LevelTxt, JsonToken) then
+ if (JsonToken.AsValue().AsText() = this.ResponseErrorTxt) then
+ if EventObject.Get(this.DescriptionTxt, JsonToken) then
+ if JsonToken.AsValue().AsText() <> '' then
+ ErrorDescription += JsonToken.AsValue().AsText() + ', ';
+ end;
+ end;
+ if ErrorDescription <> '' then
+ ErrorDescription := ErrorDescription.TrimEnd(', ');
+ exit(ErrorDescription);
+ end;
+
+ local procedure SendEDocument(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage);
+ begin
+ this.SignUpConnection.SendFilePostRequest(TempBlob, EDocument, HttpRequestMessage, HttpResponseMessage);
+ this.SetEDocumentFileID(EDocument."Entry No", this.ParseSendFileResponse(HttpResponseMessage.Content));
+ end;
+
+ local procedure ParseReceivedDocument(InputTxt: Text; var DocumentId: Text): Boolean
+ var
+ SignUpHelpers: Codeunit SignUpHelpers;
+ begin
+ DocumentId := SignUpHelpers.GetJsonValueFromText(InputTxt, this.TransactionIdTxt);
+ exit(DocumentId <> '');
+ end;
+
+ local procedure GetNumberOfReceivedDocuments(InputTxt: Text): Integer
+ var
+ JsonManagement: Codeunit "JSON Management";
+ Value: Text;
+ begin
+ InputTxt := this.LeaveJustNewLine(InputTxt);
+
+ if not JsonManagement.InitializeFromString(InputTxt) then
+ exit(0);
+
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.InboxTxt, Value);
+ JsonManagement.InitializeCollection(Value);
+
+ exit(JsonManagement.GetCollectionCount());
+ end;
+
+ local procedure ParseSendFileResponse(HttpContentResponse: HttpContent): Text
+ var
+ JsonManagement: Codeunit "JSON Management";
+ Result, Value : Text;
+ begin
+ Result := this.SignUpHelpersImpl.ParseJsonString(HttpContentResponse);
+ if Result = '' then
+ exit;
+
+ if not JsonManagement.InitializeFromString(Result) then
+ exit;
+
+ JsonManagement.GetStringPropertyValueByName(this.TransactionIdTxt, Value);
+ exit(Value);
+ end;
+
+ local procedure SetEDocumentFileID(EDocEntryNo: Integer; FileId: Text)
+ var
+ EDocument: Record "E-Document";
+ begin
+ if FileId = '' then
+ exit;
+
+ if not EDocument.Get(EDocEntryNo) then
+ exit;
+
+ EDocument."SignUp Document Id" := CopyStr(FileId, 1, MaxStrLen(EDocument."SignUp Document Id"));
+ EDocument.Modify();
+ end;
+
+ local procedure GetStatus(var JsonManagement: Codeunit "Json Management"; var Status: Text): Boolean
+ begin
+ if not JsonManagement.GetArrayPropertyValueAsStringByName(this.StatusTxt, Status) then
+ exit;
+
+ Status := Status.ToLower();
+ exit(true);
+ end;
+
+ local procedure InsertIntegrationBlob(var EDocIntegrationLogRecordRef: RecordRef; Data: Text; FieldNo: Integer)
+ var
+ TempBlob: Codeunit "Temp Blob";
+ begin
+ TempBlob.CreateOutStream().WriteText(Data);
+ TempBlob.ToRecordRef(EDocIntegrationLogRecordRef, FieldNo);
+ end;
+
+ local procedure LeaveJustNewLine(InputText: Text): Text
+ var
+ InputJson, OutputDocumentJsonObject, OutputJsonObject : JsonObject;
+ InputJsonArray, OutputDocumentJsonArray : JsonArray;
+ InputJsonToken, DocumentJsonToken : JsonToken;
+ OutputText: text;
+ DocumentList: List of [Text];
+ i: Integer;
+ begin
+ OutputText := InputText;
+ InputJson.ReadFrom(InputText);
+ if InputJson.Contains(this.InboxTxt) then begin
+ InputJson.Get(this.InboxTxt, InputJsonToken);
+ InputJsonArray := InputJsonToken.AsArray();
+ foreach InputJsonToken in InputJsonArray do
+ if InputJsonToken.AsObject().Get(this.StatusTxt, DocumentJsonToken) then
+ if DocumentJsonToken.AsValue().AsText().ToLower() = this.NewTxt then begin
+ InputJsonToken.AsObject().Get(this.InstanceIdTxt, DocumentJsonToken);
+ DocumentList.Add(DocumentJsonToken.AsValue().AsText());
+ end;
+
+ for i := 1 to DocumentList.Count do begin
+ Clear(OutputDocumentJsonObject);
+ OutputDocumentJsonObject.Add(this.InstanceIdTxt, DocumentList.Get(i));
+ OutputDocumentJsonArray.Add(OutputDocumentJsonObject);
+ end;
+
+ OutputJsonObject.Add(this.InboxTxt, OutputDocumentJsonArray);
+ OutputJsonObject.WriteTo(OutputText)
+ end;
+
+ exit(OutputText);
+ end;
+
+ local procedure ParseContentData(var InputText: Text): Boolean
+ var
+ JsonManagement: Codeunit "JSON Management";
+ Base64Convert: Codeunit "Base64 Convert";
+ Value: Text;
+ begin
+ if not JsonManagement.InitializeFromString(InputText) then
+ exit;
+
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.DocumentTxt, Value);
+ InputText := Base64Convert.FromBase64(Value);
+ exit(true);
+ end;
+
+ local procedure SendAcknowledgePatch(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"): Boolean
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ SignUpAPIRequests: Codeunit SignUpAPIRequests;
+ HttpResponseMessage: HttpResponseMessage;
+ HttpRequestMessage: HttpRequestMessage;
+ begin
+ EDocumentServiceStatus.SetLoadFields(Status);
+ EDocumentServiceStatus.Get(EDocument."Entry No", EDocumentService.Code);
+ if not (EDocumentServiceStatus.Status in [EDocumentServiceStatus.Status::Sent, EDocumentServiceStatus.Status::"Pending Response", EDocumentServiceStatus.Status::"Pending Batch"]) then
+ exit;
+
+ if SignUpAPIRequests.PatchDocument(EDocument, HttpRequestMessage, HttpResponseMessage) then begin
+ this.InsertIntegrationLog(EDocument, EDocumentService, HttpRequestMessage, HttpResponseMessage);
+ exit(true);
+ end else
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, StrSubstNo(this.CouldNotSendPatchErr, EDocument."SignUp Document Id"));
+ end;
+
+ #endregion
+
+ #region event publishers
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeGetTargetDocumentRequest()
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeMarkFetched()
+ begin
+
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/test/ExtensionLogo.png b/Apps/W1/EDocumentConnectors/SignUp/test/ExtensionLogo.png
new file mode 100644
index 000000000..4d2c9a626
Binary files /dev/null and b/Apps/W1/EDocumentConnectors/SignUp/test/ExtensionLogo.png differ
diff --git a/Apps/W1/EDocumentConnectors/SignUp/test/app.json b/Apps/W1/EDocumentConnectors/SignUp/test/app.json
new file mode 100644
index 000000000..a244074d4
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/test/app.json
@@ -0,0 +1,113 @@
+{
+ "id": "b56171bd-9a8e-47ad-a527-99f476d5af83",
+ "name": "E-Document Connector - SignUp Tests",
+ "publisher": "Microsoft",
+ "brief": "E-Document Connector - SignUp Tests",
+ "description": "E-Document Connector - SignUp Tests",
+ "version": "26.0.0.0",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2204541",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "logo": "ExtensionLogo.png",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2206603",
+ "dependencies": [
+ {
+ "id": "b56171bd-9a8e-47ad-a527-99f476d5af83",
+ "name": "E-Document Connector - SignUp",
+ "publisher": "SignUp Software AB",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b",
+ "name": "E-Document Core",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "de0dddf3-9917-430d-8d20-6e7679a08500",
+ "name": "E-Document Core Demo Data",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "5a0b41e9-7a42-4123-d521-2265186cfb31",
+ "name": "Contoso Coffee Demo Dataset",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8c",
+ "name": "E-Document Core Tests",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "dd0be2ea-f733-4d65-bb34-a28f4624fb14",
+ "name": "Library Assert",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "e7320ebb-08b3-4406-b1ec-b4927d3e280b",
+ "name": "Any",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "5d86850b-0d76-4eca-bd7b-951ad998e997",
+ "name": "Tests-TestLibraries",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "9856ae4f-d1a7-46ef-89bb-6ef056398228",
+ "name": "System Application Test Library",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "23de40a6-dfe8-4f80-80db-d70f83ce8caf",
+ "name": "Test Runner",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "5095f467-0a01-4b99-99d1-9ff1237d286f",
+ "name": "Library Variable Storage",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "bee8cf2f-494a-42f4-aabd-650e87934d39",
+ "name": "Business Foundation Test Libraries",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "40860557-a18d-42ad-aecb-22b7dd80dc80",
+ "name": "Permissions Mock",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ }
+ ],
+ "screenshots": [],
+ "platform": "26.0.0.0",
+ "application": "26.0.0.0",
+ "idRanges": [
+ {
+ "from": 148195,
+ "to": 148199
+ }
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "allowDownloadingSource": true,
+ "includeSourceInSymbolFile": true
+ },
+ "runtime": "15.0",
+ "features": [
+ "TranslationFile"
+ ],
+ "target": "OnPrem"
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationEvents.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationEvents.Codeunit.al
new file mode 100644
index 000000000..28825d089
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationEvents.Codeunit.al
@@ -0,0 +1,27 @@
+namespace Microsoft.EServices.EDocumentConnector.SignUp;
+
+
+codeunit 148197 IntegrationEvents
+{
+ EventSubscriberInstance = Manual;
+
+ var
+ LibraryLowerPermissions: Codeunit "Library - Lower Permissions";
+ IntegrationHelpers: Codeunit IntegrationHelpers;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::SignUpProcessing, OnBeforeGetTargetDocumentRequest, '', true, true)]
+ local procedure ProcessingOnBeforeGetTargetDocumentRequest()
+ begin
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCOEdit');
+ this.IntegrationHelpers.SetAPICode('/signup/200/download');
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::SignUpProcessing, OnBeforeGetTargetDocumentRequest, '', true, true)]
+ local procedure ProcessingOnBeforeMarkFetched()
+ begin
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCOEdit');
+ this.IntegrationHelpers.SetAPICode('/signup/200/markdownloaded');
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationHelpers.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationHelpers.Codeunit.al
new file mode 100644
index 000000000..8d490463f
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationHelpers.Codeunit.al
@@ -0,0 +1,67 @@
+namespace Microsoft.EServices.EDocumentConnector.SignUp;
+
+
+codeunit 148196 IntegrationHelpers
+{
+ internal procedure SetAPIWith200Code()
+ begin
+ this.SetAPICode('/signup/200');
+ end;
+
+ internal procedure SetAPIWith500Code()
+ begin
+ this.SetAPICode('/signup/500');
+ end;
+
+ internal procedure SetAPICode(Path: Text)
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ begin
+ SignUpConnectionSetup.Get();
+ SignUpConnectionSetup."Service URL" := this.SetMockServiceUrl(Path);
+ SignUpConnectionSetup.Modify(true);
+ end;
+
+ internal procedure SetCommonConnectionSetup()
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ SignUpAuthentication: Codeunit SignUpAuthentication;
+ begin
+ SignUpConnectionSetup.Get();
+ SignUpAuthentication.StorageSet(SignUpConnectionSetup."Root App ID", this.DummyId());
+ SignUpAuthentication.StorageSet(SignUpConnectionSetup."Root Secret", this.DummyId());
+ SignUpAuthentication.StorageSet(SignUpConnectionSetup."Root Tenant", this.DummyId());
+ SignUpAuthentication.StorageSet(SignUpConnectionSetup."Client ID", this.DummyId());
+ SignUpAuthentication.StorageSet(SignUpConnectionSetup."Client Secret", this.DummyId());
+ SignUpAuthentication.StorageSet(SignUpConnectionSetup."Client Tenant", this.ClientTenantId());
+
+ SignUpConnectionSetup."Authentication URL" := this.SetMockServiceUrl('/%1/oauth2/token');
+ SignUpConnectionSetup."Environment Type" := SignUpConnectionSetup."Environment Type"::Test;
+ SignUpConnectionSetup.Modify(true);
+ end;
+
+ internal procedure SetMockServiceUrl(Path: Text): Text[250]
+ begin
+ exit('http://localhost:8080' + Path);
+ end;
+
+ local procedure ClientTenantId(): Text
+ begin
+ exit('signup');
+ end;
+
+ local procedure DummyId(): Text[100]
+ begin
+ exit('0a4b7f70-452a-4883-844f-296443704124');
+ end;
+
+ internal procedure MockServiceDocumentId(): Text
+ begin
+ exit('485959a5-4a96-4a41-a208-13c30bb7e4d3');
+ end;
+
+ internal procedure MockCompanyId(): Text[100]
+ begin
+ exit('0007:SIGNUPSOFTWARE');
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationTests.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationTests.Codeunit.al
new file mode 100644
index 000000000..49c7e0aca
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/test/src/IntegrationTests.Codeunit.al
@@ -0,0 +1,633 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector.SignUp;
+
+
+using Microsoft.eServices.EDocument;
+using Microsoft.Inventory.Item;
+using Microsoft.EServices.EDocument.Service.Participant;
+using Microsoft.Foundation.Company;
+//using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Sales.Customer;
+using System.Threading;
+using Microsoft.eServices.EDocument.Integration;
+
+codeunit 148195 IntegrationTests
+{
+ Subtype = Test;
+
+ Permissions = tabledata SignUpConnectionSetup = rimd,
+ tabledata "E-Document" = r;
+
+ var
+ Customer: Record Customer;
+ Vendor: Record Vendor;
+ Item: Record Item;
+ EDocumentService: Record "E-Document Service";
+ LibraryEDocument: Codeunit "Library - E-Document";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryLowerPermissions: Codeunit "Library - Lower Permissions";
+ LibraryJobQueue: Codeunit "Library - Job Queue";
+ IntegrationHelpers: Codeunit IntegrationHelpers;
+ Assert: Codeunit Assert;
+ IsInitialized: Boolean;
+ IncorrectValueErr: Label 'Wrong value', Locked = true;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ [HandlerFunctions('ExternalHttpRequestHandler')]
+ procedure SubmitDocument()
+ var
+ EDocument: Record "E-Document";
+ JobQueueEntry: Record "Job Queue Entry";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ // Steps:
+ // Pending response -> Sent
+ this.Initialize();
+
+ // [Given] Team member
+ this.LibraryLowerPermissions.SetTeamMember();
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+
+ // [When] Posting invoice and EDocument is created
+ this.LibraryEDocument.PostInvoice(this.Customer);
+ EDocument.FindLast();
+ this.LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running ExFlow SubmitDocument
+ EDocument.FindLast();
+
+ // [Then] Document Id has been correctly set on E-Document, parsed from Integration response.
+ this.Assert.AreEqual(this.IntegrationHelpers.MockServiceDocumentId(), EDocument."Signup Document Id", 'ExFlow integration failed to set Document Id on E-Document');
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has "Pending Response"
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ this.LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running ExFlow GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is considered processed
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has Sent
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('3', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure SubmitDocument_Pending_Sent()
+ var
+ EDocument: Record "E-Document";
+ JobQueueEntry: Record "Job Queue Entry";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ // Steps:
+ // Pending response -> Pending response -> Sent
+ this.Initialize();
+ this.IntegrationHelpers.SetAPIWith200Code();
+
+ // [Given] Team member
+ this.LibraryLowerPermissions.SetTeamMember();
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+
+ // [When] Posting invoice and EDocument is created
+ this.LibraryEDocument.PostInvoice(this.Customer);
+ EDocument.FindLast();
+ this.LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running ExFlow SubmitDocument
+ EDocument.FindLast();
+
+ // [Then] Document Id has been correctly set on E-Document, parsed from Integration response
+ this.Assert.AreEqual(this.IntegrationHelpers.MockServiceDocumentId(), EDocument."Signup Document Id", 'ExFlow integration failed to set Document Id on E-Document');
+
+ // [Then] E-Document is pending response as ExFlow is async
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCOEdit');
+ this.IntegrationHelpers.SetAPICode('/signup/200/response-pending');
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ this.LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running ExFlow GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is pending response as ExFlow is async
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('3', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCOEdit');
+ this.IntegrationHelpers.SetAPIWith200Code();
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ this.LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running ExFlow GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is pending response as ExFlow is async
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to processed');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('4', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ [HandlerFunctions('EDocServicesPageHandler')]
+ procedure SubmitDocument_Error_Sent()
+ var
+ EDocument: Record "E-Document";
+ JobQueueEntry: Record "Job Queue Entry";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ // Steps:
+ // Pending response -> Error -> Pending response -> Sent
+ this.Initialize();
+ this.IntegrationHelpers.SetAPIWith200Code();
+
+ // [Given] Team member
+ this.LibraryLowerPermissions.SetTeamMember();
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+
+ // [When] Posting invoice and EDocument is created
+ this.LibraryEDocument.PostInvoice(this.Customer);
+ EDocument.FindLast();
+ this.LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running ExFlow SubmitDocument
+ EDocument.FindLast();
+
+ // [Then] Document Id has been correctly set on E-Document, parsed from Integration response
+ this.Assert.AreEqual(this.IntegrationHelpers.MockServiceDocumentId(), EDocument."Signup Document Id", 'ExFlow integration failed to set Document Id on E-Document');
+
+ // [Then] E-Document is pending response as ExFlow is async
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCOEdit');
+ this.IntegrationHelpers.SetAPICode('/signup/200/response-error');
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ this.LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running ExFlow GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is in error state
+ this.Assert.AreEqual(Enum::"E-Document Status"::Error, EDocument.Status, 'E-Document should be set to error');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has sending error
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Sending Error"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('3', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ EDocumentPage.ErrorMessagesPart.First();
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('Error', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('Reason: Http error 404 document identifier not found', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+
+ EDocumentPage.Close();
+
+ // Then user manually send
+ this.IntegrationHelpers.SetAPIWith200Code();
+ EDocument.FindLast();
+
+ // [THEN] Open E-Document page and resend
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ EDocumentPage.Send_Promoted.Invoke();
+ EDocumentPage.Close();
+
+ EDocument.FindLast();
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+
+ // [Then] E-Document is pending response as ExFlow is async
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('4', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCOEdit');
+ this.IntegrationHelpers.SetAPIWith200Code();
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ this.LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running ExFlow GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is pending response as ExFlow is async
+ this.Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to processed');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('5', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ EDocumentPage.Close();
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure SubmitDocumentServiceDown()
+ var
+ EDocument: Record "E-Document";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ this.Initialize();
+ this.IntegrationHelpers.SetAPIWith500Code();
+
+ // [Given] Team member
+ this.LibraryLowerPermissions.SetTeamMember();
+ this.LibraryLowerPermissions.AddPermissionSet('SignUpEDCORead');
+
+ // [When] Posting invoice and EDocument is created
+ this.LibraryEDocument.PostInvoice(this.Customer);
+ EDocument.FindLast();
+ this.LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running Avalara SubmitDocument
+ EDocument.FindLast();
+
+ this.Assert.AreEqual(Enum::"E-Document Status"::Error, EDocument.Status, 'E-Document should be set to error state when service is down.');
+ this.Assert.AreEqual('', EDocument."Signup Document Id", 'Document Id on E-Document should not be set.');
+
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+
+ // [THEN] E-Document has correct error status
+ this.Assert.AreEqual(Format(EDocument.Status::Error), EDocumentPage."Electronic Document Status".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), this.IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has correct error status
+ this.Assert.AreEqual(this.EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Sending Error"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), this.IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ this.LibraryEDocument.AssertEDocumentLogs(EDocument, this.EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ this.Assert.AreEqual('Error', EDocumentPage.ErrorMessagesPart."Message Type".Value(), this.IncorrectValueErr);
+ this.Assert.AreEqual('There was an error sending the request. Response code: 500 and error message: Internal Server Error', EDocumentPage.ErrorMessagesPart.Description.Value(), this.IncorrectValueErr);
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure SubmitGetDocuments()
+ var
+ EDocument: Record "E-Document";
+ //PurchaseHeader: Record "Purchase Header";
+ IntegrationEvents: Codeunit IntegrationEvents;
+ EDocumentServicesPage: TestPage "E-Document Service";
+ TmpDocCount: Integer;
+ begin
+ this.Initialize();
+
+ // Open and close E-Doc page creates auto import job due to setting
+ EDocumentServicesPage.OpenView();
+ EDocumentServicesPage.GoToRecord(this.EDocumentService);
+ EDocumentServicesPage."Resolve Unit Of Measure".SetValue(false);
+ EDocumentServicesPage."Lookup Item Reference".SetValue(true);
+ EDocumentServicesPage."Lookup Item GTIN".SetValue(false);
+ EDocumentServicesPage."Lookup Account Mapping".SetValue(false);
+ EDocumentServicesPage."Validate Line Discount".SetValue(false);
+ EDocumentServicesPage."Auto Import".SetValue(true);
+ EDocumentServicesPage.Close();
+
+ TmpDocCount := EDocument.Count();
+ BindSubscription(IntegrationEvents);
+ // Manually fire job queue job to import
+ this.LibraryEDocument.RunImportJob();
+ UnbindSubscription(IntegrationEvents);
+
+ // Assert that we have Purchase Invoice created
+ this.Assert.AreEqual(EDocument.Count(), TmpDocCount + 1, 'The document was not imported!');
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure GetMetadataProfiles()
+ var
+ SignUpMetadataProfile: Record SignUpMetadataProfile;
+ EDocServiceSupportedTypes: TestPage "E-Doc Service Supported Types";
+ begin
+ this.Initialize();
+
+ SignUpMetadataProfile.Reset();
+ SignUpMetadataProfile.DeleteAll();
+
+ // Populate metadata profiles
+ EDocServiceSupportedTypes.OpenView();
+ EDocServiceSupportedTypes.PopulateMetaData.Invoke();
+ EDocServiceSupportedTypes.Close();
+
+ this.Assert.TableIsNotEmpty(Database::SignUpMetadataProfile);
+ end;
+
+ local procedure Initialize()
+ var
+ SignUpConnectionSetup: Record SignUpConnectionSetup;
+ CompanyInformation: Record "Company Information";
+ ServiceParticipant: Record "Service Participant";
+ SignUpAuthentication: Codeunit SignUpAuthentication;
+ begin
+ this.LibraryLowerPermissions.SetOutsideO365Scope();
+
+ SignUpConnectionSetup.DeleteAll();
+ SignUpAuthentication.InitConnectionSetup();
+ this.IntegrationHelpers.SetCommonConnectionSetup();
+ this.IntegrationHelpers.SetAPIWith200Code();
+
+ if this.IsInitialized then
+ exit;
+
+ this.CreateDefaultMetadataProfile();
+ this.LibraryEDocument.SetupStandardVAT();
+ this.LibraryEDocument.SetupStandardSalesScenario(this.Customer, this.EDocumentService, Enum::"E-Document Format"::"PEPPOL BIS 3.0", Enum::"Service Integration"::"ExFlow E-Invoicing PTE");
+ this.LibraryEDocument.SetupStandardPurchaseScenario(this.Vendor, this.EDocumentService, Enum::"E-Document Format"::"PEPPOL BIS 3.0", Enum::"Service Integration"::"ExFlow E-Invoicing PTE");
+ this.EDocumentService."Auto Import" := true;
+ this.EDocumentService."Import Minutes between runs" := 5;
+ this.EDocumentService."Import Start Time" := Time();
+ this.EDocumentService.Modify();
+
+ this.LibraryInventory.CreateItem(this.Item);
+
+ this.Vendor.Name := 'CRONUS GB SELLER';
+ this.Vendor."VAT Registration No." := '777777777'; // GB777777771
+ this.Vendor."Receive E-Document To" := Enum::"E-Document Type"::"Purchase Invoice";
+ this.Vendor.Modify();
+
+ // Vendor to get invoices from
+ Clear(ServiceParticipant);
+ ServiceParticipant.Service := this.EDocumentService.Code;
+ ServiceParticipant."Participant Type" := ServiceParticipant."Participant Type"::Vendor;
+ ServiceParticipant.Participant := this.Vendor."No.";
+ ServiceParticipant."Participant Identifier" := this.IntegrationHelpers.MockCompanyId();
+ ServiceParticipant.Insert();
+
+ // Customer to send invoice to
+ Clear(ServiceParticipant);
+ ServiceParticipant.Service := this.EDocumentService.Code;
+ ServiceParticipant."Participant Type" := ServiceParticipant."Participant Type"::Customer;
+ ServiceParticipant.Participant := this.Customer."No.";
+ ServiceParticipant."Participant Identifier" := this.IntegrationHelpers.MockCompanyId();
+ ServiceParticipant.Insert();
+
+ CompanyInformation.Get();
+ CompanyInformation."VAT Registration No." := '777777777'; // GB777777771
+ CompanyInformation."SignUp Service Participant Id" := this.IntegrationHelpers.MockCompanyId();
+ CompanyInformation.Modify();
+
+ this.ApplyMetadataProfile(this.GetMetadataProfileId());
+
+ this.IsInitialized := true;
+ end;
+
+ local procedure CreateDefaultMetadataProfile()
+ var
+ SignUpMetadataProfile: Record SignUpMetadataProfile;
+ begin
+ if not SignUpMetadataProfile.IsEmpty() then
+ SignUpMetadataProfile.DeleteAll(true);
+
+ SignUpMetadataProfile.Init();
+ SignUpMetadataProfile.Validate("Profile ID", this.GetMetadataProfileId());
+ SignUpMetadataProfile.Validate("Profile Name", 'PEPPOL BIS Billing v 3 Invoice UBL');
+ SignUpMetadataProfile.Validate("Process Identifier Scheme", 'cenbii-procid-ubl');
+ SignUpMetadataProfile.Validate("Process Identifier Value", 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0');
+ SignUpMetadataProfile.Validate("Document Identifier Scheme", 'busdox-docid-qns');
+ SignUpMetadataProfile.Validate("Document Identifier Value", 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1');
+ SignUpMetadataProfile.Insert(true);
+ end;
+
+ local procedure ApplyMetadataProfile(ProfileID: Integer)
+ var
+ EDocServiceSupportedType: Record "E-Doc. Service Supported Type";
+ begin
+ if EDocServiceSupportedType.FindSet(true) then
+ repeat
+ EDocServiceSupportedType.Validate("Profile Id", ProfileID);
+ EDocServiceSupportedType.Modify(true);
+ until EDocServiceSupportedType.Next() = 0;
+ end;
+
+ local procedure GetMetadataProfileId(): Integer
+ begin
+ exit(158);
+ end;
+
+ [ModalPageHandler]
+ internal procedure EDocServicesPageHandler(var EDocumentServicesPage: TestPage "E-Document Services")
+ begin
+ EDocumentServicesPage.Filter.SetFilter(Code, this.EDocumentService.Code);
+ EDocumentServicesPage.OK().Invoke();
+ end;
+
+ [StrMenuHandler]
+ internal procedure ExternalHttpRequestHandler(Options: Text[1024]; var Choice: Integer; Instruction: Text[1024])
+ var
+ ExternalHttpRequestChoice: Option " ","Allow Always","Allow Once","Block Always","Block Once";
+ begin
+ Choice := ExternalHttpRequestChoice::"Allow Always";
+ end;
+}
\ No newline at end of file