Skip to content

Commit

Permalink
Merge pull request #34719 from Expensify/alberto-unpay3
Browse files Browse the repository at this point in the history
  • Loading branch information
blimpich authored Feb 14, 2024
2 parents 5e1879c + 1380e65 commit 821bc7b
Show file tree
Hide file tree
Showing 17 changed files with 254 additions and 18 deletions.
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,9 @@ const CONST = {
},
THREAD_DISABLED: ['CREATED'],
},
CANCEL_PAYMENT_REASONS: {
ADMIN: 'CANCEL_REASON_ADMIN',
},
ACTIONABLE_MENTION_WHISPER_RESOLUTION: {
INVITE: 'invited',
NOTHING: 'nothing',
Expand Down
31 changes: 30 additions & 1 deletion src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useMemo} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
Expand All @@ -17,7 +17,9 @@ import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import * as Expensicons from './Icon/Expensicons';
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
import {usePersonalDetails} from './OnyxProvider';
import SettlementButton from './SettlementButton';
Expand Down Expand Up @@ -62,6 +64,16 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
isPolicyAdmin && (isApproved || isManager)
: isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager);
const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);

const cancelPayment = useCallback(() => {
if (!chatReport) {
return;
}
IOU.cancelPayment(moneyRequestReport, chatReport);
setIsConfirmModalVisible(false);
}, [moneyRequestReport, chatReport]);

const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
const shouldShowPayButton = useMemo(
Expand Down Expand Up @@ -93,6 +105,13 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
);

const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)];
if (isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport)) {
threeDotsMenuItems.push({
icon: Expensicons.Trashcan,
text: translate('iou.cancelPayment'),
onSelected: () => setIsConfirmModalVisible(true),
});
}

