diff --git a/src/CONST.ts b/src/CONST.ts index 8b2c173c7ca8..72486e37abbe 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -594,6 +594,7 @@ const CONST = { ALLOWED_FILE_TYPES: ['pdf', 'jpg', 'jpeg', 'png'], FILE_LIMIT: 10, TOTAL_FILES_SIZE_LIMIT: 5242880, + PURPOSE_OF_TRANSACTION_ID: 'Intercompany_Payment', STEP: { COUNTRY: 'CountryStep', BANK_INFO: 'BankInfoStep', @@ -603,6 +604,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 d39213943c82..8fa6ab602960 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -461,12 +461,15 @@ const ONYXKEYS = { /** The user's Concierge reportID */ CONCIERGE_REPORT_ID: 'conciergeReportID', - /* Corpay fieds to be used in the bank account creation setup */ + /** Corpay fields to be used in the bank account creation setup */ CORPAY_FIELDS: 'corpayFields', /** The user's session that will be preserved when using imported state */ PRESERVED_USER_SESSION: 'preservedUserSession', + /** Corpay onboarding fields used in steps 3-5 in the global reimbursements */ + CORPAY_ONBOARDING_FIELDS: 'corpayOnboardingFields', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -1048,6 +1051,7 @@ type OnyxValuesMapping = { [ONYXKEYS.CORPAY_FIELDS]: OnyxTypes.CorpayFields; [ONYXKEYS.PRESERVED_USER_SESSION]: OnyxTypes.Session; [ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING]: OnyxTypes.DismissedProductTraining; + [ONYXKEYS.CORPAY_ONBOARDING_FIELDS]: OnyxTypes.CorpayOnboardingFields; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/languages/en.ts b/src/languages/en.ts index 0a9f71e5888d..30e377d6182d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2253,11 +2253,14 @@ 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?", + whatsYourExpectedAverageReimbursements: "What's your expected average reimbursement amount?", registrationNumber: 'Registration number', + taxIDEIN: 'Tax ID/EIN number', businessAddress: 'Business address', businessType: 'Business type', incorporation: 'Incorporation', @@ -2266,15 +2269,22 @@ const translations = { businessCategory: 'Business category', annualPaymentVolume: 'Annual payment volume', annualPaymentVolumeInCurrency: ({currencyCode}: CurrencyCodeParams) => `Annual payment volume in ${currencyCode}`, + averageReimbursementAmount: 'Average reimbursement amount', + averageReimbursementAmountInCurrency: ({currencyCode}: CurrencyCodeParams) => `Average reimbursement amount in ${currencyCode}`, selectIncorporationType: 'Select incorporation type', selectBusinessCategory: 'Select business category', selectAnnualPaymentVolume: 'Select annual payment volume', selectIncorporationCountry: 'Select incorporation country', selectIncorporationState: 'Select incorporation state', + selectAverageReimbursement: 'Select average reimbursement amount', findIncorporationType: 'Find incorporation type', findBusinessCategory: 'Find business category', findAnnualPaymentVolume: 'Find annual payment volume', findIncorporationState: 'Find incorporation state', + findAverageReimbursement: 'Find average reimbursement amount', + 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 225895a5424e..457d721633ce 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2277,11 +2277,14 @@ 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 ID/EIN/VAT/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?', + whatsYourExpectedAverageReimbursements: '¿Cuál es el monto promedio esperado de reembolso?', 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', @@ -2290,15 +2293,22 @@ const translations = { businessCategory: 'Categoría de la empresa', annualPaymentVolume: 'Volumen anual de pagos', annualPaymentVolumeInCurrency: ({currencyCode}: CurrencyCodeParams) => `Volumen anual de pagos en ${currencyCode}`, + averageReimbursementAmount: 'Monto promedio de reembolso', + averageReimbursementAmountInCurrency: ({currencyCode}: CurrencyCodeParams) => `Monto promedio de reembolso en ${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', + selectAverageReimbursement: 'Selecciona el monto promedio de reembolso', 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', + findAverageReimbursement: 'Encuentra el monto promedio de reembolso', + error: { + registrationNumber: 'Por favor, proporciona 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..8b217e253282 --- /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/SaveCorpayOnboardingCompanyDetailsParams.ts b/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts new file mode 100644 index 000000000000..d48d1efcfaa9 --- /dev/null +++ b/src/libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams.ts @@ -0,0 +1,31 @@ +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; + fundDestinationCountries: string; + fundSourceCountries: 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 type {SaveCorpayOnboardingCompanyDetails, SaveCorpayOnboardingCompanyDetailsParams}; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 4d02027b720a..af86552e4feb 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -363,3 +363,5 @@ export type {default as DismissProductTrainingParams} from './DismissProductTrai export type {default as OpenWorkspacePlanPageParams} from './OpenWorkspacePlanPage'; export type {default as ResetSMSDeliveryFailureStatusParams} from './ResetSMSDeliveryFailureStatusParams'; export type {default as CreatePerDiemRequestParams} from './CreatePerDiemRequestParams'; +export type {default as GetCorpayOnboardingFieldsParams} from './GetCorpayOnboardingFieldsParams'; +export type {SaveCorpayOnboardingCompanyDetailsParams} from './SaveCorpayOnboardingCompanyDetailsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 72251fd817dc..f826cf78ab4c 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -448,6 +448,7 @@ const WRITE_COMMANDS = { VALIDATE_USER_AND_GET_ACCESSIBLE_POLICIES: 'ValidateUserAndGetAccessiblePolicies', DISMISS_PRODUCT_TRAINING: 'DismissProductTraining', RESET_SMS_DELIVERY_FAILURE_STATUS: 'ResetSMSDeliveryFailureStatus', + SAVE_CORPAY_ONBOARDING_COMPANY_DETAILS: 'SaveCorpayOnboardingCompanyDetails', } as const; type WriteCommand = ValueOf; @@ -776,6 +777,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.REQUEST_TAX_EXEMPTION]: null; [WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT]: Parameters.UpdateWorkspaceCustomUnitParams; [WRITE_COMMANDS.RESET_SMS_DELIVERY_FAILURE_STATUS]: Parameters.ResetSMSDeliveryFailureStatusParams; + [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; @@ -971,6 +973,7 @@ const READ_COMMANDS = { OPEN_CARD_DETAILS_PAGE: 'OpenCardDetailsPage', GET_ASSIGNED_SUPPORT_DATA: 'GetAssignedSupportData', OPEN_WORKSPACE_PLAN_PAGE: 'OpenWorkspacePlanPage', + GET_CORPAY_ONBOARDING_FIELDS: 'GetCorpayOnboardingFields', } as const; type ReadCommand = ValueOf; @@ -1037,6 +1040,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_CARD_DETAILS_PAGE]: Parameters.OpenCardDetailsPageParams; [READ_COMMANDS.GET_ASSIGNED_SUPPORT_DATA]: Parameters.GetAssignedSupportDataParams; [READ_COMMANDS.OPEN_WORKSPACE_PLAN_PAGE]: Parameters.OpenWorkspacePlanPageParams; + [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 ea1ecf319cc2..3d0cc1113511 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -5,12 +5,13 @@ 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'; +import {getMonthFromExpirationDateString, getYearFromExpirationDateString} from './CardUtils'; import DateUtils from './DateUtils'; -import * as Localize from './Localize'; -import * as LoginUtils from './LoginUtils'; +import {translateLocal} from './Localize'; +import {appendCountryCode, getPhoneNumberWithoutSpecialChars} from './LoginUtils'; import {parsePhoneNumber} from './PhoneNumber'; import StringUtils from './StringUtils'; @@ -118,7 +119,7 @@ function getFieldRequiredErrors(values: FormOnyxVal return; } - errors[fieldKey] = Localize.translateLocal('common.error.fieldRequired'); + errors[fieldKey] = translateLocal('common.error.fieldRequired'); }); return errors; @@ -137,7 +138,7 @@ function isValidExpirationDate(string: string): boolean { } // Use the last of the month to check if the expiration date is in the future or not - const expirationDate = `${CardUtils.getYearFromExpirationDateString(string)}-${CardUtils.getMonthFromExpirationDateString(string)}-01`; + const expirationDate = `${getYearFromExpirationDateString(string)}-${getMonthFromExpirationDateString(string)}-01`; return isAfter(new Date(expirationDate), endOfMonth(new Date())); } @@ -202,7 +203,7 @@ function getAgeRequirementError(date: string, minimumAge: number, maximumAge: nu const testDate = parse(date, CONST.DATE.FNS_FORMAT_STRING, currentDate); if (!isValid(testDate)) { - return Localize.translateLocal('common.error.dateInvalid'); + return translateLocal('common.error.dateInvalid'); } const maximalDate = subYears(currentDate, minimumAge); @@ -213,10 +214,10 @@ function getAgeRequirementError(date: string, minimumAge: number, maximumAge: nu } if (isSameDay(testDate, maximalDate) || isAfter(testDate, maximalDate)) { - return Localize.translateLocal('privatePersonalDetails.error.dateShouldBeBefore', {dateString: format(maximalDate, CONST.DATE.FNS_FORMAT_STRING)}); + return translateLocal('privatePersonalDetails.error.dateShouldBeBefore', {dateString: format(maximalDate, CONST.DATE.FNS_FORMAT_STRING)}); } - return Localize.translateLocal('privatePersonalDetails.error.dateShouldBeAfter', {dateString: format(minimalDate, CONST.DATE.FNS_FORMAT_STRING)}); + return translateLocal('privatePersonalDetails.error.dateShouldBeAfter', {dateString: format(minimalDate, CONST.DATE.FNS_FORMAT_STRING)}); } /** @@ -228,14 +229,14 @@ function getDatePassedError(inputDate: string): string { // If input date is not valid, return an error if (!isValid(parsedDate)) { - return Localize.translateLocal('common.error.dateInvalid'); + return translateLocal('common.error.dateInvalid'); } // Clear time for currentDate so comparison is based solely on the date currentDate.setHours(0, 0, 0, 0); if (parsedDate < currentDate) { - return Localize.translateLocal('common.error.dateInvalid'); + return translateLocal('common.error.dateInvalid'); } return ''; @@ -318,7 +319,7 @@ function isValidTwoFactorCode(code: string): boolean { * Checks whether a value is a numeric string including `(`, `)`, `-` and optional leading `+` */ function isNumericWithSpecialChars(input: string): boolean { - return /^\+?[\d\\+]*$/.test(LoginUtils.getPhoneNumberWithoutSpecialChars(input)); + return /^\+?[\d\\+]*$/.test(getPhoneNumberWithoutSpecialChars(input)); } /** @@ -515,7 +516,7 @@ function isValidEmail(email: string): boolean { * @param phoneNumber */ function isValidPhoneInternational(phoneNumber: string): boolean { - const phoneNumberWithCountryCode = LoginUtils.appendCountryCode(phoneNumber); + const phoneNumberWithCountryCode = appendCountryCode(phoneNumber); const parsedPhoneNumber = parsePhoneNumber(phoneNumberWithCountryCode); return parsedPhoneNumber.possible && Str.isValidE164Phone(parsedPhoneNumber.number?.e164 ?? ''); @@ -554,6 +555,91 @@ function isValidOwnershipPercentage(value: string, totalOwnedPercentage: Record< return isValidNumber && isTotalSumValid; } +/** + * Validates the given value if it is correct ABN number - https://abr.business.gov.au/Help/AbnFormat + * @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 - https://asic.gov.au/for-business/registering-a-company/steps-to-register-a-company/australian-company-numbers/australian-company-number-digit-check/ + * @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, @@ -602,4 +688,5 @@ export { isValidPhoneInternational, isValidZipCodeInternational, isValidOwnershipPercentage, + isValidRegistrationNumber, }; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index e2bd75d43609..16397e117807 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -11,11 +11,13 @@ import type { ValidateBankAccountWithTransactionsParams, VerifyIdentityForBankAccountParams, } from '@libs/API/parameters'; +import type {SaveCorpayOnboardingCompanyDetails} from '@libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; import {translateLocal} 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'; @@ -446,6 +448,53 @@ function createCorpayBankAccount(fields: ReimbursementAccountForm) { return API.write(WRITE_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY, parameters, onyxData); } +function getCorpayOnboardingFields(country: Country | '') { + return API.read(READ_COMMANDS.GET_CORPAY_ONBOARDING_FIELDS, {countryISO: country}); +} + +function saveCorpayOnboardingCompanyDetails(parameters: SaveCorpayOnboardingCompanyDetails, bankAccountID: number) { + const formattedParams = { + inputs: JSON.stringify(parameters), + bankAccountID, + }; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isSavingCorpayOnboardingCompanyFields: true, + errors: null, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isSavingCorpayOnboardingCompanyFields: false, + isSuccess: true, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isSavingCorpayOnboardingCompanyFields: false, + isSuccess: false, + errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + ], + }; + + return API.write(WRITE_COMMANDS.SAVE_CORPAY_ONBOARDING_COMPANY_DETAILS, formattedParams, onyxData); +} + function clearReimbursementAccount() { Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, null); } @@ -454,6 +503,10 @@ function clearReimbursementAccountBankCreation() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {isCreateCorpayBankAccount: null, isSuccess: null, isLoading: null}); } +function clearReimbursementAccountSaveCorpayOnboardingCompanyDetails() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {isSuccess: null, isSavingCorpayOnboardingCompanyFields: null}); +} + /** * Function to display and fetch data for Reimbursement Account step * @param stepToOpen - current step to open @@ -682,6 +735,9 @@ export { validatePlaidSelection, getCorpayBankAccountFields, clearReimbursementAccountBankCreation, + getCorpayOnboardingFields, + saveCorpayOnboardingCompanyDetails, + clearReimbursementAccountSaveCorpayOnboardingCompanyDetails, }; export type {BusinessAddress, PersonalAddress}; diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx index 190f40181ab4..d8c7d2cccddf 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx @@ -5,7 +5,7 @@ import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; -import * as BankAccounts from '@userActions/BankAccounts'; +import {getCorpayBankAccountFields} from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; @@ -41,7 +41,7 @@ function BankInfo({onBackButtonPress, onSubmit}: BankInfoProps) { }; useEffect(() => { - BankAccounts.getCorpayBankAccountFields(country, currency); + getCorpayBankAccountFields(country, currency); }, [country, currency]); const bodyContent: Array> = diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx index f10ee293a924..cc3a9cd8d0a1 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx @@ -8,7 +8,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import type {BankInfoSubStepProps} from '@pages/ReimbursementAccount/NonUSD/BankInfo/types'; import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; -import * as BankAccounts from '@userActions/BankAccounts'; +import {clearReimbursementAccountBankCreation, createCorpayBankAccount} from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form/ReimbursementAccountForm'; @@ -72,7 +72,7 @@ function Confirmation({onNext, onMove, corpayFields}: BankInfoSubStepProps) { const handleSubmit = () => { const {formFields, isLoading, isSuccess, ...corpayData} = corpayFields ?? {}; - BankAccounts.createCorpayBankAccount({...reimbursementAccountDraft, ...corpayData} as ReimbursementAccountForm); + createCorpayBankAccount({...reimbursementAccountDraft, ...corpayData} as ReimbursementAccountForm); }; useEffect(() => { @@ -83,10 +83,10 @@ function Confirmation({onNext, onMove, corpayFields}: BankInfoSubStepProps) { if (reimbursementAccount?.isSuccess) { onNext(); - BankAccounts.clearReimbursementAccountBankCreation(); + clearReimbursementAccountBankCreation(); } - return () => BankAccounts.clearReimbursementAccountBankCreation(); + return () => clearReimbursementAccountBankCreation(); }, [onNext, reimbursementAccount?.errors, reimbursementAccount?.isCreateCorpayBankAccount, reimbursementAccount?.isLoading, reimbursementAccount?.isSuccess]); return ( diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/UploadStatement.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/UploadStatement.tsx index 0f971566b815..2815f9b85413 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/UploadStatement.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/UploadStatement.tsx @@ -12,9 +12,9 @@ import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccoun import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import {getLastFourDigits} from '@libs/BankAccountUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; +import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import WhyLink from '@pages/ReimbursementAccount/NonUSD/WhyLink'; -import * as FormActions from '@userActions/FormActions'; +import {clearErrorFields, setDraftValues, setErrorFields} from '@userActions/FormActions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; @@ -38,7 +38,7 @@ function UploadStatement({onNext, isEditing}: UploadStatementProps) { const [uploadedIDs, setUploadedID] = useState(defaultValues[BANK_STATEMENT]); const validate = useCallback((values: FormOnyxValues): FormInputErrors => { - const baseError = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const baseError = getFieldRequiredErrors(values, STEP_FIELDS); if (baseError) { return baseError; @@ -54,23 +54,23 @@ function UploadStatement({onNext, isEditing}: UploadStatementProps) { }); const handleSelectIDFile = (files: FileObject[]) => { - FormActions.setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[BANK_STATEMENT]: [...uploadedIDs, ...files]}); + setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[BANK_STATEMENT]: [...uploadedIDs, ...files]}); setUploadedID((prev) => [...prev, ...files]); }; const handleRemoveIDFile = (fileName: string) => { const newUploadedIDs = uploadedIDs.filter((file) => file.name !== fileName); - FormActions.setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[BANK_STATEMENT]: newUploadedIDs}); + setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[BANK_STATEMENT]: newUploadedIDs}); setUploadedID(newUploadedIDs); }; const setUploadError = (error: string) => { if (!error) { - FormActions.clearErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM); + clearErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM); return; } - FormActions.setErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[BANK_STATEMENT]: {onUpload: error}}); + setErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[BANK_STATEMENT]: {onUpload: error}}); }; return ( diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx index 61b42789daea..c9a8beb685d5 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/BusinessInfo.tsx @@ -1,11 +1,18 @@ import type {ComponentType} from 'react'; -import React 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 type {SaveCorpayOnboardingCompanyDetails} from '@libs/API/parameters/SaveCorpayOnboardingCompanyDetailsParams'; +import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; +import {clearReimbursementAccountSaveCorpayOnboardingCompanyDetails, getCorpayOnboardingFields, saveCorpayOnboardingCompanyDetails} 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 AverageReimbursement from './substeps/AverageReimbursement'; import BusinessType from './substeps/BusinessType'; import Confirmation from './substeps/Confirmation'; import ContactInformation from './substeps/ContactInformation'; @@ -13,6 +20,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 */ @@ -22,14 +30,90 @@ type BusinessInfoProps = { onSubmit: () => void; }; -const bodyContent: Array> = [Name, Address, ContactInformation, RegistrationNumber, IncorporationLocation, BusinessType, PaymentVolume, Confirmation]; +type BusinessInfoParamsPartial = Omit; + +const bodyContent: Array> = [ + Name, + Address, + ContactInformation, + RegistrationNumber, + TaxIDEINNumber, + IncorporationLocation, + BusinessType, + PaymentVolume, + AverageReimbursement, + 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, + COMPANY_POSTAL_CODE: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_POSTAL_CODE, + COMPANY_COUNTRY_CODE: INPUT_IDS.ADDITIONAL_DATA.CORPAY.COMPANY_COUNTRY_CODE, + 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_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, + TRADE_VOLUME: INPUT_IDS.ADDITIONAL_DATA.CORPAY.TRADE_VOLUME, + TAX_ID_EIN_NUMBER: INPUT_IDS.ADDITIONAL_DATA.CORPAY.TAX_ID_EIN_NUMBER, +}; function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { const {translate} = useLocalize(); - const submit = () => { - onSubmit(); - }; + const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); + const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const policyID = reimbursementAccount?.achData?.policyID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const currency = policy?.outputCurrency ?? ''; + const businessInfoStepValues = useMemo(() => getSubstepValues(INPUT_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); + const bankAccountID = reimbursementAccount?.achData?.bankAccountID ?? CONST.DEFAULT_NUMBER_ID; + + const country = reimbursementAccount?.achData?.additionalData?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? reimbursementAccountDraft?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? ''; + + useEffect(() => { + getCorpayOnboardingFields(country); + }, [country]); + + const submit = useCallback(() => { + const params = {} as BusinessInfoParamsPartial; + Object.values(INPUT_KEYS).forEach((currentKey) => { + params[currentKey] = businessInfoStepValues[currentKey]; + }); + + saveCorpayOnboardingCompanyDetails( + { + ...params, + fundSourceCountries: country, + fundDestinationCountries: country, + currencyNeeded: currency, + purposeOfTransactionId: CONST.NON_USD_BANK_ACCOUNT.PURPOSE_OF_TRANSACTION_ID, + }, + bankAccountID, + ); + }, [country, currency, bankAccountID, businessInfoStepValues]); + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (reimbursementAccount?.errors || reimbursementAccount?.isSavingCorpayOnboardingCompanyFields || !reimbursementAccount?.isSuccess) { + return; + } + + if (reimbursementAccount?.isSuccess) { + onSubmit(); + clearReimbursementAccountSaveCorpayOnboardingCompanyDetails(); + } + + return () => { + clearReimbursementAccountSaveCorpayOnboardingCompanyDetails(); + }; + }, [reimbursementAccount, onSubmit]); const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo, goToTheLastStep} = useSubStep({bodyContent, startFrom: 0, onFinished: submit}); @@ -58,6 +142,7 @@ function BusinessInfo({onBackButtonPress, onSubmit}: BusinessInfoProps) { isEditing={isEditing} onNext={nextScreen} onMove={moveTo} + screenIndex={screenIndex} /> ); 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/Address.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Address.tsx index cd9533b4d66f..997c14644eb8 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Address.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Address.tsx @@ -11,17 +11,17 @@ import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; type AddressProps = SubStepProps; -const {COMPANY_STREET, COMPANY_ZIP_CODE, COMPANY_STATE, COMPANY_CITY, COMPANY_COUNTRY} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; +const {COMPANY_STREET, COMPANY_POSTAL_CODE, COMPANY_STATE, COMPANY_CITY, COMPANY_COUNTRY_CODE} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; const INPUT_KEYS = { street: COMPANY_STREET, city: COMPANY_CITY, state: COMPANY_STATE, - zipCode: COMPANY_ZIP_CODE, - country: COMPANY_COUNTRY, + zipCode: COMPANY_POSTAL_CODE, + country: COMPANY_COUNTRY_CODE, }; -const STEP_FIELDS = [COMPANY_STREET, COMPANY_CITY, COMPANY_STATE, COMPANY_ZIP_CODE, COMPANY_COUNTRY]; -const STEP_FIELDS_WITHOUT_STATE = [COMPANY_STREET, COMPANY_CITY, COMPANY_ZIP_CODE, COMPANY_COUNTRY]; +const STEP_FIELDS = [COMPANY_STREET, COMPANY_CITY, COMPANY_STATE, COMPANY_POSTAL_CODE, COMPANY_COUNTRY_CODE]; +const STEP_FIELDS_WITHOUT_STATE = [COMPANY_STREET, COMPANY_CITY, COMPANY_POSTAL_CODE, COMPANY_COUNTRY_CODE]; function Address({onNext, onMove, isEditing}: AddressProps) { const {translate} = useLocalize(); @@ -31,7 +31,7 @@ function Address({onNext, onMove, isEditing}: AddressProps) { const onyxValues = useMemo(() => getSubstepValues(INPUT_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); - const businessStepCountryDraftValue = onyxValues[COMPANY_COUNTRY]; + const businessStepCountryDraftValue = onyxValues[COMPANY_COUNTRY_CODE]; const countryStepCountryDraftValue = reimbursementAccountDraft?.[INPUT_IDS.ADDITIONAL_DATA.COUNTRY] ?? ''; const countryInitialValue = businessStepCountryDraftValue !== '' && businessStepCountryDraftValue !== countryStepCountryDraftValue ? businessStepCountryDraftValue : countryStepCountryDraftValue; @@ -40,7 +40,7 @@ function Address({onNext, onMove, isEditing}: AddressProps) { street: onyxValues[COMPANY_STREET] ?? '', city: onyxValues[COMPANY_CITY] ?? '', state: onyxValues[COMPANY_STATE] ?? '', - zipCode: onyxValues[COMPANY_ZIP_CODE] ?? '', + zipCode: onyxValues[COMPANY_POSTAL_CODE] ?? '', country: businessStepCountryDraftValue ?? countryInitialValue, }; diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/AverageReimbursement.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/AverageReimbursement.tsx new file mode 100644 index 000000000000..323946761428 --- /dev/null +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/AverageReimbursement.tsx @@ -0,0 +1,80 @@ +import React, {useCallback, useMemo} 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 PushRowWithModal from '@components/PushRowWithModal'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getFieldRequiredErrors} from '@libs/ValidationUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; + +type AverageReimbursementProps = SubStepProps; + +const {TRADE_VOLUME} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; +const STEP_FIELDS = [TRADE_VOLUME]; + +function AverageReimbursement({onNext, isEditing}: AverageReimbursementProps) { + 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 policyID = reimbursementAccount?.achData?.policyID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const currency = policy?.outputCurrency ?? ''; + + const tradeVolumeRangeListOptions = useMemo(() => { + if (!corpayOnboardingFields?.picklists.TradeVolumeRange) { + return {}; + } + + return corpayOnboardingFields.picklists.TradeVolumeRange.reduce((accumulator, currentValue) => { + accumulator[currentValue.name] = currentValue.stringValue; + return accumulator; + }, {} as Record); + }, [corpayOnboardingFields]); + + const tradeVolumeDefaultValue = reimbursementAccount?.achData?.additionalData?.corpay?.[TRADE_VOLUME] ?? reimbursementAccountDraft?.[TRADE_VOLUME] ?? ''; + + const validate = useCallback((values: FormOnyxValues): FormInputErrors => { + return getFieldRequiredErrors(values, STEP_FIELDS); + }, []); + + const handleSubmit = useReimbursementAccountStepFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: isEditing, + }); + + return ( + + {translate('businessInfoStep.whatsYourExpectedAverageReimbursements')} + + + ); +} + +AverageReimbursement.displayName = 'AverageReimbursement'; + +export default AverageReimbursement; diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/BusinessType.tsx index bd083c2dd535..88d577443206 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'; @@ -9,8 +9,7 @@ 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 {applicantType, natureOfBusiness} from '@pages/ReimbursementAccount/NonUSD/BusinessInfo/mockedCorpayLists'; +import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; @@ -19,33 +18,47 @@ 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?.picklists.ApplicantType) { + return {}; + } + + return corpayOnboardingFields.picklists.ApplicantType.reduce((accumulator, currentValue) => { + accumulator[currentValue.name] = currentValue.stringValue; + return accumulator; + }, {} as Record); + }, [corpayOnboardingFields]); + + const natureOfBusinessListOptions = useMemo(() => { + if (!corpayOnboardingFields?.picklists.NatureOfBusiness) { + 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] ?? ''; const validate = useCallback((values: FormOnyxValues): FormInputErrors => { - return ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + return getFieldRequiredErrors(values, STEP_FIELDS); }, []); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, - shouldSaveDraft: true, + shouldSaveDraft: isEditing, }); return ( @@ -60,23 +73,23 @@ function BusinessType({onNext, isEditing}: BusinessTypeProps) { {translate('businessInfoStep.whatTypeOfBusinessIsIt')} ); diff --git a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx index d0f26feccf0f..bb3f0450b9df 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BusinessInfo/substeps/Confirmation.tsx @@ -2,6 +2,7 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; +import DotIndicatorMessage from '@components/DotIndicatorMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; import ScrollView from '@components/ScrollView'; @@ -9,7 +10,7 @@ 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 {getLatestErrorMessage} from '@libs/ErrorUtils'; import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -19,16 +20,18 @@ const BUSINESS_INFO_STEP_KEYS = INPUT_IDS.ADDITIONAL_DATA.CORPAY; const { COMPANY_NAME, BUSINESS_REGISTRATION_INCORPORATION_NUMBER, - COMPANY_COUNTRY, + TAX_ID_EIN_NUMBER, + COMPANY_COUNTRY_CODE, COMPANY_STREET, COMPANY_CITY, COMPANY_STATE, - COMPANY_ZIP_CODE, + COMPANY_POSTAL_CODE, BUSINESS_CONTACT_NUMBER, BUSINESS_CONFIRMATION_EMAIL, FORMATION_INCORPORATION_COUNTRY_CODE, ANNUAL_VOLUME, APPLICANT_TYPE_ID, + TRADE_VOLUME, BUSINESS_CATEGORY, } = INPUT_IDS.ADDITIONAL_DATA.CORPAY; @@ -46,12 +49,27 @@ 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 error = getLatestErrorMessage(reimbursementAccount); 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], + ); + const tradeVolumeRange = useMemo( + () => displayStringValue(corpayOnboardingFields?.picklists.TradeVolumeRange ?? [], values[TRADE_VOLUME]), + [corpayOnboardingFields?.picklists.TradeVolumeRange, values], + ); return ( @@ -77,9 +95,17 @@ function Confirmation({onNext, onMove}: SubStepProps) { onMove(3); }} /> + { + onMove(4); + }} + /> { onMove(1); @@ -106,7 +132,7 @@ function Confirmation({onNext, onMove}: SubStepProps) { title={businessType} shouldShowRightIcon onPress={() => { - onMove(5); + onMove(6); }} /> { - onMove(4); + onMove(5); }} /> { - onMove(5); + onMove(6); }} /> { - onMove(6); + onMove(7); + }} + /> + { + onMove(8); }} /> + {!!error && error.length > 0 && ( + + )}