From c8e7bbf89810c7db99a600b7bb37545bf5bf3785 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 21 Nov 2023 13:53:01 +0100 Subject: [PATCH] Uptake latest changes and add missing E-Document changes (#25263) Uptake latest changes and add missing E-Document changes. Those changes were missing because of the .gitignore file --- .github/AL-Go-Settings.json | 2 +- .gitignore | 2 +- .../GSTTaxConfiguration.Codeunit.al | 2 +- .../src/codeunit/GSTSettlement.Codeunit.al | 10 +- .../Codeunit/eInvoiceJsonHandler.Codeunit.al | 4 +- .../Codeunit/SubcontractingPost.Codeunit.al | 6 +- .../src/Page/SubOrderCompListVend.Page.al | 5 + .../src/Table/SubOrderCompListVend.Table.al | 7 + .../app/Translations/India GST.en-GB.xlf | 2 +- .../app/Translations/India GST.en-US.xlf | 2 +- .../app/src/BankAccRecAIInstall.Codeunit.al | 16 +- .../src/BankAccReconciliationExt.PageExt.al | 4 + .../BankAccReconciliationListExt.PageExt.al | 2 + .../app/src/BankRecAIMatchingImpl.Codeunit.al | 29 + .../app/src/Log/EDocDataStorage.Table.al | 36 + .../src/Log/EDocumentIntegrationLog.Table.al | 90 +++ .../src/Log/EDocumentIntegrationLogs.Page.al | 77 ++ .../app/src/Log/EDocumentLog.Codeunit.al | 268 +++++++ .../app/src/Log/EDocumentLog.Table.al | 125 +++ .../app/src/Log/EDocumentLogs.Page.al | 106 +++ .../test/src/Log/EDocLogTest.Codeunit.al | 751 ++++++++++++++++++ 21 files changed, 1518 insertions(+), 28 deletions(-) create mode 100644 Apps/W1/EDocument/app/src/Log/EDocDataStorage.Table.al create mode 100644 Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLog.Table.al create mode 100644 Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLogs.Page.al create mode 100644 Apps/W1/EDocument/app/src/Log/EDocumentLog.Codeunit.al create mode 100644 Apps/W1/EDocument/app/src/Log/EDocumentLog.Table.al create mode 100644 Apps/W1/EDocument/app/src/Log/EDocumentLogs.Page.al create mode 100644 Apps/W1/EDocument/test/src/Log/EDocLogTest.Codeunit.al diff --git a/.github/AL-Go-Settings.json b/.github/AL-Go-Settings.json index e9daaf8d3c..699de928b9 100644 --- a/.github/AL-Go-Settings.json +++ b/.github/AL-Go-Settings.json @@ -5,7 +5,7 @@ "runs-on": "windows-latest", "cacheImageName": "", "UsePsSession": false, - "artifact": "bcartifacts//23.1/base/latest", + "artifact": "bcartifacts//23.1.13431.14020/base", "country": "base", "useProjectDependencies": true, "repoVersion": "23.1", diff --git a/.gitignore b/.gitignore index 336291ba60..824e2a36ec 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ x86/ bld/ [Bb]in/ [Oo]bj/ -[Ll]og/ +/[Ll]og/ [Oo]ut/ # Visual Studio 2015 cache/options directory diff --git a/Apps/IN/INGST/app/GSTBase/src/TaxEngineSetup/GSTTaxConfiguration.Codeunit.al b/Apps/IN/INGST/app/GSTBase/src/TaxEngineSetup/GSTTaxConfiguration.Codeunit.al index 95085bdbb7..381fa51db8 100644 --- a/Apps/IN/INGST/app/GSTBase/src/TaxEngineSetup/GSTTaxConfiguration.Codeunit.al +++ b/Apps/IN/INGST/app/GSTBase/src/TaxEngineSetup/GSTTaxConfiguration.Codeunit.al @@ -323,7 +323,7 @@ codeunit 18017 "GST Tax Configuration" UseCases.Add('{75F37BC7-AA5D-483D-A8A0-653E94AD6B8D}', 3); UseCases.Add('{04F944A1-EF9C-440F-A89B-654782D13EAA}', 2); UseCases.Add('{2AB850AD-528A-498A-9E23-65E396AC61A8}', 2); - UseCases.Add('{F7C5C8B6-2EB3-478E-AE6B-66BEEB6A3861}', 7); + UseCases.Add('{F7C5C8B6-2EB3-478E-AE6B-66BEEB6A3861}', 8); UseCases.Add('{21957E13-9751-40A2-B591-67ADE93573E7}', 5); UseCases.Add('{1988B611-ABD4-44C4-9CB5-67BB88E0002C}', 1); UseCases.Add('{E5053EEB-44D1-4552-8084-67D72A90CECB}', 1); diff --git a/Apps/IN/INGST/app/GSTReturnSettlement/src/codeunit/GSTSettlement.Codeunit.al b/Apps/IN/INGST/app/GSTReturnSettlement/src/codeunit/GSTSettlement.Codeunit.al index 3aef3f36ed..cb6b36ab21 100644 --- a/Apps/IN/INGST/app/GSTReturnSettlement/src/codeunit/GSTSettlement.Codeunit.al +++ b/Apps/IN/INGST/app/GSTReturnSettlement/src/codeunit/GSTSettlement.Codeunit.al @@ -529,7 +529,7 @@ codeunit 18318 "GST Settlement" else GSTLiabilityBuffer[1]."GST Base Amount" := DetailedGSTLedgerEntry[1]."GST Base Amount"; - GSTLiabilityBuffer[1]."GST Amount" := GSTBaseValidation.RoundGSTPrecision(DetailedGSTLedgerEntry[1]."GST Amount" * CurrencyFactor); + GSTLiabilityBuffer[1]."GST Amount" := GSTBaseValidation.RoundGSTPrecisionThroughTaxComponent(DetailedGSTLedgerEntry[1]."GST Component Code", DetailedGSTLedgerEntry[1]."GST Amount" * CurrencyFactor); if (DetailedGSTLedgerEntry[1]."Cr. & Liab. Adjustment Type" = DetailedGSTLedgerEntry[1]."Cr. & Liab. Adjustment Type"::Generate) and @@ -540,7 +540,7 @@ codeunit 18318 "GST Settlement" else GSTLiabilityBuffer[1]."GST Base Amount" := DetailedGSTLedgerEntry[1]."Remaining Base Amount"; - GSTLiabilityBuffer[1]."GST Amount" := GSTBaseValidation.RoundGSTPrecision(DetailedGSTLedgerEntry[1]."Remaining GST Amount" * CurrencyFactor); + GSTLiabilityBuffer[1]."GST Amount" := GSTBaseValidation.RoundGSTPrecisionThroughTaxComponent(DetailedGSTLedgerEntry[1]."GST Component Code", DetailedGSTLedgerEntry[1]."Remaining GST Amount" * CurrencyFactor); end; if NatureOfAdj = NatureOfAdj::Reverse then begin @@ -549,7 +549,7 @@ codeunit 18318 "GST Settlement" else GSTLiabilityBuffer[1]."Applied Base Amount" := DetailedGSTLedgerEntry[1]."AdjustmentBase Amount"; - GSTLiabilityBuffer[1]."Applied Amount" := GSTBaseValidation.RoundGSTPrecision(DetailedGSTLedgerEntry[1]."Adjustment Amount" * CurrencyFactor); + GSTLiabilityBuffer[1]."Applied Amount" := GSTBaseValidation.RoundGSTPrecisionThroughTaxComponent(DetailedGSTLedgerEntry[1]."GST Component Code", DetailedGSTLedgerEntry[1]."Adjustment Amount" * CurrencyFactor); if DetailedGSTLedgerEntry[1]."GST Credit" = DetailedGSTLedgerEntry[1]."GST Credit"::Availment then GSTLiabilityBuffer[1]."Credit Amount" := GSTLiabilityBuffer[1]."Applied Amount"; end; @@ -1847,8 +1847,8 @@ codeunit 18318 "GST Settlement" TempGSTPostingBuffer1[1]."GST Group Code" := ''; TempGSTPostingBuffer1[1].Availment := DetailedGSTLedgerEntry."GST Credit" = DetailedGSTLedgerEntry."GST Credit"::Availment; TempGSTPostingBuffer1[1]."GST Group Type" := TempGSTPostingBuffer1[1]."GST Group Type"::Service; - TempGSTPostingBuffer1[1]."GST Base Amount" := GSTBaseValidation.RoundGSTPrecision(AppliedBaseAmount); - TempGSTPostingBuffer1[1]."GST Amount" := GSTBaseValidation.RoundGSTPrecision(AppliedAmount); + TempGSTPostingBuffer1[1]."GST Base Amount" := GSTBaseValidation.RoundGSTPrecisionThroughTaxComponent(DetailedGSTLedgerEntry."GST Component Code", AppliedBaseAmount); + TempGSTPostingBuffer1[1]."GST Amount" := GSTBaseValidation.RoundGSTPrecisionThroughTaxComponent(DetailedGSTLedgerEntry."GST Component Code", AppliedAmount); TempGSTPostingBuffer1[1]."GST %" := DetailedGSTLedgerEntry."GST %"; TempGSTPostingBuffer1[1]."Normal Payment" := DetailedGSTLedgerEntry."Payment Type" = "Payment Type"::Normal; TempGSTPostingBuffer1[1]."Dimension Set ID" := DimensionSetID; diff --git a/Apps/IN/INGST/app/GSTSales/src/Codeunit/eInvoiceJsonHandler.Codeunit.al b/Apps/IN/INGST/app/GSTSales/src/Codeunit/eInvoiceJsonHandler.Codeunit.al index 0a3e947dd2..2241dc9927 100644 --- a/Apps/IN/INGST/app/GSTSales/src/Codeunit/eInvoiceJsonHandler.Codeunit.al +++ b/Apps/IN/INGST/app/GSTSales/src/Codeunit/eInvoiceJsonHandler.Codeunit.al @@ -1219,7 +1219,7 @@ codeunit 18147 "e-Invoice Json Handler" SalesInvoiceLine."HSN/SAC Code", GstRate, SalesInvoiceLine.Quantity, CopyStr(SalesInvoiceLine."Unit of Measure Code", 1, 3), - SalesInvoiceLine."Unit Price", + Round(SalesInvoiceLine."Unit Price", 0.001, '='), SalesInvoiceLine."Line Amount" + SalesInvoiceLine."Line Discount Amount", SalesInvoiceLine."Line Discount Amount", 0, AssessableAmount, CGSTValue, SGSTValue, IGSTValue, CessRate, CesNonAdval, @@ -1274,7 +1274,7 @@ codeunit 18147 "e-Invoice Json Handler" SalesCrMemoLine."HSN/SAC Code", GstRate, SalesCrMemoLine.Quantity, CopyStr(SalesCrMemoLine."Unit of Measure Code", 1, 3), - SalesCrMemoLine."Unit Price", + Round(SalesCrMemoLine."Unit Price", 0.001, '='), SalesCrMemoLine."Line Amount" + SalesCrMemoLine."Line Discount Amount", SalesCrMemoLine."Line Discount Amount", 0, AssessableAmount, CGSTValue, SGSTValue, IGSTValue, CessRate, CesNonAdval, diff --git a/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingPost.Codeunit.al b/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingPost.Codeunit.al index f6a99e2648..a9edb17c7e 100644 --- a/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingPost.Codeunit.al +++ b/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingPost.Codeunit.al @@ -1274,8 +1274,10 @@ codeunit 18466 "Subcontracting Post" if (ItemLedgerEntry."Lot No." = '') and (ItemLedgerEntry."Serial No." = '') then ItemJnlLine.Validate("Applies-to Entry", ItemLedgerEntry."Entry No."); - ItemJnlLine."Location Code" := SubOrderCompListVendLocal."Vendor Location"; - ItemJnlLine."New Location Code" := SubOrderCompListVendLocal."Company Location"; + ItemJnlLine.Validate("Location Code", SubOrderCompListVendLocal."Vendor Location"); + ItemJnlLine.Validate("New Location Code", SubOrderCompListVendLocal."Company Location"); + if SubOrderCompListVendLocal."Bin Code" <> '' then + ItemJnlLine.Validate("New Bin Code", SubOrderCompListVendLocal."Bin Code"); ItemJnlLine."Variant Code" := SubOrderCompListVendLocal."Variant Code"; ItemJnlLine."Gen. Prod. Posting Group" := CompItem."Gen. Prod. Posting Group"; ItemJnlLine."Item Category Code" := CompItem."Item Category Code"; diff --git a/Apps/IN/INGST/app/GSTSubcontracting/src/Page/SubOrderCompListVend.Page.al b/Apps/IN/INGST/app/GSTSubcontracting/src/Page/SubOrderCompListVend.Page.al index 8d7b20d783..4978587505 100644 --- a/Apps/IN/INGST/app/GSTSubcontracting/src/Page/SubOrderCompListVend.Page.al +++ b/Apps/IN/INGST/app/GSTSubcontracting/src/Page/SubOrderCompListVend.Page.al @@ -87,6 +87,11 @@ page 18495 "Sub Order Comp. List Vend" ApplicationArea = Basic, Suite; ToolTip = 'Specifies the company location code for the document.'; } + field("Bin Code"; Rec."Bin Code") + { + ApplicationArea = Basic, Suite; + ToolTip = 'Specifies the company bin code for the document.'; + } field("Vendor Location"; Rec."Vendor Location") { ApplicationArea = Basic, Suite; diff --git a/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al b/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al index a84dda4ddd..2c85aecaf4 100644 --- a/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al +++ b/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al @@ -11,6 +11,7 @@ using Microsoft.Inventory.Ledger; using Microsoft.Inventory.Location; using Microsoft.Manufacturing.Document; using Microsoft.Purchases.Document; +using Microsoft.Warehouse.Structure; table 18478 "Sub Order Comp. List Vend" { @@ -348,6 +349,12 @@ table 18478 "Sub Order Comp. List Vend" Caption = 'SSI'; DataClassification = EndUserIdentifiableInformation; } + field(71; "Bin Code"; Code[20]) + { + Caption = 'Bin Code'; + TableRelation = if ("Qty. To Receive" = filter(> 0)) "Bin Content"."Bin Code" where("Location Code" = field("Company Location"), "Item No." = field("Item No."), "Variant Code" = field("Variant Code")); + DataClassification = EndUserIdentifiableInformation; + } field(89; "Qty. per Unit of Measure"; Decimal) { Caption = 'Qty. per Unit of Measure'; diff --git a/Apps/IN/INGST/app/Translations/India GST.en-GB.xlf b/Apps/IN/INGST/app/Translations/India GST.en-GB.xlf index 0f95363a3a..62c0a29477 100644 --- a/Apps/IN/INGST/app/Translations/India GST.en-GB.xlf +++ b/Apps/IN/INGST/app/Translations/India GST.en-GB.xlf @@ -10093,7 +10093,7 @@ GST Use Cases Codeunit GST Base Tax Engine Setup - Method GetConfig - NamedType {2AB850AD-528A-498A-9E23-65E396AC61A8}Lbl GST Use Cases - + Codeunit GST Base Tax Engine Setup - Method GetConfig - NamedType {F7C5C8B6-2EB3-478E-AE6B-66BEEB6A3861}Lbl diff --git a/Apps/IN/INGST/app/Translations/India GST.en-US.xlf b/Apps/IN/INGST/app/Translations/India GST.en-US.xlf index d9b465874b..9b721d7f5f 100644 --- a/Apps/IN/INGST/app/Translations/India GST.en-US.xlf +++ b/Apps/IN/INGST/app/Translations/India GST.en-US.xlf @@ -10093,7 +10093,7 @@ GST Use Cases Codeunit GST Base Tax Engine Setup - Method GetConfig - NamedType {2AB850AD-528A-498A-9E23-65E396AC61A8}Lbl GST Use Cases - + Codeunit GST Base Tax Engine Setup - Method GetConfig - NamedType {F7C5C8B6-2EB3-478E-AE6B-66BEEB6A3861}Lbl diff --git a/Apps/W1/BankAccRecWithAI/app/src/BankAccRecAIInstall.Codeunit.al b/Apps/W1/BankAccRecWithAI/app/src/BankAccRecAIInstall.Codeunit.al index 6d9c2130c4..57845b9f7d 100644 --- a/Apps/W1/BankAccRecWithAI/app/src/BankAccRecAIInstall.Codeunit.al +++ b/Apps/W1/BankAccRecWithAI/app/src/BankAccRecAIInstall.Codeunit.al @@ -1,8 +1,5 @@ namespace Microsoft.Bank.Reconciliation; -using System.AI; -using System.Environment; - codeunit 7252 "Bank Acc. Rec. AI Install" { Subtype = Install; @@ -10,18 +7,9 @@ codeunit 7252 "Bank Acc. Rec. AI Install" InherentEntitlements = X; trigger OnInstallAppPerDatabase() - begin - RegisterCapability(); - end; - - local procedure RegisterCapability() var - CopilotCapability: Codeunit "Copilot Capability"; - EnvironmentInformation: Codeunit "Environment Information"; - LearnMoreUrlTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2248547', Locked = true; + BankRecAIMatchingImpl: Codeunit "Bank Rec. AI Matching Impl."; begin - if EnvironmentInformation.IsSaaSInfrastructure() then - if not CopilotCapability.IsCapabilityRegistered(Enum::"Copilot Capability"::"Bank Account Reconciliation") then - CopilotCapability.RegisterCapability(Enum::"Copilot Capability"::"Bank Account Reconciliation", LearnMoreUrlTxt); + BankRecAIMatchingImpl.RegisterCapability(); end; } \ No newline at end of file diff --git a/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationExt.PageExt.al b/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationExt.PageExt.al index 6fd8133c8c..1a56e833d8 100644 --- a/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationExt.PageExt.al +++ b/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationExt.PageExt.al @@ -32,6 +32,8 @@ pageextension 7253 BankAccReconciliationExt extends "Bank Acc. Reconciliation" TransToGLAccAIProposal: Page "Trans. To GL Acc. AI Proposal"; LineNoFilter: Text; begin + BankRecAIMatchingImpl.RegisterCapability(); + if not AzureOpenAI.IsEnabled(Enum::"Copilot Capability"::"Bank Account Reconciliation") then exit; @@ -100,6 +102,8 @@ pageextension 7253 BankAccReconciliationExt extends "Bank Acc. Reconciliation" BankRecAIMatchingImpl: Codeunit "Bank Rec. AI Matching Impl."; AzureOpenAI: Codeunit "Azure OpenAI"; begin + BankRecAIMatchingImpl.RegisterCapability(); + if not AzureOpenAI.IsEnabled(Enum::"Copilot Capability"::"Bank Account Reconciliation") then exit; diff --git a/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationListExt.PageExt.al b/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationListExt.PageExt.al index 833542d421..b806b581ef 100644 --- a/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationListExt.PageExt.al +++ b/Apps/W1/BankAccRecWithAI/app/src/BankAccReconciliationListExt.PageExt.al @@ -27,6 +27,8 @@ pageextension 7254 BankAccReconciliationListExt extends "Bank Acc. Reconciliatio AzureOpenAI: Codeunit "Azure OpenAI"; BankAccRecAIProposal: Page "Bank Acc. Rec. AI Proposal"; begin + BankRecAIMatchingImpl.RegisterCapability(); + if not AzureOpenAI.IsEnabled(Enum::"Copilot Capability"::"Bank Account Reconciliation") then exit; diff --git a/Apps/W1/BankAccRecWithAI/app/src/BankRecAIMatchingImpl.Codeunit.al b/Apps/W1/BankAccRecWithAI/app/src/BankRecAIMatchingImpl.Codeunit.al index 7c9f73c919..87dced3bb8 100644 --- a/Apps/W1/BankAccRecWithAI/app/src/BankRecAIMatchingImpl.Codeunit.al +++ b/Apps/W1/BankAccRecWithAI/app/src/BankRecAIMatchingImpl.Codeunit.al @@ -1,9 +1,12 @@ namespace Microsoft.Bank.Reconciliation; using Microsoft.Bank.Ledger; +using Microsoft.Upgrade; using System.AI; using System.Azure.KeyVault; +using System.Environment; using System.Telemetry; +using System.Upgrade; codeunit 7250 "Bank Rec. AI Matching Impl." { @@ -553,6 +556,31 @@ codeunit 7250 "Bank Rec. AI Matching Impl." exit('Bank Account Reconciliation with AI'); end; + [EventSubscriber(ObjectType::Page, Page::"Copilot AI Capabilities", 'OnRegisterCopilotCapability', '', false, false)] + local procedure HandleOnRegisterCopilotCapability() + begin + RegisterCapability(); + end; + + procedure RegisterCapability() + var + CopilotCapability: Codeunit "Copilot Capability"; + EnvironmentInformation: Codeunit "Environment Information"; + UpgradeTag: Codeunit "Upgrade Tag"; + UpgradeTagDefinitions: Codeunit "Upgrade Tag Definitions"; + begin + if not EnvironmentInformation.IsSaaSInfrastructure() then + exit; + + if UpgradeTag.HasUpgradeTag(UpgradeTagDefinitions.GetRegisterBankAccRecCopilotCapabilityUpgradeTag()) then + exit; + + if not CopilotCapability.IsCapabilityRegistered(Enum::"Copilot Capability"::"Bank Account Reconciliation") then + CopilotCapability.RegisterCapability(Enum::"Copilot Capability"::"Bank Account Reconciliation", LearnMoreUrlTxt); + + UpgradeTag.SetUpgradeTag(UpgradeTagDefinitions.GetRegisterBankAccRecCopilotCapabilityUpgradeTag()); + end; + #if not CLEAN21 #pragma warning disable AL0432 #endif @@ -598,4 +626,5 @@ codeunit 7250 "Bank Rec. AI Matching Impl." TelemetryConstructingPromptFailedErr: label 'There was an error with constructing the chat completion prompt from the Key Vault.', Locked = true; TelemetryApproximateTokenCountExceedsLimitTxt: label 'The approximate token count for the Copilot request exceeded the limit. Sending request in chunks.', Locked = true; TelemetryChatCompletionErr: label 'Chat completion request was unsuccessful. Response code: %1', Locked = true; + LearnMoreUrlTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2248547', Locked = true; } \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Log/EDocDataStorage.Table.al b/Apps/W1/EDocument/app/src/Log/EDocDataStorage.Table.al new file mode 100644 index 0000000000..91d31dd2e9 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Log/EDocDataStorage.Table.al @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------------------------ +// 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.EDocument; + +table 6125 "E-Doc. Data Storage" +{ + DataClassification = CustomerContent; + + fields + { + field(1; "Entry No."; Integer) + { + Caption = 'Entry No.'; + AutoIncrement = true; + } + field(2; "Data Storage"; Blob) + { + Caption = 'Data Storage'; + } + + field(3; "Data Storage Size"; Integer) + { + Caption = 'Data Storage Size'; + } + } + + keys + { + key(Key1; "Entry No.") + { + Clustered = true; + } + } +} diff --git a/Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLog.Table.al b/Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLog.Table.al new file mode 100644 index 0000000000..f5b126e323 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLog.Table.al @@ -0,0 +1,90 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument; + +table 6127 "E-Document Integration Log" +{ + DataClassification = CustomerContent; + + fields + { + field(1; "Entry No."; Integer) + { + AutoIncrement = true; + Caption = 'Entry No'; + DataClassification = SystemMetadata; + } + field(2; "E-Doc. Entry No"; Integer) + { + Caption = 'E-Doc. Entry No'; + TableRelation = "E-Document"; + } + field(3; "Service Code"; Code[20]) + { + Caption = 'Service Code'; + TableRelation = "E-Document Service"; + } + field(4; "Request Blob"; BLOB) + { + Caption = 'Request Blob'; + } + field(5; "Response Blob"; BLOB) + { + Caption = 'Response Blob'; + } + field(6; "Response Status"; Integer) + { + Caption = 'Response Status'; + } + field(7; URL; Text[250]) + { + Caption = 'URL'; + } + field(8; Method; Text[10]) + { + Caption = 'Method'; + } + } + + keys + { + key(Key1; "Entry No.") + { + Clustered = true; + } + } + + var + EDcoumentRequestBlobTxt: Label 'E-Document_RequestMessage_%1.txt', Locked = true; + EDcoumentResponseBlobTxt: Label 'E-Document_ResponseMessage_%1.txt', Locked = true; + + internal procedure ExportRequestMessage() + var + InStr: InStream; + FileName: Text; + begin + Rec.CalcFields("Request Blob"); + if not Rec."Request Blob".HasValue() then + exit; + + Rec."Request Blob".CreateInStream(InStr); + FileName := StrSubstNo(EDcoumentRequestBlobTxt, "E-Doc. Entry No"); + DownloadFromStream(InStr, '', '', '', FileName); + end; + + internal procedure ExportResponseMessage() + var + InStr: InStream; + FileName: Text; + begin + Rec.CalcFields("Response Blob"); + if not Rec."Response Blob".HasValue() then + exit; + + Rec."Response Blob".CreateInStream(InStr); + FileName := StrSubstNo(EDcoumentResponseBlobTxt, "E-Doc. Entry No"); + DownloadFromStream(InStr, '', '', '', FileName); + end; +} diff --git a/Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLogs.Page.al b/Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLogs.Page.al new file mode 100644 index 0000000000..1e35cf98dd --- /dev/null +++ b/Apps/W1/EDocument/app/src/Log/EDocumentIntegrationLogs.Page.al @@ -0,0 +1,77 @@ +// ------------------------------------------------------------------------------------------------ +// 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.EDocument; + +page 6128 "E-Document Integration Logs" +{ + ApplicationArea = Basic, Suite; + Caption = 'E-Document Communication Logs'; + SourceTable = "E-Document Integration Log"; + PageType = List; + Editable = false; + + layout + { + area(Content) + { + group("Logs") + { + ShowCaption = false; + repeater(IntegrationLogs) + { + ShowCaption = false; + field("Entry No."; Rec."Entry No.") + { + ToolTip = 'Specifies the log entry no.'; + } + field("Service Code"; Rec."Service Code") + { + ToolTip = 'Specifies the service code for the document.'; + } + field(URL; Rec.URL) + { + ToolTip = 'Specifies the integration url used to send the document.'; + } + field(Method; Rec.Method) + { + ToolTip = 'Specifies the http method used to send the document.'; + } + field("Response Status"; Rec."Response Status") + { + ToolTip = 'Specifies the response status of sending the document.'; + } + } + } + } + } + actions + { + area(Processing) + { + action(ExportRequestMsg) + { + Caption = 'Export Request Message'; + ToolTip = 'Exports request message.'; + Image = ExportAttachment; + + trigger OnAction() + begin + Rec.ExportRequestMessage(); + end; + } + action(ExportResponseMsg) + { + Caption = 'Export Response Message'; + ToolTip = 'Exports resonse message.'; + Image = ExportAttachment; + + trigger OnAction() + begin + Rec.ExportResponseMessage(); + end; + } + } + } +} diff --git a/Apps/W1/EDocument/app/src/Log/EDocumentLog.Codeunit.al b/Apps/W1/EDocument/app/src/Log/EDocumentLog.Codeunit.al new file mode 100644 index 0000000000..4b4ee23b8a --- /dev/null +++ b/Apps/W1/EDocument/app/src/Log/EDocumentLog.Codeunit.al @@ -0,0 +1,268 @@ +// ------------------------------------------------------------------------------------------------ +// 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.EDocument; + +using System.Telemetry; +using System.Utilities; + +codeunit 6132 "E-Document Log" +{ + Permissions = + tabledata "E-Document" = m, + tabledata "E-Doc. Data Storage" = im, + tabledata "E-Document Log" = im, + tabledata "E-Doc. Mapping Log" = im, + tabledata "E-Document Service Status" = im, + tabledata "E-Document Integration Log" = im; + + internal procedure InsertLog(EDocument: Record "E-Document"; EDocumentServiceStatus: Enum "E-Document Service Status"): Integer + var + EDocumentService: Record "E-Document Service"; + begin + exit(InsertLog(EDocument, EDocumentService, 0, EDocumentServiceStatus)); + end; + + internal procedure InsertLog(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocumentServiceStatus: Enum "E-Document Service Status"): Integer + begin + exit(InsertLog(EDocument, EDocumentService, 0, EDocumentServiceStatus)); + end; + + internal procedure InsertLog(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; var TempBlob: Codeunit "Temp Blob"; EDocumentServiceStatus: Enum "E-Document Service Status"): Integer + begin + exit(InsertLog(EDocument, EDocumentService, AddTempBlobToLog(TempBlob), EDocumentServiceStatus)); + end; + + internal procedure InsertLog(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocDataStorageEntryNo: Integer; EDocumentServiceStatus: Enum "E-Document Service Status"): Integer + var + EDocumentLog: Record "E-Document Log"; + begin + if EDocumentService.Code <> '' then + UpdateServiceStatus(EDocument, EDocumentService, EDocumentServiceStatus); + + EDocumentLog.Validate("Document Type", EDocument."Document Type"); + EDocumentLog.Validate("Document No.", EDocument."Document No."); + EDocumentLog.Validate("E-Doc. Entry No", EDocument."Entry No"); + EDocumentLog.Validate(Status, EDocumentServiceStatus); + EDocumentLog.Validate("Service Integration", EDocumentService."Service Integration"); + EDocumentLog.Validate("Service Code", EDocumentService.Code); + EDocumentLog.Validate("Document Format", EDocumentService."Document Format"); + EDocumentLog.Validate("E-Doc. Data Storage Entry No.", EDocDataStorageEntryNo); + + EDocumentLog.Insert(); + exit(EDocumentLog."Entry No."); + end; + + internal procedure InsertLogWithIntegration(EDocumentServiceStatus: Record "E-Document Service Status"; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage) + var + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + begin + if EDocument.Get(EDocumentServiceStatus."E-Document Entry No") then; + if EDocumentService.Get(EDocumentServiceStatus."E-Document Service Code") then; + InsertLogWithIntegration(EDocument, EDocumentService, EDocumentServiceStatus.Status, 0, HttpRequest, HttpResponse); + end; + + internal procedure InsertLogWithIntegration(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; var TempBlob: Codeunit "Temp Blob"; EDocumentServiceStatus: Enum "E-Document Service Status"; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage) + begin + InsertLogWithIntegration(EDocument, EDocumentService, EDocumentServiceStatus, AddTempBlobToLog(TempBlob), HttpRequest, HttpResponse); + end; + + internal procedure InsertLogWithIntegration(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocumentServiceStatus: Enum "E-Document Service Status"; EDocDataStorageEntryNo: Integer; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage) + begin + InsertLog(EDocument, EDocumentService, EDocDataStorageEntryNo, EDocumentServiceStatus); + InsertIntegrationLog(EDocument, EDocumentService, HttpRequest, HttpResponse); + end; + + internal procedure UpdateServiceStatus(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocumentStatus: Enum "E-Document Service Status") + var + EDocumentServiceStatus: Record "E-Document Service Status"; + Exists: Boolean; + begin + EDocument.Get(EDocument."Entry No"); + Exists := EDocumentServiceStatus.Get(EDocument."Entry No", EDocumentService.Code); + EDocumentServiceStatus.Validate(Status, EDocumentStatus); + if Exists then + EDocumentServiceStatus.Modify() + else begin + EDocumentServiceStatus.Validate("E-Document Entry No", EDocument."Entry No"); + EDocumentServiceStatus.Validate("E-Document Service Code", EDocumentService.Code); + EDocumentServiceStatus.Validate(Status, EDocumentStatus); + EDocumentServiceStatus.Insert(); + end; + + UpdateEDocumentStatus(EDocument); + end; + + internal procedure InsertIntegrationLog(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage) + var + EDocumentIntegrationLog: Record "E-Document Integration Log"; + EDocumentIntegrationLogRecRef: RecordRef; + RequestTxt: Text; + begin + if EDocumentService."Service Integration" = EDocumentService."Service Integration"::"No Integration" then + exit; + + EDocumentIntegrationLog.Validate("E-Doc. Entry No", EDocument."Entry No"); + EDocumentIntegrationLog.Validate("Service Code", EDocumentService.Code); + EDocumentIntegrationLog.Validate("Response Status", HttpResponse.HttpStatusCode()); + EDocumentIntegrationLog.Validate(URL, HttpRequest.GetRequestUri()); + EDocumentIntegrationLog.Validate(Method, HttpRequest.Method()); + EDocumentIntegrationLog.Insert(); + + EDocumentIntegrationLogRecRef.GetTable(EDocumentIntegrationLog); + + if HttpRequest.Content.ReadAs(RequestTxt) then begin + InsertIntegrationBlob(EDocumentIntegrationLogRecRef, RequestTxt, EDocumentIntegrationLog.FieldNo(EDocumentIntegrationLog."Request Blob")); + EDocumentIntegrationLogRecRef.Modify(); + end; + + if HttpResponse.Content.ReadAs(RequestTxt) then begin + InsertIntegrationBlob(EDocumentIntegrationLogRecRef, RequestTxt, EDocumentIntegrationLog.FieldNo(EDocumentIntegrationLog."Response Blob")); + EDocumentIntegrationLogRecRef.Modify(); + end; + end; + + local procedure InsertIntegrationBlob(var EDocumentIntegrationLogRecRef: RecordRef; Data: Text; FieldNo: Integer) + var + TempBlob: Codeunit "Temp Blob"; + OutStreamObj: OutStream; + begin + TempBlob.CreateOutStream(OutStreamObj); + OutStreamObj.WriteText(Data); + + TempBlob.ToRecordRef(EDocumentIntegrationLogRecRef, FieldNo); + end; + + local procedure UpdateEDocumentStatus(var EDocument: Record "E-Document") + var + IsHandled: Boolean; + begin + OnUpdateEDocumentStatus(EDocument, IsHandled); + + if IsHandled then + exit; + + if EDocumentHasErrors(EDocument) then + exit; + + SetDocumentStatus(EDocument); + end; + + internal procedure SetDataStorage(EDocumentLog: Record "E-Document Log"; DataStorageEntryNo: Integer) + begin + if EDocumentLog."E-Doc. Data Storage Entry No." <> 0 then + Error(EDocDataStorageAlreadySetErr); + + EDocumentLog.Validate("E-Doc. Data Storage Entry No.", DataStorageEntryNo); + EDocumentLog.Modify(); + end; + + internal procedure AddTempBlobToLog(var TempBlob: Codeunit "Temp Blob"): Integer + var + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocRecRef: RecordRef; + begin + EDocDataStorage.Init(); + EDocDataStorage.Insert(); + EDocDataStorage."Data Storage Size" := TempBlob.Length(); + EDocRecRef.GetTable(EDocDataStorage); + TempBlob.ToRecordRef(EDocRecRef, EDocDataStorage.FieldNo("Data Storage")); + EDocRecRef.Modify(); + exit(EDocDataStorage."Entry No."); + end; + + internal procedure InsertMappingLog(EDocumentLogEntryNo: Integer; var Changes: Record "E-Doc. Mapping" temporary) + var + EDocumentLog: Record "E-Document Log"; + EDocumentMappingLog: Record "E-Doc. Mapping Log"; + begin + EDocumentLog.Get(EDocumentLogEntryNo); + if Changes.FindSet() then + repeat + EDocumentMappingLog.Init(); + EDocumentMappingLog."Entry No." := 0; + EDocumentMappingLog.Validate("E-Doc Log Entry No.", EDocumentLogEntryNo); + EDocumentMappingLog.Validate("E-Doc Entry No.", EDocumentLog."E-Doc. Entry No"); + EDocumentMappingLog.Validate("Table ID", Changes."Table ID"); + EDocumentMappingLog.Validate("Field ID", Changes."Field ID"); + EDocumentMappingLog.Validate("Find Value", Changes."Find Value"); + EDocumentMappingLog.Validate("Replace Value", Changes."Replace Value"); + EDocumentMappingLog.Insert(); + until Changes.Next() = 0; + end; + + internal procedure GetDocumentBlobFromLog(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; var TempBlob: Codeunit "Temp Blob"; EDocumentServiceStatus: Enum "E-Document Service Status"): Boolean + var + EDocumentLog: Record "E-Document Log"; + EDocumentHelper: Codeunit "E-Document Processing"; + Telemetry: Codeunit Telemetry; + TelemetryDimensions: Dictionary of [Text, Text]; + begin + EDocumentLog.SetLoadFields("E-Doc. Entry No", Status); + EDocumentLog.SetRange("E-Doc. Entry No", EDocument."Entry No"); + EDocumentLog.SetRange("Service Code", EDocumentService.Code); + EDocumentLog.SetRange("Service Integration", EDocumentService."Service Integration"); + EDocumentLog.SetRange("Document Format", EDocumentService."Document Format"); + EDocumentLog.SetRange(Status, EDocumentServiceStatus); + if not EDocumentLog.FindLast() then begin + EDocumentHelper.GetTelemetryDimensions(EDocumentService, EDocument, TelemetryDimensions); + Telemetry.LogMessage('0000LCE', EDocTelemetryGetLogFailureLbl, Verbosity::Error, DataClassification::OrganizationIdentifiableInformation, TelemetryScope::All, TelemetryDimensions); + exit(false); + end; + EDocumentLog.GetDataStorage(TempBlob); + exit(TempBlob.HasValue()); + end; + + internal procedure GetLastServiceFromLog(EDocument: Record "E-Document") EDocumentService: Record "E-Document Service" + var + EDocumentLog: Record "E-Document Log"; + begin + EDocumentLog.SetRange("E-Doc. Entry No", EDocument."Entry No"); + EDocumentLog.FindLast(); + EDocumentService.Get(EDocumentLog."Service Code"); + end; + + local procedure SetDocumentStatus(var EDocument: Record "E-Document") + var + EDocumentServiceStatus: Record "E-Document Service Status"; + EDocServiceCount: Integer; + begin + EDocumentServiceStatus.SetRange("E-Document Entry No", EDocument."Entry No"); + EDocServiceCount := EDocumentServiceStatus.Count; + + EDocumentServiceStatus.SetFilter(Status, '%1|%2|%3|%4|%5|%6', EDocumentServiceStatus.Status::Sent, EDocumentServiceStatus.Status::Exported, EDocumentServiceStatus.Status::"Imported Document Created", + EDocumentServiceStatus.Status::"Journal Line Created", EDocumentServiceStatus.Status::"Order Updated", EDocumentServiceStatus.Status::Approved); + if EDocumentServiceStatus.Count = EDocServiceCount then + EDocument.Status := EDocument.Status::Processed + else + EDocument.Status := EDocument.Status::"In Progress"; + + EDocument.Modify(); + end; + + local procedure EDocumentHasErrors(var EDocument: Record "E-Document"): Boolean + var + EDocumentServiceStatus: Record "E-Document Service Status"; + begin + EDocumentServiceStatus.SetRange("E-Document Entry No", EDocument."Entry No"); + EDocumentServiceStatus.SetFilter(Status, '%1|%2|%3|%4|%5', EDocumentServiceStatus.Status::"Sending Error", EDocumentServiceStatus.Status::"Export Error", EDocumentServiceStatus.Status::"Cancel Error", EDocumentServiceStatus.Status::"Imported Document Processing Error", EDocumentServiceStatus.Status::Rejected); + + if EDocumentServiceStatus.IsEmpty() then + exit(false); + + EDocument.Validate(Status, EDocument.Status::Error); + EDocument.Modify(); + exit(true); + end; + + var + EDocDataStorageAlreadySetErr: Label 'E-Doc. Data Storage can not be overwritten with new entry'; + EDocTelemetryGetLogFailureLbl: Label 'E-Document Blog Log Failure', Locked = true; + + [IntegrationEvent(false, false)] + local procedure OnUpdateEDocumentStatus(var EDocument: Record "E-Document"; var IsHandled: Boolean) + begin + end; +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Log/EDocumentLog.Table.al b/Apps/W1/EDocument/app/src/Log/EDocumentLog.Table.al new file mode 100644 index 0000000000..acba8419e4 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Log/EDocumentLog.Table.al @@ -0,0 +1,125 @@ +// ------------------------------------------------------------------------------------------------ +// 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.EDocument; + +using System.Utilities; + +table 6124 "E-Document Log" +{ + DataClassification = CustomerContent; + + fields + { + field(1; "Entry No."; Integer) + { + AutoIncrement = true; + Caption = 'Entry No'; + } + field(2; "E-Doc. Entry No"; Integer) + { + Caption = 'E-Doc. Entry No'; + TableRelation = "E-Document"; + } + field(3; "Service Code"; Code[20]) + { + Caption = 'Service Code'; + TableRelation = "E-Document Service"; + } + field(4; "E-Doc. Data Storage Entry No."; Integer) + { + Caption = 'Data Storage'; + TableRelation = "E-Doc. Data Storage"; + } + field(5; "E-Doc. Data Storage Size"; Integer) + { + Caption = 'Data Storage'; + FieldClass = FlowField; + CalcFormula = lookup("E-Doc. Data Storage"."Data Storage Size" where("Entry No." = field("E-Doc. Data Storage Entry No."))); + } + field(6; Status; Enum "E-Document Service Status") + { + Caption = 'E-Document Status'; + } + field(7; "Service Integration"; Enum "E-Document Integration") + { + Caption = 'Integration Code'; + } + field(8; "Document Type"; Enum "E-Document Type") + { + Caption = 'Document Type'; + Editable = false; + } + field(9; "Document No."; Code[20]) + { + Caption = 'Document No.'; + Editable = false; + } + field(11; "Document Format"; Enum "E-Document Format") + { + Caption = 'Document Format'; + DataClassification = SystemMetadata; + } + } + + keys + { + key(Key1; "Entry No.") + { + Clustered = true; + } + key(Key2; "E-Doc. Entry No") + { + IncludedFields = Status; + MaintainSiftIndex = false; + } + key(Key3; Status, "Service Code", "Document Format", "Service Integration") + { + } + } + + var + EDOCLogFileTxt: Label 'E-Document_Log_%1', Locked = true; + + internal procedure ExportDataStorage() + var + EDocDataStorage: Record "E-Doc. Data Storage"; + InStr: InStream; + FileName: Text; + begin + EDocDataStorage.Get("E-Doc. Data Storage Entry No."); + EDocDataStorage.CalcFields("Data Storage"); + if not EDocDataStorage."Data Storage".HasValue() then + exit; + + FileName := StrSubstNo(EDOCLogFileTxt, "E-Doc. Entry No"); + EDocDataStorage."Data Storage".CreateInStream(InStr); + + OnBeforeExportDataStorage(Rec, FileName); + + DownloadFromStream(InStr, '', '', '', FileName); + end; + + internal procedure GetDataStorage(var TempBlob: Codeunit "Temp Blob") + var + EDocDataStorage: Record "E-Doc. Data Storage"; + begin + EDocDataStorage.Get("E-Doc. Data Storage Entry No."); + EDocDataStorage.CalcFields("Data Storage"); + if not EDocDataStorage."Data Storage".HasValue() then + exit; + + TempBlob.FromRecord(EDocDataStorage, EDocDataStorage.FieldNo("Data Storage")); + end; + + internal procedure CanHaveMappingLogs(): Boolean + begin + exit(Rec.Status in [Enum::"E-Document Service Status"::Exported, Enum::"E-Document Service Status"::Imported]); + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeExportDataStorage(EDocumentLog: Record "E-Document Log"; var FileName: Text) + begin + end; +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Log/EDocumentLogs.Page.al b/Apps/W1/EDocument/app/src/Log/EDocumentLogs.Page.al new file mode 100644 index 0000000000..8a12fa9d9e --- /dev/null +++ b/Apps/W1/EDocument/app/src/Log/EDocumentLogs.Page.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.EDocument; + +page 6125 "E-Document Logs" +{ + ApplicationArea = Basic, Suite; + Caption = 'E-Document Logs'; + SourceTable = "E-Document Log"; + PageType = List; + Editable = false; + SourceTableView = sorting("Entry No.") order(descending); + + layout + { + area(Content) + { + group("Document Data Lines") + { + ShowCaption = false; + repeater(DocumentLines) + { + ShowCaption = false; + field("Entry No."; Rec."Entry No.") + { + ToolTip = 'Specifies the log entry no.'; + } + field("Service Code"; Rec."Service Code") + { + ToolTip = 'Specifies the service code for the document.'; + } + field("Document Format"; Rec."Document Format") + { + ToolTip = 'Specifies the document format for the document.'; + } + field("Service Integration"; Rec."Service Integration") + { + ToolTip = 'Specifies the integration code for the document.'; + } + field(Status; Rec.Status) + { + ToolTip = 'Specifies the status of the document.'; + } + field("Created At"; Rec.SystemCreatedAt) + { + ToolTip = 'Specifies the time log was created'; + } + field("Data Storage Size"; Rec."E-Doc. Data Storage Size") + { + Caption = 'File Size (B)'; + ToolTip = 'Specifies the file size of the document in bytes.'; + } + } + } + } + } + actions + { + area(Processing) + { + action(ExportFile) + { + Caption = 'Export File'; + ToolTip = 'Exports file.'; + Image = ExportAttachment; + + trigger OnAction() + begin + Rec.ExportDataStorage(); + end; + } + action("OpenMappingLogs") + { + Caption = 'Open Mapping Logs'; + ToolTip = 'Opens mapping logs related to E-Document'; + Image = Log; + + trigger OnAction() + var + EDocMappingLog: Record "E-Doc. Mapping Log"; + EDocMappingLogsPage: Page "E-Doc. Mapping Logs"; + begin + if not Rec.CanHaveMappingLogs() then begin + Message(RecordDoesNotHaveMappingLogsMsg, Rec.Status); + exit; + end; + + EDocMappingLog.SetRange("E-Doc Log Entry No.", Rec."Entry No."); + if not EDocMappingLog.FindSet() then begin + Message(NoMappingLogsFoundMsg, rec."Service Code"); + exit; + end; + + EDocMappingLogsPage.SetTableView(EDocMappingLog); + EDocMappingLogsPage.RunModal(); + end; + } + } + } + + var + RecordDoesNotHaveMappingLogsMsg: Label '%1 Log entry type does not support Mapping Logs.', Comment = '%1 - The log status indicating the type'; + NoMappingLogsFoundMsg: Label 'No Mapping Logs were found for this entry. Mapping Logs are only generated when E-Document Service %1 has defined export/import mapping rules.', Comment = '%1 - E-Document Service code'; +} diff --git a/Apps/W1/EDocument/test/src/Log/EDocLogTest.Codeunit.al b/Apps/W1/EDocument/test/src/Log/EDocLogTest.Codeunit.al new file mode 100644 index 0000000000..5a88de9705 --- /dev/null +++ b/Apps/W1/EDocument/test/src/Log/EDocLogTest.Codeunit.al @@ -0,0 +1,751 @@ +codeunit 139616 "E-Doc Log Test" +{ + Subtype = Test; + TestPermissions = Disabled; + EventSubscriberInstance = Manual; + + trigger OnRun() + begin + // [FEATURE] [E-Document] + IsInitialized := false; + end; + + var + + Assert: Codeunit Assert; + LibraryVariableStorage: Codeunit "Library - Variable Storage"; + LibraryEDoc: Codeunit "Library - E-Document"; + LibraryJobQueue: Codeunit "Library - Job Queue"; + IsInitialized: Boolean; + IncorrectValueErr: Label 'Incorrect value found'; + FailLastEntryInBatch, ErrorInExport : Boolean; + + [Test] + procedure CreateEDocumentSuccess() + var + SalesInvHeader: Record "Sales Invoice Header"; + EDocument: Record "E-Document"; + EDocLog: Record "E-Document Log"; + EDocMappingLogs: Record "E-Doc. Mapping Log"; + CustomerNo, DocumentSendingProfile : Code[20]; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument creation + + // [GIVEN] Creating a EDocument from Sales Invoice + Initialize(); + CustomerNo := LibraryEDoc.CreateCustomerNoWithEDocSendingProfile(DocumentSendingProfile); + LibraryEDoc.CreateSimpleFlow(DocumentSendingProfile, LibraryEDoc.CreateService()); + + LibraryEDoc.CreateEDocumentFromSales(EDocument, CustomerNo); + EDocLog.SetRange(Status, EDocLog.Status::Created); + EDocLog.FindLast(); + SalesInvHeader.FindLast(); + + // [THEN] Fields on document log is correctly + Assert.AreEqual(EDocument."Entry No", EDocLog."E-Doc. Entry No", IncorrectValueErr); + Assert.AreEqual(SalesInvHeader."No.", EDocLog."Document No.", IncorrectValueErr); + Assert.AreEqual(EDocLog."Document Type"::"Sales Invoice", EDocLog."Document Type", IncorrectValueErr); + Assert.AreEqual(0, EDocLog."E-Doc. Data Storage Entry No.", IncorrectValueErr); + Assert.AreEqual(0, EDocLog."E-Doc. Data Storage Size", IncorrectValueErr); + Assert.AreEqual('', EDocLog."Service Code", IncorrectValueErr); + Assert.AreEqual(EDocLog."Service Integration"::"No Integration", EDocLog."Service Integration", IncorrectValueErr); + Assert.AreEqual(EDocLog.Status::Created, EDocLog.Status, IncorrectValueErr); + Assert.AreEqual(EDocument.Status::"In Progress", EDocument.Status, IncorrectValueErr); + + // [THEN] No mapping logs are not created + asserterror EDocMappingLogs.Get(EDocLog."Entry No."); + Assert.AssertRecordNotFound(); + end; + + [Test] + procedure ExportEDocNoMappingSuccess() + var + SalesInvHeader: Record "Sales Invoice Header"; + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + EDocLog: Record "E-Document Log"; + EDocMappingLog: Record "E-Doc. Mapping Log"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocExportMgt: Codeunit "E-Doc. Export"; + EDocLogTest: Codeunit "E-Doc Log Test"; + CustomerNo, DocumentSendingProfile, ServiceCode : Code[20]; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument export without mapping + // Expected Outcomes: + // 1. An E-Document is successfully exported without mapping. + // 2. Document log fields are correctly populated. + // 3. E-Document Service Status is updated to "Exported." + // 4. No mapping logs are created in this scenario. + + // [GIVEN] Exporting E-Document for service without mapping + Initialize(); + CustomerNo := LibraryEDoc.CreateCustomerNoWithEDocSendingProfile(DocumentSendingProfile); + ServiceCode := LibraryEDoc.CreateService(); + LibraryEDoc.CreateSimpleFlow(DocumentSendingProfile, ServiceCode); + + LibraryEDoc.CreateEDocumentFromSales(EDocument, CustomerNo); + EDocumentService.Get(ServiceCode); + BindSubscription(EDocLogTest); + EDocExportMgt.ExportEDocument(EDocument, EDocumentService); + UnbindSubscription(EDocLogTest); + EDocLog.FindLast(); + SalesInvHeader.FindLast(); + EDocument.Get(EDocument."Entry No"); + + // [THEN] Fields on document log is correctly + Assert.AreEqual(EDocument."Entry No", EDocLog."E-Doc. Entry No", IncorrectValueErr); + Assert.AreEqual(SalesInvHeader."No.", EDocLog."Document No.", IncorrectValueErr); + Assert.AreEqual(EDocLog."Document Type"::"Sales Invoice", EDocLog."Document Type", IncorrectValueErr); + Assert.AreNotEqual(0, EDocLog."E-Doc. Data Storage Entry No.", IncorrectValueErr); + Assert.AreEqual(0, EDocLog."E-Doc. Data Storage Size", IncorrectValueErr); + Assert.AreEqual(EDocumentService.Code, EDocLog."Service Code", IncorrectValueErr); + Assert.AreEqual(EDocLog."Service Integration"::Mock, EDocLog."Service Integration", IncorrectValueErr); + Assert.AreEqual(EDocLog.Status::Exported, EDocLog.Status, IncorrectValueErr); + + // [THEN] EDoc Service Status is updated + EDocServiceStatus.Get(EDocLog."E-Doc. Entry No", EDocLog."Service Code"); + Assert.AreEqual(EDocLog.Status, EDocServiceStatus.Status, IncorrectValueErr); + + // [THEN] No mapping logs are not created + asserterror EDocMappingLog.Get(EDocLog."Entry No."); + Assert.AssertRecordNotFound(); + end; + + [Test] + procedure ExportEDocWithMappingSuccess() + var + SalesInvHeader: Record "Sales Invoice Header"; + EDocMapping: Record "E-Doc. Mapping"; + TransformationRule: Record "Transformation Rule"; + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocLog: Record "E-Document Log"; + EDocMappingLog: Record "E-Doc. Mapping Log"; + EDocExportMgt: Codeunit "E-Doc. Export"; + EDocLogTest: Codeunit "E-Doc Log Test"; + CustomerNo, DocumentSendingProfile, ServiceCode : Code[20]; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument export with mapping + // --------------------------------------------------------------------------- + // [Expected Outcome] + // [1] An E-Document is exported successfully with mapping. + // [2] Document log fields are correctly populated. + // [3] E-Document Service Status is updated to "Exported." + // [4] A mapping log is correctly created. + + // [GIVEN] Exporting E-Document for service with mapping + Initialize(); + TransformationRule.Get(TransformationRule.GetLowercaseCode()); + CustomerNo := LibraryEDoc.CreateCustomerNoWithEDocSendingProfile(DocumentSendingProfile); + ServiceCode := LibraryEDoc.CreateServiceWithMapping(EDocMapping, TransformationRule); + LibraryEDoc.CreateSimpleFlow(DocumentSendingProfile, ServiceCode); + + LibraryEDoc.CreateEDocumentFromSales(EDocument, CustomerNo); + EDocMapping."Table ID" := Database::"Sales Invoice Header"; + EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Name"); + EDocMapping.Modify(); + + EDocumentService.Get(ServiceCode); + BindSubscription(EDocLogTest); + EDocExportMgt.ExportEDocument(EDocument, EDocumentService); + UnBindSubscription(EDocLogTest); + + EDocLog.FindLast(); + SalesInvHeader.FindLast(); + EDocument.Get(EDocument."Entry No"); + + // [THEN] Fields on document log is correctly + Assert.AreEqual(EDocument."Entry No", EDocLog."E-Doc. Entry No", IncorrectValueErr); + Assert.AreEqual(SalesInvHeader."No.", EDocLog."Document No.", IncorrectValueErr); + Assert.AreEqual(EDocLog."Document Type"::"Sales Invoice", EDocLog."Document Type", IncorrectValueErr); + Assert.AreNotEqual(0, EDocLog."E-Doc. Data Storage Entry No.", IncorrectValueErr); + Assert.AreEqual(0, EDocLog."E-Doc. Data Storage Size", IncorrectValueErr); + Assert.AreEqual(EDocumentService.Code, EDocLog."Service Code", IncorrectValueErr); + Assert.AreEqual(EDocLog."Service Integration"::Mock, EDocLog."Service Integration", IncorrectValueErr); + Assert.AreEqual(EDocLog.Status::Exported, EDocLog.Status, IncorrectValueErr); + + // [THEN] EDoc Service Status is updated + EDocServiceStatus.Get(EDocLog."E-Doc. Entry No", EDocLog."Service Code"); + Assert.AreEqual(EDocLog.Status, EDocServiceStatus.Status, IncorrectValueErr); + + // [THEN] Mapping log is correctly created + EDocMappingLog.SetRange("E-Doc Log Entry No.", EDocLog."Entry No."); + EDocMappingLog.FindSet(); + Assert.AreEqual(1, EDocMappingLog.Count(), IncorrectValueErr); + Assert.AreEqual(EDocMapping."Table ID", EDocMappingLog."Table ID", IncorrectValueErr); + Assert.AreEqual(EDocMapping."Field ID", EDocMappingLog."Field ID", IncorrectValueErr); + Assert.AreEqual(SalesInvHeader."Bill-to Name", EDocMappingLog."Find Value", IncorrectValueErr); + Assert.AreEqual(TransformationRule.TransformText(SalesInvHeader."Bill-to Name"), EDocMappingLog."Replace Value", IncorrectValueErr); + end; + + [Test] + procedure ExportEDocFailure() + var + SalesInvHeader: Record "Sales Invoice Header"; + EDocMapping: Record "E-Doc. Mapping"; + TransformationRule: Record "Transformation Rule"; + EDocumentA: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocLog: Record "E-Document Log"; + EDocMappingLog: Record "E-Doc. Mapping Log"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocLogTest: Codeunit "E-Doc Log Test"; + ServiceCode: Code[20]; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument export when Create interface has errors + // --------------------------------------------------------------------------- + // [Expected Outcomes] + // [1] Two logs should be created: one for document creation and another for the export error. + // [2] No data storage log entries should be generated. + // [3] The document log fields should be accurately populated, indicating "Export Failed" status. + // [4] The E-Doc Service Status should reflect the error status. + // [5] No mapping logs should be generated as part of this scenario. + + // [GIVEN] Exporting E-Document with errors on edocument + Initialize(); + BindSubscription(EDocLogTest); // Bind subscription to get events to insert into blobs + EDocLogTest.SetExportError(); + + TransformationRule.Get(TransformationRule.GetLowercaseCode()); + ServiceCode := LibraryEDoc.CreateServiceWithMapping(EDocMapping, TransformationRule, false); + LibraryEDoc.CreateSimpleFlow(ServiceCode); + EDocumentService.Get(ServiceCode); + EDocumentService."Use Batch Processing" := false; + EDocumentService.Modify(); + LibraryJobQueue.SetDoNotHandleCodeunitJobQueueEnqueueEvent(true); + + // [WHEN] Post a documents + LibraryEDoc.PostSalesDocument(); + EDocumentA.FindLast(); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentA.RecordId()); + + // [THEN] Two logs are created and one data storage log is saved + EDocumentA.FindSet(); + Assert.RecordCount(EDocumentA, 1); + + // ( Created + Export Error ) * 2 + Assert.AreEqual(2, EDocLog.Count(), IncorrectValueErr); + + asserterror EDocDataStorage.FindSet(); + Assert.AreEqual(0, EDocDataStorage.Count(), IncorrectValueErr); + + EDocLog.FindLast(); + SalesInvHeader.Get(EDocumentA."Document No."); + + // [THEN] Fields on document log is correctly with 'Export Failed' + Assert.AreEqual(EDocumentA."Entry No", EDocLog."E-Doc. Entry No", IncorrectValueErr); + Assert.AreEqual(SalesInvHeader."No.", EDocLog."Document No.", IncorrectValueErr); + Assert.AreEqual(EDocLog."Document Type"::"Sales Invoice", EDocLog."Document Type", IncorrectValueErr); + Assert.AreEqual(0, EDocLog."E-Doc. Data Storage Entry No.", IncorrectValueErr); + Assert.AreEqual(0, EDocLog."E-Doc. Data Storage Size", IncorrectValueErr); + Assert.AreEqual(EDocumentService.Code, EDocLog."Service Code", IncorrectValueErr); + Assert.AreEqual(EDocLog."Service Integration"::Mock, EDocLog."Service Integration", IncorrectValueErr); + Assert.AreEqual(EDocLog.Status::"Export Error", EDocLog.Status, IncorrectValueErr); + + // [THEN] EDoc Service Status is updated + EDocServiceStatus.Get(EDocLog."E-Doc. Entry No", EDocLog."Service Code"); + Assert.AreEqual(EDocLog.Status, EDocServiceStatus.Status, IncorrectValueErr); + + // [THEN] Mapping log is not logged + EDocMappingLog.SetRange("E-Doc Log Entry No.", EDocLog."Entry No."); + asserterror EDocMappingLog.FindSet(); + Assert.AssertNothingInsideFilter(); + end; + + [Test] + procedure ExportEDocBatchMappingSuccess() + var + SalesInvHeader: Record "Sales Invoice Header"; + EDocMapping: Record "E-Doc. Mapping"; + TransformationRule: Record "Transformation Rule"; + EDocumentA, EDocumentB : Record "E-Document"; + EDocumentService: Record "E-Document Service"; + EDocLog: Record "E-Document Log"; + EDocMappingLog: Record "E-Doc. Mapping Log"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocLogTest: Codeunit "E-Doc Log Test"; + ServiceCode: Code[20]; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument batch export with mapping + // --------------------------------------------------------------------------- + // [Expected Outcomes] + // [1] 8 logs are created: one for each of the two posted documents. + // [2] One data storage log entry is saved for each document, totaling two. + // [3] Each log entry contains the correct information related to the document. + // [4] The E-Doc Service Status is updated to "Sent" for each exported document. + // [5] Mapping logs are correctly created, capturing mapping details. + + // [GIVEN] Exporting E-Documents for service with mapping + Initialize(); + BindSubscription(EDocLogTest); // Bind subscription to get events to insert into blobs + + TransformationRule.Get(TransformationRule.GetLowercaseCode()); + ServiceCode := LibraryEDoc.CreateServiceWithMapping(EDocMapping, TransformationRule, true); + LibraryEDoc.CreateSimpleFlow(ServiceCode); + LibraryJobQueue.SetDoNotHandleCodeunitJobQueueEnqueueEvent(true); + EDocMapping."Table ID" := Database::"Sales Invoice Header"; + EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Name"); + EDocMapping.Modify(); + + LibraryVariableStorage.Clear(); + EDocLogTest.SetVariableStorage(LibraryVariableStorage); + + EDocumentService.Get(ServiceCode); + EDocumentService."Batch Mode" := EDocumentService."Batch Mode"::Threshold; + EDocumentService."Batch Threshold" := 2; + EDocumentService.Modify(); + + // [WHEN] Post two documents + LibraryEDoc.PostSalesDocument(); + EDocumentA.FindLast(); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentA.RecordId()); + + LibraryEDoc.PostSalesDocument(); + EDocumentB.FindLast(); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentB.RecordId()); + + // [THEN] 8 logs are created and one data storage log is saved + EDocumentA.FindSet(); + Assert.RecordCount(EDocumentA, 2); + + // ( Created + Pending + Exported + Sent ) * 2 + Assert.AreEqual(8, EDocLog.Count(), IncorrectValueErr); + + EDocDataStorage.FindSet(); + Assert.AreEqual(2, EDocDataStorage.Count(), IncorrectValueErr); + Assert.AreEqual(4, EDocDataStorage."Data Storage Size", IncorrectValueErr); + + // [THEN] Each log contains correct information + repeat + EDocLog.SetRange("E-Doc. Entry No", EDocumentA."Entry No"); + EDocLog.SetRange(Status, EDocLog.Status::Exported); + EDocLog.FindFirst(); + EDocLog.CalcFields("E-Doc. Data Storage Size"); + SalesInvHeader.Get(EDocumentA."Document No."); + + // [THEN] Fields on document log is correctly + Assert.AreEqual(EDocumentA."Entry No", EDocLog."E-Doc. Entry No", IncorrectValueErr); + Assert.AreEqual(SalesInvHeader."No.", EDocLog."Document No.", IncorrectValueErr); + Assert.AreEqual(EDocLog."Document Type"::"Sales Invoice", EDocLog."Document Type", IncorrectValueErr); + Assert.AreNotEqual(0, EDocLog."E-Doc. Data Storage Entry No.", IncorrectValueErr); + Assert.AreEqual(4, EDocLog."E-Doc. Data Storage Size", IncorrectValueErr); + Assert.AreEqual(EDocumentService.Code, EDocLog."Service Code", IncorrectValueErr); + Assert.AreEqual(EDocLog."Service Integration"::Mock, EDocLog."Service Integration", IncorrectValueErr); + Assert.AreEqual(EDocLog.Status::Exported, EDocLog.Status, IncorrectValueErr); + + // [THEN] EDoc Service Status is updated + EDocServiceStatus.Get(EDocLog."E-Doc. Entry No", EDocLog."Service Code"); + Assert.AreEqual(EDocServiceStatus.Status::Sent, EDocServiceStatus.Status, IncorrectValueErr); + + // [THEN] Mapping log is correctly created + EDocMappingLog.SetRange("E-Doc Log Entry No.", EDocLog."Entry No."); + EDocMappingLog.FindSet(); + Assert.AreEqual(1, EDocMappingLog.Count(), IncorrectValueErr); + Assert.AreEqual(EDocMapping."Table ID", EDocMappingLog."Table ID", IncorrectValueErr); + Assert.AreEqual(EDocMapping."Field ID", EDocMappingLog."Field ID", IncorrectValueErr); + Assert.AreEqual(SalesInvHeader."Bill-to Name", EDocMappingLog."Find Value", IncorrectValueErr); + Assert.AreEqual(TransformationRule.TransformText(SalesInvHeader."Bill-to Name"), EDocMappingLog."Replace Value", IncorrectValueErr); + until EdocumentA.Next() = 0; + end; + + [Test] + procedure ExportEDocBatchThresholdFailure() + var + EDocMapping: Record "E-Doc. Mapping"; + TransformationRule: Record "Transformation Rule"; + EDocumentA, EDocumentB : Record "E-Document"; + EDocumentService, EDocumentService2 : Record "E-Document Service"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocLog: Record "E-Document Log"; + EDocMappingLog: Record "E-Doc. Mapping Log"; + EDocLogTest: Codeunit "E-Doc Log Test"; + ServiceCode: Code[20]; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument threshold batch export when there is errors during export + // --------------------------------------------------------------------------- + // [Expected Outcomes] + // [1] Initialize the test environment. + // [2] Simulate an error in batch export by setting the last entry to error. + // [3] Create E-Documents and a service with batch export settings (threshold: 2). + // [4] Post two sales documents, both marked as export errors. + // [5] Validate the state of Document A and B, including its logs and service status. + // [6] Ensure no mapping logs or data storage is created for either document. + + // [GIVEN] A flow to send to service with threshold batch + Initialize(); + + BindSubscription(EDocLogTest); // Bind subscription to get events to insert into blobs + EDocLogTest.SetLastEntryInBatchToError(); // Make sure last entry in create batch fails + + TransformationRule.Get(TransformationRule.GetLowercaseCode()); + ServiceCode := LibraryEDoc.CreateServiceWithMapping(EDocMapping, TransformationRule, true); + LibraryEDoc.CreateSimpleFlow(ServiceCode); + LibraryJobQueue.SetDoNotHandleCodeunitJobQueueEnqueueEvent(true); + + EDocumentService.Get(ServiceCode); + EDocumentService."Batch Mode" := EDocumentService."Batch Mode"::Threshold; + EDocumentService."Batch Threshold" := 2; + EDocumentService.Modify(); + + // [WHEN] Post first documents + LibraryEDoc.PostSalesDocument(); + EDocumentA.FindLast(); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentA.RecordId()); + + // [THEN] First documents is pending batch for the service + EDocServiceStatus.SetRange("E-Document Service Code", ServiceCode); + EDocServiceStatus.SetRange(Status, EDocServiceStatus.Status::"Pending Batch"); + Assert.RecordCount(EDocServiceStatus, 1); + + // [WHEN] Post second document + LibraryEDoc.PostSalesDocument(); + EDocumentB.FindLast(); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentB.RecordId()); + + // [THEN] All documents are marked as export error + EDocServiceStatus.SetRange("E-Document Service Code", ServiceCode); + EDocServiceStatus.SetRange(Status, EDocServiceStatus.Status::"Export Error"); + Assert.RecordCount(EDocServiceStatus, 2); + + // CHECKS FOR DOCUMENT A (Export error) + EDocumentA.Get(EDocumentA."Entry No"); + Assert.AreEqual(Enum::"E-Document Status"::Error, EDocumentA.Status, IncorrectValueErr); + + // [THEN] There are 3 logs for document that was successfully sent + EDocLog.SetRange("E-Doc. Entry No", EDocumentA."Entry No"); + Assert.RecordCount(EDocLog, 3); + + AssertEDocLogState(EDocumentA, EDocLog, EDocumentService2, Enum::"E-Document Service Status"::Created); + AssertEDocLogState(EDocumentA, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Pending Batch"); + AssertEDocLogState(EDocumentA, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Export Error"); + + // [THEN] Mapping log is not created + EDocMappingLog.SetRange("E-Doc Log Entry No.", EDocLog."Entry No."); + asserterror EDocMappingLog.FindSet(); + Assert.AssertNothingInsideFilter(); + + // [THEN] No Data Storage created + asserterror EDocDataStorage.Get(EDocLog."E-Doc. Data Storage Entry No."); + + // CHECKS FOR DOCUMENT B (EXPORT ERROR) + EDocumentB.Get(EDocumentB."Entry No"); + Assert.AreEqual(Enum::"E-Document Status"::Error, EDocumentB.Status, IncorrectValueErr); + + // [THEN] There are 3 logs for document that failed during export + EDocLog.SetRange("E-Doc. Entry No", EDocumentB."Entry No"); + EDocLog.SetRange(Status); + Assert.RecordCount(EDocLog, 3); + + AssertEDocLogState(EDocumentB, EDocLog, EDocumentService2, Enum::"E-Document Service Status"::Created); + AssertEDocLogState(EDocumentB, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Pending Batch"); + AssertEDocLogState(EDocumentB, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Export Error"); + + // [THEN] Mapping log is not created + EDocMappingLog.SetRange("E-Doc Log Entry No.", EDocLog."Entry No."); + asserterror EDocMappingLog.FindSet(); + Assert.AssertNothingInsideFilter(); + + // [THEN] No Data Storage created + asserterror EDocDataStorage.Get(EDocLog."E-Doc. Data Storage Entry No."); + end; + + [Test] + procedure ExporEDocBatchtRecurrentFailure() + var + EDocMapping: Record "E-Doc. Mapping"; + TransformationRule: Record "Transformation Rule"; + EDocumentA, EDocumentB : Record "E-Document"; + EDocumentService, EDocumentService2 : Record "E-Document Service"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocLog: Record "E-Document Log"; + EDocMappingLog: Record "E-Doc. Mapping Log"; + EDocLogTest: Codeunit "E-Doc Log Test"; + EDocumentBackgroundJobs: Codeunit "E-Document Background Jobs"; + ServiceCode: Code[20]; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument recurrent batch when there is errors during export for a document + // --------------------------------------------------------------------------- + // [Expected Outcomes] + // [1] Initialize the test environment. + // [2] Simulate an error in batch export by setting the last entry to error. + // [3] Create E-Documents and a service with recurrent batch settings. + // [4] Post two sales documents, one successfully processed and one marked as an export error. + // [5] Validate the state of Document A, including logs and service status, after a successful export. + // [6] Validate logs, data storage, and fields for Document A's successful export. + // [7] Validate the state of Document B, marked as an export error. + // [8] Validate logs, data storage, and fields for Document B's export error. + // [9] Ensure no mapping logs are created. + + // [GIVEN] A flow to send to service with recurrent batch + Initialize(); + + BindSubscription(EDocLogTest); // Bind subscription to get events to insert into blobs + EDocLogTest.SetLastEntryInBatchToError(); // Make sure last entry in create batch fails + + TransformationRule.Get(TransformationRule.GetLowercaseCode()); + ServiceCode := LibraryEDoc.CreateServiceWithMapping(EDocMapping, TransformationRule, true); + LibraryEDoc.CreateSimpleFlow(ServiceCode); + LibraryJobQueue.SetDoNotHandleCodeunitJobQueueEnqueueEvent(true); + + EDocumentService.Get(ServiceCode); + EDocumentService."Batch Mode" := EDocumentService."Batch Mode"::Recurrent; + EDocumentService."Batch Minutes between runs" := 1; + EDocumentService."Batch Start Time" := Time(); + EDocumentService.Modify(); + + // [WHEN] Post two documents + LibraryEDoc.PostSalesDocument(); + EDocumentA.FindLast(); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentA.RecordId()); + + LibraryEDoc.PostSalesDocument(); + EDocumentB.FindLast(); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentB.RecordId()); + + // [THEN] Two documents are pending batch for the service + EDocServiceStatus.SetRange("E-Document Service Code", ServiceCode); + EDocServiceStatus.SetRange(Status, EDocServiceStatus.Status::"Pending Batch"); + Assert.RecordCount(EDocServiceStatus, 2); + + // [Given] Run recurrent batch job + EDocumentBackgroundJobs.HandleRecurrentBatchJob(EDocumentService); + LibraryJobQueue.FindAndRunJobQueueEntryByRecordId(EDocumentService.RecordId()); + + // Document A is successfully processed + EDocumentA.Get(EDocumentA."Entry No"); + Assert.AreEqual(Enum::"E-Document Status"::Processed, EDocumentA.Status, IncorrectValueErr); + + // [THEN] EDocServiceStatus is set to Sent for Document A + EDocServiceStatus.SetRange("E-Document Entry No", EDocumentA."Entry No"); + EDocServiceStatus.SetRange(Status); + EDocServiceStatus.FindFirst(); + Assert.AreEqual(Enum::"E-Document Service Status"::Sent, EDocServiceStatus.Status, IncorrectValueErr); + + // [THEN] There are four logs for Document A that was successfully sent + EDocLog.SetRange("E-Doc. Entry No", EDocumentA."Entry No"); + Assert.RecordCount(EDocLog, 4); + + AssertEDocLogState(EDocumentA, EDocLog, EDocumentService2, Enum::"E-Document Service Status"::Created); + AssertEDocLogState(EDocumentA, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Pending Batch"); + AssertEDocLogState(EDocumentA, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Exported"); + + // [THEN] Mapping log is correctly created for Exported log + EDocMappingLog.SetRange("E-Doc Log Entry No.", EDocLog."Entry No."); + EDocMappingLog.FindSet(); + + // [THEN] Data storage is created for the exported document, and for temp blob at send + // [THEN] Exported Blob has size 4 + EDocDataStorage.FindSet(); + Assert.AreEqual(2, EDocDataStorage.Count(), IncorrectValueErr); + EDocDataStorage.Get(EDocLog."E-Doc. Data Storage Entry No."); + Assert.AreEqual(4, EDocDataStorage."Data Storage Size", IncorrectValueErr); + + // [THEN] Fields on document log is correctly for Sent log + AssertEDocLogState(EDocumentA, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Sent"); + + // [THEN] Exported Blob has size 4 + EDocDataStorage.Get(EDocLog."E-Doc. Data Storage Entry No."); + Assert.AreEqual(4, EDocDataStorage."Data Storage Size", IncorrectValueErr); + + // Document B has gotten an error + EDocumentB.Get(EDocumentB."Entry No"); + Assert.AreEqual(Enum::"E-Document Status"::Error, EDocumentB.Status, IncorrectValueErr); + + // [THEN] EDocServiceStatus is set to Export Error for Document B + EDocServiceStatus.SetRange("E-Document Entry No", EDocumentB."Entry No"); + EDocServiceStatus.FindFirst(); + Assert.AreEqual(Enum::"E-Document Service Status"::"Export Error", EDocServiceStatus.Status, IncorrectValueErr); + + // [THEN] There are 3 logs for document that failed during export + EDocLog.SetRange("E-Doc. Entry No", EDocumentB."Entry No"); + EDocLog.SetRange(Status); + Assert.RecordCount(EDocLog, 3); + + AssertEDocLogState(EDocumentB, EDocLog, EDocumentService2, Enum::"E-Document Service Status"::Created); + AssertEDocLogState(EDocumentB, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Pending Batch"); + AssertEDocLogState(EDocumentB, EDocLog, EDocumentService, Enum::"E-Document Service Status"::"Export Error"); + + // [THEN] Mapping log is not created + EDocMappingLog.SetRange("E-Doc Log Entry No.", EDocLog."Entry No."); + asserterror EDocMappingLog.FindSet(); + Assert.AssertNothingInsideFilter(); + end; + + [Test] + procedure IntegrationLogs() + var + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + EDocumentLogRec: Record "E-Document Log"; + EDocumentIntegrationLog: Record "E-Document Integration Log"; + EDocumentServiceStatus: Record "E-Document Service Status"; + EDocumentLog: Codeunit "E-Document Log"; + TempBlob: Codeunit "Temp Blob"; + HttpRequest: HttpRequestMessage; + HttpResponse: HttpResponseMessage; + begin + // [FEATURE] [E-Document] [Log] + // [SCENARIO] EDocument Log on EDocument recurrent batch when there is errors during export for a document + // [GIVEN] + InitIntegrationData(EDocument, EDocumentService, EDocumentServiceStatus, HttpRequest, HttpResponse); + + // [WHEN] Inserting integration logs + EDocumentLog.InsertLogWithIntegration(EDocumentServiceStatus, HttpRequest, HttpResponse); + + // [THEN] It should insert EDocumentLog and EDocument integration log. + Assert.IsTrue(EDocumentLogRec.FindFirst(), 'There should be an edocument log entry'); + Assert.IsTrue(EDocumentIntegrationLog.FindFirst(), 'There should be an edocument integration log entry'); + Assert.AreEqual(EDocumentIntegrationLog."E-Doc. Entry No", EDocument."Entry No", 'EDocument integration log should be linked to edocument'); + Assert.AreEqual(HttpRequest.Method(), EDocumentIntegrationLog.Method, 'Integration log should contain method type from request message'); + Assert.AreEqual(HttpRequest.GetRequestUri(), EDocumentIntegrationLog.URL, 'Integration log should contain url from request message'); + + EDocumentIntegrationLog.CalcFields("Request Blob"); + EDocumentIntegrationLog.CalcFields("Response Blob"); + + TempBlob.FromRecord(EDocumentIntegrationLog, EDocumentIntegrationLog.FieldNo(EDocumentIntegrationLog."Request Blob")); + Assert.AreEqual('Test request', LibraryEDoc.TempBlobToTxt(TempBlob), 'Integration log request blob is not correct'); + + Clear(TempBlob); + TempBlob.FromRecord(EDocumentIntegrationLog, EDocumentIntegrationLog.FieldNo(EDocumentIntegrationLog."Response Blob")); + Assert.AreEqual('Test response', LibraryEDoc.TempBlobToTxt(TempBlob), 'Integration log response blob is not correct'); + end; + + local procedure InitIntegrationData(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var EDocumentServiceStatus: Record "E-Document Service Status"; HttpRequest: HttpRequestMessage; HttpResponse: HttpResponseMessage) + var + EDocumentIntegrationLog: Record "E-Document Integration Log"; + begin + EDocument.DeleteAll(); + EDocumentService.DeleteAll(); + EDocumentIntegrationLog.DeleteAll(); + HttpRequest.SetRequestUri('http://cronus.test'); + HttpRequest.Method := 'POST'; + + HttpRequest.Content.WriteFrom('Test request'); + HttpResponse.Content.WriteFrom('Test response'); + + EDocument.Insert(); + EDocumentService.Code := 'Test Service 1'; + EDocumentService."Service Integration" := EDocumentService."Service Integration"::Mock; + EDocumentService.Insert(); + + EDocumentServiceStatus."E-Document Entry No" := EDocument."Entry No"; + EDocumentServiceStatus."E-Document Service Code" := EDocumentService.Code; + EDocumentServiceStatus.Insert(); + end; + + local procedure AssertEDocLogState(var EDocument: Record "E-Document"; var EDocLog: Record "E-Document Log"; var EDocumentService: Record "E-Document Service"; Status: Enum "E-Document Service Status") + begin + EDocLog.SetRange(Status, Status); + Assert.RecordCount(EDocLog, 1); + EDocLog.FindFirst(); + AssertLogValues(EDocument, EDocLog, EDocumentService, Status); + end; + + local procedure AssertLogValues(var EDocument: Record "E-Document"; var EDocLog: Record "E-Document Log"; var EDocumentService: Record "E-Document Service"; Status: Enum "E-Document Service Status") + begin + Assert.AreEqual(EDocument."Entry No", EDocLog."E-Doc. Entry No", IncorrectValueErr); + Assert.AreEqual(EDocLog."Document Type"::"Sales Invoice", EDocLog."Document Type", IncorrectValueErr); + Assert.AreEqual(EDocumentService.Code, EDocLog."Service Code", IncorrectValueErr); + Assert.AreEqual(EDocumentService."Service Integration", EDocLog."Service Integration", IncorrectValueErr); + Assert.AreEqual(Status, EDocLog.Status, IncorrectValueErr); + end; + + local procedure Initialize() + var + TransformationRule: Record "Transformation Rule"; + SalesInvHeader: Record "Sales Invoice Header"; + begin + IsInitialized := true; + ErrorInExport := false; + FailLastEntryInBatch := false; + LibraryEDoc.Initialize(); + + LibraryVariableStorage.Clear(); + TransformationRule.DeleteAll(); + TransformationRule.CreateDefaultTransformations(); + + SalesInvHeader.DeleteAll(); + end; + + procedure SetVariableStorage(var NewLibraryVariableStorage: Codeunit "Library - Variable Storage") + begin + LibraryVariableStorage := NewLibraryVariableStorage; + end; + + procedure GetVariableStorage(var NewLibraryVariableStorage: Codeunit "Library - Variable Storage") + begin + NewLibraryVariableStorage := LibraryVariableStorage; + end; + + procedure SetLastEntryInBatchToError() + begin + FailLastEntryInBatch := true; + end; + + procedure SetExportError() + begin + ErrorInExport := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Export", 'OnAfterCreateEDocument', '', false, false)] + local procedure OnAfterCreateEDocument(var EDocument: Record "E-Document") + begin + LibraryVariableStorage.Enqueue(EDocument); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Export", 'OnBeforeCreateEDocument', '', false, false)] + local procedure OnBeforeCreatedEDocument(var EDocument: Record "E-Document") + begin + LibraryVariableStorage.Enqueue(EDocument); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Format Mock", 'OnCheck', '', false, false)] + local procedure OnCheck(var SourceDocumentHeader: RecordRef; EDocService: Record "E-Document Service"; EDocumentProcessingPhase: enum "E-Document Processing Phase") + begin + LibraryVariableStorage.Enqueue(SourceDocumentHeader); + LibraryVariableStorage.Enqueue(EDocService); + LibraryVariableStorage.Enqueue(EDocumentProcessingPhase); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Format Mock", 'OnCreate', '', false, false)] + local procedure OnCreate(EDocService: Record "E-Document Service"; var EDocument: Record "E-Document"; var SourceDocumentHeader: RecordRef; var SourceDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob") + var + EDocErrorHelper: Codeunit "E-Document Error Helper"; + OutStream: OutStream; + begin + TempBlob.CreateOutStream(OutStream); + OutStream.WriteText('TEST'); + LibraryVariableStorage.Enqueue(TempBlob.Length()); + + if ErrorInExport then + EDocErrorHelper.LogSimpleErrorMessage(EDocument, 'ERROR'); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Format Mock", 'OnCreateBatch', '', false, false)] + local procedure OnCreateBatch(EDocService: Record "E-Document Service"; var EDocuments: Record "E-Document"; var SourceDocumentHeaders: RecordRef; var SourceDocumentsLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + var + EDocErrorHelper: Codeunit "E-Document Error Helper"; + OutStream: OutStream; + begin + TempBlob.CreateOutStream(OutStream); + OutStream.WriteText('TEST'); + LibraryVariableStorage.Enqueue(TempBlob.Length()); + + if FailLastEntryInBatch then begin + EDocuments.FindLast(); + EDocErrorHelper.LogSimpleErrorMessage(EDocuments, 'ERROR'); + end; + end; +} \ No newline at end of file