Skip to content
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

[Report Next Steps Audit] Simplify the App optimistic next steps #45160

Merged
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b1ff6b7
[Report Next Steps Audit] Simplify the App optimistic next steps
rayane-djouah Jul 10, 2024
d9e085b
Merge branch 'main' into Simplify-the-App-optimistic-next-steps
rayane-djouah Jul 10, 2024
45a9312
fix type error
rayane-djouah Jul 10, 2024
54a8cf2
Update NextStepUtils and fix failing tests
rayane-djouah Jul 10, 2024
8bdc2e3
keep using %expenses
rayane-djouah Jul 11, 2024
a8ff46b
Merge branch 'main' into Simplify-the-App-optimistic-next-steps
rayane-djouah Jul 11, 2024
f9030c8
fix tests
rayane-djouah Jul 11, 2024
d683c23
Add accompanying icon to next steps
rayane-djouah Jul 11, 2024
48f782a
add space
rayane-djouah Jul 11, 2024
cc40d1b
Merge branch 'main' into Simplify-the-App-optimistic-next-steps
rayane-djouah Jul 12, 2024
1145c2a
Simplify icon mapping
rayane-djouah Jul 12, 2024
80b8bca
Merge branch 'Expensify:main' into Simplify-the-App-optimistic-next-s…
rayane-djouah Jul 12, 2024
373381b
type safety
rayane-djouah Jul 12, 2024
5f53411
prettier
rayane-djouah Jul 12, 2024
5d9f0e5
replace "Waiting for you to submit these expenses"
rayane-djouah Jul 12, 2024
7fafd21
Apply suggestions from code review
rayane-djouah Jul 17, 2024
fbdb4e2
Improve type safety
rayane-djouah Jul 17, 2024
f2cc571
add the 's in "Waiting for ownerDisplayName's expense(s) to automatic…
rayane-djouah Jul 18, 2024
bc7dad6
roll back to src={iconMap[nextStep.icon] || Expensicons.Hourglass}
rayane-djouah Jul 18, 2024
307baa6
Remove prevented self submitting next step
rayane-djouah Jul 19, 2024
89932ea
Merge branch 'main' into Simplify-the-App-optimistic-next-steps
rayane-djouah Jul 19, 2024
48bad73
Merge branch 'Expensify:main' into Simplify-the-App-optimistic-next-s…
rayane-djouah Jul 19, 2024
9f25eb4
Update comments
rayane-djouah Jul 19, 2024
4cb1131
Merge branch 'Expensify:main' into Simplify-the-App-optimistic-next-s…
rayane-djouah Jul 20, 2024
71ddbb3
Update comment
rayane-djouah Jul 22, 2024
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
6 changes: 5 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,11 @@ const CONST = {
},
},
NEXT_STEP: {
FINISHED: 'Finished!',
ICONS: {
HOURGLASS: 'hourglass',
CHECKMARK: 'checkmark',
STOPWATCH: 'stopwatch',
},
},
COMPOSER: {
MAX_LINES: 16,
Expand Down
27 changes: 20 additions & 7 deletions src/components/MoneyReportHeaderStatusBar.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import React, {useMemo} from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import type {ValueOf} from 'type-fest';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as NextStepUtils from '@libs/NextStepUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type ReportNextStep from '@src/types/onyx/ReportNextStep';
import Badge from './Badge';
import type IconAsset from '@src/types/utils/IconAsset';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import RenderHTML from './RenderHTML';

type MoneyReportHeaderStatusBarProps = {
/** The next step for the report */
nextStep: ReportNextStep;
};

type IconName = ValueOf<typeof CONST.NEXT_STEP.ICONS>;
type IconMap = Record<IconName, IconAsset>;
const iconMap: IconMap = {
[CONST.NEXT_STEP.ICONS.HOURGLASS]: Expensicons.Hourglass,
[CONST.NEXT_STEP.ICONS.CHECKMARK]: Expensicons.Checkmark,
[CONST.NEXT_STEP.ICONS.STOPWATCH]: Expensicons.Stopwatch,
};

function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const theme = useTheme();
const messageContent = useMemo(() => {
const messageArray = nextStep.message;
return NextStepUtils.parseMessage(messageArray);
Expand All @@ -25,9 +36,11 @@ function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps)
return (
<View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.overflowHidden, styles.w100, styles.headerStatusBarContainer]}>
<View style={[styles.mr3]}>
<Badge
text={translate(nextStep.title === CONST.NEXT_STEP.FINISHED ? 'iou.finished' : 'iou.nextStep')}
badgeStyles={styles.ml0}
<Icon
src={iconMap[nextStep.icon] || Expensicons.Hourglass}
rayane-djouah marked this conversation as resolved.
Show resolved Hide resolved
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
fill={theme.icon}
/>
</View>
<View style={[styles.dFlex, styles.flexRow, styles.flexShrink1]}>
Expand Down
147 changes: 38 additions & 109 deletions src/libs/NextStepUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type {Message} from '@src/types/onyx/ReportNextStep';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import DateUtils from './DateUtils';
import EmailUtils from './EmailUtils';
import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import * as PolicyUtils from './PolicyUtils';
import * as ReportUtils from './ReportUtils';

Expand Down Expand Up @@ -53,17 +52,13 @@ function parseMessage(messages: Message[] | undefined) {
});

