diff --git a/Mobile-Expensify b/Mobile-Expensify index 70de73073e5d..e1f7b4f3e00c 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 70de73073e5d23f7d27b4bb15fb499ae32ae1ed4 +Subproject commit e1f7b4f3e00c6d0158e6ae8f86d0c98782f2e3ff diff --git a/android/app/build.gradle b/android/app/build.gradle index 29b64a9b0c7f..e9e1bba6ab95 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -114,8 +114,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009027807 - versionName "9.2.78-7" + versionCode 1009027808 + versionName "9.2.78-8" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index d97e3ac76a12..1e5abed0466e 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -44,7 +44,7 @@ CFBundleVersion - 9.2.78.7 + 9.2.78.8 FullStory OrgId diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 341b583e50c6..f12b846204b4 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.78 CFBundleVersion - 9.2.78.7 + 9.2.78.8 NSExtension NSExtensionPointIdentifier diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist index 35441022a6f9..53c3134814c6 100644 --- a/ios/ShareViewController/Info.plist +++ b/ios/ShareViewController/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.78 CFBundleVersion - 9.2.78.7 + 9.2.78.8 NSExtension NSExtensionAttributes diff --git a/package-lock.json b/package-lock.json index 78be1ab41cee..f0753176398b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.2.78-7", + "version": "9.2.78-8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.2.78-7", + "version": "9.2.78-8", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fd0f5dcaffb5..c5e42f3ed2e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.2.78-7", + "version": "9.2.78-8", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 71aa7ca1b498..b2a24922efe1 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -106,6 +106,7 @@ function mapTransactionItemToSelectedEntry( item.keyForList, { isSelected: true, + canDelete: item.canDelete, canReject: canRejectRequest, canHold: canHoldRequest, isHeld: isOnHold(item), @@ -198,6 +199,7 @@ function prepareTransactionsList( ...selectedTransactions, [item.keyForList]: { isSelected: true, + canDelete: item.canDelete, canReject: canRejectRequest, canHold: canHoldRequest, isHeld: isOnHold(item), @@ -541,6 +543,7 @@ function Search({ ), // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing isSelected: areAllMatchingItemsSelected || selectedTransactions[transactionItem.transactionID]?.isSelected || isExpenseReportType, + canDelete: transactionItem.canDelete, canReject: canRejectRequest, reportID: transactionItem.reportID, policyID: transactionItem.report?.policyID, @@ -592,6 +595,7 @@ function Search({ ), // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing isSelected: areAllMatchingItemsSelected || selectedTransactions[transactionItem.transactionID].isSelected, + canDelete: transactionItem.canDelete, canReject: canRejectRequest, reportID: transactionItem.reportID, policyID: transactionItem.report?.policyID, diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 8f5beab5e9c6..77beb3a9a150 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -11,6 +11,9 @@ type SelectedTransactionInfo = { /** Whether the transaction is selected */ isSelected: boolean; + /** If the transaction can be deleted */ + canDelete: boolean; + /** If the transaction can be rejected */ canReject: boolean; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index caf32aa867c2..e00127fdb1ef 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2658,6 +2658,7 @@ function getTransactionFromTransactionListItem(item: TransactionListItemType): O isTaxAmountColumnWide, violations, hash, + canDelete, accountID, policyID, ...transaction diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 19709ee3f08d..8a60573f64ac 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -59,10 +59,8 @@ import {navigateToParticipantPage} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SearchFullscreenNavigatorParamList} from '@libs/Navigation/types'; -import {getActiveAdminWorkspaces, hasDynamicExternalWorkflow, hasOnlyPersonalPolicies as hasOnlyPersonalPoliciesUtil, hasVBBA, isPaidGroupPolicy} from '@libs/PolicyUtils'; -import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {getActiveAdminWorkspaces, hasDynamicExternalWorkflow, hasOnlyPersonalPolicies as hasOnlyPersonalPoliciesUtil, isPaidGroupPolicy} from '@libs/PolicyUtils'; import { - canDeleteMoneyRequestReport, generateReportID, getPolicyExpenseChat, getReportOrDraftReport, @@ -671,24 +669,7 @@ function SearchPage({route}: SearchPageProps) { }); } - const shouldShowDeleteOption = - !isOffline && - selectedTransactionsKeys.every((id) => { - const transaction = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]; - if (!transaction) { - return false; - } - const parentReportID = transaction.reportID; - const parentReport = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`]; - if (!parentReport) { - return false; - } - const reportActions = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`]; - const parentReportAction = Object.values(reportActions ?? {}).find( - (action) => (isMoneyRequestAction(action) ? getOriginalMessage(action)?.IOUTransactionID : undefined) === transaction.transactionID, - ); - return canDeleteMoneyRequestReport(parentReport, [transaction], parentReportAction ? [parentReportAction] : []); - }); + const shouldShowDeleteOption = !isOffline && selectedTransactionsKeys.every((id) => selectedTransactions[id].canDelete); if (shouldShowDeleteOption) { options.push({ @@ -735,16 +716,15 @@ function SearchPage({route}: SearchPageProps) { status, hash, selectedTransactions, - expensifyIcons, translate, areAllMatchingItemsSelected, isOffline, selectedReports, - queryJSON, + selectedTransactionReportIDs, lastPaymentMethods, selectedReportIDs, allTransactions, - selectedTransactionReportIDs, + queryJSON, selectedPolicyIDs, policies, integrationsExportTemplates, @@ -754,14 +734,14 @@ function SearchPage({route}: SearchPageProps) { dismissedRejectUseExplanation, bulkPayButtonOptions, onBulkPaySelected, - areAllTransactionsFromSubmitter, - dismissedHoldUseExplanation, allReports, - currentSearchResults?.data, theme.icon, styles.colorMuted, styles.fontWeightNormal, styles.textWrap, + expensifyIcons, + dismissedHoldUseExplanation, + areAllTransactionsFromSubmitter, ]); const handleDeleteExpenses = () => { diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 8b90d653cbbb..08b66a5086af 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -86,6 +86,9 @@ type SearchTransaction = { /** The transaction amount */ amount: number; + /** If the transaction can be deleted */ + canDelete: boolean; + /** The edited transaction amount */ modifiedAmount: number; diff --git a/tests/unit/MoneyRequestReportUtilsTest.ts b/tests/unit/MoneyRequestReportUtilsTest.ts index 9cae1e021d32..7bfdb2105183 100644 --- a/tests/unit/MoneyRequestReportUtilsTest.ts +++ b/tests/unit/MoneyRequestReportUtilsTest.ts @@ -55,6 +55,7 @@ const transactionItemBaseMock: TransactionListItemType = { policy: policyBaseMock, reportAction: reportActionBaseMock, holdReportAction: undefined, + canDelete: true, cardID: undefined, cardName: undefined, category: '', diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index fc6bb19fe607..1354ba19f2bb 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -386,6 +386,7 @@ const searchResults: OnyxTypes.SearchResults = { [`report_${reportID5}`]: report5, [`transactions_${transactionID}`]: { amount: -5000, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -415,6 +416,7 @@ const searchResults: OnyxTypes.SearchResults = { }, [`transactions_${transactionID2}`]: { amount: -5000, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -445,6 +447,7 @@ const searchResults: OnyxTypes.SearchResults = { ...allViolations, [`transactions_${transactionID3}`]: { amount: 1200, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -474,6 +477,7 @@ const searchResults: OnyxTypes.SearchResults = { }, [`transactions_${transactionID4}`]: { amount: 3200, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -759,6 +763,7 @@ const transactionsListItems = [ policy, reportAction: reportAction1, holdReportAction: undefined, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -810,6 +815,7 @@ const transactionsListItems = [ policy, reportAction: reportAction2, holdReportAction: undefined, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -871,6 +877,7 @@ const transactionsListItems = [ policy, reportAction: reportAction3, holdReportAction: undefined, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -927,6 +934,7 @@ const transactionsListItems = [ policy, reportAction: reportAction4, holdReportAction: undefined, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -1020,6 +1028,7 @@ const transactionReportGroupListItems = [ reportAction: reportAction1, holdReportAction: undefined, amount: -5000, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -1114,6 +1123,7 @@ const transactionReportGroupListItems = [ reportAction: reportAction2, holdReportAction: undefined, amount: -5000, + canDelete: true, cardID: undefined, cardName: undefined, category: '', @@ -1215,7 +1225,122 @@ const transactionReportGroupListItems = [ displayName: 'Approver', login: 'approver@policy.com', }, - transactions: [transactionsListItems.at(2), transactionsListItems.at(3)], + transactions: [ + { + amount: 1200, + action: 'view', + allActions: ['view'], + report: report3, + policy, + reportAction: reportAction3, + holdReportAction: undefined, + canDelete: true, + cardID: undefined, + cardName: undefined, + category: '', + comment: {comment: ''}, + created: '2025-03-05', + currency: 'VND', + hasEReceipt: false, + merchant: '(none)', + modifiedAmount: 0, + modifiedCreated: '', + modifiedCurrency: '', + modifiedMerchant: '', + parentTransactionID: '', + reportID: '99999', + tag: '', + transactionID: '3', + from: { + accountID: 18439984, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + displayName: 'Admin', + login: 'admin@policy.com', + }, + to: { + accountID: 1111111, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + displayName: 'Approver', + login: 'approver@policy.com', + }, + formattedFrom: 'Admin', + formattedTo: 'Approver', + formattedTotal: 1200, + formattedMerchant: '', + date: '2025-03-05', + shouldShowMerchant: true, + shouldShowYear: true, + keyForList: '3', + isAmountColumnWide: false, + isTaxAmountColumnWide: false, + receipt: undefined, + taxAmount: undefined, + mccGroup: undefined, + modifiedMCCGroup: undefined, + pendingAction: undefined, + errors: undefined, + violations: [], + groupAmount: -5000, + groupCurrency: 'USD', + }, + { + amount: 3200, + action: 'view', + allActions: ['view'], + report: report3, + policy, + reportAction: reportAction4, + holdReportAction: undefined, + canDelete: true, + cardID: undefined, + cardName: undefined, + category: '', + comment: {comment: ''}, + created: '2025-03-05', + currency: 'VND', + hasEReceipt: false, + merchant: '(none)', + modifiedAmount: 0, + modifiedCreated: '', + modifiedCurrency: '', + modifiedMerchant: '', + parentTransactionID: '', + reportID: '99999', + tag: '', + transactionID: '4', + from: { + accountID: 18439984, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + displayName: 'Admin', + login: 'admin@policy.com', + }, + to: { + accountID: 1111111, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + displayName: 'Approver', + login: 'approver@policy.com', + }, + formattedFrom: 'Admin', + formattedTo: 'Approver', + formattedTotal: 3200, + formattedMerchant: '', + date: '2025-03-05', + shouldShowMerchant: true, + shouldShowYear: true, + keyForList: '4', + isAmountColumnWide: false, + isTaxAmountColumnWide: false, + receipt: undefined, + taxAmount: undefined, + mccGroup: undefined, + modifiedMCCGroup: undefined, + pendingAction: undefined, + errors: undefined, + violations: [], + groupAmount: -5000, + groupCurrency: 'USD', + }, + ], }, { groupedBy: 'expense-report', @@ -1696,7 +1821,7 @@ describe('SearchUIUtils', () => { expect(distanceTransaction).toBeDefined(); expect(distanceTransaction?.iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.DISTANCE); - const expectedPropertyCount = 45; + const expectedPropertyCount = 46; expect(Object.keys(distanceTransaction ?? {}).length).toBe(expectedPropertyCount); }); @@ -1729,7 +1854,7 @@ describe('SearchUIUtils', () => { expect(distanceTransaction).toBeDefined(); expect(distanceTransaction?.iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.DISTANCE); - const expectedPropertyCount = 45; + const expectedPropertyCount = 46; expect(Object.keys(distanceTransaction ?? {}).length).toBe(expectedPropertyCount); }); @@ -2321,6 +2446,7 @@ describe('SearchUIUtils', () => { // eslint-disable-next-line @typescript-eslint/naming-convention transactions_1805965960759424086: { amount: 0, + canDelete: false, category: 'Employee Meals Remote (Fringe Benefit)', comment: { comment: '', @@ -2443,6 +2569,7 @@ describe('SearchUIUtils', () => { // eslint-disable-next-line @typescript-eslint/naming-convention transactions_1805965960759424086: { amount: 0, + canDelete: false, cardID: undefined, cardName: undefined, category: 'Employee Meals Remote (Fringe Benefit)', diff --git a/tests/unit/Search/handleActionButtonPressTest.ts b/tests/unit/Search/handleActionButtonPressTest.ts index 5bb137e4228e..4deb193a807c 100644 --- a/tests/unit/Search/handleActionButtonPressTest.ts +++ b/tests/unit/Search/handleActionButtonPressTest.ts @@ -92,6 +92,7 @@ const mockReportItemWithHold = { action: 'view', allActions: ['view'], amount: -1200, + canDelete: true, category: '', comment: { comment: '', @@ -180,6 +181,7 @@ const mockReportItemWithHold = { action: 'view', allActions: ['view'], amount: -12300, + canDelete: true, category: '', comment: { comment: '', diff --git a/tests/unit/TransactionGroupListItemTest.tsx b/tests/unit/TransactionGroupListItemTest.tsx index 05cc0ab57376..6e360fdafeb7 100644 --- a/tests/unit/TransactionGroupListItemTest.tsx +++ b/tests/unit/TransactionGroupListItemTest.tsx @@ -26,6 +26,7 @@ jest.mock('@libs/SearchUIUtils', () => ({ const mockTransaction: TransactionListItemType = { accountID: 1, amount: 0, + canDelete: true, category: '', groupAmount: 1284, groupCurrency: 'USD',