Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1162976
inital testing
Tony-MK Jun 24, 2025
b33699c
renaming varible
Tony-MK Jun 24, 2025
a383b2c
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jun 27, 2025
193740a
using helper function for deleting money request
Tony-MK Jun 27, 2025
fb801ab
prettier
Tony-MK Jun 27, 2025
580c098
Merge branch 'Expensify:main' into duplicateTransactions
Tony-MK Jun 27, 2025
3afab42
Merge branch 'Expensify:main' into duplicateTransactions
Tony-MK Jun 27, 2025
c1bfda2
applied fix to updateMoneyRequestDate
Tony-MK Jun 27, 2025
f97a346
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jun 28, 2025
900bec9
Merge branch 'main' into duplicateTransactions
Tony-MK Jul 2, 2025
b8b1bab
clearing up comments
Tony-MK Jul 2, 2025
ee7721f
prettier
Tony-MK Jul 2, 2025
b2eab4b
prettier
Tony-MK Jul 2, 2025
545f1d7
Merge branch 'Expensify:main' into duplicateTransactions
Tony-MK Jul 3, 2025
7f7df39
refactoring function into one (#61)
Tony-MK Jul 3, 2025
0c53536
Merge branch 'Expensify:main' into duplicateTransactions
Tony-MK Jul 3, 2025
4fd1419
prettier
Tony-MK Jul 3, 2025
dbcbb9f
Merge branch 'Expensify:main' into duplicateTransactions
Tony-MK Jul 9, 2025
afa7c11
Merge branch 'main' into duplicateTransactions
Tony-MK Jul 9, 2025
e5c6c8f
Merge branch 'Expensify:main' into duplicateTransactions
Tony-MK Jul 9, 2025
deb5be8
revert condition
Tony-MK Jul 9, 2025
c1d4082
Merge branch 'duplicateTransactions' of https://github.com/Tony-MK/Ex…
Tony-MK Jul 9, 2025
582e3a0
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jul 10, 2025
8a2c58c
Merge branch 'main' into duplicateTransactions
Tony-MK Jul 15, 2025
74ee6d8
lint
Tony-MK Jul 15, 2025
dbfc695
prettier
Tony-MK Jul 15, 2025
7d5cbd3
lint
Tony-MK Jul 15, 2025
29af4bc
prettier
Tony-MK Jul 15, 2025
05d0534
fixed tests...
Tony-MK Jul 15, 2025
4f2a157
opps, tests again...
Tony-MK Jul 15, 2025
2968ed9
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jul 16, 2025
a7370fa
applied suggestions
Tony-MK Jul 16, 2025
ec0b927
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jul 16, 2025
9e6a003
lint
Tony-MK Jul 17, 2025
21a667d
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jul 21, 2025
f17b8fa
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jul 24, 2025
2aea09b
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jul 24, 2025
5f6dd47
useDuplicateTransactionsAndViolations
Tony-MK Jul 24, 2025
76a83ad
prettier...
Tony-MK Jul 24, 2025
47be6d1
lint
Tony-MK Jul 25, 2025
233da31
Resolving conflicts
Tony-MK Jul 25, 2025
3ece8d2
Merge branch 'main' into duplicateTransactions
Tony-MK Jul 27, 2025
9480a57
Update useDuplicateTransactionsAndViolations.ts
Tony-MK Jul 27, 2025
b6a5894
lint
Tony-MK Jul 27, 2025
7c16719
useDuplicateTransactionAndViolation.ts
Tony-MK Jul 28, 2025
9657362
moving useDuplicateTransactionsAndViolations
Tony-MK Jul 28, 2025
9705cf9
applied suggestions
Tony-MK Jul 28, 2025
6e8a9af
applied changes
Tony-MK Jul 28, 2025
cb54fde
lint
Tony-MK Jul 29, 2025
97ac841
applied suggestions
Tony-MK Jul 30, 2025
496809d
typecheck
Tony-MK Jul 30, 2025
2e4ae6e
refactoring getValidDuplicateTransactionIDs
Tony-MK Jul 31, 2025
8464f3d
Merge branch 'main' of https://github.com/Expensify/App into duplicat…
Tony-MK Jul 31, 2025
daa8de5
reverting line
Tony-MK Aug 1, 2025
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
6 changes: 5 additions & 1 deletion src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac
import {ActivityIndicator, InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations';
import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility';
import useLocalize from '@hooks/useLocalize';
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
Expand Down Expand Up @@ -191,6 +192,7 @@ function MoneyReportHeader({
{canBeMissing: true},
);

const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transactions.map((t) => t.transactionID));
const isExported = useMemo(() => isExportedUtils(reportActions), [reportActions]);
// wrapped in useMemo to improve performance because this is an operation on array
const integrationNameFromExportMessage = useMemo(() => {
Expand Down Expand Up @@ -1137,7 +1139,9 @@ function MoneyReportHeader({
}
// it's deleting transaction but not the report which leads to bug (that is actually also on staging)
// Money request should be deleted when interactions are done, to not show the not found page before navigating to goBackRoute
InteractionManager.runAfterInteractions(() => deleteMoneyRequest(transaction?.transactionID, requestParentReportAction));
InteractionManager.runAfterInteractions(() =>
deleteMoneyRequest(transaction?.transactionID, requestParentReportAction, duplicateTransactions, duplicateTransactionViolations),
);
goBackRoute = getNavigationUrlOnMoneyRequestDelete(transaction.transactionID, requestParentReportAction, false);
}

Expand Down
7 changes: 4 additions & 3 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations';
import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -80,7 +81,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
{canBeMissing: true},
);
const transactionViolations = useTransactionViolations(transaction?.transactionID);

const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transaction?.transactionID ? [transaction.transactionID] : []);
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {canBeMissing: true});
Expand Down Expand Up @@ -351,9 +352,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
throw new Error('Data missing');
}
if (isTrackExpenseAction(parentReportAction)) {
deleteTrackExpense(report?.parentReportID, transaction.transactionID, parentReportAction, true);
deleteTrackExpense(report?.parentReportID, transaction.transactionID, parentReportAction, duplicateTransactions, duplicateTransactionViolations, true);
} else {
deleteMoneyRequest(transaction.transactionID, parentReportAction, true);
deleteMoneyRequest(transaction.transactionID, parentReportAction, duplicateTransactions, duplicateTransactionViolations, true);
removeTransaction(transaction.transactionID);
}
onBackButtonPress();
Expand Down
141 changes: 141 additions & 0 deletions src/hooks/useDuplicateTransactionsAndViolations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {useMemo} from 'react';
import type {OnyxCollection} from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Transaction, TransactionViolations} from '@src/types/onyx';
import useOnyx from './useOnyx';

