diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ab1332db0b2d..49deee5d0731 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -828,6 +828,18 @@ const ONYXKEYS = { /** The selected accounting integration bank account ID for card reconciliation */ EXPENSIFY_CARD_RECONCILIATION_BANK_ACCOUNT_ID: 'expensifyCard_bankAccount_', + /** Stores which connection is set up to use Travel Invoicing Continuous Reconciliation */ + TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION: 'travelInvoicing_continuousReconciliationConnection_', + + /** The value that indicates whether Travel Invoicing Continuous Reconciliation should be used on the workspace */ + TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION: 'travelInvoicing_useContinuousReconciliation_', + + /** Pending action for Travel Invoicing Continuous Reconciliation enabled status */ + TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION: 'travelInvoicing_useContinuousReconciliationPendingAction_', + + /** The selected accounting integration bank account ID for Travel Invoicing reconciliation */ + TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID: 'travelInvoicing_reconciliationBankAccount_', + /** Currently displaying feed */ LAST_SELECTED_FEED: 'lastSelectedFeed_', @@ -1344,6 +1356,10 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean | string; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION]: OnyxTypes.CardContinuousReconciliation; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_RECONCILIATION_BANK_ACCOUNT_ID]: string; + [ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName; + [ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION]: boolean; + [ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION]: OnyxTypes.CardContinuousReconciliation; + [ONYXKEYS.COLLECTION.TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID]: string; [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeedWithDomainID; [ONYXKEYS.COLLECTION.LAST_SELECTED_EXPENSIFY_CARD_FEED]: OnyxTypes.FundID; [ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST]: OnyxTypes.CardOnWaitlist; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index f585920b058f..2804e45c7e15 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -333,7 +333,8 @@ const DYNAMIC_ROUTES = { }, WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS: { path: 'account-reconciliation-settings', - entryScreens: [SCREENS.WORKSPACE.ACCOUNTING.CARD_RECONCILIATION, SCREENS.WORKSPACE.DYNAMIC_WORKSPACE_EXPENSIFY_CARD_SETTINGS_ACCOUNT], + entryScreens: [SCREENS.WORKSPACE.ACCOUNTING.CARD_RECONCILIATION, SCREENS.WORKSPACE.DYNAMIC_WORKSPACE_EXPENSIFY_CARD_SETTINGS_ACCOUNT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED], + queryParams: ['connection', 'reconciliationAccountSettingsType'], }, ADDRESS_COUNTRY: { path: 'country', diff --git a/src/languages/de.ts b/src/languages/de.ts index d0ec1b4109b5..43e67c62e567 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6384,7 +6384,11 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU chooseBankAccount: 'Wählen Sie das Bankkonto aus, mit dem Ihre Zahlungen mit der Expensify Karte abgeglichen werden.', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `Stellen Sie sicher, dass dieses Konto mit Ihrem Expensify Karte-Abrechnungskonto (endend auf ${lastFourPAN}) übereinstimmt, damit die fortlaufende Abstimmung richtig funktioniert.`, + chooseTravelInvoicingBankAccount: 'Wählen Sie das Bankkonto aus, mit dem Ihre Zahlungen aus der Reiseabrechnung abgeglichen werden.', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Stellen Sie sicher, dass dieses Konto mit Ihrem Abrechnungskonto für Reiseabrechnungen (endet auf ${lastFourPAN}) übereinstimmt, damit die kontinuierliche Abstimmung ordnungsgemäß funktioniert.`, }, + syncTravelInvoicingSettlements: 'Reiseabrechnungs­abgleiche synchronisieren', }, export: { notReadyHeading: 'Nicht bereit zum Export', diff --git a/src/languages/en.ts b/src/languages/en.ts index bc1f220eb6d4..1a3296a09169 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6401,14 +6401,18 @@ const translations = { cardReconciliation: 'Card reconciliation', reconciliationAccount: 'Reconciliation account', continuousReconciliation: 'Continuous Reconciliation', + syncTravelInvoicingSettlements: 'Sync travel invoicing settlements', saveHoursOnReconciliation: 'Save hours on reconciliation each accounting period by having Expensify continuously reconcile Expensify Card statements and settlements on your behalf.', enableContinuousReconciliation: (accountingAdvancedSettingsLink: string, connectionName: string) => `In order to enable Continuous Reconciliation, please enable auto-sync for ${connectionName}.`, chooseReconciliationAccount: { chooseBankAccount: 'Choose the bank account that your Expensify Card payments will be reconciled against.', + chooseTravelInvoicingBankAccount: 'Choose the bank account that your travel invoicing payments will be reconciled against.', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `Make sure this account matches your Expensify Card settlement account (ending in ${lastFourPAN}) so Continuous Reconciliation works properly.`, + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Make sure this account matches your travel invoicing settlement account (ending in ${lastFourPAN}) so Continuous Reconciliation works properly.`, }, }, hr: { diff --git a/src/languages/es.ts b/src/languages/es.ts index b0f575184446..7734add9f0b7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6167,7 +6167,11 @@ ${amount} para ${merchant} - ${date}`, chooseBankAccount: 'Elige la cuenta bancaria con la que se conciliarán los pagos de tu Tarjeta Expensify.', settlementAccountReconciliation: (settlementAccountUrl, lastFourPAN) => `Asegúrate de que esta cuenta coincide con la cuenta de liquidación de tu Tarjeta Expensify (que termina en ${lastFourPAN}) para que la conciliación continua funcione correctamente.`, + chooseTravelInvoicingBankAccount: 'Elige la cuenta bancaria con la que se reconciliarán los pagos de tus facturas de viaje.', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Asegúrate de que esta cuenta coincida con tu cuenta de liquidación de facturas de viaje (que termina en ${lastFourPAN}) para que la conciliación continua funcione correctamente.`, }, + syncTravelInvoicingSettlements: 'Sincronizar liquidaciones de facturación de viajes', }, card: { issueCard: 'Emitir tarjeta', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 9f9ca8872ac8..f868e47d5217 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6406,7 +6406,11 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. chooseBankAccount: 'Choisissez le compte bancaire avec lequel les paiements de votre Carte Expensify seront rapprochés.', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `Assurez-vous que ce compte correspond à votre compte de règlement Carte Expensify (se terminant par ${lastFourPAN}) afin que la réconciliation continue fonctionne correctement.`, + chooseTravelInvoicingBankAccount: 'Choisissez le compte bancaire sur lequel les paiements de facturation de voyage seront rapprochés.', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Assurez-vous que ce compte correspond à votre compte de règlement de facturation de voyage (se terminant par ${lastFourPAN}) afin que le rapprochement continu fonctionne correctement.`, }, + syncTravelInvoicingSettlements: 'Synchroniser les règlements de facturation de voyage', }, export: { notReadyHeading: 'Pas prêt à être exporté', diff --git a/src/languages/it.ts b/src/languages/it.ts index bc2372f91dab..7a964999b879 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6374,7 +6374,11 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. chooseBankAccount: 'Scegli il conto bancario con cui verranno riconciliati i pagamenti della tua Carta Expensify.', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `Assicurati che questo conto corrisponda al tuo conto di regolamento della Carta Expensify (con finale ${lastFourPAN}) affinché la Riconciliazione continua funzioni correttamente.`, + chooseTravelInvoicingBankAccount: 'Scegli il conto bancario su cui verranno riconciliati i pagamenti della fatturazione di viaggio.', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Assicurati che questo conto corrisponda al tuo conto di regolamento per la fatturazione dei viaggi (che termina con ${lastFourPAN}) in modo che la Riconciliazione continua funzioni correttamente.`, }, + syncTravelInvoicingSettlements: 'Sincronizza le liquidazioni delle fatture di viaggio', }, export: { notReadyHeading: 'Non pronto per l’esportazione', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index ce66751429fb..a3468e0f8f2b 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6303,7 +6303,11 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO chooseBankAccount: 'Expensify カードの支払いを照合する銀行口座を選択してください。', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `継続消込が正しく機能するように、この口座が、末尾が ${lastFourPAN} のExpensify カード精算口座と一致していることを確認してください。`, + chooseTravelInvoicingBankAccount: '出張請求の支払いの消込に使用する銀行口座を選択してください。', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Continuous Reconciliation が正しく機能するように、この口座が、旅行の請求書決済用口座(末尾が ${lastFourPAN} の口座)と一致していることを確認してください。`, }, + syncTravelInvoicingSettlements: '出張請求の精算を同期', }, export: { notReadyHeading: 'エクスポートの準備ができていません', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index f49b1a731a59..92104dc6f956 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6352,7 +6352,11 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ chooseBankAccount: 'Kies de bankrekening waarop de betalingen met je Expensify Kaart worden afgestemd.', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `Zorg ervoor dat deze rekening overeenkomt met je Expensify Kaart-afwikkelingsrekening (eindigend op ${lastFourPAN}), zodat Continue Afstemming goed werkt.`, + chooseTravelInvoicingBankAccount: 'Kies de bankrekening waarop de betalingen van je reiskostenfacturen worden afgeletterd.', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Zorg ervoor dat deze rekening overeenkomt met je afwikkelingsrekening voor reiskostenfacturatie (die eindigt op ${lastFourPAN}), zodat Continue Afstemming goed werkt.`, }, + syncTravelInvoicingSettlements: 'Reisfactureringsafrekeningen synchroniseren', }, export: { notReadyHeading: 'Niet klaar om te exporteren', @@ -6903,10 +6907,10 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar title: 'Expensify Kaarten bieden altijd ingebouwde bescherming', description: `Expensify weigert deze uitgaven altijd: - • Services voor volwassenen - • Geldautomaten (ATM's) - • Gokken - • Geldoverschrijvingen + • Services voor volwassenen + • Geldautomaten (ATM's) + • Gokken + • Geldoverschrijvingen er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, }, addSpendRule: 'Uitgaveregel toevoegen', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 7494a45ab943..51df414c80e4 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6345,7 +6345,11 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy chooseBankAccount: 'Wybierz konto bankowe, do którego będą uzgadniane płatności kartą Karta Expensify.', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `Upewnij się, że to konto jest takie samo jak twoje konto rozliczeniowe Karty Expensify (kończące się na ${lastFourPAN}), aby Ciągłe Uzgadnianie działało poprawnie.`, + chooseTravelInvoicingBankAccount: 'Wybierz konto bankowe, z którym będą uzgadniane płatności za faktury podróżne.', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Upewnij się, że to konto jest takie samo jak konto rozliczeniowe do fakturowania podróży (kończące się na ${lastFourPAN}), żeby Ciągłe Uzgadnianie działało poprawnie.`, }, + syncTravelInvoicingSettlements: 'Synchronizuj rozliczenia faktur podróżnych', }, export: { notReadyHeading: 'Niegotowe do eksportu', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 62cd43e552f4..53ff031ebabd 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6353,7 +6353,11 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS chooseBankAccount: 'Escolha a conta bancária na qual os pagamentos do seu Cartão Expensify serão conciliados.', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `Certifique-se de que esta conta corresponda à sua conta de liquidação do Cartão Expensify (terminada em ${lastFourPAN}) para que a Reconciliação Contínua funcione corretamente.`, + chooseTravelInvoicingBankAccount: 'Escolha a conta bancária na qual os pagamentos de faturamento de viagem serão conciliados.', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => + `Certifique-se de que esta conta corresponda à sua conta de liquidação de faturamento de viagem (terminada em ${lastFourPAN}) para que a Conciliação Contínua funcione corretamente.`, }, + syncTravelInvoicingSettlements: 'Sincronizar liquidações de faturamento de viagens', }, export: { notReadyHeading: 'Não está pronto para exportar', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 131fdd725408..5d5f6455c3e5 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -6198,7 +6198,10 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM chooseBankAccount: '选择用于对账 Expensify 卡付款的银行账户。', settlementAccountReconciliation: (settlementAccountUrl: string, lastFourPAN: string) => `请确保此账户与您的Expensify 卡结算账户(末尾为 ${lastFourPAN})一致,以便持续对账功能正常运行。`, + chooseTravelInvoicingBankAccount: '选择用于核对差旅开票付款的银行账户。', + travelInvoicingSettlementAccountReconciliation: (lastFourPAN: string) => `请确保此账户与您的差旅发票结算账户(以 ${lastFourPAN} 结尾)一致,以确保持续对账功能正常运行。`, }, + syncTravelInvoicingSettlements: '同步差旅开票结算', }, export: { notReadyHeading: '尚未准备好导出', diff --git a/src/libs/API/parameters/SetTravelInvoicingReconciliationBankAccountParams.ts b/src/libs/API/parameters/SetTravelInvoicingReconciliationBankAccountParams.ts new file mode 100644 index 000000000000..d24a4567cab1 --- /dev/null +++ b/src/libs/API/parameters/SetTravelInvoicingReconciliationBankAccountParams.ts @@ -0,0 +1,6 @@ +type SetTravelInvoicingReconciliationBankAccountParams = { + domainName: string; + travelInvoicingReconciliationBankAccountID: string; +}; + +export default SetTravelInvoicingReconciliationBankAccountParams; diff --git a/src/libs/API/parameters/ToggleTravelInvoicingContinuousReconciliationParams.ts b/src/libs/API/parameters/ToggleTravelInvoicingContinuousReconciliationParams.ts new file mode 100644 index 000000000000..634c9f986192 --- /dev/null +++ b/src/libs/API/parameters/ToggleTravelInvoicingContinuousReconciliationParams.ts @@ -0,0 +1,7 @@ +type ToggleTravelInvoicingContinuousReconciliationParams = { + policyAccountID: number; + shouldUseContinuousReconciliation: boolean; + travelInvoicingContinuousReconciliationConnection?: string; +}; + +export default ToggleTravelInvoicingContinuousReconciliationParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 3f4d14b75143..5da12b9d1915 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -409,7 +409,9 @@ export type {default as UpdateCardSettlementAccountParams} from './UpdateCardSet export type {default as ConfigureTravelInvoicingForPolicyParams} from './ConfigureTravelInvoicingForPolicyParams'; export type {default as DeactivateTravelInvoicingParams} from './DeactivateTravelInvoicingParams'; export type {default as SetTravelInvoicingSettlementAccountParams} from './SetTravelInvoicingSettlementAccountParams'; +export type {default as SetTravelInvoicingReconciliationBankAccountParams} from './SetTravelInvoicingReconciliationBankAccountParams'; export type {default as PayTravelInvoicingSpendParams} from './PayTravelInvoicingSpendParams'; +export type {default as ToggleTravelInvoicingContinuousReconciliationParams} from './ToggleTravelInvoicingContinuousReconciliationParams'; export type {default as UpdateTravelInvoicingMonthlyLimitParams} from './UpdateTravelInvoicingMonthlyLimitParams'; export type {default as UpdateTravelInvoicingSettlementFrequencyParams} from './UpdateTravelInvoicingSettlementFrequencyParams'; export type {default as RetryTravelCardsProvisioningParams} from './RetryTravelCardsProvisioningParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 62d5656211fa..c0f93e7ed5f7 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -510,6 +510,8 @@ const WRITE_COMMANDS = { CONFIGURE_TRAVEL_INVOICING_FOR_POLICY: 'ConfigureTravelInvoicingForPolicy', DEACTIVATE_TRAVEL_INVOICING: 'DeactivateTravelInvoicing', SET_TRAVEL_INVOICING_SETTLEMENT_ACCOUNT: 'SetTravelInvoicingSettlementAccount', + SET_TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT: 'SetTravelInvoicingReconciliationBankAccount', + TOGGLE_TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION: 'ToggleTravelInvoicingContinuousReconciliation', UPDATE_TRAVEL_INVOICE_SETTLEMENT_FREQUENCY: 'UpdateTravelInvoiceSettlementFrequency', UPDATE_TRAVEL_INVOICING_MONTHLY_LIMIT: 'UpdateTravelInvoicingMonthlyLimit', PAY_TRAVEL_INVOICING_SPEND: 'PayTravelInvoicingSpend', @@ -1157,6 +1159,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CONFIGURE_TRAVEL_INVOICING_FOR_POLICY]: Parameters.ConfigureTravelInvoicingForPolicyParams; [WRITE_COMMANDS.DEACTIVATE_TRAVEL_INVOICING]: Parameters.DeactivateTravelInvoicingParams; [WRITE_COMMANDS.SET_TRAVEL_INVOICING_SETTLEMENT_ACCOUNT]: Parameters.SetTravelInvoicingSettlementAccountParams; + [WRITE_COMMANDS.SET_TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT]: Parameters.SetTravelInvoicingReconciliationBankAccountParams; + [WRITE_COMMANDS.TOGGLE_TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION]: Parameters.ToggleTravelInvoicingContinuousReconciliationParams; [WRITE_COMMANDS.UPDATE_TRAVEL_INVOICE_SETTLEMENT_FREQUENCY]: Parameters.UpdateTravelInvoicingSettlementFrequencyParams; [WRITE_COMMANDS.UPDATE_TRAVEL_INVOICING_MONTHLY_LIMIT]: Parameters.UpdateTravelInvoicingMonthlyLimitParams; [WRITE_COMMANDS.PAY_TRAVEL_INVOICING_SPEND]: Parameters.PayTravelInvoicingSpendParams; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index d8012d8938d5..d064c348ed35 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -9,6 +9,7 @@ import type {SaveSearchParams} from '@libs/API/parameters'; import type {ReimbursementAccountStepToOpen} from '@libs/ReimbursementAccountUtils'; import type {AvatarSource} from '@libs/UserAvatarUtils'; import type {AttachmentModalContainerModalProps} from '@pages/media/AttachmentModalScreen/types'; +import type RECONCILIATION_ACCOUNT_SETTINGS_TYPE from '@pages/workspace/accounting/reconciliation/constants'; import type CONST from '@src/CONST'; import type {Country, IOUAction, IOUType, OdometerImageType} from '@src/CONST'; import type NAVIGATORS from '@src/NAVIGATORS'; @@ -1151,6 +1152,7 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.ACCOUNTING.DYNAMIC_RECONCILIATION_ACCOUNT_SETTINGS]: { policyID: string; connection: ValueOf; + reconciliationAccountSettingsType?: ValueOf; }; [SCREENS.TWO_FACTOR_AUTH.DISABLED]: undefined; [SCREENS.TWO_FACTOR_AUTH.DISABLE]: undefined; diff --git a/src/libs/actions/TravelInvoicing.ts b/src/libs/actions/TravelInvoicing.ts index 2397022fe544..111b0bf73cdf 100644 --- a/src/libs/actions/TravelInvoicing.ts +++ b/src/libs/actions/TravelInvoicing.ts @@ -9,7 +9,9 @@ import type { OpenPolicyTravelPageParams, PayTravelInvoicingSpendParams, RetryTravelCardsProvisioningParams, + SetTravelInvoicingReconciliationBankAccountParams, SetTravelInvoicingSettlementAccountParams, + ToggleTravelInvoicingContinuousReconciliationParams, UpdateTravelInvoicingMonthlyLimitParams, UpdateTravelInvoicingSettlementFrequencyParams, } from '@libs/API/parameters'; @@ -22,6 +24,7 @@ import enhanceParameters from '@libs/Network/enhanceParameters'; import {getTravelInvoicingCardSettingsKey} from '@libs/TravelInvoicingUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ConnectionName} from '@src/types/onyx/Policy'; /** * Opens the Travel page for a policy and fetches Travel Invoicing data. @@ -159,6 +162,120 @@ function setTravelInvoicingSettlementAccount(policyID: string, workspaceAccountI API.write(WRITE_COMMANDS.SET_TRAVEL_INVOICING_SETTLEMENT_ACCOUNT, params, {optimisticData, successData, failureData}); } +type TravelInvoicingContinuousReconciliationUpdate = OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION + | typeof ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION + | typeof ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION +>; + +function toggleTravelInvoicingContinuousReconciliation( + workspaceAccountID: number, + shouldUseContinuousReconciliation: boolean, + connectionName: ConnectionName, + oldConnectionName?: ConnectionName, +) { + const parameters: ToggleTravelInvoicingContinuousReconciliationParams = shouldUseContinuousReconciliation + ? { + policyAccountID: workspaceAccountID, + shouldUseContinuousReconciliation, + travelInvoicingContinuousReconciliationConnection: connectionName, + } + : { + policyAccountID: workspaceAccountID, + shouldUseContinuousReconciliation, + }; + + const optimisticData: TravelInvoicingContinuousReconciliationUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: shouldUseContinuousReconciliation, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION}${workspaceAccountID}`, + value: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: connectionName, + }, + ]; + + const successData: TravelInvoicingContinuousReconciliationUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: shouldUseContinuousReconciliation, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION}${workspaceAccountID}`, + value: null, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: connectionName, + }, + ]; + + const failureData: TravelInvoicingContinuousReconciliationUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: !shouldUseContinuousReconciliation, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION}${workspaceAccountID}`, + value: null, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: oldConnectionName ?? null, + }, + ]; + + API.write(WRITE_COMMANDS.TOGGLE_TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION, parameters, { + optimisticData, + successData, + failureData, + }); +} + +function setTravelInvoicingReconciliationBankAccount( + workspaceAccountID: number, + domainName: string, + travelInvoicingReconciliationBankAccountID: string, + currentReconciliationBankAccountID?: string, +) { + const parameters: SetTravelInvoicingReconciliationBankAccountParams = { + domainName, + travelInvoicingReconciliationBankAccountID, + }; + + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID}${workspaceAccountID}`, + value: travelInvoicingReconciliationBankAccountID, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID}${workspaceAccountID}`, + value: currentReconciliationBankAccountID ?? null, + }, + ]; + + API.write(WRITE_COMMANDS.SET_TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT, parameters, {optimisticData, failureData}); +} + /** * Clears any errors from the Travel Invoicing settlement account settings. * Also resets the paymentBankAccountID to the previous valid value (or null if none existed). @@ -630,6 +747,8 @@ export { exportTravelInvoiceStatementCSV, configureTravelInvoicingForPolicy, deactivateTravelInvoicing, + toggleTravelInvoicingContinuousReconciliation, + setTravelInvoicingReconciliationBankAccount, clearTravelInvoicingErrors, retryTravelCardsProvisioning, updateTravelInvoicingMonthlyLimit, diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 650919ddbe39..b81904a4f3fb 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -24,6 +24,8 @@ function removePolicyConnection(policy: Policy, connectionName: PolicyConnection | typeof ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS | typeof ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION | typeof ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION + | typeof ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION + | typeof ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION > > = [ { @@ -50,6 +52,16 @@ function removePolicyConnection(policy: Policy, connectionName: PolicyConnection key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, value: null, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: null, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: null, + }, ]; const successData: Array> = []; diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx index 9913c5931b71..2a8d72f8a987 100644 --- a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx @@ -7,6 +7,7 @@ import ConnectionLayout from '@components/ConnectionLayout'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import { updateNetSuiteAutoCreateEntities, @@ -16,6 +17,8 @@ import { updateNetSuiteSyncReimbursedReports, } from '@libs/actions/connections/NetSuiteCommands'; import {clearNetSuiteErrorField} from '@libs/actions/Policy/Policy'; +import {toggleTravelInvoicingContinuousReconciliation} from '@libs/actions/TravelInvoicing'; +import {getCardSettings, getConnectionBankAccountsForReconciliation} from '@libs/CardUtils'; import {getLatestErrorField} from '@libs/ErrorUtils'; import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import Navigation from '@libs/Navigation/Navigation'; @@ -26,6 +29,7 @@ import { getFilteredReimbursableAccountOptions, settingsPendingAction, } from '@libs/PolicyUtils'; +import {getIsTravelInvoicingEnabled} from '@libs/TravelInvoicingUtils'; import type {ExtendedMenuItemWithSubscribedSettings, MenuItemToRender} from '@pages/workspace/accounting/netsuite/types'; import { shouldHideCustomFormIDOptions, @@ -34,22 +38,36 @@ import { shouldHideReimbursedReportsSection, shouldHideReportsExportTo, } from '@pages/workspace/accounting/netsuite/utils'; +import RECONCILIATION_ACCOUNT_SETTINGS_TYPE from '@pages/workspace/accounting/reconciliation/constants'; import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; import withPolicyConnections from '@pages/workspace/withPolicyConnections'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id ?? CONST.DEFAULT_NUMBER_ID.toString(); + const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const config = policy?.connections?.netsuite?.options?.config; const autoSyncConfig = policy?.connections?.netsuite?.config; + const autoSync = !!autoSyncConfig?.autoSync?.enabled; const accountingMethod = policy?.connections?.netsuite?.options?.config?.accountingMethod; const {payableList} = policy?.connections?.netsuite?.options?.data ?? {}; + const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); + const travelSettings = getCardSettings(cardSettings, CONST.TRAVEL.PROGRAM_TRAVEL_US); + const isTravelInvoicingEnabled = getIsTravelInvoicingEnabled(travelSettings); + const [travelInvoicingContinuousReconciliation] = useOnyx(`${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`); + const [travelInvoicingContinuousReconciliationPendingAction] = useOnyx(`${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION}${workspaceAccountID}`); + const [travelInvoicingContinuousReconciliationConnection] = useOnyx(`${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`); + const [travelInvoicingReconciliationBankAccountID] = useOnyx(`${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID}${workspaceAccountID}`); + const travelInvoicingReconciliationBankAccount = getConnectionBankAccountsForReconciliation(policy?.connections, CONST.POLICY.CONNECTIONS.NAME.NETSUITE).find( + (account) => account.id === travelInvoicingReconciliationBankAccountID, + ); const shouldShowCustomFormIDOptions = useSharedValue(!shouldHideCustomFormIDOptions(config)); const shouldAnimateAccordionSection = useSharedValue(false); @@ -73,6 +91,14 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { return getFilteredApprovalAccountOptions(payableList).find(({id}) => id === config?.approvalAccount); }, [config?.approvalAccount, payableList, translate]); + const navigateToTravelInvoicingReconciliationAccountSettings = () => { + Navigation.navigate( + createDynamicRoute( + `${DYNAMIC_ROUTES.WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS.path}?connection=${CONST.POLICY.CONNECTIONS.ROUTE.NETSUITE}&reconciliationAccountSettingsType=${RECONCILIATION_ACCOUNT_SETTINGS_TYPE.TRAVEL_INVOICING}`, + ), + ); + }; + const renderDefaultMenuItem = (item: MenuItemToRender) => { return ( Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.NETSUITE_AUTO_SYNC.path)), hintText: (() => { - if (!autoSyncConfig?.autoSync?.enabled) { + if (!autoSync) { return undefined; } return translate( @@ -145,6 +171,33 @@ function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { key: 'divider2', shouldHide: shouldHideReimbursedReportsSection(config), }, + { + type: 'toggle', + title: translate('workspace.accounting.syncTravelInvoicingSettlements'), + isActive: !!travelInvoicingContinuousReconciliation, + switchAccessibilityLabel: translate('workspace.accounting.syncTravelInvoicingSettlements'), + disabled: !autoSync, + onToggle: (isEnabled) => { + toggleTravelInvoicingContinuousReconciliation(workspaceAccountID, isEnabled, CONST.POLICY.CONNECTIONS.NAME.NETSUITE, travelInvoicingContinuousReconciliationConnection); + if (isEnabled) { + navigateToTravelInvoicingReconciliationAccountSettings(); + } + }, + pendingAction: travelInvoicingContinuousReconciliationPendingAction, + shouldHide: !isTravelInvoicingEnabled, + }, + { + type: 'menuitem', + description: translate('workspace.accounting.reconciliationAccount'), + onPress: navigateToTravelInvoicingReconciliationAccountSettings, + title: travelInvoicingReconciliationBankAccount?.name, + shouldHide: !isTravelInvoicingEnabled || !travelInvoicingContinuousReconciliation, + }, + { + type: 'divider', + key: 'dividerTravelInvoicing', + shouldHide: !isTravelInvoicingEnabled, + }, { type: 'toggle', title: translate('workspace.netsuite.advancedConfig.inviteEmployees'), diff --git a/src/pages/workspace/accounting/reconciliation/DynamicReconciliationAccountSettingsPage.tsx b/src/pages/workspace/accounting/reconciliation/DynamicReconciliationAccountSettingsPage.tsx index e74de28d7928..c45550774e13 100644 --- a/src/pages/workspace/accounting/reconciliation/DynamicReconciliationAccountSettingsPage.tsx +++ b/src/pages/workspace/accounting/reconciliation/DynamicReconciliationAccountSettingsPage.tsx @@ -1,5 +1,6 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import ConnectionLayout from '@components/ConnectionLayout'; import RenderHTML from '@components/RenderHTML'; import SelectionList from '@components/SelectionList'; @@ -17,94 +18,204 @@ import {getCardProgramKey, getCardSettings, getConnectionBankAccountsForReconcil import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getDomainNameForPolicy} from '@libs/PolicyUtils'; +import {getTravelSettlementAccount} from '@libs/TravelInvoicingUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import {setCardReconciliationAccount} from '@userActions/Card'; +import {setTravelInvoicingReconciliationBankAccount} from '@userActions/TravelInvoicing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {DYNAMIC_ROUTES} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {BankAccountList, Policy} from '@src/types/onyx'; +import type {ConnectionName} from '@src/types/onyx/Policy'; +import RECONCILIATION_ACCOUNT_SETTINGS_TYPE from './constants'; type DynamicReconciliationAccountSettingsPageProps = PlatformStackScreenProps; +type ReconciliationAccountSettingsLayoutProps = { + policyID: string; + connectionName: ConnectionName; + connectionBankAccounts: ReturnType; + goBack: () => void; + description: string; + html: string; + selectedBankAccountID?: string; + onSelectBankAccount: (newBankAccountID?: string) => void; +}; -function DynamicReconciliationAccountSettingsPage({route}: DynamicReconciliationAccountSettingsPageProps) { - const {policyID, connection} = route.params; +type DynamicReconciliationProps = { + policyID: string; + workspaceAccountID: number; + domainName: string; + bankAccountList: OnyxEntry; + goBack: () => void; + connectionName: ConnectionName; + connectionBankAccounts: ReturnType; +}; + +const policyConnectionsSelector = (policy: OnyxEntry) => policy?.connections; +const policyWorkspaceAccountIDSelector = (policy: OnyxEntry) => policy?.workspaceAccountID; +function ReconciliationAccountSettingsLayout({ + policyID, + connectionName, + connectionBankAccounts, + goBack, + description, + html, + selectedBankAccountID, + onSelectBankAccount, +}: ReconciliationAccountSettingsLayoutProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); - const backPath = useDynamicBackPath(DYNAMIC_ROUTES.WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS.path); - const connectionName = getConnectionNameFromRouteParam(connection); + const options = connectionBankAccounts.map((bankAccount) => ({ + text: bankAccount.name, + value: bankAccount.id, + keyForList: bankAccount.id, + isSelected: bankAccount.id === selectedBankAccountID, + })); + + return ( + + {description} + + + + + onSelectBankAccount(value)} + ListItem={SingleSelectListItem} + initiallyFocusedItemKey={selectedBankAccountID} + /> + + ); +} + +function ExpensifyCardDynamicReconciliation({policyID, workspaceAccountID, domainName, bankAccountList, goBack, connectionName, connectionBankAccounts}: DynamicReconciliationProps) { + const {translate} = useLocalize(); const defaultFundID = useDefaultFundID(policyID); - const [connections] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {selector: (policy) => policy?.connections}); - const [workspaceAccountID = CONST.DEFAULT_NUMBER_ID] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {selector: (policy) => policy?.workspaceAccountID}); - const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${defaultFundID}`); const programKey = getCardProgramKey(cardSettings); const settings = getCardSettings(cardSettings, programKey); const paymentBankAccountID = settings?.paymentBankAccountID; const [reconciliationBankAccountID] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_RECONCILIATION_BANK_ACCOUNT_ID}${workspaceAccountID}`); - const selectedBankAccount = useMemo(() => bankAccountList?.[paymentBankAccountID?.toString() ?? ''], [paymentBankAccountID, bankAccountList]); - const bankAccountNumber = useMemo(() => selectedBankAccount?.accountData?.accountNumber ?? '', [selectedBankAccount?.accountData?.accountNumber]); + const selectedBankAccount = bankAccountList?.[paymentBankAccountID?.toString() ?? '']; + const bankAccountNumber = selectedBankAccount?.accountData?.accountNumber ?? ''; const settlementAccountEnding = getLastFourDigits(bankAccountNumber); - const domainName = settings?.domainName ?? getDomainNameForPolicy(policyID); + const reconciliationDomainName = settings?.domainName ?? domainName; const {environmentURL} = useEnvironment(); - const connectionBankAccounts = getConnectionBankAccountsForReconciliation(connections, connectionName); + const selectBankAccount = (newBankAccountID?: string) => { + if (!newBankAccountID) { + return; + } + setCardReconciliationAccount(workspaceAccountID, reconciliationDomainName, newBankAccountID, reconciliationBankAccountID); + goBack(); + }; - const goBack = useCallback(() => { - Navigation.goBack(backPath); - }, [backPath]); + return ( + + ); +} - const options = useMemo(() => { - return connectionBankAccounts.map((bankAccount) => ({ - text: bankAccount.name, - value: bankAccount.id, - keyForList: bankAccount.id, - isSelected: bankAccount.id === reconciliationBankAccountID, - })); - }, [connectionBankAccounts, reconciliationBankAccountID]); +function TravelInvoicingDynamicReconciliation({policyID, workspaceAccountID, domainName, bankAccountList, goBack, connectionName, connectionBankAccounts}: DynamicReconciliationProps) { + const {translate} = useLocalize(); + + const [travelInvoicingCardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); + const [travelInvoicingReconciliationBankAccountID] = useOnyx(`${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID}${workspaceAccountID}`); + const travelInvoicingSettings = getCardSettings(travelInvoicingCardSettings, CONST.TRAVEL.PROGRAM_TRAVEL_US); + const travelInvoicingSettlementAccount = getTravelSettlementAccount(travelInvoicingSettings, bankAccountList); + const settlementAccountEnding = travelInvoicingSettlementAccount?.last4 ?? ''; const selectBankAccount = (newBankAccountID?: string) => { if (!newBankAccountID) { return; } - setCardReconciliationAccount(workspaceAccountID, domainName, newBankAccountID, reconciliationBankAccountID); + setTravelInvoicingReconciliationBankAccount(workspaceAccountID, domainName, newBankAccountID, travelInvoicingReconciliationBankAccountID); goBack(); }; return ( - - {translate('workspace.accounting.chooseReconciliationAccount.chooseBankAccount')} - - - + connectionBankAccounts={connectionBankAccounts} + goBack={goBack} + description={translate('workspace.accounting.chooseReconciliationAccount.chooseTravelInvoicingBankAccount')} + html={translate('workspace.accounting.chooseReconciliationAccount.travelInvoicingSettlementAccountReconciliation', settlementAccountEnding)} + selectedBankAccountID={travelInvoicingReconciliationBankAccountID} + onSelectBankAccount={selectBankAccount} + /> + ); +} - selectBankAccount(value)} - ListItem={SingleSelectListItem} - initiallyFocusedItemKey={reconciliationBankAccountID} +function DynamicReconciliationAccountSettingsPage({route}: DynamicReconciliationAccountSettingsPageProps) { + const {policyID, connection, reconciliationAccountSettingsType} = route.params; + const backPath = useDynamicBackPath(DYNAMIC_ROUTES.WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS.path); + + const connectionName = getConnectionNameFromRouteParam(connection); + const domainName = getDomainNameForPolicy(policyID); + + const [connections] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {selector: policyConnectionsSelector}); + const [workspaceAccountID = CONST.DEFAULT_NUMBER_ID] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {selector: policyWorkspaceAccountIDSelector}); + const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + + const connectionBankAccounts = getConnectionBankAccountsForReconciliation(connections, connectionName); + + const goBack = useCallback(() => { + Navigation.goBack(backPath); + }, [backPath]); + + if (reconciliationAccountSettingsType === RECONCILIATION_ACCOUNT_SETTINGS_TYPE.TRAVEL_INVOICING) { + return ( + - + ); + } + + return ( + ); } diff --git a/src/pages/workspace/accounting/reconciliation/constants.ts b/src/pages/workspace/accounting/reconciliation/constants.ts new file mode 100644 index 000000000000..1bed15f8e047 --- /dev/null +++ b/src/pages/workspace/accounting/reconciliation/constants.ts @@ -0,0 +1,6 @@ +const RECONCILIATION_ACCOUNT_SETTINGS_TYPE = { + EXPENSIFY_CARD: 'expensifyCard', + TRAVEL_INVOICING: 'travelInvoicing', +} as const; + +export default RECONCILIATION_ACCOUNT_SETTINGS_TYPE; diff --git a/tests/unit/TravelInvoicingTest.ts b/tests/unit/TravelInvoicingTest.ts index 37b0aaa74ed0..537c2d23a5b4 100644 --- a/tests/unit/TravelInvoicingTest.ts +++ b/tests/unit/TravelInvoicingTest.ts @@ -5,7 +5,9 @@ import { configureTravelInvoicingForPolicy, deactivateTravelInvoicing, retryTravelCardsProvisioning, + setTravelInvoicingReconciliationBankAccount, setTravelInvoicingSettlementAccount, + toggleTravelInvoicingContinuousReconciliation, updateTravelInvoiceSettlementFrequency, } from '@libs/actions/TravelInvoicing'; // We need to import API because it is used in the tests @@ -123,6 +125,86 @@ describe('TravelInvoicing', () => { }); }); + it('toggleTravelInvoicingContinuousReconciliation sends travel-specific optimistic, success, and failure data', () => { + const workspaceAccountID = 456; + const connectionName = CONST.POLICY.CONNECTIONS.NAME.NETSUITE; + const oldConnectionName = CONST.POLICY.CONNECTIONS.NAME.QBO; + + toggleTravelInvoicingContinuousReconciliation(workspaceAccountID, true, connectionName, oldConnectionName); + + expect(spyAPIWrite).toHaveBeenCalledWith( + 'ToggleTravelInvoicingContinuousReconciliation', + { + policyAccountID: workspaceAccountID, + shouldUseContinuousReconciliation: true, + travelInvoicingContinuousReconciliationConnection: connectionName, + }, + expect.objectContaining({ + optimisticData: expect.arrayContaining([ + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: true, + }), + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION}${workspaceAccountID}`, + value: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }), + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: connectionName, + }), + ]), + successData: expect.arrayContaining([ + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION_PENDING_ACTION}${workspaceAccountID}`, + value: null, + }), + ]), + failureData: expect.arrayContaining([ + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: false, + }), + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: oldConnectionName, + }), + ]), + }), + ); + }); + + it('setTravelInvoicingReconciliationBankAccount sends the selected bank account and reverts on failure', () => { + const workspaceAccountID = 456; + const domainName = 'expensify_policy_123.expensify.com'; + const selectedBankAccountID = 'account-123'; + const previousBankAccountID = 'account-111'; + + setTravelInvoicingReconciliationBankAccount(workspaceAccountID, domainName, selectedBankAccountID, previousBankAccountID); + + expect(spyAPIWrite).toHaveBeenCalledWith( + 'SetTravelInvoicingReconciliationBankAccount', + { + domainName, + travelInvoicingReconciliationBankAccountID: selectedBankAccountID, + }, + expect.objectContaining({ + optimisticData: expect.arrayContaining([ + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID}${workspaceAccountID}`, + value: selectedBankAccountID, + }), + ]), + failureData: expect.arrayContaining([ + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.TRAVEL_INVOICING_RECONCILIATION_BANK_ACCOUNT_ID}${workspaceAccountID}`, + value: previousBankAccountID, + }), + ]), + }), + ); + }); + it('clearTravelInvoicingSettlementFrequencyErrors clears errors', () => { const workspaceAccountID = 456; const cardSettingsKey = getTravelInvoicingCardSettingsKey(workspaceAccountID);