Skip to content

Commit b92b8f2

Browse files
authored
Merge pull request Expensify#64228 from software-mansion-labs/kicu/62783-transactions-preview-amount
Fix computing receiver and payer for IOU transaction preview
2 parents a8bf844 + c5ce6a0 commit b92b8f2

9 files changed

Lines changed: 84 additions & 96 deletions

File tree

.storybook/webpackMockPaths.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,5 @@ export default {
66
'react-native$': 'react-native-web',
77
'@react-native-community/netinfo': path.resolve(__dirname, '../__mocks__/@react-native-community/netinfo.ts'),
88
'@react-navigation/native': path.resolve(__dirname, '../__mocks__/@react-navigation/native'),
9-
'@libs/TransactionPreviewUtils': path.resolve(__dirname, '../src/libs/__mocks__/TransactionPreviewUtils.ts'),
109
};
1110
/* eslint-enable @typescript-eslint/naming-convention */

src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function MoneyRequestReportPreview({
6262
[StyleUtils, currentWidth, currentWrapperWidth, shouldUseNarrowLayout, transactions.length],
6363
);
6464

65-
const shouldShowIOUData = useMemo(() => {
65+
const shouldShowPayerAndReceiver = useMemo(() => {
6666
if (!isIOUReport(iouReport) && action.childType !== CONST.REPORT.TYPE.IOU) {
6767
return false;
6868
}
@@ -99,7 +99,7 @@ function MoneyRequestReportPreview({
9999
transactionID={item.transactionID}
100100
reportPreviewAction={action}
101101
onPreviewPressed={openReportFromPreview}
102-
shouldShowIOUData={shouldShowIOUData}
102+
shouldShowPayerAndReceiver={shouldShowPayerAndReceiver}
103103
/>
104104
);
105105

src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ import {getCleanedTagName} from '@libs/PolicyUtils';
2424
import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils';
2525
import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
2626
import type {TransactionDetails} from '@libs/ReportUtils';
27-
import {canEditMoneyRequest, getTransactionDetails, getWorkspaceIcon, isIOUReport, isPolicyExpenseChat, isReportApproved, isSettled} from '@libs/ReportUtils';
27+
import {canEditMoneyRequest, getTransactionDetails, getWorkspaceIcon, isPolicyExpenseChat, isReportApproved, isSettled} from '@libs/ReportUtils';
2828
import StringUtils from '@libs/StringUtils';
2929
import type {TranslationPathOrText} from '@libs/TransactionPreviewUtils';
30-
import {createTransactionPreviewConditionals, getIOUData, getTransactionPreviewTextAndTranslationPaths} from '@libs/TransactionPreviewUtils';
30+
import {createTransactionPreviewConditionals, getIOUPayerAndReceiver, getTransactionPreviewTextAndTranslationPaths} from '@libs/TransactionPreviewUtils';
3131
import {isScanning} from '@libs/TransactionUtils';
3232
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
3333
import variables from '@styles/variables';
@@ -41,9 +41,10 @@ function TransactionPreviewContent({
4141
isHovered,
4242
chatReport,
4343
personalDetails,
44-
iouReport,
44+
report,
4545
transaction,
4646
violations,
47+
transactionRawAmount,
4748
offlineWithFeedbackOnClose,
4849
containerStyles,
4950
transactionPreviewWidth,
@@ -53,32 +54,33 @@ function TransactionPreviewContent({
5354
walletTermsErrors,
5455
reportPreviewAction,
5556
shouldHideOnDelete = true,
56-
shouldShowIOUData,
57+
shouldShowPayerAndReceiver,
5758
navigateToReviewFields,
5859
isReviewDuplicateTransactionPage = false,
5960
}: TransactionPreviewContentProps) {
6061
const theme = useTheme();
6162
const styles = useThemeStyles();
6263
const {translate} = useLocalize();
6364

64-
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${iouReport?.policyID}`, {canBeMissing: true});
65+
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true});
6566
const transactionDetails = useMemo<Partial<TransactionDetails>>(() => getTransactionDetails(transaction, undefined, policy) ?? {}, [transaction, policy]);
66-
const managerID = iouReport?.managerID ?? reportPreviewAction?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
67-
const ownerAccountID = iouReport?.ownerAccountID ?? reportPreviewAction?.childOwnerAccountID ?? CONST.DEFAULT_NUMBER_ID;
67+
const {amount, comment: requestComment, merchant, tag, category, currency: requestCurrency} = transactionDetails;
68+
69+
const managerID = report?.managerID ?? reportPreviewAction?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
70+
const ownerAccountID = report?.ownerAccountID ?? reportPreviewAction?.childOwnerAccountID ?? CONST.DEFAULT_NUMBER_ID;
6871
const isReportAPolicyExpenseChat = isPolicyExpenseChat(chatReport);
69-
const {amount: requestAmount, comment: requestComment, merchant, tag, category, currency: requestCurrency} = transactionDetails;
70-
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(iouReport?.reportID)}`, {canBeMissing: true});
72+
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(report?.reportID)}`, {canBeMissing: true});
7173

7274
const transactionPreviewCommonArguments = useMemo(
7375
() => ({
74-
iouReport,
76+
iouReport: report,
7577
transaction,
7678
action,
7779
isBillSplit,
7880
violations,
7981
transactionDetails,
8082
}),
81-
[action, iouReport, isBillSplit, transaction, transactionDetails, violations],
83+
[action, report, isBillSplit, transaction, transactionDetails, violations],
8284
);
8385

8486
const conditionals = useMemo(
@@ -118,19 +120,15 @@ function TransactionPreviewContent({
118120
const displayAmountText = getTranslatedText(previewText.displayAmountText);
119121
const displayDeleteAmountText = getTranslatedText(previewText.displayDeleteAmountText);
120122

121-
const iouData = shouldShowIOUData
122-
? getIOUData(managerID, ownerAccountID, isIOUReport(iouReport) || reportPreviewAction?.childType === CONST.REPORT.TYPE.IOU, personalDetails, requestAmount ?? 0)
123-
: undefined;
124-
const {from, to} = iouData ?? {from: null, to: null};
125123
const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || transaction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
126124
const shouldShowCategoryOrTag = shouldShowCategory || shouldShowTag;
127125
const shouldShowMerchantOrDescription = shouldShowDescription || shouldShowMerchant;
128-
const shouldShowIOUHeader = !!from && !!to;
126+
129127
const description = truncate(StringUtils.lineBreaksToSpaces(Parser.htmlToText(requestComment ?? '')), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH});
130128
const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH});
131-
const isApproved = isReportApproved({report: iouReport});
132-
const isIOUSettled = isSettled(iouReport?.reportID);
133-
const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial;
129+
const isApproved = isReportApproved({report});
130+
const isIOUSettled = isSettled(report?.reportID);
131+
const isSettlementOrApprovalPartial = !!report?.pendingFields?.partial;
134132
const isTransactionScanning = isScanning(transaction);
135133
const displayAmount = isDeleted ? displayDeleteAmountText : displayAmountText;
136134
const receiptImages = [{...getThumbnailAndImageURIs(transaction), transaction}];
@@ -142,23 +140,34 @@ function TransactionPreviewContent({
142140
sortedParticipantAvatars.push(getWorkspaceIcon(chatReport));
143141
}
144142

143+
// Compute the from/to data only for IOU reports
144+
const {from, to} = useMemo(() => {
145+
if (!shouldShowPayerAndReceiver) {
146+
return {
147+
from: undefined,
148+
to: undefined,
149+
};
150+
}
151+
152+
// For IOU or Split, we want the unprocessed amount because it is important whether the amount was positive or negative
153+
const payerAndReceiver = getIOUPayerAndReceiver(managerID, ownerAccountID, personalDetails, transactionRawAmount);
154+
155+
return {
156+
from: payerAndReceiver.from,
157+
to: payerAndReceiver.to,
158+
};
159+
}, [managerID, ownerAccountID, personalDetails, shouldShowPayerAndReceiver, transactionRawAmount]);
160+
161+
const shouldShowIOUHeader = !!from && !!to;
162+
145163
// If available, retrieve the split share from the splits object of the transaction, if not, display an even share.
146164
const splitShare = useMemo(
147165
() =>
148166
shouldShowSplitShare
149167
? (transaction?.comment?.splits?.find((split) => split.accountID === sessionAccountID)?.amount ??
150-
calculateAmount(isReportAPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount ?? 0, requestCurrency ?? '', action?.actorAccountID === sessionAccountID))
168+
calculateAmount(isReportAPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, amount ?? 0, requestCurrency ?? '', action?.actorAccountID === sessionAccountID))
151169
: 0,
152-
[
153-
shouldShowSplitShare,
154-
isReportAPolicyExpenseChat,
155-
action?.actorAccountID,
156-
participantAccountIDs.length,
157-
transaction?.comment?.splits,
158-
requestAmount,
159-
requestCurrency,
160-
sessionAccountID,
161-
],
170+
[shouldShowSplitShare, isReportAPolicyExpenseChat, action?.actorAccountID, participantAccountIDs.length, transaction?.comment?.splits, amount, requestCurrency, sessionAccountID],
162171
);
163172

164173
const shouldWrapDisplayAmount = !(isBillSplit || shouldShowMerchantOrDescription || isTransactionScanning);

src/components/ReportActionItem/TransactionPreview/index.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ function TransactionPreview(props: TransactionPreviewProps) {
4141
contextAction,
4242
} = props;
4343

44-
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {canBeMissing: true});
44+
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {canBeMissing: true});
4545
const route = useRoute<PlatformStackRouteProp<TransactionDuplicateNavigatorParamList, typeof SCREENS.TRANSACTION_DUPLICATE.REVIEW>>();
46-
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`, {canBeMissing: true});
46+
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`, {canBeMissing: true});
4747
const isMoneyRequestAction = isMoneyRequestActionReportActionsUtils(action);
4848
const transactionID = transactionIDFromProps ?? (isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : null);
4949
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true});
@@ -75,8 +75,8 @@ function TransactionPreview(props: TransactionPreviewProps) {
7575
}, [chatReportID]);
7676

7777
const navigateToReviewFields = useCallback(() => {
78-
Navigation.navigate(getReviewNavigationRoute(route, report, transaction, duplicates));
79-
}, [duplicates, report, route, transaction]);
78+
Navigation.navigate(getReviewNavigationRoute(route, transactionThreadReport, transaction, duplicates));
79+
}, [duplicates, transactionThreadReport, route, transaction]);
8080

8181
let transactionPreview = transaction;
8282

@@ -86,6 +86,10 @@ function TransactionPreview(props: TransactionPreviewProps) {
8686
transactionPreview = originalTransaction;
8787
}
8888

89+
// See description of `transactionRawAmount` prop for more context
90+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
91+
const transactionRawAmount = (transaction?.modifiedAmount || transaction?.amount) ?? 0;
92+
8993
const iouAction = isBillSplit && originalTransaction ? (getIOUActionForReportID(chatReportID, originalTransaction.transactionID) ?? action) : action;
9094

9195
const shouldDisableOnPress = isBillSplit && isEmptyObject(transaction);
@@ -112,7 +116,8 @@ function TransactionPreview(props: TransactionPreviewProps) {
112116
chatReport={chatReport}
113117
personalDetails={personalDetails}
114118
transaction={transactionPreview}
115-
iouReport={iouReport}
119+
transactionRawAmount={transactionRawAmount}
120+
report={report}
116121
violations={violations}
117122
offlineWithFeedbackOnClose={offlineWithFeedbackOnClose}
118123
navigateToReviewFields={navigateToReviewFields}
@@ -135,7 +140,8 @@ function TransactionPreview(props: TransactionPreviewProps) {
135140
chatReport={chatReport}
136141
personalDetails={personalDetails}
137142
transaction={originalTransaction}
138-
iouReport={iouReport}
143+
transactionRawAmount={transactionRawAmount}
144+
report={report}
139145
violations={violations}
140146
offlineWithFeedbackOnClose={offlineWithFeedbackOnClose}
141147
navigateToReviewFields={navigateToReviewFields}

src/components/ReportActionItem/TransactionPreview/types.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type TransactionPreviewStyleType = {
1111
};
1212

1313
type TransactionPreviewProps = {
14-
/** The active IOUReport, used for Onyx subscription */
14+
/** The active reportID linked to the transaction */
1515
iouReportID: string | undefined;
1616

1717
/** The associated chatReport */
@@ -65,7 +65,7 @@ type TransactionPreviewProps = {
6565
reportPreviewAction?: ReportAction;
6666

6767
/** Whether to show payer/receiver data in the preview */
68-
shouldShowIOUData?: boolean;
68+
shouldShowPayerAndReceiver?: boolean;
6969

7070
/** In case we want to override context menu action */
7171
contextAction?: OnyxEntry<ReportAction>;
@@ -93,15 +93,19 @@ type TransactionPreviewContentProps = {
9393
/** Records any errors related to wallet terms. */
9494
walletTermsErrors: Errors | undefined;
9595

96-
/** Represents the IOU report entry from Onyx */
97-
iouReport: OnyxEntry<Report>;
96+
/** Represents the report linked to the transaction */
97+
report: OnyxEntry<Report>;
9898

9999
/** Flag to determine if a transaction involves a bill split among multiple parties. */
100100
isBillSplit: boolean;
101101

102102
/** Holds the transaction data entry from Onyx */
103103
transaction: OnyxEntry<Transaction>;
104104

105+
/** The original amount value on the transaction. This is used to deduce who is the sender and who is the receiver of the money request
106+
* In case of Splits the property `transaction` is actually an original transaction (for the whole split) and it does not have the data required to deduce who is the sender */
107+
transactionRawAmount: number;
108+
105109
/** Represents the action entry from Onyx */
106110
action: OnyxEntry<ReportAction>;
107111

@@ -130,7 +134,7 @@ type TransactionPreviewContentProps = {
130134
reportPreviewAction?: ReportAction;
131135

132136
/** Whether to show payer/receiver data in the preview */
133-
shouldShowIOUData?: boolean;
137+
shouldShowPayerAndReceiver?: boolean;
134138

135139
/** Is this component used during duplicate review flow */
136140
isReviewDuplicateTransactionPage?: boolean;

src/libs/TransactionPreviewUtils.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ const emptyPersonalDetails: OnyxTypes.PersonalDetails = {
4242
login: undefined,
4343
};
4444

45-
function getIOUData(managerID: number, ownerAccountID: number, isIOUReport: boolean, personalDetails: OnyxTypes.PersonalDetailsList | undefined, amount: number) {
45+
/**
46+
* Returns the data for displaying payer and receiver (`from` and `to`) values for given ids and amount.
47+
* In IOU transactions we can deduce who is the payer and receiver based on sign (positive/negative) of the amount.
48+
*/
49+
function getIOUPayerAndReceiver(managerID: number, ownerAccountID: number, personalDetails: OnyxTypes.PersonalDetailsList | undefined, amount: number) {
4650
let fromID = ownerAccountID;
4751
let toID = managerID;
4852

@@ -51,12 +55,10 @@ function getIOUData(managerID: number, ownerAccountID: number, isIOUReport: bool
5155
toID = ownerAccountID;
5256
}
5357

54-
return fromID && toID && isIOUReport
55-
? {
56-
from: personalDetails ? personalDetails[fromID] : emptyPersonalDetails,
57-
to: personalDetails ? personalDetails[toID] : emptyPersonalDetails,
58-
}
59-
: undefined;
58+
return {
59+
from: personalDetails ? personalDetails[fromID] : emptyPersonalDetails,
60+
to: personalDetails ? personalDetails[toID] : emptyPersonalDetails,
61+
};
6062
}
6163

6264
const getReviewNavigationRoute = (
@@ -348,5 +350,12 @@ function createTransactionPreviewConditionals({
348350
};
349351
}
350352

351-
export {getReviewNavigationRoute, getIOUData, getTransactionPreviewTextAndTranslationPaths, createTransactionPreviewConditionals, getViolationTranslatePath, getUniqueActionErrors};
353+
export {
354+
getReviewNavigationRoute,
355+
getIOUPayerAndReceiver,
356+
getTransactionPreviewTextAndTranslationPaths,
357+
createTransactionPreviewConditionals,
358+
getViolationTranslatePath,
359+
getUniqueActionErrors,
360+
};
352361
export type {TranslationPathOrText};

src/libs/__mocks__/TransactionPreviewUtils.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/stories/MoneyRequestReportPreview.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ const mockRenderItem: ListRenderItem<Transaction> = ({item}) => (
4141
isHovered={false}
4242
chatReport={chatReportR14932}
4343
personalDetails={personalDetails}
44-
iouReport={iouReportR14932}
44+
report={iouReportR14932}
4545
transaction={item}
46+
transactionRawAmount={item.amount}
4647
violations={item.errors ? violationsR14932 : []}
4748
offlineWithFeedbackOnClose={() => undefined}
4849
navigateToReviewFields={() => undefined}

src/stories/TransactionPreviewContent.stories.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const story: Meta<typeof TransactionPreviewContent> = {
103103
isHovered: false,
104104
chatReport: chatReportR14932,
105105
personalDetails,
106-
iouReport: iouReportR14932,
106+
report: iouReportR14932,
107107
transaction: transactionR14932,
108108
violations: [],
109109
offlineWithFeedbackOnClose(): void {},
@@ -119,7 +119,7 @@ const story: Meta<typeof TransactionPreviewContent> = {
119119
},
120120
argTypes: {
121121
...disabledProperties,
122-
iouReport: generateArgTypes(iouReportMap),
122+
report: generateArgTypes(iouReportMap),
123123
transaction: generateArgTypes(transactionsMap),
124124
violations: generateArgTypes(violationsMap),
125125
action: generateArgTypes(actionMap),
@@ -180,7 +180,7 @@ KeepButtonSplitRBRCategoriesAndTag.args = {
180180

181181
KeepButtonIOURbrCategoriesAndTag.args = {
182182
...KeepButtonRBRCategoriesAndTag.args,
183-
iouReport: iouReportWithModifiedType(CONST.REPORT.TYPE.IOU),
183+
report: iouReportWithModifiedType(CONST.REPORT.TYPE.IOU),
184184
};
185185

186186
DeletedKeepButtonSplitRBRCategoriesAndTag.args = {

0 commit comments

Comments
 (0)