From 272c3675687730d34a53b1db2603bdec222d9729 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 24 Jul 2026 17:54:58 +0700 Subject: [PATCH 1/8] feat: add download receipts option to report --- src/CONST/index.ts | 3 +++ src/components/ExportDownloadStatusModal.tsx | 11 +++++++- src/hooks/useExportActions.ts | 18 +++++++++++++ src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ .../parameters/ExportReceiptsToZipParams.ts | 6 +++++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 ++ src/libs/PopoverMenuSections.ts | 2 +- src/libs/ReportSecondaryActionUtils.ts | 2 ++ src/libs/actions/Export.ts | 27 ++++++++++++++++++- 11 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/libs/API/parameters/ExportReceiptsToZipParams.ts diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 2dedfa19fe1c..f56be9753cf7 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -497,6 +497,7 @@ const CONST = { TYPE: { CSV: 'csv', PDF: 'pdf', + RECEIPTS: 'receipts', }, }, @@ -1466,6 +1467,7 @@ const CONST = { CANCEL_PAYMENT: 'cancelPayment', HOLD: 'hold', DOWNLOAD_PDF: 'downloadPDF', + DOWNLOAD_RECEIPTS: 'downloadReceipts', PRINT: 'print', CHANGE_WORKSPACE: 'changeWorkspace', CHANGE_APPROVER: 'changeApprover', @@ -8516,6 +8518,7 @@ const CONST = { EXPORT: 'MoreMenu-Export', EXPORT_FILE: 'MoreMenu-ExportFile', DOWNLOAD_PDF: 'MoreMenu-DownloadPDF', + DOWNLOAD_RECEIPTS: 'MoreMenu-DownloadReceipts', PRINT: 'MoreMenu-Print', CLOSE_PDF_MODAL: 'MoreMenu-ClosePDFModal', SUBMIT: 'MoreMenu-Submit', diff --git a/src/components/ExportDownloadStatusModal.tsx b/src/components/ExportDownloadStatusModal.tsx index e5f636413ec2..754859894709 100644 --- a/src/components/ExportDownloadStatusModal.tsx +++ b/src/components/ExportDownloadStatusModal.tsx @@ -176,7 +176,16 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E } if (isFailed) { - const resolvedFailedBody = failedBody ?? (exportType === CONST.EXPORT_DOWNLOAD.TYPE.CSV ? translate('exportDownload.csvFailedBody') : translate('exportDownload.pdfFailedBody')); + const getDefaultFailedBody = () => { + if (exportType === CONST.EXPORT_DOWNLOAD.TYPE.CSV) { + return translate('exportDownload.csvFailedBody'); + } + if (exportType === CONST.EXPORT_DOWNLOAD.TYPE.RECEIPTS) { + return translate('exportDownload.receiptsFailedBody'); + } + return translate('exportDownload.pdfFailedBody'); + }; + const resolvedFailedBody = failedBody ?? getDefaultFailedBody(); return ( <> {translate('exportDownload.failedTitle')} diff --git a/src/hooks/useExportActions.ts b/src/hooks/useExportActions.ts index 19762a8c9266..503ba94bf501 100644 --- a/src/hooks/useExportActions.ts +++ b/src/hooks/useExportActions.ts @@ -2,6 +2,7 @@ import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import {useExportDownloadStatus} from '@components/MoneyReportHeaderActions/ExportDownloadStatusContext'; import type {PopoverMenuItem} from '@components/PopoverMenu'; +import {exportReceiptsToZip} from '@libs/actions/Export'; import {openOldDotLink} from '@libs/actions/Link'; import {exportReportToCSV, exportReportToPDF, exportToIntegration, markAsManuallyExported} from '@libs/actions/Report'; import {getExportTemplates, queueExportSearchWithTemplate} from '@libs/actions/Search'; @@ -264,6 +265,23 @@ function useExportActions({reportID, policy, onPDFModalOpen}: UseExportActionsPa exportReportToPDF({reportID: moneyRequestReport.reportID}); }, }, + [CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS]: { + value: CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS, + text: translate('common.downloadReceipts'), + icon: expensifyIcons.Download, + sentryLabel: CONST.SENTRY_LABEL.MORE_MENU.DOWNLOAD_RECEIPTS, + onSelected: () => { + if (isOffline) { + showOfflineModal(); + return; + } + if (!moneyRequestReport?.reportID) { + return; + } + const exportID = exportReceiptsToZip([moneyRequestReport.reportID]); + trackExport(exportID); + }, + }, [CONST.REPORT.SECONDARY_ACTIONS.PRINT]: { value: CONST.REPORT.SECONDARY_ACTIONS.PRINT, text: translate('common.print'), diff --git a/src/languages/en.ts b/src/languages/en.ts index c0968a1c508f..f2df5ec2c650 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -470,6 +470,7 @@ const translations = { perDiem: 'Per diem', validate: 'Validate', downloadAsPDF: 'Download as PDF', + downloadReceipts: 'Download receipts', downloadAsCSV: 'Download as CSV', submitViaPDF: 'Submit via PDF', print: 'Print', @@ -10400,6 +10401,7 @@ const translations = { failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: 'Your receipts could not be downloaded. Please try again later.', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/es.ts b/src/languages/es.ts index 293750179f19..b6fd5812f01b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -392,6 +392,7 @@ const translations: TranslationDeepObject = { perDiem: 'Per diem', validate: 'Validar', downloadAsPDF: 'Descargar como PDF', + downloadReceipts: 'Descargar recibos', downloadAsCSV: 'Descargar como CSV', submitViaPDF: 'Enviar por PDF', print: 'Imprimir', @@ -10427,6 +10428,7 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`, failedTitle: 'Exportación fallida', csvFailedBody: 'No se pudo completar la exportación. Inténtalo de nuevo más tarde.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: 'No se pudieron descargar los recibos. Inténtalo de nuevo más tarde.', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Cerrar', diff --git a/src/libs/API/parameters/ExportReceiptsToZipParams.ts b/src/libs/API/parameters/ExportReceiptsToZipParams.ts new file mode 100644 index 000000000000..748877f66164 --- /dev/null +++ b/src/libs/API/parameters/ExportReceiptsToZipParams.ts @@ -0,0 +1,6 @@ +type ExportReceiptsToZipParams = { + reportIDs: string; + exportID: string; +}; + +export default ExportReceiptsToZipParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index dda7b55b18e2..52bd2068341b 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -484,6 +484,7 @@ export type {default as ImportPerDiemRatesParams} from './ImportPerDiemRatesPara export type {default as ExportPerDiemCSVParams} from './ExportPerDiemCSVParams'; export type {default as ExportReportPDFParams} from './ExportReportPDFParams'; export type {default as ExportReportsToPDFParams} from './ExportReportsToPDFParams'; +export type {default as ExportReceiptsToZipParams} from './ExportReceiptsToZipParams'; export type {default as UpdateWorkspaceCustomUnitParams} from './UpdateWorkspaceCustomUnitParams'; export type {default as DismissProductTrainingParams} from './DismissProductTraining'; export type {default as AddWorkEmailParams} from './AddWorkEmailParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 6db64f2167ce..1a67fde9f678 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -184,6 +184,7 @@ const WRITE_COMMANDS = { MOVE_IOU_REPORT_TO_EXISTING_POLICY: 'MoveIOUReportToExistingPolicy', EXPORT_REPORT_TO_PDF: 'ExportReportToPDF', EXPORT_REPORTS_TO_PDF: 'ExportReportsToPDF', + EXPORT_RECEIPTS_TO_ZIP: 'ExportReceiptsToZip', RENAME_WORKSPACE_CATEGORY: 'RenameWorkspaceCategory', CREATE_POLICY_TAG: 'CreatePolicyTag', RENAME_POLICY_TAG: 'RenamePolicyTag', @@ -826,6 +827,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.EXPORT_PER_DIEM_CSV]: Parameters.ExportPerDiemCSVParams; [WRITE_COMMANDS.EXPORT_REPORT_TO_PDF]: Parameters.ExportReportPDFParams; [WRITE_COMMANDS.EXPORT_REPORTS_TO_PDF]: Parameters.ExportReportsToPDFParams; + [WRITE_COMMANDS.EXPORT_RECEIPTS_TO_ZIP]: Parameters.ExportReceiptsToZipParams; [WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY]: Parameters.RenameWorkspaceCategoriesParams; [WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams; [WRITE_COMMANDS.DELETE_WORKSPACE_CATEGORIES]: Parameters.DeleteWorkspaceCategoriesParams; diff --git a/src/libs/PopoverMenuSections.ts b/src/libs/PopoverMenuSections.ts index dd2b627b9d6c..475c0f0442e4 100644 --- a/src/libs/PopoverMenuSections.ts +++ b/src/libs/PopoverMenuSections.ts @@ -23,7 +23,7 @@ const REPORT_MORE_MENU_SECTIONS = [ ], [REPORT_SECONDARY_ACTIONS.SPLIT, REPORT_SECONDARY_ACTIONS.MERGE, REPORT_SECONDARY_ACTIONS.DUPLICATE_EXPENSE, REPORT_SECONDARY_ACTIONS.MOVE_EXPENSE], [REPORT_SECONDARY_ACTIONS.DUPLICATE_REPORT, REPORT_SECONDARY_ACTIONS.CHANGE_WORKSPACE, REPORT_SECONDARY_ACTIONS.CHANGE_APPROVER], - [REPORT_SECONDARY_ACTIONS.EXPORT, REPORT_SECONDARY_ACTIONS.DOWNLOAD_PDF, REPORT_SECONDARY_ACTIONS.PRINT], + [REPORT_SECONDARY_ACTIONS.EXPORT, REPORT_SECONDARY_ACTIONS.DOWNLOAD_PDF, REPORT_SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS, REPORT_SECONDARY_ACTIONS.PRINT], [REPORT_SECONDARY_ACTIONS.VIEW_DETAILS, REPORT_SECONDARY_ACTIONS.DELETE], ]; diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index 189c03208e12..f53ef22207ca 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -1071,6 +1071,8 @@ function getSecondaryReportActions({ options.push(CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_PDF); + options.push(CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS); + if (!isOpenReportUtils(report)) { options.push(CONST.REPORT.SECONDARY_ACTIONS.PRINT); } diff --git a/src/libs/actions/Export.ts b/src/libs/actions/Export.ts index f732c8b051c8..095cf818f97e 100644 --- a/src/libs/actions/Export.ts +++ b/src/libs/actions/Export.ts @@ -102,4 +102,29 @@ function exportReportsToPDF(reportIDs: string[]): string { return exportID; } -export {sendExportFileFromConcierge, clearExportDownload, clearStaleExportDownloads, exportReportsToPDF}; +function exportReceiptsToZip(reportIDs: string[]): string { + const exportID = rand64(); + const onyxKey = `${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${exportID}` as const; + + const optimisticData: AnyOnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.SET, + key: onyxKey, + value: {state: CONST.EXPORT_DOWNLOAD.STATE.PREPARING, exportType: CONST.EXPORT_DOWNLOAD.TYPE.RECEIPTS}, + }, + ]; + + const failureData: AnyOnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.SET, + key: onyxKey, + value: {state: CONST.EXPORT_DOWNLOAD.STATE.FAILED, exportType: CONST.EXPORT_DOWNLOAD.TYPE.RECEIPTS}, + }, + ]; + + write(WRITE_COMMANDS.EXPORT_RECEIPTS_TO_ZIP, {reportIDs: JSON.stringify(reportIDs), exportID}, {optimisticData, failureData}); + + return exportID; +} + +export {sendExportFileFromConcierge, clearExportDownload, clearStaleExportDownloads, exportReportsToPDF, exportReceiptsToZip}; From f89f64d55b62ceab2486dc533820953349d88558 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 29 Jul 2026 17:22:33 +0700 Subject: [PATCH 2/8] fix: test --- tests/unit/ReportSecondaryActionUtilsTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index 1d6ad8567502..d81e4de4371b 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -137,6 +137,7 @@ describe('getSecondaryAction', () => { const result = [ CONST.REPORT.SECONDARY_ACTIONS.EXPORT, CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_PDF, + CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS, CONST.REPORT.SECONDARY_ACTIONS.PRINT, CONST.REPORT.SECONDARY_ACTIONS.VIEW_DETAILS, ]; From bc8a2497346048976230d450bf5fbfb619e00241 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 29 Jul 2026 17:28:01 +0700 Subject: [PATCH 3/8] fix: add missing trans --- src/languages/de.ts | 2 ++ src/languages/fr.ts | 2 ++ src/languages/it.ts | 2 ++ src/languages/ja.ts | 2 ++ src/languages/nl.ts | 2 ++ src/languages/pl.ts | 2 ++ src/languages/pt-BR.ts | 2 ++ src/languages/zh-hans.ts | 2 ++ 8 files changed, 16 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index 9de44d1e8e56..81105ba43040 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -438,6 +438,7 @@ const translations: TranslationDeepObject = { perDiem: 'Tagegeld', validate: 'Validieren', downloadAsPDF: 'Als PDF herunterladen', + downloadReceipts: 'Belege herunterladen', downloadAsCSV: 'Als CSV herunterladen', submitViaPDF: 'Per PDF einreichen', print: 'Drucken', @@ -10336,6 +10337,7 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: 'Ihre Belege konnten nicht heruntergeladen werden. Bitte versuchen Sie es später erneut.', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 450f95629c11..8b9d8f8a80c4 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -438,6 +438,7 @@ const translations: TranslationDeepObject = { perDiem: 'Indemnité journalière', validate: 'Valider', downloadAsPDF: 'Télécharger en PDF', + downloadReceipts: 'Télécharger les reçus', downloadAsCSV: 'Télécharger au format CSV', submitViaPDF: 'Soumettre via PDF', print: 'Imprimer', @@ -10370,6 +10371,7 @@ Voici un *reçu test* pour vous montrer comment ça fonctionne :`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: "Vos reçus n'ont pas pu être téléchargés. Veuillez réessayer plus tard.", readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/it.ts b/src/languages/it.ts index d5bed0439aec..a6879ce555a7 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -438,6 +438,7 @@ const translations: TranslationDeepObject = { perDiem: 'Diaria', validate: 'Convalida', downloadAsPDF: 'Scarica come PDF', + downloadReceipts: 'Scarica ricevute', downloadAsCSV: 'Scarica come CSV', submitViaPDF: 'Invia tramite PDF', print: 'Stampa', @@ -10316,6 +10317,7 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: 'Non è stato possibile scaricare le ricevute. Riprova più tardi.', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 5e2ea8e74236..6719f637ceb1 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -437,6 +437,7 @@ const translations: TranslationDeepObject = { perDiem: '日当', validate: '検証', downloadAsPDF: 'PDFとしてダウンロード', + downloadReceipts: '領収書をダウンロード', downloadAsCSV: 'CSVとしてダウンロード', submitViaPDF: 'PDFで提出', print: '印刷', @@ -10179,6 +10180,7 @@ ${reportName}`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: '領収書をダウンロードできませんでした。後でもう一度お試しください。', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 628a776a9dcf..05fd6ba86d32 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -437,6 +437,7 @@ const translations: TranslationDeepObject = { perDiem: 'Dagvergoeding', validate: 'Valideren', downloadAsPDF: 'Downloaden als PDF', + downloadReceipts: 'Bonnetjes downloaden', downloadAsCSV: 'Downloaden als CSV', submitViaPDF: 'Indienen via pdf', print: 'Afdrukken', @@ -10287,6 +10288,7 @@ Hier is een *proefbon* om je te laten zien hoe het werkt:`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: 'Uw bonnetjes konden niet worden gedownload. Probeer het later opnieuw.', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 77d2c1056a80..449cb4f33f3a 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -437,6 +437,7 @@ const translations: TranslationDeepObject = { perDiem: 'Dieta', validate: 'Zatwierdź', downloadAsPDF: 'Pobierz jako PDF', + downloadReceipts: 'Pobierz paragony', downloadAsCSV: 'Pobierz jako CSV', submitViaPDF: 'Prześlij przez PDF', print: 'Drukuj', @@ -10258,6 +10259,7 @@ Oto *paragon testowy*, żeby pokazać Ci, jak to działa:`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: 'Nie udało się pobrać paragonów. Spróbuj ponownie później.', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 369cb67465f9..265c0d41a467 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -437,6 +437,7 @@ const translations: TranslationDeepObject = { perDiem: 'Diária', validate: 'Validar', downloadAsPDF: 'Baixar como PDF', + downloadReceipts: 'Baixar recibos', downloadAsCSV: 'Baixar como CSV', submitViaPDF: 'Enviar via PDF', print: 'Imprimir', @@ -10272,6 +10273,7 @@ Aqui está um *comprovante de teste* para mostrar como funciona:`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: 'Não foi possível baixar seus recibos. Tente novamente mais tarde.', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 00ed57d71760..0760898f7333 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -437,6 +437,7 @@ const translations: TranslationDeepObject = { perDiem: '每日津贴', validate: '验证', downloadAsPDF: '下载为 PDF', + downloadReceipts: '下载收据', downloadAsCSV: '下载为 CSV', submitViaPDF: '通过 PDF 提交', print: '打印', @@ -9942,6 +9943,7 @@ ${reportName}`, failedTitle: 'Export failed', csvFailedBody: 'Your export could not be completed. Please try again later.', pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.', + receiptsFailedBody: '无法下载您的收据。请稍后重试。', readyPartialBody: ({count, total}: {count: number; total: number}) => `${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in Concierge.`, close: 'Close', From 10b2715ac378c70ac43e6d7ec9294a886d431378 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 29 Jul 2026 18:04:07 +0700 Subject: [PATCH 4/8] fix: add check before adding DOWNLOAD_RECEIPTS --- src/libs/ReportSecondaryActionUtils.ts | 4 +++- tests/unit/ReportSecondaryActionUtilsTest.ts | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index 22244a151e96..67ed30878dc0 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -1075,7 +1075,9 @@ function getSecondaryReportActions({ options.push(CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_PDF); - options.push(CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS); + if (reportTransactions.some(hasReceiptTransactionUtils)) { + options.push(CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS); + } if (!isOpenReportUtils(report)) { options.push(CONST.REPORT.SECONDARY_ACTIONS.PRINT); diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index d81e4de4371b..1d6ad8567502 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -137,7 +137,6 @@ describe('getSecondaryAction', () => { const result = [ CONST.REPORT.SECONDARY_ACTIONS.EXPORT, CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_PDF, - CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_RECEIPTS, CONST.REPORT.SECONDARY_ACTIONS.PRINT, CONST.REPORT.SECONDARY_ACTIONS.VIEW_DETAILS, ]; From 108a2a0e2597f9633a2156ee5bc796b8e805f4d5 Mon Sep 17 00:00:00 2001 From: truph01 Date: Thu, 30 Jul 2026 17:40:47 +0700 Subject: [PATCH 5/8] fix: cover eReceipt case --- src/components/ExportDownloadStatusModal.tsx | 20 ++++++++++++++++++-- src/languages/de.ts | 2 ++ src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/languages/fr.ts | 2 ++ src/languages/it.ts | 2 ++ src/languages/ja.ts | 2 ++ src/languages/nl.ts | 2 ++ src/languages/pl.ts | 2 ++ src/languages/pt-BR.ts | 2 ++ src/languages/zh-hans.ts | 2 ++ src/types/onyx/ExportDownload.ts | 3 +++ 12 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/components/ExportDownloadStatusModal.tsx b/src/components/ExportDownloadStatusModal.tsx index 754859894709..67a656160b81 100644 --- a/src/components/ExportDownloadStatusModal.tsx +++ b/src/components/ExportDownloadStatusModal.tsx @@ -61,11 +61,13 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E const exportType = displayedExport?.exportType; const failedReportCount = displayedExport?.failedReportCount ?? 0; const reportCount = displayedExport?.reportCount ?? 0; + const receiptCount = displayedExport?.receiptCount; const isPreparing = state === CONST.EXPORT_DOWNLOAD.STATE.PREPARING && !shouldSendFromConcierge; const isConcierge = !!shouldSendFromConcierge; const isReady = state === CONST.EXPORT_DOWNLOAD.STATE.READY; const isFailed = state === CONST.EXPORT_DOWNLOAD.STATE.FAILED; const isPartialFailure = isReady && failedReportCount > 0; + const isEmptyReceipts = isReady && exportType === CONST.EXPORT_DOWNLOAD.TYPE.RECEIPTS && receiptCount === 0; // Build the secure download URL the same way downloadReportPDF does, so the host always follows // the app's current environment (instead of the env baked into a backend-built URL) and authenticates @@ -82,12 +84,12 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E }; useEffect(() => { - if (!isReady || !fileName || shouldSendFromConcierge) { + if (!isReady || !fileName || shouldSendFromConcierge || isEmptyReceipts) { return; } downloadFile(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isReady, fileName, shouldSendFromConcierge]); + }, [isReady, fileName, shouldSendFromConcierge, isEmptyReceipts]); const handleSendFromConcierge = () => { sendExportFileFromConcierge(exportID, displayedExport ?? undefined); @@ -149,6 +151,20 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E ); } + if (isEmptyReceipts) { + return ( + <> + {translate('exportDownload.noReceiptsTitle')} + {translate('exportDownload.noReceiptsBody')} +