Skip to content
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

fix: Use new ResolveDuplicates when approver is resolving duplicates #48522

Merged
merged 25 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
36b7e06
fix: Use new ResolveDuplicates when approver is resolving duplicates
nkdengineer Sep 4, 2024
2d1d4ca
add new api params
nkdengineer Sep 4, 2024
6325f73
Merge branch 'main' into fix/48416
nkdengineer Sep 9, 2024
8086b11
complete parameter for API
nkdengineer Sep 9, 2024
06bdd10
Merge branch 'main' into fix/48416
nkdengineer Sep 11, 2024
107be8e
fix the order issue
nkdengineer Sep 11, 2024
c5023dc
fix lint
nkdengineer Sep 11, 2024
e881a3b
Merge branch 'main' into fix/48416
nkdengineer Sep 11, 2024
b549c50
merge main
nkdengineer Sep 12, 2024
96d0da4
create a util to get iou action
nkdengineer Sep 12, 2024
754932d
rename variable
nkdengineer Sep 12, 2024
b7a27c3
fix lint
nkdengineer Sep 12, 2024
b331032
use getIOUActionForTransactions
nkdengineer Sep 12, 2024
85cc2e9
merge main
nkdengineer Sep 12, 2024
5fcf063
remove unuse variable
nkdengineer Sep 12, 2024
f56ffab
Update src/libs/API/parameters/ResolveDuplicatesParams.ts
nkdengineer Sep 12, 2024
c076bdc
Merge branch 'main' into fix/48416
nkdengineer Sep 13, 2024
aee9154
fix lint
nkdengineer Sep 13, 2024
0ba5ac8
Merge branch 'main' into fix/48416
nkdengineer Sep 17, 2024
c776014
update param
nkdengineer Sep 17, 2024
46c75e1
Merge branch 'main' into fix/48416
nkdengineer Sep 19, 2024
e32ed93
Merge branch 'main' into fix/48416
nkdengineer Sep 24, 2024
98287b6
fix native bug
nkdengineer Sep 24, 2024
23e2313
Update src/libs/actions/IOU.ts
nkdengineer Sep 25, 2024
b3cce2d
Merge branch 'main' into fix/48416
nkdengineer Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Comment on lines +75 to +84
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we move these here instead of MoneyRequestPreview?


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 @@ -284,6 +284,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 @@ -701,6 +702,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) {
nkdengineer marked this conversation as resolved.
Show resolved Hide resolved
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
Loading