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