diff --git a/src/CONST.ts b/src/CONST.ts index 9b284752d074..258a66676e00 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -497,6 +497,7 @@ const CONST = { MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', MOVED: 'MOVED', REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', + REIMBURSEMENTDEQUEUED: 'REIMBURSEMENTDEQUEUED', RENAMED: 'RENAMED', REPORTPREVIEW: 'REPORTPREVIEW', SUBMITTED: 'SUBMITTED', @@ -1155,6 +1156,9 @@ const CONST = { SVG: 'svg', }, RECEIPT_ERROR: 'receiptError', + CANCEL_REASON: { + PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED', + }, }, GROWL: { diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 466a5a6eec51..1855152ef640 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -220,6 +220,8 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.approved')}`; } else if (props.iouReport.isWaitingOnBankAccount) { message += ` • ${props.translate('iou.pending')}`; + } else if (props.iouReport.isCancelledIOU) { + message += ` • ${props.translate('iou.canceled')}`; } return message; }; @@ -280,7 +282,7 @@ function MoneyRequestPreview(props) { - {getPreviewHeaderText() + (isSettled ? ` • ${getSettledMessage()}` : '')} + {getPreviewHeaderText() + (isSettled && !props.iouReport.isCancelledIOU ? ` • ${getSettledMessage()}` : '')} {hasFieldErrors && ( `${manager} approved:`, payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => + `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 6ea01dc4bd14..ad191301d338 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -8,6 +8,7 @@ import type { BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, BeginningOfChatHistoryDomainRoomPartOneParams, + CanceledRequestParams, CharacterLimitParams, ConfirmThatParams, DateShouldBeAfterParams, @@ -534,6 +535,7 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', + canceled: 'Canceló', posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', receiptScanning: 'Escaneo de recibo en curso…', @@ -564,6 +566,8 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => + `Canceló el pago ${amount}, porque ${submitterDisplayName} no habilitó su billetera Expensify en un plazo de 30 días.`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, diff --git a/src/languages/types.ts b/src/languages/types.ts index a012ebdfb95b..96dd85dfb627 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -125,6 +125,8 @@ type PayerSettledParams = {amount: number | string}; type WaitingOnBankAccountParams = {submitterDisplayName: string}; +type CanceledRequestParams = {amount: string; submitterDisplayName: string}; + type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; type PaidElsewhereWithAmountParams = {payer: string; amount: string}; @@ -282,6 +284,7 @@ export type { ManagerApprovedParams, PayerSettledParams, WaitingOnBankAccountParams, + CanceledRequestParams, SettledAfterAddedBankAccountParams, PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index c616587c3983..e3b6ec77380e 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -397,6 +397,8 @@ function getLastMessageTextForReport(report) { lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastIOUMoneyReport, true, ReportUtils.isChatReport(report)); } else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report); + } else if (ReportActionUtils.isReimbursementDeQueuedAction(lastReportAction)) { + lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(report); } else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) { lastMessageTextFromReport = ReportUtils.getDeletedParentActionMessageForChatReport(lastReportAction); } else if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml, translationKey: report.lastMessageTranslationKey})) { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index bd475a57954e..f58021e17064 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -109,6 +109,10 @@ function isChannelLogMemberAction(reportAction: OnyxEntry) { ); } +function isReimbursementDeQueuedAction(reportAction: OnyxEntry): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED; +} + /** * Returns whether the comment is a thread parent message/the first message in a thread */ @@ -698,6 +702,7 @@ export { hasRequestFromCurrentAccount, getFirstVisibleReportActionID, isChannelLogMemberAction, + isReimbursementDeQueuedAction, }; export type {LastVisibleMessage}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d93661778b83..b6c7b3a34c33 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1480,6 +1480,16 @@ function getReimbursementQueuedActionMessage(reportAction: OnyxEntry): string { + const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, true) ?? ''; + const amount = CurrencyUtils.convertToDisplayString(report?.total ?? 0, report?.currency); + + return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount}); +} + /** * Returns the last visible message for a given report after considering the given optimistic actions * @@ -1684,6 +1694,10 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry< return `${payerPaidAmountMessage} • ${Localize.translateLocal('iou.pending')}`; } + if (report?.isCancelledIOU) { + return `${payerPaidAmountMessage} • ${Localize.translateLocal('iou.canceled')}`; + } + if (hasNonReimbursableTransactions(report?.reportID)) { return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName, amount: formattedAmount}); } @@ -4376,6 +4390,7 @@ export { shouldUseFullTitleToDisplay, parseReportRouteParams, getReimbursementQueuedActionMessage, + getReimbursementDeQueuedActionMessage, getPersonalDetailsForAccountID, getChannelLogMemberMessage, getRoom, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 17d49bd0f486..10b08533ff45 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -34,6 +34,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import usePrevious from '@hooks/usePrevious'; import compose from '@libs/compose'; import ControlSelection from '@libs/ControlSelection'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import focusTextInputAfterAnimation from '@libs/focusTextInputAfterAnimation'; import Navigation from '@libs/Navigation/Navigation'; @@ -413,6 +414,11 @@ function ReportActionItem(props) { ); + } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName']); + const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); + + children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; } else { diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index c5d9c27d34a1..eea4f1dba26e 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -195,6 +195,11 @@ type OriginalMessageReimbursementQueued = { originalMessage: unknown; }; +type OriginalMessageReimbursementDequeued = { + actionName: typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED; + originalMessage: unknown; +}; + type OriginalMessageMoved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.MOVED; originalMessage: { @@ -220,6 +225,7 @@ type OriginalMessage = | OriginalMessagePolicyTask | OriginalMessageModifiedExpense | OriginalMessageReimbursementQueued + | OriginalMessageReimbursementDequeued | OriginalMessageMoved; export default OriginalMessage; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 81a92c4bf603..418607a4e9b4 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -118,6 +118,9 @@ type Report = { /** Whether the report is waiting on a bank account */ isWaitingOnBankAccount?: boolean; + /** Whether the report is cancelled */ + isCancelledIOU?: boolean; + /** Whether the last message was deleted */ isLastMessageDeletedParentAction?: boolean;