diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/.resources/SchemeIds.txt b/Apps/W1/EDocumentConnectors/Tietoevry/app/.resources/SchemeIds.txt
new file mode 100644
index 0000000000..1510d0197a
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/.resources/SchemeIds.txt
@@ -0,0 +1 @@
+{"schemeids": "0002 0007 0009 0037 0060 0088 0096 0097 0106 0130 0135 0142 0147 0151 0170 0183 0184 0188 0190 0191 0192 0193 0194 0195 0196 0198 0199 0200 0201 0202 0203 0204 0205 0208 0209 0210 0211 0212 0213 0215 0216 0217 0218 0219 0220 0221 0225 0230 9901 9910 9913 9914 9915 9918 9919 9920 9922 9923 9924 9925 9926 9927 9928 9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 9943 9944 9945 9946 9947 9948 9949 9950 9951 9952 9953 9957 9959 AN AQ AS AU EM" }
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/ExtensionLogo.png b/Apps/W1/EDocumentConnectors/Tietoevry/app/ExtensionLogo.png
new file mode 100644
index 0000000000..4d2c9a626c
Binary files /dev/null and b/Apps/W1/EDocumentConnectors/Tietoevry/app/ExtensionLogo.png differ
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/app.json b/Apps/W1/EDocumentConnectors/Tietoevry/app/app.json
new file mode 100644
index 0000000000..cc67dba438
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/app.json
@@ -0,0 +1,52 @@
+{
+ "id": "3b3c094c-ae7f-43f2-9246-22111b688d2e",
+ "name": "E-Document Connector - Tietoevry",
+ "publisher": "Microsoft",
+ "brief": "E-Document Connector - Tietoevry",
+ "description": "E-Document Connector - Tietoevry",
+ "version": "26.0.0.0",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2204541",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "logo": "ExtensionLogo.png",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2206603",
+ "dependencies": [
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b",
+ "name": "E-Document Core",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ }
+ ],
+ "internalsVisibleTo": [
+ {
+ "id": "985549ff-145a-4b5c-acd1-db6d425f6cbc",
+ "name": "E-Document Connector - Tietoevry Tests",
+ "publisher": "Microsoft"
+ }
+ ],
+ "screenshots": [
+
+ ],
+ "platform": "26.0.0.0",
+ "idRanges": [
+ {
+ "from": 6390,
+ "to": 6399
+ }
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "allowDownloadingSource": true,
+ "includeSourceInSymbolFile": true
+ },
+ "application": "26.0.0.0",
+ "target": "OnPrem",
+ "features": [
+ "TranslationFile"
+ ],
+ "resourceFolders": [
+ ".resources"
+ ]
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Authenticator.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Authenticator.Codeunit.al
new file mode 100644
index 0000000000..31266f6c8d
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Authenticator.Codeunit.al
@@ -0,0 +1,139 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using System.Security.Authentication;
+codeunit 6394 "Authenticator"
+{
+ Access = Internal;
+ Permissions = tabledata "OAuth 2.0 Setup" = im,
+ tabledata "Connection Setup" = rim;
+
+ procedure CreateConnectionSetupRecord()
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ if not ConnectionSetup.Get() then begin
+ ConnectionSetup."Authentication URL" := this.AuthURLTxt;
+ ConnectionSetup."API URL" := this.APIURLTxt;
+ ConnectionSetup."Sandbox Authentication URL" := this.SandboxAuthURLTxt;
+ ConnectionSetup."Sandbox API URL" := this.SandboxAPIURLTxt;
+ ConnectionSetup."Send Mode" := ConnectionSetup."Send Mode"::Test; //Sandbox
+ ConnectionSetup.Insert();
+ end;
+ end;
+
+ procedure SetClientId(var ClientIdKey: Guid; ClientID: SecretText)
+ begin
+ this.SetIsolatedStorageValue(ClientIdKey, ClientID, DataScope::Company);
+ end;
+
+ procedure SetClientSecret(var ClienSecretKey: Guid; ClientSecret: SecretText)
+ begin
+ this.SetIsolatedStorageValue(ClienSecretKey, ClientSecret, DataScope::Company);
+ end;
+
+ procedure GetAccessToken() Token: SecretText
+ var
+ ConnectionSetup: Record "Connection Setup";
+ Requests: Codeunit Requests;
+ ExpiresIn: Integer;
+ ClientId, ClientSecret, TokenTxt, Response : SecretText;
+ TokenKey: Guid;
+ begin
+ ConnectionSetup.Get();
+
+ // Reuse token if it lives longer than 1 min in future
+ if (ConnectionSetup."Token Expiry" > CurrentDateTime() + 60 * 1000) and (not IsNullGuid(ConnectionSetup."Token - Key")) then
+ if this.GetTokenValue(ConnectionSetup."Token - Key", Token, DataScope::Company) then
+ exit;
+
+ if not this.GetTokenValue(ConnectionSetup."Client ID - Key", ClientId, DataScope::Company) then
+ Error(this.TietoevryClientIdErr, ConnectionSetup.TableCaption);
+
+ if not this.GetTokenValue(ConnectionSetup."Client Secret - Key", ClientSecret, DataScope::Company) then
+ Error(this.TietoevryClientSecretErr, ConnectionSetup.TableCaption);
+
+ Requests.Init();
+ Requests.CreateAuthenticateRequest(ClientId, ClientSecret);
+ this.ExecuteResponse(Requests, Response);
+ if not this.ParseResponse(Response, TokenTxt, ExpiresIn) then
+ Error(this.TietoevryParseTokenErr);
+
+ // Save token for reuse
+ this.SetIsolatedStorageValue(TokenKey, TokenTxt, DataScope::Company);
+ // Read again as we want fresh record to modify
+ ConnectionSetup.Get();
+ ConnectionSetup."Token - Key" := TokenKey;
+ ConnectionSetup."Token Expiry" := CurrentDateTime() + ExpiresIn * 1000;
+ ConnectionSetup.Modify();
+ Commit();
+ exit(TokenTxt);
+ end;
+
+ [NonDebuggable]
+ local procedure ExecuteResponse(var Request: Codeunit Requests; var Response: SecretText)
+ var
+ HttpExecutor: Codeunit "Http Executor";
+ begin
+ Response := HttpExecutor.ExecuteHttpRequest(Request);
+ end;
+
+ [NonDebuggable]
+ [TryFunction]
+ local procedure ParseResponse(Response: SecretText; var Token: SecretText; var ExpiresIn: Integer)
+ var
+ ResponseJson: JsonObject;
+ TokenJson, ExpiryJson : JsonToken;
+ begin
+ ResponseJson.ReadFrom(Response.Unwrap());
+ ResponseJson.Get('access_token', TokenJson);
+ Token := TokenJson.AsValue().AsText();
+ ResponseJson.Get('expires_in', ExpiryJson);
+ ExpiresIn := ExpiryJson.AsValue().AsInteger();
+ end;
+
+ procedure IsClientCredsSet(var ClientId: Text; var ClientSecret: Text): Boolean
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ ConnectionSetup.Get();
+
+ if this.HasToken(ConnectionSetup."Client ID - Key", DataScope::Company) then
+ ClientId := '*';
+ if this.HasToken(ConnectionSetup."Client Secret - Key", DataScope::Company) then
+ ClientSecret := '*';
+ end;
+
+ procedure SetIsolatedStorageValue(var ValueKey: Guid; Value: SecretText; TokenDataScope: DataScope)
+ begin
+ if IsNullGuid(ValueKey) then
+ ValueKey := CreateGuid();
+
+ IsolatedStorage.Set(ValueKey, Value, TokenDataScope);
+ end;
+
+ local procedure GetTokenValue(TokenKey: Text; var TokenValueAsSecret: SecretText; TokenDataScope: DataScope): Boolean
+ begin
+ if not this.HasToken(TokenKey, TokenDataScope) then
+ exit(false);
+
+ exit(IsolatedStorage.Get(TokenKey, TokenDataScope, TokenValueAsSecret));
+ end;
+
+ local procedure HasToken(TokenKey: Text; TokenDataScope: DataScope): Boolean
+ begin
+ exit(IsolatedStorage.Contains(TokenKey, TokenDataScope));
+ end;
+
+ var
+ AuthURLTxt: Label 'https://auth.infotorg.no/auth/realms/fms-realm/protocol/openid-connect', Locked = true;
+ APIURLTxt: Label 'https://accesspoint-api.dataplatfor.ms', Locked = true;
+ SandboxAuthURLTxt: Label 'https://auth-qa.infotorg.no/auth/realms/fms-realm/protocol/openid-connect', Locked = true;
+ SandboxAPIURLTxt: Label 'https://accesspoint-api.qa.dataplatfor.ms', Locked = true;
+ TietoevryClientIdErr: Label 'Tietoevry Client Id is not set in %1', Comment = '%1 - Client id';
+ TietoevryClientSecretErr: Label 'Tietoevry Client Secret is not set in %1', Comment = '%1 - Client secret';
+ TietoevryParseTokenErr: Label 'Failed to parse response for Tietoevry Access token request';
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/ConnectionSetup.Table.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/ConnectionSetup.Table.al
new file mode 100644
index 0000000000..1940e71f1e
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/ConnectionSetup.Table.al
@@ -0,0 +1,97 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+table 6392 "Connection Setup"
+{
+ Access = Internal;
+ DataClassification = SystemMetadata;
+
+ fields
+ {
+ field(1; Id; Code[10])
+ {
+ Caption = 'Id';
+ ToolTip = 'Specifies the connection setup id.';
+ }
+ field(2; "Client Id - Key"; Guid)
+ {
+ Caption = 'Client Id';
+ ToolTip = 'Specifies the client id key.';
+ DataClassification = EndUserIdentifiableInformation;
+ }
+ field(3; "Client Secret - Key"; Guid)
+ {
+ Caption = 'Client Secret';
+ ToolTip = 'Specifies the client secret key.';
+ DataClassification = EndUserIdentifiableInformation;
+ }
+ field(4; "Authentication URL"; Text[250])
+ {
+ Caption = 'Authentication URL';
+ ToolTip = 'Specifies the URL to connect to Tietoevry.';
+ Editable = false;
+ }
+ field(5; "API URL"; Text[250])
+ {
+ Caption = 'API URL';
+ ToolTip = 'Specifies the URL to connect to Tietoevry''s api.';
+ Editable = false;
+ }
+ field(6; "Sandbox Authentication URL"; Text[250])
+ {
+ Caption = 'Sandbox Authentication URL';
+ ToolTip = 'Specifies the URL to connect to Tietoevry''s sandbox.';
+ Editable = false;
+ }
+ field(7; "Sandbox API URL"; Text[250])
+ {
+ Caption = 'Sandbox Authentication URL';
+ ToolTip = 'Specifies the URL to connect to Tietoevry sandbox api.';
+ Editable = false;
+ }
+ field(8; "Token - Key"; Guid)
+ {
+ Caption = 'Token';
+ ToolTip = 'Specifies the token key.';
+ }
+ field(9; "Token Expiry"; DateTime)
+ {
+ Caption = 'Token Expiry';
+ ToolTip = 'Specifies the token expiry date.';
+ }
+ field(10; "Company Id"; Text[250])
+ {
+ Caption = 'Company ID';
+ DataClassification = EndUserIdentifiableInformation;
+ ToolTip = 'Specifies the company ID.';
+
+ trigger OnValidate()
+ var
+ TietoevryProcessing: Codeunit Processing;
+ begin
+ if not TietoevryProcessing.IsValidSchemeId(Rec."Company Id") then
+ Error(this.NotAValidCompanyIdErr, Rec."Company Id", Rec.FieldCaption("Company Id"));
+ end;
+ }
+ field(13; "Send Mode"; Enum "Send Mode")
+ {
+ Caption = 'Send Mode';
+ ToolTip = 'Specifies the send mode.';
+ }
+ }
+
+ keys
+ {
+ key(Key1; Id)
+ {
+ Clustered = true;
+ }
+ }
+
+ var
+ NotAValidCompanyIdErr: Label '%1 is not a valid %2', Comment = '%1 = Company Id, %2 = fieldname';
+
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/ConnectionSetupCard.Page.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/ConnectionSetupCard.Page.al
new file mode 100644
index 0000000000..1937063f74
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/ConnectionSetupCard.Page.al
@@ -0,0 +1,132 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using System.Telemetry;
+
+page 6392 "Connection Setup Card"
+{
+ PageType = Card;
+ SourceTable = "Connection Setup";
+ ApplicationArea = Basic, Suite;
+ UsageCategory = None;
+ Caption = 'Tietoevry Connection Setup';
+ Permissions = tabledata "Connection Setup" = rm;
+ DeleteAllowed = false;
+ InsertAllowed = false;
+
+ layout
+ {
+ area(Content)
+ {
+ group(General)
+ {
+ field(ClientID; this.ClientID)
+ {
+ Caption = 'Client ID';
+ ToolTip = 'Specifies the client ID.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ ShowMandatory = true;
+
+ trigger OnValidate()
+ begin
+ this.TietoevryAuth.SetClientId(Rec."Client ID - Key", this.ClientID);
+ end;
+ }
+ field(ClientSecret; this.ClientSecret)
+ {
+ Caption = 'Client Secret';
+ ToolTip = 'Specifies the client secret.';
+ ApplicationArea = Basic, Suite;
+ ExtendedDatatype = Masked;
+ ShowMandatory = true;
+
+ trigger OnValidate()
+ begin
+ this.TietoevryAuth.SetClientSecret(Rec."Client Secret - Key", this.ClientSecret);
+ end;
+ }
+ field("Authentication URL"; Rec."Authentication URL")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field("API URL"; Rec."API URL")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field("Sandbox Authentication URL"; Rec."Sandbox Authentication URL")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field("Sandbox API URL"; Rec."Sandbox API URL")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field("Company Id"; Rec."Company Id")
+ {
+ ApplicationArea = Basic, Suite;
+ }
+ field("Send Mode"; Rec."Send Mode")
+ {
+ ApplicationArea = Basic, Suite;
+ ShowMandatory = true;
+ }
+ }
+ }
+ }
+ actions
+ {
+ area(processing)
+ {
+ action(Authenticate)
+ {
+ ApplicationArea = Basic, Suite;
+ Caption = 'Authenticate';
+ Image = Setup;
+ Promoted = true;
+ PromotedCategory = Process;
+ PromotedOnly = true;
+ ToolTip = 'Verify the Authentication Details';
+
+ trigger OnAction()
+ var
+ TietoevryAuth: Codeunit Authenticator;
+ [NonDebuggable]
+ Token: SecretText;
+ begin
+ Token := TietoevryAuth.GetAccessToken();
+ if not Token.IsEmpty() then
+ Message(this.TietoevryAuthSuccessMsg)
+ else
+ Error(this.TietoevryAuthFailedErr);
+ end;
+ }
+ }
+ }
+ trigger OnOpenPage()
+ var
+ FeatureTelemetry: Codeunit "Feature Telemetry";
+ begin
+ FeatureTelemetry.LogUptake('', this.TietoevryProcessing.GetTietoevryTok(), Enum::"Feature Uptake Status"::Discovered);
+ this.TietoevryAuth.CreateConnectionSetupRecord();
+ this.TietoevryAuth.IsClientCredsSet(this.ClientID, this.ClientSecret);
+ end;
+
+ trigger OnClosePage()
+ begin
+ Rec.TestField("Company Id");
+ Rec.TestField("Send Mode");
+ end;
+
+ var
+
+ TietoevryAuth: Codeunit "Authenticator";
+ TietoevryProcessing: Codeunit Processing;
+ TietoevryAuthSuccessMsg: Label 'Authenticated successfully';
+ TietoevryAuthFailedErr: Label 'Authentication failed';
+ [NonDebuggable]
+ ClientID, ClientSecret : Text;
+}
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Events.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Events.Codeunit.al
new file mode 100644
index 0000000000..25e9112199
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Events.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.Tietoevry;
+
+using Microsoft.Sales.Peppol;
+using Microsoft.Sales.Document;
+using Microsoft.Foundation.Company;
+using Microsoft.Sales.Customer;
+using System.Automation;
+using Microsoft.Foundation.Reporting;
+using Microsoft.eServices.EDocument;
+using Microsoft.eServices.EDocument.Service.Participant;
+codeunit 6395 Events
+{
+ SingleInstance = true;
+ EventSubscriberInstance = StaticAutomatic;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Validation", OnCheckSalesDocumentOnBeforeCheckCompanyVATRegNo, '', false, false)]
+ local procedure "PEPPOL Validation_OnCheckSalesDocumentOnBeforeCheckCompanyVATRegNo"(SalesHeader: Record "Sales Header"; CompanyInformation: Record "Company Information"; var IsHandled: Boolean)
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ if not ConnectionSetup.Get() then
+ exit;
+
+ IsHandled := true;
+ if CompanyInformation."VAT Registration No." = '' then
+ Error(this.MissingCompInfVATRegNoErr, CompanyInformation.TableCaption());
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Validation", OnCheckSalesDocumentOnBeforeCheckCustomerVATRegNo, '', false, false)]
+ local procedure "PEPPOL Validation_OnCheckSalesDocumentOnBeforeCheckCustomerVATRegNo"(SalesHeader: Record "Sales Header"; Customer: Record Customer; var IsHandled: Boolean)
+ var
+ ServiceParticipant: Record "Service Participant";
+ EDocumentService: Record "E-Document Service";
+ ConnectionSetup: Record "Connection Setup";
+ DocumentSendingProfile: Record "Document Sending Profile";
+ begin
+ if not ConnectionSetup.Get() then
+ exit;
+
+ DocumentSendingProfile.GetDefaultForCustomer(Customer."No.", DocumentSendingProfile);
+ if DocumentSendingProfile."Electronic Document" <> DocumentSendingProfile."Electronic Document"::"Extended E-Document Service Flow" then
+ exit;
+
+ if not this.GetServicesInWorkflow(EDocumentService, DocumentSendingProfile."Electronic Service Flow") then
+ exit;
+
+ EDocumentService.SetRange("Service Integration V2", EDocumentService."Service Integration V2"::Tietoevry);
+ if not EDocumentService.FindSet() then
+ exit;
+ IsHandled := true;
+ repeat
+ if (SalesHeader."Document Type" in [SalesHeader."Document Type"::Invoice, SalesHeader."Document Type"::Order, SalesHeader."Document Type"::"Credit Memo"]) and Customer.Get(SalesHeader."Bill-to Customer No.") then
+ if Customer."VAT Registration No." = '' then
+ Error(this.MissingCustInfoErr, Customer.FieldCaption("VAT Registration No."), Customer."No.");
+
+ if not ServiceParticipant.Get(EDocumentService.Code, ServiceParticipant."Participant Type"::Customer, SalesHeader."Bill-to Customer No.") then
+ ServiceParticipant.Init();
+
+ if ServiceParticipant."Participant Identifier" = '' then
+ Error(this.MissingCustInfoErr, ServiceParticipant.FieldCaption("Participant Identifier"), Customer."No.");
+ until EDocumentService.Next() = 0;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Service Participant", 'OnAfterValidateEvent', 'Participant Identifier', false, false)]
+ local procedure OnAfterValidateServiceParticipant(var Rec: Record "Service Participant"; var xRec: Record "Service Participant"; CurrFieldNo: Integer)
+ var
+ EDocumentService: Record "E-Document Service";
+ begin
+ if not EDocumentService.Get(Rec.Service) then
+ exit;
+ if EDocumentService."Service Integration V2" <> EDocumentService."Service Integration V2"::Tietoevry then
+ exit;
+ if Rec."Participant Identifier" <> '' then
+ if not this.TietoevryProcessing.IsValidSchemeId(Rec."Participant Identifier") then
+ Rec.FieldError(Rec."Participant Identifier");
+ end;
+
+ internal procedure GetServicesInWorkflow(var EDocServices: Record "E-Document Service"; WorkFlowCode: Code[20]): Boolean
+ var
+ WorkflowStepArgument: Record "Workflow Step Argument";
+ WorkflowStep: Record "Workflow Step";
+ Workflow: Record Workflow;
+ Filter: Text;
+ begin
+ Workflow.Get(WorkFlowCode);
+ WorkflowStep.SetRange("Workflow Code", Workflow.Code);
+ WorkflowStep.SetRange(Type, WorkflowStep.Type::Response);
+ if WorkflowStep.FindSet() then
+ repeat
+ WorkflowStepArgument.Get(WorkflowStep.Argument);
+ this.AddFilter(Filter, WorkflowStepArgument."E-Document Service");
+ until WorkflowStep.Next() = 0;
+
+ if Filter = '' then
+ exit(false);
+
+ EDocServices.SetFilter(Code, Filter);
+ exit(true);
+ end;
+
+ internal procedure AddFilter(var Filter: Text; Value: Text)
+ begin
+ if Value = '' then
+ exit;
+
+ if Filter = '' then
+ Filter := Value
+ else
+ Filter += '|' + Value;
+ end;
+
+ var
+ TietoevryProcessing: Codeunit "Processing";
+ MissingCompInfVATRegNoErr: Label 'You must specify VAT Registration No. in %1.', Comment = '%1=Company Information';
+ MissingCustInfoErr: Label 'You must specify %1 for Customer %2.', Comment = '%1=Fieldcaption %2=Customer No.';
+}
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Extensions/EDocument.TableExt.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Extensions/EDocument.TableExt.al
new file mode 100644
index 0000000000..5c770aac4e
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Extensions/EDocument.TableExt.al
@@ -0,0 +1,26 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.EServices.EDocumentConnector.Tietoevry;
+
+using Microsoft.eServices.EDocument;
+
+tableextension 6390 "E-Document" extends "E-Document"
+{
+ fields
+ {
+ field(6390; "Tietoevry Document Id"; Text[50])
+ {
+ DataClassification = SystemMetadata;
+ }
+ field(6391; "Message Profile Id"; Text[50])
+ {
+ DataClassification = SystemMetadata;
+ }
+ field(6392; "Message Document Id"; Text[200])
+ {
+ DataClassification = SystemMetadata;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Formats/FormatEvents.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Formats/FormatEvents.Codeunit.al
new file mode 100644
index 0000000000..985d564afc
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Formats/FormatEvents.Codeunit.al
@@ -0,0 +1,136 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using Microsoft.Sales.Peppol;
+using Microsoft.Sales.Document;
+using Microsoft.eServices.EDocument;
+using Microsoft.eServices.EDocument.Service.Participant;
+using System.IO;
+using Microsoft.eServices.EDocument.IO.Peppol;
+using System.Utilities;
+
+codeunit 6398 "Format Events"
+{
+
+ SingleInstance = true;
+ EventSubscriberInstance = StaticAutomatic;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"EDoc PEPPOL BIS 3.0", OnAfterCreatePEPPOLXMLDocument, '', false, false)]
+ local procedure OnAfterCreatePEPPOLXMLDocument(EDocumentService: Record "E-Document Service"; var EDocument: Record "E-Document"; var SourceDocumentHeader: RecordRef; var SourceDocumentLines: RecordRef; var TempBlob: Codeunit "Temp Blob");
+ var
+ TempXMLBuffer: Record "XML Buffer" temporary;
+ DocInStream: InStream;
+ MessageDocumentId: Text;
+ begin
+ if EDocumentService."Service Integration V2" <> EDocumentService."Service Integration V2"::Tietoevry then
+ exit;
+
+ TempBlob.CreateInStream(DocInStream);
+ TempXMLBuffer.LoadFromStream(DocInStream);
+ TempXMLBuffer.SetRange(Type, TempXMLBuffer.Type::Element);
+ TempXMLBuffer.SetRange(Name, 'ProfileID');
+ if TempXMLBuffer.FindFirst() then
+ EDocument."Message Profile Id" := TempXMLBuffer.Value;
+
+ TempXMLBuffer.SetRange(Type, TempXMLBuffer.Type::Attribute);
+ TempXMLBuffer.SetRange(Name, 'xmlns');
+ if TempXMLBuffer.FindFirst() then
+ MessageDocumentId := TempXMLBuffer.Value;
+
+ TempXMLBuffer.SetRange(Type, TempXMLBuffer.Type::Element);
+ TempXMLBuffer.SetRange(Name);
+ if TempXMLBuffer.FindFirst() then
+ MessageDocumentId += '::' + TempXMLBuffer.Name;
+
+ TempXMLBuffer.SetRange(Type, TempXMLBuffer.Type::Element);
+ TempXMLBuffer.SetRange(Name, 'CustomizationID');
+ if TempXMLBuffer.FindFirst() then
+ MessageDocumentId += '##' + TempXMLBuffer.Value + '::2.1';
+
+ EDocument."Message Document Id" := MessageDocumentId;
+ EDocument.Modify();
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management", OnAfterGetAccountingSupplierPartyInfoByFormat, '', false, false)]
+ local procedure "PEPPOL Management_OnAfterGetAccountingSupplierPartyInfoByFormat"(var SupplierEndpointID: Text; var SupplierSchemeID: Text; var SupplierName: Text; IsBISBilling: Boolean)
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ if not IsBISBilling then
+ exit;
+ if not ConnectionSetup.Get() then
+ exit;
+
+ this.SplitId(ConnectionSetup."Company Id", SupplierSchemeID, SupplierEndpointID);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management", OnAfterGetAccountingSupplierPartyLegalEntityByFormat, '', false, false)]
+ local procedure "PEPPOL Management_OnAfterGetAccountingSupplierPartyLegalEntityByFormat"(var PartyLegalEntityRegName: Text; var PartyLegalEntityCompanyID: Text; var PartyLegalEntitySchemeID: Text; var SupplierRegAddrCityName: Text; var SupplierRegAddrCountryIdCode: Text; var SupplRegAddrCountryIdListId: Text; IsBISBilling: Boolean)
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ if not IsBISBilling then
+ exit;
+ if not ConnectionSetup.Get() then
+ exit;
+
+ this.SplitId(ConnectionSetup."Company Id", PartyLegalEntitySchemeID, PartyLegalEntityCompanyID);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management", OnAfterGetAccountingCustomerPartyInfoByFormat, '', false, false)]
+ local procedure "PEPPOL Management_OnAfterGetAccountingCustomerPartyInfoByFormat"(SalesHeader: Record "Sales Header"; var CustomerEndpointID: Text; var CustomerSchemeID: Text; var CustomerPartyIdentificationID: Text; var CustomerPartyIDSchemeID: Text; var CustomerName: Text; IsBISBilling: Boolean)
+ var
+ ServiceParticipant: Record "Service Participant";
+ EDocumentService: Record "E-Document Service";
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ if not IsBISBilling then
+ exit;
+ if not ConnectionSetup.Get() then
+ exit;
+ EDocumentService.SetRange("Service Integration V2", EDocumentService."Service Integration V2"::Tietoevry);
+ if not EDocumentService.FindFirst() then
+ exit;
+ ServiceParticipant.Get(EDocumentService.Code, ServiceParticipant."Participant Type"::Customer, SalesHeader."Bill-to Customer No.");
+ this.SplitId(ServiceParticipant."Participant Identifier", CustomerSchemeID, CustomerEndpointID);
+ this.SplitId(ServiceParticipant."Participant Identifier", CustomerPartyIDSchemeID, CustomerPartyIdentificationID);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management", OnAfterGetAccountingCustomerPartyLegalEntityByFormat, '', false, false)]
+ local procedure "PEPPOL Management_OnAfterGetAccountingCustomerPartyLegalEntityByFormat"(SalesHeader: Record "Sales Header"; var CustPartyLegalEntityRegName: Text; var CustPartyLegalEntityCompanyID: Text; var CustPartyLegalEntityIDSchemeID: Text; IsBISBilling: Boolean)
+ var
+ ServiceParticipant: Record "Service Participant";
+ EDocumentService: Record "E-Document Service";
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ if not IsBISBilling then
+ exit;
+ if not ConnectionSetup.Get() then
+ exit;
+ EDocumentService.SetRange("Service Integration V2", EDocumentService."Service Integration V2"::Tietoevry);
+ if not EDocumentService.FindFirst() then
+ exit;
+
+ ServiceParticipant.Get(EDocumentService.Code, ServiceParticipant."Participant Type"::Customer, SalesHeader."Bill-to Customer No.");
+ this.SplitId(ServiceParticipant."Participant Identifier", CustPartyLegalEntityIDSchemeID, CustPartyLegalEntityCompanyID);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"E-Document Log", 'OnBeforeExportDataStorage', '', false, false)]
+ local procedure SetFileExt(EDocumentLog: Record "E-Document Log"; var FileName: Text)
+ begin
+ FileName += '.xml';
+ end;
+
+
+ local procedure SplitId(Input: Text; var SchemeId: Text; var EndpointId: Text)
+ var
+ Parts: List of [Text];
+ begin
+ Parts := Input.Split(':');
+ SchemeId := Parts.Get(1);
+ EndpointId := Parts.Get(2);
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/HttpExecutor.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/HttpExecutor.Codeunit.al
new file mode 100644
index 0000000000..0e0e068742
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/HttpExecutor.Codeunit.al
@@ -0,0 +1,118 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using System.Telemetry;
+
+///
+/// Execute http requests for Tietoevry API
+///
+codeunit 6397 "Http Executor"
+{
+
+ Access = Internal;
+
+ ///
+ /// Execute http calls. Handle response with error logging.
+ ///
+ procedure ExecuteHttpRequest(var Request: Codeunit Requests) Response: Text
+ var
+ HttpResponse: HttpResponseMessage;
+ begin
+ exit(this.ExecuteHttpRequest(Request, HttpResponse));
+ end;
+
+ ///
+ /// Execute http calls. Handle response with error logging and store response in HttpResponse
+ ///
+ procedure ExecuteHttpRequest(var Request: Codeunit Requests; HttpResponse: HttpResponseMessage) Response: Text
+ var
+ FeatureTelemetry: Codeunit "Feature Telemetry";
+ HttpClient: HttpClient;
+ begin
+ FeatureTelemetry.LogUptake('', this.TietoevryProcessing.GetTietoevryTok(), Enum::"Feature Uptake Status"::Used);
+ FeatureTelemetry.LogUsage('', this.TietoevryProcessing.GetTietoevryTok(), 'Tietoevry request.');
+
+ HttpClient.Send(Request.GetRequest(), this.HttpResponseMessage);
+ HttpResponse := this.HttpResponseMessage;
+ this.HandleHttpResponse(this.HttpResponseMessage, Response);
+ end;
+
+ ///
+ /// Return response from last http call
+ ///
+ procedure GetResponse(): HttpResponseMessage
+ begin
+ exit(this.HttpResponseMessage);
+ end;
+
+ ///
+ /// Throw error for requests not of status 200 and 201.
+ ///
+ local procedure HandleHttpResponse(LocalHttpResponseMessage: HttpResponseMessage; var Response: Text)
+ var
+ FriendlyErrorMsg: Text;
+ begin
+ GetContent(LocalHttpResponseMessage, Response);
+ case LocalHttpResponseMessage.HttpStatusCode() of
+ 200:
+ begin
+ Session.LogMessage('', this.HTTPSuccessMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.TietoevryProcessing.GetTietoevryTok());
+ exit;
+ end;
+ 201:
+ begin
+ Session.LogMessage('', this.HTTPSuccessAndCreatedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.TietoevryProcessing.GetTietoevryTok());
+ exit;
+ end;
+ 400:
+ FriendlyErrorMsg := this.HTTPBadRequestMsg;
+ 401:
+ FriendlyErrorMsg := this.HTTPUnauthorizedMsg;
+ 402 .. 499:
+ if not this.Parse400Messages(Response, FriendlyErrorMsg) then
+ FriendlyErrorMsg := this.HTTPBadRequestMsg;
+ 500:
+ FriendlyErrorMsg := this.HTTPInternalServerErrorMsg;
+ 503:
+ FriendlyErrorMsg := this.HTTPServiceUnavailableMsg;
+ else
+ FriendlyErrorMsg := this.HTTPGeneralErrMsg;
+ end;
+
+ FriendlyErrorMsg := StrSubstNo(this.HttpErrorMsg, LocalHttpResponseMessage.HttpStatusCode(), FriendlyErrorMsg);
+ Session.LogMessage('', StrSubstNo(this.HttpErrorMsg, LocalHttpResponseMessage.HttpStatusCode(), Response), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.TietoevryProcessing.GetTietoevryTok());
+ Error(FriendlyErrorMsg);
+ end;
+
+ [TryFunction]
+ local procedure Parse400Messages(Content: Text; var Message: Text)
+ var
+ ResponseJson: JsonObject;
+ JsonToken: JsonToken;
+ begin
+ ResponseJson.ReadFrom(Content);
+ ResponseJson.Get('message', JsonToken);
+ Message := JsonToken.AsValue().AsText();
+ end;
+
+ [TryFunction]
+ local procedure GetContent(HttpResponseMsg: HttpResponseMessage; var Response: Text)
+ begin
+ HttpResponseMsg.Content.ReadAs(Response);
+ end;
+
+ var
+ TietoevryProcessing: Codeunit Processing;
+ HttpResponseMessage: HttpResponseMessage;
+ HTTPSuccessMsg: Label 'The HTTP request was successful and the body contains the resource fetched.'; // 200
+ HTTPSuccessAndCreatedMsg: Label 'The HTTP request was successful and a new resource was created.'; //201
+ HTTPBadRequestMsg: Label 'The HTTP request was incorrectly formed or invalid.'; // 400
+ HTTPUnauthorizedMsg: Label 'The HTTP request is not authorized. Authentication credentials are not valid.'; // 401
+ HTTPInternalServerErrorMsg: Label 'The HTTP request is not successful. An internal server error occurred.'; // 500
+ HTTPServiceUnavailableMsg: Label 'The HTTP request is not successful. The service is unavailable.'; // 503
+ HTTPGeneralErrMsg: Label 'Something went wrong, try again later.';
+ HttpErrorMsg: Label 'Error Code: %1, Error Message: %2', Comment = '%1 = Error Code, %2 = Error Message';
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/IntegrationImpl.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/IntegrationImpl.Codeunit.al
new file mode 100644
index 0000000000..3f1050ac7b
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/IntegrationImpl.Codeunit.al
@@ -0,0 +1,55 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using System.Utilities;
+using Microsoft.EServices.EDocument;
+using Microsoft.eServices.EDocument.Integration.Send;
+using Microsoft.eServices.EDocument.Integration.Receive;
+using Microsoft.eServices.EDocument.Integration.Interfaces;
+
+codeunit 6392 "Integration Impl." implements IDocumentSender, IDocumentResponseHandler, IDocumentReceiver, IReceivedDocumentMarker
+{
+ Access = Internal;
+
+ procedure Send(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; SendContext: Codeunit SendContext)
+ begin
+ this.TietoevryProcessing.SendEDocument(EDocument, EDocumentService, SendContext);
+ end;
+
+ procedure GetResponse(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; SendContext: Codeunit SendContext): Boolean
+ begin
+ exit(this.TietoevryProcessing.GetDocumentStatus(EDocument, SendContext));
+ end;
+
+ procedure ReceiveDocuments(var EDocumentService: Record "E-Document Service"; ReceivedEDocuments: Codeunit "Temp Blob List"; ReceiveContext: Codeunit ReceiveContext)
+ begin
+ this.TietoevryProcessing.ReceiveDocuments(EDocumentService, ReceivedEDocuments, ReceiveContext);
+ end;
+
+ procedure DownloadDocument(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; DocumentMetadataBlob: codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ begin
+ this.TietoevryProcessing.DownloadDocument(EDocument, EDocumentService, DocumentMetadataBlob, ReceiveContext);
+ end;
+
+ procedure MarkFetched(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var DocumentBlob: Codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ begin
+ this.TietoevryProcessing.AcknowledgeDocument(EDocument, EDocumentService, DocumentBlob, ReceiveContext);
+ end;
+
+ [EventSubscriber(ObjectType::Page, Page::"E-Document Service", OnBeforeOpenServiceIntegrationSetupPage, '', false, false)]
+ local procedure OnBeforeOpenServiceIntegrationSetupPage(EDocumentService: Record "E-Document Service"; var IsServiceIntegrationSetupRun: Boolean)
+ var
+ ConnectionSetupCard: Page "Connection Setup Card";
+ begin
+ if EDocumentService."Service Integration V2" <> EDocumentService."Service Integration V2"::Tietoevry then
+ exit;
+ ConnectionSetupCard.RunModal();
+ IsServiceIntegrationSetupRun := true;
+ end;
+
+ var
+ TietoevryProcessing: Codeunit Processing;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/IntegrationV2.EnumExt.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/IntegrationV2.EnumExt.al
new file mode 100644
index 0000000000..135d0f64f9
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/IntegrationV2.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.Tietoevry;
+
+using Microsoft.eServices.EDocument.Integration.Interfaces;
+using Microsoft.eServices.EDocument.Integration;
+
+enumextension 6391 IntegrationV2 extends "Service Integration"
+{
+ value(6390; "Tietoevry")
+ {
+ Implementation = IDocumentSender = "Integration Impl.", IDocumentReceiver = "Integration Impl.";
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEDocConnectorEdit.PermissionSetExt.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEDocConnectorEdit.PermissionSetExt.al
new file mode 100644
index 0000000000..f0484c9d18
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEDocConnectorEdit.PermissionSetExt.al
@@ -0,0 +1,12 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using System.Security.AccessControl;
+
+permissionsetextension 6394 "Tietoevry EDoc. Connector - Edit" extends "D365 BASIC"
+{
+ IncludedPermissionSets = "Tietoevry Edit";
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEDocConnectorRead.PermissionSetExt.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEDocConnectorRead.PermissionSetExt.al
new file mode 100644
index 0000000000..d121e222ad
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEDocConnectorRead.PermissionSetExt.al
@@ -0,0 +1,12 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using System.Security.AccessControl;
+
+permissionsetextension 6392 "Tietoevry EDoc. Connector - Read" extends "D365 READ"
+{
+ IncludedPermissionSets = "Tietoevry Read";
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEdit.PermissionSet.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEdit.PermissionSet.al
new file mode 100644
index 0000000000..9e40cbeed7
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryEdit.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.Tietoevry;
+
+permissionset 6392 "Tietoevry Edit"
+{
+ Access = Public;
+ Assignable = true;
+ IncludedPermissionSets = "Tietoevry Read";
+ Caption = 'Tietoevry E-Document Connector - Edit';
+
+ Permissions = tabledata "Connection Setup" = imd;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryObjects.PermissionSet.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryObjects.PermissionSet.al
new file mode 100644
index 0000000000..57d0434892
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryObjects.PermissionSet.al
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+permissionset 6390 "Tietoevry Objects"
+{
+ Access = Public;
+ Assignable = false;
+ Caption = 'Tietoevry E-Document Connector - Objects';
+
+ Permissions =
+ table "Connection Setup" = X,
+ page "Connection Setup Card" = X,
+ codeunit "Integration Impl." = X,
+ codeunit Processing = X,
+ codeunit Authenticator = X,
+ codeunit Requests = X,
+ codeunit Events = X,
+ codeunit "Http Executor" = X;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryRead.PermissionSet.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryRead.PermissionSet.al
new file mode 100644
index 0000000000..01cadadafc
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Permissions/TietoevryRead.PermissionSet.al
@@ -0,0 +1,14 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+permissionset 6391 "Tietoevry Read"
+{
+ Access = Public;
+ Assignable = true;
+ Caption = 'Tietoevry E-Document Connector - Read';
+
+ Permissions = tabledata "Connection Setup" = r;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Processing.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Processing.Codeunit.al
new file mode 100644
index 0000000000..be72229ed5
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Processing.Codeunit.al
@@ -0,0 +1,284 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using Microsoft.EServices.EDocument;
+using System.Utilities;
+using Microsoft.eServices.EDocument.Integration.Send;
+using Microsoft.eServices.EDocument.Integration.Receive;
+
+codeunit 6399 Processing
+{
+ Access = Internal;
+ Permissions = tabledata "E-Document" = m,
+ tabledata "E-Document Service Status" = m,
+ tabledata "Connection Setup" = rm;
+
+
+ ///
+ /// Calls Tietoevry API for SubmitDocument.
+ ///
+ procedure SendEDocument(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; SendContext: Codeunit SendContext)
+ var
+ Request: Codeunit Requests;
+ HttpExecutor: Codeunit "Http Executor";
+ TempBlob: Codeunit "Temp Blob";
+ InStream: InStream;
+ RequestContent: Text;
+ ResponseContent: Text;
+ begin
+ TempBlob := SendContext.GetTempBlob();
+
+ TempBlob.CreateInStream(InStream, TextEncoding::UTF8);
+ InStream.Read(RequestContent);
+
+ Request.Init();
+ Request.Authenticate().CreateSubmitDocumentRequest(EDocument, EDocumentService, RequestContent);
+ ResponseContent := HttpExecutor.ExecuteHttpRequest(Request);
+ SendContext.Http().SetHttpRequestMessage(Request.GetRequest());
+ SendContext.Http().SetHttpResponseMessage(HttpExecutor.GetResponse());
+
+ EDocument.Get(EDocument."Entry No");
+ EDocument."Tietoevry Document Id" := this.ParseDocumentId(ResponseContent);
+ EDocument.Modify(true);
+ end;
+
+ ///
+ /// Calls Tietoevry API for GetDocumentStatus.
+ /// If request is successfull, but status is Error, then errors are logged and error is thrown to set document to Sending Error state
+ ///
+ /// False if status is Pending, True if status is Complete.
+ procedure GetDocumentStatus(var EDocument: Record "E-Document"; SendContext: Codeunit SendContext): Boolean
+ var
+ Request: Codeunit Requests;
+ HttpExecutor: Codeunit "Http Executor";
+ ResponseContent: Text;
+ begin
+ EDocument.TestField("Tietoevry Document Id");
+
+ Request.Init();
+ Request.Authenticate().CreateGetDocumentStatusRequest(EDocument."Tietoevry Document Id");
+ ResponseContent := HttpExecutor.ExecuteHttpRequest(Request);
+ SendContext.Http().SetHttpRequestMessage(Request.GetRequest());
+ SendContext.Http().SetHttpResponseMessage(HttpExecutor.GetResponse());
+ exit(this.ParseGetDocumentStatusResponse(EDocument, ResponseContent));
+ end;
+
+ ///
+ /// Get a list of messages to collect.
+ ///
+ procedure ReceiveDocuments(var EDocumentService: Record "E-Document Service"; ReceivedEDocuments: Codeunit "Temp Blob List"; ReceiveContext: Codeunit ReceiveContext)
+ var
+ TempBlob: Codeunit "Temp Blob";
+ Request: Codeunit Requests;
+ HttpExecutor: Codeunit "Http Executor";
+ ResponseContent: Text;
+ OutStream: OutStream;
+ HttpRequest: HttpRequestMessage;
+ HttpResponse: HttpResponseMessage;
+ Response: JsonArray;
+ ValueObject: JsonToken;
+ begin
+ Request.Init();
+ Request.Authenticate().CreateReceiveDocumentsRequest();
+ HttpRequest := Request.GetRequest();
+ ResponseContent := HttpExecutor.ExecuteHttpRequest(Request, HttpResponse);
+ ReceiveContext.Http().SetHttpRequestMessage(HttpRequest);
+ ReceiveContext.Http().SetHttpResponseMessage(HttpResponse);
+
+ Response.ReadFrom(ResponseContent);
+ this.RemoveExistingDocumentsFromResponse(Response);
+
+ foreach ValueObject in Response do begin
+ Clear(TempBlob);
+ TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8);
+ OutStream.Write(ValueObject.AsValue().AsText());
+ ReceivedEDocuments.Add(TempBlob);
+ end;
+ end;
+
+ ///
+ /// Download document XML from Tietoevry API
+ ///
+ procedure DownloadDocument(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; DocumentMetadataBlob: codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ var
+ Request: Codeunit Requests;
+ HttpExecutor: Codeunit "Http Executor";
+ ResponseContent: Text;
+ InStream: InStream;
+ DocumentId: Text;
+ OutStream: OutStream;
+ begin
+ DocumentMetadataBlob.CreateInStream(InStream, TextEncoding::UTF8);
+ InStream.ReadText(DocumentId);
+
+ if DocumentId = '' then begin
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.DocumentIdNotFoundErr);
+ exit;
+ end;
+
+ EDocument."Tietoevry Document Id" := CopyStr(DocumentId, 1, MaxStrLen(EDocument."Tietoevry Document Id"));
+ EDocument.Modify();
+
+ Request.Init();
+ Request.Authenticate().CreateDownloadRequest(DocumentId);
+ ResponseContent := HttpExecutor.ExecuteHttpRequest(Request);
+ ReceiveContext.Http().SetHttpRequestMessage(Request.GetRequest());
+ ReceiveContext.Http().SetHttpResponseMessage(HttpExecutor.GetResponse());
+
+ ReceiveContext.GetTempBlob().CreateOutStream(OutStream, TextEncoding::UTF8);
+ OutStream.WriteText(ResponseContent);
+ end;
+
+ ///
+ /// Mark document as read from Tietoevry API
+ ///
+ procedure AcknowledgeDocument(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; DocumentMetadataBlob: codeunit "Temp Blob"; ReceiveContext: Codeunit ReceiveContext)
+ var
+ Request: Codeunit Requests;
+ HttpExecutor: Codeunit "Http Executor";
+ ResponseContent: Text;
+ begin
+ Request.Init();
+ Request.Authenticate().CreateAcknowledgeRequest(EDocument."Tietoevry Document Id");
+ ResponseContent := HttpExecutor.ExecuteHttpRequest(Request);
+ ReceiveContext.Http().SetHttpRequestMessage(Request.GetRequest());
+ ReceiveContext.Http().SetHttpResponseMessage(HttpExecutor.GetResponse());
+ end;
+
+
+ ///
+ /// Remove document ids from array that are already created as E-Documents.
+ ///
+ local procedure RemoveExistingDocumentsFromResponse(var Documents: JsonArray)
+ var
+ DocumentId: Text;
+ I: Integer;
+ NewArray: JsonArray;
+ begin
+ for I := 0 to Documents.Count() - 1 do begin
+ DocumentId := this.GetDocumentIdFromArray(Documents, I);
+ if not this.DocumentExists(DocumentId) then
+ NewArray.Add(DocumentId);
+ end;
+ Documents := NewArray;
+ end;
+
+ ///
+ /// Check if E-Document with Document Id exists in E-Document table
+ ///
+ local procedure DocumentExists(DocumentId: Text): Boolean
+ var
+ EDocument: Record "E-Document";
+ begin
+ EDocument.SetRange("Tietoevry Document Id", DocumentId);
+ exit(not EDocument.IsEmpty());
+ end;
+
+ ///
+ /// Parse company id
+ ///
+ local procedure ParseDocumentId(ResponseMsg: Text): Text[50]
+ var
+ EDocument: Record "E-Document";
+ DocumentId: Text;
+ ResponseJson: JsonObject;
+ ValueJson: JsonToken;
+ begin
+ ResponseJson.ReadFrom(ResponseMsg);
+ ResponseJson.Get('id', ValueJson);
+
+ DocumentId := ValueJson.AsValue().AsText();
+ if StrLen(DocumentId) > MaxStrLen(EDocument."Tietoevry Document Id") then
+ Error(this.TietoevryIdLongerErr);
+
+ exit(CopyStr(DocumentId, 1, MaxStrLen(EDocument."Tietoevry Document Id")));
+ end;
+
+ ///
+ /// Parse Document Response. If erros log all events
+ ///
+ local procedure ParseGetDocumentStatusResponse(var EDocument: Record "E-Document"; ResponseMsg: Text): Boolean
+ var
+ ResponseJson: JsonObject;
+ ValueJson, EventToken : JsonToken;
+ begin
+ ResponseJson.ReadFrom(ResponseMsg);
+ ResponseJson.Get('id', ValueJson);
+ if EDocument."Tietoevry Document Id" <> ValueJson.AsValue().AsText() then
+ Error(this.IncorrectDocumentIdInResponseErr);
+
+ ResponseJson.Get('status', ValueJson);
+ case UpperCase(ValueJson.AsValue().AsText()) of
+ 'PROCESSED':
+ exit(true);
+ 'PENDING':
+ exit(false);
+ 'FAILED':
+ begin
+ if not EventToken.ReadFrom(ResponseMsg) then begin
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, this.TietoevryProcessingDocFailedErr);
+ exit(false);
+ end;
+ if ResponseJson.Get('details', ValueJson) then
+ this.EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, ValueJson.AsValue().AsText());
+
+ exit(false);
+ end;
+ else
+ exit(false);
+ end;
+ end;
+
+ ///
+ /// Returns id from json array
+ ///
+ local procedure GetDocumentIdFromArray(DocumentArray: JsonArray; Index: Integer): Text
+ var
+ DocumentJsonToken, IdToken : JsonToken;
+ begin
+ DocumentArray.Get(Index, DocumentJsonToken);
+ DocumentJsonToken.AsObject().Get('id', IdToken);
+ exit(IdToken.AsValue().AsText());
+ end;
+
+ procedure GetTietoevryTok(): Text
+ begin
+ exit(this.TietoevryTok);
+ end;
+
+ internal procedure IsValidSchemeId(PeppolId: Text[50]) Result: Boolean;
+ var
+ ValidSchemeId: Text;
+ ValidSchemeIdList: List of [Text];
+ SplitSeparator: Text;
+ SchemeId: Text;
+ ResInStream: InStream;
+ JsonObject: JsonObject;
+ JsonToken: JsonToken;
+ begin
+ SplitSeparator := ' ';
+ NavApp.GetResource(ResourceSchemeIdPath, ResInStream, TextEncoding::UTF8);
+ ResInStream.ReadText(ValidSchemeId);
+ JsonObject.ReadFrom(ValidSchemeId);
+ JsonObject.Get('schemeids', JsonToken);
+ ValidSchemeId := JsonToken.AsValue().AsText();
+ ValidSchemeIdList := ValidSchemeId.Split(SplitSeparator);
+
+ foreach SchemeId in ValidSchemeIdList do
+ if PeppolId.StartsWith(SchemeId) then
+ exit(true);
+ exit(false);
+ end;
+
+ var
+ EDocumentErrorHelper: Codeunit "E-Document Error Helper";
+ IncorrectDocumentIdInResponseErr: Label 'Document ID returned by API does not match E-Document.';
+ DocumentIdNotFoundErr: Label 'Document ID not found in response.';
+ TietoevryProcessingDocFailedErr: Label 'An error has been identified in the submitted document.';
+ TietoevryIdLongerErr: Label 'Tietoevry returned id longer than supported by framework.';
+ TietoevryTok: Label 'E-Document - Tietoevry', Locked = true;
+ ResourceSchemeIdPath: Label 'SchemeIds.txt', Locked = true;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Requests.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Requests.Codeunit.al
new file mode 100644
index 0000000000..971379640e
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/Requests.Codeunit.al
@@ -0,0 +1,247 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using Microsoft.eServices.EDocument;
+using System.Text;
+using System.Reflection;
+using Microsoft.eServices.EDocument.Service.Participant;
+
+
+///
+/// Construct meta data object for Tietoevry request
+///
+codeunit 6396 Requests
+{
+
+ Access = Internal;
+ Permissions = tabledata "Connection Setup" = r;
+
+ var
+ TietoevryAuth: Codeunit "Authenticator";
+ HttpRequestMessage: HttpRequestMessage;
+ BaseUrl, AuthUrl, CompanyId : Text;
+ AccessToken: SecretText;
+ ServiceParticipantNotFoundErr: Label 'No Service Participant defined for Customer %1 and E-Document Service %2.', Comment = '%1 - The customer no., %2 - The e-document service code';
+
+ ///
+ /// Create request for /outbound API
+ /// https://accesspoint.qa.dataplatfor.ms/swagger-ui/#/Outbound%20Resource/post_outbound
+ ///
+ /// The data object is the details of the invoice.
+ /// A request object that can be used for the endpoint.
+ procedure CreateSubmitDocumentRequest(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; Data: Text): Codeunit Requests
+ var
+ ServiceParticipant: Record "Service Participant";
+ Base64Convert: Codeunit "Base64 Convert";
+ HttpHeaders, HttpContentHeaders : HttpHeaders;
+ Content: Text;
+ ContentJson: JsonObject;
+ begin
+ Clear(this.HttpRequestMessage);
+ this.HttpRequestMessage.SetRequestUri(this.BaseUrl + '/outbound');
+ this.HttpRequestMessage.Method := 'POST';
+
+ this.HttpRequestMessage.GetHeaders(HttpHeaders);
+ HttpHeaders.Add('Authorization', this.AddBearer(this.AccessToken));
+
+ EDocument.TestField("Bill-to/Pay-to No.");
+ if not ServiceParticipant.Get(EDocumentService.Code, ServiceParticipant."Participant Type"::Customer, EDocument."Bill-to/Pay-to No.") then
+ Error(ServiceParticipantNotFoundErr, EDocument."Bill-to/Pay-to No.", EDocumentService.Code);
+
+ ContentJson.Add('payload', Base64Convert.ToBase64(Data));
+ ContentJson.Add('sender', CompanyId);
+ ContentJson.Add('receiver', ServiceParticipant."Participant Identifier");
+ ContentJson.Add('profileId', EDocument."Message Profile Id");
+ ContentJson.Add('documentId', EDocument."Message Document Id");
+ ContentJson.Add('channel', 'PEPPOL');
+ ContentJson.Add('reference', Format(EDocument."Entry No"));
+ ContentJson.WriteTo(Content);
+
+
+ this.HttpRequestMessage.Content.WriteFrom(Content);
+
+ this.HttpRequestMessage.Content.GetHeaders(HttpContentHeaders);
+ if HttpContentHeaders.Contains('Content-Type') then
+ HttpContentHeaders.Remove('Content-Type');
+ HttpContentHeaders.Add('Content-Type', 'application/json');
+
+ exit(this);
+ end;
+
+ ///
+ /// Create request for /outbound/:id API
+ /// https://accesspoint.qa.dataplatfor.ms/swagger-ui/#/Outbound%20Resource/get_outbound__id_
+ ///
+ /// A request object that can be used for the endpoint.
+ procedure CreateGetDocumentStatusRequest(Id: Text): Codeunit Requests
+ var
+ HttpHeaders: HttpHeaders;
+ begin
+ Clear(this.HttpRequestMessage);
+ this.HttpRequestMessage.SetRequestUri(this.BaseUrl + '/outbound/' + Id);
+ this.HttpRequestMessage.Method := 'GET';
+
+ this.HttpRequestMessage.GetHeaders(HttpHeaders);
+ HttpHeaders.Add('Authorization', this.AddBearer(this.AccessToken));
+ HttpHeaders.Add('Accept', 'application/json');
+
+ exit(this);
+ end;
+
+ ///
+ /// Create request for /inbound?receiver=$companyid
+ /// Takes a path as query parameters are computed for each request.
+ /// https://accesspoint.qa.dataplatfor.ms/swagger-ui/#/Inbound%20Resource/get_inbound
+ ///
+ /// A request object that can be used for the endpoint.
+ procedure CreateReceiveDocumentsRequest(): Codeunit Requests
+ var
+ TypeHelper: Codeunit "Type Helper";
+ HttpHeaders: HttpHeaders;
+ begin
+ Clear(this.HttpRequestMessage);
+ this.HttpRequestMessage.SetRequestUri(this.BaseUrl + '/inbound?receiver=' + TypeHelper.UrlEncode(this.CompanyId));
+ this.HttpRequestMessage.Method := 'GET';
+
+ this.HttpRequestMessage.GetHeaders(HttpHeaders);
+ HttpHeaders.Add('Authorization', this.AddBearer(this.AccessToken));
+ HttpHeaders.Add('Accept', 'application/json');
+
+ exit(this);
+ end;
+
+ ///
+ /// Create request for /inbound/$id
+ /// https://accesspoint.qa.dataplatfor.ms/swagger-ui/#/Inbound%20Resource/get_inbound__id___payload_type__document
+ ///
+ /// Document Id
+ /// A request object that can be used for the endpoint.
+ procedure CreateDownloadRequest(Id: Text): Codeunit Requests
+ var
+ HttpHeaders: HttpHeaders;
+ begin
+ Clear(this.HttpRequestMessage);
+ this.HttpRequestMessage.SetRequestUri(this.BaseUrl + '/inbound/' + Id + '/PAYLOAD/document');
+ this.HttpRequestMessage.Method := 'GET';
+
+ this.HttpRequestMessage.GetHeaders(HttpHeaders);
+ HttpHeaders.Add('Authorization', this.AddBearer(this.AccessToken));
+ HttpHeaders.Add('Accept', 'application/octet-stream');
+
+ exit(this);
+ end;
+
+ ///
+ /// Create request for /inbound/$id/read
+ /// https://accesspoint.qa.dataplatfor.ms/swagger-ui/#/Inbound%20Resource/post_inbound__id__read
+ ///
+ /// Document Id
+ /// A request object that can be used for the endpoint.
+ procedure CreateAcknowledgeRequest(Id: Text): Codeunit Requests
+ var
+ HttpHeaders: HttpHeaders;
+ begin
+ Clear(this.HttpRequestMessage);
+ this.HttpRequestMessage.SetRequestUri(this.BaseUrl + '/inbound/' + Id + '/read');
+ this.HttpRequestMessage.Method := 'POST';
+
+ this.HttpRequestMessage.GetHeaders(HttpHeaders);
+ HttpHeaders.Add('Authorization', this.AddBearer(this.AccessToken));
+ HttpHeaders.Add('Accept', 'application/json');
+
+ exit(this);
+ end;
+
+ ///
+ /// Create request to get access token for Tietoevry API
+ ///
+ /// A request object that can be used for the endpoint.
+ [NonDebuggable]
+ procedure CreateAuthenticateRequest(ClientId: SecretText; ClientSecret: SecretText): Codeunit Requests;
+ var
+ HttpContentHeaders: HttpHeaders;
+ begin
+ Clear(this.HttpRequestMessage);
+ this.HttpRequestMessage.SetRequestUri(this.AuthUrl + '/token');
+ this.HttpRequestMessage.Method := 'POST';
+ this.HttpRequestMessage.Content.WriteFrom('grant_type=client_credentials&client_id=' + ClientId.Unwrap() + '&client_secret=' + ClientSecret.Unwrap());
+
+ this.HttpRequestMessage.Content.GetHeaders(HttpContentHeaders);
+ if HttpContentHeaders.Contains('Content-Type') then
+ HttpContentHeaders.Remove('Content-Type');
+ HttpContentHeaders.Add('Content-Type', 'application/x-www-form-urlencoded');
+
+ exit(this);
+ end;
+
+ procedure GetRequest(): HttpRequestMessage
+ begin
+ exit(this.HttpRequestMessage);
+ end;
+
+ procedure Init()
+ begin
+ this.TietoevryAuth.CreateConnectionSetupRecord();
+ this.BaseUrl := this.GetBaseUrl();
+ this.AuthUrl := this.GetAuthUrl();
+ this.CompanyId := this.GetCompanyId();
+ end;
+
+ ///
+ /// Set access token on request.
+ ///
+ procedure Authenticate(): Codeunit Requests
+ begin
+ this.AccessToken := this.TietoevryAuth.GetAccessToken();
+ exit(this);
+ end;
+
+ [NonDebuggable]
+ local procedure AddBearer(Token: SecretText): SecretText
+ begin
+ exit('Bearer ' + Token.Unwrap());
+ end;
+
+ procedure GetBaseUrl(): Text
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ ConnectionSetup.Get();
+
+ case ConnectionSetup."Send Mode" of
+ "Send Mode"::Production:
+ exit(ConnectionSetup."API URL");
+ "Send Mode"::Test:
+ exit(ConnectionSetup."Sandbox API URL");
+ else
+ Error('Unsupported %1 in %2', ConnectionSetup.FieldCaption("Send Mode"), ConnectionSetup.TableCaption);
+ end;
+ end;
+
+ local procedure GetAuthUrl(): Text
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ ConnectionSetup.Get();
+
+ case ConnectionSetup."Send Mode" of
+ "Send Mode"::Production:
+ exit(ConnectionSetup."Authentication URL");
+ "Send Mode"::Test:
+ exit(ConnectionSetup."Sandbox Authentication URL");
+ else
+ Error('Unsupported %1 in %2', ConnectionSetup.FieldCaption("Send Mode"), ConnectionSetup.TableCaption);
+ end;
+ end;
+
+ procedure GetCompanyId(): Text
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ ConnectionSetup.Get();
+ exit(ConnectionSetup."Company Id");
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/app/src/SendMode.Enum.al b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/SendMode.Enum.al
new file mode 100644
index 0000000000..3b5ba257e5
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/app/src/SendMode.Enum.al
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.EServices.EDocumentConnector.Tietoevry;
+
+enum 6390 "Send Mode"
+{
+ Access = Internal;
+ Extensible = false;
+ value(0; Production)
+ {
+ Caption = 'Production';
+ }
+ value(1; Test)
+ {
+ Caption = 'Test';
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/test/ExtensionLogo.png b/Apps/W1/EDocumentConnectors/Tietoevry/test/ExtensionLogo.png
new file mode 100644
index 0000000000..4d2c9a626c
Binary files /dev/null and b/Apps/W1/EDocumentConnectors/Tietoevry/test/ExtensionLogo.png differ
diff --git a/Apps/W1/EDocumentConnectors/Tietoevry/test/app.json b/Apps/W1/EDocumentConnectors/Tietoevry/test/app.json
new file mode 100644
index 0000000000..a27f6fa6f0
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/test/app.json
@@ -0,0 +1,78 @@
+{
+ "id": "985549ff-145a-4b5c-acd1-db6d425f6cbc",
+ "name": "E-Document Connector - Tietoevry Tests",
+ "publisher": "Microsoft",
+ "brief": "E-Document Connector - Tietoevry Tests",
+ "description": "E-Document Connector - Tietoevry Tests",
+ "version": "26.0.0.0",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2204541",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "logo": "ExtensionLogo.png",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2206603",
+ "dependencies": [
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b",
+ "name": "E-Document Core",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "3b3c094c-ae7f-43f2-9246-22111b688d2e",
+ "name": "E-Document Connector - Tietoevry",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8c",
+ "name": "E-Document Core Tests",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "5d86850b-0d76-4eca-bd7b-951ad998e997",
+ "name": "Tests-TestLibraries",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "9856ae4f-d1a7-46ef-89bb-6ef056398228",
+ "name": "System Application Test Library",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "5095f467-0a01-4b99-99d1-9ff1237d286f",
+ "publisher": "Microsoft",
+ "name": "Library Variable Storage",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "40860557-a18d-42ad-aecb-22b7dd80dc80",
+ "name": "Permissions Mock",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ }
+ ],
+ "screenshots": [
+
+ ],
+ "platform": "26.0.0.0",
+ "idRanges": [
+ {
+ "from": 148193,
+ "to": 148194
+ }
+ ],
+ "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/Tietoevry/test/src/IntegrationTests.Codeunit.al b/Apps/W1/EDocumentConnectors/Tietoevry/test/src/IntegrationTests.Codeunit.al
new file mode 100644
index 0000000000..cfdfec3e89
--- /dev/null
+++ b/Apps/W1/EDocumentConnectors/Tietoevry/test/src/IntegrationTests.Codeunit.al
@@ -0,0 +1,619 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Tietoevry;
+
+using Microsoft.eServices.EDocument;
+using Microsoft.Sales.Customer;
+using Microsoft.Purchases.Document;
+using Microsoft.Foundation.Company;
+using Microsoft.Purchases.Vendor;
+using System.Threading;
+using Microsoft.eServices.EDocument.Integration;
+codeunit 148193 "Integration Tests"
+{
+
+ Subtype = Test;
+ Permissions = tabledata "Connection Setup" = rimd,
+ tabledata "E-Document" = r;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure SubmitDocument()
+ var
+ EDocument: Record "E-Document";
+ JobQueueEntry: Record "Job Queue Entry";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ // Steps:
+ // Pending response -> Sent
+ Initialize();
+
+ // [Given] Team member
+ LibraryPermission.SetTeamMember();
+
+ // [When] Posting invoice and EDocument is created
+ LibraryEDocument.PostInvoice(Customer);
+ EDocument.FindLast();
+ LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running Tietoevry SubmitDocument
+ EDocument.FindLast();
+
+ // [Then] Tietoevry Document Id has been correctly set on E-Document, parsed from Integration response.
+ Assert.AreEqual(MockServiceDocumentId(), EDocument."Tietoevry Document Id", 'Tietoevry integration failed to set Tietoevry Document Id on E-Document');
+ Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has "Pending Response"
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ SetTietoevryConnectionBaseUrl('/Tietoevry/response-complete');
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running Tietoevry GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is considered processed
+ Assert.AreEqual(Enum::"E-Document Status"::Processed, EDocument.Status, 'E-Document should be set to processed');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has Sent
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::Sent), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('3', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sent");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure SubmitDocument_Pending_Sent()
+ var
+ EDocument: Record "E-Document";
+ JobQueueEntry: Record "Job Queue Entry";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ // Steps:
+ // Pending response -> Pending response -> Sent
+ Initialize();
+ SetAPIWith200Code();
+
+ // [Given] Team member
+ LibraryPermission.SetTeamMember();
+
+ // [When] Posting invoice and EDocument is created
+ LibraryEDocument.PostInvoice(Customer);
+ EDocument.FindLast();
+ LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running Tietoevry SubmitDocument
+ EDocument.FindLast();
+
+ // [Then] Tietoevry Document Id has been correctly set on E-Document, parsed from Integration response
+ Assert.AreEqual(MockServiceDocumentId(), EDocument."Tietoevry Document Id", 'Tietoevry integration failed to set Tietoevry Document Id on E-Document');
+
+ // [Then] E-Document is pending response as Tietoevry is async
+ Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ SetTietoevryConnectionBaseUrl('/Tietoevry/response-pending');
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running Tietoevry GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is pending response as Tietoevry is async
+ Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('3', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ SetTietoevryConnectionBaseUrl('/Tietoevry/response-complete');
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running Tietoevry GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is pending response as Tietoevry is async
+ Assert.AreEqual(Enum::"E-Document Status"::Processed, EDocument.Status, 'E-Document should be set to processed');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::Sent), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('4', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::Sent);
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ [HandlerFunctions('EDocServicesPageHandler')]
+ procedure SubmitDocument_Error_Sent()
+ var
+ EDocument: Record "E-Document";
+ JobQueueEntry: Record "Job Queue Entry";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ // Steps:
+ // Pending response -> Error -> Pending response -> Sent
+ Initialize();
+ SetAPIWith200Code();
+
+ // [Given] Team member
+ LibraryPermission.SetTeamMember();
+
+ // [When] Posting invoice and EDocument is created
+ LibraryEDocument.PostInvoice(Customer);
+ EDocument.FindLast();
+ LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running Tietoevry SubmitDocument
+ EDocument.FindLast();
+
+ // [Then] Tietoevry Document Id has been correctly set on E-Document, parsed from Integration response
+ Assert.AreEqual(MockServiceDocumentId(), EDocument."Tietoevry Document Id", 'Tietoevry integration failed to set Tietoevry Document Id on E-Document');
+
+ // [Then] E-Document is pending response as Tietoevry is async
+ Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+
+ // [WHEN] Executing Get Response succesfully
+ SetTietoevryConnectionBaseUrl('/Tietoevry/response-error');
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running Tietoevry GetResponse
+ EDocument.FindLast();
+
+ // [Then] E-Document is in error state
+ Assert.AreEqual(Enum::"E-Document Status"::Error, EDocument.Status, 'E-Document should be set to error');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has sending error
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Sending Error"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('3', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ EDocumentPage.ErrorMessagesPart.First();
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('Error', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('Document started processing', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+
+ EDocumentPage.ErrorMessagesPart.Next();
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('Error', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('Wrong data in send xml', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+
+ EDocumentPage.ErrorMessagesPart.Next();
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('Error', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('An error has been identified in the submitted document.', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+
+ EDocumentPage.Close();
+
+ // Then user manually send
+
+ SetTietoevryConnectionBaseUrl('/Tietoevry/Tietoevry/200');
+ EDocument.FindLast();
+
+ // [THEN] Open E-Document page and resend
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ EDocumentPage.Send_Promoted.Invoke();
+ EDocumentPage.Close();
+
+ EDocument.FindLast();
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+
+ // [Then] E-Document is pending response as Tietoevry is async
+ Assert.AreEqual(Enum::"E-Document Status"::"In Progress", EDocument.Status, 'E-Document should be set to in progress');
+
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Pending Response"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('4', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+
+ SetTietoevryConnectionBaseUrl('/Tietoevry/response-complete');
+
+ JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"E-Document Get Response");
+ LibraryJobQueue.RunJobQueueDispatcher(JobQueueEntry);
+
+ // [When] EDocument is fetched after running Tietoevry GetResponse
+
+ EDocument.FindLast();
+
+ // [Then] E-Document is pending response as Tietoevry is async
+ Assert.AreEqual(Enum::"E-Document Status"::Processed, EDocument.Status, 'E-Document should be set to processed');
+
+ // [THEN] Open E-Document page
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has pending response
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::Sent), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('5', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Pending Response");
+ EDocLogList.Add(Enum::"E-Document Service Status"::Sent);
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ EDocumentPage.Close();
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure SubmitDocumentTietoevryServiceDown()
+ var
+ EDocument: Record "E-Document";
+ EDocumentPage: TestPage "E-Document";
+ EDocLogList: List of [Enum "E-Document Service Status"];
+ begin
+ Initialize();
+ SetAPIWith500Code();
+
+ // [Given] Team member
+ LibraryPermission.SetTeamMember();
+
+ // [When] Posting invoice and EDocument is created
+ LibraryEDocument.PostInvoice(Customer);
+ EDocument.FindLast();
+ LibraryEDocument.RunEDocumentJobQueue(EDocument);
+
+ // [When] EDocument is fetched after running Tietoevry SubmitDocument
+ EDocument.FindLast();
+
+ Assert.AreEqual(Enum::"E-Document Status"::Error, EDocument.Status, 'E-Document should be set to error state when service is down.');
+ Assert.AreEqual('', EDocument."Tietoevry Document Id", 'Tietoevry Document Id on E-Document should not be set.');
+
+ EDocumentPage.OpenView();
+ EDocumentPage.GoToRecord(EDocument);
+
+ // [THEN] E-Document has correct error status
+ Assert.AreEqual(Format(EDocument.Status::Error), EDocumentPage."Electronic Document Status".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(EDocument.Direction::Outgoing), EDocumentPage.Direction.Value(), IncorrectValueErr);
+ Assert.AreEqual(EDocument."Document No.", EDocumentPage."Document No.".Value(), IncorrectValueErr);
+
+ // [THEN] E-Document Service Status has correct error status
+ Assert.AreEqual(EDocumentService.Code, EDocumentPage.EdocoumentServiceStatus."E-Document Service Code".Value(), IncorrectValueErr);
+ Assert.AreEqual(Format(Enum::"E-Document Service Status"::"Sending Error"), EDocumentPage.EdocoumentServiceStatus.Status.Value(), IncorrectValueErr);
+ Assert.AreEqual('2', EDocumentPage.EdocoumentServiceStatus.Logs.Value(), IncorrectValueErr);
+
+ Clear(EDocLogList);
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Exported");
+ EDocLogList.Add(Enum::"E-Document Service Status"::"Sending Error");
+ LibraryEDocument.AssertEDocumentLogs(EDocument, EDocumentService, EDocLogList);
+
+ // [THEN] E-Document Errors and Warnings has correct status
+ Assert.AreEqual('Error', EDocumentPage.ErrorMessagesPart."Message Type".Value(), IncorrectValueErr);
+ Assert.AreEqual('Error Code: 500, Error Message: The HTTP request is not successful. An internal server error occurred.', EDocumentPage.ErrorMessagesPart.Description.Value(), IncorrectValueErr);
+ end;
+
+ ///
+ /// Test needs MockService running to work.
+ ///
+ [Test]
+ procedure SubmitGetDocuments()
+ var
+ EDocument: Record "E-Document";
+ PurchaseHeader: Record "Purchase Header";
+ EDocServicePage: TestPage "E-Document Service";
+ begin
+ Initialize();
+ SetAPIWithReceiveCode();
+ SetCompanyIdInConnectionSetup(MockCompanyId(), 'Mock Name');
+
+ // Open and close E-Doc page creates auto import job due to setting
+ EDocServicePage.OpenView();
+ EDocServicePage.GoToRecord(EDocumentService);
+ EDocServicePage."Resolve Unit Of Measure".SetValue(false);
+ EDocServicePage."Lookup Item Reference".SetValue(true);
+ EDocServicePage."Lookup Item GTIN".SetValue(false);
+ EDocServicePage."Lookup Account Mapping".SetValue(false);
+ EDocServicePage."Validate Line Discount".SetValue(false);
+ EDocServicePage.Close();
+
+ // Manually fire job queue job to import
+ LibraryEDocument.RunImportJob();
+
+ // Assert that we have Purchase Invoice created
+ EDocument.FindLast();
+ PurchaseHeader.Get(EDocument."Document Record ID");
+ Assert.AreEqual(Vendor."No.", PurchaseHeader."Buy-from Vendor No.", 'Wrong Vendor');
+ end;
+
+ local procedure Initialize()
+ var
+ ConnectionSetup: Record "Connection Setup";
+ CompanyInformation: Record "Company Information";
+ TietoevryAuth: Codeunit Authenticator;
+ KeyGuid: Guid;
+ begin
+ LibraryPermission.SetOutsideO365Scope();
+ // Clean up token between runs
+ if ConnectionSetup.Get() then
+ if IsolatedStorage.Delete(ConnectionSetup."Token - Key", DataScope::Company) then;
+
+ ConnectionSetup.DeleteAll();
+ TietoevryAuth.CreateConnectionSetupRecord();
+ SetAPIWith200Code();
+
+ ConnectionSetup.Get();
+ TietoevryAuth.SetClientId(KeyGuid, MockServiceGuid());
+ ConnectionSetup."Client Id - Key" := KeyGuid;
+ TietoevryAuth.SetClientSecret(KeyGuid, MockServiceGuid());
+ ConnectionSetup."Client Secret - Key" := KeyGuid;
+ ConnectionSetup.Modify(true);
+
+ if IsInitialized then
+ exit;
+
+ LibraryEDocument.SetupStandardVAT();
+ LibraryEDocument.SetupStandardSalesScenario(Customer, EDocumentService, Enum::"E-Document Format"::"PEPPOL BIS 3.0", Enum::"Service Integration"::Tietoevry);
+
+ LibraryEDocument.SetupStandardPurchaseScenario(Vendor, EDocumentService, Enum::"E-Document Format"::"PEPPOL BIS 3.0", Enum::"Service Integration"::Tietoevry);
+ EDocumentService."Auto Import" := true;
+ EDocumentService."Import Minutes between runs" := 10;
+ EDocumentService."Import Start Time" := Time();
+ EDocumentService.Modify();
+
+ Vendor."VAT Registration No." := 'GB777777771';
+ Vendor."Receive E-Document To" := Enum::"E-Document Type"::"Purchase Invoice";
+ Vendor.Modify();
+
+ CompanyInformation.Get();
+ CompanyInformation."VAT Registration No." := 'GB777777771';
+ CompanyInformation.Modify();
+
+ IsInitialized := true;
+ end;
+
+ local procedure SetTietoevryConnectionBaseUrl(Base: Text)
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ ConnectionSetup.Get();
+ ConnectionSetup."API URL" := SetMockServiceUrl(Base);
+ ConnectionSetup."Sandbox API URL" := ConnectionSetup."API URL";
+ ConnectionSetup.Modify(true);
+ end;
+
+ local procedure SetCompanyIdInConnectionSetup(Id: Text[100]; Name: Text[100])
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ ConnectionSetup.Get();
+ ConnectionSetup."Company Id" := Id;
+ ConnectionSetup.Modify(true);
+ end;
+
+ local procedure SetAPIWithReceiveCode()
+ begin
+ SetAPICode('/Tietoevry/200/receive');
+ end;
+
+ local procedure SetAPIWith200Code()
+ begin
+ SetAPICode('/Tietoevry/200');
+ end;
+
+ local procedure SetAPIWith500Code()
+ begin
+ SetAPICode('/Tietoevry/500');
+ end;
+
+ local procedure SetAPICode(Path: Text)
+ var
+ ConnectionSetup: Record "Connection Setup";
+ begin
+ ConnectionSetup.Get();
+ ConnectionSetup."API URL" := SetMockServiceUrl(Path);
+ ConnectionSetup."Authentication URL" := SetMockServiceUrl(Path);
+ ConnectionSetup."Sandbox API URL" := ConnectionSetup."API URL";
+ ConnectionSetup."Sandbox Authentication URL" := ConnectionSetup."Authentication URL";
+ ConnectionSetup."Send Mode" := ConnectionSetup."Send Mode"::Test;
+ ConnectionSetup.Modify(true);
+ end;
+
+ // Mock values used in mock response files.
+ // Do not change without fixing MockService files
+
+ local procedure SetMockServiceUrl(Path: Text): Text[200]
+ begin
+ exit('https://localhost:8080' + Path);
+ end;
+
+ local procedure MockServiceGuid(): Text
+ begin
+ exit('1590fa93-f12c-446c-8e41-c86d082fe3e0');
+ end;
+
+ local procedure MockServiceDocumentId(): Text
+ begin
+ exit('52f60401-44d0-4667-ad47-4afe519abb53');
+ end;
+
+ local procedure MockCompanyId(): Text[100]
+ begin
+ exit('610f55f3-76b6-42eb-a697-2b0b2e02a5bf');
+ end;
+
+ [ModalPageHandler]
+ internal procedure EDocServicesPageHandler(var EDocServicesPage: TestPage "E-Document Services")
+ begin
+ EDocServicesPage.Filter.SetFilter(Code, EDocumentService.Code);
+ EDocServicesPage.OK().Invoke();
+ end;
+
+ var
+ Customer: Record Customer;
+ Vendor: Record Vendor;
+ EDocumentService: Record "E-Document Service";
+ LibraryEDocument: Codeunit "Library - E-Document";
+ LibraryPermission: Codeunit "Library - Lower Permissions";
+ LibraryJobQueue: Codeunit "Library - Job Queue";
+ Assert: Codeunit Assert;
+ IsInitialized: Boolean;
+ IncorrectValueErr: Label 'Wrong value';
+
+}
\ No newline at end of file