return (
<View style={[styles.pt0]}>
Expand Down Expand Up @@ -178,6 +197,16 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
</View>
)}
</View>
<ConfirmModal
title={translate('iou.cancelPayment')}
isVisible={isConfirmModalVisible}
onConfirm={cancelPayment}
onCancel={() => setIsConfirmModalVisible(false)}
prompt={translate('iou.cancelPaymentConfirmation')}
confirmText={translate('iou.cancelPayment')}
cancelText={translate('common.dismiss')}
danger
/>
</View>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
import type {
AddressLineParams,
AdminCanceledRequestParams,
AlreadySignedInParams,
AmountEachParams,
ApprovedAmountParams,
Expand Down Expand Up @@ -114,6 +115,7 @@ type AllCountries = Record<Country, string>;
export default {
common: {
cancel: 'Cancel',
dismiss: 'Dismiss',
yes: 'Yes',
no: 'No',
ok: 'OK',
Expand Down Expand Up @@ -583,6 +585,8 @@ export default {
requestMoney: 'Request money',
sendMoney: 'Send money',
pay: 'Pay',
cancelPayment: 'Cancel payment',
cancelPaymentConfirmation: 'Are you sure that you want to cancel this payment?',
viewDetails: 'View details',
pending: 'Pending',
canceled: 'Canceled',
Expand Down Expand Up @@ -621,6 +625,7 @@ export default {
payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`,
approvedAmount: ({amount}: ApprovedAmountParams) => `approved ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`,
adminCanceledRequest: ({manager, amount}: AdminCanceledRequestParams) => `${manager} cancelled the ${amount} payment.`,
canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) =>
`Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Str from 'expensify-common/lib/str';
import CONST from '@src/CONST';
import type {
AddressLineParams,
AdminCanceledRequestParams,
AlreadySignedInParams,
AmountEachParams,
ApprovedAmountParams,
Expand Down Expand Up @@ -104,6 +105,7 @@ import type {
export default {
common: {
cancel: 'Cancelar',
dismiss: 'Descartar',
yes: 'Sí',
no: 'No',
ok: 'OK',
Expand Down Expand Up @@ -576,6 +578,8 @@ export default {
requestMoney: 'Pedir dinero',
sendMoney: 'Enviar dinero',
pay: 'Pagar',
cancelPayment: 'Cancelar el pago',
cancelPaymentConfirmation: '¿Estás seguro de que quieres cancelar este pago?',
viewDetails: 'Ver detalles',
pending: 'Pendiente',
canceled: 'Canceló',
Expand Down Expand Up @@ -614,6 +618,7 @@ export default {
payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`,
approvedAmount: ({amount}: ApprovedAmountParams) => `aprobó ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`,
adminCanceledRequest: ({manager, amount}: AdminCanceledRequestParams) => `${manager} canceló el pago de ${amount}.`,
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) =>
Expand Down
3 changes: 3 additions & 0 deletions src/languages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ type WaitingOnBankAccountParams = {submitterDisplayName: string};

type CanceledRequestParams = {amount: string; submitterDisplayName: string};

type AdminCanceledRequestParams = {manager: string; amount: string};

type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string};

type PaidElsewhereWithAmountParams = {payer?: string; amount: string};
Expand Down Expand Up @@ -292,6 +294,7 @@ type ElectronicFundsParams = {percentage: string; amount: string};
type LogSizeParams = {size: number};

export type {
AdminCanceledRequestParams,
ApprovedAmountParams,
AddressLineParams,
AlreadySignedInParams,
Expand Down
8 changes: 8 additions & 0 deletions src/libs/API/parameters/CancelPaymentParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type CancelPaymentParams = {
iouReportID: string;
chatReportID: string;
managerAccountID: number;
reportActionID: string;
};

export default CancelPaymentParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,6 @@ export type {default as ReplaceReceiptParams} from './ReplaceReceiptParams';
export type {default as SubmitReportParams} from './SubmitReportParams';
export type {default as DetachReceiptParams} from './DetachReceiptParams';
export type {default as PayMoneyRequestParams} from './PayMoneyRequestParams';
export type {default as CancelPaymentParams} from './CancelPaymentParams';
export type {default as AcceptACHContractForBankAccount} from './AcceptACHContractForBankAccount';
export type {default as UpdateWorkspaceDescriptionParams} from './UpdateWorkspaceDescriptionParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const WRITE_COMMANDS = {
DETACH_RECEIPT: 'DetachReceipt',
PAY_MONEY_REQUEST_WITH_WALLET: 'PayMoneyRequestWithWallet',
PAY_MONEY_REQUEST: 'PayMoneyRequest',
CANCEL_PAYMENT: 'CancelPayment',
ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount',
UPDATE_WORKSPACE_DESCRIPTION: 'UpdateWorkspaceDescription',
} as const;
Expand Down Expand Up @@ -281,6 +282,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.DETACH_RECEIPT]: Parameters.DetachReceiptParams;
[WRITE_COMMANDS.PAY_MONEY_REQUEST_WITH_WALLET]: Parameters.PayMoneyRequestParams;
[WRITE_COMMANDS.PAY_MONEY_REQUEST]: Parameters.PayMoneyRequestParams;
[WRITE_COMMANDS.CANCEL_PAYMENT]: Parameters.CancelPaymentParams;
[WRITE_COMMANDS.ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT]: Parameters.AcceptACHContractForBankAccount;
[WRITE_COMMANDS.UPDATE_WORKSPACE_DESCRIPTION]: Parameters.UpdateWorkspaceDescriptionParams;
};
Expand Down
2 changes: 1 addition & 1 deletion src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ function getLastMessageTextForReport(report: OnyxEntry<Report>, lastActorDetails
} else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) {
lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report);
} else if (ReportActionUtils.isReimbursementDeQueuedAction(lastReportAction)) {
lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(report);
lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(lastReportAction, report);
} else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) {
lastMessageTextFromReport = ReportUtils.getDeletedParentActionMessageForChatReport(lastReportAction);
} else if (ReportActionUtils.isPendingRemove(lastReportAction) && ReportActionUtils.isThreadParentMessage(lastReportAction, report?.reportID ?? '')) {
Expand Down
6 changes: 6 additions & 0 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry<ReportAction>, key:
return false;
}

// Ignore markedAsReimbursed action here since we're already display message that explains the request was paid
// elsewhere in the IOU reportAction
if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED) {
return false;
}

if (isWhisperActionTargetedToOthers(reportAction)) {
return false;
}
Expand Down
73 changes: 64 additions & 9 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@ import type {
} from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCreated, OriginalMessageRenamed, PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type {
ChangeLog,
IOUMessage,
OriginalMessageActionName,
OriginalMessageCreated,
OriginalMessageReimbursementDequeued,
OriginalMessageRenamed,
PaymentMethodType,
ReimbursementDeQueuedMessage,
} from '@src/types/onyx/OriginalMessage';
import type {Status} from '@src/types/onyx/PersonalDetails';
import type {NotificationPreference} from '@src/types/onyx/Report';
import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction';
Expand Down Expand Up @@ -186,6 +195,11 @@ type OptimisticSubmittedReportAction = Pick<
'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction'
>;

type OptimisticCancelPaymentReportAction = Pick<
ReportAction,
'actionName' | 'actorAccountID' | 'message' | 'originalMessage' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction'
>;

type OptimisticEditedTaskReportAction = Pick<
ReportAction,
'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'person'
Expand Down Expand Up @@ -1714,11 +1728,55 @@ function getReimbursementQueuedActionMessage(reportAction: OnyxEntry<ReportActio
/**
* Returns the preview message for `REIMBURSEMENTDEQUEUED` action
*/
function getReimbursementDeQueuedActionMessage(report: OnyxEntry<Report>): string {
function getReimbursementDeQueuedActionMessage(reportAction: OnyxEntry<ReportActionBase & OriginalMessageReimbursementDequeued>, report: OnyxEntry<Report> | EmptyObject): string {
const originalMessage = reportAction?.originalMessage as ReimbursementDeQueuedMessage | undefined;
const amount = originalMessage?.amount;
const currency = originalMessage?.currency;
const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency);
if (originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN) {
const payerOrApproverName = isExpenseReport(report) ? getPolicyName(report, false) : getDisplayNameForParticipant(report?.managerID) ?? '';
return Localize.translateLocal('iou.adminCanceledRequest', {manager: payerOrApproverName, amount: formattedAmount});
}
const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, true) ?? '';
const amount = CurrencyUtils.convertToDisplayString(report?.total ?? 0, report?.currency);
return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount: formattedAmount});
}

return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount});
/**
* Builds an optimistic REIMBURSEMENTDEQUEUED report action with a randomly generated reportActionID.
*
*/
function buildOptimisticCancelPaymentReportAction(expenseReportID: string, amount: number, currency: string): OptimisticCancelPaymentReportAction {
return {
actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED,
actorAccountID: currentUserAccountID,
message: [
{
cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN,
expenseReportID,
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
text: '',
amount,
currency,
},
],
originalMessage: {
cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN,
expenseReportID,
amount,
currency,
},
person: [
{
style: 'strong',
text: currentUserPersonalDetails?.displayName ?? currentUserEmail,
type: 'TEXT',
},
],
reportActionID: NumberUtils.rand64(),
shouldShow: true,
created: DateUtils.getDBTime(),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};
}

/**
Expand Down Expand Up @@ -1977,10 +2035,6 @@ function getMoneyRequestReportName(report: OnyxEntry<Report>, 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: payerOrApproverName, amount: formattedAmount});
}
Expand Down Expand Up @@ -4559,7 +4613,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry<ReportAction>)
// differentiate between these two scenarios, we check if the `originalMessage` contains the `IOUDetails`
// property. If it does, it indicates that this is a 'Send money' action.
const {amount, currency} = originalMessage.IOUDetails ?? originalMessage;
const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency) ?? '';
const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(amount), currency) ?? '';
const payerName = isExpenseReport(iouReport) ? getPolicyName(iouReport) : getDisplayNameForParticipant(iouReport?.managerID, true);

switch (originalMessage.paymentType) {
Expand Down Expand Up @@ -4945,6 +4999,7 @@ export {
buildOptimisticIOUReportAction,
buildOptimisticReportPreview,
buildOptimisticModifiedExpenseReportAction,
buildOptimisticCancelPaymentReportAction,
updateReportPreview,
buildOptimisticTaskReportAction,
buildOptimisticAddCommentReportAction,
Expand Down
Loading

0 comments on commit 821bc7b

Please sign in to comment.