-
Notifications
You must be signed in to change notification settings - Fork 438
Add expense activity history foundation #10255
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
Merged
Prangshuman Das (t-prda)
merged 42 commits into
main
from
feature/expense-activity-log-foundation
Aug 17, 2026
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
3f61e55
Add expense activity history foundation
t-prda 5a92237
Fix expense activity review issues
t-prda 2becc02
Resolve activity page object IDs
t-prda 259268e
Resolve localized activity codeunit IDs
t-prda 29f330a
Harden activity history access
t-prda 3da9c13
Resolve activity API test object ID
t-prda 5bbe88d
Document activity API preload
t-prda ad5c4db
Use committed reads for activity API
t-prda 928440a
Index retention and parse capability JSON
t-prda 0104770
Make persistent activity scenarios rerunnable
t-prda 58ae2aa
Add activity and posted-report query keys
t-prda a36355e
Rename activity event key
t-prda 6284d35
Use default activity API read isolation
t-prda 775467b
Stabilize activity tests under restricted permissions
t-prda 311cc8b
Avoid expense policy page ID clashes
t-prda 9d17c0e
Preserve report-specific reopen authorization
t-prda f5291a1
Align activity API and table patterns
t-prda 31f533f
Consolidate activity API E2E coverage
t-prda 71778e1
Entitle activity retention subscriber
t-prda 59de18f
Align retention subscriber permissions
t-prda df5ed7c
Test public reopen authorization path
t-prda d34aae0
Merge latest BCApps main
t-prda f6530cd
Restore permissions before expense test cleanup
t-prda 5bcd231
Grant retention users activity subscriber access
t-prda ef68098
Defer activity retention policy
t-prda 766f9f2
Align activity API reads and E2E fixtures
t-prda a4b631f
Align activity factbox permissions
t-prda cdc74f6
Keep activity API agent-only
t-prda 9d2ee7e
Fix reopen test variable scope
t-prda b93fe54
Assert individual expense capability states
t-prda f9fd11f
Read capability JSON values directly
t-prda 08bbc11
Clear capability JSON loop state
t-prda e001f2c
Snapshot report compliance in activity history
t-prda 7d21351
Verify activity compliance API field
t-prda 9c060a2
Snapshot report rule violations in activity history
t-prda e536a0b
Remove redundant activity compliance data
t-prda c31f31e
Snapshot expense and attached receipt counts
t-prda f906af6
Preserve capability API JSON property names
t-prda 3d37d18
Address activity API review feedback
t-prda 25d0e34
Grant activity logger access to BC users
t-prda c91d245
Load expense report number during cleanup
t-prda 71d5453
Classify expense activity log sensitivity
t-prda File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -134,7 +134,7 @@ | |
| }, | ||
| { | ||
| "from": 7073, | ||
| "to": 7103 | ||
| "to": 7123 | ||
| } | ||
| ], | ||
| "features": [ | ||
|
|
||
230 changes: 230 additions & 0 deletions
230
src/Apps/W1/ExpenseAgent/app/src/APIs/ActivityLog/ExpenseActivityLogAPI.Page.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | ||
|
t-prda marked this conversation as resolved.
|
||
| var | ||
| ExpenseAgentAPIValidation: Codeunit "Expense Agent API Validation"; | ||
| begin | ||
| ExpenseAgentAPIValidation.VerifyAgentAccess(); | ||
| end; | ||
|
|
||
| trigger OnOpenPage() | ||
|
t-prda marked this conversation as resolved.
|
||
| begin | ||
| // Avoid JIT load consistency errors by including fields read in OnAfterGetRecord in the initial record buffer. | ||
| Rec.AddLoadFields("Reimbursement Currency Code"); | ||
| end; | ||
|
t-prda marked this conversation as resolved.
t-prda marked this conversation as resolved.
|
||
|
|
||
| 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 | ||
|
t-prda marked this conversation as resolved.
|
||
| 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; | ||
|
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.