@@ -8,7 +8,7 @@ import {useSearchQueryContext, useSearchResultsContext, useSearchSelectionAction
88import type { BulkPaySelectionData , PaymentData , SearchColumnType , SearchFilterKey , SearchQueryJSON , SelectedReports , SelectedTransactions } from '@components/Search/types' ;
99
1010import { getExpensifyCardStatementPDF } from '@libs/actions/CompanyCards' ;
11- import { exportReportsToPDF } from '@libs/actions/Export' ;
11+ import { exportReceiptsToZip , exportReportsToPDF } from '@libs/actions/Export' ;
1212import { unholdRequest } from '@libs/actions/IOU/Hold' ;
1313import { payInvoice , payMoneyRequest } from '@libs/actions/IOU/PayMoneyRequest' ;
1414import { approveMoneyRequest } from '@libs/actions/IOU/ReportWorkflow' ;
@@ -2249,6 +2249,45 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
22492249 } ) ;
22502250 }
22512251
2252+ const hasSelectedReportsWithExpenses = selectedReports . some ( ( report ) => ( currentSearchResults ?. data ?. [ `${ ONYXKEYS . COLLECTION . REPORT } ${ report . reportID } ` ] ?. transactionCount ?? 0 ) > 0 ) ;
2253+ if ( isExpenseReportSearch && selectedReportIDs . length > 0 && hasSelectedReportsWithExpenses ) {
2254+ options . push ( {
2255+ icon : expensifyIcons . Download ,
2256+ text : translate ( 'common.downloadReceipts' ) ,
2257+ value : CONST . SEARCH . BULK_ACTION_TYPES . DOWNLOAD_RECEIPTS ,
2258+ shouldCloseModalOnSelect : true ,
2259+ onSelected : ( ) => {
2260+ if ( isOffline ) {
2261+ setIsOfflineModalVisible ( true ) ;
2262+ return ;
2263+ }
2264+ const exportID = exportReceiptsToZip ( { reportIDs : selectedReportIDs } ) ;
2265+ trackExport ( exportID ) ;
2266+ } ,
2267+ } ) ;
2268+ }
2269+
2270+ const isExpenseSearch = queryJSON ?. type === CONST . SEARCH . DATA_TYPES . EXPENSE || searchResults ?. search . type === CONST . SEARCH . DATA_TYPES . EXPENSE ;
2271+ // A group selected before its children load is stored under a group_ key, which is not a real
2272+ // transaction ID. Drop those keys so ExportReceiptsToZip only receives valid transaction IDs.
2273+ const transactionIDs = selectedTransactionsKeys . filter ( ( key ) => ! key . startsWith ( CONST . SEARCH . GROUP_PREFIX ) ) ;
2274+ if ( isExpenseSearch && selectedTransactionsKeys . length > 0 && transactionIDs . length > 0 ) {
2275+ options . push ( {
2276+ icon : expensifyIcons . Download ,
2277+ text : translate ( 'common.downloadReceipts' ) ,
2278+ value : CONST . SEARCH . BULK_ACTION_TYPES . DOWNLOAD_RECEIPTS ,
2279+ shouldCloseModalOnSelect : true ,
2280+ onSelected : ( ) => {
2281+ if ( isOffline ) {
2282+ setIsOfflineModalVisible ( true ) ;
2283+ return ;
2284+ }
2285+ const exportID = exportReceiptsToZip ( { transactionIDs} ) ;
2286+ trackExport ( exportID ) ;
2287+ } ,
2288+ } ) ;
2289+ }
2290+
22522291 // A standalone bulk action (not nested under Export). "Select all matching" only loads the visible
22532292 // rows, so we would export an incomplete set of settlements while the UI claims everything is selected;
22542293 // the statement has no whole-query export path (unlike CSV/templates), so require an explicit selection.
0 commit comments