Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[E-Document Connector] Logiq E-Document Connector #27562

Closed
wants to merge 7 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions Apps/W1/EDocumentConnectors/Logiq/app/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"id": "f4a198ad-cd8c-44bb-aff1-814e0e28ab79",
"name": "E-Document Connector - Logiq",
"publisher": "Microsoft",
"version": "26.0.0.0",
"brief": "E-Document Connector - Logiq",
"description": "E-Document Connector - Logiq",
"privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
"EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
"help": "https://go.microsoft.com/fwlink/?linkid=2204541",
"url": "https://go.microsoft.com/fwlink/?LinkId=724011",
"contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2206603",
"logo": "ExtensionLogo.png",
"dependencies": [
{
"id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b",
"name": "E-Document Core",
"publisher": "Microsoft",
"version": "26.0.0.0"
}
],
"internalsVisibleTo": [
{
"id": "d57ff58b-78cc-488f-81d0-c42079507a2d",
"name": "E-Document Connector - Logiq Tests",
"publisher": "Microsoft"
}
],
"screenshots": [],
"platform": "26.0.0.0",
"application": "26.0.0.0",
"idRanges": [
{
"from": 6430,
"to": 6439
}
],
"resourceExposurePolicy": {
"allowDebugging": true,
"allowDownloadingSource": true,
"includeSourceInSymbolFile": true
},
"features": [
"TranslationFile"
],
"target": "Cloud"
}
24 changes: 24 additions & 0 deletions Apps/W1/EDocumentConnectors/Logiq/app/src/APIEngine.Enum.al
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.EServices.EDocumentConnector.Logiq;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All files should contain the license comments as the first thing.


