diff --git a/assets/images/product-illustrations/emptystate__records.svg b/assets/images/product-illustrations/emptystate__records.svg new file mode 100644 index 0000000000000..e8c7c14e86c78 --- /dev/null +++ b/assets/images/product-illustrations/emptystate__records.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index 051ff1b71ae5e..59c448190cbc4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1387,7 +1387,12 @@ const CONST = { 3: 'createAccessToken', 4: 'enterCredentials', }, - IMPORT_CUSTOM_FIELDS: ['customSegments', 'customLists'], + IMPORT_CUSTOM_FIELDS: { + CUSTOM_SEGMENTS: 'customSegments', + CUSTOM_LISTS: 'customLists', + }, + CUSTOM_SEGMENT_FIELDS: ['segmentName', 'internalID', 'scriptID', 'mapping'], + CUSTOM_LIST_FIELDS: ['listName', 'internalID', 'transactionFieldID', 'mapping'], SYNC_OPTIONS: { SYNC_REIMBURSED_REPORTS: 'syncReimbursedReports', SYNC_PEOPLE: 'syncPeople', @@ -1404,6 +1409,13 @@ const CONST = { }, }, + NETSUITE_IMPORT: { + HELP_LINKS: { + CUSTOM_SEGMENTS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite#custom-segments', + CUSTOM_LISTS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite#custom-lists', + }, + }, + NETSUITE_EXPORT_DATE: { LAST_EXPENSE: 'LAST_EXPENSE', EXPORTED: 'EXPORTED', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 906f8ef7095ea..ffd451931d975 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -560,6 +560,8 @@ const ONYXKEYS = { ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardFormDraft', SAGE_INTACCT_CREDENTIALS_FORM: 'sageIntacctCredentialsForm', SAGE_INTACCT_CREDENTIALS_FORM_DRAFT: 'sageIntacctCredentialsFormDraft', + NETSUITE_CUSTOM_FIELD_FORM: 'netSuiteCustomFieldForm', + NETSUITE_CUSTOM_FIELD_FORM_DRAFT: 'netSuiteCustomFieldFormDraft', NETSUITE_TOKEN_INPUT_FORM: 'netsuiteTokenInputForm', NETSUITE_TOKEN_INPUT_FORM_DRAFT: 'netsuiteTokenInputFormDraft', }, @@ -625,6 +627,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; [ONYXKEYS.FORMS.SAGE_INTACCT_CREDENTIALS_FORM]: FormTypes.SageIntactCredentialsForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_FIELD_FORM]: FormTypes.NetSuiteCustomFieldForm; [ONYXKEYS.FORMS.NETSUITE_TOKEN_INPUT_FORM]: FormTypes.NetSuiteTokenInputForm; }; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 82e49e31a1662..4994d32b480cf 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1001,6 +1001,21 @@ const ROUTES = { getRoute: (policyID: string, importField: TupleToUnion) => `settings/workspaces/${policyID}/accounting/netsuite/import/mapping/${importField}` as const, }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField', + getRoute: (policyID: string, importCustomField: ValueOf) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField/view/:valueIndex', + getRoute: (policyID: string, importCustomField: ValueOf, valueIndex: number) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}/view/${valueIndex}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_EDIT: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField/edit/:valueIndex/:fieldName', + getRoute: (policyID: string, importCustomField: ValueOf, valueIndex: number, fieldName: string) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}/edit/${valueIndex}/${fieldName}` as const, + }, POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS: { route: 'settings/workspaces/:policyID/accounting/netsuite/import/customer-projects', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import/customer-projects` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 875de4363028a..57efec10b7eca 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -280,6 +280,9 @@ const SCREENS = { XERO_BILL_PAYMENT_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Bill_Payment_Account_Selector', XERO_EXPORT_BANK_ACCOUNT_SELECT: 'Policy_Accounting_Xero_Export_Bank_Account_Select', NETSUITE_IMPORT_MAPPING: 'Policy_Accounting_NetSuite_Import_Mapping', + NETSUITE_IMPORT_CUSTOM_FIELD: 'Policy_Accounting_NetSuite_Import_Custom_Field', + NETSUITE_IMPORT_CUSTOM_FIELD_VIEW: 'Policy_Accounting_NetSuite_Import_Custom_Field_View', + NETSUITE_IMPORT_CUSTOM_FIELD_EDIT: 'Policy_Accounting_NetSuite_Import_Custom_Field_Edit', NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects', NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects_Select', NETSUITE_TOKEN_INPUT: 'Policy_Accounting_NetSuite_Token_Input', diff --git a/src/components/ConnectionLayout.tsx b/src/components/ConnectionLayout.tsx index dc8638f018d47..48f670a71e65a 100644 --- a/src/components/ConnectionLayout.tsx +++ b/src/components/ConnectionLayout.tsx @@ -61,8 +61,11 @@ type ConnectionLayoutProps = { /** Name of the current connection */ connectionName: ConnectionName; - /** Block the screen when the connection is not empty */ - reverseConnectionEmptyCheck?: boolean; + /** Whether or not to block user from accessing the page */ + shouldBeBlocked?: boolean; + + /** Whether the screen should load for empty connection */ + isForEmptyConnection?: boolean; /** Handler for back button press */ onBackButtonPress?: () => void; @@ -97,7 +100,8 @@ function ConnectionLayout({ shouldUseScrollView = true, headerTitleAlreadyTranslated, titleAlreadyTranslated, - reverseConnectionEmptyCheck = false, + shouldBeBlocked = false, + isForEmptyConnection = false, onBackButtonPress = () => Navigation.goBack(), }: ConnectionLayoutProps) { const {translate} = useLocalize(); @@ -118,12 +122,14 @@ function ConnectionLayout({ [title, titleStyle, children, titleAlreadyTranslated], ); + const shouldBlockByConnection = isForEmptyConnection ? !isConnectionEmpty : isConnectionEmpty; + return ( {title} - {!!subtitle && ( + {(!!subtitle || !!subtitleComponent) && ( - {subtitle} + {subtitleComponent ?? {subtitle}} )} diff --git a/src/languages/en.ts b/src/languages/en.ts index 1607e9b858d59..4d04409431506 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2400,8 +2400,40 @@ export default { }, importTaxDescription: 'Import tax groups from NetSuite', importCustomFields: { - customSegments: 'Custom segments/records', - customLists: 'Custom lists', + customSegments: { + title: 'Custom segments/records', + addButtonText: 'Add custom segment/record', + recordTitle: 'Custom segment', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS, + helpLinkText: 'View detailed instructions', + helpText: ' on configuring custom segements/records.', + emptyTitle: 'Add a custom segment or custom record', + fields: { + segmentName: 'Name', + internalID: 'Internal ID', + scriptID: 'Script ID', + mapping: 'Displayed as', + }, + removeTitle: 'Remove custom segment/record', + removePrompt: 'Are you sure you want to remove this custom segment/record?', + }, + customLists: { + title: 'Custom lists', + addButtonText: 'Add custom list', + recordTitle: 'Custom list', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS, + helpLinkText: 'View detailed instructions', + helpText: ' on configuring custom lists.', + emptyTitle: 'Add a custom list', + fields: { + listName: 'Name', + internalID: 'Internal ID', + transactionFieldID: 'Transaction field ID', + mapping: 'Displayed as', + }, + removeTitle: 'Remove custom list', + removePrompt: 'Are you sure you want to remove this custom list?', + }, }, importTypes: { [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: { diff --git a/src/languages/es.ts b/src/languages/es.ts index c940fe78e7cf0..9eb0f04032a0f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2441,8 +2441,40 @@ export default { }, importTaxDescription: 'Importar grupos de impuestos desde NetSuite', importCustomFields: { - customSegments: 'Segmentos/registros personalizados', - customLists: 'Listas personalizado', + customSegments: { + title: 'Segmentos/registros personalizados', + addButtonText: 'Añadir segmento/registro personalizado', + recordTitle: 'Segmento personalizado', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS, + helpLinkText: 'Ver instrucciones detalladas', + helpText: ' sobre la configuración de segmentos/registros personalizado.', + emptyTitle: 'Añadir un segmento personalizado o un registro personalizado', + fields: { + segmentName: 'Name', + internalID: 'Internal ID', + scriptID: 'Script ID', + mapping: 'Displayed as', + }, + removeTitle: 'Eliminar segmento/registro personalizado', + removePrompt: '¿Está seguro de que desea eliminar este segmento/registro personalizado?', + }, + customLists: { + title: 'Listas personalizados', + addButtonText: 'Añadir lista personalizado', + recordTitle: 'Lista personalizado', + helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_LISTS, + helpLinkText: 'Ver instrucciones detalladas', + helpText: ' sobre cómo configurar listas personalizado.', + emptyTitle: 'Añadir una lista personalizado', + fields: { + listName: 'Name', + internalID: 'Internal ID', + transactionFieldID: 'Transaction field ID', + mapping: 'Displayed as', + }, + removeTitle: 'Eliminar lista personalizado', + removePrompt: '¿Está seguro de que desea eliminar esta lista personalizado?', + }, }, importTypes: { [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: { diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 9bcc7972d5266..7b2a039c61a33 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -257,6 +257,8 @@ const WRITE_COMMANDS = { UPDATE_NETSUITE_TAX_POSTING_ACCOUNT: 'UpdateNetSuiteTaxPostingAccount', UPDATE_NETSUITE_ALLOW_FOREIGN_CURRENCY: 'UpdateNetSuiteAllowForeignCurrency', UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD: 'UpdateNetSuiteExportToNextOpenPeriod', + UPDATE_NETSUITE_CUSTOM_SEGMENTS: 'UpdateNetSuiteCustomSegments', + UPDATE_NETSUITE_CUSTOM_LISTS: 'UpdateNetSuiteCustomLists', UPDATE_NETSUITE_AUTO_SYNC: 'UpdateNetSuiteAutoSync', UPDATE_NETSUITE_SYNC_REIMBURSED_REPORTS: 'UpdateNetSuiteSyncReimbursedReports', UPDATE_NETSUITE_SYNC_PEOPLE: 'UpdateNetSuiteSyncPeople', @@ -534,6 +536,9 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_NETSUITE_TAX_POSTING_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'bankAccountID', string>; [WRITE_COMMANDS.UPDATE_NETSUITE_ALLOW_FOREIGN_CURRENCY]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_SEGMENTS]: Parameters.UpdateNetSuiteGenericTypeParams<'customSegments', string>; // JSON string NetSuiteCustomSegment[] + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_LISTS]: Parameters.UpdateNetSuiteGenericTypeParams<'customLists', string>; // JSON string NetSuiteCustomList[] [WRITE_COMMANDS.UPDATE_NETSUITE_AUTO_SYNC]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_REIMBURSED_REPORTS]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_PEOPLE]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index fe631e4cd0b1a..40d93a0b19485 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -324,6 +324,12 @@ const SettingsModalStackNavigator = createModalStackNavigator('../../../../pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: () => require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING]: () => require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportMappingPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldView').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT]: () => + require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS]: () => require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectsPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT]: () => 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 78732767f4c1b..83bc3078b060f 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -59,6 +59,9 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index eb2646a048c76..95ad12cd7df9d 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -357,6 +357,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_MAPPING.route}, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING.route}, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW.route}, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_EDIT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 183e21fc67b40..fdd8ddd452ad9 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -432,6 +432,21 @@ type SettingsNavigatorParamList = { policyID: string; importField: TupleToUnion; }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD]: { + policyID: string; + importCustomField: TupleToUnion; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_VIEW]: { + policyID: string; + importCustomField: TupleToUnion; + internalID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD_EDIT]: { + policyID: string; + importCustomField: TupleToUnion; + internalID: string; + fieldName: string; + }; [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT]: { policyID: string; }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 4c071317907b9..2f7e5136f2b63 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -8,7 +8,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; -import type {ConnectionLastSync, Connections, CustomUnit, NetSuiteConnection, PolicyFeatureName, Rate, Tenant} from '@src/types/onyx/Policy'; +import type {ConnectionLastSync, Connections, CustomUnit, NetSuiteConnection, NetSuiteCustomList, NetSuiteCustomSegment, PolicyFeatureName, Rate, Tenant} from '@src/types/onyx/Policy'; import type PolicyEmployee from '@src/types/onyx/PolicyEmployee'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import Navigation from './Navigation/Navigation'; @@ -565,6 +565,14 @@ function getCustomersOrJobsLabelNetSuite(policy: Policy | undefined, translate: return importedValueLabel.charAt(0).toUpperCase() + importedValueLabel.slice(1); } +function isCustomSegmentRecord(customRecord: NetSuiteCustomList | NetSuiteCustomSegment): boolean { + return 'segmentName' in customRecord; +} + +function getNameFromCustomSegmentRecord(customRecord: NetSuiteCustomList | NetSuiteCustomSegment): string { + return 'segmentName' in customRecord ? customRecord.segmentName : customRecord.listName; +} + function getIntegrationLastSuccessfulDate(connection?: Connections[keyof Connections]) { if (!connection) { return undefined; @@ -682,6 +690,8 @@ export { getIntegrationLastSuccessfulDate, getCurrentConnectionName, getCustomersOrJobsLabelNetSuite, + isCustomSegmentRecord, + getNameFromCustomSegmentRecord, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index 20f7fcd6e483d..b6b6e01657fcc 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -7,7 +7,7 @@ import {WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Connections} from '@src/types/onyx/Policy'; +import type {Connections, NetSuiteCustomList, NetSuiteCustomSegment} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; type SubsidiaryParam = { @@ -427,6 +427,31 @@ function updateNetSuiteCrossSubsidiaryCustomersConfiguration(policyID: string, i API.write(WRITE_COMMANDS.UPDATE_NETSUITE_CROSS_SUBSIDIARY_CUSTOMER_CONFIGURATION, params, onyxData); } +function updateNetSuiteCustomSegments(policyID: string, records: NetSuiteCustomSegment[], oldRecords: NetSuiteCustomSegment[]) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, 'customSegments', records, oldRecords); + + API.write( + WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_SEGMENTS, + { + policyID, + customSegments: JSON.stringify(records), + }, + onyxData, + ); +} + +function updateNetSuiteCustomLists(policyID: string, records: NetSuiteCustomList[], oldRecords: NetSuiteCustomList[]) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, 'customLists', records, oldRecords); + API.write( + WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_LISTS, + { + policyID, + customLists: JSON.stringify(records), + }, + onyxData, + ); +} + function updateNetSuiteExporter(policyID: string, exporter: string, oldExporter: string) { const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.EXPORTER, exporter, oldExporter); @@ -749,6 +774,8 @@ export { updateNetSuiteExportToNextOpenPeriod, updateNetSuiteImportMapping, updateNetSuiteCrossSubsidiaryCustomersConfiguration, + updateNetSuiteCustomSegments, + updateNetSuiteCustomLists, updateNetSuiteAutoSync, updateNetSuiteSyncReimbursedReports, updateNetSuiteSyncPeople, diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx index 41d4282571639..579f89249a34e 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx @@ -60,8 +60,8 @@ function NetSuiteTokenInputPage({policy}: WithPolicyConnectionsProps) { featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED} contentContainerStyle={[styles.flex1]} titleStyle={styles.ph5} - reverseConnectionEmptyCheck - connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO} + isForEmptyConnection + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} onBackButtonPress={handleBackButtonPress} shouldIncludeSafeAreaPaddingBottom > diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx new file mode 100644 index 0000000000000..5cc6bc9f04dbf --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx @@ -0,0 +1,170 @@ +import React, {useCallback, useMemo} from 'react'; +import type {ValueOf} from 'type-fest'; +import ConnectionLayout from '@components/ConnectionLayout'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {SelectorType} from '@components/SelectionScreen'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {updateNetSuiteCustomLists, updateNetSuiteCustomSegments} from '@libs/actions/connections/NetSuiteCommands'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {NetSuiteCustomList, NetSuiteCustomSegment} from '@src/types/onyx/Policy'; + +type CustomRecord = NetSuiteCustomList | NetSuiteCustomSegment; +type ImportCustomFieldsKeys = ValueOf; +type ImportListItem = SelectorType & { + value: ValueOf; +}; + +type NetSuiteImportCustomFieldViewProps = WithPolicyConnectionsProps & { + route: { + params: { + importCustomField: ImportCustomFieldsKeys; + valueIndex: number; + fieldName: string; + }; + }; +}; + +function NetSuiteImportCustomFieldEdit({ + policy, + route: { + params: {importCustomField, valueIndex, fieldName}, + }, +}: NetSuiteImportCustomFieldViewProps) { + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const config = policy?.connections?.netsuite?.options?.config; + const allRecords = useMemo(() => config?.syncOptions?.[importCustomField] ?? [], [config?.syncOptions, importCustomField]); + + const customRecord: CustomRecord | undefined = allRecords[valueIndex]; + const fieldValue = customRecord?.[fieldName as keyof CustomRecord] ?? ''; + + const updateRecord = useCallback( + (formValues: Partial>) => { + const newValue = formValues[fieldName as keyof typeof formValues]; + + if (customRecord) { + const updatedRecords = allRecords.map((record, index) => { + if (index === Number(valueIndex)) { + return { + ...record, + [fieldName]: newValue, + }; + } + return record; + }); + + if (PolicyUtils.isCustomSegmentRecord(customRecord)) { + updateNetSuiteCustomSegments(policyID, updatedRecords as NetSuiteCustomSegment[], allRecords as NetSuiteCustomSegment[]); + } else { + updateNetSuiteCustomLists(policyID, updatedRecords as NetSuiteCustomList[], allRecords as NetSuiteCustomList[]); + } + } + + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW.getRoute(policyID, importCustomField, valueIndex)); + }, + [allRecords, customRecord, fieldName, importCustomField, policyID, valueIndex], + ); + + const validate = useCallback( + (formValues: FormOnyxValues) => { + const errors: FormInputErrors = {}; + + const key = fieldName as keyof typeof formValues; + if (!formValues[key]) { + ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); + } + + return errors; + }, + [fieldName, translate], + ); + + const renderForm = useMemo( + () => + customRecord && ( + + + + ), + [customRecord, fieldName, fieldValue, importCustomField, styles.flexGrow1, styles.ph5, translate, updateRecord, validate], + ); + + const renderSelection = useMemo(() => { + const options = [CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG, CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]; + + const selectionData: ImportListItem[] = + options.map((option) => ({ + text: translate(`workspace.netsuite.import.importTypes.${option}.label`), + keyForList: option, + isSelected: fieldValue === option, + value: option, + alternateText: translate(`workspace.netsuite.import.importTypes.${option}.description`), + })) ?? []; + + return ( + customRecord && ( + + updateRecord({ + [fieldName]: selected.value, + }) + } + sections={[{data: selectionData}]} + ListItem={RadioListItem} + initiallyFocusedOptionKey={selectionData?.find((record) => record.isSelected)?.keyForList} + /> + ) + ); + }, [customRecord, fieldName, fieldValue, translate, updateRecord]); + + return ( + + {fieldName === 'mapping' ? renderSelection : renderForm} + + ); +} + +NetSuiteImportCustomFieldEdit.displayName = 'NetSuiteImportCustomFieldEdit'; +export default withPolicyConnections(NetSuiteImportCustomFieldEdit); diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage.tsx new file mode 100644 index 0000000000000..ce7fc22ba5866 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage.tsx @@ -0,0 +1,136 @@ +import React, {useMemo} from 'react'; +import type {StyleProp, TextStyle} from 'react-native'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import Button from '@components/Button'; +import ConnectionLayout from '@components/ConnectionLayout'; +import FixedFooter from '@components/FixedFooter'; +import * as Illustrations from '@components/Icon/Illustrations'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import type {ThemeStyles} from '@styles/index'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +type ImportCustomFieldsKeys = ValueOf; + +type NetSuiteImportCustomFieldPageProps = WithPolicyConnectionsProps & { + route: { + params: { + importCustomField: ImportCustomFieldsKeys; + }; + }; +}; + +type HelpLinkComponentProps = { + importCustomField: ImportCustomFieldsKeys; + translate: LocaleContextProps['translate']; + styles: ThemeStyles; + alignmentStyle: StyleProp; +}; + +function HelpLinkComponent({importCustomField, styles, translate, alignmentStyle}: HelpLinkComponentProps) { + return ( + + + {translate(`workspace.netsuite.import.importCustomFields.${importCustomField}.helpLinkText`)} + + {translate(`workspace.netsuite.import.importCustomFields.${importCustomField}.helpText`)} + + ); +} + +function NetSuiteImportCustomFieldPage({ + policy, + route: { + params: {importCustomField}, + }, +}: NetSuiteImportCustomFieldPageProps) { + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const config = policy?.connections?.netsuite?.options?.config; + const data = config?.syncOptions?.[importCustomField] ?? []; + + const listEmptyComponent = useMemo( + () => ( + + } + containerStyle={[styles.flex1, styles.justifyContentCenter]} + /> + ), + [importCustomField, styles, translate], + ); + + const listHeaderComponent = useMemo( + () => ( + + + + ), + [styles, importCustomField, translate], + ); + + return ( + + {data.length === 0 ? listEmptyComponent : listHeaderComponent} + + {data.map((record, index) => ( + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW.getRoute(policyID, importCustomField, index))} + /> + ))} + + +