Skip to content
Merged
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/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
const defaultExpensePolicy = useDefaultExpensePolicy();
const activePolicyExpenseChat = getPolicyExpenseChat(accountID, defaultExpensePolicy?.id);
const isOnHold = isOnHoldTransactionUtils(transaction);
const isDuplicate = isDuplicateTransactionUtils(transaction, email ?? '', accountID, report, policy);
const isDuplicate = isDuplicateTransactionUtils(transaction, email ?? '', accountID, report, policy, transactionViolations);
const reportID = report?.reportID;
const {removeTransaction, currentSearchHash} = useSearchContext();
const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction, originalTransaction);
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useSelectedTransactionsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function useSelectedTransactionsActions({
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH, {canBeMissing: true});
const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES, {canBeMissing: true});
const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS, {canBeMissing: true});
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});

const expensifyIcons = useMemoizedLazyExpensifyIcons(['Stopwatch', 'Trashcan', 'ArrowRight', 'Table', 'DocumentMerge', 'Export', 'ArrowCollapse', 'ArrowSplit', 'ThumbsDown']);
const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(selectedTransactionIDs);
Expand Down Expand Up @@ -222,7 +223,7 @@ function useSelectedTransactionsActions({
});
}

const hasNoRejectedTransaction = selectedTransactionIDs.every((id) => !hasTransactionBeenRejected(id));
const hasNoRejectedTransaction = selectedTransactionIDs.every((id) => !hasTransactionBeenRejected(allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? []));
const canRejectTransactions =
selectedTransactionsList.length > 0 && isMoneyRequestReport && !!session?.email && !!report && canRejectReportAction(session.email, report, policy) && hasNoRejectedTransaction;
if (canRejectTransactions) {
Expand Down Expand Up @@ -387,6 +388,7 @@ function useSelectedTransactionsActions({
allReports,
session?.accountID,
showDeleteModal,
allTransactionViolations,
expensifyIcons.Stopwatch,
expensifyIcons.ThumbsDown,
expensifyIcons.Table,
Expand Down
11 changes: 4 additions & 7 deletions src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
};

let deprecatedAllReports: OnyxCollection<Report> = {};
Onyx.connect({

Check warning on line 129 in src/libs/TransactionUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -135,7 +135,7 @@
});

let deprecatedAllTransactionViolations: OnyxCollection<TransactionViolations> = {};
Onyx.connect({

Check warning on line 138 in src/libs/TransactionUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
waitForCollectionCallback: true,
callback: (value) => (deprecatedAllTransactionViolations = value),
Expand Down Expand Up @@ -1292,9 +1292,8 @@
/**
* Check if a transaction has been rejected
*/
function hasTransactionBeenRejected(transactionID: string): boolean {
const transactionViolations = getTransactionViolationsOfTransaction(transactionID);
return transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.AUTO_REPORTED_REJECTED_EXPENSE);
function hasTransactionBeenRejected(transactionViolations: OnyxEntry<TransactionViolations>): boolean {
return !!transactionViolations && transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.AUTO_REPORTED_REJECTED_EXPENSE);
}

/**
Expand Down Expand Up @@ -1601,14 +1600,12 @@
currentUserAccountID: number,
iouReport: OnyxEntry<Report>,
policy: OnyxEntry<Policy>,
transactionViolation?: OnyxEntry<TransactionViolations>,
transactionViolation: OnyxEntry<TransactionViolations>,
): boolean {
if (!transaction) {
return false;
}
const duplicatedTransactionViolation = (transactionViolation ?? deprecatedAllTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`])?.find(
(violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
);
const duplicatedTransactionViolation = transactionViolation?.find((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION);
const hasDuplicatedTransactionViolation = !!duplicatedTransactionViolation;
const isDuplicatedTransactionViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation, currentUserEmail, currentUserAccountID, iouReport, policy);

Expand Down
15 changes: 9 additions & 6 deletions src/libs/actions/MergeTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import {getIOUActionForReportID} from '@libs/ReportActionsUtils';
import {getReportOrDraftReport, getReportTransactions, getTransactionDetails, isCurrentUserSubmitter, isMoneyRequestReportEligibleForMerge, isReportManager} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import {getTransactionViolationsOfTransaction, isDistanceRequest, isTransactionPendingDelete} from '@src/libs/TransactionUtils';
import {isDistanceRequest, isTransactionPendingDelete} from '@src/libs/TransactionUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {CardList, MergeTransaction, Policy, PolicyCategories, PolicyTagLists, Report, Transaction} from '@src/types/onyx';
import type {CardList, MergeTransaction, Policy, PolicyCategories, PolicyTagLists, Report, Transaction, TransactionViolations} from '@src/types/onyx';
import {getUpdateMoneyRequestParams, getUpdateTrackExpenseParams} from './IOU';
import type {UpdateMoneyRequestData} from './IOU';

Expand Down Expand Up @@ -183,6 +183,7 @@ function getTransactionsForMerging({

function getOnyxTargetTransactionData(
targetTransaction: Transaction,
targetTransactionViolations: OnyxEntry<TransactionViolations>,
mergeTransaction: MergeTransaction,
policy: OnyxEntry<Policy>,
policyTags: OnyxEntry<PolicyTagLists>,
Expand All @@ -194,7 +195,6 @@ function getOnyxTargetTransactionData(
let data: UpdateMoneyRequestData;
const isUnreportedExpense = !mergeTransaction.reportID || mergeTransaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
const transactionThreadReportID = getTransactionThreadReportID(targetTransaction);
const violations = getTransactionViolationsOfTransaction(targetTransaction.transactionID);

// Compare mergeTransaction with targetTransaction and remove fields with same values
const targetTransactionDetails = getTransactionDetails(targetTransaction);
Expand Down Expand Up @@ -222,7 +222,7 @@ function getOnyxTargetTransactionData(
policy,
policyTagList: policyTags,
policyCategories,
violations,
violations: targetTransactionViolations ?? [],
shouldBuildOptimisticModifiedExpenseReportAction,
currentUserAccountIDParam,
currentUserEmailParam,
Expand Down Expand Up @@ -264,6 +264,7 @@ type MergeTransactionRequestParams = {
mergeTransactionID: string;
mergeTransaction: MergeTransaction;
targetTransaction: Transaction;
allTransactionViolations: OnyxCollection<TransactionViolations>;
sourceTransaction: Transaction;
policy: OnyxEntry<Policy>;
policyTags: OnyxEntry<PolicyTagLists>;
Expand All @@ -280,6 +281,7 @@ function mergeTransactionRequest({
mergeTransaction,
targetTransaction,
sourceTransaction,
allTransactionViolations,
policy,
policyTags,
policyCategories,
Expand Down Expand Up @@ -317,6 +319,7 @@ function mergeTransactionRequest({

const onyxTargetTransactionData = getOnyxTargetTransactionData(
targetTransaction,
allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + targetTransaction.transactionID] ?? [],
mergeTransaction,
policy,
policyTags,
Expand Down Expand Up @@ -411,7 +414,7 @@ function mergeTransactionRequest({

// Optimistic delete duplicated transaction violations
const optimisticTransactionViolations: OnyxUpdate[] = [targetTransaction.transactionID, sourceTransaction.transactionID].map((id) => {
const violations = getTransactionViolationsOfTransaction(id);
const violations = allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? [];

return {
onyxMethod: Onyx.METHOD.MERGE,
Expand All @@ -421,7 +424,7 @@ function mergeTransactionRequest({
};
});
const failureTransactionViolations: OnyxUpdate[] = [targetTransaction.transactionID, sourceTransaction.transactionID].map((id) => {
const violations = getTransactionViolationsOfTransaction(id);
const violations = allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? [];

return {
onyxMethod: Onyx.METHOD.MERGE,
Expand Down
6 changes: 5 additions & 1 deletion src/pages/Search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ function SearchPage({route}: SearchPageProps) {
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES, {canBeMissing: true});
const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS, {canBeMissing: true});
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});

const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
Expand Down Expand Up @@ -557,7 +558,9 @@ function SearchPage({route}: SearchPageProps) {
}

// Check if all selected transactions can be rejected
const hasNoRejectedTransaction = selectedTransactionsKeys.every((id) => !hasTransactionBeenRejected(id));
const hasNoRejectedTransaction = selectedTransactionsKeys.every(
(id) => !hasTransactionBeenRejected(allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? []),
);

const shouldShowRejectOption =
queryJSON?.type !== CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT &&
Expand Down Expand Up @@ -866,6 +869,7 @@ function SearchPage({route}: SearchPageProps) {
dismissedHoldUseExplanation,
dismissedRejectUseExplanation,
areAllTransactionsFromSubmitter,
allTransactionViolations,
currentSearchResults?.data,
isDelegateAccessRestricted,
showDelegateNoAccessModal,
Expand Down
4 changes: 4 additions & 0 deletions src/pages/TransactionMerge/ConfirmationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
const [mergeTransaction, mergeTransactionMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true});
const {targetTransaction, sourceTransaction, targetTransactionReport} = useMergeTransactions({mergeTransaction});
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {
canBeMissing: false,
});

const policyID = targetTransactionReport?.policyID;
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true});
Expand All @@ -67,6 +70,7 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
mergeTransactionID: transactionID,
mergeTransaction,
targetTransaction,
allTransactionViolations,
sourceTransaction,
policy,
policyTags,
Expand Down
76 changes: 75 additions & 1 deletion tests/actions/MergeTransactionTest.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Onyx from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
import {areTransactionsEligibleForMerge, mergeTransactionRequest, setMergeTransactionKey, setupMergeTransactionData} from '@libs/actions/MergeTransaction';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {MergeTransaction as MergeTransactionType, Report, Transaction, TransactionViolation} from '@src/types/onyx';
import type {MergeTransaction as MergeTransactionType, Report, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx';
import createRandomMergeTransaction from '../utils/collections/mergeTransaction';
import {createExpenseReport} from '../utils/collections/reports';
import createRandomTransaction, {createRandomDistanceRequestTransaction} from '../utils/collections/transaction';
Expand All @@ -26,6 +27,23 @@ function createMockViolations(): TransactionViolation[] {
];
}

// Helper function to create allTransactionViolations collection
function createAllTransactionViolations(
targetTransactionID: string,
sourceTransactionID: string,
targetViolations?: TransactionViolation[],
sourceViolations?: TransactionViolation[],
): OnyxCollection<TransactionViolations> {
const allViolations: OnyxCollection<TransactionViolations> = {};
if (targetViolations) {
allViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${targetTransactionID}`] = targetViolations;
}
if (sourceViolations) {
allViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${sourceTransactionID}`] = sourceViolations;
}
return allViolations;
}

describe('mergeTransactionRequest', () => {
let mockFetch: MockFetch;

Expand Down Expand Up @@ -76,6 +94,32 @@ describe('mergeTransactionRequest', () => {
};
const mergeTransactionID = 'merge789';

// Sample violations for testing
const targetViolations: TransactionViolation[] = [
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
showInReview: true,
},
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.MISSING_TAG,
showInReview: true,
},
];
const sourceViolations: TransactionViolation[] = [
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
showInReview: true,
},
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.OVER_LIMIT,
showInReview: true,
},
];

// Set up initial state in Onyx
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${targetTransaction.transactionID}`, targetTransaction);
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${sourceTransaction.transactionID}`, sourceTransaction);
Expand All @@ -91,6 +135,7 @@ describe('mergeTransactionRequest', () => {
mergeTransaction,
targetTransaction,
sourceTransaction,
allTransactionViolations: createAllTransactionViolations(targetTransaction.transactionID, sourceTransaction.transactionID, targetViolations, sourceViolations),
policy: undefined,
policyTags: undefined,
policyCategories: undefined,
Expand Down Expand Up @@ -213,6 +258,7 @@ describe('mergeTransactionRequest', () => {
mergeTransaction,
targetTransaction,
sourceTransaction,
allTransactionViolations: createAllTransactionViolations(targetTransaction.transactionID, sourceTransaction.transactionID, mockViolations, mockViolations),
policy: undefined,
policyTags: undefined,
policyCategories: undefined,
Expand Down Expand Up @@ -310,6 +356,7 @@ describe('mergeTransactionRequest', () => {
mergeTransaction,
targetTransaction,
sourceTransaction,
allTransactionViolations: createAllTransactionViolations(targetTransaction.transactionID, sourceTransaction.transactionID, mockViolations, mockViolations),
policy: undefined,
policyTags: undefined,
policyCategories: undefined,
Expand Down Expand Up @@ -374,6 +421,32 @@ describe('mergeTransactionRequest', () => {
};
const mergeTransactionID = 'merge789';

// Sample violations for testing
const targetViolations: TransactionViolation[] = [
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
showInReview: true,
},
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.MISSING_COMMENT,
showInReview: true,
},
];
const sourceViolations: TransactionViolation[] = [
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
showInReview: true,
},
{
type: CONST.VIOLATION_TYPES.VIOLATION,
name: CONST.VIOLATIONS.RECEIPT_REQUIRED,
showInReview: true,
},
];

// Set up initial state
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${targetTransaction.transactionID}`, targetTransaction);
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${sourceTransaction.transactionID}`, sourceTransaction);
Expand All @@ -389,6 +462,7 @@ describe('mergeTransactionRequest', () => {
mergeTransaction,
targetTransaction,
sourceTransaction,
allTransactionViolations: createAllTransactionViolations(targetTransaction.transactionID, sourceTransaction.transactionID, targetViolations, sourceViolations),
policy: undefined,
policyTags: undefined,
policyCategories: undefined,
Expand Down
10 changes: 4 additions & 6 deletions tests/unit/ReportSecondaryActionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,11 +420,9 @@ describe('getSecondaryAction', () => {

await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);

await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${TRANSACTION_ID}`, [
{
name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
} as TransactionViolation,
]);
const violation = {
name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
} as TransactionViolation;

await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);

Expand All @@ -435,7 +433,7 @@ describe('getSecondaryAction', () => {
chatReport,
reportTransactions: [transaction],
originalTransaction: {} as Transaction,
violations: {},
violations: {[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${TRANSACTION_ID}`]: [violation]},
policy,
});
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.APPROVE)).toBe(true);
Expand Down
Loading