Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5ec533a
add failing test reproducing the issue
fahimj Oct 17, 2025
af13b6b
add unit test to verify issue 72742
fahimj Oct 17, 2025
bf12d36
Enhance IOU tests by adding senior manager role and updating approval…
fahimj Oct 17, 2025
b47bbf3
Implement getBypassApproverIfTakenControl function to enhance approva…
fahimj Oct 17, 2025
0d1f14e
add unit test to verify it should update next step mesage to show wai…
fahimj Oct 17, 2025
bdd7422
revert attempt to fix optimistic next approver message
fahimj Oct 18, 2025
6683a9b
Merge branch 'main' into fix/69249-2
fahimj Oct 18, 2025
d67dcd6
Resolve merge conflict in setDraftSplitTransaction function signature
fahimj Oct 18, 2025
985de4a
Resolve merge conflict in import comments
fahimj Oct 18, 2025
8b6fff3
Revert "Resolve merge conflict in import comments"
fahimj Oct 18, 2025
b8b84f3
eslint update
fahimj Oct 18, 2025
3a0d6dc
Add isUnapprove parameter to submitReport function and update test case
fahimj Oct 21, 2025
73106a9
Merge branch 'main' into fix/69249-2
fahimj Oct 21, 2025
d545287
remove deprecated translateLocal and use useLocalize
fahimj Oct 21, 2025
6d2078c
Revert "remove deprecated translateLocal and use useLocalize"
fahimj Oct 22, 2025
dd92817
Merge branch 'main' into fix/69249-2
fahimj Oct 22, 2025
ce023a2
Merge branch 'main' into fix/69249-2
fahimj Oct 25, 2025
0f79906
Refactor IOU actions to include policy and account details in approve…
fahimj Oct 25, 2025
48e869d
Refactor approveMoneyRequest function to simplify parameter passing f…
fahimj Oct 25, 2025
ebf8ae5
Update getNextApproverAccountID function to return submitToAccountID …
fahimj Oct 27, 2025
23ad616
Merge branch 'main' into fix/69249-2
fahimj Oct 30, 2025
0d0f1b8
fix: Disable ESLint warning for deprecated InteractionManager usage i…
fahimj Oct 30, 2025
a6dba16
refactor: Rename function and variable name
fahimj Nov 3, 2025
8c76e60
Merge branch 'main' into fix/69249-2
fahimj Nov 3, 2025
8240152
Merge branch 'main' into fix/69249-2
fahimj Nov 3, 2025
132baa0
Merge branch 'main' into fix/69249-2
fahimj Nov 3, 2025
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
53 changes: 50 additions & 3 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
getReportActionMessageText,
getReportActionText,
getRetractedMessage,
getSortedReportActions,
getTravelUpdateMessage,
getWorkspaceCurrencyUpdateMessage,
getWorkspaceFrequencyUpdateMessage,
Expand Down Expand Up @@ -942,7 +943,7 @@
const parsedReportActionMessageCache: Record<string, string> = {};

