Skip to content
4 changes: 4 additions & 0 deletions src/Apps/W1/ExpenseAgent/app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@
{
"from": 7073,
"to": 7099
},
{
"from": 7101,
"to": 7110
}
],
"features": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.ExpenseAgent;

page 7104 "Expense Policies API"
{
APIGroup = 'expense';
APIPublisher = 'microsoft';
APIVersion = 'beta';
EntityCaption = 'Expense Policy';
EntitySetCaption = 'Expense Policies';
DelayedInsert = true;
EntityName = 'expensePolicy';
EntitySetName = 'expensePolicies';
PageType = API;
ODataKeyFields = SystemId;
SourceTable = "Expense Policy";
AboutText = 'Provides access to data from the Expense Policy table';
InsertAllowed = false;
ModifyAllowed = false;
DeleteAllowed = false;

layout
{
area(Content)
{
repeater(General)
{
field(id; Rec.SystemId)
{
Caption = 'Id';
Editable = false;
}
field(expenseCategoryCode; Rec."Expense Category Code")
{
Caption = 'Expense Category Code';
}
field(lineNo; Rec."Line No.")
{
Caption = 'Line No.';
}
field(description; Rec.Description)
{
Caption = 'Description';
}
field(policyText; Rec."Policy Text")
{
Caption = 'Policy Text';
}
field(enabled; Rec.Enabled)
{
Caption = 'Enabled';
}
}
}
}

trigger OnInit()
var
ExpenseAgentAPIValidation: Codeunit "Expense Agent API Validation";
begin
ExpenseAgentAPIValidation.VerifyAgentAccess();
end;
}
Original file line number Diff line number Diff line change
@@ -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.ExpenseAgent;

page 7102 "Expense Policy Flags API"
{
APIGroup = 'expense';
APIPublisher = 'microsoft';
APIVersion = 'beta';
EntityCaption = 'Expense Policy Flag';
EntitySetCaption = 'Expense Policy Flags';
DelayedInsert = true;
EntityName = 'expensePolicyFlag';
EntitySetName = 'expensePolicyFlags';
PageType = API;
ODataKeyFields = SystemId;
SourceTable = "Expense Policy Flag";
Comment thread
encimita marked this conversation as resolved.
AboutText = 'Provides access to data from the Expense Policy Flag table';
InsertAllowed = true;
ModifyAllowed = false;
DeleteAllowed = false;

layout
{
area(Content)
{
repeater(General)
{
field(id; Rec.SystemId)
{
Caption = 'Id';
Editable = false;
}
field(subjectSystemId; Rec."Subject System Id")
{
Caption = 'Subject System Id';
}
field(subjectType; Rec."Subject Type")
{
Caption = 'Subject Type';
}
field(subjectVersion; Rec."Subject Version")
{
Caption = 'Subject Version';
Editable = false;
}
field(expenseCategoryCode; Rec."Expense Category Code")
{
Caption = 'Expense Category Code';
Editable = false;
}
field(policySystemId; Rec."Policy System Id")
{
Caption = 'Policy System Id';
}
field(policyVersion; Rec."Policy Version")
{
Caption = 'Policy Version';
Editable = false;
}
field(isCurrent; Rec."Is Current")
{
Caption = 'Is Current';
Editable = false;
}
field(reason; Rec."Reason")
{
Caption = 'Reason';
}
field(flaggedAt; Rec."Flagged At")
{
Caption = 'Flagged At';
}
field(compliant; Rec."Compliant")
{
Caption = 'Compliant';
}
}
}
}

trigger OnInit()
var
ExpenseAgentAPIValidation: Codeunit "Expense Agent API Validation";
begin
ExpenseAgentAPIValidation.VerifyAgentAccess();
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,21 @@ page 6929 "Expense Report Lines API"
Caption = 'Project Task Description';
Editable = false;
}
field(policiesEvaluatedAt; Rec."Policies Evaluated At")
{
Caption = 'Policies Evaluated At';
Editable = false;
}
field(policyStatus; PolicyStatusDisplay)
{
Caption = 'Policy Status';
Editable = false;
}
field(hasPolicyViolation; HasPolicyViolationDisplay)
{
Caption = 'Has Policy Violation';
Editable = false;
}

part(expense; "Expenses API")
{
Expand Down Expand Up @@ -368,6 +383,20 @@ page 6929 "Expense Report Lines API"
SubPageLink = "Expense Report No." = field("Document No."),
"Report Line No." = field("Line No.");
}
part(expensePolicyFlags; "Expense Policy Flags API")
{
Caption = 'Expense Policy Flags';
EntityName = 'expensePolicyFlag';
EntitySetName = 'expensePolicyFlags';
SubPageLink = "Subject System Id" = field(SystemId), "Subject Type" = const("Expense Report Line"), "Subject Version" = field("Policy Eval Version");
}
part(policiesToEvaluate; "Exp. Policies To Eval API")
{
Caption = 'Policies To Evaluate';
EntityName = 'policyToEvaluate';
EntitySetName = 'policiesToEvaluate';
SubPageLink = "Subject System Id" = field(SystemId);
}
}
}
}
Expand All @@ -382,7 +411,10 @@ page 6929 "Expense Report Lines API"
TotalMileage: Decimal;
JobDescription: Text[100];
JobTaskDescription: Text[100];
PolicyStatusDisplay: Enum "Expense Policy Status";
HasPolicyViolationDisplay: Boolean;
TargetExpenseReportNotFoundErr: Label 'Expense report with Id %1 not found.', Comment = '%1 = Expense Report Header SystemId';
OutstandingPoliciesErr: Label 'Cannot mark policies evaluated: one or more applicable policies have not yet been evaluated for the current version of this expense report line. Retrieve the outstanding policies, submit a verdict for each, and try again.';

