Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/hooks/useDeleteTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac
continue;
}
const originalTransaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
const originalTransactionIouActions = getIOUActionForTransactions([transactionID], report?.reportID);
const originalTransactionIouActions = getIOUActionForTransactions([transactionID], allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`]);
const iouReportID = isMoneyRequestAction(originalTransactionIouActions.at(0)) ? originalTransactionIouActions.at(0)?.reportID : undefined;
const candidateIOUReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`];
// For self-DM tracks and split bills, action.reportID resolves to a chat report, not an IOU/expense report.
Expand Down
45 changes: 27 additions & 18 deletions src/libs/actions/IOU/Duplicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,27 @@ import type {PerDiemExpenseInformation} from './PerDiem';
import type {CreateDistanceRequestInformation} from './Split';
import type {CreateTrackExpenseParams} from './TrackExpense';

import {getAllReportActionsFromIOU, getAllReports, getAllTransactions} from '.';
import {getAllReports, getAllTransactions} from '.';
import {getCleanUpTransactionThreadReportOnyxData} from './DeleteMoneyRequest';
import {getMoneyRequestParticipantsFromReport} from './MoneyRequest';
import {submitPerDiemExpense} from './PerDiem';
import {createDistanceRequest} from './Split';
import {requestMoney, trackExpense} from './TrackExpense';

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

