From 1db16d9aa254f4bf0eaa2a067446196a1f61a28e Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 26 Feb 2024 17:14:29 +0100 Subject: [PATCH 001/124] feat: create new rate field --- .../MoneyRequestConfirmationList.js | 12 ++++++ ...oraryForRefactorRequestConfirmationList.js | 20 ++++++++++ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/DistanceRequestUtils.ts | 39 ++++++++++++++++--- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index df2781d3ea89..7bb4bb868c99 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -730,6 +730,18 @@ function MoneyRequestConfirmationList(props) { interactive={!props.isReadOnly} /> )} + {props.isDistanceRequest && ( + Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))} + disabled={didConfirm || !isTypeRequest} + interactive={!props.isReadOnly} + /> + )} {shouldShowMerchant && ( {}} + // onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm || !isTypeRequest} + interactive={!isReadOnly} + /> + ), + // TODO: hide when betas ready + shouldShow: isDistanceRequest, + isSupplementary: true, + }, { item: ( Date: Mon, 26 Feb 2024 19:20:47 +0100 Subject: [PATCH 002/124] feat: create rate selection page --- src/ROUTES.ts | 5 ++ src/SCREENS.ts | 1 + ...oraryForRefactorRequestConfirmationList.js | 23 +++---- .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + .../iou/request/step/IOURequestStepRate.tsx | 65 +++++++++++++++++++ 6 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 src/pages/iou/request/step/IOURequestStepRate.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a8786bda3ffb..c01fdac72aef 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -369,6 +369,11 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}`, backTo), }, + MONEY_REQUEST_STEP_RATE: { + route: ':action/:iouType/rate/:transactionID/:reportID', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action}/${iouType}/rate/${transactionID}/${reportID}`, backTo), + }, MONEY_REQUEST_STEP_MERCHANT: { route: ':action/:iouType/merchant/:transactionID/:reportID', getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 520895c89c98..ddaf52a03a59 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -143,6 +143,7 @@ const SCREENS = { STEP_DATE: 'Money_Request_Step_Date', STEP_DESCRIPTION: 'Money_Request_Step_Description', STEP_DISTANCE: 'Money_Request_Step_Distance', + STEP_RATE: 'Money_Request_Step_Rate', STEP_MERCHANT: 'Money_Request_Step_Merchant', STEP_PARTICIPANTS: 'Money_Request_Step_Participants', STEP_SCAN: 'Money_Request_Step_Scan', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 6fb0e8bc0f08..f2fcb3ac96eb 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -1,10 +1,10 @@ -import {useIsFocused} from '@react-navigation/native'; -import {format} from 'date-fns'; +import { useIsFocused } from '@react-navigation/native'; +import { format } from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import { View } from 'react-native'; +import { withOnyx } from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -21,9 +21,9 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import playSound, {SOUNDS} from '@libs/Sound'; +import playSound, { SOUNDS } from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {policyPropTypes} from '@pages/workspace/withPolicy'; +import { policyPropTypes } from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -44,7 +44,8 @@ import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails, { withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes } from './withCurrentUserPersonalDetails'; + const propTypes = { /** Callback to inform parent modal of success */ @@ -701,9 +702,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ description={translate('common.rate')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - // TODO: Add the onPress function - onPress={() => {}} - // onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + onPress={() => { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} disabled={didConfirm || !isTypeRequest} interactive={!isReadOnly} /> diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 2be262aa5f0f..941900c3bf2c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -89,6 +89,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepDate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: () => require('../../../pages/iou/request/step/IOURequestStepDescription').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: () => require('../../../pages/iou/request/step/IOURequestStepDistance').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STEP_RATE]: () => require('../../../pages/iou/request/step/IOURequestStepRate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: () => require('../../../pages/iou/request/step/IOURequestStepMerchant').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: () => require('../../../pages/iou/request/step/IOURequestStepParticipants').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_SCAN]: () => require('../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 48d649cc4dd9..9163c0b9bfe5 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -416,6 +416,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_DATE]: ROUTES.MONEY_REQUEST_STEP_DATE.route, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.route, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: ROUTES.MONEY_REQUEST_STEP_DISTANCE.route, + [SCREENS.MONEY_REQUEST.STEP_RATE]: ROUTES.MONEY_REQUEST_STEP_RATE.route, [SCREENS.MONEY_REQUEST.HOLD]: ROUTES.MONEY_REQUEST_HOLD_REASON.route, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: ROUTES.MONEY_REQUEST_STEP_MERCHANT.route, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.route, diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx new file mode 100644 index 000000000000..347e627624e9 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; +import type {Route} from '@src/ROUTES'; + +type Props = { + // eslint-disable-next-line react/no-unused-prop-types + lastSelectedDistanceRate: string; + /** The route object passed to this screen */ + route: { + /** The params passed to this screen */ + params: { + /** The route to go back to */ + backTo: Route; + }; + }; +}; + +const mockRates = [ + {text: 'Default Rate', alternateText: '0.656/mile', keyForList: 'DefaultRate'}, + {text: 'Custom Rate', alternateText: '0.700/mile', keyForList: 'CustomRate'}, +]; + +function IOURequestStepRate({ + route: { + params: {backTo}, + }, +}: Props) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + Navigation.goBack(backTo)} + shouldShowWrapper={Boolean(backTo)} + testID="rate" + > + {translate('themePage.chooseThemeBelowOrSync')} + + {}} + initiallyFocusedOptionKey="DefaultRate" + /> + + ); +} + +IOURequestStepRate.displayName = 'IOURequestStepRate'; + +// export default withOnyx({ +// lastSelectedDistanceRate: { +// key: 'ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATE', +// }, +// })(IOURequestStepRate); + +export default IOURequestStepRate; From b3e1f48577d3cd8e1b4c041ce661ae684507fdd0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 29 Feb 2024 10:07:54 +0100 Subject: [PATCH 003/124] feat: add translation for the rate page --- src/languages/en.ts | 1 + src/languages/es.ts | 2 ++ src/pages/iou/request/step/IOURequestStepRate.tsx | 6 +++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 920705883b02..d904a52c1b7a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -696,6 +696,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', + chooseARate: 'Choose a rate to use below', }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1b858f707851..842f357639c8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -691,6 +691,8 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', + // TODO: check if this is the correct translation + chooseARate: 'Elige una tarifa para utilizar a continuación', }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 347e627624e9..d82238f2c7ea 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -23,8 +23,8 @@ type Props = { }; const mockRates = [ - {text: 'Default Rate', alternateText: '0.656/mile', keyForList: 'DefaultRate'}, - {text: 'Custom Rate', alternateText: '0.700/mile', keyForList: 'CustomRate'}, + {text: 'Default Rate', alternateText: '$0.656 / mile', keyForList: 'DefaultRate'}, + {text: 'Custom Rate', alternateText: '$0.700 / mile', keyForList: 'CustomRate'}, ]; function IOURequestStepRate({ @@ -42,7 +42,7 @@ function IOURequestStepRate({ shouldShowWrapper={Boolean(backTo)} testID="rate" > - {translate('themePage.chooseThemeBelowOrSync')} + {translate('iou.chooseARate')} Date: Thu, 29 Feb 2024 17:59:45 +0100 Subject: [PATCH 004/124] feat: wip - display rates for policy --- ...oraryForRefactorRequestConfirmationList.js | 4 +- src/libs/DistanceRequestUtils.ts | 119 +++++++++++++++--- .../iou/request/step/IOURequestStepRate.tsx | 49 +++++--- src/types/onyx/Policy.ts | 4 +- 4 files changed, 139 insertions(+), 37 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 46e4cb3d6733..267662f07157 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -681,7 +681,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + callback: (policy, key) => { + if (!policy || !key || !policy.name) { + return; + } + + policies[key] = policy; + }, +}); + /** * Retrieves the default mileage rate based on a given policy. * @@ -67,22 +81,21 @@ function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { */ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string { const convertedDistance = convertDistanceUnit(distanceInMeters, unit); - return convertedDistance.toFixed(2); + // TODO: add logic for currencies for which we need to round to 4 decimals + return convertedDistance.toFixed(3); } /** -* @param hasRoute Whether the route exists for the distance request -* @param distanceInMeters Distance traveled -* @param unit Unit that should be used to display the distance -* @param rate Expensable amount allowed per unit -* @param currency The currency associated with the rate -* @param translate Translate function -* @param toLocaleDigit Function to convert to localized digit -* @returns A string that describes the distance traveled and the rate used for expense calculation -*/ + * @param hasRoute Whether the route exists for the distance request + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param currency The currency associated with the rate + * @param translate Translate function + * @param toLocaleDigit Function to convert to localized digit + * @returns A string that describes the distance traveled and the rate used for expense calculation + */ function getRateForDisplay( hasRoute: boolean, - distanceInMeters: number, unit: Unit, rate: number, currency: string, @@ -101,6 +114,28 @@ function getRateForDisplay( return `${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } +// TODO: I wonder if it would be better to rfactor these functions to pass params in an object +/** + * @param hasRoute Whether the route exists for the distance request + * @param distanceInMeters Distance traveled + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param translate Translate function + * @returns A string that describes the distance traveled + */ +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, translate: LocaleContextProps['translate']): string { + if (!hasRoute) { + return translate('iou.routePending'); + } + + const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); + const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); + const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); + const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; + + return `${distanceInUnits} ${unitString}`; +} + /** * @param hasRoute Whether the route exists for the distance request * @param distanceInMeters Distance traveled @@ -124,13 +159,52 @@ function getDistanceMerchant( return translate('iou.routePending'); } - const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); - const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); - const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); - const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; - const ratePerUnit = getRateForDisplay(hasRoute, distanceInMeters, unit, rate, currency, translate, toLocaleDigit); + const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, translate); + const ratePerUnit = getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); + + return `${distanceInUnits} @ ${ratePerUnit}`; +} + +/** + * Retrieves the mileage rates for given policy. + * + * @param policyID - The policy ID from which to extract the mileage rates. + * + * @returns An array of mileage rates or an empty array if not found. + */ +function getMileageRates(policyID?: string): MileageRate[] | [] { + if (!policyID) { + return []; + } + + const mileageRates: MileageRate[] = []; + + const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? null; + + if (!policy || !policy?.customUnits) { + return mileageRates; + } + + const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceUnit?.rates) { + return mileageRates; + } + + const rates = Object.values(distanceUnit.rates); + + for (const rate of rates) { + if (rate.enabled) { + mileageRates.push({ + rate: rate.rate ?? 0, + name: rate.name, + currency: rate.currency ?? 'USD', + unit: distanceUnit.attributes.unit, + customUnitRateID: rate.customUnitRateID, + }); + } + } - return `${distanceInUnits} ${unitString} @ ${ratePerUnit}`; + return mileageRates; } /** @@ -147,4 +221,11 @@ function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): n return Math.round(roundedDistance * rate); } -export default {getDefaultMileageRate, getDistanceMerchant, getDistanceRequestAmount, getRateForDisplay}; +export default { + getDefaultMileageRate, + getDistanceMerchant, + getDistanceRequestAmount, + getRateForDisplay, + getMileageRates, + getDistanceForDisplay, +}; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index d82238f2c7ea..3b7889de9651 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -1,17 +1,27 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import compose from '@libs/compose'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; +import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; +import type {Policy} from '@src/types/onyx'; type Props = { // eslint-disable-next-line react/no-unused-prop-types lastSelectedDistanceRate: string; + + /** Policy details */ + policy: OnyxEntry; + /** The route object passed to this screen */ route: { /** The params passed to this screen */ @@ -22,18 +32,22 @@ type Props = { }; }; -const mockRates = [ - {text: 'Default Rate', alternateText: '$0.656 / mile', keyForList: 'DefaultRate'}, - {text: 'Custom Rate', alternateText: '$0.700 / mile', keyForList: 'CustomRate'}, -]; - function IOURequestStepRate({ + policy, route: { params: {backTo}, }, }: Props) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = useLocalize(); + const rates = DistanceRequestUtils.getMileageRates(policy?.id); + + const data = rates.map((rate) => ({ + text: rate.name ?? '', + alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), + keyForList: rate.name ?? '', + value: rate.customUnitRateID, + })); return ( {translate('iou.chooseARate')} {}} - initiallyFocusedOptionKey="DefaultRate" + // TODO: change for lastSelectedDistanceRates + initiallyFocusedOptionKey="Default Rate" /> ); @@ -56,10 +71,14 @@ function IOURequestStepRate({ IOURequestStepRate.displayName = 'IOURequestStepRate'; -// export default withOnyx({ -// lastSelectedDistanceRate: { -// key: 'ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATE', -// }, -// })(IOURequestStepRate); - -export default IOURequestStepRate; +export default compose( + withWritableReportOrNotFound, + withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, + // lastSelectedDistanceRates: { + // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + // }, + }), +)(IOURequestStepRate); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 1eece2d3a1e0..8e408951a84f 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -19,8 +19,10 @@ type Attributes = { type MileageRate = { unit: Unit; - rate?: number; currency: string; + customUnitRateID?: string; + rate?: number; + name?: string; }; type CustomUnit = OnyxCommon.OnyxValueWithOfflineFeedback<{ From cde1d19d37cdd0203ca6023612dfe12ea0f46537 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 16:12:00 +0100 Subject: [PATCH 005/124] feat: wip - get rate for p2p --- ...oraryForRefactorRequestConfirmationList.js | 52 ++++++++++++++++--- src/libs/DistanceRequestUtils.ts | 13 +++-- ...yForRefactorRequestParticipantsSelector.js | 4 +- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 267662f07157..5a7019e63428 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -239,6 +239,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, + lastSelectedDistanceRate = {}, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -250,6 +251,12 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSend = iouType === CONST.IOU.TYPE.SEND; const {unit, rate, currency} = mileageRate; + + // const rate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' + + // const {unit, rate, currency} = lastSelectedDistanceRate || mileageRate; + + const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; const taxRates = lodashGet(policy, 'taxRates', {}); @@ -266,6 +273,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; + console.log({transaction}); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field @@ -622,6 +631,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); + // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] + // change for true for development + const canUseP2PDistanceRequests = true; + // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. const classifiedFields = [ @@ -681,7 +694,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ), - shouldShow: isDistanceRequest, + shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, isSupplementary: true, }, { item: ( Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm || !isTypeRequest} + interactive={!isReadOnly} + /> + ), + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, + isSupplementary: true, + }, + { + item: ( + { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()), + ); }} disabled={didConfirm || !isTypeRequest} - interactive={!isReadOnly} + interactive={!isReadOnly && isPolicyExpenseChat} /> ), - // TODO: hide when betas ready - shouldShow: isDistanceRequest, + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, isSupplementary: true, }, { @@ -950,12 +982,16 @@ export default compose( policyTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, + // TODO: add NVP_LAST_SELECTED_DISTANCE_RATES + // lastSelectedDistanceRates: { + // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + // }, mileageRate: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - }, + } }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index a605ace8da87..191edd93bfb9 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -12,6 +12,8 @@ type DefaultMileageRate = { rate?: number; currency?: string; unit: Unit; + name?: string; + customUnitRateID?: string; }; const policies: OnyxCollection = {}; @@ -55,6 +57,8 @@ function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | rate: distanceRate.rate, currency: distanceRate.currency, unit: distanceUnit.attributes.unit, + name: distanceRate.name, + customUnitRateID: distanceRate.customUnitRateID, }; } @@ -91,6 +95,7 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string return convertedDistance.toFixed(3); } +// TODO: I wonder if it would be better to refactor these functions to pass params in an object /** * @param hasRoute Whether the route exists for the distance request * @param unit Unit that should be used to display the distance @@ -120,7 +125,7 @@ function getRateForDisplay( return `${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } -// TODO: I wonder if it would be better to rfactor these functions to pass params in an object +// TODO: this function will be added in https://github.com/Expensify/App/pull/37185, remove it to avoid conflicts /** * @param hasRoute Whether the route exists for the distance request * @param distanceInMeters Distance traveled @@ -129,8 +134,8 @@ function getRateForDisplay( * @param translate Translate function * @returns A string that describes the distance traveled */ -function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, translate: LocaleContextProps['translate']): string { - if (!hasRoute) { +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, rate: number, translate: LocaleContextProps['translate']): string { + if (!hasRoute || !rate) { return translate('iou.routePending'); } @@ -165,7 +170,7 @@ function getDistanceMerchant( return translate('iou.routePending'); } - const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, translate); + const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, rate, translate); const ratePerUnit = getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); return `${distanceInUnits} @ ${ratePerUnit}`; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 2865316b7fd5..b42b94afc686 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -121,7 +121,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType === CONST.IOU.TYPE.REQUEST, // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, false, {}, [], @@ -131,7 +131,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, false, ); From c3cb80bf507de6c4922b28e170893cdcaa126ebb Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 17:38:59 +0100 Subject: [PATCH 006/124] feat: wip - new rate field logic --- src/CONST.ts | 7 ++++ ...oraryForRefactorRequestConfirmationList.js | 40 +++++++++---------- src/libs/DistanceRequestUtils.ts | 39 +++++++++--------- src/libs/actions/IOU.ts | 13 ++++++ .../iou/request/step/IOURequestStepRate.tsx | 12 +++++- 5 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8d4eaac44a38..2bd047f6bf51 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1398,6 +1398,13 @@ const CONST = { RATE_DECIMALS: 3, }, + // TODO: remove this mock when https://github.com/Expensify/App/issues/36982 is done + CURRENCY_TO_DEFAULT_MILEAGE_RATE: { + USD: { unit: "mile", rate: 0.5 }, + EUR: { unit: "kilometer", rate: 0.8 }, + GBP: { unit: "mile", rate: 0.45 }, + }, + TERMS: { CFPB_PREPAID: 'cfpb.gov/prepaid', CFPB_COMPLAINT: 'cfpb.gov/complaint', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 5a7019e63428..9c2d77239015 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -1,10 +1,10 @@ -import { useIsFocused } from '@react-navigation/native'; -import { format } from 'date-fns'; +import {useIsFocused} from '@react-navigation/native'; +import {format} from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; -import { View } from 'react-native'; -import { withOnyx } from 'react-native-onyx'; +import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -21,9 +21,9 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import playSound, { SOUNDS } from '@libs/Sound'; +import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; -import { policyPropTypes } from '@pages/workspace/withPolicy'; +import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -44,8 +44,7 @@ import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; -import withCurrentUserPersonalDetails, { withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes } from './withCurrentUserPersonalDetails'; - +import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; const propTypes = { /** Callback to inform parent modal of success */ @@ -239,7 +238,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, - lastSelectedDistanceRate = {}, + mileageRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -250,12 +249,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; const isTypeSend = iouType === CONST.IOU.TYPE.SEND; + // TODO: uncomment after Splits and P2P are enabled https://github.com/Expensify/App/pull/37185, mileageRate prop should be be removed + // const mileageRate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' ? DistanceRequestUtils.getRateForP2P(policy.outputCurrency) : mileageRates[transaction.comment.customUnit.customUnitRateID]; const {unit, rate, currency} = mileageRate; - // const rate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' - - // const {unit, rate, currency} = lastSelectedDistanceRate || mileageRate; - + // will hardcoded rates will have a currency property? If not we have to get currency other way const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; @@ -273,8 +271,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; - console.log({transaction}); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field @@ -633,7 +629,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] // change for true for development - const canUseP2PDistanceRequests = true; + const canUseP2PDistanceRequests = false; // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. @@ -982,16 +978,16 @@ export default compose( policyTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, - // TODO: add NVP_LAST_SELECTED_DISTANCE_RATES - // lastSelectedDistanceRates: { - // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - // }, mileageRate: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, + mileageRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + selector: (policy) => DistanceRequestUtils.getMileageRates(policy ? policy.id : ''), + }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - } + }, }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 191edd93bfb9..dd95612af3e6 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,4 +1,4 @@ -import type {OnyxEntry, OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; @@ -183,13 +183,13 @@ function getDistanceMerchant( * * @returns An array of mileage rates or an empty array if not found. */ -function getMileageRates(policyID?: string): DefaultMileageRate[] | [] { +function getMileageRates(policyID?: string): Record { + const mileageRates = {}; + if (!policyID) { - return []; + return mileageRates; } - const mileageRates: DefaultMileageRate[] = []; - const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? null; if (!policy || !policy?.customUnits) { @@ -201,23 +201,25 @@ function getMileageRates(policyID?: string): DefaultMileageRate[] | [] { return mileageRates; } - const rates = Object.values(distanceUnit.rates); - - for (const rate of rates) { - if (rate.enabled) { - mileageRates.push({ - rate: rate.rate ?? 0, - name: rate.name, - currency: rate.currency ?? 'USD', - unit: distanceUnit.attributes.unit, - customUnitRateID: rate.customUnitRateID, - }); - } - } + Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { + // TODO: fix TS error + mileageRates[rateID] = { + rate: rate.rate, + currency: rate.currency, + unit: distanceUnit.attributes.unit, + name: rate.name, + customUnitRateID: rate.customUnitRateID, + }; + }); return mileageRates; } +// TODO: probably will need to be changed +function getRateForP2P(currency) { + return CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; +} + /** * Calculates the request amount based on distance, unit, and rate. * @@ -239,4 +241,5 @@ export default { getRateForDisplay, getMileageRates, getDistanceForDisplay, + getRateForP2P, }; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5f9657755b02..91dd95b12592 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -355,6 +355,17 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: }); } +/** Set the last selected distance rate for policy */ +// TODO: probably need to be changed +function setLastSelectedDistanceRates(policyID: string, rateID: string) { + Onyx.merge('lastSelectedDistanceRates', {[policyID]: rateID}); +} + +/** Update transaction distance rate */ +function updateDistanceRequestRate(transactionID: string, rateID: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {customUnit: {customUnitRateID: rateID}}}); +} + /** Reset money request info from the store with its initial value */ function resetMoneyRequestInfo(id = '') { // Disabling this line since currentDate can be an empty string @@ -4327,4 +4338,6 @@ export { cancelPayment, navigateToStartStepIfScanFileCannotBeRead, savePreferredPaymentMethod, + setLastSelectedDistanceRates, + updateDistanceRequestRate, }; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 3b7889de9651..a2cf7a0af1c8 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -9,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as IOU from '@libs/actions/IOU'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -42,13 +43,20 @@ function IOURequestStepRate({ const {translate, toLocaleDigit} = useLocalize(); const rates = DistanceRequestUtils.getMileageRates(policy?.id); - const data = rates.map((rate) => ({ + const data = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), keyForList: rate.name ?? '', value: rate.customUnitRateID, })); + const selectDistanceRate = (customUnitRateID) => { + IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); + // TODO: get a proper transaction ID + IOU.updateDistanceRequestRate('1', customUnitRateID); + Navigation.goBack(backTo); + } + return ( {}} + onSelectRow={({value}) => selectDistanceRate(value)} // TODO: change for lastSelectedDistanceRates initiallyFocusedOptionKey="Default Rate" /> From 5bf399af20bdb7816bedf1ed40ee37fa83e3fffa Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 17:51:11 +0100 Subject: [PATCH 007/124] fix: revert filtering changes --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index b42b94afc686..2865316b7fd5 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -121,7 +121,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType === CONST.IOU.TYPE.REQUEST, // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - true, + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, {}, [], @@ -131,7 +131,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - true, + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, ); From ef7b30e6a95bc95ed2ead63f27e25636398f7c6b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 10:19:52 +0100 Subject: [PATCH 008/124] refactor: use canUseP2PDistanceRequests from usePermissions --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 182aa5464772..7333d405363b 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -664,10 +664,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); - // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] - // change for true for development - const canUseP2PDistanceRequests = false; - // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. const classifiedFields = [ From 6f3afddef1d036658a48b38ca8f0caf6c696c85f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 17:28:24 +0100 Subject: [PATCH 009/124] fix: remove mock rates --- src/CONST.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 9b2502cb17d1..d4fbd0ff6ef3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1431,13 +1431,6 @@ const CONST = { FAKE_P2P_ID: '_FAKE_P2P_ID_', }, - // TODO: remove this mock when https://github.com/Expensify/App/issues/36982 is done - CURRENCY_TO_DEFAULT_MILEAGE_RATE: { - USD: { unit: "mile", rate: 0.5 }, - EUR: { unit: "kilometer", rate: 0.8 }, - GBP: { unit: "mile", rate: 0.45 }, - }, - TERMS: { CFPB_PREPAID: 'cfpb.gov/prepaid', CFPB_COMPLAINT: 'cfpb.gov/complaint', From 50174f38629f23ef3363e51613755b05e30ec3d0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 17:29:24 +0100 Subject: [PATCH 010/124] feat: create function for getting personal policy --- src/libs/PolicyUtils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 4689fd03ebd0..6a3829b10934 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -12,6 +13,14 @@ import Navigation from './Navigation/Navigation'; type MemberEmailsToAccountIDs = Record; type UnitRate = {rate: number}; +let allPolicies: OnyxCollection; + +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + waitForCollectionCallback: true, + callback: (value) => (allPolicies = value), +}); + /** * Filter out the active policies, which will exclude policies with pending deletion * These are policies that we can use to create reports with in NewDot. @@ -265,6 +274,10 @@ function goBackFromInvalidPolicy() { Navigation.navigateWithSwitchPolicyID({route: ROUTES.ALL_SETTINGS}); } +function getPersonalPolicy() { + return Object.values(allPolicies ?? {}).find((policy) => policy?.type === CONST.POLICY.TYPE.PERSONAL); +} + export { getActivePolicies, hasAccountingConnections, @@ -295,6 +308,7 @@ export { getPathWithoutPolicyID, getPolicyMembersByIdWithoutCurrentUser, goBackFromInvalidPolicy, + getPersonalPolicy }; export type {MemberEmailsToAccountIDs}; From eb3aff8ee97f1f055d22376ea7004ba575ff1cb9 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 14 Mar 2024 19:15:31 +0100 Subject: [PATCH 011/124] feat: add rate field to MoneyRequestView --- .../ReportActionItem/MoneyRequestView.tsx | 68 +++++++++++++++---- src/libs/DistanceRequestUtils.ts | 17 ++--- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 70c65d1d66ce..bcaa7f830925 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -21,6 +21,7 @@ import type {ViolationField} from '@hooks/useViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; @@ -89,8 +90,8 @@ function MoneyRequestView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); - const {canUseViolations} = usePermissions(); + const {translate, toLocaleDigit} = useLocalize(); + const {canUseViolations, canUseP2PDistanceRequests} = usePermissions(); const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; const moneyRequestReport = parentReport; const { @@ -151,6 +152,20 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; + const hasRoute = TransactionUtils.hasRoute(transaction); + const distance = transaction?.routes?.route0?.distance ?? 0; + const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '0'; + + const rates = DistanceRequestUtils.getMileageRates(policy?.id); + const {unit, currency, rate} = rates[rateID as string] ?? { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + currency: CONST.CURRENCY.USD, + rate: 0, + }; + + const rateToDisplay = DistanceRequestUtils.getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); + const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); + const saveBillable = useCallback( (newBillable: boolean) => { // If the value hasn't changed, don't request to save changes on the server and just close the modal @@ -236,6 +251,44 @@ function MoneyRequestView({ [transactionAmount, isSettled, isCancelled, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], ); + const distanceRequestFields = canUseP2PDistanceRequests ? ( + <> + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + + {}} + /> + + + ) : ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + ) + return ( @@ -320,16 +373,7 @@ function MoneyRequestView({ /> {isDistanceRequest ? ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - + distanceRequestFields ) : ( { - const mileageRates = {}; + const mileageRates: Record = {}; if (!policyID) { return mileageRates; @@ -202,7 +201,6 @@ function getMileageRates(policyID?: string): Record } Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { - // TODO: fix TS error mileageRates[rateID] = { rate: rate.rate, currency: rate.currency, @@ -215,8 +213,7 @@ function getMileageRates(policyID?: string): Record return mileageRates; } -// TODO: probably will need to be changed -function getRateForP2P(currency) { +function getRateForP2P(currency: string) { return CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; } From 7dc206cf66ae8f96ddea9883b16334618b4a5379 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 18 Mar 2024 19:57:19 -0300 Subject: [PATCH 012/124] feat: change customRateID when participant changes, other improvements --- ...oraryForRefactorRequestConfirmationList.js | 25 ++++--- .../ReportActionItem/MoneyRequestView.tsx | 72 +++++++++---------- src/libs/DistanceRequestUtils.ts | 3 +- src/libs/TransactionUtils.ts | 9 +++ src/libs/actions/IOU.ts | 6 +- ...yForRefactorRequestParticipantsSelector.js | 2 +- .../step/IOURequestStepParticipants.js | 35 ++++++++- .../iou/request/step/IOURequestStepRate.tsx | 28 +++++--- 8 files changed, 116 insertions(+), 64 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 93831606416e..ce5668924271 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -224,7 +224,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ isReadOnly, isScanRequest, listStyles, - mileageRate, + mileageRates, onConfirm, onSelectParticipant, onSendMoney, @@ -242,7 +242,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, - mileageRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -254,14 +253,19 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSend = iouType === CONST.IOU.TYPE.SEND; const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit); - // TODO: uncomment after Splits and P2P are enabled https://github.com/Expensify/App/pull/37185, mileageRate prop should be be removed - // const mileageRate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' ? DistanceRequestUtils.getRateForP2P(policy.outputCurrency) : mileageRates[transaction.comment.customUnit.customUnitRateID]; - const {unit, rate, currency} = mileageRate; + const personalPolicy = policyID === CONST.POLICY.ID_FAKE ? PolicyUtils.getPersonalPolicy() : policy; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(personalPolicy.outputCurrency) + : mileageRates[transaction.comment.customUnit.customUnitRateID]; - // will hardcoded rates will have a currency property? If not we have to get currency other way + const {unit, rate} = mileageRate || { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * 100, + }; + + const currency = personalPolicy.outputCurrency; const distance = lodashGet(transaction, 'routes.route0.distance', 0); - const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; const taxRates = lodashGet(policy, 'taxRates', {}); // A flag for showing the categories field @@ -292,7 +296,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const formattedAmount = isDistanceRequestWithPendingRoute ? '' : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, + isDistanceRequest && iouAmount === 0 ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); @@ -359,13 +363,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit, isMerchantRequired, merchantError]); useEffect(() => { - if (!shouldCalculateDistanceAmount) { + if (!isDistanceRequest) { return; } const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate); IOU.setMoneyRequestAmount_temporaryForRefactor(transaction.transactionID, amount, currency); - }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]); + }, [isDistanceRequest, distance, rate, unit, transaction, currency]); /** * Returns the participants with amount @@ -757,7 +761,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - - - {}} - /> - - - ) : ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - - ) + <> + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + + {}} + /> + + + ) : ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + ); return ( diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index e954ce4d266d..28714a92ed84 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -91,8 +91,7 @@ function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { */ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string { const convertedDistance = convertDistanceUnit(distanceInMeters, unit); - // TODO: add logic for currencies for which we need to round to 4 decimals - return convertedDistance.toFixed(3); + return convertedDistance.toFixed(2); } // TODO: I wonder if it would be better to refactor these functions to pass params in an object diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index bc94c8fee8fc..f694199fbeb4 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -584,6 +584,14 @@ function getEnabledTaxRateCount(options: TaxRates) { return Object.values(options).filter((option: TaxRate) => !option.isDisabled).length; } +/** + * Check if the customUnitRateID has a value default for P2P distance requests + */ + +function isCustomUnitRateIDForP2P(transaction: Transaction): boolean { + return transaction?.comment?.customUnit?.customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; +} + export { buildOptimisticTransaction, calculateTaxAmount, @@ -633,6 +641,7 @@ export { waypointHasValidAddress, getRecentTransactions, hasViolation, + isCustomUnitRateIDForP2P, }; export type {TransactionChanges}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 477fb99900b8..31cd49269e2c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -253,8 +253,9 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, waypoint1: {}, }; const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; - if (ReportUtils.isPolicyExpenseChat(report)) { + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; } comment.customUnit = {customUnitRateID}; @@ -373,9 +374,8 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: } /** Set the last selected distance rate for policy */ -// TODO: probably need to be changed function setLastSelectedDistanceRates(policyID: string, rateID: string) { - Onyx.merge('lastSelectedDistanceRates', {[policyID]: rateID}); + Onyx.merge(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {[policyID]: rateID}); } /** Update transaction distance rate */ diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index cdc3e72f98f4..7bdcafef61e6 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -190,7 +190,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const addSingleParticipant = (option) => { onParticipantsAdded([ { - ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText'), + ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText', 'policyID'), selected: true, }, ]); diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 5ca465d8fb78..8c92961a28c4 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -1,16 +1,21 @@ import {useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; import StepScreenWrapper from './StepScreenWrapper'; @@ -24,6 +29,9 @@ const propTypes = { /* Onyx Props */ /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, + + // eslint-disable-next-line + lastSelectedDistanceRates: PropTypes.object, }; const defaultProps = { @@ -36,6 +44,7 @@ function IOURequestStepParticipants({ }, transaction, transaction: {participants = []}, + lastSelectedDistanceRates = {}, }) { const {translate} = useLocalize(); const navigation = useNavigation(); @@ -100,6 +109,22 @@ function IOURequestStepParticipants({ } IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); + + // change customUnitRateID when choosing + if (val[0].isPolicyExpenseChat && TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { + let customUnitRateID = ''; + if (val[0].policyID && lastSelectedDistanceRates[val[0].policyID]) { + customUnitRateID = lastSelectedDistanceRates[val[0].policyID]; + } else { + const policy = ReportUtils.getPolicy(val[0].policyID); + const defaultRate = DistanceRequestUtils.getDefaultMileageRate(policy); + customUnitRateID = defaultRate ? defaultRate.customUnitRateID : ''; + } + IOU.updateDistanceRequestRate(transactionID, customUnitRateID); + } else if (!TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { + IOU.updateDistanceRequestRate(transactionID, CONST.CUSTOM_UNITS.FAKE_P2P_ID); + } + numberOfParticipants.current = val.length; // When multiple participants are selected, the reportID is generated at the end of the confirmation step. @@ -163,4 +188,12 @@ IOURequestStepParticipants.displayName = 'IOURequestStepParticipants'; IOURequestStepParticipants.propTypes = propTypes; IOURequestStepParticipants.defaultProps = defaultProps; -export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepParticipants); +export default compose( + withWritableReportOrNotFound, + withFullTransactionOrNotFound, + withOnyx({ + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, + }), +)(IOURequestStepParticipants); diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index a2cf7a0af1c8..10513f7a44ca 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -6,19 +6,21 @@ import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as IOU from '@libs/actions/IOU'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as IOU from '@libs/actions/IOU'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; + type Props = { - // eslint-disable-next-line react/no-unused-prop-types - lastSelectedDistanceRate: string; + /** Object of last selected rates for the policies */ + lastSelectedDistanceRates: Record; /** Policy details */ policy: OnyxEntry; @@ -38,11 +40,14 @@ function IOURequestStepRate({ route: { params: {backTo}, }, + lastSelectedDistanceRates = {}, }: Props) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const rates = DistanceRequestUtils.getMileageRates(policy?.id); + const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? '0'; + const data = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), @@ -50,9 +55,10 @@ function IOURequestStepRate({ value: rate.customUnitRateID, })); - const selectDistanceRate = (customUnitRateID) => { + const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; + + function selectDistanceRate(customUnitRateID = '0') { IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); - // TODO: get a proper transaction ID IOU.updateDistanceRequestRate('1', customUnitRateID); Navigation.goBack(backTo); } @@ -70,8 +76,7 @@ function IOURequestStepRate({ sections={[{data}]} ListItem={RadioListItem} onSelectRow={({value}) => selectDistanceRate(value)} - // TODO: change for lastSelectedDistanceRates - initiallyFocusedOptionKey="Default Rate" + initiallyFocusedOptionKey={initiallyFocusedOption} /> ); @@ -82,11 +87,14 @@ IOURequestStepRate.displayName = 'IOURequestStepRate'; export default compose( withWritableReportOrNotFound, withOnyx({ + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS policy: { + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, - // lastSelectedDistanceRates: { - // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - // }, + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, }), )(IOURequestStepRate); From a0227e07854c76bbc9039d5df2971de4c5294a5f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 19 Mar 2024 09:42:20 -0300 Subject: [PATCH 013/124] fix: run prettier --- src/pages/iou/request/step/IOURequestStepRate.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 10513f7a44ca..583fa8c055c7 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -17,7 +17,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; - type Props = { /** Object of last selected rates for the policies */ lastSelectedDistanceRates: Record; From eb350eec27ba9858929864cdc29aada4453b88e1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 19 Mar 2024 09:53:29 -0300 Subject: [PATCH 014/124] fix: lint fix --- src/pages/iou/request/step/IOURequestStepParticipants.js | 2 +- src/pages/iou/request/step/IOURequestStepRate.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 8c92961a28c4..a66387709f61 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -137,7 +137,7 @@ function IOURequestStepParticipants({ // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. selectedReportID.current = lodashGet(val, '[0].reportID', reportID); }, - [reportID, transactionID, iouType, participants, updateRouteParams], + [iouType, participants, transactionID, transaction, reportID, updateRouteParams, lastSelectedDistanceRates], ); const goToNextStep = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 583fa8c055c7..de570ca3c67f 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -10,12 +10,12 @@ import * as IOU from '@libs/actions/IOU'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; -import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; +import StepScreenWrapper from './StepScreenWrapper'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type Props = { /** Object of last selected rates for the policies */ From 4b823f46eeb833902249b682b7b211721df433c5 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 23 Mar 2024 21:50:16 +0800 Subject: [PATCH 015/124] only add the saved user on first render --- src/pages/workspace/WorkspaceInvitePage.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 00d7eaa33f6b..fe7d82658e82 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -1,6 +1,6 @@ import {useNavigation} from '@react-navigation/native'; import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import type {SectionListData} from 'react-native'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -67,6 +67,7 @@ function WorkspaceInvitePage({ const [personalDetails, setPersonalDetails] = useState([]); const [usersToInvite, setUsersToInvite] = useState([]); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); + const firstRenderRef = useRef(true); const navigation = useNavigation>(); const openWorkspaceInvitePage = () => { const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetailsProp); @@ -120,12 +121,16 @@ function WorkspaceInvitePage({ }); const newSelectedOptions: MemberForList[] = []; - Object.keys(invitedEmailsToAccountIDsDraft ?? {}).forEach((login) => { - if (!(login in detailsMap)) { - return; - } - newSelectedOptions.push({...detailsMap[login], isSelected: true}); - }); + if (firstRenderRef.current) { + // We only want to add the saved selected user on first render + firstRenderRef.current = false; + Object.keys(invitedEmailsToAccountIDsDraft ?? {}).forEach((login) => { + if (!(login in detailsMap)) { + return; + } + newSelectedOptions.push({...detailsMap[login], isSelected: true}); + }); + } selectedOptions.forEach((option) => { newSelectedOptions.push(option.login && option.login in detailsMap ? {...detailsMap[option.login], isSelected: true} : option); }); From 0682afbf2bd04faf5f56b44aae122d797a5983e4 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 13:56:02 +0100 Subject: [PATCH 016/124] fix: add missing screen --- src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index ef1fc3c2dfb0..7fead4088bf1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -81,6 +81,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/iou/request/step/IOURequestStepDate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: () => require('../../../../pages/iou/request/step/IOURequestStepDescription').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: () => require('../../../../pages/iou/request/step/IOURequestStepDistance').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STEP_RATE]: () => require('../../../pages/iou/request/step/IOURequestStepRate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: () => require('../../../../pages/iou/request/step/IOURequestStepMerchant').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: () => require('../../../../pages/iou/request/step/IOURequestStepParticipants').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_SCAN]: () => require('../../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, From 6105b5d917e8172779de97cca9947694ee3dcb4c Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 17:05:33 +0100 Subject: [PATCH 017/124] fix: show check mark on lastSelectedDistanceRate --- src/pages/iou/request/step/IOURequestStepRate.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index de570ca3c67f..c4e638a48c2f 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -52,11 +52,12 @@ function IOURequestStepRate({ alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), keyForList: rate.name ?? '', value: rate.customUnitRateID, + isSelected: lastSelectedRate === rate.customUnitRateID, })); const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; - function selectDistanceRate(customUnitRateID = '0') { + function selectDistanceRate(customUnitRateID: string) { IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); IOU.updateDistanceRequestRate('1', customUnitRateID); Navigation.goBack(backTo); @@ -74,7 +75,7 @@ function IOURequestStepRate({ selectDistanceRate(value)} + onSelectRow={({value}) => selectDistanceRate(value ?? '')} initiallyFocusedOptionKey={initiallyFocusedOption} /> @@ -96,4 +97,5 @@ export default compose( key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, }, }), + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS )(IOURequestStepRate); From 063a8b6464615d1d3e4bc252e86bef7ef5d5cdb1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 17:15:46 +0100 Subject: [PATCH 018/124] fix: fix distance edit route --- src/components/ReportActionItem/MoneyRequestView.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 8a2ad321cedc..9011e8193077 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -260,7 +260,9 @@ function MoneyRequestView({ interactive={canEditDistance} shouldShowRightIcon={canEditDistance} titleStyle={styles.flex1} - onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + onPress={() => + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID)) + } /> @@ -285,9 +287,7 @@ function MoneyRequestView({ shouldShowRightIcon={canEditDistance} titleStyle={styles.flex1} onPress={() => - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID), - ) + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID)) } /> From 7fe595a9f274790a987949ff4fcfb960647983f9 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 18:46:11 +0100 Subject: [PATCH 019/124] fix: path fix --- src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 7fead4088bf1..75a9806e4ee8 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -81,7 +81,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/iou/request/step/IOURequestStepDate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: () => require('../../../../pages/iou/request/step/IOURequestStepDescription').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: () => require('../../../../pages/iou/request/step/IOURequestStepDistance').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.STEP_RATE]: () => require('../../../pages/iou/request/step/IOURequestStepRate').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STEP_RATE]: () => require('../../../../pages/iou/request/step/IOURequestStepRate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: () => require('../../../../pages/iou/request/step/IOURequestStepMerchant').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: () => require('../../../../pages/iou/request/step/IOURequestStepParticipants').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_SCAN]: () => require('../../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, From 9e68814a1f476f205be5bb0a3188723ca38b568d Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 19:08:20 +0100 Subject: [PATCH 020/124] fix: minor fix --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 394db0bddbe3..5dd94c331e80 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -254,7 +254,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeTrackExpense = iouType === CONST.IOU.TYPE.TRACK_EXPENSE; const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit); - const personalPolicy = policyID === CONST.POLICY.ID_FAKE ? PolicyUtils.getPersonalPolicy() : policy; + // if there is no policyID that means that the transaction was started from Global Create and the participant is not a policy + const personalPolicy = policyID === CONST.POLICY.ID_FAKE || !policyID ? PolicyUtils.getPersonalPolicy() : policy; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(personalPolicy.outputCurrency) : mileageRates[transaction.comment.customUnit.customUnitRateID]; From 68eecf0cb83aae10fd3c18ef735bd243986d3107 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 19:17:55 +0100 Subject: [PATCH 021/124] fix: enable fields for split, fix route --- ...neyTemporaryForRefactorRequestConfirmationList.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 5dd94c331e80..e4bc65b9840d 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -757,13 +757,17 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} - disabled={didConfirm || !isTypeRequest} + onPress={() => + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()), + ) + } + disabled={didConfirm} interactive={!isReadOnly} /> ), @@ -784,7 +788,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()), ); }} - disabled={didConfirm || !isTypeRequest} + disabled={didConfirm} interactive={!isReadOnly && isPolicyExpenseChat} /> ), From 35baf304a5f41f213f653836ec10378b173fbfd8 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 19:29:58 +0100 Subject: [PATCH 022/124] fix: minor fix --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index e4bc65b9840d..5a28b8be1ef5 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -266,7 +266,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * 100, }; - const currency = personalPolicy.outputCurrency; + const currency = mileageRate.currency || personalPolicy.outputCurrency; const distance = lodashGet(transaction, 'routes.route0.distance', 0); const taxRates = lodashGet(policy, 'taxRates', {}); From 9803669d05f0ad55c5d47ec6215c0f09df88bd95 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:35:34 -0500 Subject: [PATCH 023/124] Create Enable-and-set-up-expense-violations.md New article for enabling and setting up expense violations --- .../Enable-and-set-up-expense-violations.md | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md diff --git a/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md b/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md new file mode 100644 index 000000000000..b6cea251785b --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md @@ -0,0 +1,111 @@ +--- +title: Enable and set up expense violations +description: Set up rules for expenses and enable violations +--- +
+ +Expensify automatically detects expense errors or discrepancies as violations that must be corrected. You can also set rules for a workspace that will trigger a violation if the rule is not met. These rules can be set for categories, tags, and even for specific domain groups. + +When reviewing submitted expense reports, approvers will see violations highlighted with an exclamation mark. There are two types of violations: +- **Yellow**: Automated highlights that require attention but may not require corrective action. For example, if a receipt was SmartScanned and then the amount was modified, a yellow violation will be added to call out the change for review. +- **Red**: Violations directly tied to your workspace settings. These violations must be addressed before the report can be submitted and reimbursed. + +You can hover over the icon to see a brief description, and you can find more detailed information below the list of expenses. + +{% include info.html %} +If your workspace has automations set to automatically submit reports for approval, the report that contains violations will not be submitted automatically until the violations are corrected. (However, if a comment is added to an expense, it will override the violation as the member is providing a reason for submission *unless* domain workspace rules are set to be strictly enforced, as detailed near the bottom of this article.) +{% include end-info.html %} + +# Enable or disable expense violations + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Expenses** tab on the left. +5. Click the “Enable violations” toggle. +6. If desired, enter the expense rules that will be used to create violations: + - **Max expense age (days)**: How old an expense can be + - **Max expense amount**: How much a single expense can cost + - **Receipt required amount**: How much a single expense can cost before a receipt is required + +{% include info.html %} +Expensify includes certain system mandatory violations that can't be disabled, even if your policy has violations turned off. +{% include end-info.html %} + +# Set category rules + +Admin on a Control workspace can enable specific rules for each category, including setting expense caps for specific categories, requiring receipts, and more. These rules can allow you to have a default expense limit of $2,500 but to only allow a daily entertainment limit of $150 per person. You can also choose to not require receipts for mileage or per diem expenses. + +To set up category rules, +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Click **Edit** to the right of the category. +6. Enter your category rules, as desired: + - **GL Code and Payroll Code**: You can add general ledger (GL) or payroll codes to the category for accounting. GL codes populate automatically if you have an accounting integration connected with Expensify. + - **Max Amount**: You can set specific expense caps for the expense category. Use the Limit Type dropdown to determine if the amount is set per individual expense or per day, then enter the maximum amount into this field. + - **Receipts**: You can determine whether receipts are required for the category. For example, many companies disable receipt requirements for toll expenses. + - **Description**: You can determine whether a description is required for expenses under this category. + - **Description Hint**: You can add a hint in the description field to prompt the expense creator on what they should enter into the description field for expenses under this category. + - **Approver**: You can set a specific approver for expenses labeled with this category. + +If users are in violation of these rules, the violations will be shown in red on the report. + +{% include info.html %} +If Scheduled Submit is enabled on a workspace, expenses with category violations will not be auto-submitted unless the expense has a comment added. +{% include end-info.html %} + +# Make categories required + +This means all expenses must be coded with a Category. + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Enable the “People must categorize expenses” toggle. + +Each Workspace Member will now be required to select a category for their expense. If they do not select a category, the report will receive a violation, which can prevent submission if Scheduled Submit is enabled. + +# Make tags required + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Tags** tab on the left. +5. Enable the “People must tag expenses” toggle. + +Each Workspace Member will now be required to select a tag for their expense before they’re able to submit it. + +# Require strict compliance by domain group + +You can require strict compliance to require members of a specific domain group to submit reports that meet **all** workspace rules before they can submit their expense report—even if they add a note. Every rule and regulation on the workspace must be met before a report can be submitted. + +{% include info.html %} +This will prevent members from submitting any reports where a manager has granted them a manual exception for any of the workspace rules. +{% include end-info.html %} + +To enable strict domain group compliance for reports, + +1. Hover over Settings, then click **Domains**. +2. Click the **Groups** tab on the left. +3. Click **Edit** to the right of the desired workspace name. +4. Enable the “Strictly enforce expense workspace rules” toggle. + +# FAQs + +**Why can’t my employees see the categories on their expenses?** + +The employee may have their default workspace set as their personal workspace. Look under the details section on top right of the report to ensure it is being reported under the correct workspace. + +**Will the account numbers from our accounting system (QuickBooks Online, Sage Intacct, etc.) show in the category list for employees?** + +The general ledger (GL) account numbers are visible only for Workspace Admins in the workspace settings when they are part of a control workspace. This information is not visible to other members of the workspace. However, if you wish to have this information available to your employees when they are categorizing their expenses, you can edit the account name in your accounting software to include the GL number (for example, Accounts Payable - 12345). + +**What causes a category violation?** + +- An expense is categorized with a category that is not included in the workspace's categories. This may happen if the employee creates an expense under the wrong workspace, which will cause a "category out of workspace" violation. +- If the workspace categories are being imported from an accounting integration and they’ve been updated in the accounting system but not in Expensify, this can cause an old category to still be in use on an open report which would throw a violation on submission. Simply reselect a proper category to clear violation. + +
From e9a15d7d7790bed485cc18ce40cddbcd45ac1d32 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 28 Mar 2024 22:19:08 +0100 Subject: [PATCH 024/124] fix: change copy on rate selection page --- src/languages/en.ts | 3 ++- src/languages/es.ts | 3 ++- src/languages/types.ts | 3 +++ src/pages/iou/request/step/IOURequestStepRate.tsx | 8 +++++--- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index de5d4d80ea48..f45e4bd1c555 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -49,6 +49,7 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, + ReimbursementRateUnit, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -726,7 +727,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', - chooseARate: 'Choose a rate to use below', + chooseARate: ({unit}: ReimbursementRateUnit) => `Select a workspace reimbursement rate per ${unit}`, }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index c00ec12216fd..7fc558f9673b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -48,6 +48,7 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, + ReimbursementRateUnit, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -724,7 +725,7 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', - chooseARate: 'Elige una tarifa para utilizar a continuación', + chooseARate: ({unit}: ReimbursementRateUnit) => `Seleccione una tasa de reembolso del espacio de trabajo por ${unit}`, }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/languages/types.ts b/src/languages/types.ts index c365363f84af..86f4b4709181 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -297,6 +297,8 @@ type HeldRequestParams = {comment: string}; type DistanceRateOperationsParams = {count: number}; +type ReimbursementRateUnit = {unit: string}; + export type { AdminCanceledRequestParams, ApprovedAmountParams, @@ -400,4 +402,5 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, + ReimbursementRateUnit, }; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index c4e638a48c2f..8395dbc60ffa 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -45,16 +45,18 @@ function IOURequestStepRate({ const {translate, toLocaleDigit} = useLocalize(); const rates = DistanceRequestUtils.getMileageRates(policy?.id); - const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? '0'; + const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? rates[0]?.customUnitRateID; const data = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), keyForList: rate.name ?? '', value: rate.customUnitRateID, - isSelected: lastSelectedRate === rate.customUnitRateID, + isSelected: lastSelectedRate ? lastSelectedRate === rate.customUnitRateID : Boolean(rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE), })); + const unit = Object.values(rates)[0]?.unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); + const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; function selectDistanceRate(customUnitRateID: string) { @@ -70,7 +72,7 @@ function IOURequestStepRate({ shouldShowWrapper={Boolean(backTo)} testID="rate" > - {translate('iou.chooseARate')} + {translate('iou.chooseARate', {unit})} Date: Fri, 29 Mar 2024 14:04:01 +0100 Subject: [PATCH 025/124] fix: minor fix --- src/pages/iou/request/step/IOURequestStepParticipants.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index a66387709f61..7511b2f8d06e 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -30,8 +30,8 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, - // eslint-disable-next-line - lastSelectedDistanceRates: PropTypes.object, + // eslint-disable-next-line react/require-default-props + lastSelectedDistanceRates: PropTypes.shape({}), }; const defaultProps = { From 4217a9838ef4002f76ec3d917f956e2ca5562ce1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 29 Mar 2024 16:31:48 +0100 Subject: [PATCH 026/124] fix: remove a comment --- src/libs/DistanceRequestUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index a676e2617f72..0aa5a0c4b3a5 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -94,7 +94,6 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string return convertedDistance.toFixed(2); } -// TODO: I wonder if it would be better to refactor these functions to pass params in an object /** * @param hasRoute Whether the route exists for the distance request * @param unit Unit that should be used to display the distance From c7b8854b701d3a6c04a5d5cbf30c816c00a5651c Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 4 Apr 2024 21:48:45 +0200 Subject: [PATCH 027/124] fix: display distance and rate properly --- src/CONST.ts | 3 ++- src/components/ReportActionItem/MoneyRequestView.tsx | 9 ++++----- src/libs/TransactionUtils.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d33cf174cf48..e1d055d89028 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3,11 +3,12 @@ import dateAdd from 'date-fns/add'; import dateSubtract from 'date-fns/sub'; import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; +import type {Unit} from '@src/types/onyx/Policy'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; type RateAndUnit = { - unit: string; + unit: Unit; rate: number; }; type CurrencyDefaultMileageRate = Record; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 6f184c10f5d6..597b0494928d 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -176,11 +176,10 @@ function MoneyRequestView({ const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '0'; const rates = DistanceRequestUtils.getMileageRates(policy?.id); - const {unit, currency, rate} = rates[rateID as string] ?? { - unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, - currency: CONST.CURRENCY.USD, - rate: 0, - }; + const currency = policy ? policy.outputCurrency : PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(currency) : rates[rateID as string]; + const {unit, rate} = mileageRate; const rateToDisplay = DistanceRequestUtils.getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index be6822b936c4..2b5f5aff4a1b 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -624,7 +624,7 @@ function getEnabledTaxRateCount(options: TaxRates) { * Check if the customUnitRateID has a value default for P2P distance requests */ -function isCustomUnitRateIDForP2P(transaction: Transaction): boolean { +function isCustomUnitRateIDForP2P(transaction: OnyxEntry): boolean { return transaction?.comment?.customUnit?.customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; } From b16107aae43d7f37a835fe0cac32122b0fcf9814 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 4 Apr 2024 22:07:21 +0200 Subject: [PATCH 028/124] fix: minor fixes --- src/CONST.ts | 2 +- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e1d055d89028..3ce64396a538 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3,9 +3,9 @@ import dateAdd from 'date-fns/add'; import dateSubtract from 'date-fns/sub'; import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; -import type {Unit} from '@src/types/onyx/Policy'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; +import type {Unit} from './types/onyx/Policy'; type RateAndUnit = { unit: Unit; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 4de7d52e9e81..91274c119625 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -790,7 +790,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ /> ), shouldShow: isDistanceRequest && canUseP2PDistanceRequests, - isSupplementary: true, + isSupplementary: false, }, { item: ( @@ -811,7 +811,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ /> ), shouldShow: isDistanceRequest && canUseP2PDistanceRequests, - isSupplementary: true, + isSupplementary: false, }, { item: ( From 3bd9aad21f9666b9a863a41318bd5af05be1fdc8 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 5 Apr 2024 17:39:53 +0200 Subject: [PATCH 029/124] fix: apply requested changes --- ...neyTemporaryForRefactorRequestConfirmationList.js | 6 +++--- src/languages/en.ts | 4 ++-- src/languages/es.ts | 4 ++-- src/languages/types.ts | 5 +++-- .../iou/request/step/IOURequestStepParticipants.js | 12 +++++++----- src/pages/iou/request/step/IOURequestStepRate.tsx | 7 ++++--- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 91274c119625..51436b5d8737 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -769,7 +769,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ /> ), shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, - isSupplementary: true, + isSupplementary: false, }, { item: ( @@ -796,7 +796,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( ), shouldShow: isDistanceRequest && canUseP2PDistanceRequests, diff --git a/src/languages/en.ts b/src/languages/en.ts index 55313718222d..4c93e8bb6207 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -49,7 +49,7 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - ReimbursementRateUnit, + ReimbursementRateParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -737,7 +737,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', - chooseARate: ({unit}: ReimbursementRateUnit) => `Select a workspace reimbursement rate per ${unit}`, + chooseARate: ({unit}: ReimbursementRateParams) => `Select a workspace reimbursement rate per ${unit}`, }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 108a781bb298..406466d44489 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -48,7 +48,7 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - ReimbursementRateUnit, + ReimbursementRateParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -735,7 +735,7 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', - chooseARate: ({unit}: ReimbursementRateUnit) => `Seleccione una tasa de reembolso del espacio de trabajo por ${unit}`, + chooseARate: ({unit}: ReimbursementRateParams) => `Seleccione una tasa de reembolso del espacio de trabajo por ${unit}`, }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/languages/types.ts b/src/languages/types.ts index 86f4b4709181..59e248e4e43a 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -1,4 +1,5 @@ import type {ReportAction} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; import type en from './en'; type AddressLineParams = { @@ -297,7 +298,7 @@ type HeldRequestParams = {comment: string}; type DistanceRateOperationsParams = {count: number}; -type ReimbursementRateUnit = {unit: string}; +type ReimbursementRateParams = {unit: Unit}; export type { AdminCanceledRequestParams, @@ -402,5 +403,5 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, - ReimbursementRateUnit, + ReimbursementRateParams, }; diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index f66d998e1d1f..c6ae0c0fdb0e 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -110,13 +110,15 @@ function IOURequestStepParticipants({ IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); + const participant = val[0]; + // change customUnitRateID when choosing - if (val[0].isPolicyExpenseChat && TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { + if (participant.isPolicyExpenseChat && TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { let customUnitRateID = ''; - if (val[0].policyID && lastSelectedDistanceRates[val[0].policyID]) { - customUnitRateID = lastSelectedDistanceRates[val[0].policyID]; + if (lastSelectedDistanceRates[participant.policyID]) { + customUnitRateID = lastSelectedDistanceRates[participant.policyID]; } else { - const policy = ReportUtils.getPolicy(val[0].policyID); + const policy = ReportUtils.getPolicy(participant.policyID); const defaultRate = DistanceRequestUtils.getDefaultMileageRate(policy); customUnitRateID = defaultRate ? defaultRate.customUnitRateID : ''; } @@ -135,7 +137,7 @@ function IOURequestStepParticipants({ } // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. - selectedReportID.current = lodashGet(val, '[0].reportID', reportID); + selectedReportID.current = participant.reportID; }, [iouType, participants, transactionID, transaction, reportID, updateRouteParams, lastSelectedDistanceRates], ); diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 8395dbc60ffa..43f37a97eb76 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -14,6 +14,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; import StepScreenWrapper from './StepScreenWrapper'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; @@ -47,7 +48,7 @@ function IOURequestStepRate({ const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? rates[0]?.customUnitRateID; - const data = Object.values(rates).map((rate) => ({ + const sections = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), keyForList: rate.name ?? '', @@ -55,7 +56,7 @@ function IOURequestStepRate({ isSelected: lastSelectedRate ? lastSelectedRate === rate.customUnitRateID : Boolean(rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE), })); - const unit = Object.values(rates)[0]?.unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); + const unit = (Object.values(rates)[0]?.unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer')) as Unit; const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; @@ -75,7 +76,7 @@ function IOURequestStepRate({ {translate('iou.chooseARate', {unit})} selectDistanceRate(value ?? '')} initiallyFocusedOptionKey={initiallyFocusedOption} From bd83588de6a8763f62aedbdbddc7e038640f8ad5 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 5 Apr 2024 17:40:48 +0200 Subject: [PATCH 030/124] fix: revert unnecessary change --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 51436b5d8737..755871c3d551 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -796,7 +796,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( ), shouldShow: isDistanceRequest && canUseP2PDistanceRequests, From 7712ceca4d980517d9330caf155ee50357923f19 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 8 Apr 2024 15:27:17 +0200 Subject: [PATCH 031/124] fix: get rates from withOnyx hoc, rename DefaultMileageRate --- src/components/MoneyRequestConfirmationList.tsx | 4 ++-- src/libs/DistanceRequestUtils.ts | 10 +++++----- src/pages/iou/request/step/IOURequestStepRate.tsx | 12 +++++++++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index e1e39279336f..86506933e6f4 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -12,7 +12,7 @@ import usePermissions from '@hooks/usePermissions'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import type {DefaultMileageRate} from '@libs/DistanceRequestUtils'; +import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Log from '@libs/Log'; @@ -64,7 +64,7 @@ type MoneyRequestConfirmationListOnyxProps = { session: OnyxEntry; /** Unit and rate used for if the money request is a distance request */ - mileageRate: OnyxEntry; + mileageRate: OnyxEntry; }; type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & { /** Callback to inform parent modal of success */ diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 0aa5a0c4b3a5..c930e6ec9068 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -8,7 +8,7 @@ import type Policy from '@src/types/onyx/Policy'; import * as CurrencyUtils from './CurrencyUtils'; import * as PolicyUtils from './PolicyUtils'; -type DefaultMileageRate = { +type MileageRate = { customUnitRateID?: string; rate?: number; currency?: string; @@ -38,7 +38,7 @@ Onyx.connect({ * @returns [currency] - The currency associated with the rate. * @returns [unit] - The unit of measurement for the distance. */ -function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | null { +function getDefaultMileageRate(policy: OnyxEntry): MileageRate | null { if (!policy?.customUnits) { return null; } @@ -180,8 +180,8 @@ function getDistanceMerchant( * * @returns An array of mileage rates or an empty array if not found. */ -function getMileageRates(policyID?: string): Record { - const mileageRates: Record = {}; +function getMileageRates(policyID?: string): Record { + const mileageRates: Record = {}; if (!policyID) { return mileageRates; @@ -239,4 +239,4 @@ export default { getRateForP2P, }; -export type {DefaultMileageRate}; +export type {MileageRate}; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 43f37a97eb76..3dee03fa0f60 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -8,6 +8,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as IOU from '@libs/actions/IOU'; import compose from '@libs/compose'; +import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; @@ -33,6 +34,9 @@ type Props = { backTo: Route; }; }; + + /** Mileage rates */ + rates: Record; }; function IOURequestStepRate({ @@ -41,10 +45,10 @@ function IOURequestStepRate({ params: {backTo}, }, lastSelectedDistanceRates = {}, + rates, }: Props) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const rates = DistanceRequestUtils.getMileageRates(policy?.id); const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? rates[0]?.customUnitRateID; @@ -99,6 +103,12 @@ export default compose( lastSelectedDistanceRates: { key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, }, + rates: { + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS + selector: (policy) => DistanceRequestUtils.getMileageRates(policy ? policy.id : ''), + }, }), // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS )(IOURequestStepRate); From 72d31422b1fc1d6f1756cf4591f103a4975ec1c2 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 9 Apr 2024 01:13:41 +0200 Subject: [PATCH 032/124] fix: apply requested changes --- src/CONST.ts | 2 +- ...oraryForRefactorRequestConfirmationList.js | 12 +++--- .../ReportActionItem/MoneyRequestView.tsx | 12 +++++- src/libs/DistanceRequestUtils.ts | 42 ++++++++++++++----- src/libs/Permissions.ts | 1 + src/libs/actions/IOU.ts | 35 +++++++++++----- .../step/IOURequestStepParticipants.js | 41 ++---------------- .../iou/request/step/IOURequestStepRate.tsx | 6 +-- 8 files changed, 78 insertions(+), 73 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index a8584ab3859d..fb1d2ac7d288 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4286,6 +4286,6 @@ const CONST = { type Country = keyof typeof CONST.ALL_COUNTRIES; -export type {Country}; +export type {Country, RateAndUnit}; export default CONST; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 1d23b80ff099..70f12af2dd42 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -261,19 +261,16 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeTrackExpense = iouType === CONST.IOU.TYPE.TRACK_EXPENSE; const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit); - // if there is no policyID that means that the transaction was started from Global Create and the participant is not a policy - const personalPolicy = policyID === CONST.POLICY.ID_FAKE || !policyID ? PolicyUtils.getPersonalPolicy() : policy; + const customUnitRateID = lodashGet(transaction, 'comment.customUnit.customUnitRateID', ''); - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(personalPolicy.outputCurrency) - : mileageRates[transaction.comment.customUnit.customUnitRateID]; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policy.outputCurrency) : mileageRates[customUnitRateID]; const {unit, rate} = mileageRate || { unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * 100, }; - const currency = mileageRate.currency || personalPolicy.outputCurrency; + const currency = mileageRate && mileageRate.currency ? mileageRate.currency : policy.outputCurrency; const distance = lodashGet(transaction, 'routes.route0.distance', 0); const taxRates = lodashGet(policy, 'taxRates', {}); @@ -1097,10 +1094,11 @@ export default compose( }, mileageRates: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - selector: (policy) => DistanceRequestUtils.getMileageRates(policy ? policy.id : ''), + selector: DistanceRequestUtils.getMileageRates, }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + selector: (policy, props) => (!policy || (props && props.policyID) === CONST.POLICY.ID_FAKE ? PolicyUtils.getPersonalPolicy() : policy), }, }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 0f48eb9d683f..2dff075afc14 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -21,6 +21,7 @@ import type {ViolationField} from '@hooks/useViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -64,6 +65,9 @@ type MoneyRequestViewOnyxPropsWithoutTransaction = { /** The actions from the parent report */ parentReportActions: OnyxEntry; + + /** The rates for the policy */ + rates: Record; }; type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutTransaction & { @@ -90,6 +94,7 @@ function MoneyRequestView({ policy, transactionViolations, shouldShowAnimatedBackground, + rates, }: MoneyRequestViewProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -172,15 +177,14 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; const hasRoute = TransactionUtils.hasRoute(transaction); - const distance = transaction?.routes?.route0?.distance ?? 0; const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '0'; - const rates = DistanceRequestUtils.getMileageRates(policy?.id); const currency = policy ? policy.outputCurrency : PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(currency) : rates[rateID as string]; const {unit, rate} = mileageRate; + const distance = DistanceRequestUtils.getDistanceFromMerchant(transactionMerchant, unit); const rateToDisplay = DistanceRequestUtils.getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); @@ -554,6 +558,10 @@ export default withOnyx `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, canEvict: false, }, + rates: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + selector: DistanceRequestUtils.getMileageRates, + }, })( withOnyx({ transaction: { diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index c930e6ec9068..61629c5d58bf 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,10 +1,12 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import type {RateAndUnit} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import * as CurrencyUtils from './CurrencyUtils'; import * as PolicyUtils from './PolicyUtils'; @@ -28,6 +30,9 @@ Onyx.connect({ }, }); +const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters +const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter + /** * Retrieves the default mileage rate based on a given policy. * @@ -38,7 +43,7 @@ Onyx.connect({ * @returns [currency] - The currency associated with the rate. * @returns [unit] - The unit of measurement for the distance. */ -function getDefaultMileageRate(policy: OnyxEntry): MileageRate | null { +function getDefaultMileageRate(policy: OnyxEntry | EmptyObject): MileageRate | null { if (!policy?.customUnits) { return null; } @@ -71,9 +76,6 @@ function getDefaultMileageRate(policy: OnyxEntry): MileageRate | null { * @returns The converted distance in the specified unit. */ function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { - const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters - const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter - switch (unit) { case CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS: return distanceInMeters * METERS_TO_KM; @@ -176,20 +178,18 @@ function getDistanceMerchant( /** * Retrieves the mileage rates for given policy. * - * @param policyID - The policy ID from which to extract the mileage rates. + * @param policy - The policy from which to extract the mileage rates. * * @returns An array of mileage rates or an empty array if not found. */ -function getMileageRates(policyID?: string): Record { +function getMileageRates(policy: OnyxEntry): Record { const mileageRates: Record = {}; - if (!policyID) { + if (!policy) { return mileageRates; } - const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? null; - - if (!policy || !policy?.customUnits) { + if (!policy?.customUnits) { return mileageRates; } @@ -211,7 +211,7 @@ function getMileageRates(policyID?: string): Record { return mileageRates; } -function getRateForP2P(currency: string) { +function getRateForP2P(currency: string): RateAndUnit { return CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; } @@ -229,6 +229,25 @@ function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): n return Math.round(roundedDistance * rate); } +/** + * Extracts the distance from a merchant string. + * + * @param merchant - The merchant string containing the distance. + * @returns The distance extracted from the merchant string. + */ +function getDistanceFromMerchant(merchant: string | undefined, unit: Unit): number { + if (!merchant) { + return 0; + } + + const distance = Number(merchant.split(' ')[0]); + if (!distance) { + return 0; + } + + return unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS ? distance / METERS_TO_KM : distance / METERS_TO_MILES; +} + export default { getDefaultMileageRate, getDistanceMerchant, @@ -237,6 +256,7 @@ export default { getMileageRates, getDistanceForDisplay, getRateForP2P, + getDistanceFromMerchant, }; export type {MileageRate}; diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 1973e665b20f..e71359724677 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -27,6 +27,7 @@ function canUseTrackExpense(betas: OnyxEntry): boolean { } function canUseP2PDistanceRequests(betas: OnyxEntry): boolean { + return true; return !!betas?.includes(CONST.BETAS.P2P_DISTANCE_REQUESTS) || canUseAllBetas(betas); } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0240b711d804..e122078c17d9 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -284,13 +284,15 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, waypoint0: {}, waypoint1: {}, }; - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; - let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; - if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { - customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; + if (!isFromGlobalCreate) { + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; + let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { + customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; + } + comment.customUnit = {customUnitRateID}; } - comment.customUnit = {customUnitRateID}; } // Store the transaction in Onyx and mark it as not saved so it can be cleaned up later @@ -418,13 +420,24 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: }); } -/** Set the last selected distance rate for policy */ -function setLastSelectedDistanceRates(policyID: string, rateID: string) { - Onyx.merge(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {[policyID]: rateID}); +/** Set custom unit rateID for the transaction draft */ +function setCustomUnitRateID(transactionID: string, reportID: string) { + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; + const policy = getPolicy(report?.policyID) ?? null; + + let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; + + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { + customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {customUnit: {customUnitRateID}}}); } /** Update transaction distance rate */ -function updateDistanceRequestRate(transactionID: string, rateID: string) { +function updateDistanceRequestRate(transactionID: string, rateID: string, policyID: string) { + Onyx.merge(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {[policyID]: rateID}); Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {customUnit: {customUnitRateID: rateID}}}); } @@ -5296,6 +5309,7 @@ export { setMoneyRequestTaxAmount, setMoneyRequestTaxRate, setShownHoldUseExplanation, + setCustomUnitRateID, updateMoneyRequestDate, updateMoneyRequestBillable, updateMoneyRequestMerchant, @@ -5317,6 +5331,5 @@ export { trackExpense, canIOUBePaid, canApproveIOU, - setLastSelectedDistanceRates, updateDistanceRequestRate, }; diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index c6ae0c0fdb0e..734adfbc486c 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -1,21 +1,16 @@ import {useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; -import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; import StepScreenWrapper from './StepScreenWrapper'; @@ -29,9 +24,6 @@ const propTypes = { /* Onyx Props */ /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, - - // eslint-disable-next-line react/require-default-props - lastSelectedDistanceRates: PropTypes.shape({}), }; const defaultProps = { @@ -44,7 +36,6 @@ function IOURequestStepParticipants({ }, transaction, transaction: {participants = []}, - lastSelectedDistanceRates = {}, }) { const {translate} = useLocalize(); const navigation = useNavigation(); @@ -109,23 +100,7 @@ function IOURequestStepParticipants({ } IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); - - const participant = val[0]; - - // change customUnitRateID when choosing - if (participant.isPolicyExpenseChat && TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { - let customUnitRateID = ''; - if (lastSelectedDistanceRates[participant.policyID]) { - customUnitRateID = lastSelectedDistanceRates[participant.policyID]; - } else { - const policy = ReportUtils.getPolicy(participant.policyID); - const defaultRate = DistanceRequestUtils.getDefaultMileageRate(policy); - customUnitRateID = defaultRate ? defaultRate.customUnitRateID : ''; - } - IOU.updateDistanceRequestRate(transactionID, customUnitRateID); - } else if (!TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { - IOU.updateDistanceRequestRate(transactionID, CONST.CUSTOM_UNITS.FAKE_P2P_ID); - } + IOU.setCustomUnitRateID(transactionID, reportID); numberOfParticipants.current = val.length; @@ -137,9 +112,9 @@ function IOURequestStepParticipants({ } // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. - selectedReportID.current = participant.reportID; + selectedReportID.current = val[0].reportID; }, - [iouType, participants, transactionID, transaction, reportID, updateRouteParams, lastSelectedDistanceRates], + [iouType, participants, transactionID, reportID, updateRouteParams], ); const goToNextStep = useCallback( @@ -190,12 +165,4 @@ IOURequestStepParticipants.displayName = 'IOURequestStepParticipants'; IOURequestStepParticipants.propTypes = propTypes; IOURequestStepParticipants.defaultProps = defaultProps; -export default compose( - withWritableReportOrNotFound, - withFullTransactionOrNotFound, - withOnyx({ - lastSelectedDistanceRates: { - key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - }, - }), -)(IOURequestStepParticipants); +export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepParticipants); diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 3dee03fa0f60..59a5ab67a8a7 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -65,8 +65,7 @@ function IOURequestStepRate({ const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; function selectDistanceRate(customUnitRateID: string) { - IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); - IOU.updateDistanceRequestRate('1', customUnitRateID); + IOU.updateDistanceRequestRate('1', customUnitRateID, policy?.id ?? ''); Navigation.goBack(backTo); } @@ -106,8 +105,7 @@ export default compose( rates: { // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, - // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS - selector: (policy) => DistanceRequestUtils.getMileageRates(policy ? policy.id : ''), + selector: DistanceRequestUtils.getMileageRates, }, }), // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS From fcbdddd98235bd2e8b3004fe0f6681eb126f76f1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 9 Apr 2024 21:09:49 +0200 Subject: [PATCH 033/124] fix: remove unnecessary line --- src/libs/Permissions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index e71359724677..1973e665b20f 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -27,7 +27,6 @@ function canUseTrackExpense(betas: OnyxEntry): boolean { } function canUseP2PDistanceRequests(betas: OnyxEntry): boolean { - return true; return !!betas?.includes(CONST.BETAS.P2P_DISTANCE_REQUESTS) || canUseAllBetas(betas); } From 7bf1e976e6e7db510ef1afaf1f0cc54604d423a5 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 9 Apr 2024 21:24:42 +0200 Subject: [PATCH 034/124] fix: change hardcoded transactionID value for the one from the route params --- src/pages/iou/request/step/IOURequestStepRate.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 59a5ab67a8a7..5952f37c81b3 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -32,6 +32,9 @@ type Props = { params: { /** The route to go back to */ backTo: Route; + + /** The ID of the transaction being configured */ + transactionID: string; }; }; @@ -42,7 +45,7 @@ type Props = { function IOURequestStepRate({ policy, route: { - params: {backTo}, + params: {backTo, transactionID}, }, lastSelectedDistanceRates = {}, rates, @@ -65,7 +68,7 @@ function IOURequestStepRate({ const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; function selectDistanceRate(customUnitRateID: string) { - IOU.updateDistanceRequestRate('1', customUnitRateID, policy?.id ?? ''); + IOU.updateDistanceRequestRate(transactionID, customUnitRateID, policy?.id ?? ''); Navigation.goBack(backTo); } From fc2c2601fef6c21771d9039d583a9f970c34d8ba Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 10 Apr 2024 14:44:40 +0200 Subject: [PATCH 035/124] fix: type IOURequestStepRate properly --- src/libs/Navigation/types.ts | 6 ++ .../iou/request/step/IOURequestStepRate.tsx | 64 +++++++------------ .../step/withWritableReportOrNotFound.tsx | 1 + 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 1f1e7ec9a459..0bc20cb2baa1 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -439,6 +439,12 @@ type MoneyRequestNavigatorParamList = { iouType: string; reportID: string; }; + [SCREENS.MONEY_REQUEST.STEP_RATE]: { + iouType: ValueOf; + transactionID: string; + backTo: Routes; + reportID: string; + }; [SCREENS.MONEY_REQUEST.STEP_CONFIRMATION]: { action: ValueOf; iouType: ValueOf; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 5952f37c81b3..e56ea39e9b13 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -7,41 +7,31 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as IOU from '@libs/actions/IOU'; -import compose from '@libs/compose'; import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import type {Policy} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import StepScreenWrapper from './StepScreenWrapper'; +import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -type Props = { +type IOURequestStepRateOnyxProps = { /** Object of last selected rates for the policies */ - lastSelectedDistanceRates: Record; + lastSelectedDistanceRates: OnyxEntry>; /** Policy details */ policy: OnyxEntry; - /** The route object passed to this screen */ - route: { - /** The params passed to this screen */ - params: { - /** The route to go back to */ - backTo: Route; - - /** The ID of the transaction being configured */ - transactionID: string; - }; - }; - /** Mileage rates */ rates: Record; }; +type IOURequestStepRateProps = IOURequestStepRateOnyxProps & WithWritableReportOrNotFoundProps; + function IOURequestStepRate({ policy, route: { @@ -49,23 +39,23 @@ function IOURequestStepRate({ }, lastSelectedDistanceRates = {}, rates, -}: Props) { +}: IOURequestStepRateProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? rates[0]?.customUnitRateID; + const lastSelectedRateID = lastSelectedDistanceRates?.[policy?.id ?? '0'] ?? rates[0]?.customUnitRateID ?? ''; const sections = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), keyForList: rate.name ?? '', value: rate.customUnitRateID, - isSelected: lastSelectedRate ? lastSelectedRate === rate.customUnitRateID : Boolean(rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE), + isSelected: lastSelectedRateID ? lastSelectedRateID === rate.customUnitRateID : Boolean(rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE), })); const unit = (Object.values(rates)[0]?.unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer')) as Unit; - const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; + const initiallyFocusedOption = rates[lastSelectedRateID]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; function selectDistanceRate(customUnitRateID: string) { IOU.updateDistanceRequestRate(transactionID, customUnitRateID, policy?.id ?? ''); @@ -93,23 +83,17 @@ function IOURequestStepRate({ IOURequestStepRate.displayName = 'IOURequestStepRate'; -export default compose( - withWritableReportOrNotFound, - withOnyx({ - // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS - policy: { - // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - }, - // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS - lastSelectedDistanceRates: { - key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - }, - rates: { - // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, - selector: DistanceRequestUtils.getMileageRates, - }, - }), - // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS -)(IOURequestStepRate); +const IOURequestStepRateWithOnyx = withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, + rates: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '0'}`, + selector: DistanceRequestUtils.getMileageRates, + }, +})(IOURequestStepRate); + +export default withWritableReportOrNotFound(IOURequestStepRateWithOnyx); diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 515f6f97f280..b7cd77c6c8a6 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -21,6 +21,7 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_WAYPOINT | typeof SCREENS.MONEY_REQUEST.STEP_DESCRIPTION | typeof SCREENS.MONEY_REQUEST.STEP_CATEGORY + | typeof SCREENS.MONEY_REQUEST.STEP_RATE | typeof SCREENS.MONEY_REQUEST.STEP_CONFIRMATION | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE | typeof SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT; From 7fb35ab243994dac4573956f4eb317712d364bad Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 10 Apr 2024 16:46:30 +0200 Subject: [PATCH 036/124] fix: minor fix --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 623de07a05f8..70b0b7e3e6b2 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -181,7 +181,7 @@ function MoneyRequestView({ const currency = policy ? policy.outputCurrency : PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(currency) : rates[rateID as string]; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(currency) : rates[rateID as string] ?? {}; const {unit, rate} = mileageRate; const distance = DistanceRequestUtils.getDistanceFromMerchant(transactionMerchant, unit); From ddf117d2ae48aaf939f0b69e2c12ab3d4339fd83 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 10 Apr 2024 20:24:20 +0200 Subject: [PATCH 037/124] fix: minor fixes --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- src/pages/iou/request/step/IOURequestStepParticipants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index ea783e6322b8..cda8a1a7048b 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -253,7 +253,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const theme = useTheme(); const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const {canUseViolations} = usePermissions(); + const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(iouType); const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 734adfbc486c..7dc94af66cd0 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -100,7 +100,7 @@ function IOURequestStepParticipants({ } IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); - IOU.setCustomUnitRateID(transactionID, reportID); + IOU.setCustomUnitRateID(transactionID, val[0].reportID); numberOfParticipants.current = val.length; From 118aa7beef2563d65f1d84cb4dc44ecc5bd8ae12 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 10 Apr 2024 22:18:20 +0200 Subject: [PATCH 038/124] fix: add comment --- src/libs/DistanceRequestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 61629c5d58bf..28de14fba57d 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -244,7 +244,7 @@ function getDistanceFromMerchant(merchant: string | undefined, unit: Unit): numb if (!distance) { return 0; } - + // we need to convert the distance back to meters (it's saved in kilometers or miles in merchant) to pass it to getDistanceForDisplay return unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS ? distance / METERS_TO_KM : distance / METERS_TO_MILES; } From 1511b2e76d8bc6dab733af078c0b70c4c1e46493 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 12:38:20 +0200 Subject: [PATCH 039/124] fix: display only rate when no rate name --- .../iou/request/step/IOURequestStepRate.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index e56ea39e9b13..8d1d7a97af63 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -45,13 +45,17 @@ function IOURequestStepRate({ const lastSelectedRateID = lastSelectedDistanceRates?.[policy?.id ?? '0'] ?? rates[0]?.customUnitRateID ?? ''; - const sections = Object.values(rates).map((rate) => ({ - text: rate.name ?? '', - alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), - keyForList: rate.name ?? '', - value: rate.customUnitRateID, - isSelected: lastSelectedRateID ? lastSelectedRateID === rate.customUnitRateID : Boolean(rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE), - })); + const sections = Object.values(rates).map((rate) => { + const rateForDisplay = DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit); + + return { + text: rate.name ?? rateForDisplay, + alternateText: rate.name ? rateForDisplay : '', + keyForList: rate.customUnitRateID, + value: rate.customUnitRateID, + isSelected: lastSelectedRateID ? lastSelectedRateID === rate.customUnitRateID : Boolean(rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE), + }; + }); const unit = (Object.values(rates)[0]?.unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer')) as Unit; From 5c03b38814489542fb45e354242efcec0c51585b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 13:03:38 +0200 Subject: [PATCH 040/124] fix: remove use of lastSelectedDistanceRate --- src/libs/TransactionUtils.ts | 8 +++++ .../iou/request/step/IOURequestStepRate.tsx | 35 ++++++++++++------- .../step/withFullTransactionOrNotFound.tsx | 1 + 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index d42d0775d4b4..831cc46ff553 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -638,6 +638,13 @@ function isCustomUnitRateIDForP2P(transaction: OnyxEntry): boolean return transaction?.comment?.customUnit?.customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; } +/** + * Check if the customUnitRateID has a value default for P2P distance requests + */ +function getRateID(transaction: OnyxEntry): string | undefined { + return transaction?.comment?.customUnit?.customUnitRateID?.toString(); +} + /** * Gets the default tax name */ @@ -710,6 +717,7 @@ export { getRecentTransactions, hasViolation, isCustomUnitRateIDForP2P, + getRateID, }; export type {TransactionChanges}; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 8d1d7a97af63..915bc0c5c8b5 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -10,19 +10,18 @@ import * as IOU from '@libs/actions/IOU'; import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as TransactionUtils from '@libs/TransactionUtils'; +import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import type {Policy} from '@src/types/onyx'; +import type {Policy, Transaction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import StepScreenWrapper from './StepScreenWrapper'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type IOURequestStepRateOnyxProps = { - /** Object of last selected rates for the policies */ - lastSelectedDistanceRates: OnyxEntry>; - /** Policy details */ policy: OnyxEntry; @@ -30,20 +29,28 @@ type IOURequestStepRateOnyxProps = { rates: Record; }; -type IOURequestStepRateProps = IOURequestStepRateOnyxProps & WithWritableReportOrNotFoundProps; +type IOURequestStepRateProps = IOURequestStepRateOnyxProps & + WithWritableReportOrNotFoundProps & { + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + transaction: OnyxEntry; + }; function IOURequestStepRate({ policy, route: { params: {backTo, transactionID}, }, - lastSelectedDistanceRates = {}, + transaction, rates, }: IOURequestStepRateProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const lastSelectedRateID = lastSelectedDistanceRates?.[policy?.id ?? '0'] ?? rates[0]?.customUnitRateID ?? ''; + const lastSelectedRateID = TransactionUtils.getRateID(transaction) ?? ''; + + const navigateBack = () => { + Navigation.goBack(backTo); + }; const sections = Object.values(rates).map((rate) => { const rateForDisplay = DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit); @@ -63,13 +70,13 @@ function IOURequestStepRate({ function selectDistanceRate(customUnitRateID: string) { IOU.updateDistanceRequestRate(transactionID, customUnitRateID, policy?.id ?? ''); - Navigation.goBack(backTo); + navigateBack(); } return ( Navigation.goBack(backTo)} + onBackButtonPress={navigateBack} shouldShowWrapper={Boolean(backTo)} testID="rate" > @@ -91,13 +98,15 @@ const IOURequestStepRateWithOnyx = withOnyx `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, - lastSelectedDistanceRates: { - key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - }, rates: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '0'}`, selector: DistanceRequestUtils.getMileageRates, }, })(IOURequestStepRate); -export default withWritableReportOrNotFound(IOURequestStepRateWithOnyx); +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepRateWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepRateWithOnyx); +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepRateWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepRateWithWritableReportOrNotFound); + +export default IOURequestStepRateWithFullTransactionOrNotFound; diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index 3d741725032b..d1bbf3956bc2 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -24,6 +24,7 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS | typeof SCREENS.MONEY_REQUEST.STEP_MERCHANT | typeof SCREENS.MONEY_REQUEST.STEP_TAG + | typeof SCREENS.MONEY_REQUEST.STEP_RATE | typeof SCREENS.MONEY_REQUEST.STEP_CONFIRMATION | typeof SCREENS.MONEY_REQUEST.STEP_CATEGORY | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE; From 96c281d4682e765ca245a1a52bc0c3e2207c5128 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 13:45:04 +0200 Subject: [PATCH 041/124] fix: minor fix --- src/pages/iou/request/step/IOURequestStepRate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 915bc0c5c8b5..d7ae3ee0a19e 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -11,13 +11,13 @@ import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; -import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {Policy, Transaction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import StepScreenWrapper from './StepScreenWrapper'; +import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; From 4658af361ed628db87f0384d12cbe2d4c4423f77 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 17:31:41 +0200 Subject: [PATCH 042/124] fix: amend comment --- src/libs/TransactionUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 831cc46ff553..1847e69a21e6 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -639,7 +639,7 @@ function isCustomUnitRateIDForP2P(transaction: OnyxEntry): boolean } /** - * Check if the customUnitRateID has a value default for P2P distance requests + * Get rate ID from the transaction object */ function getRateID(transaction: OnyxEntry): string | undefined { return transaction?.comment?.customUnit?.customUnitRateID?.toString(); From 6234c99ae920095d354dbdf64c897698d7383af9 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 18:25:59 +0200 Subject: [PATCH 043/124] fix: lint --- ...eyTemporaryForRefactorRequestConfirmationList.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 7d382a10f30d..b8c5e5f2417e 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -335,7 +335,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0); IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amount, currency ?? ''); - }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]); + }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency, transactionID]); // Calculate and set tax amount in transaction draft useEffect(() => { @@ -343,11 +343,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); if (transaction?.taxAmount && previousTransactionAmount === transaction?.amount) { - return IOU.setMoneyRequestTaxAmount(transaction?.transactionID, transaction?.taxAmount, true); + return IOU.setMoneyRequestTaxAmount(transactionID, transaction?.taxAmount, true); } IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true); - }, [taxRates?.defaultValue, transaction, previousTransactionAmount]); + }, [taxRates?.defaultValue, transaction, previousTransactionAmount, transactionID]); /** * Returns the participants with amount @@ -475,7 +475,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate ?? 0, currency ?? 'USD', translate, toLocaleDigit); IOU.setMoneyRequestMerchant(transactionID, distanceMerchant, true); - }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transaction]); + }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); // Auto select the category if there is only one enabled category and it is required useEffect(() => { @@ -484,7 +484,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ return; } IOU.setMoneyRequestCategory(transactionID, enabledCategories[0].name); - }, [iouCategory, shouldShowCategories, policyCategories, transaction, isCategoryRequired]); + }, [iouCategory, shouldShowCategories, policyCategories, isCategoryRequired, transactionID]); // Auto select the tag if there is only one enabled tag and it is required useEffect(() => { @@ -500,7 +500,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ if (updatedTagsString !== TransactionUtils.getTag(transaction) && updatedTagsString) { IOU.setMoneyRequestTag(transactionID, updatedTagsString); } - }, [policyTagLists, transaction, policyTags, canUseViolations]); + }, [policyTagLists, transaction, policyTags, canUseViolations, transactionID]); /** */ From 96a70328ed190d8f2e51f724d16d6e6d65f0ac1b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 19:21:11 +0200 Subject: [PATCH 044/124] fix: add missing arg --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index b8c5e5f2417e..e7fc364799c8 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -210,7 +210,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(); + const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(iouType); const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; From 5de20289c5d4076b5a6dbfefca92a41dd1ef08c7 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 20:39:08 +0200 Subject: [PATCH 045/124] fix: minor fix --- src/libs/actions/IOU.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 549e17aa8407..5a8514830571 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -425,7 +425,9 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: }); } -/** Set custom unit rateID for the transaction draft */ +/** + * Set custom unit rateID for the transaction draft + */ function setCustomUnitRateID(transactionID: string, reportID: string) { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; From 80f9b5e99c854023f93fe45c028cef5204c7130a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 11 Apr 2024 22:58:16 +0200 Subject: [PATCH 046/124] fix: recalculate amount, fallback to default rate --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index e7fc364799c8..e87516dc5e00 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -221,15 +221,16 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policy?.outputCurrency ?? CONST.CURRENCY.USD) - : mileageRates?.[customUnitRateID]; - - const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; + : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); const {unit, rate} = mileageRate ?? { unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * 100, }; + const prevRate = usePrevious(rate); + const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); + const currency = (mileageRate as MileageRate)?.currency ?? policy?.outputCurrency ?? CONST.CURRENCY.USD; const distance = transaction?.routes?.route0?.distance ?? 0; From 42206ee397ee97cbcaa9402ec43fb521bdfd0eae Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 12 Apr 2024 13:02:34 +0200 Subject: [PATCH 047/124] fix: extract logic to a new function --- src/libs/actions/IOU.ts | 83 ++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5a8514830571..50405164360f 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -268,6 +268,22 @@ function getPolicy(policyID: string | undefined): OnyxTypes.Policy | EmptyObject return allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? {}; } +/** + * Returns custom unit rate ID for the transaction + */ +function getCustomUnitRateID(reportID: string) { + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; + const policy = getPolicy(report?.policyID) ?? null; + + let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; + + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { + customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; + } + return customUnitRateID; +} + /** * Initialize money request info * @param reportID to attach the transaction to @@ -290,12 +306,7 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, waypoint1: {}, }; if (!isFromGlobalCreate) { - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; - let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; - if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { - customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; - } + const customUnitRateID = getCustomUnitRateID(reportID); comment.customUnit = {customUnitRateID}; } } @@ -362,7 +373,11 @@ function startMoneyRequest(iouType: ValueOf, reportID: st // eslint-disable-next-line @typescript-eslint/naming-convention function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false) { if (removeOriginalCurrency) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { + amount, + currency, + originalCurrency: null, + }); return; } Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency}); @@ -377,7 +392,10 @@ function setMoneyRequestCreated(transactionID: string, created: string, isDraft: function setMoneyRequestCurrency_temporaryForRefactor(transactionID: string, currency: string, removeOriginalCurrency = false, isEditing = false) { const fieldToUpdate = isEditing ? 'modifiedCurrency' : 'currency'; if (removeOriginalCurrency) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {[fieldToUpdate]: currency, originalCurrency: null}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { + [fieldToUpdate]: currency, + originalCurrency: null, + }); return; } Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {[fieldToUpdate]: currency}); @@ -429,15 +447,7 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: * Set custom unit rateID for the transaction draft */ function setCustomUnitRateID(transactionID: string, reportID: string) { - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; - const policy = getPolicy(report?.policyID) ?? null; - - let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; - - if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { - customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; - } + const customUnitRateID = getCustomUnitRateID(reportID); Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {customUnit: {customUnitRateID}}}); } @@ -475,7 +485,14 @@ function resetMoneyRequestInfo(id = '') { function getReceiptError(receipt?: Receipt, filename?: string, isScanRequest = true, errorKey?: number): Errors | ErrorFields { return isEmptyObject(receipt) || !isScanRequest ? ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage', false, errorKey) - : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source?.toString() ?? '', filename: filename ?? ''}, errorKey); + : ErrorUtils.getMicroSecondOnyxErrorObject( + { + error: CONST.IOU.RECEIPT_ERROR, + source: receipt.source?.toString() ?? '', + filename: filename ?? '', + }, + errorKey, + ); } /** Builds the Onyx data for a money request */ @@ -2711,7 +2728,13 @@ function createSplitsAndOnyxData( // Loop through participants creating individual chats, iouReports and reportActionIDs as needed const splitAmount = IOUUtils.calculateAmount(participants.length, amount, currency, false); - const splits: Split[] = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID, amount: IOUUtils.calculateAmount(participants.length, amount, currency, true)}]; + const splits: Split[] = [ + { + email: currentUserEmailForIOUSplit, + accountID: currentUserAccountID, + amount: IOUUtils.calculateAmount(participants.length, amount, currency, true), + }, + ]; const hasMultipleParticipants = participants.length > 1; participants.forEach((participant) => { @@ -5307,10 +5330,25 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On const shouldAddAsReport = !isEmptyObject(chatReport) && ReportUtils.isSelfDM(chatReport); const participants: Participant[] = ReportUtils.isPolicyExpenseChat(chatReport) || shouldAddAsReport - ? [{accountID: 0, reportID: chatReport?.reportID, isPolicyExpenseChat: ReportUtils.isPolicyExpenseChat(chatReport), selected: true}] - : (chatReport?.participantAccountIDs ?? []).filter((accountID) => currentUserAccountID !== accountID).map((accountID) => ({accountID, selected: true})); + ? [ + { + accountID: 0, + reportID: chatReport?.reportID, + isPolicyExpenseChat: ReportUtils.isPolicyExpenseChat(chatReport), + selected: true, + }, + ] + : (chatReport?.participantAccountIDs ?? []) + .filter((accountID) => currentUserAccountID !== accountID) + .map((accountID) => ({ + accountID, + selected: true, + })); - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { + participants, + participantsAutoAssigned: true, + }); } function setMoneyRequestId(id: string) { @@ -5466,6 +5504,7 @@ function unholdRequest(transactionID: string, reportID: string) { {optimisticData, successData, failureData}, ); } + // eslint-disable-next-line rulesdir/no-negated-variables function navigateToStartStepIfScanFileCannotBeRead( receiptFilename: string | undefined, From 592bf7a542290977e5565931023eda7f6c99811f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 12 Apr 2024 22:18:27 +0200 Subject: [PATCH 048/124] fix: fix problem with workspace customRateID --- ...raryForRefactorRequestConfirmationList.tsx | 27 ++++++++++++++++- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/DistanceRequestUtils.ts | 26 +++++++++++++++- src/libs/actions/IOU.ts | 30 ++----------------- .../step/IOURequestStepParticipants.tsx | 4 ++- 6 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index e87516dc5e00..44042a976230 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -66,6 +66,12 @@ type MoneyRequestConfirmationListOnyxProps = { /** Unit and rate used for if the money request is a distance request */ mileageRates: OnyxEntry>; + + /** Mileage rate default for the policy */ + defaultMileageRate: OnyxEntry; + + /** Last selected distance rates */ + lastSelectedDistanceRates: OnyxEntry>; }; type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & { @@ -205,6 +211,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ onToggleBillable, hasSmartScanFailed, reportActionID, + defaultMileageRate, + lastSelectedDistanceRates, }: MoneyRequestConfirmationListProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -217,8 +225,19 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSend = iouType === CONST.IOU.TYPE.SEND; const isTypeTrackExpense = iouType === CONST.IOU.TYPE.TRACK_EXPENSE; + const transactionID = transaction?.transactionID ?? ''; const customUnitRateID = TransactionUtils.getRateID(transaction) ?? ''; + useEffect(() => { + if (customUnitRateID || !canUseP2PDistanceRequests) { + return; + } + if (!customUnitRateID && defaultMileageRate) { + const rateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? defaultMileageRate?.customUnitRateID ?? ''; + IOU.setCustomUnitRateID(transactionID, rateID); + } + }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID]); + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policy?.outputCurrency ?? CONST.CURRENCY.USD) : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); @@ -235,7 +254,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const distance = transaction?.routes?.route0?.distance ?? 0; const taxRates = policy?.taxRates ?? null; - const transactionID = transaction?.transactionID ?? ''; // A flag for showing the categories field const shouldShowCategories = isPolicyExpenseChat && (!!iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); @@ -1031,6 +1049,10 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, + defaultMileageRate: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + selector: DistanceRequestUtils.getDefaultMileageRate, + }, mileageRates: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getMileageRates, @@ -1038,4 +1060,7 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, })(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/languages/en.ts b/src/languages/en.ts index 5ee4be293ff5..bf41b8954e63 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -635,6 +635,7 @@ export default { posted: 'Posted', deleteReceipt: 'Delete receipt', routePending: 'Route pending...', + defaultRate: 'Default rate', receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', missingAmount: 'Missing amount', diff --git a/src/languages/es.ts b/src/languages/es.ts index ae732c6f6418..8423e045d437 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -631,6 +631,7 @@ export default { posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', + defaultRate: 'Tasa predeterminada', receiptScanning: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 28de14fba57d..c673245343b1 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -4,11 +4,13 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {RateAndUnit} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import * as CurrencyUtils from './CurrencyUtils'; import * as PolicyUtils from './PolicyUtils'; +import * as ReportUtils from './ReportUtils'; type MileageRate = { customUnitRateID?: string; @@ -30,6 +32,13 @@ Onyx.connect({ }, }); +let allReports: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter @@ -114,7 +123,7 @@ function getRateForDisplay( toLocaleDigit: LocaleContextProps['toLocaleDigit'], ): string { if (!hasRoute || !rate || !currency) { - return translate('iou.routePending'); + return translate('iou.defaultRate'); } const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); @@ -248,6 +257,20 @@ function getDistanceFromMerchant(merchant: string | undefined, unit: Unit): numb return unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS ? distance / METERS_TO_KM : distance / METERS_TO_MILES; } +/** + * Returns custom unit rate ID for the distance transaction + */ +function getCustomUnitRateID(reportID: string) { + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; + + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { + return ''; + } + + return CONST.CUSTOM_UNITS.FAKE_P2P_ID; +} + export default { getDefaultMileageRate, getDistanceMerchant, @@ -257,6 +280,7 @@ export default { getDistanceForDisplay, getRateForP2P, getDistanceFromMerchant, + getCustomUnitRateID, }; export type {MileageRate}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8dd13ebec7c4..8e9ea0e73726 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -236,14 +236,6 @@ Onyx.connect({ }, }); -let lastSelectedDistanceRates: OnyxEntry = {}; -Onyx.connect({ - key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - callback: (value) => { - lastSelectedDistanceRates = value; - }, -}); - let quickAction: OnyxEntry = {}; Onyx.connect({ key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, @@ -282,22 +274,6 @@ function getPolicy(policyID: string | undefined): OnyxTypes.Policy | EmptyObject return allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? {}; } -/** - * Returns custom unit rate ID for the transaction - */ -function getCustomUnitRateID(reportID: string) { - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; - const policy = getPolicy(report?.policyID) ?? null; - - let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; - - if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { - customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; - } - return customUnitRateID; -} - /** * Initialize money request info * @param reportID to attach the transaction to @@ -320,7 +296,7 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, waypoint1: {}, }; if (!isFromGlobalCreate) { - const customUnitRateID = getCustomUnitRateID(reportID); + const customUnitRateID = DistanceRequestUtils.getCustomUnitRateID(reportID); comment.customUnit = {customUnitRateID}; } } @@ -460,9 +436,7 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: /** * Set custom unit rateID for the transaction draft */ -function setCustomUnitRateID(transactionID: string, reportID: string) { - const customUnitRateID = getCustomUnitRateID(reportID); - +function setCustomUnitRateID(transactionID: string, customUnitRateID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {customUnit: {customUnitRateID}}}); } diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index df57c4a8c53a..76a193a1d5de 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -4,6 +4,7 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -107,7 +108,8 @@ function IOURequestStepParticipants({ } IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); - IOU.setCustomUnitRateID(transactionID, val[0]?.reportID ?? ''); + const rateID = DistanceRequestUtils.getCustomUnitRateID(val[0]?.reportID ?? ''); + IOU.setCustomUnitRateID(transactionID, rateID); numberOfParticipants.current = val.length; From 543113159f2f1330fb767d2cdd085fe4e51b3814 Mon Sep 17 00:00:00 2001 From: kmichel Date: Sun, 14 Apr 2024 00:39:55 -0700 Subject: [PATCH 049/124] fix issue --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 9380ce43c46a..80f0fd596ed6 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -2,7 +2,7 @@ import type {AVPlaybackStatus, VideoFullscreenUpdateEvent} from 'expo-av'; import {ResizeMode, Video, VideoFullscreenUpdate} from 'expo-av'; import type {MutableRefObject} from 'react'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; import type {GestureResponderEvent} from 'react-native'; import {View} from 'react-native'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -172,6 +172,15 @@ function BaseVideoPlayer({ }); }, [currentVideoPlayerRef, handleFullscreenUpdate, handlePlaybackStatusUpdate]); + useLayoutEffect(() => + () => { + if(shouldUseSharedVideoElement || videoPlayerRef.current !== currentVideoPlayerRef.current) { + return; + } + currentVideoPlayerRef.current = null; + } + , [currentVideoPlayerRef, shouldUseSharedVideoElement]); + useEffect(() => { if (!isUploading || !videoPlayerRef.current) { return; From dfa02a37496039f00ed9c35c881fa26c17e70ea8 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 15 Apr 2024 10:10:40 +0200 Subject: [PATCH 050/124] fix: minor fix --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 44042a976230..fc23af450687 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -232,7 +232,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ if (customUnitRateID || !canUseP2PDistanceRequests) { return; } - if (!customUnitRateID && defaultMileageRate) { + if (!customUnitRateID) { const rateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? defaultMileageRate?.customUnitRateID ?? ''; IOU.setCustomUnitRateID(transactionID, rateID); } From 410fdb17d092650dcc795b4edad2b03b40401b0e Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 15 Apr 2024 18:59:41 +0200 Subject: [PATCH 051/124] fix: apply requested changes --- src/ROUTES.ts | 6 ++--- src/SCREENS.ts | 2 +- ...raryForRefactorRequestConfirmationList.tsx | 21 +++++++++--------- .../ReportActionItem/MoneyRequestView.tsx | 13 +++++++++-- src/libs/DistanceRequestUtils.ts | 14 +++++++----- .../ModalStackNavigators/index.tsx | 2 +- src/libs/Navigation/linkingConfig/config.ts | 2 +- src/libs/Navigation/types.ts | 2 +- ...ate.tsx => IOURequestStepDistanceRate.tsx} | 22 +++++++++---------- .../step/withFullTransactionOrNotFound.tsx | 2 +- .../step/withWritableReportOrNotFound.tsx | 2 +- 11 files changed, 51 insertions(+), 37 deletions(-) rename src/pages/iou/request/step/{IOURequestStepRate.tsx => IOURequestStepDistanceRate.tsx} (82%) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a0909b2c582f..e92b29464005 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -370,10 +370,10 @@ const ROUTES = { getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => getUrlWithBackToParam(`${action}/${iouType}/distance/${transactionID}/${reportID}`, backTo), }, - MONEY_REQUEST_STEP_RATE: { - route: ':action/:iouType/rate/:transactionID/:reportID', + MONEY_REQUEST_STEP_DISTANCE_RATE: { + route: ':action/:iouType/distanceRate/:transactionID/:reportID', getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/rate/${transactionID}/${reportID}`, backTo), + getUrlWithBackToParam(`${action}/${iouType}/distanceRate/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_MERCHANT: { route: ':action/:iouType/merchant/:transactionID/:reportID', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 303e6e322799..ac81bf1c5f92 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -148,7 +148,7 @@ const SCREENS = { STEP_DATE: 'Money_Request_Step_Date', STEP_DESCRIPTION: 'Money_Request_Step_Description', STEP_DISTANCE: 'Money_Request_Step_Distance', - STEP_RATE: 'Money_Request_Step_Rate', + STEP_DISTANCE_RATE: 'Money_Request_Step_Rate', STEP_MERCHANT: 'Money_Request_Step_Merchant', STEP_PARTICIPANTS: 'Money_Request_Step_Participants', STEP_SCAN: 'Money_Request_Step_Scan', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index fc23af450687..66ba8b78a735 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -9,6 +9,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; @@ -219,6 +220,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const {translate, toLocaleDigit} = useLocalize(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(iouType); + const {isOffline} = useNetwork(); const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; @@ -242,10 +244,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ? DistanceRequestUtils.getRateForP2P(policy?.outputCurrency ?? CONST.CURRENCY.USD) : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); - const {unit, rate} = mileageRate ?? { - unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, - rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * 100, - }; + const {unit, rate} = mileageRate ?? {}; const prevRate = usePrevious(rate); const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); @@ -283,7 +282,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const formattedAmount = isDistanceRequestWithPendingRoute ? '' : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0) : iouAmount, + shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); @@ -352,7 +351,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ return; } - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0); + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amount, currency ?? ''); }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency, transactionID]); @@ -763,16 +762,18 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), + ); }} disabled={didConfirm} - interactive={!isReadOnly && isPolicyExpenseChat} + interactive={Boolean(rate) && !isReadOnly && isPolicyExpenseChat} /> ), shouldShow: isDistanceRequest && canUseP2PDistanceRequests, diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 79f59d4e4868..74d04c8807e1 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -12,6 +12,7 @@ import Switch from '@components/Switch'; import Text from '@components/Text'; import ViolationMessages from '@components/ViolationMessages'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -99,6 +100,7 @@ function MoneyRequestView({ const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const {isOffline} = useNetwork(); const {isSmallScreenWidth} = useWindowDimensions(); const {translate, toLocaleDigit} = useLocalize(); const {canUseViolations, canUseP2PDistanceRequests} = usePermissions(); @@ -185,7 +187,7 @@ function MoneyRequestView({ const {unit, rate} = mileageRate; const distance = DistanceRequestUtils.getDistanceFromMerchant(transactionMerchant, unit); - const rateToDisplay = DistanceRequestUtils.getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); + const rateToDisplay = DistanceRequestUtils.getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit, isOffline); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); const saveBillable = useCallback( @@ -471,7 +473,14 @@ function MoneyRequestView({ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, orderWeight, transaction?.transactionID ?? '', report.reportID), ) } - brickRoadIndicator={getErrorForField('tag', {tagListIndex: index, tagListName: name}) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + brickRoadIndicator={ + getErrorForField('tag', { + tagListIndex: index, + tagListName: name, + }) + ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR + : undefined + } error={getErrorForField('tag', {tagListIndex: index, tagListName: name})} /> diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index c673245343b1..66ab9238be7c 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -116,15 +116,19 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string */ function getRateForDisplay( hasRoute: boolean, - unit: Unit, + unit: Unit | undefined, rate: number | undefined, currency: string | undefined, translate: LocaleContextProps['translate'], toLocaleDigit: LocaleContextProps['toLocaleDigit'], + isOffline?: boolean, ): string { - if (!hasRoute || !rate || !currency) { + if (isOffline) { return translate('iou.defaultRate'); } + if (!hasRoute || !rate || !currency || !unit) { + return translate('iou.routePending'); + } const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); const ratePerUnit = PolicyUtils.getUnitRateValue(toLocaleDigit, {rate}); @@ -142,8 +146,8 @@ function getRateForDisplay( * @param translate Translate function * @returns A string that describes the distance traveled */ -function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, rate: number | undefined, translate: LocaleContextProps['translate']): string { - if (!hasRoute || !rate) { +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit | undefined, rate: number | undefined, translate: LocaleContextProps['translate']): string { + if (!hasRoute || !rate || !unit) { return translate('iou.routePending'); } @@ -168,7 +172,7 @@ function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit function getDistanceMerchant( hasRoute: boolean, distanceInMeters: number, - unit: Unit, + unit: Unit | undefined, rate: number | undefined, currency: string, translate: LocaleContextProps['translate'], diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 062a146e2624..6568cab36a0d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -81,7 +81,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/iou/request/step/IOURequestStepDate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: () => require('../../../../pages/iou/request/step/IOURequestStepDescription').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: () => require('../../../../pages/iou/request/step/IOURequestStepDistance').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.STEP_RATE]: () => require('../../../../pages/iou/request/step/IOURequestStepRate').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE]: () => require('@pages/iou/request/step/IOURequestStepDistanceRate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: () => require('../../../../pages/iou/request/step/IOURequestStepMerchant').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: () => require('../../../../pages/iou/request/step/IOURequestStepParticipants').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_SCAN]: () => require('../../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 67eab41ada5a..554dd963bcdc 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -568,7 +568,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_DATE]: ROUTES.MONEY_REQUEST_STEP_DATE.route, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.route, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: ROUTES.MONEY_REQUEST_STEP_DISTANCE.route, - [SCREENS.MONEY_REQUEST.STEP_RATE]: ROUTES.MONEY_REQUEST_STEP_RATE.route, + [SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE]: ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.route, [SCREENS.MONEY_REQUEST.HOLD]: ROUTES.MONEY_REQUEST_HOLD_REASON.route, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: ROUTES.MONEY_REQUEST_STEP_MERCHANT.route, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index a48b2ba10e35..e89bb61fe566 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -478,7 +478,7 @@ type MoneyRequestNavigatorParamList = { transactionID: string; reportID: string; }; - [SCREENS.MONEY_REQUEST.STEP_RATE]: { + [SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE]: { iouType: ValueOf; transactionID: string; backTo: Routes; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx similarity index 82% rename from src/pages/iou/request/step/IOURequestStepRate.tsx rename to src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index d7ae3ee0a19e..dc1f6fff2968 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -21,7 +21,7 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -type IOURequestStepRateOnyxProps = { +type IOURequestStepDistanceRateOnyxProps = { /** Policy details */ policy: OnyxEntry; @@ -29,20 +29,20 @@ type IOURequestStepRateOnyxProps = { rates: Record; }; -type IOURequestStepRateProps = IOURequestStepRateOnyxProps & - WithWritableReportOrNotFoundProps & { +type IOURequestStepDistanceRateProps = IOURequestStepDistanceRateOnyxProps & + WithWritableReportOrNotFoundProps & { /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ transaction: OnyxEntry; }; -function IOURequestStepRate({ +function IOURequestStepDistanceRate({ policy, route: { params: {backTo, transactionID}, }, transaction, rates, -}: IOURequestStepRateProps) { +}: IOURequestStepDistanceRateProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); @@ -92,9 +92,9 @@ function IOURequestStepRate({ ); } -IOURequestStepRate.displayName = 'IOURequestStepRate'; +IOURequestStepDistanceRate.displayName = 'IOURequestStepDistanceRate'; -const IOURequestStepRateWithOnyx = withOnyx({ +const IOURequestStepDistanceRateWithOnyx = withOnyx({ policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, @@ -102,11 +102,11 @@ const IOURequestStepRateWithOnyx = withOnyx `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '0'}`, selector: DistanceRequestUtils.getMileageRates, }, -})(IOURequestStepRate); +})(IOURequestStepDistanceRate); // eslint-disable-next-line rulesdir/no-negated-variables -const IOURequestStepRateWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepRateWithOnyx); +const IOURequestStepDistanceRateWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepDistanceRateWithOnyx); // eslint-disable-next-line rulesdir/no-negated-variables -const IOURequestStepRateWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepRateWithWritableReportOrNotFound); +const IOURequestStepDistanceRateWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepDistanceRateWithWritableReportOrNotFound); -export default IOURequestStepRateWithFullTransactionOrNotFound; +export default IOURequestStepDistanceRateWithFullTransactionOrNotFound; diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index 76050afaa734..70d5642f3873 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -27,7 +27,7 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS | typeof SCREENS.MONEY_REQUEST.STEP_MERCHANT | typeof SCREENS.MONEY_REQUEST.STEP_TAG - | typeof SCREENS.MONEY_REQUEST.STEP_RATE + | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE | typeof SCREENS.MONEY_REQUEST.STEP_CONFIRMATION | typeof SCREENS.MONEY_REQUEST.STEP_CATEGORY | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 723200fa0c12..ece519d87a1b 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -21,7 +21,7 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_WAYPOINT | typeof SCREENS.MONEY_REQUEST.STEP_DESCRIPTION | typeof SCREENS.MONEY_REQUEST.STEP_CATEGORY - | typeof SCREENS.MONEY_REQUEST.STEP_RATE + | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE | typeof SCREENS.MONEY_REQUEST.STEP_CONFIRMATION | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE | typeof SCREENS.MONEY_REQUEST.STEP_AMOUNT From 3c47a2c3a58f44a08c41ea3af5d53d8845f90d99 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 15 Apr 2024 19:27:54 +0200 Subject: [PATCH 052/124] fix: restore setting customUnitRateID when data available --- src/libs/DistanceRequestUtils.ts | 17 ++++++++++++++--- src/libs/PolicyUtils.ts | 11 +++++++++++ src/libs/actions/IOU.ts | 23 +++-------------------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 66ab9238be7c..f827606ca927 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -4,7 +4,7 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {RateAndUnit} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; +import type {LastSelectedDistanceRates, Report} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; @@ -32,6 +32,14 @@ Onyx.connect({ }, }); +let lastSelectedDistanceRates: OnyxEntry = {}; +Onyx.connect({ + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + callback: (value) => { + lastSelectedDistanceRates = value; + }, +}); + let allReports: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, @@ -267,12 +275,15 @@ function getDistanceFromMerchant(merchant: string | undefined, unit: Unit): numb function getCustomUnitRateID(reportID: string) { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; + const policy = PolicyUtils.getPolicy(report?.policyID ?? parentReport?.policyID ?? ''); + + let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { - return ''; + customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? getDefaultMileageRate(policy)?.customUnitRateID ?? ''; } - return CONST.CUSTOM_UNITS.FAKE_P2P_ID; + return customUnitRateID; } export default { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 8002ad5145df..7df88296651a 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -331,6 +331,16 @@ function getPolicyIDFromNavigationState() { return getPolicyIDFromState(navigationRef.getRootState() as State); } +/** + * Returns the policy of the report + */ +function getPolicy(policyID: string | undefined): Policy | EmptyObject { + if (!allPolicies || !policyID) { + return {}; + } + return allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? {}; +} + export { getActivePolicies, hasAccountingConnections, @@ -371,6 +381,7 @@ export { getTaxByID, hasPolicyCategoriesError, getPolicyIDFromNavigationState, + getPolicy, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8e9ea0e73726..fde110776096 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -244,13 +244,6 @@ Onyx.connect({ }, }); -let allPolicies: OnyxCollection; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY, - waitForCollectionCallback: true, - callback: (value) => (allPolicies = value), -}); - const reportActionsByReport: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, @@ -264,16 +257,6 @@ Onyx.connect({ }, }); -/** - * Returns the policy of the report - */ -function getPolicy(policyID: string | undefined): OnyxTypes.Policy | EmptyObject { - if (!allPolicies || !policyID) { - return {}; - } - return allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? {}; -} - /** * Initialize money request info * @param reportID to attach the transaction to @@ -4916,7 +4899,7 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObj return Object.values(chatReportActions).some((action) => { const iouReport = ReportUtils.getReport(action.childReportID ?? ''); - const policy = getPolicy(iouReport?.policyID); + const policy = PolicyUtils.getPolicy(iouReport?.policyID); const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy) || canApproveIOU(iouReport, chatReport, policy); return action.childReportID?.toString() !== excludedIOUReportID && action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && shouldShowSettlementButton; }); @@ -5058,7 +5041,7 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full function submitReport(expenseReport: OnyxTypes.Report) { const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; const parentReport = ReportUtils.getReport(expenseReport.parentReportID); - const policy = getPolicy(expenseReport.policyID); + const policy = PolicyUtils.getPolicy(expenseReport.policyID); const isCurrentUserManager = currentUserPersonalDetails.accountID === expenseReport.managerID; const isSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy); const adminAccountID = policy.role === CONST.POLICY.ROLE.ADMIN ? currentUserPersonalDetails.accountID : undefined; @@ -5182,7 +5165,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { function cancelPayment(expenseReport: OnyxTypes.Report, chatReport: OnyxTypes.Report) { const optimisticReportAction = ReportUtils.buildOptimisticCancelPaymentReportAction(expenseReport.reportID, -(expenseReport.total ?? 0), expenseReport.currency ?? ''); - const policy = getPolicy(chatReport.policyID); + const policy = PolicyUtils.getPolicy(chatReport.policyID); const isFree = policy && policy.type === CONST.POLICY.TYPE.FREE; const approvalMode = policy.approvalMode ?? CONST.POLICY.APPROVAL_MODE.BASIC; let stateNum: ValueOf = CONST.REPORT.STATE_NUM.SUBMITTED; From b5c4fc5e51b6147705ec03e2dd7a639a724a3834 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 16 Apr 2024 13:33:36 +0800 Subject: [PATCH 053/124] add pending fields to transaction thread --- src/libs/actions/IOU.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 85f4b74f3436..420d5e6a8e29 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -554,7 +554,10 @@ function buildOnyxDataForMoneyRequest( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, - value: transactionThreadReport, + value: { + ...transactionThreadReport, + pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + }, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -737,6 +740,7 @@ function buildOnyxDataForMoneyRequest( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, value: { + pendingFields: null, errorFields: { createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), }, @@ -895,7 +899,10 @@ function buildOnyxDataForTrackExpense( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, - value: transactionThreadReport, + value: { + ...transactionThreadReport, + pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + }, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -1061,6 +1068,7 @@ function buildOnyxDataForTrackExpense( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, value: { + pendingFields: null, errorFields: { createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), }, From 949bf4ebac3bd6d4ffde596fb43c3a107e896944 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 16 Apr 2024 12:26:43 +0200 Subject: [PATCH 054/124] fix: fallback to first rate when all custom named --- src/libs/DistanceRequestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index f827606ca927..d35d7b0d711c 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -70,7 +70,7 @@ function getDefaultMileageRate(policy: OnyxEntry | EmptyObject): Mileage return null; } - const distanceRate = Object.values(distanceUnit.rates).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const distanceRate = Object.values(distanceUnit.rates).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE) ?? Object.values(distanceUnit.rates)[0]; if (!distanceRate) { return null; } From 28948abcf110c4edc6a616f1df87c323d7a3dc15 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 16 Apr 2024 18:30:15 +0200 Subject: [PATCH 055/124] fix: apply requested changes --- src/libs/DistanceRequestUtils.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index d35d7b0d711c..6f82ec155c2d 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -71,9 +71,6 @@ function getDefaultMileageRate(policy: OnyxEntry | EmptyObject): Mileage } const distanceRate = Object.values(distanceUnit.rates).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE) ?? Object.values(distanceUnit.rates)[0]; - if (!distanceRate) { - return null; - } return { customUnitRateID: distanceRate.customUnitRateID, @@ -131,7 +128,7 @@ function getRateForDisplay( toLocaleDigit: LocaleContextProps['toLocaleDigit'], isOffline?: boolean, ): string { - if (isOffline) { + if (isOffline && !rate) { return translate('iou.defaultRate'); } if (!hasRoute || !rate || !currency || !unit) { From 6bcd2570348c1f220c7e00888193ccd5bc09c6b2 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 16 Apr 2024 18:55:14 +0200 Subject: [PATCH 056/124] fix: check for quantity instead of route --- src/components/MoneyRequestConfirmationList.tsx | 2 +- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/TransactionUtils.ts | 4 ++-- src/pages/iou/request/step/IOURequestStepDistance.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index aaf86a052960..035bd440e256 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -264,7 +264,7 @@ function MoneyRequestConfirmationList({ // A flag for showing the billable field const shouldShowBillable = !(policy?.disabledFields?.defaultBillable ?? true); - const hasRoute = TransactionUtils.hasRoute(transaction); + const hasRoute = TransactionUtils.hasRoute(transaction, isDistanceRequest); const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate); const formattedAmount = isDistanceRequestWithPendingRoute ? '' diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 66ba8b78a735..e43a2db22ec0 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -277,7 +277,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // A flag for showing the billable field const shouldShowBillable = policy?.disabledFields?.defaultBillable === false; - const hasRoute = TransactionUtils.hasRoute(transaction); + const hasRoute = TransactionUtils.hasRoute(transaction, isDistanceRequest); const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate); const formattedAmount = isDistanceRequestWithPendingRoute ? '' diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 74d04c8807e1..2e148292c0d7 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -178,7 +178,7 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; - const hasRoute = TransactionUtils.hasRoute(transaction); + const hasRoute = TransactionUtils.hasRoute(transaction, isDistanceRequest); const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '0'; const currency = policy ? policy.outputCurrency : PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 39edac4d5c17..a54ec416cad0 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -517,8 +517,8 @@ function hasMissingSmartscanFields(transaction: OnyxEntry): boolean /** * Check if the transaction has a defined route */ -function hasRoute(transaction: OnyxEntry): boolean { - return !!transaction?.routes?.route0?.geometry?.coordinates; +function hasRoute(transaction: OnyxEntry, isDistanceRequestType: boolean): boolean { + return isDistanceRequestType && !!transaction?.comment?.customUnit?.quantity; } function getAllReportTransactions(reportID?: string): Transaction[] { diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index fae07ad2249c..45da24c55c5c 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -67,7 +67,7 @@ function IOURequestStepDistance({ const isLoadingRoute = transaction?.comment?.isLoading ?? false; const isLoading = transaction?.isLoading ?? false; const hasRouteError = !!transaction?.errorFields?.route; - const hasRoute = TransactionUtils.hasRoute(transaction); + const hasRoute = TransactionUtils.hasRoute(transaction, true); const validatedWaypoints = TransactionUtils.getValidWaypoints(waypoints); const previousValidatedWaypoints = usePrevious(validatedWaypoints); const haveValidatedWaypointsChanged = !isEqual(previousValidatedWaypoints, validatedWaypoints); From 520cf1f022739c66476f775d2416e7082c719658 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 16 Apr 2024 19:39:07 +0200 Subject: [PATCH 057/124] fix: minor fix --- src/libs/TransactionUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index a54ec416cad0..806c879a3709 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -518,7 +518,7 @@ function hasMissingSmartscanFields(transaction: OnyxEntry): boolean * Check if the transaction has a defined route */ function hasRoute(transaction: OnyxEntry, isDistanceRequestType: boolean): boolean { - return isDistanceRequestType && !!transaction?.comment?.customUnit?.quantity; + return !!transaction?.routes?.route0?.geometry?.coordinates || (isDistanceRequestType && !!transaction?.comment?.customUnit?.quantity); } function getAllReportTransactions(reportID?: string): Transaction[] { From 706ba6fc51b70a6087dbf583a98c4409c8cef7c1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 16 Apr 2024 20:47:03 +0200 Subject: [PATCH 058/124] fix: resolve conflicts --- ...raryForRefactorRequestConfirmationList.tsx | 72 +++++++++++-------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 5abb602df995..a8c1c22093ab 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -709,9 +709,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ title={iouComment} description={translate('common.description')} onPress={() => { - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), - ); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); }} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} @@ -732,17 +730,49 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ description={translate('common.distance')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), - ) - } + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing disabled={didConfirm} // todo: handle edit for transaction while moving from track expense interactive={!isReadOnly && !isMovingTransactionFromTrackExpense} /> ), + shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, + isSupplementary: false, + }, + { + item: ( + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + } + disabled={didConfirm} + interactive={!isReadOnly} + /> + ), + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, + isSupplementary: false, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={Boolean(rate) && !isReadOnly && isPolicyExpenseChat} + /> + ), shouldShow: isDistanceRequest, isSupplementary: true, }, @@ -798,9 +828,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ title={iouCategory} description={translate('common.category')} numberOfLinesTitle={2} - onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())) - } + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} disabled={didConfirm} @@ -821,11 +849,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ title={TransactionUtils.getTagForDisplay(transaction, index)} description={name} numberOfLinesTitle={2} - onPress={() => - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), - ) - } + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} style={[styles.moneyRequestMenuItem]} disabled={didConfirm} interactive={!isReadOnly} @@ -845,9 +869,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ description={taxRates?.name} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())) - } + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} disabled={didConfirm} interactive={!isReadOnly} /> @@ -864,11 +886,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ description={translate('iou.taxAmount')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), - ) - } + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} disabled={didConfirm} interactive={!isReadOnly} /> @@ -970,11 +988,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ !isDistanceRequest && iouType === CONST.IOU.TYPE.REQUEST && ( - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), - ) - } + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} /> ))} {primaryFields} From fb59ef104cc108b2ad99cf66234c7b2958a95f69 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 16 Apr 2024 20:52:10 +0200 Subject: [PATCH 059/124] fix: navigation fix --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index a8c1c22093ab..14a061b59c21 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -768,7 +768,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ description={translate('common.rate')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} disabled={didConfirm} interactive={Boolean(rate) && !isReadOnly && isPolicyExpenseChat} /> From 2955ee9aae1d449fbf3260e7ad66dd76c07cc4f1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 16 Apr 2024 22:20:40 +0200 Subject: [PATCH 060/124] fix: translation --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 9f0022c41ff0..2450ce6a3c7a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -635,7 +635,7 @@ export default { canceled: 'Canceled', posted: 'Posted', deleteReceipt: 'Delete receipt', - routePending: 'Route pending...', + routePending: 'Pending...', defaultRate: 'Default rate', receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', diff --git a/src/languages/es.ts b/src/languages/es.ts index a0cedeb5fd82..ee4b20479ee7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -631,7 +631,7 @@ export default { canceled: 'Canceló', posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', - routePending: 'Ruta pendiente...', + routePending: 'Pendiente...', defaultRate: 'Tasa predeterminada', receiptScanning: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', From 068f3abceefcb559685c3175e29b6fec5814f8d3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 17 Apr 2024 12:12:27 +0700 Subject: [PATCH 061/124] fix bump enpensify-common --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 478ae3c12b3c..46d9e3c8aef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -20212,8 +20212,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", - "integrity": "sha512-/NAZoAXqeqFWHvC61dueqq9VjRugF69urUtDdDhsfvu1sQE2PCnBoM7a+ACoAEWRYrnP82cyHHhdSA8e7fPuAg==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", + "integrity": "sha512-zz0/y0apISP1orxXEQOgn+Uod45O4wVypwwtaqcDPV4dH1tC3i4L98NoLSZvLn7Y17EcceSkfN6QCEsscgFTDQ==", "license": "MIT", "dependencies": { "classnames": "2.5.0", diff --git a/package.json b/package.json index e5092e132eae..83b140cd23f5 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", From 80efdea3e2f6766123ef518ac1372c45c2dc6342 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 12:38:20 +0700 Subject: [PATCH 062/124] Display violation when tax rate is invalid --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 ++ src/libs/Permissions.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 73fc7e9bae6e..6d4a0bd7c308 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -451,6 +451,8 @@ function MoneyRequestView({ ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID), ) } + brickRoadIndicator={getErrorForField('tax') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={getErrorForField('tax')} /> )} diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 105736faeba0..80916a1c7bb2 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,6 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { + return true; return !!betas?.includes(CONST.BETAS.ALL); } From f6ef05fdd6160654ee814967ae9aecc5471efbc6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 11:26:26 +0200 Subject: [PATCH 063/124] fix: minor fix --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index bbcb1dea5365..4d536a13a619 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -253,7 +253,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const prevRate = usePrevious(rate); const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); - const currency = (mileageRate as MileageRate)?.currency ?? policy?.outputCurrency ?? CONST.CURRENCY.USD; + const currency = (mileageRate as MileageRate)?.currency ?? policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; const distance = transaction?.routes?.route0?.distance ?? 0; const taxRates = policy?.taxRates ?? null; From a73490b51c6bccf48b43df791bfb8c4188d3d9c6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 11:41:17 +0200 Subject: [PATCH 064/124] fix: currency and rate for p2p --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 4d536a13a619..6a5a1ad8104f 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -244,8 +244,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ } }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID]); + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policy?.outputCurrency ?? CONST.CURRENCY.USD) + ? DistanceRequestUtils.getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); const {unit, rate} = mileageRate ?? {}; @@ -253,7 +255,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const prevRate = usePrevious(rate); const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); - const currency = (mileageRate as MileageRate)?.currency ?? policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + const currency = (mileageRate as MileageRate).currency ?? policyCurrency; const distance = transaction?.routes?.route0?.distance ?? 0; const taxRates = policy?.taxRates ?? null; From d6d6f96144378e98b3ea9b6bc19c946c0a44ee32 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 18:10:31 +0200 Subject: [PATCH 065/124] fix: remove hasRoute condition from getRateForDisplay --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/DistanceRequestUtils.ts | 6 ++---- src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 6a5a1ad8104f..ce50eb949574 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -768,7 +768,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ { - const rateForDisplay = DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit); + const rateForDisplay = DistanceRequestUtils.getRateForDisplay(rate.unit, rate.rate, rate.currency, translate, toLocaleDigit); return { text: rate.name ?? rateForDisplay, From 6c2c0a1c4d0008670440654051bf01bbe0e19822 Mon Sep 17 00:00:00 2001 From: kmichel Date: Wed, 17 Apr 2024 17:51:23 -0700 Subject: [PATCH 066/124] add comment --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 80f0fd596ed6..0480ee4314e9 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -172,6 +172,8 @@ function BaseVideoPlayer({ }); }, [currentVideoPlayerRef, handleFullscreenUpdate, handlePlaybackStatusUpdate]); + // use `useLayoutEffect` instead of `useEffect` because ref is null when unmount in `useEffect` hook + // ref url: https://reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing useLayoutEffect(() => () => { if(shouldUseSharedVideoElement || videoPlayerRef.current !== currentVideoPlayerRef.current) { @@ -212,7 +214,7 @@ function BaseVideoPlayer({ } return; } - + videoPlayerRef.current = currentVideoPlayerRef.current; if (currentlyPlayingURL === url && newParentRef && 'appendChild' in newParentRef) { newParentRef.appendChild(sharedElement as HTMLDivElement); From f31b294a2df9819f4504c3d511d0276acc5a60ed Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 12:25:17 +0530 Subject: [PATCH 067/124] link verify redirect --- .github/scripts/detectRedirectCycle.ts | 4 ++++ .github/scripts/verifyRedirect.sh | 19 +++++++++++++++---- .github/workflows/deployExpensifyHelp.yml | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 .github/scripts/detectRedirectCycle.ts diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts new file mode 100644 index 000000000000..d0d8ed4eb923 --- /dev/null +++ b/.github/scripts/detectRedirectCycle.ts @@ -0,0 +1,4 @@ +import fs from 'fs'; + +const docsDir = `${process.cwd()}/docs`; + diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh index 737d9bffacf9..b8942cd5b23d 100644 --- a/.github/scripts/verifyRedirect.sh +++ b/.github/scripts/verifyRedirect.sh @@ -5,11 +5,22 @@ declare -r REDIRECTS_FILE="docs/redirects.csv" +declare -r RED='\033[0;31m' +declare -r GREEN='\033[0;32m' +declare -r NC='\033[0m' + duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE) +if [[ -n "$duplicates" ]]; then + echo "${RED}duplicate redirects are not allowed: $duplicates ${NC}" + exit 1 +fi -if [[ -z "$duplicates" ]]; then - exit 0 +npm run detectRedirectCycle +DETECT_CYCLE_EXIT_CODE=$? +if [[ DETECT_CYCLE_EXIT_CODE -eq 1 ]]; then + echo -e "${RED}The redirects.csv has a cycle. Please remove the redirect cycle because it will cause an infinite redirect loop ${NC}" + exit 1 fi -echo "duplicate redirects are not allowed: $duplicates" -exit 1 +echo -e "${GREEN}The redirects.csv is valid!${NC}" +exit 0 diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index 699bd379fb77..f16aab1caaa7 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -36,8 +36,8 @@ jobs: - name: Create docs routes file run: ./.github/scripts/createDocsRoutes.sh - - - name: Check duplicates in redirect.csv + + - name: Check duplicates in redirects.csv run: ./.github/scripts/verifyRedirect.sh - name: Build with Jekyll From 7b8f8b9fa1bd912224fc18497ef45f19ea19a198 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 12:43:15 +0530 Subject: [PATCH 068/124] add csv-parse dev dependency --- package-lock.json | 7 +++++++ package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index 64dd4fb0c885..fcce92f9d2f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -201,6 +201,7 @@ "concurrently": "^8.2.2", "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", + "csv-parse": "^5.5.5", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", "electron": "^29.2.0", @@ -17740,6 +17741,12 @@ "version": "3.1.1", "license": "MIT" }, + "node_modules/csv-parse": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.5.tgz", + "integrity": "sha512-erCk7tyU3yLWAhk6wvKxnyPtftuy/6Ak622gOO7BCJ05+TYffnPCJF905wmOQm+BpkX54OdAl8pveJwUdpnCXQ==", + "dev": true + }, "node_modules/dag-map": { "version": "1.0.2", "license": "MIT" diff --git a/package.json b/package.json index 78e1a3a13e2c..fdcf786b52b1 100644 --- a/package.json +++ b/package.json @@ -252,6 +252,7 @@ "concurrently": "^8.2.2", "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", + "csv-parse": "^5.5.5", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", "electron": "^29.2.0", From 459d4e8e47eb8f2c39421fae32f1f679c166a887 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 16:32:49 +0700 Subject: [PATCH 069/124] fix just display track expense of selfDM is loaded --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index b55fecf48ffb..eb8408998fa2 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -260,6 +260,8 @@ function FloatingActionButtonAndPopover( } }; + const selfDMReportID = useMemo(() => ReportUtils.findSelfDMReportID(), [isLoading]); + return ( interceptAnonymousUser(Report.startNewChat), }, - ...(canUseTrackExpense + ...(canUseTrackExpense && selfDMReportID ? [ { icon: Expensicons.DocumentPlus, From 55b4f52ef2c9c290701268a9ba5b9e7bd3e67e04 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 17:11:41 +0700 Subject: [PATCH 070/124] fix lint --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index eb8408998fa2..b74a98a1a94a 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -260,7 +260,10 @@ function FloatingActionButtonAndPopover( } }; - const selfDMReportID = useMemo(() => ReportUtils.findSelfDMReportID(), [isLoading]); + const selfDMReportID = useMemo(() => { + return ReportUtils.findSelfDMReportID(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading]); return ( From 0b61a9df1a444d9240664fd17d8d7661a6e7865f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 17:19:29 +0700 Subject: [PATCH 071/124] fix lint --- .../SidebarScreen/FloatingActionButtonAndPopover.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index b74a98a1a94a..76ef4aff1ab4 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -259,11 +259,8 @@ function FloatingActionButtonAndPopover( showCreateMenu(); } }; - - const selfDMReportID = useMemo(() => { - return ReportUtils.findSelfDMReportID(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLoading]); + // eslint-disable-next-line react-hooks/exhaustive-deps + const selfDMReportID = useMemo(() => ReportUtils.findSelfDMReportID(), [isLoading]); return ( From bdfbbdbc9289515c3baa36c77e3d8ee82a8134fd Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 18 Apr 2024 12:28:02 +0200 Subject: [PATCH 072/124] fix: prettier --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4177f16e9c59..cd26914b52ad 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -50,8 +50,8 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - ReimbursementRateParams, PaySomeoneParams, + ReimbursementRateParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, From a640d416a5e36c4fdf2ddb89557b15886d111451 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 18:16:45 +0530 Subject: [PATCH 073/124] detect cycle in redirects.csv --- .github/scripts/detectRedirectCycle.ts | 57 +++++++++++++++++++++++++- package.json | 1 + 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index d0d8ed4eb923..90191f947162 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -1,4 +1,59 @@ +import {parse} from 'csv-parse'; import fs from 'fs'; -const docsDir = `${process.cwd()}/docs`; +const parser = parse({skip_empty_lines: true}); +const adjacencyList: any = {}; +function addEdge(source: string, target: string) { + if (!adjacencyList[source]) { + adjacencyList[source] = []; + } + adjacencyList[source].push(target); +} + +function isCyclic(currentNode: string, visited: Map, backEdges: Map): boolean { + visited.set(currentNode, true); + backEdges.set(currentNode, true); + + // Do a depth first search for all the neighbours. If a node is found in backedge, a cycle is detected. + const neighbours = adjacencyList[currentNode]; + if (neighbours) { + for (const node of neighbours) { + if (!visited.has(node)) { + if (isCyclic(node, visited, backEdges)) return true; + } else if (backEdges.has(node)) return true; + } + } + + backEdges.delete(currentNode); + + return false; +} + +function detectCycle() { + const visited: Map = new Map(); + const backEdges: Map = new Map(); + + for (let node in adjacencyList) { + if (visited.has(node)) { + continue; + } + if (isCyclic(node, visited, backEdges)) { + const cycle = Array.from(backEdges.keys()); + console.log(`Infinite redirect found in cycle: ${cycle.join(' -> ')} -> ${node}`); + return true; + } + } +} + +fs.createReadStream(`${process.cwd()}/docs/redirects.csv`) + .pipe(parser) + .on('data', async (row) => { + addEdge(row[0], row[1]); + }) + .on('end', async () => { + if (detectCycle()) { + process.exit(1); + } + process.exit(0); + }); diff --git a/package.json b/package.json index fdcf786b52b1..abf4376ab683 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "desktop-build": "scripts/build-desktop.sh production", "desktop-build-staging": "scripts/build-desktop.sh staging", "createDocsRoutes": "ts-node .github/scripts/createDocsRoutes.ts", + "detectRedirectCycle": "ts-node .github/scripts/detectRedirectCycle.ts", "desktop-build-adhoc": "scripts/build-desktop.sh adhoc", "ios-build": "fastlane ios build", "android-build": "fastlane android build", From 104e9e4b6185863a9f747b9988beac6cb49670d5 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 18:24:30 +0530 Subject: [PATCH 074/124] remove the found cycle in redirects.csv --- .github/scripts/detectRedirectCycle.ts | 2 +- docs/redirects.csv | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index 90191f947162..d2e13f061eee 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -40,7 +40,7 @@ function detectCycle() { } if (isCyclic(node, visited, backEdges)) { const cycle = Array.from(backEdges.keys()); - console.log(`Infinite redirect found in cycle: ${cycle.join(' -> ')} -> ${node}`); + console.log(`Infinite redirect found in the cycle: ${cycle.join(' -> ')} -> ${node}`); return true; } } diff --git a/docs/redirects.csv b/docs/redirects.csv index af595ecc5f83..3f90265b5f6b 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -123,7 +123,6 @@ https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/ https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt,https://help.expensify.com/articles/expensify-classic/expensify-billing/Tax-Exempt https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approving-Reports,https://help.expensify.com/expensify-classic/hubs/reports/ https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members,https://help.expensify.com/articles/expensify-classic/workspaces/Invite-members-and-assign-roles -https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules From 6637db8498cca22940eea69f868bc0fd52095db6 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 18:43:27 +0530 Subject: [PATCH 075/124] add redirects --- docs/redirects.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/redirects.csv b/docs/redirects.csv index 3f90265b5f6b..e442bd32fea9 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -157,3 +157,5 @@ https://help.expensify.com/articles/expensify-classic/workspaces/Budgets,https:/ https://help.expensify.com/articles/expensify-classic/workspaces/Categories,https://help.expensify.com/articles/expensify-classic/workspaces/Create-categories https://help.expensify.com/articles/expensify-classic/workspaces/Tags,https://help.expensify.com/articles/expensify-classic/workspaces/Create-tags https://help.expensify.com/expensify-classic/hubs/manage-employees-and-report-approvals,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approval-Workflows +https://help.expensify.com/articles/expensify-classic/reports/Report-Audit-Log-and-Comments,https://help.expensify.com/articles/expensify-classic/reports/Print-or-download-a-report +https://help.expensify.com/articles/expensify-classic/reports/The-Reports-Page,https://help.expensify.com/articles/expensify-classic/reports/Report-statuses From 26d8d5e74d1b7042f0f2dbca2c149e088687254c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 18:57:50 +0530 Subject: [PATCH 076/124] rm async --- .github/scripts/detectRedirectCycle.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index d2e13f061eee..cc4d147b5b20 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -48,10 +48,10 @@ function detectCycle() { fs.createReadStream(`${process.cwd()}/docs/redirects.csv`) .pipe(parser) - .on('data', async (row) => { + .on('data', (row) => { addEdge(row[0], row[1]); }) - .on('end', async () => { + .on('end', () => { if (detectCycle()) { process.exit(1); } From 22795da4f9897f7e3b72b685aad5475a2861d521 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 18:58:11 +0530 Subject: [PATCH 077/124] fix lint --- .github/scripts/detectRedirectCycle.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index cc4d147b5b20..a02021b3be55 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -20,8 +20,12 @@ function isCyclic(currentNode: string, visited: Map, backEdges: if (neighbours) { for (const node of neighbours) { if (!visited.has(node)) { - if (isCyclic(node, visited, backEdges)) return true; - } else if (backEdges.has(node)) return true; + if (isCyclic(node, visited, backEdges)) { + return true; + } + } else if (backEdges.has(node)) { + return true; + } } } From 1972309453d77826d7de1aca81a1a5b053e9a85f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 18:59:12 +0530 Subject: [PATCH 078/124] fix lint --- .github/scripts/detectRedirectCycle.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index a02021b3be55..b4dabaac110f 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -39,13 +39,12 @@ function detectCycle() { const backEdges: Map = new Map(); for (let node in adjacencyList) { - if (visited.has(node)) { - continue; - } - if (isCyclic(node, visited, backEdges)) { - const cycle = Array.from(backEdges.keys()); - console.log(`Infinite redirect found in the cycle: ${cycle.join(' -> ')} -> ${node}`); - return true; + if (!visited.has(node)) { + if (isCyclic(node, visited, backEdges)) { + const cycle = Array.from(backEdges.keys()); + console.log(`Infinite redirect found in the cycle: ${cycle.join(' -> ')} -> ${node}`); + return true; + } } } } From 32c569cae4726fe5d7fe3aec4f159812e4e82ccc Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 18:59:51 +0530 Subject: [PATCH 079/124] fix lint --- .github/scripts/detectRedirectCycle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index b4dabaac110f..fdce5b4f080a 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -38,7 +38,7 @@ function detectCycle() { const visited: Map = new Map(); const backEdges: Map = new Map(); - for (let node in adjacencyList) { + for (const node in adjacencyList) { if (!visited.has(node)) { if (isCyclic(node, visited, backEdges)) { const cycle = Array.from(backEdges.keys()); From 22075e5cbbb79852a8e522543c4c4ab9be34dd6d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:02:02 +0530 Subject: [PATCH 080/124] fix type --- .github/scripts/detectRedirectCycle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index fdce5b4f080a..28474ce21ab1 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -2,7 +2,7 @@ import {parse} from 'csv-parse'; import fs from 'fs'; const parser = parse({skip_empty_lines: true}); -const adjacencyList: any = {}; +const adjacencyList: Record> = {}; function addEdge(source: string, target: string) { if (!adjacencyList[source]) { From 033e6c0aa4d1d999b1007254833a8d33a89056a6 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:03:07 +0530 Subject: [PATCH 081/124] fix type --- .github/scripts/detectRedirectCycle.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index 28474ce21ab1..97a49d131604 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -34,7 +34,7 @@ function isCyclic(currentNode: string, visited: Map, backEdges: return false; } -function detectCycle() { +function detectCycle(): boolean { const visited: Map = new Map(); const backEdges: Map = new Map(); @@ -47,6 +47,7 @@ function detectCycle() { } } } + return false; } fs.createReadStream(`${process.cwd()}/docs/redirects.csv`) From e430bb08514459e484f313cb182ca5647bf2d717 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:10:33 +0530 Subject: [PATCH 082/124] fix lint --- .github/scripts/detectRedirectCycle.ts | 5 +++-- .github/workflows/deployExpensifyHelp.yml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index 97a49d131604..ab526d598fef 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -2,7 +2,7 @@ import {parse} from 'csv-parse'; import fs from 'fs'; const parser = parse({skip_empty_lines: true}); -const adjacencyList: Record> = {}; +const adjacencyList: Record = {}; function addEdge(source: string, target: string) { if (!adjacencyList[source]) { @@ -38,7 +38,7 @@ function detectCycle(): boolean { const visited: Map = new Map(); const backEdges: Map = new Map(); - for (const node in adjacencyList) { + for (const [node, _] of Object.entries(adjacencyList)) { if (!visited.has(node)) { if (isCyclic(node, visited, backEdges)) { const cycle = Array.from(backEdges.keys()); @@ -53,6 +53,7 @@ function detectCycle(): boolean { fs.createReadStream(`${process.cwd()}/docs/redirects.csv`) .pipe(parser) .on('data', (row) => { + // Create a directed graph of sourceURL -> targetURL addEdge(row[0], row[1]); }) .on('end', () => { diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index f16aab1caaa7..f7f826d66f9b 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -37,7 +37,7 @@ jobs: - name: Create docs routes file run: ./.github/scripts/createDocsRoutes.sh - - name: Check duplicates in redirects.csv + - name: Check for duplicates and cycles in redirects.csv run: ./.github/scripts/verifyRedirect.sh - name: Build with Jekyll From 458bc3503cf12ae8bfea7e55e7562ff8d40080f2 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:11:24 +0530 Subject: [PATCH 083/124] fix lint --- .github/scripts/detectRedirectCycle.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index ab526d598fef..d78358271261 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -1,6 +1,7 @@ import {parse} from 'csv-parse'; import fs from 'fs'; +// eslint-disable-next-line @typescript-eslint/naming-convention const parser = parse({skip_empty_lines: true}); const adjacencyList: Record = {}; From 08ee70495cbad141e2d5657c32f2ecae96c8e9cd Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:15:14 +0530 Subject: [PATCH 084/124] make script executable --- .github/scripts/verifyRedirect.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh index b8942cd5b23d..3a687391b394 100644 --- a/.github/scripts/verifyRedirect.sh +++ b/.github/scripts/verifyRedirect.sh @@ -24,3 +24,4 @@ fi echo -e "${GREEN}The redirects.csv is valid!${NC}" exit 0 + From da441105403dd6167d63216527e97f5100d0f7e3 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:19:51 +0530 Subject: [PATCH 085/124] debug: --- .github/scripts/verifyRedirect.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh index 3a687391b394..79f8f25f3cac 100644 --- a/.github/scripts/verifyRedirect.sh +++ b/.github/scripts/verifyRedirect.sh @@ -9,11 +9,11 @@ declare -r RED='\033[0;31m' declare -r GREEN='\033[0;32m' declare -r NC='\033[0m' -duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE) -if [[ -n "$duplicates" ]]; then - echo "${RED}duplicate redirects are not allowed: $duplicates ${NC}" - exit 1 -fi +# duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE) +# if [[ -n "$duplicates" ]]; then +# echo "${RED}duplicate redirects are not allowed: $duplicates ${NC}" +# exit 1 +# fi npm run detectRedirectCycle DETECT_CYCLE_EXIT_CODE=$? @@ -24,4 +24,3 @@ fi echo -e "${GREEN}The redirects.csv is valid!${NC}" exit 0 - From bfe2096162498b6835fe8bc9181e9649c25530b1 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:21:48 +0530 Subject: [PATCH 086/124] debug: --- .github/scripts/verifyRedirect.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh index 79f8f25f3cac..dfe9492b2112 100644 --- a/.github/scripts/verifyRedirect.sh +++ b/.github/scripts/verifyRedirect.sh @@ -15,12 +15,12 @@ declare -r NC='\033[0m' # exit 1 # fi -npm run detectRedirectCycle -DETECT_CYCLE_EXIT_CODE=$? -if [[ DETECT_CYCLE_EXIT_CODE -eq 1 ]]; then - echo -e "${RED}The redirects.csv has a cycle. Please remove the redirect cycle because it will cause an infinite redirect loop ${NC}" - exit 1 -fi +# npm run detectRedirectCycle +# DETECT_CYCLE_EXIT_CODE=$? +# if [[ DETECT_CYCLE_EXIT_CODE -eq 1 ]]; then +# echo -e "${RED}The redirects.csv has a cycle. Please remove the redirect cycle because it will cause an infinite redirect loop ${NC}" +# exit 1 +# fi echo -e "${GREEN}The redirects.csv is valid!${NC}" exit 0 From 0232f44c4abcf3a4843ab8c811e6ee402ea111df Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:25:51 +0530 Subject: [PATCH 087/124] provide permission --- .github/scripts/detectRedirectCycle.ts | 0 .github/scripts/verifyRedirect.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/scripts/detectRedirectCycle.ts mode change 100644 => 100755 .github/scripts/verifyRedirect.sh diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts old mode 100644 new mode 100755 diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh old mode 100644 new mode 100755 From 409b1c1c215fb1c16e5c0a075b834cccbebf1f71 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:26:27 +0530 Subject: [PATCH 088/124] provide permission --- .github/scripts/detectRedirectCycle.ts | 0 .github/scripts/verifyRedirect.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 .github/scripts/detectRedirectCycle.ts mode change 100755 => 100644 .github/scripts/verifyRedirect.sh diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts old mode 100755 new mode 100644 diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh old mode 100755 new mode 100644 From eb6c38eda2b01f3fd1704a0eaf6280639a3631bf Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:28:49 +0530 Subject: [PATCH 089/124] delete file --- .github/scripts/verifyRedirect.sh | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/scripts/verifyRedirect.sh diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh deleted file mode 100644 index dfe9492b2112..000000000000 --- a/.github/scripts/verifyRedirect.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# HelpDot - Verifies that redirects.csv does not have any duplicates -# Duplicate sourceURLs break redirection on cloudflare pages - -declare -r REDIRECTS_FILE="docs/redirects.csv" - -declare -r RED='\033[0;31m' -declare -r GREEN='\033[0;32m' -declare -r NC='\033[0m' - -# duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE) -# if [[ -n "$duplicates" ]]; then -# echo "${RED}duplicate redirects are not allowed: $duplicates ${NC}" -# exit 1 -# fi - -# npm run detectRedirectCycle -# DETECT_CYCLE_EXIT_CODE=$? -# if [[ DETECT_CYCLE_EXIT_CODE -eq 1 ]]; then -# echo -e "${RED}The redirects.csv has a cycle. Please remove the redirect cycle because it will cause an infinite redirect loop ${NC}" -# exit 1 -# fi - -echo -e "${GREEN}The redirects.csv is valid!${NC}" -exit 0 From 08e2545fb1ef2008d4c2e3caf44334cfd598b12a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:30:58 +0530 Subject: [PATCH 090/124] add file as executable --- .github/scripts/verifyRedirect.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 .github/scripts/verifyRedirect.sh diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh new file mode 100755 index 000000000000..b8942cd5b23d --- /dev/null +++ b/.github/scripts/verifyRedirect.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# HelpDot - Verifies that redirects.csv does not have any duplicates +# Duplicate sourceURLs break redirection on cloudflare pages + +declare -r REDIRECTS_FILE="docs/redirects.csv" + +declare -r RED='\033[0;31m' +declare -r GREEN='\033[0;32m' +declare -r NC='\033[0m' + +duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE) +if [[ -n "$duplicates" ]]; then + echo "${RED}duplicate redirects are not allowed: $duplicates ${NC}" + exit 1 +fi + +npm run detectRedirectCycle +DETECT_CYCLE_EXIT_CODE=$? +if [[ DETECT_CYCLE_EXIT_CODE -eq 1 ]]; then + echo -e "${RED}The redirects.csv has a cycle. Please remove the redirect cycle because it will cause an infinite redirect loop ${NC}" + exit 1 +fi + +echo -e "${GREEN}The redirects.csv is valid!${NC}" +exit 0 From 2b5015fd36b39668a8ee15f0319e0c53bfb53eeb Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:45:06 +0530 Subject: [PATCH 091/124] fix lint --- .github/scripts/detectRedirectCycle.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index d78358271261..220f59f9c170 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -1,8 +1,10 @@ import {parse} from 'csv-parse'; import fs from 'fs'; -// eslint-disable-next-line @typescript-eslint/naming-convention -const parser = parse({skip_empty_lines: true}); +const parser = parse({ + // eslint-disable-next-line @typescript-eslint/naming-convention + skip_empty_lines: true, +}); const adjacencyList: Record = {}; function addEdge(source: string, target: string) { @@ -39,7 +41,7 @@ function detectCycle(): boolean { const visited: Map = new Map(); const backEdges: Map = new Map(); - for (const [node, _] of Object.entries(adjacencyList)) { + for (const [node] of Object.entries(adjacencyList)) { if (!visited.has(node)) { if (isCyclic(node, visited, backEdges)) { const cycle = Array.from(backEdges.keys()); From 3295539764f4c8a8b93172c02b9f1d1f19b8771f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:46:35 +0530 Subject: [PATCH 092/124] fix lint --- .github/scripts/detectRedirectCycle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index 220f59f9c170..3443ad746709 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -2,7 +2,7 @@ import {parse} from 'csv-parse'; import fs from 'fs'; const parser = parse({ - // eslint-disable-next-line @typescript-eslint/naming-convention + // eslint-disable-next-line @typescript-eslint/camelcase skip_empty_lines: true, }); const adjacencyList: Record = {}; From 1f2abbd03c82e00a6a965b2366d81103de124eb0 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 18 Apr 2024 19:56:21 +0530 Subject: [PATCH 093/124] remove: skip empty lines --- .github/scripts/detectRedirectCycle.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts index 3443ad746709..9df86d28fc6f 100644 --- a/.github/scripts/detectRedirectCycle.ts +++ b/.github/scripts/detectRedirectCycle.ts @@ -1,10 +1,7 @@ import {parse} from 'csv-parse'; import fs from 'fs'; -const parser = parse({ - // eslint-disable-next-line @typescript-eslint/camelcase - skip_empty_lines: true, -}); +const parser = parse(); const adjacencyList: Record = {}; function addEdge(source: string, target: string) { From 7d62bc9b2f9f0136b85c2f9187bb0c658ee1bddc Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 18 Apr 2024 21:08:13 +0530 Subject: [PATCH 094/124] fix: Submit Expense - Error message about corrupted file when dragging and dropping image receipt. Signed-off-by: Krishna Gupta --- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 654f9e9d9f91..b44aa633d27f 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -481,6 +481,7 @@ function IOURequestStepScan({ onDrop={(e) => { const file = e?.dataTransfer?.files[0]; if (file) { + file.uri = URL.createObjectURL(file); setReceiptAndNavigate(file); } }} From 5229462382bb70d6b3d49f05f131d58f81bf2075 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 18 Apr 2024 19:06:41 +0200 Subject: [PATCH 095/124] fix: add missing export --- src/libs/actions/IOU.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index f6cee42538fe..2ba6feea635f 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6039,6 +6039,7 @@ export { submitReport, trackExpense, unholdRequest, + updateDistanceRequestRate, updateMoneyRequestAmountAndCurrency, updateMoneyRequestBillable, updateMoneyRequestCategory, From 60f2822b4aabf19a4142d996b0660d169bafa58f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 19 Apr 2024 01:03:31 +0700 Subject: [PATCH 096/124] revert hard code --- src/libs/Permissions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 80916a1c7bb2..105736faeba0 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,6 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true; return !!betas?.includes(CONST.BETAS.ALL); } From 9c8fbdf0ea9bc2911e94df237ae12b473e3df11b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 18 Apr 2024 20:49:20 +0200 Subject: [PATCH 097/124] fix: minor fix --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 0701f2ff1db4..b9b657988712 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -255,7 +255,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const prevRate = usePrevious(rate); const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); - const currency = (mileageRate as MileageRate).currency ?? policyCurrency; + const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; const distance = transaction?.routes?.route0?.distance ?? 0; const taxRates = policy?.taxRates ?? null; From 63c5eddc276135c799d6404199b3567e89db76cc Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 18 Apr 2024 20:56:35 +0200 Subject: [PATCH 098/124] fix: copy temporary confirmation list content to the original file --- .../MoneyRequestConfirmationList.tsx | 980 ++++++++++-------- 1 file changed, 565 insertions(+), 415 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index fa6af89e5bfd..b9b657988712 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1,13 +1,16 @@ import {useIsFocused} from '@react-navigation/native'; import {format} from 'date-fns'; -import React, {useCallback, useEffect, useMemo, useReducer, useState} from 'react'; +import Str from 'expensify-common/lib/str'; +import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; +import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; @@ -19,11 +22,14 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import {isTaxTrackingEnabled} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; +import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import * as IOU from '@userActions/IOU'; -import type {IOUType} from '@src/CONST'; +import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; @@ -31,17 +37,19 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; -import type {ReceiptSource} from '@src/types/onyx/Transaction'; +import Button from './Button'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import type {DropdownOption} from './ButtonWithDropdownMenu/types'; import ConfirmedRoute from './ConfirmedRoute'; +import ConfirmModal from './ConfirmModal'; import FormHelpMessage from './FormHelpMessage'; +import * as Expensicons from './Icon/Expensicons'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import OptionsSelector from './OptionsSelector'; +import PDFThumbnail from './PDFThumbnail'; import ReceiptEmptyState from './ReceiptEmptyState'; import ReceiptImage from './ReceiptImage'; import SettlementButton from './SettlementButton'; -import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; import Text from './Text'; @@ -55,21 +63,25 @@ type MoneyRequestConfirmationListOnyxProps = { /** The policy of the report */ policy: OnyxEntry; - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: OnyxEntry; - /** The session of the logged in user */ session: OnyxEntry; - /** Unit and rate used for if the money request is a distance request */ - mileageRate: OnyxEntry; + /** Unit and rate used for if the expense is a distance request */ + mileageRates: OnyxEntry>; + + /** Mileage rate default for the policy */ + defaultMileageRate: OnyxEntry; + + /** Last selected distance rates */ + lastSelectedDistanceRates: OnyxEntry>; }; + type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & { /** Callback to inform parent modal of success */ - onConfirm?: (selectedParticipants: Array) => void; + onConfirm?: (selectedParticipants: Participant[]) => void; - /** Callback to parent modal to send money */ - onSendMoney?: (paymentMethod: IOUType | PaymentMethodType | undefined) => void; + /** Callback to parent modal to pay someone */ + onSendMoney?: (paymentMethod: PaymentMethodType | undefined) => void; /** Callback to inform a participant is selected */ onSelectParticipant?: (option: Participant) => void; @@ -98,9 +110,6 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & /** IOU Category */ iouCategory?: string; - /** IOU Tag */ - iouTag?: string; - /** IOU isBillable */ iouIsBillable?: boolean; @@ -108,10 +117,10 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & onToggleBillable?: (isOn: boolean) => void; /** Selected participants from MoneyRequestModal with login / accountID */ - selectedParticipants: Array; + selectedParticipants: Participant[]; - /** Payee of the money request with login */ - payeePersonalDetails?: OnyxEntry; + /** Payee of the expense with login */ + payeePersonalDetails?: OnyxTypes.PersonalDetails; /** Can the participants be modified or not */ canModifyParticipants?: boolean; @@ -129,7 +138,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & reportID?: string; /** File path of the receipt */ - receiptPath?: ReceiptSource; + receiptPath?: string; /** File name of the receipt */ receiptFilename?: string; @@ -137,19 +146,16 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & /** List styles for OptionsSelector */ listStyles?: StyleProp; - /** ID of the transaction that represents the money request */ - transactionID?: string; - - /** Transaction that represents the money request */ + /** Transaction that represents the expense */ transaction?: OnyxEntry; - /** Whether the money request is a distance request */ + /** Whether the expense is a distance expense */ isDistanceRequest?: boolean; - /** Whether the money request is a scan request */ + /** Whether the expense is a scan expense */ isScanRequest?: boolean; - /** Whether we're editing a split bill */ + /** Whether we're editing a split expense */ isEditingSplitBill?: boolean; /** Whether we should show the amount, date, and merchant fields. */ @@ -161,11 +167,18 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & /** Whether smart scan failed */ hasSmartScanFailed?: boolean; - /** The ID of the report action */ reportActionID?: string; + + action?: IOUAction; }; -function MoneyRequestConfirmationList({ +const getTaxAmount = (transaction: OnyxEntry, defaultTaxValue: string) => { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const percentage = (transaction?.taxRate ? transaction?.taxRate?.data?.value : defaultTaxValue) || ''; + return TransactionUtils.calculateTaxAmount(percentage, transaction?.amount ?? 0); +}; + +function MoneyTemporaryForRefactorRequestConfirmationList({ transaction = null, onSendMoney, onConfirm, @@ -174,7 +187,7 @@ function MoneyRequestConfirmationList({ isScanRequest = false, iouAmount, policyCategories, - mileageRate, + mileageRates, isDistanceRequest = false, policy, isPolicyExpenseChat = false, @@ -185,22 +198,9 @@ function MoneyRequestConfirmationList({ iouCurrencyCode, iouMerchant, hasMultipleParticipants, - selectedParticipants: selectedParticipantsProp, - payeePersonalDetails: payeePersonalDetailsProp, - iou = { - id: '', - amount: 0, - currency: CONST.CURRENCY.USD, - comment: '', - merchant: '', - category: '', - tag: '', - billable: false, - created: '', - participants: [], - receiptPath: '', - }, - canModifyParticipants: canModifyParticipantsProp = false, + selectedParticipants: pickedParticipants, + payeePersonalDetails, + canModifyParticipants = false, session, isReadOnly = false, bankAccountRoute = '', @@ -213,69 +213,88 @@ function MoneyRequestConfirmationList({ iouCreated, iouIsBillable = false, onToggleBillable, - iouTag = '', - transactionID = '', hasSmartScanFailed, reportActionID, + defaultMileageRate, + lastSelectedDistanceRates, + action = CONST.IOU.ACTION.CREATE, }: MoneyRequestConfirmationListProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const {canUseViolations} = usePermissions(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(iouType); + const {isOffline} = useNetwork(); const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; - const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; + const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; const isTypeSend = iouType === CONST.IOU.TYPE.SEND; + const isTypeTrackExpense = iouType === CONST.IOU.TYPE.TRACK_EXPENSE; - const isSplitWithScan = isSplitBill && isScanRequest; + const transactionID = transaction?.transactionID ?? ''; + const customUnitRateID = TransactionUtils.getRateID(transaction) ?? ''; - const {unit, rate, currency} = mileageRate ?? { - unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, - rate: 0, - currency: CONST.CURRENCY.USD, - }; - const distance = transaction?.routes?.route0.distance ?? 0; - const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; - const taxRates = policy?.taxRates; + useEffect(() => { + if (customUnitRateID || !canUseP2PDistanceRequests) { + return; + } + if (!customUnitRateID) { + const rateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? defaultMileageRate?.customUnitRateID ?? ''; + IOU.setCustomUnitRateID(transactionID, rateID); + } + }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID]); + + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(policyCurrency) + : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); + + const {unit, rate} = mileageRate ?? {}; + + const prevRate = usePrevious(rate); + const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); + + const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; + + const distance = transaction?.routes?.route0?.distance ?? 0; + const taxRates = policy?.taxRates ?? null; // A flag for showing the categories field const shouldShowCategories = isPolicyExpenseChat && (!!iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); - // Do not hide fields in case of send money request - const shouldShowAllFields = !!isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill; + // Do not hide fields in case of paying someone + const shouldShowAllFields = isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill; - // In Send Money and Split Bill with Scan flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item - const shouldShowDate = shouldShowAllFields && !isTypeSend && !isSplitWithScan; - const shouldShowMerchant = shouldShowAllFields && !isTypeSend && !isDistanceRequest && !isSplitWithScan; + const shouldShowDate = (shouldShowSmartScanFields || isDistanceRequest) && !isTypeSend; + const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend; const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field - const shouldShowTags = isPolicyExpenseChat && (!!iouTag || OptionsListUtils.hasEnabledTags(policyTagLists)); + const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); - // A flag for showing tax fields - tax rate and tax amount - const shouldShowTax = isPolicyExpenseChat && (policy?.tax?.trackingEnabled ?? policy?.isTaxTrackingEnabled); + // A flag for showing tax rate + const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy); // A flag for showing the billable field - const shouldShowBillable = !(policy?.disabledFields?.defaultBillable ?? true); - + const shouldShowBillable = policy?.disabledFields?.defaultBillable === false; + const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action); const hasRoute = TransactionUtils.hasRoute(transaction, isDistanceRequest); - const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate); + const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate) && !isMovingTransactionFromTrackExpense; const formattedAmount = isDistanceRequestWithPendingRoute ? '' : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0) : iouAmount, + shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); + const taxRateTitle = taxRates && transaction ? TransactionUtils.getDefaultTaxName(taxRates, transaction) : ''; - const defaultTaxKey = taxRates?.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) ${CONST.DOT_SEPARATOR} ${translate('common.default')}`) ?? ''; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing is not working when a left hand side value is '' - const taxRateTitle = transaction?.taxRate?.text || defaultTaxName; + const previousTransactionAmount = usePrevious(transaction?.amount); const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); @@ -283,6 +302,14 @@ function MoneyRequestConfirmationList({ const [didConfirm, setDidConfirm] = useState(false); const [didConfirmSplit, setDidConfirmSplit] = useState(false); + const [merchantError, setMerchantError] = useState(false); + + const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); + + const navigateBack = () => { + Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); + }; + const shouldDisplayFieldError: boolean = useMemo(() => { if (!isEditingSplitBill) { return false; @@ -292,60 +319,89 @@ function MoneyRequestConfirmationList({ }, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]); const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const shouldDisplayMerchantError = isPolicyExpenseChat && shouldDisplayFieldError && isMerchantEmpty; + const isMerchantRequired = isPolicyExpenseChat && !isScanRequest && shouldShowMerchant; + + const isCategoryRequired = canUseViolations && !!policy?.requiresCategory; useEffect(() => { - if (shouldDisplayFieldError && didConfirmSplit) { - setFormError('iou.error.genericSmartscanFailureMessage'); + if ((!isMerchantRequired && isMerchantEmpty) || !merchantError) { return; } + if (!isMerchantEmpty && merchantError) { + setMerchantError(false); + if (formError === 'iou.error.invalidMerchant') { + setFormError(''); + } + } + }, [formError, isMerchantEmpty, merchantError, isMerchantRequired]); + + useEffect(() => { if (shouldDisplayFieldError && hasSmartScanFailed) { setFormError('iou.receiptScanningFailed'); return; } + if (shouldDisplayFieldError && didConfirmSplit) { + setFormError('iou.error.genericSmartscanFailureMessage'); + return; + } + if (merchantError) { + setFormError('iou.error.invalidMerchant'); + return; + } // reset the form error whenever the screen gains or loses focus setFormError(''); - }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit]); + }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit, isMerchantRequired, merchantError]); useEffect(() => { if (!shouldCalculateDistanceAmount) { return; } - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0); - IOU.setMoneyRequestAmount(amount); - }, [shouldCalculateDistanceAmount, distance, rate, unit]); + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); + IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amount, currency ?? ''); + }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency, transactionID]); + + // Calculate and set tax amount in transaction draft + useEffect(() => { + const taxAmount = getTaxAmount(transaction, taxRates?.defaultValue ?? '').toString(); + const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); + + if (transaction?.taxAmount && previousTransactionAmount === transaction?.amount) { + return IOU.setMoneyRequestTaxAmount(transactionID, transaction?.taxAmount, true); + } + + IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true); + }, [taxRates?.defaultValue, transaction, previousTransactionAmount, transactionID]); /** * Returns the participants with amount */ const getParticipantsWithAmount = useCallback( - (participantsList: Array): Array => { - const calculatedIouAmount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? ''); - return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( - participantsList, - iouAmount > 0 ? CurrencyUtils.convertToDisplayString(calculatedIouAmount, iouCurrencyCode) : '', - ); + (participantsList: Participant[]) => { + const amount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? ''); + return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(participantsList, amount > 0 ? CurrencyUtils.convertToDisplayString(amount, iouCurrencyCode) : ''); }, [iouAmount, iouCurrencyCode], ); - // If completing a split bill fails, set didConfirm to false to allow the user to edit the fields again + // If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again if (isEditingSplitBill && didConfirm) { setDidConfirm(false); } - const splitOrRequestOptions: Array> = useMemo(() => { + const splitOrRequestOptions: Array> = useMemo(() => { let text; - if (isSplitBill && iouAmount === 0) { - text = translate('iou.split'); - } else if ((!!receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) { - text = translate('iou.expense'); + if (isTypeTrackExpense) { + text = translate('iou.trackExpense'); + } else if (isTypeSplit && iouAmount === 0) { + text = translate('iou.splitExpense'); + } else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) { + text = translate('iou.submitExpense'); if (iouAmount !== 0) { text = translate('iou.submitAmount', {amount: formattedAmount}); } } else { - const translationKey = isSplitBill ? 'iou.splitAmount' : 'iou.submitAmount'; + const translationKey = isTypeSplit ? 'iou.splitAmount' : 'iou.submitAmount'; text = translate(translationKey, {amount: formattedAmount}); } return [ @@ -354,19 +410,19 @@ function MoneyRequestConfirmationList({ value: iouType, }, ]; - }, [isSplitBill, isTypeRequest, iouType, iouAmount, receiptPath, formattedAmount, isDistanceRequestWithPendingRoute, translate]); + }, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount]); - const selectedParticipants: Array = useMemo( - () => selectedParticipantsProp.filter((participant) => participant.selected), - [selectedParticipantsProp], - ); - const payeePersonalDetails = useMemo(() => payeePersonalDetailsProp ?? currentUserPersonalDetails, [payeePersonalDetailsProp, currentUserPersonalDetails]); - const canModifyParticipants = !isReadOnly && canModifyParticipantsProp && hasMultipleParticipants; - const shouldDisablePaidBySection = canModifyParticipants; + const selectedParticipants = useMemo(() => pickedParticipants.filter((participant) => participant.selected), [pickedParticipants]); + const personalDetailsOfPayee = useMemo(() => payeePersonalDetails ?? currentUserPersonalDetails, [payeePersonalDetails, currentUserPersonalDetails]); + const userCanModifyParticipants = useRef(!isReadOnly && canModifyParticipants && hasMultipleParticipants); + useEffect(() => { + userCanModifyParticipants.current = !isReadOnly && canModifyParticipants && hasMultipleParticipants; + }, [isReadOnly, canModifyParticipants, hasMultipleParticipants]); + const shouldDisablePaidBySection = userCanModifyParticipants.current; - const optionSelectorSections: OptionsListUtils.CategorySection[] = useMemo(() => { + const optionSelectorSections = useMemo(() => { const sections = []; - const unselectedParticipants = selectedParticipantsProp.filter((participant) => !participant.selected); + const unselectedParticipants = pickedParticipants.filter((participant) => !participant.selected); if (hasMultipleParticipants) { const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants); let formattedParticipantsList = [...new Set([...formattedSelectedParticipants, ...unselectedParticipants])]; @@ -380,7 +436,7 @@ function MoneyRequestConfirmationList({ const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, iouAmount, iouCurrencyCode ?? '', true); const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( - payeePersonalDetails, + personalDetailsOfPayee, iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode) : '', ); @@ -398,9 +454,9 @@ function MoneyRequestConfirmationList({ }, ); } else { - const formattedSelectedParticipants = selectedParticipantsProp.map((participant) => ({ + const formattedSelectedParticipants = selectedParticipants.map((participant) => ({ ...participant, - isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), + isDisabled: !participant.isPolicyExpenseChat && !participant.isSelfDM && ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); sections.push({ title: translate('common.to'), @@ -411,26 +467,26 @@ function MoneyRequestConfirmationList({ return sections; }, [ selectedParticipants, + pickedParticipants, hasMultipleParticipants, iouAmount, iouCurrencyCode, getParticipantsWithAmount, - selectedParticipantsProp, - payeePersonalDetails, + personalDetailsOfPayee, translate, shouldDisablePaidBySection, canModifyParticipants, ]); - const selectedOptions: Array = useMemo(() => { + const selectedOptions = useMemo(() => { if (!hasMultipleParticipants) { return []; } - return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetails)]; - }, [selectedParticipants, hasMultipleParticipants, payeePersonalDetails]); + return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetailsOfPayee)]; + }, [selectedParticipants, hasMultipleParticipants, personalDetailsOfPayee]); useEffect(() => { - if (!isDistanceRequest) { + if (!isDistanceRequest || isMovingTransactionFromTrackExpense) { return; } @@ -441,10 +497,39 @@ function MoneyRequestConfirmationList({ */ IOU.setMoneyRequestPendingFields(transactionID, {waypoints: isDistanceRequestWithPendingRoute ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : null}); - const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate ?? 0, currency ?? CONST.CURRENCY.USD, translate, toLocaleDigit); - IOU.setMoneyRequestMerchant(transactionID, distanceMerchant, false); - }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transactionID]); + const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate ?? 0, currency ?? 'USD', translate, toLocaleDigit); + IOU.setMoneyRequestMerchant(transactionID, distanceMerchant, true); + }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transactionID, isMovingTransactionFromTrackExpense]); + // Auto select the category if there is only one enabled category and it is required + useEffect(() => { + const enabledCategories = Object.values(policyCategories ?? {}).filter((category) => category.enabled); + if (iouCategory || !shouldShowCategories || enabledCategories.length !== 1 || !isCategoryRequired) { + return; + } + IOU.setMoneyRequestCategory(transactionID, enabledCategories[0].name); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [iouCategory, shouldShowCategories, policyCategories, isCategoryRequired]); + + // Auto select the tag if there is only one enabled tag and it is required + useEffect(() => { + let updatedTagsString = TransactionUtils.getTag(transaction); + policyTagLists.forEach((tagList, index) => { + const enabledTags = Object.values(tagList.tags).filter((tag) => tag.enabled); + const isTagListRequired = tagList.required === undefined ? false : tagList.required && canUseViolations; + if (!isTagListRequired || enabledTags.length !== 1 || TransactionUtils.getTag(transaction, index)) { + return; + } + updatedTagsString = IOUUtils.insertTagIntoTransactionTagsString(updatedTagsString, enabledTags[0] ? enabledTags[0].name : '', index); + }); + if (updatedTagsString !== TransactionUtils.getTag(transaction) && updatedTagsString) { + IOU.setMoneyRequestTag(transactionID, updatedTagsString); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [policyTagLists, policyTags, canUseViolations]); + + /** + */ const selectParticipant = useCallback( (option: Participant) => { // Return early if selected option is currently logged in user. @@ -460,24 +545,33 @@ function MoneyRequestConfirmationList({ * Navigate to report details or profile of selected user */ const navigateToReportOrUserDetail = (option: ReportUtils.OptionData) => { - if (option.accountID) { - const activeRoute = Navigation.getActiveRouteWithoutParams(); + const activeRoute = Navigation.getActiveRouteWithoutParams(); + + if (option.isSelfDM) { + Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserPersonalDetails.accountID, activeRoute)); + return; + } + if (option.accountID) { Navigation.navigate(ROUTES.PROFILE.getRoute(option.accountID, activeRoute)); } else if (option.reportID) { - Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(option.reportID)); + Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(option.reportID, activeRoute)); } }; + /** + * @param {String} paymentMethod + */ const confirm = useCallback( - (paymentMethod: IOUType | PaymentMethodType | undefined) => { - if (!selectedParticipants.length) { + (paymentMethod: PaymentMethodType | undefined) => { + if (selectedParticipants.length === 0) { return; } - if (iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { - setFormError('iou.error.invalidCategoryLength'); + if ((isMerchantRequired && isMerchantEmpty) || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction ?? null))) { + setMerchantError(true); return; } + if (iouType === CONST.IOU.TYPE.SEND) { if (!paymentMethod) { return; @@ -488,34 +582,38 @@ function MoneyRequestConfirmationList({ Log.info(`[IOU] Sending money via: ${paymentMethod}`); onSendMoney?.(paymentMethod); } else { - // validate the amount for distance requests + // validate the amount for distance expenses const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); if (isDistanceRequest && !isDistanceRequestWithPendingRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) { setFormError('common.error.invalidAmount'); return; } - if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction ?? null)) { setDidConfirmSplit(true); + setFormError('iou.error.genericSmartscanFailureMessage'); return; } + playSound(SOUNDS.DONE); setDidConfirm(true); onConfirm?.(selectedParticipants); } }, [ selectedParticipants, - onSendMoney, - onConfirm, - isEditingSplitBill, + isMerchantRequired, + isMerchantEmpty, + shouldDisplayFieldError, + transaction, iouType, + onSendMoney, + iouCurrencyCode, isDistanceRequest, - iouCategory, isDistanceRequestWithPendingRoute, - iouCurrencyCode, iouAmount, - transaction, + isEditingSplitBill, + onConfirm, ], ); @@ -534,6 +632,7 @@ function MoneyRequestConfirmationList({ onPress={confirm} enablePaymentsRoute={ROUTES.IOU_SEND_ENABLE_PAYMENTS} addBankAccountRoute={bankAccountRoute} + shouldShowPersonalBankAccountOption currency={iouCurrencyCode} policyID={policyID} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} @@ -545,7 +644,6 @@ function MoneyRequestConfirmationList({ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }} - shouldShowPersonalBankAccountOption enterKeyEventListenerPriority={1} /> ) : ( @@ -553,333 +651,381 @@ function MoneyRequestConfirmationList({ success pressOnEnter isDisabled={shouldDisableButton} - // eslint-disable-next-line @typescript-eslint/naming-convention - onPress={(_event, value) => confirm(value)} + onPress={(event, value) => confirm(value as PaymentMethodType)} options={splitOrRequestOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} enterKeyEventListenerPriority={1} /> ); + return ( <> - {!!formError.length && ( + {!!formError && ( )} + {button} ); - }, [isReadOnly, iouType, bankAccountRoute, iouCurrencyCode, policyID, selectedParticipants.length, confirm, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); + }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); + + // An intermediate structure that helps us classify the fields as "primary" and "supplementary". + // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. + const classifiedFields = [ + { + item: ( + { + if (isDistanceRequest) { + return; + } + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.AMOUNT)); + return; + } + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + style={[styles.moneyRequestMenuItem, styles.mt2]} + titleStyle={styles.moneyRequestConfirmationAmount} + disabled={didConfirm} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction ?? null) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction ?? null) ? translate('common.error.enterAmount') : ''} + /> + ), + shouldShow: shouldShowSmartScanFields, + isSupplementary: false, + }, + { + item: ( + { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + style={[styles.moneyRequestMenuItem]} + titleStyle={styles.flex1} + disabled={didConfirm} + interactive={!isReadOnly} + numberOfLinesTitle={2} + /> + ), + shouldShow: true, + isSupplementary: false, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + disabled={didConfirm} + // todo: handle edit for transaction while moving from track expense + interactive={!isReadOnly && !isMovingTransactionFromTrackExpense} + /> + ), + shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, + isSupplementary: false, + }, + { + item: ( + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + } + disabled={didConfirm} + interactive={!isReadOnly} + /> + ), + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, + isSupplementary: false, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={Boolean(rate) && !isReadOnly && isPolicyExpenseChat} + /> + ), + shouldShow: isDistanceRequest, + isSupplementary: true, + }, + { + item: ( + { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + disabled={didConfirm} + interactive={!isReadOnly} + brickRoadIndicator={merchantError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={merchantError ? translate('common.error.fieldRequired') : ''} + rightLabel={isMerchantRequired ? translate('common.required') : ''} + /> + ), + shouldShow: shouldShowMerchant, + isSupplementary: !isMerchantRequired, + }, + { + item: ( + { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + disabled={didConfirm} + interactive={!isReadOnly} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? translate('common.error.enterDate') : ''} + /> + ), + shouldShow: shouldShowDate, + isSupplementary: true, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + style={[styles.moneyRequestMenuItem]} + titleStyle={styles.flex1} + disabled={didConfirm} + interactive={!isReadOnly} + rightLabel={isCategoryRequired ? translate('common.required') : ''} + /> + ), + shouldShow: shouldShowCategories, + isSupplementary: action === CONST.IOU.ACTION.CATEGORIZE ? false : !isCategoryRequired, + }, + ...policyTagLists.map(({name, required}, index) => { + const isTagRequired = required === undefined ? false : canUseViolations && required; + return { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + style={[styles.moneyRequestMenuItem]} + disabled={didConfirm} + interactive={!isReadOnly} + rightLabel={isTagRequired ? translate('common.required') : ''} + /> + ), + shouldShow: shouldShowTags, + isSupplementary: !isTagRequired, + }; + }), + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={!isReadOnly} + /> + ), + shouldShow: shouldShowTax, + isSupplementary: true, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={!isReadOnly} + /> + ), + shouldShow: shouldShowTax, + isSupplementary: true, + }, + { + item: ( + + {translate('common.billable')} + onToggleBillable?.(isOn)} + /> + + ), + shouldShow: shouldShowBillable, + isSupplementary: true, + }, + ]; + + const primaryFields = classifiedFields.filter((classifiedField) => classifiedField.shouldShow && !classifiedField.isSupplementary).map((primaryField) => primaryField.item); + + const supplementaryFields = classifiedFields + .filter((classifiedField) => classifiedField.shouldShow && classifiedField.isSupplementary) + .map((supplementaryField) => supplementaryField.item); const { image: receiptImage, thumbnail: receiptThumbnail, isThumbnail, fileExtension, - } = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); + isLocalFile, + } = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); + + const resolvedThumbnail = isLocalFile ? receiptThumbnail : tryResolveUrlFromApiRoot(receiptThumbnail ?? ''); + const resolvedReceiptImage = isLocalFile ? receiptImage : tryResolveUrlFromApiRoot(receiptImage ?? ''); + + const receiptThumbnailContent = useMemo( + () => + isLocalFile && Str.isPDF(receiptFilename) ? ( + setIsAttachmentInvalid(true)} + /> + ) : ( + + ), + [isLocalFile, receiptFilename, resolvedThumbnail, styles.moneyRequestImage, isAttachmentInvalid, isThumbnail, resolvedReceiptImage, receiptThumbnail, fileExtension], + ); + return ( // @ts-expect-error This component is deprecated and will not be migrated to TypeScript (context: https://expensify.slack.com/archives/C01GTK53T8Q/p1709232289899589?thread_ts=1709156803.359359&cid=C01GTK53T8Q) {isDistanceRequest && ( - + )} - {/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */} - {receiptImage || receiptThumbnail ? ( - - ) : ( - // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") - PolicyUtils.isPaidGroupPolicy(policy) && - !isDistanceRequest && - iouType === CONST.IOU.TYPE.REQUEST && ( - - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( - CONST.IOU.ACTION.CREATE, - iouType, - transaction?.transactionID ?? '', - reportID, - Navigation.getActiveRouteWithoutParams(), - ), - ) - } + {(!isMovingTransactionFromTrackExpense || !hasRoute) && + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + (receiptImage || receiptThumbnail + ? receiptThumbnailContent + : // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") + PolicyUtils.isPaidGroupPolicy(policy) && + !isDistanceRequest && + iouType === CONST.IOU.TYPE.REQUEST && ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + /> + ))} + {primaryFields} + {!shouldShowAllFields && ( + + +