trigger OnInit()
var
Expand All @@ -395,7 +427,7 @@ page 6929 "Expense Report Lines API"
trigger OnOpenPage()
begin
// Avoid JIT load consistency errors by ensuring fields read in OnAfterGetRecord are included in the initial record buffer.
Rec.AddLoadFields("Expense Currency Code", "Expense User No.", Mileage, "Round Trip");
Rec.AddLoadFields("Expense Currency Code", "Expense User No.", Mileage, "Round Trip", "Policy Eval Version", "Evaluated Policy Version");
end;

trigger OnAfterGetRecord()
Expand All @@ -408,6 +440,8 @@ page 6929 "Expense Report Lines API"
XCurrencyCodeDisplay := CurrencyCodeDisplay;
ExpenseUserSystemId := ExpenseUser.GetSystemIdByExpenseUserNo(Rec."Expense User No.");
TotalMileage := ExpenseAutoPopulation.GetEffectiveDistance(Rec.Mileage, Rec."Round Trip");
PolicyStatusDisplay := Rec.GetPolicyStatus();
HasPolicyViolationDisplay := Rec.HasCurrentPolicyViolation();

JobDescription := '';
JobTaskDescription := '';
Expand Down Expand Up @@ -493,4 +527,24 @@ page 6929 "Expense Report Lines API"
ActionContext.AddEntityKey(Rec.FieldNo(SystemId), Rec.SystemId);
ActionContext.SetResultCode(WebServiceActionResultCode::Updated);
end;

[ServiceEnabled]
procedure MarkPoliciesEvaluated(var ActionContext: WebServiceActionContext)
var
PoliciesToEvalBuilder: Codeunit "Exp. Policies To Eval Builder";
begin
// Guard the agent contract: refuse to mark the line evaluated while an applicable policy still
// lacks a verdict for the current version. A partial agent run would otherwise expose a
// Cleared/Flagged status that silently ignores the unevaluated policies. The caller should
// fetch the outstanding policies (policiesToEvaluate), submit a verdict for each, and retry.
if PoliciesToEvalBuilder.HasOutstandingPolicies(Rec) then
Error(OutstandingPoliciesErr);

Rec.MarkPoliciesEvaluated();

ActionContext.SetObjectType(ObjectType::Page);
ActionContext.SetObjectId(Page::"Expense Report Lines API");
ActionContext.AddEntityKey(Rec.FieldNo(SystemId), Rec.SystemId);
ActionContext.SetResultCode(WebServiceActionResultCode::Updated);
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.ExpenseAgent;

page 7108 "Exp. Policies To Eval API"
{
PageType = API;
APIPublisher = 'microsoft';
APIGroup = 'expense';
APIVersion = 'beta';
EntityName = 'policyToEvaluate';
EntitySetName = 'policiesToEvaluate';
EntityCaption = 'Policy To Evaluate';
EntitySetCaption = 'Policies To Evaluate';
SourceTable = "Exp. Policy To Eval Buffer";
SourceTableTemporary = true;
ODataKeyFields = "Subject System Id", "Policy System Id";
DataAccessIntent = ReadOnly;
Editable = false;
InsertAllowed = false;
ModifyAllowed = false;
DeleteAllowed = false;
Extensible = false;
AboutTitle = 'Policies To Evaluate';
AboutText = 'Returns the enabled policies that still need to be evaluated for an expense report line - policies newly introduced or bumped to a new version, or any policy not yet evaluated at the line''s current version. Read this to know which policies to (re)run when a line is stale.';

layout
{
area(Content)
{
repeater(General)
{
field(subjectSystemId; Rec."Subject System Id")
{
Caption = 'Subject System Id';
}
field(subjectVersion; Rec."Subject Version")
{
Caption = 'Subject Version';
}
field(policySystemId; Rec."Policy System Id")
{
Caption = 'Policy System Id';
}
field(policyLineNo; Rec."Policy Line No.")
{
Caption = 'Policy Line No.';
}
field(policyVersion; Rec."Policy Version")
{
Caption = 'Policy Version';
}
field(expenseCategoryCode; Rec."Expense Category Code")
{
Caption = 'Expense Category Code';
}
field(description; Rec."Description")
{
Caption = 'Description';
}
field(policyText; Rec."Policy Text")
{
Caption = 'Policy Text';
}
}
}
}

var
Builder: Codeunit "Exp. Policies To Eval Builder";
BuiltSubjectFilter: Text;
HasBuilt: Boolean;

trigger OnInit()
var
ExpenseAgentAPIValidation: Codeunit "Expense Agent API Validation";
begin
ExpenseAgentAPIValidation.VerifyAgentAccess();
end;

trigger OnFindRecord(Which: Text): Boolean
var
SubjectFilter: Text;
FilterView: Text;
begin
SubjectFilter := Rec.GetFilter("Subject System Id");
// Rebuild whenever the parent subject changes so $expand across many lines stays correct.
if (not HasBuilt) or (SubjectFilter <> BuiltSubjectFilter) then begin
FilterView := Rec.GetView();
Builder.Build(Rec, SubjectFilter);
Rec.SetView(FilterView);
BuiltSubjectFilter := SubjectFilter;
HasBuilt := true;
end;
exit(Rec.Find(Which));
end;

trigger OnNextRecord(Steps: Integer): Integer
begin
exit(Rec.Next(Steps));
end;
}
Loading
Loading