let conciergeReportID: OnyxEntry<string>;
Onyx.connect({

Check warning on line 946 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CONCIERGE_REPORT_ID,
callback: (value) => {
conciergeReportID = value;
Expand All @@ -950,7 +951,7 @@
});

const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';
Onyx.connect({

Check warning on line 954 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, val is undefined
Expand All @@ -968,7 +969,7 @@
let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
let allPersonalDetailLogins: string[];
let currentUserPersonalDetails: OnyxEntry<PersonalDetails>;
Onyx.connect({

Check warning on line 972 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
if (currentUserAccountID) {
Expand All @@ -980,14 +981,14 @@
});

let allReportsDraft: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 984 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
waitForCollectionCallback: true,
callback: (value) => (allReportsDraft = value),
});

let allPolicies: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 991 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => (allPolicies = value),
Expand All @@ -995,7 +996,7 @@

let allReports: OnyxCollection<Report>;
let reportsByPolicyID: ReportByPolicyMap;
Onyx.connect({

Check warning on line 999 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand Down Expand Up @@ -1036,14 +1037,14 @@
});

let allBetas: OnyxEntry<Beta[]>;
Onyx.connect({

Check warning on line 1040 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.BETAS,
callback: (value) => (allBetas = value),
});

let allTransactions: OnyxCollection<Transaction> = {};
let reportsTransactions: Record<string, Transaction[]> = {};
Onyx.connect({

Check warning on line 1047 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -1069,7 +1070,7 @@
});

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 1073 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -1082,7 +1083,7 @@

let allReportMetadata: OnyxCollection<ReportMetadata>;
const allReportMetadataKeyValue: Record<string, ReportMetadata> = {};
Onyx.connect({

Check warning on line 1086 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_METADATA,
waitForCollectionCallback: true,
callback: (value) => {
Expand Down Expand Up @@ -4422,6 +4423,13 @@
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(report?.policyID);

// If the current user took control, then they are the final approver and we don't have a next approver
// If someone else took control or rerouted, they are the next approver
const bypassApproverAccountID = getBypassApproverAccountIDIfTakenControl(report);
if (bypassApproverAccountID) {
return bypassApproverAccountID === currentUserAccountID && !isUnapproved ? undefined : bypassApproverAccountID;
}

const approvalChain = getApprovalChain(policy, report);
const submitToAccountID = getSubmitToAccountID(policy, report);

Expand All @@ -4430,16 +4438,19 @@
return currentUserAccountID;
}

return report?.managerID;
return report?.managerID ?? submitToAccountID;
}

if (approvalChain.length === 0) {
return submitToAccountID;
}

const nextApproverEmail = approvalChain.length === 1 ? approvalChain.at(0) : approvalChain.at(approvalChain.indexOf(currentUserEmail ?? '') + 1);
const currentUserIndex = approvalChain.indexOf(currentUserEmail ?? '');
const nextApproverEmail = currentUserIndex === -1 ? approvalChain.at(0) : approvalChain.at(currentUserIndex + 1);

if (!nextApproverEmail) {
return submitToAccountID;
// If there's no next approver in the chain, return undefined to indicate the current user is the final approver
return undefined;
}

return getAccountIDsByLogins([nextApproverEmail]).at(0);
Expand Down Expand Up @@ -11855,6 +11866,42 @@
return isPaidGroupPolicyPolicyUtils(newPolicy) && !!newPolicy.role;
}

/**
* Checks if someone took control of the report and if that take control is still valid
* A take control is invalidated if there's a SUBMITTED action after it
*/
function getBypassApproverAccountIDIfTakenControl(expenseReport: OnyxEntry<Report>): number | null {
if (!expenseReport?.reportID) {
return null;
}

if (!isProcessingReport(expenseReport)) {
return null;
}

const reportActions = getAllReportActions(expenseReport.reportID);
if (!reportActions) {
return null;
}

// Sort actions by created timestamp to get chronological order
const sortedActions = getSortedReportActions(Object.values(reportActions ?? {}), true);

// Look through actions in reverse chronological order (newest first)
// If we find a SUBMITTED action, there's no valid take control since any take control would be older
for (const action of sortedActions) {
if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED)) {
return null;
}

if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL)) {
return action.actorAccountID ?? null;
}
}

return null;
}

function getApprovalChain(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>): string[] {
const approvalChain: string[] = [];
const fullApprovalChain: string[] = [];
Expand Down
21 changes: 6 additions & 15 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10192,13 +10192,6 @@ function getIOUReportActionToApproveOrPay(chatReport: OnyxEntry<OnyxTypes.Report
});
}

function isLastApprover(approvalChain: string[]): boolean {
if (approvalChain.length === 0) {
return true;
}
return approvalChain.at(-1) === currentUserEmail;
}

function approveMoneyRequest(
expenseReport: OnyxEntry<OnyxTypes.Report>,
policy: OnyxEntry<OnyxTypes.Policy>,
Expand Down Expand Up @@ -10226,22 +10219,19 @@ function approveMoneyRequest(
}
const optimisticApprovedReportAction = buildOptimisticApprovedReportAction(total, expenseReport.currency ?? '', expenseReport.reportID);

// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line @typescript-eslint/no-deprecated
const approvalChain = getApprovalChain(getPolicy(expenseReport.policyID), expenseReport);

const predictedNextStatus = isLastApprover(approvalChain) ? CONST.REPORT.STATUS_NUM.APPROVED : CONST.REPORT.STATUS_NUM.SUBMITTED;
const predictedNextState = isLastApprover(approvalChain) ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED;
const managerID = isLastApprover(approvalChain) ? expenseReport.managerID : getNextApproverAccountID(expenseReport);
const nextApproverAccountID = getNextApproverAccountID(expenseReport);
const predictedNextStatus = !nextApproverAccountID ? CONST.REPORT.STATUS_NUM.APPROVED : CONST.REPORT.STATUS_NUM.SUBMITTED;
const predictedNextState = !nextApproverAccountID ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED;
const managerID = !nextApproverAccountID ? expenseReport.managerID : nextApproverAccountID;

const optimisticNextStep = buildNextStepNew({
report: expenseReport,
predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN,
policy,
currentUserAccountIDParam,
currentUserEmailParam,
hasViolations,
isASAPSubmitBetaEnabled,
predictedNextStatus,
});
const chatReport = getReportOrDraftReport(expenseReport.chatReportID);

Expand Down Expand Up @@ -10878,6 +10868,7 @@ function submitReport(
currentUserEmailParam,
hasViolations,
isASAPSubmitBetaEnabled,
isUnapprove: true,
});
const approvalChain = getApprovalChain(policy, expenseReport);
const managerID = getAccountIDsByLogins(approvalChain).at(0);
Expand Down
Loading
Loading