/**
* Selects violations related to provided transaction IDs and if present, the violations of their duplicates.
* @param transactionIDs - An array of transaction IDs to fetch their violations for.
* @param allTransactionsViolations - A collection of all transaction violations currently in the onyx db.
* @returns - A collection of violations related to the transaction IDs and if present, the violations of their duplicates.
* @private
*/
function selectViolationsWithDuplicates(transactionIDs: string[], allTransactionsViolations: OnyxCollection<TransactionViolations>): OnyxCollection<TransactionViolations> {
if (!allTransactionsViolations || !transactionIDs?.length) {
return {};
}

const result: OnyxCollection<TransactionViolations> = {};

for (const transactionID of transactionIDs) {
const key = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`;
const transactionViolations = allTransactionsViolations[key];

if (!transactionViolations) {
continue;
}

result[key] = transactionViolations;

transactionViolations
.filter((violations) => violations.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)
.flatMap((violations) => violations?.data?.duplicates ?? [])
.forEach((duplicateID) => {
if (!duplicateID) {
return;
}

const duplicateKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicateID}`;
const duplicateViolations = allTransactionsViolations[duplicateKey];

if (duplicateViolations) {
result[duplicateKey] = duplicateViolations;
}
});
}

return result;
}

/**
* Selects transactions related to provided transaction IDs and if present, the duplicate transactions.
* @param transactionIDs - An array of transaction IDs to fetch their transactions for.
* @param allTransactions - A collection of all transactions currently in the onyx.
* @param duplicateTransactionViolations - A collection of all duplicate transaction violations currently in the onyx.
* @returns - A collection of transactions related to the transaction IDs and if present, the duplicate transactions.
*/