const formattedHtml = nextStepHTML
.replace(/%expenses/g, 'these expenses')
.replace(/%Expenses/g, 'These expenses')
.replace(/%expenses/g, 'expense(s)')
.replace(/%Expenses/g, 'Expense(s)')
.replace(/%tobe/g, 'are');

return `<next-step>${formattedHtml}</next-step>`;
}

type BuildNextStepParameters = {
isPaidWithExpensify?: boolean;
};

/**
* Generates an optimistic nextStep based on a current report status and other properties.
*
Expand All @@ -72,21 +67,20 @@ type BuildNextStepParameters = {
* @param parameters.isPaidWithExpensify - Whether a report has been paid with Expensify or outside
* @returns nextStep
*/
function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<typeof CONST.REPORT.STATUS_NUM>, {isPaidWithExpensify}: BuildNextStepParameters = {}): ReportNextStep | null {
function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<typeof CONST.REPORT.STATUS_NUM>): ReportNextStep | null {
if (!ReportUtils.isExpenseReport(report)) {
return null;
}

const {policyID = '', ownerAccountID = -1, managerID = -1} = report ?? {};
const {policyID = '', ownerAccountID = -1} = report ?? {};
const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? ({} as Policy);
const {harvesting, preventSelfApproval, autoReportingOffset} = policy;
const {harvesting, autoReportingOffset} = policy;
const autoReportingFrequency = PolicyUtils.getCorrectedAutoReportingFrequency(policy);
const submitToAccountID = PolicyUtils.getSubmitToAccountID(policy, ownerAccountID);
const isOwner = currentUserAccountID === ownerAccountID;
const isManager = currentUserAccountID === managerID;
const isSelfApproval = currentUserAccountID === submitToAccountID;
const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? '';
const managerDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitToAccountID) ?? '';
const ownerDisplayName = ReportUtils.getDisplayNameForParticipant(ownerAccountID);
const managerDisplayName = ReportUtils.getDisplayNameForParticipant(submitToAccountID);
const reimburserAccountID = PolicyUtils.getReimburserAccountID(policy);
const reimburserDisplayName = ReportUtils.getDisplayNameForParticipant(reimburserAccountID);
const type: ReportNextStep['type'] = 'neutral';
let optimisticNextStep: ReportNextStep | null;

Expand All @@ -96,24 +90,24 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
// Self review
optimisticNextStep = {
type,
title: 'Next Steps:',
icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
message: [
{
text: 'Waiting for ',
rayane-djouah marked this conversation as resolved.
Show resolved Hide resolved
},
{
text: 'you',
text: `${ownerDisplayName}`,
type: 'strong',
},
{
text: ' to ',
},
{
text: 'submit',
text: 'add',
type: 'strong',
},
{
text: ' these expenses.',
text: ' expenses.',
},
],
};
Expand All @@ -122,7 +116,14 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
if (harvesting?.enabled && autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL) {
optimisticNextStep.message = [
{
text: 'These expenses are scheduled to ',
text: 'Waiting for ',
rayane-djouah marked this conversation as resolved.
Show resolved Hide resolved
},
{
text: `${ownerDisplayName}`,
rayane-djouah marked this conversation as resolved.
Show resolved Hide resolved
type: 'strong',
},
{
text: "'s %expenses to ",
},
];
let harvestingSuffix = '';
Expand Down Expand Up @@ -150,7 +151,7 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
[CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: 'on Sunday',
[CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: 'on the 1st and 16th of each month',
[CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: formattedDate ? `on the ${formattedDate} of each month` : '',
[CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: 'at the end of your trip',
[CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: 'at the end of their trip',
rayane-djouah marked this conversation as resolved.
Show resolved Hide resolved
[CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT]: '',
[CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: '',
};
Expand All @@ -160,78 +161,21 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
}
}

optimisticNextStep.message.push(
{
text: `automatically submit${harvestingSuffix}!`,
type: 'strong',
},
{
text: ' No further action required!',
},
);
}

// Prevented self submitting
if (preventSelfApproval && isSelfApproval) {
optimisticNextStep.message = [
{
text: "Oops! Looks like you're submitting to ",
},
{
text: 'yourself',
type: 'strong',
},
{
text: '. Approving your own reports is ',
},
{
text: 'forbidden',
type: 'strong',
},
{
text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.',
},
];
optimisticNextStep.message.push({
text: `automatically submit${harvestingSuffix}`,
type: 'strong',
});
}

break;

// Generates an optimistic nextStep once a report has been submitted
case CONST.REPORT.STATUS_NUM.SUBMITTED: {
const verb = isManager ? 'review' : 'approve';

// Another owner
optimisticNextStep = {
type,
title: 'Next Steps:',
icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
message: [
{
text: ownerLogin,
type: 'strong',
},
{
text: ' is waiting for ',
},
{
text: 'you',
type: 'strong',
},
{
text: ' to ',
},
{
text: verb,
type: 'strong',
},
{
text: ' these %expenses.',
},
],
};

// Self review & another reviewer
if (!isSelfApproval || (isSelfApproval && isOwner)) {
optimisticNextStep.message = [
{
text: 'Waiting for ',
},
Expand All @@ -243,26 +187,26 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
text: ' to ',
},
{
text: verb,
text: 'approve',
type: 'strong',
},
{
text: ' %expenses.',
},
];
}
],
};

break;
}

