Skip to content

Commit

Permalink
Merge pull request #48522 from nkdengineer/fix/48416
Browse files Browse the repository at this point in the history
fix: Use new ResolveDuplicates when approver is resolving duplicates
  • Loading branch information
pecanoro authored Sep 25, 2024
2 parents 9119f6b + b3cce2d commit a373b27
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,19 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {MoneyRequestPreviewProps, PendingMessageProps} from './types';

function MoneyRequestPreviewContent({
iouReport,
isBillSplit,
session,
action,
personalDetails,
chatReport,
transaction,
contextMenuAnchor,
chatReportID,
reportID,
onPreviewPressed,
containerStyles,
walletTerms,
checkIfContextMenuActive = () => {},
shouldShowPendingConversionMessage = false,
isHovered = false,
isWhisper = false,
transactionViolations,
shouldDisplayContextMenu = true,
iouReportID,
}: MoneyRequestPreviewProps) {
const theme = useTheme();
const styles = useThemeStyles();
Expand All @@ -78,6 +72,16 @@ function MoneyRequestPreviewContent({
const {windowWidth} = useWindowDimensions();
const route = useRoute<RouteProp<TransactionDuplicateNavigatorParamList, typeof SCREENS.TRANSACTION_DUPLICATE.REVIEW>>();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || '-1'}`);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID || '-1'}`);

const isMoneyRequestAction = ReportActionsUtils.isMoneyRequestAction(action);
const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : '-1';
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);

const sessionAccountID = session?.accountID;
const managerID = iouReport?.managerID ?? -1;
Expand Down
36 changes: 5 additions & 31 deletions src/components/ReportActionItem/MoneyRequestPreview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,18 @@
import lodashIsEmpty from 'lodash/isEmpty';
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import {useOnyx} from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import MoneyRequestPreviewContent from './MoneyRequestPreviewContent';
import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './types';
import type {MoneyRequestPreviewProps} from './types';

