Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8756,7 +8756,11 @@ function buildOptimisticResolvedDuplicatesReportAction(): OptimisticDismissedVio
};
}

function buildOptimisticChangeApproverReportAction(managerID: number, actorAccountID: number): OptimisticChangedApproverReportAction {
function buildOptimisticChangeApproverReportAction(
managerID: number,
actorAccountID: number,
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
): OptimisticChangedApproverReportAction {
const created = DateUtils.getDBTime();
return {
actionName: managerID === actorAccountID ? CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL : CONST.REPORT.ACTIONS.TYPE.REROUTE,
Expand All @@ -8766,7 +8770,7 @@ function buildOptimisticChangeApproverReportAction(managerID: number, actorAccou
message: [
{
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
text: `changed the approver to ${getDisplayNameForParticipant({accountID: managerID, formatPhoneNumber: formatPhoneNumberPhoneUtils})}`,
text: `changed the approver to ${getDisplayNameForParticipant({accountID: managerID, formatPhoneNumber})}`,
html: `changed the approver to <mention-user accountID="${managerID}"/>`,
},
],
Expand Down
67 changes: 53 additions & 14 deletions src/libs/actions/IOU/ReportWorkflow.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {LocaleContextProps} from '@components/LocaleContextProvider';

import * as API from '@libs/API';
import type {
AddReportApproverParams,
Expand Down Expand Up @@ -1731,8 +1733,9 @@ function assignReportToMe(
isASAPSubmitBetaEnabled: boolean,
reportCurrentNextStepDeprecated: OnyxEntry<OnyxTypes.ReportNextStepDeprecated>,
isTrackIntentUser: boolean | undefined,
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
) {
const takeControlReportAction = buildOptimisticChangeApproverReportAction(accountID, accountID);
const takeControlReportAction = buildOptimisticChangeApproverReportAction(accountID, accountID, formatPhoneNumber);

// buildOptimisticNextStep is used in parallel
const optimisticNextStepDeprecated = buildNextStepNew({
Expand Down Expand Up @@ -1838,19 +1841,55 @@ function assignReportToMe(
API.write(WRITE_COMMANDS.ASSIGN_REPORT_TO_ME, params, onyxData);
}

function addReportApprover(
report: OnyxTypes.Report,
newApproverEmail: string,
newApproverAccountID: number,
accountID: number,
email: string,
policy: OnyxEntry<OnyxTypes.Policy>,
hasViolations: boolean,
isASAPSubmitBetaEnabled: boolean,
reportCurrentNextStepDeprecated: OnyxEntry<OnyxTypes.ReportNextStepDeprecated>,
isTrackIntentUser: boolean | undefined,
) {
const takeControlReportAction = buildOptimisticChangeApproverReportAction(newApproverAccountID, accountID);
type AddReportApproverOptions = {
/** Expense report whose approver is being changed. */
report: OnyxTypes.Report;

/** Email of the approver being added. */
newApproverEmail: string;

/** Account ID of the approver being added. */
newApproverAccountID: number;

/** Account ID of the user changing the approver. */
accountID: number;

/** Email of the user changing the approver. */
email: string;

/** Policy associated with the expense report. */
policy: OnyxEntry<OnyxTypes.Policy>;

/** Whether the report currently has violations. */
hasViolations: boolean;

/** Whether ASAP Submit beta behavior is enabled. */
isASAPSubmitBetaEnabled: boolean;

/** Existing next-step data used to roll back optimistic updates on failure. */
reportCurrentNextStepDeprecated: OnyxEntry<OnyxTypes.ReportNextStepDeprecated>;

/** Whether the current user is in the track intent onboarding state. */
isTrackIntentUser: boolean | undefined;

/** Locale-aware formatter used for optimistic approver display names. */
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'];
Comment thread
marufsharifi marked this conversation as resolved.
};

function addReportApprover({
report,
newApproverEmail,
newApproverAccountID,
accountID,
email,
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportCurrentNextStepDeprecated,
isTrackIntentUser,
formatPhoneNumber,
}: AddReportApproverOptions) {
const takeControlReportAction = buildOptimisticChangeApproverReportAction(newApproverAccountID, accountID, formatPhoneNumber);

// buildOptimisticNextStep is used in parallel
const optimisticNextStepDeprecated = buildNextStepNew({
Expand Down
27 changes: 24 additions & 3 deletions src/pages/DynamicReportChangeApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type DynamicReportChangeApproverPageProps = WithReportOrNotFoundProps & Platform

function DynamicReportChangeApproverPage({report, policy, isLoadingReportData}: DynamicReportChangeApproverPageProps) {
const reportID = report?.reportID;
const {translate} = useLocalize();
const {translate, formatPhoneNumber} = useLocalize();
const styles = useThemeStyles();
const {environmentURL} = useEnvironment();
const currentUserDetails = useCurrentUserPersonalDetails();
Expand Down Expand Up @@ -89,9 +89,30 @@ function DynamicReportChangeApproverPage({report, policy, isLoadingReportData}:
Navigation.navigate(ROUTES.REPORT_CHANGE_APPROVER_ADD_APPROVER.getRoute(report.reportID));
return;
}
assignReportToMe(report, currentUserDetails.accountID, currentUserDetails.email ?? '', policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser);
assignReportToMe(
report,
currentUserDetails.accountID,
currentUserDetails.email ?? '',
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
isTrackIntentUser,
formatPhoneNumber,
);
Navigation.dismissToPreviousRHP();
}, [selectedApproverType, report, currentUserDetails.accountID, currentUserDetails.email, policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser]);
}, [
selectedApproverType,
report,
currentUserDetails.accountID,
currentUserDetails.email,
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
isTrackIntentUser,
formatPhoneNumber,
]);

const approverTypes = useMemo(() => {
const data: Array<ListItem<ApproverType>> = [
Expand Down
15 changes: 8 additions & 7 deletions src/pages/ReportAddApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,19 @@ function ReportAddApproverPage({report, isLoadingReportData, policy}: ReportAddA
return;
}
startWithLoading(() => {
addReportApprover(
addReportApprover({
report,
selectedApproverEmail,
Number(employeeAccountID),
currentUserDetails.accountID,
currentUserDetails.email ?? '',
newApproverEmail: selectedApproverEmail,
newApproverAccountID: Number(employeeAccountID),
accountID: currentUserDetails.accountID,
email: currentUserDetails.email ?? '',
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
reportCurrentNextStepDeprecated: reportNextStep,
isTrackIntentUser,
);
formatPhoneNumber,
});
Navigation.dismissToPreviousRHP();
});
};
Expand Down
15 changes: 8 additions & 7 deletions src/pages/Search/SearchAddApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,19 @@ function SearchAddApproverPage() {

const hasViolations = hasViolationsReportUtils(report.reportID, transactionViolations, currentUserDetails.accountID, currentUserDetails.email ?? '');
const reportNextStep = allReportNextSteps?.[`${ONYXKEYS.COLLECTION.NEXT_STEP}${selectedReport.reportID}`];
addReportApprover(
addReportApprover({
report,
selectedApproverEmail,
Number(employeeAccountID),
currentUserDetails.accountID,
currentUserDetails.email ?? '',
newApproverEmail: selectedApproverEmail,
newApproverAccountID: Number(employeeAccountID),
accountID: currentUserDetails.accountID,
email: currentUserDetails.email ?? '',
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
reportCurrentNextStepDeprecated: reportNextStep,
isTrackIntentUser,
);
formatPhoneNumber,
});
}

// Note: This clears both reports and transactions
Expand Down
14 changes: 12 additions & 2 deletions src/pages/Search/SearchChangeApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function shouldAutoApplyApprover({
export {shouldAutoApplyApprover};

function SearchChangeApproverPage() {
const {translate} = useLocalize();
const {translate, formatPhoneNumber} = useLocalize();
const styles = useThemeStyles();
const {environmentURL} = useEnvironment();
const currentUserDetails = useCurrentUserPersonalDetails();
Expand Down Expand Up @@ -175,7 +175,17 @@ function SearchChangeApproverPage() {
if (report.managerID !== currentUserDetails.accountID) {
const hasViolations = hasViolationsReportUtils(report.reportID, transactionViolations, currentUserDetails.accountID, currentUserDetails.email ?? '');
const reportNextStep = allReportNextSteps?.[`${ONYXKEYS.COLLECTION.NEXT_STEP}${selectedReport.reportID}`];
assignReportToMe(report, currentUserDetails.accountID, currentUserDetails.email ?? '', policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser);
assignReportToMe(
report,
currentUserDetails.accountID,
currentUserDetails.email ?? '',
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
isTrackIntentUser,
formatPhoneNumber,
);
}
}

Expand Down
52 changes: 51 additions & 1 deletion tests/actions/IOUTest/ReportWorkflowTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import OnyxListItemProvider from '@components/OnyxListItemProvider';
import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations';

import {
addReportApprover,
approveMoneyRequest,
canApproveIOU,
canIOUBePaid,
Expand Down Expand Up @@ -53,7 +54,7 @@ import createRandomReportAction from '../../utils/collections/reportActions';
import {createRandomReport} from '../../utils/collections/reports';
import createRandomTransaction from '../../utils/collections/transaction';
import getOnyxValue from '../../utils/getOnyxValue';
import {createGlobalFetchMock, getOnyxData, getRequiredOnyxUpdate, getRequiredOnyxUpdates, getRequiredWriteCall, localeCompare} from '../../utils/TestHelper';
import {createGlobalFetchMock, formatPhoneNumber, getOnyxData, getRequiredOnyxUpdate, getRequiredOnyxUpdates, getRequiredWriteCall, localeCompare} from '../../utils/TestHelper';
import {isObject} from '../../utils/typeGuards';
import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
import waitForBatchedUpdatesWithAct from '../../utils/waitForBatchedUpdatesWithAct';
Expand Down Expand Up @@ -3092,6 +3093,55 @@ describe('actions/IOU/ReportWorkflow', () => {
});
});

describe('change approver formatter forwarding', () => {
it('uses the injected formatter for the optimistic approver display name', async () => {
// eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting API.write optimistic data to verify formatter forwarding.
const apiWriteSpy = jest.spyOn(API, 'write').mockImplementation(() => Promise.resolve());
const approverAccountID = 8332403627;
const approverLogin = '+18332403627@expensify.sms';
const formatPhoneNumberSpy = jest.fn(formatPhoneNumber);
const policy = createRandomPolicy(1);
const report: Report = {
...createRandomReport(1, undefined),
reportID: 'change-approver-report',
type: CONST.REPORT.TYPE.EXPENSE,
policyID: policy.id,
managerID: CARLOS_ACCOUNT_ID,
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};

await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[approverAccountID]: {
accountID: approverAccountID,
login: approverLogin,
isOptimisticPersonalDetail: true,
},
});
await waitForBatchedUpdates();

addReportApprover({
report,
newApproverEmail: approverLogin,
newApproverAccountID: approverAccountID,
accountID: RORY_ACCOUNT_ID,
email: RORY_EMAIL,
policy,
hasViolations: false,
isASAPSubmitBetaEnabled: false,
reportCurrentNextStepDeprecated: undefined,
isTrackIntentUser: false,
formatPhoneNumber: formatPhoneNumberSpy,
});

const [, , onyxData] = getRequiredWriteCall(apiWriteSpy.mock.calls, 0);
const reportActionsUpdate = getRequiredOnyxUpdate(onyxData, 'optimisticData', `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, Onyx.METHOD.MERGE, true);
const reportAction = getRequiredReportAction(reportActionsUpdate);

expect(formatPhoneNumberSpy).toHaveBeenCalledWith(approverLogin);
expect(reportAction.message).toEqual([expect.objectContaining({text: `changed the approver to ${formatPhoneNumber(approverLogin)}`})]);
});
});

describe('approveMoneyRequest Submit workspace upgrade', () => {
const submitPolicyID = 'submit-policy-id';
const teamPolicyID = 'team-policy-id';
Expand Down
Loading