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