Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions src/libs/IOUUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {OnyxInputOrEntry, PersonalDetails, Policy, Report} from '@src/types/onyx';
import type {Attendee} from '@src/types/onyx/IOU';
import type Transaction from '@src/types/onyx/Transaction';
import SafeString from '@src/utils/SafeString';
import type {IOURequestType} from './actions/IOU';
import {getCurrencyUnit} from './CurrencyUtils';
import Navigation from './Navigation/Navigation';
import Performance from './Performance';
import {isPaidGroupPolicy} from './PolicyUtils';
import {getReportTransactions} from './ReportUtils';
import {getCurrency, getTagArrayFromName} from './TransactionUtils';
import {getReportTransactions, isExpenseRequest, isPolicyExpenseChat} from './ReportUtils';
import {getCurrency, getTagArrayFromName, isMerchantMissing, isScanRequest} from './TransactionUtils';

function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: IOUType, transactionID: string, reportID: string, iouAction?: IOUAction): void {
if (iouAction === CONST.IOU.ACTION.CATEGORIZE || iouAction === CONST.IOU.ACTION.SUBMIT || iouAction === CONST.IOU.ACTION.SHARE) {
Expand Down Expand Up @@ -239,6 +240,32 @@ function formatCurrentUserToAttendee(currentUser?: PersonalDetails, reportID?: s
return [initialAttendee];
}

/**
* Checks if merchant is required and missing for a transaction.
* Merchant is required for policy expense chats, expense requests, or when any participant is a policy expense chat.
* For scan requests, merchant is not required unless it's a split bill being edited.
*
* @param transaction - The transaction to check
* @param report - The report associated with the transaction
* @param isEditingSplitBill - Whether this is editing a split bill
* @returns true if merchant is required and missing, false otherwise
*/
function shouldRequireMerchant(transaction: OnyxInputOrEntry<Transaction> | undefined, report: OnyxInputOrEntry<Report> | undefined, isEditingSplitBill = false): boolean {
if (!transaction || !report) {
return false;
}

// Check if merchant is required based on report type and participants
const isMerchantRequired = !!(isPolicyExpenseChat(report) || isExpenseRequest(report) || transaction?.participants?.some((participant) => !!participant.isPolicyExpenseChat));

// For scan requests, merchant is not required unless it's a split bill being edited
if (isScanRequest(transaction) && !isEditingSplitBill) {
return false;
}

return isMerchantRequired && isMerchantMissing(transaction);
}

export {
calculateAmount,
insertTagIntoTransactionTagsString,
Expand All @@ -251,4 +278,5 @@ export {
formatCurrentUserToAttendee,
navigateToParticipantPage,
shouldShowReceiptEmptyState,
shouldRequireMerchant,
};
13 changes: 11 additions & 2 deletions src/pages/iou/request/step/IOURequestStepAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import useReportIsArchived from '@hooks/useReportIsArchived';
import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep';
import {setTransactionReport} from '@libs/actions/Transaction';
import {convertToBackendAmount} from '@libs/CurrencyUtils';
import {navigateToParticipantPage} from '@libs/IOUUtils';
import {navigateToParticipantPage, shouldRequireMerchant} from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
import {isPaidGroupPolicy} from '@libs/PolicyUtils';
Expand Down Expand Up @@ -240,8 +240,15 @@ function IOURequestStepAmount({
setSplitShares(transaction, amountInSmallestCurrencyUnits, selectedCurrency || CONST.CURRENCY.USD, participantAccountIDs);
}
setMoneyRequestParticipantsFromReport(transactionID, report, currentUserPersonalDetails.accountID).then(() => {
// Check if merchant is required and missing before proceeding
// If so, navigate to merchant step first
if (shouldRequireMerchant(transaction, report, isEditingSplitBill)) {
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, CONST.IOU.TYPE.SUBMIT, transactionID, reportID, undefined, reportActionID));
return;
}
navigateToConfirmationPage();
});

return;
}

Expand All @@ -261,7 +268,9 @@ function IOURequestStepAmount({
const resetToDefaultWorkspace = () => {
setTransactionReport(transactionID, {reportID: transactionReportID}, true);
setMoneyRequestParticipantsFromReport(transactionID, activePolicyExpenseChat, currentUserPersonalDetails.accountID).then(() => {
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.SUBMIT, transactionID, activePolicyExpenseChat?.reportID));
Navigation.navigate(
ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, CONST.IOU.TYPE.SUBMIT, transactionID, activePolicyExpenseChat?.reportID, undefined, reportActionID),
);
});
};

