diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 979bafcdab6dc..40ab87055ca82 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -536,6 +536,8 @@ const ONYXKEYS = { MONEY_REQUEST_DATE_FORM_DRAFT: 'moneyRequestCreatedFormDraft', MONEY_REQUEST_HOLD_FORM: 'moneyHoldReasonForm', MONEY_REQUEST_HOLD_FORM_DRAFT: 'moneyHoldReasonFormDraft', + MONEY_REQUEST_COMPANY_INFO_FORM: 'moneyRequestCompanyInfoForm', + MONEY_REQUEST_COMPANY_INFO_FORM_DRAFT: 'moneyRequestCompanyInfoFormDraft', NEW_CONTACT_METHOD_FORM: 'newContactMethodForm', NEW_CONTACT_METHOD_FORM_DRAFT: 'newContactMethodFormDraft', WAYPOINT_FORM: 'waypointForm', @@ -645,6 +647,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.MONEY_REQUEST_AMOUNT_FORM]: FormTypes.MoneyRequestAmountForm; [ONYXKEYS.FORMS.MONEY_REQUEST_DATE_FORM]: FormTypes.MoneyRequestDateForm; [ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM]: FormTypes.MoneyRequestHoldReasonForm; + [ONYXKEYS.FORMS.MONEY_REQUEST_COMPANY_INFO_FORM]: FormTypes.MoneyRequestCompanyInfoForm; [ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM]: FormTypes.NewContactMethodForm; [ONYXKEYS.FORMS.WAYPOINT_FORM]: FormTypes.WaypointForm; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: FormTypes.SettingsStatusSetForm; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1589d67c985a8..8e69e06a31050 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -366,6 +366,11 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => getUrlWithBackToParam(`create/${iouType as string}/from/${transactionID}/${reportID}`, backTo), }, + MONEY_REQUEST_STEP_COMPANY_INFO: { + route: 'create/:iouType/company-info/:transactionID/:reportID', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType as string}/company-info/${transactionID}/${reportID}`, backTo), + }, MONEY_REQUEST_STEP_CONFIRMATION: { route: ':action/:iouType/confirmation/:transactionID/:reportID', getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, participantsAutoAssigned?: boolean) => diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 831a058ebbbb8..20507e4bde2d5 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -192,6 +192,7 @@ const SCREENS = { STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate', STEP_SPLIT_PAYER: 'Money_Request_Step_Split_Payer', STEP_SEND_FROM: 'Money_Request_Step_Send_From', + STEP_COMPANY_INFO: 'Money_Request_Step_Company_Info', CURRENCY: 'Money_Request_Currency', WAYPOINT: 'Money_Request_Waypoint', EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint', diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 70a819d3a56eb..510fc55fe0ec1 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -26,6 +26,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; +import {hasInvoicingDetails} from '@userActions/Policy/Policy'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -373,7 +374,11 @@ function MoneyRequestConfirmationList({ const splitOrRequestOptions: Array> = useMemo(() => { let text; if (isTypeInvoice) { - text = translate('iou.sendInvoice', {amount: formattedAmount}); + if (hasInvoicingDetails(policy)) { + text = translate('iou.sendInvoice', {amount: formattedAmount}); + } else { + text = translate('common.next'); + } } else if (isTypeTrackExpense) { text = translate('iou.trackExpense'); } else if (isTypeSplit && iouAmount === 0) { @@ -393,7 +398,7 @@ function MoneyRequestConfirmationList({ value: iouType, }, ]; - }, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount, isTypeInvoice]); + }, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, policy, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount, isTypeInvoice]); const onSplitShareChange = useCallback( (accountID: number, value: number) => { @@ -678,6 +683,11 @@ function MoneyRequestConfirmationList({ */ const confirm = useCallback( (paymentMethod: PaymentMethodType | undefined) => { + if (iouType === CONST.IOU.TYPE.INVOICE && !hasInvoicingDetails(policy)) { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_COMPANY_INFO.getRoute(iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + return; + } + if (selectedParticipants.length === 0) { return; } @@ -745,6 +755,9 @@ function MoneyRequestConfirmationList({ iouAmount, onConfirm, shouldPlaySound, + transactionID, + reportID, + policy, ], ); diff --git a/src/languages/en.ts b/src/languages/en.ts index 47c00f7d731e1..241a043404d2b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -716,6 +716,13 @@ export default { receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", receiptScanningFailed: 'Receipt scanning failed. Please enter the details manually.', transactionPendingDescription: 'Transaction pending. It may take a few days to post.', + companyInfo: 'Company info', + companyInfoDescription: 'We need a few more details before you can send your first invoice.', + yourCompanyName: 'Your company name', + yourCompanyWebsite: 'Your company website', + yourCompanyWebsiteNote: "If you don't have a website, you can provide your company's LinkedIn or social media profile instead.", + invalidDomainError: 'You have entered an invalid domain. To continue, please enter a valid domain.', + publicDomainError: 'You have entered a public domain. To continue, please enter a private domain.', expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('expense', 'expenses', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pending` : '' diff --git a/src/languages/es.ts b/src/languages/es.ts index 690ebd641b544..f8196b1b58ac4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -711,6 +711,13 @@ export default { receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', transactionPendingDescription: 'Transacción pendiente. Puede tardar unos días en contabilizarse.', + companyInfo: 'Información de la empresa', + companyInfoDescription: 'Necesitamos algunos detalles más antes de que pueda enviar su primera factura.', + yourCompanyName: 'Nombre de su empresa', + yourCompanyWebsite: 'Sitio web de su empresa', + yourCompanyWebsiteNote: 'Si no tiene un sitio web, puede proporcionar el perfil de LinkedIn o de las redes sociales de su empresa.', + invalidDomainError: 'Ha introducido un dominio no válido. Para continuar, introduzca un dominio válido.', + publicDomainError: 'Ha introducido un dominio público. Para continuar, introduzca un dominio privado.', expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('gasto', 'gastos', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' diff --git a/src/libs/API/parameters/SendInvoiceParams.ts b/src/libs/API/parameters/SendInvoiceParams.ts index f8ba0647fb0a9..c95ffce14b2c9 100644 --- a/src/libs/API/parameters/SendInvoiceParams.ts +++ b/src/libs/API/parameters/SendInvoiceParams.ts @@ -18,6 +18,8 @@ type SendInvoiceParams = RequireAtLeastOne< reportPreviewReportActionID: string; transactionID: string; transactionThreadReportID: string; + companyName?: string; + companyWebsite?: string; }, 'receiverEmail' | 'receiverInvoiceRoomID' >; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 5b84fba03a398..d02cf14f85eb4 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -90,6 +90,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/iou/request/step/IOURequestStepWaypoint').default, [SCREENS.MONEY_REQUEST.STEP_SPLIT_PAYER]: () => require('../../../../pages/iou/request/step/IOURequestStepSplitPayer').default, [SCREENS.MONEY_REQUEST.STEP_SEND_FROM]: () => require('../../../../pages/iou/request/step/IOURequestStepSendFrom').default, + [SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO]: () => require('../../../../pages/iou/request/step/IOURequestStepCompanyInfo').default, [SCREENS.MONEY_REQUEST.HOLD]: () => require('../../../../pages/iou/HoldReasonPage').default, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: () => require('../../../../pages/settings/Wallet/AddDebitCardPage').default, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 87802e1130cd2..0997da0b10fe9 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -885,6 +885,7 @@ const config: LinkingOptions['config'] = { }, [SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_ROOT]: ROUTES.SETTINGS_CATEGORIES_ROOT.route, [SCREENS.MONEY_REQUEST.STEP_SEND_FROM]: ROUTES.MONEY_REQUEST_STEP_SEND_FROM.route, + [SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO]: ROUTES.MONEY_REQUEST_STEP_COMPANY_INFO.route, [SCREENS.MONEY_REQUEST.STEP_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_CATEGORY]: ROUTES.MONEY_REQUEST_STEP_CATEGORY.route, [SCREENS.MONEY_REQUEST.STEP_CONFIRMATION]: ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c527a02c16af9..fa62f6b1107c1 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -778,6 +778,12 @@ type MoneyRequestNavigatorParamList = { reportID: string; backTo: Routes; }; + [SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO]: { + iouType: IOUType; + transactionID: string; + reportID: string; + backTo: Routes; + }; [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: { action: IOUAction; iouType: Exclude; diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index b0c99f4a60266..bcca6c8402870 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -1,5 +1,5 @@ import {addYears, endOfMonth, format, isAfter, isBefore, isSameDay, isValid, isWithinInterval, parse, parseISO, startOfDay, subYears} from 'date-fns'; -import {Str, Url} from 'expensify-common'; +import {PUBLIC_DOMAINS, Str, Url} from 'expensify-common'; import isEmpty from 'lodash/isEmpty'; import isObject from 'lodash/isObject'; import type {OnyxCollection} from 'react-native-onyx'; @@ -242,6 +242,11 @@ function isValidWebsite(url: string): boolean { return new RegExp(`^${Url.URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i').test(url) && isLowerCase; } +/** Checks if the domain is public */ +function isPublicDomain(domain: string): boolean { + return PUBLIC_DOMAINS.some((publicDomain) => publicDomain === domain.toLowerCase()); +} + function validateIdentity(identity: Record): Record { const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob']; const errors: Record = {}; @@ -534,4 +539,5 @@ export { isExistingTaxName, isValidSubscriptionSize, isExistingTaxCode, + isPublicDomain, }; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0db7c511b9071..8d6f7c85915f6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -916,6 +916,8 @@ function buildOnyxDataForInvoice( policy?: OnyxEntry, policyTagList?: OnyxEntry, policyCategories?: OnyxEntry, + companyName?: string, + companyWebsite?: string, ): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); const optimisticData: OnyxUpdate[] = [ @@ -1193,6 +1195,49 @@ function buildOnyxDataForInvoice( }, ]; + if (companyName && companyWebsite) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`, + value: { + invoice: { + companyName, + companyWebsite, + pendingFields: { + companyName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + companyWebsite: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`, + value: { + invoice: { + pendingFields: { + companyName: null, + companyWebsite: null, + }, + }, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`, + value: { + invoice: { + companyName: undefined, + companyWebsite: undefined, + pendingFields: { + companyName: null, + companyWebsite: null, + }, + }, + }, + }); + } + // We don't need to compute violations unless we're on a paid policy if (!policy || !PolicyUtils.isPaidGroupPolicy(policy)) { return [optimisticData, successData, failureData]; @@ -1790,6 +1835,8 @@ function getSendInvoiceInformation( policy?: OnyxEntry, policyTagList?: OnyxEntry, policyCategories?: OnyxEntry, + companyName?: string, + companyWebsite?: string, ): SendInvoiceInformation { const {amount = 0, currency = '', created = '', merchant = '', category = '', tag = '', taxCode = '', taxAmount = 0, billable, comment, participants} = transaction ?? {}; const trimmedComment = (comment?.comment ?? '').trim(); @@ -1897,6 +1944,8 @@ function getSendInvoiceInformation( policy, policyTagList, policyCategories, + companyName, + companyWebsite, ); return { @@ -3624,9 +3673,11 @@ function sendInvoice( policy?: OnyxEntry, policyTagList?: OnyxEntry, policyCategories?: OnyxEntry, + companyName?: string, + companyWebsite?: string, ) { const {senderWorkspaceID, receiver, invoiceRoom, createdChatReportActionID, invoiceReportID, reportPreviewReportActionID, transactionID, transactionThreadReportID, onyxData} = - getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); + getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories, companyName, companyWebsite); const parameters: SendInvoiceParams = { senderWorkspaceID, @@ -3643,6 +3694,8 @@ function sendInvoice( reportPreviewReportActionID, transactionID, transactionThreadReportID, + companyName, + companyWebsite, ...(invoiceChatReport?.reportID ? {receiverInvoiceRoomID: invoiceChatReport.reportID} : {receiverEmail: receiver.login ?? ''}), }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index e621ec32c9055..2eb836d019c53 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -209,6 +209,14 @@ function getPrimaryPolicy(activePolicyID?: OnyxEntry): Policy | undefine return primaryPolicy ?? activeAdminWorkspaces[0]; } +/** Check if the policy has invoicing company details */ +// eslint-disable-next-line react/no-unused-prop-types,@typescript-eslint/no-unused-vars +function hasInvoicingDetails(policy: OnyxEntry): boolean { + // TODO: uncomment when invoicing details inside a policy are supported. + // return !!policy.invoice.companyName && !!policy.invoice.companyWebsite; + return true; +} + /** * Check if the user has any active free policies (aka workspaces) */ @@ -3241,6 +3249,7 @@ export { requestExpensifyCardLimitIncrease, getAdminPoliciesConnectedToNetSuite, getAdminPoliciesConnectedToSageIntacct, + hasInvoicingDetails, }; export type {NewCustomUnit}; diff --git a/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx new file mode 100644 index 0000000000000..b1454b76aca62 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx @@ -0,0 +1,123 @@ +import {Str} from 'expensify-common'; +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 useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import playSound, {SOUNDS} from '@libs/Sound'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import Navigation from '@navigation/Navigation'; +import * as IOU from '@userActions/IOU'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/MoneyRequestCompanyInfoForm'; +import StepScreenWrapper from './StepScreenWrapper'; +import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; +import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; +import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; + +type IOURequestStepCompanyInfoProps = WithWritableReportOrNotFoundProps & + WithFullTransactionOrNotFoundProps; + +function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepCompanyInfoProps) { + const {backTo} = route.params; + + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {inputCallbackRef} = useAutoFocusInput(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, report)}`); + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, report)}`); + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, report)}`); + + const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(transaction?.amount ?? 0), transaction?.currency); + + const extractUrlDomain = (url: string): string | undefined => { + const DOMAIN_BASE_REGEX = '^(?:https?:\\/\\/)?(?:www\\.)?([^\\/]+)'; + const match = String(url).match(DOMAIN_BASE_REGEX); + + return match?.[1]; + }; + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.COMPANY_NAME, INPUT_IDS.COMPANY_WEBSITE]); + + if (values.companyWebsite) { + if (!ValidationUtils.isValidWebsite(values.companyWebsite)) { + errors.companyWebsite = translate('bankAccount.error.website'); + } else { + const domain = extractUrlDomain(values.companyWebsite); + + if (!domain || !Str.isValidDomainName(domain)) { + errors.companyWebsite = translate('iou.invalidDomainError'); + } else if (ValidationUtils.isPublicDomain(domain)) { + errors.companyWebsite = translate('iou.publicDomainError'); + } + } + } + + return errors; + }, + [translate], + ); + + const submit = (values: FormOnyxValues) => { + playSound(SOUNDS.DONE); + IOU.sendInvoice(currentUserPersonalDetails.accountID, transaction, report, undefined, policy, policyTags, policyCategories, values.companyName, values.companyWebsite); + }; + + return ( + Navigation.goBack(backTo)} + shouldShowWrapper + testID={IOURequestStepCompanyInfo.displayName} + > + {translate('iou.companyInfoDescription')} + + + + + + ); +} + +IOURequestStepCompanyInfo.displayName = 'IOURequestStepCompanyInfo'; + +export default withWritableReportOrNotFound(withFullTransactionOrNotFound(IOURequestStepCompanyInfo)); diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index e6072af08b71e..9bf454b2c27d4 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -35,7 +35,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE | typeof SCREENS.MONEY_REQUEST.STEP_SCAN | typeof SCREENS.MONEY_REQUEST.STEP_CURRENCY - | typeof SCREENS.MONEY_REQUEST.STEP_SEND_FROM; + | typeof SCREENS.MONEY_REQUEST.STEP_SEND_FROM + | typeof SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO; type Route = RouteProp; diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 9539ee1e15962..65ba0117fdf80 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -42,7 +42,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_MERCHANT | typeof SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT | typeof SCREENS.MONEY_REQUEST.STEP_SCAN - | typeof SCREENS.MONEY_REQUEST.STEP_SEND_FROM; + | typeof SCREENS.MONEY_REQUEST.STEP_SEND_FROM + | typeof SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO; type Route = RouteProp; diff --git a/src/types/form/MoneyRequestCompanyInfoForm.ts b/src/types/form/MoneyRequestCompanyInfoForm.ts new file mode 100644 index 0000000000000..94ff6867e75d2 --- /dev/null +++ b/src/types/form/MoneyRequestCompanyInfoForm.ts @@ -0,0 +1,20 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + COMPANY_NAME: 'companyName', + COMPANY_WEBSITE: 'companyWebsite', +} as const; + +type InputID = ValueOf; + +type MoneyRequestCompanyInfoForm = Form< + InputID, + { + [INPUT_IDS.COMPANY_NAME]: string; + [INPUT_IDS.COMPANY_WEBSITE]: string; + } +>; + +export type {MoneyRequestCompanyInfoForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index cb9d87dd7a39d..60c068b2bea17 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -17,6 +17,7 @@ export type {MoneyRequestDateForm} from './MoneyRequestDateForm'; export type {MoneyRequestDescriptionForm} from './MoneyRequestDescriptionForm'; export type {MoneyRequestMerchantForm} from './MoneyRequestMerchantForm'; export type {MoneyRequestHoldReasonForm} from './MoneyRequestHoldReasonForm'; +export type {MoneyRequestCompanyInfoForm} from './MoneyRequestCompanyInfoForm'; export type {NewContactMethodForm} from './NewContactMethodForm'; export type {ChangeBillingCurrencyForm} from './ChangeBillingCurrencyForm'; export type {NewRoomForm} from './NewRoomForm'; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 5b781ed7c0344..ec361f321c98f 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1292,6 +1292,15 @@ type PolicyReportField = { defaultExternalID?: string | null; }; +/** Policy invoicing details */ +type PolicyInvoicingDetails = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Stripe Connect company name */ + companyName?: string; + + /** Stripe Connect company website */ + companyWebsite?: string; +}>; + /** Names of policy features */ type PolicyFeatureName = ValueOf; @@ -1447,6 +1456,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< */ isTaxTrackingEnabled?: boolean; + /** Policy invoicing details */ + invoice?: PolicyInvoicingDetails; + /** Tax data */ tax?: { /** Whether or not the policy has tax tracking enabled */