Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ tableextension 6908 "Expense Spend Request" extends "Spend Request"
TestStatusOpen();
end;
}
field(6910; "Requested For Name"; Text[100])
Comment thread
v-rohangarg20 marked this conversation as resolved.
{
Caption = 'Requested For Name';
FieldClass = FlowField;
CalcFormula = lookup("Expense User".Name where("No." = field("Requested For")));
ToolTip = 'Specifies the name of the expense user for whom the spend request is being created.';
}
field(6911; "Actual Start Date and Time"; DateTime)
{
Caption = 'Actual Start Date and Time';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ codeunit 6908 "Expense Event Subscriber"
exit;

SpendRequest.Status := SpendRequest.Status::Approved;
SpendRequest."Approved/Rejected At" := CurrentDateTime();
SpendRequest."Approved/Rejected by User ID" := UserSecurityId();
SpendRequest."Approved/Rejected by User Name" := CopyStr(UserId(), 1, MaxStrLen(SpendRequest."Approved/Rejected by User Name"));
SpendRequest.Modify();
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ page 7129 "Travel Request Card"
field("No."; Rec."No.")
{
ToolTip = 'Specifies the number of the travel request.';
Editable = Rec."No." = '';

trigger OnAssistEdit()
begin
Expand All @@ -38,11 +39,18 @@ page 7129 "Travel Request Card"
field("Requested For"; Rec."Requested For")
{
ToolTip = 'Specifies the expense user for whom the travel request is being created.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Requested For Name"; Rec."Requested For Name")
{
ToolTip = 'Specifies the name of the expense user for whom the travel request is being created.';
Editable = Rec.Status = Rec.Status::Open;
}
field(Purpose; Rec.Purpose)
{
MultiLine = true;
ToolTip = 'Specifies the purpose of the travel request.';
Editable = Rec.Status = Rec.Status::Open;
}
field(Status; Rec.Status)
{
Expand Down Expand Up @@ -93,6 +101,7 @@ page 7129 "Travel Request Card"
{
Importance = Additional;
ToolTip = 'Specifies the code for Shortcut Dimension 1, which is one of two global dimension codes that you set up.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Shortcut Dimension 2 Code"; Rec."Shortcut Dimension 2 Code")
{
Expand All @@ -115,21 +124,25 @@ page 7129 "Travel Request Card"
{
Importance = Promoted;
ToolTip = 'Specifies the expected start date of the travel.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Expected End Date"; Rec."Expected End Date")
{
Importance = Promoted;
ToolTip = 'Specifies the expected end date of the travel.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Actual Start Date and Time"; Rec."Actual Start Date and Time")
{
Importance = Additional;
ToolTip = 'Specifies the actual start date and time of the travel.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Actual End Date and Time"; Rec."Actual End Date and Time")
{
Importance = Additional;
ToolTip = 'Specifies the actual end date and time of the travel.';
Editable = Rec.Status = Rec.Status::Open;
}
}
group("Travel Details")
Expand All @@ -141,33 +154,40 @@ page 7129 "Travel Request Card"
MultiLine = true;
Importance = Additional;
ToolTip = 'Specifies the business justification for the travel.';
Editable = Rec.Status = Rec.Status::Open;
}
field("International Travel"; Rec."International Travel")
{
Importance = Additional;
ToolTip = 'Specifies whether the travel is international.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Origin Country"; Rec."Origin Country/Region Code")
{
ToolTip = 'Specifies the origin country for the travel.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Destination Country"; Rec."Dest. Country/Region Code")
{
ToolTip = 'Specifies the destination country for the travel.';
Editable = Rec.Status = Rec.Status::Open;
}
field(Restrictions; Rec.Restrictions)
{
Importance = Additional;
ToolTip = 'Specifies any travel restrictions that apply.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Travel Policy Acknowledgment"; Rec."Travel Policy Acknowledgment")
{
ToolTip = 'Specifies whether the travel policy has been acknowledged.';
Editable = Rec.Status = Rec.Status::Open;
}
field("Per Diem Included"; Rec."Per Diem Included")
{
Importance = Additional;
ToolTip = 'Specifies whether per diem is included in the travel request.';
Editable = Rec.Status = Rec.Status::Open;
}
}
group(Approval)
Expand Down
128 changes: 128 additions & 0 deletions src/Apps/W1/ExpenseAgent/test/src/SpendRequestTest.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ using Microsoft.Finance.GeneralLedger.Preview;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.SpendRequest;
using Microsoft.HumanResources.Employee;
using System.Security.AccessControl;

codeunit 148339 "Spend Request Test"
{
Expand Down Expand Up @@ -51,6 +52,13 @@ codeunit 148339 "Spend Request Test"
CategoryClearedMsg: Label 'The expense category should be cleared when the line is not a Category line.';
MixedTypesMsg: Label 'Category and Lump Sum lines should coexist on the same travel request.';
CategoryLineOnlyErr: Label 'You can select an %1 only when %2 is %3.', Locked = true;
ApproverUserIdRecordedMsg: Label 'The approving user should be recorded on the travel request.';
ApproverUserNameRecordedMsg: Label 'The approving user name should be recorded on the travel request.';
ApproverDateTimeRecordedMsg: Label 'The approval date and time should be recorded on the travel request.';
RequestedForNameMirrorsMsg: Label 'The requested-for name should mirror the expense user name.';
RequestedForNameClearedMsg: Label 'The requested-for name should be cleared when the requester is removed.';
RequesterFieldsEditableMsg: Label 'The requester fields and purpose should be editable while the travel request is open.';
RequesterFieldsReadOnlyMsg: Label 'The requester fields and purpose should be read-only once the travel request leaves the open status.';

[Test]
[HandlerFunctions('SpendReqConfirmHandler')]
Expand Down Expand Up @@ -344,6 +352,29 @@ codeunit 148339 "Spend Request Test"
Assert.AreEqual(SpendRequest.Status::Approved, SpendRequest.Status, SpendReqApprovedMsg);
end;

[Test]
procedure ReleaseSpendReqRecordsApproverInfoWhenAgentDisabled()
var
SpendRequest: Record "Spend Request";
ExpenseUser: Record "Expense User";
ReleaseSpendRequest: Codeunit "Release Spend Request";
begin
// [SCENARIO 650348] Auto-approving a travel request records approver audit fields.
Initialize();

// [GIVEN] A releasable spend request with every prerequisite satisfied.
CreateReleasableSpendRequest(SpendRequest, ExpenseUser);

// [WHEN] The spend request is Released.
ReleaseSpendRequest.Release(SpendRequest);

// [THEN] Approver user id, user name, and timestamp are recorded.
SpendRequest.Get(SpendRequest."No.");
Assert.AreEqual(UserSecurityId(), SpendRequest."Approved/Rejected by User ID", ApproverUserIdRecordedMsg);
Assert.AreEqual(GetExpectedApproverName(), SpendRequest."Approved/Rejected by User Name", ApproverUserNameRecordedMsg);
Assert.AreNotEqual(0DT, SpendRequest."Approved/Rejected At", ApproverDateTimeRecordedMsg);
end;

[Test]
procedure ReleaseSpendReqStaysReleasedWhenAgentEnabled()
var
Expand Down Expand Up @@ -553,6 +584,93 @@ codeunit 148339 "Spend Request Test"
Assert.RecordCount(Traveler, 1);
end;

[Test]
[HandlerFunctions('SpendReqConfirmHandler')]
procedure RequestedForNameMirrorsExpenseUserName()
var
SpendRequest: Record "Spend Request";
ExpenseUser: Record "Expense User";
begin
// [SCENARIO 650348] "Requested For Name" mirrors the requester and is cleared when the requester is removed.
Initialize();

// [GIVEN] An expense user and an open travel request.
LibraryExpense.CreateExpenseUser(ExpenseUser);
LibraryExpense.CreateSpendRequest(SpendRequest);

// [WHEN] The expense user is set as requester.
SpendRequest.Validate("Requested For", ExpenseUser."No.");
SpendRequest.Modify(true);

// [THEN] The denormalized requester name is populated.
SpendRequest.CalcFields("Requested For Name");
Assert.AreEqual(ExpenseUser.Name, SpendRequest."Requested For Name", RequestedForNameMirrorsMsg);

// [WHEN] The requester is removed.
SpendRequest.Validate("Requested For", '');
SpendRequest.Modify(true);

// [THEN] The denormalized requester name is cleared.
SpendRequest.CalcFields("Requested For Name");
Assert.AreEqual('', SpendRequest."Requested For Name", RequestedForNameClearedMsg);
end;

[Test]
procedure PurposeCannotBeEditedWhenRequestNotOpen()
var
SpendRequest: Record "Spend Request";
ExpenseUser: Record "Expense User";
begin
// [SCENARIO 650348] The purpose cannot be changed once the travel request leaves the Open status.
Initialize();

// [GIVEN] An approved (non-open) travel request.
CreateReleasableSpendRequest(SpendRequest, ExpenseUser);
LibraryExpense.SetSpendRequestStatus(SpendRequest, SpendRequest.Status::Approved);

// [WHEN] The purpose is changed.
asserterror SpendRequest.Validate(Purpose, 'Updated purpose after approval');

// [THEN] It fails because the request must be open to be edited.
Assert.ExpectedError(StatusNotOpenErr);
end;

[Test]
procedure TravelRequestIdentityFieldsEditableOnlyWhenOpen()
var
SpendRequest: Record "Spend Request";
ExpenseUser: Record "Expense User";
TravelRequestCard: TestPage "Travel Request Card";
begin
// [SCENARIO 650348] The requester and purpose fields on the travel request card are editable only while the request is Open.
Initialize();

// [GIVEN] An open travel request.
CreateReleasableSpendRequest(SpendRequest, ExpenseUser);

// [WHEN] The card is opened while the request is Open.
TravelRequestCard.OpenEdit();
TravelRequestCard.GoToRecord(SpendRequest);

// [THEN] The requester fields and purpose are editable.
Assert.IsTrue(TravelRequestCard."Requested For".Editable(), RequesterFieldsEditableMsg);
Assert.IsTrue(TravelRequestCard.Purpose.Editable(), RequesterFieldsEditableMsg);
TravelRequestCard.Close();

// [GIVEN] The request is approved and therefore no longer open.
LibraryExpense.SetSpendRequestStatus(SpendRequest, SpendRequest.Status::Approved);

// [WHEN] The card is reopened.
TravelRequestCard.OpenEdit();
TravelRequestCard.GoToRecord(SpendRequest);

// [THEN] The requester fields and purpose are read-only.
Assert.IsFalse(TravelRequestCard."Requested For".Editable(), RequesterFieldsReadOnlyMsg);
Assert.IsFalse(TravelRequestCard."Requested For Name".Editable(), RequesterFieldsReadOnlyMsg);
Assert.IsFalse(TravelRequestCard.Purpose.Editable(), RequesterFieldsReadOnlyMsg);
TravelRequestCard.Close();
end;

[Test]
procedure AddDuplicateTravelerFails()
var
Expand Down Expand Up @@ -1096,6 +1214,16 @@ codeunit 148339 "Spend Request Test"
Traveler.DeleteAll();
end;

local procedure GetExpectedApproverName(): Code[50]
var
User: Record User;
begin
if User.ReadPermission() then
if User.Get(UserSecurityId()) then
exit(CopyStr(User."User Name", 1, 50));
exit(CopyStr(UserId(), 1, 50));
end;

[PageHandler]
procedure SpendReqGLPostingPreviewHandler(var GLPostingPreview: TestPage "G/L Posting Preview")
begin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ table 6840 "Spend Request"
{
Caption = 'Purpose';
ToolTip = 'Specifies the purpose of the spend request.';

trigger OnValidate()
begin
TestStatusOpen();
end;
}
field(9; "Currency Code"; Code[10])
{
Expand Down
Loading