enum 6430 "API Engine"
{
Extensible = false;
Access = Internal;

value(0; " ")
{
Caption = '', Locked = true;
}
value(1; Engine1)
{
Caption = 'Engine 1';
}
value(2; Engine3)
{
Caption = 'Engine 3';
}
}
190 changes: 190 additions & 0 deletions Apps/W1/EDocumentConnectors/Logiq/app/src/Auth.Codeunit.al
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.EServices.EDocumentConnector.Logiq;
codeunit 6430 Auth
{

Permissions =
tabledata "Connection Setup" = r,
tabledata "Connection User Setup" = rm;

internal procedure SetIsolatedStorageValue(var ValueKey: Guid; Value: SecretText; TokenDataScope: DataScope)
begin
if IsNullGuid(ValueKey) then
ValueKey := CreateGuid();

IsolatedStorage.Set(ValueKey, Value, TokenDataScope);
end;

internal procedure SetIsolatedStorageValue(var ValueKey: Guid; Value: SecretText)
begin
this.SetIsolatedStorageValue(ValueKey, Value, DataScope::Company);
end;

internal procedure GetIsolatedStorageValue(var ValueKey: Guid; var Value: SecretText; TokenDataScope: DataScope)
begin
if IsNullGuid(ValueKey) then
exit;
IsolatedStorage.Get(ValueKey, TokenDataScope, Value);
end;

internal procedure GetIsolatedStorageValue(var ValueKey: Guid; var Value: SecretText)
begin
this.GetIsolatedStorageValue(ValueKey, Value, DataScope::Company);
end;

[NonDebuggable]
internal procedure GetTokens()
var
LogiqConnectionSetup: Record "Connection Setup";
LogiqConnectionUserSetup: Record "Connection User Setup";
Client: HttpClient;
Headers: HttpHeaders;
Content: HttpContent;
RequestMessage: HttpRequestMessage;
ResponseMessage: HttpResponseMessage;
AccessToken, RefreshToken : SecretText;
AccessTokExpires, RefreshTokExpires : DateTime;
begin
this.CheckSetup(LogiqConnectionSetup);
this.CheckUserCredentials(LogiqConnectionUserSetup);

RequestMessage.Method('POST');
RequestMessage.SetRequestUri(LogiqConnectionSetup."Authentication URL");

this.BuildTokenRequestBody(Content);

Content.GetHeaders(Headers);
Headers.Clear();
Headers.Add('Content-Type', 'application/x-www-form-urlencoded');

RequestMessage.Content(Content);

Client.Send(RequestMessage, ResponseMessage);

if not ResponseMessage.IsSuccessStatusCode() then begin
if GuiAllowed then
Message(this.AuthenticationFailedErr);
exit;
end;

this.ParseTokens(ResponseMessage, AccessToken, RefreshToken, AccessTokExpires, RefreshTokExpires);

this.SaveTokens(AccessToken, RefreshToken, AccessTokExpires, RefreshTokExpires);
end;

local procedure BuildTokenRequestBody(var Content: HttpContent)
var
LogiqConnectionSetup: Record "Connection Setup";
LogiqConnectionUserSetup: Record "Connection User Setup";
BodyText: SecretText;
begin
LogiqConnectionSetup.Get();
LogiqConnectionUserSetup.Get(UserSecurityId());

if (not IsNullGuid(LogiqConnectionUserSetup."Refresh Token - Key")) and (LogiqConnectionUserSetup."Refresh Token Expiration" > (CurrentDateTime + 60 * 1000)) then
BodyText := SecretText.SecretStrSubstNo(this.RefreshTokenBodyTok, LogiqConnectionSetup."Client ID", LogiqConnectionSetup.GetClientSecret(), LogiqConnectionUserSetup.GetRefreshToken())
else
BodyText := SecretText.SecretStrSubstNo(this.CredentialsBodyTok, LogiqConnectionSetup."Client ID", LogiqConnectionSetup.GetClientSecret(), LogiqConnectionUserSetup.Username, LogiqConnectionUserSetup.GetPassword());

Content.WriteFrom(BodyText);
end;

internal procedure CheckUserCredentials(var LogiqConnectionUserSetup: Record "Connection User Setup")
begin
if not LogiqConnectionUserSetup.Get(UserSecurityId()) then
Error(this.NoUserSetupErr);

if (LogiqConnectionUserSetup.Username = '') or (IsNullGuid(LogiqConnectionUserSetup."Password - Key")) then
Error(this.MissingCredentialsErr);
end;

internal procedure CheckUserSetup(var LogiqConnectionUserSetup: Record "Connection User Setup")
var
begin
this.CheckUserCredentials(LogiqConnectionUserSetup);

if (LogiqConnectionUserSetup."API Engine" = LogiqConnectionUserSetup."API Engine"::" ") then
Error(this.MissingAPIEngineErr);

if (LogiqConnectionUserSetup."Document Transfer Endpoint" = '') or (LogiqConnectionUserSetup."Document Status Endpoint" = '') then
Error(this.MissingEndpointsErr);
end;

internal procedure CheckSetup(var LogiqConnectionSetup: Record "Connection Setup")
begin
if not LogiqConnectionSetup.Get() then
Error(this.NoSetupErr);

if (LogiqConnectionSetup."Client ID" = '') or (IsNullGuid(LogiqConnectionSetup."Client Secret")) then
Error(this.MissingClientInfoErr);

if LogiqConnectionSetup."Authentication URL" = '' then
Error(this.MissingAuthUrlErr);

if LogiqConnectionSetup."Base URL" = '' then
Error(this.MissingBaseUrlErr);
end;

[NonDebuggable]
local procedure ParseTokens(ResponseMessage: HttpResponseMessage; var AccessToken: SecretText; var RefreshToken: SecretText; var AccessTokExpires: DateTime; var RefreshTokExpires: DateTime)
var
ContentJson: JsonObject;
JsonTok: JsonToken;
ResponseTxt: Text;
begin
ResponseMessage.Content.ReadAs(ResponseTxt);
ContentJson.ReadFrom(ResponseTxt);
if ContentJson.Get('access_token', JsonTok) then
AccessToken := JsonTok.AsValue().AsText();
if ContentJson.Get('refresh_token', JsonTok) then
RefreshToken := JsonTok.AsValue().AsText();
if ContentJson.Get('expires_in', JsonTok) then
AccessTokExpires := CurrentDateTime + JsonTok.AsValue().AsInteger() * 1000;
if ContentJson.Get('refresh_expires_in', JsonTok) then
RefreshTokExpires := CurrentDateTime + JsonTok.AsValue().AsInteger() * 1000;
end;

local procedure SaveTokens(AccessToken: SecretText; RefreshToken: SecretText; AccessTokExpires: DateTime; RefreshTokExpires: DateTime)
var
LogiqConnectionUserSetup: Record "Connection User Setup";
begin
LogiqConnectionUserSetup.Get(UserSecurityId());
this.SetIsolatedStorageValue(LogiqConnectionUserSetup."Access Token - Key", AccessToken, DataScope::User);
this.SetIsolatedStorageValue(LogiqConnectionUserSetup."Refresh Token - Key", RefreshToken, DataScope::User);
LogiqConnectionUserSetup."Access Token Expiration" := AccessTokExpires;
LogiqConnectionUserSetup."Refresh Token Expiration" := RefreshTokExpires;
LogiqConnectionUserSetup.Modify(false);
end;

internal procedure HasToken(ValueKey: Guid; DataScope: DataScope): Boolean
begin
exit(IsolatedStorage.Contains(ValueKey, DataScope));
end;

internal procedure CheckUpdateTokens()
var
LogiqConnectionUserSetup: Record "Connection User Setup";
begin
if not LogiqConnectionUserSetup.Get(UserSecurityId()) then
Error(this.NoUserSetupErr);
if IsNullGuid(LogiqConnectionUserSetup."Access Token - Key") or (LogiqConnectionUserSetup."Access Token Expiration" < (CurrentDateTime + 5 * 60 * 1000)) then
this.GetTokens();
end;

var
AuthenticationFailedErr: Label 'Logiq authentication failed. Please check the user credentials.';
CredentialsBodyTok: Label 'grant_type=password&scope=openid&client_id=%1&client_secret=%2&username=%3&password=%4', Locked = true;
MissingAPIEngineErr: Label 'API Engine is missing. Please select the API Engine in the Logiq Connection User Setup page.';
MissingAuthUrlErr: Label 'Authentication URL is missing. Please fill the Authentication URL in the Logiq Connection Setup page.';
MissingBaseUrlErr: Label 'Base URL is missing. Please fill the API Base URL in the Logiq Connection Setup page.';
MissingClientInfoErr: Label 'Client ID or Client Secret is missing. Please fill the Client ID and Client Secret in the Logiq Connection Setup page.';
MissingCredentialsErr: Label 'User credentials are missing. Please enter username and password in the Logiq Connection User Setup page.';
MissingEndpointsErr: Label 'Endpoints are missing. Please fill the Document Transfer Endpoint and Document Status Endpoint in the Logiq Connection User Setup page.';
NoSetupErr: Label 'No setup found. Please fill the setup in the Logiq Connection Setup page.';
NoUserSetupErr: Label 'No user setup found. Please fill the user setup in the Logiq Connection User Setup page.';
RefreshTokenBodyTok: Label 'grant_type=refresh_token&client_id=%1&client_secret=%2&refresh_token=%3', Locked = true;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.EServices.EDocumentConnector.Logiq;

page 6430 "Connection Setup"
{
Caption = 'Logiq Connection Setup';
PageType = Card;
ApplicationArea = Basic, Suite;
UsageCategory = None;
SourceTable = "Connection Setup";

layout
{
area(Content)
{
group(General)
{
Caption = 'General';

field(ClientID; Rec."Client ID")
{
ShowMandatory = true;
}
field(ClientSecret; this.ClientSecret)
{
Caption = 'Client Secret';
ToolTip = 'Specifies the client secret token.';
ExtendedDatatype = Masked;
ShowMandatory = true;

trigger OnValidate()
begin
this.LogiqAuth.SetIsolatedStorageValue(Rec."Client Secret", this.ClientSecret);
end;
}
field("Authentication URL"; Rec."Authentication URL")
{
}
field("Base URL"; Rec."Base URL")
{
}
field("File List Endpoint"; Rec."File List Endpoint")
{
}
}
}
}

actions
{
area(Processing)
{
action(Connect)
{
ApplicationArea = All;
Caption = 'User Setup';
Image = Setup;
ToolTip = 'Open page for User Setup.';

trigger OnAction()
var
LogiqConnectionUserSetup: Record "Connection User Setup";
LoqiqConnectionUserSetupPage: Page "Connection User Setup";
begin
LogiqConnectionUserSetup.FindUserSetup(UserSecurityId());
LoqiqConnectionUserSetupPage.SetRecord(LogiqConnectionUserSetup);
LoqiqConnectionUserSetupPage.Run();
end;
}
}
area(Promoted)
{
actionref(Connect_Promoted; Connect)
{
}
}
}

var
LogiqAuth: Codeunit Auth;
[NonDebuggable]
ClientSecret: Text;

trigger OnOpenPage()
begin
if not Rec.Get() then begin
Rec.Init();
Rec.Insert(true);
end;

if this.LogiqAuth.HasToken(Rec."Client Secret", DataScope::Company) then
this.ClientSecret := '*';
end;
}
Loading

Unchanged files with check annotations Beta

{
Caption = ' ';
InstructionalText = 'Select which bank accounts to set up.';
field("No."; "No.")

Check warning on line 28 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankAssistBankAccount.Page.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Editable = false;
ToolTip = 'Specifies the number of the involved entry or record, according to the specified number series.';
}
field(Name; Name)

Check warning on line 34 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankAssistBankAccount.Page.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Editable = false;
ToolTip = 'Specifies the name of the bank where you have the bank account.';
}
field(CurrencyCode; "Currency Code")

