diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/ExtensionLogo.png b/Apps/W1/EDocumentConnectors/SignUp/app/ExtensionLogo.png
new file mode 100644
index 0000000000..4d2c9a626c
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 0000000000..5d02eb4470
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/app.json
@@ -0,0 +1,47 @@
+{
+ "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"
+ },
+ {
+ "id": "d852a468-263e-49e5-bfda-f09e33342b89",
+ "name": "E-Documents Connector with External Endpoints",
+ "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": "OnPrem",
+ "features": [
+ "TranslationFile"
+ ]
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/APIRequests.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/APIRequests.Codeunit.al
new file mode 100644
index 0000000000..74059d2555
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/APIRequests.Codeunit.al
@@ -0,0 +1,110 @@
+// ------------------------------------------------------------------------------------------------
+// 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 6380 APIRequests
+{
+ Access = Internal;
+
+ var
+ APIRequestsImpl: Codeunit APIRequestsImpl;
+
+ #region public methods
+
+ ///
+ /// The method sends a file to the API.
+ /// https://[BASEURL]/api/Peppol
+ ///
+ /// 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
+ begin
+ exit(this.APIRequestsImpl.SendFilePostRequest(TempBlob, EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method checks the status of the sent document.
+ /// https://[BASE URL]/api/Peppol/status?peppolInstanceId=
+ ///
+ /// 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
+ begin
+ exit(this.APIRequestsImpl.GetSentDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method modifies the document.
+ /// https://[BASE URL]/api/Peppol/outbox?peppolInstanceId=
+ ///
+ /// 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
+ begin
+ exit(this.APIRequestsImpl.PatchDocument(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets the received document request.
+ /// https://[BASE URL]/api/Peppol/Inbox?peppolId=
+ ///
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure GetReceivedDocumentsRequest(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.APIRequestsImpl.GetReceivedDocumentsRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets the target document request.
+ /// https://[BASE URL]/api/Peppol/inbox-document?peppolId=
+ ///
+ /// Document ID
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure GetTargetDocumentRequest(DocumentId: Text; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.APIRequestsImpl.GetTargetDocumentRequest(DocumentId, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method modifies the received document.
+ /// // https://[BASE URL]/api/Peppol/inbox?peppolInstanceId=
+ ///
+ /// EDocument table
+ /// Http Request Message
+ /// Http Response Message
+ /// True if successfully completed
+ procedure PatchReceivedDocument(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.APIRequestsImpl.PatchReceivedDocument(EDocument, HttpRequestMessage, HttpResponseMessage));
+ 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
+ begin
+ exit(this.APIRequestsImpl.GetMarketPlaceCredentials(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/APIRequestsImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/APIRequestsImpl.Codeunit.al
new file mode 100644
index 0000000000..bb47a40cff
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/APIRequestsImpl.Codeunit.al
@@ -0,0 +1,286 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Company;
+using Microsoft.Sales.Customer;
+using System.Security.Authentication;
+using System.Text;
+using System.Utilities;
+using System.Xml;
+
+codeunit 6389 APIRequestsImpl
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ #region variables
+
+ var
+ 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';
+ UnSupportedDocumentTypeTxt: Label 'Document %1 is not supported.', Comment = '%1 = EDocument Type', Locked = true;
+ 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;
+ 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;
+ DocumentIdValueTxt: Label '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', Locked = true;
+ DocumentIdSchemeTxt: Label 'documentIdScheme', Locked = true;
+ BusdoxDocIdQNSTxt: Label 'busdox-docid-qns', Locked = true;
+ ProcessIdTxt: Label 'processId', Locked = true;
+ ProcessIdValueTxt: Label 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0', Locked = true;
+ ProcessIdSchemeTxt: Label 'processIdScheme', Locked = true;
+ ProcessIdSchemeValueTxt: Label 'cenbii-procid-ubl', Locked = true;
+ SendModeTxt: Label 'sendMode', Locked = true;
+ DocumentTxt: Label 'document', Locked = true;
+
+ #endregion
+
+ #region public methods
+
+ // https:///api/Peppol
+ procedure SendFilePostRequest(var TempBlob: Codeunit "Temp Blob"; EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ ConnectionSetup: Record ConnectionSetup;
+ HttpContent: HttpContent;
+ Payload: Text;
+ begin
+ Payload := this.XmlToTxt(TempBlob);
+ if Payload = '' then
+ exit;
+
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ ConnectionSetup.SetLoadFields("Company Id", "Environment Type", ServiceURL);
+ this.GetSetup(ConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::POST, ConnectionSetup.ServiceURL + '/api/Peppol');
+ this.PrepareContent(HttpContent, Payload, EDocument, ConnectionSetup);
+ HttpRequestMessage.Content(HttpContent);
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ // https:///api/Peppol/status?peppolInstanceId=
+ procedure GetSentDocumentStatus(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ ConnectionSetup: Record ConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ ConnectionSetup.SetLoadFields(ServiceURL);
+ this.GetSetup(ConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::GET, ConnectionSetup.ServiceURL + '/api/Peppol/status?peppolInstanceId=' + EDocument."Document Id");
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ // https:///api/Peppol/outbox?peppolInstanceId=
+ procedure PatchDocument(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ ConnectionSetup: Record ConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ ConnectionSetup.SetLoadFields(ServiceURL);
+ this.GetSetup(ConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::PATCH, ConnectionSetup.ServiceURL + '/api/Peppol/outbox?peppolInstanceId=' + EDocument."Document Id");
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ // https:///api/Peppol/Inbox?peppolId=
+ procedure GetReceivedDocumentsRequest(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ ConnectionSetup: Record ConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ ConnectionSetup.SetLoadFields(ServiceURL, "Company Id");
+ this.GetSetup(ConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::GET, ConnectionSetup.ServiceURL + '/api/Peppol/Inbox?peppolId=' + this.SenderReceiverPrefixTxt + ConnectionSetup."Company Id");
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ // https:///api/Peppol/inbox-document?peppolId=
+ procedure GetTargetDocumentRequest(DocumentId: Text; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ ConnectionSetup: Record ConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ ConnectionSetup.SetLoadFields(ServiceURL, "Company Id");
+ this.GetSetup(ConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::GET, ConnectionSetup.ServiceURL + '/api/Peppol/inbox-document?peppolId=' + this.SenderReceiverPrefixTxt + ConnectionSetup."Company Id" + '&peppolInstanceId=' + DocumentId);
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ // https:///api/Peppol/inbox?peppolInstanceId=
+ procedure PatchReceivedDocument(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ ConnectionSetup: Record ConnectionSetup;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+ ConnectionSetup.SetLoadFields(ServiceURL);
+ this.GetSetup(ConnectionSetup);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::PATCH, ConnectionSetup.ServiceURL + '/api/Peppol/inbox?peppolInstanceId=' + EDocument."Document Id");
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ procedure GetMarketPlaceCredentials(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ Authentication: Codeunit Authentication;
+ begin
+ this.InitRequest(HttpRequestMessage, HttpResponseMessage);
+
+ HttpRequestMessage := this.PrepareRequestMsg("Http Request Type"::POST, Authentication.GetRootUrl() + '/api/Registration/init?EntraTenantId=' + Authentication.GetBCInstanceIdentifier());
+ exit(this.SendRequest(HttpRequestMessage, HttpResponseMessage, true));
+ end;
+
+ #endregion
+
+ #region local methods
+
+ local procedure InitRequest(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ begin
+ Clear(HttpRequestMessage);
+ Clear(HttpResponseMessage);
+ end;
+
+ local procedure GetSetup(var ConnectionSetup: Record ConnectionSetup)
+ var
+ MissingSetupErrorInfo: ErrorInfo;
+ begin
+ if not IsNullGuid(ConnectionSetup.SystemId) then
+ exit;
+
+ if not ConnectionSetup.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 PrepareContent(var HttpContent: HttpContent; Payload: Text; EDocument: Record "E-Document"; ConnectionSetup: Record ConnectionSetup)
+ var
+ ContentText: Text;
+ HttpHeaders: HttpHeaders;
+ begin
+ Clear(HttpContent);
+ ContentText := this.PrepareContentForSend(this.GetDocumentType(EDocument), ConnectionSetup."Company Id", this.GetCustomerID(EDocument), this.GetSenderCountryCode(), Payload, ConnectionSetup."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(HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.SendRequest(HttpRequestMessage, HttpResponseMessage, false);
+ end;
+
+ local procedure SendRequest(HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage; RootRequest: Boolean): Boolean
+ var
+ Authentication: Codeunit Authentication;
+ HttpClient: HttpClient;
+ HttpHeaders: HttpHeaders;
+ begin
+ HttpRequestMessage.GetHeaders(HttpHeaders);
+ if RootRequest then
+ HttpHeaders.Add(this.AuthorizationTxt, Authentication.GetRootBearerAuthToken())
+ else
+ HttpHeaders.Add(this.AuthorizationTxt, Authentication.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", "E-Document Type"::"Sales Credit Memo", "E-Document Type"::"Service Invoice", "E-Document Type"::"Service Credit Memo":
+ exit(this.InvoiceTxt);
+ "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"): Text[50]
+ var
+ Customer: Record Customer;
+ begin
+ Customer.SetLoadFields("Service Participant Id");
+ Customer.Get(EDocument."Bill-to/Pay-to No.");
+ Customer.TestField("Service Participant Id");
+ exit(Customer."Service Participant Id");
+ 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(DocumentType: Text; SendingCompanyID: Text; RecieverCompanyID: Text; SenderCountryCode: Text; Payload: Text; SendMode: Enum EnvironmentType): Text
+ var
+ Base64Convert: Codeunit "Base64 Convert";
+ JsonObject: JsonObject;
+ ContentText: Text;
+ begin
+ JsonObject.Add(this.DocumentTypeTxt, DocumentType);
+ JsonObject.Add(this.ReceiverTxt, this.SenderReceiverPrefixTxt + RecieverCompanyID);
+ JsonObject.Add(this.SenderTxt, this.SenderReceiverPrefixTxt + SendingCompanyID);
+ JsonObject.Add(this.SenderCountryCodeTxt, SenderCountryCode);
+ JsonObject.Add(this.DocumentIdTxt, this.DocumentIdValueTxt);
+ JsonObject.Add(this.DocumentIdSchemeTxt, this.BusdoxDocIdQNSTxt);
+ JsonObject.Add(this.ProcessIdTxt, this.ProcessIdValueTxt);
+ JsonObject.Add(this.ProcessIdSchemeTxt, this.ProcessIdSchemeValueTxt);
+ 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/Authentication.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Authentication.Codeunit.al
new file mode 100644
index 0000000000..4cab43fe24
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Authentication.Codeunit.al
@@ -0,0 +1,98 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+codeunit 6381 Authentication
+{
+ Access = Internal;
+
+ var
+ AuthenticationImpl: Codeunit AuthenticationImpl;
+
+ ///
+ /// The method initializes the connection setup.
+ ///
+ procedure InitConnectionSetup()
+ begin
+ this.AuthenticationImpl.InitConnectionSetup();
+ end;
+
+ ///
+ /// The method returns the onboarding URL.
+ ///
+ /// Onboarding URL
+ procedure GetRootOnboardingUrl(): Text
+ begin
+ exit(this.AuthenticationImpl.GetRootOnboardingUrl());
+ end;
+
+ ///
+ /// The method creates the client credentials.
+ ///
+ [NonDebuggable]
+ procedure CreateClientCredentials()
+ begin
+ this.AuthenticationImpl.CreateClientCredentials();
+ end;
+
+ ///
+ /// The method returns the bearer authentication text.
+ ///
+ /// Bearer authentication token
+ procedure GetBearerAuthToken(): SecretText;
+ begin
+ exit(this.AuthenticationImpl.GetBearerAuthToken());
+ end;
+
+ ///
+ /// The method returns the root bearer authentication token.
+ ///
+ /// Root bearer authentication token
+ procedure GetRootBearerAuthToken(): SecretText;
+ begin
+ exit(this.AuthenticationImpl.GetRootBearerAuthToken());
+ end;
+
+ ///
+ /// The mehod saves the token to the storage.
+ ///
+ /// Token Key
+ /// Token
+ [NonDebuggable]
+ procedure StorageSet(var TokenKey: Guid; Value: Text)
+ begin
+ this.AuthenticationImpl.StorageSet(TokenKey, Value);
+ end;
+
+ ///
+ /// The mehod saves the token to the storage.
+ ///
+ /// Token Key
+ /// Token
+ [NonDebuggable]
+ procedure StorageSet(var TokenKey: Guid; Value: SecretText)
+ begin
+ this.AuthenticationImpl.StorageSet(TokenKey, Value);
+ end;
+
+ ///
+ /// The method returns BC instance identifier.
+ ///
+ /// Identifier
+ procedure GetBCInstanceIdentifier() Identifier: Text
+ begin
+ exit(this.AuthenticationImpl.GetBCInstanceIdentifier());
+ end;
+
+ ///
+ /// The method returns the root URL.
+ ///
+ ///
+ [NonDebuggable]
+ procedure GetRootUrl() ReturnValue: Text
+ begin
+ exit(this.AuthenticationImpl.GetRootUrl());
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/AuthenticationImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/AuthenticationImpl.Codeunit.al
new file mode 100644
index 0000000000..eb67cae275
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/AuthenticationImpl.Codeunit.al
@@ -0,0 +1,344 @@
+// ------------------------------------------------------------------------------------------------
+// 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.KeyVault;
+using System.Environment;
+using System.Security.Authentication;
+using System.Azure.Identity;
+
+codeunit 6390 AuthenticationImpl
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ #region variables
+ var
+ ConnectionSetup: Record ConnectionSetup;
+ HelpersImpl: Codeunit HelpersImpl;
+ 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;
+ ProdTenantIdTxt: Label '0d725623-dc26-484f-a090-b09d2003d092', 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;
+ RootIdTxt: Label '-root-id', Locked = true;
+ SignUpRootSecretTxt: Label 'signup-root-secret', Locked = true;
+ SignUpRootTenantTxt: Label 'signup-root-tenant', Locked = true;
+ SignUpAccessTokenKeyTxt: Label '{E45BB975-E67B-4A87-AC24-D409A5EF8301}', Locked = true;
+
+ #endregion
+
+ #region public methods
+
+ procedure InitConnectionSetup()
+ begin
+ if this.ConnectionSetup.Get() then
+ exit;
+
+ this.ConnectionSetup."Authentication URL" := this.AuthURLTxt;
+ this.ConnectionSetup.ServiceURL := this.ProdServiceAPITxt;
+ this.StorageSet(this.ConnectionSetup."Client Tenant", this.ProdTenantIdTxt);
+ this.ConnectionSetup.Insert();
+ end;
+
+ procedure GetRootOnboardingUrl(): Text
+ begin
+ exit(this.GetRootUrl() + '/supm/landingpage?EntraTenantId=' + this.GetBCInstanceIdentifier());
+ end;
+
+ [NonDebuggable]
+ procedure CreateClientCredentials()
+ var
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ ClientId, Response : Text;
+ ClientSecret: SecretText;
+ begin
+ if not this.GetClientCredentials(HttpRequestMessage, HttpResponseMessage) then
+ Error(this.ErrorUnableToCreateClientCredentialsLbl);
+
+ if not HttpResponseMessage.Content.ReadAs(Response) then
+ exit;
+
+ ClientId := this.HelpersImpl.GetJsonValueFromText(Response, this.ClientIdTxt);
+ ClientSecret := this.HelpersImpl.GetJsonValueFromText(Response, this.ClientSecretTxt);
+
+ if (ClientId <> '') and (not ClientSecret.IsEmpty()) then
+ this.SaveClientCredentials(ClientId, ClientSecret);
+ end;
+
+ procedure GetBearerAuthToken(): SecretText;
+ begin
+ exit(SecretStrSubstNo(this.BearerTxt, this.GetAuthToken()));
+ end;
+
+ procedure GetRootBearerAuthToken(): SecretText;
+ begin
+ exit(SecretStrSubstNo(this.BearerTxt, this.GetRootAuthToken()));
+ end;
+
+ [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;
+
+ procedure StorageSet(var TokenKey: Guid; Value: SecretText): Boolean
+ begin
+ exit(this.StorageSet(TokenKey, Value, DataScope::Module));
+ end;
+
+ 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;
+
+ [NonDebuggable]
+ procedure GetRootUrl() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.SignupRootUrlTxt, ReturnValue) then
+ exit;
+
+ if not this.ConnectionSetup.GetSetup() then
+ exit;
+
+ this.ConnectionSetup.TestField("Root Market URL");
+ ReturnValue := this.StorageGetText(this.ConnectionSetup."Root Market URL", DataScope::Module);
+ end;
+
+ #endregion
+
+ #region local methods
+
+ local procedure GetAuthToken() AccessToken: SecretText;
+ var
+ HttpError: Text;
+ begin
+ AccessToken := this.StorageGet(this.SignUpAccessTokenKeyTxt, DataScope::Company);
+
+ if this.HelpersImpl.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.ConnectionSetup);
+
+ this.ConnectionSetup.GetSetup();
+ this.StorageSet(this.ConnectionSetup."Client ID", ClientId);
+ this.StorageSet(this.ConnectionSetup."Client Secret", ClientSecret);
+ this.ConnectionSetup.Modify();
+
+ Clear(this.ConnectionSetup);
+ end;
+
+ [NonDebuggable]
+ local procedure GetClientCredentials(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ APIRequests: Codeunit APIRequests;
+ begin
+ APIRequests.GetMarketPlaceCredentials(HttpRequestMessage, HttpResponseMessage);
+
+ if not HttpResponseMessage.IsSuccessStatusCode() then
+ exit;
+
+ exit(this.HelpersImpl.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
+ begin
+ exit(this.GetAccessToken(AccessToken, this.GetRootId(), this.GetRootSecret(), this.GetRootTenant()));
+ end;
+
+ [NonDebuggable]
+ local procedure GetClientAccessToken(var AccessToken: SecretText): Boolean
+ var
+ ModuleDataScope: DataScope;
+ begin
+ ModuleDataScope := ModuleDataScope::Module;
+ this.ConnectionSetup.GetSetup();
+
+ exit(this.GetAccessToken(AccessToken, this.StorageGetText(this.ConnectionSetup."Client ID", ModuleDataScope),
+ this.StorageGet(this.ConnectionSetup."Client Secret", ModuleDataScope),
+ this.StorageGetText(this.ConnectionSetup."Client Tenant", ModuleDataScope)));
+ end;
+
+ [NonDebuggable]
+ local procedure GetAccessToken(var AccessToken: SecretText; ClientId: Text; ClientSecret: SecretText; ClientTenant: Text) Success: Boolean
+ var
+ OAuth2: Codeunit OAuth2;
+ begin
+ Success := OAuth2.AcquireTokenWithClientCredentials(ClientId, ClientSecret, StrSubstNo(this.ConnectionSetup."Authentication URL", ClientTenant), '', ClientId, AccessToken);
+ exit(Success and not AccessToken.IsEmpty());
+ 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;
+
+ [NonDebuggable]
+ local procedure GetRootId() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.RootIdTxt, ReturnValue) then
+ exit;
+
+ if not this.ConnectionSetup.GetSetup() then
+ exit;
+
+ this.ConnectionSetup.TestField("Root App ID");
+ ReturnValue := this.StorageGetText(this.ConnectionSetup."Root App ID", DataScope::Module);
+ end;
+
+ [NonDebuggable]
+ local procedure GetRootSecret() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.SignUpRootSecretTxt, ReturnValue) then
+ exit;
+
+ if not this.ConnectionSetup.GetSetup() then
+ exit;
+
+ this.ConnectionSetup.TestField("Root Secret");
+ ReturnValue := this.StorageGetText(this.ConnectionSetup."Root Secret", DataScope::Module);
+ end;
+
+ [NonDebuggable]
+ local procedure GetRootTenant() ReturnValue: Text
+ begin
+ if this.FetchSecretFromKeyVault(this.SignUpRootTenantTxt, ReturnValue) then
+ exit;
+
+ if not this.ConnectionSetup.GetSetup() then
+ exit;
+
+ this.ConnectionSetup.TestField("Root Tenant");
+ ReturnValue := this.StorageGetText(this.ConnectionSetup."Root Tenant", DataScope::Module);
+ 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.GetAadTenantId();
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Connection.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Connection.Codeunit.al
new file mode 100644
index 0000000000..69d6451946
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Connection.Codeunit.al
@@ -0,0 +1,76 @@
+// ------------------------------------------------------------------------------------------------
+// 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 6382 Connection
+{
+ Access = Internal;
+
+ var
+ ConnectionImpl: Codeunit ConnectionImpl;
+
+ ///
+ /// 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
+ exit(this.ConnectionImpl.SendFilePostRequest(TempBlob, EDocument, HttpRequestMessage, 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
+ exit(this.ConnectionImpl.CheckDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets received documents.
+ ///
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure GetReceivedDocuments(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.ConnectionImpl.GetReceivedDocuments(HttpRequestMessage, HttpResponseMessage));
+ 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
+ exit(this.ConnectionImpl.GetTargetDocumentRequest(DocumentId, HttpRequestMessage, HttpResponseMessage));
+ 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
+ exit(this.ConnectionImpl.RemoveDocumentFromReceived(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/ConnectionImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/ConnectionImpl.Codeunit.al
new file mode 100644
index 0000000000..520cd055c6
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/ConnectionImpl.Codeunit.al
@@ -0,0 +1,116 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+using Microsoft.Purchases.Posting;
+using System.Utilities;
+
+codeunit 6391 ConnectionImpl
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+ Permissions = tabledata "E-Document" = m;
+
+ #region variables
+
+ var
+ APIRequests: Codeunit APIRequests;
+ HelpersImpl: Codeunit HelpersImpl;
+ 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.';
+
+ #endregion
+
+ #region public methods
+
+ procedure SendFilePostRequest(var TempBlob: Codeunit "Temp Blob"; var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.APIRequests.SendFilePostRequest(TempBlob, EDocument, HttpRequestMessage, HttpResponseMessage);
+ exit(this.CheckIfSuccessfulRequest(EDocument, HttpResponseMessage));
+ end;
+
+ procedure CheckDocumentStatus(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.APIRequests.GetSentDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage);
+ exit(this.CheckIfSuccessfulRequest(EDocument, HttpResponseMessage));
+ end;
+
+ procedure GetReceivedDocuments(var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.APIRequests.GetReceivedDocumentsRequest(HttpRequestMessage, HttpResponseMessage);
+
+ if not HttpResponseMessage.IsSuccessStatusCode() then
+ exit;
+
+ exit(this.HelpersImpl.ParseJsonString(HttpResponseMessage.Content) <> '');
+ end;
+
+ procedure GetTargetDocumentRequest(DocumentId: Text; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.APIRequests.GetTargetDocumentRequest(DocumentId, HttpRequestMessage, HttpResponseMessage);
+ exit(HttpResponseMessage.IsSuccessStatusCode());
+ end;
+
+ procedure RemoveDocumentFromReceived(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ this.APIRequests.PatchReceivedDocument(EDocument, HttpRequestMessage, HttpResponseMessage);
+ exit(HttpResponseMessage.IsSuccessStatusCode());
+ 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
+ EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, StrSubstNo(this.UnsuccessfulResponseErr, HttpResponseMessage.HttpStatusCode, HttpResponseMessage.ReasonPhrase));
+ end;
+
+ #endregion
+
+ #region event subscribers
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnAfterCheckAndUpdate, '', false, false)]
+ local procedure CheckOnPosting(var PurchaseHeader: Record "Purchase Header"; CommitIsSuppressed: Boolean; PreviewMode: Boolean)
+ var
+ EDocument: Record "E-Document";
+ EDocumentService: Record "E-Document Service";
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ begin
+ if PurchaseHeader.IsTemporary() then
+ exit;
+
+ EDocument.SetLoadFields("Entry No");
+ EDocument.SetRange("Document Record ID", PurchaseHeader.RecordId);
+ if not EDocument.FindFirst() then
+ exit;
+
+ EDocumentService.SetLoadFields(Code);
+ EDocumentService.SetRange("Service Integration", EDocumentService."Service Integration"::"ExFlow E-Invoicing");
+ if not EDocumentService.FindFirst() then
+ exit;
+
+ EDocumentServiceStatus.SetLoadFields(Status);
+ EDocumentServiceStatus.SetRange("E-Document Entry No", EDocument."Entry No");
+ EDocumentServiceStatus.SetRange("E-Document Service Code", EDocumentService.Code);
+ if EDocumentServiceStatus.FindSet() then
+ repeat
+ EDocumentServiceStatus.TestField(EDocumentServiceStatus.Status, EDocumentServiceStatus.Status::Approved);
+ until EDocumentServiceStatus.Next() = 0;
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/HelpersImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/HelpersImpl.Codeunit.al
new file mode 100644
index 0000000000..d6bb899b42
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/HelpersImpl.Codeunit.al
@@ -0,0 +1,92 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+codeunit 6385 HelpersImpl
+{
+ 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;
+
+ #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/Integration/IntegrationEnumExt.EnumExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/IntegrationEnumExt.EnumExt.al
new file mode 100644
index 0000000000..db4953f886
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/IntegrationEnumExt.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;
+
+enumextension 6380 IntegrationEnumExt extends "E-Document Integration"
+{
+ value(6380; "ExFlow E-Invoicing")
+ {
+ Caption = 'ExFlow E-Invoicing';
+ Implementation = "E-Document Integration" = IntegrationImpl;
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/IntegrationImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/IntegrationImpl.Codeunit.al
new file mode 100644
index 0000000000..019ad06a32
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Integration/IntegrationImpl.Codeunit.al
@@ -0,0 +1,61 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+codeunit 6386 IntegrationImpl implements "E-Document Integration"
+{
+ Access = Internal;
+
+ var
+ Processing: Codeunit Processing;
+ BatchSendNotSupportedErr: Label 'Batch sending is not supported in this version';
+ CancelNotSupportedErr: Label 'Cancel is not supported in this version';
+
+ procedure Send(var EDocument: Record "E-Document"; var TempBlob: Codeunit "Temp Blob"; var IsAsync: Boolean; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ var
+ begin
+ this.Processing.SendEDocument(EDocument, TempBlob, IsAsync, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ procedure SendBatch(var EDocuments: Record "E-Document"; var TempBlob: Codeunit "Temp Blob"; var IsAsync: Boolean; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ begin
+ IsAsync := false;
+ Error(this.BatchSendNotSupportedErr);
+ end;
+
+ procedure GetResponse(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.Processing.GetDocumentResponse(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ procedure GetApproval(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.Processing.GetDocumentApproval(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ procedure Cancel(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ Error(this.CancelNotSupportedErr);
+ end;
+
+ procedure ReceiveDocument(var TempBlob: Codeunit "Temp Blob"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ begin
+ this.Processing.ReceiveDocument(TempBlob, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ procedure GetDocumentCountInBatch(var TempBlob: Codeunit "Temp Blob"): Integer
+ begin
+ exit(this.Processing.GetDocumentCountInBatch(TempBlob));
+ end;
+
+ procedure GetIntegrationSetup(var SetupPage: Integer; var SetupTable: Integer)
+ begin
+ SetupPage := Page::ConnectionSetupCard;
+ SetupTable := Database::ConnectionSetup;
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/GetReadyStatusJob.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/GetReadyStatusJob.Codeunit.al
new file mode 100644
index 0000000000..64b10485a4
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/GetReadyStatusJob.Codeunit.al
@@ -0,0 +1,90 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Threading;
+using Microsoft.EServices.EDocument;
+
+codeunit 6384 GetReadyStatusJob
+{
+ TableNo = "Job Queue Entry";
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ EDocTelemetryGetResponseScopeStartLbl: Label 'E-Document Get Response: Start Scope', Locked = true;
+ EDocTelemetryGetResponseScopeEndLbl: Label 'E-Document Get Response: End Scope', Locked = true;
+
+ trigger OnRun()
+ var
+ JobHelperImpl: Codeunit JobHelperImpl;
+ BlankRecordId: RecordId;
+ begin
+ if not this.IsEDocumentStatusSent() then
+ exit;
+
+ this.ProcessSentDocuments();
+
+ if this.IsEDocumentStatusSent() then
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::GetReadyStatusJob, BlankRecordId, 300000);
+ end;
+
+ local procedure ProcessSentDocuments()
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ EDocumentService: Record "E-Document Service";
+ EDocument: Record "E-Document";
+ JobHelperImpl: Codeunit JobHelperImpl;
+ begin
+ EDocumentServiceStatus.SetLoadFields("E-Document Service Code", "E-Document Entry No");
+ EDocumentServiceStatus.SetRange(Status, EDocumentServiceStatus.Status::Sent);
+ if EDocumentServiceStatus.FindSet() then
+ repeat
+ JobHelperImpl.FetchEDocumentAndService(EDocument, EDocumentService, EDocumentServiceStatus);
+ this.HandleResponse(EDocument, EDocumentService);
+ until EDocumentServiceStatus.Next() = 0;
+ end;
+
+ local procedure HandleResponse(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service")
+ var
+ Processing: Codeunit Processing;
+ JobHelperImpl: Codeunit JobHelperImpl;
+ BlankRecordId: RecordId;
+ HttpResponseMessage: HttpResponseMessage;
+ HttpRequestMessage: HttpRequestMessage;
+ begin
+ if this.GetResponse(EDocument, HttpRequestMessage, HttpResponseMessage) then begin
+ Processing.InsertLogWithIntegration(EDocument, EDocumentService, Enum::"E-Document Service Status"::Approved, 0, HttpRequestMessage, HttpResponseMessage);
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::PatchSentJob, BlankRecordId, 300000);
+ end;
+ end;
+
+ local procedure GetResponse(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage) ReturnStatus: Boolean
+ var
+ Processing: Codeunit Processing;
+ Telemetry: Codeunit Telemetry;
+ TelemetryDimensions: Dictionary of [Text, Text];
+ begin
+ // Commit before create document with error handling
+ Commit();
+
+ Telemetry.LogMessage('', this.EDocTelemetryGetResponseScopeStartLbl, Verbosity::Normal, DataClassification::OrganizationIdentifiableInformation, TelemetryScope::All, TelemetryDimensions);
+
+ if Processing.GetDocumentSentResponse(EDocument, HttpRequestMessage, HttpResponseMessage) then
+ ReturnStatus := true;
+
+ Telemetry.LogMessage('', this.EDocTelemetryGetResponseScopeEndLbl, Verbosity::Normal, DataClassification::OrganizationIdentifiableInformation, TelemetryScope::All);
+ end;
+
+ local procedure IsEDocumentStatusSent(): Boolean
+ var
+ EdocumentServiceStatus: Record "E-Document Service Status";
+ begin
+ EdocumentServiceStatus.SetRange(Status, EdocumentServiceStatus.Status::Sent);
+ exit(not EdocumentServiceStatus.IsEmpty());
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/JobHelperImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/JobHelperImpl.Codeunit.al
new file mode 100644
index 0000000000..fac63284e2
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/JobHelperImpl.Codeunit.al
@@ -0,0 +1,83 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Threading;
+using Microsoft.EServices.EDocument;
+
+codeunit 6392 JobHelperImpl
+{
+ TableNo = "Job Queue Entry";
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ #region variables
+
+ var
+ Telemetry: Codeunit Telemetry;
+ JobQueueCategoryTok: Label 'EDocument', Locked = true, Comment = 'Max Length 10';
+ EDocumentJobTelemetryLbl: Label 'E-Document Background Job Scheduled', Locked = true;
+ JobQueueIdTxt: Label 'Job Queue Id', Locked = true;
+ CodeunitIsTxt: Label 'Codeunit Id', Locked = true;
+ RecordIdTxt: Label 'Record Id', Locked = true;
+ UserSessionIdTxt: Label 'User Session ID', Locked = true;
+ EarliestStartDateTimeTxt: Label 'Earliest Start Date/Time', Locked = true;
+
+ #endregion
+
+ #region public methods
+
+ procedure ScheduleEDocumentJob(CodeunitId: Integer; JobRecordId: RecordId; EarliestStartDateTime: Integer): Guid
+ var
+ JobQueueEntry: Record "Job Queue Entry";
+ TelemetryDimensions: Dictionary of [Text, Text];
+ begin
+ if this.IsJobQueueScheduled(CodeunitId) then
+ exit;
+
+ JobQueueEntry.Init();
+ JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit;
+ JobQueueEntry."Object ID to Run" := CodeunitId;
+ JobQueueEntry."Record ID to Process" := JobRecordId;
+ JobQueueEntry."User Session ID" := SessionId();
+ JobQueueEntry."Job Queue Category Code" := this.JobQueueCategoryTok;
+ JobQueueEntry."No. of Attempts to Run" := 0;
+ JobQueueEntry."Earliest Start Date/Time" := CurrentDateTime + EarliestStartDateTime;
+
+ TelemetryDimensions.Add(this.JobQueueIdTxt, JobQueueEntry.ID);
+ TelemetryDimensions.Add(this.CodeunitIsTxt, Format(CodeunitId));
+ TelemetryDimensions.Add(this.RecordIdTxt, Format(JobRecordId));
+ TelemetryDimensions.Add(this.UserSessionIdTxt, Format(JobQueueEntry."User Session ID"));
+ TelemetryDimensions.Add(this.EarliestStartDateTimeTxt, Format(JobQueueEntry."Earliest Start Date/Time"));
+ this.Telemetry.LogMessage('', this.EDocumentJobTelemetryLbl, Verbosity::Normal, DataClassification::OrganizationIdentifiableInformation, TelemetryScope::All, TelemetryDimensions);
+ Codeunit.Run(Codeunit::"Job Queue - Enqueue", JobQueueEntry);
+ exit(JobQueueEntry.ID);
+ end;
+
+ procedure FetchEDocumentAndService(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; EDocumentServiceStatus: Record "E-Document Service Status")
+ begin
+ EDocumentService.SetLoadFields("Service Integration", "Document Format");
+ EDocumentService.Get(EDocumentServiceStatus."E-Document Service Code");
+ EDocument.Get(EDocumentServiceStatus."E-Document Entry No");
+ end;
+
+ #endregion
+
+ #region local methods
+
+ local procedure IsJobQueueScheduled(CodeunitId: Integer): Boolean
+ var
+ JobQueueEntry: Record "Job Queue Entry";
+ begin
+ JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit);
+ JobQueueEntry.SetRange("Object ID to Run", CodeunitId);
+ JobQueueEntry.SetFilter(Status, '%1|%2', JobQueueEntry.Status::Ready, JobQueueEntry.Status::"In Process");
+ exit(not JobQueueEntry.IsEmpty());
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/PatchSentJob.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/PatchSentJob.Codeunit.al
new file mode 100644
index 0000000000..69e07127ba
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Jobs/PatchSentJob.Codeunit.al
@@ -0,0 +1,79 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Threading;
+using Microsoft.EServices.EDocument;
+using System.Security.Authentication;
+
+codeunit 6387 PatchSentJob
+{
+ TableNo = "Job Queue Entry";
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ trigger OnRun()
+ var
+ JobHelperImpl: Codeunit JobHelperImpl;
+ BlankRecordId: RecordId;
+ begin
+ if not this.IsEDocumentApproved() then
+ exit;
+
+ this.ProcessApprovedDocuments();
+
+ if this.IsEDocumentApproved() then
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::PatchSentJob, BlankRecordId, 300000);
+ end;
+
+ local procedure ProcessApprovedDocuments()
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ EDocumentService: Record "E-Document Service";
+ EDocumentIntegrationLog: Record "E-Document Integration Log";
+ EDocument: Record "E-Document";
+ APIRequests: Codeunit APIRequests;
+ Processing: Codeunit Processing;
+ JobHelperImpl: Codeunit JobHelperImpl;
+ HttpResponseMessage: HttpResponseMessage;
+ HttpRequestMessage: HttpRequestMessage;
+ begin
+ EDocumentServiceStatus.SetLoadFields("E-Document Service Code", "E-Document Entry No");
+ EDocumentServiceStatus.SetRange(Status, EDocumentServiceStatus.Status::Approved);
+ if EDocumentServiceStatus.FindSet() then
+ repeat
+ JobHelperImpl.FetchEDocumentAndService(EDocument, EDocumentService, EDocumentServiceStatus);
+
+ EDocumentIntegrationLog.Reset();
+ EDocumentIntegrationLog.SetRange("E-Doc. Entry No", EDocument."Entry No");
+ EDocumentIntegrationLog.SetRange("Response Status", 204);
+ EDocumentIntegrationLog.SetRange(Method, Format("Http Request Type"::PATCH));
+ if EDocumentIntegrationLog.IsEmpty() then
+ if APIRequests.PatchDocument(EDocument, HttpRequestMessage, HttpResponseMessage) then
+ Processing.InsertIntegrationLog(EDocument, EDocumentService, HttpRequestMessage, HttpResponseMessage);
+ until EDocumentServiceStatus.Next() = 0;
+ end;
+
+ local procedure IsEDocumentApproved(): Boolean
+ var
+ EdocumentServiceStatus: Record "E-Document Service Status";
+ EDocumentIntegrationLog: Record "E-Document Integration Log";
+ HasRecords: Boolean;
+ begin
+ EdocumentServiceStatus.SetLoadFields("E-Document Entry No");
+ EdocumentServiceStatus.SetRange(Status, EdocumentServiceStatus.Status::Approved);
+ if EdocumentServiceStatus.FindSet() then
+ repeat
+ EDocumentIntegrationLog.Reset();
+ EDocumentIntegrationLog.SetRange("E-Doc. Entry No", EdocumentServiceStatus."E-Document Entry No");
+ EDocumentIntegrationLog.SetRange("Response Status", 204);
+ EDocumentIntegrationLog.SetRange(Method, Format("Http Request Type"::PATCH));
+ HasRecords := EDocumentIntegrationLog.IsEmpty();
+ until (EdocumentServiceStatus.Next() = 0) or (HasRecords);
+
+ exit(HasRecords);
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Edit.PermissionSet.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Edit.PermissionSet.al
new file mode 100644
index 0000000000..a2d386e6b3
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Edit.PermissionSet.al
@@ -0,0 +1,15 @@
+// ------------------------------------------------------------------------------------------------
+// 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 Edit
+{
+ Access = Internal;
+ Assignable = false;
+ Caption = 'SignUp E-Doc. Connector - Edit', MaxLength = 30;
+ IncludedPermissionSets = Read;
+
+ Permissions = tabledata ConnectionSetup = imd;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Objects.PermissionSet.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Objects.PermissionSet.al
new file mode 100644
index 0000000000..9ade56ad84
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Objects.PermissionSet.al
@@ -0,0 +1,29 @@
+// ------------------------------------------------------------------------------------------------
+// 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 6380 Objects
+{
+ Access = Internal;
+ Assignable = false;
+ Caption = 'SignUp E-Doc. Connector - Obj.', MaxLength = 30;
+
+ Permissions = table ConnectionSetup = X,
+ page ConnectionSetupCard = X,
+ codeunit IntegrationImpl = X,
+ codeunit PatchSentJob = X,
+ codeunit JobHelperImpl = X,
+ codeunit GetReadyStatusJob = X,
+ codeunit APIRequests = X,
+ codeunit APIRequestsImpl = X,
+ codeunit Authentication = X,
+ codeunit AuthenticationImpl = X,
+ codeunit Connection = X,
+ codeunit ConnectionImpl = X,
+ codeunit HelpersImpl = X,
+ codeunit Processing = X,
+ codeunit ProcessingImpl = X;
+
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Read.PermissionSet.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Read.PermissionSet.al
new file mode 100644
index 0000000000..2129fcf1a1
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/Read.PermissionSet.al
@@ -0,0 +1,15 @@
+// ------------------------------------------------------------------------------------------------
+// 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 6381 Read
+{
+ Access = Internal;
+ Assignable = false;
+ Caption = 'SignUp E-Doc. Connector - Read', MaxLength = 30;
+ IncludedPermissionSets = Objects;
+
+ Permissions = tabledata ConnectionSetup = r;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDocConnectorEdit.PermissionSetExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDocConnectorEdit.PermissionSetExt.al
new file mode 100644
index 0000000000..94cbf00cde
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDocConnectorEdit.PermissionSetExt.al
@@ -0,0 +1,11 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector;
+
+permissionsetextension 6380 "SignUp EDoc. Connector - Edit" extends "EDocConnector - Edit"
+{
+ IncludedPermissionSets = Edit;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDocConnectorRead.PermissionSetExt.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDocConnectorRead.PermissionSetExt.al
new file mode 100644
index 0000000000..9c27703bce
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Permissions/SignUpEDocConnectorRead.PermissionSetExt.al
@@ -0,0 +1,11 @@
+// ------------------------------------------------------------------------------------------------
+// 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.EDocumentConnector;
+
+permissionsetextension 6381 "SignUp EDoc. Connector - Read" extends "EDocConnector - Read"
+{
+ IncludedPermissionSets = Read;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Processing.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Processing.Codeunit.al
new file mode 100644
index 0000000000..a19c17812c
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Processing.Codeunit.al
@@ -0,0 +1,120 @@
+// ------------------------------------------------------------------------------------------------
+// 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 6388 Processing
+{
+ Access = Internal;
+
+ #region variables
+ var
+ ProcessingImpl: Codeunit ProcessingImpl;
+
+ #endregion
+
+ #region public methods
+
+ ///
+ /// The method sends the E-Document to the API.
+ ///
+ /// E-Document record
+ /// TempBlob
+ /// IsAsync
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ procedure SendEDocument(var EDocument: Record "E-Document"; var TempBlob: Codeunit "Temp Blob"; var IsAsync: Boolean; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ begin
+ this.ProcessingImpl.SendEDocument(EDocument, TempBlob, IsAsync, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ ///
+ /// The method gets the E-Document response.
+ ///
+ /// E-Document record
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure GetDocumentResponse(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.ProcessingImpl.GetDocumentResponse(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets the E-Document sent response.
+ ///
+ /// E-Document record
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure GetDocumentSentResponse(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.ProcessingImpl.GetDocumentSentResponse(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method gets the E-Document approval.
+ ///
+ /// E-Document record
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ /// True - if completed successfully
+ procedure GetDocumentApproval(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ begin
+ exit(this.ProcessingImpl.GetDocumentApproval(EDocument, HttpRequestMessage, HttpResponseMessage));
+ end;
+
+ ///
+ /// The method receives the document.
+ ///
+ /// TempBlob
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ procedure ReceiveDocument(var TempBlob: Codeunit "Temp Blob"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ begin
+ this.ProcessingImpl.ReceiveDocument(TempBlob, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ ///
+ /// The method gets the document count in batch.
+ ///
+ /// TempBlob
+ /// True - if completed successfully
+ procedure GetDocumentCountInBatch(var TempBlob: Codeunit "Temp Blob"): Integer
+ begin
+ exit(this.ProcessingImpl.GetDocumentCountInBatch(TempBlob));
+ end;
+
+ ///
+ /// The method inserts the integration log.
+ ///
+ /// E-Document record
+ /// E-Document Service record
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ procedure InsertIntegrationLog(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; HttpRequestMessage: HttpRequestMessage; HttpResponseMessage: HttpResponseMessage)
+ begin
+ this.ProcessingImpl.InsertIntegrationLog(EDocument, EDocumentService, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ ///
+ /// The method inserts the log with integration.
+ ///
+ /// E-Document record
+ /// E-Document Service record
+ /// E-Document Service Status
+ /// E-Document Data Storage Entry No.
+ /// HttpRequestMessage
+ /// HttpResponseMessage
+ procedure InsertLogWithIntegration(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service";
+ EDocumentServiceStatus: Enum "E-Document Service Status"; EDocDataStorageEntryNo: Integer; HttpRequestMessage: HttpRequestMessage; HttpResponseMessage: HttpResponseMessage)
+ begin
+ this.ProcessingImpl.InsertLogWithIntegration(EDocument, EDocumentService, EDocumentServiceStatus, EDocDataStorageEntryNo, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/ProcessingImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/ProcessingImpl.Codeunit.al
new file mode 100644
index 0000000000..0412890010
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/ProcessingImpl.Codeunit.al
@@ -0,0 +1,580 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Telemetry;
+using System.Text;
+using System.Utilities;
+
+codeunit 6383 ProcessingImpl
+{
+ 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" = ri,
+ tabledata "E-Document Log" = ri;
+
+ #region variables
+ var
+ Connection: Codeunit Connection;
+ HelpersImpl: Codeunit HelpersImpl;
+ EDocumentHelper: Codeunit "E-Document Helper";
+ EDocumentLogHelper: Codeunit "E-Document Log Helper";
+ EDocumentErrorHelper: Codeunit "E-Document Error Helper";
+ GetApprovalCheckStatusErr: Label 'You cannot ask for approval with the E-Document in this current status %1. You can request for approval when E-document status is Sent or Pending Response.', Comment = '%1 - Status';
+ CouldNotRetrieveDocumentErr: Label 'Could not retrieve document with id: %1 from the service', Comment = '%1 - Document ID';
+ DocumentIdNotFoundErr: Label 'Document ID not found in response';
+ ExternalServiceTok: Label 'E-Document - SignUp', Locked = true;
+ InboxTxt: Label 'inbox', Locked = true;
+ InstanceIdTxt: Label 'instanceId', Locked = true;
+ PeppolInstanceIdTxt: Label 'peppolInstanceId', Locked = true;
+ StatusTxt: Label 'status', Locked = true;
+ SentTxt: Label 'sent', Locked = true;
+ ReadyTxt: Label 'ready', Locked = true;
+ FailedTxt: Label 'failed', Locked = true;
+ DescriptionTxt: Label 'description', Locked = true;
+ ReasonTxt: Label 'Reason: ', Locked = true;
+ NewTxt: Label 'new', Locked = true;
+ DocumentTxt: Label 'document', Locked = true;
+ StandardBusinessDocumentHeaderTxt: Label '', Locked = true;
+ InvoiceTxt: Label '', Locked = true;
+
+
+ #endregion
+
+ #region public methods
+
+ procedure SendEDocument(var EDocument: Record "E-Document"; var TempBlob: Codeunit "Temp Blob"; var IsAsync: Boolean; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ EDocumentService: Record "E-Document Service";
+ FeatureTelemetry: Codeunit "Feature Telemetry";
+ begin
+ IsAsync := true;
+
+ this.EDocumentHelper.GetEdocumentService(EDocument, EDocumentService);
+ 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."Document Id" = '' then
+ this.SendEDocument(EDocument, TempBlob, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ FeatureTelemetry.LogUptake('', this.ExternalServiceTok, Enum::"Feature Uptake Status"::Used);
+ end;
+
+ procedure GetDocumentResponse(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ ErrorDescription: Text;
+ begin
+ if not this.Connection.CheckDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage) then
+ exit;
+ exit(not this.DocumentHasErrorOrStillInProcessing(EDocument, HttpResponseMessage, ErrorDescription));
+ end;
+
+ procedure GetDocumentSentResponse(var EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ EDocumentService: Record "E-Document Service";
+ APIRequests: Codeunit APIRequests;
+ Status, StatusDescription : Text;
+ begin
+ this.EDocumentHelper.GetEdocumentService(EDocument, EdocumentService);
+ APIRequests.GetSentDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage);
+ if not this.ParseGetADocumentApprovalResponse(HttpResponseMessage.Content, Status, StatusDescription) then
+ exit;
+
+ case Status of
+ this.ReadyTxt:
+ exit(true);
+ this.FailedTxt:
+ begin
+ if StatusDescription <> '' then
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.ReasonTxt + StatusDescription);
+ this.InsertLogWithIntegration(EDocument, EDocumentService, Enum::"E-Document Service Status"::Rejected, 0, HttpRequestMessage, HttpResponseMessage);
+ exit;
+ end;
+ end;
+ end;
+
+ procedure GetDocumentApproval(EDocument: Record "E-Document"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage): Boolean
+ var
+ EDocumentService: Record "E-Document Service";
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ APIRequests: Codeunit APIRequests;
+ JobHelperImpl: Codeunit JobHelperImpl;
+ BlankRecordId: RecordId;
+ Status, StatusDescription : Text;
+ begin
+ this.EDocumentHelper.GetEdocumentService(EDocument, EdocumentService);
+ EDocumentServiceStatus.SetLoadFields(Status);
+ EDocumentServiceStatus.Get(EDocument."Entry No", EdocumentService.Code);
+ if not (EDocumentServiceStatus.Status in [EDocumentServiceStatus.Status::Sent, EDocumentServiceStatus.Status::"Pending Response"]) then
+ Error(this.GetApprovalCheckStatusErr, EDocumentServiceStatus.Status);
+
+ APIRequests.GetSentDocumentStatus(EDocument, HttpRequestMessage, HttpResponseMessage);
+ if not this.ParseGetADocumentApprovalResponse(HttpResponseMessage.Content, Status, StatusDescription) then
+ exit;
+
+ case Status of
+ this.ReadyTxt:
+ begin
+ if EDocumentServiceStatus.Status = EDocumentServiceStatus.Status::Approved then
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::PatchSentJob, BlankRecordId, 300000)
+ else
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::GetReadyStatusJob, BlankRecordId, 300000);
+ exit(true);
+ end;
+ this.FailedTxt:
+ begin
+ if StatusDescription <> '' then
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.ReasonTxt + StatusDescription);
+ this.InsertLogWithIntegration(EDocument, EDocumentService, Enum::"E-Document Service Status"::Rejected, 0, HttpRequestMessage, HttpResponseMessage);
+ exit;
+ end;
+ end;
+ end;
+
+ procedure ReceiveDocument(var TempBlob: Codeunit "Temp Blob"; var HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ var
+ ContentData: Text;
+ begin
+ if not this.Connection.GetReceivedDocuments(HttpRequestMessage, HttpResponseMessage) then
+ exit;
+
+ if not HttpResponseMessage.Content.ReadAs(ContentData) then
+ exit;
+
+ TempBlob.CreateOutStream(TextEncoding::UTF8).WriteText(ContentData);
+ end;
+
+ procedure GetDocumentCountInBatch(var TempBlob: Codeunit "Temp Blob"): Integer
+ var
+ ResponseTxt: Text;
+ begin
+ TempBlob.CreateInStream().ReadText(ResponseTxt);
+ exit(this.GetNumberOfReceivedDocuments(ResponseTxt));
+ end;
+
+ 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" = EDocumentService."Service Integration"::"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;
+
+ procedure InsertLogWithIntegration(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service";
+ EDocumentServiceStatus: Enum "E-Document Service Status"; EDocDataStorageEntryNo: Integer; HttpRequestMessage: HttpRequestMessage; HttpResponseMessage: HttpResponseMessage)
+ begin
+ this.InsertLog(EDocument, EDocumentService, EDocDataStorageEntryNo, EDocumentServiceStatus);
+
+ if (HttpRequestMessage.GetRequestUri() <> '') and (HttpResponseMessage.Headers.Keys().Count > 0) then
+ this.InsertIntegrationLog(EDocument, EDocumentService, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ #endregion
+
+ #region local methods
+
+ local procedure ParseGetADocumentApprovalResponse(HttpContentResponse: HttpContent; var Status: Text; var StatusDescription: Text): Boolean
+ var
+ JsonManagement: Codeunit "JSON Management";
+ Result: Text;
+ begin
+ Result := this.HelpersImpl.ParseJsonString(HttpContentResponse);
+ if Result = '' then
+ exit;
+
+ if not JsonManagement.InitializeFromString(Result) then
+ exit;
+
+ Status := this.GetStatus(JsonManagement);
+
+ if Status in [this.ReadyTxt, this.SentTxt] then
+ exit(true);
+
+ if Status = this.FailedTxt then begin
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.DescriptionTxt, StatusDescription);
+ exit(true);
+ end;
+
+ exit;
+ end;
+
+ local procedure InsertLog(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocDataStorageEntryNo: Integer; EDocumentServiceStatus: Enum "E-Document Service Status"): Integer
+ var
+ EDocumentLog: Record "E-Document Log";
+ begin
+ if EDocumentService.Code <> '' then
+ this.UpdateServiceStatus(EDocument, EDocumentService, EDocumentServiceStatus);
+
+ EDocumentLog.Validate("Document Type", EDocument."Document Type");
+ EDocumentLog.Validate("Document No.", EDocument."Document No.");
+ EDocumentLog.Validate("E-Doc. Entry No", EDocument."Entry No");
+ EDocumentLog.Validate(Status, EDocumentServiceStatus);
+ EDocumentLog.Validate("Service Integration", EDocumentService."Service Integration");
+ EDocumentLog.Validate("Service Code", EDocumentService.Code);
+ EDocumentLog.Validate("Document Format", EDocumentService."Document Format");
+ EDocumentLog.Validate("E-Doc. Data Storage Entry No.", EDocDataStorageEntryNo);
+ EDocumentLog.Insert();
+
+ exit(EDocumentLog."Entry No.");
+ end;
+
+ local procedure UpdateServiceStatus(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocumentStatus: Enum "E-Document Service Status")
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ Exists: Boolean;
+ begin
+ EDocument.Get(EDocument."Entry No");
+ Exists := EDocumentServiceStatus.Get(EDocument."Entry No", EDocumentService.Code);
+ EDocumentServiceStatus.Validate(Status, EDocumentStatus);
+ if Exists then
+ EDocumentServiceStatus.Modify()
+ else begin
+ EDocumentServiceStatus.Validate("E-Document Entry No", EDocument."Entry No");
+ EDocumentServiceStatus.Validate("E-Document Service Code", EDocumentService.Code);
+ EDocumentServiceStatus.Validate(Status, EDocumentStatus);
+ EDocumentServiceStatus.Insert();
+ end;
+
+ this.UpdateEDocumentStatus(EDocument);
+ end;
+
+ local procedure SendEDocument(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"; HttpRequestMessage: HttpRequestMessage; HttpResponseMessage: HttpResponseMessage);
+ begin
+ this.Connection.SendFilePostRequest(TempBlob, EDocument, HttpRequestMessage, HttpResponseMessage);
+ this.SetEDocumentFileID(EDocument."Entry No", this.ParseSendFileResponse(HttpResponseMessage.Content));
+ end;
+
+ local procedure ParseReceivedDocument(InputTxt: Text; Index: Integer; var DocumentId: Text): Boolean
+ var
+ JsonManagement: Codeunit "JSON Management";
+ IncrementalTable, Value : Text;
+ begin
+ if not JsonManagement.InitializeFromString(InputTxt) then
+ exit;
+
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.InboxTxt, Value);
+ JsonManagement.InitializeCollection(Value);
+
+ if Index = 0 then
+ Index := 1;
+
+ if Index > JsonManagement.GetCollectionCount() then
+ exit;
+
+ JsonManagement.GetObjectFromCollectionByIndex(IncrementalTable, Index - 1);
+
+ Clear(JsonManagement);
+ JsonManagement.InitializeObject(IncrementalTable);
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.InstanceIdTxt, DocumentId);
+
+ exit(true);
+ 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.HelpersImpl.ParseJsonString(HttpContentResponse);
+ if Result = '' then
+ exit;
+
+ if not JsonManagement.InitializeFromString(Result) then
+ exit;
+
+ JsonManagement.GetStringPropertyValueByName(this.PeppolInstanceIdTxt, 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."Document Id" := CopyStr(FileId, 1, MaxStrLen(EDocument."Document Id"));
+ EDocument.Modify();
+ end;
+
+ local procedure DocumentHasErrorOrStillInProcessing(EDocument: Record "E-Document"; HttpResponseMessage: HttpResponseMessage; var ErrorDescription: Text): Boolean
+ var
+ EDocumentService: Record "E-Document Service";
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ JsonManagement: Codeunit "JSON Management";
+ JobHelperImpl: Codeunit JobHelperImpl;
+ BlankRecordId: RecordId;
+ Result, Status : Text;
+ begin
+ Result := this.HelpersImpl.ParseJsonString(HttpResponseMessage.Content);
+ if Result = '' then
+ exit(true);
+
+ if not JsonManagement.InitializeFromString(Result) then
+ exit(true);
+
+ Status := this.GetStatus(JsonManagement);
+
+ if Status in [this.SentTxt] then begin
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::GetReadyStatusJob, BlankRecordId, 120000);
+ exit;
+ end;
+
+ if Status in [this.ReadyTxt] then begin
+ this.EDocumentHelper.GetEdocumentService(EDocument, EDocumentService);
+ EDocumentServiceStatus.SetLoadFields(Status);
+ EDocumentServiceStatus.Get(EDocument."Entry No", EdocumentService.Code);
+ if EDocumentServiceStatus.Status = EDocumentServiceStatus.Status::Approved then
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::PatchSentJob, BlankRecordId, 180000)
+ else
+ JobHelperImpl.ScheduleEDocumentJob(Codeunit::GetReadyStatusJob, BlankRecordId, 120000);
+ exit;
+ end;
+
+ if Status = this.FailedTxt then begin
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.DescriptionTxt, ErrorDescription);
+ exit;
+ end;
+
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.DescriptionTxt, ErrorDescription);
+ exit(true);
+ end;
+
+ local procedure GetStatus(var JsonManagement: Codeunit "Json Management") Status: Text
+ begin
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.StatusTxt, Status);
+ Status := Status.ToLower();
+ end;
+
+ local procedure UpdateEDocumentStatus(var EDocument: Record "E-Document")
+ var
+ IsHandled: Boolean;
+ begin
+ if IsHandled then
+ exit;
+
+ if this.EDocumentHasErrors(EDocument) then
+ exit;
+
+ this.SetDocumentStatus(EDocument);
+ end;
+
+ local procedure EDocumentHasErrors(var EDocument: Record "E-Document"): Boolean
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ begin
+ EDocumentServiceStatus.SetRange("E-Document Entry No", EDocument."Entry No");
+ EDocumentServiceStatus.SetFilter(Status, '%1|%2|%3|%4|%5',
+ EDocumentServiceStatus.Status::"Sending Error",
+ EDocumentServiceStatus.Status::"Export Error",
+ EDocumentServiceStatus.Status::"Cancel Error",
+ EDocumentServiceStatus.Status::"Imported Document Processing Error",
+ EDocumentServiceStatus.Status::Rejected);
+
+ if EDocumentServiceStatus.IsEmpty() then
+ exit;
+
+ EDocument.Validate(Status, EDocument.Status::Error);
+ EDocument.Modify();
+ 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 SetDocumentStatus(var EDocument: Record "E-Document")
+ var
+ EDocumentServiceStatus: Record "E-Document Service Status";
+ EDocServiceCount: Integer;
+ begin
+ EDocumentServiceStatus.SetRange("E-Document Entry No", EDocument."Entry No");
+ EDocServiceCount := EDocumentServiceStatus.Count;
+
+ EDocumentServiceStatus.SetFilter(Status, '%1|%2|%3|%4|%5',
+ EDocumentServiceStatus.Status::Exported,
+ EDocumentServiceStatus.Status::"Imported Document Created",
+ EDocumentServiceStatus.Status::"Journal Line Created",
+ EDocumentServiceStatus.Status::Approved,
+ EDocumentServiceStatus.Status::Canceled);
+ if EDocumentServiceStatus.Count() = EDocServiceCount then
+ EDocument.Status := EDocument.Status::Processed
+ else
+ EDocument.Status := EDocument.Status::"In Progress";
+
+ EDocument.Modify();
+ 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;
+ ParsePosition: Integer;
+ begin
+ if not JsonManagement.InitializeFromString(InputText) then
+ exit;
+
+ JsonManagement.GetArrayPropertyValueAsStringByName(this.DocumentTxt, Value);
+ InputText := Base64Convert.FromBase64(Value);
+ ParsePosition := StrPos(InputText, this.StandardBusinessDocumentHeaderTxt);
+ if ParsePosition > 0 then begin
+ InputText := CopyStr(InputText, parsePosition, StrLen(InputText));
+ ParsePosition := StrPos(InputText, this.InvoiceTxt);
+ InputText := CopyStr(InputText, parsePosition, StrLen(InputText));
+ ParsePosition := StrPos(InputText, this.StandardBusinessDocumentTxt);
+ InputText := CopyStr(InputText, 1, parsePosition - 1);
+ end;
+
+ exit(true);
+ end;
+
+ #endregion
+
+ #region event subscribers
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Integration Management", OnGetEDocumentApprovalReturnsFalse, '', false, false)]
+ local procedure OnGetEDocumentApprovalReturnsFalse(EDocuments: Record "E-Document"; EDocumentService: Record "E-Document Service"; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage; var IsHandled: Boolean)
+ var
+ HttpContent: HttpContent;
+ Status, StatusDescription : Text;
+ begin
+ HttpContent := HttpResponse.Content;
+ if not this.ParseGetADocumentApprovalResponse(HttpContent, Status, StatusDescription) then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Import", OnAfterInsertImportedEdocument, '', false, false)]
+ local procedure OnAfterInsertEdocument(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; var TempBlob: Codeunit "Temp Blob"; EDocCount: Integer; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage)
+ var
+ HttpRequestMessage: HttpRequestMessage;
+ HttpResponseMessage: HttpResponseMessage;
+ ContentData, DocumentId : Text;
+ begin
+ if EDocumentService."Service Integration" <> EDocumentService."Service Integration"::"ExFlow E-Invoicing" then
+ exit;
+
+ if not HttpResponse.Content.ReadAs(ContentData) then
+ exit;
+
+ ContentData := this.LeaveJustNewLine(ContentData);
+
+ if not this.ParseReceivedDocument(ContentData, EDocument."Index In Batch", DocumentId) then begin
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.DocumentIdNotFoundErr);
+ exit;
+ end;
+
+ this.Connection.GetTargetDocumentRequest(DocumentId, HttpRequestMessage, HttpResponseMessage);
+ this.EDocumentLogHelper.InsertIntegrationLog(EDocument, EDocumentService, HttpRequestMessage, 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));
+
+ Clear(TempBlob);
+ TempBlob.CreateOutStream(TextEncoding::UTF8).WriteText(ContentData);
+ EDocument."Document Id" := CopyStr(DocumentId, 1, MaxStrLen(EDocument."Document Id"));
+ this.EDocumentLogHelper.InsertLog(EDocument, EDocumentService, TempBlob, "E-Document Service Status"::Imported);
+ this.Connection.RemoveDocumentFromReceived(EDocument, HttpRequestMessage, HttpResponseMessage);
+ this.EDocumentLogHelper.InsertIntegrationLog(EDocument, EDocumentService, HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ #endregion
+
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/ConnectionSetup.Table.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/ConnectionSetup.Table.al
new file mode 100644
index 0000000000..7c07b7eec2
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/ConnectionSetup.Table.al
@@ -0,0 +1,92 @@
+// ------------------------------------------------------------------------------------------------
+// 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 ConnectionSetup
+{
+ Access = Internal;
+ DataPerCompany = false;
+ DataClassification = CustomerContent;
+
+ fields
+ {
+ field(1; PK; Code[10])
+ {
+ Caption = 'PK', Locked = true;
+ ToolTip = 'PK', Locked = true;
+ }
+ field(4; "Authentication URL"; Text[250])
+ {
+ Caption = 'Authentication URL';
+ ToolTip = 'Specifies the URL to connect Microsoft Entra.';
+ }
+ field(9; "Company Id"; Text[100])
+ {
+ Caption = 'Company ID';
+ ToolTip = 'Specifies the company ID.';
+ }
+ field(10; "Client ID"; Guid)
+ {
+ Caption = 'Client ID';
+ ToolTip = 'Specifies the client ID.';
+ }
+ field(11; "Client Secret"; Guid)
+ {
+ Caption = 'Client Secret';
+ ToolTip = 'Specifies the client secret.';
+ }
+ field(12; "Environment Type"; Enum EnvironmentType)
+ {
+ Caption = 'Environment Type';
+ ToolTip = 'Specifies the environment type.';
+ }
+ field(13; ServiceURL; Text[250])
+ {
+ Caption = 'Service URL';
+ ToolTip = 'Specifies the service URL.';
+ }
+ field(20; "Root App ID"; Guid)
+ {
+ Caption = 'Root App ID';
+ ToolTip = 'Specifies the root app ID.';
+ }
+ field(21; "Root Secret"; Guid)
+ {
+ Caption = 'Root App Secret';
+ ToolTip = 'Specifies the root application secret.';
+ }
+ field(22; "Root Tenant"; Guid)
+ {
+ Caption = 'Root App Tenant';
+ ToolTip = 'Specifies the root application tenant.';
+ }
+ field(23; "Root Market URL"; Guid)
+ {
+ Caption = 'Root Market URL';
+ ToolTip = 'Specifies the root market URL.';
+ }
+ field(24; "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/ConnectionSetupCard.Page.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/ConnectionSetupCard.Page.al
new file mode 100644
index 0000000000..2d804b4c11
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/ConnectionSetupCard.Page.al
@@ -0,0 +1,213 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Environment;
+
+page 6380 ConnectionSetupCard
+{
+ AdditionalSearchTerms = 'SignUp,electronic document,e-invoice,e-document,external,connection,connector';
+ PageType = Card;
+ SourceTable = ConnectionSetup;
+ 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;
+ Visible = not this.IsSaaSInfrastructure;
+ ShowMandatory = true;
+
+ trigger OnValidate()
+ begin
+ this.Authentication.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;
+ Visible = not this.IsSaaSInfrastructure;
+ ShowMandatory = true;
+
+ trigger OnValidate()
+ begin
+ this.SaveSecret(Rec."Client Secret", this.ClientSecret)
+ end;
+ }
+ field(ClientTenant; this.ClientTenant)
+ {
+ Caption = 'Client Tenant ID';
+ ToolTip = 'Specifies the client tenant id token.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ Visible = not this.IsSaaSInfrastructure;
+
+ trigger OnValidate()
+ begin
+ this.Authentication.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 = not this.IsSaaSInfrastructure;
+
+ trigger OnValidate()
+ begin
+ this.Authentication.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 = not this.IsSaaSInfrastructure;
+
+ 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 = not this.IsSaaSInfrastructure;
+
+ trigger OnValidate()
+ begin
+ this.Authentication.StorageSet(Rec."Root Tenant", this.RootTenant);
+ end;
+ }
+ field(RootUrl; this.RootUrl)
+ {
+ Caption = 'Root Url';
+ ToolTip = 'Specifies the root url token.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ Visible = not this.IsSaaSInfrastructure;
+
+ trigger OnValidate()
+ begin
+ this.Authentication.StorageSet(Rec."Root Market URL", this.RootUrl);
+ end;
+ }
+ field("Authentication URL"; Rec."Authentication URL")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field(ServiceURL; Rec.ServiceURL)
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field("Company Id"; Rec."Company Id")
+ {
+ ApplicationArea = Basic, Suite;
+ ShowMandatory = true;
+ }
+ 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;
+ Visible = this.IsSaaSInfrastructure;
+ ToolTip = 'Create client credentials and open the onboarding process in a web browser.';
+
+ trigger OnAction()
+ begin
+ this.Authentication.CreateClientCredentials();
+ CurrPage.Update();
+ this.SetPageVariables();
+ Hyperlink(this.Authentication.GetRootOnboardingUrl());
+ this.FeatureTelemetry.LogUptake('', this.ExternalServiceTok, Enum::"Feature Uptake Status"::"Set up");
+ end;
+ }
+ }
+ }
+
+ trigger OnOpenPage()
+ var
+ EnvironmentInformation: Codeunit "Environment Information";
+ begin
+ this.IsSaaSInfrastructure := EnvironmentInformation.IsSaaSInfrastructure();
+ this.Authentication.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;
+ if not IsNullGuid(Rec."Root Market URL") then
+ this.RootUrl := this.MaskTxt;
+ end;
+
+ [NonDebuggable]
+ local procedure SaveSecret(var TokenKey: Guid; Value: SecretText)
+ begin
+ this.Authentication.StorageSet(TokenKey, Value);
+ end;
+
+ var
+ Authentication: Codeunit Authentication;
+ FeatureTelemetry: Codeunit "Feature Telemetry";
+ [NonDebuggable]
+ ClientID, ClientSecret, ClientTenant, ClientUrl, RootID, RootSecret, RootTenant, RootUrl : Text;
+ IsSaaSInfrastructure: 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/EnvironmentType.Enum.al b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/EnvironmentType.Enum.al
new file mode 100644
index 0000000000..a9ff9166b2
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/SignUp/app/src/Setup/EnvironmentType.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 EnvironmentType
+{
+ 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