Didn't find what you were looking for?
Concierge is here to answer all your questions.
diff --git a/src/CONST.ts b/src/CONST.ts
index 4bef4022af62..b07b622cec05 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1200,6 +1200,24 @@ const CONST = {
EXPENSIFY_EMAIL_DOMAIN: '@expensify.com',
},
+ INTEGRATION_ENTITY_MAP_TYPES: {
+ DEFAULT: 'DEFAULT',
+ NONE: 'NONE',
+ TAG: 'TAG',
+ REPORT_FIELD: 'REPORT_FIELD',
+ NOT_IMPORTED: 'NOT_IMPORTED',
+ IMPORTED: 'IMPORTED',
+ },
+ QUICK_BOOKS_ONLINE: 'quickbooksOnline',
+
+ QUICK_BOOKS_IMPORTS: {
+ SYNC_CLASSES: 'syncClasses',
+ ENABLE_NEW_CATEGORIES: 'enableNewCategories',
+ SYNC_CUSTOMERS: 'syncCustomers',
+ SYNC_LOCATIONS: 'syncLocations',
+ SYNC_TAXES: 'syncTaxes',
+ },
+
ACCOUNT_ID: {
ACCOUNTING: Number(Config?.EXPENSIFY_ACCOUNT_ID_ACCOUNTING ?? 9645353),
ADMIN: Number(Config?.EXPENSIFY_ACCOUNT_ID_ADMIN ?? -1),
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 25a84c4480e3..60fca9fac87b 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -1,5 +1,6 @@
import type {IsEqual, ValueOf} from 'type-fest';
import type CONST from './CONST';
+import type {IOURequestType} from './libs/actions/IOU';
// This is a file containing constants for all the routes we want to be able to go to
@@ -396,7 +397,7 @@ const ROUTES = {
// straight to those flows without needing to have optimistic transaction and report IDs.
MONEY_REQUEST_START: {
route: 'start/:iouType/:iouRequestType',
- getRoute: (iouType: ValueOf
, iouRequestType: ValueOf) => `start/${iouType}/${iouRequestType}` as const,
+ getRoute: (iouType: ValueOf, iouRequestType: IOURequestType) => `start/${iouType}/${iouRequestType}` as const,
},
MONEY_REQUEST_CREATE_TAB_DISTANCE: {
route: ':action/:iouType/start/:transactionID/:reportID/distance',
@@ -697,6 +698,30 @@ const ROUTES = {
route: 'r/:reportID/transaction/:transactionID/receipt',
getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const,
},
+ WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import` as const,
+ },
+ WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/accounts',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/accounts` as const,
+ },
+ WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/classes',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/classes` as const,
+ },
+ WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/customers',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/customers` as const,
+ },
+ WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/locations',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/locations` as const,
+ },
+ WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const,
+ },
} as const;
/**
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index e7cd76a1907b..b3c2012e90d2 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -253,6 +253,12 @@ const SCREENS = {
DISTANCE_RATES: 'Distance_Rates',
CREATE_DISTANCE_RATE: 'Create_Distance_Rate',
DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings',
+ QUICKBOOKS_ONLINE_IMPORT: 'Workspace_Accounting_Quickbooks_Online_Import',
+ QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: 'Workspace_Accounting_Quickbooks_Online_Import_Chart_Of_Accounts',
+ QUICKBOOKS_ONLINE_CLASSES: 'Workspace_Accounting_Quickbooks_Online_Import_Classes',
+ QUICKBOOKS_ONLINE_CUSTOMERS: 'Workspace_Accounting_Quickbooks_Online_Import_Customers',
+ QUICKBOOKS_ONLINE_LOCATIONS: 'Workspace_Accounting_Quickbooks_Online_Import_Locations',
+ QUICKBOOKS_ONLINE_TAXES: 'Workspace_Accounting_Quickbooks_Online_Import_Taxes',
DISTANCE_RATE_DETAILS: 'Distance_Rate_Details',
DISTANCE_RATE_EDIT: 'Distance_Rate_Edit',
},
diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx
index 396c10151fbf..f6afb4dae2d6 100644
--- a/src/components/AvatarWithDisplayName.tsx
+++ b/src/components/AvatarWithDisplayName.tsx
@@ -141,6 +141,7 @@ function AvatarWithDisplayName({
)}
diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx
index 0307b67114e5..f26d7c25c7e2 100644
--- a/src/components/CategoryPicker.tsx
+++ b/src/components/CategoryPicker.tsx
@@ -21,7 +21,7 @@ type CategoryPickerProps = CategoryPickerOnyxProps & {
/** It's used by withOnyx HOC */
// eslint-disable-next-line react/no-unused-prop-types
policyID: string;
- selectedCategory: string;
+ selectedCategory?: string;
onSubmit: (item: ListItem) => void;
};
@@ -38,7 +38,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC
{
name: selectedCategory,
enabled: true,
- accountID: null,
+ accountID: undefined,
isSelected: true,
},
];
diff --git a/src/components/OnboardingWelcomeVideo.tsx b/src/components/OnboardingWelcomeVideo.tsx
index f38bd4edcc10..257bd1b9c457 100644
--- a/src/components/OnboardingWelcomeVideo.tsx
+++ b/src/components/OnboardingWelcomeVideo.tsx
@@ -1,6 +1,7 @@
import type {VideoReadyForDisplayEvent} from 'expo-av';
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
+import {GestureHandlerRootView} from 'react-native-gesture-handler';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnboardingLayout from '@hooks/useOnboardingLayout';
@@ -134,22 +135,24 @@ function OnboardingWelcomeVideo() {
: {}),
}}
>
-
- {getWelcomeVideo()}
-
-
- {translate('onboarding.welcomeVideo.title')}
- {translate('onboarding.welcomeVideo.description')}
+
+
+ {getWelcomeVideo()}
+
+
+ {translate('onboarding.welcomeVideo.title')}
+ {translate('onboarding.welcomeVideo.description')}
+
+
-
-
+
)}
diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx
index 3109453ca6b0..0ad32f18659b 100644
--- a/src/components/ParentNavigationSubtitle.tsx
+++ b/src/components/ParentNavigationSubtitle.tsx
@@ -1,8 +1,10 @@
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
+import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
import type {ParentNavigationSummaryParams} from '@src/languages/types';
import ROUTES from '@src/ROUTES';
@@ -15,20 +17,25 @@ type ParentNavigationSubtitleProps = {
/** parent Report ID */
parentReportID?: string;
+ /** parent Report Action ID */
+ parentReportActionID?: string;
+
/** PressableWithoutFeedack additional styles */
pressableStyles?: StyleProp;
};
-function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportID = '', pressableStyles}: ParentNavigationSubtitleProps) {
+function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportActionID, parentReportID = '', pressableStyles}: ParentNavigationSubtitleProps) {
const styles = useThemeStyles();
const {workspaceName, reportName} = parentNavigationSubtitleData;
-
+ const {isOffline} = useNetwork();
const {translate} = useLocalize();
return (
{
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(parentReportID));
+ const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '');
+ const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '');
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(parentReportID, isVisibleAction && !isOffline ? parentReportActionID : undefined));
}}
accessibilityLabel={translate('threads.parentNavigationSummary', {reportName, workspaceName})}
role={CONST.ROLE.LINK}
diff --git a/src/components/TagPicker/index.tsx b/src/components/TagPicker/index.tsx
index b9d2d61efa7d..ff5768efaede 100644
--- a/src/components/TagPicker/index.tsx
+++ b/src/components/TagPicker/index.tsx
@@ -14,7 +14,7 @@ import type {PolicyTag, PolicyTagList, PolicyTags, RecentlyUsedTags} from '@src/
type SelectedTagOption = {
name: string;
enabled: boolean;
- accountID: number | null;
+ accountID: number | undefined;
};
type TagPickerOnyxProps = {
@@ -68,7 +68,7 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe
{
name: selectedTag,
enabled: true,
- accountID: null,
+ accountID: undefined,
},
];
}, [selectedTag]);
diff --git a/src/languages/en.ts b/src/languages/en.ts
index a1a7d96d5c46..3b670f7b6ebc 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1818,6 +1818,7 @@ export default {
reimburse: 'Reimbursements',
categories: 'Categories',
tags: 'Tags',
+ reportFields: 'Report Fields',
taxes: 'Taxes',
bills: 'Bills',
invoices: 'Invoices',
@@ -1849,6 +1850,29 @@ export default {
welcomeNote: ({workspaceName}: WelcomeNoteParams) =>
`You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`,
},
+ qbo: {
+ import: 'Import',
+ importDescription: 'Choose which coding configurations are imported from QuickBooks Online to Expensify.',
+ classes: 'Classes',
+ accounts: 'Chart of accounts',
+ locations: 'Locations',
+ taxes: 'Taxes',
+ customers: 'Customers/Projects',
+ imported: 'Imported',
+ displayedAs: 'Displayed as',
+ notImported: 'Not imported',
+ importedAsTags: 'Imported, displayed as tags',
+ importedAsReportFields: 'Imported, displayed as report fields',
+ accountsDescription: 'Chart of Accounts import as categories when connected to an accounting integration, this cannot be disabled.',
+ accountsSwitchTitle: 'Enable newly imported Chart of Accounts.',
+ accountsSwitchDescription: 'New categories imported from QuickBooks Online to Expensify will be either enabled or disabled by default.',
+ classesDescription: 'Choose whether to import classes, and see where classes are displayed.',
+ customersDescription: 'Choose whether to import customers/projects and see where customers/projects are displayed.',
+ locationsDescription: 'Choose whether to import locations, and see where locations are displayed.',
+ taxesDescription: 'Choose whether to import tax rates and tax defaults from your accounting integration.',
+ locationsAdditionalDescription:
+ 'Locations are imported as Tags. This limits exporting expense reports as Vendor Bills or Checks to QuickBooks Online. To unlock these export options, either disable Locations import or upgrade to the Control Plan to export Locations encoded as a Report Field.',
+ },
type: {
free: 'Free',
control: 'Control',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 5ea339d4fe36..5027174b2922 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1845,6 +1845,7 @@ export default {
reimburse: 'Reembolsos',
categories: 'Categorías',
tags: 'Etiquetas',
+ reportFields: 'Campos de informe',
taxes: 'Impuestos',
bills: 'Pagar facturas',
invoices: 'Enviar facturas',
@@ -1876,6 +1877,29 @@ export default {
welcomeNote: ({workspaceName}: WelcomeNoteParams) =>
`¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`,
},
+ qbo: {
+ import: 'Importación',
+ importDescription: 'Elige que configuraciónes de codificación son importadas desde QuickBooks Online a Expensify.',
+ classes: 'Clases',
+ accounts: 'Plan de cuentas',
+ locations: 'Lugares',
+ taxes: 'Impuestos',
+ customers: 'Clientes/Proyectos',
+ imported: 'Importado',
+ displayedAs: 'Mostrado como',
+ notImported: 'No importado',
+ importedAsTags: 'Importado, mostrado como etiqueta',
+ importedAsReportFields: 'Importado, mostrado como campo de informe',
+ accountsDescription: 'Los planes de cuentas se importan como categorías cuando está conectado con una integración de contaduría, esto no se puede desactivar.',
+ accountsSwitchTitle: 'Habilita el plan de cuentas recien importado',
+ accountsSwitchDescription: 'Las nuevas categorías importadas desde QuickBooks Online a Expensify serán activadas o desactivadas por defecto.',
+ classesDescription: 'Elige si quieres importar las clases y donde las clases son mostradas.',
+ customersDescription: 'Elige si queres importar clientes/proyectos y donde los clientes/proyectos son mostrados.',
+ locationsDescription: 'Elige si quieres importar lugares y donde los lugares son mostrados.',
+ taxesDescription: 'Elige si quires importar las tasas de impuestos y los impuestos por defecto de tu integración de contaduría.',
+ locationsAdditionalDescription:
+ 'Los lugares son importados como Etiquegas. Esto limita a exportar los informes de gastos como Factura del Proveedor o Cheques a Quicbooks Online. Para desbloquear estas opciones de exportación desactiva la importación de Lugares o cambia al Plan Control para exportar Lugares como Campos de Informes.',
+ },
type: {
free: 'Gratis',
control: 'Control',
diff --git a/src/libs/API/parameters/RequestMoneyParams.ts b/src/libs/API/parameters/RequestMoneyParams.ts
index b55f9fd7a2a9..ce8fb99c3f25 100644
--- a/src/libs/API/parameters/RequestMoneyParams.ts
+++ b/src/libs/API/parameters/RequestMoneyParams.ts
@@ -17,7 +17,7 @@ type RequestMoneyParams = {
createdChatReportActionID: string;
createdIOUReportActionID: string;
reportPreviewReportActionID: string;
- receipt: Receipt;
+ receipt?: Receipt;
receiptState?: ValueOf;
category?: string;
tag?: string;
diff --git a/src/libs/API/parameters/TrackExpenseParams.ts b/src/libs/API/parameters/TrackExpenseParams.ts
index f48c8666f109..9c8d9761d888 100644
--- a/src/libs/API/parameters/TrackExpenseParams.ts
+++ b/src/libs/API/parameters/TrackExpenseParams.ts
@@ -15,7 +15,7 @@ type TrackExpenseParams = {
createdChatReportActionID: string;
createdIOUReportActionID?: string;
reportPreviewReportActionID?: string;
- receipt: Receipt;
+ receipt?: Receipt;
receiptState?: ValueOf;
category?: string;
tag?: string;
diff --git a/src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts b/src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts
new file mode 100644
index 000000000000..be062435eaa4
--- /dev/null
+++ b/src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts
@@ -0,0 +1,12 @@
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
+
+type UpdatePolicyConnectionConfigParams = {
+ policyID: string;
+ connectionName: string;
+ settingName: ValueOf;
+ settingValue: ValueOf;
+ idempotencyKey: string;
+};
+
+export default UpdatePolicyConnectionConfigParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 8ef3f255184e..6f5505b263fb 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -197,4 +197,5 @@ export type {default as DeletePolicyTagsParams} from './DeletePolicyTagsParams';
export type {default as SetPolicyCustomTaxNameParams} from './SetPolicyCustomTaxNameParams';
export type {default as SetPolicyForeignCurrencyDefaultParams} from './SetPolicyForeignCurrencyDefaultParams';
export type {default as SetPolicyCurrencyDefaultParams} from './SetPolicyCurrencyDefaultParams';
+export type {default as UpdatePolicyConnectionConfigParams} from './UpdatePolicyConnectionConfigParams';
export type {default as RenamePolicyTaxParams} from './RenamePolicyTaxParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index aa4e57561ba0..fc19ba60693c 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -185,6 +185,7 @@ const WRITE_COMMANDS = {
ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest',
DECLINE_JOIN_REQUEST: 'DeclineJoinRequest',
CREATE_POLICY_TAX: 'CreatePolicyTax',
+ UPDATE_POLICY_CONNECTION_CONFIG: 'UpdatePolicyConnectionConfig',
SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled',
DELETE_POLICY_TAXES: 'DeletePolicyTaxes',
UPDATE_POLICY_TAX_VALUE: 'UpdatePolicyTaxValue',
@@ -389,6 +390,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.RENAME_POLICY_TAX]: Parameters.RenamePolicyTaxParams;
[WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_UNIT]: Parameters.SetPolicyDistanceRatesUnitParams;
[WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY]: Parameters.SetPolicyDistanceRatesDefaultCategoryParams;
+ [WRITE_COMMANDS.UPDATE_POLICY_CONNECTION_CONFIG]: Parameters.UpdatePolicyConnectionConfigParams;
[WRITE_COMMANDS.UPDATE_POLICY_DISTANCE_RATE_VALUE]: Parameters.UpdatePolicyDistanceRateValueParams;
[WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED]: Parameters.SetPolicyDistanceRatesEnabledParams;
[WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams;
diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts
index 65390982f18c..415872750243 100644
--- a/src/libs/IOUUtils.ts
+++ b/src/libs/IOUUtils.ts
@@ -3,11 +3,12 @@ import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Report, Transaction} from '@src/types/onyx';
+import type {IOURequestType} from './actions/IOU';
import * as CurrencyUtils from './CurrencyUtils';
import Navigation from './Navigation/Navigation';
import * as TransactionUtils from './TransactionUtils';
-function navigateToStartMoneyRequestStep(requestType: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) {
+function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: ValueOf, transactionID: string, reportID: string) {
// If the participants were automatically added to the transaction, then the user needs taken back to the starting step
switch (requestType) {
case CONST.IOU.REQUEST_TYPE.DISTANCE:
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index aab9ad12baf1..b4c97ed40556 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -260,6 +260,12 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/ExitSurvey/ExitSurveyReasonPage').default as React.ComponentType,
[SCREENS.SETTINGS.EXIT_SURVEY.RESPONSE]: () => require('../../../../pages/settings/ExitSurvey/ExitSurveyResponsePage').default as React.ComponentType,
[SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: () => require('../../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksImportPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksChartOfAccountsPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksCustomersPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksTaxesPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksLocationsPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksClassesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () =>
require('../../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType,
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index 984f2ef20bbd..14e007c4c6d5 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -18,6 +18,14 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET,
SCREENS.WORKSPACE.WORKFLOWS_PAYER,
],
+ [SCREENS.WORKSPACE.ACCOUNTING]: [
+ SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT,
+ SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS,
+ SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES,
+ SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES,
+ SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS,
+ SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS,
+ ],
[SCREENS.WORKSPACE.TAXES]: [
SCREENS.WORKSPACE.TAXES_SETTINGS,
SCREENS.WORKSPACE.TAX_CREATE,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 2706f40f6b68..f7cdc54335ab 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -263,6 +263,12 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.CURRENCY]: {
path: ROUTES.WORKSPACE_PROFILE_CURRENCY.route,
},
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT.route},
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS.route},
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES.route},
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS.route},
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS.route},
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES.route},
[SCREENS.WORKSPACE.DESCRIPTION]: {
path: ROUTES.WORKSPACE_PROFILE_DESCRIPTION.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index e77dceb8736f..ed34b8ee3856 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -241,6 +241,24 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES]: {
+ policyID: string;
+ };
[SCREENS.GET_ASSISTANCE]: {
backTo: Routes;
};
@@ -342,12 +360,6 @@ type MoneyRequestNavigatorParamList = {
iouType: string;
reportID: string;
};
- [SCREENS.MONEY_REQUEST.STEP_CONFIRMATION]: {
- action: ValueOf;
- iouType: string;
- transactionID: string;
- reportID: string;
- };
[SCREENS.MONEY_REQUEST.CURRENCY]: {
iouType: string;
reportID: string;
@@ -373,6 +385,7 @@ type MoneyRequestNavigatorParamList = {
action: ValueOf;
iouType: ValueOf;
transactionID: string;
+ reportActionID: string;
reportID: string;
backTo: Routes;
};
@@ -432,6 +445,14 @@ type MoneyRequestNavigatorParamList = {
iouType: string;
reportID: string;
};
+ [SCREENS.MONEY_REQUEST.STEP_CONFIRMATION]: {
+ action: ValueOf;
+ iouType: ValueOf;
+ transactionID: string;
+ reportID: string;
+ pageIndex?: string;
+ backTo?: string;
+ };
};
type NewTaskNavigatorParamList = {
@@ -626,6 +647,9 @@ type WorkspacesCentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.ACCOUNTING]: {
+ policyID: string;
+ };
};
type FullScreenNavigatorParamList = {
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index e1a3e9207ad8..f61f51cd5350 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -169,7 +169,7 @@ type MemberForList = {
keyForList: string;
isSelected: boolean;
isDisabled: boolean;
- accountID?: number | null;
+ accountID?: number;
login: string;
icons?: OnyxCommon.Icon[];
pendingAction?: OnyxCommon.PendingAction;
@@ -357,7 +357,7 @@ function isPersonalDetailsReady(personalDetails: OnyxEntry)
/**
* Get the participant option for a report.
*/
-function getParticipantsOption(participant: ReportUtils.OptionData, personalDetails: OnyxEntry): Participant {
+function getParticipantsOption(participant: ReportUtils.OptionData | Participant, personalDetails: OnyxEntry): Participant {
const detail = getPersonalDetailsForAccountIDs([participant.accountID ?? -1], personalDetails)[participant.accountID ?? -1];
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const login = detail?.login || participant.login || '';
@@ -644,21 +644,22 @@ function createOption(
const {showChatPreviewLine = false, forcePolicyNamePreview = false, showPersonalDetails = false} = config ?? {};
const result: ReportUtils.OptionData = {
text: undefined,
- alternateText: null,
+ alternateText: undefined,
pendingAction: undefined,
allReportErrors: undefined,
brickRoadIndicator: null,
icons: undefined,
tooltipText: null,
ownerAccountID: undefined,
- subtitle: null,
+ subtitle: undefined,
participantsList: undefined,
accountID: 0,
- login: null,
+ login: undefined,
reportID: '',
- phoneNumber: null,
- keyForList: null,
- searchText: null,
+ phoneNumber: undefined,
+ hasDraftComment: false,
+ keyForList: undefined,
+ searchText: undefined,
isDefaultRoom: false,
isPinned: false,
isWaitingOnBankAccount: false,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index c9241054e74c..85f5c414dbe4 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -379,23 +379,24 @@ type CustomIcon = {
type OptionData = {
text?: string;
- alternateText?: string | null;
+ alternateText?: string;
allReportErrors?: Errors;
brickRoadIndicator?: ValueOf | '' | null;
tooltipText?: string | null;
alternateTextMaxLines?: number;
boldStyle?: boolean;
customIcon?: CustomIcon;
- subtitle?: string | null;
- login?: string | null;
- accountID?: number | null;
+ subtitle?: string;
+ login?: string;
+ accountID?: number;
pronouns?: string;
status?: Status | null;
- phoneNumber?: string | null;
+ phoneNumber?: string;
isUnread?: boolean | null;
isUnreadWithMention?: boolean | null;
- keyForList?: string | null;
- searchText?: string | null;
+ hasDraftComment?: boolean | null;
+ keyForList?: string;
+ searchText?: string;
isIOUReportOwner?: boolean | null;
isArchivedRoom?: boolean | null;
shouldShowSubscript?: boolean | null;
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index e1dd7ea684b1..c5439d687089 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -209,19 +209,20 @@ function getOptionData({
const result: ReportUtils.OptionData = {
text: '',
- alternateText: null,
+ alternateText: undefined,
allReportErrors: OptionsListUtils.getAllReportErrors(report, reportActions),
brickRoadIndicator: null,
tooltipText: null,
- subtitle: null,
- login: null,
- accountID: null,
+ subtitle: undefined,
+ login: undefined,
+ accountID: undefined,
reportID: '',
- phoneNumber: null,
+ phoneNumber: undefined,
isUnread: null,
isUnreadWithMention: null,
- keyForList: null,
- searchText: null,
+ hasDraftComment: false,
+ keyForList: undefined,
+ searchText: undefined,
isPinned: false,
hasOutstandingChildRequest: false,
isIOUReportOwner: null,
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 96c5ba8c78ec..9c398998588e 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -3,10 +3,12 @@ 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 type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import type {RecentWaypoint, Report, TaxRate, TaxRates, TaxRatesWithDefault, Transaction, TransactionViolation} from '@src/types/onyx';
import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import type {IOURequestType} from './actions/IOU';
import {isCorporateCard, isExpensifyCard} from './CardUtils';
import DateUtils from './DateUtils';
import * as Localize from './Localize';
@@ -45,22 +47,23 @@ function isDistanceRequest(transaction: OnyxEntry): boolean {
return type === CONST.TRANSACTION.TYPE.CUSTOM_UNIT && customUnitName === CONST.CUSTOM_UNITS.NAME_DISTANCE;
}
-function isScanRequest(transaction: Transaction): boolean {
+function isScanRequest(transaction: OnyxEntry): boolean {
// This is used during the request creation flow before the transaction has been saved to the server
if (lodashHas(transaction, 'iouRequestType')) {
- return transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN;
+ return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN;
}
return Boolean(transaction?.receipt?.source);
}
-function getRequestType(transaction: Transaction): ValueOf {
+function getRequestType(transaction: OnyxEntry): IOURequestType {
if (isDistanceRequest(transaction)) {
return CONST.IOU.REQUEST_TYPE.DISTANCE;
}
if (isScanRequest(transaction)) {
return CONST.IOU.REQUEST_TYPE.SCAN;
}
+
return CONST.IOU.REQUEST_TYPE.MANUAL;
}
@@ -452,12 +455,13 @@ function getCreated(transaction: OnyxEntry, dateFormat: string = CO
/**
* Returns the translation key to use for the header title
*/
-function getHeaderTitleTranslationKey(transaction: Transaction): string {
- const headerTitles = {
+function getHeaderTitleTranslationKey(transaction: OnyxEntry): TranslationPaths {
+ const headerTitles: Record = {
[CONST.IOU.REQUEST_TYPE.DISTANCE]: 'tabSelector.distance',
[CONST.IOU.REQUEST_TYPE.MANUAL]: 'tabSelector.manual',
[CONST.IOU.REQUEST_TYPE.SCAN]: 'tabSelector.scan',
};
+
return headerTitles[getRequestType(transaction)];
}
@@ -539,7 +543,11 @@ function getWaypointIndex(key: string): number {
/**
* Filters the waypoints which are valid and returns those
*/
-function getValidWaypoints(waypoints: WaypointCollection, reArrangeIndexes = false): WaypointCollection {
+function getValidWaypoints(waypoints: WaypointCollection | undefined, reArrangeIndexes = false): WaypointCollection {
+ if (!waypoints) {
+ return {};
+ }
+
const sortedIndexes = Object.keys(waypoints)
.map(getWaypointIndex)
.sort((a, b) => a - b);
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 463642cdecbf..0382732e6f47 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -128,6 +128,11 @@ type SendMoneyParamsData = {
failureData: OnyxUpdate[];
};
+type GPSPoint = {
+ lat: number;
+ long: number;
+};
+
let betas: OnyxTypes.Beta[] = [];
Onyx.connect({
key: ONYXKEYS.BETAS,
@@ -334,7 +339,7 @@ function updateMoneyRequestTypeParams(routes: StackNavigationState, reportID: string, requestType?: ValueOf) {
+function startMoneyRequest(iouType: ValueOf, reportID: string, requestType?: IOURequestType) {
clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID);
switch (requestType) {
case CONST.IOU.REQUEST_TYPE.MANUAL:
@@ -406,7 +411,7 @@ function setMoneyRequestBillable_temporaryForRefactor(transactionID: string, bil
}
// eslint-disable-next-line @typescript-eslint/naming-convention
-function setMoneyRequestParticipants_temporaryForRefactor(transactionID: string, participants: Participant[]) {
+function setMoneyRequestParticipants_temporaryForRefactor(transactionID: string, participants: Participant[] = []) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants});
}
@@ -2251,16 +2256,16 @@ function updateDistanceRequest(
* Request money from another user
*/
function requestMoney(
- report: OnyxTypes.Report,
+ report: OnyxEntry,
amount: number,
currency: string,
created: string,
merchant: string,
- payeeEmail: string,
+ payeeEmail: string | undefined,
payeeAccountID: number,
participant: Participant,
comment: string,
- receipt: Receipt,
+ receipt: Receipt | undefined,
category?: string,
tag?: string,
taxCode = '',
@@ -2269,12 +2274,12 @@ function requestMoney(
policy?: OnyxEntry,
policyTagList?: OnyxEntry,
policyCategories?: OnyxEntry,
- gpsPoints = undefined,
+ gpsPoints?: GPSPoint,
) {
// If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
- const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
- const moneyRequestReportID = isMoneyRequestReport ? report.reportID : '';
+ const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report?.chatReportID) : report;
+ const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : '';
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const {
payerAccountID,
@@ -2309,7 +2314,7 @@ function requestMoney(
payeeEmail,
moneyRequestReportID,
);
- const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID;
+ const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport.reportID;
const parameters: RequestMoneyParams = {
debtorEmail: payerEmail,
@@ -2342,7 +2347,9 @@ function requestMoney(
API.write(WRITE_COMMANDS.REQUEST_MONEY, parameters, onyxData);
resetMoneyRequestInfo();
Navigation.dismissModal(activeReportID);
- Report.notifyNewAction(activeReportID, payeeAccountID);
+ if (activeReportID) {
+ Report.notifyNewAction(activeReportID, payeeAccountID);
+ }
}
/**
@@ -2354,11 +2361,11 @@ function trackExpense(
currency: string,
created: string,
merchant: string,
- payeeEmail: string,
+ payeeEmail: string | undefined,
payeeAccountID: number,
participant: Participant,
comment: string,
- receipt: Receipt,
+ receipt?: Receipt,
category?: string,
tag?: string,
taxCode = '',
@@ -2367,7 +2374,7 @@ function trackExpense(
policy?: OnyxEntry,
policyTagList?: OnyxEntry,
policyCategories?: OnyxEntry,
- gpsPoints = undefined,
+ gpsPoints?: GPSPoint,
) {
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
@@ -2857,25 +2864,41 @@ function createSplitsAndOnyxData(
};
}
+type SplitBillActionsParams = {
+ participants: Participant[];
+ currentUserLogin: string;
+ currentUserAccountID: number;
+ amount: number;
+ comment: string;
+ currency: string;
+ merchant: string;
+ created: string;
+ category?: string;
+ tag?: string;
+ billable?: boolean;
+ iouRequestType?: IOURequestType;
+ existingSplitChatReportID?: string;
+};
+
/**
* @param amount - always in smallest currency unit
* @param existingSplitChatReportID - Either a group DM or a workspace chat
*/
-function splitBill(
- participants: Participant[],
- currentUserLogin: string,
- currentUserAccountID: number,
- amount: number,
- comment: string,
- currency: string,
- merchant: string,
- created: string,
- category: string,
- tag: string,
- existingSplitChatReportID = '',
+function splitBill({
+ participants,
+ currentUserLogin,
+ currentUserAccountID,
+ amount,
+ comment,
+ currency,
+ merchant,
+ created,
+ category = '',
+ tag = '',
billable = false,
- iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
-) {
+ iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
+ existingSplitChatReportID = '',
+}: SplitBillActionsParams) {
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const {splitData, splits, onyxData} = createSplitsAndOnyxData(
participants,
@@ -2921,20 +2944,20 @@ function splitBill(
/**
* @param amount - always in the smallest currency unit
*/
-function splitBillAndOpenReport(
- participants: Participant[],
- currentUserLogin: string,
- currentUserAccountID: number,
- amount: number,
- comment: string,
- currency: string,
- merchant: string,
- created: string,
- category: string,
- tag: string,
- billable: boolean,
- iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
-) {
+function splitBillAndOpenReport({
+ participants,
+ currentUserLogin,
+ currentUserAccountID,
+ amount,
+ comment,
+ currency,
+ merchant,
+ created,
+ category = '',
+ tag = '',
+ billable = false,
+ iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
+}: SplitBillActionsParams) {
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const {splitData, splits, onyxData} = createSplitsAndOnyxData(
participants,
@@ -2977,23 +3000,36 @@ function splitBillAndOpenReport(
Report.notifyNewAction(splitData.chatReportID, currentUserAccountID);
}
+type StartSplitBilActionParams = {
+ participants: Participant[];
+ currentUserLogin: string;
+ currentUserAccountID: number;
+ comment: string;
+ receipt: Receipt;
+ existingSplitChatReportID?: string;
+ billable?: boolean;
+ category: string | undefined;
+ tag: string | undefined;
+ currency: string;
+};
+
/** Used exclusively for starting a split bill request that contains a receipt, the split request will be completed once the receipt is scanned
* or user enters details manually.
*
* @param existingSplitChatReportID - Either a group DM or a workspace chat
*/
-function startSplitBill(
- participants: Participant[],
- currentUserLogin: string,
- currentUserAccountID: number,
- comment: string,
- category: string,
- tag: string,
- currency: string,
- receipt: Receipt,
+function startSplitBill({
+ participants,
+ currentUserLogin,
+ currentUserAccountID,
+ comment,
+ receipt,
existingSplitChatReportID = '',
billable = false,
-) {
+ category = '',
+ tag = '',
+ currency,
+}: StartSplitBilActionParams) {
const currentUserEmailForIOUSplit = PhoneNumber.addSMSDomainIfPhoneNumber(currentUserLogin);
const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
const {splitChatReport, existingSplitChatReport} = getOrCreateOptimisticSplitChatReport(existingSplitChatReportID, participants, participantAccountIDs, currentUserAccountID);
@@ -4744,7 +4780,7 @@ function sendMoneyElsewhere(report: OnyxTypes.Report, amount: number, currency:
* @param managerID - Account ID of the person sending the money
* @param recipient - The user receiving the money
*/
-function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency: string, comment: string, managerID: number, recipient: Participant) {
+function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency: string, comment: string, managerID: number, recipient: Participant | ReportUtils.OptionData) {
const {params, optimisticData, successData, failureData} = getSendMoneyParams(report, amount, currency, comment, CONST.IOU.PAYMENT_TYPE.EXPENSIFY, managerID, recipient);
API.write(WRITE_COMMANDS.SEND_MONEY_WITH_WALLET, params, {optimisticData, successData, failureData});
@@ -5406,14 +5442,14 @@ function unholdRequest(transactionID: string, reportID: string) {
}
// eslint-disable-next-line rulesdir/no-negated-variables
function navigateToStartStepIfScanFileCannotBeRead(
- receiptFilename: string,
- receiptPath: string,
+ receiptFilename: string | undefined,
+ receiptPath: ReceiptSource | undefined,
onSuccess: (file: File) => void,
- requestType: ValueOf,
+ requestType: IOURequestType,
iouType: ValueOf,
transactionID: string,
reportID: string,
- receiptType: string,
+ receiptType: string | undefined,
) {
if (!receiptFilename || !receiptPath) {
return;
@@ -5427,7 +5463,7 @@ function navigateToStartStepIfScanFileCannotBeRead(
}
IOUUtils.navigateToStartMoneyRequestStep(requestType, iouType, transactionID, reportID);
};
- FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure, receiptType);
+ FileUtils.readFileAsync(receiptPath.toString(), receiptFilename, onSuccess, onFailure, receiptType);
}
/** Save the preferred payment method for a policy */
@@ -5435,6 +5471,7 @@ function savePreferredPaymentMethod(policyID: string, paymentMethod: PaymentMeth
Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: paymentMethod});
}
+export type {GPSPoint as GpsPoint, IOURequestType};
export {
setMoneyRequestParticipants,
createDistanceRequest,
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index d99ccccf1596..d6ab203e85a4 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -3721,6 +3721,77 @@ function openPolicyDistanceRatesPage(policyID?: string) {
API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params);
}
+function updatePolicyConnectionConfig(policyID: string, settingName: ValueOf, settingValue: ValueOf) {
+ const parameters = {policyID, connectionName: CONST.QUICK_BOOKS_ONLINE, settingName, settingValue, idempotencyKey: settingName};
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ quickbooksOnline: {
+ config: {
+ [settingName]: settingValue,
+ pendingFields: {
+ [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ quickbooksOnline: {
+ config: {
+ [settingName]: settingValue,
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'),
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ quickbooksOnline: {
+ config: {
+ [settingName]: settingValue,
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ API.write(WRITE_COMMANDS.UPDATE_POLICY_CONNECTION_CONFIG, parameters, {optimisticData, failureData, successData});
+}
+
function navigateWhenEnableFeature(policyID: string, featureRoute: Route) {
const isNarrowLayout = getIsNarrowLayout();
if (isNarrowLayout) {
@@ -4995,6 +5066,7 @@ export {
setWorkspaceTagEnabled,
setWorkspaceCurrencyDefault,
setForeignCurrencyDefault,
+ updatePolicyConnectionConfig,
setPolicyCustomTaxName,
clearPolicyErrorField,
isCurrencySupportedForDirectReimbursement,
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index 9093bf32b9dd..ccfe329419e3 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -253,6 +253,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
)}
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index 1b8957b833b0..1164666e4992 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -270,6 +270,7 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction,
)}
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index 940cba181db7..902e38f4a181 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -586,7 +586,7 @@ function ReportScreen({
return false;
}
const action = sortedAllReportActions.find((item) => item.reportActionID === reportActionIDFromRoute);
- return action && ReportActionsUtils.isDeletedAction(action);
+ return action && !ReportActionsUtils.shouldReportActionBeVisible(action, action.reportActionID);
}, [reportActionIDFromRoute, sortedAllReportActions]);
if (isLinkedReportActionDeleted ?? (!shouldShowSkeleton && reportActionIDFromRoute && reportActions?.length === 0 && !isLinkingToMessage)) {
diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx
index 4377ff7e55b1..31019b60aefb 100644
--- a/src/pages/home/report/ReportActionItem.tsx
+++ b/src/pages/home/report/ReportActionItem.tsx
@@ -753,18 +753,15 @@ function ReportActionItem({
}
if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) {
return (
- // eslint-disable-next-line react/jsx-no-useless-fragment
- <>
+
{transactionThreadReport && !isEmptyObject(transactionThreadReport) ? (
<>
{transactionCurrency !== report.currency && (
-
-
-
+
)}
>
) : (
-
-
-
+
)}
- >
+
);
}
diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx
index 3d98973c86c4..537f6292c3e4 100644
--- a/src/pages/home/report/ReportActionItemParentAction.tsx
+++ b/src/pages/home/report/ReportActionItemParentAction.tsx
@@ -2,11 +2,13 @@ import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import onyxSubscribe from '@libs/onyxSubscribe';
+import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -58,6 +60,7 @@ function ReportActionItemParentAction({
const {isSmallScreenWidth} = useWindowDimensions();
const ancestorIDs = useRef(ReportUtils.getAllAncestorReportActionIDs(report));
const [allAncestors, setAllAncestors] = useState([]);
+ const {isOffline} = useNetwork();
useEffect(() => {
const unsubscribeReports: Array<() => void> = [];
@@ -103,7 +106,12 @@ function ReportActionItemParentAction({
>
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? ''))}
+ onPress={() => {
+ const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID ?? '');
+ Navigation.navigate(
+ ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? '', isVisibleAction && !isOffline ? ancestor.reportAction.reportActionID : undefined),
+ );
+ }}
parentReportAction={parentReportAction}
report={ancestor.report}
reportActions={reportActions}
diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx
index d1b9c420b0af..85566b8c5fa0 100644
--- a/src/pages/home/report/ReportActionsList.tsx
+++ b/src/pages/home/report/ReportActionsList.tsx
@@ -464,10 +464,10 @@ function ReportActionsList({
setCurrentUnreadMarker(reportAction.reportActionID);
}
});
- if (!markerFound) {
+ if (!markerFound && !linkedReportActionID) {
setCurrentUnreadMarker(null);
}
- }, [sortedVisibleReportActions, report.reportID, shouldDisplayNewMarker, currentUnreadMarker]);
+ }, [sortedVisibleReportActions, report.reportID, shouldDisplayNewMarker, currentUnreadMarker, linkedReportActionID]);
useEffect(() => {
calculateUnreadMarker();
diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx
index e6ae2c155d65..e3dc873a3283 100755
--- a/src/pages/home/report/ReportActionsView.tsx
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -351,10 +351,7 @@ function ReportActionsView({
// and there are fewer than 23 items, indicating we've reached the oldest message.
const isLoadingOlderReportsFirstNeeded = checkIfContentSmallerThanList() && reportActions.length > 23;
- if (
- (reportActionID && indexOfLinkedAction > -1 && !hasNewestReportAction && !isLoadingOlderReportsFirstNeeded) ||
- (!reportActionID && !hasNewestReportAction && !isLoadingOlderReportsFirstNeeded)
- ) {
+ if ((reportActionID && indexOfLinkedAction > -1 && !isLoadingOlderReportsFirstNeeded) || (!reportActionID && !isLoadingOlderReportsFirstNeeded)) {
handleReportActionPagination({firstReportActionID: newestReportAction?.reportActionID});
}
}, [
@@ -363,7 +360,6 @@ function ReportActionsView({
checkIfContentSmallerThanList,
reportActionID,
indexOfLinkedAction,
- hasNewestReportAction,
handleReportActionPagination,
network.isOffline,
reportActions.length,
diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx
index 083129e15e6d..c728cdf20db4 100644
--- a/src/pages/home/report/ThreadDivider.tsx
+++ b/src/pages/home/report/ThreadDivider.tsx
@@ -5,9 +5,11 @@ import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithoutFeedback} from '@components/Pressable';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
+import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import type {Ancestor} from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
@@ -22,11 +24,17 @@ function ThreadDivider({ancestor}: ThreadDividerProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
+ const {isOffline} = useNetwork();
return (
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor?.report?.parentReportID ?? ''))}
+ onPress={() => {
+ const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID ?? '');
+ Navigation.navigate(
+ ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? '', isVisibleAction && !isOffline ? ancestor.reportAction.reportActionID : undefined),
+ );
+ }}
accessibilityLabel={translate('threads.thread')}
role={CONST.ROLE.BUTTON}
style={[styles.flexRow, styles.alignItemsCenter, styles.gap1]}
diff --git a/src/pages/iou/request/step/IOURequestStepCategory.js b/src/pages/iou/request/step/IOURequestStepCategory.js
deleted file mode 100644
index 4f0c77480c04..000000000000
--- a/src/pages/iou/request/step/IOURequestStepCategory.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import lodashGet from 'lodash/get';
-import lodashIsEmpty from 'lodash/isEmpty';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import CategoryPicker from '@components/CategoryPicker';
-import categoryPropTypes from '@components/categoryPropTypes';
-import tagPropTypes from '@components/tagPropTypes';
-import Text from '@components/Text';
-import transactionPropTypes from '@components/transactionPropTypes';
-import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
-import Navigation from '@libs/Navigation/Navigation';
-import * as OptionsListUtils from '@libs/OptionsListUtils';
-import * as ReportUtils from '@libs/ReportUtils';
-import * as TransactionUtils from '@libs/TransactionUtils';
-import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as IOU from '@userActions/IOU';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import {policyPropTypes} from '@src/pages/workspace/withPolicy';
-import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes';
-import StepScreenWrapper from './StepScreenWrapper';
-import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
-import withWritableReportOrNotFound from './withWritableReportOrNotFound';
-
-const propTypes = {
- /** Navigation route context info provided by react navigation */
- route: IOURequestStepRoutePropTypes.isRequired,
-
- /* Onyx Props */
- /** Holds data related to Money Request view state, rather than the underlying Money Request data. */
- transaction: transactionPropTypes,
-
- /** The draft transaction that holds data to be persisted on the current transaction */
- splitDraftTransaction: transactionPropTypes,
-
- /** The report attached to the transaction */
- report: reportPropTypes,
-
- /** The policy of the report */
- policy: policyPropTypes.policy,
-
- /** Collection of categories attached to a policy */
- policyCategories: PropTypes.objectOf(categoryPropTypes),
-
- /** Collection of tags attached to a policy */
- policyTags: tagPropTypes,
-
- /** The actions from the parent report */
- reportActions: PropTypes.shape(reportActionPropTypes),
-
- /** Session info for the currently logged in user. */
- session: PropTypes.shape({
- /** Currently logged in user accountID */
- accountID: PropTypes.number,
-
- /** Currently logged in user email */
- email: PropTypes.string,
- }).isRequired,
-};
-
-const defaultProps = {
- report: {},
- transaction: {},
- splitDraftTransaction: {},
- policy: null,
- policyTags: null,
- policyCategories: null,
- reportActions: {},
-};
-
-function IOURequestStepCategory({
- report,
- route: {
- params: {transactionID, backTo, action, iouType, reportActionID},
- },
- transaction,
- splitDraftTransaction,
- policy,
- policyTags,
- policyCategories,
- session,
- reportActions,
-}) {
- const styles = useThemeStyles();
- const {translate} = useLocalize();
- const isEditing = action === CONST.IOU.ACTION.EDIT;
- const isEditingSplitBill = isEditing && iouType === CONST.IOU.TYPE.SPLIT;
- const {category: transactionCategory} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction);
-
- const reportAction = reportActions[report.parentReportActionID || reportActionID];
- const shouldShowCategory = ReportUtils.isGroupPolicy(report) && (transactionCategory || OptionsListUtils.hasEnabledOptions(_.values(policyCategories)));
- const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT;
- const canEditSplitBill = isSplitBill && reportAction && session.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction);
- // eslint-disable-next-line rulesdir/no-negated-variables
- const shouldShowNotFoundPage = !shouldShowCategory || (isEditing && (isSplitBill ? !canEditSplitBill : !ReportUtils.canEditMoneyRequest(reportAction)));
-
- const navigateBack = () => {
- Navigation.goBack(backTo);
- };
-
- /**
- * @param {Object} category
- * @param {String} category.searchText
- */
- const updateCategory = (category) => {
- const isSelectedCategory = category.searchText === transactionCategory;
- const updatedCategory = isSelectedCategory ? '' : category.searchText;
-
- // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value
- if (isEditingSplitBill) {
- IOU.setDraftSplitTransaction(transaction.transactionID, {category: updatedCategory});
- navigateBack();
- return;
- }
-
- if (isEditing) {
- IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories);
- navigateBack();
- return;
- }
-
- IOU.setMoneyRequestCategory(transactionID, updatedCategory);
- navigateBack();
- };
-
- return (
-
- {translate('iou.categorySelection')}
-
-
- );
-}
-
-IOURequestStepCategory.displayName = 'IOURequestStepCategory';
-IOURequestStepCategory.propTypes = propTypes;
-IOURequestStepCategory.defaultProps = defaultProps;
-
-export default compose(
- withWritableReportOrNotFound,
- withFullTransactionOrNotFound,
- withOnyx({
- splitDraftTransaction: {
- key: ({route}) => {
- const transactionID = lodashGet(route, 'params.transactionID', 0);
- return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`;
- },
- },
- policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
- },
- policyCategories: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`,
- },
- policyTags: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
- },
- reportActions: {
- key: ({
- report,
- route: {
- params: {action, iouType},
- },
- }) => {
- let reportID = '0';
- if (action === CONST.IOU.ACTION.EDIT) {
- reportID = iouType === CONST.IOU.TYPE.SPLIT ? report.reportID : report.parentReportID;
- }
- return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`;
- },
- canEvict: false,
- },
- session: {
- key: ONYXKEYS.SESSION,
- },
- }),
-)(IOURequestStepCategory);
diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx
new file mode 100644
index 000000000000..26b918e0c999
--- /dev/null
+++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx
@@ -0,0 +1,173 @@
+import lodashIsEmpty from 'lodash/isEmpty';
+import React from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
+import {withOnyx} from 'react-native-onyx';
+import CategoryPicker from '@components/CategoryPicker';
+import type {ListItem} from '@components/SelectionList/types';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import * as OptionsListUtils from '@libs/OptionsListUtils';
+import * as ReportUtils from '@libs/ReportUtils';
+import * as TransactionUtils from '@libs/TransactionUtils';
+import * as IOU from '@userActions/IOU';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
+import type {Policy, PolicyCategories, PolicyTagList, ReportActions, Session, Transaction} from '@src/types/onyx';
+import StepScreenWrapper from './StepScreenWrapper';
+import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound';
+import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
+import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
+import withWritableReportOrNotFound from './withWritableReportOrNotFound';
+
+type IOURequestStepCategoryOnyxProps = {
+ /** The draft transaction that holds data to be persisted on the current transaction */
+ splitDraftTransaction: OnyxEntry;
+
+ /** The policy of the report */
+ policy: OnyxEntry;
+
+ /** Collection of categories attached to a policy */
+ policyCategories: OnyxEntry;
+
+ /** Collection of tags attached to a policy */
+ policyTags: OnyxEntry;
+
+ /** The actions from the parent report */
+ reportActions: OnyxEntry;
+
+ /** Session info for the currently logged in user. */
+ session: OnyxEntry;
+};
+
+type IOURequestStepCategoryProps = IOURequestStepCategoryOnyxProps &
+ WithWritableReportOrNotFoundProps &
+ WithFullTransactionOrNotFoundProps;
+
+function IOURequestStepCategory({
+ report,
+ route: {
+ params: {transactionID, backTo, action, iouType, reportActionID},
+ },
+ transaction,
+ splitDraftTransaction,
+ policy,
+ policyTags,
+ policyCategories,
+ reportActions,
+ session,
+}: IOURequestStepCategoryProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const isEditing = action === CONST.IOU.ACTION.EDIT;
+ const isEditingSplitBill = isEditing && iouType === CONST.IOU.TYPE.SPLIT;
+ const transactionCategory = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction)?.category;
+
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ const reportAction = reportActions?.[report?.parentReportActionID || reportActionID] ?? null;
+
+ // The transactionCategory can be an empty string, so to maintain the logic we'd like to keep it in this shape until utils refactor
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ const shouldShowCategory = ReportUtils.isGroupPolicy(report) && (!!transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {})));
+
+ const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT;
+ const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction);
+ // eslint-disable-next-line rulesdir/no-negated-variables
+ const shouldShowNotFoundPage = !shouldShowCategory || (isEditing && (isSplitBill ? !canEditSplitBill : !ReportUtils.canEditMoneyRequest(reportAction)));
+
+ const navigateBack = () => {
+ Navigation.goBack(backTo);
+ };
+
+ const updateCategory = (category: ListItem) => {
+ const categorySearchText = category.searchText ?? '';
+ const isSelectedCategory = categorySearchText === transactionCategory;
+ const updatedCategory = isSelectedCategory ? '' : categorySearchText;
+
+ if (transaction) {
+ // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value
+ if (isEditingSplitBill) {
+ IOU.setDraftSplitTransaction(transaction.transactionID, {category: updatedCategory});
+ navigateBack();
+ return;
+ }
+
+ if (isEditing && report) {
+ IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories);
+ navigateBack();
+ return;
+ }
+ }
+
+ IOU.setMoneyRequestCategory(transactionID, updatedCategory);
+
+ navigateBack();
+ };
+
+ return (
+
+ {translate('iou.categorySelection')}
+
+
+ );
+}
+
+IOURequestStepCategory.displayName = 'IOURequestStepCategory';
+
+const IOURequestStepCategoryWithOnyx = withOnyx({
+ splitDraftTransaction: {
+ key: ({route}) => {
+ const transactionID = route?.params.transactionID ?? 0;
+ return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`;
+ },
+ },
+ policy: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
+ },
+ policyCategories: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`,
+ },
+ policyTags: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
+ },
+ reportActions: {
+ key: ({
+ report,
+ route: {
+ params: {action, iouType},
+ },
+ }) => {
+ let reportID = '0';
+ if (action === CONST.IOU.ACTION.EDIT && report) {
+ if (iouType === CONST.IOU.TYPE.SPLIT) {
+ reportID = report.reportID;
+ } else if (report.parentReportID) {
+ reportID = report.parentReportID;
+ }
+ }
+ return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`;
+ },
+ canEvict: false,
+ },
+ session: {
+ key: ONYXKEYS.SESSION,
+ },
+})(IOURequestStepCategory);
+/* eslint-disable rulesdir/no-negated-variables */
+const IOURequestStepCategoryWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepCategoryWithOnyx);
+/* eslint-disable rulesdir/no-negated-variables */
+const IOURequestStepCategoryWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepCategoryWithFullTransactionOrNotFound);
+export default IOURequestStepCategoryWithWritableReportOrNotFound;
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
similarity index 67%
rename from src/pages/iou/request/step/IOURequestStepConfirmation.js
rename to src/pages/iou/request/step/IOURequestStepConfirmation.tsx
index 1999b7e56f3c..d20a576d279e 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.js
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
@@ -1,24 +1,20 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import categoryPropTypes from '@components/categoryPropTypes';
+import type {ValueOf} from 'type-fest';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import MoneyRequestConfirmationList from '@components/MoneyTemporaryForRefactorRequestConfirmationList';
import {usePersonalDetails} from '@components/OnyxProvider';
import ScreenWrapper from '@components/ScreenWrapper';
-import tagPropTypes from '@components/tagPropTypes';
-import transactionPropTypes from '@components/transactionPropTypes';
-import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import compose from '@libs/compose';
+import {openDraftWorkspaceRequest} from '@libs/actions/Policy';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import getCurrentPosition from '@libs/getCurrentPosition';
import * as IOUUtils from '@libs/IOUUtils';
@@ -27,51 +23,35 @@ import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
-import reportPropTypes from '@pages/reportPropTypes';
-import {policyPropTypes} from '@pages/workspace/withPolicy';
import * as IOU from '@userActions/IOU';
-import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes';
+import type SCREENS from '@src/SCREENS';
+import type {Policy, PolicyCategories, PolicyTagList} from '@src/types/onyx';
+import type {Participant} from '@src/types/onyx/IOU';
+import type {Receipt} from '@src/types/onyx/Transaction';
+import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound';
import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import withWritableReportOrNotFound from './withWritableReportOrNotFound';
+import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
-const propTypes = {
- /** Navigation route context info provided by react navigation */
- route: IOURequestStepRoutePropTypes.isRequired,
-
- /* Onyx Props */
- /** The personal details of the current user */
- ...withCurrentUserPersonalDetailsPropTypes,
-
+type IOURequestStepConfirmationOnyxProps = {
/** The policy of the report */
- ...policyPropTypes,
-
- /** The tag configuration of the report's policy */
- policyTags: tagPropTypes,
+ policy: OnyxEntry;
/** The category configuration of the report's policy */
- policyCategories: PropTypes.objectOf(categoryPropTypes),
+ policyCategories: OnyxEntry;
- /** The full IOU report */
- report: reportPropTypes,
-
- /** The transaction object being modified in Onyx */
- transaction: transactionPropTypes,
-};
-const defaultProps = {
- personalDetails: {},
- policy: null,
- policyCategories: null,
- policyTags: null,
- report: {},
- transaction: {},
- ...withCurrentUserPersonalDetailsDefaultProps,
+ /** The tag configuration of the report's policy */
+ policyTags: OnyxEntry;
};
+
+type IOURequestStepConfirmationProps = IOURequestStepConfirmationOnyxProps &
+ WithWritableReportOrNotFoundProps &
+ WithFullTransactionOrNotFoundProps;
+
function IOURequestStepConfirmation({
- currentUserPersonalDetails,
policy,
policyTags,
policyCategories,
@@ -80,20 +60,25 @@ function IOURequestStepConfirmation({
params: {iouType, reportID, transactionID},
},
transaction,
-}) {
+}: IOURequestStepConfirmationProps) {
+ const currentUserPersonalDetails = useCurrentUserPersonalDetails();
+ const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
+
const styles = useThemeStyles();
const {translate} = useLocalize();
const {windowWidth} = useWindowDimensions();
const {isOffline} = useNetwork();
- const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
- const [receiptFile, setReceiptFile] = useState();
- const receiptFilename = lodashGet(transaction, 'filename');
- const receiptPath = lodashGet(transaction, 'receipt.source');
- const receiptType = lodashGet(transaction, 'receipt.type');
- const foreignTaxDefault = lodashGet(policy, 'taxRates.foreignTaxDefault');
- const transactionTaxCode = transaction.taxRate ? transaction.taxRate.data.code : foreignTaxDefault;
- const transactionTaxAmount = transaction.taxAmount;
+ const [receiptFile, setReceiptFile] = useState();
+
+ const receiptFilename = transaction?.filename;
+ const receiptPath = transaction?.receipt?.source;
+ const receiptType = transaction?.receipt?.type;
+ const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault;
+ const transactionTaxCode = transaction?.taxRate ? transaction.taxRate.data?.code : foreignTaxDefault;
+ const transactionTaxAmount = transaction?.taxAmount;
+
const requestType = TransactionUtils.getRequestType(transaction);
+
const headerTitle = useMemo(() => {
if (iouType === CONST.IOU.TYPE.SPLIT) {
return translate('iou.split');
@@ -109,17 +94,17 @@ function IOURequestStepConfirmation({
const participants = useMemo(
() =>
- _.map(transaction.participants, (participant) => {
- const participantAccountID = lodashGet(participant, 'accountID', 0);
+ transaction?.participants?.map((participant) => {
+ const participantAccountID = participant.accountID ?? 0;
return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant);
}),
- [transaction.participants, personalDetails],
+ [transaction?.participants, personalDetails],
);
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
const formHasBeenSubmitted = useRef(false);
useEffect(() => {
- if (!transaction || !transaction.originalCurrency) {
+ if (!transaction?.originalCurrency) {
return;
}
// If user somehow lands on this page without the currency reset, then reset it here.
@@ -128,32 +113,31 @@ function IOURequestStepConfirmation({
}, []);
useEffect(() => {
- const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat);
- if (policyExpenseChat) {
- Policy.openDraftWorkspaceRequest(policyExpenseChat.policyID);
+ const policyExpenseChat = participants?.find((participant) => participant.isPolicyExpenseChat);
+ if (policyExpenseChat?.policyID) {
+ openDraftWorkspaceRequest(policyExpenseChat.policyID);
}
- }, [isOffline, participants, transaction.billable, policy, transactionID]);
+ }, [isOffline, participants, transaction?.billable, policy, transactionID]);
- const defaultBillable = lodashGet(policy, 'defaultBillable', false);
+ const defaultBillable = !!policy?.defaultBillable;
useEffect(() => {
IOU.setMoneyRequestBillable_temporaryForRefactor(transactionID, defaultBillable);
}, [transactionID, defaultBillable]);
useEffect(() => {
- if (!transaction.category) {
+ if (!transaction?.category) {
return;
}
- if (policyCategories && policyCategories[transaction.category] && !policyCategories[transaction.category].enabled) {
+ if (policyCategories?.[transaction.category] && !policyCategories[transaction.category].enabled) {
IOU.setMoneyRequestCategory(transactionID, '');
}
- }, [policyCategories, transaction.category, transactionID]);
- const defaultCategory = lodashGet(
- _.find(lodashGet(policy, 'customUnits', {}), (customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE),
- 'defaultCategory',
- '',
- );
+ }, [policyCategories, transaction?.category, transactionID]);
+
+ const policyDistance = Object.values(policy?.customUnits ?? {}).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
+ const defaultCategory = policyDistance?.defaultCategory ?? '';
+
useEffect(() => {
- if (requestType !== CONST.IOU.REQUEST_TYPE.DISTANCE || !_.isEmpty(transaction.category)) {
+ if (requestType !== CONST.IOU.REQUEST_TYPE.DISTANCE || !!transaction?.category) {
return;
}
IOU.setMoneyRequestCategory(transactionID, defaultCategory);
@@ -164,7 +148,7 @@ function IOURequestStepConfirmation({
const navigateBack = useCallback(() => {
// If there is not a report attached to the IOU with a reportID, then the participants were manually selected and the user needs taken
// back to the participants step
- if (!transaction.participantsAutoAssigned) {
+ if (!transaction?.participantsAutoAssigned) {
Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID));
return;
}
@@ -179,8 +163,8 @@ function IOURequestStepConfirmation({
// This is because until the request is saved, the receipt file is only stored in the browsers memory as a blob:// and if the browser is refreshed, then
// the image ceases to exist. The best way for the user to recover from this is to start over from the start of the request process.
useEffect(() => {
- const onSuccess = (file) => {
- const receipt = file;
+ const onSuccess = (file: File) => {
+ const receipt: Receipt = file;
receipt.state = file && requestType === CONST.IOU.REQUEST_TYPE.MANUAL ? CONST.IOU.RECEIPT_STATE.OPEN : CONST.IOU.RECEIPT_STATE.SCANREADY;
setReceiptFile(receipt);
};
@@ -188,13 +172,12 @@ function IOURequestStepConfirmation({
IOU.navigateToStartStepIfScanFileCannotBeRead(receiptFilename, receiptPath, onSuccess, requestType, iouType, transactionID, reportID, receiptType);
}, [receiptType, receiptPath, receiptFilename, requestType, iouType, transactionID, reportID]);
- /**
- * @param {Array} selectedParticipants
- * @param {String} trimmedComment
- * @param {File} [receiptObj]
- */
const requestMoney = useCallback(
- (selectedParticipants, trimmedComment, receiptObj, gpsPoints) => {
+ (selectedParticipants: Participant[], trimmedComment: string, receiptObj?: Receipt, gpsPoints?: IOU.GpsPoint) => {
+ if (!transaction) {
+ return;
+ }
+
IOU.requestMoney(
report,
transaction.amount,
@@ -220,13 +203,11 @@ function IOURequestStepConfirmation({
[report, transaction, transactionTaxCode, transactionTaxAmount, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, policy, policyTags, policyCategories],
);
- /**
- * @param {Array} selectedParticipants
- * @param {String} trimmedComment
- * @param {File} [receiptObj]
- */
const trackExpense = useCallback(
- (selectedParticipants, trimmedComment, receiptObj, gpsPoints) => {
+ (selectedParticipants: Participant[], trimmedComment: string, receiptObj?: Receipt, gpsPoints?: IOU.GpsPoint) => {
+ if (!report || !transaction) {
+ return;
+ }
IOU.trackExpense(
report,
transaction.amount,
@@ -249,31 +230,14 @@ function IOURequestStepConfirmation({
gpsPoints,
);
},
- [
- report,
- transaction.amount,
- transaction.currency,
- transaction.created,
- transaction.merchant,
- transaction.category,
- transaction.tag,
- transaction.billable,
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- transactionTaxCode,
- transactionTaxAmount,
- policy,
- policyTags,
- policyCategories,
- ],
+ [report, transaction, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, transactionTaxCode, transactionTaxAmount, policy, policyTags, policyCategories],
);
- /**
- * @param {Array} selectedParticipants
- * @param {String} trimmedComment
- */
const createDistanceRequest = useCallback(
- (selectedParticipants, trimmedComment) => {
+ (selectedParticipants: Participant[], trimmedComment: string) => {
+ if (!report || !transaction) {
+ return;
+ }
IOU.createDistanceRequest(
report,
selectedParticipants[0],
@@ -295,8 +259,8 @@ function IOURequestStepConfirmation({
);
const createTransaction = useCallback(
- (selectedParticipants) => {
- const trimmedComment = lodashGet(transaction, 'comment.comment', '').trim();
+ (selectedParticipants: Participant[]) => {
+ const trimmedComment = (transaction?.comment.comment ?? '').trim();
// Don't let the form be submitted multiple times while the navigator is waiting to take the user to a different page
if (formHasBeenSubmitted.current) {
@@ -307,63 +271,69 @@ function IOURequestStepConfirmation({
// If we have a receipt let's start the split bill by creating only the action, the transaction, and the group DM if needed
if (iouType === CONST.IOU.TYPE.SPLIT && receiptFile) {
- IOU.startSplitBill(
- selectedParticipants,
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- trimmedComment,
- transaction.category,
- transaction.tag,
- transaction.currency,
- receiptFile,
- report.reportID,
- transaction.billable,
- );
+ if (currentUserPersonalDetails.login && !!transaction) {
+ IOU.startSplitBill({
+ participants: selectedParticipants,
+ currentUserLogin: currentUserPersonalDetails.login,
+ currentUserAccountID: currentUserPersonalDetails.accountID,
+ comment: trimmedComment,
+ receipt: receiptFile,
+ existingSplitChatReportID: report?.reportID,
+ billable: transaction.billable,
+ category: transaction.category,
+ tag: transaction.tag,
+ currency: transaction.currency,
+ });
+ }
return;
}
// IOUs created from a group report will have a reportID param in the route.
// Since the user is already viewing the report, we don't need to navigate them to the report
- if (iouType === CONST.IOU.TYPE.SPLIT && !transaction.isFromGlobalCreate) {
- IOU.splitBill(
- selectedParticipants,
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- transaction.amount,
- trimmedComment,
- transaction.currency,
- transaction.merchant,
- transaction.created,
- transaction.category,
- transaction.tag,
- report.reportID,
- transaction.billable,
- transaction.iouRequestType,
- );
+ if (iouType === CONST.IOU.TYPE.SPLIT && !transaction?.isFromGlobalCreate) {
+ if (currentUserPersonalDetails.login && !!transaction) {
+ IOU.splitBill({
+ participants: selectedParticipants,
+ currentUserLogin: currentUserPersonalDetails.login,
+ currentUserAccountID: currentUserPersonalDetails.accountID,
+ amount: transaction.amount,
+ comment: trimmedComment,
+ currency: transaction.currency,
+ merchant: transaction.merchant,
+ created: transaction.created,
+ category: transaction.category,
+ tag: transaction.tag,
+ existingSplitChatReportID: report?.reportID,
+ billable: transaction.billable,
+ iouRequestType: transaction.iouRequestType,
+ });
+ }
return;
}
// If the request is created from the global create menu, we also navigate the user to the group report
if (iouType === CONST.IOU.TYPE.SPLIT) {
- IOU.splitBillAndOpenReport(
- selectedParticipants,
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- transaction.amount,
- trimmedComment,
- transaction.currency,
- transaction.merchant,
- transaction.created,
- transaction.category,
- transaction.tag,
- transaction.billable,
- transaction.iouRequestType,
- );
+ if (currentUserPersonalDetails.login && !!transaction) {
+ IOU.splitBillAndOpenReport({
+ participants: selectedParticipants,
+ currentUserLogin: currentUserPersonalDetails.login,
+ currentUserAccountID: currentUserPersonalDetails.accountID,
+ amount: transaction.amount,
+ comment: trimmedComment,
+ currency: transaction.currency,
+ merchant: transaction.merchant,
+ created: transaction.created,
+ category: transaction.category,
+ tag: transaction.tag,
+ billable: !!transaction.billable,
+ iouRequestType: transaction.iouRequestType,
+ });
+ }
return;
}
if (iouType === CONST.IOU.TYPE.TRACK_EXPENSE) {
- if (receiptFile) {
+ if (receiptFile && transaction) {
// If the transaction amount is zero, then the money is being requested through the "Scan" flow and the GPS coordinates need to be included.
if (transaction.amount === 0) {
getCurrentPosition(
@@ -397,7 +367,7 @@ function IOURequestStepConfirmation({
return;
}
- if (receiptFile) {
+ if (receiptFile && !!transaction) {
// If the transaction amount is zero, then the money is being requested through the "Scan" flow and the GPS coordinates need to be included.
if (transaction.amount === 0) {
getCurrentPosition(
@@ -443,7 +413,7 @@ function IOURequestStepConfirmation({
requestMoney,
currentUserPersonalDetails.login,
currentUserPersonalDetails.accountID,
- report.reportID,
+ report?.reportID,
trackExpense,
createDistanceRequest,
],
@@ -455,12 +425,16 @@ function IOURequestStepConfirmation({
* @param {String} paymentMethodType
*/
const sendMoney = useCallback(
- (paymentMethodType) => {
- const currency = transaction.currency;
+ (paymentMethodType: ValueOf) => {
+ const currency = transaction?.currency;
+
+ const trimmedComment = transaction?.comment?.comment ? transaction.comment.comment.trim() : '';
- const trimmedComment = transaction.comment && transaction.comment.comment ? transaction.comment.comment.trim() : '';
+ const participant = participants?.[0];
- const participant = participants[0];
+ if (!participant || !report || !transaction?.amount || !currency) {
+ return;
+ }
if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) {
IOU.sendMoneyElsewhere(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant);
@@ -471,11 +445,11 @@ function IOURequestStepConfirmation({
IOU.sendMoneyWithWallet(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant);
}
},
- [transaction.amount, transaction.comment, transaction.currency, participants, currentUserPersonalDetails.accountID, report],
+ [transaction?.amount, transaction?.comment, transaction?.currency, participants, currentUserPersonalDetails.accountID, report],
);
- const addNewParticipant = (option) => {
- const newParticipants = _.map(transaction.participants, (participant) => {
+ const addNewParticipant = (option: Participant) => {
+ const newParticipants = transaction?.participants?.map((participant) => {
if (participant.accountID === option.accountID) {
return {...participant, selected: !participant.selected};
}
@@ -484,16 +458,13 @@ function IOURequestStepConfirmation({
IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, newParticipants);
};
- /**
- * @param {Boolean} billable
- */
- const setBillable = (billable) => {
+ const setBillable = (billable: boolean) => {
IOU.setMoneyRequestBillable_temporaryForRefactor(transactionID, billable);
};
// This loading indicator is shown because the transaction originalCurrency is being updated later than the component mounts.
// To prevent the component from rendering with the wrong currency, we show a loading indicator until the correct currency is set.
- const isLoading = !!(transaction && transaction.originalCurrency);
+ const isLoading = !!transaction?.originalCurrency;
return (
{isLoading && }
+ {/* @ts-expect-error TODO: Remove this once MoneyRequestConfirmationList (https://github.com/Expensify/App/issues/36130) is migrated to TypeScript. */}
@@ -556,23 +528,21 @@ function IOURequestStepConfirmation({
);
}
-IOURequestStepConfirmation.propTypes = propTypes;
-IOURequestStepConfirmation.defaultProps = defaultProps;
IOURequestStepConfirmation.displayName = 'IOURequestStepConfirmation';
-export default compose(
- withCurrentUserPersonalDetails,
- withWritableReportOrNotFound,
- withFullTransactionOrNotFound,
- withOnyx({
- policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
- },
- policyCategories: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`,
- },
- policyTags: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
- },
- }),
-)(IOURequestStepConfirmation);
+const IOURequestStepConfirmationWithOnyx = withOnyx({
+ policy: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
+ },
+ policyCategories: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`,
+ },
+ policyTags: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
+ },
+})(IOURequestStepConfirmation);
+/* eslint-disable rulesdir/no-negated-variables */
+const IOURequestStepConfirmationWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepConfirmationWithOnyx);
+/* eslint-disable rulesdir/no-negated-variables */
+const IOURequestStepConfirmationWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepConfirmationWithFullTransactionOrNotFound);
+export default IOURequestStepConfirmationWithWritableReportOrNotFound;
diff --git a/src/pages/iou/request/step/StepScreenDragAndDropWrapper.js b/src/pages/iou/request/step/StepScreenDragAndDropWrapper.tsx
similarity index 79%
rename from src/pages/iou/request/step/StepScreenDragAndDropWrapper.js
rename to src/pages/iou/request/step/StepScreenDragAndDropWrapper.tsx
index ceb0d5a44351..39ac12b6bdcf 100644
--- a/src/pages/iou/request/step/StepScreenDragAndDropWrapper.js
+++ b/src/pages/iou/request/step/StepScreenDragAndDropWrapper.tsx
@@ -1,4 +1,4 @@
-import PropTypes from 'prop-types';
+import type {PropsWithChildren} from 'react';
import React, {useState} from 'react';
import {View} from 'react-native';
import DragAndDropProvider from '@components/DragAndDrop/Provider';
@@ -7,31 +7,24 @@ import ScreenWrapper from '@components/ScreenWrapper';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
-const propTypes = {
- /** The things to display inside the screenwrapper */
- children: PropTypes.node.isRequired,
-
+type StepScreenDragAndDropWrapperProps = {
/** The title to show in the header (should be translated already) */
- headerTitle: PropTypes.string.isRequired,
+ headerTitle: string;
/** A function triggered when the back button is pressed */
- onBackButtonPress: PropTypes.func.isRequired,
+ onBackButtonPress: () => void;
/** A function triggered when the entry transition is ended. Useful for auto-focusing elements. */
- onEntryTransitionEnd: PropTypes.func,
+ onEntryTransitionEnd?: () => void;
/** Whether or not the wrapper should be shown (sometimes screens can be embedded inside another screen that already is using a wrapper) */
- shouldShowWrapper: PropTypes.bool.isRequired,
+ shouldShowWrapper: boolean;
/** An ID used for unit testing */
- testID: PropTypes.string.isRequired,
-};
-
-const defaultProps = {
- onEntryTransitionEnd: () => {},
+ testID: string;
};
-function StepScreenDragAndDropWrapper({testID, headerTitle, onBackButtonPress, onEntryTransitionEnd, children, shouldShowWrapper}) {
+function StepScreenDragAndDropWrapper({testID, headerTitle, onBackButtonPress, onEntryTransitionEnd, children, shouldShowWrapper}: PropsWithChildren) {
const styles = useThemeStyles();
const [isDraggingOver, setIsDraggingOver] = useState(false);
@@ -65,7 +58,5 @@ function StepScreenDragAndDropWrapper({testID, headerTitle, onBackButtonPress, o
}
StepScreenDragAndDropWrapper.displayName = 'StepScreenDragAndDropWrapper';
-StepScreenDragAndDropWrapper.propTypes = propTypes;
-StepScreenDragAndDropWrapper.defaultProps = defaultProps;
export default StepScreenDragAndDropWrapper;
diff --git a/src/pages/iou/request/step/StepScreenWrapper.js b/src/pages/iou/request/step/StepScreenWrapper.tsx
similarity index 59%
rename from src/pages/iou/request/step/StepScreenWrapper.js
rename to src/pages/iou/request/step/StepScreenWrapper.tsx
index 3739cbbcc188..e64f2792d2e4 100644
--- a/src/pages/iou/request/step/StepScreenWrapper.js
+++ b/src/pages/iou/request/step/StepScreenWrapper.tsx
@@ -1,46 +1,46 @@
-import PropTypes from 'prop-types';
import React from 'react';
+import type {PropsWithChildren} from 'react';
import {View} from 'react-native';
-import _ from 'underscore';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
+import callOrReturn from '@src/types/utils/callOrReturn';
-const propTypes = {
- /** The things to display inside the screenwrapper */
- children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
-
+type StepScreenWrapperProps = {
/** The title to show in the header (should be translated already) */
- headerTitle: PropTypes.string.isRequired,
+ headerTitle: string;
/** A function triggered when the back button is pressed */
- onBackButtonPress: PropTypes.func.isRequired,
+ onBackButtonPress: () => void;
/** A function triggered when the entry transition is ended. Useful for auto-focusing elements. */
- onEntryTransitionEnd: PropTypes.func,
+ onEntryTransitionEnd?: () => void;
/** Whether or not the wrapper should be shown (sometimes screens can be embedded inside another screen that already is using a wrapper) */
- shouldShowWrapper: PropTypes.bool.isRequired,
+ shouldShowWrapper: boolean;
/** Whether or not to display not found page */
- shouldShowNotFoundPage: PropTypes.bool,
+ shouldShowNotFoundPage?: boolean;
/** An ID used for unit testing */
- testID: PropTypes.string.isRequired,
+ testID: string;
/** Whether or not to include safe area padding */
- includeSafeAreaPaddingBottom: PropTypes.bool,
-};
-
-const defaultProps = {
- onEntryTransitionEnd: () => {},
- includeSafeAreaPaddingBottom: false,
- shouldShowNotFoundPage: false,
+ includeSafeAreaPaddingBottom?: boolean;
};
-function StepScreenWrapper({testID, headerTitle, onBackButtonPress, onEntryTransitionEnd, children, shouldShowWrapper, shouldShowNotFoundPage, includeSafeAreaPaddingBottom}) {
+function StepScreenWrapper({
+ testID,
+ headerTitle,
+ onBackButtonPress,
+ onEntryTransitionEnd,
+ children,
+ shouldShowWrapper,
+ shouldShowNotFoundPage,
+ includeSafeAreaPaddingBottom,
+}: PropsWithChildren) {
const styles = useThemeStyles();
if (!shouldShowWrapper) {
@@ -62,14 +62,8 @@ function StepScreenWrapper({testID, headerTitle, onBackButtonPress, onEntryTrans
onBackButtonPress={onBackButtonPress}
/>
{
- // If props.children is a function, call it to provide the insets to the children.
- _.isFunction(children)
- ? children({
- insets,
- safeAreaPaddingBottomStyle,
- didScreenTransitionEnd,
- })
- : children
+ // If props.children is a function, call it to provide the insets to the children
+ callOrReturn(children, {insets, safeAreaPaddingBottomStyle, didScreenTransitionEnd})
}
@@ -78,8 +72,4 @@ function StepScreenWrapper({testID, headerTitle, onBackButtonPress, onEntryTrans
);
}
-StepScreenWrapper.displayName = 'StepScreenWrapper';
-StepScreenWrapper.propTypes = propTypes;
-StepScreenWrapper.defaultProps = defaultProps;
-
export default StepScreenWrapper;
diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx
index 5f8a981ab3bb..9fee88b45d0c 100644
--- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx
+++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx
@@ -21,6 +21,8 @@ type MoneyRequestRouteName =
| typeof SCREENS.MONEY_REQUEST.STEP_WAYPOINT
| typeof SCREENS.MONEY_REQUEST.STEP_DESCRIPTION
| typeof SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT
+ | typeof SCREENS.MONEY_REQUEST.STEP_CONFIRMATION
+ | typeof SCREENS.MONEY_REQUEST.STEP_CATEGORY
| typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE;
type Route = RouteProp;
diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx
index 00ebba2b56c4..515f6f97f280 100644
--- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx
+++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx
@@ -20,6 +20,8 @@ type WithWritableReportOrNotFoundOnyxProps = {
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_CONFIRMATION
| typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE
| typeof SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT;
@@ -53,7 +55,7 @@ export default function , WithWritableReportOrNotFoundOnyxProps>({
report: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID ?? '0'}`,
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '0'}`,
},
})(forwardRef(WithWritableReportOrNotFound));
}
diff --git a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx
index 2c87e8803be6..6fada466e2bb 100644
--- a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx
+++ b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx
@@ -18,6 +18,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
// import useWaitForNavigation from '@hooks/useWaitForNavigation';
import useWindowDimensions from '@hooks/useWindowDimensions';
+import Navigation from '@navigation/Navigation';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
@@ -25,6 +26,7 @@ import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
function WorkspaceAccountingPage({policy}: WithPolicyProps) {
const theme = useTheme();
@@ -141,7 +143,7 @@ function WorkspaceAccountingPage({policy}: WithPolicyProps) {
shouldShowRightIcon: true,
title: translate('workspace.accounting.import'),
wrapperStyle: [styles.sectionMenuItemTopDescription],
- onPress: () => {},
+ onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT.getRoute(policyID)),
},
{
icon: Expensicons.Send,
@@ -161,7 +163,7 @@ function WorkspaceAccountingPage({policy}: WithPolicyProps) {
},
]),
],
- [translate, theme.spinner, isSyncInProgress, overflowMenu, threeDotsMenuPosition, styles.popoverMenuIcon, threeDotsMenuContainerRef, styles.sectionMenuItemTopDescription],
+ [styles.sectionMenuItemTopDescription, styles.popoverMenuIcon, translate, isSyncInProgress, theme.spinner, overflowMenu, threeDotsMenuPosition, policyID],
);
const headerThreeDotsMenuItems: ThreeDotsMenuProps['menuItems'] = [
diff --git a/src/pages/workspace/accounting/qbo/QuickbooksChartOfAccountsPage.tsx b/src/pages/workspace/accounting/qbo/QuickbooksChartOfAccountsPage.tsx
new file mode 100644
index 000000000000..593e5f9dc00a
--- /dev/null
+++ b/src/pages/workspace/accounting/qbo/QuickbooksChartOfAccountsPage.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import {View} from 'react-native';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Switch from '@components/Switch';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import variables from '@styles/variables';
+import * as Policy from '@userActions/Policy';
+import CONST from '@src/CONST';
+
+function QuickbooksChartOfAccountsPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '';
+ const {enableNewCategories, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const isSwitchOn = Boolean(enableNewCategories && enableNewCategories !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
+
+ return (
+
+
+
+
+
+ {translate('workspace.qbo.accountsDescription')}
+
+
+ {translate('workspace.qbo.accountsSwitchTitle')}
+
+
+
+
+ Policy.updatePolicyConnectionConfig(
+ policyID,
+ CONST.QUICK_BOOKS_IMPORTS.ENABLE_NEW_CATEGORIES,
+ isSwitchOn ? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE : CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG,
+ )
+ }
+ />
+
+
+
+
+ {translate('workspace.qbo.accountsSwitchDescription')}
+
+
+
+
+
+ );
+}
+
+QuickbooksChartOfAccountsPage.displayName = 'QuickbooksChartOfAccountsPage';
+
+export default withPolicy(QuickbooksChartOfAccountsPage);
diff --git a/src/pages/workspace/accounting/qbo/QuickbooksClassesPage.tsx b/src/pages/workspace/accounting/qbo/QuickbooksClassesPage.tsx
new file mode 100644
index 000000000000..f8c631b31476
--- /dev/null
+++ b/src/pages/workspace/accounting/qbo/QuickbooksClassesPage.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import {View} from 'react-native';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Switch from '@components/Switch';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import variables from '@styles/variables';
+import * as Policy from '@userActions/Policy';
+import CONST from '@src/CONST';
+
+function QuickbooksClassesPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '';
+ const {syncClasses, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const isSwitchOn = Boolean(syncClasses && syncClasses !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
+ const isReportFieldsSelected = syncClasses === CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD;
+
+ return (
+
+
+
+
+
+ {translate('workspace.qbo.classesDescription')}
+
+
+ {translate('workspace.qbo.import')}
+
+
+
+
+ Policy.updatePolicyConnectionConfig(
+ policyID,
+ CONST.QUICK_BOOKS_IMPORTS.SYNC_CLASSES,
+ isSwitchOn ? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE : CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG,
+ )
+ }
+ />
+
+
+
+ {isSwitchOn && (
+
+
+
+ )}
+
+
+
+
+ );
+}
+
+QuickbooksClassesPage.displayName = 'QuickbooksClassesPage';
+
+export default withPolicy(QuickbooksClassesPage);
diff --git a/src/pages/workspace/accounting/qbo/QuickbooksCustomersPage.tsx b/src/pages/workspace/accounting/qbo/QuickbooksCustomersPage.tsx
new file mode 100644
index 000000000000..27fde14081e7
--- /dev/null
+++ b/src/pages/workspace/accounting/qbo/QuickbooksCustomersPage.tsx
@@ -0,0 +1,80 @@
+import React from 'react';
+import {View} from 'react-native';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Switch from '@components/Switch';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import variables from '@styles/variables';
+import * as Policy from '@userActions/Policy';
+import CONST from '@src/CONST';
+
+function QuickbooksCustomersPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '';
+ const {syncCustomers, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const isSwitchOn = Boolean(syncCustomers && syncCustomers !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
+ const isReportFieldsSelected = syncCustomers === CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD;
+ return (
+
+
+
+
+
+ {translate('workspace.qbo.customersDescription')}
+
+
+ {translate('workspace.qbo.import')}
+
+
+
+
+ Policy.updatePolicyConnectionConfig(
+ policyID,
+ CONST.QUICK_BOOKS_IMPORTS.SYNC_CUSTOMERS,
+ isSwitchOn ? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE : CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG,
+ )
+ }
+ />
+
+
+
+ {isSwitchOn && (
+
+
+
+ )}
+
+
+
+
+ );
+}
+
+QuickbooksCustomersPage.displayName = 'QuickbooksCustomersPage';
+
+export default withPolicy(QuickbooksCustomersPage);
diff --git a/src/pages/workspace/accounting/qbo/QuickbooksImportPage.tsx b/src/pages/workspace/accounting/qbo/QuickbooksImportPage.tsx
new file mode 100644
index 000000000000..531b413c9d08
--- /dev/null
+++ b/src/pages/workspace/accounting/qbo/QuickbooksImportPage.tsx
@@ -0,0 +1,107 @@
+import React from 'react';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@navigation/Navigation';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+
+function QuickbooksImportPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const quickbooksOnlineConfigTitles = {
+ [CONST.INTEGRATION_ENTITY_MAP_TYPES.DEFAULT]: translate('workspace.qbo.imported'),
+ [CONST.INTEGRATION_ENTITY_MAP_TYPES.IMPORTED]: translate('workspace.qbo.imported'),
+ [CONST.INTEGRATION_ENTITY_MAP_TYPES.NOT_IMPORTED]: translate('workspace.qbo.notImported'),
+ [CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE]: translate('workspace.qbo.notImported'),
+ [CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG]: translate('workspace.qbo.importedAsTags'),
+ [CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]: translate('workspace.qbo.importedAsReportFields'),
+ };
+ const policyID = policy?.id ?? '';
+ const {syncClasses, syncCustomers, syncLocations, syncTaxes, enableNewCategories, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+
+ const sections = [
+ {
+ description: translate('workspace.qbo.accounts'),
+ action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS.getRoute(policyID)),
+ hasError: Boolean(policy?.errors?.enableNewCategories),
+ title: enableNewCategories,
+ pendingAction: pendingFields?.enableNewCategories,
+ },
+ {
+ description: translate('workspace.qbo.classes'),
+ action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES.getRoute(policyID)),
+ hasError: Boolean(policy?.errors?.syncClasses),
+ title: syncClasses,
+ pendingAction: pendingFields?.syncClasses,
+ },
+ {
+ description: translate('workspace.qbo.customers'),
+ action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS.getRoute(policyID)),
+ hasError: Boolean(policy?.errors?.syncCustomers),
+ title: syncCustomers,
+ pendingAction: pendingFields?.syncCustomers,
+ },
+ {
+ description: translate('workspace.qbo.locations'),
+ action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS.getRoute(policyID)),
+ hasError: Boolean(policy?.errors?.syncLocations),
+ title: syncLocations,
+ pendingAction: pendingFields?.syncLocations,
+ },
+ {
+ description: translate('workspace.qbo.taxes'),
+ action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES.getRoute(policyID)),
+ hasError: Boolean(policy?.errors?.syncTaxes),
+ title: syncTaxes,
+ pendingAction: pendingFields?.syncTaxes,
+ },
+ ];
+
+ return (
+
+
+
+
+
+ {translate('workspace.qbo.importDescription')}
+ {sections.map((section) => (
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+QuickbooksImportPage.displayName = 'PolicyQuickbooksImportPage';
+
+export default withPolicy(QuickbooksImportPage);
diff --git a/src/pages/workspace/accounting/qbo/QuickbooksLocationsPage.tsx b/src/pages/workspace/accounting/qbo/QuickbooksLocationsPage.tsx
new file mode 100644
index 000000000000..21da79587c0c
--- /dev/null
+++ b/src/pages/workspace/accounting/qbo/QuickbooksLocationsPage.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import {View} from 'react-native';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Switch from '@components/Switch';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import variables from '@styles/variables';
+import * as Policy from '@userActions/Policy';
+import CONST from '@src/CONST';
+
+function QuickbooksLocationsPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '';
+ const {syncLocations, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const isSwitchOn = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
+ const isReportFieldsSelected = syncLocations === CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD;
+
+ return (
+
+
+
+
+
+ {translate('workspace.qbo.locationsDescription')}
+
+
+ {translate('workspace.qbo.import')}
+
+
+
+
+ Policy.updatePolicyConnectionConfig(
+ policyID,
+ CONST.QUICK_BOOKS_IMPORTS.SYNC_LOCATIONS,
+ isSwitchOn ? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE : CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG,
+ )
+ }
+ />
+
+
+
+ {isSwitchOn && (
+
+
+
+ )}
+
+ {translate('workspace.qbo.locationsAdditionalDescription')}
+
+
+
+
+
+ );
+}
+
+QuickbooksLocationsPage.displayName = 'QuickbooksLocationsPage';
+
+export default withPolicy(QuickbooksLocationsPage);
diff --git a/src/pages/workspace/accounting/qbo/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/QuickbooksTaxesPage.tsx
new file mode 100644
index 000000000000..293d6518baa0
--- /dev/null
+++ b/src/pages/workspace/accounting/qbo/QuickbooksTaxesPage.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import {View} from 'react-native';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Switch from '@components/Switch';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import variables from '@styles/variables';
+import * as Policy from '@userActions/Policy';
+import CONST from '@src/CONST';
+
+function QuickbooksTaxesPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '';
+ const {syncTaxes, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const isSwitchOn = Boolean(syncTaxes && syncTaxes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
+ return (
+
+
+
+
+
+ {translate('workspace.qbo.taxesDescription')}
+
+
+ {translate('workspace.qbo.import')}
+
+
+
+
+ Policy.updatePolicyConnectionConfig(
+ policyID,
+ CONST.QUICK_BOOKS_IMPORTS.SYNC_TAXES,
+ isSwitchOn ? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE : CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG,
+ )
+ }
+ />
+
+
+
+
+
+
+
+ );
+}
+
+QuickbooksTaxesPage.displayName = 'QuickbooksTaxesPage';
+
+export default withPolicy(QuickbooksTaxesPage);
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index 2ccc203aa1f4..a21e98f4bfec 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -141,12 +141,12 @@ type QBOConnectionData = {
vendors: Vendor[];
};
-type IntegrationEntityMap = 'NONE' | 'DEFAULT' | 'TAG' | 'REPORT_FIELD';
+type IntegrationEntityMap = (typeof CONST.INTEGRATION_ENTITY_MAP_TYPES)[keyof typeof CONST.INTEGRATION_ENTITY_MAP_TYPES];
/**
* User configuration for the QuickBooks Online accounting integration.
*/
-type QBOConnectionConfig = {
+type QBOConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{
realmId: string;
companyName: string;
autoSync: {
@@ -166,14 +166,18 @@ type QBOConnectionConfig = {
syncClasses: IntegrationEntityMap;
syncCustomers: IntegrationEntityMap;
syncLocations: IntegrationEntityMap;
+ syncAccounts: IntegrationEntityMap;
+ syncTaxes: IntegrationEntityMap;
exportDate: string;
lastConfigurationTime: number;
syncTax: boolean;
- enableNewCategories: boolean;
+ enableNewCategories: IntegrationEntityMap;
+ errors?: OnyxCommon.Errors;
+ errorFields?: OnyxCommon.ErrorFields;
export: {
exporter: string;
};
-};
+}>;
type Connection = {
lastSync?: ConnectionLastSync;
data: ConnectionData;
@@ -457,6 +461,7 @@ export type {
TaxRate,
TaxRates,
TaxRatesWithDefault,
+ IntegrationEntityMap,
PolicyFeatureName,
PendingJoinRequestPolicy,
PolicyConnectionSyncStage,
diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts
index 1750fa61e514..281b6b4228ce 100644
--- a/src/types/onyx/Transaction.ts
+++ b/src/types/onyx/Transaction.ts
@@ -1,4 +1,5 @@
import type {KeysOfUnion, ValueOf} from 'type-fest';
+import type {IOURequestType} from '@libs/actions/IOU';
import type CONST from '@src/CONST';
import type ONYXKEYS from '@src/ONYXKEYS';
import type CollectionDataSet from '@src/types/utils/CollectionDataSet';
@@ -133,7 +134,7 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback<
filename?: string;
/** Used during the creation flow before the transaction is saved to the server */
- iouRequestType?: ValueOf;
+ iouRequestType?: IOURequestType;
/** The original merchant name */
merchant: string;
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index b1b597d4a6e3..9f063c2be6c3 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -1072,20 +1072,22 @@ describe('actions/IOU', () => {
fetch.pause();
IOU.splitBill(
// TODO: Migrate after the backend accepts accountIDs
- [
- [CARLOS_EMAIL, String(CARLOS_ACCOUNT_ID)],
- [JULES_EMAIL, String(JULES_ACCOUNT_ID)],
- [VIT_EMAIL, String(VIT_ACCOUNT_ID)],
- ].map(([email, accountID]) => ({login: email, accountID: Number(accountID)})),
- RORY_EMAIL,
- RORY_ACCOUNT_ID,
- amount,
- comment,
- CONST.CURRENCY.USD,
- merchant,
- '',
- '',
- '',
+ {
+ participants: [
+ [CARLOS_EMAIL, String(CARLOS_ACCOUNT_ID)],
+ [JULES_EMAIL, String(JULES_ACCOUNT_ID)],
+ [VIT_EMAIL, String(VIT_ACCOUNT_ID)],
+ ].map(([email, accountID]) => ({login: email, accountID: Number(accountID)})),
+ currentUserLogin: RORY_EMAIL,
+ currentUserAccountID: RORY_ACCOUNT_ID,
+ amount,
+ comment,
+ currency: CONST.CURRENCY.USD,
+ merchant,
+ created: '',
+ tag: '',
+ existingSplitChatReportID: '',
+ },
);
return waitForBatchedUpdates();
})
diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts
index baefd1bd6d66..6333ee6f1bc7 100644
--- a/tests/unit/OptionsListUtilsTest.ts
+++ b/tests/unit/OptionsListUtilsTest.ts
@@ -1213,22 +1213,22 @@ describe('OptionsListUtils', () => {
Engineering: {
enabled: false,
name: 'Engineering',
- accountID: null,
+ accountID: undefined,
},
Medical: {
enabled: true,
name: 'Medical',
- accountID: null,
+ accountID: undefined,
},
Accounting: {
enabled: true,
name: 'Accounting',
- accountID: null,
+ accountID: undefined,
},
HR: {
enabled: true,
name: 'HR',
- accountID: null,
+ accountID: undefined,
},
};
const smallResultList: OptionsListUtils.CategorySection[] = [
@@ -1291,57 +1291,57 @@ describe('OptionsListUtils', () => {
Engineering: {
enabled: false,
name: 'Engineering',
- accountID: null,
+ accountID: undefined,
},
Medical: {
enabled: true,
name: 'Medical',
- accountID: null,
+ accountID: undefined,
},
Accounting: {
enabled: true,
name: 'Accounting',
- accountID: null,
+ accountID: undefined,
},
HR: {
enabled: true,
name: 'HR',
- accountID: null,
+ accountID: undefined,
},
Food: {
enabled: true,
name: 'Food',
- accountID: null,
+ accountID: undefined,
},
Traveling: {
enabled: false,
name: 'Traveling',
- accountID: null,
+ accountID: undefined,
},
Cleaning: {
enabled: true,
name: 'Cleaning',
- accountID: null,
+ accountID: undefined,
},
Software: {
enabled: true,
name: 'Software',
- accountID: null,
+ accountID: undefined,
},
OfficeSupplies: {
enabled: false,
name: 'Office Supplies',
- accountID: null,
+ accountID: undefined,
},
Taxes: {
enabled: true,
name: 'Taxes',
- accountID: null,
+ accountID: undefined,
},
Benefits: {
enabled: true,
name: 'Benefits',
- accountID: null,
+ accountID: undefined,
},
};
const largeResultList: OptionsListUtils.CategorySection[] = [