Skip to content

Commit

Permalink
Merge pull request #51949 from twilight2294/issuenewpage
Browse files Browse the repository at this point in the history
New Feature: "When to export" selector for auto-sync for NetSuite
  • Loading branch information
yuwenmemon authored Nov 14, 2024
2 parents 753de15 + 4d1245b commit 7a00538
Show file tree
Hide file tree
Showing 18 changed files with 274 additions and 16 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,7 @@ const CONST = {
EXPORT_TO_NEXT_OPEN_PERIOD: 'exportToNextOpenPeriod',
IMPORT_FIELDS: ['departments', 'classes', 'locations'],
AUTO_SYNC: 'autoSync',
ACCOUNTING_METHOD: 'accountingMethod',
REIMBURSEMENT_ACCOUNT_ID: 'reimbursementAccountID',
COLLECTION_ACCOUNT: 'collectionAccount',
AUTO_CREATE_ENTITIES: 'autoCreateEntities',
Expand Down
8 changes: 8 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,14 @@ const ROUTES = {
getRoute: (policyID: string, expenseType: ValueOf<typeof CONST.NETSUITE_EXPENSE_TYPE>) =>
`settings/workspaces/${policyID}/connections/netsuite/advanced/custom-form-id/${expenseType}` as const,
},
POLICY_ACCOUNTING_NETSUITE_AUTO_SYNC: {
route: 'settings/workspaces/:policyID/connections/netsuite/advanced/autosync',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/autosync` as const,
},
POLICY_ACCOUNTING_NETSUITE_ACCOUNTING_METHOD: {
route: 'settings/workspaces/:policyID/connections/netsuite/advanced/autosync/accounting-method',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/autosync/accounting-method` as const,
},
POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES: {
route: 'settings/workspaces/:policyID/accounting/sage-intacct/prerequisites',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/prerequisites` as const,
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ const SCREENS = {
NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT: 'Policy_Accounting_NetSuite_Journal_Entry_Approval_Level_Select',
NETSUITE_APPROVAL_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Approval_Account_Select',
NETSUITE_CUSTOM_FORM_ID: 'Policy_Accounting_NetSuite_Custom_Form_ID',
NETSUITE_AUTO_SYNC: 'Policy_Accounting_NetSuite_Auto_Sync',
NETSUITE_ACCOUNTING_METHOD: 'Policy_Accounting_NetSuite_Accounting_Method',
SAGE_INTACCT_PREREQUISITES: 'Policy_Accounting_Sage_Intacct_Prerequisites',
ENTER_SAGE_INTACCT_CREDENTIALS: 'Policy_Enter_Sage_Intacct_Credentials',
EXISTING_SAGE_INTACCT_CONNECTIONS: 'Policy_Existing_Sage_Intacct_Connections',
Expand Down
1 change: 1 addition & 0 deletions src/components/SelectionScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ function SelectionScreen<T = string>({
sectionListStyle={!!sections.length && [styles.flexGrow0]}
shouldSingleExecuteRowSelect={shouldSingleExecuteRowSelect}
shouldUpdateFocusedIndex={shouldUpdateFocusedIndex}
isAlternateTextMultilineSupported
>
<ErrorMessageRow
errors={errors}
Expand Down
14 changes: 14 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ const translations = {
dropMessage: 'Drop your file here',
ignore: 'Ignore',
enabled: 'Enabled',
disabled: 'Disabled',
import: 'Import',
offlinePrompt: "You can't take this action right now.",
outstanding: 'Outstanding',
Expand Down Expand Up @@ -2903,6 +2904,18 @@ const translations = {
[CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_BOTH]: 'Supervisor and accounting approved',
},
},
accountingMethods: {
label: 'When to Export',
description: 'Choose when to export the expenses:',
values: {
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.ACCRUAL]: 'Accrual',
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.CASH]: 'Cash',
},
alternateText: {
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.ACCRUAL]: 'Out-of-pocket expenses will export when final approved',
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.CASH]: 'Out-of-pocket expenses will export when paid',
},
},
exportVendorBillsTo: {
label: 'Vendor bill approval level',
description: 'Once a vendor bill is approved in Expensify and exported to NetSuite, you can set an additional level of approval in NetSuite prior to posting.',
Expand Down Expand Up @@ -3906,6 +3919,7 @@ const translations = {
exportDate: 'Export date',
defaultVendor: 'Default vendor',
autoSync: 'Auto-sync',
autoSyncDescription: 'Sync NetSuite and Expensify automatically, every day. Export finalized report in realtime',
reimbursedReports: 'Sync reimbursed reports',
cardReconciliation: 'Card reconciliation',
reconciliationAccount: 'Reconciliation account',
Expand Down
15 changes: 15 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {CONST as COMMON_CONST} from 'expensify-common';
import CONST from '@src/CONST';
import type en from './en';
import type {
Expand Down Expand Up @@ -454,6 +455,7 @@ const translations = {
dropTitle: 'Suéltalo',
dropMessage: 'Suelta tu archivo aquí',
enabled: 'Habilitado',
disabled: 'Desactivada',
ignore: 'Ignorar',
import: 'Importar',
offlinePrompt: 'No puedes realizar esta acción ahora mismo.',
Expand Down Expand Up @@ -2941,6 +2943,18 @@ const translations = {
[CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_BOTH]: 'Aprobado por supervisor y contabilidad',
},
},
accountingMethods: {
label: 'Cuándo Exportar',
description: 'Elige cuándo exportar los gastos:',
values: {
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.ACCRUAL]: 'Devengo',
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.CASH]: 'Efectivo',
},
alternateText: {
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.ACCRUAL]: 'Los gastos por cuenta propia se exportarán cuando estén aprobados definitivamente',
[COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.CASH]: 'Los gastos por cuenta propia se exportarán cuando estén pagados',
},
},
exportVendorBillsTo: {
label: 'Nivel de aprobación de facturas de proveedores',
description:
Expand Down Expand Up @@ -3912,6 +3926,7 @@ const translations = {
exportDate: 'Fecha de exportación',
defaultVendor: 'Proveedor predeterminado',
autoSync: 'Autosincronización',
autoSyncDescription: 'Sincroniza NetSuite y Expensify automáticamente, todos los días. Exporta el informe finalizado en tiempo real',
reimbursedReports: 'Sincronizar informes reembolsados',
cardReconciliation: 'Conciliación de tarjetas',
reconciliationAccount: 'Cuenta de conciliación',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {CONST as COMMON_CONST} from 'expensify-common';
import type {ValueOf} from 'type-fest';

type UpdateNetSuiteAccountingMethodParams = {
policyID: string;
accountingMethod: ValueOf<typeof COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD>;
};

export default UpdateNetSuiteAccountingMethodParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type {default as OpenPolicyInitialPageParams} from './OpenPolicyInitialPa
export type {default as SyncPolicyToQuickbooksOnlineParams} from './SyncPolicyToQuickbooksOnlineParams';
export type {default as SyncPolicyToXeroParams} from './SyncPolicyToXeroParams';
export type {default as SyncPolicyToNetSuiteParams} from './SyncPolicyToNetSuiteParams';
export type {default as UpdateNetSuiteAccountingMethodParams} from './UpdateNetSuiteAccountingMethodParams';
export type {default as SyncPolicyToQuickbooksDesktopParams} from './SyncPolicyToQuickbooksDesktopParams';
export type {default as DeleteContactMethodParams} from './DeleteContactMethodParams';
export type {default as DeletePaymentBankAccountParams} from './DeletePaymentBankAccountParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ const WRITE_COMMANDS = {
UPDATE_NETSUITE_COLLECTION_ACCOUNT: 'UpdateNetSuiteCollectionAccount',
UPDATE_NETSUITE_EXPORT_REPORTS_TO: 'UpdateNetSuiteExportReportsTo',
UPDATE_NETSUITE_VENDOR_BILLS_TO: 'UpdateNetSuiteExportVendorBillsTo',
UPDATE_NETSUITE_ACCOUNTING_METHOD: 'UpdateNetSuiteAccountingMethod',
UPDATE_NETSUITE_JOURNALS_TO: 'UpdateNetSuiteExportJournalsTo',
UPDATE_NETSUITE_APPROVAL_ACCOUNT: 'UpdateNetSuiteApprovalAccount',
UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE: 'UpdateNetSuiteCustomFormIDOptionsReimbursable',
Expand Down Expand Up @@ -830,6 +831,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_NETSUITE_COLLECTION_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'bankAccountID', string>;
[WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_REPORTS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf<typeof CONST.NETSUITE_REPORTS_APPROVAL_LEVEL>>;
[WRITE_COMMANDS.UPDATE_NETSUITE_VENDOR_BILLS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf<typeof CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL>>;
[WRITE_COMMANDS.UPDATE_NETSUITE_ACCOUNTING_METHOD]: Parameters.UpdateNetSuiteAccountingMethodParams;
[WRITE_COMMANDS.UPDATE_NETSUITE_JOURNALS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf<typeof CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL>>;
[WRITE_COMMANDS.UPDATE_NETSUITE_APPROVAL_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'value', string>;
[WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE]: Parameters.UpdateNetSuiteCustomFormIDParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,9 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_APPROVAL_ACCOUNT_SELECT]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_CUSTOM_FORM_ID]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_AUTO_SYNC]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteAutoSyncPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ACCOUNTING_METHOD]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteAccountingMethodPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_APPROVAL_ACCOUNT_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_CUSTOM_FORM_ID,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_AUTO_SYNC,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ACCOUNTING_METHOD,
SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES,
SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS,
SCREENS.WORKSPACE.ACCOUNTING.EXISTING_SAGE_INTACCT_CONNECTIONS,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_CUSTOM_FORM_ID]: {
path: ROUTES.POLICY_ACCOUNTING_NETSUITE_CUSTOM_FORM_ID.route,
},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_AUTO_SYNC]: {
path: ROUTES.POLICY_ACCOUNTING_NETSUITE_AUTO_SYNC.route,
},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ACCOUNTING_METHOD]: {
path: ROUTES.POLICY_ACCOUNTING_NETSUITE_ACCOUNTING_METHOD.route,
},
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.route},
[SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ENTER_CREDENTIALS.route},
[SCREENS.WORKSPACE.ACCOUNTING.EXISTING_SAGE_INTACCT_CONNECTIONS]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXISTING_CONNECTIONS.route},
Expand Down
17 changes: 17 additions & 0 deletions src/libs/actions/connections/NetSuiteCommands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {CONST as COMMON_CONST} from 'expensify-common';
import isObject from 'lodash/isObject';
import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
Expand Down Expand Up @@ -935,6 +936,21 @@ function updateNetSuiteExportReportsTo(
API.write(WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_REPORTS_TO, parameters, onyxData);
}

function updateNetSuiteAccountingMethod(
policyID: string,
accountingMethod: ValueOf<typeof COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD>,
oldAccountingMethod: ValueOf<typeof COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD>,
) {
const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.ACCOUNTING_METHOD, accountingMethod, oldAccountingMethod);

const parameters = {
policyID,
accountingMethod,
};

API.write(WRITE_COMMANDS.UPDATE_NETSUITE_ACCOUNTING_METHOD, parameters, onyxData);
}

function updateNetSuiteExportVendorBillsTo(
policyID: string,
approvalLevel: ValueOf<typeof CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL>,
Expand Down Expand Up @@ -1037,4 +1053,5 @@ export {
updateNetSuiteApprovalAccount,
updateNetSuiteCustomFormIDOptions,
updateNetSuiteCustomersJobsMapping,
updateNetSuiteAccountingMethod,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {CONST as COMMON_CONST} from 'expensify-common';
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import type {ValueOf} from 'type-fest';
import RadioListItem from '@components/SelectionList/RadioListItem';
import type {ListItem} from '@components/SelectionList/types';
import SelectionScreen from '@components/SelectionScreen';
import type {SelectorType} from '@components/SelectionScreen';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Connections from '@libs/actions/connections/NetSuiteCommands';
import {settingsPendingAction} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ROUTES from '@src/ROUTES';

type MenuListItem = ListItem & {
value: ValueOf<typeof COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD>;
};

function NetSuiteAccountingMethodPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
const policyID = policy?.id ?? '-1';
const styles = useThemeStyles();
const config = policy?.connections?.netsuite?.options?.config;
const autoSyncConfig = policy?.connections?.netsuite?.config;
const accountingMethod = config?.accountingMethod ?? COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.CASH;
const data: MenuListItem[] = Object.values(COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD).map((accountingMethodType) => ({
value: accountingMethodType,
text: translate(`workspace.netsuite.advancedConfig.accountingMethods.values.${accountingMethodType}` as TranslationPaths),
alternateText: translate(`workspace.netsuite.advancedConfig.accountingMethods.alternateText.${accountingMethodType}` as TranslationPaths),
keyForList: accountingMethodType,
isSelected: accountingMethod === accountingMethodType,
}));

const pendingAction =
settingsPendingAction([CONST.NETSUITE_CONFIG.AUTO_SYNC], autoSyncConfig?.pendingFields) ?? settingsPendingAction([CONST.NETSUITE_CONFIG.ACCOUNTING_METHOD], config?.pendingFields);

const headerContent = useMemo(
() => (
<View>
<Text style={[styles.ph5, styles.pb5]}>{translate('workspace.netsuite.advancedConfig.accountingMethods.description')}</Text>
</View>
),
[translate, styles.pb5, styles.ph5],
);

const selectExpenseReportApprovalLevel = useCallback(
(row: MenuListItem) => {
if (row.value !== config?.accountingMethod) {
Connections.updateNetSuiteAccountingMethod(policyID, row.value, config?.accountingMethod ?? COMMON_CONST.INTEGRATIONS.ACCOUNTING_METHOD.CASH);
}
Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_AUTO_SYNC.getRoute(policyID));
},
[config?.accountingMethod, policyID],
);

return (
<SelectionScreen
displayName={NetSuiteAccountingMethodPage.displayName}
title="workspace.netsuite.advancedConfig.accountingMethods.label"
headerContent={headerContent}
sections={[{data}]}
listItem={RadioListItem}
onSelectRow={(selection: SelectorType) => selectExpenseReportApprovalLevel(selection as MenuListItem)}
initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList}
policyID={policyID}
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN]}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_AUTO_SYNC.getRoute(policyID))}
connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE}
pendingAction={pendingAction}
/>
);
}

NetSuiteAccountingMethodPage.displayName = 'NetSuiteExpenseReportApprovalLevelSelectPage';

export default withPolicyConnections(NetSuiteAccountingMethodPage);
Loading

0 comments on commit 7a00538

Please sign in to comment.