type DiscardedSource = {
Expand Down Expand Up @@ -174,6 +174,7 @@ type MergeDuplicatesFuncParams = MergeDuplicatesParams & {
taxAmount?: number;
taxValue?: string;
allTransactionViolations: OnyxCollection<OnyxTypes.TransactionViolations>;
allReportActionsList: OnyxCollection<OnyxTypes.ReportActions>;
};

/** Merge several transactions into one by updating the fields of the one we want to keep and deleting the rest */
Expand All @@ -184,6 +185,7 @@ function mergeDuplicates({
taxAmount,
taxValue,
allTransactionViolations,
allReportActionsList,
...params
}: MergeDuplicatesFuncParams) {
const allParams: MergeDuplicatesParams = {...params};
Expand Down Expand Up @@ -255,7 +257,7 @@ function mergeDuplicates({
if (transaction.reimbursable) {
entry.reimbursableAmount += transaction.amount;
}
entry.actions.push(...getIOUActionForTransactions([id], transaction.reportID));
entry.actions.push(...getIOUActionForTransactions([id], allReportActionsList?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction.reportID}`]));
sources.set(transaction.reportID, entry);
}
const deletedTime = DateUtils.getDBTime();
Expand Down Expand Up @@ -312,7 +314,10 @@ function mergeDuplicates({
const optimisticReportAction = buildOptimisticResolvedDuplicatesReportAction();

const transactionThreadReportID =
optimisticTransactionThreadReportID ?? (params.reportID ? getIOUActionForTransactions([params.transactionID], params.reportID).at(0)?.childReportID : undefined);
optimisticTransactionThreadReportID ??
(params.reportID
? getIOUActionForTransactions([params.transactionID], allReportActionsList?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`]).at(0)?.childReportID
: undefined);
const optimisticReportActionData: OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS> = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
Expand Down Expand Up @@ -431,12 +436,14 @@ function resolveDuplicates({
taxValue,
transactionThreadReportIDMap,
allTransactionViolations,
allReportActionsList,
...params
}: MergeDuplicatesParams & {
taxAmount?: number;
taxValue?: string;
transactionThreadReportIDMap: Record<string, string | undefined>;
allTransactionViolations: OnyxCollection<OnyxTypes.TransactionViolations>;
allReportActionsList: OnyxCollection<OnyxTypes.ReportActions>;
}) {
if (!params.transactionID) {
return;
Expand Down Expand Up @@ -541,7 +548,9 @@ function resolveDuplicates({
});
}

const transactionThreadReportID = params.reportID ? getIOUActionForTransactions([params.transactionID], params.reportID).at(0)?.childReportID : undefined;
const transactionThreadReportID = params.reportID
? getIOUActionForTransactions([params.transactionID], allReportActionsList?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`]).at(0)?.childReportID
: undefined;
const optimisticReportAction = buildOptimisticDismissedViolationReportAction({
reason: 'manual',
violationName: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
Expand Down
9 changes: 5 additions & 4 deletions src/pages/TransactionDuplicate/Confirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function Confirmation() {
const {goBack} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'confirmation', route.params.threadReportID, route.params.backTo);
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${newTransaction?.reportID}`);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newTransaction?.reportID}`);
const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
Comment thread
cretadn22 marked this conversation as resolved.
const reportAction = Object.values(reportActions ?? {}).find(
(action) => ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID === reviewDuplicates?.transactionID,
);
Expand Down Expand Up @@ -123,7 +124,7 @@ function Confirmation() {
// Suppress the NotFound guard for the discarded thread the server tears down on merge.
const keptReportRoute = ROUTES.REPORT_WITH_ID.getRoute(mergeParams.reportID);
setDeleteTransactionNavigateBackUrl(keptReportRoute);
mergeDuplicates({...mergeParams, ...taxData, currentUserAccountID, currentUserLogin: currentUserLogin ?? '', allTransactionViolations});
mergeDuplicates({...mergeParams, ...taxData, currentUserAccountID, currentUserLogin: currentUserLogin ?? '', allTransactionViolations, allReportActionsList: allReportActions});
if (isSuperWideRHPDisplayed) {
Navigation.dismissToSuperWideRHP();
return;
Expand All @@ -137,12 +138,12 @@ function Confirmation() {
Navigation.dismissModal({
afterTransition: () => Navigation.navigate(keptReportRoute, {forceReplace: true}),
});
}, [childReportID, transactionsMergeParams, taxData, currentUserAccountID, currentUserLogin, isSuperWideRHPDisplayed, allTransactionViolations]);
}, [childReportID, transactionsMergeParams, taxData, currentUserAccountID, currentUserLogin, isSuperWideRHPDisplayed, allTransactionViolations, allReportActions]);

const handleResolveDuplicates = useCallback(() => {
resolveDuplicates({...transactionsMergeParams, ...taxData, transactionThreadReportIDMap, allTransactionViolations});
resolveDuplicates({...transactionsMergeParams, ...taxData, transactionThreadReportIDMap, allTransactionViolations, allReportActionsList: allReportActions});
Navigation.dismissToSuperWideRHP();
}, [transactionsMergeParams, taxData, transactionThreadReportIDMap, allTransactionViolations]);
}, [transactionsMergeParams, taxData, transactionThreadReportIDMap, allTransactionViolations, allReportActions]);

const contextMenuStateValue = useMemo(
() => ({
Expand Down
2 changes: 1 addition & 1 deletion src/pages/iou/SplitExpensePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) {
const originalTransactionID = draftTransaction?.comment?.originalTransactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
// For selfDM expenses, the IOU action lives in the selfDM report, not in an expense report.
const iouReportIDForActions = expenseReport?.reportID ?? (isSelfDM(draftTransactionReport) ? draftTransactionReport?.reportID : undefined);
const iouActions = getIOUActionForTransactions([originalTransactionID], iouReportIDForActions);
const iouActions = getIOUActionForTransactions([originalTransactionID], allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportIDForActions}`]);
const {iouReport} = useGetIOUReportFromReportAction(iouActions.at(0));
const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(iouReport?.reportID)}`);

Expand Down
47 changes: 40 additions & 7 deletions tests/actions/IOUTest/DuplicateTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type {ReportActionsCollectionDataSet} from '@src/types/onyx/ReportAction'
import type Transaction from '@src/types/onyx/Transaction';
import type {TransactionCollectionDataSet} from '@src/types/onyx/Transaction';

import type {OnyxEntry, OnyxInputValue} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry, OnyxInputValue} from 'react-native-onyx';

import Onyx from 'react-native-onyx';

Expand Down Expand Up @@ -229,6 +229,10 @@ describe('actions/Duplicate', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations,
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations,
},
allReportActionsList: {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action456: iouAction1, action789: iouAction2},
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${childReportID}`]: {},
},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -315,6 +319,7 @@ describe('actions/Duplicate', () => {
currentUserLogin: RORY_EMAIL,
currentUserAccountID: RORY_ACCOUNT_ID,
allTransactionViolations: {[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: []},
allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -374,6 +379,7 @@ describe('actions/Duplicate', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: [],
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: [],
},
allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -559,6 +565,9 @@ describe('actions/Duplicate', () => {
expect(iouAction1?.childVisibleActionCount).toBe(2);
expect(iouAction2?.childVisibleActionCount).toBe(1);
expect(previewAction?.childVisibleActionCount).toBe(3);
if (!iouAction1 || !iouAction2) {
throw new Error('Expected iouAction1 and iouAction2 to be defined after fetching from Onyx.');
}

await waitForBatchedUpdates();

Expand Down Expand Up @@ -589,6 +598,7 @@ describe('actions/Duplicate', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations,
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations,
},
allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {[iouAction1ID]: iouAction1, [iouAction2ID]: iouAction2}},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -697,6 +707,7 @@ describe('actions/Duplicate', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations,
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations,
},
allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {mainAction123: mainIouAction, action456: dupIouAction}},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -784,6 +795,10 @@ describe('actions/Duplicate', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations,
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${crossReportDuplicateID}`]: crossDuplicateViolations,
},
allReportActionsList: {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${keptReportID}`]: {actionMain: mainIouAction},
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${crossReportID}`]: {actionCross: crossIouAction},
},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -866,13 +881,14 @@ describe('actions/Duplicate', () => {
{name: CONST.VIOLATIONS.MISSING_CATEGORY, type: CONST.VIOLATION_TYPES.VIOLATION},
];

const createMockIouAction = (transactionID: string, reportActionID: string, childReportID: string) => ({
const createMockIouAction = (transactionID: string, reportActionID: string, childReportID: string): ReportAction => ({
reportActionID,
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
originalMessage: {
created: '2024-01-01 12:00:00',
originalMessage: createMock<OriginalMessageIOU>({
IOUTransactionID: transactionID,
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
},
}),
message: [{type: 'TEXT', text: 'Test IOU message'}],
childReportID,
});
Expand Down Expand Up @@ -943,6 +959,9 @@ describe('actions/Duplicate', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations,
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations,
},
allReportActionsList: {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action456: iouAction1, action789: iouAction2, mainAction123: mainIouAction},
},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -1035,7 +1054,7 @@ describe('actions/Duplicate', () => {
};

// When: Call resolveDuplicates with undefined transactionID
resolveDuplicates({...resolveParams, allTransactionViolations: {}});
resolveDuplicates({...resolveParams, allTransactionViolations: {}, allReportActionsList: undefined});
await waitForBatchedUpdates();

// Then: Verify API was not called
Expand Down Expand Up @@ -1081,6 +1100,7 @@ describe('actions/Duplicate', () => {
resolveDuplicates({
...resolveParams,
allTransactionViolations: {[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations},
allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {mainAction123: mainIouAction}},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -1117,7 +1137,14 @@ describe('actions/Duplicate', () => {
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${duplicate1ID}`, duplicateTransaction);
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`, mainViolations);
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`, duplicateViolations);
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {});
// A non-money-request action and an IOU action with no IOUTransactionID should both be filtered out without matching.
const commentAction = createMock<ReportAction>({reportActionID: 'commentAction', actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT});
const iouActionWithoutTransactionID = createMock<ReportAction>({
reportActionID: 'iouActionWithoutTransactionID',
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
originalMessage: createMock<OriginalMessageIOU>({type: CONST.IOU.REPORT_ACTION_TYPE.CREATE}),
});
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {commentAction, iouActionWithoutTransactionID});
await waitForBatchedUpdates();