function MoneyRequestPreview(props: MoneyRequestPreviewProps) {
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${props.iouReportID || '-1'}`);
// We should not render the component if there is no iouReport and it's not a split or track expense.
// Moved outside of the component scope to allow for easier use of hooks in the main component.
// eslint-disable-next-line react/jsx-props-no-spreading
return lodashIsEmpty(props.iouReport) && !(props.isBillSplit || props.isTrackExpense) ? null : <MoneyRequestPreviewContent {...props} />;
return lodashIsEmpty(iouReport) && !(props.isBillSplit || props.isTrackExpense) ? null : <MoneyRequestPreviewContent {...props} />;
}

MoneyRequestPreview.displayName = 'MoneyRequestPreview';

export default withOnyx<MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
chatReport: {
key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`,
},
iouReport: {
key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`,
},
session: {
key: ONYXKEYS.SESSION,
},
transaction: {
key: ({action}) => {
const isMoneyRequestAction = ReportActionsUtils.isMoneyRequestAction(action);
const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : 0;
return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
},
},
walletTerms: {
key: ONYXKEYS.WALLET_TERMS,
},
transactionViolations: {
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
},
})(MoneyRequestPreview);
export default MoneyRequestPreview;
28 changes: 2 additions & 26 deletions src/components/ReportActionItem/MoneyRequestPreview/types.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,9 @@
import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import type * as OnyxTypes from '@src/types/onyx';
import type IconAsset from '@src/types/utils/IconAsset';

type MoneyRequestPreviewOnyxProps = {
/** All of the personal details for everyone */
personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;

/** Chat report associated with iouReport */
chatReport: OnyxEntry<OnyxTypes.Report>;

/** IOU report data object */
iouReport: OnyxEntry<OnyxTypes.Report>;

/** Session info for the currently logged in user. */
session: OnyxEntry<OnyxTypes.Session>;

/** The transaction attached to the action.message.iouTransactionID */
transaction: OnyxEntry<OnyxTypes.Transaction>;

/** The transaction violations attached to the action.message.iouTransactionID */
transactionViolations: OnyxCollection<OnyxTypes.TransactionViolation[]>;

/** Information about the user accepting the terms for payments */
walletTerms: OnyxEntry<OnyxTypes.WalletTerms>;
};

type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
type MoneyRequestPreviewProps = {
/** The active IOUReport, used for Onyx subscription */
// The iouReportID is used inside withOnyx HOC
// eslint-disable-next-line react/no-unused-prop-types
Expand Down Expand Up @@ -90,4 +66,4 @@ type PendingProps = {

type PendingMessageProps = PendingProps | NoPendingProps;

export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps, PendingMessageProps};
export type {MoneyRequestPreviewProps, PendingMessageProps};
24 changes: 24 additions & 0 deletions src/libs/API/parameters/ResolveDuplicatesParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type ResolveDuplicatesParams = {
/** The ID of the transaction that we want to keep */
transactionID: string;

/** The list of other duplicated transactions */
transactionIDList: string[];
created: string;
merchant: string;
amount: number;
currency: string;
category: string;
comment: string;
billable: boolean;
reimbursable: boolean;
tag: string;

/** The reportActionID of the dismissed violation action in the kept transaction thread report */
dismissedViolationReportActionID: string;

/** The ID list of the hold report actions corresponding to the transactionIDList */
reportActionIDList: string[];
};

export default ResolveDuplicatesParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export type {default as SendInvoiceParams} from './SendInvoiceParams';
export type {default as PayInvoiceParams} from './PayInvoiceParams';
export type {default as MarkAsCashParams} from './MarkAsCashParams';
export type {default as TransactionMergeParams} from './TransactionMergeParams';
export type {default as ResolveDuplicatesParams} from './ResolveDuplicatesParams';
export type {default as UpdateSubscriptionTypeParams} from './UpdateSubscriptionTypeParams';
export type {default as SignUpUserParams} from './SignUpUserParams';
export type {default as UpdateSubscriptionAutoRenewParams} from './UpdateSubscriptionAutoRenewParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ const WRITE_COMMANDS = {
PAY_INVOICE: 'PayInvoice',
MARK_AS_CASH: 'MarkAsCash',
TRANSACTION_MERGE: 'Transaction_Merge',
RESOLVE_DUPLICATES: 'ResolveDuplicates',
UPDATE_SUBSCRIPTION_TYPE: 'UpdateSubscriptionType',
SIGN_UP_USER: 'SignUpUser',
UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew',
Expand Down Expand Up @@ -703,6 +704,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.PAY_INVOICE]: Parameters.PayInvoiceParams;
[WRITE_COMMANDS.MARK_AS_CASH]: Parameters.MarkAsCashParams;
[WRITE_COMMANDS.TRANSACTION_MERGE]: Parameters.TransactionMergeParams;
[WRITE_COMMANDS.RESOLVE_DUPLICATES]: Parameters.ResolveDuplicatesParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_TYPE]: Parameters.UpdateSubscriptionTypeParams;
[WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams;
Expand Down
150 changes: 138 additions & 12 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
PayMoneyRequestParams,
ReplaceReceiptParams,
RequestMoneyParams,
ResolveDuplicatesParams,
SendInvoiceParams,
SendMoneyParams,
SetNameValuePairParams,
Expand Down Expand Up @@ -8011,6 +8012,21 @@ function getIOURequestPolicyID(transaction: OnyxEntry<OnyxTypes.Transaction>, re
return workspaceSender?.policyID ?? report?.policyID ?? '-1';
}

function getIOUActionForTransactions(transactionIDList: string[], iouReportID: string): Array<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU>> {
return Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`] ?? {})?.filter(
(reportAction): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> => {
if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) {
return false;
}
const message = ReportActionsUtils.getOriginalMessage(reportAction);
if (!message?.IOUTransactionID) {
return false;
}
return transactionIDList.includes(message.IOUTransactionID);
},
);
}

/** Merge several transactions into one by updating the fields of the one we want to keep and deleting the rest */
function mergeDuplicates(params: TransactionMergeParams) {
const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`];
Expand Down Expand Up @@ -8095,18 +8111,7 @@ function mergeDuplicates(params: TransactionMergeParams) {
},
};

const iouActionsToDelete = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`] ?? {})?.filter(
(reportAction): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> => {
if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) {
return false;
}
const message = ReportActionsUtils.getOriginalMessage(reportAction);
if (!message?.IOUTransactionID) {
return false;
}
return params.transactionIDList.includes(message.IOUTransactionID);
},
);
const iouActionsToDelete = getIOUActionForTransactions(params.transactionIDList, params.reportID);