Check warning on line 40 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankAssistBankAccount.Page.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Editable = false;
Caption = 'Currency Code';
ToolTip = 'Specifies the relevant currency code for the bank account.';
}
field("Bank Account No."; "Bank Account No.")

Check warning on line 47 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankAssistBankAccount.Page.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Editable = false;
ToolTip = 'Specifies the number used by the bank for the bank account.';
}
field(Chose; "Automatic Logon Possible")

Check warning on line 53 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankAssistBankAccount.Page.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Editable = true;
{
addAfter("Creditor No.")
{
field("Bank Name format"; "AMC Bank Name")

Check warning on line 17 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankBankAccountCard.PageExt.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Importance = Additional;
}
addafter("Payment Export Format")
{
field("Bank Filename"; "AMC Bank File Name")

Check warning on line 27 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankBankAccountCard.PageExt.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Importance = Additional;
{
addlast(Group)
{
field("XTL Journal"; "AMC Bank XTL Journal")

Check warning on line 13 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankCreditTrfRegExt.PageExt.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
Importance = Additional;
group(User)
{
Caption = 'User information';
field("User Name"; "User Name")

Check warning on line 46 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankingSetup.Page.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Basic, Suite;
ShowMandatory = true;
group(SolutionGrp)
{
Caption = '';
field("Solution"; "Solution")

Check warning on line 77 in Apps/W1/AMCBanking365Fundamentals/app/Pages/AMCBankingSetup.Page.al

GitHub Actions / Build 1st Party Apps (W1) (Translated) / 1st Party Apps (W1) (Translated)

AL0605 Use of implicit 'with' will be removed in the future. Qualify with 'Rec'.
{
ApplicationArea = Suite;
Visible = true;
local procedure CreateDataExchDef(var DataExchDef: Record "Data Exch. Def"; ProcessingXMLport: Integer; ColumnSeparator: Option; FileType: Option)
begin
DataExchDef.InsertRecForExport(LibraryUtility.GenerateRandomCode(DataExchDef.FieldNo(Code), DATABASE::"Data Exch. Def"),
LibraryUtility.GenerateGUID(), DataExchDef.Type::"Payment Export", ProcessingXMLport, FileType);

Check warning on line 66 in Apps/W1/AMCBanking365Fundamentals/test/src/Bank/PaymentExportXMLPort.Codeunit.al

GitHub Actions / Build 1st Party Apps Tests (W1) (Translated) / 1st Party Apps Tests (W1) (Translated)

AL0603 An implicit conversion is being performed from a value of type 'Enum System.IO."Data Exchange Definition Type"' to a value of type 'Option'. This conversion can lead to unexpected runtime issues.
DataExchDef."Column Separator" := ColumnSeparator;
DataExchDef."File Encoding" := DataExchDef."File Encoding"::WINDOWS;
DataExchDef.Modify();
SetGenJnlLine(GenJournalLine);
SetTableView(Vendor);
InitializeRequest(WorkDate(), false, 0, false, WorkDate(), LibraryUtility.GenerateGUID(), false,
GenJournalBatch."Bal. Account Type", GenJournalBatch."Bal. Account No.", 0);

Check warning on line 436 in Apps/W1/AMCBanking365Fundamentals/test/src/Bank/PmtExportAMCExtractData.Codeunit.al

GitHub Actions / Build 1st Party Apps Tests (W1) (Translated) / 1st Party Apps Tests (W1) (Translated)

AL0603 An implicit conversion is being performed from a value of type 'Integer' to a value of type 'Enum Microsoft.Bank.BankAccount."Bank Payment Type"'. This conversion can lead to unexpected runtime issues.
UseRequestPage(false);
RunModal();
end;
SetGenJnlLine(GenJournalLine);
SetTableView(Vendor);
InitializeRequest(WorkDate(), false, 0, SkipExportedPayments, WorkDate(), LibraryUtility.GenerateGUID(), false,
GenJournalBatch."Bal. Account Type", GenJournalBatch."Bal. Account No.", 0);

Check warning on line 691 in Apps/W1/AMCBanking365Fundamentals/test/src/Bank/PmtExportAMCTestFormat.Codeunit.al

GitHub Actions / Build 1st Party Apps Tests (W1) (Translated) / 1st Party Apps Tests (W1) (Translated)

AL0603 An implicit conversion is being performed from a value of type 'Integer' to a value of type 'Enum Microsoft.Bank.BankAccount."Bank Payment Type"'. This conversion can lead to unexpected runtime issues.
UseRequestPage(false);
RunModal();
end;
VerifyPropertyInJSON(CompanyInformationJSON, 'phoneNumber', CompanyInformation."Phone No.");
VerifyPropertyInJSON(CompanyInformationJSON, 'faxNumber', CompanyInformation."Fax No.");
VerifyPropertyInJSON(CompanyInformationJSON, 'email', CompanyInformation."E-Mail");
VerifyPropertyInJSON(CompanyInformationJSON, 'website', CompanyInformation."Home Page");

Check warning on line 154 in Apps/W1/APIV1/test/src/APIV1CompanyInfoE2E.Codeunit.al

GitHub Actions / Build 1st Party Apps Tests (W1) (Translated) / 1st Party Apps Tests (W1) (Translated)

AL0432 Field 'Home Page' is marked for removal. Reason: Field length will be increased to 255.. Tag: 24.0.
VerifyPropertyInJSON(CompanyInformationJSON, 'taxRegistrationNumber', CompanyInformation."VAT Registration No.");
VerifyPropertyInJSON(CompanyInformationJSON, 'industry', CompanyInformation."Industrial Classification");
VerifyPropertyInJSON(CompanyInformationJSON, 'currencyCode', GeneralLedgerSetup."LCY Code");
Assert.AreEqual('123456789', Customer."Phone No.", 'Customer should have the correct phone number.');
Assert.AreEqual('a@b.com', Customer."E-Mail", 'Customer should have the correct email.');
Assert.AreEqual('microsoft.com', Customer."Home Page", 'Customer should have the correct website.');

Check warning on line 440 in Apps/W1/APIV1/test/src/APIV1CustomersE2E.Codeunit.al

GitHub Actions / Build 1st Party Apps Tests (W1) (Translated) / 1st Party Apps Tests (W1) (Translated)

AL0432 Field 'Home Page' is marked for removal. Reason: Field length will be increased to 255.. Tag: 24.0.
Assert.AreEqual(TRUE, Customer."Tax Liable", 'Customer should have the correct ''tax liable'' information.');
Assert.AreEqual(TaxAreaID, Customer."Tax Area ID", 'Customer should have the correct tax area id.');
Assert.AreEqual(Currency.Code, Customer."Currency Code", 'Customer should have the correct currency code.');

Check warning on line 1 in Apps/W1/APIV1/test/src/TODO - Tests.txt

GitHub Actions / Build 1st Party Apps Tests (W1) (Translated) / 1st Party Apps Tests (W1) (Translated)

AL1025 The file at location 'c:\shared\Apps\W1\APIV1\test\src\TODO - Tests.txt' does not match any definition.
Employees Page + Tests should be added
Time Registration Entries should be tested differently