const resolveParams = {
Expand All @@ -1137,13 +1164,14 @@ describe('actions/Duplicate', () => {
transactionThreadReportIDMap: {},
};

// When: Call resolveDuplicates without IOU actions
// When: Call resolveDuplicates without matching IOU actions
resolveDuplicates({
...resolveParams,
allTransactionViolations: {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations,
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicateViolations,
},
allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {commentAction, iouActionWithoutTransactionID}},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -1229,6 +1257,7 @@ describe('actions/Duplicate', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations,
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${crossReportDuplicateID}`]: crossDuplicateViolations,
},
allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportA}`]: {actionMain: mainIouAction}},
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -2210,6 +2239,9 @@ describe('actions/Duplicate', () => {
actions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouAction.reportActionID}`] = iouAction;
}
const actionCollectionDataSet: ReportActionsCollectionDataSet = {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`]: actions};
const allReportActionsList: OnyxCollection<ReportActions> = {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`]: Object.fromEntries(iouActions.map((iouAction) => [iouAction.reportActionID, iouAction])),
};

return waitForBatchedUpdates()
.then(() => Onyx.multiSet({...transactionCollectionDataSet, ...actionCollectionDataSet}))
Expand All @@ -2228,6 +2260,7 @@ describe('actions/Duplicate', () => {
[transaction2.transactionID]: 'transactionThread-2',
},
allTransactionViolations: {},
allReportActionsList,
});
return waitForBatchedUpdates();
})
Expand Down
Loading