From 7a82f1925922c65b96a58d68ccf8c5c206bbebcc Mon Sep 17 00:00:00 2001 From: Teodoras Stefanovicius Date: Mon, 19 Aug 2024 17:25:38 +0300 Subject: [PATCH] [E-Document Connector] Logiq E-Document Connector #27058 --- Apps/W1/EDocumentsConnector/app/app.json | 2 +- .../app/src/Logiq/LogiqAPIEngine.Enum.al | 19 + .../app/src/Logiq/LogiqAuth.Codeunit.al | 182 +++++++++ .../src/Logiq/LogiqConnectionSetup.Page.al | 98 +++++ .../src/Logiq/LogiqConnectionSetup.Table.al | 69 ++++ .../Logiq/LogiqConnectionUserSetup.Page.al | 126 ++++++ .../Logiq/LogiqConnectionUserSetup.Table.al | 146 +++++++ .../Logiq/LogiqEDocServiceStatus.EnumExt.al | 16 + .../app/src/Logiq/LogiqEDocument.PageExt.al | 45 +++ .../app/src/Logiq/LogiqEDocument.TableExt.al | 15 + .../LogiqEDocumentIntegration.Codeunit.al | 54 +++ .../LogiqEDocumentIntegration.EnumExt.al | 12 + .../LogiqEDocumentManagement.Codeunit.al | 380 ++++++++++++++++++ .../EDocConnectorEdit.PermissionSet.al | 4 +- .../EDocConnectorObjects.PermissionSet.al | 9 +- .../EDocConnectorRead.PermissionSet.al | 4 +- 16 files changed, 1177 insertions(+), 4 deletions(-) create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAPIEngine.Enum.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAuth.Codeunit.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Page.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Table.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Page.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Table.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocServiceStatus.EnumExt.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.PageExt.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.TableExt.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.Codeunit.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.EnumExt.al create mode 100644 Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentManagement.Codeunit.al diff --git a/Apps/W1/EDocumentsConnector/app/app.json b/Apps/W1/EDocumentsConnector/app/app.json index 193d264aab..021940a754 100644 --- a/Apps/W1/EDocumentsConnector/app/app.json +++ b/Apps/W1/EDocumentsConnector/app/app.json @@ -26,7 +26,7 @@ "idRanges": [ { "from": 6360, - "to": 6369 + "to": 6389 } ], "resourceExposurePolicy": { diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAPIEngine.Enum.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAPIEngine.Enum.al new file mode 100644 index 0000000000..5c8e42f8a2 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAPIEngine.Enum.al @@ -0,0 +1,19 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +enum 6380 "Logiq API Engine" +{ + Extensible = false; + + value(0; " ") + { + Caption = '', Locked = true; + } + value(1; Engine1) + { + Caption = 'Engine 1'; + } + value(2; Engine3) + { + Caption = 'Engine 3'; + } +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAuth.Codeunit.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAuth.Codeunit.al new file mode 100644 index 0000000000..954545cee6 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAuth.Codeunit.al @@ -0,0 +1,182 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +codeunit 6380 "Logiq Auth" +{ + internal procedure SetIsolatedStorageValue(var ValueKey: Guid; Value: SecretText; TokenDataScope: DataScope) + begin + if IsNullGuid(ValueKey) then + ValueKey := CreateGuid(); + + IsolatedStorage.Set(ValueKey, Value, TokenDataScope); + end; + + internal procedure SetIsolatedStorageValue(var ValueKey: Guid; Value: SecretText) + begin + SetIsolatedStorageValue(ValueKey, Value, DataScope::Company); + end; + + internal procedure GetIsolatedStorageValue(var ValueKey: Guid; var Value: SecretText; TokenDataScope: DataScope) + begin + if IsNullGuid(ValueKey) then + exit; + IsolatedStorage.Get(ValueKey, TokenDataScope, Value); + end; + + internal procedure GetIsolatedStorageValue(var ValueKey: Guid; var Value: SecretText) + begin + GetIsolatedStorageValue(ValueKey, Value, DataScope::Company); + end; + + [NonDebuggable] + internal procedure GetTokens() + var + LogiqConnectionSetup: Record "Logiq Connection Setup"; + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + Client: HttpClient; + Headers: HttpHeaders; + Content: HttpContent; + RequestMessage: HttpRequestMessage; + ResponseMessage: HttpResponseMessage; + AccessToken, RefreshToken : SecretText; + AccessTokExpires, RefreshTokExpires : DateTime; + AuthenticationFailedErr: Label 'Logiq authentication failed. Please check the user credentials.'; + begin + CheckSetup(LogiqConnectionSetup); + CheckUserCredentials(LogiqConnectionUserSetup); + + RequestMessage.Method('POST'); + RequestMessage.SetRequestUri(LogiqConnectionSetup."Authentication URL"); + + BuildTokenRequestBody(Content); + + Content.GetHeaders(Headers); + Headers.Clear(); + Headers.Add('Content-Type', 'application/x-www-form-urlencoded'); + + RequestMessage.Content(Content); + + Client.Send(RequestMessage, ResponseMessage); + + if not ResponseMessage.IsSuccessStatusCode() then begin + if GuiAllowed then + Message(AuthenticationFailedErr); + exit; + end; + + ParseTokens(ResponseMessage, AccessToken, RefreshToken, AccessTokExpires, RefreshTokExpires); + + SaveTokens(AccessToken, RefreshToken, AccessTokExpires, RefreshTokExpires); + end; + + local procedure BuildTokenRequestBody(var Content: HttpContent) + var + LogiqConnectionSetup: Record "Logiq Connection Setup"; + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + BodyText: SecretText; + CredentialsBodyTok: Label 'grant_type=password&scope=openid&client_id=%1&client_secret=%2&username=%3&password=%4', Locked = true; + RefreshTokenBodyTok: Label 'grant_type=refresh_token&client_id=%1&client_secret=%2&refresh_token=%3', Locked = true; + begin + LogiqConnectionSetup.Get(); + LogiqConnectionUserSetup.Get(UserId()); + + if (not IsNullGuid(LogiqConnectionUserSetup."Refresh Token")) and (LogiqConnectionUserSetup."Refresh Token Expiration" > (CurrentDateTime + 60 * 1000)) then + BodyText := SecretText.SecretStrSubstNo(RefreshTokenBodyTok, LogiqConnectionSetup."Client ID", LogiqConnectionSetup.GetClientSecret(), LogiqConnectionUserSetup.GetRefreshToken()) + else + BodyText := SecretText.SecretStrSubstNo(CredentialsBodyTok, LogiqConnectionSetup."Client ID", LogiqConnectionSetup.GetClientSecret(), LogiqConnectionUserSetup.Username, LogiqConnectionUserSetup.GetPassword()); + + Content.WriteFrom(BodyText); + end; + + internal procedure CheckUserCredentials(var LogiqConnectionUserSetup: Record "Logiq Connection User Setup") + var + NoSetupErr: Label 'No user setup found. Please fill the user setup in the Logiq Connection User Setup page.'; + MissingCredentialsErr: Label 'User credentials are missing. Please enter username and password in the Logiq Connection User Setup page.'; + begin + if not LogiqConnectionUserSetup.Get(UserId()) then + Error(NoSetupErr); + + if (LogiqConnectionUserSetup.Username = '') or (IsNullGuid(LogiqConnectionUserSetup."Password")) then + Error(MissingCredentialsErr); + end; + + internal procedure CheckUserSetup(var LogiqConnectionUserSetup: Record "Logiq Connection User Setup") + var + MissingAPIEngineErr: Label 'API Engine is missing. Please select the API Engine in the Logiq Connection User Setup page.'; + MissingEndpointsErr: Label 'Endpoints are missing. Please fill the Document Transfer Endpoint and Document Status Endpoint in the Logiq Connection User Setup page.'; + begin + CheckUserCredentials(LogiqConnectionUserSetup); + + if (LogiqConnectionUserSetup."API Engine" = LogiqConnectionUserSetup."API Engine"::" ") then + Error(MissingAPIEngineErr); + + if (LogiqConnectionUserSetup."Document Transfer Endpoint" = '') or (LogiqConnectionUserSetup."Document Status Endpoint" = '') then + Error(MissingEndpointsErr); + end; + + internal procedure CheckSetup(var LogiqConnectionSetup: Record "Logiq Connection Setup") + var + NoSetupErr: Label 'No setup found. Please fill the setup in the Logiq Connection Setup page.'; + MissingClientInfoErr: Label 'Client ID or Client Secret is missing. Please fill the Client ID and Client Secret in the Logiq Connection Setup page.'; + MissingAuthUrlErr: Label 'Authentication URL is missing. Please fill the Authentication URL in the Logiq Connection Setup page.'; + MissingBaseUrlErr: Label 'Base URL is missing. Please fill the API Base URL in the Logiq Connection Setup page.'; + begin + if not LogiqConnectionSetup.Get() then + Error(NoSetupErr); + + if (LogiqConnectionSetup."Client ID" = '') or (IsNullGuid(LogiqConnectionSetup."Client Secret")) then + Error(MissingClientInfoErr); + + if LogiqConnectionSetup."Authentication URL" = '' then + Error(MissingAuthUrlErr); + + if LogiqConnectionSetup."Base URL" = '' then + Error(MissingBaseUrlErr); + end; + + [NonDebuggable] + local procedure ParseTokens(ResponseMessage: HttpResponseMessage; var AccessToken: SecretText; var RefreshToken: SecretText; var AccessTokExpires: DateTime; var RefreshTokExpires: DateTime) + var + ContentJson: JsonObject; + JsonTok: JsonToken; + ResponseTxt: Text; + begin + ResponseMessage.Content.ReadAs(ResponseTxt); + ContentJson.ReadFrom(ResponseTxt); + if ContentJson.Get('access_token', JsonTok) then + AccessToken := JsonTok.AsValue().AsText(); + if ContentJson.Get('refresh_token', JsonTok) then + RefreshToken := JsonTok.AsValue().AsText(); + if ContentJson.Get('expires_in', JsonTok) then + AccessTokExpires := CurrentDateTime + JsonTok.AsValue().AsInteger() * 1000; + if ContentJson.Get('refresh_expires_in', JsonTok) then + RefreshTokExpires := CurrentDateTime + JsonTok.AsValue().AsInteger() * 1000; + end; + + local procedure SaveTokens(AccessToken: SecretText; RefreshToken: SecretText; AccessTokExpires: DateTime; RefreshTokExpires: DateTime) + var + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + begin + LogiqConnectionUserSetup.Get(UserId()); + SetIsolatedStorageValue(LogiqConnectionUserSetup."Access Token", AccessToken, DataScope::User); + SetIsolatedStorageValue(LogiqConnectionUserSetup."Refresh Token", RefreshToken, DataScope::User); + LogiqConnectionUserSetup."Access Token Expiration" := AccessTokExpires; + LogiqConnectionUserSetup."Refresh Token Expiration" := RefreshTokExpires; + LogiqConnectionUserSetup.Modify(false); + end; + + internal procedure HasToken(ValueKey: Guid; DataScope: DataScope): Boolean + begin + exit(IsolatedStorage.Contains(ValueKey, DataScope)); + end; + + internal procedure CheckUpdateTokens() + var + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + NoSetupErr: Label 'No user setup found. Please fill the user setup in the Logiq Connection User Setup page.'; + begin + if not LogiqConnectionUserSetup.Get(UserId()) then + Error(NoSetupErr); + if IsNullGuid(LogiqConnectionUserSetup."Access Token") or (LogiqConnectionUserSetup."Access Token Expiration" < (CurrentDateTime + 5 * 60 * 1000)) then + GetTokens(); + end; +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Page.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Page.al new file mode 100644 index 0000000000..a2416d8344 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Page.al @@ -0,0 +1,98 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +page 6380 "Logiq Connection Setup" +{ + Caption = 'E-Document External Connection Setup'; + PageType = Card; + ApplicationArea = Basic, Suite; + UsageCategory = None; + SourceTable = "Logiq Connection Setup"; + + layout + { + area(Content) + { + group(General) + { + Caption = 'General'; + + field(ClientID; Rec."Client ID") + { + Caption = 'Client ID'; + ToolTip = 'Specifies the client ID token.'; + ShowMandatory = true; + } + field(ClientSecret; ClientSecret) + { + Caption = 'Client Secret'; + ToolTip = 'Specifies the client secret token.'; + ExtendedDatatype = Masked; + ShowMandatory = true; + + trigger OnValidate() + begin + LogiqAuth.SetIsolatedStorageValue(Rec."Client Secret", ClientSecret); + end; + } + field("Authentication URL"; Rec."Authentication URL") + { + ToolTip = 'Specifies the Authorization URL.'; + } + field("Base URL"; Rec."Base URL") + { + ToolTip = 'Specifies the Base URL.'; + } + field("File List Endpoint"; Rec."File List Endpoint") + { + ToolTip = 'Specifies the Endpoint to list available files.'; + } + } + } + } + + actions + { + area(Processing) + { + action(Connect) + { + ApplicationArea = All; + Caption = 'User Setup'; + Image = Setup; + ToolTip = 'Open page for User Setup.'; + + trigger OnAction() + var + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + LoqiqConnectionUserSetupPage: Page "Logiq Connection User Setup"; + begin + LogiqConnectionUserSetup.FindUserSetup(CopyStr(UserId(), 1, 50)); + LoqiqConnectionUserSetupPage.SetRecord(LogiqConnectionUserSetup); + LoqiqConnectionUserSetupPage.Run(); + end; + } + } + area(Promoted) + { + actionref(Connect_Promoted; Connect) + { + } + } + } + + var + LogiqAuth: Codeunit "Logiq Auth"; + [NonDebuggable] + ClientSecret: Text; + + trigger OnOpenPage() + begin + if not Rec.Get() then begin + Rec.Init(); + Rec.Insert(true); + end; + + if LogiqAuth.HasToken(Rec."Client Secret", DataScope::Company) then + ClientSecret := '*'; + end; +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Table.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Table.al new file mode 100644 index 0000000000..03a005da28 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionSetup.Table.al @@ -0,0 +1,69 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +table 6380 "Logiq Connection Setup" +{ + Caption = 'Logiq Connection Setup'; + DataClassification = CustomerContent; + fields + { + field(1; PK; Code[10]) + { + DataClassification = CustomerContent; + } + field(21; "Authentication URL"; Text[100]) + { + Caption = 'Authorization URL'; + DataClassification = CustomerContent; + } + field(22; "Base URL"; Text[100]) + { + Caption = 'Base URL'; + DataClassification = CustomerContent; + } + field(25; "File List Endpoint"; Text[100]) + { + Caption = 'File List Endpoint'; + DataClassification = CustomerContent; + } + field(31; "Client ID"; Text[100]) + { + Caption = 'Client ID'; + DataClassification = EndUserIdentifiableInformation; + } + field(32; "Client Secret"; Guid) + { + Caption = 'Client Secret'; + DataClassification = EndUserIdentifiableInformation; + } + } + + keys + { + key(PK; PK) + { + Clustered = true; + } + } + + var + LogiqAuth: Codeunit "Logiq Auth"; + + trigger OnInsert() + var + AuthenticationUrlTok: Label 'https://pilot-sso.logiq.no/auth/realms/connect-api/protocol/openid-connect/token', Locked = true; + FileListTok: Label '1.0/listfiles', Locked = true; + BaseUrlTok: Label 'https://pilot-api.logiq.no/edi/connect/', Locked = true; + begin + Rec."Authentication URL" := AuthenticationUrlTok; + Rec."File List Endpoint" := FileListTok; + Rec."Base URL" := BaseUrlTok; + end; + + internal procedure GetClientSecret(): SecretText + var + ClientSecret: SecretText; + begin + LogiqAuth.GetIsolatedStorageValue(Rec."Client Secret", ClientSecret); + exit(ClientSecret); + end; +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Page.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Page.al new file mode 100644 index 0000000000..94570d667e --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Page.al @@ -0,0 +1,126 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +page 6381 "Logiq Connection User Setup" +{ + Caption = 'Logiq Connection User Setup'; + PageType = Card; + DeleteAllowed = false; + InsertAllowed = false; + LinksAllowed = false; + ShowFilter = false; + SourceTable = "Logiq Connection User Setup"; + UsageCategory = None; + ApplicationArea = Basic, Suite; + + layout + { + area(Content) + { + group(General) + { + Caption = 'General'; + + field(Username; Rec.Username) + { + ShowMandatory = true; + + trigger OnValidate() + begin + if IsFullCredentials() then + CheckCredentialsAndUpdateTokens(); + end; + } + field(Password; PasswordTxt) + { + Caption = 'Password'; + ToolTip = 'Specifies the user password.'; + ExtendedDatatype = Masked; + ShowMandatory = true; + + trigger OnValidate() + begin + if PasswordTxt = '' then + Rec.DeletePassword() + else + LogiqAuth.SetIsolatedStorageValue(Rec.Password, PasswordTxt, DataScope::User); + + if IsFullCredentials() then + CheckCredentialsAndUpdateTokens(); + end; + } + field("Access Token Expiration"; Rec."Access Token Expiration") + { + Editable = false; + } + field("Refresh Token Expiration"; Rec."Refresh Token Expiration") + { + Editable = false; + } + field("API Engine"; Rec."API Engine") + { + ToolTip = 'Specifies the value of the API Engine field.'; + } + field("Document Transfer Endpoint"; Rec."Document Transfer Endpoint") + { + ToolTip = 'Specifies the value of the Document Transfer Endpoint field.'; + } + field("Document Status Endpoint"; Rec."Document Status Endpoint") + { + ToolTip = 'Specifies the value of the Document Status Endpoint field.'; + } + } + } + } + + actions + { + area(Processing) + { + action("Get Tokens") + { + ApplicationArea = All; + Caption = 'Get Tokens'; + ToolTip = 'Get Logiq access tokens for current user.'; + Image = Setup; + + trigger OnAction() + begin + LogiqAuth.GetTokens(); + end; + } + } + area(Promoted) + { + group(Category_Process) + { + actionref("Get Tokens_Promoted"; "Get Tokens") + { + } + } + } + } + + var + LogiqAuth: Codeunit "Logiq Auth"; + [NonDebuggable] + PasswordTxt: Text; + + trigger OnOpenPage() + begin + if not IsNullGuid(Rec.Password) then + if LogiqAuth.HasToken(Rec.Password, DataScope::User) then + PasswordTxt := '*'; + end; + + local procedure IsFullCredentials(): Boolean + begin + exit((Rec.Username <> '') and (PasswordTxt <> '')); + end; + + local procedure CheckCredentialsAndUpdateTokens(): Boolean + begin + Rec.DeleteUserTokens(); + CurrPage.Update(); + LogiqAuth.GetTokens(); + end; +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Table.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Table.al new file mode 100644 index 0000000000..4218d029d8 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqConnectionUserSetup.Table.al @@ -0,0 +1,146 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +using System.Security.AccessControl; + +table 6381 "Logiq Connection User Setup" +{ + Caption = 'Logiq Connection User Setup'; + DataClassification = CustomerContent; + + fields + { + field(1; "User ID"; Text[50]) + { + Caption = 'User ID'; + TableRelation = User."User Name"; + } + field(21; Username; Text[100]) + { + Caption = 'Username'; + ToolTip = 'Specifies the user name.'; + } + field(22; Password; Guid) + { + Caption = 'Password'; + } + field(23; "Access Token"; Guid) + { + Caption = 'Access Token'; + } + field(24; "Access Token Expiration"; DateTime) + { + Caption = 'Access Token Expires At'; + ToolTip = 'Specifies the access token expiration date.'; + } + field(25; "Refresh Token"; Guid) + { + Caption = 'Refresh Token'; + } + field(26; "Refresh Token Expiration"; DateTime) + { + Caption = 'Refresh Token Expires At'; + ToolTip = 'Specifies the refresh token expiration date.'; + } + field(31; "API Engine"; Enum "Logiq API Engine") + { + Caption = 'API Engine'; + DataClassification = CustomerContent; + ToolTip = 'Specifies the value of the API Engine field.'; + trigger OnValidate() + var + Engine1TransferTok: Label '2.0/transfer', Locked = true; + Engine1StatusTok: Label '2.0/transfer-status/externalId/', Locked = true; + Engine3TransferTok: Label '2.0/send', Locked = true; + Engine3StatusTok: Label '2.0/status/externalId/', Locked = true; + begin + case Rec."API Engine" of + Rec."API Engine"::Engine1: + begin + Rec."Document Transfer Endpoint" := Engine1TransferTok; + Rec."Document Status Endpoint" := Engine1StatusTok; + end; + Rec."API Engine"::Engine3: + begin + Rec."Document Transfer Endpoint" := Engine3TransferTok; + Rec."Document Status Endpoint" := Engine3StatusTok; + end; + end; + end; + } + field(32; "Document Transfer Endpoint"; Text[100]) + { + Caption = 'Document Transfer Endpoint'; + DataClassification = CustomerContent; + ToolTip = 'Specifies the Document Transfer Endpoint.'; + } + field(33; "Document Status Endpoint"; Text[100]) + { + Caption = 'Document Status Endpoint'; + DataClassification = CustomerContent; + ToolTip = 'Specifies the Document Status Endpoint.'; + } + } + keys + { + key(PK; "User ID") + { + Clustered = true; + } + } + + var + LogiqAuth: Codeunit "Logiq Auth"; + + internal procedure FindUserSetup(UserID: Text[50]) + begin + if not Rec.Get(UserID) then begin + Rec.Init(); + Rec."User ID" := UserID; + Rec.Insert(false); + end; + end; + + internal procedure GetPassword(): SecretText + var + ClientSecret: SecretText; + begin + LogiqAuth.GetIsolatedStorageValue(Rec.Password, ClientSecret, DataScope::User); + exit(ClientSecret); + end; + + internal procedure GetAccessToken(): SecretText + var + AccessToken: SecretText; + begin + LogiqAuth.GetIsolatedStorageValue(Rec."Access Token", AccessToken, DataScope::User); + exit(AccessToken); + end; + + internal procedure GetRefreshToken(): SecretText + var + RefreshToken: SecretText; + begin + LogiqAuth.GetIsolatedStorageValue(Rec."Refresh Token", RefreshToken, DataScope::User); + exit(RefreshToken); + end; + + internal procedure DeleteUserTokens() + begin + if (not IsNullGuid(Rec."Access Token")) then + if IsolatedStorage.Contains(Rec."Access Token", DataScope::User) then + IsolatedStorage.Delete(Rec."Access Token", DataScope::User); + if (not IsNullGuid(Rec."Refresh Token")) then + if IsolatedStorage.Contains(Rec."Refresh Token", DataScope::User) then + IsolatedStorage.Delete(Rec."Refresh Token", DataScope::User); + Rec."Access Token Expiration" := 0DT; + Rec."Refresh Token Expiration" := 0DT; + Rec.Modify(false); + end; + + internal procedure DeletePassword() + begin + if (not IsNullGuid(Rec.Password)) then + if IsolatedStorage.Contains(Rec.Password, DataScope::User) then + IsolatedStorage.Delete(Rec.Password, DataScope::User); + end; +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocServiceStatus.EnumExt.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocServiceStatus.EnumExt.al new file mode 100644 index 0000000000..5add156a41 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocServiceStatus.EnumExt.al @@ -0,0 +1,16 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +using Microsoft.eServices.EDocument; + +enumextension 6380 "Logiq E-Doc. Service Status" extends "E-Document Service Status" +{ + + value(6380; "In Progress Logiq") + { + Caption = 'In Progress'; + } + value(6381; "Failed Logiq") + { + Caption = 'Failed'; + } +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.PageExt.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.PageExt.al new file mode 100644 index 0000000000..5e19bc9284 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.PageExt.al @@ -0,0 +1,45 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +using Microsoft.eServices.EDocument; + +pageextension 6380 "Logiq E-Document" extends "E-Document" +{ + actions + { + addafter(GetApproval) + { + action(UpdateStatus) + { + Caption = 'Update Status'; + ApplicationArea = Basic, Suite; + Image = Status; + ToolTip = 'Check the status of the document in Logiq system.'; + + trigger OnAction() + var + EDocService: Record "E-Document Service"; + LogiqEDocumentManagement: Codeunit "Logiq E-Document Management"; + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + EDocServices: Page "E-Document Services"; + DocNotSentErr: Label 'Status can only be updated for documents that are succesfully sent to Logiq.'; + begin + if (Rec."Logiq External Id" = '') then + Error(DocNotSentErr); + + EDocServices.LookupMode := true; + if EDocServices.RunModal() = Action::LookupOK then begin + EDocServices.GetRecord(EDocService); + EDocumentErrorHelper.ClearErrorMessages(Rec); + LogiqEDocumentManagement.UpdateStatus(Rec, EDocService); + end; + end; + } + } + addlast(Category_Process) + { + actionref(UpdateStatus_Promoted; UpdateStatus) + { + } + } + } +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.TableExt.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.TableExt.al new file mode 100644 index 0000000000..3b1ea95ad2 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocument.TableExt.al @@ -0,0 +1,15 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +using Microsoft.eServices.EDocument; + +tableextension 6380 "Logiq E-Document" extends "E-Document" +{ + fields + { + field(50100; "Logiq External Id"; Text[50]) + { + Caption = 'Logiq External Id'; + DataClassification = ToBeClassified; + } + } +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.Codeunit.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.Codeunit.al new file mode 100644 index 0000000000..9972fd7b85 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.Codeunit.al @@ -0,0 +1,54 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +using Microsoft.eServices.EDocument; +using System.Utilities; + +codeunit 6381 "Logiq E-Document Integration" implements "E-Document Integration" +{ + Access = Internal; + + procedure Send(var EDocument: Record "E-Document"; var TempBlob: Codeunit "Temp Blob"; var IsAsync: Boolean; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage) + begin + LogiqEDocumentManagement.Send(EDocument, TempBlob, IsAsync, HttpRequest, HttpResponse); + end; + +#pragma warning disable AA0150 + procedure SendBatch(var EDocuments: Record "E-Document"; var TempBlob: Codeunit "Temp Blob"; var IsAsync: Boolean; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage) + begin + Error('Batch sending is not supported'); + end; +#pragma warning restore AA0150 + procedure GetResponse(var EDocument: Record "E-Document"; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage): Boolean + begin + Error('Getting response is not supported'); + end; + + procedure GetApproval(var EDocument: Record "E-Document"; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage): Boolean + begin + Error('Approval is not supported'); + end; + + procedure Cancel(var EDocument: Record "E-Document"; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage): Boolean + begin + Error('Cancelling sent document is not supported'); + end; + + procedure ReceiveDocument(var TempBlob: Codeunit "Temp Blob"; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage) + begin + LogiqEDocumentManagement.DownloadDocuments(TempBlob, HttpRequest, HttpResponse); + end; + + procedure GetDocumentCountInBatch(var TempBlob: Codeunit "Temp Blob"): Integer + begin + exit(LogiqEDocumentManagement.GetDocumentCountInBatch(TempBlob)); + end; + + procedure GetIntegrationSetup(var SetupPage: Integer; var SetupTable: Integer) + begin + SetupTable := Database::"Logiq Connection Setup"; + SetupPage := Page::"Logiq Connection Setup"; + end; + + var + LogiqEDocumentManagement: Codeunit "Logiq E-Document Management"; +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.EnumExt.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.EnumExt.al new file mode 100644 index 0000000000..38578fd294 --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentIntegration.EnumExt.al @@ -0,0 +1,12 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +using Microsoft.eServices.EDocument; + +enumextension 6381 "Logiq E-Document Integration" extends "E-Document Integration" +{ + value(6381; "Logiq") + { + Caption = 'Logiq'; + Implementation = "E-Document Integration" = "Logiq E-Document Integration"; + } +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentManagement.Codeunit.al b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentManagement.Codeunit.al new file mode 100644 index 0000000000..6c7707dfcf --- /dev/null +++ b/Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqEDocumentManagement.Codeunit.al @@ -0,0 +1,380 @@ +namespace JLogiqEDocumentsConnector.JLogiqEDocumentsConnector; + +using Microsoft.eServices.EDocument; +using System.Utilities; +using System.Xml; + +codeunit 6382 "Logiq E-Document Management" +{ + Access = Internal; + Permissions = tabledata "E-Document" = m; + +#pragma warning disable AA0150 + internal procedure Send(var EDocument: Record "E-Document"; var TempBlob: Codeunit "Temp Blob"; var IsAsync: Boolean; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage) + var + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + LogiqAuth: Codeunit "Logiq Auth"; + Client: HttpClient; + Headers: HttpHeaders; + Content: HttpContent; + ContentHeaders: HttpHeaders; + BodyText: Text; + FileNameText: Text; + Boundary: Text; + FileNameTok: Label '%1.xml', Locked = true; + ContentTypeTok: Label 'multipart/form-data; boundary="%1"', Locked = true; + begin + HttpRequest.Method('POST'); + + LogiqAuth.CheckUserSetup(LogiqConnectionUserSetup); + LogiqAuth.CheckUpdateTokens(); + + HttpRequest.SetRequestUri(BuildRequestUri(LogiqConnectionUserSetup."Document Transfer Endpoint")); + + HttpRequest.GetHeaders(Headers); + if Headers.Contains('Authorization') then + Headers.Remove('Authorization'); + Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', LogiqConnectionUserSetup.GetAccessToken())); + + FileNameText := StrSubstNo(FileNameTok, EDocument."Document No."); + Boundary := DelChr(Format(CreateGuid()), '<>=', '{}&[]*()!@#$%^+=;:"''<>,.?/|\\~`'); + + BodyText := GetFileContentAsMultipart(TempBlob, FileNameText, Boundary); + Content.WriteFrom(BodyText); + + Content.GetHeaders(ContentHeaders); + if ContentHeaders.Contains('Content-Type') then + ContentHeaders.Remove('Content-Type'); + ContentHeaders.Add('Content-Type', StrSubstNo(ContentTypeTok, Boundary)); + + HttpRequest.Content(Content); + + Client.Send(HttpRequest, HttpResponse); + + if HttpResponse.IsSuccessStatusCode() then + SaveLogiqExternalId(EDocument, GetExternalIdFromReponse(HttpResponse)) + else + LogSendingError(EDocument, HttpResponse); + end; +#pragma warning restore AA0150 + + local procedure SaveLogiqExternalId(EDocument: Record "E-Document"; ExternalId: Text[50]) + begin + if EDocument.Get(EDocument."Entry No") then begin + EDocument."Logiq External Id" := ExternalId; + EDocument.Modify(false); + end; + end; + + internal procedure UpdateStatus(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service") + var + Status: Enum "E-Document Service Status"; + HttpRequest: HttpRequestMessage; + HttpResponse: HttpResponseMessage; + RequestSuccessful: Boolean; + FailedHttpCallMsg: Label 'Failed to get status of document %1 in Logiq system. Http call returned status code %2 with error: %3', Comment = '%1=Document No., %2=HTTP status code, %3=error message'; + begin + if EDocumentService."Service Integration" <> EDocumentService."Service Integration"::Logiq then + exit; + + RequestSuccessful := GetStatus(EDocument, HttpRequest, HttpResponse); + + if RequestSuccessful then begin + Status := ParseDocumentStatus(HttpResponse); + EDocumentLogHelper.InsertLog(EDocument, EDocumentService, Status); + EDocumentLogHelper.InsertIntegrationLog(EDocument, EDocumentService, HttpRequest, HttpResponse); + end else begin + EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, StrSubstNo(FailedHttpCallMsg, EDocument."Document No.", HttpResponse.HttpStatusCode, HttpResponse.ReasonPhrase)); + if EDocument.Status <> EDocument.Status::Processed then + EDocumentLogHelper.InsertIntegrationLog(EDocument, EDocumentService, HttpRequest, HttpResponse); + Error(FailedHttpCallMsg, EDocument."Document No.", HttpResponse.HttpStatusCode, HttpResponse.ReasonPhrase); + end; + end; + + internal procedure GetStatus(var EDocument: Record "E-Document"; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage): Boolean + var + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + LogiqAuth: Codeunit "Logiq Auth"; + Client: HttpClient; + Headers: HttpHeaders; + begin + HttpRequest.Method('GET'); + + LogiqAuth.CheckUserSetup(LogiqConnectionUserSetup); + LogiqAuth.CheckUpdateTokens(); + + HttpRequest.SetRequestUri(BuildRequestUri(JoinUrlParts(LogiqConnectionUserSetup."Document Status Endpoint", EDocument."Logiq External Id"))); + + HttpRequest.GetHeaders(Headers); + if Headers.Contains('Authorization') then + Headers.Remove('Authorization'); + Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', LogiqConnectionUserSetup.GetAccessToken())); + + Client.Send(HttpRequest, HttpResponse); + + exit(HttpResponse.IsSuccessStatusCode()); + end; + + internal procedure DownloadDocuments(var TempBlob: Codeunit "Temp Blob"; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage) + var + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + LogiqConnectionSetup: Record "Logiq Connection Setup"; + LogiqAuth: Codeunit "Logiq Auth"; + Client: HttpClient; + Headers: HttpHeaders; + InStr: InStream; + OutStr: OutStream; + DownloadDocumentsErr: Label 'Failed to download documents from Logiq system. Http response code: %1; error: %2', Comment = '%1=HTTP response code,%2=error message'; + begin + HttpRequest.Method('GET'); + + LogiqAuth.CheckSetup(LogiqConnectionSetup); + LogiqAuth.CheckUserSetup(LogiqConnectionUserSetup); + LogiqAuth.CheckUpdateTokens(); + + HttpRequest.SetRequestUri(JoinUrlParts(LogiqConnectionSetup."Base URL", LogiqConnectionSetup."File List Endpoint")); + + HttpRequest.GetHeaders(Headers); + if Headers.Contains('Authorization') then + Headers.Remove('Authorization'); + Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', LogiqConnectionUserSetup.GetAccessToken())); + + Client.Send(HttpRequest, HttpResponse); + + if HttpResponse.IsSuccessStatusCode() then begin + HttpResponse.Content.ReadAs(InStr); + TempBlob.CreateOutStream(OutStr, TextEncoding::UTF8); + CopyStream(OutStr, InStr); + end else + Error(DownloadDocumentsErr, HttpResponse.HttpStatusCode, HttpResponse.ReasonPhrase); + end; + + internal procedure GetDocumentCountInBatch(var TempBlob: Codeunit "Temp Blob"): Integer + var + JsonArray: JsonArray; + InStr: InStream; + begin + TempBlob.CreateInStream(InStr, TextEncoding::UTF8); + + if not JsonArray.ReadFrom(InStr) then + exit(0); + + exit(JsonArray.Count()); + end; + + internal procedure DownloadFile(FileName: Text; var HttpRequest: HttpRequestMessage; var HttpResponse: HttpResponseMessage) + var + LogiqConnectionUserSetup: Record "Logiq Connection User Setup"; + LogiqConnectionSetup: Record "Logiq Connection Setup"; + LogiqAuth: Codeunit "Logiq Auth"; + Client: HttpClient; + Headers: HttpHeaders; + begin + HttpRequest.Method('GET'); + + LogiqAuth.CheckSetup(LogiqConnectionSetup); + LogiqAuth.CheckUserSetup(LogiqConnectionUserSetup); + LogiqAuth.CheckUpdateTokens(); + + HttpRequest.SetRequestUri(FileName); + + HttpRequest.GetHeaders(Headers); + if Headers.Contains('Authorization') then + Headers.Remove('Authorization'); + Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', LogiqConnectionUserSetup.GetAccessToken())); + + Client.Send(HttpRequest, HttpResponse); + end; + + local procedure BuildRequestUri(Endpoint: Text) FullUrl: Text + var + LogiqConnectionSetup: Record "Logiq Connection Setup"; + LogiqAuth: Codeunit "Logiq Auth"; + begin + LogiqAuth.CheckSetup(LogiqConnectionSetup); + + FullUrl := JoinUrlParts(LogiqConnectionSetup."Base URL", Endpoint); + end; + + local procedure JoinUrlParts(Part1: Text; Part2: Text) JoinedUrl: Text + begin + if Part1.EndsWith('/') then begin + if Part2.StartsWith('/') then + Part2 := Part2.Substring(2); + end else + if not Part2.StartsWith('/') then + Part2 := '/' + Part2; + + JoinedUrl := Part1 + Part2; + end; + + local procedure GetFileContentAsMultipart(FileBlob: Codeunit "Temp Blob"; FileName: Text; Boundary: Text): Text + var + XMLDOMManagement: Codeunit "XML DOM Management"; + MultiPartContentBuilder: TextBuilder; + ContentTok: Label 'Content-Disposition: form-data; name="bizDoc"; filename="%1"', Locked = true; + ContentTypeTok: Label 'Content-Type: text/xml', Locked = true; + FileNameTok: Label 'Content-Disposition: form-data; name="filename"', Locked = true; + BizDoc: Text; + InStr: InStream; + begin + MultiPartContentBuilder.AppendLine('--' + Format(Boundary)); + + // bizDoc + FileBlob.CreateInStream(InStr, TextEncoding::UTF8); + XMLDOMManagement.TryGetXMLAsText(InStr, BizDoc); + + MultiPartContentBuilder.AppendLine(StrSubstNo(ContentTok, FileName)); + MultiPartContentBuilder.AppendLine(ContentTypeTok); + MultiPartContentBuilder.AppendLine(''); + MultiPartContentBuilder.AppendLine(BizDoc); + + // filename + MultiPartContentBuilder.AppendLine('--' + Format(Boundary)); + MultiPartContentBuilder.AppendLine(FileNameTok); + MultiPartContentBuilder.AppendLine(''); + MultiPartContentBuilder.AppendLine(FileName); + + MultiPartContentBuilder.AppendLine('--' + Format(Boundary) + '--'); + exit(MultiPartContentBuilder.ToText()); + end; + + local procedure GetExternalIdFromReponse(ResponseMessage: HttpResponseMessage) ExternalId: Text[50] + var + ResponseTxt: Text; + JsonObj: JsonObject; + JsonTok: JsonToken; + InvalidResponseErr: Label 'Invalid response from Logiq E-Document API'; + begin + ResponseMessage.Content.ReadAs(ResponseTxt); + if not JsonObj.ReadFrom(ResponseTxt) then + Error(InvalidResponseErr); + + if JsonObj.Get('externalId', JsonTok) then + if JsonTok.IsValue() then + ExternalId := CopyStr(JsonTok.AsValue().AsText(), 1, MaxStrLen(ExternalId)) + else + if JsonTok.IsObject then begin + JsonObj := JsonTok.AsObject(); + if JsonObj.Get('value', JsonTok) then + ExternalId := CopyStr(JsonTok.AsValue().AsText(), 1, MaxStrLen(ExternalId)); + end; + end; + + local procedure LogSendingError(EDocument: Record "E-Document"; ResponseMessage: HttpResponseMessage) + var + EDocumentsErrorHelper: Codeunit "E-Document Error Helper"; + BlockedByEnvErr: Label 'Logiq E-Document API is blocked by environment'; + SendingFailedErr: Label 'Sending document failed with HTTP Status code %1. Error message: %2', Comment = '%1=HTTP Status code, %2=error message'; + begin + if ResponseMessage.IsBlockedByEnvironment() then + EDocumentsErrorHelper.LogSimpleErrorMessage(EDocument, BlockedByEnvErr) + else + EDocumentsErrorHelper.LogSimpleErrorMessage(EDocument, StrSubstNo(SendingFailedErr, ResponseMessage.HttpStatusCode(), ResponseMessage.ReasonPhrase())); + end; + + local procedure ParseReceivedFileName(ContentTxt: Text; Index: Integer; var FileName: Text): Boolean + var + JsonArray: JsonArray; + JsonObj: JsonObject; + JsonTok: JsonToken; + begin + if not JsonArray.ReadFrom(ContentTxt) then + exit(false); + + if Index > JsonArray.Count() then + exit(false); + + if Index = 0 then + JsonArray.Get(Index, JsonTok) + else + JsonArray.Get(Index - 1, JsonTok); + if not JsonTok.IsObject() then + exit(false); + + JsonObj := JsonTok.AsObject(); + if not JsonObj.Get('fileName', JsonTok) then + exit(false); + + FileName := JsonTok.AsValue().AsText(); + + if FileName = '' then + exit(false) + else + exit(true); + end; + + local procedure ParseDocumentStatus(Response: HttpResponseMessage) Status: Enum "E-Document Service Status" + var + InStr: InStream; + JsonObj: JsonObject; + JsonTok: JsonToken; + begin + Response.Content.ReadAs(InStr); + if not JsonObj.ReadFrom(InStr) then + exit(Status::"In Progress Logiq"); + + if JsonObj.Get('state', JsonTok) then + if JsonTok.IsValue() then + case JsonTok.AsValue().AsText() of + 'distributed': + exit(Status::Approved); + 'failed': + exit(Status::"Failed Logiq"); + else + exit(Status::"In Progress Logiq"); + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Import", OnAfterInsertImportedEdocument, '', false, false)] + local procedure OnAfterInsertEdocument(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; var TempBlob: Codeunit "Temp Blob"; EDocCount: Integer; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage) + var + LocalHttpRequest: HttpRequestMessage; + LocalHttpResponse: HttpResponseMessage; + DocumentOutStream: OutStream; + ContentData, FileName : Text; + FileNameNotFoundErr: Label 'File name not found in response'; + FileNotFoundErr: Label 'File %1 could not be downloaded', Comment = '%1=file name'; + begin + if EDocumentService."Service Integration" <> EDocumentService."Service Integration"::Logiq then + exit; + + HttpResponse.Content.ReadAs(ContentData); + if not ParseReceivedFileName(ContentData, EDocument."Index In Batch", FileName) then begin + EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, FileNameNotFoundErr); + exit; + end; + + DownloadFile(FileName, LocalHttpRequest, LocalHttpResponse); + EDocumentLogHelper.InsertIntegrationLog(EDocument, EDocumentService, LocalHttpRequest, LocalHttpResponse); + + LocalHttpResponse.Content.ReadAs(ContentData); + if ContentData = '' then + EDocumentErrorHelper.LogSimpleErrorMessage(EDocument, StrSubstNo(FileNotFoundErr, FileName)); + + Clear(TempBlob); + TempBlob.CreateOutStream(DocumentOutStream, TextEncoding::UTF8); + DocumentOutStream.WriteText(ContentData); + + EDocumentLogHelper.InsertLog(EDocument, EDocumentService, TempBlob, "E-Document Service Status"::Imported); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Document Log", OnUpdateEDocumentStatus, '', false, false)] + local procedure OnUpdateEDocumentStatus(var EDocument: Record "E-Document"; var IsHandled: Boolean) + var + EDocumentServiceStatus: Record "E-Document Service Status"; + begin + EDocumentServiceStatus.SetRange("E-Document Entry No", EDocument."Entry No"); + EDocumentServiceStatus.SetRange(Status, EDocumentServiceStatus.Status::"Failed Logiq"); + + if not EDocumentServiceStatus.IsEmpty() then begin + EDocument.Validate(Status, EDocument.Status::Error); + EDocument.Modify(false); + IsHandled := true; + end; + end; + + var + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + EDocumentLogHelper: Codeunit "E-Document Log Helper"; +} diff --git a/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorEdit.PermissionSet.al b/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorEdit.PermissionSet.al index 119ada8223..371649b8d7 100644 --- a/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorEdit.PermissionSet.al +++ b/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorEdit.PermissionSet.al @@ -10,5 +10,7 @@ permissionset 6361 "EDocConnector - Edit" Assignable = true; IncludedPermissionSets = "EDocConnector - Read"; - Permissions = tabledata "E-Doc. Ext. Connection Setup" = IM; + Permissions = tabledata "E-Doc. Ext. Connection Setup" = IM + tabledata "Logiq Connection Setup" = IM, + tabledata "Logiq Connection User Setup" = IM; } \ No newline at end of file diff --git a/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorObjects.PermissionSet.al b/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorObjects.PermissionSet.al index 10100bd37a..263d715afc 100644 --- a/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorObjects.PermissionSet.al +++ b/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorObjects.PermissionSet.al @@ -10,11 +10,18 @@ permissionset 6363 "EDoc. Connector Objects" Assignable = false; Permissions = table "E-Doc. Ext. Connection Setup" = X, + table "Logiq Connection Setup" = X, + table "Logiq Connection User Setup" = X, page "EDoc Ext Connection Setup Card" = X, + page "Logiq Connection Setup" = X, + page "Logiq Connection User Setup" = X, codeunit "Pagero API Requests" = X, codeunit "Pagero Auth." = X, codeunit "Pagero Connection" = X, codeunit "Pagero Integration Impl." = X, codeunit "Pagero Processing" = X, - codeunit "Pagero Application Response" = X; + codeunit "Pagero Application Response" = X, + codeunit "Logiq Auth" = X, + codeunit "Logiq E-Document Integration" = X, + codeunit "Logiq E-Document Management" = X; } \ No newline at end of file diff --git a/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorRead.PermissionSet.al b/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorRead.PermissionSet.al index 13aa38d033..9170b307c9 100644 --- a/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorRead.PermissionSet.al +++ b/Apps/W1/EDocumentsConnector/app/src/Permissions/EDocConnectorRead.PermissionSet.al @@ -10,5 +10,7 @@ permissionset 6362 "EDocConnector - Read" Assignable = true; IncludedPermissionSets = "EDoc. Connector Objects"; - Permissions = tabledata "E-Doc. Ext. Connection Setup" = R; + Permissions = tabledata "E-Doc. Ext. Connection Setup" = R + tabledata "Logiq Connection Setup" = R, + tabledata "Logiq Connection User Setup" = R; } \ No newline at end of file