From 059083b45640bad56f1797a2b496f16e3fb992ae Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Wed, 30 Oct 2024 15:05:29 +0700 Subject: [PATCH 001/345] fix: report fields are not disabled after disconnect accounting integration --- src/libs/PolicyUtils.ts | 5 ++++ src/libs/actions/connections/index.ts | 40 ++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 36e5ccef3308..c79cd86ec38f 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -381,6 +381,10 @@ function isControlPolicy(policy: OnyxEntry): boolean { return policy?.type === CONST.POLICY.TYPE.CORPORATE; } +function isCollectPolicy(policy: OnyxEntry): boolean { + return policy?.type === CONST.POLICY.TYPE.TEAM; +} + function isTaxTrackingEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry, isDistanceRequest: boolean): boolean { const distanceUnit = getDistanceRateCustomUnit(policy); const customUnitID = distanceUnit?.customUnitID ?? 0; @@ -1160,6 +1164,7 @@ export { getApprovalWorkflow, getReimburserAccountID, isControlPolicy, + isCollectPolicy, isNetSuiteCustomSegmentRecord, getNameFromNetSuiteCustomField, isNetSuiteCustomFieldPropertyEditable, diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index e4ef3e4ed047..404177cb4205 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -11,6 +11,7 @@ import type { } from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; @@ -42,11 +43,48 @@ function removePolicyConnection(policyID: string, connectionName: PolicyConnecti }, ]; + const successData: OnyxUpdate[] = []; + const failureData: OnyxUpdate[] = []; + const policy = PolicyUtils.getPolicy(policyID); + if (PolicyUtils.isCollectPolicy(policy) && ['quickbooksOnline', 'xero'].includes(connectionName)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + areReportFieldsEnabled: false, + pendingFields: { + areReportFieldsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }); + + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: { + areReportFieldsEnabled: null, + }, + }, + }); + + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + areReportFieldsEnabled: policy?.areReportFieldsEnabled, + pendingFields: { + areReportFieldsEnabled: null, + }, + }, + }); + } + const parameters: RemovePolicyConnectionParams = { policyID, connectionName, }; - API.write(WRITE_COMMANDS.REMOVE_POLICY_CONNECTION, parameters, {optimisticData}); + API.write(WRITE_COMMANDS.REMOVE_POLICY_CONNECTION, parameters, {optimisticData, successData, failureData}); } function createPendingFields( From 9e0e6aa0eccfd469091a3655db138e18ba9e162f Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 15 Nov 2024 11:21:07 +0100 Subject: [PATCH 002/345] feat: step 3 logic --- src/languages/en.ts | 3 + src/languages/es.ts | 3 + .../GetCorpayOnboardingFieldsParams.ts | 7 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/ValidationUtils.ts | 87 +++++++++++++++++++ src/libs/actions/BankAccounts.ts | 6 ++ .../NonUSD/BusinessInfo/BusinessInfo.tsx | 15 +++- .../substeps/RegistrationNumber.tsx | 22 +++-- 9 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 910d4397d0a0..a2cc1c76def0 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2205,6 +2205,9 @@ const translations = { findBusinessCategory: 'Find business category', findAnnualPaymentVolume: 'Find annual payment volume', findIncorporationState: 'Find incorporation state', + error: { + registrationNumber: 'Please provide a valid registration number.', + }, }, beneficialOwnerInfoStep: { doYouOwn25percent: 'Do you own 25% or more of', diff --git a/src/languages/es.ts b/src/languages/es.ts index 0944c3c638a1..15283b73df94 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2230,6 +2230,9 @@ const translations = { findBusinessCategory: 'Buscar categoría de la empresa', findAnnualPaymentVolume: 'Buscar volumen anual de pagos', findIncorporationState: 'Buscar estado de constitución', + error: { + registrationNumber: 'Por favor, proporcione un número de registro válido.', + }, }, beneficialOwnerInfoStep: { doYouOwn25percent: '¿Posees el 25% o más de', diff --git a/src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts b/src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts new file mode 100644 index 000000000000..a657d45a7bdf --- /dev/null +++ b/src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts @@ -0,0 +1,7 @@ +import type {Country} from '@src/CONST'; + +type GetCorpayOnboardingFieldsParams = { + countryISO: Country; +}; + +export default GetCorpayOnboardingFieldsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 681114fd3b08..9be2ebb65cd6 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -349,3 +349,4 @@ export type {default as UpdateQuickbooksDesktopCompanyCardExpenseAccountTypePara export type {default as TogglePolicyPerDiemParams} from './TogglePolicyPerDiemParams'; export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerDiemRatesPageParams'; export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams'; +export type {default as GetCorpayOnboardingFieldsParams} from './GetCorpayOnboardingFieldsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index bd8a58555617..b970edb20b38 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -945,6 +945,7 @@ const READ_COMMANDS = { OPEN_DRAFT_DISTANCE_EXPENSE: 'OpenDraftDistanceExpense', START_ISSUE_NEW_CARD_FLOW: 'StartIssueNewCardFlow', OPEN_CARD_DETAILS_PAGE: 'OpenCardDetailsPage', + GET_CORPAY_ONBOARDING_FIELDS: 'GetCorpayOnboardingFields', } as const; type ReadCommand = ValueOf; @@ -1007,6 +1008,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE]: null; [READ_COMMANDS.START_ISSUE_NEW_CARD_FLOW]: Parameters.StartIssueNewCardFlowParams; [READ_COMMANDS.OPEN_CARD_DETAILS_PAGE]: Parameters.OpenCardDetailsPageParams; + [READ_COMMANDS.GET_CORPAY_ONBOARDING_FIELDS]: Parameters.GetCorpayOnboardingFieldsParams; }; const SIDE_EFFECT_REQUEST_COMMANDS = { diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 0367325db6b1..23b7f743d44f 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -5,6 +5,7 @@ import isObject from 'lodash/isObject'; import type {OnyxCollection} from 'react-native-onyx'; import type {FormInputErrors, FormOnyxKeys, FormOnyxValues, FormValue} from '@components/Form/types'; import CONST from '@src/CONST'; +import type {Country} from '@src/CONST'; import type {OnyxFormKey} from '@src/ONYXKEYS'; import type {Report, TaxRates} from '@src/types/onyx'; import * as CardUtils from './CardUtils'; @@ -529,6 +530,91 @@ function isValidZipCodeInternational(zipCode: string): boolean { return /^[a-z0-9][a-z0-9\- ]{0,10}[a-z0-9]$/.test(zipCode); } +/** + * Validates the given value if it is correct ABN number + * @param registrationNumber - number to validate. + */ +function isValidABN(registrationNumber: string): boolean { + const cleanedAbn: string = registrationNumber.replaceAll(/[ _]/g, ''); + if (cleanedAbn.length !== 11) { + return false; + } + + const weights: number[] = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; + const checksum: number = [...cleanedAbn].reduce((total: number, char: string, index: number) => { + let digit = Number(char); + if (index === 0) { + digit--; + } // First digit special rule + return total + digit * (weights.at(index) ?? 0); // Using optional chaining for safety + }, 0); + + return checksum % 89 === 0; +} + +/** + * Validates the given value if it is correct ACN number + * @param registrationNumber - number to validate. + */ +function isValidACN(registrationNumber: string): boolean { + const cleanedAcn: string = registrationNumber.replaceAll(/\s|-/g, ''); + if (cleanedAcn.length !== 9 || Number.isNaN(Number(cleanedAcn))) { + return false; + } + + const weights: number[] = [8, 7, 6, 5, 4, 3, 2, 1]; + const tally: number = weights.reduce((total: number, weight: number, index: number) => { + return total + Number(cleanedAcn[index]) * weight; + }, 0); + + const checkDigit: number = 10 - (tally % 10); + return checkDigit === Number(cleanedAcn[8]) || (checkDigit === 10 && Number(cleanedAcn[8]) === 0); +} + +/** + * Validates the given value if it is correct australian registration number. + * @param registrationNumber + */ +function isValidAURegistrationNumber(registrationNumber: string): boolean { + return isValidABN(registrationNumber) || isValidACN(registrationNumber); +} + +/** + * Validates the given value if it is correct british registration number. + * @param registrationNumber + */ +function isValidGBRegistrationNumber(registrationNumber: string): boolean { + return /^(?:\d{8}|[A-Z]{2}\d{6})$/.test(registrationNumber); +} + +/** + * Validates the given value if it is correct canadian registration number. + * @param registrationNumber + */ +function isValidCARegistrationNumber(registrationNumber: string): boolean { + return /^\d{9}(?:[A-Z]{2}\d{4})?$/.test(registrationNumber); +} + +/** + * Validates the given value if it is correct registration number for the given country. + * @param registrationNumber + * @param country + */ +function isValidRegistrationNumber(registrationNumber: string, country: Country | '') { + switch (country) { + case CONST.COUNTRY.AU: + return isValidAURegistrationNumber(registrationNumber); + case CONST.COUNTRY.GB: + return isValidGBRegistrationNumber(registrationNumber); + case CONST.COUNTRY.CA: + return isValidCARegistrationNumber(registrationNumber); + case CONST.COUNTRY.US: + return isValidTaxID(registrationNumber); + default: + return true; + } +} + export { meetsMinimumAgeRequirement, meetsMaximumAgeRequirement, @@ -576,4 +662,5 @@ export { isValidEmail, isValidPhoneInternational, isValidZipCodeInternational, + isValidRegistrationNumber, }; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index bac1dba9ec71..86282e5d830d 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -16,6 +16,7 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; +import type {Country} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; @@ -500,6 +501,10 @@ function getCorpayBankAccountFields(country: string, currency: string) { }; } +function getCorpayOnboardingFields(country: Country) { + return API.read(READ_COMMANDS.GET_CORPAY_ONBOARDING_FIELDS, {countryISO: country}); +} + function clearReimbursementAccount() { Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, null); } @@ -730,6 +735,7 @@ export { clearPersonalBankAccountSetupType, validatePlaidSelection, getCorpayBankAccountFields, + getCorpayOnboardingFields, }; export type {BusinessAddress, PersonalAddress}; diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx index 61b42789daea..95fc3c9cdcef 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx @@ -1,10 +1,14 @@ import type {ComponentType} from 'react'; -import React from 'react'; +import React, {useEffect} from 'react'; +import {useOnyx} from 'react-native-onyx'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; +import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import Address from './substeps/Address'; import BusinessType from './substeps/BusinessType'; import Confirmation from './substeps/Confirmation'; @@ -27,6 +31,15 @@ const bodyContent: Array> = [Name, Address, ContactI function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { const {translate} = useLocalize(); + const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); + const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + + const country = reimbursementAccount?.achData?.additionalData?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? reimbursementAccountDraft?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? ''; + + useEffect(() => { + BankAccounts.getCorpayOnboardingFields(country); + }, [country]); + const submit = () => { onSubmit(); }; diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/RegistrationNumber.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/RegistrationNumber.tsx index c2e02688a23f..8ec195d5c0c1 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/RegistrationNumber.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/RegistrationNumber.tsx @@ -21,7 +21,7 @@ import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; type RegistrationNumberProps = SubStepProps; -const {BUSINESS_REGISTRATION_INCORPORATION_NUMBER} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; +const {BUSINESS_REGISTRATION_INCORPORATION_NUMBER, COMPANY_COUNTRY} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; const STEP_FIELDS = [BUSINESS_REGISTRATION_INCORPORATION_NUMBER]; function RegistrationNumber({onNext, isEditing}: RegistrationNumberProps) { @@ -33,11 +33,23 @@ function RegistrationNumber({onNext, isEditing}: RegistrationNumberProps) { const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); const defaultValue = reimbursementAccount?.achData?.additionalData?.corpay?.[BUSINESS_REGISTRATION_INCORPORATION_NUMBER] ?? reimbursementAccountDraft?.[BUSINESS_REGISTRATION_INCORPORATION_NUMBER] ?? ''; + const businessStepCountryDraftValue = reimbursementAccount?.achData?.additionalData?.corpay?.[COMPANY_COUNTRY] ?? reimbursementAccountDraft?.[COMPANY_COUNTRY] ?? ''; - // TODO Validation for registration number depending on the country will be added in https://github.com/Expensify/App/issues/50905 - const validate = useCallback((values: FormOnyxValues): FormInputErrors => { - return ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - }, []); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if ( + values[BUSINESS_REGISTRATION_INCORPORATION_NUMBER] && + !ValidationUtils.isValidRegistrationNumber(values[BUSINESS_REGISTRATION_INCORPORATION_NUMBER], businessStepCountryDraftValue) + ) { + errors[BUSINESS_REGISTRATION_INCORPORATION_NUMBER] = translate('businessInfoStep.error.registrationNumber'); + } + + return errors; + }, + [businessStepCountryDraftValue, translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, From 5669047ebb58951a27e8d9d50b6e6686ddc2bb6a Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 18 Nov 2024 09:41:32 +0100 Subject: [PATCH 003/345] feat: break --- .../API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts diff --git a/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts b/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts new file mode 100644 index 000000000000..e69de29bb2d1 From e81d66ee9bc4de2d59e1643b34ca7c13e7d74222 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 18 Nov 2024 09:41:40 +0100 Subject: [PATCH 004/345] feat: break --- .../GetCorpayOnboardingFieldsParams.ts | 2 +- ...aveCorpayOnboardingCompanyDetailsParams.ts | 23 +++++++++++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/BankAccounts.ts | 8 +++- .../NonUSD/BusinessInfo/BusinessInfo.tsx | 40 +++++++++++++++++-- 6 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts b/src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts index a657d45a7bdf..8b217e253282 100644 --- a/src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts +++ b/src/libs/API/parameters/GetCorpayOnboardingFieldsParams.ts @@ -1,7 +1,7 @@ import type {Country} from '@src/CONST'; type GetCorpayOnboardingFieldsParams = { - countryISO: Country; + countryISO: Country | ''; }; export default GetCorpayOnboardingFieldsParams; diff --git a/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts b/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts index e69de29bb2d1..a665ca59293a 100644 --- a/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts +++ b/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts @@ -0,0 +1,23 @@ +type SaveCorpayOnboardingCompanyDetailsParams = { + companyName: string; + companyStreetAddress: string; + companyCity: string; + companyState?: string; + companyPostalCode: string; + companyCountryCode: string; + businessContactNumber: string; + businessConfirmationEmail: string; + formationIncorporationCountryCode: string; + formationIncorporationState?: string; + businessRegistrationIncorporationNumber: string; + applicantTypeID: string; + purposeOfTransactionId: string; + currencyNeeded: string; + tradeVolume: string; + annualVolume: string; + fundDestinationCountries: string; + fundSourceCountries: string; + bankAccountID: string; +}; + +export default SaveCorpayOnboardingCompanyDetailsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 9be2ebb65cd6..abe6361d4285 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -350,3 +350,4 @@ export type {default as TogglePolicyPerDiemParams} from './TogglePolicyPerDiemPa export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerDiemRatesPageParams'; export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams'; export type {default as GetCorpayOnboardingFieldsParams} from './GetCorpayOnboardingFieldsParams'; +export type {default as SaveCorpayOnboardingCompanyDetailsParams} from './SaveCorpayOnboardingCompanyDetailsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b970edb20b38..93fd1c95b0f7 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -437,6 +437,7 @@ const WRITE_COMMANDS = { SELF_TOUR_VIEWED: 'SelfTourViewed', UPDATE_INVOICE_COMPANY_NAME: 'UpdateInvoiceCompanyName', UPDATE_INVOICE_COMPANY_WEBSITE: 'UpdateInvoiceCompanyWebsite', + SAVE_CORPAY_ONBOARDING_COMPANY_DETAILS: 'SaveCorpayOnboardingCompanyDetails', } as const; type WriteCommand = ValueOf; @@ -759,6 +760,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams; [WRITE_COMMANDS.REQUEST_TAX_EXEMPTION]: null; + [WRITE_COMMANDS.SAVE_CORPAY_ONBOARDING_COMPANY_DETAILS]: Parameters.SaveCorpayOnboardingCompanyDetailsParams; [WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH]: Parameters.DeleteMoneyRequestOnSearchParams; [WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.HoldMoneyRequestOnSearchParams; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 86282e5d830d..245ac271fd65 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -8,6 +8,7 @@ import type { ConnectBankAccountParams, DeletePaymentBankAccountParams, OpenReimbursementAccountPageParams, + SaveCorpayOnboardingCompanyDetailsParams, ValidateBankAccountWithTransactionsParams, VerifyIdentityForBankAccountParams, } from '@libs/API/parameters'; @@ -501,10 +502,14 @@ function getCorpayBankAccountFields(country: string, currency: string) { }; } -function getCorpayOnboardingFields(country: Country) { +function getCorpayOnboardingFields(country: Country | '') { return API.read(READ_COMMANDS.GET_CORPAY_ONBOARDING_FIELDS, {countryISO: country}); } +function saveCorpayOnboardingCompanyDetails(parameters: SaveCorpayOnboardingCompanyDetailsParams) { + return API.write(WRITE_COMMANDS.SAVE_CORPAY_ONBOARDING_COMPANY_DETAILS, parameters); +} + function clearReimbursementAccount() { Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, null); } @@ -736,6 +741,7 @@ export { validatePlaidSelection, getCorpayBankAccountFields, getCorpayOnboardingFields, + saveCorpayOnboardingCompanyDetails, }; export type {BusinessAddress, PersonalAddress}; diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx index 95fc3c9cdcef..b0cbaa4f5871 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx @@ -1,10 +1,11 @@ import type {ComponentType} from 'react'; -import React, {useEffect} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; +import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -28,11 +29,28 @@ type BusinessInfoProps = { const bodyContent: Array> = [Name, Address, ContactInformation, RegistrationNumber, IncorporationLocation, BusinessType, PaymentVolume, Confirmation]; +const INPUT_KEYS = { + NAME: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_NAME, + STREET: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_STREET, + CITY: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_CITY, + STATE: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_STATE, + ZIP_CODE: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_ZIP_CODE, + COUNTRY: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_COUNTRY, + CONTACT_NUMBER: INPUT_IDS.ADDITIONAL_DATA.CORPAY.BUSINESS_CONTACT_NUMBER, + CONFIRMATION_EMAIL: INPUT_IDS.ADDITIONAL_DATA.CORPAY.BUSINESS_CONFIRMATION_EMAIL, + INCORPORATION_STATE: INPUT_IDS.ADDITIONAL_DATA.CORPAY.FORMATION_INCORPORATION_STATE, + INCORPORATION_COUNTRY: INPUT_IDS.ADDITIONAL_DATA.CORPAY.FORMATION_INCORPORATION_COUNTRY_CODE, + BUSINESS_CATEGORY: INPUT_IDS.ADDITIONAL_DATA.CORPAY.BUSINESS_CATEGORY, + APPLICANT_TYPE_ID: INPUT_IDS.ADDITIONAL_DATA.CORPAY.APPLICANT_TYPE_ID, + ANNUAL_VOLUME: INPUT_IDS.ADDITIONAL_DATA.CORPAY.ANNUAL_VOLUME, +}; + function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { const {translate} = useLocalize(); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const onyxValues = useMemo(() => getSubstepValues(INPUT_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); const country = reimbursementAccount?.achData?.additionalData?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? reimbursementAccountDraft?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? ''; @@ -40,9 +58,25 @@ function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { BankAccounts.getCorpayOnboardingFields(country); }, [country]); - const submit = () => { + const submit = useCallback(() => { + // TODO pass proper params + BankAccounts.saveCorpayOnboardingCompanyDetails({ + companyName: onyxValues[INPUT_KEYS.NAME], + companyStreetAddress: onyxValues[INPUT_KEYS.STREET], + companyCity: onyxValues[INPUT_KEYS.CITY], + companyState: onyxValues[INPUT_KEYS.STATE], + companyPostalCode: onyxValues[INPUT_KEYS.ZIP_CODE], + companyCountryCode: onyxValues[INPUT_KEYS.COUNTRY], + businessContactNumber: onyxValues[INPUT_KEYS.CONTACT_NUMBER], + businessConfirmationEmail: onyxValues[INPUT_KEYS.CONFIRMATION_EMAIL], + formationIncorporationState: onyxValues[INPUT_KEYS.INCORPORATION_STATE], + formationIncorporationCountryCode: onyxValues[INPUT_KEYS.INCORPORATION_COUNTRY], + businessCategory: onyxValues[INPUT_KEYS.BUSINESS_CATEGORY], + applicantTypeID: onyxValues[INPUT_KEYS.APPLICANT_TYPE_ID], + annualVolume: onyxValues[INPUT_KEYS.ANNUAL_VOLUME], + }); onSubmit(); - }; + }, [onSubmit, onyxValues]); const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo, goToTheLastStep} = useSubStep({bodyContent, startFrom: 0, onFinished: submit}); From 2da487650f808c3e117810892451e2cbbd018709 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 22 Nov 2024 12:00:14 +0100 Subject: [PATCH 005/345] feat: proper params --- src/CONST.ts | 1 + src/languages/en.ts | 2 + src/languages/es.ts | 2 + ...aveCorpayOnboardingCompanyDetailsParams.ts | 26 +++++--- src/libs/API/parameters/index.ts | 2 +- src/libs/actions/BankAccounts.ts | 9 ++- .../NonUSD/BusinessInfo/BusinessInfo.tsx | 62 +++++++++++++------ .../BusinessInfo/substeps/Confirmation.tsx | 17 +++-- .../NonUSD/BusinessInfo/substeps/Name.tsx | 36 ++++------- .../BusinessInfo/substeps/TaxIDEINNumber.tsx | 57 +++++++++++++++++ 10 files changed, 156 insertions(+), 58 deletions(-) create mode 100644 src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/TaxIDEINNumber.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 06feb863a80a..ead3bf0c2f09 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -611,6 +611,7 @@ const CONST = { ALLOWED_FILE_TYPES: ['pdf', 'jpg', 'jpeg', 'png'], FILE_LIMIT: 10, TOTAL_FILES_SIZE_LIMIT: 5242880, + PURPOSE_OF_TRANSACTION_ID: 8, STEP: { COUNTRY: 'CountryStep', BANK_INFO: 'BankInfoStep', diff --git a/src/languages/en.ts b/src/languages/en.ts index e6a78a52cdbd..3320061a5ad9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2188,11 +2188,13 @@ const translations = { whatsTheBusinessAddress: "What's the business address?", whatsTheBusinessContactInformation: "What's the business contact information?", whatsTheBusinessRegistrationNumber: "What's the business registration number?", + whatsTheBusinessTaxIDEIN: "What's the business tax ID/EIN/VAT/GST Registration number?", whatsThisNumber: "What's this number?", whereWasTheBusinessIncorporated: 'Where was the business incorporated?', whatTypeOfBusinessIsIt: 'What type of business is it?', whatsTheBusinessAnnualPayment: "What's the business's annual payment volume?", registrationNumber: 'Registration number', + taxIDEIN: 'Tax ID/EIN number', businessAddress: 'Business address', businessType: 'Business type', incorporation: 'Incorporation', diff --git a/src/languages/es.ts b/src/languages/es.ts index ebdaa47e7e3b..c457cc82080d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2212,11 +2212,13 @@ const translations = { whatsTheBusinessAddress: '¿Cuál es la dirección de la empresa?', whatsTheBusinessContactInformation: '¿Cuál es la información de contacto de la empresa?', whatsTheBusinessRegistrationNumber: '¿Cuál es el número de registro de la empresa?', + whatsTheBusinessTaxIDEIN: '¿Cuál es el número de identificación fiscal/EIN/IVA/GST de la empresa?', whatsThisNumber: '¿Qué es este número?', whereWasTheBusinessIncorporated: '¿Dónde se constituyó la empresa?', whatTypeOfBusinessIsIt: '¿Qué tipo de empresa es?', whatsTheBusinessAnnualPayment: '¿Cuál es el volumen anual de pagos de la empresa?', registrationNumber: 'Número de registro', + taxIDEIN: 'Número de identificación fiscal/EIN', businessAddress: 'Dirección de la empresa', businessType: 'Tipo de empresa', incorporation: 'Constitución', diff --git a/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts b/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts index a665ca59293a..d48d1efcfaa9 100644 --- a/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts +++ b/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts @@ -1,23 +1,31 @@ -type SaveCorpayOnboardingCompanyDetailsParams = { +import type CONST from '@src/CONST'; + +type SaveCorpayOnboardingCompanyDetails = { + annualVolume: string; + applicantTypeId: string; companyName: string; companyStreetAddress: string; companyCity: string; companyState?: string; companyPostalCode: string; companyCountryCode: string; + currencyNeeded: string; businessContactNumber: string; businessConfirmationEmail: string; + businessRegistrationIncorporationNumber: string; formationIncorporationCountryCode: string; formationIncorporationState?: string; - businessRegistrationIncorporationNumber: string; - applicantTypeID: string; - purposeOfTransactionId: string; - currencyNeeded: string; - tradeVolume: string; - annualVolume: string; fundDestinationCountries: string; fundSourceCountries: string; - bankAccountID: string; + natureOfBusiness: string; + purposeOfTransactionId: typeof CONST.NON_USD_BANK_ACCOUNT.PURPOSE_OF_TRANSACTION_ID; + tradeVolume: string; + taxIDEINNumber: string; +}; + +type SaveCorpayOnboardingCompanyDetailsParams = { + inputs: string; + bankAccountID: number; }; -export default SaveCorpayOnboardingCompanyDetailsParams; +export type {SaveCorpayOnboardingCompanyDetails, SaveCorpayOnboardingCompanyDetailsParams}; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index abe6361d4285..42bb401e0899 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -350,4 +350,4 @@ export type {default as TogglePolicyPerDiemParams} from './TogglePolicyPerDiemPa export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerDiemRatesPageParams'; export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams'; export type {default as GetCorpayOnboardingFieldsParams} from './GetCorpayOnboardingFieldsParams'; -export type {default as SaveCorpayOnboardingCompanyDetailsParams} from './SaveCorpayOnboardingCompanyDetailsParams'; +export type {SaveCorpayOnboardingCompanyDetailsParams} from './SaveCorpayOnboardingCompanyDetailsParams'; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 245ac271fd65..10b4b6636624 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -8,10 +8,10 @@ import type { ConnectBankAccountParams, DeletePaymentBankAccountParams, OpenReimbursementAccountPageParams, - SaveCorpayOnboardingCompanyDetailsParams, ValidateBankAccountWithTransactionsParams, VerifyIdentityForBankAccountParams, } from '@libs/API/parameters'; +import type {SaveCorpayOnboardingCompanyDetails} from '@libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as Localize from '@libs/Localize'; @@ -506,8 +506,11 @@ function getCorpayOnboardingFields(country: Country | '') { return API.read(READ_COMMANDS.GET_CORPAY_ONBOARDING_FIELDS, {countryISO: country}); } -function saveCorpayOnboardingCompanyDetails(parameters: SaveCorpayOnboardingCompanyDetailsParams) { - return API.write(WRITE_COMMANDS.SAVE_CORPAY_ONBOARDING_COMPANY_DETAILS, parameters); +function saveCorpayOnboardingCompanyDetails(parameters: SaveCorpayOnboardingCompanyDetails, bankAccountID: number) { + return API.write(WRITE_COMMANDS.SAVE_CORPAY_ONBOARDING_COMPANY_DETAILS, { + inputs: JSON.stringify(parameters), + bankAccountID, + }); } function clearReimbursementAccount() { diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx index b0cbaa4f5871..4f4cbf205b67 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx @@ -18,6 +18,7 @@ import IncorporationLocation from './substeps/IncorporationLocation'; import Name from './substeps/Name'; import PaymentVolume from './substeps/PaymentVolume'; import RegistrationNumber from './substeps/RegistrationNumber'; +import TaxIDEINNumber from './substeps/TaxIDEINNumber'; type BusinessInfoProps = { /** Handles back button press */ @@ -27,7 +28,17 @@ type BusinessInfoProps = { onSubmit: () => void; }; -const bodyContent: Array> = [Name, Address, ContactInformation, RegistrationNumber, IncorporationLocation, BusinessType, PaymentVolume, Confirmation]; +const bodyContent: Array> = [ + Name, + Address, + ContactInformation, + RegistrationNumber, + TaxIDEINNumber, + IncorporationLocation, + BusinessType, + PaymentVolume, + Confirmation, +]; const INPUT_KEYS = { NAME: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_NAME, @@ -40,9 +51,11 @@ const INPUT_KEYS = { CONFIRMATION_EMAIL: INPUT_IDS.ADDITIONAL_DATA.CORPAY.BUSINESS_CONFIRMATION_EMAIL, INCORPORATION_STATE: INPUT_IDS.ADDITIONAL_DATA.CORPAY.FORMATION_INCORPORATION_STATE, INCORPORATION_COUNTRY: INPUT_IDS.ADDITIONAL_DATA.CORPAY.FORMATION_INCORPORATION_COUNTRY_CODE, + BUSINESS_REGISTRATION_INCORPORATION_NUMBER: INPUT_IDS.ADDITIONAL_DATA.CORPAY.BUSINESS_REGISTRATION_INCORPORATION_NUMBER, BUSINESS_CATEGORY: INPUT_IDS.ADDITIONAL_DATA.CORPAY.BUSINESS_CATEGORY, APPLICANT_TYPE_ID: INPUT_IDS.ADDITIONAL_DATA.CORPAY.APPLICANT_TYPE_ID, ANNUAL_VOLUME: INPUT_IDS.ADDITIONAL_DATA.CORPAY.ANNUAL_VOLUME, + TAX_ID_EIN_NUMBER: INPUT_IDS.ADDITIONAL_DATA.CORPAY.TAX_ID_EIN_NUMBER, }; function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { @@ -50,7 +63,11 @@ function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const policyID = reimbursementAccount?.achData?.policyID ?? '-1'; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const currency = policy?.outputCurrency ?? ''; const onyxValues = useMemo(() => getSubstepValues(INPUT_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); + const bankAccountID = reimbursementAccount?.achData?.bankAccountID ?? 0; const country = reimbursementAccount?.achData?.additionalData?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? reimbursementAccountDraft?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? ''; @@ -59,24 +76,33 @@ function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { }, [country]); const submit = useCallback(() => { - // TODO pass proper params - BankAccounts.saveCorpayOnboardingCompanyDetails({ - companyName: onyxValues[INPUT_KEYS.NAME], - companyStreetAddress: onyxValues[INPUT_KEYS.STREET], - companyCity: onyxValues[INPUT_KEYS.CITY], - companyState: onyxValues[INPUT_KEYS.STATE], - companyPostalCode: onyxValues[INPUT_KEYS.ZIP_CODE], - companyCountryCode: onyxValues[INPUT_KEYS.COUNTRY], - businessContactNumber: onyxValues[INPUT_KEYS.CONTACT_NUMBER], - businessConfirmationEmail: onyxValues[INPUT_KEYS.CONFIRMATION_EMAIL], - formationIncorporationState: onyxValues[INPUT_KEYS.INCORPORATION_STATE], - formationIncorporationCountryCode: onyxValues[INPUT_KEYS.INCORPORATION_COUNTRY], - businessCategory: onyxValues[INPUT_KEYS.BUSINESS_CATEGORY], - applicantTypeID: onyxValues[INPUT_KEYS.APPLICANT_TYPE_ID], - annualVolume: onyxValues[INPUT_KEYS.ANNUAL_VOLUME], - }); + BankAccounts.saveCorpayOnboardingCompanyDetails( + { + annualVolume: onyxValues[INPUT_KEYS.ANNUAL_VOLUME], + applicantTypeId: onyxValues[INPUT_KEYS.APPLICANT_TYPE_ID], + companyName: onyxValues[INPUT_KEYS.NAME], + companyStreetAddress: onyxValues[INPUT_KEYS.STREET], + companyCity: onyxValues[INPUT_KEYS.CITY], + companyState: onyxValues[INPUT_KEYS.STATE], + companyPostalCode: onyxValues[INPUT_KEYS.ZIP_CODE], + companyCountryCode: onyxValues[INPUT_KEYS.COUNTRY], + currencyNeeded: currency, + businessContactNumber: onyxValues[INPUT_KEYS.CONTACT_NUMBER], + businessConfirmationEmail: onyxValues[INPUT_KEYS.CONFIRMATION_EMAIL], + businessRegistrationIncorporationNumber: onyxValues[INPUT_KEYS.BUSINESS_REGISTRATION_INCORPORATION_NUMBER], + formationIncorporationState: onyxValues[INPUT_KEYS.INCORPORATION_STATE], + formationIncorporationCountryCode: onyxValues[INPUT_KEYS.INCORPORATION_COUNTRY], + fundSourceCountries: onyxValues[INPUT_KEYS.COUNTRY], + fundDestinationCountries: onyxValues[INPUT_KEYS.STATE], + natureOfBusiness: onyxValues[INPUT_KEYS.BUSINESS_CATEGORY], + purposeOfTransactionId: CONST.NON_USD_BANK_ACCOUNT.PURPOSE_OF_TRANSACTION_ID, + tradeVolume: onyxValues[INPUT_KEYS.ANNUAL_VOLUME], + taxIDEINNumber: onyxValues[INPUT_KEYS.TAX_ID_EIN_NUMBER], + }, + bankAccountID, + ); onSubmit(); - }, [onSubmit, onyxValues]); + }, [bankAccountID, currency, onSubmit, onyxValues]); const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo, goToTheLastStep} = useSubStep({bodyContent, startFrom: 0, onFinished: submit}); diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx index d0f26feccf0f..156967d63239 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx @@ -19,6 +19,7 @@ const BUSINESS_INFO_STEP_KEYS = INPUT_IDS.ADDITIONAL_DATA.CORPAY; const { COMPANY_NAME, BUSINESS_REGISTRATION_INCORPORATION_NUMBER, + TAX_ID_EIN_NUMBER, COMPANY_COUNTRY, COMPANY_STREET, COMPANY_CITY, @@ -77,6 +78,14 @@ function Confirmation({onNext, onMove}: SubStepProps) { onMove(3); }} /> + { + onMove(4); + }} + /> { - onMove(5); + onMove(6); }} /> { - onMove(4); + onMove(5); }} /> { - onMove(5); + onMove(6); }} /> { - onMove(6); + onMove(7); }} /> diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Name.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Name.tsx index 2edf94637a81..b0199aaa8308 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Name.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Name.tsx @@ -1,14 +1,10 @@ import React, {useCallback} from 'react'; import {useOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import SingleFieldStep from '@components/SubStepForms/SingleFieldStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -19,9 +15,8 @@ type NameProps = SubStepProps; const {COMPANY_NAME} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; const STEP_FIELDS = [COMPANY_NAME]; -function Name({onNext, isEditing}: NameProps) { +function Name({onNext, onMove, isEditing}: NameProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); @@ -47,25 +42,20 @@ function Name({onNext, isEditing}: NameProps) { }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} + formTitle={translate('businessInfoStep.whatsTheBusinessName')} validate={validate} onSubmit={handleSubmit} - style={[styles.mh5, styles.flexGrow1]} - > - {translate('businessInfoStep.whatsTheBusinessName')} - - + inputId={COMPANY_NAME} + inputLabel={translate('businessInfoStep.legalBusinessName')} + inputMode={CONST.INPUT_MODE.TEXT} + defaultValue={defaultValue} + shouldShowHelpLinks={false} + /> ); } diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/TaxIDEINNumber.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/TaxIDEINNumber.tsx new file mode 100644 index 000000000000..4ae32cf5b642 --- /dev/null +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/TaxIDEINNumber.tsx @@ -0,0 +1,57 @@ +import React, {useCallback} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import SingleFieldStep from '@components/SubStepForms/SingleFieldStep'; +import useLocalize from '@hooks/useLocalize'; +import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; + +type TaxIDEINNumberProps = SubStepProps; + +const {TAX_ID_EIN_NUMBER} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; +const STEP_FIELDS = [TAX_ID_EIN_NUMBER]; + +function TaxIDEINNumber({onNext, onMove, isEditing}: TaxIDEINNumberProps) { + const {translate} = useLocalize(); + + const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); + const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const defaultValue = reimbursementAccount?.achData?.additionalData?.corpay?.[TAX_ID_EIN_NUMBER] ?? reimbursementAccountDraft?.[TAX_ID_EIN_NUMBER] ?? ''; + + const validate = useCallback((values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + return errors; + }, []); + + const handleSubmit = useReimbursementAccountStepFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: isEditing, + }); + + return ( + + isEditing={isEditing} + onNext={onNext} + onMove={onMove} + formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} + formTitle={translate('businessInfoStep.whatsTheBusinessTaxIDEIN')} + validate={validate} + onSubmit={handleSubmit} + inputId={TAX_ID_EIN_NUMBER} + inputLabel={translate('businessInfoStep.taxIDEIN')} + inputMode={CONST.INPUT_MODE.NUMERIC} + defaultValue={defaultValue} + shouldShowHelpLinks={false} + /> + ); +} + +TaxIDEINNumber.displayName = 'TaxIDEINNumber'; + +export default TaxIDEINNumber; From 52c65628c6ceee20c464c4bfc51eeba0511a8bcc Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 28 Nov 2024 09:53:46 +0100 Subject: [PATCH 006/345] testing --- .../ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx index 4f4cbf205b67..7518d49baf00 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx @@ -69,7 +69,8 @@ function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { const onyxValues = useMemo(() => getSubstepValues(INPUT_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); const bankAccountID = reimbursementAccount?.achData?.bankAccountID ?? 0; - const country = reimbursementAccount?.achData?.additionalData?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? reimbursementAccountDraft?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? ''; + const country = + reimbursementAccount?.achData?.additionalData?.[INPUT_IDS.ADDITIONAL_DATA.DESTINATION_COUNTRY] ?? reimbursementAccountDraft?.[INPUT_IDS.ADDITIONAL_DATA.DESTINATION_COUNTRY] ?? ''; useEffect(() => { BankAccounts.getCorpayOnboardingFields(country); From b63de9b16856030e81b9a0dfe170b693197c3b90 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 9 Dec 2024 09:48:01 +0100 Subject: [PATCH 007/345] feat: picklists --- src/CONST.ts | 9 + src/ONYXKEYS.ts | 6 +- .../NonUSD/BusinessInfo/mockedCorpayLists.ts | 413 ------------------ .../BusinessInfo/substeps/BusinessType.tsx | 39 +- .../BusinessInfo/substeps/PaymentVolume.tsx | 22 +- src/types/onyx/CorpayOnboardingFields.ts | 35 ++ src/types/onyx/index.ts | 2 + 7 files changed, 91 insertions(+), 435 deletions(-) delete mode 100644 src/pages/ReimbursementAccount/NonUSD/BusinessInfo/mockedCorpayLists.ts create mode 100644 src/types/onyx/CorpayOnboardingFields.ts diff --git a/src/CONST.ts b/src/CONST.ts index d743fcdfffe4..390c8fb27e6c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -624,6 +624,15 @@ const CONST = { AGREEMENTS: 'AgreementsStep', FINISH: 'FinishStep', }, + BUSINESS_INFO_STEP: { + PICKLIST: { + ANNUAL_VOLUME_RANGE: 'AnnualVolumeRange', + APPLICANT_TYPE: 'ApplicantType', + NATURE_OF_BUSINESS: 'NatureOfBusiness', + PURPOSE_OF_TRANSACTION: 'PurposeOfTransaction', + TRADE_VOLUME_RANGE: 'TradeVolumeRange', + }, + }, BENEFICIAL_OWNER_INFO_STEP: { SUBSTEP: { IS_USER_BENEFICIAL_OWNER: 1, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index adbbbebddb15..4b9e54dbb869 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -461,9 +461,12 @@ const ONYXKEYS = { /** The user's Concierge reportID */ CONCIERGE_REPORT_ID: 'conciergeReportID', - /* Corpay fieds to be used in the bank account creation setup */ + /** Corpay fieds to be used in the bank account creation setup */ CORPAY_FIELDS: 'corpayFields', + /** Corpay onboarding fields used in steps 3-5 in the global reimbursements */ + CORPAY_ONBOARDING_FIELDS: 'corpayOnboardingFields', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -1031,6 +1034,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; [ONYXKEYS.CORPAY_FIELDS]: OnyxTypes.CorpayFields; + [ONYXKEYS.CORPAY_ONBOARDING_FIELDS]: OnyxTypes.CorpayOnboardingFields; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/mockedCorpayLists.ts b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/mockedCorpayLists.ts deleted file mode 100644 index 3acde3dc6577..000000000000 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/mockedCorpayLists.ts +++ /dev/null @@ -1,413 +0,0 @@ -// TODO - Remove this file once GetCorpayOnboardingFields method is fully implemented. It should when we start work on https://github.com/Expensify/App/issues/50905 - -const annualVolumeRange = [ - { - id: '0', - name: 'Undefined', - stringValue: 'Undefined', - }, - { - id: '1', - name: 'LessThan25000', - stringValue: 'Less than 25000', - }, - { - id: '2', - name: 'TwentyFiveThousandToFiftyThousand', - stringValue: '25,000 - 50,000', - }, - { - id: '3', - name: 'FiftyThousandToSeventyFiveThousand', - stringValue: '50,000 – 75,000', - }, - { - id: '4', - name: 'SeventyFiveToOneHundredThousand', - stringValue: '75,000 – 100,000', - }, - { - id: '5', - name: 'OneHundredToOneHundredFiftyThousand', - stringValue: '100,000 – 150,000', - }, - { - id: '6', - name: 'OneHundredFiftyToTwoHundredThousand', - stringValue: '150,000 – 200,000', - }, - { - id: '7', - name: 'TwoHundredToTwoHundredFiftyThousand', - stringValue: '200,000 – 250,000', - }, - { - id: '8', - name: 'TwoHundredFiftyToThreeHundredThousand', - stringValue: '250,000 – 300,000', - }, - { - id: '9', - name: 'ThreeHundredToFourHundredThousand', - stringValue: '300,000 – 400,000', - }, - { - id: '10', - name: 'FourHundredToFiveHundredThousand', - stringValue: '400,000 – 500,000', - }, - { - id: '11', - name: 'FiveHundredToSevenHundredFiftyThousand', - stringValue: '500,000 – 750,000', - }, - { - id: '12', - name: 'SevenHundredFiftyThousandToOneMillion', - stringValue: '750,000 – 1 million', - }, - { - id: '13', - name: 'OneMillionToTwoMillion', - stringValue: '1 million – 2 million', - }, - { - id: '14', - name: 'TwoMillionToThreeMillion', - stringValue: '2 million – 3 million', - }, - { - id: '15', - name: 'ThreeMillionToFiveMillion', - stringValue: '3 million – 5 million', - }, - { - id: '16', - name: 'FiveMillionToSevenPointFiveMillion', - stringValue: '5 million – 7.5 million', - }, - { - id: '17', - name: 'SevenPointFiveMillionToTenMillion', - stringValue: '7.5 million – 10 million', - }, - { - id: '18', - name: 'GreaterThan10Million', - stringValue: 'Greater than 10 Million', - }, -]; - -// eslint-disable-next-line rulesdir/no-negated-variables -const applicantType = [ - { - id: '0', - name: 'Undefined', - stringValue: 'Undefined', - }, - { - id: '1', - name: 'Corporation', - stringValue: 'Corporation', - }, - { - id: '2', - name: 'Limited_Liability_Company', - stringValue: 'Limited Liability Company (e.g., LLC, LC)', - }, - { - id: '3', - name: 'Partnership', - stringValue: 'Partnership', - }, - { - id: '4', - name: 'Partnership_UK', - stringValue: 'Partnership UK', - }, - { - id: '5', - name: 'Unincorporated_Entity', - stringValue: 'Unincorporated Entity', - }, - { - id: '6', - name: 'Sole_Proprietorship_Sole_Trader', - stringValue: 'Sole Proprietorship/Sole Trader', - }, - { - id: '7', - name: 'Private_person_Entity', - stringValue: 'Private person/ Entity', - }, - { - id: '8', - name: 'Personal_Account', - stringValue: 'Personal Account', - }, - { - id: '9', - name: 'Financial_Institution', - stringValue: 'Financial Institution', - }, - { - id: '10', - name: 'Non_Profit', - stringValue: 'Not for Profit', - }, - { - id: '11', - name: 'Online_User_Verification', - stringValue: 'Online User Verification', - }, - { - id: '12', - name: 'Charitable_Organization', - stringValue: 'Charitable Organizationt', - }, - { - id: '13', - name: 'Trust', - stringValue: 'Trust', - }, -]; - -const natureOfBusiness = [ - { - id: '0', - name: 'Undefined', - stringValue: 'Undefined', - }, - { - id: '10', - name: 'Aerospace and defense', - stringValue: 'Aerospace and defense', - }, - { - id: '20', - name: 'Agriculture and agric-food', - stringValue: 'Agriculture and agric-food', - }, - { - id: '30', - name: 'Apparel / Clothing', - stringValue: 'Apparel / Clothing', - }, - { - id: '40', - name: 'Automotive / Trucking', - stringValue: 'Automotive / Trucking', - }, - { - id: '50', - name: 'Books / Magazines', - stringValue: 'Books / Magazines', - }, - { - id: '60', - name: 'Broadcasting', - stringValue: 'Broadcasting', - }, - { - id: '70', - name: 'Building products', - stringValue: 'Building products', - }, - { - id: '80', - name: 'Chemicals', - stringValue: 'Chemicals', - }, - { - id: '90', - name: 'Dairy', - stringValue: 'Dairy', - }, - { - id: '100', - name: 'E-business', - stringValue: 'E-business', - }, - { - id: '105', - name: 'Educational Institutes', - stringValue: 'Educational Institutes', - }, - { - id: '110', - name: 'Environment', - stringValue: 'Environment', - }, - { - id: '120', - name: 'Explosives', - stringValue: 'Explosives', - }, - { - id: '140', - name: 'Fisheries and oceans', - stringValue: 'Fisheries and oceans', - }, - { - id: '150', - name: 'Food / Beverage distribution', - stringValue: 'Food / Beverage distribution', - }, - { - id: '160', - name: 'Footwear', - stringValue: 'Footwear', - }, - { - id: '170', - name: 'Forest industries', - stringValue: 'Forest industries', - }, - { - id: '180', - name: 'Furniture', - stringValue: 'Furniture', - }, - { - id: '190', - name: 'Giftware and crafts', - stringValue: 'Giftware and crafts', - }, - { - id: '200', - name: 'Horticulture', - stringValue: 'Horticulture', - }, - { - id: '210', - name: 'Hydroelectric energy', - stringValue: 'Hydroelectric energy', - }, - { - id: '220', - name: 'Information and communication technologies', - stringValue: 'Information and communication technologies', - }, - { - id: '230', - name: 'Intelligent systems', - stringValue: 'Intelligent systems', - }, - { - id: '240', - name: 'Livestock', - stringValue: 'Livestock', - }, - { - id: '250', - name: 'Medical devices', - stringValue: 'Medical devices', - }, - { - id: '251', - name: 'Medical treatment', - stringValue: 'Medical treatment', - }, - { - id: '260', - name: 'Minerals, metals and mining', - stringValue: 'Minerals, metals and mining', - }, - { - id: '270', - name: 'Oil and gas', - stringValue: 'Oil and gas', - }, - { - id: '280', - name: 'Pharmaceuticals and biopharmaceuticals', - stringValue: 'Pharmaceuticals and biopharmaceuticals', - }, - { - id: '290', - name: 'Plastics', - stringValue: 'Plastics', - }, - { - id: '300', - name: 'Poultry and eggs', - stringValue: 'Poultry and eggs', - }, - { - id: '310', - name: 'Printing /Publishing', - stringValue: 'Printing /Publishing', - }, - { - id: '320', - name: 'Product design and development', - stringValue: 'Product design and development', - }, - { - id: '330', - name: 'Railway', - stringValue: 'Railway', - }, - { - id: '340', - name: 'Retail', - stringValue: 'Retail', - }, - { - id: '350', - name: 'Shipping and industrial marine', - stringValue: 'Shipping and industrial marine', - }, - { - id: '360', - name: 'Soil', - stringValue: 'Soil', - }, - { - id: '370', - name: 'Sound recording', - stringValue: 'Sound recording', - }, - { - id: '380', - name: 'Sporting goods', - stringValue: 'Sporting goods', - }, - { - id: '390', - name: 'Telecommunications equipment', - stringValue: 'Telecommunications equipment', - }, - { - id: '400', - name: 'Television', - stringValue: 'Television', - }, - { - id: '410', - name: 'Textiles', - stringValue: 'Textiles', - }, - { - id: '420', - name: 'Tourism', - stringValue: 'Tourism', - }, - { - id: '425', - name: 'Trademarks / Law', - stringValue: 'Trademarks / Law', - }, - { - id: '430', - name: 'Water supply', - stringValue: 'Water supply', - }, - { - id: '440', - name: 'Wholesale', - stringValue: 'Wholesale', - }, -]; - -export {annualVolumeRange, applicantType, natureOfBusiness}; diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx index bd083c2dd535..439687548718 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -10,7 +10,6 @@ import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccoun import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; -import {applicantType, natureOfBusiness} from '@pages/ReimbursementAccount/NonUSD/BusinessInfo/mockedCorpayLists'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; @@ -19,21 +18,35 @@ type BusinessTypeProps = SubStepProps; const {BUSINESS_CATEGORY, APPLICANT_TYPE_ID} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; const STEP_FIELDS = [BUSINESS_CATEGORY, APPLICANT_TYPE_ID]; -const INCORPORATION_TYPE_LIST_OPTIONS = applicantType.reduce((accumulator, currentValue) => { - accumulator[currentValue.name] = currentValue.stringValue; - return accumulator; -}, {} as Record); -const BUSINESS_CATEGORY_LIST_OPTIONS = natureOfBusiness.reduce((accumulator, currentValue) => { - accumulator[currentValue.name] = currentValue.stringValue; - return accumulator; -}, {} as Record); - function BusinessType({onNext, isEditing}: BusinessTypeProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const [corpayOnboardingFields] = useOnyx(ONYXKEYS.CORPAY_ONBOARDING_FIELDS); + + const incorporationTypeListOptions = useMemo(() => { + if (!corpayOnboardingFields) { + return {}; + } + + return corpayOnboardingFields.picklists.ApplicantType.reduce((accumulator, currentValue) => { + accumulator[currentValue.name] = currentValue.stringValue; + return accumulator; + }, {} as Record); + }, [corpayOnboardingFields]); + + const natureOfBusinessListOptions = useMemo(() => { + if (!corpayOnboardingFields) { + return {}; + } + + return corpayOnboardingFields.picklists.NatureOfBusiness.reduce((accumulator, currentValue) => { + accumulator[currentValue.name] = currentValue.stringValue; + return accumulator; + }, {} as Record); + }, [corpayOnboardingFields]); const incorporationTypeDefaultValue = reimbursementAccount?.achData?.additionalData?.corpay?.[APPLICANT_TYPE_ID] ?? reimbursementAccountDraft?.[APPLICANT_TYPE_ID] ?? ''; const businessCategoryDefaultValue = reimbursementAccount?.achData?.additionalData?.corpay?.[BUSINESS_CATEGORY] ?? reimbursementAccountDraft?.[BUSINESS_CATEGORY] ?? ''; @@ -60,7 +73,7 @@ function BusinessType({onNext, isEditing}: BusinessTypeProps) { {translate('businessInfoStep.whatTypeOfBusinessIsIt')} { - accumulator[currentValue.name] = currentValue.stringValue; - return accumulator; -}, {} as Record); - function PaymentVolume({onNext, isEditing}: PaymentVolumeProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const [corpayOnboardingFields] = useOnyx(ONYXKEYS.CORPAY_ONBOARDING_FIELDS); + + const annualVolumeRangeListOptions = useMemo(() => { + if (!corpayOnboardingFields) { + return {}; + } + + return corpayOnboardingFields.picklists.AnnualVolumeRange.reduce((accumulator, currentValue) => { + accumulator[currentValue.name] = currentValue.stringValue; + return accumulator; + }, {} as Record); + }, [corpayOnboardingFields]); const annualVolumeDefaultValue = reimbursementAccount?.achData?.additionalData?.corpay?.[ANNUAL_VOLUME] ?? reimbursementAccountDraft?.[ANNUAL_VOLUME] ?? ''; @@ -55,7 +61,7 @@ function PaymentVolume({onNext, isEditing}: PaymentVolumeProps) { {translate('businessInfoStep.whatsTheBusinessAnnualPayment')} ; + +/** CorpayOnboardingFields */ +type CorpayOnboardingFields = { + /** Fields for step 3 */ + company: string[]; + + /** Fields for step 4 */ + beneficialOwnerFields: string[]; + + /** Fields for step 5 */ + companyDirectorFields: string[]; + + /** Fields for step 5 */ + director: string[]; + + /** Fields for step 4 */ + owner: string[]; + + /** Picklists for step 3 */ + picklists: Record, Picklist>; +}; + +export default CorpayOnboardingFields; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 21fed723a07e..596452d4d9c2 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -17,6 +17,7 @@ import type {AddNewCompanyCardFeed, CompanyCardFeed} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; import type CorpayFields from './CorpayFields'; +import type CorpayOnboardingFields from './CorpayOnboardingFields'; import type Credentials from './Credentials'; import type Currency from './Currency'; import type {CurrencyList} from './Currency'; @@ -126,6 +127,7 @@ export type { CardOnWaitlist, Credentials, CorpayFields, + CorpayOnboardingFields, Currency, CurrencyList, CustomStatusDraft, From 39b8cf5c146e3e9bc4cca832eaa2712cf51c4ceb Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 9 Dec 2024 11:56:48 +0100 Subject: [PATCH 008/345] fix: small fix --- .../BusinessInfo/substeps/BusinessType.tsx | 4 ++-- .../BusinessInfo/substeps/Confirmation.tsx | 17 +++++++++++++---- .../BusinessInfo/substeps/PaymentVolume.tsx | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx index 439687548718..175e0e7c3085 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx @@ -27,7 +27,7 @@ function BusinessType({onNext, isEditing}: BusinessTypeProps) { const [corpayOnboardingFields] = useOnyx(ONYXKEYS.CORPAY_ONBOARDING_FIELDS); const incorporationTypeListOptions = useMemo(() => { - if (!corpayOnboardingFields) { + if (!corpayOnboardingFields?.picklists.ApplicantType) { return {}; } @@ -38,7 +38,7 @@ function BusinessType({onNext, isEditing}: BusinessTypeProps) { }, [corpayOnboardingFields]); const natureOfBusinessListOptions = useMemo(() => { - if (!corpayOnboardingFields) { + if (!corpayOnboardingFields?.picklists.NatureOfBusiness) { return {}; } diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx index 156967d63239..67c4cec921eb 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx @@ -9,7 +9,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; -import {annualVolumeRange, applicantType, natureOfBusiness} from '@pages/ReimbursementAccount/NonUSD/BusinessInfo/mockedCorpayLists'; import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -47,12 +46,22 @@ function Confirmation({onNext, onMove}: SubStepProps) { const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const [corpayOnboardingFields] = useOnyx(ONYXKEYS.CORPAY_ONBOARDING_FIELDS); const values = useMemo(() => getSubstepValues(BUSINESS_INFO_STEP_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); - const paymentVolume = useMemo(() => displayStringValue(annualVolumeRange, values[ANNUAL_VOLUME]), [values]); - const businessCategory = useMemo(() => displayStringValue(natureOfBusiness, values[BUSINESS_CATEGORY]), [values]); - const businessType = useMemo(() => displayStringValue(applicantType, values[APPLICANT_TYPE_ID]), [values]); + const paymentVolume = useMemo( + () => displayStringValue(corpayOnboardingFields?.picklists.AnnualVolumeRange ?? [], values[ANNUAL_VOLUME]), + [corpayOnboardingFields?.picklists.AnnualVolumeRange, values], + ); + const businessCategory = useMemo( + () => displayStringValue(corpayOnboardingFields?.picklists.NatureOfBusiness ?? [], values[BUSINESS_CATEGORY]), + [corpayOnboardingFields?.picklists.NatureOfBusiness, values], + ); + const businessType = useMemo( + () => displayStringValue(corpayOnboardingFields?.picklists.ApplicantType ?? [], values[APPLICANT_TYPE_ID]), + [corpayOnboardingFields?.picklists.ApplicantType, values], + ); return ( diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/PaymentVolume.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/PaymentVolume.tsx index 45308397478f..e4297b16fea5 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/PaymentVolume.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/PaymentVolume.tsx @@ -27,7 +27,7 @@ function PaymentVolume({onNext, isEditing}: PaymentVolumeProps) { const [corpayOnboardingFields] = useOnyx(ONYXKEYS.CORPAY_ONBOARDING_FIELDS); const annualVolumeRangeListOptions = useMemo(() => { - if (!corpayOnboardingFields) { + if (!corpayOnboardingFields?.picklists.AnnualVolumeRange) { return {}; } From 56570a7035d49a071e31476182e9663ab22d49b5 Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 10 Dec 2024 15:58:14 +0700 Subject: [PATCH 009/345] fix: Hmm it's not here in RHP is displayed after deleting a workspace --- src/libs/PolicyUtils.ts | 15 +++++++++ src/libs/ReportUtils.ts | 4 +-- tests/unit/PolicyUtilsTest.ts | 63 +++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index f6b277d69d6b..28027303007f 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -62,6 +62,14 @@ Onyx.connect({ callback: (value) => (allPolicies = value), }); +let currentUserEmail: string; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (val) => { + currentUserEmail = val?.email ?? ''; + }, +}); + Onyx.connect({ key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, callback: (value) => (activePolicyId = value), @@ -1131,6 +1139,12 @@ function areAllGroupPoliciesExpenseChatDisabled(policies = allPolicies) { return !groupPolicies.some((policy) => !!policy?.isPolicyExpenseChatEnabled); } +function getFilteredPolicies(policies: OnyxCollection) { + return Object.values(policies ?? {}).filter( + (policy) => policy && policy.type !== CONST.POLICY.TYPE.PERSONAL && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !!getPolicyRole(policy, currentUserEmail), + ); +} + export { canEditTaxRate, extractPolicyIDFromPath, @@ -1254,6 +1268,7 @@ export { getActivePolicy, isPolicyAccessible, areAllGroupPoliciesExpenseChatDisabled, + getFilteredPolicies, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9fff3ade23f9..0398ba048d85 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8212,9 +8212,7 @@ function createDraftTransactionAndNavigateToParticipantSelector(transactionID: s mccGroup, } as Transaction); - const filteredPolicies = Object.values(allPolicies ?? {}).filter( - (policy) => policy && policy.type !== CONST.POLICY.TYPE.PERSONAL && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - ); + const filteredPolicies = PolicyUtils.getFilteredPolicies(allPolicies); if (actionName === CONST.IOU.ACTION.CATEGORIZE) { const activePolicy = getPolicy(activePolicyID); diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 71830443063a..d63ea8cdd565 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -1,5 +1,15 @@ +import Onyx from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; import * as PolicyUtils from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {Policy} from '@src/types/onyx'; +import createRandomPolicy from '../utils/collections/policies'; +import * as TestHelper from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +const CARLOS_EMAIL = 'cmartins@expensifail.com'; +const CARLOS_ACCOUNT_ID = 1; function toLocaleDigitMock(dot: string): string { return dot; } @@ -81,4 +91,57 @@ describe('PolicyUtils', () => { }); }); }); + describe('getFilteredPolicies', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + initialKeyStates: { + // Given mock data for session + [ONYXKEYS.SESSION]: {accountID: CARLOS_ACCOUNT_ID, email: CARLOS_EMAIL}, + }, + }); + }); + + beforeEach(() => { + global.fetch = TestHelper.getGlobalFetchMock(); + return Onyx.clear().then(waitForBatchedUpdates); + }); + it('should return empty array', () => { + // Given mock data for policies + const policies = { + 1: { + ...createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE), + role: '', + }, + }; + // When calling getFilteredPolicies with policies + const result = PolicyUtils.getFilteredPolicies(policies as OnyxCollection); + // The result should be empty array since the policies contains only one policy which does not have role field. + expect(result.length).toBe(0); + }); + it('should return array contains policy which has id = 1', () => { + // Given mock data for policies + const randomPolicy1 = createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE); + const policies = { + 1: randomPolicy1, + }; + // When calling getFilteredPolicies with policies + const result = PolicyUtils.getFilteredPolicies(policies as OnyxCollection); + // The result should contains the policy which has id = 1 since it is a valid policy. + expect(result).toContainEqual(randomPolicy1); + }); + it('should return empty array', () => { + // Given mock data for policies + const policies = { + 1: { + ...createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE), + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }; + // When calling getFilteredPolicies with policies + const result = PolicyUtils.getFilteredPolicies(policies as OnyxCollection); + // The result should be empty array since the policies contains only one policy which has pendingAction is 'delete' . + expect(result).toEqual([]); + }); + }); }); From a01d0cbea604204d28db04cb2563424119cf55ae Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 10 Dec 2024 16:14:40 +0700 Subject: [PATCH 010/345] fix: remove space end line --- tests/unit/PolicyUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index d63ea8cdd565..26cda5ac4036 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -140,7 +140,7 @@ describe('PolicyUtils', () => { }; // When calling getFilteredPolicies with policies const result = PolicyUtils.getFilteredPolicies(policies as OnyxCollection); - // The result should be empty array since the policies contains only one policy which has pendingAction is 'delete' . + // The result should be empty array since the policies contains only one policy which has pendingAction is 'delete'. expect(result).toEqual([]); }); }); From 8d50fecc65f5b195ffbbd98fff836f79dd49497a Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 10 Dec 2024 16:18:24 +0700 Subject: [PATCH 011/345] fix: lint --- tests/unit/PolicyUtilsTest.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 26cda5ac4036..8d0945c629bb 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -3,7 +3,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import * as PolicyUtils from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {Policy} from '@src/types/onyx'; +import type {Policy} from '@src/types/onyx'; import createRandomPolicy from '../utils/collections/policies'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -109,6 +109,7 @@ describe('PolicyUtils', () => { it('should return empty array', () => { // Given mock data for policies const policies = { + // eslint-disable-next-line @typescript-eslint/naming-convention 1: { ...createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE), role: '', @@ -123,6 +124,7 @@ describe('PolicyUtils', () => { // Given mock data for policies const randomPolicy1 = createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE); const policies = { + // eslint-disable-next-line @typescript-eslint/naming-convention 1: randomPolicy1, }; // When calling getFilteredPolicies with policies @@ -133,6 +135,7 @@ describe('PolicyUtils', () => { it('should return empty array', () => { // Given mock data for policies const policies = { + // eslint-disable-next-line @typescript-eslint/naming-convention 1: { ...createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, From 57bbc2aacb84b79224964b81b2b2583b935642e7 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 11 Dec 2024 18:10:36 +0700 Subject: [PATCH 012/345] fix: reuse getActivePolicies --- src/libs/PolicyUtils.ts | 17 ++++++++--------- src/libs/ReportUtils.ts | 2 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 3 +-- tests/unit/PolicyUtilsTest.ts | 16 +++++++--------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 28027303007f..2dc436a4d76c 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -79,9 +79,15 @@ Onyx.connect({ * Filter out the active policies, which will exclude policies with pending deletion * These are policies that we can use to create reports with in NewDot. */ -function getActivePolicies(policies: OnyxCollection | null): Policy[] { +function getActivePolicies(policies: OnyxCollection | null, excludePersonalPolicy = false): Policy[] { return Object.values(policies ?? {}).filter( - (policy): policy is Policy => !!policy && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !!policy.name && !!policy.id, + (policy): policy is Policy => + !!policy && + policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && + !!policy.name && + !!policy.id && + (!excludePersonalPolicy || policy.type !== CONST.POLICY.TYPE.PERSONAL) && + !!getPolicyRole(policy, currentUserEmail), ); } /** @@ -1139,12 +1145,6 @@ function areAllGroupPoliciesExpenseChatDisabled(policies = allPolicies) { return !groupPolicies.some((policy) => !!policy?.isPolicyExpenseChatEnabled); } -function getFilteredPolicies(policies: OnyxCollection) { - return Object.values(policies ?? {}).filter( - (policy) => policy && policy.type !== CONST.POLICY.TYPE.PERSONAL && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !!getPolicyRole(policy, currentUserEmail), - ); -} - export { canEditTaxRate, extractPolicyIDFromPath, @@ -1268,7 +1268,6 @@ export { getActivePolicy, isPolicyAccessible, areAllGroupPoliciesExpenseChatDisabled, - getFilteredPolicies, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index be97bbb1e5e8..026d55966943 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8216,7 +8216,7 @@ function createDraftTransactionAndNavigateToParticipantSelector(transactionID: s mccGroup, } as Transaction); - const filteredPolicies = PolicyUtils.getFilteredPolicies(allPolicies); + const filteredPolicies = PolicyUtils.getActivePolicies(allPolicies, true); if (actionName === CONST.IOU.ACTION.CATEGORIZE) { const activePolicy = getPolicy(activePolicyID); diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 37686ba1c3a7..a23d35d955a2 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -64,8 +64,7 @@ function WorkspaceNewRoomPage() { const workspaceOptions = useMemo( () => - PolicyUtils.getActivePolicies(policies) - ?.filter((policy) => policy.type !== CONST.POLICY.TYPE.PERSONAL) + PolicyUtils.getActivePolicies(policies, true) .map((policy) => ({ label: policy.name, value: policy.id, diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 8d0945c629bb..927f1171d7c2 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -91,7 +91,7 @@ describe('PolicyUtils', () => { }); }); }); - describe('getFilteredPolicies', () => { + describe('getActivePolicies', () => { beforeAll(() => { Onyx.init({ keys: ONYXKEYS, @@ -107,7 +107,7 @@ describe('PolicyUtils', () => { return Onyx.clear().then(waitForBatchedUpdates); }); it('should return empty array', () => { - // Given mock data for policies + // Given mock data for policies, contains only one workspace does not has role prop. const policies = { // eslint-disable-next-line @typescript-eslint/naming-convention 1: { @@ -115,25 +115,23 @@ describe('PolicyUtils', () => { role: '', }, }; - // When calling getFilteredPolicies with policies - const result = PolicyUtils.getFilteredPolicies(policies as OnyxCollection); + const result = PolicyUtils.getActivePolicies(policies as OnyxCollection); // The result should be empty array since the policies contains only one policy which does not have role field. expect(result.length).toBe(0); }); it('should return array contains policy which has id = 1', () => { - // Given mock data for policies + // Given mock data for policies, contains only one control workspace const randomPolicy1 = createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE); const policies = { // eslint-disable-next-line @typescript-eslint/naming-convention 1: randomPolicy1, }; - // When calling getFilteredPolicies with policies - const result = PolicyUtils.getFilteredPolicies(policies as OnyxCollection); + const result = PolicyUtils.getActivePolicies(policies as OnyxCollection); // The result should contains the policy which has id = 1 since it is a valid policy. expect(result).toContainEqual(randomPolicy1); }); it('should return empty array', () => { - // Given mock data for policies + // Given mock data for policies, contains only one control workspace which is pending delete. const policies = { // eslint-disable-next-line @typescript-eslint/naming-convention 1: { @@ -142,7 +140,7 @@ describe('PolicyUtils', () => { }, }; // When calling getFilteredPolicies with policies - const result = PolicyUtils.getFilteredPolicies(policies as OnyxCollection); + const result = PolicyUtils.getActivePolicies(policies as OnyxCollection); // The result should be empty array since the policies contains only one policy which has pendingAction is 'delete'. expect(result).toEqual([]); }); From 9f17db9785029a406661985d97a59aac8ebbd3f3 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 11 Dec 2024 18:27:10 +0700 Subject: [PATCH 013/345] fix: unit test --- tests/unit/PolicyUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 927f1171d7c2..b8c2e562b21e 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -121,7 +121,7 @@ describe('PolicyUtils', () => { }); it('should return array contains policy which has id = 1', () => { // Given mock data for policies, contains only one control workspace - const randomPolicy1 = createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE); + const randomPolicy1 = {...createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE), pendingAction: null}; const policies = { // eslint-disable-next-line @typescript-eslint/naming-convention 1: randomPolicy1, From 0382872a89f4d058bf38c3377ecdb1a51e12cdc8 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 11 Dec 2024 18:42:56 +0700 Subject: [PATCH 014/345] fix: remove comment --- tests/unit/PolicyUtilsTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index b8c2e562b21e..880e21ea0aca 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -139,7 +139,6 @@ describe('PolicyUtils', () => { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, }; - // When calling getFilteredPolicies with policies const result = PolicyUtils.getActivePolicies(policies as OnyxCollection); // The result should be empty array since the policies contains only one policy which has pendingAction is 'delete'. expect(result).toEqual([]); From 4d60d3a7e88ddbc7592652b75aafea7a40e2e6c7 Mon Sep 17 00:00:00 2001 From: war-in Date: Wed, 11 Dec 2024 15:46:11 +0100 Subject: [PATCH 015/345] fix `Not found page` on shortcuts --- src/libs/actions/Session/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index e50eba7e596f..51c1bca18b83 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -557,7 +557,7 @@ function signInAfterTransitionFromOldDot(transitionURL: string) { Log.hmmm('[HybridApp] Initialization of HybridApp has failed. Forcing transition', {error}); }) .finally(() => { - resolve(`${route}?singleNewDotEntry=${isSingleNewDotEntry}` as Route); + resolve(`${route}${isSingleNewDotEntry === 'true' ? '?singleNewDotEntry=true' : ''}` as Route); }); }); From 1e6ee20d898833b668bb08f84b4958467f49c1df Mon Sep 17 00:00:00 2001 From: truph01 Date: Thu, 12 Dec 2024 03:08:36 +0700 Subject: [PATCH 016/345] fix: update comments --- tests/unit/PolicyUtilsTest.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 880e21ea0aca..e13db0f3a04b 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -96,7 +96,6 @@ describe('PolicyUtils', () => { Onyx.init({ keys: ONYXKEYS, initialKeyStates: { - // Given mock data for session [ONYXKEYS.SESSION]: {accountID: CARLOS_ACCOUNT_ID, email: CARLOS_EMAIL}, }, }); @@ -107,7 +106,7 @@ describe('PolicyUtils', () => { return Onyx.clear().then(waitForBatchedUpdates); }); it('should return empty array', () => { - // Given mock data for policies, contains only one workspace does not has role prop. + // Given a user with a single archived paid policy. const policies = { // eslint-disable-next-line @typescript-eslint/naming-convention 1: { @@ -115,23 +114,23 @@ describe('PolicyUtils', () => { role: '', }, }; - const result = PolicyUtils.getActivePolicies(policies as OnyxCollection); - // The result should be empty array since the policies contains only one policy which does not have role field. + const result = PolicyUtils.getActivePolicies(policies as OnyxCollection, true); + // The result should be empty array since the policies contains only archived paid policy. expect(result.length).toBe(0); }); it('should return array contains policy which has id = 1', () => { - // Given mock data for policies, contains only one control workspace + // Given a user with only a paid policy. const randomPolicy1 = {...createRandomPolicy(1, CONST.POLICY.TYPE.CORPORATE), pendingAction: null}; const policies = { // eslint-disable-next-line @typescript-eslint/naming-convention 1: randomPolicy1, }; - const result = PolicyUtils.getActivePolicies(policies as OnyxCollection); - // The result should contains the policy which has id = 1 since it is a valid policy. + const result = PolicyUtils.getActivePolicies(policies as OnyxCollection, true); + // The result should contain the mock paid policy. expect(result).toContainEqual(randomPolicy1); }); it('should return empty array', () => { - // Given mock data for policies, contains only one control workspace which is pending delete. + // Given a user with only one control workspace which is pending delete. const policies = { // eslint-disable-next-line @typescript-eslint/naming-convention 1: { @@ -139,8 +138,8 @@ describe('PolicyUtils', () => { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, }; - const result = PolicyUtils.getActivePolicies(policies as OnyxCollection); - // The result should be empty array since the policies contains only one policy which has pendingAction is 'delete'. + const result = PolicyUtils.getActivePolicies(policies as OnyxCollection, true); + // The result should be empty array since there is only one policy which has pendingAction is 'delete'. expect(result).toEqual([]); }); }); From ea29168f4336ff0657b9fa8a6960a4f3d0f75b55 Mon Sep 17 00:00:00 2001 From: Tomasz Lesniakiewicz Date: Thu, 12 Dec 2024 22:41:10 +0100 Subject: [PATCH 017/345] add logic to scrol when message appears --- src/pages/home/report/ReportActionsList.tsx | 35 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 24119bedaa8c..254099b341d9 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -164,6 +164,7 @@ function ReportActionsList({ const lastMessageTime = useRef(null); const [isVisible, setIsVisible] = useState(Visibility.isVisible); const isFocused = useIsFocused(); + const [pendingBottomScroll, setPendingBottomScroll] = useState(false); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`); const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID}); @@ -426,6 +427,12 @@ function ReportActionsList({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, []); + const shouldScrollToBottom = useCallback(() => { + const prevActions = Object.values(prevSortedVisibleReportActionsObjects); + const lastPrevAction = prevActions.at(0); // Safely access the first element + return lastAction?.reportActionID === lastPrevAction?.reportActionID; + }, [prevSortedVisibleReportActionsObjects, lastAction]); + const scrollToBottomForCurrentUserAction = useCallback( (isFromCurrentUser: boolean) => { // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where @@ -433,6 +440,11 @@ function ReportActionsList({ if (!isFromCurrentUser) { return; } + + if (!isFromCurrentUser || scrollingVerticalOffset.current === 0) { + return; + } + if (!hasNewestReportActionRef.current) { if (isInNarrowPaneModal) { return; @@ -440,10 +452,29 @@ function ReportActionsList({ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID)); return; } - InteractionManager.runAfterInteractions(() => reportScrollManager.scrollToBottom()); + + if (shouldScrollToBottom()) { + setPendingBottomScroll(true); + } else { + InteractionManager.runAfterInteractions(() => reportScrollManager.scrollToBottom()); + } }, - [isInNarrowPaneModal, reportScrollManager, report.reportID], + [isInNarrowPaneModal, reportScrollManager, report.reportID, shouldScrollToBottom], ); + + useEffect(() => { + if (!pendingBottomScroll) { + return; + } + + if (shouldScrollToBottom()) { + InteractionManager.runAfterInteractions(() => { + reportScrollManager.scrollToBottom(); + setPendingBottomScroll(false); + }); + } + }, [pendingBottomScroll, prevSortedVisibleReportActionsObjects, reportScrollManager, shouldScrollToBottom]); + useEffect(() => { // Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function? // Answer: On web, when navigating to another report screen, the previous report screen doesn't get unmounted, From 768c6d0cf15f36ed3031f9f577db21359f1b5e88 Mon Sep 17 00:00:00 2001 From: Tomasz Lesniakiewicz Date: Thu, 12 Dec 2024 23:26:21 +0100 Subject: [PATCH 018/345] adjust isNewMessageDisplayed --- src/pages/home/report/ReportActionsList.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 254099b341d9..fd008c69ef4c 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -427,19 +427,16 @@ function ReportActionsList({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, []); - const shouldScrollToBottom = useCallback(() => { + const isNewMessageDisplayed = useCallback(() => { const prevActions = Object.values(prevSortedVisibleReportActionsObjects); - const lastPrevAction = prevActions.at(0); // Safely access the first element - return lastAction?.reportActionID === lastPrevAction?.reportActionID; + const lastPrevAction = prevActions.at(0); + return lastAction?.reportActionID !== lastPrevAction?.reportActionID; }, [prevSortedVisibleReportActionsObjects, lastAction]); const scrollToBottomForCurrentUserAction = useCallback( (isFromCurrentUser: boolean) => { // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where // they are now in the list. - if (!isFromCurrentUser) { - return; - } if (!isFromCurrentUser || scrollingVerticalOffset.current === 0) { return; @@ -453,13 +450,13 @@ function ReportActionsList({ return; } - if (shouldScrollToBottom()) { + if (!isNewMessageDisplayed()) { setPendingBottomScroll(true); } else { InteractionManager.runAfterInteractions(() => reportScrollManager.scrollToBottom()); } }, - [isInNarrowPaneModal, reportScrollManager, report.reportID, shouldScrollToBottom], + [isInNarrowPaneModal, reportScrollManager, report.reportID, isNewMessageDisplayed], ); useEffect(() => { @@ -467,13 +464,13 @@ function ReportActionsList({ return; } - if (shouldScrollToBottom()) { + if (isNewMessageDisplayed()) { InteractionManager.runAfterInteractions(() => { reportScrollManager.scrollToBottom(); setPendingBottomScroll(false); }); } - }, [pendingBottomScroll, prevSortedVisibleReportActionsObjects, reportScrollManager, shouldScrollToBottom]); + }, [pendingBottomScroll, prevSortedVisibleReportActionsObjects, reportScrollManager, isNewMessageDisplayed]); useEffect(() => { // Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function? From 2f27b87127add2731072639e790f1d57b75cc3d5 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 13 Dec 2024 16:33:24 +0700 Subject: [PATCH 019/345] Update tests/unit/PolicyUtilsTest.ts Co-authored-by: Joel Davies --- tests/unit/PolicyUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index e13db0f3a04b..11497f87f8a8 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -115,7 +115,7 @@ describe('PolicyUtils', () => { }, }; const result = PolicyUtils.getActivePolicies(policies as OnyxCollection, true); - // The result should be empty array since the policies contains only archived paid policy. + // The result should be an empty array since we have no active policies. expect(result.length).toBe(0); }); it('should return array contains policy which has id = 1', () => { From 814bb3da28a9543b2dc0f5fb2a539660fe9b5e28 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 13 Dec 2024 16:33:40 +0700 Subject: [PATCH 020/345] Update tests/unit/PolicyUtilsTest.ts Co-authored-by: Joel Davies --- tests/unit/PolicyUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 11497f87f8a8..b1560da49aa5 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -126,7 +126,7 @@ describe('PolicyUtils', () => { 1: randomPolicy1, }; const result = PolicyUtils.getActivePolicies(policies as OnyxCollection, true); - // The result should contain the mock paid policy. + // The result should contain the mock paid policy, since it is our only active paid policy. expect(result).toContainEqual(randomPolicy1); }); it('should return empty array', () => { From 73cfbfa593a521560090a5008fbb91f5202c9e2d Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 13 Dec 2024 16:33:59 +0700 Subject: [PATCH 021/345] Update tests/unit/PolicyUtilsTest.ts Co-authored-by: Joel Davies --- tests/unit/PolicyUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index b1560da49aa5..488b7f797f0d 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -139,7 +139,7 @@ describe('PolicyUtils', () => { }, }; const result = PolicyUtils.getActivePolicies(policies as OnyxCollection, true); - // The result should be empty array since there is only one policy which has pendingAction is 'delete'. + // The result should be an empty array since there is only one policy which is pending deletion, so we have no active paid policies. expect(result).toEqual([]); }); }); From 99d155a4947fcbeeb1039a7521a52b0bb00a0c0d Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 13 Dec 2024 12:00:54 +0100 Subject: [PATCH 022/345] make adhoc locally buildable --- scripts/run-build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run-build.sh b/scripts/run-build.sh index 7689aabbbf59..13cc2d5b7d07 100755 --- a/scripts/run-build.sh +++ b/scripts/run-build.sh @@ -25,7 +25,7 @@ if [ "$#" -eq 1 ]; then BUILD="$1" elif [ "$#" -eq 2 ]; then if [ "$1" == "--new-dot" ]; then - BUILD="$2" + BUILD="$2" NEW_DOT_FLAG="true" elif [ "$2" == "--new-dot" ]; then BUILD="$1" @@ -45,7 +45,7 @@ IS_HYBRID_APP_REPO=$(scripts/is-hybrid-app.sh) IOS_MODE="Debug" ANDROID_MODE="Debug" SCHEME="Expensify" - APP_ID="org.me.mobiexpensifyg" + APP_ID="org.me.mobiexpensifyg.dev" echo -e "\n${GREEN}Starting a HybridApp build!${NC}" PROJECT_ROOT_PATH="Mobile-Expensify/" From beb05d17e1553b253c644fabe4a32b4f6ce92a80 Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 13 Dec 2024 13:40:03 +0100 Subject: [PATCH 023/345] bump submodule --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index af549932c171..58a176c12169 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit af549932c17151a57655466e4912e038b21f501a +Subproject commit 58a176c121690ac0cc11b00e8672211134f6dba4 From 3f76d599341d1bf361342d68ae7f669633709cd7 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 16 Dec 2024 11:46:37 +0100 Subject: [PATCH 024/345] feat: hold --- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ .../BusinessInfo/substeps/Confirmation.tsx | 13 +++++++++++ .../BusinessInfo/substeps/PaymentVolume.tsx | 23 ++++++++++++++++++- 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index e1b52a98fcfe..5ab6b05c02c9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2216,15 +2216,19 @@ const translations = { businessCategory: 'Business category', annualPaymentVolume: 'Annual payment volume', annualPaymentVolumeInCurrency: ({currencyCode}: CurrencyCodeParams) => `Annual payment volume in ${currencyCode}`, + tradeVolumeRange: 'Trade volume range', + tradeVolumeRangeInCurrency: ({currencyCode}: CurrencyCodeParams) => `Trade volume range in ${currencyCode}`, selectIncorporationType: 'Select incorporation type', selectBusinessCategory: 'Select business category', selectAnnualPaymentVolume: 'Select annual payment volume', selectIncorporationCountry: 'Select incorporation country', selectIncorporationState: 'Select incorporation state', + selectTradeVolumeRange: 'Select trade volume range', findIncorporationType: 'Find incorporation type', findBusinessCategory: 'Find business category', findAnnualPaymentVolume: 'Find annual payment volume', findIncorporationState: 'Find incorporation state', + findTradeVolumeRange: 'Find trade volume range', error: { registrationNumber: 'Please provide a valid registration number.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 63a814fe0e67..a81f1eb2114d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2240,15 +2240,19 @@ const translations = { businessCategory: 'Categoría de la empresa', annualPaymentVolume: 'Volumen anual de pagos', annualPaymentVolumeInCurrency: ({currencyCode}: CurrencyCodeParams) => `Volumen anual de pagos en ${currencyCode}`, + tradeVolumeRange: 'Trade volume range', + tradeVolumeRangeInCurrency: ({currencyCode}: CurrencyCodeParams) => `Trade volume range in ${currencyCode}`, selectIncorporationType: 'Seleccione tipo de constitución', selectBusinessCategory: 'Seleccione categoría de la empresa', selectAnnualPaymentVolume: 'Seleccione volumen anual de pagos', selectIncorporationCountry: 'Seleccione país de constitución', selectIncorporationState: 'Seleccione estado de constitución', + selectTradeVolumeRange: 'Select trade volume range', findIncorporationType: 'Buscar tipo de constitución', findBusinessCategory: 'Buscar categoría de la empresa', findAnnualPaymentVolume: 'Buscar volumen anual de pagos', findIncorporationState: 'Buscar estado de constitución', + findTradeVolumeRange: 'Find trade volume range', error: { registrationNumber: 'Por favor, proporcione un número de registro válido.', }, diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx index 67c4cec921eb..91f3812b909c 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx @@ -29,6 +29,7 @@ const { FORMATION_INCORPORATION_COUNTRY_CODE, ANNUAL_VOLUME, APPLICANT_TYPE_ID, + TRADE_VOLUME, BUSINESS_CATEGORY, } = INPUT_IDS.ADDITIONAL_DATA.CORPAY; @@ -62,6 +63,10 @@ function Confirmation({onNext, onMove}: SubStepProps) { () => displayStringValue(corpayOnboardingFields?.picklists.ApplicantType ?? [], values[APPLICANT_TYPE_ID]), [corpayOnboardingFields?.picklists.ApplicantType, values], ); + const tradeVolumeRange = useMemo( + () => displayStringValue(corpayOnboardingFields?.picklists.TradeVolumeRange ?? [], values[TRADE_VOLUME]), + [corpayOnboardingFields?.picklists.TradeVolumeRange, values], + ); return ( @@ -151,6 +156,14 @@ function Confirmation({onNext, onMove}: SubStepProps) { onMove(7); }} /> + { + onMove(7); + }} + />