diff --git a/.eslintrc.changed.js b/.eslintrc.changed.js index a1c2c452273e..55472b10ea86 100644 --- a/.eslintrc.changed.js +++ b/.eslintrc.changed.js @@ -11,15 +11,14 @@ module.exports = { overrides: [ { files: [ - 'src/libs/ReportUtils.ts', 'src/libs/actions/IOU.ts', 'src/libs/actions/Report.ts', 'src/libs/actions/Task.ts', 'src/libs/OptionsListUtils.ts', - 'src/libs/ReportActionsUtils.ts', 'src/libs/TransactionUtils/index.ts', 'src/pages/home/ReportScreen.tsx', 'src/pages/workspace/WorkspaceInitialPage.tsx', + 'src/pages/home/report/PureReportActionItem.tsx', ], rules: { 'rulesdir/no-default-id-values': 'off', diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index b588b3dd5359..38e4c5776e3e 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1300,7 +1300,7 @@ function getValidOptions( if (reportPreviewAction) { const iouReportID = ReportActionUtils.getIOUReportIDFromReportActionPreview(reportPreviewAction); - const iouReportActions = allSortedReportActions[iouReportID] ?? []; + const iouReportActions = iouReportID ? allSortedReportActions[iouReportID] ?? [] : []; const lastIOUAction = iouReportActions.find((iouAction) => iouAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); if (lastIOUAction) { reportOption.lastIOUCreationDate = lastIOUAction.lastModified; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 91381a0b0119..8fa1171aea1e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -257,7 +257,7 @@ const isPolicyEmployee = (policyID: string | undefined, policies: OnyxCollection /** * Checks if the current user is an owner (creator) of the policy. */ -const isPolicyOwner = (policy: OnyxInputOrEntry, currentUserAccountID: number): boolean => policy?.ownerAccountID === currentUserAccountID; +const isPolicyOwner = (policy: OnyxInputOrEntry, currentUserAccountID: number | undefined): boolean => !!currentUserAccountID && policy?.ownerAccountID === currentUserAccountID; /** * Create an object mapping member emails to their accountIDs. Filter for members without errors if includeMemberWithErrors is false, and get the login email from the personalDetail object using the accountID. @@ -420,7 +420,7 @@ function isTaxTrackingEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry): reportAction is ReportAction { @@ -347,7 +347,7 @@ function isInviteOrRemovedAction( /** * Returns whether the comment is a thread parent message/the first message in a thread */ -function isThreadParentMessage(reportAction: OnyxEntry, reportID: string): boolean { +function isThreadParentMessage(reportAction: OnyxEntry, reportID: string | undefined): boolean { const {childType, childVisibleActionCount = 0, childReportID} = reportAction ?? {}; return childType === CONST.REPORT.TYPE.CHAT && (childVisibleActionCount > 0 || String(childReportID) === reportID); } @@ -771,7 +771,11 @@ function replaceBaseURLInPolicyChangeLogAction(reportAction: ReportAction): Repo return updatedReportAction; } -function getLastVisibleAction(reportID: string, canUserPerformWriteAction?: boolean, actionsToMerge: Record | null> = {}): OnyxEntry { +function getLastVisibleAction( + reportID: string | undefined, + canUserPerformWriteAction?: boolean, + actionsToMerge: Record | null> = {}, +): OnyxEntry { let reportActions: Array = []; if (!isEmpty(actionsToMerge)) { reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge ?? {}, true)) as Array< @@ -801,7 +805,7 @@ function formatLastMessageText(lastMessageText: string) { } function getLastVisibleMessage( - reportID: string, + reportID: string | undefined, canUserPerformWriteAction?: boolean, actionsToMerge: Record | null> = {}, reportAction: OnyxInputOrEntry | undefined = undefined, @@ -893,12 +897,12 @@ function getLastClosedReportAction(reportActions: OnyxEntry): Ony * 4. We will get the second last action from filtered actions because the last * action is always the created action */ -function getFirstVisibleReportActionID(sortedReportActions: ReportAction[] = [], isOffline = false): string { +function getFirstVisibleReportActionID(sortedReportActions: ReportAction[] = [], isOffline = false): string | undefined { if (!Array.isArray(sortedReportActions)) { return ''; } const sortedFilterReportActions = sortedReportActions.filter((action) => !isDeletedAction(action) || (action?.childVisibleActionCount ?? 0) > 0 || isOffline); - return sortedFilterReportActions.length > 1 ? sortedFilterReportActions.at(sortedFilterReportActions.length - 2)?.reportActionID ?? '-1' : ''; + return sortedFilterReportActions.length > 1 ? sortedFilterReportActions.at(sortedFilterReportActions.length - 2)?.reportActionID : undefined; } /** @@ -991,8 +995,8 @@ function getReportPreviewAction(chatReportID: string | undefined, iouReportID: s /** * Get the iouReportID for a given report action. */ -function getIOUReportIDFromReportActionPreview(reportAction: OnyxEntry): string { - return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) ? getOriginalMessage(reportAction)?.linkedReportID ?? '-1' : '-1'; +function getIOUReportIDFromReportActionPreview(reportAction: OnyxEntry): string | undefined { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) ? getOriginalMessage(reportAction)?.linkedReportID : undefined; } /** @@ -1057,7 +1061,11 @@ const iouRequestTypes = new Set>([ * Gets the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions. * Returns a reportID if there is exactly one transaction thread for the report, and null otherwise. */ -function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): string | undefined { +function getOneTransactionThreadReportID( + reportID: string | undefined, + reportActions: OnyxEntry | ReportAction[], + isOffline: boolean | undefined = undefined, +): string | undefined { // If the report is not an IOU, Expense report, or Invoice, it shouldn't be treated as one-transaction report. const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) { @@ -1126,7 +1134,7 @@ function doesReportHaveVisibleActions(reportID: string, canUserPerformWriteActio return visibleReportActionsWithoutTaskSystemMessage.length > 0; } -function getAllReportActions(reportID: string): ReportActions { +function getAllReportActions(reportID: string | undefined): ReportActions { return allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}; } @@ -1500,7 +1508,7 @@ function isReportActionUnread(reportAction: OnyxEntry, lastReadTim */ function isCurrentActionUnread(report: OnyxEntry, reportAction: ReportAction): boolean { const lastReadTime = report?.lastReadTime ?? ''; - const sortedReportActions = getSortedReportActions(Object.values(getAllReportActions(report?.reportID ?? '-1'))); + const sortedReportActions = getSortedReportActions(Object.values(getAllReportActions(report?.reportID))); const currentActionIndex = sortedReportActions.findIndex((action) => action.reportActionID === reportAction.reportActionID); if (currentActionIndex === -1) { return false; @@ -1560,7 +1568,8 @@ function getDismissedViolationMessageText(originalMessage: ReportAction) { @@ -1577,7 +1586,7 @@ function didMessageMentionCurrentUser(reportAction: OnyxInputOrEntry'); + return accountIDsFromMessage.includes(currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID) || emailsFromMessage.includes(currentEmail) || message.includes(''); } /** @@ -1592,9 +1601,9 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - const reportActions = getAllReportActions(report?.reportID ?? ''); + const reportActions = getAllReportActions(report?.reportID); const action = Object.values(reportActions ?? {})?.find((reportAction) => { - const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : -1; + const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : undefined; return IOUTransactionID === transactionID; }); return action; @@ -1641,7 +1650,7 @@ function getExportIntegrationActionFragments(reportAction: OnyxEntry '2022-11-14'; const base62ReportID = getBase62ReportID(Number(reportID)); @@ -1775,7 +1784,7 @@ function getRenamedAction(reportAction: OnyxEntry>) { const originalMessage = getOriginalMessage(reportAction); - const submittersNames = PersonalDetailsUtils.getPersonalDetailsByIDs(originalMessage?.submittersAccountIDs ?? [], currentUserAccountID ?? -1).map( + const submittersNames = PersonalDetailsUtils.getPersonalDetailsByIDs(originalMessage?.submittersAccountIDs ?? [], currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID).map( ({displayName, login}) => displayName ?? login ?? 'Unknown Submitter', ); return Localize.translateLocal('workspaceActions.removedFromApprovalWorkflow', {submittersNames, count: submittersNames.length}); @@ -1802,9 +1811,9 @@ function getCardIssuedMessage(reportAction: OnyxEntry, shouldRende ? getOriginalMessage(reportAction) : undefined; - const assigneeAccountID = cardIssuedActionOriginalMessage?.assigneeAccountID ?? -1; - const cardID = cardIssuedActionOriginalMessage?.cardID ?? -1; - const assigneeDetails = PersonalDetailsUtils.getPersonalDetailsByIDs([assigneeAccountID], currentUserAccountID ?? -1).at(0); + const assigneeAccountID = cardIssuedActionOriginalMessage?.assigneeAccountID ?? CONST.DEFAULT_NUMBER_ID; + const cardID = cardIssuedActionOriginalMessage?.cardID ?? CONST.DEFAULT_NUMBER_ID; + const assigneeDetails = PersonalDetailsUtils.getPersonalDetailsByIDs([assigneeAccountID], currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID).at(0); const isPolicyAdmin = PolicyUtils.isPolicyAdmin(PolicyUtils.getPolicy(policyID)); const assignee = shouldRenderHTML ? `` : assigneeDetails?.firstName ?? assigneeDetails?.login ?? ''; const navigateRoute = isPolicyAdmin ? ROUTES.EXPENSIFY_CARD_DETAILS.getRoute(policyID, String(cardID)) : ROUTES.SETTINGS_DOMAINCARD_DETAIL.getRoute(String(cardID)); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 589c5f1a6adc..4b7aaa62cdbf 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1,7 +1,6 @@ import {format} from 'date-fns'; import {Str} from 'expensify-common'; import lodashEscape from 'lodash/escape'; -import lodashFindLastIndex from 'lodash/findLastIndex'; import lodashIntersection from 'lodash/intersection'; import isEmpty from 'lodash/isEmpty'; import lodashIsEqual from 'lodash/isEqual'; @@ -612,7 +611,9 @@ let currentUserPersonalDetails: OnyxEntry; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, callback: (value) => { - currentUserPersonalDetails = value?.[currentUserAccountID ?? -1] ?? undefined; + if (currentUserAccountID) { + currentUserPersonalDetails = value?.[currentUserAccountID] ?? undefined; + } allPersonalDetails = value ?? {}; allPersonalDetailLogins = Object.values(allPersonalDetails).map((personalDetail) => personalDetail?.login ?? ''); }, @@ -787,6 +788,14 @@ function getReportOrDraftReport(reportID: string | undefined): OnyxEntry return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; } +function getReportTransactions(reportID: string | undefined): Transaction[] { + if (!reportID) { + return []; + } + + return reportsTransactions[reportID] ?? []; +} + /** * Check if a report is a draft report */ @@ -799,7 +808,7 @@ function isDraftReport(reportID: string | undefined): boolean { /** * Returns the report */ -function getReport(reportID: string): OnyxEntry { +function getReport(reportID: string | undefined): OnyxEntry { return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; } @@ -807,7 +816,7 @@ function getReport(reportID: string): OnyxEntry { * Returns the report */ function getReportNameValuePairs(reportID?: string): OnyxEntry { - return allReportNameValuePair?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID ?? -1}`]; + return allReportNameValuePair?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]; } /** @@ -1051,8 +1060,8 @@ function isSettled(reportOrID: OnyxInputOrEntry | SearchReport | string /** * Whether the current user is the submitter of the report */ -function isCurrentUserSubmitter(reportID: string): boolean { - if (!allReports) { +function isCurrentUserSubmitter(reportID: string | undefined): boolean { + if (!allReports || !reportID) { return false; } const report = allReports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; @@ -1308,7 +1317,8 @@ function getDefaultNotificationPreferenceForReport(report: OnyxEntry): V * Get the notification preference given a report. This should ALWAYS default to 'hidden'. Do not change this! */ function getReportNotificationPreference(report: OnyxEntry): ValueOf { - return report?.participants?.[currentUserAccountID ?? -1]?.notificationPreference ?? CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const participant = currentUserAccountID ? report?.participants?.[currentUserAccountID] : undefined; + return participant?.notificationPreference ?? CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; } const CONCIERGE_ACCOUNT_ID_STRING = CONST.ACCOUNT_ID.CONCIERGE.toString(); @@ -1643,7 +1653,11 @@ function isPolicyExpenseChatAdmin(report: OnyxEntry, policies: OnyxColle /** * Checks if the current user is the admin of the policy. */ -function isPolicyAdmin(policyID: string, policies: OnyxCollection): boolean { +function isPolicyAdmin(policyID: string | undefined, policies: OnyxCollection): boolean { + if (!policyID) { + return false; + } + const policyRole = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.role; return policyRole === CONST.POLICY.ROLE.ADMIN; @@ -1653,7 +1667,7 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection): bool * Checks whether all the transactions linked to the IOU report are of the Distance Request type with pending routes */ function hasOnlyTransactionsWithPendingRoutes(iouReportID: string | undefined): boolean { - const transactions = reportsTransactions[iouReportID ?? ''] ?? []; + const transactions = getReportTransactions(iouReportID); // Early return false in case not having any transaction if (!transactions || transactions.length === 0) { @@ -1737,11 +1751,7 @@ function isMoneyRequestReport(reportOrID: OnyxInputOrEntry | SearchRepor * Checks if a report contains only Non-Reimbursable transactions */ function hasOnlyNonReimbursableTransactions(iouReportID: string | undefined): boolean { - if (!iouReportID) { - return false; - } - - const transactions = reportsTransactions[iouReportID ?? ''] ?? []; + const transactions = getReportTransactions(iouReportID); if (!transactions || transactions.length === 0) { return false; } @@ -1752,7 +1762,7 @@ function hasOnlyNonReimbursableTransactions(iouReportID: string | undefined): bo /** * Checks if a report has only one transaction associated with it */ -function isOneTransactionReport(reportID: string): boolean { +function isOneTransactionReport(reportID: string | undefined): boolean { const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? ([] as ReportAction[]); return ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions) !== null; } @@ -1786,9 +1796,9 @@ function isOneTransactionThread(reportID: string | undefined, parentReportID: st */ function getDisplayedReportID(reportID: string): string { const report = getReport(reportID); - const parentReportID = report?.parentReportID ?? ''; - const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, report?.parentReportActionID ?? ''); - return isOneTransactionThread(reportID, parentReportID, parentReportAction) ? parentReportID : reportID; + const parentReportID = report?.parentReportID; + const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, report?.parentReportActionID); + return parentReportID && isOneTransactionThread(reportID, parentReportID, parentReportAction) ? parentReportID : reportID; } /** @@ -1797,7 +1807,8 @@ function getDisplayedReportID(reportID: string): string { */ function isOneOnOneChat(report: OnyxEntry): boolean { const participants = report?.participants ?? {}; - const isCurrentUserParticipant = participants[currentUserAccountID ?? 0] ? 1 : 0; + const participant = currentUserAccountID ? participants[currentUserAccountID] : undefined; + const isCurrentUserParticipant = participant ? 1 : 0; const participantAmount = Object.keys(participants).length - isCurrentUserParticipant; if (participantAmount !== 1) { return false; @@ -2381,6 +2392,8 @@ function getIcons( policy?: OnyxInputOrEntry, invoiceReceiverPolicy?: OnyxInputOrEntry, ): Icon[] { + const ownerDetails = report?.ownerAccountID ? personalDetails?.[report.ownerAccountID] : undefined; + if (isEmptyObject(report)) { const fallbackIcon: Icon = { source: defaultIcon ?? FallbackAvatar, @@ -2393,12 +2406,13 @@ function getIcons( if (isExpenseRequest(report)) { const parentReportAction = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`]?.[report.parentReportActionID]; const workspaceIcon = getWorkspaceIcon(report, policy); + const actorDetails = parentReportAction?.actorAccountID ? personalDetails?.[parentReportAction.actorAccountID] : undefined; const memberIcon = { - source: personalDetails?.[parentReportAction?.actorAccountID ?? -1]?.avatar ?? FallbackAvatar, + source: actorDetails?.avatar ?? FallbackAvatar, id: parentReportAction?.actorAccountID, type: CONST.ICON_TYPE_AVATAR, - name: personalDetails?.[parentReportAction?.actorAccountID ?? -1]?.displayName ?? '', - fallbackIcon: personalDetails?.[parentReportAction?.actorAccountID ?? -1]?.fallbackIcon, + name: actorDetails?.displayName ?? '', + fallbackIcon: actorDetails?.fallbackIcon, }; return [memberIcon, workspaceIcon]; @@ -2407,13 +2421,14 @@ function getIcons( const parentReportAction = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`]?.[report.parentReportActionID]; const actorAccountID = getReportActionActorAccountID(parentReportAction, report, report); - const actorDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[actorAccountID ?? -1], '', false); + const actorDetails = actorAccountID ? personalDetails?.[actorAccountID] : undefined; + const actorDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(actorDetails, '', false); const actorIcon = { id: actorAccountID, - source: personalDetails?.[actorAccountID ?? -1]?.avatar ?? FallbackAvatar, + source: actorDetails?.avatar ?? FallbackAvatar, name: LocalePhoneNumber.formatPhoneNumber(actorDisplayName), type: CONST.ICON_TYPE_AVATAR, - fallbackIcon: personalDetails?.[parentReportAction?.actorAccountID ?? -1]?.fallbackIcon, + fallbackIcon: actorDetails?.fallbackIcon, }; if (isWorkspaceThread(report)) { @@ -2425,10 +2440,10 @@ function getIcons( if (isTaskReport(report)) { const ownerIcon = { id: report?.ownerAccountID, - source: personalDetails?.[report?.ownerAccountID ?? -1]?.avatar ?? FallbackAvatar, + source: ownerDetails?.avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, - name: personalDetails?.[report?.ownerAccountID ?? -1]?.displayName ?? '', - fallbackIcon: personalDetails?.[report?.ownerAccountID ?? -1]?.fallbackIcon, + name: ownerDetails?.displayName ?? '', + fallbackIcon: ownerDetails?.fallbackIcon, }; if (isWorkspaceTaskReport(report)) { @@ -2475,33 +2490,34 @@ function getIcons( if (isPolicyExpenseChat(report) || isExpenseReport(report)) { const workspaceIcon = getWorkspaceIcon(report, policy); const memberIcon = { - source: personalDetails?.[report?.ownerAccountID ?? -1]?.avatar ?? FallbackAvatar, + source: ownerDetails?.avatar ?? FallbackAvatar, id: report?.ownerAccountID, type: CONST.ICON_TYPE_AVATAR, - name: personalDetails?.[report?.ownerAccountID ?? -1]?.displayName ?? '', - fallbackIcon: personalDetails?.[report?.ownerAccountID ?? -1]?.fallbackIcon, + name: ownerDetails?.displayName ?? '', + fallbackIcon: ownerDetails?.fallbackIcon, }; return isExpenseReport(report) ? [memberIcon, workspaceIcon] : [workspaceIcon, memberIcon]; } if (isIOUReport(report)) { + const managerDetails = report?.managerID ? personalDetails?.[report.managerID] : undefined; const managerIcon = { - source: personalDetails?.[report?.managerID ?? -1]?.avatar ?? FallbackAvatar, + source: managerDetails?.avatar ?? FallbackAvatar, id: report?.managerID, type: CONST.ICON_TYPE_AVATAR, - name: personalDetails?.[report?.managerID ?? -1]?.displayName ?? '', - fallbackIcon: personalDetails?.[report?.managerID ?? -1]?.fallbackIcon, + name: managerDetails?.displayName ?? '', + fallbackIcon: managerDetails?.fallbackIcon, }; const ownerIcon = { id: report?.ownerAccountID, - source: personalDetails?.[report?.ownerAccountID ?? -1]?.avatar ?? FallbackAvatar, + source: ownerDetails?.avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, - name: personalDetails?.[report?.ownerAccountID ?? -1]?.displayName ?? '', - fallbackIcon: personalDetails?.[report?.ownerAccountID ?? -1]?.fallbackIcon, + name: ownerDetails?.displayName ?? '', + fallbackIcon: ownerDetails?.fallbackIcon, }; const isManager = currentUserAccountID === report?.managerID; // For one transaction IOUs, display a simplified report icon - if (isOneTransactionReport(report?.reportID ?? '-1')) { + if (isOneTransactionReport(report?.reportID)) { return [ownerIcon]; } @@ -2509,7 +2525,7 @@ function getIcons( } if (isSelfDM(report)) { - return getIconsForParticipants([currentUserAccountID ?? -1], personalDetails); + return getIconsForParticipants(currentUserAccountID ? [currentUserAccountID] : [], personalDetails); } if (isSystemChat(report)) { @@ -2767,7 +2783,7 @@ function buildOptimisticCancelPaymentReportAction(expenseReportID: string, amoun */ function getLastVisibleMessage(reportID: string | undefined, actionsToMerge: ReportActions = {}): LastVisibleMessage { const report = getReportOrDraftReport(reportID); - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID ?? '-1', canUserPerformWriteAction(report), actionsToMerge); + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID, canUserPerformWriteAction(report), actionsToMerge); // For Chat Report with deleted parent actions, let us fetch the correct message if (ReportActionsUtils.isDeletedParentAction(lastVisibleAction) && !isEmptyObject(report) && isChatReport(report)) { @@ -2778,7 +2794,7 @@ function getLastVisibleMessage(reportID: string | undefined, actionsToMerge: Rep } // Fetch the last visible message for report represented by reportID and based on actions to merge. - return ReportActionsUtils.getLastVisibleMessage(reportID ?? '-1', canUserPerformWriteAction(report), actionsToMerge); + return ReportActionsUtils.getLastVisibleMessage(reportID, canUserPerformWriteAction(report), actionsToMerge); } /** @@ -2906,11 +2922,10 @@ function requiresAttentionFromCurrentUser(optionOrReport: OnyxEntry | Op } /** - * Returns number of transactions that are nonReimbursable - * + * Checks if the report contains at least one Non-Reimbursable transaction */ function hasNonReimbursableTransactions(iouReportID: string | undefined): boolean { - const transactions = reportsTransactions[iouReportID ?? ''] ?? []; + const transactions = getReportTransactions(iouReportID); return transactions.filter((transaction) => transaction.reimbursable === false).length > 0; } @@ -2956,7 +2971,7 @@ function getMoneyRequestSpendBreakdown(report: OnyxInputOrEntry, allRepo */ function getPolicyExpenseChatName(report: OnyxEntry, policy?: OnyxEntry): string | undefined { const ownerAccountID = report?.ownerAccountID; - const personalDetails = allPersonalDetails?.[ownerAccountID ?? -1]; + const personalDetails = ownerAccountID ? allPersonalDetails?.[ownerAccountID] : undefined; const login = personalDetails ? personalDetails.login : null; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const reportOwnerDisplayName = getDisplayNameForParticipant(ownerAccountID) || login || report?.reportName; @@ -2975,7 +2990,7 @@ function getPolicyExpenseChatName(report: OnyxEntry, policy?: OnyxEntry< // If this user is not admin and this policy expense chat has been archived because of account merging, this must be an old workspace chat // of the account which was merged into the current user's account. Use the name of the policy as the name of the report. if (isArchivedRoom(report, getReportNameValuePairs(report?.reportID))) { - const lastAction = ReportActionsUtils.getLastVisibleAction(report?.reportID ?? '-1'); + const lastAction = ReportActionsUtils.getLastVisibleAction(report?.reportID); const archiveReason = ReportActionsUtils.isClosedAction(lastAction) ? ReportActionsUtils.getOriginalMessage(lastAction)?.reason : CONST.REPORT.ARCHIVE_REASON.DEFAULT; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED && policyExpenseChatRole !== CONST.POLICY.ROLE.ADMIN) { return getPolicyName(report, false, policy); @@ -3006,7 +3021,7 @@ function isReportFieldOfTypeTitle(reportField: OnyxEntry): bo /** * Check if Report has any held expenses */ -function isHoldCreator(transaction: OnyxEntry, reportID: string): boolean { +function isHoldCreator(transaction: OnyxEntry, reportID: string | undefined): boolean { const holdReportAction = ReportActionsUtils.getReportAction(reportID, `${transaction?.comment?.hold ?? ''}`); return isActionCreator(holdReportAction); } @@ -3022,7 +3037,7 @@ function isReportFieldDisabled(report: OnyxEntry, reportField: OnyxEntry const isReportSettled = isSettled(report?.reportID); const isReportClosed = isClosedReport(report); const isTitleField = isReportFieldOfTypeTitle(reportField); - const isAdmin = isPolicyAdmin(report?.policyID ?? '-1', {[`${ONYXKEYS.COLLECTION.POLICY}${policy?.id ?? '-1'}`]: policy}); + const isAdmin = isPolicyAdmin(report?.policyID, {[`${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`]: policy}); return isTitleField ? !reportField?.deletable : !isAdmin && (isReportSettled || isReportClosed); } @@ -3053,7 +3068,7 @@ function getReportFieldKey(reportFieldId: string | undefined) { /** * Get the report fields attached to the policy given policyID */ -function getReportFieldsByPolicyID(policyID: string): Record { +function getReportFieldsByPolicyID(policyID: string | undefined): Record { const policyReportFields = Object.entries(allPolicies ?? {}).find(([key]) => key.replace(ONYXKEYS.COLLECTION.POLICY, '') === policyID); const fieldList = policyReportFields?.[1]?.fieldList; @@ -3105,8 +3120,8 @@ function getAvailableReportFields(report: Report, policyReportFields: PolicyRepo * Get the title for an IOU or expense chat which will be showing the payer and the amount */ function getMoneyRequestReportName(report: OnyxEntry, policy?: OnyxEntry, invoiceReceiverPolicy?: OnyxEntry): string { - const isReportSettled = isSettled(report?.reportID ?? '-1'); - const reportFields = isReportSettled ? report?.fieldList : getReportFieldsByPolicyID(report?.policyID ?? '-1'); + const isReportSettled = isSettled(report?.reportID); + const reportFields = isReportSettled ? report?.fieldList : getReportFieldsByPolicyID(report?.policyID); const titleReportField = Object.values(reportFields ?? {}).find((reportField) => reportField?.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID); if (titleReportField && report?.reportName && isPaidGroupPolicyExpenseReport(report)) { @@ -3229,7 +3244,7 @@ function canEditMoneyRequest(reportAction: OnyxInputOrEntry) return {canHoldRequest: false, canUnholdRequest: false}; } - const moneyRequestReportID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID ?? 0; + const moneyRequestReportID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID; const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); if (!moneyRequestReportID || !moneyRequestReport) { @@ -3375,7 +3390,7 @@ function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry) const isRequestSettled = isSettled(moneyRequestReport?.reportID); const isApproved = isReportApproved(moneyRequestReport); - const transactionID = moneyRequestReport ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : 0; + const transactionID = moneyRequestReport ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : undefined; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const parentReportAction = isThread(moneyRequestReport) @@ -3383,7 +3398,7 @@ function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry) : undefined; const isRequestIOU = isIOUReport(moneyRequestReport); - const isHoldActionCreator = isHoldCreator(transaction, reportAction.childReportID ?? '-1'); + const isHoldActionCreator = isHoldCreator(transaction, reportAction.childReportID); const isTrackExpenseMoneyReport = isTrackExpenseReport(moneyRequestReport); const isActionOwner = @@ -3391,7 +3406,7 @@ function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry) typeof currentUserPersonalDetails?.accountID === 'number' && parentReportAction.actorAccountID === currentUserPersonalDetails?.accountID; const isApprover = isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; - const isAdmin = isPolicyAdmin(moneyRequestReport.policyID ?? '-1', allPolicies); + const isAdmin = isPolicyAdmin(moneyRequestReport.policyID, allPolicies); const isOnHold = TransactionUtils.isOnHold(transaction); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const isClosed = isClosedReport(moneyRequestReport); @@ -3413,25 +3428,31 @@ const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry, bac if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { return; } - const moneyRequestReportID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID ?? 0; + const moneyRequestReportID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID; const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); if (!moneyRequestReportID || !moneyRequestReport) { return; } - const transactionID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID ?? ''; + const transactionID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID; + + if (!transactionID || !reportAction.childReportID) { + Log.warn('Missing transactionID and reportAction.childReportID during the change of the money request hold status'); + return; + } + 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 ?? '', searchHash); + IOU.unholdRequest(transactionID, reportAction.childReportID, searchHash); } else { const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); Navigation.navigate( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID ?? '', backTo || activeRoute, searchHash), + ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID, backTo || activeRoute, searchHash), ); } }; @@ -3440,7 +3461,7 @@ const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry, bac * Gets all transactions on an IOU report with a receipt */ function getTransactionsWithReceipts(iouReportID: string | undefined): Transaction[] { - const transactions = reportsTransactions[iouReportID ?? ''] ?? []; + const transactions = getReportTransactions(iouReportID); return transactions.filter((transaction) => TransactionUtils.hasReceipt(transaction)); } @@ -3467,10 +3488,10 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use withOnyx or Onyx.connect() instead. */ function getLinkedTransaction(reportAction: OnyxEntry): OnyxEntry { - let transactionID = ''; + let transactionID; if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { - transactionID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID ?? '-1'; + transactionID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID; } return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; @@ -3480,7 +3501,7 @@ function getLinkedTransaction(reportAction: OnyxEntry { if (!ReportActionsUtils.isMoneyRequestAction(action)) { @@ -3508,7 +3529,7 @@ function getReportActionWithMissingSmartscanFields(iouReportID: string): ReportA /** * Check if iouReportID has required missing fields */ -function shouldShowRBRForMissingSmartscanFields(iouReportID: string): boolean { +function shouldShowRBRForMissingSmartscanFields(iouReportID: string | undefined): boolean { return !!getReportActionWithMissingSmartscanFields(iouReportID); } @@ -3683,7 +3704,7 @@ function getReportPreviewMessage( } if (report.isWaitingOnBankAccount) { - const submitterDisplayName = getDisplayNameForParticipant(report.ownerAccountID ?? -1, true) ?? ''; + const submitterDisplayName = getDisplayNameForParticipant(report.ownerAccountID, true) ?? ''; return Localize.translateLocal('iou.waitingOnBankAccount', {submitterDisplayName}); } @@ -3883,7 +3904,7 @@ function getInvoicePayerName(report: OnyxEntry, invoiceReceiverPolicy?: /** * Parse html of reportAction into text */ -function parseReportActionHtmlToText(reportAction: OnyxEntry, reportID: string, childReportID?: string): string { +function parseReportActionHtmlToText(reportAction: OnyxEntry, reportID: string | undefined, childReportID?: string): string { if (!reportAction) { return ''; } @@ -3947,7 +3968,7 @@ function getReportActionMessage(reportAction: OnyxEntry, reportID? return getReimbursementQueuedActionMessage(reportAction, getReportOrDraftReport(reportID), false); } - return parseReportActionHtmlToText(reportAction, reportID ?? '', childReportID); + return parseReportActionHtmlToText(reportAction, reportID, childReportID); } /** @@ -3956,8 +3977,8 @@ function getReportActionMessage(reportAction: OnyxEntry, reportID? function getInvoicesChatName(report: OnyxEntry, receiverPolicy: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; - const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? '-1'; + const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : CONST.DEFAULT_NUMBER_ID; + const invoiceReceiverPolicyID = isIndividual ? undefined : invoiceReceiver?.policyID; const invoiceReceiverPolicy = receiverPolicy ?? getPolicy(invoiceReceiverPolicyID); const isCurrentUserReceiver = (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyAdmin(invoiceReceiverPolicy)); @@ -4058,7 +4079,7 @@ function getReportName( } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : undefined); - const reportActionMessage = getReportActionMessage(parentReportAction, report?.parentReportID, report?.reportID ?? '').replace(/(\n+|\r\n|\n|\r)/gm, ' '); + const reportActionMessage = getReportActionMessage(parentReportAction, report?.parentReportID, report?.reportID).replace(/(\n+|\r\n|\n|\r)/gm, ' '); if (isAttachment && reportActionMessage) { return `[${Localize.translateLocal('common.attachment')}]`; } @@ -4271,7 +4292,11 @@ function goBackToDetailsPage(report: OnyxEntry, backTo?: string) { return; } - Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1', backTo)); + if (report?.reportID) { + Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID, backTo)); + } else { + Log.warn('Missing reportID during navigation back to the details page'); + } } function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean) { @@ -4458,7 +4483,7 @@ function buildOptimisticAddCommentReportAction( const isAttachmentOnly = file && !text; const isAttachmentWithText = !!text && file !== undefined; - const accountID = actorAccountID ?? currentUserAccountID ?? -1; + const accountID = actorAccountID ?? currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID; const delegateAccountDetails = PersonalDetailsUtils.getPersonalDetailByEmail(delegateEmail); // Remove HTML from text when applying optimistic offline comment @@ -4618,7 +4643,7 @@ function buildOptimisticIOUReport( const formattedTotal = CurrencyUtils.convertToDisplayString(total, currency); const personalDetails = getPersonalDetailsForAccountID(payerAccountID); const payerEmail = 'login' in personalDetails ? personalDetails.login : ''; - const policyID = getReport(chatReportID)?.policyID ?? '-1'; + const policyID = getReport(chatReportID)?.policyID; const policy = getPolicy(policyID); const participants: Participants = { @@ -4684,8 +4709,7 @@ function populateOptimisticReportFormula(formula: string, report: OptimisticExpe /** Builds an optimistic invoice report with a randomly generated reportID */ function buildOptimisticInvoiceReport(chatReportID: string, policyID: string, receiverAccountID: number, receiverName: string, total: number, currency: string): OptimisticExpenseReport { const formattedTotal = CurrencyUtils.convertToDisplayString(total, currency); - - return { + const invoiceReport = { reportID: generateReportID(), chatReportID, policyID, @@ -4699,9 +4723,6 @@ function buildOptimisticInvoiceReport(chatReportID: string, policyID: string, re statusNum: CONST.REPORT.STATUS_NUM.OPEN, total, participants: { - [currentUserAccountID ?? -1]: { - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, - }, [receiverAccountID]: { notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }, @@ -4709,6 +4730,12 @@ function buildOptimisticInvoiceReport(chatReportID: string, policyID: string, re parentReportID: chatReportID, lastVisibleActionCreated: DateUtils.getDBTime(), }; + + if (currentUserAccountID) { + invoiceReport.participants[currentUserAccountID] = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}; + } + + return invoiceReport; } /** @@ -5035,8 +5062,8 @@ function buildOptimisticIOUReportAction( originalMessage.participantAccountIDs = currentUserAccountID ? [currentUserAccountID] : []; } else { originalMessage.participantAccountIDs = currentUserAccountID - ? [currentUserAccountID, ...participants.map((participant) => participant.accountID ?? -1)] - : participants.map((participant) => participant.accountID ?? -1); + ? [currentUserAccountID, ...participants.map((participant) => participant.accountID ?? CONST.DEFAULT_NUMBER_ID)] + : participants.map((participant) => participant.accountID ?? CONST.DEFAULT_NUMBER_ID); } } @@ -5247,14 +5274,14 @@ function buildOptimisticReportPreview( }, ], created, - accountID: iouReport?.managerID ?? -1, + accountID: iouReport?.managerID, // The preview is initially whispered if created with a receipt, so the actor is the current user as well actorAccountID: hasReceipt ? currentUserAccountID : reportActorAccountID, childReportID: childReportID ?? iouReport?.reportID, childMoneyRequestCount: 1, childLastActorAccountID: currentUserAccountID, childLastMoneyRequestComment: comment, - childRecentReceiptTransactionIDs: hasReceipt && !isEmptyObject(transaction) ? {[transaction?.transactionID ?? '-1']: created} : undefined, + childRecentReceiptTransactionIDs: hasReceipt && !isEmptyObject(transaction) && transaction?.transactionID ? {[transaction.transactionID]: created} : undefined, }; } @@ -5432,11 +5459,13 @@ function updateReportPreview( : recentReceiptTransactions, // As soon as we add a transaction without a receipt to the report, it will have ready expenses, // so we remove the whisper - originalMessage: { - ...(originalMessage ?? {}), - whisperedTo: hasReceipt ? originalMessage?.whisperedTo : [], - linkedReportID: originalMessage?.linkedReportID ?? '0', - }, + originalMessage: originalMessage + ? { + ...originalMessage, + whisperedTo: hasReceipt ? originalMessage.whisperedTo : [], + linkedReportID: originalMessage.linkedReportID, + } + : undefined, }; } @@ -6086,7 +6115,7 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp const pendingChatMembers = getPendingChatMembers(currentUserAccountID ? [currentUserAccountID] : [], [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); const adminsChatData = { ...buildOptimisticChatReport( - [currentUserAccountID ?? -1], + currentUserAccountID ? [currentUserAccountID] : [], CONST.REPORT.WORKSPACE_CHAT_ROOMS.ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, policyID, @@ -6102,7 +6131,7 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp }; const expenseChatData = buildOptimisticChatReport( - [currentUserAccountID ?? -1], + currentUserAccountID ? [currentUserAccountID] : [], '', CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, policyID, @@ -6246,7 +6275,7 @@ function buildTransactionThread( participantAccountIDs, getTransactionReportName(reportAction), undefined, - moneyRequestReport?.policyID ?? '-1', + moneyRequestReport?.policyID, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, false, '', @@ -6427,7 +6456,7 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV } // We only show the RBR to the submitter - if (!isCurrentUserSubmitter(report.reportID ?? '')) { + if (!isCurrentUserSubmitter(report.reportID)) { return false; } @@ -6447,7 +6476,7 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV * Checks to see if a report contains a violation */ function hasViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { - const transactions = reportsTransactions[reportID] ?? []; + const transactions = getReportTransactions(reportID); return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } @@ -6455,7 +6484,7 @@ function hasViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { - const transactions = reportsTransactions[reportID] ?? []; + const transactions = getReportTransactions(reportID); return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } @@ -6463,7 +6492,7 @@ function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxC * Checks to see if a report contains a violation of type `notice` */ function hasNoticeTypeViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { - const transactions = reportsTransactions[reportID] ?? []; + const transactions = getReportTransactions(reportID); return transactions.some((transaction) => TransactionUtils.hasNoticeTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } @@ -6479,7 +6508,7 @@ function hasReportViolations(reportID: string) { function shouldAdminsRoomBeVisible(report: OnyxEntry): boolean { const accountIDs = Object.entries(report?.participants ?? {}).map(([accountID]) => Number(accountID)); const adminAccounts = PersonalDetailsUtils.getLoginsByAccountIDs(accountIDs).filter((login) => !PolicyUtils.isExpensifyTeam(login)); - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(report?.reportID ?? ''); + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(report?.reportID); if ((lastVisibleAction ? ReportActionsUtils.isCreatedAction(lastVisibleAction) : report?.lastActionType === CONST.REPORT.ACTIONS.TYPE.CREATED) && adminAccounts.length <= 1) { return false; } @@ -6508,7 +6537,7 @@ function getAllReportActionsErrorsAndReportActionThatRequiresAttention(report: O const parentReportAction: OnyxEntry = !report?.parentReportID || !report?.parentReportActionID ? undefined - : allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID ?? '-1'}`]?.[report.parentReportActionID ?? '-1']; + : allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`]?.[report.parentReportActionID]; if (!isArchivedRoom(report)) { if (ReportActionsUtils.wasActionTakenByCurrentUser(parentReportAction) && ReportActionsUtils.isTransactionThread(parentReportAction)) { @@ -6519,9 +6548,9 @@ function getAllReportActionsErrorsAndReportActionThatRequiresAttention(report: O reportAction = undefined; } } else if ((isIOUReport(report) || isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) { - if (shouldShowRBRForMissingSmartscanFields(report?.reportID ?? '-1') && !isSettled(report?.reportID)) { + if (shouldShowRBRForMissingSmartscanFields(report?.reportID) && !isSettled(report?.reportID)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); - reportAction = getReportActionWithMissingSmartscanFields(report?.reportID ?? '-1'); + reportAction = getReportActionWithMissingSmartscanFields(report?.reportID); } } else if (hasSmartscanError(reportActionsArray)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); @@ -6641,7 +6670,7 @@ function reasonForReportToBeInOptionList({ } // If this is a transaction thread associated with a report that only has one transaction, omit it - if (isOneTransactionThread(report.reportID, report.parentReportID ?? '-1', parentReportAction)) { + if (isOneTransactionThread(report.reportID, report.parentReportID, parentReportAction)) { return null; } @@ -6724,11 +6753,7 @@ function reasonForReportToBeInOptionList({ } // Hide chat threads where the parent message is pending removal - if ( - !isEmptyObject(parentReportAction) && - ReportActionsUtils.isPendingRemove(parentReportAction) && - ReportActionsUtils.isThreadParentMessage(parentReportAction, report?.reportID ?? '') - ) { + if (!isEmptyObject(parentReportAction) && ReportActionsUtils.isPendingRemove(parentReportAction) && ReportActionsUtils.isThreadParentMessage(parentReportAction, report?.reportID)) { return null; } @@ -6793,10 +6818,11 @@ function getInvoiceChatByParticipants(receiverID: string | number, receiverType: /** * Attempts to find a policy expense report in onyx that is owned by ownerAccountID in a given policy */ -function getPolicyExpenseChat(ownerAccountID: number, policyID: string | undefined): OnyxEntry { - if (!policyID) { +function getPolicyExpenseChat(ownerAccountID: number | undefined, policyID: string | undefined): OnyxEntry { + if (!ownerAccountID || !policyID) { return; } + return Object.values(allReports ?? {}).find((report: OnyxEntry) => { // If the report has been deleted, then skip it if (!report) { @@ -6878,21 +6904,6 @@ function shouldShowFlagComment(reportAction: OnyxInputOrEntry, rep ); } -/** - * @param sortedAndFilteredReportActions - reportActions for the report, sorted newest to oldest, and filtered for only those that should be visible - */ -function getNewMarkerReportActionID(report: OnyxEntry, sortedAndFilteredReportActions: ReportAction[]): string { - if (!isUnread(report)) { - return ''; - } - - const newMarkerIndex = lodashFindLastIndex(sortedAndFilteredReportActions, (reportAction) => (reportAction.created ?? '') > (report?.lastReadTime ?? '')); - - return newMarkerIndex !== -1 && 'reportActionID' in (sortedAndFilteredReportActions?.at(newMarkerIndex) ?? {}) - ? sortedAndFilteredReportActions.at(newMarkerIndex)?.reportActionID ?? '' - : ''; -} - /** * Performs the markdown conversion, and replaces code points > 127 with C escape sequences * Used for compatibility with the backend auth validator for AddComment, and to account for MD in comments @@ -7076,7 +7087,7 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, policy: OnyxEntry 0; - const isPolicyOwnedByExpensifyAccounts = report?.policyID ? CONST.EXPENSIFY_ACCOUNT_IDS.includes(getPolicy(report?.policyID ?? '-1')?.ownerAccountID ?? -1) : false; + const policyOwnerAccountID = getPolicy(report?.policyID)?.ownerAccountID; + const isPolicyOwnedByExpensifyAccounts = policyOwnerAccountID ? CONST.EXPENSIFY_ACCOUNT_IDS.includes(policyOwnerAccountID) : false; if (doParticipantsIncludeExpensifyAccounts && !isPolicyOwnedByExpensifyAccounts) { return []; } @@ -7198,7 +7210,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boo return false; } - const invoiceReport = getReportOrDraftReport(report?.iouReportID ?? '-1'); + const invoiceReport = getReportOrDraftReport(report?.iouReportID); if (invoiceReport?.ownerAccountID === currentUserAccountID) { return false; @@ -7281,7 +7293,7 @@ function shouldReportShowSubscript(report: OnyxEntry): boolean { return true; } - if (isExpenseReport(report) && isOneTransactionReport(report?.reportID ?? '-1')) { + if (isExpenseReport(report) && isOneTransactionReport(report?.reportID)) { return true; } @@ -7332,7 +7344,7 @@ function isMoneyRequestReportPendingDeletion(reportOrID: OnyxEntry | str return false; } - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); + const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); return parentReportAction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } @@ -7353,7 +7365,7 @@ function canUserPerformWriteAction(report: OnyxEntry) { */ function getOriginalReportID(reportID: string, reportAction: OnyxInputOrEntry): string | undefined { const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; - const currentReportAction = reportActions?.[reportAction?.reportActionID ?? '-1'] ?? null; + const currentReportAction = reportAction?.reportActionID ? reportActions?.[reportAction.reportActionID] : undefined; const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions ?? ([] as ReportAction[])); const isThreadReportParentAction = reportAction?.childReportID?.toString() === reportID; if (Object.keys(currentReportAction ?? {}).length === 0) { @@ -7398,7 +7410,9 @@ function canCreateRequest(report: OnyxEntry, policy: OnyxEntry, } function getWorkspaceChats(policyID: string, accountIDs: number[], reports: OnyxCollection = allReports): Array> { - return Object.values(reports ?? {}).filter((report) => isPolicyExpenseChat(report) && (report?.policyID ?? '-1') === policyID && accountIDs.includes(report?.ownerAccountID ?? -1)); + return Object.values(reports ?? {}).filter( + (report) => isPolicyExpenseChat(report) && report?.policyID === policyID && report?.ownerAccountID && accountIDs.includes(report?.ownerAccountID), + ); } /** @@ -7407,7 +7421,7 @@ function getWorkspaceChats(policyID: string, accountIDs: number[], reports: Onyx * @param policyID - the workspace ID to get all associated reports */ function getAllWorkspaceReports(policyID: string): Array> { - return Object.values(allReports ?? {}).filter((report) => (report?.policyID ?? '-1') === policyID); + return Object.values(allReports ?? {}).filter((report) => report?.policyID === policyID); } /** @@ -7557,7 +7571,7 @@ function getTaskAssigneeChatOnyxData( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${assigneeChatReportID}`, - value: {[optimisticAssigneeAddComment.reportAction.reportActionID ?? '-1']: optimisticAssigneeAddComment.reportAction as ReportAction}, + value: {[optimisticAssigneeAddComment.reportAction.reportActionID]: optimisticAssigneeAddComment.reportAction as ReportAction}, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -7568,12 +7582,12 @@ function getTaskAssigneeChatOnyxData( successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${assigneeChatReportID}`, - value: {[optimisticAssigneeAddComment.reportAction.reportActionID ?? '-1']: {isOptimisticAction: null}}, + value: {[optimisticAssigneeAddComment.reportAction.reportActionID]: {isOptimisticAction: null}}, }); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${assigneeChatReportID}`, - value: {[optimisticAssigneeAddComment.reportAction.reportActionID ?? '-1']: {pendingAction: null}}, + value: {[optimisticAssigneeAddComment.reportAction.reportActionID]: {pendingAction: null}}, }); } @@ -7606,7 +7620,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry, switch (originalMessage.paymentType) { case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: - translationKey = hasMissingInvoiceBankAccount(IOUReportID ?? '-1') ? 'iou.payerSettledWithMissingBankAccount' : 'iou.paidElsewhereWithAmount'; + translationKey = hasMissingInvoiceBankAccount(IOUReportID) ? 'iou.payerSettledWithMissingBankAccount' : 'iou.paidElsewhereWithAmount'; break; case CONST.IOU.PAYMENT_TYPE.EXPENSIFY: case CONST.IOU.PAYMENT_TYPE.VBBA: @@ -7693,7 +7707,7 @@ function isValidReport(report?: OnyxEntry): boolean { /** * Check to see if we are a participant of this report. */ -function isReportParticipant(accountID: number, report: OnyxEntry): boolean { +function isReportParticipant(accountID: number | undefined, report: OnyxEntry): boolean { if (!accountID) { return false; } @@ -7712,7 +7726,7 @@ function isReportParticipant(accountID: number, report: OnyxEntry): bool * Check to see if the current user has access to view the report. */ function canCurrentUserOpenReport(report: OnyxEntry): boolean { - return (isReportParticipant(currentUserAccountID ?? 0, report) || isPublicRoom(report)) && canAccessReport(report, allPolicies, allBetas); + return (isReportParticipant(currentUserAccountID, report) || isPublicRoom(report)) && canAccessReport(report, allPolicies, allBetas); } function shouldUseFullTitleToDisplay(report: OnyxEntry): boolean { @@ -7736,7 +7750,7 @@ function canEditReportDescription(report: OnyxEntry, policy: OnyxEntry

, session: OnyxEntry TransactionUtils.isOnHold(transaction)); } @@ -7817,7 +7831,8 @@ function getAllHeldTransactions(iouReportID?: string): Transaction[] { * Check if Report has any held expenses */ function hasHeldExpenses(iouReportID?: string, allReportTransactions?: SearchTransaction[]): boolean { - const transactions = allReportTransactions ?? reportsTransactions[iouReportID ?? ''] ?? []; + const iouReportTransactions = getReportTransactions(iouReportID); + const transactions = allReportTransactions ?? iouReportTransactions; return transactions.some((transaction) => TransactionUtils.isOnHold(transaction)); } @@ -7825,7 +7840,8 @@ function hasHeldExpenses(iouReportID?: string, allReportTransactions?: SearchTra * Check if all expenses in the Report are on hold */ function hasOnlyHeldExpenses(iouReportID?: string, allReportTransactions?: SearchTransaction[]): boolean { - const reportTransactions = allReportTransactions ?? reportsTransactions[iouReportID ?? ''] ?? []; + const transactionsByIouReportID = getReportTransactions(iouReportID); + const reportTransactions = allReportTransactions ?? transactionsByIouReportID; return reportTransactions.length > 0 && !reportTransactions.some((transaction) => !TransactionUtils.isOnHold(transaction)); } @@ -7845,7 +7861,7 @@ function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEn return true; } - const allReportTransactions = reportsTransactions[report.reportID] ?? []; + const allReportTransactions = getReportTransactions(report.reportID); const hasPendingTransaction = allReportTransactions.some((transaction) => !!transaction.pendingAction); const hasTransactionWithDifferentCurrency = allReportTransactions.some((transaction) => transaction.currency !== report.currency); @@ -7915,7 +7931,7 @@ function getAllAncestorReportActions(report: Report | null | undefined, currentU while (parentReportID) { const parentReport = currentUpdatedReport && currentUpdatedReport.reportID === parentReportID ? currentUpdatedReport : getReportOrDraftReport(parentReportID); - const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); + const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID); if ( !parentReport || @@ -7957,7 +7973,7 @@ function getAllAncestorReportActionIDs(report: Report | null | undefined, includ while (parentReportID) { const parentReport = getReportOrDraftReport(parentReportID); - const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); + const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID); if ( !parentReportAction || @@ -7968,8 +7984,10 @@ function getAllAncestorReportActionIDs(report: Report | null | undefined, includ break; } - allAncestorIDs.reportIDs.push(parentReportID ?? '-1'); - allAncestorIDs.reportActionsIDs.push(parentReportActionID ?? '-1'); + allAncestorIDs.reportIDs.push(parentReportID); + if (parentReportActionID) { + allAncestorIDs.reportActionsIDs.push(parentReportActionID); + } if (!parentReport) { break; @@ -8007,7 +8025,7 @@ function getOptimisticDataForParentReportAction(reportID: string, lastVisibleAct const ancestorReportAction = ReportActionsUtils.getReportAction(ancestorReport.reportID, ancestors.reportActionsIDs.at(index) ?? ''); - if (!ancestorReportAction || isEmptyObject(ancestorReportAction)) { + if (!ancestorReportAction?.reportActionID || isEmptyObject(ancestorReportAction)) { return null; } @@ -8015,7 +8033,7 @@ function getOptimisticDataForParentReportAction(reportID: string, lastVisibleAct onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${ancestorReport.reportID}`, value: { - [ancestorReportAction?.reportActionID ?? '-1']: updateOptimisticParentReportAction(ancestorReportAction, lastVisibleActionCreated, type), + [ancestorReportAction.reportActionID]: updateOptimisticParentReportAction(ancestorReportAction, lastVisibleActionCreated, type), }, }; }); @@ -8120,7 +8138,7 @@ function getTripTransactions(tripRoomReportID: string | undefined, reportFieldTo const tripTransactionReportIDs = Object.values(allReports ?? {}) .filter((report) => report && report?.[reportFieldToCompare] === tripRoomReportID) .map((report) => report?.reportID); - return tripTransactionReportIDs.flatMap((reportID) => reportsTransactions[reportID ?? ''] ?? []); + return tripTransactionReportIDs.flatMap((reportID) => getReportTransactions(reportID)); } function getTripIDFromTransactionParentReportID(transactionParentReportID: string | undefined): string | undefined { @@ -8138,13 +8156,13 @@ function hasActionsWithErrors(reportID: string): boolean { } function isNonAdminOrOwnerOfPolicyExpenseChat(report: OnyxInputOrEntry, policy: OnyxInputOrEntry): boolean { - return isPolicyExpenseChat(report) && !(PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, currentUserAccountID ?? -1) || isReportOwner(report)); + return isPolicyExpenseChat(report) && !(PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, currentUserAccountID) || isReportOwner(report)); } function isAdminOwnerApproverOrReportOwner(report: OnyxEntry, policy: OnyxEntry): boolean { const isApprover = isMoneyRequestReport(report) && report?.managerID !== null && currentUserPersonalDetails?.accountID === report?.managerID; - return PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, currentUserAccountID ?? -1) || isReportOwner(report) || isApprover; + return PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, currentUserAccountID) || isReportOwner(report) || isApprover; } /** @@ -8300,18 +8318,22 @@ function createDraftTransactionAndNavigateToParticipantSelector( if (actionName === CONST.IOU.ACTION.CATEGORIZE) { const activePolicy = getPolicy(activePolicyID); if (activePolicy && activePolicy?.type !== CONST.POLICY.TYPE.PERSONAL && activePolicy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID ?? -1, activePolicyID ?? '-1')?.reportID ?? '-1'; + const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID, activePolicyID)?.reportID; IOU.setMoneyRequestParticipants(transactionID, [ { selected: true, accountID: 0, isPolicyExpenseChat: true, reportID: policyExpenseReportID, - policyID: activePolicyID ?? '-1', + policyID: activePolicyID, searchText: activePolicy?.name, }, ]); - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, policyExpenseReportID)); + if (policyExpenseReportID) { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, policyExpenseReportID)); + } else { + Log.warn('policyExpenseReportID is not valid during expense categorizing'); + } return; } if (filteredPolicies.length === 0 || filteredPolicies.length > 1) { @@ -8320,18 +8342,22 @@ function createDraftTransactionAndNavigateToParticipantSelector( } const policyID = filteredPolicies.at(0)?.id; - const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID ?? -1, policyID ?? '-1')?.reportID ?? '-1'; + const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID, policyID)?.reportID; IOU.setMoneyRequestParticipants(transactionID, [ { selected: true, accountID: 0, isPolicyExpenseChat: true, reportID: policyExpenseReportID, - policyID: policyID ?? '-1', + policyID, searchText: activePolicy?.name, }, ]); - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, policyExpenseReportID)); + if (policyExpenseReportID) { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, policyExpenseReportID)); + } else { + Log.warn('policyExpenseReportID is not valid during expense categorizing'); + } return; } @@ -8520,7 +8546,7 @@ function getApprovalChain(policy: OnyxEntry, expenseReport: OnyxEntry, expenseReport: OnyxEntry isInvoiceReport(report)); } -function getReportMetadata(reportID?: string) { - return allReportMetadataKeyValue[reportID ?? '-1']; +function getReportMetadata(reportID: string | undefined) { + return reportID ? allReportMetadataKeyValue[reportID] : undefined; } export { @@ -8687,7 +8713,6 @@ export { getLastVisibleMessage, getMoneyRequestOptions, getMoneyRequestSpendBreakdown, - getNewMarkerReportActionID, getNonHeldAndFullAmount, getOptimisticDataForParentReportAction, getOriginalReportID, diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 34dd2a9d1350..bb3e04a90b84 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -687,7 +687,8 @@ function PureReportActionItem({ ${translate('parentReportAction.deletedReport')}`} /> ) : ( { expect(ReportUtils.getReport).toBeUndefined(); }); + it('does not export getReportTransactions', () => { + // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal + expect(ReportUtils.getReportTransactions).toBeUndefined(); + }); + it('does not export isOneTransactionReport', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(ReportUtils.isOneTransactionReport).toBeUndefined();