diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 41560694f353..4de976aaf160 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2404,6 +2404,14 @@ function isReportFieldOfTypeTitle(reportField: OnyxEntry): bo return reportField?.type === 'formula' && reportField?.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID; } +/** + * Check if Report has any held expenses + */ +function isHoldCreator(transaction: OnyxEntry, reportID: string): boolean { + const holdReportAction = ReportActionsUtils.getReportAction(reportID, `${transaction?.comment?.hold ?? ''}`); + return isActionCreator(holdReportAction); +} + /** * Check if report fields are available to use in a report */ @@ -2721,6 +2729,71 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } +function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { + if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + const moneyRequestReport = getReport(String(moneyRequestReportID)); + + if (!moneyRequestReportID || !moneyRequestReport) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + + const isRequestSettled = isSettled(moneyRequestReport?.reportID); + const isApproved = isReportApproved(moneyRequestReport); + const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + + const parentReport = getReport(String(moneyRequestReport.parentReportID)); + const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport); + + const isRequestIOU = parentReport?.type === 'iou'; + const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; + const isTrackExpenseMoneyReport = isTrackExpenseReport(moneyRequestReport); + const isActionOwner = + typeof parentReportAction?.actorAccountID === 'number' && + typeof currentUserPersonalDetails?.accountID === 'number' && + parentReportAction.actorAccountID === currentUserPersonalDetails?.accountID; + const isApprover = isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; + const isOnHold = TransactionUtils.isOnHold(transaction); + const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + + const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); + const isDeletedParentAction = isEmptyObject(parentReportAction) || ReportActionsUtils.isDeletedAction(parentReportAction); + + const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; + const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; + const canUnholdRequest = !!(canHoldOrUnholdRequest && isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus))); + + return {canHoldRequest, canUnholdRequest}; +} + +const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry): void => { + if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + return; + } + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + + const moneyRequestReport = getReport(String(moneyRequestReportID)); + if (!moneyRequestReportID || !moneyRequestReport) { + return; + } + + const transactionID = reportAction?.originalMessage?.IOUTransactionID ?? ''; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + const isOnHold = TransactionUtils.isOnHold(transaction); + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport.policyID}`] ?? null; + + if (isOnHold) { + IOU.unholdRequest(transactionID, reportAction.childReportID ?? ''); + } else { + const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID ?? '', activeRoute)); + } +}; + /** * Gets all transactions on an IOU report with a receipt */ @@ -6386,14 +6459,6 @@ function navigateToPrivateNotes(report: OnyxEntry, session: OnyxEntry, reportID: string): boolean { - const holdReportAction = ReportActionsUtils.getReportAction(reportID, `${transaction?.comment?.hold ?? ''}`); - return isActionCreator(holdReportAction); -} - /** * Get all held transactions of a iouReport */ @@ -6905,6 +6970,7 @@ export { canCreateTaskInReport, canCurrentUserOpenReport, canDeleteReportAction, + canHoldUnholdReportAction, canEditFieldOfMoneyRequest, canEditMoneyRequest, canEditPolicyDescription, @@ -7132,6 +7198,7 @@ export { shouldShowMerchantColumn, isCurrentUserInvoiceReceiver, isDraftReport, + changeMoneyRequestHoldStatus, createDraftWorkspaceAndNavigateToConfirmationScreen, }; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index aa7b6aa3bfb5..11d81d23dd06 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -256,6 +256,40 @@ const ContextMenuActions: ContextMenuAction[] = [ }, getDescription: () => {}, }, + { + isAnonymousAction: false, + textTranslateKey: 'iou.unholdExpense', + icon: Expensicons.Stopwatch, + shouldShow: (type, reportAction) => + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canUnholdRequest, + onPress: (closePopover, {reportAction}) => { + if (closePopover) { + hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); + return; + } + + // No popover to hide, call changeMoneyRequestHoldStatus immediately + ReportUtils.changeMoneyRequestHoldStatus(reportAction); + }, + getDescription: () => {}, + }, + { + isAnonymousAction: false, + textTranslateKey: 'iou.hold', + icon: Expensicons.Stopwatch, + shouldShow: (type, reportAction) => + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canHoldRequest, + onPress: (closePopover, {reportAction}) => { + if (closePopover) { + hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); + return; + } + + // No popover to hide, call changeMoneyRequestHoldStatus immediately + ReportUtils.changeMoneyRequestHoldStatus(reportAction); + }, + getDescription: () => {}, + }, { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.joinThread',