const deletedTime = DateUtils.getDBTime();
const expenseReportActionsOptimisticData: OnyxUpdate = {
Expand Down Expand Up @@ -8167,6 +8172,125 @@ function mergeDuplicates(params: TransactionMergeParams) {
API.write(WRITE_COMMANDS.TRANSACTION_MERGE, params, {optimisticData, failureData});
}

/** Instead of merging the duplicates, it updates the transaction we want to keep and puts the others on hold without deleting them */
function resolveDuplicates(params: TransactionMergeParams) {
const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`];

const optimisticTransactionData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`,
value: {
...originalSelectedTransaction,
billable: params.billable,
comment: {
comment: params.comment,
},
category: params.category,
created: params.created,
currency: params.currency,
modifiedMerchant: params.merchant,
reimbursable: params.reimbursable,
tag: params.tag,
},
};

const failureTransactionData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`,
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
value: originalSelectedTransaction as OnyxTypes.Transaction,
};

const optimisticTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => {
const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? [];
const newViolation = {name: CONST.VIOLATIONS.HOLD, type: CONST.VIOLATION_TYPES.VIOLATION};
const updatedViolations = id === params.transactionID ? violations : [...violations, newViolation];
return {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`,
value: updatedViolations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION),
};
});

const failureTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => {
const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? [];
return {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`,
value: violations,
};
});

const iouActionList = getIOUActionForTransactions(params.transactionIDList, params.reportID);
const transactionThreadReportIDList = iouActionList.map((action) => action?.childReportID);
const orderedTransactionIDList = iouActionList.map((action) => {
const message = ReportActionsUtils.getOriginalMessage(action);
return message?.IOUTransactionID ?? '';
});

const optimisticHoldActions: OnyxUpdate[] = [];
const failureHoldActions: OnyxUpdate[] = [];
const reportActionIDList: string[] = [];
transactionThreadReportIDList.forEach((transactionThreadReportID) => {
const createdReportAction = ReportUtils.buildOptimisticHoldReportAction();
reportActionIDList.push(createdReportAction.reportActionID);
optimisticHoldActions.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
value: {
[createdReportAction.reportActionID]: createdReportAction,
},
});
failureHoldActions.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
value: {
[createdReportAction.reportActionID]: {
errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericHoldExpenseFailureMessage'),
},
},
});
});

const transactionThreadReportID = getIOUActionForTransactions([params.transactionID], params.reportID)?.[0]?.childReportID;
const optimisticReportAction = ReportUtils.buildOptimisticDismissedViolationReportAction({
reason: 'manual',
violationName: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
});

const optimisticReportActionData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
value: {
[optimisticReportAction.reportActionID]: optimisticReportAction,
},
};

const failureReportActionData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
value: {
[optimisticReportAction.reportActionID]: null,
},
};

const optimisticData: OnyxUpdate[] = [];
const failureData: OnyxUpdate[] = [];

optimisticData.push(optimisticTransactionData, ...optimisticTransactionViolations, ...optimisticHoldActions, optimisticReportActionData);
failureData.push(failureTransactionData, ...failureTransactionViolations, ...failureHoldActions, failureReportActionData);
const {reportID, transactionIDList, receiptID, ...otherParams} = params;

const parameters: ResolveDuplicatesParams = {
...otherParams,
reportActionIDList,
transactionIDList: orderedTransactionIDList,
dismissedViolationReportActionID: optimisticReportAction.reportActionID,
};

API.write(WRITE_COMMANDS.RESOLVE_DUPLICATES, parameters, {optimisticData, failureData});
}

export {
adjustRemainingSplitShares,
approveMoneyRequest,
Expand Down Expand Up @@ -8237,5 +8361,7 @@ export {
updateMoneyRequestTaxAmount,
updateMoneyRequestTaxRate,
mergeDuplicates,
resolveDuplicates,
prepareToCleanUpMoneyRequest,
};
export type {GPSPoint as GpsPoint, IOURequestType};
Loading

0 comments on commit a373b27

Please sign in to comment.