function selectTransactionsWithDuplicates(
transactionIDs: string[],
allTransactions: OnyxCollection<Transaction>,
duplicateTransactionViolations: OnyxCollection<TransactionViolations>,
): OnyxCollection<Transaction> {
if (!allTransactions) {
return {};
}

const result: OnyxCollection<Transaction> = {};

for (const transactionID of transactionIDs) {
const key = `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
const transaction = allTransactions[key];
if (transaction) {
result[key] = transaction;
}

const transactionViolations = duplicateTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`];

if (!transactionViolations) {
continue;
}

transactionViolations
.filter((violations) => violations.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)
.flatMap((violations) => violations?.data?.duplicates ?? [])
.forEach((duplicateID) => {
if (!duplicateID) {
return;
}

const duplicateKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${duplicateID}`;
const duplicateTransaction = allTransactions[duplicateKey];

if (duplicateTransaction) {
result[duplicateKey] = duplicateTransaction;
}
});
}
return result;
}

type DuplicateTransactionsAndViolations = {
duplicateTransactions: OnyxCollection<Transaction>;
duplicateTransactionViolations: OnyxCollection<TransactionViolations>;
};

/**
* A hook to fetch transactions, their violations and if present, the duplicate transactions and their violations.
* @param transactionIDs - Array of transaction IDs to check for duplicates.
* @returns - An object containing duplicate transactions and their violations.
*/
function useDuplicateTransactionsAndViolations(transactionIDs: string[]): DuplicateTransactionsAndViolations {
const violationsSelectorMemo = useMemo(() => {
return (allTransactionsViolations: OnyxCollection<TransactionViolations>) => selectViolationsWithDuplicates(transactionIDs, allTransactionsViolations);
}, [transactionIDs]);

const [duplicateTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {
canBeMissing: true,
selector: violationsSelectorMemo,
});

const transactionSelector = useMemo(() => {
Comment thread
Tony-MK marked this conversation as resolved.
return (allTransactions: OnyxCollection<Transaction>) => selectTransactionsWithDuplicates(transactionIDs, allTransactions, duplicateTransactionViolations);
}, [transactionIDs, duplicateTransactionViolations]);

const [duplicateTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
canBeMissing: true,
selector: transactionSelector,
});

return useMemo(
() => ({
duplicateTransactions,
duplicateTransactionViolations,
}),
[duplicateTransactions, duplicateTransactionViolations],
);
}

export default useDuplicateTransactionsAndViolations;
7 changes: 5 additions & 2 deletions src/hooks/useSelectedTransactionsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {OriginalMessageIOU, Report, ReportAction, Session, Transaction} from '@src/types/onyx';
import useDuplicateTransactionsAndViolations from './useDuplicateTransactionsAndViolations';
import useLocalize from './useLocalize';
import useOnyx from './useOnyx';
import useReportIsArchived from './useReportIsArchived';
Expand All @@ -45,6 +46,7 @@ function useSelectedTransactionsActions({
}) {
const {selectedTransactionIDs, clearSelectedTransactions} = useSearchContext();
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(selectedTransactionIDs);
const isReportArchived = useReportIsArchived(report?.reportID);
const selectedTransactions = useMemo(
() =>
Expand All @@ -62,6 +64,7 @@ function useSelectedTransactionsActions({
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const isTrackExpenseThread = isTrackExpenseReport(report);
const isInvoice = isInvoiceReport(report);

let iouType: IOUType = CONST.IOU.TYPE.SUBMIT;

if (isTrackExpenseThread) {
Expand All @@ -87,15 +90,15 @@ function useSelectedTransactionsActions({
return;
}

deleteMoneyRequest(transactionID, action, undefined, deletedTransactionIDs);
deleteMoneyRequest(transactionID, action, duplicateTransactions, duplicateTransactionViolations, false, deletedTransactionIDs);
deletedTransactionIDs.push(transactionID);
});
clearSelectedTransactions(true);
if (allTransactionsLength - transactionsWithActions.length <= 1) {
turnOffMobileSelectionMode();
}
setIsDeleteModalVisible(false);
}, [allTransactionsLength, reportActions, selectedTransactionIDs, clearSelectedTransactions]);
}, [duplicateTransactions, duplicateTransactionViolations, allTransactionsLength, reportActions, selectedTransactionIDs, clearSelectedTransactions]);

const showDeleteModal = useCallback(() => {
setIsDeleteModalVisible(true);
Expand Down
Loading
Loading