diff --git a/src/languages/en.ts b/src/languages/en.ts index 75bfadee2ca7..1c15a62c2f73 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -9843,6 +9843,7 @@ const translations = { keepThisOne: 'Keep this one', confirmDetails: `Confirm the details you're keeping`, confirmDuplicatesInfo: `The duplicates you don't keep will be held for the submitter to delete.`, + cannotMergeDuplicates: 'You can only merge expenses on reports that are open or pending first approval. You may need to retract the report, merge the expenses, then resubmit.', hold: 'This expense was put on hold', resolvedDuplicates: 'resolved the duplicate', companyCardRequired: 'Company card purchases required', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5e173ef30b78..e2dfef35175c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -9885,6 +9885,7 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`, keepThisOne: 'Mantener éste', confirmDetails: 'Confirma los detalles que conservas', confirmDuplicatesInfo: 'Los duplicados que no conserves se mantendrán para que el remitente los elimine.', + cannotMergeDuplicates: 'Solo puedes combinar gastos en informes que estén abiertos o pendientes de la primera aprobación. Es posible que debas retirar el informe, combinar los gastos y luego volver a enviarlo.', hold: 'Este gasto está retenido', resolvedDuplicates: 'resolvió el duplicado', companyCardRequired: 'Se requieren compras con la tarjeta de la empresa.', diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 879516700609..841cf56c778e 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -32,8 +32,8 @@ import { isCurrentUserSubmitter, isInvoiceReport, isOpenExpenseReport, + isOpenReport, isProcessingReport, - isReportIDApproved, isSelfDM, isSettled, isThread, @@ -2589,8 +2589,19 @@ function removeTransactionFromDuplicateTransactionViolation( } } +/** + * Keeps only transactions that Auth's MergeTransactions command would accept, i.e. those whose report is still + * open or awaiting first-level approval. Anything approved, closed (Submit & Close), or reimbursed is rejected + * server-side, so filtering here prevents sending a merge request that would fail. + */ function removeSettledAndApprovedTransactions(transactions: Array>): Transaction[] { - return transactions.filter((transaction) => !!transaction && !isSettled(transaction?.reportID) && !isReportIDApproved(transaction?.reportID)) as Transaction[]; + return transactions.filter((transaction) => { + if (!transaction) { + return false; + } + const report = getReportOrDraftReport(transaction.reportID); + return isOpenReport(report) || isProcessingReport(report); + }) as Transaction[]; } /** diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 1703fb5b7eae..4643ea16b6a2 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -1,6 +1,7 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import Button from '@components/ButtonComposed'; import FixedFooter from '@components/FixedFooter'; +import FormHelpMessage from '@components/FormHelpMessage'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MoneyRequestView from '@components/ReportActionItem/MoneyRequestView'; @@ -44,7 +45,7 @@ import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import type {OnyxEntry} from 'react-native-onyx'; import {useRoute} from '@react-navigation/native'; -import React, {useCallback, useMemo, useRef} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; function Confirmation() { @@ -113,6 +114,7 @@ function Confirmation() { }; }, [reviewDuplicatesTaxCode, reviewDuplicatesTaxAmount, taxRates, duplicatedTransactionTaxCode]); const isReportOwner = iouReport?.ownerAccountID === currentUserPersonalDetails?.accountID; + const [mergeErrorMessage, setMergeErrorMessage] = useState(''); const currentUserAccountID = currentUserPersonalDetails.accountID; const currentUserLogin = currentUserPersonalDetails?.login; const childReportID = reportAction?.childReportID; @@ -221,14 +223,30 @@ function Confirmation() { + {!!mergeErrorMessage && ( + + )}