-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Wave Collect] [Workflows] Display meaningful message when currency is not available for Direct Reimbursement #38318
Changes from 13 commits
16cae1b
cff3658
47c3c74
e515c8e
a280421
ef1d156
3aa3925
a7d48f2
b3d9939
20af98e
be8fd34
e053abc
c1c65f6
f385e85
7cc1c1a
9f0a1fe
7343b48
b7b2a07
84a1c83
7c95b21
a2a788e
428bbe6
39f9161
cf6a4bd
c2a82ca
14a7018
801c6ae
086c310
fc71b6d
45c7923
c1d1aa6
172c8b3
1140772
a932d16
256049f
adb5d5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,6 +129,14 @@ type WorkspaceMembersRoleData = { | |
role: typeof CONST.POLICY.ROLE.ADMIN | typeof CONST.POLICY.ROLE.USER; | ||
}; | ||
|
||
const CURRENCY_AU = 'AUD'; | ||
const CURRENCY_CA = 'CAD'; | ||
const CURRENCY_GB = 'GBP'; | ||
const CURRENCY_US = 'USD'; | ||
const CURRENCY_EUR = 'EUR'; | ||
|
||
const DIRECT_REIMBURSEMENT_CURRENCIES: string[] = [CURRENCY_AU, CURRENCY_CA, CURRENCY_EUR, CURRENCY_GB, CURRENCY_US]; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have these defined here, it also seems like we support NZD, which is missing in your list above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 definitely lets reuse this from Const or add there what is missing there There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const allPolicies: OnyxCollection<Policy> = {}; | ||
Onyx.connect({ | ||
key: ONYXKEYS.COLLECTION.POLICY, | ||
|
@@ -249,6 +257,9 @@ function updateLastAccessedWorkspace(policyID: OnyxEntry<string>) { | |
Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID); | ||
} | ||
|
||
function hasValidCurrencyForDirectReimbursement(currency: string) { | ||
lakchote marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return DIRECT_REIMBURSEMENT_CURRENCIES.includes(currency); | ||
} | ||
/** | ||
* Check if the user has any active free policies (aka workspaces) | ||
*/ | ||
|
@@ -466,6 +477,7 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency | |
}, | ||
autoReportingFrequency: policy.autoReportingFrequency ?? null, | ||
pendingFields: {isAutoApprovalEnabled: null, harvesting: null}, | ||
errorFields: {autoReporting: ErrorUtils.getMicroSecondOnyxError('workflowsDelayedSubmissionPage.autoReportingErrorMessage')}, | ||
}, | ||
}, | ||
]; | ||
|
@@ -506,6 +518,7 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf | |
value: { | ||
autoReportingFrequency: policy.autoReportingFrequency ?? null, | ||
pendingFields: {autoReportingFrequency: null}, | ||
errorFields: {autoReportingFrequency: ErrorUtils.getMicroSecondOnyxError('workflowsDelayedSubmissionPage.autoReportingFrequencyErrorMessage')}, | ||
}, | ||
}, | ||
]; | ||
|
@@ -546,6 +559,7 @@ function setWorkspaceAutoReportingMonthlyOffset(policyID: string, autoReportingO | |
value: { | ||
autoReportingOffset: policy.autoReportingOffset ?? null, | ||
pendingFields: {autoReportingOffset: null}, | ||
errorFields: {autoReportingOffset: ErrorUtils.getMicroSecondOnyxError('workflowsDelayedSubmissionPage.monthlyOffsetErrorMessage')}, | ||
}, | ||
}, | ||
]; | ||
|
@@ -594,6 +608,7 @@ function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMo | |
approvalMode: policy.approvalMode ?? null, | ||
isAutoApprovalEnabled: policy.isAutoApprovalEnabled ?? null, | ||
pendingFields: {approvalMode: null}, | ||
errorFields: {approvalMode: ErrorUtils.getMicroSecondOnyxError('workflowsApprovalPage.genericErrorMessage')}, | ||
}, | ||
}, | ||
]; | ||
|
@@ -657,6 +672,26 @@ function setWorkspacePayer(policyID: string, reimburserEmail: string, reimburser | |
API.write(WRITE_COMMANDS.SET_WORKSPACE_PAYER, params, {optimisticData, failureData, successData}); | ||
} | ||
|
||
function clearWorkspaceAutoReportingError(policyID: string) { | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errorFields: {autoReporting: null}}); | ||
} | ||
|
||
function clearWorkspaceAutoReportingFrequencyError(policyID: string) { | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errorFields: {autoReportingFrequency: null}}); | ||
} | ||
|
||
function clearWorkspaceGeneralSettingError(policyID: string) { | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errorFields: {generalSettings: null}}); | ||
} | ||
|
||
function clearWorkspaceAutoReportingOffsetError(policyID: string) { | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errorFields: {autoReportingOffset: null}}); | ||
} | ||
|
||
function clearWorkspaceApprovalError(policyID: string) { | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errorFields: {approvalMode: null}}); | ||
} | ||
|
||
lakchote marked this conversation as resolved.
Show resolved
Hide resolved
|
||
function clearWorkspacePayerError(policyID: string) { | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errorFields: {reimburserEmail: null}}); | ||
} | ||
|
@@ -1358,6 +1393,7 @@ function updateGeneralSettings(policyID: string, name: string, currency: string) | |
}, | ||
}, | ||
]; | ||
|
||
const failureData: OnyxUpdate[] = [ | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
|
@@ -3908,4 +3944,10 @@ export { | |
clearWorkspaceReimbursementErrors, | ||
deleteWorkspaceCategories, | ||
setWorkspaceTagEnabled, | ||
clearWorkspaceApprovalError, | ||
clearWorkspaceAutoReportingError, | ||
clearWorkspaceAutoReportingFrequencyError, | ||
clearWorkspaceAutoReportingOffsetError, | ||
clearWorkspaceGeneralSettingError, | ||
hasValidCurrencyForDirectReimbursement, | ||
}; |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,8 +1,9 @@ | ||||
import type {StackScreenProps} from '@react-navigation/stack'; | ||||
import React, {useCallback, useEffect, useMemo} from 'react'; | ||||
import React, {useCallback, useEffect, useMemo, useState} from 'react'; | ||||
import {FlatList, View} from 'react-native'; | ||||
import type {OnyxEntry} from 'react-native-onyx'; | ||||
import {withOnyx} from 'react-native-onyx'; | ||||
import ConfirmModal from '@components/ConfirmModal'; | ||||
import * as Illustrations from '@components/Icon/Illustrations'; | ||||
import MenuItem from '@components/MenuItem'; | ||||
import OfflineWithFeedback from '@components/OfflineWithFeedback'; | ||||
|
@@ -53,6 +54,7 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses | |||
const policyApproverName = useMemo(() => PersonalDetailsUtils.getPersonalDetailByEmail(policyApproverEmail ?? '')?.displayName ?? policyApproverEmail, [policyApproverEmail]); | ||||
const containerStyle = useMemo(() => [styles.ph8, styles.mhn8, styles.ml11, styles.pv3, styles.pr0, styles.pl4, styles.mr0, styles.widthAuto, styles.mt4], [styles]); | ||||
const canUseDelayedSubmission = Permissions.canUseWorkflowsDelayedSubmission(betas); | ||||
const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false); | ||||
|
||||
const displayNameForAuthorizedPayer = useMemo(() => { | ||||
const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs([policy?.reimburserAccountID ?? 0], session?.accountID ?? 0); | ||||
|
@@ -66,6 +68,15 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses | |||
Policy.openPolicyWorkflowsPage(policy?.id ?? route.params.policyID); | ||||
}; | ||||
|
||||
const confirmCurrencyChangeAndHideModal = useCallback(() => { | ||||
if (!policy) { | ||||
return; | ||||
} | ||||
Policy.updateGeneralSettings(policy.id, policy.name, CONST.CURRENCY.USD); | ||||
setIsCurrencyModalOpen(false); | ||||
navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); | ||||
}, [policy, route.params.policyID]); | ||||
|
||||
useNetwork({onReconnect: fetchData}); | ||||
|
||||
useEffect(() => { | ||||
|
@@ -78,6 +89,8 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses | |||
const hasVBA = state === BankAccount.STATE.OPEN; | ||||
const bankDisplayName = bankName ? `${bankName} ${accountNumber ? `${accountNumber.slice(-5)}` : ''}` : ''; | ||||
const hasReimburserEmailError = !!policy?.errorFields?.reimburserEmail; | ||||
const hasApprovalError = !!policy?.errorFields?.approvalMode; | ||||
const hasDelayedSubmissionError = !!policy?.errorFields?.autoReporting; | ||||
|
||||
return [ | ||||
...(canUseDelayedSubmission | ||||
|
@@ -108,10 +121,13 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses | |||
shouldShowRightIcon | ||||
wrapperStyle={containerStyle} | ||||
hoverAndPressStyle={[styles.mr0, styles.br2]} | ||||
brickRoadIndicator={hasDelayedSubmissionError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} | ||||
/> | ||||
), | ||||
isActive: (policy?.harvesting?.enabled && policy.autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT) ?? false, | ||||
isActive: (policy?.harvesting?.enabled && policy.autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT && !hasDelayedSubmissionError) ?? false, | ||||
pendingAction: policy?.pendingFields?.isAutoApprovalEnabled, | ||||
errors: ErrorUtils.getLatestErrorField(policy ?? {}, 'autoReporting'), | ||||
lakchote marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
onCloseError: () => Policy.clearWorkspaceAutoReportingError(policy?.id ?? ''), | ||||
}, | ||||
] | ||||
: []), | ||||
|
@@ -132,18 +148,20 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses | |||
shouldShowRightIcon | ||||
wrapperStyle={containerStyle} | ||||
hoverAndPressStyle={[styles.mr0, styles.br2]} | ||||
brickRoadIndicator={hasApprovalError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} | ||||
/> | ||||
), | ||||
isActive: policy?.isAutoApprovalEnabled ?? false, | ||||
isActive: (policy?.isAutoApprovalEnabled && !hasApprovalError) ?? false, | ||||
pendingAction: policy?.pendingFields?.approvalMode, | ||||
errors: ErrorUtils.getLatestErrorField(policy ?? {}, 'approvalMode'), | ||||
lakchote marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
onCloseError: () => Policy.clearWorkspaceApprovalError(policy?.id ?? ''), | ||||
}, | ||||
{ | ||||
icon: Illustrations.WalletAlt, | ||||
title: translate('workflowsPage.makeOrTrackPaymentsTitle'), | ||||
subtitle: translate('workflowsPage.makeOrTrackPaymentsDescription'), | ||||
onToggle: () => { | ||||
const isActive = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES; | ||||
const newReimbursementChoice = isActive ? CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL : CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES; | ||||
const newReimbursementChoice = !hasVBA ? CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL : CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will never toggle on from a clean state with no VBA, as we will set to
It also won't toggle off as we don't set |
||||
const newReimburserAccountID = | ||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | ||||
PersonalDetailsUtils.getPersonalDetailByEmail(policy?.reimburserEmail ?? '')?.accountID || policy?.reimburserAccountID || policy?.ownerAccountID; | ||||
|
@@ -157,7 +175,13 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses | |||
descriptionTextStyle={styles.textNormalThemeText} | ||||
title={hasVBA ? translate('common.bankAccount') : translate('workflowsPage.connectBankAccount')} | ||||
description={state === BankAccount.STATE.OPEN ? bankDisplayName : undefined} | ||||
onPress={() => navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID))} | ||||
onPress={() => { | ||||
if (!Policy.hasValidCurrencyForDirectReimbursement(policy?.outputCurrency ?? '')) { | ||||
setIsCurrencyModalOpen(true); | ||||
return; | ||||
} | ||||
navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); | ||||
}} | ||||
shouldShowRightIcon | ||||
wrapperStyle={containerStyle} | ||||
hoverAndPressStyle={[styles.mr0, styles.br2]} | ||||
|
@@ -250,6 +274,16 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses | |||
renderItem={renderOptionItem} | ||||
keyExtractor={(item: ToggleSettingOptionRowProps) => item.title} | ||||
/> | ||||
<ConfirmModal | ||||
title={translate('workspace.bankAccount.workspaceCurrency')} | ||||
isVisible={isCurrencyModalOpen} | ||||
onConfirm={confirmCurrencyChangeAndHideModal} | ||||
onCancel={() => setIsCurrencyModalOpen(false)} | ||||
prompt={translate('workspace.bankAccount.updateCurrencyPrompt')} | ||||
confirmText={translate('workspace.bankAccount.updateToUSD')} | ||||
cancelText={translate('common.cancel')} | ||||
danger | ||||
/> | ||||
</View> | ||||
</Section> | ||||
</View> | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has marketing reviewed these copies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asked about it here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lakchote This is usually done using Marketing label autoassigner on the linked issue so we get one dedicated person for this