// Generates an optimistic nextStep once a report has been closed for example in the case of Submit and Close approval flow
case CONST.REPORT.STATUS_NUM.CLOSED:
optimisticNextStep = {
icon: CONST.NEXT_STEP.ICONS.CHECKMARK,
type,
title: 'Finished!',
message: [
{
text: 'No further action required!',
text: 'Finished! No further action required.',
},
],
};
Expand All @@ -283,10 +227,10 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
) {
optimisticNextStep = {
type,
title: 'Finished!',
icon: CONST.NEXT_STEP.ICONS.CHECKMARK,
message: [
{
text: 'No further action required!',
text: 'Finished! No further action required.',
},
],
};
Expand All @@ -295,13 +239,13 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
// Self review
optimisticNextStep = {
type,
title: 'Next Steps:',
icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
message: [
{
text: 'Waiting for ',
},
{
text: 'you',
text: reimburserDisplayName,
type: 'strong',
},
{
Expand All @@ -323,29 +267,14 @@ function buildNextStep(report: OnyxEntry<Report>, predictedNextStatus: ValueOf<t
// Paid with wallet
optimisticNextStep = {
type,
title: 'Finished!',
icon: CONST.NEXT_STEP.ICONS.CHECKMARK,
message: [
{
text: 'You',
type: 'strong',
},
{
text: ' have marked these expenses as ',
},
{
text: 'paid',
type: 'strong',
text: 'Finished! No further action required.',
},
],
};

// Paid outside of Expensify
if (isPaidWithExpensify === false) {
optimisticNextStep.message?.push({text: ' outside of Expensify'});
}

optimisticNextStep.message?.push({text: '.'});

break;

// Resets a nextStep
Expand Down
9 changes: 9 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,14 @@ function getSubmitToAccountID(policy: OnyxEntry<Policy>, employeeAccountID: numb
return getAccountIDsByLogins([employee.submitsTo ?? defaultApprover])[0];
}

/**
* Returns the accountID of the policy reimburser, if not available — falls back to the policy owner.
*/
function getReimburserAccountID(policy: OnyxEntry<Policy>): number {
const reimburserEmail = policy?.achAccount?.reimburser ?? policy?.owner ?? '';
return getAccountIDsByLogins([reimburserEmail])[0];
}

function getPersonalPolicy() {
return Object.values(allPolicies ?? {}).find((policy) => policy?.type === CONST.POLICY.TYPE.PERSONAL);
}
Expand Down Expand Up @@ -856,6 +864,7 @@ export {
getIntegrationLastSuccessfulDate,
getCurrentConnectionName,
getCustomersOrJobsLabelNetSuite,
getReimburserAccountID,
isControlPolicy,
isNetSuiteCustomSegmentRecord,
getNameFromNetSuiteCustomField,
Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6207,7 +6207,7 @@ function getPayMoneyRequestParams(
let optimisticNextStep = null;
if (!isInvoiceReport) {
currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`] ?? null;
optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithExpensify: paymentMethodType === CONST.IOU.PAYMENT_TYPE.VBBA});
optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.REIMBURSED);
}

const optimisticData: OnyxUpdate[] = [
Expand Down
Loading
Loading