From ab67bc431c5b4993ac78429841bf24cdc9fda31c Mon Sep 17 00:00:00 2001 From: Zany Renney <56830058+zanyrenney@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:19:06 +0100 Subject: [PATCH 01/40] Create Managing Employees and Reports > Approving Reports Help Site article for report managing, specially how to approve reports. --- ... Employees and Reports > Approving Reports | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports new file mode 100644 index 000000000000..0f9b2f742cd9 --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports @@ -0,0 +1,144 @@ +--- +title: How To: Manage Employees and Reports > Approving Reports +description: This page will help you understand the lifecycle of a report and how to approve reports that are submitted to you. +--- + + +# About + + +# How-to manage reports + From ab00bc9131f254a0fd4004954bc81229ad126fe6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 25 Oct 2023 17:23:42 +0200 Subject: [PATCH 02/40] Rename file --- .../{DotIndicatorMessage.js => DotIndicatorMessage.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{DotIndicatorMessage.js => DotIndicatorMessage.tsx} (100%) diff --git a/src/components/DotIndicatorMessage.js b/src/components/DotIndicatorMessage.tsx similarity index 100% rename from src/components/DotIndicatorMessage.js rename to src/components/DotIndicatorMessage.tsx From 95d80ab047a3e3701536364eb51c6ef43d91fda4 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 25 Oct 2023 17:46:06 +0200 Subject: [PATCH 03/40] [TS migration] Migrate 'DotIndicatorMessage.js' component to TypeScript --- src/components/DotIndicatorMessage.tsx | 68 +++++++------------------- src/components/Text.tsx | 20 ++------ 2 files changed, 23 insertions(+), 65 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index fc4d74339d6e..0ff5eacfe01b 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -1,75 +1,45 @@ import React from 'react'; -import _ from 'underscore'; -import PropTypes from 'prop-types'; -import {View} from 'react-native'; +import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import styles from '../styles/styles'; -import stylePropTypes from '../styles/stylePropTypes'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import themeColors from '../styles/themes/default'; import Text from './Text'; import * as Localize from '../libs/Localize'; -const propTypes = { - /** - * In most cases this should just be errors from onxyData - * if you are not passing that data then this needs to be in a similar shape like - * { - * timestamp: 'message', - * } - */ - messages: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))])), - - // The type of message, 'error' shows a red dot, 'success' shows a green dot - type: PropTypes.oneOf(['error', 'success']).isRequired, - - // Additional styles to apply to the container */ - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.arrayOf(PropTypes.object), - - // Additional styles to apply to the text - textStyles: stylePropTypes, -}; - -const defaultProps = { - messages: {}, - style: [], - textStyles: [], +type DotIndicatorMessageProps = { + messages: Record; + type: 'error' | 'success'; + style?: StyleProp; + textStyles?: StyleProp; }; -function DotIndicatorMessage(props) { - if (_.isEmpty(props.messages)) { +function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndicatorMessageProps) { + if (Object.keys(messages).length === 0) { return null; } - // To ensure messages are presented in order we are sort of destroying the data we are given - // and rebuilding as an array so we can render the messages in order. We don't really care about - // the microtime timestamps anyways so isn't the end of the world that we sort of lose them here. - // BEWARE: if you decide to refactor this and keep the microtime keys it could cause performance issues - const sortedMessages = _.chain(props.messages) - .keys() - .sortBy() - .map((key) => props.messages[key]) + // Fetch the keys, sort them, and map through each key to get the corresponding message + const sortedMessages: string[] = Object.keys(messages) + .sort() + .map((key) => messages[key]); - // Using uniq here since some fields are wrapped by the same OfflineWithFeedback component (e.g. WorkspaceReimburseView) - // and can potentially pass the same error. - .uniq() - .map((message) => Localize.translateIfPhraseKey(message)) - .value(); + // Removing duplicates using Set and transforming the result into an array + const uniqueMessages: string[] = [...new Set(sortedMessages)].map((message) => Localize.translateIfPhraseKey(message)); return ( - + - {_.map(sortedMessages, (message, i) => ( + {uniqueMessages.map((message, i) => ( {message} @@ -79,8 +49,6 @@ function DotIndicatorMessage(props) { ); } -DotIndicatorMessage.propTypes = propTypes; -DotIndicatorMessage.defaultProps = defaultProps; DotIndicatorMessage.displayName = 'DotIndicatorMessage'; export default DotIndicatorMessage; diff --git a/src/components/Text.tsx b/src/components/Text.tsx index 60a59aae1520..b2ac26fbb367 100644 --- a/src/components/Text.tsx +++ b/src/components/Text.tsx @@ -1,7 +1,7 @@ import React, {ForwardedRef} from 'react'; // eslint-disable-next-line no-restricted-imports -import {Text as RNText} from 'react-native'; -import type {TextStyle} from 'react-native'; +import {Text as RNText, StyleSheet} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; import fontFamily from '../styles/fontFamily'; import themeColors from '../styles/themes/default'; import variables from '../styles/variables'; @@ -23,29 +23,19 @@ type TextProps = { family?: keyof typeof fontFamily; /** Any additional styles to apply */ - style?: TextStyle | TextStyle[]; + style?: StyleProp; }; function Text( {color = themeColors.text, fontSize = variables.fontSizeNormal, textAlign = 'left', children = null, family = 'EXP_NEUE', style = {}, ...props}: TextProps, ref: ForwardedRef, ) { - // If the style prop is an array of styles, we need to mix them all together - const mergedStyles = !Array.isArray(style) - ? style - : style.reduce( - (finalStyles, s) => ({ - ...finalStyles, - ...s, - }), - {}, - ); const componentStyle: TextStyle = { color, fontSize, textAlign, fontFamily: fontFamily[family], - ...mergedStyles, + ...StyleSheet.flatten(style), }; if (!componentStyle.lineHeight && componentStyle.fontSize === variables.fontSizeNormal) { @@ -56,7 +46,7 @@ function Text( From 9b0f4cade249bbd8138e1480edf5bdc611275f03 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 25 Oct 2023 17:47:05 +0200 Subject: [PATCH 04/40] Eslint disable react/no-array-index-key --- src/components/DotIndicatorMessage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 0ff5eacfe01b..94b2fef9f7be 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -38,6 +38,7 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica {uniqueMessages.map((message, i) => ( From f59ef2d6c374b2797399dfac3e45fb5ac93af290 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 25 Oct 2023 17:47:30 +0200 Subject: [PATCH 05/40] Use message as key --- src/components/DotIndicatorMessage.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 94b2fef9f7be..e8882d12b3c1 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -36,10 +36,9 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica /> - {uniqueMessages.map((message, i) => ( + {uniqueMessages.map((message) => ( {message} From 2800ff7e6ea812fd3c7729aab0ce699dff963042 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 26 Oct 2023 11:10:10 +0200 Subject: [PATCH 06/40] Disable eslint error --- src/components/DotIndicatorMessage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index e8882d12b3c1..94b2fef9f7be 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -36,9 +36,10 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica /> - {uniqueMessages.map((message) => ( + {uniqueMessages.map((message, i) => ( {message} From ad386a4a024f35253c80480518ac40788883fbe6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 26 Oct 2023 11:53:02 +0200 Subject: [PATCH 07/40] Fix tests --- tests/ui/UnreadIndicatorsTest.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index a9ffe258ac7f..183420c77ecb 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -221,7 +221,7 @@ describe('Unread Indicators', () => { // And that the text is bold const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameText = screen.queryByLabelText(displayNameHintText); - expect(lodashGet(displayNameText, ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold); + expect(lodashGet(displayNameText, ['props', 'style', 'fontWeight'])).toBe(fontWeightBold); return navigateToSidebarOption(0); }) @@ -360,11 +360,11 @@ describe('Unread Indicators', () => { const displayNameTexts = screen.queryAllByLabelText(displayNameHintTexts); expect(displayNameTexts).toHaveLength(2); const firstReportOption = displayNameTexts[0]; - expect(lodashGet(firstReportOption, ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold); + expect(lodashGet(firstReportOption, ['props', 'style', 'fontWeight'])).toBe(fontWeightBold); expect(lodashGet(firstReportOption, ['props', 'children'])).toBe('C User'); const secondReportOption = displayNameTexts[1]; - expect(lodashGet(secondReportOption, ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold); + expect(lodashGet(secondReportOption, ['props', 'style', 'fontWeight'])).toBe(fontWeightBold); expect(lodashGet(secondReportOption, ['props', 'children'])).toBe('B User'); // Tap the new report option and navigate back to the sidebar again via the back button @@ -376,9 +376,9 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(2); - expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(undefined); + expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(undefined); expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('C User'); - expect(lodashGet(displayNameTexts[1], ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold); + expect(lodashGet(displayNameTexts[1], ['props', 'style', 'fontWeight'])).toBe(fontWeightBold); expect(lodashGet(displayNameTexts[1], ['props', 'children'])).toBe('B User'); })); @@ -412,7 +412,7 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(1); - expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold); + expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(fontWeightBold); expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User'); // Navigate to the report again and back to the sidebar @@ -424,7 +424,7 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(1); - expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(undefined); + expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(undefined); expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User'); // Navigate to the report again and verify the new line indicator is missing From a1f3570fcbcc76ffac9c8d159b4957e1d4fa5eb6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 27 Oct 2023 12:32:56 +0200 Subject: [PATCH 08/40] Improve Text types --- src/components/Text.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/Text.tsx b/src/components/Text.tsx index b2ac26fbb367..21b61a7680c0 100644 --- a/src/components/Text.tsx +++ b/src/components/Text.tsx @@ -1,12 +1,12 @@ import React, {ForwardedRef} from 'react'; // eslint-disable-next-line no-restricted-imports -import {Text as RNText, StyleSheet} from 'react-native'; -import type {StyleProp, TextStyle} from 'react-native'; +import {Text as RNText, TextProps as RNTextProps, StyleSheet} from 'react-native'; +import type {TextStyle} from 'react-native'; import fontFamily from '../styles/fontFamily'; import themeColors from '../styles/themes/default'; import variables from '../styles/variables'; -type TextProps = { +type TextProps = RNTextProps & { /** The color of the text */ color?: string; @@ -21,9 +21,6 @@ type TextProps = { /** The family of the font to use */ family?: keyof typeof fontFamily; - - /** Any additional styles to apply */ - style?: StyleProp; }; function Text( From 4497e35456d7b41197db1d1adf4914b519e41a52 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 31 Oct 2023 12:04:30 +0100 Subject: [PATCH 09/40] Fix passing styles --- src/components/DotIndicatorMessage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 0bba72ec219b..7828c2f5feb4 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -56,7 +56,7 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica {message} From 33dcb5f239e1a64d092f9a20d21619fb2ae20458 Mon Sep 17 00:00:00 2001 From: Zany Renney <56830058+zanyrenney@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:21:33 +0000 Subject: [PATCH 10/40] Update Managing Employees and Reports > Approving Reports --- ... Employees and Reports > Approving Reports | 74 +++++++++---------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports index 0f9b2f742cd9..133ab524b9c6 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports @@ -2,46 +2,44 @@ title: How To: Manage Employees and Reports > Approving Reports description: This page will help you understand the lifecycle of a report and how to approve reports that are submitted to you. --- - - +​ # About - - +​ +​ # How-to manage reports - From 561dc5aa631a8767b544536805a51ec6475641c4 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 8 Nov 2023 14:25:38 +0100 Subject: [PATCH 11/40] fix: fix lhn previews --- src/languages/en.ts | 10 ++++---- src/languages/es.ts | 10 ++++---- src/libs/OptionsListUtils.js | 46 +++++++++++++++++++++++++++++++++--- src/libs/ReportUtils.js | 39 ++++++++++++++++++++++++++---- 4 files changed, 88 insertions(+), 17 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 38efe0ef92f6..dd187d414424 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -557,19 +557,19 @@ export default { splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`, didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `split ${formattedAmount}${comment ? ` for ${comment}` : ''}`, amountEach: ({amount}: AmountEachParams) => `${amount} each`, - payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} owes ${amount}`, + payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer ? `${payer} ` : ''}owes ${amount}`, payerOwes: ({payer}: PayerOwesParams) => `${payer} owes: `, - payerPaidAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} paid ${amount}`, + payerPaidAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}paid ${amount}`, payerPaid: ({payer}: PayerPaidParams) => `${payer} paid: `, - payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} spent ${amount}`, + payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}spent ${amount}`, payerSpent: ({payer}: PayerPaidParams) => `${payer} spent: `, managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`, payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, - paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`, - paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer} paid ${amount} using Expensify`, + paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} elsewhere`, + paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} using Expensify`, noReimbursableExpenses: 'This report has an invalid amount', pendingConversionMessage: "Total will update when you're back online", changedTheRequest: 'changed the request', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2bdb71ae82f7..6c8f79b26f80 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -549,19 +549,19 @@ export default { splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`, didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `dividió ${formattedAmount}${comment ? ` para ${comment}` : ''}`, amountEach: ({amount}: AmountEachParams) => `${amount} cada uno`, - payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} debe ${amount}`, + payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer ? `${payer} ` : ''}debe ${amount}`, payerOwes: ({payer}: PayerOwesParams) => `${payer} debe: `, - payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer} pagó ${amount}`, + payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount}`, payerPaid: ({payer}: PayerPaidParams) => `${payer} pagó: `, - payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} gastó ${amount}`, + payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}gastó ${amount}`, payerSpent: ({payer}: PayerPaidParams) => `${payer} gastó: `, managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, - paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, - paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer} pagó ${amount} con Expensify`, + paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount} de otra forma`, + paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount} con Expensify`, noReimbursableExpenses: 'El importe de este informe no es válido', pendingConversionMessage: 'El total se actualizará cuando estés online', changedTheRequest: 'cambió la solicitud', diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 54d09b75eff2..ae445a6e46a5 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -9,6 +9,7 @@ import _ from 'underscore'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as CollectionUtils from './CollectionUtils'; +import * as CurrencyUtils from './CurrencyUtils'; import * as ErrorUtils from './ErrorUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; @@ -372,6 +373,39 @@ function getAllReportErrors(report, reportActions) { return allReportErrors; } +/** + * Get the preview message to be displayed in the option list. + * @param {Object} report + * @param {Object} reportAction + * @param {Boolean} isPreviewMessageForParentChatReport + * @returns {String} + */ + +function getReportPreviewMessageForOptionList(report, reportAction, isPreviewMessageForParentChatReport = false) { + // for the request action preview we want to show the requestor instead of the user who owes the money + if (!isPreviewMessageForParentChatReport && reportAction.originalMessage && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE) { + const amount = Math.abs(reportAction.originalMessage.amount); + const formattedAmount = CurrencyUtils.convertToDisplayString(amount, report.currency); + const shouldShowActorName = currentUserAccountID !== reportAction.actorAccountID; + const actorDisplayName = shouldShowActorName ? `${ReportUtils.getDisplayNameForParticipant(reportAction.actorAccountID, true)}: ` : ''; + + return `${actorDisplayName}${Localize.translateLocal('iou.requestedAmount', {formattedAmount})}`; + } + + const shouldShowWorkspaceName = ReportUtils.isExpenseReport(report) && isPreviewMessageForParentChatReport; + const actor = ReportUtils.getActorNameForPreviewMessage({ + report, + shouldShowWorkspaceName, + actorID: reportAction.actorAccountID, + shouldUseShortForm: !isPreviewMessageForParentChatReport, + }); + const shouldShowActorName = shouldShowWorkspaceName || isPreviewMessageForParentChatReport || currentUserAccountID !== reportAction.actorAccountID; + const actorDisplayName = shouldShowActorName ? `${actor}${isPreviewMessageForParentChatReport ? ' ' : ': '}` : ''; + const message = ReportUtils.getReportPreviewMessage(report, reportAction, true, isPreviewMessageForParentChatReport, true); + + return `${actorDisplayName}${message}`; +} + /** * Get the last message text from the report directly or from other sources for special cases. * @param {Object} report @@ -385,10 +419,16 @@ function getLastMessageTextForReport(report) { let lastMessageTextFromReport = ''; const lastActionName = lodashGet(lastReportAction, 'actionName', ''); - if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml, translationKey: report.lastMessageTranslationKey})) { + if ( + ReportUtils.isReportMessageAttachment({ + text: report.lastMessageText, + html: report.lastMessageHtml, + translationKey: report.lastMessageTranslationKey, + }) + ) { lastMessageTextFromReport = `[${Localize.translateLocal(report.lastMessageTranslationKey || 'common.attachment')}]`; } else if (ReportActionUtils.isMoneyRequestAction(lastReportAction)) { - const properSchemaForMoneyRequestMessage = ReportUtils.getReportPreviewMessage(report, lastReportAction, true); + const properSchemaForMoneyRequestMessage = getReportPreviewMessageForOptionList(report, lastReportAction, false); lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForMoneyRequestMessage); } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { const iouReport = ReportUtils.getReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); @@ -399,7 +439,7 @@ function getLastMessageTextForReport(report) { reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && ReportActionUtils.isMoneyRequestAction(reportAction), ); - lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastIOUMoneyReport, true, ReportUtils.isChatReport(report)); + lastMessageTextFromReport = getReportPreviewMessageForOptionList(iouReport, lastIOUMoneyReport, ReportUtils.isChatReport(report)); } else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report); } else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) { diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8d24d98b19e8..ef4d94702081 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1775,6 +1775,20 @@ function getTransactionReportName(reportAction) { }); } +/** + * Get actor name to display in the message preview + * + * @param {Object} report + * @param {Number} actorID + * @param {Boolean} shouldShowWorkspaceName + * @param {Boolean} shouldUseShortForm + * @returns {String} + */ + +function getActorNameForPreviewMessage({report, actorID, shouldShowWorkspaceName = false, shouldUseShortForm = false}) { + return shouldShowWorkspaceName ? getPolicyName(report) : getDisplayNameForParticipant(actorID, shouldUseShortForm); +} + /** * Get money request message for an IOU report * @@ -1782,9 +1796,10 @@ function getTransactionReportName(reportAction) { * @param {Object} [reportAction={}] This can be either a report preview action or the IOU action * @param {Boolean} [shouldConsiderReceiptBeingScanned=false] * @param {Boolean} isPreviewMessageForParentChatReport + * @param {Boolean} shouldHideParticipantName * @returns {String} */ -function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = false, isPreviewMessageForParentChatReport = false) { +function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = false, isPreviewMessageForParentChatReport = false, shouldHideParticipantName = false) { const reportActionMessage = lodashGet(reportAction, 'message[0].html', ''); if (_.isEmpty(report) || !report.reportID) { @@ -1808,7 +1823,13 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip } const totalAmount = getMoneyRequestReimbursableTotal(report); - const payerName = isExpenseReport(report) ? getPolicyName(report) : getDisplayNameForParticipant(report.managerID, true); + const payerDisplayName = getActorNameForPreviewMessage({ + report, + actorID: report.managerID, + shouldUseShortForm: true, + shouldShowWorkspaceName: isExpenseReport(report), + }); + const payerName = shouldHideParticipantName ? '' : payerDisplayName; const formattedAmount = CurrencyUtils.convertToDisplayString(totalAmount, report.currency); if (isReportApproved(report) && getPolicyType(report, allPolicies) === CONST.POLICY.TYPE.CORPORATE) { @@ -1867,7 +1888,11 @@ function getProperSchemaForModifiedExpenseMessage(newValue, oldValue, valueName, if (!newValue) { return Localize.translateLocal('iou.removedTheRequest', {valueName: displayValueName, oldValueToDisplay}); } - return Localize.translateLocal('iou.updatedTheRequest', {valueName: displayValueName, newValueToDisplay, oldValueToDisplay}); + return Localize.translateLocal('iou.updatedTheRequest', { + valueName: displayValueName, + newValueToDisplay, + oldValueToDisplay, + }); } /** @@ -1882,7 +1907,10 @@ function getProperSchemaForModifiedExpenseMessage(newValue, oldValue, valueName, function getProperSchemaForModifiedDistanceMessage(newDistance, oldDistance, newAmount, oldAmount) { if (!oldDistance) { - return Localize.translateLocal('iou.setTheDistance', {newDistanceToDisplay: newDistance, newAmountToDisplay: newAmount}); + return Localize.translateLocal('iou.setTheDistance', { + newDistanceToDisplay: newDistance, + newAmountToDisplay: newAmount, + }); } return Localize.translateLocal('iou.updatedTheDistance', { newDistanceToDisplay: newDistance, @@ -2619,6 +2647,7 @@ function buildOptimisticIOUReportAction( whisperedToAccountIDs: _.contains([CONST.IOU.RECEIPT_STATE.SCANREADY, CONST.IOU.RECEIPT_STATE.SCANNING], receipt.state) ? [currentUserAccountID] : [], }; } + /** * Builds an optimistic APPROVED report action with a randomly generated reportActionID. * @@ -3257,6 +3286,7 @@ function canAccessReport(report, policies, betas, allReportActions) { return true; } + /** * Check if the report is the parent report of the currently viewed report or at least one child report has report action * @param {Object} report @@ -4328,4 +4358,5 @@ export { getReimbursementQueuedActionMessage, getPersonalDetailsForAccountID, getRoom, + getActorNameForPreviewMessage, }; From 0718d21941b57c697cc133da90d47b13ac61e76c Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 8 Nov 2023 17:45:57 +0100 Subject: [PATCH 12/40] fix: fix preview for splits --- src/libs/OptionsListUtils.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 5d5b5a7abda5..91b1a75511f3 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -393,14 +393,15 @@ function getReportPreviewMessageForOptionList(report, reportAction, isPreviewMes } const shouldShowWorkspaceName = ReportUtils.isExpenseReport(report) && isPreviewMessageForParentChatReport; + const actorID = report.managerID || reportAction.actorAccountID; const actor = ReportUtils.getActorNameForPreviewMessage({ report, shouldShowWorkspaceName, - actorID: reportAction.actorAccountID, + actorID, shouldUseShortForm: !isPreviewMessageForParentChatReport, }); - const shouldShowActorName = shouldShowWorkspaceName || isPreviewMessageForParentChatReport || currentUserAccountID !== reportAction.actorAccountID; - const actorDisplayName = shouldShowActorName ? `${actor}${isPreviewMessageForParentChatReport ? ' ' : ': '}` : ''; + const shouldShowActorName = shouldShowWorkspaceName || isPreviewMessageForParentChatReport || currentUserAccountID !== actorID; + const actorDisplayName = shouldShowActorName && actor ? `${actor}${isPreviewMessageForParentChatReport ? ' ' : ': '}` : ''; const message = ReportUtils.getReportPreviewMessage(report, reportAction, true, isPreviewMessageForParentChatReport, true); return `${actorDisplayName}${message}`; From 6aca1ddd46525d26ba9ae1330a755760b854ec60 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 8 Nov 2023 18:37:04 +0100 Subject: [PATCH 13/40] fix: get actor name for thread messages --- src/libs/OptionsListUtils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 91b1a75511f3..cffb54ac829b 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -449,7 +449,9 @@ function getLastMessageTextForReport(report) { ) { lastMessageTextFromReport = lodashGet(lastReportAction, 'message[0].text', ''); } else { - lastMessageTextFromReport = report ? report.lastMessageText || '' : ''; + const shouldShowLastActor = ReportUtils.isThread(report) && (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) && currentUserAccountID !== report.lastActorAccountID; + const lastActorDisplayName = shouldShowLastActor ? `${ReportUtils.getDisplayNameForParticipant(report.lastActorAccountID, true)}: ` : ''; + lastMessageTextFromReport = report ? `${lastActorDisplayName}${report.lastMessageText}` : ''; // Yeah this is a bit ugly. If the latest report action that is not a whisper has been moderated as pending remove // then set the last message text to the text of the latest visible action that is not a whisper or the report creation message. From 888e2978547eb9b85945e537928f829c125fe1fe Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 8 Nov 2023 18:52:03 +0100 Subject: [PATCH 14/40] chore: run prettier --- src/libs/OptionsListUtils.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index cffb54ac829b..8b8174f8ff8c 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -401,7 +401,7 @@ function getReportPreviewMessageForOptionList(report, reportAction, isPreviewMes shouldUseShortForm: !isPreviewMessageForParentChatReport, }); const shouldShowActorName = shouldShowWorkspaceName || isPreviewMessageForParentChatReport || currentUserAccountID !== actorID; - const actorDisplayName = shouldShowActorName && actor ? `${actor}${isPreviewMessageForParentChatReport ? ' ' : ': '}` : ''; + const actorDisplayName = shouldShowActorName && actor ? `${actor}${isPreviewMessageForParentChatReport ? ' ' : ': '}` : ''; const message = ReportUtils.getReportPreviewMessage(report, reportAction, true, isPreviewMessageForParentChatReport, true); return `${actorDisplayName}${message}`; @@ -449,7 +449,8 @@ function getLastMessageTextForReport(report) { ) { lastMessageTextFromReport = lodashGet(lastReportAction, 'message[0].text', ''); } else { - const shouldShowLastActor = ReportUtils.isThread(report) && (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) && currentUserAccountID !== report.lastActorAccountID; + const shouldShowLastActor = + ReportUtils.isThread(report) && (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) && currentUserAccountID !== report.lastActorAccountID; const lastActorDisplayName = shouldShowLastActor ? `${ReportUtils.getDisplayNameForParticipant(report.lastActorAccountID, true)}: ` : ''; lastMessageTextFromReport = report ? `${lastActorDisplayName}${report.lastMessageText}` : ''; From 01e355bd0ab9ad03d1a33dc7fcc38602a0fd14d6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 9 Nov 2023 11:11:25 +0100 Subject: [PATCH 15/40] fix: apply requested changes --- src/libs/OptionsListUtils.js | 2 +- src/libs/ReportUtils.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 8b8174f8ff8c..18971722fcdf 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -377,7 +377,7 @@ function getAllReportErrors(report, reportActions) { * Get the preview message to be displayed in the option list. * @param {Object} report * @param {Object} reportAction - * @param {Boolean} isPreviewMessageForParentChatReport + * @param {Boolean} [isPreviewMessageForParentChatReport] * @returns {String} */ diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f3961b6d724a..bd7988be88a5 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1790,8 +1790,8 @@ function getTransactionReportName(reportAction) { * * @param {Object} report * @param {Number} actorID - * @param {Boolean} shouldShowWorkspaceName - * @param {Boolean} shouldUseShortForm + * @param {Boolean} [shouldShowWorkspaceName] + * @param {Boolean} [shouldUseShortForm] * @returns {String} */ @@ -1805,8 +1805,8 @@ function getActorNameForPreviewMessage({report, actorID, shouldShowWorkspaceName * @param {Object} report * @param {Object} [reportAction={}] This can be either a report preview action or the IOU action * @param {Boolean} [shouldConsiderReceiptBeingScanned=false] - * @param {Boolean} isPreviewMessageForParentChatReport - * @param {Boolean} shouldHideParticipantName + * @param {Boolean} [isPreviewMessageForParentChatReport] + * @param {Boolean} [shouldHideParticipantName] * @returns {String} */ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = false, isPreviewMessageForParentChatReport = false, shouldHideParticipantName = false) { From 18401c6030e103963298efbac7c7045a17ac910c Mon Sep 17 00:00:00 2001 From: Zany Renney <56830058+zanyrenney@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:28:18 +0000 Subject: [PATCH 16/40] Update Approving-Reports.md --- .../Approving-Reports.md | 136 +++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md index 3ee1c8656b4b..9fa8bdcda4f6 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md @@ -1,5 +1,135 @@ --- -title: Coming Soon -description: Coming Soon +title: How To: Manage Employees and Reports > Approving Reports +description: This page will help you understand the lifecycle of a report and how to approve reports that are submitted to you. --- -## Resource Coming Soon! +​ +# About +This article provides a comprehensive guide on report management within our platform. From viewing, editing, and submitting your employees' Open reports to handling rejections and unapproving, as well as harnessing the power of our "Guided Review" feature. Additionally, we'll delve into best practices for Concierge, offering insights on how to streamline and automate your report approval processes for maximum efficiency. +Let's dive in! +​ +​ +# How-to manage reports +This section covers the most essential information a user needs to operate a feature i.e. what to click on. We’ll go over any action the user might take when configuring or using the feature, starting from configuration and moving to usage. +​ +What options does a user have when configuring this feature? +What options does a user have then interacting with this feature? +What elements of this feature are pay-walled vs. free? +As a Workspace admin, you have the ability to view, edit, and submit your employees' Open reports. +​ +We recommend beginning this process from the web version of Expensify because it offers more functionality compared to the mobile app. Here's how to get started: +Click on the "Reports" tab. +Select the "All Submitters" and "Open" filters. +This will display all employee reports on your Workspaces that have not yet been submitted. +​ +## Viewing Employee Reports +Viewing employee reports can vary depending on whether you're using the web or mobile versions of Expensify. We generally recommend using the web version for this purpose, as it offers the following advantages: +You will only receive reports directly submitted to you when using the mobile app. +The option to filter reports via the Reports page is exclusively available in the web version, making it more convenient when reviewing multiple reports during a session. +​ +## Viewing employee reports on the mobile app +When using the mobile app to view reports, please note the following: +Tapping on the Reports list will only display your own reports; you won't see reports from other Workspace members. +To view another Workspace member's report in the Expensify app, it must be submitted directly to you, and you must access it through a link from an email or via Home. +When you access a report in this manner, you will have the option to approve/reject it or go through the review process if there are expenses that require your attention. +Once you've approved or rejected the report, it won't be accessible in the app anymore. To view it again, please visit the website and follow the steps mentioned above. +​ +## Editing employee reports +If a report has been submitted directly to you, follow these steps to edit the expense details. Please note that you cannot change the expense amount; to make changes affecting the report total, you must reject it and return it to the employee. +Here's what to do: +Click on any expense within the report to edit the expense details. +Remember that you cannot modify the expense amount directly. To make changes affecting the report total, reject the report, and it will be sent back to the employee for revisions. +If you're a Workspace admin and need to edit a report that wasn't submitted directly to you, use the "Take Control" button at the top of the report. Keep in mind that taking control of a report will disrupt the approval workflow. +​ +Additionally, here are some other editing options for Admins: +Undelete deleted company card expenses via the Reconciliation Dashboard (requires Domain Admin privileges). +Add unreported company card expenses to an existing Open (unsubmitted) report or create a new report via the Reconciliation Dashboard (requires Domain Admin privileges). +Add or modify expense coding, including Category, Tag(s), and Attendees. +Attach a receipt to an expense that doesn't have one. +Move card expenses between two Open (unsubmitted) reports. +Merge duplicate expenses (only applicable if they are not card transactions). +Change the Workspace associated with a report. +​ +​ +## Submitting Employee Reports +As a Workspace Admin, you have the option to submit any of your employee's Open reports. If an employee is unable to do it themselves, a Workspace admin can submit an expense report on their behalf to initiate the approval process. Follow these steps: +Click the "Submit" button located at the top of the report. +​ +## Report History and Comments +Please keep in mind that any changes made by the admin are tracked under "Report History and Comments." If you change the reimbursable status of an expense (e.g., from Reimbursable to Non-Reimbursable), an email notification will be sent to the employee to notify them of this change. +​ +## Rejecting or Unapproving a Report +If you need to reject a report that has been submitted to you or Unapprove a report that has already been approved. +To reject the report, click Reject rather than beginning the Review process. If there are multiple approvers involved, you can choose how far back to reject the report. +​ +Rejecting a report will return the report back to the submitter in an Open status or, in the case of a multi-level approval workflow, back to the previous approver in a Processing status (awaiting their approval). You may need to do this if the submitter is not ready to submit the report, or perhaps the report as a whole needs to be rejected based on the configuration of your organization's expense Workspace. +## Unapprove a Report +You can click the red Unapprove button at the top of the report to undo approving a report. Keep in mind that you'll only see the Unapprove button if you're a report approver on an admin that has taken control of the report. +​ +​ +## Marking a Report as Reimbursed Outside of Expensify +If you are reimbursing reports via paper check, through payroll or any other method that takes place outside of Expensify, you'll want to keep track of which reports have been taken care of by marking reports as reimbursed. +Log into your Expensify account using your preferred web browser, (ie: Chrome or Safari) +Head to your Reports page and locate the report +Click the report name to open it +Click on Reimburse +Choose "I'll do it manually - just mark it as reimbursed". This will change the report status to Reimbursed +The submitter can then go into the report and confirm that they received the reimbursement by clicking the button at the top of the report. +This will change the report status to Reimbursed: CONFIRMED +​ +# How to Use Guided Review to Approve Reports +Guided Review helps alert you to what might be out-of-Workspace for an Expense Report. You'll be guided through all report violations and warnings and given the option to Reject or Edit items that need review prior to approving a report. +Guided Review helps approvers quickly identify reports that need more attention so they can pass over reports that can be quickly approved. Both Submitters and Approvers have actionable notifications for the following: violations, warnings, and notices. These notifications are important since they will be included in “review mode” for the approver to make clear approve or reject decisions. +Via the Website: +​ +1. Simply click Review at the top left of the report and the system will begin to walk you through the entire report. +2. Choose to Reject, View, or skip over an item needing review. If you wish to stop the process at any time, click the X in the progress bar in the top right corner. +Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected. +View: This will allow you to open the expense so you can view and fix any incorrect data. +Next: This will allow you to skip over the current item and move forward to review the rest of the report. +Finish: Click this to finish reviewing the report! +3. Click the Finish button if you are done reviewing, or reject/edit the last item to finish the review process. +4. Approve the report! Approve and Forward the report if there is another person who needs to review the report in your approval workflow, or you can Final Approve if you are the final approver. Note: When in Guided Review, you'll automatically Approve the report adhering to your Company's Approval Workflow once you Approve the final expense on the report. You'll then be immediately taken to the next report requiring your attention - making Approving multiple expenses a quick and painless process! +Via the Mobile App: +​ +​ +1. From Home, under Reports that need your attention, click Begin Review, and the system will bring you to the first expense on the oldest report in Home. +​ +2. Edit the expense: Make any necessary edits to the expense by tapping the corresponding field. Be sure to address any Violations and Notes on the expense! Notes are indicated at the top of the expense with a yellow exclamation point, while violations appear on the expense with a red exclamation point: +3. Choose Reject or Accept at the top of the expense. +Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected, and a comment will be added to the report it was rejected from. If this is the only expense on the report, the entire report will be rejected (and the expense will remain on the report). +If Scheduled Submit is being used, rejected expenses will auto-report to the next Open report on the Workspace (as if it were a new expense). If an open report doesn't exist, Concierge will create a new one. +​ +​ +If Scheduled Submit is not being used, any rejected expenses will be Unreported in the submitter's account and need to be manually applied to a new report. +Accept: This will move to the next expense on the report, leaving behind any outstanding violations or notes. If this is the last expense on the report, you'll be all done! +Once you've made it through all of the expenses on the report, you'll be all set! +​ +​ +​ +# Deep Dive +## Concierge Report Management +​ +Concierge report approval removes the need for you to manually click "Approve" on endless reports! Instead, you can set up your group Workspace to capture all the requirements you have for your team's expenses. As long as all the rules have been followed and Concierge's additional audit is passed (more below), we will automatically approve such reports on behalf of the approver after submission. +Before you start: +Ensure are a Workspace admin on a group Workspace +Set your workflow to Submit-and-Approve or Advanced Approval workflow +​ +​ +## Then follow these steps: +Set up your group Workspace so that all of your expense requirements are defined. Setting automatic categories for employees and category rules (e.g., maximum amounts, receipt requirements, etc.) are great examples! +Navigate to Settings > Workspaces > Group > [Workspace Name] > Members. +Scroll down to Approval Mode and select either Submit-and-Approve or Advanced Approval. +Under Expense Approvals, select a Manual Approval Threshold greater than $0. +​ +With this setup, manual approval will only be required: +For reports that fail audit (i.e. there is at least one Workspace violation on the report) +For reports that contain at least one expense over the Manual Approval Threshold +For any percentage of reports that you'd like to spot-check (this is set at 5% or 1 in 20 by default). +If the report meets all of the requirements you specify in your Workspace settings and all expenses are under the Manual Approval Threshold, then Concierge will automatically move your report through each step of your designated approval workflow (unless it's routed for a spot-check). +​ +​ +## Concierge Receipt Audit +Concierge Receipt Audit is a real-time audit and compliance of receipts submitted by employees and Workspace users. Concierge checks every receipt for accuracy and compliance, flagging any expenses that seem fishy before expense reports are even submitted for approval. All risky expenses are highlighted for manual review, leaving you with more control over and visibility into employee expenses. +1. Concierge will SmartScan every receipt to verify the data input by the user matches the currency, date, and amount on the physical receipt. +2. After the report is submitted for approval, Concierge highlights any differences between the SmartScanned values and the employee's chosen input. +3. Each receipt that has been verified will show the "Verified" logo. From c5efdfb2d124eed50391a59f336aa2c9de83b107 Mon Sep 17 00:00:00 2001 From: Zany Renney <56830058+zanyrenney@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:42:52 +0000 Subject: [PATCH 17/40] Update docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md Co-authored-by: Rushat Gabhane --- .../manage-employees-and-report-approvals/Approving-Reports.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md index 9fa8bdcda4f6..8969f7a5adbf 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md @@ -1,5 +1,5 @@ --- -title: How To: Manage Employees and Reports > Approving Reports +title: How To Manage Employees and Reports > Approving Reports description: This page will help you understand the lifecycle of a report and how to approve reports that are submitted to you. --- ​ From 3d656d6af264912f77b52336b2781117637a842d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 Nov 2023 11:34:07 +0100 Subject: [PATCH 18/40] Add styles and theme hook to ts file --- src/components/DotIndicatorMessage.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 7828c2f5feb4..b482881ecae7 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -1,9 +1,9 @@ import React from 'react'; import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import * as Localize from '@libs/Localize'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; @@ -29,6 +29,9 @@ type DotIndicatorMessageProps = { }; function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndicatorMessageProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + if (Object.keys(messages).length === 0) { return null; } @@ -48,7 +51,7 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica From 92a142e55ff4b6e665459d4a5b4141c88101e8b7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 Nov 2023 11:37:43 +0100 Subject: [PATCH 19/40] Fix the messages type --- src/components/DotIndicatorMessage.tsx | 6 +++--- src/libs/Localize/index.ts | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index b482881ecae7..72733c935c7e 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -16,7 +16,7 @@ type DotIndicatorMessageProps = { * timestamp: 'message', * } */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; @@ -37,12 +37,12 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica } // Fetch the keys, sort them, and map through each key to get the corresponding message - const sortedMessages: string[] = Object.keys(messages) + const sortedMessages = Object.keys(messages) .sort() .map((key) => messages[key]); // Removing duplicates using Set and transforming the result into an array - const uniqueMessages: string[] = [...new Set(sortedMessages)].map((message) => Localize.translateIfPhraseKey(message)); + const uniqueMessages = [...new Set(sortedMessages)].map((message) => Localize.translateIfPhraseKey(message)); const isErrorMessage = type === 'error'; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 6910bc7e9bdb..488ff0d9b98a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -96,10 +96,12 @@ function translateLocal(phrase: TKey, ...variable return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); } +type MaybePhraseKey = string | [string, Record & {isTranslated?: true}] | []; + /** * Return translated string for given error. */ -function translateIfPhraseKey(message: string | [string, Record & {isTranslated?: true}] | []): string { +function translateIfPhraseKey(message: MaybePhraseKey): string { if (!message || (Array.isArray(message) && message.length === 0)) { return ''; } @@ -138,4 +140,4 @@ function getDevicePreferredLocale(): string { } export {translate, translateLocal, translateIfPhraseKey, arrayToString, getDevicePreferredLocale}; -export type {PhraseParameters, Phrase}; +export type {PhraseParameters, Phrase, MaybePhraseKey}; From 519c08c1e6442cc1cd2a3cc6c92d8b9744fd38b6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 23 Nov 2023 10:26:35 +0100 Subject: [PATCH 20/40] fix: minor fix for displaying last actor --- src/libs/ReportUtils.js | 11 +++++++++-- src/libs/SidebarUtils.ts | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c1f44785c5ca..6cb37b57a431 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1857,7 +1857,14 @@ function getActorNameForPreviewMessage({report, actorID, shouldShowWorkspaceName * @param {Object} [policy] * @returns {String} */ -function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = false, isPreviewMessageForParentChatReport = false, shouldHideParticipantName = false, policy = undefined) { +function getReportPreviewMessage( + report, + reportAction = {}, + shouldConsiderReceiptBeingScanned = false, + isPreviewMessageForParentChatReport = false, + shouldHideParticipantName = false, + policy = undefined, +) { const reportActionMessage = lodashGet(reportAction, 'message[0].html', ''); if (_.isEmpty(report) || !report.reportID) { @@ -1886,7 +1893,7 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip actorID: report.managerID, shouldUseShortForm: true, shouldShowWorkspaceName: isExpenseReport(report), - policy + policy, }); const payerName = shouldHideParticipantName ? '' : payerDisplayName; const formattedAmount = CurrencyUtils.convertToDisplayString(totalAmount, report.currency); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 763a0000ba35..c3312ebebae9 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -406,7 +406,8 @@ function getOptionData( } : null; } - const lastActorDisplayName = hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName : ''; + const lastActorDisplayName = + hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName?.split(' ')[0] : ''; let lastMessageText = lastMessageTextFromReport; const reportAction = lastReportActions?.[report.reportID]; From 4e398e841b4e94c28ca856c157510c8968e67e0f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 23 Nov 2023 14:09:35 +0100 Subject: [PATCH 21/40] fix: apply requested changes --- src/libs/OptionsListUtils.js | 4 ++-- src/libs/ReportUtils.js | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 860aa1a6b380..14bee6e79776 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -375,14 +375,14 @@ function getAllReportErrors(report, reportActions) { /** * Get the preview message to be displayed in the option list. + * * @param {Object} report * @param {Object} reportAction * @param {Boolean} [isPreviewMessageForParentChatReport] * @returns {String} */ - function getReportPreviewMessageForOptionList(report, reportAction, isPreviewMessageForParentChatReport = false) { - // for the request action preview we want to show the requestor instead of the user who owes the money + // For the request action preview we want to show the requestor instead of the user who owes the money if (!isPreviewMessageForParentChatReport && reportAction.originalMessage && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE) { const amount = Math.abs(reportAction.originalMessage.amount); const formattedAmount = CurrencyUtils.convertToDisplayString(amount, report.currency); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 6cb37b57a431..3deed6ec6a70 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1838,10 +1838,9 @@ function getTransactionReportName(reportAction) { * @param {Number} actorID * @param {Boolean} [shouldShowWorkspaceName] * @param {Boolean} [shouldUseShortForm] - * @param {Object} [policy] - * @returns {String} + * @param {Object|undefined} [policy] + * @returns {String} */ - function getActorNameForPreviewMessage({report, actorID, shouldShowWorkspaceName = false, shouldUseShortForm = false, policy = undefined}) { return shouldShowWorkspaceName ? getPolicyName(report, false, policy) : getDisplayNameForParticipant(actorID, shouldUseShortForm); } From 13f4925ba9c055c1b088c90ee78e87fc806bd094 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 10:41:49 -0400 Subject: [PATCH 22/40] Remove all unused mock betas --- src/CONST.ts | 70 --------------------------- src/libs/E2E/apiMocks/openApp.ts | 73 ----------------------------- src/libs/E2E/apiMocks/signinUser.ts | 73 ----------------------------- 3 files changed, 216 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 436ac4ebbc31..6f388ae93b73 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -260,76 +260,6 @@ const CONST = { THREADS: 'threads', CUSTOM_STATUS: 'customStatus', NEW_DOT_SAML: 'newDotSAML', - PDF_META_STORE: 'pdfMetaStore', - REPORT_ACTION_CONTEXT_MENU: 'reportActionContextMenu', - SUBMIT_POLICY: 'submitPolicy', - ATTENDEES: 'attendees', - AUTO_EXPORT: 'autoExport', - AUTO_EXPORT_INTACCT: 'autoExportIntacct', - AUTO_EXPORT_QBO: 'autoExportQbo', - AUTO_EXPORT_XERO: 'autoExportXero', - AUTO_JOIN_POLICY: 'autoJoinPolicy', - AUTOMATED_TAX_EXEMPTION: 'automatedTaxExemption', - BILL_PAY: 'billPay', - CATEGORY_DEFAULT_TAX: 'categoryDefaultTax', - COLLECTABLE_DEPOSIT_ACCOUNTS: 'collectableDepositAccounts', - CONCIERGE_TRAVEL: 'conciergeTravel', - CONNECTED_CARDS: 'connectedCards', - DISCREPANCY: 'discrepancy', - DOMAIN_CONTACT_BILLING: 'domainContactBilling', - DOMAIN_TWO_FACTOR_AUTH: 'domainTwoFactorAuth', - DUPLICATE_DETECTION: 'duplicateDetection', - EMAIL_SUPPRESSION_BETA: 'emailSuppressionBeta', - EXPENSES_V2: 'expensesV2', - EXPENSIFY_CARD: 'expensifyCard', - EXPENSIFY_CARD_INTACCT_RECONCILIATION: 'expensifyCardIntacctReconciliation', - EXPENSIFY_CARD_NETSUITE_RECONCILIATION: 'expensifyCardNetSuiteReconciliation', - EXPENSIFY_CARD_QBO_RECONCILIATION: 'expensifyCardQBOReconciliation', - EXPENSIFY_CARD_RAPID_INCREASE_FRAUD: 'expensifyCardRapidIncreaseFraud', - EXPENSIFY_CARD_XERO_RECONCILIATION: 'expensifyCardXeroReconciliation', - EXPENSIFY_ORG: 'expensifyOrg', - FIX_VIOLATION_PUSH_NOTIFICATION: 'fixViolationPushNotification', - FREE_PLAN_FULL_LAUNCH: 'freePlanFullLaunch', - FREE_PLAN_SOFT_LAUNCH: 'freePlanSoftLaunch', - GUSTO: 'gusto', - INBOX_CACHE: 'inboxCache', - INBOX_HIDDEN_TASKS: 'inboxHiddenTasks', - INDIRECT_INTEGRATION_SETUP: 'indirectIntegrationSetup', - IOU: 'IOU', - JOIN_POLICY: 'joinPolicy', - LOAD_POLICY_ASYNC: 'loadPolicyAsync', - MAP_RECEIPT: 'mapReceipt', - MERGE_API: 'mergeAPI', - MOBILE_REALTIME_REPORT_COMMENTS: 'mobileRealtimeReportComments', - MOBILE_SECURE_RECEIPTS: 'mobileSecureReceipts', - MONTHLY_SETTLEMENT: 'monthlySettlement', - NAMES_AND_AVATARS: 'namesAndAvatars', - NATIVE_CHAT: 'nativeChat', - NEW_PRICING: 'newPricing', - NEWSLETTER_THREE: 'newsletterThree', - NEXT_STEPS: 'nextSteps', - OPEN_FACE_HAMBURGER: 'openFaceHamburger', - PER_DIEM: 'perDiem', - PER_DIEM_INTERNATIONAL: 'perDiemInternational', - PRICING_COPY_CHANGES: 'pricingCopyChanges', - QBO_INVOICES: 'qboInvoices', - QUICKBOOKS_DESKTOP_V2: 'quickbooksDesktopV2', - REALTIME_REPORT_COMMENTS: 'realtimeReportComments', - S2W_ANNOUNCEMENT: 's2wAnnouncement', - SCHEDULED_AUTO_REPORTING: 'scheduledAutoReporting', - SECURE_RECEIPTS: 'secureReceipts', - SECURE_RECEIPTS_REPORTS: 'secureReceiptsReports', - SELF_SERVICE_HARD_LAUNCH: 'selfServiceHardLaunch', - SEND_MONEY: 'sendMoney', - SMART_SCAN_USER_DISPUTES: 'smartScanUserDisputes', - SMS_SIGN_UP: 'smsSignUp', - STRIPE_CONNECT: 'stripeConnect', - SUMMARY_EMAIL: 'summaryEmail', - SWIPE_TO_WIN: 'swipeToWin', - TAX_FOR_MILEAGE: 'taxForMileage', - TWO_FACTOR_AUTH: 'twoFactorAuth', - VENMO_INTEGRATION: 'venmoIntegration', - ZENEFITS_INTEGRATION: 'zenefitsIntegration', VIOLATIONS: 'violations', }, BUTTON_STATES: { diff --git a/src/libs/E2E/apiMocks/openApp.ts b/src/libs/E2E/apiMocks/openApp.ts index 13fc9f1f6784..3e22948654bc 100644 --- a/src/libs/E2E/apiMocks/openApp.ts +++ b/src/libs/E2E/apiMocks/openApp.ts @@ -1459,79 +1459,6 @@ const openApp = (): Response => ({ key: 'betas', value: [ 'all', - 'pdfMetaStore', - 'reportActionContextMenu', - 'submitPolicy', - 'attendees', - 'autoExport', - 'autoExportIntacct', - 'autoExportQbo', - 'autoExportXero', - 'autoJoinPolicy', - 'automatedTaxExemption', - 'billPay', - 'categoryDefaultTax', - 'collectableDepositAccounts', - 'conciergeTravel', - 'connectedCards', - 'discrepancy', - 'domainContactBilling', - 'domainTwoFactorAuth', - 'duplicateDetection', - 'emailSuppressionBeta', - 'expensesV2', - 'expensifyCard', - 'expensifyCardIntacctReconciliation', - 'expensifyCardNetSuiteReconciliation', - 'expensifyCardQBOReconciliation', - 'expensifyCardRapidIncreaseFraud', - 'expensifyCardXeroReconciliation', - 'expensifyOrg', - 'fixViolationPushNotification', - 'freePlan', - 'freePlanFullLaunch', - 'freePlanSoftLaunch', - 'gusto', - 'inboxCache', - 'inboxHiddenTasks', - 'indirectIntegrationSetup', - 'IOU', - 'joinPolicy', - 'loadPolicyAsync', - 'mapReceipt', - 'mergeAPI', - 'mobileRealtimeReportComments', - 'mobileSecureReceipts', - 'monthlySettlement', - 'namesAndAvatars', - 'nativeChat', - 'newPricing', - 'newsletterThree', - 'nextSteps', - 'openFaceHamburger', - 'pdfMetaStore', - 'perDiem', - 'perDiemInternational', - 'pricingCopyChanges', - 'qboInvoices', - 'quickbooksDesktopV2', - 'realtimeReportComments', - 's2wAnnouncement', - 'scheduledAutoReporting', - 'secureReceipts', - 'secureReceiptsReports', - 'selfServiceHardLaunch', - 'sendMoney', - 'smartScanUserDisputes', - 'smsSignUp', - 'stripeConnect', - 'submitPolicy', - 'summaryEmail', - 'swipeToWin', - 'taxForMileage', - 'twoFactorAuth', - 'venmoIntegration', - 'zenefitsIntegration', ], }, { diff --git a/src/libs/E2E/apiMocks/signinUser.ts b/src/libs/E2E/apiMocks/signinUser.ts index d94d2fda9016..55b89ea6b3db 100644 --- a/src/libs/E2E/apiMocks/signinUser.ts +++ b/src/libs/E2E/apiMocks/signinUser.ts @@ -38,79 +38,6 @@ const signinUser = ({email}: SigninParams): Response => ({ key: 'betas', value: [ 'all', - 'pdfMetaStore', - 'reportActionContextMenu', - 'submitPolicy', - 'attendees', - 'autoExport', - 'autoExportIntacct', - 'autoExportQbo', - 'autoExportXero', - 'autoJoinPolicy', - 'automatedTaxExemption', - 'billPay', - 'categoryDefaultTax', - 'collectableDepositAccounts', - 'conciergeTravel', - 'connectedCards', - 'discrepancy', - 'domainContactBilling', - 'domainTwoFactorAuth', - 'duplicateDetection', - 'emailSuppressionBeta', - 'expensesV2', - 'expensifyCard', - 'expensifyCardIntacctReconciliation', - 'expensifyCardNetSuiteReconciliation', - 'expensifyCardQBOReconciliation', - 'expensifyCardRapidIncreaseFraud', - 'expensifyCardXeroReconciliation', - 'expensifyOrg', - 'fixViolationPushNotification', - 'freePlan', - 'freePlanFullLaunch', - 'freePlanSoftLaunch', - 'gusto', - 'inboxCache', - 'inboxHiddenTasks', - 'indirectIntegrationSetup', - 'IOU', - 'joinPolicy', - 'loadPolicyAsync', - 'mapReceipt', - 'mergeAPI', - 'mobileRealtimeReportComments', - 'mobileSecureReceipts', - 'monthlySettlement', - 'namesAndAvatars', - 'nativeChat', - 'newPricing', - 'newsletterThree', - 'nextSteps', - 'openFaceHamburger', - 'pdfMetaStore', - 'perDiem', - 'perDiemInternational', - 'pricingCopyChanges', - 'qboInvoices', - 'quickbooksDesktopV2', - 'realtimeReportComments', - 's2wAnnouncement', - 'scheduledAutoReporting', - 'secureReceipts', - 'secureReceiptsReports', - 'selfServiceHardLaunch', - 'sendMoney', - 'smartScanUserDisputes', - 'smsSignUp', - 'stripeConnect', - 'submitPolicy', - 'summaryEmail', - 'swipeToWin', - 'taxForMileage', - 'twoFactorAuth', - 'venmoIntegration', - 'zenefitsIntegration', ], }, { From 5373dd62f5d496f2159e7e5bd3776f6d25919a73 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 11:03:12 -0400 Subject: [PATCH 23/40] Remove released betas --- src/CONST.ts | 7 -- src/components/AddPaymentMethodMenu.js | 77 +++++-------------- src/components/LHNOptionsList/OptionRowLHN.js | 2 +- src/components/SettlementButton.js | 2 +- src/libs/E2E/apiMocks/beginSignin.ts | 5 -- src/libs/Permissions.ts | 20 ----- src/libs/__mocks__/Permissions.ts | 1 - src/libs/actions/User.js | 1 - src/pages/ProfilePage.js | 2 +- .../AttachmentPickerWithMenuItems.js | 2 +- .../home/report/ReportActionItemSingle.js | 2 +- .../FloatingActionButtonAndPopover.js | 6 +- .../SignInOrAvatarWithOptionalStatus.js | 5 +- src/pages/settings/InitialSettingsPage.js | 2 +- src/pages/settings/Profile/ProfilePage.js | 6 +- src/pages/settings/Wallet/AddDebitCardPage.js | 4 - .../settings/Wallet/WalletPage/WalletPage.js | 19 +++-- src/pages/tasks/NewTaskDescriptionPage.js | 4 - src/pages/tasks/NewTaskDetailsPage.js | 4 - src/pages/tasks/NewTaskPage.js | 5 -- src/pages/tasks/NewTaskTitlePage.js | 4 - src/pages/workspace/WorkspacesListPage.js | 3 +- 22 files changed, 43 insertions(+), 140 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 436ac4ebbc31..fc4973e2cf39 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -248,17 +248,11 @@ const CONST = { BETAS: { ALL: 'all', CHRONOS_IN_CASH: 'chronosInCash', - PAY_WITH_EXPENSIFY: 'payWithExpensify', - FREE_PLAN: 'freePlan', DEFAULT_ROOMS: 'defaultRooms', - BETA_EXPENSIFY_WALLET: 'expensifyWallet', BETA_COMMENT_LINKING: 'commentLinking', INTERNATIONALIZATION: 'internationalization', POLICY_ROOMS: 'policyRooms', - PASSWORDLESS: 'passwordless', - TASKS: 'tasks', THREADS: 'threads', - CUSTOM_STATUS: 'customStatus', NEW_DOT_SAML: 'newDotSAML', PDF_META_STORE: 'pdfMetaStore', REPORT_ACTION_CONTEXT_MENU: 'reportActionContextMenu', @@ -320,7 +314,6 @@ const CONST = { SECURE_RECEIPTS: 'secureReceipts', SECURE_RECEIPTS_REPORTS: 'secureReceiptsReports', SELF_SERVICE_HARD_LAUNCH: 'selfServiceHardLaunch', - SEND_MONEY: 'sendMoney', SMART_SCAN_USER_DISPUTES: 'smartScanUserDisputes', SMS_SIGN_UP: 'smsSignUp', STRIPE_CONNECT: 'stripeConnect', diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 4f1500132106..da98045c0a7e 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -1,19 +1,15 @@ -import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import Permissions from '@libs/Permissions'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import iouReportPropTypes from '@pages/iouReportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as Expensicons from './Icon/Expensicons'; import PopoverMenu from './PopoverMenu'; import refPropTypes from './refPropTypes'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; import withWindowDimensions from './withWindowDimensions'; const propTypes = { @@ -23,12 +19,6 @@ const propTypes = { /** Callback to execute when the component closes. */ onClose: PropTypes.func.isRequired, - /** Callback to execute when the payment method is selected. */ - onItemSelected: PropTypes.func.isRequired, - - /** The IOU/Expense report we are paying */ - iouReport: iouReportPropTypes, - /** Anchor position for the AddPaymentMenu. */ anchorPosition: PropTypes.shape({ horizontal: PropTypes.number, @@ -47,15 +37,10 @@ const propTypes = { /** Popover anchor ref */ anchorRef: refPropTypes, - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), + ...withLocalizePropTypes, }; const defaultProps = { - iouReport: {}, anchorPosition: {}, anchorAlignment: { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, @@ -63,50 +48,32 @@ const defaultProps = { }, betas: [], anchorRef: () => {}, - session: {}, }; -function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session, betas}) { - const {translate} = useLocalize(); - +function AddPaymentMethodMenu(props) { return ( { + props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT); + }, + }, + ...([ { - text: translate('common.personalBankAccount'), - icon: Expensicons.Bank, - onSelected: () => { - onItemSelected(CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT); - }, - }, - ] - : []), - ...(!ReportActionsUtils.hasRequestFromCurrentAccount(lodashGet(iouReport, 'reportID', 0), lodashGet(session, 'accountID', 0)) - ? [ - { - text: translate('common.businessBankAccount'), - icon: Expensicons.Building, - onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT), - }, - ] - : []), - ...(Permissions.canUseWallet(betas) - ? [ - { - text: translate('common.debitCard'), + text: props.translate('common.debitCard'), icon: Expensicons.CreditCard, - onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD), + onSelected: () => props.onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD), }, - ] - : []), + ]), ]} withoutOverlay /> @@ -119,12 +86,10 @@ AddPaymentMethodMenu.displayName = 'AddPaymentMethodMenu'; export default compose( withWindowDimensions, + withLocalize, withOnyx({ betas: { key: ONYXKEYS.BETAS, }, - session: { - key: ONYXKEYS.SESSION, - }, }), )(AddPaymentMethodMenu); diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 8420f3db7a1e..117d15339dae 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -157,7 +157,7 @@ function OptionRowLHN(props) { const statusClearAfterDate = lodashGet(optionItem, 'status.clearAfter', ''); const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = Permissions.canUseCustomStatus(props.betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); + const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && _.isEmpty(optionItem.chatType) && !optionItem.isThread && lodashGet(optionItem, 'displayNamesWithTooltips.length', 0) > 2; diff --git a/src/components/SettlementButton.js b/src/components/SettlementButton.js index 27ba3d08a16f..f281ddd6c003 100644 --- a/src/components/SettlementButton.js +++ b/src/components/SettlementButton.js @@ -155,7 +155,7 @@ function SettlementButton({ value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, }, }; - const canUseWallet = !isExpenseReport && currency === CONST.CURRENCY.USD && Permissions.canUsePayWithExpensify(betas) && Permissions.canUseWallet(betas); + const canUseWallet = !isExpenseReport && currency === CONST.CURRENCY.USD; // To achieve the one tap pay experience we need to choose the correct payment type as default, // if user already paid for some request or expense, let's use the last payment method or use default. diff --git a/src/libs/E2E/apiMocks/beginSignin.ts b/src/libs/E2E/apiMocks/beginSignin.ts index 298846250a12..c5002f1f3dd8 100644 --- a/src/libs/E2E/apiMocks/beginSignin.ts +++ b/src/libs/E2E/apiMocks/beginSignin.ts @@ -17,11 +17,6 @@ const beginSignin = ({email}: SigninParams): Response => ({ validated: true, }, }, - { - onyxMethod: 'set', - key: 'betas', - value: ['passwordless'], - }, ], jsonCode: 200, requestID: '783e54ef4b38cff5-SJC', diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 0c8843b87415..c3e01735fb07 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -9,18 +9,10 @@ function canUseChronos(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.CHRONOS_IN_CASH) || canUseAllBetas(betas); } -function canUsePayWithExpensify(betas: Beta[]): boolean { - return betas?.includes(CONST.BETAS.PAY_WITH_EXPENSIFY) || canUseAllBetas(betas); -} - function canUseDefaultRooms(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.DEFAULT_ROOMS) || canUseAllBetas(betas); } -function canUseWallet(betas: Beta[]): boolean { - return betas?.includes(CONST.BETAS.BETA_EXPENSIFY_WALLET) || canUseAllBetas(betas); -} - function canUseCommentLinking(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.BETA_COMMENT_LINKING) || canUseAllBetas(betas); } @@ -34,14 +26,6 @@ function canUsePolicyRooms(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.POLICY_ROOMS) || canUseAllBetas(betas); } -function canUseTasks(betas: Beta[]): boolean { - return betas?.includes(CONST.BETAS.TASKS) || canUseAllBetas(betas); -} - -function canUseCustomStatus(betas: Beta[]): boolean { - return betas?.includes(CONST.BETAS.CUSTOM_STATUS) || canUseAllBetas(betas); -} - function canUseViolations(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.VIOLATIONS) || canUseAllBetas(betas); } @@ -55,13 +39,9 @@ function canUseLinkPreviews(): boolean { export default { canUseChronos, - canUsePayWithExpensify, canUseDefaultRooms, - canUseWallet, canUseCommentLinking, canUsePolicyRooms, - canUseTasks, - canUseCustomStatus, canUseLinkPreviews, canUseViolations, }; diff --git a/src/libs/__mocks__/Permissions.ts b/src/libs/__mocks__/Permissions.ts index e95d13f52803..4f47c0b756b7 100644 --- a/src/libs/__mocks__/Permissions.ts +++ b/src/libs/__mocks__/Permissions.ts @@ -12,5 +12,4 @@ export default { ...jest.requireActual('../Permissions'), canUseDefaultRooms: (betas: Beta[]) => betas.includes(CONST.BETAS.DEFAULT_ROOMS), canUsePolicyRooms: (betas: Beta[]) => betas.includes(CONST.BETAS.POLICY_ROOMS), - canUseCustomStatus: (betas: Beta[]) => betas.includes(CONST.BETAS.CUSTOM_STATUS), }; diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 3c91dc4624cd..ad6fd7be10dd 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -77,7 +77,6 @@ function closeAccount(message) { * Resends a validation link to a given login * * @param {String} login - * @param {Boolean} isPasswordless - temporary param to trigger passwordless flow in backend */ function resendValidateCode(login) { Session.resendValidateCode(login); diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 4b3c927ef317..538601ad68ad 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -133,7 +133,7 @@ function ProfilePage(props) { const statusEmojiCode = lodashGet(details, 'status.emojiCode', ''); const statusText = lodashGet(details, 'status.text', ''); - const hasStatus = !!statusEmojiCode && Permissions.canUseCustomStatus(props.betas); + const hasStatus = !!statusEmojiCode; const statusContent = `${statusEmojiCode} ${statusText}`; const navigateBackTo = lodashGet(props.route, 'params.backTo', ROUTES.HOME); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 44183b23e43f..17dad987377c 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -152,7 +152,7 @@ function AttachmentPickerWithMenuItems({ * @returns {Boolean} */ const taskOption = useMemo(() => { - if (!Permissions.canUseTasks(betas) || !ReportUtils.canCreateTaskInReport(report)) { + if (!ReportUtils.canCreateTaskInReport(report)) { return []; } diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 955e024bd7a8..d60c2fc6914c 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -205,7 +205,7 @@ function ReportActionItemSingle(props) { ); }; - const hasEmojiStatus = !displayAllActors && status && status.emojiCode && Permissions.canUseCustomStatus(props.betas); + const hasEmojiStatus = !displayAllActors && status && status.emojiCode; const formattedDate = DateUtils.getStatusUntilDate(lodashGet(status, 'clearAfter')); const statusText = lodashGet(status, 'text', ''); const statusTooltipText = formattedDate ? `${statusText} (${formattedDate})` : statusText; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 739f7e3e0295..2e18dee79b59 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -207,15 +207,13 @@ function FloatingActionButtonAndPopover(props) { text: props.translate('iou.sendMoney'), onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), }, - ...(Permissions.canUseTasks(props.betas) - ? [ + ...([ { icon: Expensicons.Task, text: props.translate('newTaskPage.assignTask'), onSelected: () => interceptAnonymousUser(() => Task.clearOutTaskInfoAndNavigate()), }, - ] - : []), + ]), { icon: Expensicons.Heart, text: props.translate('sidebarScreen.saveTheWorld'), diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js index 8e41e1c6af6a..6dc3aaec45b1 100644 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js @@ -32,9 +32,8 @@ const defaultProps = { }, }; -function SignInOrAvatarWithOptionalStatus({currentUserPersonalDetails, isCreateMenuOpen, betas}) { - const statusEmojiCode = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); - const emojiStatus = Permissions.canUseCustomStatus(betas) ? statusEmojiCode : ''; +function SignInOrAvatarWithOptionalStatus({currentUserPersonalDetails, isCreateMenuOpen}) { + const emojiStatus = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); if (Session.isAnonymousUser()) { return ; diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index d88105b31360..bf5d1a9aa4a2 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -289,7 +289,7 @@ function InitialSettingsPage(props) { * @returns {Number} the user wallet balance */ const getWalletBalance = (isPaymentItem) => - isPaymentItem && Permissions.canUseWallet(props.betas) ? CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance) : undefined; + isPaymentItem && CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance); return ( <> diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 1e4485b4c36b..6dae3f5b01f7 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -83,15 +83,13 @@ function ProfilePage(props) { pageRoute: ROUTES.SETTINGS_CONTACT_METHODS.route, brickRoadIndicator: contactMethodBrickRoadIndicator, }, - ...(Permissions.canUseCustomStatus(props.betas) - ? [ + ...([ { description: props.translate('statusPage.status'), title: emojiCode ? `${emojiCode} ${lodashGet(props, 'currentUserPersonalDetails.status.text', '')}` : '', pageRoute: ROUTES.SETTINGS_STATUS, }, - ] - : []), + ]), { description: props.translate('pronounsPage.pronouns'), title: getPronouns(), diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index 45893dfb5d65..ffa13a0a1441 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -104,10 +104,6 @@ function DebitCardPage(props) { return errors; }; - if (!Permissions.canUseWallet(props.betas)) { - return ; - } - return ( nameOnCardRef.current && nameOnCardRef.current.focus()} diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js index 4ef540f91eef..19d7c182c546 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js @@ -106,7 +106,7 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen }, [shouldShowEmptyState, windowWidth]); const getSelectedPaymentMethodID = useCallback(() => { - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { return paymentMethod.selectedPaymentMethod.bankAccountID; } if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { @@ -152,12 +152,12 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen // The delete/default menu if (accountType) { let formattedSelectedPaymentMethod; - if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { formattedSelectedPaymentMethod = { title: account.addressName, icon: account.icon, description: PaymentUtils.getPaymentMethodDescription(accountType, account), - type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, + type: CONST.PAYMENT_METHODS.BANK_ACCOUNT, }; } else if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { formattedSelectedPaymentMethod = { @@ -202,7 +202,7 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen return; } - if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (paymentType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { BankAccounts.openPersonalBankAccountSetupView(); return; } @@ -228,7 +228,7 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen const previousPaymentMethod = _.find(paymentMethods, (method) => method.isDefault); const currentPaymentMethod = _.find(paymentMethods, (method) => method.methodID === paymentMethod.methodID); - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID, null, previousPaymentMethod, currentPaymentMethod); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { PaymentMethods.makeDefaultPaymentMethod(null, paymentMethod.selectedPaymentMethod.fundID, previousPaymentMethod, currentPaymentMethod); @@ -243,7 +243,7 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen ]); const deletePaymentMethod = useCallback(() => { - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { BankAccounts.deletePaymentBankAccount(paymentMethod.selectedPaymentMethod.bankAccountID); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { PaymentMethods.deletePaymentCard(paymentMethod.selectedPaymentMethod.fundID); @@ -294,7 +294,7 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen // We should reset selected payment method state values and close corresponding modals if the selected payment method is deleted let shouldResetPaymentMethodData = false; - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && _.isEmpty(bankAccountList[paymentMethod.methodID])) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && _.isEmpty(bankAccountList[paymentMethod.methodID])) { shouldResetPaymentMethodData = true; } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && _.isEmpty(fundList[paymentMethod.methodID])) { shouldResetPaymentMethodData = true; @@ -309,8 +309,7 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen const shouldShowMakeDefaultButton = !paymentMethod.isSelectedPaymentMethodDefault && - Permissions.canUseWallet(betas) && - !(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && paymentMethod.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS); + !(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT && paymentMethod.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS); // Determines whether or not the modal popup is mounted from the bottom of the screen instead of the side mount on Web or Desktop screens const isPopoverBottomMount = anchorPosition.anchorPositionTop === 0 || isSmallScreenWidth; @@ -364,7 +363,7 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen navigateToWalletOrTransferBalancePage(source)} onSelectPaymentMethod={(selectedPaymentMethod) => { - if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.BANK_ACCOUNT) { return; } // To allow upgrading to a gold wallet, continue with the KYC flow after adding a bank account diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index e571edce39e9..49e685de6a62 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -52,10 +52,6 @@ function NewTaskDescriptionPage(props) { Navigation.goBack(ROUTES.NEW_TASK); }; - if (!Permissions.canUseTasks(props.betas)) { - Navigation.dismissModal(); - return null; - } return ( Date: Thu, 23 Nov 2023 11:07:47 -0400 Subject: [PATCH 24/40] remove more places --- src/components/AddPaymentMethodMenu.js | 5 ----- src/pages/settings/Wallet/WalletPage/WalletPage.js | 6 +----- tests/perf-test/ReportActionCompose.perf-test.js | 4 ---- tests/perf-test/ReportScreen.perf-test.js | 1 - 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index da98045c0a7e..ea445399c96e 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -3,7 +3,6 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import compose from '@libs/compose'; -import Permissions from '@libs/Permissions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as Expensicons from './Icon/Expensicons'; @@ -31,9 +30,6 @@ const propTypes = { vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - /** Popover anchor ref */ anchorRef: refPropTypes, @@ -46,7 +42,6 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }, - betas: [], anchorRef: () => {}, }; diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js index 19d7c182c546..cbb8471da8da 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js @@ -25,7 +25,6 @@ import compose from '@libs/compose'; import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import Navigation from '@libs/Navigation/Navigation'; import * as PaymentUtils from '@libs/PaymentUtils'; -import Permissions from '@libs/Permissions'; import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList'; import WalletEmptyState from '@pages/settings/Wallet/WalletEmptyState'; import useTheme from '@styles/themes/useTheme'; @@ -38,7 +37,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {defaultProps, propTypes} from './walletPagePropTypes'; -function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymentMethods, network, shouldListenForResize, userWallet, walletTerms}) { +function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethods, network, shouldListenForResize, userWallet, walletTerms}) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -564,9 +563,6 @@ WalletPage.displayName = 'WalletPage'; export default compose( withNetwork(), withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, cardList: { key: ONYXKEYS.CARD_LIST, }, diff --git a/tests/perf-test/ReportActionCompose.perf-test.js b/tests/perf-test/ReportActionCompose.perf-test.js index 6419d8f895ef..05d2b5a4906a 100644 --- a/tests/perf-test/ReportActionCompose.perf-test.js +++ b/tests/perf-test/ReportActionCompose.perf-test.js @@ -22,10 +22,6 @@ jest.mock('react-native-reanimated', () => ({ useAnimatedRef: jest.fn, })); -jest.mock('../../src/libs/Permissions', () => ({ - canUseTasks: jest.fn(() => true), -})); - jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index f8a44b54cc39..a49aaff4d697 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -54,7 +54,6 @@ jest.mock('../../src/hooks/useEnvironment', () => ); jest.mock('../../src/libs/Permissions', () => ({ - canUseTasks: jest.fn(() => true), canUseLinkPreviews: jest.fn(() => true), })); From e564ecc90c7f6c2af1edb22b78161e24eb352f44 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 11:09:56 -0400 Subject: [PATCH 25/40] reset src/components/AddPaymentMethodMenu.js --- src/components/AddPaymentMethodMenu.js | 75 +++++++++++++++++++------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index ea445399c96e..547913ad7048 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -1,14 +1,18 @@ +import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import iouReportPropTypes from '@pages/iouReportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as Expensicons from './Icon/Expensicons'; import PopoverMenu from './PopoverMenu'; import refPropTypes from './refPropTypes'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; import withWindowDimensions from './withWindowDimensions'; const propTypes = { @@ -18,6 +22,12 @@ const propTypes = { /** Callback to execute when the component closes. */ onClose: PropTypes.func.isRequired, + /** Callback to execute when the payment method is selected. */ + onItemSelected: PropTypes.func.isRequired, + + /** The IOU/Expense report we are paying */ + iouReport: iouReportPropTypes, + /** Anchor position for the AddPaymentMenu. */ anchorPosition: PropTypes.shape({ horizontal: PropTypes.number, @@ -30,43 +40,68 @@ const propTypes = { vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + /** List of betas available to current user */ + betas: PropTypes.arrayOf(PropTypes.string), + /** Popover anchor ref */ anchorRef: refPropTypes, - ...withLocalizePropTypes, + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user accountID */ + accountID: PropTypes.number, + }), }; const defaultProps = { + iouReport: {}, anchorPosition: {}, anchorAlignment: { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }, + betas: [], anchorRef: () => {}, + session: {}, }; -function AddPaymentMethodMenu(props) { +function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session, betas}) { + const {translate} = useLocalize(); + return ( { - props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT); - }, - }, + ...(ReportUtils.isIOUReport(iouReport) + ? [ + { + text: translate('common.personalBankAccount'), + icon: Expensicons.Bank, + onSelected: () => { + onItemSelected(CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT); + }, + }, + ] + : []), + ...(!ReportActionsUtils.hasRequestFromCurrentAccount(lodashGet(iouReport, 'reportID', 0), lodashGet(session, 'accountID', 0)) + ? [ + { + text: translate('common.businessBankAccount'), + icon: Expensicons.Building, + onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT), + }, + ] + : []), ...([ { - text: props.translate('common.debitCard'), + text: translate('common.debitCard'), icon: Expensicons.CreditCard, - onSelected: () => props.onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD), + onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD), }, ]), ]} @@ -81,10 +116,12 @@ AddPaymentMethodMenu.displayName = 'AddPaymentMethodMenu'; export default compose( withWindowDimensions, - withLocalize, withOnyx({ betas: { key: ONYXKEYS.BETAS, }, + session: { + key: ONYXKEYS.SESSION, + }, }), )(AddPaymentMethodMenu); From 45494aef8ee06f05e07473388e07c2ed095ffc85 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 11:21:46 -0400 Subject: [PATCH 26/40] Remove more references to beta --- src/components/LHNOptionsList/OptionRowLHN.js | 5 ----- src/components/SettlementButton.js | 13 ++----------- src/pages/ProfilePage.js | 4 ---- .../AttachmentPickerWithMenuItems.js | 16 ++-------------- src/pages/home/report/ReportActionItemSingle.js | 12 +----------- .../FloatingActionButtonAndPopover.js | 8 -------- .../sidebar/SignInOrAvatarWithOptionalStatus.js | 17 +---------------- src/pages/settings/InitialSettingsPage.js | 10 +--------- src/pages/settings/Profile/ProfilePage.js | 4 ---- src/pages/settings/Wallet/AddDebitCardPage.js | 9 --------- src/pages/tasks/NewTaskDescriptionPage.js | 8 -------- src/pages/tasks/NewTaskDetailsPage.js | 8 -------- src/pages/tasks/NewTaskPage.js | 7 ------- src/pages/tasks/NewTaskTitlePage.js | 8 -------- src/pages/workspace/WorkspacesListPage.js | 1 - 15 files changed, 7 insertions(+), 123 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 117d15339dae..4e01ee4d7830 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -20,7 +20,6 @@ import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; import {getGroupChatName} from '@libs/GroupChatUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import Permissions from '@libs/Permissions'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; import * as ContextMenuActions from '@pages/home/report/ContextMenu/ContextMenuActions'; @@ -36,9 +35,6 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types hoverStyle: PropTypes.object, - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - /** The ID of the report that the option is for */ reportID: PropTypes.string.isRequired, @@ -65,7 +61,6 @@ const defaultProps = { style: null, optionItem: null, isFocused: false, - betas: [], }; function OptionRowLHN(props) { diff --git a/src/components/SettlementButton.js b/src/components/SettlementButton.js index f281ddd6c003..8cf9655d34dc 100644 --- a/src/components/SettlementButton.js +++ b/src/components/SettlementButton.js @@ -5,7 +5,6 @@ import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import compose from '@libs/compose'; -import Permissions from '@libs/Permissions'; import * as ReportUtils from '@libs/ReportUtils'; import iouReportPropTypes from '@pages/iouReportPropTypes'; import * as BankAccounts from '@userActions/BankAccounts'; @@ -34,9 +33,6 @@ const propTypes = { /** The IOU/Expense report we are paying */ iouReport: iouReportPropTypes, - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - /** The route to redirect if user does not have a payment method setup */ enablePaymentsRoute: PropTypes.string.isRequired, @@ -89,9 +85,8 @@ const defaultProps = { currency: CONST.CURRENCY.USD, chatReportID: '', - // The "betas" array, "iouReport" and "nvp_lastPaymentMethod" objects needs to be stable to prevent the "useMemo" + // The "iouReport" and "nvp_lastPaymentMethod" objects needs to be stable to prevent the "useMemo" // hook from being recreated unnecessarily, hence the use of CONST.EMPTY_ARRAY and CONST.EMPTY_OBJECT - betas: CONST.EMPTY_ARRAY, iouReport: CONST.EMPTY_OBJECT, nvp_lastPaymentMethod: CONST.EMPTY_OBJECT, style: [], @@ -113,7 +108,6 @@ function SettlementButton({ addBankAccountRoute, kycWallAnchorAlignment, paymentMethodDropdownAnchorAlignment, - betas, buttonSize, chatReportID, currency, @@ -171,7 +165,7 @@ function SettlementButton({ return _.sortBy(buttonOptions, (method) => (method.value === paymentMethod ? 0 : 1)); } return buttonOptions; - }, [betas, currency, formattedAmount, iouReport, nvp_lastPaymentMethod, policyID, translate]); + }, [currency, formattedAmount, iouReport, nvp_lastPaymentMethod, policyID, translate]); const selectPaymentType = (event, iouPaymentType, triggerKYCFlow) => { if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) { @@ -219,9 +213,6 @@ SettlementButton.displayName = 'SettlementButton'; export default compose( withNavigation, withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, nvp_lastPaymentMethod: { key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD, }, diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 538601ad68ad..ffe8271629f4 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -25,7 +25,6 @@ import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -292,9 +291,6 @@ export default compose( isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, }, - betas: { - key: ONYXKEYS.BETAS, - }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 17dad987377c..ec9421bfa1cb 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -1,7 +1,6 @@ import PropTypes from 'prop-types'; import React, {useMemo} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import AttachmentPicker from '@components/AttachmentPicker'; import Icon from '@components/Icon'; @@ -12,19 +11,14 @@ import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; -import Permissions from '@libs/Permissions'; import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; const propTypes = { - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - /** The report currently being looked at */ report: PropTypes.shape({ /** ID of the report */ @@ -87,7 +81,6 @@ const propTypes = { }; const defaultProps = { - betas: [], reportParticipantIDs: [], }; @@ -98,7 +91,6 @@ const defaultProps = { * @returns {React.Component} */ function AttachmentPickerWithMenuItems({ - betas, report, reportParticipantIDs, displayFileInModal, @@ -163,7 +155,7 @@ function AttachmentPickerWithMenuItems({ onSelected: () => Task.clearOutTaskInfoAndNavigate(reportID), }, ]; - }, [betas, report, reportID, translate]); + }, [report, reportID, translate]); const onPopoverMenuClose = () => { setMenuVisibility(false); @@ -287,8 +279,4 @@ AttachmentPickerWithMenuItems.propTypes = propTypes; AttachmentPickerWithMenuItems.defaultProps = defaultProps; AttachmentPickerWithMenuItems.displayName = 'AttachmentPickerWithMenuItems'; -export default withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, -})(AttachmentPickerWithMenuItems); +export default AttachmentPickerWithMenuItems; diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index d60c2fc6914c..2752d5b5c536 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -2,7 +2,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Avatar from '@components/Avatar'; import MultipleAvatars from '@components/MultipleAvatars'; @@ -18,7 +17,6 @@ import compose from '@libs/compose'; import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import reportPropTypes from '@pages/reportPropTypes'; @@ -26,7 +24,6 @@ import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; import themeColors from '@styles/themes/default'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import ReportActionItemDate from './ReportActionItemDate'; import ReportActionItemFragment from './ReportActionItemFragment'; @@ -267,11 +264,4 @@ ReportActionItemSingle.propTypes = propTypes; ReportActionItemSingle.defaultProps = defaultProps; ReportActionItemSingle.displayName = 'ReportActionItemSingle'; -export default compose( - withLocalize, - withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, - }), -)(ReportActionItemSingle); +export default withLocalize(ReportActionItemSingle); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 2e18dee79b59..fd9f0854b5a1 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -13,7 +13,6 @@ import withWindowDimensions from '@components/withWindowDimensions'; import usePrevious from '@hooks/usePrevious'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import useThemeStyles from '@styles/useThemeStyles'; import * as App from '@userActions/App'; import * as IOU from '@userActions/IOU'; @@ -54,9 +53,6 @@ const propTypes = { name: PropTypes.string, }), - /* Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - /** Indicated whether the report data is loading */ isLoading: PropTypes.bool, @@ -74,7 +70,6 @@ const defaultProps = { onHideCreateMenu: () => {}, onShowCreateMenu: () => {}, allPolicies: {}, - betas: [], isLoading: false, innerRef: null, demoInfo: {}, @@ -276,9 +271,6 @@ export default compose( key: ONYXKEYS.COLLECTION.POLICY, selector: policySelector, }, - betas: { - key: ONYXKEYS.BETAS, - }, isLoading: { key: ONYXKEYS.IS_LOADING_APP, }, diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js index 6dc3aaec45b1..8e680a20d419 100644 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js @@ -2,13 +2,9 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; -import {withOnyx} from 'react-native-onyx'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import compose from '@libs/compose'; -import Permissions from '@libs/Permissions'; import personalDetailsPropType from '@pages/personalDetailsPropType'; import * as Session from '@userActions/Session'; -import ONYXKEYS from '@src/ONYXKEYS'; import AvatarWithOptionalStatus from './AvatarWithOptionalStatus'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; import SignInButton from './SignInButton'; @@ -19,13 +15,9 @@ const propTypes = { /** Whether the create menu is open or not */ isCreateMenuOpen: PropTypes.bool, - - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), }; const defaultProps = { - betas: [], isCreateMenuOpen: false, currentUserPersonalDetails: { status: {emojiCode: ''}, @@ -52,11 +44,4 @@ function SignInOrAvatarWithOptionalStatus({currentUserPersonalDetails, isCreateM SignInOrAvatarWithOptionalStatus.propTypes = propTypes; SignInOrAvatarWithOptionalStatus.defaultProps = defaultProps; SignInOrAvatarWithOptionalStatus.displayName = 'SignInOrAvatarWithOptionalStatus'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, - }), -)(SignInOrAvatarWithOptionalStatus); +export default withCurrentUserPersonalDetails(SignInOrAvatarWithOptionalStatus); diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index bf5d1a9aa4a2..89d97ff360a7 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -25,7 +25,6 @@ import useWaitForNavigation from '@hooks/useWaitForNavigation'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; @@ -89,9 +88,6 @@ const propTypes = { /** Bank account attached to free plan */ reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - /** Information about the user accepting the terms for payments */ walletTerms: walletTermsPropTypes, @@ -120,7 +116,6 @@ const defaultProps = { currentBalance: 0, }, reimbursementAccount: {}, - betas: [], walletTerms: {}, bankAccountList: {}, fundList: null, @@ -324,7 +319,7 @@ function InitialSettingsPage(props) { })} ); - }, [getDefaultMenuItems, props.betas, props.userWallet.currentBalance, translate, isExecuting, singleExecution]); + }, [getDefaultMenuItems, props.userWallet.currentBalance, translate, isExecuting, singleExecution]); const headerContent = ( @@ -426,9 +421,6 @@ export default compose( userWallet: { key: ONYXKEYS.USER_WALLET, }, - betas: { - key: ONYXKEYS.BETAS, - }, bankAccountList: { key: ONYXKEYS.BANK_ACCOUNT_LIST, }, diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 6dae3f5b01f7..74515c52dcf3 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -15,7 +15,6 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import * as UserUtils from '@libs/UserUtils'; import userPropTypes from '@pages/settings/userPropTypes'; import useThemeStyles from '@styles/useThemeStyles'; @@ -182,8 +181,5 @@ export default compose( user: { key: ONYXKEYS.USER, }, - betas: { - key: ONYXKEYS.BETAS, - }, }), )(ProfilePage); diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index ffa13a0a1441..ed4a545ff208 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -14,9 +14,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import * as ValidationUtils from '@libs/ValidationUtils'; -import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import useThemeStyles from '@styles/useThemeStyles'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; @@ -28,16 +26,12 @@ const propTypes = { formData: PropTypes.shape({ setupComplete: PropTypes.bool, }), - - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), }; const defaultProps = { formData: { setupComplete: false, }, - betas: [], }; function DebitCardPage(props) { @@ -208,7 +202,4 @@ export default withOnyx({ formData: { key: ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, }, - betas: { - key: ONYXKEYS.BETAS, - }, })(DebitCardPage); diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index 49e685de6a62..002ca2944e52 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -13,7 +13,6 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; import useThemeStyles from '@styles/useThemeStyles'; import * as Task from '@userActions/Task'; @@ -22,9 +21,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; const propTypes = { - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - /** Grab the Share description of the Task */ task: PropTypes.shape({ /** Description of the Task */ @@ -35,7 +31,6 @@ const propTypes = { }; const defaultProps = { - betas: [], task: { description: '', }, @@ -101,9 +96,6 @@ NewTaskDescriptionPage.defaultProps = defaultProps; export default compose( withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, task: { key: ONYXKEYS.TASK, }, diff --git a/src/pages/tasks/NewTaskDetailsPage.js b/src/pages/tasks/NewTaskDetailsPage.js index 173aa13410d8..818ae4440842 100644 --- a/src/pages/tasks/NewTaskDetailsPage.js +++ b/src/pages/tasks/NewTaskDetailsPage.js @@ -14,7 +14,6 @@ import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import useThemeStyles from '@styles/useThemeStyles'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; @@ -22,9 +21,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; const propTypes = { - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - /** Task title and description data */ task: PropTypes.shape({ title: PropTypes.string, @@ -35,7 +31,6 @@ const propTypes = { }; const defaultProps = { - betas: [], task: {}, }; @@ -135,9 +130,6 @@ NewTaskDetailsPage.defaultProps = defaultProps; export default compose( withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, task: { key: ONYXKEYS.TASK, }, diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js index ca2a203eaabc..0e10015b445c 100644 --- a/src/pages/tasks/NewTaskPage.js +++ b/src/pages/tasks/NewTaskPage.js @@ -15,7 +15,6 @@ import compose from '@libs/compose'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import Permissions from '@libs/Permissions'; import * as ReportUtils from '@libs/ReportUtils'; import reportPropTypes from '@pages/reportPropTypes'; import useThemeStyles from '@styles/useThemeStyles'; @@ -33,9 +32,6 @@ const propTypes = { parentReportID: PropTypes.string, }), - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf( PropTypes.shape({ @@ -226,9 +222,6 @@ NewTaskPage.defaultProps = defaultProps; export default compose( withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, task: { key: ONYXKEYS.TASK, }, diff --git a/src/pages/tasks/NewTaskTitlePage.js b/src/pages/tasks/NewTaskTitlePage.js index 7eb886bad944..f1c583e4aa2a 100644 --- a/src/pages/tasks/NewTaskTitlePage.js +++ b/src/pages/tasks/NewTaskTitlePage.js @@ -12,7 +12,6 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; import useThemeStyles from '@styles/useThemeStyles'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; @@ -20,9 +19,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; const propTypes = { - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - /** Grab the Share title of the Task */ task: PropTypes.shape({ /** Title of the Task */ @@ -33,7 +29,6 @@ const propTypes = { }; const defaultProps = { - betas: [], task: { title: '', }, @@ -107,9 +102,6 @@ NewTaskTitlePage.defaultProps = defaultProps; export default compose( withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, task: { key: ONYXKEYS.TASK, }, diff --git a/src/pages/workspace/WorkspacesListPage.js b/src/pages/workspace/WorkspacesListPage.js index 29e68a5121fa..1e51c64a711c 100755 --- a/src/pages/workspace/WorkspacesListPage.js +++ b/src/pages/workspace/WorkspacesListPage.js @@ -12,7 +12,6 @@ import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import usePermissions from '@hooks/usePermissions'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; From eece94181783429ae41256a86652b70221f26bc3 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 11:27:56 -0400 Subject: [PATCH 27/40] remove more forgotten references --- src/components/AddPaymentMethodMenu.js | 9 +-------- src/pages/home/report/ReportActionItemSingle.js | 1 - src/pages/tasks/NewTaskPage.js | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 547913ad7048..223d880777bd 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -40,9 +40,6 @@ const propTypes = { vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - /** Popover anchor ref */ anchorRef: refPropTypes, @@ -60,12 +57,11 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }, - betas: [], anchorRef: () => {}, session: {}, }; -function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session, betas}) { +function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session}) { const {translate} = useLocalize(); return ( @@ -117,9 +113,6 @@ AddPaymentMethodMenu.displayName = 'AddPaymentMethodMenu'; export default compose( withWindowDimensions, withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 2752d5b5c536..e69531e2cc53 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -13,7 +13,6 @@ import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import compose from '@libs/compose'; import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js index 0e10015b445c..24bdb39d5bff 100644 --- a/src/pages/tasks/NewTaskPage.js +++ b/src/pages/tasks/NewTaskPage.js @@ -53,7 +53,6 @@ const propTypes = { }; const defaultProps = { - betas: [], task: {}, personalDetails: {}, reports: {}, From 6c74c47b391e4f4f51f2a7956271d359dbf38c34 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 11:28:38 -0400 Subject: [PATCH 28/40] run prettier --- src/libs/E2E/apiMocks/openApp.ts | 4 +--- src/libs/E2E/apiMocks/signinUser.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/libs/E2E/apiMocks/openApp.ts b/src/libs/E2E/apiMocks/openApp.ts index 3e22948654bc..a0583d8439c0 100644 --- a/src/libs/E2E/apiMocks/openApp.ts +++ b/src/libs/E2E/apiMocks/openApp.ts @@ -1457,9 +1457,7 @@ const openApp = (): Response => ({ { onyxMethod: 'set', key: 'betas', - value: [ - 'all', - ], + value: ['all'], }, { onyxMethod: 'merge', diff --git a/src/libs/E2E/apiMocks/signinUser.ts b/src/libs/E2E/apiMocks/signinUser.ts index 55b89ea6b3db..a7d841196c48 100644 --- a/src/libs/E2E/apiMocks/signinUser.ts +++ b/src/libs/E2E/apiMocks/signinUser.ts @@ -36,9 +36,7 @@ const signinUser = ({email}: SigninParams): Response => ({ { onyxMethod: 'set', key: 'betas', - value: [ - 'all', - ], + value: ['all'], }, { onyxMethod: 'merge', From 2849596a9d29b1ab63725dc39426c3dea3fecf81 Mon Sep 17 00:00:00 2001 From: Zany Renney <56830058+zanyrenney@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:44:13 +0000 Subject: [PATCH 29/40] Delete docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports --- ... Employees and Reports > Approving Reports | 136 ------------------ 1 file changed, 136 deletions(-) delete mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports deleted file mode 100644 index 133ab524b9c6..000000000000 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Managing Employees and Reports > Approving Reports +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: How To: Manage Employees and Reports > Approving Reports -description: This page will help you understand the lifecycle of a report and how to approve reports that are submitted to you. ---- -​ -# About -This article provides a comprehensive guide on report management within our platform. From viewing, editing, and submitting your employees' Open reports to handling rejections and unapproving, as well as harnessing the power of our "Guided Review" feature. Additionally, we'll delve into best practices for Concierge, offering insights on how to streamline and automate your report approval processes for maximum efficiency. -Let's dive in! -​ -​ -# How-to manage reports -This section covers the most essential information a user needs to operate a feature i.e. what to click on. We’ll go over any action the user might take when configuring or using the feature, starting from configuration and moving to usage. -​ -What options does a user have when configuring this feature? -What options does a user have then interacting with this feature? -What elements of this feature are pay-walled vs. free? -As a Workspace admin, you have the ability to view, edit, and submit your employees' Open reports. -​ -We recommend beginning this process from the web version of Expensify because it offers more functionality compared to the mobile app. Here's how to get started: -Click on the "Reports" tab. -Select the "All Submitters" and "Open" filters. -This will display all employee reports on your Workspaces that have not yet been submitted. -​ -## Viewing Employee Reports -Viewing employee reports can vary depending on whether you're using the web or mobile versions of Expensify. We generally recommend using the web version for this purpose, as it offers the following advantages: -You will only receive reports directly submitted to you when using the mobile app. -The option to filter reports via the Reports page is exclusively available in the web version, making it more convenient when reviewing multiple reports during a session. -​ -## Viewing employee reports on the mobile app -When using the mobile app to view reports, please note the following: -Tapping on the Reports list will only display your own reports; you won't see reports from other Workspace members. -To view another Workspace member's report in the Expensify app, it must be submitted directly to you, and you must access it through a link from an email or via Home. -When you access a report in this manner, you will have the option to approve/reject it or go through the review process if there are expenses that require your attention. -Once you've approved or rejected the report, it won't be accessible in the app anymore. To view it again, please visit the website and follow the steps mentioned above. -​ -## Editing employee reports -If a report has been submitted directly to you, follow these steps to edit the expense details. Please note that you cannot change the expense amount; to make changes affecting the report total, you must reject it and return it to the employee. -Here's what to do: -Click on any expense within the report to edit the expense details. -Remember that you cannot modify the expense amount directly. To make changes affecting the report total, reject the report, and it will be sent back to the employee for revisions. -If you're a Workspace admin and need to edit a report that wasn't submitted directly to you, use the "Take Control" button at the top of the report. Keep in mind that taking control of a report will disrupt the approval workflow. -​ -Additionally, here are some other editing options for Admins: -Undelete deleted company card expenses via the Reconciliation Dashboard (requires Domain Admin privileges). -Add unreported company card expenses to an existing Open (unsubmitted) report or create a new report via the Reconciliation Dashboard (requires Domain Admin privileges). -Add or modify expense coding, including Category, Tag(s), and Attendees. -Attach a receipt to an expense that doesn't have one. -Move card expenses between two Open (unsubmitted) reports. -Merge duplicate expenses (only applicable if they are not card transactions). -Change the Workspace associated with a report. -​ -​ -## Submitting Employee Reports -As a Workspace Admin, you have the option to submit any of your employee's Open reports. If an employee is unable to do it themselves, a Workspace admin can submit an expense report on their behalf to initiate the approval process. Follow these steps: -Click the "Submit" button located at the top of the report. -​ -## Report History and Comments -Please keep in mind that any changes made by the admin are tracked under "Report History and Comments." If you change the reimbursable status of an expense (e.g., from Reimbursable to Non-Reimbursable), an email notification will be sent to the employee to notify them of this change. -​ -## Rejecting or Unapproving a Report -If you need to reject a report that has been submitted to you or Unapprove a report that has already been approved. -To reject the report, click Reject rather than beginning the Review process. If there are multiple approvers involved, you can choose how far back to reject the report. -​ -Rejecting a report will return the report back to the submitter in an Open status or, in the case of a multi-level approval workflow, back to the previous approver in a Processing status (awaiting their approval). You may need to do this if the submitter is not ready to submit the report, or perhaps the report as a whole needs to be rejected based on the configuration of your organization's expense Workspace. -## Unapprove a Report -You can click the red Unapprove button at the top of the report to undo approving a report. Keep in mind that you'll only see the Unapprove button if you're a report approver on an admin that has taken control of the report. -​ -​ -## Marking a Report as Reimbursed Outside of Expensify -If you are reimbursing reports via paper check, through payroll or any other method that takes place outside of Expensify, you'll want to keep track of which reports have been taken care of by marking reports as reimbursed. -Log into your Expensify account using your preferred web browser, (ie: Chrome or Safari) -Head to your Reports page and locate the report -Click the report name to open it -Click on Reimburse -Choose "I'll do it manually - just mark it as reimbursed". This will change the report status to Reimbursed -The submitter can then go into the report and confirm that they received the reimbursement by clicking the button at the top of the report. -This will change the report status to Reimbursed: CONFIRMED -​ -# How to Use Guided Review to Approve Reports -Guided Review helps alert you to what might be out-of-Workspace for an Expense Report. You'll be guided through all report violations and warnings and given the option to Reject or Edit items that need review prior to approving a report. -Guided Review helps approvers quickly identify reports that need more attention so they can pass over reports that can be quickly approved. Both Submitters and Approvers have actionable notifications for the following: violations, warnings, and notices. These notifications are important since they will be included in “review mode” for the approver to make clear approve or reject decisions. -Via the Website: -​ -1. Simply click Review at the top left of the report and the system will begin to walk you through the entire report. -2. Choose to Reject, View, or skip over an item needing review. If you wish to stop the process at any time, click the X in the progress bar in the top right corner. -Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected. -View: This will allow you to open the expense so you can view and fix any incorrect data. -Next: This will allow you to skip over the current item and move forward to review the rest of the report. -Finish: Click this to finish reviewing the report! -3. Click the Finish button if you are done reviewing, or reject/edit the last item to finish the review process. -4. Approve the report! Approve and Forward the report if there is another person who needs to review the report in your approval workflow, or you can Final Approve if you are the final approver. Note: When in Guided Review, you'll automatically Approve the report adhering to your Company's Approval Workflow once you Approve the final expense on the report. You'll then be immediately taken to the next report requiring your attention - making Approving multiple expenses a quick and painless process! -Via the Mobile App: -​ -​ -1. From Home, under Reports that need your attention, click Begin Review, and the system will bring you to the first expense on the oldest report in Home. -​ -2. Edit the expense: Make any necessary edits to the expense by tapping the corresponding field. Be sure to address any Violations and Notes on the expense! Notes are indicated at the top of the expense with a yellow exclamation point, while violations appear on the expense with a red exclamation point: -3. Choose Reject or Accept at the top of the expense. -Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected, and a comment will be added to the report it was rejected from. If this is the only expense on the report, the entire report will be rejected (and the expense will remain on the report). -If Scheduled Submit is being used, rejected expenses will auto-report to the next Open report on the Workspace (as if it were a new expense). If an open report doesn't exist, Concierge will create a new one. -​ -​ -If Scheduled Submit is not being used, any rejected expenses will be Unreported in the submitter's account and need to be manually applied to a new report. -Accept: This will move to the next expense on the report, leaving behind any outstanding violations or notes. If this is the last expense on the report, you'll be all done! -Once you've made it through all of the expenses on the report, you'll be all set! -​ -​ -​ -# Deep Dive -## Concierge Report Management -​ -Concierge report approval removes the need for you to manually click "Approve" on endless reports! Instead, you can set up your group Workspace to capture all the requirements you have for your team's expenses. As long as all the rules have been followed and Concierge's additional audit is passed (more below), we will automatically approve such reports on behalf of the approver after submission. -Before you start: -Ensure are a Workspace admin on a group Workspace -Set your workflow to Submit-and-Approve or Advanced Approval workflow -​ -​ -## Then follow these steps: -Set up your group Workspace so that all of your expense requirements are defined. Setting automatic categories for employees and category rules (e.g., maximum amounts, receipt requirements, etc.) are great examples! -Navigate to Settings > Workspaces > Group > [Workspace Name] > Members. -Scroll down to Approval Mode and select either Submit-and-Approve or Advanced Approval. -Under Expense Approvals, select a Manual Approval Threshold greater than $0. -​ -With this setup, manual approval will only be required: -For reports that fail audit (i.e. there is at least one Workspace violation on the report) -For reports that contain at least one expense over the Manual Approval Threshold -For any percentage of reports that you'd like to spot-check (this is set at 5% or 1 in 20 by default). -If the report meets all of the requirements you specify in your Workspace settings and all expenses are under the Manual Approval Threshold, then Concierge will automatically move your report through each step of your designated approval workflow (unless it's routed for a spot-check). -​ -​ -## Concierge Receipt Audit -Concierge Receipt Audit is a real-time audit and compliance of receipts submitted by employees and Workspace users. Concierge checks every receipt for accuracy and compliance, flagging any expenses that seem fishy before expense reports are even submitted for approval. All risky expenses are highlighted for manual review, leaving you with more control over and visibility into employee expenses. -1. Concierge will SmartScan every receipt to verify the data input by the user matches the currency, date, and amount on the physical receipt. -2. After the report is submitted for approval, Concierge highlights any differences between the SmartScanned values and the employee's chosen input. -3. Each receipt that has been verified will show the "Verified" logo. - From d78a69a5b153361e63630230920fa59a30b36db6 Mon Sep 17 00:00:00 2001 From: Zany Renney <56830058+zanyrenney@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:12:39 +0000 Subject: [PATCH 30/40] Update Approving-Reports.md --- .../Approving-Reports.md | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md index 8969f7a5adbf..4f030ef1f92b 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md @@ -79,8 +79,8 @@ This will change the report status to Reimbursed: CONFIRMED # How to Use Guided Review to Approve Reports Guided Review helps alert you to what might be out-of-Workspace for an Expense Report. You'll be guided through all report violations and warnings and given the option to Reject or Edit items that need review prior to approving a report. Guided Review helps approvers quickly identify reports that need more attention so they can pass over reports that can be quickly approved. Both Submitters and Approvers have actionable notifications for the following: violations, warnings, and notices. These notifications are important since they will be included in “review mode” for the approver to make clear approve or reject decisions. -Via the Website: ​ +Via the Website:​ 1. Simply click Review at the top left of the report and the system will begin to walk you through the entire report. 2. Choose to Reject, View, or skip over an item needing review. If you wish to stop the process at any time, click the X in the progress bar in the top right corner. Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected. @@ -89,27 +89,28 @@ Next: This will allow you to skip over the current item and move forward to revi Finish: Click this to finish reviewing the report! 3. Click the Finish button if you are done reviewing, or reject/edit the last item to finish the review process. 4. Approve the report! Approve and Forward the report if there is another person who needs to review the report in your approval workflow, or you can Final Approve if you are the final approver. Note: When in Guided Review, you'll automatically Approve the report adhering to your Company's Approval Workflow once you Approve the final expense on the report. You'll then be immediately taken to the next report requiring your attention - making Approving multiple expenses a quick and painless process! -Via the Mobile App: ​ ​ +Via the Mobile App:​ 1. From Home, under Reports that need your attention, click Begin Review, and the system will bring you to the first expense on the oldest report in Home. -​ 2. Edit the expense: Make any necessary edits to the expense by tapping the corresponding field. Be sure to address any Violations and Notes on the expense! Notes are indicated at the top of the expense with a yellow exclamation point, while violations appear on the expense with a red exclamation point: 3. Choose Reject or Accept at the top of the expense. +​ Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected, and a comment will be added to the report it was rejected from. If this is the only expense on the report, the entire report will be rejected (and the expense will remain on the report). -If Scheduled Submit is being used, rejected expenses will auto-report to the next Open report on the Workspace (as if it were a new expense). If an open report doesn't exist, Concierge will create a new one. ​ +If Scheduled Submit is being used, rejected expenses will auto-report to the next Open report on the Workspace (as if it were a new expense). If an open report doesn't exist, Concierge will create a new one. +​​ ​ If Scheduled Submit is not being used, any rejected expenses will be Unreported in the submitter's account and need to be manually applied to a new report. +​ Accept: This will move to the next expense on the report, leaving behind any outstanding violations or notes. If this is the last expense on the report, you'll be all done! Once you've made it through all of the expenses on the report, you'll be all set! ​ -​ -​ # Deep Dive ## Concierge Report Management ​ Concierge report approval removes the need for you to manually click "Approve" on endless reports! Instead, you can set up your group Workspace to capture all the requirements you have for your team's expenses. As long as all the rules have been followed and Concierge's additional audit is passed (more below), we will automatically approve such reports on behalf of the approver after submission. +​ Before you start: Ensure are a Workspace admin on a group Workspace Set your workflow to Submit-and-Approve or Advanced Approval workflow @@ -117,19 +118,24 @@ Set your workflow to Submit-and-Approve or Advanced Approval workflow ​ ## Then follow these steps: Set up your group Workspace so that all of your expense requirements are defined. Setting automatic categories for employees and category rules (e.g., maximum amounts, receipt requirements, etc.) are great examples! +​ Navigate to Settings > Workspaces > Group > [Workspace Name] > Members. +​ Scroll down to Approval Mode and select either Submit-and-Approve or Advanced Approval. +​ Under Expense Approvals, select a Manual Approval Threshold greater than $0. ​ +​ With this setup, manual approval will only be required: -For reports that fail audit (i.e. there is at least one Workspace violation on the report) -For reports that contain at least one expense over the Manual Approval Threshold -For any percentage of reports that you'd like to spot-check (this is set at 5% or 1 in 20 by default). -If the report meets all of the requirements you specify in your Workspace settings and all expenses are under the Manual Approval Threshold, then Concierge will automatically move your report through each step of your designated approval workflow (unless it's routed for a spot-check). +- For reports that fail audit (i.e. there is at least one Workspace violation on the report) +- For reports that contain at least one expense over the Manual Approval Threshold +- For any percentage of reports that you'd like to spot-check (this is set at 5% or 1 in 20 by default). +- If the report meets all of the requirements you specify in your Workspace settings and all expenses are under the Manual Approval Threshold, then Concierge will automatically move your report through each step of your designated approval workflow (unless it's routed for a spot-check).​ ​ ​ ## Concierge Receipt Audit Concierge Receipt Audit is a real-time audit and compliance of receipts submitted by employees and Workspace users. Concierge checks every receipt for accuracy and compliance, flagging any expenses that seem fishy before expense reports are even submitted for approval. All risky expenses are highlighted for manual review, leaving you with more control over and visibility into employee expenses. +​ 1. Concierge will SmartScan every receipt to verify the data input by the user matches the currency, date, and amount on the physical receipt. 2. After the report is submitted for approval, Concierge highlights any differences between the SmartScanned values and the employee's chosen input. 3. Each receipt that has been verified will show the "Verified" logo. From 434edcd3758e79310e7adadbdd69f5b2c19e4ae6 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 12:17:43 -0400 Subject: [PATCH 31/40] run prettier --- src/components/AddPaymentMethodMenu.js | 14 +++++++------- .../FloatingActionButtonAndPopover.js | 14 +++++++------- src/pages/settings/InitialSettingsPage.js | 3 +-- src/pages/settings/Profile/ProfilePage.js | 14 +++++++------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 223d880777bd..ce26985932d6 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -93,13 +93,13 @@ function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignme }, ] : []), - ...([ - { - text: translate('common.debitCard'), - icon: Expensicons.CreditCard, - onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD), - }, - ]), + ...[ + { + text: translate('common.debitCard'), + icon: Expensicons.CreditCard, + onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD), + }, + ], ]} withoutOverlay /> diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index fd9f0854b5a1..a1ba7042f158 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -202,13 +202,13 @@ function FloatingActionButtonAndPopover(props) { text: props.translate('iou.sendMoney'), onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), }, - ...([ - { - icon: Expensicons.Task, - text: props.translate('newTaskPage.assignTask'), - onSelected: () => interceptAnonymousUser(() => Task.clearOutTaskInfoAndNavigate()), - }, - ]), + ...[ + { + icon: Expensicons.Task, + text: props.translate('newTaskPage.assignTask'), + onSelected: () => interceptAnonymousUser(() => Task.clearOutTaskInfoAndNavigate()), + }, + ], { icon: Expensicons.Heart, text: props.translate('sidebarScreen.saveTheWorld'), diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 89d97ff360a7..1bd57bcab32b 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -283,8 +283,7 @@ function InitialSettingsPage(props) { * @param {Boolean} isPaymentItem whether the item being rendered is the payments menu item * @returns {Number} the user wallet balance */ - const getWalletBalance = (isPaymentItem) => - isPaymentItem && CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance); + const getWalletBalance = (isPaymentItem) => isPaymentItem && CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance); return ( <> diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 74515c52dcf3..fc6c38f96335 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -82,13 +82,13 @@ function ProfilePage(props) { pageRoute: ROUTES.SETTINGS_CONTACT_METHODS.route, brickRoadIndicator: contactMethodBrickRoadIndicator, }, - ...([ - { - description: props.translate('statusPage.status'), - title: emojiCode ? `${emojiCode} ${lodashGet(props, 'currentUserPersonalDetails.status.text', '')}` : '', - pageRoute: ROUTES.SETTINGS_STATUS, - }, - ]), + ...[ + { + description: props.translate('statusPage.status'), + title: emojiCode ? `${emojiCode} ${lodashGet(props, 'currentUserPersonalDetails.status.text', '')}` : '', + pageRoute: ROUTES.SETTINGS_STATUS, + }, + ], { description: props.translate('pronounsPage.pronouns'), title: getPronouns(), From 8b5306468bc9660124bad97df6142f19354649f0 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 23 Nov 2023 16:44:51 +0000 Subject: [PATCH 32/40] Update version to 1.4.3-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 025fa63b2a05..5bff594b1db6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001040203 - versionName "1.4.2-3" + versionCode 1001040300 + versionName "1.4.3-0" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index fb8390ed33da..2a903e0c8103 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.2 + 1.4.3 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.2.3 + 1.4.3.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 555d20bf7323..601ea9ccecad 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.2 + 1.4.3 CFBundleSignature ???? CFBundleVersion - 1.4.2.3 + 1.4.3.0 diff --git a/package-lock.json b/package-lock.json index 18f74f2422cb..b2b942ab3319 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.2-3", + "version": "1.4.3-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.2-3", + "version": "1.4.3-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 88c2ab282bd5..44ea03b30ef8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.2-3", + "version": "1.4.3-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 0d43818afcaf24b7284b8723127cabeea286bae6 Mon Sep 17 00:00:00 2001 From: Zany Renney <56830058+zanyrenney@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:52:25 +0000 Subject: [PATCH 33/40] Update Approving-Reports.md --- .../Approving-Reports.md | 174 +++++++++++------- 1 file changed, 111 insertions(+), 63 deletions(-) diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md index 4f030ef1f92b..32ce41d3cbf3 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md @@ -2,20 +2,27 @@ title: How To Manage Employees and Reports > Approving Reports description: This page will help you understand the lifecycle of a report and how to approve reports that are submitted to you. --- -​ # About -This article provides a comprehensive guide on report management within our platform. From viewing, editing, and submitting your employees' Open reports to handling rejections and unapproving, as well as harnessing the power of our "Guided Review" feature. Additionally, we'll delve into best practices for Concierge, offering insights on how to streamline and automate your report approval processes for maximum efficiency. +This article provides a comprehensive guide on report management within our platform. From viewing, editing, and submitting your employees' Open reports to handling rejections and unapproving, as well as harnessing the power of our "Guided Review" feature. Additionally, we'll delve into best practices for Concierge, offering insights on how to streamline and automate your report approval processes for maximum efficiency. Let's dive in! -​ -​ + + # How-to manage reports This section covers the most essential information a user needs to operate a feature i.e. what to click on. We’ll go over any action the user might take when configuring or using the feature, starting from configuration and moving to usage. -​ + + What options does a user have when configuring this feature? + + What options does a user have then interacting with this feature? + + What elements of this feature are pay-walled vs. free? -As a Workspace admin, you have the ability to view, edit, and submit your employees' Open reports. -​ + + +As a Workspace admin, you have the ability to view, edit, and submit your employees' Open reports. + + We recommend beginning this process from the web version of Expensify because it offers more functionality compared to the mobile app. Here's how to get started: Click on the "Reports" tab. Select the "All Submitters" and "Open" filters. @@ -23,119 +30,160 @@ This will display all employee reports on your Workspaces that have not yet been ​ ## Viewing Employee Reports Viewing employee reports can vary depending on whether you're using the web or mobile versions of Expensify. We generally recommend using the web version for this purpose, as it offers the following advantages: + + You will only receive reports directly submitted to you when using the mobile app. + + The option to filter reports via the Reports page is exclusively available in the web version, making it more convenient when reviewing multiple reports during a session. ​ ## Viewing employee reports on the mobile app When using the mobile app to view reports, please note the following: + + Tapping on the Reports list will only display your own reports; you won't see reports from other Workspace members. + + To view another Workspace member's report in the Expensify app, it must be submitted directly to you, and you must access it through a link from an email or via Home. + + When you access a report in this manner, you will have the option to approve/reject it or go through the review process if there are expenses that require your attention. + + Once you've approved or rejected the report, it won't be accessible in the app anymore. To view it again, please visit the website and follow the steps mentioned above. ​ ## Editing employee reports -If a report has been submitted directly to you, follow these steps to edit the expense details. Please note that you cannot change the expense amount; to make changes affecting the report total, you must reject it and return it to the employee. +If a report has been submitted directly to you, follow these steps to edit the expense details. Please note that you cannot change the expense amount; to make changes affecting the report total, you must reject it and return it to the employee. + + Here's what to do: -Click on any expense within the report to edit the expense details. -Remember that you cannot modify the expense amount directly. To make changes affecting the report total, reject the report, and it will be sent back to the employee for revisions. -If you're a Workspace admin and need to edit a report that wasn't submitted directly to you, use the "Take Control" button at the top of the report. Keep in mind that taking control of a report will disrupt the approval workflow. +- Click on any expense within the report to edit the expense details. +- Remember that you cannot modify the expense amount directly. To make changes affecting the report total, reject the report, and it will be sent back to the employee for revisions. +- If you're a Workspace admin and need to edit a report that wasn't submitted directly to you, use the "Take Control" button at the top of the report. Keep in mind that taking control of a report will disrupt the approval workflow. ​ Additionally, here are some other editing options for Admins: -Undelete deleted company card expenses via the Reconciliation Dashboard (requires Domain Admin privileges). -Add unreported company card expenses to an existing Open (unsubmitted) report or create a new report via the Reconciliation Dashboard (requires Domain Admin privileges). -Add or modify expense coding, including Category, Tag(s), and Attendees. -Attach a receipt to an expense that doesn't have one. -Move card expenses between two Open (unsubmitted) reports. -Merge duplicate expenses (only applicable if they are not card transactions). -Change the Workspace associated with a report. -​ -​ +- Undelete deleted company card expenses via the Reconciliation Dashboard (requires Domain Admin privileges). +- Add unreported company card expenses to an existing Open (unsubmitted) report or create a new report via the Reconciliation Dashboard (requires Domain Admin privileges). +- Add or modify expense coding, including Category, Tag(s), and Attendees. +- Attach a receipt to an expense that doesn't have one. +- Move card expenses between two Open (unsubmitted) reports. +- Merge duplicate expenses (only applicable if they are not card transactions). +- Change the Workspace associated with a report.​ + + ## Submitting Employee Reports As a Workspace Admin, you have the option to submit any of your employee's Open reports. If an employee is unable to do it themselves, a Workspace admin can submit an expense report on their behalf to initiate the approval process. Follow these steps: Click the "Submit" button located at the top of the report. -​ + + ## Report History and Comments Please keep in mind that any changes made by the admin are tracked under "Report History and Comments." If you change the reimbursable status of an expense (e.g., from Reimbursable to Non-Reimbursable), an email notification will be sent to the employee to notify them of this change. -​ + + ## Rejecting or Unapproving a Report If you need to reject a report that has been submitted to you or Unapprove a report that has already been approved. + + To reject the report, click Reject rather than beginning the Review process. If there are multiple approvers involved, you can choose how far back to reject the report. -​ + + Rejecting a report will return the report back to the submitter in an Open status or, in the case of a multi-level approval workflow, back to the previous approver in a Processing status (awaiting their approval). You may need to do this if the submitter is not ready to submit the report, or perhaps the report as a whole needs to be rejected based on the configuration of your organization's expense Workspace. + + ## Unapprove a Report -You can click the red Unapprove button at the top of the report to undo approving a report. Keep in mind that you'll only see the Unapprove button if you're a report approver on an admin that has taken control of the report. -​ -​ +You can click the red Unapprove button at the top of the report to undo approving a report. Keep in mind that you'll only see the Unapprove button if you're a report approver on an admin that has taken control of the report.​ + + ## Marking a Report as Reimbursed Outside of Expensify If you are reimbursing reports via paper check, through payroll or any other method that takes place outside of Expensify, you'll want to keep track of which reports have been taken care of by marking reports as reimbursed. -Log into your Expensify account using your preferred web browser, (ie: Chrome or Safari) -Head to your Reports page and locate the report -Click the report name to open it -Click on Reimburse -Choose "I'll do it manually - just mark it as reimbursed". This will change the report status to Reimbursed -The submitter can then go into the report and confirm that they received the reimbursement by clicking the button at the top of the report. -This will change the report status to Reimbursed: CONFIRMED -​ + + +1. Log into your Expensify account using your preferred web browser, (ie: Chrome or Safari) +2. Head to your Reports page and locate the report +3. Click the report name to open it +4. Click on Reimburse +5. Choose "I'll do it manually - just mark it as reimbursed". This will change the report status to Reimbursed +6. The submitter can then go into the report and confirm that they received the reimbursement by clicking the button at the top of the report. +7. This will change the report status to Reimbursed: CONFIRMED + + # How to Use Guided Review to Approve Reports Guided Review helps alert you to what might be out-of-Workspace for an Expense Report. You'll be guided through all report violations and warnings and given the option to Reject or Edit items that need review prior to approving a report. + + Guided Review helps approvers quickly identify reports that need more attention so they can pass over reports that can be quickly approved. Both Submitters and Approvers have actionable notifications for the following: violations, warnings, and notices. These notifications are important since they will be included in “review mode” for the approver to make clear approve or reject decisions. -​ + + Via the Website:​ 1. Simply click Review at the top left of the report and the system will begin to walk you through the entire report. 2. Choose to Reject, View, or skip over an item needing review. If you wish to stop the process at any time, click the X in the progress bar in the top right corner. Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected. View: This will allow you to open the expense so you can view and fix any incorrect data. Next: This will allow you to skip over the current item and move forward to review the rest of the report. -Finish: Click this to finish reviewing the report! +Finish: Click this to finish reviewing the report! 3. Click the Finish button if you are done reviewing, or reject/edit the last item to finish the review process. 4. Approve the report! Approve and Forward the report if there is another person who needs to review the report in your approval workflow, or you can Final Approve if you are the final approver. Note: When in Guided Review, you'll automatically Approve the report adhering to your Company's Approval Workflow once you Approve the final expense on the report. You'll then be immediately taken to the next report requiring your attention - making Approving multiple expenses a quick and painless process! -​ -​ + + + + Via the Mobile App:​ 1. From Home, under Reports that need your attention, click Begin Review, and the system will bring you to the first expense on the oldest report in Home. 2. Edit the expense: Make any necessary edits to the expense by tapping the corresponding field. Be sure to address any Violations and Notes on the expense! Notes are indicated at the top of the expense with a yellow exclamation point, while violations appear on the expense with a red exclamation point: 3. Choose Reject or Accept at the top of the expense. -​ + + Reject: This will remove the expense from the report and send it back to the submitter. An email will be sent to the submitter explaining this expense has been rejected, and a comment will be added to the report it was rejected from. If this is the only expense on the report, the entire report will be rejected (and the expense will remain on the report). -​ -If Scheduled Submit is being used, rejected expenses will auto-report to the next Open report on the Workspace (as if it were a new expense). If an open report doesn't exist, Concierge will create a new one. -​​ -​ -If Scheduled Submit is not being used, any rejected expenses will be Unreported in the submitter's account and need to be manually applied to a new report. -​ -Accept: This will move to the next expense on the report, leaving behind any outstanding violations or notes. If this is the last expense on the report, you'll be all done! -Once you've made it through all of the expenses on the report, you'll be all set! -​ + + +If Scheduled Submit is being used, rejected expenses will auto-report to the next Open report on the Workspace (as if it were a new expense). If an open report doesn't exist, Concierge will create a new one. +​ + + +If Scheduled Submit is not being used, any rejected expenses will be Unreported in the submitter's account and need to be manually applied to a new report. +​ +Accept: This will move to the next expense on the report, leaving behind any outstanding violations or notes. If this is the last expense on the report, you'll be all done! +Once you've made it through all of the expenses on the report, you'll be all set! + + # Deep Dive ## Concierge Report Management -​ + + Concierge report approval removes the need for you to manually click "Approve" on endless reports! Instead, you can set up your group Workspace to capture all the requirements you have for your team's expenses. As long as all the rules have been followed and Concierge's additional audit is passed (more below), we will automatically approve such reports on behalf of the approver after submission. ​ Before you start: Ensure are a Workspace admin on a group Workspace -Set your workflow to Submit-and-Approve or Advanced Approval workflow -​ -​ +Set your workflow to Submit-and-Approve or Advanced Approval workflow​ + + ## Then follow these steps: Set up your group Workspace so that all of your expense requirements are defined. Setting automatic categories for employees and category rules (e.g., maximum amounts, receipt requirements, etc.) are great examples! -​ -Navigate to Settings > Workspaces > Group > [Workspace Name] > Members. -​ + + +Navigate to Settings > Workspaces > Group > [Workspace Name] > Members.​ + + Scroll down to Approval Mode and select either Submit-and-Approve or Advanced Approval. -​ -Under Expense Approvals, select a Manual Approval Threshold greater than $0. -​ -​ + + +Under Expense Approvals, select a Manual Approval Threshold greater than $0.​ + + With this setup, manual approval will only be required: - For reports that fail audit (i.e. there is at least one Workspace violation on the report) - For reports that contain at least one expense over the Manual Approval Threshold - For any percentage of reports that you'd like to spot-check (this is set at 5% or 1 in 20 by default). - If the report meets all of the requirements you specify in your Workspace settings and all expenses are under the Manual Approval Threshold, then Concierge will automatically move your report through each step of your designated approval workflow (unless it's routed for a spot-check).​ -​ -​ + + + + ## Concierge Receipt Audit Concierge Receipt Audit is a real-time audit and compliance of receipts submitted by employees and Workspace users. Concierge checks every receipt for accuracy and compliance, flagging any expenses that seem fishy before expense reports are even submitted for approval. All risky expenses are highlighted for manual review, leaving you with more control over and visibility into employee expenses. -​ + + 1. Concierge will SmartScan every receipt to verify the data input by the user matches the currency, date, and amount on the physical receipt. 2. After the report is submitted for approval, Concierge highlights any differences between the SmartScanned values and the employee's chosen input. -3. Each receipt that has been verified will show the "Verified" logo. +3. Each receipt that has been verified will show the "Verified" logo. + From 295ca0e75ddcd515e8c25b9b496dfbdf9a97bebf Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 16:48:59 -0400 Subject: [PATCH 34/40] remove more unused betas --- src/CONST.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8b91738e65e5..f1364ebbb5bf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -250,10 +250,7 @@ const CONST = { CHRONOS_IN_CASH: 'chronosInCash', DEFAULT_ROOMS: 'defaultRooms', BETA_COMMENT_LINKING: 'commentLinking', - INTERNATIONALIZATION: 'internationalization', POLICY_ROOMS: 'policyRooms', - THREADS: 'threads', - NEW_DOT_SAML: 'newDotSAML', VIOLATIONS: 'violations', }, BUTTON_STATES: { From 473a57aabe122b2f74525df1041d5c1d4f4e9632 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Thu, 23 Nov 2023 16:49:10 -0400 Subject: [PATCH 35/40] undo bad changes --- .../settings/Wallet/WalletPage/WalletPage.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js index cbb8471da8da..3ac8586daf97 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js @@ -105,7 +105,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod }, [shouldShowEmptyState, windowWidth]); const getSelectedPaymentMethodID = useCallback(() => { - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { return paymentMethod.selectedPaymentMethod.bankAccountID; } if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { @@ -151,12 +151,12 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod // The delete/default menu if (accountType) { let formattedSelectedPaymentMethod; - if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { formattedSelectedPaymentMethod = { title: account.addressName, icon: account.icon, description: PaymentUtils.getPaymentMethodDescription(accountType, account), - type: CONST.PAYMENT_METHODS.BANK_ACCOUNT, + type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, }; } else if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { formattedSelectedPaymentMethod = { @@ -201,7 +201,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod return; } - if (paymentType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { BankAccounts.openPersonalBankAccountSetupView(); return; } @@ -227,7 +227,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod const previousPaymentMethod = _.find(paymentMethods, (method) => method.isDefault); const currentPaymentMethod = _.find(paymentMethods, (method) => method.methodID === paymentMethod.methodID); - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID, null, previousPaymentMethod, currentPaymentMethod); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { PaymentMethods.makeDefaultPaymentMethod(null, paymentMethod.selectedPaymentMethod.fundID, previousPaymentMethod, currentPaymentMethod); @@ -242,7 +242,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod ]); const deletePaymentMethod = useCallback(() => { - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { BankAccounts.deletePaymentBankAccount(paymentMethod.selectedPaymentMethod.bankAccountID); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { PaymentMethods.deletePaymentCard(paymentMethod.selectedPaymentMethod.fundID); @@ -293,7 +293,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod // We should reset selected payment method state values and close corresponding modals if the selected payment method is deleted let shouldResetPaymentMethodData = false; - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && _.isEmpty(bankAccountList[paymentMethod.methodID])) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && _.isEmpty(bankAccountList[paymentMethod.methodID])) { shouldResetPaymentMethodData = true; } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && _.isEmpty(fundList[paymentMethod.methodID])) { shouldResetPaymentMethodData = true; @@ -308,7 +308,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod const shouldShowMakeDefaultButton = !paymentMethod.isSelectedPaymentMethodDefault && - !(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT && paymentMethod.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS); + !(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && paymentMethod.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS); // Determines whether or not the modal popup is mounted from the bottom of the screen instead of the side mount on Web or Desktop screens const isPopoverBottomMount = anchorPosition.anchorPositionTop === 0 || isSmallScreenWidth; @@ -362,7 +362,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod navigateToWalletOrTransferBalancePage(source)} onSelectPaymentMethod={(selectedPaymentMethod) => { - if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { return; } // To allow upgrading to a gold wallet, continue with the KYC flow after adding a bank account From 7a99f201ff3ba6d6a94d6294a06ac46e2fba436c Mon Sep 17 00:00:00 2001 From: mkhutornyi Date: Thu, 23 Nov 2023 22:33:52 +0100 Subject: [PATCH 36/40] refactor ref forwarding in CheckboxWithLabel --- src/components/CheckboxWithLabel.js | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 146e37ceb730..b78a7a136158 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -7,7 +7,6 @@ import variables from '@styles/variables'; import Checkbox from './Checkbox'; import FormHelpMessage from './FormHelpMessage'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; -import refPropTypes from './refPropTypes'; import Text from './Text'; /** @@ -54,9 +53,6 @@ const propTypes = { /** The default value for the checkbox */ defaultValue: PropTypes.bool, - /** React ref being forwarded to the Checkbox input */ - forwardedRef: refPropTypes, - /** The ID used to uniquely identify the input in a Form */ /* eslint-disable-next-line react/no-unused-prop-types */ inputID: PropTypes.string, @@ -79,11 +75,10 @@ const defaultProps = { isChecked: false, value: false, defaultValue: false, - forwardedRef: () => {}, accessibilityLabel: undefined, }; -function CheckboxWithLabel(props) { +const CheckboxWithLabel = React.forwardRef((props, ref) => { const styles = useThemeStyles(); // We need to pick the first value that is strictly a boolean // https://github.com/Expensify/App/issues/16885#issuecomment-1520846065 @@ -106,7 +101,7 @@ function CheckboxWithLabel(props) { label={props.label} style={[styles.checkboxWithLabelCheckboxStyle]} hasError={Boolean(props.errorText)} - forwardedRef={props.forwardedRef} + ref={ref} accessibilityLabel={props.accessibilityLabel || props.label} /> ); -} +}); CheckboxWithLabel.propTypes = propTypes; CheckboxWithLabel.defaultProps = defaultProps; CheckboxWithLabel.displayName = 'CheckboxWithLabel'; -const CheckboxWithLabelWithRef = React.forwardRef((props, ref) => ( - -)); - -CheckboxWithLabelWithRef.displayName = 'CheckboxWithLabelWithRef'; - -export default CheckboxWithLabelWithRef; +export default CheckboxWithLabel; From 98d2c49f63a82aca552c674bf170fef7aa7e3e33 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 23 Nov 2023 15:05:20 -0700 Subject: [PATCH 37/40] navigate user to BBA flow --- src/pages/settings/Wallet/WalletPage/WalletPage.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js index 4ef540f91eef..3e70c89f7f20 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js @@ -207,6 +207,11 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen return; } + if (paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) { + Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); + return; + } + throw new Error('Invalid payment method type selected'); }; From 1163dc3837dfb04a985f3106026ede7b77108cca Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 23 Nov 2023 15:09:18 -0700 Subject: [PATCH 38/40] consolidate methods --- src/pages/settings/Wallet/WalletPage/WalletPage.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js index 3e70c89f7f20..734966256988 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js @@ -202,16 +202,11 @@ function WalletPage({bankAccountList, betas, cardList, fundList, isLoadingPaymen return; } - if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) { BankAccounts.openPersonalBankAccountSetupView(); return; } - if (paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) { - Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); - return; - } - throw new Error('Invalid payment method type selected'); }; From 36105a3d62c54b5430f674dc1d7ec3b7752ab8ae Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 23 Nov 2023 22:24:02 +0000 Subject: [PATCH 39/40] Update version to 1.4.3-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 5bff594b1db6..8ec8bbe26b93 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001040300 - versionName "1.4.3-0" + versionCode 1001040301 + versionName "1.4.3-1" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 2a903e0c8103..c4783372c8f4 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.3.0 + 1.4.3.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 601ea9ccecad..210b7e9375e2 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.3.0 + 1.4.3.1 diff --git a/package-lock.json b/package-lock.json index b2b942ab3319..8b20f4e42d08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.3-0", + "version": "1.4.3-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.3-0", + "version": "1.4.3-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 44ea03b30ef8..9c350f59010b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.3-0", + "version": "1.4.3-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From f52c59d55be2d6d2775623d92c3043bd6d42cc0f Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 23 Nov 2023 23:13:13 +0000 Subject: [PATCH 40/40] Update version to 1.4.3-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8ec8bbe26b93..242584ef04ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001040301 - versionName "1.4.3-1" + versionCode 1001040302 + versionName "1.4.3-2" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index c4783372c8f4..26e97aceb8aa 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.3.1 + 1.4.3.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 210b7e9375e2..854f911a582b 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.3.1 + 1.4.3.2 diff --git a/package-lock.json b/package-lock.json index 8b20f4e42d08..ac172bdef99b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.3-1", + "version": "1.4.3-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.3-1", + "version": "1.4.3-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 9c350f59010b..042ab7ca6e83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.3-1", + "version": "1.4.3-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",