Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
f89f029
Expense Agent: port policy evaluation backend to BCApps
encimita Aug 6, 2026
b0e571a
Expense Agent: clear lines without applicable policies
encimita Aug 6, 2026
0b09b4d
Expense Agent: address PR review comments (policy invalidation, super…
encimita Aug 7, 2026
ec51c1f
Harden policy-flag API: guard mark-evaluated and validate flag inserts
encimita Aug 7, 2026
eca39ec
Surface expense-line policy status in the client with drilldown (open…
encimita Aug 7, 2026
9580474
Simplify policy flag: drop Policy Line No., rename Description to Reason
encimita Aug 7, 2026
b290796
Add Subject Type to expense policy flag primary key
encimita Aug 7, 2026
b7a7902
Renumber Posted Exp. Policy Flag Card page 7110 -> 7106
encimita Aug 7, 2026
070175a
Add distinct "No Policies" policy status for lines with nothing to ev…
encimita Aug 10, 2026
4b25940
Add in-transaction policy currency gate on expense report submit
encimita Aug 10, 2026
49213d0
Lock the machine-detectable submit-gate token instead of commenting it
encimita Aug 10, 2026
e68d287
Merge origin/main into copilot/expense-agent-policy-evaluation-251762
encimita Aug 10, 2026
2fb1305
Fix policy-currency test failures and address review perf comments
encimita Aug 10, 2026
2505259
Fix policy-currency submit gate test and address PR review comments
encimita Aug 10, 2026
133dab9
Expense Agent: fix policy-currency CI test failures
encimita Aug 11, 2026
1a79aad
Expense Agent: address privacy and key-selection review comments
encimita Aug 11, 2026
ea05b91
Expense Agent: avoid string concatenation in Error() (AA0231)
encimita Aug 11, 2026
b62e4fa
Merge remote-tracking branch 'origin/main' into copilot/expense-agent…
encimita Aug 11, 2026
3f61e55
Add expense activity history foundation
t-prda Aug 13, 2026
5a92237
Fix expense activity review issues
t-prda Aug 13, 2026
2becc02
Resolve activity page object IDs
t-prda Aug 13, 2026
259268e
Resolve localized activity codeunit IDs
t-prda Aug 13, 2026
29f330a
Harden activity history access
t-prda Aug 13, 2026
3da9c13
Resolve activity API test object ID
t-prda Aug 13, 2026
5bbe88d
Document activity API preload
t-prda Aug 13, 2026
ad5c4db
Use committed reads for activity API
t-prda Aug 13, 2026
928440a
Index retention and parse capability JSON
t-prda Aug 13, 2026
0104770
Make persistent activity scenarios rerunnable
t-prda Aug 13, 2026
58ae2aa
Add activity and posted-report query keys
t-prda Aug 13, 2026
a36355e
Rename activity event key
t-prda Aug 13, 2026
6284d35
Use default activity API read isolation
t-prda Aug 13, 2026
775467b
Stabilize activity tests under restricted permissions
t-prda Aug 14, 2026
311cc8b
Avoid expense policy page ID clashes
t-prda Aug 14, 2026
9d17c0e
Preserve report-specific reopen authorization
t-prda Aug 14, 2026
f5291a1
Align activity API and table patterns
t-prda Aug 14, 2026
31f533f
Consolidate activity API E2E coverage
t-prda Aug 14, 2026
71778e1
Entitle activity retention subscriber
t-prda Aug 14, 2026
59de18f
Align retention subscriber permissions
t-prda Aug 14, 2026
df5ed7c
Test public reopen authorization path
t-prda Aug 14, 2026
d34aae0
Merge latest BCApps main
t-prda Aug 14, 2026
f6530cd
Restore permissions before expense test cleanup
t-prda Aug 14, 2026
5bcd231
Grant retention users activity subscriber access
t-prda Aug 14, 2026
ef68098
Defer activity retention policy
t-prda Aug 14, 2026
766f9f2
Align activity API reads and E2E fixtures
t-prda Aug 14, 2026
a4b631f
Align activity factbox permissions
t-prda Aug 14, 2026
cdc74f6
Keep activity API agent-only
t-prda Aug 14, 2026
9d2ee7e
Fix reopen test variable scope
t-prda Aug 14, 2026
b93fe54
Assert individual expense capability states
t-prda Aug 14, 2026
f9fd11f
Read capability JSON values directly
t-prda Aug 14, 2026
08bbc11
Clear capability JSON loop state
t-prda Aug 14, 2026
e001f2c
Snapshot report compliance in activity history
t-prda Aug 14, 2026
7d21351
Verify activity compliance API field
t-prda Aug 14, 2026
9c060a2
Snapshot report rule violations in activity history
t-prda Aug 14, 2026
e536a0b
Remove redundant activity compliance data
t-prda Aug 14, 2026
c31f31e
Snapshot expense and attached receipt counts
t-prda Aug 14, 2026
f906af6
Preserve capability API JSON property names
t-prda Aug 14, 2026
3d37d18
Address activity API review feedback
t-prda Aug 14, 2026
25d0e34
Grant activity logger access to BC users
t-prda Aug 14, 2026
c91d245
Load expense report number during cleanup
t-prda Aug 14, 2026
71d5453
Classify expense activity log sensitivity
t-prda Aug 14, 2026
a74e0fa
Merge origin/main into expense agent policy evaluation
encimita Aug 17, 2026
fa7c926
Merge BCApps PR #10255
t-prda Aug 17, 2026
55dcf05
Merge BCApps PR #10031 with activity history
t-prda Aug 17, 2026
4ff3874
Resolve NAV object ID conflicts
t-prda Aug 17, 2026
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
2 changes: 1 addition & 1 deletion src/Apps/W1/ExpenseAgent/app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
},
{
"from": 7073,
"to": 7103
"to": 7126
}
],
"features": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// ------------------------------------------------------------------------------------------------
// 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 7122 "Expense Activity Log API"
{
APIGroup = 'expense';
APIPublisher = 'microsoft';
APIVersion = 'beta';
EntityCaption = 'Expense Activity Log Entry';
EntitySetCaption = 'Expense Activity Log Entries';
EntityName = 'expenseActivityLogEntry';
EntitySetName = 'expenseActivityLogEntries';
PageType = API;
DelayedInsert = true;
SourceTable = "Expense Activity Log Entry";
ODataKeyFields = SystemId;
DataAccessIntent = ReadOnly;
Editable = false;
InsertAllowed = false;
ModifyAllowed = false;
DeleteAllowed = false;
Extensible = false;
AboutText = 'Provides activity history when scoped through an expense report, posted expense report, or expense user. Direct unscoped access is not allowed. Expense user history requires the historyActorRole filter.';

layout
{
area(Content)
{
repeater(General)
{
field(id; Rec.SystemId)
{
Caption = 'ID';
}
field(entryNumber; Rec."Entry No.")
{
Caption = 'Entry Number';
}
field(sourceTableId; Rec."Source Table ID")
{
Caption = 'Source Table ID';
}
field(sourceId; Rec."Source Record System ID")
{
Caption = 'Source ID';
}
field(subjectTableId; Rec."Subject Table ID")
{
Caption = 'Subject Table ID';
}
field(subjectId; Rec."Subject System ID")
{
Caption = 'Subject ID';
}
field(documentNumber; Rec."Document No.")
{
Caption = 'Document Number';
}
field(documentDescription; Rec."Document Description")
{
Caption = 'Document Description';
}
field(eventType; Rec."Event Type")
{
Caption = 'Event Type';
}
field(occurredAt; Rec."Occurred At")
{
Caption = 'Occurred At';
}
field(initiatedBy; Rec."Initiated By")
{
Caption = 'Initiated By';
}
field(actorRole; Rec."Actor Role")
{
Caption = 'Actor Role';
}
field(actorTableId; Rec."Actor Table ID")
{
Caption = 'Actor Table ID';
}
field(actorId; Rec."Actor Record System ID")
{
Caption = 'Actor ID';
}
field(actorDisplayName; Rec."Actor Display Name")
{
Caption = 'Actor Display Name';
}
field(comment; Rec.Comment)
{
Caption = 'Comment';
}
field(amountLCY; Rec."Amount (LCY)")
{
Caption = 'Amount (LCY)';
}
field(currencyLCY; CurrencyLCY)
{
Caption = 'Currency (LCY)';
}
field(nonRefundableAmountLCY; Rec."Non-Refundable Amount (LCY)")
{
Caption = 'Non-Refundable Amount (LCY)';
}
field(reimbursableAmount; Rec."Reimbursable Amount")
{
Caption = 'Reimbursable Amount';
}
field(reimbursableAmountLCY; Rec."Reimbursable Amount (LCY)")
{
Caption = 'Reimbursable Amount (LCY)';
}
field(refundableAmount; Rec."Refundable Amount")
{
Caption = 'Refundable Amount';
}
field(refundableAmountLCY; Rec."Refundable Amount (LCY)")
{
Caption = 'Refundable Amount (LCY)';
}
field(reimbursementCurrencyCode; ReimbursementCurrencyCode)
{
Caption = 'Reimbursement Currency Code';
}
field(reimbursementCurrencyFactor; Rec."Reimbursement Currency Factor")
{
Caption = 'Reimbursement Currency Factor';
}
field(categories; Rec.Categories)
{
Caption = 'Categories';
}
field(attachedReceiptCount; Rec."Attached Receipt Count")
{
Caption = 'Attached Receipt Count';
}
field(expenseCount; Rec."Expense Count")
{
Caption = 'Expense Count';
}
field(historyActorRole; Rec."History Actor Role Filter")
{
Caption = 'History Actor Role';
}
}
}
}

var
CurrencyHelper: Codeunit "Expense API Currency Helper";
CurrencyLCY: Code[10];
HistoryScopeApplied: Boolean;
ReimbursementCurrencyCode: Code[10];
HistoryActorRoleRequiredErr: Label 'The historyActorRole filter must be specified as Submitter or Approver.';
ActivityScopeRequiredErr: Label 'Activity log entries must be requested through an expense report, posted expense report, or expense user.';

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

trigger OnOpenPage()
begin
// Avoid JIT load consistency errors by including fields read in OnAfterGetRecord in the initial record buffer.
Rec.AddLoadFields("Reimbursement Currency Code");
end;

trigger OnAfterGetRecord()
begin
Clear(CurrencyLCY);
Clear(ReimbursementCurrencyCode);
if Rec."Event Type" in [Rec."Event Type"::Submitted, Rec."Event Type"::Resubmitted, Rec."Event Type"::Posted] then begin
CurrencyLCY := CurrencyHelper.GetCurrencyCodeForAPI('');
ReimbursementCurrencyCode := CurrencyHelper.GetCurrencyCodeForAPI(Rec."Reimbursement Currency Code");
end;
end;

trigger OnFindRecord(Which: Text): Boolean
begin
ApplyHistoryScope();
exit(Rec.Find(Which));
end;

local procedure ApplyHistoryScope()
var
OriginalFilterGroup: Integer;
HasHistoryActorFilters: Boolean;
HasSourceFilters: Boolean;
HistoryActorRoleFilter: Text;
begin
if HistoryScopeApplied then
exit;

OriginalFilterGroup := Rec.FilterGroup();
HistoryActorRoleFilter := Rec.GetFilter("History Actor Role Filter");
Rec.FilterGroup(4);
HasSourceFilters :=
(Rec.GetFilter("Source Table ID") <> '') and
(Rec.GetFilter("Source Record System ID") <> '');
HasHistoryActorFilters :=
(Rec.GetFilter("History Actor Table ID Filter") <> '') and
(Rec.GetFilter("History Actor System ID Filter") <> '');
if HistoryActorRoleFilter = '' then
HistoryActorRoleFilter := Rec.GetFilter("History Actor Role Filter");
Rec.FilterGroup(0);
if HasHistoryActorFilters then begin
if HistoryActorRoleFilter <> '' then begin
Rec.SetCurrentKey("Occurred At", "Entry No.");
Rec.Ascending(false);
Rec.SetRange("History Subject Match", true)
end else
Error(HistoryActorRoleRequiredErr);
end else
if HasSourceFilters then begin
Rec.SetCurrentKey("Source Table ID", "Source Record System ID", "Occurred At", "Entry No.");
Rec.Ascending(false);
end else
Error(ActivityScopeRequiredErr);
Rec.FilterGroup(OriginalFilterGroup);
HistoryScopeApplied := true;
end;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// ------------------------------------------------------------------------------------------------
// 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;
Editable = 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 7125 "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";
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;
}
Loading
Loading