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 @@
Codeunit GST Base Tax Engine Setup - Method GetConfig - NamedType {2AB850AD-528A-498A-9E23-65E396AC61A8}Lbl
-
+
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 @@
Codeunit GST Base Tax Engine Setup - Method GetConfig - NamedType {2AB850AD-528A-498A-9E23-65E396AC61A8}Lbl
-
+
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