Skip to content

Add ach cancelled action #58674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 2, 2025
3 changes: 2 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,7 @@ const CONST = {
OUTDATED_BANK_ACCOUNT: 'OUTDATEDBANKACCOUNT', // OldDot Action
REIMBURSED: 'REIMBURSED',
REIMBURSEMENT_ACH_BOUNCE: 'REIMBURSEMENTACHBOUNCE', // OldDot Action
REIMBURSEMENT_ACH_CANCELLED: 'REIMBURSEMENTACHCANCELLED', // OldDot Action
REIMBURSEMENT_ACH_CANCELED: 'REIMBURSEMENTACHCANCELED', // OldDot Action
REIMBURSEMENT_ACCOUNT_CHANGED: 'REIMBURSEMENTACCOUNTCHANGED', // OldDot Action
REIMBURSEMENT_DELAYED: 'REIMBURSEMENTDELAYED', // OldDot Action
REIMBURSEMENT_QUEUED: 'REIMBURSEMENTQUEUED',
Expand Down Expand Up @@ -1325,6 +1325,7 @@ const CONST = {
},
CANCEL_PAYMENT_REASONS: {
ADMIN: 'CANCEL_REASON_ADMIN',
USER: 'CANCEL_REASON_USER',
},
ACTIONABLE_MENTION_WHISPER_RESOLUTION: {
INVITE: 'invited',
Expand Down
8 changes: 4 additions & 4 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import {
isMoneyRequestAction,
isOldDotReportAction,
isPendingRemove,
isReimbursementDeQueuedAction,
isReimbursementDeQueuedOrCanceledAction,
isReimbursementQueuedAction,
isReportPreviewAction,
isTaskAction,
Expand All @@ -86,7 +86,7 @@ import {
getMoneyRequestSpendBreakdown,
getParticipantsAccountIDsForDisplay,
getPolicyName,
getReimbursementDeQueuedActionMessage,
getReimbursementDeQueuedOrCanceledActionMessage,
getReimbursementQueuedActionMessage,
getRejectedReportMessage,
getReportAutomaticallyApprovedMessage,
Expand Down Expand Up @@ -705,8 +705,8 @@ function getLastMessageTextForReport(report: OnyxEntry<Report>, lastActorDetails
lastMessageTextFromReport = formatReportLastMessageText(reportPreviewMessage);
} else if (isReimbursementQueuedAction(lastReportAction)) {
lastMessageTextFromReport = getReimbursementQueuedActionMessage({reportAction: lastReportAction, reportOrID: report});
} else if (isReimbursementDeQueuedAction(lastReportAction)) {
lastMessageTextFromReport = getReimbursementDeQueuedActionMessage(lastReportAction, report, true);
} else if (isReimbursementDeQueuedOrCanceledAction(lastReportAction)) {
lastMessageTextFromReport = getReimbursementDeQueuedOrCanceledActionMessage(lastReportAction, report, true);
} else if (isDeletedParentAction(lastReportAction) && reportUtilsIsChatReport(report)) {
lastMessageTextFromReport = getDeletedParentActionMessageForChatReport(lastReportAction);
} else if (isPendingRemove(lastReportAction) && report?.reportID && isThreadParentMessage(lastReportAction, report.reportID)) {
Expand Down
16 changes: 14 additions & 2 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,20 @@ function isLeavePolicyAction(reportAction: OnyxEntry<ReportAction>): reportActio
return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.LEAVE_POLICY);
}

function isReimbursementCanceledAction(reportAction: OnyxEntry<ReportAction>): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELED> {
return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELED);
}

function isReimbursementDeQueuedAction(reportAction: OnyxEntry<ReportAction>): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED> {
return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED);
}

function isReimbursementDeQueuedOrCanceledAction(
reportAction: OnyxEntry<ReportAction>,
): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED | typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELED> {
return isReimbursementDeQueuedAction(reportAction) || isReimbursementCanceledAction(reportAction);
}

function isClosedAction(reportAction: OnyxEntry<ReportAction>): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.CLOSED> {
return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.CLOSED);
}
Expand Down Expand Up @@ -1379,7 +1389,7 @@ function isOldDotReportAction(action: ReportAction | OldDotReportAction) {
CONST.REPORT.ACTIONS.TYPE.MARK_REIMBURSED_FROM_INTEGRATION,
CONST.REPORT.ACTIONS.TYPE.OUTDATED_BANK_ACCOUNT,
CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_BOUNCE,
CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELLED,
CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELED,
CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACCOUNT_CHANGED,
CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DELAYED,
CONST.REPORT.ACTIONS.TYPE.SELECTED_FOR_RANDOM_AUDIT,
Expand Down Expand Up @@ -1449,7 +1459,7 @@ function getMessageOfOldDotReportAction(oldDotAction: PartialReportAction | OldD
return translateLocal('report.actions.type.outdatedBankAccount');
case CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_BOUNCE:
return translateLocal('report.actions.type.reimbursementACHBounce');
case CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELLED:
case CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELED:
return translateLocal('report.actions.type.reimbursementACHCancelled');
case CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACCOUNT_CHANGED:
return translateLocal('report.actions.type.reimbursementAccountChanged');
Expand Down Expand Up @@ -2396,7 +2406,9 @@ export {
isPayAction,
isPendingRemove,
isPolicyChangeLogAction,
isReimbursementCanceledAction,
isReimbursementDeQueuedAction,
isReimbursementDeQueuedOrCanceledAction,
isReimbursementQueuedAction,
isRenamedAction,
isReportActionAttachment,
Expand Down
10 changes: 5 additions & 5 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3051,10 +3051,10 @@ function getReimbursementQueuedActionMessage({
}

/**
* Returns the preview message for `REIMBURSEMENT_DEQUEUED` action
* Returns the preview message for `REIMBURSEMENT_DEQUEUED` or `REIMBURSEMENT_ACH_CANCELED` action
*/
function getReimbursementDeQueuedActionMessage(
reportAction: OnyxEntry<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED>>,
function getReimbursementDeQueuedOrCanceledActionMessage(
reportAction: OnyxEntry<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED | typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELED>>,
reportOrID: OnyxEntry<Report> | string | SearchReport,
isLHNPreview = false,
): string {
Expand All @@ -3063,7 +3063,7 @@ function getReimbursementDeQueuedActionMessage(
const amount = originalMessage?.amount;
const currency = originalMessage?.currency;
const formattedAmount = convertToDisplayString(amount, currency);
if (originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN) {
if (originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN || originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.USER) {
const payerOrApproverName = report?.managerID === currentUserAccountID || !isLHNPreview ? '' : getDisplayNameForParticipant({accountID: report?.managerID, shouldUseShortForm: true});
return translateLocal('iou.adminCanceledRequest', {manager: payerOrApproverName, amount: formattedAmount});
}
Expand Down Expand Up @@ -9612,7 +9612,7 @@ export {
getPolicyExpenseChatName,
getPolicyName,
getPolicyType,
getReimbursementDeQueuedActionMessage,
getReimbursementDeQueuedOrCanceledActionMessage,
getReimbursementQueuedActionMessage,
getReportActionActorAccountID,
getReportDescription,
Expand Down
8 changes: 4 additions & 4 deletions src/pages/home/report/ContextMenu/ContextMenuActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import {
isModifiedExpenseAction,
isMoneyRequestAction,
isOldDotReportAction,
isReimbursementDeQueuedAction,
isReimbursementDeQueuedOrCanceledAction,
isReimbursementQueuedAction,
isRenamedAction,
isReportActionAttachment,
Expand All @@ -87,7 +87,7 @@ import {
getIOUSubmittedMessage,
getIOUUnapprovedMessage,
getOriginalReportID,
getReimbursementDeQueuedActionMessage,
getReimbursementDeQueuedOrCanceledActionMessage,
getReimbursementQueuedActionMessage,
getRejectedReportMessage,
getReportAutomaticallyApprovedMessage,
Expand Down Expand Up @@ -490,9 +490,9 @@ const ContextMenuActions: ContextMenuAction[] = [
} else if (isModifiedExpenseAction(reportAction)) {
const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction({reportOrID: reportID, reportAction});
Clipboard.setString(modifyExpenseMessage);
} else if (isReimbursementDeQueuedAction(reportAction)) {
} else if (isReimbursementDeQueuedOrCanceledAction(reportAction)) {
const {expenseReportID} = getOriginalMessage(reportAction) ?? {};
const displayMessage = getReimbursementDeQueuedActionMessage(reportAction, expenseReportID);
const displayMessage = getReimbursementDeQueuedOrCanceledActionMessage(reportAction, expenseReportID);
Clipboard.setString(displayMessage);
} else if (isMoneyRequestAction(reportAction)) {
const displayMessage = getIOUReportActionDisplayMessage(reportAction, transaction);
Expand Down
14 changes: 7 additions & 7 deletions src/pages/home/report/PureReportActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ import {
isMessageDeleted,
isMoneyRequestAction,
isPendingRemove,
isReimbursementDeQueuedAction,
isReimbursementDeQueuedOrCanceledAction,
isReimbursementQueuedAction,
isRenamedAction,
isTagModificationAction,
Expand Down Expand Up @@ -289,8 +289,8 @@ type PureReportActionItemProps = {
/** What missing payment method does this report action indicate, if any? */
missingPaymentMethod?: MissingPaymentMethod | undefined;

/** Returns the preview message for `REIMBURSEMENT_DEQUEUED` action */
reimbursementDeQueuedActionMessage?: string;
/** Returns the preview message for `REIMBURSEMENT_DEQUEUED` or `REIMBURSEMENT_ACH_CANCELED` action */
reimbursementDeQueuedOrCanceledActionMessage?: string;

/** The report action message when expense has been modified. */
modifiedExpenseMessage?: string;
Expand Down Expand Up @@ -360,7 +360,7 @@ function PureReportActionItem({
isClosedExpenseReportWithNoExpenses,
isCurrentUserTheOnlyParticipant = () => false,
missingPaymentMethod,
reimbursementDeQueuedActionMessage = '',
reimbursementDeQueuedOrCanceledActionMessage = '',
modifiedExpenseMessage = '',
getTransactionsWithReceipts = () => [],
clearError = () => {},
Expand Down Expand Up @@ -836,8 +836,8 @@ function PureReportActionItem({
</>
</ReportActionItemBasicMessage>
);
} else if (isReimbursementDeQueuedAction(action)) {
children = <ReportActionItemBasicMessage message={reimbursementDeQueuedActionMessage} />;
} else if (isReimbursementDeQueuedOrCanceledAction(action)) {
children = <ReportActionItemBasicMessage message={reimbursementDeQueuedOrCanceledActionMessage} />;
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) {
children = <ReportActionItemBasicMessage message={modifiedExpenseMessage} />;
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED)) {
Expand Down Expand Up @@ -1344,7 +1344,7 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => {
prevProps.isChronosReport === nextProps.isChronosReport &&
prevProps.isClosedExpenseReportWithNoExpenses === nextProps.isClosedExpenseReportWithNoExpenses &&
lodashIsEqual(prevProps.missingPaymentMethod, nextProps.missingPaymentMethod) &&
prevProps.reimbursementDeQueuedActionMessage === nextProps.reimbursementDeQueuedActionMessage &&
prevProps.reimbursementDeQueuedOrCanceledActionMessage === nextProps.reimbursementDeQueuedOrCanceledActionMessage &&
prevProps.modifiedExpenseMessage === nextProps.modifiedExpenseMessage &&
prevProps.userBillingFundID === nextProps.userBillingFundID &&
prevProps.reportAutomaticallyForwardedMessage === nextProps.reportAutomaticallyForwardedMessage
Expand Down
75 changes: 46 additions & 29 deletions src/pages/home/report/ReportActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,29 @@ import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider';
import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
import * as ReportActions from '@userActions/ReportActions';
import * as Transaction from '@userActions/Transaction';
import {getIOUReportIDFromReportActionPreview, getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {
chatIncludesChronosWithID,
createDraftTransactionAndNavigateToParticipantSelector,
getIndicatedMissingPaymentMethod,
getOriginalReportID,
getReimbursementDeQueuedOrCanceledActionMessage,
getReportAutomaticallyForwardedMessage,
getTransactionsWithReceipts,
isArchivedNonExpenseReportWithID,
isChatThread,
isClosedExpenseReportWithNoExpenses,
isCurrentUserTheOnlyParticipant,
} from '@libs/ReportUtils';
import {
deleteReportActionDraft,
dismissTrackExpenseActionableWhisper,
resolveActionableMentionWhisper,
resolveActionableReportMentionWhisper,
toggleEmojiReaction,
} from '@userActions/Report';
import {clearAllRelatedReportActionErrors} from '@userActions/ReportActions';
import {clearError} from '@userActions/Transaction';
import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ReportAction} from '@src/types/onyx';
Expand All @@ -17,20 +35,19 @@ import PureReportActionItem from './PureReportActionItem';
function ReportActionItem({action, report, ...props}: PureReportActionItemProps) {
const reportID = report?.reportID;
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action), [reportID, action]);
const originalReportID = useMemo(() => getOriginalReportID(reportID, action), [reportID, action]);
const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, {
selector: (draftMessagesForReport) => {
const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID];
return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message;
},
});
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action)}`);
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getIOUReportIDFromReportActionPreview(action)}`);
const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`);
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
const [linkedTransactionRouteError] = useOnyx(
`${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID}`,
{selector: (transaction) => transaction?.errorFields?.route ?? null},
);
const [linkedTransactionRouteError] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${isMoneyRequestAction(action) && getOriginalMessage(action)?.IOUTransactionID}`, {
selector: (transaction) => transaction?.errorFields?.route ?? null,
});
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state.
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || undefined}`);

Expand All @@ -41,8 +58,8 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps)
const personalDetails = usePersonalDetails();
const blockedFromConcierge = useBlockedFromConcierge();
const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID);
const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report;
const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID, action);
const linkedReport = isChatThread(report) ? parentReport : report;
const missingPaymentMethod = getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID, action);

return (
<PureReportActionItem
Expand All @@ -60,27 +77,27 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps)
personalDetails={personalDetails}
blockedFromConcierge={blockedFromConcierge}
originalReportID={originalReportID}
deleteReportActionDraft={Report.deleteReportActionDraft}
isArchivedRoom={ReportUtils.isArchivedNonExpenseReportWithID(originalReportID)}
isChronosReport={ReportUtils.chatIncludesChronosWithID(originalReportID)}
toggleEmojiReaction={Report.toggleEmojiReaction}
createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector}
resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper}
resolveActionableMentionWhisper={Report.resolveActionableMentionWhisper}
isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport)}
isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant}
deleteReportActionDraft={deleteReportActionDraft}
isArchivedRoom={isArchivedNonExpenseReportWithID(originalReportID)}
isChronosReport={chatIncludesChronosWithID(originalReportID)}
toggleEmojiReaction={toggleEmojiReaction}
createDraftTransactionAndNavigateToParticipantSelector={createDraftTransactionAndNavigateToParticipantSelector}
resolveActionableReportMentionWhisper={resolveActionableReportMentionWhisper}
resolveActionableMentionWhisper={resolveActionableMentionWhisper}
isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses(iouReport)}
isCurrentUserTheOnlyParticipant={isCurrentUserTheOnlyParticipant}
missingPaymentMethod={missingPaymentMethod}
reimbursementDeQueuedActionMessage={ReportUtils.getReimbursementDeQueuedActionMessage(
action as OnyxEntry<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED>>,
reimbursementDeQueuedOrCanceledActionMessage={getReimbursementDeQueuedOrCanceledActionMessage(
action as OnyxEntry<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED | typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELED>>,
report,
)}
modifiedExpenseMessage={ModifiedExpenseMessage.getForReportAction({reportOrID: reportID, reportAction: action})}
getTransactionsWithReceipts={ReportUtils.getTransactionsWithReceipts}
clearError={Transaction.clearError}
clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors}
dismissTrackExpenseActionableWhisper={Report.dismissTrackExpenseActionableWhisper}
getTransactionsWithReceipts={getTransactionsWithReceipts}
clearError={clearError}
clearAllRelatedReportActionErrors={clearAllRelatedReportActionErrors}
dismissTrackExpenseActionableWhisper={dismissTrackExpenseActionableWhisper}
userBillingFundID={userBillingFundID}
reportAutomaticallyForwardedMessage={ReportUtils.getReportAutomaticallyForwardedMessage(action as ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.FORWARDED>, reportID)}
reportAutomaticallyForwardedMessage={getReportAutomaticallyForwardedMessage(action as ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.FORWARDED>, reportID)}
/>
);
}
Expand Down
Loading