Expand Down
9 changes: 9 additions & 0 deletions src/pages/iou/request/step/IOURequestStepMerchant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {isValidInputLength} from '@libs/ValidationUtils';
import {setDraftSplitTransaction, setMoneyRequestMerchant, updateMoneyRequestMerchant} from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/MoneyRequestMerchantForm';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
Expand Down Expand Up @@ -129,7 +130,15 @@ function IOURequestStepMerchant({
currentUserEmailParam,
isASAPSubmitBetaEnabled,
);
navigateBack();
return;
}

if (!backTo) {
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(action, iouType, transactionID, reportID));
return;
}

navigateBack();
};

Expand Down
18 changes: 16 additions & 2 deletions src/pages/iou/request/step/IOURequestStepParticipants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {isPaidGroupPolicy} from '@libs/PolicyUtils';
import {findSelfDMReportID, generateReportID, isInvoiceRoomWithID} from '@libs/ReportUtils';
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
import {endSpan} from '@libs/telemetry/activeSpans';
import {getRequestType, isCorporateCardTransaction, isPerDiemRequest} from '@libs/TransactionUtils';
import {getRequestType, isCorporateCardTransaction, isMerchantMissing, isPerDiemRequest} from '@libs/TransactionUtils';
import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyRequestParticipantsSelector';
import {
navigateToStartStepIfScanFileCannotBeRead,
Expand Down Expand Up @@ -95,6 +95,7 @@ function IOURequestStepParticipants({

// We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant
const selectedReportID = useRef<string>(participants?.length === 1 ? (participants.at(0)?.reportID ?? reportID) : reportID);
const selectedParticipants = useRef<Participant[]>(participants);
// We can assume that shouldAutoReport is true as the initial value is not used. shouldAutoReport is only used after the selectedReportID changes in addParticipant where we'd update shouldAutoReport too
const shouldAutoReport = useRef(true);
const numberOfParticipants = useRef(participants?.length ?? 0);
Expand Down Expand Up @@ -231,6 +232,7 @@ function IOURequestStepParticipants({
(val: Participant[]) => {
HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS);

selectedParticipants.current = val;
const firstParticipant = val.at(0);

if (firstParticipant?.isSelfDM && !isSplitRequest) {
Expand Down Expand Up @@ -354,6 +356,9 @@ function IOURequestStepParticipants({
return;
}

const firstParticipant = selectedParticipants.current?.at(0);
const isMerchantRequired = !!firstParticipant?.isPolicyExpenseChat && isMerchantMissing(initialTransaction);

const iouConfirmationPageRoute = ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(
action,
iouType === CONST.IOU.TYPE.CREATE || iouType === CONST.IOU.TYPE.TRACK ? CONST.IOU.TYPE.SUBMIT : iouType,
Expand All @@ -364,9 +369,18 @@ function IOURequestStepParticipants({
action === CONST.IOU.ACTION.SHARE ? Navigation.getActiveRoute() : undefined,
);

// eslint-disable-next-line no-nested-ternary
const route = isCategorizing
? ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, initialTransactionID, selectedReportID.current || reportID, iouConfirmationPageRoute)
: iouConfirmationPageRoute;
: isMerchantRequired
? ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(
action,
iouType === CONST.IOU.TYPE.CREATE || iouType === CONST.IOU.TYPE.TRACK ? CONST.IOU.TYPE.SUBMIT : iouType,
initialTransactionID,
newReportID,
undefined,
)
: iouConfirmationPageRoute;

Performance.markStart(CONST.TIMING.OPEN_CREATE_EXPENSE_APPROVE);
waitForKeyboardDismiss(() => {
Expand Down