diff --git a/src/components/MoneyRequestConfirmationListFooter/hooks/useFooterDerivedFlags.ts b/src/components/MoneyRequestConfirmationListFooter/hooks/useFooterDerivedFlags.ts index 663638b2a0d5..e6385b778400 100644 --- a/src/components/MoneyRequestConfirmationListFooter/hooks/useFooterDerivedFlags.ts +++ b/src/components/MoneyRequestConfirmationListFooter/hooks/useFooterDerivedFlags.ts @@ -67,7 +67,7 @@ function useFooterDerivedFlags({ isTypeInvoice, shouldShowSmartScanFields, }: UseFooterDerivedFlagsParams) { - const {policyForMovingExpensesID, policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses(); + const {policyForMovingExpenses, shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(); const transaction = useTransactionSelector(transactionID, derivedFlagsSliceSelector); @@ -95,7 +95,6 @@ function useFooterDerivedFlags({ const shouldShowBillable = isBillableEnabledOnPolicy(policy); const shouldShowReimbursable = (isPolicyExpenseChat || isTrackExpense) && !!policy && policy?.disabledFields?.reimbursable !== true && !isManagedCardTransaction(transaction) && !isTypeInvoice; - const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy; const shouldShowTimeRequestFields = isTimeRequest && action === CONST.IOU.ACTION.CREATE; return { diff --git a/src/components/MoneyRequestHeaderSecondaryActions.tsx b/src/components/MoneyRequestHeaderSecondaryActions.tsx index 35b8d2d4b0d8..37a9c75d0d48 100644 --- a/src/components/MoneyRequestHeaderSecondaryActions.tsx +++ b/src/components/MoneyRequestHeaderSecondaryActions.tsx @@ -167,8 +167,7 @@ function MoneyRequestHeaderSecondaryActions({reportID, onBackButtonPress}: Money const defaultExpensePolicy = useDefaultExpensePolicy(); const [defaultPolicyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(defaultExpensePolicy?.id)}`); const [defaultPolicyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(defaultExpensePolicy?.id)}`); - const {policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses(isPerDiemRequest(transaction)); - const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy; + const {shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(isPerDiemRequest(transaction)); const {deleteTransactions, shouldOpenSplitExpenseEditFlowOnDelete} = useDeleteTransactions({report: parentReport, reportActions: parentReportAction ? [parentReportAction] : [], policy}); const {iouReport, chatReport: chatIOUReport, isChatIOUReportArchived} = useGetIOUReportFromReportAction(parentReportAction); const isParentReportArchived = useReportIsArchived(report?.parentReportID); diff --git a/src/components/Navigation/QuickCreationActionsBar/index.tsx b/src/components/Navigation/QuickCreationActionsBar/index.tsx index 554aebbd66fe..e8e42053de3b 100644 --- a/src/components/Navigation/QuickCreationActionsBar/index.tsx +++ b/src/components/Navigation/QuickCreationActionsBar/index.tsx @@ -59,8 +59,7 @@ function QuickCreationActionsBar() { const {isBetaEnabled} = usePermissions(); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? ''); - const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(); - const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy; + const {shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(); const isSubmit2026BetaEnabled = isBetaEnabled(CONST.BETAS.SUBMIT_2026); const groupPoliciesWithChatEnabledSelector = (policies: OnyxCollection) => getGroupPoliciesWhereReportCanBeCreated(policies, isSubmit2026BetaEnabled, email); const [groupPoliciesWithChatEnabled = CONST.EMPTY_ARRAY] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: groupPoliciesWithChatEnabledSelector}, [email, isSubmit2026BetaEnabled]); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 78ed00e3ff41..4ed962507065 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -230,7 +230,7 @@ function MoneyRequestView({ const transaction = updatedTransaction ?? onyxTransaction; const isExpenseUnreported = isExpenseUnreportedTransactionUtils(transaction); const personalPolicy = usePersonalPolicy(); - const {policyForMovingExpensesID, policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses(); + const {policyForMovingExpensesID, policyForMovingExpenses, shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(); const isTimeRequest = isTimeRequestTransactionUtils(transaction); const [policiesWithPerDiem] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { @@ -583,7 +583,6 @@ function MoneyRequestView({ amountTitle = translate('iou.receiptStatusTitle'); } - const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy; const updatedTransactionDescription = getDescription(updatedTransaction) || undefined; const shouldHideEmptyDescription = (isFromReviewDuplicates || isFromMergeTransaction) && !(updatedTransactionDescription ?? transactionDescription); const isEmptyUpdatedMerchant = isInvalidMerchantValue(updatedTransaction?.modifiedMerchant); diff --git a/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx b/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx index dbeddacea013..a973d932b3d1 100644 --- a/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx +++ b/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx @@ -63,8 +63,7 @@ function SearchActionsBarCreateButton() { const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); - const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(); - const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy; + const {shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(); const defaultChatEnabledPolicy = useMemo( () => getDefaultChatEnabledPolicy(groupPoliciesWithChatEnabled as Array>, activePolicy), [activePolicy, groupPoliciesWithChatEnabled], diff --git a/src/hooks/usePolicyForMovingExpenses.ts b/src/hooks/usePolicyForMovingExpenses.ts index 09d20dbec1cf..01f8451cdbab 100644 --- a/src/hooks/usePolicyForMovingExpenses.ts +++ b/src/hooks/usePolicyForMovingExpenses.ts @@ -82,7 +82,14 @@ function getPolicyQualificationResult( return {singlePolicyID, isMemberOfMoreThanOnePolicy, validExpensePolicyID}; } -function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: boolean, expensePolicyID?: string) { +type PolicyForMovingExpenses = { + policyForMovingExpensesID: string | undefined; + policyForMovingExpenses: OnyxEntry; + shouldSelectPolicy: boolean; + shouldNavigateToUpgradePath: boolean; +}; + +function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: boolean, expensePolicyID?: string, isUnreportedManagedCardTransaction?: boolean): PolicyForMovingExpenses { const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, { selector: activePolicySelector, @@ -104,24 +111,30 @@ function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: const resolvedPolicyID = validExpensePolicyID ?? singlePolicyID; const [resolvedPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${resolvedPolicyID}`); + // If this is an employee's card transaction that we manage, then we should report it to their default policy + // which we don't know. Sending an empty `policyID` instructs the backend to auto-select the preferred policy. + if (isUnreportedManagedCardTransaction) { + return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false}; + } + // If an expense policy ID is provided and valid, prefer it over the active policy if (validExpensePolicyID) { - return {policyForMovingExpensesID: validExpensePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false}; + return {policyForMovingExpensesID: validExpensePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false}; } if (activePolicy && (!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(activePolicy)) && (!isTimeRequest || isTimeTrackingEnabled(activePolicy))) { - return {policyForMovingExpensesID: activePolicyID, policyForMovingExpenses: activePolicy, shouldSelectPolicy: false}; + return {policyForMovingExpensesID: activePolicyID, policyForMovingExpenses: activePolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false}; } if (singlePolicyID && !isMemberOfMoreThanOnePolicy) { - return {policyForMovingExpensesID: singlePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false}; + return {policyForMovingExpensesID: singlePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false}; } if (isMemberOfMoreThanOnePolicy) { - return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: true}; + return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: true, shouldNavigateToUpgradePath: false}; } - return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false}; + return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, shouldNavigateToUpgradePath: true}; } export default usePolicyForMovingExpenses; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 89cd20e19bcb..7a4c00bda840 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -2959,6 +2959,10 @@ function isExpenseUnreported(transaction?: Transaction): transaction is Unreport return transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; } +function isUnreportedManagedCardTransaction(transaction?: Transaction): boolean { + return isExpenseUnreported(transaction) && isManagedCardTransaction(transaction); +} + /** * Returns true if the violation should block report submission. */ @@ -3225,4 +3229,5 @@ export { hasSmartScanFailedWithMissingFields, isDeletedTransaction, getDistanceRequestType, + isUnreportedManagedCardTransaction, }; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index dfaee0078f28..c7425fb93c6a 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -4104,28 +4104,30 @@ function buildNewReportOptimisticData( value: {[reportActionID]: optimisticCreateAction}, }, { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`, + value: optimisticNextStepDeprecated, + }, + ]; + if (parentReport) { + optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`, value: {[reportPreviewReportActionID]: optimisticReportPreview}, - }, - { + }); + optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, value: {iouReportID: reportID, ...outstandingChildRequest}, - }, - { + }); + optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${parentReport?.reportID}`, + key: `${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${parentReport.reportID}`, value: { hasOnceLoadedReportActions: true, }, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`, - value: optimisticNextStepDeprecated, - }, - ]; + }); + } optimisticData.push(...updateTitleFieldToMatchPolicy(reportID, policy)); @@ -4172,13 +4174,14 @@ function buildNewReportOptimisticData( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: {[reportActionID]: {errors: {createReport: getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage')}}}, }, - - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, - value: {hasOutstandingChildRequest: parentReport?.hasOutstandingChildRequest}, - }, ]; + if (parentReport) { + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, + value: {hasOutstandingChildRequest: parentReport.hasOutstandingChildRequest}, + }); + } const successData: Array> = [ { @@ -4203,17 +4206,19 @@ function buildNewReportOptimisticData( }, }, }, - { + ]; + if (parentReport) { + successData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`, value: { [reportPreviewReportActionID]: { pendingAction: null, errors: null, }, }, - }, - ]; + }); + } return { optimisticReportName: optimisticReportData.reportName, diff --git a/src/pages/Search/SearchTransactionsChangeReport.tsx b/src/pages/Search/SearchTransactionsChangeReport.tsx index 4b98dd9dc802..10cf9a474110 100644 --- a/src/pages/Search/SearchTransactionsChangeReport.tsx +++ b/src/pages/Search/SearchTransactionsChangeReport.tsx @@ -15,6 +15,7 @@ import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNav import Navigation from '@libs/Navigation/Navigation'; import {generateReportID, getPersonalDetailsForAccountID, getReportOrDraftReport, hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import {isUnreportedManagedCardTransaction} from '@libs/TransactionUtils'; import IOURequestEditReportCommon from '@pages/iou/request/step/IOURequestEditReportCommon'; @@ -51,6 +52,7 @@ function SearchTransactionsChangeReport() { const [betas] = useOnyx(ONYXKEYS.BETAS); const [allPolicyTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); const hasPerDiemTransactions = useHasPerDiemTransactions(selectedTransactionsKeys); + const hasUnreportedManagedCardTransactions = transactions.some((transaction) => isUnreportedManagedCardTransaction(transaction)); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const {isBetaEnabled} = usePermissions(); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); @@ -66,7 +68,12 @@ function SearchTransactionsChangeReport() { // Get the policyID from the selected transactions' report to pass to usePolicyForMovingExpenses // This ensures the "Create report" button shows the correct workspace instead of the user's default const selectedReportPolicyID = selectedReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${selectedReportID}`]?.policyID : undefined; - const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(hasPerDiemTransactions, undefined, selectedReportPolicyID); + const {policyForMovingExpensesID, shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses( + hasPerDiemTransactions, + undefined, + selectedReportPolicyID, + hasUnreportedManagedCardTransactions, + ); const policyForMovingExpenses = policyForMovingExpensesID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyForMovingExpensesID}`] : undefined; const areAllTransactionsUnreported = selectedTransactionsKeys.length > 0 && selectedTransactionsKeys.every((transactionKey) => selectedTransactions[transactionKey]?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID); @@ -173,7 +180,7 @@ function SearchTransactionsChangeReport() { }); const createReport = () => { - if (!policyForMovingExpensesID && !shouldSelectPolicy && selectedTransactionsKeys.length > 0) { + if (shouldNavigateToUpgradePath && selectedTransactionsKeys.length > 0) { const firstTransactionID = selectedTransactionsKeys.at(0); if (firstTransactionID) { Navigation.navigate( @@ -193,7 +200,7 @@ function SearchTransactionsChangeReport() { Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute(true))); return; } - if (!policyForMovingExpensesID) { + if (shouldNavigateToUpgradePath) { Navigation.navigate( ROUTES.MONEY_REQUEST_UPGRADE.getRoute({ action: CONST.IOU.ACTION.CREATE, @@ -274,6 +281,7 @@ function SearchTransactionsChangeReport() { targetOwnerAccountID={targetOwnerAccountID} transactionPolicyID={selectedReportPolicyID} isPerDiemRequest={hasPerDiemTransactions} + isUnreportedManagedCardTransaction={hasUnreportedManagedCardTransactions} /> ); } diff --git a/src/pages/iou/SplitExpenseEditPage.tsx b/src/pages/iou/SplitExpenseEditPage.tsx index 151af5c81058..1a7bb504f36a 100644 --- a/src/pages/iou/SplitExpenseEditPage.tsx +++ b/src/pages/iou/SplitExpenseEditPage.tsx @@ -86,8 +86,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { // Detect selfDM splits whose source workspace is gone: nothing for the Rate step to render. const hasAnyPaidWorkspace = hasAnyPaidPolicy(allPolicies ?? {}); - const {policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses(); - const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy; + const {shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(); const effectivePolicyID = effectivePolicy?.id; diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index c6ea9c6b5042..58f9f45486b7 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -17,6 +17,7 @@ import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNav import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailsForAccountID, hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import {isUnreportedManagedCardTransaction} from '@libs/TransactionUtils'; import {createNewReport} from '@userActions/Report'; @@ -65,6 +66,8 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { () => getPersonalDetailsForAccountID(selectedReport?.ownerAccountID, personalDetails) as PersonalDetails, [personalDetails, selectedReport?.ownerAccountID], ); + const [transactions] = useTransactionsByID(transactionIDs); + const hasUnreportedManagedCardTransactions = transactions.some((transaction) => isUnreportedManagedCardTransaction(transaction)); const hasPerDiemTransactions = useHasPerDiemTransactions(transactionIDs); // When moving an expense that belongs to another user, or when the selection includes per diem @@ -74,12 +77,11 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { const isOwnedByOther = selectedReport?.ownerAccountID !== currentUserPersonalDetails.accountID; const isOwnedByOtherOrHasPerDiem = isOwnedByOther || hasPerDiemTransactions; const targetExpensePolicyID = isOwnedByOtherOrHasPerDiem ? selectedReport?.policyID : undefined; - const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(hasPerDiemTransactions, undefined, targetExpensePolicyID); + const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(hasPerDiemTransactions, undefined, targetExpensePolicyID, hasUnreportedManagedCardTransactions); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, currentUserPersonalDetails.accountID ?? CONST.DEFAULT_NUMBER_ID, currentUserPersonalDetails.email ?? ''); const policyForMovingExpenses = policyForMovingExpensesID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyForMovingExpensesID}`] : undefined; const [betas] = useOnyx(ONYXKEYS.BETAS); - const [transactions] = useTransactionsByID(transactionIDs); const selectReport = (item: TransactionGroupListItem, report?: OnyxEntry) => { if (transactionIDs.length === 0 || item.value === reportID) { Navigation.dismissToSuperWideRHP(); @@ -135,7 +137,7 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { }; const createReportForPolicy = (shouldDismissEmptyReportsConfirmation?: boolean) => { - if (!hasPerDiemTransactions && !policyForMovingExpenses?.id) { + if (!hasPerDiemTransactions && !hasUnreportedManagedCardTransactions && !policyForMovingExpenses?.id) { return; } @@ -170,7 +172,7 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { handleCreateReport(); return; } - if (!hasPerDiemTransactions && !policyForMovingExpensesID && !shouldSelectPolicy) { + if (!hasPerDiemTransactions && !hasUnreportedManagedCardTransactions && !policyForMovingExpensesID && !shouldSelectPolicy) { return; } if (shouldSelectPolicy) { @@ -190,6 +192,7 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { isEditing={action === CONST.IOU.ACTION.EDIT} createReport={createReport} isPerDiemRequest={hasPerDiemTransactions} + isUnreportedManagedCardTransaction={hasUnreportedManagedCardTransactions} transactionPolicyID={targetExpensePolicyID} /> ); diff --git a/src/pages/iou/request/step/IOURequestEditReportCommon.tsx b/src/pages/iou/request/step/IOURequestEditReportCommon.tsx index 28486f114c20..6e7cafc56a8c 100644 --- a/src/pages/iou/request/step/IOURequestEditReportCommon.tsx +++ b/src/pages/iou/request/step/IOURequestEditReportCommon.tsx @@ -56,6 +56,7 @@ type Props = { createReport?: () => void; isPerDiemRequest: boolean; isTimeRequest?: boolean; + isUnreportedManagedCardTransaction?: boolean; }; function IOURequestEditReportCommon({ @@ -73,6 +74,7 @@ function IOURequestEditReportCommon({ createReport, isPerDiemRequest, isTimeRequest = false, + isUnreportedManagedCardTransaction = false, }: Props) { const icons = useMemoizedLazyExpensifyIcons(['Close', 'Document']); const {inputCallbackRef} = useAutoFocusInput(); @@ -100,7 +102,7 @@ function IOURequestEditReportCommon({ // Use the caller-provided transactionPolicyID so that the "Create report" button shows the correct workspace. // Each caller is responsible for passing the appropriate policyID (e.g., selectedReport?.policyID ?? transactionPolicyID). // When no transactionPolicyID is provided (e.g., from IOURequestEditReport), the hook falls back to the user's default workspace. - const {policyForMovingExpenses} = usePolicyForMovingExpenses(isPerDiemRequest, isTimeRequest, transactionPolicyID); + const {policyForMovingExpenses} = usePolicyForMovingExpenses(isPerDiemRequest, isTimeRequest, transactionPolicyID, isUnreportedManagedCardTransaction); const [perDiemWarningModalVisible, setPerDiemWarningModalVisible] = useState(false); diff --git a/src/pages/iou/request/step/IOURequestStepReport.tsx b/src/pages/iou/request/step/IOURequestStepReport.tsx index 4c8143c19ab4..5ae26d41a7d2 100644 --- a/src/pages/iou/request/step/IOURequestStepReport.tsx +++ b/src/pages/iou/request/step/IOURequestStepReport.tsx @@ -16,7 +16,7 @@ import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/crea import Navigation from '@libs/Navigation/Navigation'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {getPersonalDetailsForAccountID, getReportOrDraftReport, isPolicyExpenseChat, isReportOutstanding} from '@libs/ReportUtils'; -import {isPerDiemRequest, isTimeRequest as isTimeRequestUtil} from '@libs/TransactionUtils'; +import {isPerDiemRequest, isTimeRequest as isTimeRequestUtil, isUnreportedManagedCardTransaction as isUnreportedManagedCardTransactionUtil} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -76,6 +76,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { : selectedReport?.ownerAccountID; const ownerPersonalDetails = getPersonalDetailsForAccountID(ownerAccountID, personalDetails) as PersonalDetails; const isPerDiemTransaction = isPerDiemRequest(transaction); + const isUnreportedManagedCardTransaction = isUnreportedManagedCardTransactionUtil(transaction); const transactionPolicyID = transaction?.participants?.at(0)?.isPolicyExpenseChat ? transaction?.participants.at(0)?.policyID : undefined; // When moving an expense that belongs to another user, or when the selection includes per diem @@ -86,7 +87,12 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { // we need to fall back to transactionPolicyID because for a new workspace there is no report created yet // and if we choose this workspace as participant we want to create a new report in the chosen workspace - const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(isPerDiemTransaction, isTimeRequestUtil(transaction), targetExpensePolicyID); + const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses( + isPerDiemTransaction, + isTimeRequestUtil(transaction), + targetExpensePolicyID, + isUnreportedManagedCardTransaction, + ); // No violations exist for a report that hasn't been created yet — kept as a literal to avoid subscribing to the entire TRANSACTION_VIOLATIONS collection. const hasViolations = false; @@ -147,7 +153,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { const shouldShowNotFoundPage = useShowNotFoundPageInIOUStep(action, iouType, reportActionID, reportOrDraftReport, transaction); const createReportForPolicy = (shouldDismissEmptyReportsConfirmation?: boolean) => { - if (!isPerDiemTransaction && !policyForMovingExpenses?.id) { + if (!isPerDiemTransaction && !isUnreportedManagedCardTransaction && !policyForMovingExpenses?.id) { return; } @@ -173,7 +179,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { handleCreateReport(); return; } - if (!isPerDiemTransaction && !policyForMovingExpensesID && !shouldSelectPolicy) { + if (!isPerDiemTransaction && !isUnreportedManagedCardTransaction && !policyForMovingExpensesID && !shouldSelectPolicy) { return; } if (shouldSelectPolicy) { @@ -205,7 +211,8 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { shouldShowNotFoundPage={shouldShowNotFoundPage} isPerDiemRequest={transaction ? isPerDiemTransaction : false} isTimeRequest={transaction ? isTimeRequestUtil(transaction) : false} - createReport={policyForMovingExpensesID || shouldSelectPolicy || isPerDiemTransaction ? createReport : undefined} + isUnreportedManagedCardTransaction={transaction ? isUnreportedManagedCardTransaction : false} + createReport={policyForMovingExpensesID || shouldSelectPolicy || isPerDiemTransaction || isUnreportedManagedCardTransaction ? createReport : undefined} targetOwnerAccountID={ownerAccountID} /> ); diff --git a/tests/ui/components/SearchActionsBarCreateButtonTest.tsx b/tests/ui/components/SearchActionsBarCreateButtonTest.tsx index 35edcd4342c1..3196941fd6c6 100644 --- a/tests/ui/components/SearchActionsBarCreateButtonTest.tsx +++ b/tests/ui/components/SearchActionsBarCreateButtonTest.tsx @@ -113,6 +113,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: true, }); await act(async () => { @@ -173,6 +174,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: 'some-policy', policyForMovingExpenses: MOCK_POLICY, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: false, }); // Set up multiple policies with chat enabled @@ -210,6 +212,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: MOCK_POLICY_ID, policyForMovingExpenses: MOCK_POLICY, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: false, }); await act(async () => { @@ -276,6 +279,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: MOCK_POLICY_ID, policyForMovingExpenses: MOCK_POLICY, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: false, }); await act(async () => { @@ -305,6 +309,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: MOCK_POLICY_ID, policyForMovingExpenses: MOCK_POLICY, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: false, }); await act(async () => { @@ -345,6 +350,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: MOCK_POLICY_ID, policyForMovingExpenses: MOCK_POLICY, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: false, }); await act(async () => { @@ -383,6 +389,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: MOCK_POLICY_ID, policyForMovingExpenses: MOCK_POLICY, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: false, }); await act(async () => { @@ -424,6 +431,7 @@ describe('SearchActionsBarCreateButton', () => { policyForMovingExpensesID: MOCK_POLICY_ID, policyForMovingExpenses: MOCK_POLICY, shouldSelectPolicy: false, + shouldNavigateToUpgradePath: false, }); await act(async () => {