From c2d7b70ac955922d0f2f7eb43f498a986a47b042 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 5 Jul 2024 21:10:25 +0530 Subject: [PATCH 01/12] Implemented remaining pages of NetSuite Advanced --- src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 29 ++++ src/SCREENS.ts | 7 + src/languages/en.ts | 7 + src/languages/es.ts | 7 + src/libs/API/types.ts | 17 +++ .../ModalStackNavigators/index.tsx | 13 ++ .../FULL_SCREEN_TO_RHP_MAPPING.ts | 7 + src/libs/Navigation/linkingConfig/config.ts | 21 +++ src/libs/Navigation/types.ts | 22 +++ src/libs/PolicyUtils.ts | 49 ++++++- .../actions/connections/NetSuiteCommands.ts | 128 +++++++++++++++++- .../advanced/NetSuiteAdvancedPage.tsx | 21 +-- .../NetSuiteApprovalAccountSelectPage.tsx | 90 ++++++++++++ .../NetSuiteCollectionAccountSelectPage.tsx | 89 ++++++++++++ .../advanced/NetSuiteCustomFormIDPage.tsx | 83 ++++++++++++ ...teExpenseReportApprovalLevelSelectPage.tsx | 74 ++++++++++ ...iteJournalEntryApprovalLevelSelectPage.tsx | 77 +++++++++++ ...NetSuiteReimbursementAccountSelectPage.tsx | 89 ++++++++++++ ...SuiteVendorBillApprovalLevelSelectPage.tsx | 81 +++++++++++ src/types/form/NetSuiteCustomFormIDForm.ts | 16 +++ src/types/form/index.ts | 1 + src/types/onyx/Policy.ts | 26 ++-- 23 files changed, 936 insertions(+), 21 deletions(-) create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteCollectionAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteExpenseReportApprovalLevelSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteJournalEntryApprovalLevelSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteReimbursementAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteVendorBillApprovalLevelSelectPage.tsx create mode 100644 src/types/form/NetSuiteCustomFormIDForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 906f8ef7095e..5d6b5492d15c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -562,6 +562,8 @@ const ONYXKEYS = { SAGE_INTACCT_CREDENTIALS_FORM_DRAFT: 'sageIntacctCredentialsFormDraft', NETSUITE_TOKEN_INPUT_FORM: 'netsuiteTokenInputForm', NETSUITE_TOKEN_INPUT_FORM_DRAFT: 'netsuiteTokenInputFormDraft', + NETSUITE_CUSTOM_FORM_ID_FORM: 'netsuiteCustomFormIDForm', + NETSUITE_CUSTOM_FORM_ID_FORM_DRAFT: 'netsuiteCustomFormIDFormDraft', }, } as const; @@ -626,6 +628,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; [ONYXKEYS.FORMS.SAGE_INTACCT_CREDENTIALS_FORM]: FormTypes.SageIntactCredentialsForm; [ONYXKEYS.FORMS.NETSUITE_TOKEN_INPUT_FORM]: FormTypes.NetSuiteTokenInputForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_FORM_ID_FORM]: FormTypes.NetSuiteCustomFormIDForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 82e49e31a166..149af6b16828 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1070,6 +1070,35 @@ const ROUTES = { route: 'settings/workspaces/:policyID/connections/netsuite/advanced/', getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/` as const, }, + POLICY_ACCOUNTING_NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/reimbursement-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/reimbursement-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_COLLECTION_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/collection-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/collection-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/expense-report-approval-level/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/expense-report-approval-level/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/vendor-bill-approval-level/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/vendor-bill-approval-level/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/journal-entry-approval-level/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/journal-entry-approval-level/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_APPROVAL_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/approval-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/approval-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_CUSTOM_FORM_ID: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/custom-form-id/:expenseType', + getRoute: (policyID: string, expenseType: ValueOf) => + `settings/workspaces/${policyID}/connections/netsuite/advanced/custom-form-id/${expenseType}` 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, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 875de4363028..418d0559c380 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -299,6 +299,13 @@ const SCREENS = { NETSUITE_TAX_POSTING_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Tax_Posting_Account_Select', NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Provincial_Tax_Posting_Account_Select', NETSUITE_ADVANCED: 'Policy_Accounting_NetSuite_Advanced', + NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Reimbursement_Account_Select', + NETSUITE_COLLECTION_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Collection_Account_Select', + NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT: 'Policy_Accounting_NetSuite_Expense_Report_Approval_Level_Select', + NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT: 'Policy_Accounting_NetSuite_Vendor_Bill_Approval_Level_Select', + 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', 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', diff --git a/src/languages/en.ts b/src/languages/en.ts index 1607e9b858d5..477db7b95af6 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2293,8 +2293,12 @@ export default { autoSyncDescription: 'Expensify will automatically sync with NetSuite every day.', reimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the NetSuite account below.', reimbursementsAccount: 'Reimbursements account', + reimbursementsAccountDescription: "Choose the bank account you'll use for reimbursements, and we'll create the associated payment in NetSuite.", collectionsAccount: 'Collections account', + collectionsAccountDescription: 'Once an invoice is marked as paid in Expensify and exported to NetSuite, it’ll appear against the account below.', approvalAccount: 'A/P approval account', + approvalAccountDescription: + 'Choose the account that transactions will be approved against in NetSuite. If you’re syncing reimbursed reports, this is also the account that bill payments will be created against.', defaultApprovalAccount: 'NetSuite default', inviteEmployees: 'Invite employees and set approvals', inviteEmployeesDescription: @@ -2308,6 +2312,7 @@ export default { customFormIDNonReimbursable: 'Non-reimbursable expense', exportReportsTo: { label: 'Expense report approval level', + description: 'Once an expense report is approved in Expensify and exported to NetSuite, you can set an additional level of approval in NetSuite prior to posting.', values: { [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_NONE]: 'NetSuite default preference', [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_SUPERVISOR_APPROVED]: 'Only supervisor approved', @@ -2317,6 +2322,7 @@ export default { }, 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.', values: { [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED_NONE]: 'NetSuite default preference', [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVAL_PENDING]: 'Pending approval', @@ -2325,6 +2331,7 @@ export default { }, exportJournalsTo: { label: 'Journal entry approval level', + description: 'Once a journal entry is approved in Expensify and exported to NetSuite, you can set an additional level of approval in NetSuite prior to posting.', values: { [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED_NONE]: 'NetSuite default preference', [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVAL_PENDING]: 'Pending approval', diff --git a/src/languages/es.ts b/src/languages/es.ts index c940fe78e7cf..6dae9b8068a3 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2334,8 +2334,12 @@ export default { reimbursedReportsDescription: 'Cada vez que se pague un informe utilizando Expensify ACH, se creará el correspondiente pago de la factura en la cuenta de NetSuite indicadas a continuación.', reimbursementsAccount: 'Cuenta de reembolsos', + reimbursementsAccountDescription: "Choose the bank account you'll use for reimbursements, and we'll create the associated payment in NetSuite.", collectionsAccount: 'Cuenta de cobros', + collectionsAccountDescription: 'Once an invoice is marked as paid in Expensify and exported to NetSuite, it’ll appear against the account below.', approvalAccount: 'Cuenta de aprobación de cuentas por pagar', + approvalAccountDescription: + 'Choose the account that transactions will be approved against in NetSuite. If you’re syncing reimbursed reports, this is also the account that bill payments will be created against.', defaultApprovalAccount: 'Preferencia predeterminada de NetSuite', inviteEmployees: 'Invitar empleados y establecer aprobaciones', inviteEmployeesDescription: @@ -2349,6 +2353,7 @@ export default { customFormIDNonReimbursable: 'Gasto no reembolsable', exportReportsTo: { label: 'Nivel de aprobación del informe de gastos', + description: 'Once an expense report is approved in Expensify and exported to NetSuite, you can set an additional level of approval in NetSuite prior to posting.', values: { [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_SUPERVISOR_APPROVED]: 'Solo aprobado por el supervisor', @@ -2358,6 +2363,7 @@ export default { }, exportVendorBillsTo: { label: 'Nivel de aprobación de facturas de proveedores', + 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.', values: { [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVAL_PENDING]: 'Aprobación pendiente', @@ -2366,6 +2372,7 @@ export default { }, exportJournalsTo: { label: 'Nivel de aprobación de asientos contables', + description: 'Once a journal entry is approved in Expensify and exported to NetSuite, you can set an additional level of approval in NetSuite prior to posting.', values: { [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVAL_PENDING]: 'Aprobación pendiente', diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 1bf1efc8af24..c5a93cf9f6d8 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1,5 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import type {NetSuiteCustomFormIDOptions} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import type * as Parameters from './parameters'; import type SignInUserParams from './parameters/SignInUserParams'; @@ -263,6 +264,14 @@ const WRITE_COMMANDS = { UPDATE_NETSUITE_AUTO_CREATE_ENTITIES: 'UpdateNetSuiteAutoCreateEntities', UPDATE_NETSUITE_ENABLE_NEW_CATEGORIES: 'UpdateNetSuiteEnableNewCategories', UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_ENABLED: 'UpdateNetSuiteCustomFormIDOptionsEnabled', + UPDATE_NETSUITE_REIMBURSEMENT_ACCOUNT_ID: 'UpdateNetSuiteReimbursementAccountID', + UPDATE_NETSUITE_COLLECTION_ACCOUNT: 'UpdateNetSuiteCollectionAccount', + UPDATE_NETSUITE_EXPORT_REPORTS_TO: 'UpdateNetSuiteExportReportsTo', + UPDATE_NETSUITE_VENDOR_BILLS_TO: 'UpdateNetSuiteExportVendorBillsTo', + UPDATE_NETSUITE_JOURNALS_TO: 'UpdateNetSuiteExportJournalsTo', + UPDATE_NETSUITE_APPROVAL_ACCOUNT: 'UpdateNetSuiteApprovalAccount', + UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE: 'UpdateNetSuiteCustomFormIDOptionsReimbursable', + UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE: 'UpdateNetSuiteCustomFormIDOptionsNonReimbursable', REQUEST_EXPENSIFY_CARD_LIMIT_INCREASE: 'RequestExpensifyCardLimitIncrease', CONNECT_POLICY_TO_SAGE_INTACCT: 'ConnectPolicyToSageIntacct', CONNECT_POLICY_TO_NETSUITE: 'ConnectPolicyToNetSuite', @@ -540,6 +549,14 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_NETSUITE_AUTO_CREATE_ENTITIES]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_ENABLE_NEW_CATEGORIES]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_ENABLED]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_REIMBURSEMENT_ACCOUNT_ID]: Parameters.UpdateNetSuiteGenericTypeParams<'bankAccountID', string>; + [WRITE_COMMANDS.UPDATE_NETSUITE_COLLECTION_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'bankAccountID', string>; + [WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_REPORTS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf>; + [WRITE_COMMANDS.UPDATE_NETSUITE_VENDOR_BILLS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf>; + [WRITE_COMMANDS.UPDATE_NETSUITE_JOURNALS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf>; + [WRITE_COMMANDS.UPDATE_NETSUITE_APPROVAL_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'value', string>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'reimbursable', NetSuiteCustomFormIDOptions>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'nonreimbursable', NetSuiteCustomFormIDOptions>; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index fe631e4cd0b1..1d13d5d21dc1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -352,6 +352,19 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/netsuite/export/NetSuiteProvincialTaxPostingAccountSelectPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED]: () => require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT]: () => + require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteReimbursementAccountSelectPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_COLLECTION_ACCOUNT_SELECT]: () => + require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteCollectionAccountSelectPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT]: () => + require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteExpenseReportApprovalLevelSelectPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT]: () => + require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteVendorBillApprovalLevelSelectPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT]: () => + require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteJournalEntryApprovalLevelSelectPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_APPROVAL_ACCOUNT_SELECT]: () => + require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_CUSTOM_FORM_ID]: () => require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage').default, [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: () => require('../../../../pages/workspace/accounting/intacct/IntacctPrerequisitesPage').default, [SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS]: () => 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 78732767f4c1..e5ea2d9fa2e4 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -75,6 +75,13 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TAX_POSTING_ACCOUNT_SELECT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_COLLECTION_ACCOUNT_SELECT, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT, + 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.SAGE_INTACCT_PREREQUISITES, SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS, SCREENS.WORKSPACE.ACCOUNTING.EXISTING_SAGE_INTACCT_CONNECTIONS, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index eb2646a048c7..ff83a64d580e 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -401,6 +401,27 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED]: { path: ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.route, }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_COLLECTION_ACCOUNT_SELECT]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_COLLECTION_ACCOUNT_SELECT.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_APPROVAL_ACCOUNT_SELECT]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_APPROVAL_ACCOUNT_SELECT.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_CUSTOM_FORM_ID]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_CUSTOM_FORM_ID.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}, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 183e21fc67b4..746b37bbe26a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -479,6 +479,28 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED]: { policyID: string; }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_COLLECTION_ACCOUNT_SELECT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_APPROVAL_ACCOUNT_SELECT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_CUSTOM_FORM_ID]: { + policyID: string; + expenseType: ValueOf; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 4c071317907b..392ac1156c92 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -8,9 +8,10 @@ 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, NetSuiteAccount, NetSuiteConnection, PolicyFeatureName, Rate, Tenant} from '@src/types/onyx/Policy'; import type PolicyEmployee from '@src/types/onyx/PolicyEmployee'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import * as Localize from './Localize'; import Navigation from './Navigation/Navigation'; import * as NetworkStore from './Network/NetworkStore'; import {getAccountIDsByLogins, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; @@ -538,6 +539,49 @@ function canUseProvincialTaxNetSuite(subsidiaryCountry?: string) { return subsidiaryCountry === '_canada'; } +function getNetSuiteReimbursableAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { + const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; + let accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === '_bank'); + accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === '_creditCard')); + + return accountsToConsider.map(({id, name}) => ({ + value: id, + text: name, + keyForList: id, + isSelected: selectedBankAccountId === id, + })); +} + +function getNetSuiteCollectionAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { + const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; + const accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === '_bank'); + + return accountsToConsider.map(({id, name}) => ({ + value: id, + text: name, + keyForList: id, + isSelected: selectedBankAccountId === id, + })); +} + +function getNetSuiteApprovalAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { + const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; + const defaultApprovalAccount: NetSuiteAccount = { + id: CONST.NETSUITE_APPROVAL_ACCOUNT_DEFAULT, + name: Localize.translateLocal('workspace.netsuite.advancedConfig.defaultApprovalAccount'), + type: '_accountsPayable', + }; + let accountsToConsider = [defaultApprovalAccount]; + accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === '_accountsPayable')); + + return accountsToConsider.map(({id, name}) => ({ + value: id, + text: name, + keyForList: id, + isSelected: selectedBankAccountId === id, + })); +} + function getCustomersOrJobsLabelNetSuite(policy: Policy | undefined, translate: LocaleContextProps['translate']): string | undefined { const importMapping = policy?.connections?.netsuite?.options?.config?.syncOptions?.mapping; if (!importMapping?.customers && !importMapping?.jobs) { @@ -670,6 +714,9 @@ export { getNetSuiteVendorOptions, canUseTaxNetSuite, canUseProvincialTaxNetSuite, + getNetSuiteReimbursableAccountOptions, + getNetSuiteCollectionAccountOptions, + getNetSuiteApprovalAccountOptions, getNetSuitePayableAccountOptions, getNetSuiteReceivableAccountOptions, getNetSuiteInvoiceItemOptions, diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index 20f7fcd6e483..f77180026a4f 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, NetSuiteCustomFormID} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; type SubsidiaryParam = { @@ -729,7 +729,125 @@ function updateNetSuiteCustomFormIDOptionsEnabled(policyID: string, value: boole API.write(WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_ENABLED, parameters, onyxData); } +function updateNetSuiteReimbursementAccountID(policyID: string, bankAccountID: string, oldBankAccountID?: string) { + const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.REIMBURSEMENT_ACCOUNT_ID, bankAccountID, oldBankAccountID); + + const parameters = { + policyID, + bankAccountID, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_REIMBURSEMENT_ACCOUNT_ID, parameters, onyxData); +} + +function updateNetSuiteCollectionAccount(policyID: string, bankAccountID: string, oldBankAccountID?: string) { + const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.REIMBURSEMENT_ACCOUNT_ID, bankAccountID, oldBankAccountID); + + const parameters = { + policyID, + bankAccountID, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_COLLECTION_ACCOUNT, parameters, onyxData); +} + +function updateNetSuiteExportReportsTo( + policyID: string, + approvalLevel: ValueOf, + oldApprovalLevel: ValueOf, +) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_REPORTS_TO, approvalLevel, oldApprovalLevel); + + const parameters = { + policyID, + value: approvalLevel, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_REPORTS_TO, parameters, onyxData); +} + +function updateNetSuiteExportVendorBillsTo( + policyID: string, + approvalLevel: ValueOf, + oldApprovalLevel: ValueOf, +) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_VENDOR_BILLS_TO, approvalLevel, oldApprovalLevel); + + const parameters = { + policyID, + value: approvalLevel, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_VENDOR_BILLS_TO, parameters, onyxData); +} + +function updateNetSuiteExportJournalsTo( + policyID: string, + approvalLevel: ValueOf, + oldApprovalLevel: ValueOf, +) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_JOURNALS_TO, approvalLevel, oldApprovalLevel); + + const parameters = { + policyID, + value: approvalLevel, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_JOURNALS_TO, parameters, onyxData); +} + +function updateNetSuiteApprovalAccount(policyID: string, value: string, oldValue: string) { + const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.APPROVAL_ACCOUNT, value, oldValue); + + const parameters = { + policyID, + value, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_APPROVAL_ACCOUNT, parameters, onyxData); +} + +function getNetSuiteCustomFormIDOptionsParamsAndCommand(policyID: string, isReimbursable: boolean, expenseType: ValueOf, value: string) { + if (isReimbursable) { + return { + command: WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE, + parameters: { + policyID, + reimbursable: { + [expenseType]: value, + }, + }, + }; + } + return { + command: WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE, + parameters: { + policyID, + nonreimbursable: { + [expenseType]: value, + }, + }, + }; +} + +function updateNetSuiteCustomFormIDOptions( + policyID: string, + value: string, + isReimbursable: boolean, + exportDestination: ValueOf, + oldCustomFormID?: NetSuiteCustomFormID, +) { + const customFormIDKey = isReimbursable ? 'reimbursable' : 'nonReimbursable'; + const data = { + [customFormIDKey]: { + [CONST.NETSUITE_MAP_EXPORT_DESTINATION[exportDestination]]: value, + }, + }; + const oldData = { + [customFormIDKey]: oldCustomFormID?.[customFormIDKey] ?? null, + }; + const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS, data, oldData); + + const {command, parameters} = getNetSuiteCustomFormIDOptionsParamsAndCommand(policyID, isReimbursable, CONST.NETSUITE_MAP_EXPORT_DESTINATION[exportDestination], value); + API.write(command, parameters, onyxData); +} + export { + connectPolicyToNetSuite, updateNetSuiteSubsidiary, updateNetSuiteSyncTaxConfiguration, updateNetSuiteExporter, @@ -755,5 +873,11 @@ export { updateNetSuiteAutoCreateEntities, updateNetSuiteEnableNewCategories, updateNetSuiteCustomFormIDOptionsEnabled, - connectPolicyToNetSuite, + updateNetSuiteReimbursementAccountID, + updateNetSuiteCollectionAccount, + updateNetSuiteExportReportsTo, + updateNetSuiteExportVendorBillsTo, + updateNetSuiteExportJournalsTo, + updateNetSuiteApprovalAccount, + updateNetSuiteCustomFormIDOptions, }; diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx index 741395555f4a..c1836ce2d1a7 100644 --- a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx @@ -7,12 +7,14 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Connections from '@libs/actions/connections/NetSuiteCommands'; import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; import type {DividerLineItem, MenuItem, ToggleItem} from '@pages/workspace/accounting/netsuite/types'; import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; import withPolicyConnections from '@pages/workspace/withPolicyConnections'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { const {translate} = useLocalize(); @@ -72,26 +74,29 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.reimbursementsAccount'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT.getRoute(policyID)), brickRoadIndicator: config?.errorFields?.reimbursementAccountID ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: selectedReimbursementAccount ? selectedReimbursementAccount.name : undefined, pendingAction: config?.pendingFields?.reimbursementAccountID, errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.REIMBURSEMENT_ACCOUNT_ID), onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.REIMBURSEMENT_ACCOUNT_ID), + shouldHide: config?.reimbursableExpensesExportDestination === CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY, }, { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.collectionsAccount'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_COLLECTION_ACCOUNT_SELECT.getRoute(policyID)), brickRoadIndicator: config?.errorFields?.collectionAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: selectedCollectionAccount ? selectedCollectionAccount.name : undefined, pendingAction: config?.pendingFields?.collectionAccount, errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.COLLECTION_ACCOUNT), onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.COLLECTION_ACCOUNT), + shouldHide: config?.reimbursableExpensesExportDestination === CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY, }, { type: 'divider', key: 'divider2', + shouldHide: config?.reimbursableExpensesExportDestination === CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY, }, { type: 'toggle', @@ -137,7 +142,7 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.exportReportsTo.label'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT.getRoute(policyID)), brickRoadIndicator: config?.errorFields?.exportReportsTo ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: config?.syncOptions.exportReportsTo ? translate(`workspace.netsuite.advancedConfig.exportReportsTo.values.${config.syncOptions.exportReportsTo}`) : undefined, pendingAction: config?.syncOptions.pendingFields?.exportReportsTo, @@ -148,7 +153,7 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.exportVendorBillsTo.label'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT.getRoute(policyID)), brickRoadIndicator: config?.errorFields?.exportVendorBillsTo ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: config?.syncOptions.exportVendorBillsTo ? translate(`workspace.netsuite.advancedConfig.exportVendorBillsTo.values.${config.syncOptions.exportVendorBillsTo}`) : undefined, pendingAction: config?.syncOptions.pendingFields?.exportVendorBillsTo, @@ -161,7 +166,7 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.exportJournalsTo.label'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT.getRoute(policyID)), brickRoadIndicator: config?.errorFields?.exportJournalsTo ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: config?.syncOptions.exportJournalsTo ? translate(`workspace.netsuite.advancedConfig.exportJournalsTo.values.${config.syncOptions.exportJournalsTo}`) : undefined, pendingAction: config?.syncOptions.pendingFields?.exportJournalsTo, @@ -174,7 +179,7 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.approvalAccount'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_APPROVAL_ACCOUNT_SELECT.getRoute(policyID)), brickRoadIndicator: config?.errorFields?.approvalAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: selectedApprovalAccount ? selectedApprovalAccount.name : undefined, pendingAction: config?.pendingFields?.approvalAccount, @@ -200,7 +205,7 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.customFormIDReimbursable'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_CUSTOM_FORM_ID.getRoute(policyID, CONST.NETSUITE_EXPENSE_TYPE.REIMBURSABLE)), brickRoadIndicator: config?.errorFields?.customFormIDOptions ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: config?.customFormIDOptions?.reimbursable[CONST.NETSUITE_MAP_EXPORT_DESTINATION[config.reimbursableExpensesExportDestination]], pendingAction: config?.pendingFields?.customFormIDOptions, @@ -211,7 +216,7 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { { type: 'menuitem', description: translate('workspace.netsuite.advancedConfig.customFormIDNonReimbursable'), - onPress: () => {}, // TODO: This will be implemented in a future PR + onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_CUSTOM_FORM_ID.getRoute(policyID, CONST.NETSUITE_EXPENSE_TYPE.NON_REIMBURSABLE)), brickRoadIndicator: config?.errorFields?.customFormIDOptions ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: config?.customFormIDOptions?.reimbursable[CONST.NETSUITE_MAP_EXPORT_DESTINATION[config.nonreimbursableExpensesExportDestination]], pendingAction: config?.pendingFields?.customFormIDOptions, diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx new file mode 100644 index 000000000000..ed823d698d2f --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx @@ -0,0 +1,90 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import BlockingView from '@components/BlockingViews/BlockingView'; +import * as Illustrations from '@components/Icon/Illustrations'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {SelectorType} from '@components/SelectionScreen'; +import SelectionScreen 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 Navigation from '@libs/Navigation/Navigation'; +import {getNetSuiteApprovalAccountOptions} from '@libs/PolicyUtils'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +function NetSuiteApprovalAccountSelectPage({policy}: WithPolicyConnectionsProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const policyID = policy?.id ?? '-1'; + + const config = policy?.connections?.netsuite.options.config; + const netsuiteApprovalAccountOptions = useMemo( + () => getNetSuiteApprovalAccountOptions(policy ?? undefined, config?.approvalAccount), + // The default option will be language dependent, so we need to recompute the options when the language changes + // eslint-disable-next-line react-hooks/exhaustive-deps + [config?.approvalAccount, policy, translate], + ); + + const initiallyFocusedOptionKey = useMemo(() => netsuiteApprovalAccountOptions?.find((mode) => mode.isSelected)?.keyForList, [netsuiteApprovalAccountOptions]); + + const updateCollectionAccount = useCallback( + ({value}: SelectorType) => { + if (config?.approvalAccount !== value) { + Connections.updateNetSuiteApprovalAccount(policyID, value, config?.approvalAccount ?? CONST.NETSUITE_APPROVAL_ACCOUNT_DEFAULT); + } + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); + }, + [policyID, config?.approvalAccount], + ); + + const listEmptyContent = useMemo( + () => ( + + ), + [translate, styles.pb10], + ); + + const headerContent = useMemo( + () => ( + + {translate('workspace.netsuite.advancedConfig.approvalAccountDescription')} + + ), + [translate, styles.pb5, styles.ph5], + ); + + return ( + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID))} + title="workspace.netsuite.advancedConfig.approvalAccount" + listEmptyContent={listEmptyContent} + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + /> + ); +} + +NetSuiteApprovalAccountSelectPage.displayName = 'NetSuiteApprovalAccountSelectPage'; + +export default withPolicyConnections(NetSuiteApprovalAccountSelectPage); diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCollectionAccountSelectPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCollectionAccountSelectPage.tsx new file mode 100644 index 000000000000..c1851a84f384 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCollectionAccountSelectPage.tsx @@ -0,0 +1,89 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import BlockingView from '@components/BlockingViews/BlockingView'; +import * as Illustrations from '@components/Icon/Illustrations'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {SelectorType} from '@components/SelectionScreen'; +import SelectionScreen 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 Navigation from '@libs/Navigation/Navigation'; +import {getNetSuiteCollectionAccountOptions} from '@libs/PolicyUtils'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +function NetSuiteCollectionAccountSelectPage({policy}: WithPolicyConnectionsProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const policyID = policy?.id ?? '-1'; + + const config = policy?.connections?.netsuite.options.config; + const netsuiteCollectionAccountOptions = useMemo( + () => getNetSuiteCollectionAccountOptions(policy ?? undefined, config?.collectionAccount), + [config?.collectionAccount, policy], + ); + + const initiallyFocusedOptionKey = useMemo(() => netsuiteCollectionAccountOptions?.find((mode) => mode.isSelected)?.keyForList, [netsuiteCollectionAccountOptions]); + + const updateCollectionAccount = useCallback( + ({value}: SelectorType) => { + if (config?.collectionAccount !== value) { + Connections.updateNetSuiteCollectionAccount(policyID, value, config?.collectionAccount); + } + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); + }, + [policyID, config?.collectionAccount], + ); + + const listEmptyContent = useMemo( + () => ( + + ), + [translate, styles.pb10], + ); + + const headerContent = useMemo( + () => ( + + {translate('workspace.netsuite.advancedConfig.collectionsAccountDescription')} + + ), + [translate, styles.pb5, styles.ph5], + ); + + return ( + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID))} + title="workspace.netsuite.advancedConfig.collectionsAccount" + listEmptyContent={listEmptyContent} + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + shouldBeBlocked={config?.reimbursableExpensesExportDestination === CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY} + /> + ); +} + +NetSuiteCollectionAccountSelectPage.displayName = 'NetSuiteCollectionAccountSelectPage'; + +export default withPolicyConnections(NetSuiteCollectionAccountSelectPage); diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx new file mode 100644 index 000000000000..ce89a2daac76 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx @@ -0,0 +1,83 @@ +import {useRoute} from '@react-navigation/native'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import ConnectionLayout from '@components/ConnectionLayout'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Connections from '@libs/actions/connections/NetSuiteCommands'; +import Navigation from '@libs/Navigation/Navigation'; +import type {ExpenseRouteParams} from '@pages/workspace/accounting/netsuite/types'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; + +function NetSuiteCustomFormIDPage({policy}: WithPolicyConnectionsProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const policyID = policy?.id ?? '-1'; + const route = useRoute(); + const params = route.params as ExpenseRouteParams; + const isReimbursable = params.expenseType === CONST.NETSUITE_EXPENSE_TYPE.REIMBURSABLE; + + const config = policy?.connections?.netsuite?.options.config; + + const exportDestination = + (isReimbursable ? config?.reimbursableExpensesExportDestination : config?.nonreimbursableExpensesExportDestination) ?? CONST.NETSUITE_EXPORT_DESTINATION.EXPENSE_REPORT; + const customFormIDKey = isReimbursable ? 'reimbursable' : 'nonReimbursable'; + + const updateCustomFormID = useCallback( + (formValues: FormOnyxValues) => { + if (config?.customFormIDOptions?.[customFormIDKey]?.[CONST.NETSUITE_MAP_EXPORT_DESTINATION[exportDestination]] !== formValues[params.expenseType]) { + Connections.updateNetSuiteCustomFormIDOptions(policyID, formValues[params.expenseType], isReimbursable, exportDestination, config?.customFormIDOptions); + } + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); + }, + [config?.customFormIDOptions, customFormIDKey, exportDestination, isReimbursable, params.expenseType, policyID], + ); + + return ( + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID))} + headerTitle={`workspace.netsuite.advancedConfig.${isReimbursable ? 'customFormIDReimbursable' : 'customFormIDNonReimbursable'}`} + accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]} + policyID={policyID} + featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED} + contentContainerStyle={styles.pb2} + titleStyle={styles.ph5} + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + > + + + + + + + ); +} + +NetSuiteCustomFormIDPage.displayName = 'NetSuiteCustomFormIDPage'; + +export default withPolicyConnections(NetSuiteCustomFormIDPage); diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteExpenseReportApprovalLevelSelectPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteExpenseReportApprovalLevelSelectPage.tsx new file mode 100644 index 000000000000..ed70da6f1608 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteExpenseReportApprovalLevelSelectPage.tsx @@ -0,0 +1,74 @@ +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 Navigation from '@navigation/Navigation'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +type MenuListItem = ListItem & { + value: ValueOf; +}; + +function NetSuiteExpenseReportApprovalLevelSelectPage({policy}: WithPolicyConnectionsProps) { + const {translate} = useLocalize(); + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const config = policy?.connections?.netsuite.options.config; + const data: MenuListItem[] = Object.values(CONST.NETSUITE_REPORTS_APPROVAL_LEVEL).map((approvalType) => ({ + value: approvalType, + text: translate(`workspace.netsuite.advancedConfig.exportReportsTo.values.${approvalType}`), + keyForList: approvalType, + isSelected: config?.syncOptions.exportReportsTo === approvalType, + })); + + const headerContent = useMemo( + () => ( + + {translate('workspace.netsuite.advancedConfig.exportReportsTo.description')} + + ), + [translate, styles.pb5, styles.ph5], + ); + + const selectExpenseReportApprovalLevel = useCallback( + (row: MenuListItem) => { + if (row.value !== config?.syncOptions.exportReportsTo) { + Connections.updateNetSuiteExportReportsTo(policyID, row.value, config?.syncOptions.exportReportsTo ?? CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_NONE); + } + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); + }, + [config?.syncOptions.exportReportsTo, policyID], + ); + + return ( + 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_ADVANCED.getRoute(policyID))} + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + shouldBeBlocked={config?.reimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.EXPENSE_REPORT} + /> + ); +} + +NetSuiteExpenseReportApprovalLevelSelectPage.displayName = 'NetSuiteExpenseReportApprovalLevelSelectPage'; + +export default withPolicyConnections(NetSuiteExpenseReportApprovalLevelSelectPage); diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteJournalEntryApprovalLevelSelectPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteJournalEntryApprovalLevelSelectPage.tsx new file mode 100644 index 000000000000..ee1d1108ffd6 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteJournalEntryApprovalLevelSelectPage.tsx @@ -0,0 +1,77 @@ +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 Navigation from '@navigation/Navigation'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +type MenuListItem = ListItem & { + value: ValueOf; +}; + +function NetSuiteJournalEntryApprovalLevelSelectPage({policy}: WithPolicyConnectionsProps) { + const {translate} = useLocalize(); + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const config = policy?.connections?.netsuite.options.config; + const data: MenuListItem[] = Object.values(CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL).map((approvalType) => ({ + value: approvalType, + text: translate(`workspace.netsuite.advancedConfig.exportJournalsTo.values.${approvalType}`), + keyForList: approvalType, + isSelected: config?.syncOptions.exportJournalsTo === approvalType, + })); + + const headerContent = useMemo( + () => ( + + {translate('workspace.netsuite.advancedConfig.exportJournalsTo.description')} + + ), + [translate, styles.pb5, styles.ph5], + ); + + const selectJournalApprovalLevel = useCallback( + (row: MenuListItem) => { + if (row.value !== config?.syncOptions.exportJournalsTo) { + Connections.updateNetSuiteExportJournalsTo(policyID, row.value, config?.syncOptions.exportJournalsTo ?? CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED_NONE); + } + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); + }, + [config?.syncOptions.exportJournalsTo, policyID], + ); + + return ( + selectJournalApprovalLevel(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_ADVANCED.getRoute(policyID))} + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + shouldBeBlocked={ + config?.reimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY && + config?.nonreimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY + } + /> + ); +} + +NetSuiteJournalEntryApprovalLevelSelectPage.displayName = 'NetSuiteJournalEntryApprovalLevelSelectPage'; + +export default withPolicyConnections(NetSuiteJournalEntryApprovalLevelSelectPage); diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteReimbursementAccountSelectPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteReimbursementAccountSelectPage.tsx new file mode 100644 index 000000000000..4a9898fb187e --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteReimbursementAccountSelectPage.tsx @@ -0,0 +1,89 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import BlockingView from '@components/BlockingViews/BlockingView'; +import * as Illustrations from '@components/Icon/Illustrations'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {SelectorType} from '@components/SelectionScreen'; +import SelectionScreen 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 Navigation from '@libs/Navigation/Navigation'; +import {getNetSuiteReimbursableAccountOptions} from '@libs/PolicyUtils'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +function NetSuiteReimbursementAccountSelectPage({policy}: WithPolicyConnectionsProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const policyID = policy?.id ?? '-1'; + + const config = policy?.connections?.netsuite.options.config; + const netsuiteReimbursableAccountOptions = useMemo( + () => getNetSuiteReimbursableAccountOptions(policy ?? undefined, config?.reimbursementAccountID), + [config?.reimbursementAccountID, policy], + ); + + const initiallyFocusedOptionKey = useMemo(() => netsuiteReimbursableAccountOptions?.find((mode) => mode.isSelected)?.keyForList, [netsuiteReimbursableAccountOptions]); + + const updateReimbursementAccount = useCallback( + ({value}: SelectorType) => { + if (config?.reimbursementAccountID !== value) { + Connections.updateNetSuiteReimbursementAccountID(policyID, value, config?.reimbursementAccountID); + } + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); + }, + [policyID, config?.reimbursementAccountID], + ); + + const listEmptyContent = useMemo( + () => ( + + ), + [translate, styles.pb10], + ); + + const headerContent = useMemo( + () => ( + + {translate('workspace.netsuite.advancedConfig.reimbursementsAccountDescription')} + + ), + [translate, styles.pb5, styles.ph5], + ); + + return ( + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID))} + title="workspace.netsuite.advancedConfig.reimbursementsAccount" + listEmptyContent={listEmptyContent} + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + shouldBeBlocked={config?.reimbursableExpensesExportDestination === CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY} + /> + ); +} + +NetSuiteReimbursementAccountSelectPage.displayName = 'NetSuiteReimbursementAccountSelectPage'; + +export default withPolicyConnections(NetSuiteReimbursementAccountSelectPage); diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteVendorBillApprovalLevelSelectPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteVendorBillApprovalLevelSelectPage.tsx new file mode 100644 index 000000000000..ecbaacd46bad --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteVendorBillApprovalLevelSelectPage.tsx @@ -0,0 +1,81 @@ +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 Navigation from '@navigation/Navigation'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +type MenuListItem = ListItem & { + value: ValueOf; +}; + +function NetSuiteVendorBillApprovalLevelSelectPage({policy}: WithPolicyConnectionsProps) { + const {translate} = useLocalize(); + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const config = policy?.connections?.netsuite.options.config; + const data: MenuListItem[] = Object.values(CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL).map((approvalType) => ({ + value: approvalType, + text: translate(`workspace.netsuite.advancedConfig.exportVendorBillsTo.values.${approvalType}`), + keyForList: approvalType, + isSelected: config?.syncOptions.exportVendorBillsTo === approvalType, + })); + + const headerContent = useMemo( + () => ( + + {translate('workspace.netsuite.advancedConfig.exportVendorBillsTo.description')} + + ), + [translate, styles.pb5, styles.ph5], + ); + + const selectVendorBillApprovalLevel = useCallback( + (row: MenuListItem) => { + if (row.value !== config?.syncOptions.exportVendorBillsTo) { + Connections.updateNetSuiteExportVendorBillsTo( + policyID, + row.value, + config?.syncOptions.exportVendorBillsTo ?? CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED_NONE, + ); + } + Navigation.goBack(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); + }, + [config?.syncOptions.exportVendorBillsTo, policyID], + ); + + return ( + selectVendorBillApprovalLevel(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_ADVANCED.getRoute(policyID))} + connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + shouldBeBlocked={ + config?.reimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.VENDOR_BILL && + config?.nonreimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.VENDOR_BILL + } + /> + ); +} + +NetSuiteVendorBillApprovalLevelSelectPage.displayName = 'NetSuiteVendorBillApprovalLevelSelectPage'; + +export default withPolicyConnections(NetSuiteVendorBillApprovalLevelSelectPage); diff --git a/src/types/form/NetSuiteCustomFormIDForm.ts b/src/types/form/NetSuiteCustomFormIDForm.ts new file mode 100644 index 000000000000..eb1ea8625075 --- /dev/null +++ b/src/types/form/NetSuiteCustomFormIDForm.ts @@ -0,0 +1,16 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; +import type Form from './Form'; + +type InputID = ValueOf; + +type NetSuiteCustomFormIDForm = Form< + InputID, + { + [CONST.NETSUITE_EXPENSE_TYPE.REIMBURSABLE]: string; + [CONST.NETSUITE_EXPENSE_TYPE.NON_REIMBURSABLE]: string; + } +>; + +// eslint-disable-next-line import/prefer-default-export +export type {NetSuiteCustomFormIDForm}; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index b3503099b4b3..44c0cb81cb91 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -57,4 +57,5 @@ export type {SubscriptionSizeForm} from './SubscriptionSizeForm'; export type {WorkspaceReportFieldsForm} from './WorkspaceReportFieldsForm'; export type {SageIntactCredentialsForm} from './SageIntactCredentialsForm'; export type {NetSuiteTokenInputForm} from './NetSuiteTokenInputForm'; +export type {NetSuiteCustomFormIDForm} from './NetSuiteCustomFormIDForm'; export type {default as Form} from './Form'; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 098869d7bf8e..86560166cb4e 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -718,6 +718,18 @@ type NetSuiteCustomFormIDOptions = { journalEntry?: string; }; +/** The custom form ID object */ +type NetSuiteCustomFormID = { + /** The custom form selections for reimbursable transactions */ + reimbursable: NetSuiteCustomFormIDOptions; + + /** The custom form selections for non-reimbursable transactions */ + nonReimbursable: NetSuiteCustomFormIDOptions; + + /** Whether we'll use the custom form selections upon export to NetSuite */ + enabled: boolean; +}; + /** User configuration for the NetSuite accounting integration. */ type NetSuiteConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Invoice Item Preference */ @@ -880,16 +892,7 @@ type NetSuiteConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ payableAcct: string; /** Configurations for customer to set custom forms for which reimbursable and non-reimbursable transactions will export to in NetSuite */ - customFormIDOptions?: { - /** The custom form selections for reimbursable transactions */ - reimbursable: NetSuiteCustomFormIDOptions; - - /** The custom form selections for non-reimbursable transactions */ - nonReimbursable: NetSuiteCustomFormIDOptions; - - /** Whether we'll use the custom form selections upon export to NetSuite */ - enabled: boolean; - }; + customFormIDOptions?: NetSuiteCustomFormID; /** The account to use for Invoices export to NetSuite */ collectionAccount?: string; @@ -1355,4 +1358,7 @@ export type { NetSuiteConnection, ConnectionLastSync, NetSuiteSubsidiary, + NetSuiteAccount, + NetSuiteCustomFormIDOptions, + NetSuiteCustomFormID, }; From 35eee592797473ce0c8a258ebb4dd881ab65d28d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 5 Jul 2024 21:13:58 +0530 Subject: [PATCH 02/12] Bug fix --- .../accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx index c1836ce2d1a7..eb2c6489241f 100644 --- a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx @@ -218,7 +218,7 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { description: translate('workspace.netsuite.advancedConfig.customFormIDNonReimbursable'), onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_CUSTOM_FORM_ID.getRoute(policyID, CONST.NETSUITE_EXPENSE_TYPE.NON_REIMBURSABLE)), brickRoadIndicator: config?.errorFields?.customFormIDOptions ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, - title: config?.customFormIDOptions?.reimbursable[CONST.NETSUITE_MAP_EXPORT_DESTINATION[config.nonreimbursableExpensesExportDestination]], + title: config?.customFormIDOptions?.nonReimbursable[CONST.NETSUITE_MAP_EXPORT_DESTINATION[config.nonreimbursableExpensesExportDestination]], pendingAction: config?.pendingFields?.customFormIDOptions, errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), From acfa474662a516c0e5374649c5d16a04f0334a99 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 5 Jul 2024 21:21:10 +0530 Subject: [PATCH 03/12] Small bugfix in API --- src/libs/API/types.ts | 5 ++--- src/libs/actions/connections/NetSuiteCommands.ts | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index c5a93cf9f6d8..1b7e04aca254 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1,6 +1,5 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; -import type {NetSuiteCustomFormIDOptions} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import type * as Parameters from './parameters'; import type SignInUserParams from './parameters/SignInUserParams'; @@ -555,8 +554,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_NETSUITE_VENDOR_BILLS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf>; [WRITE_COMMANDS.UPDATE_NETSUITE_JOURNALS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf>; [WRITE_COMMANDS.UPDATE_NETSUITE_APPROVAL_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'value', string>; - [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'reimbursable', NetSuiteCustomFormIDOptions>; - [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'nonreimbursable', NetSuiteCustomFormIDOptions>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'reimbursable', string>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'nonreimbursable', string>; }; const READ_COMMANDS = { diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index f77180026a4f..a91fd9b1ca65 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -807,9 +807,9 @@ function getNetSuiteCustomFormIDOptionsParamsAndCommand(policyID: string, isReim command: WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE, parameters: { policyID, - reimbursable: { + reimbursable: JSON.stringify({ [expenseType]: value, - }, + }), }, }; } @@ -817,9 +817,9 @@ function getNetSuiteCustomFormIDOptionsParamsAndCommand(policyID: string, isReim command: WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE, parameters: { policyID, - nonreimbursable: { + nonreimbursable: JSON.stringify({ [expenseType]: value, - }, + }), }, }; } From f7b2940479b78f2c16657769417863c3eb75cdef Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 5 Jul 2024 22:43:27 +0530 Subject: [PATCH 04/12] Small bugfix in Custom Form ID API --- .../UpdateNetSuiteCustomFormIDParams.ts | 10 ++++++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 4 +-- .../actions/connections/NetSuiteCommands.ts | 32 ++++--------------- 4 files changed, 20 insertions(+), 27 deletions(-) create mode 100644 src/libs/API/parameters/UpdateNetSuiteCustomFormIDParams.ts diff --git a/src/libs/API/parameters/UpdateNetSuiteCustomFormIDParams.ts b/src/libs/API/parameters/UpdateNetSuiteCustomFormIDParams.ts new file mode 100644 index 000000000000..01e5db8c1089 --- /dev/null +++ b/src/libs/API/parameters/UpdateNetSuiteCustomFormIDParams.ts @@ -0,0 +1,10 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + +type UpdateNetSuiteCustomFormIDParams = { + policyID: string; + formID: string; + formType: ValueOf; +}; + +export default UpdateNetSuiteCustomFormIDParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 848af7e34634..dae5d022baa9 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -243,3 +243,4 @@ export type {default as CreateWorkspaceReportFieldParams} from './CreateWorkspac export type {default as OpenPolicyExpensifyCardsPageParams} from './OpenPolicyExpensifyCardsPageParams'; export type {default as RequestExpensifyCardLimitIncreaseParams} from './RequestExpensifyCardLimitIncreaseParams'; export type {default as UpdateNetSuiteGenericTypeParams} from './UpdateNetSuiteGenericTypeParams'; +export type {default as UpdateNetSuiteCustomFormIDParams} from './UpdateNetSuiteCustomFormIDParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 1b7e04aca254..3c6b0751398b 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -554,8 +554,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_NETSUITE_VENDOR_BILLS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf>; [WRITE_COMMANDS.UPDATE_NETSUITE_JOURNALS_TO]: Parameters.UpdateNetSuiteGenericTypeParams<'value', ValueOf>; [WRITE_COMMANDS.UPDATE_NETSUITE_APPROVAL_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'value', string>; - [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'reimbursable', string>; - [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE]: Parameters.UpdateNetSuiteGenericTypeParams<'nonreimbursable', string>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE]: Parameters.UpdateNetSuiteCustomFormIDParams; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE]: Parameters.UpdateNetSuiteCustomFormIDParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index a91fd9b1ca65..1858a8b9baa0 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -801,29 +801,6 @@ function updateNetSuiteApprovalAccount(policyID: string, value: string, oldValue API.write(WRITE_COMMANDS.UPDATE_NETSUITE_APPROVAL_ACCOUNT, parameters, onyxData); } -function getNetSuiteCustomFormIDOptionsParamsAndCommand(policyID: string, isReimbursable: boolean, expenseType: ValueOf, value: string) { - if (isReimbursable) { - return { - command: WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE, - parameters: { - policyID, - reimbursable: JSON.stringify({ - [expenseType]: value, - }), - }, - }; - } - return { - command: WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE, - parameters: { - policyID, - nonreimbursable: JSON.stringify({ - [expenseType]: value, - }), - }, - }; -} - function updateNetSuiteCustomFormIDOptions( policyID: string, value: string, @@ -842,8 +819,13 @@ function updateNetSuiteCustomFormIDOptions( }; const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS, data, oldData); - const {command, parameters} = getNetSuiteCustomFormIDOptionsParamsAndCommand(policyID, isReimbursable, CONST.NETSUITE_MAP_EXPORT_DESTINATION[exportDestination], value); - API.write(command, parameters, onyxData); + const commandName = isReimbursable ? WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE : WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE; + const parameters = { + policyID, + formType: CONST.NETSUITE_MAP_EXPORT_DESTINATION[exportDestination], + formID: value, + }; + API.write(commandName, parameters, onyxData); } export { From bea76a844c47191e8597b7bc1b297ae34a701cf3 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 5 Jul 2024 22:54:58 +0530 Subject: [PATCH 05/12] Fixing style of form --- .../accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx index ce89a2daac76..5b62674f9c9b 100644 --- a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx @@ -49,9 +49,10 @@ function NetSuiteCustomFormIDPage({policy}: WithPolicyConnectionsProps) { accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]} policyID={policyID} featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED} - contentContainerStyle={styles.pb2} + contentContainerStyle={[styles.flex1]} titleStyle={styles.ph5} connectionName={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} + shouldIncludeSafeAreaPaddingBottom > Date: Fri, 5 Jul 2024 23:14:43 +0530 Subject: [PATCH 06/12] Fixing style --- src/components/ConnectionLayout.tsx | 6 +++++- .../netsuite/advanced/NetSuiteCustomFormIDPage.tsx | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/ConnectionLayout.tsx b/src/components/ConnectionLayout.tsx index dc8638f018d4..4bcfdc61077f 100644 --- a/src/components/ConnectionLayout.tsx +++ b/src/components/ConnectionLayout.tsx @@ -66,6 +66,9 @@ type ConnectionLayoutProps = { /** Handler for back button press */ onBackButtonPress?: () => void; + + /** Whether or not to block user from accessing the page */ + shouldBeBlocked?: boolean; }; type ConnectionLayoutContentProps = Pick; @@ -99,6 +102,7 @@ function ConnectionLayout({ titleAlreadyTranslated, reverseConnectionEmptyCheck = false, onBackButtonPress = () => Navigation.goBack(), + shouldBeBlocked = false, }: ConnectionLayoutProps) { const {translate} = useLocalize(); @@ -123,7 +127,7 @@ function ConnectionLayout({ policyID={policyID} accessVariants={accessVariants} featureName={featureName} - shouldBeBlocked={reverseConnectionEmptyCheck ? !isConnectionEmpty : isConnectionEmpty} + shouldBeBlocked={(reverseConnectionEmptyCheck ? !isConnectionEmpty : isConnectionEmpty) || shouldBeBlocked} > Date: Sun, 7 Jul 2024 19:45:47 +0530 Subject: [PATCH 07/12] Addressing comments --- src/CONST.ts | 11 +++++++++++ src/libs/PolicyUtils.ts | 10 +++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 051ff1b71ae5..8803895c4072 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1456,6 +1456,17 @@ const CONST = { JOURNALS_APPROVED: 'JOURNALS_APPROVED', }, + NETSUITE_ACCOUNT_TYPE: { + ACCOUNTS_PAYABLE: '_accountsPayable', + ACCOUNTS_RECEIVABLE: '_accountsReceivable', + OTHER_CURRENT_LIABILITY: '_otherCurrentLiability', + CREDIT_CARD: '_creditCard', + BANK: '_bank', + OTHER_CURRENT_ASSET: '_otherCurrentAsset', + LONG_TERM_LIABILITY: '_longTermLiability', + EXPENSE: '_expense', + }, + NETSUITE_APPROVAL_ACCOUNT_DEFAULT: 'APPROVAL_ACCOUNT_DEFAULT', /** diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 392ac1156c92..396df0143e83 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -541,8 +541,8 @@ function canUseProvincialTaxNetSuite(subsidiaryCountry?: string) { function getNetSuiteReimbursableAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; - let accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === '_bank'); - accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === '_creditCard')); + let accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK); + accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.CREDIT_CARD)); return accountsToConsider.map(({id, name}) => ({ value: id, @@ -554,7 +554,7 @@ function getNetSuiteReimbursableAccountOptions(policy: Policy | undefined, selec function getNetSuiteCollectionAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; - const accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === '_bank'); + const accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK); return accountsToConsider.map(({id, name}) => ({ value: id, @@ -569,10 +569,10 @@ function getNetSuiteApprovalAccountOptions(policy: Policy | undefined, selectedB const defaultApprovalAccount: NetSuiteAccount = { id: CONST.NETSUITE_APPROVAL_ACCOUNT_DEFAULT, name: Localize.translateLocal('workspace.netsuite.advancedConfig.defaultApprovalAccount'), - type: '_accountsPayable', + type: CONST.NETSUITE_ACCOUNT_TYPE.ACCOUNTS_PAYABLE, }; let accountsToConsider = [defaultApprovalAccount]; - accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === '_accountsPayable')); + accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.ACCOUNTS_PAYABLE)); return accountsToConsider.map(({id, name}) => ({ value: id, From 7adc3bc7ff46fff8ffc2f2c71b2b8e78e0f99de3 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 7 Jul 2024 21:28:49 +0530 Subject: [PATCH 08/12] Adding validation to custom form ID --- src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ .../advanced/NetSuiteCustomFormIDPage.tsx | 18 +++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 477db7b95af6..3ebfe44c7d41 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2338,6 +2338,9 @@ export default { [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED]: 'Approved for posting', }, }, + error: { + customFormID: 'Please enter a valid numeric custom form ID.', + }, }, noAccountsFound: 'No accounts found', noAccountsFoundDescription: 'Add the account in NetSuite and sync the connection again.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6dae9b8068a3..e3f72946519d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2379,6 +2379,9 @@ export default { [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED]: 'Aprobado para publicación', }, }, + error: { + customFormID: 'Please enter a valid numeric custom form ID.', + }, }, noAccountsFound: 'No se han encontrado cuentas', noAccountsFoundDescription: 'Añade la cuenta en NetSuite y sincroniza la conexión de nuevo.', diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx index 7b0aa82f5c84..2400e6497d63 100644 --- a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx @@ -4,12 +4,14 @@ import {View} from 'react-native'; import ConnectionLayout from '@components/ConnectionLayout'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormOnyxValues} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Connections from '@libs/actions/connections/NetSuiteCommands'; +import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as ValidationUtils from '@libs/ValidationUtils'; import type {ExpenseRouteParams} from '@pages/workspace/accounting/netsuite/types'; import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; import withPolicyConnections from '@pages/workspace/withPolicyConnections'; @@ -31,6 +33,19 @@ function NetSuiteCustomFormIDPage({policy}: WithPolicyConnectionsProps) { (isReimbursable ? config?.reimbursableExpensesExportDestination : config?.nonreimbursableExpensesExportDestination) ?? CONST.NETSUITE_EXPORT_DESTINATION.EXPENSE_REPORT; const customFormIDKey = isReimbursable ? 'reimbursable' : 'nonReimbursable'; + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + + if (values[params.expenseType] && !ValidationUtils.isNumeric(values[params.expenseType])) { + ErrorUtils.addErrorMessage(errors, params.expenseType, translate('workspace.netsuite.advancedConfig.error.customFormID')); + } + + return errors; + }, + [params.expenseType, translate], + ); + const updateCustomFormID = useCallback( (formValues: FormOnyxValues) => { if (config?.customFormIDOptions?.[customFormIDKey]?.[CONST.NETSUITE_MAP_EXPORT_DESTINATION[exportDestination]] !== formValues[params.expenseType]) { @@ -59,6 +74,7 @@ function NetSuiteCustomFormIDPage({policy}: WithPolicyConnectionsProps) { Date: Sun, 7 Jul 2024 21:43:34 +0530 Subject: [PATCH 09/12] Fixing API call --- src/libs/actions/connections/NetSuiteCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index 1858a8b9baa0..d9a32bf43911 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -740,7 +740,7 @@ function updateNetSuiteReimbursementAccountID(policyID: string, bankAccountID: s } function updateNetSuiteCollectionAccount(policyID: string, bankAccountID: string, oldBankAccountID?: string) { - const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.REIMBURSEMENT_ACCOUNT_ID, bankAccountID, oldBankAccountID); + const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.COLLECTION_ACCOUNT, bankAccountID, oldBankAccountID); const parameters = { policyID, From 596e569b43fd875d90cb7bb62450d4d7706079a8 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:50:15 +0530 Subject: [PATCH 10/12] Apply suggestions from code review Co-authored-by: Yuwen Memon --- src/languages/es.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index e3f72946519d..05689037d018 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2334,12 +2334,12 @@ export default { reimbursedReportsDescription: 'Cada vez que se pague un informe utilizando Expensify ACH, se creará el correspondiente pago de la factura en la cuenta de NetSuite indicadas a continuación.', reimbursementsAccount: 'Cuenta de reembolsos', - reimbursementsAccountDescription: "Choose the bank account you'll use for reimbursements, and we'll create the associated payment in NetSuite.", + reimbursementsAccountDescription: 'Elija la cuenta bancaria que utilizará para los reembolsos y crearemos el pago asociado en NetSuite.', collectionsAccount: 'Cuenta de cobros', - collectionsAccountDescription: 'Once an invoice is marked as paid in Expensify and exported to NetSuite, it’ll appear against the account below.', + collectionsAccountDescription: 'Una vez que una factura se marca como pagada en Expensify y se exporta a NetSuite, aparecerá contra la cuenta de abajo.', approvalAccount: 'Cuenta de aprobación de cuentas por pagar', approvalAccountDescription: - 'Choose the account that transactions will be approved against in NetSuite. If you’re syncing reimbursed reports, this is also the account that bill payments will be created against.', + 'Elija la cuenta con la que se aprobarán las transacciones en NetSuite. Si está sincronizando informes reembolsados, esta es también la cuenta con la que se crearán los pagos de facturas.', defaultApprovalAccount: 'Preferencia predeterminada de NetSuite', inviteEmployees: 'Invitar empleados y establecer aprobaciones', inviteEmployeesDescription: @@ -2353,7 +2353,7 @@ export default { customFormIDNonReimbursable: 'Gasto no reembolsable', exportReportsTo: { label: 'Nivel de aprobación del informe de gastos', - description: 'Once an expense report is approved in Expensify and exported to NetSuite, you can set an additional level of approval in NetSuite prior to posting.', + description: 'Una vez aprobado un informe de gastos en Expensify y exportado a NetSuite, puede establecer un nivel adicional de aprobación en NetSuite antes de su contabilización.', values: { [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_SUPERVISOR_APPROVED]: 'Solo aprobado por el supervisor', @@ -2363,7 +2363,7 @@ export default { }, exportVendorBillsTo: { label: 'Nivel de aprobación de facturas de proveedores', - 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.', + description: 'Una vez aprobada una factura de proveedor en Expensify y exportada a NetSuite, puede establecer un nivel adicional de aprobación en NetSuite antes de su contabilización.', values: { [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVAL_PENDING]: 'Aprobación pendiente', @@ -2372,7 +2372,7 @@ export default { }, exportJournalsTo: { label: 'Nivel de aprobación de asientos contables', - description: 'Once a journal entry is approved in Expensify and exported to NetSuite, you can set an additional level of approval in NetSuite prior to posting.', + description: 'Una vez aprobado un asiento en Expensify y exportado a NetSuite, puede establecer un nivel adicional de aprobación en NetSuite antes de contabilizarlo.', values: { [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVAL_PENDING]: 'Aprobación pendiente', @@ -2380,7 +2380,7 @@ export default { }, }, error: { - customFormID: 'Please enter a valid numeric custom form ID.', + customFormID: 'Introduzca un ID numérico válido para el formulario personalizado.', }, }, noAccountsFound: 'No se han encontrado cuentas', From 11b56c49f04ee6f24a55423e9371731a2b489f70 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 8 Jul 2024 22:43:33 +0530 Subject: [PATCH 11/12] Applying suggestions --- src/CONST.ts | 4 ++++ src/languages/es.ts | 6 ++++-- src/libs/PolicyUtils.ts | 15 +++++++-------- src/libs/actions/connections/NetSuiteCommands.ts | 2 +- .../advanced/NetSuiteCustomFormIDPage.tsx | 2 +- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8803895c4072..9c7754dcce4d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1388,6 +1388,10 @@ const CONST = { 4: 'enterCredentials', }, IMPORT_CUSTOM_FIELDS: ['customSegments', 'customLists'], + CUSTOM_FORM_ID_TYPE: { + REIMBURSABLE: 'reimbursable', + NON_REIMBURSABLE: 'nonReimbursable', + }, SYNC_OPTIONS: { SYNC_REIMBURSED_REPORTS: 'syncReimbursedReports', SYNC_PEOPLE: 'syncPeople', diff --git a/src/languages/es.ts b/src/languages/es.ts index 05689037d018..c5c9a802d001 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2353,7 +2353,8 @@ export default { customFormIDNonReimbursable: 'Gasto no reembolsable', exportReportsTo: { label: 'Nivel de aprobación del informe de gastos', - description: 'Una vez aprobado un informe de gastos en Expensify y exportado a NetSuite, puede establecer un nivel adicional de aprobación en NetSuite antes de su contabilización.', + description: + 'Una vez aprobado un informe de gastos en Expensify y exportado a NetSuite, puede establecer un nivel adicional de aprobación en NetSuite antes de su contabilización.', values: { [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_SUPERVISOR_APPROVED]: 'Solo aprobado por el supervisor', @@ -2363,7 +2364,8 @@ export default { }, exportVendorBillsTo: { label: 'Nivel de aprobación de facturas de proveedores', - description: 'Una vez aprobada una factura de proveedor en Expensify y exportada a NetSuite, puede establecer un nivel adicional de aprobación en NetSuite antes de su contabilización.', + description: + 'Una vez aprobada una factura de proveedor en Expensify y exportada a NetSuite, puede establecer un nivel adicional de aprobación en NetSuite antes de su contabilización.', values: { [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVAL_PENDING]: 'Aprobación pendiente', diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 396df0143e83..f967d6a971c7 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -541,10 +541,10 @@ function canUseProvincialTaxNetSuite(subsidiaryCountry?: string) { function getNetSuiteReimbursableAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; - let accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK); - accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.CREDIT_CARD)); + let accountOptions = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK); + accountOptions = accountOptions.concat((payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.CREDIT_CARD)); - return accountsToConsider.map(({id, name}) => ({ + return accountOptions.map(({id, name}) => ({ value: id, text: name, keyForList: id, @@ -554,9 +554,9 @@ function getNetSuiteReimbursableAccountOptions(policy: Policy | undefined, selec function getNetSuiteCollectionAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; - const accountsToConsider = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK); + const accountOptions = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK); - return accountsToConsider.map(({id, name}) => ({ + return accountOptions.map(({id, name}) => ({ value: id, text: name, keyForList: id, @@ -571,10 +571,9 @@ function getNetSuiteApprovalAccountOptions(policy: Policy | undefined, selectedB name: Localize.translateLocal('workspace.netsuite.advancedConfig.defaultApprovalAccount'), type: CONST.NETSUITE_ACCOUNT_TYPE.ACCOUNTS_PAYABLE, }; - let accountsToConsider = [defaultApprovalAccount]; - accountsToConsider = accountsToConsider.concat((payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.ACCOUNTS_PAYABLE)); + const accountOptions = [defaultApprovalAccount].concat((payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.ACCOUNTS_PAYABLE)); - return accountsToConsider.map(({id, name}) => ({ + return accountOptions.map(({id, name}) => ({ value: id, text: name, keyForList: id, diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index d9a32bf43911..66bf482d6ef2 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -808,7 +808,7 @@ function updateNetSuiteCustomFormIDOptions( exportDestination: ValueOf, oldCustomFormID?: NetSuiteCustomFormID, ) { - const customFormIDKey = isReimbursable ? 'reimbursable' : 'nonReimbursable'; + const customFormIDKey = isReimbursable ? CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_TYPE.REIMBURSABLE : CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_TYPE.NON_REIMBURSABLE; const data = { [customFormIDKey]: { [CONST.NETSUITE_MAP_EXPORT_DESTINATION[exportDestination]]: value, diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx index 2400e6497d63..696d9ec3e0c9 100644 --- a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx @@ -31,7 +31,7 @@ function NetSuiteCustomFormIDPage({policy}: WithPolicyConnectionsProps) { const exportDestination = (isReimbursable ? config?.reimbursableExpensesExportDestination : config?.nonreimbursableExpensesExportDestination) ?? CONST.NETSUITE_EXPORT_DESTINATION.EXPENSE_REPORT; - const customFormIDKey = isReimbursable ? 'reimbursable' : 'nonReimbursable'; + const customFormIDKey = isReimbursable ? CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_TYPE.REIMBURSABLE : CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_TYPE.NON_REIMBURSABLE; const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { From 0c68e3840acee53dc49fafeaf4511665d313ff9d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 8 Jul 2024 23:00:52 +0530 Subject: [PATCH 12/12] Applying suggestions --- src/libs/PolicyUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index f967d6a971c7..fc9a04e2507c 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -541,8 +541,7 @@ function canUseProvincialTaxNetSuite(subsidiaryCountry?: string) { function getNetSuiteReimbursableAccountOptions(policy: Policy | undefined, selectedBankAccountId: string | undefined): SelectorType[] { const payableAccounts = policy?.connections?.netsuite.options.data.payableList ?? []; - let accountOptions = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK); - accountOptions = accountOptions.concat((payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.CREDIT_CARD)); + const accountOptions = (payableAccounts ?? []).filter(({type}) => type === CONST.NETSUITE_ACCOUNT_TYPE.BANK || type === CONST.NETSUITE_ACCOUNT_TYPE.CREDIT_CARD); return accountOptions.map(({id, name}) => ({ value: id,