From a41f3680f53b2d0257c6170537b72ed021996e12 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Thu, 30 Apr 2026 18:48:57 -0700 Subject: [PATCH 1/8] Render travelInvoicingUrls link in NetSuite export audit trail --- src/libs/ReportActionsUtils.ts | 28 ++++++++++++++++++++++++++-- src/types/onyx/OriginalMessage.ts | 5 +++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 5bed9598ed7b..2d11d74e260f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -2717,6 +2717,7 @@ function getExportIntegrationActionFragments(translate: LocalizedTranslate, repo const {label, markedManually, automaticAction} = originalMessage; const reimbursableUrls = originalMessage.reimbursableUrls ?? []; const nonReimbursableUrls = originalMessage.nonReimbursableUrls ?? []; + const travelInvoicingUrls = originalMessage.travelInvoicingUrls ?? []; const reportID = reportAction?.reportID; const wasExportedAfterBase62 = (reportAction?.created ?? '') > '2022-11-14'; const base62ReportID = getBase62ReportID(Number(reportID)); @@ -2748,7 +2749,7 @@ function getExportIntegrationActionFragments(translate: LocalizedTranslate, repo url: '', }); } - if (reimbursableUrls.length || nonReimbursableUrls.length) { + if (reimbursableUrls.length || nonReimbursableUrls.length || travelInvoicingUrls.length) { result.push({ text: translate('report.actions.type.exportedToIntegration.automaticActionThree'), url: '', @@ -2763,7 +2764,7 @@ function getExportIntegrationActionFragments(translate: LocalizedTranslate, repo url: reimbursableUrl.startsWith('https://') ? reimbursableUrl : '', }); } - if (reimbursableUrls.length === 1 && nonReimbursableUrls.length) { + if (reimbursableUrls.length === 1 && (nonReimbursableUrls.length || travelInvoicingUrls.length)) { result.push({ text: translate('common.and'), url: '', @@ -2797,6 +2798,29 @@ function getExportIntegrationActionFragments(translate: LocalizedTranslate, repo result.push({text, url}); } + if (nonReimbursableUrls.length && travelInvoicingUrls.length) { + result.push({ + text: translate('common.and'), + url: '', + }); + } + if (travelInvoicingUrls.length) { + const text = 'travel card expenses'; + let url = ''; + + if (travelInvoicingUrls.length === 1) { + const travelInvoicingUrl = travelInvoicingUrls.at(0) ?? ''; + url = travelInvoicingUrl.startsWith('https://') ? travelInvoicingUrl : ''; + } else { + if (label === CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.netsuite) { + url = NETSUITE_NON_REIMBURSABLE_EXPENSES_URL_PREFIX; + url += wasExportedAfterBase62 ? base62ReportID : reportID; + } + } + + result.push({text, url}); + } + return result; } diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 5083cb28364e..a3965265eff8 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -1228,6 +1228,11 @@ type OriginalMessageExportIntegration = { */ reimbursableUrls?: string[]; + /** + * A list of URLs to the Travel Invoicing Journal Entry records in NetSuite (only present for ER/VB workspaces with TI configured) + */ + travelInvoicingUrls?: string[]; + /** * The type of the export action */ From 69b6a31f6dad6d2c4b0e1dc8848a9bb35a52d027 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Fri, 1 May 2026 18:02:49 -0700 Subject: [PATCH 2/8] Fix eslint: collapse else-if in travelInvoicingUrls block --- src/libs/ReportActionsUtils.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 2d11d74e260f..8e93f4fcebff 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -2811,11 +2811,9 @@ function getExportIntegrationActionFragments(translate: LocalizedTranslate, repo if (travelInvoicingUrls.length === 1) { const travelInvoicingUrl = travelInvoicingUrls.at(0) ?? ''; url = travelInvoicingUrl.startsWith('https://') ? travelInvoicingUrl : ''; - } else { - if (label === CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.netsuite) { - url = NETSUITE_NON_REIMBURSABLE_EXPENSES_URL_PREFIX; - url += wasExportedAfterBase62 ? base62ReportID : reportID; - } + } else if (label === CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.netsuite) { + url = NETSUITE_NON_REIMBURSABLE_EXPENSES_URL_PREFIX; + url += wasExportedAfterBase62 ? base62ReportID : reportID; } result.push({text, url}); From 713999bb6780385419a2f2d6c652797074e61087 Mon Sep 17 00:00:00 2001 From: Benjamin Limpich Date: Fri, 1 May 2026 18:05:32 -0700 Subject: [PATCH 3/8] Update comment for travelInvoicingUrls Clarified comment for travelInvoicingUrls property. --- src/types/onyx/OriginalMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index a3965265eff8..7f78deb33591 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -1229,7 +1229,7 @@ type OriginalMessageExportIntegration = { reimbursableUrls?: string[]; /** - * A list of URLs to the Travel Invoicing Journal Entry records in NetSuite (only present for ER/VB workspaces with TI configured) + * A list of URLs to the Travel Invoicing Journal Entry records */ travelInvoicingUrls?: string[]; From f6b91793f3773b7ed2e0e40023b35516dd0b986e Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Mon, 4 May 2026 13:47:19 -0700 Subject: [PATCH 4/8] Localize travel card expenses link label --- src/languages/en.ts | 1 + src/libs/ReportActionsUtils.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 1c3252df3621..9144ed659e55 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7903,6 +7903,7 @@ const translations = { automaticActionThree: 'and successfully created a record for', reimburseableLink: 'out-of-pocket expenses', nonReimbursableLink: 'company card expenses', + travelCardLink: 'travel card expenses', pending: (label: string) => `started exporting this report to ${label}...`, }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 8e93f4fcebff..743fcdb7b65b 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -2805,7 +2805,7 @@ function getExportIntegrationActionFragments(translate: LocalizedTranslate, repo }); } if (travelInvoicingUrls.length) { - const text = 'travel card expenses'; + const text = translate('report.actions.type.exportedToIntegration.travelCardLink'); let url = ''; if (travelInvoicingUrls.length === 1) { From 6ebd6624d13002a4746876f8aaca772323d8952e Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Mon, 4 May 2026 13:50:27 -0700 Subject: [PATCH 5/8] add translation files --- src/languages/de.ts | 1 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + 8 files changed, 8 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index 7928a3efa24b..e1a06b231eaa 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7924,6 +7924,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc reimburseableLink: 'Auslagen', nonReimbursableLink: 'Firmenkarten-Ausgaben', pending: (label: string) => `hat begonnen, diesen Bericht nach ${label} zu exportieren...`, + travelCardLink: 'Reisespesen mit Firmenkarte', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `Fehler beim Exportieren dieses Berichts nach ${label} („${errorMessage}${linkText ? `${linkText}` : ''}“)`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index ebffd180d40c..5237eecf0601 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7946,6 +7946,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e reimburseableLink: 'dépenses personnelles', nonReimbursableLink: 'Dépenses de carte d’entreprise', pending: (label: string) => `a commencé à exporter cette note de frais vers ${label}...`, + travelCardLink: 'dépenses par carte de voyage', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `échec de l’exportation de cette note de frais vers ${label} (« ${errorMessage}${linkText ? `${linkText}` : ''} »)`, diff --git a/src/languages/it.ts b/src/languages/it.ts index 508ebadbfa17..a711682c206a 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7913,6 +7913,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, reimburseableLink: 'spese vive', nonReimbursableLink: 'spese con carta aziendale', pending: (label: string) => `ha iniziato a esportare questo resoconto su ${label}...`, + travelCardLink: 'spese con carta di viaggio', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `esportazione di questo report su ${label} non riuscita ("${errorMessage}${linkText ? `${linkText}` : ''}")`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 9b8ae1ae442f..129b04e144db 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7815,6 +7815,7 @@ ${reportName} reimburseableLink: '立替経費', nonReimbursableLink: '会社カード経費', pending: (label: string) => `このレポートの${label}へのエクスポートを開始しました…`, + travelCardLink: 'トラベルカード経費', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `このレポートを${label}にエクスポートできませんでした("${errorMessage}${linkText ? `${linkText}` : ''}")`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index c58cd2742dcb..6c4be1c32d5b 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7887,6 +7887,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, reimburseableLink: 'uit eigen zak gemaakte uitgaven', nonReimbursableLink: 'uitgaven met bedrijfskaart', pending: (label: string) => `is begonnen dit rapport te exporteren naar ${label}...`, + travelCardLink: 'reiskartuitgaven', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `het exporteren van dit rapport naar ${label} is mislukt ("${errorMessage}${linkText ? `${linkText}` : ''}")`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 9599f1c479d7..a2b4203ef1cc 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7877,6 +7877,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, reimburseableLink: 'wydatki z własnej kieszeni', nonReimbursableLink: 'wydatki z firmowej karty', pending: (label: string) => `rozpoczęto eksport tego raportu do ${label}...`, + travelCardLink: 'wydatki z karty podróżnej', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `nie udało się wyeksportować tego raportu do ${label} („${errorMessage}${linkText ? `${linkText}` : ''}”)`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 7787e4330f8f..c7b2790441e8 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7880,6 +7880,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, reimburseableLink: 'despesas reembolsáveis', nonReimbursableLink: 'despesas com cartão corporativo', pending: (label: string) => `iniciou a exportação deste relatório para ${label}...`, + travelCardLink: 'despesas do cartão de viagem', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `falha ao exportar este relatório para ${label} ("${errorMessage}${linkText ? `${linkText}` : ''}")`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 732946f48239..80aaece9c5d0 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7678,6 +7678,7 @@ ${reportName} reimburseableLink: '自付报销费用', nonReimbursableLink: '公司卡消费', pending: (label: string) => `已开始将此报表导出到 ${label}…`, + travelCardLink: '差旅卡费用', }, integrationsMessage: (errorMessage: string, label: string, linkText?: string, linkURL?: string) => `未能将此报表导出到 ${label}(“${errorMessage}${linkText ? `${linkText}` : ''}”)`, From 34a17003fc79e5757a0473d63394a2c187c759c3 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Mon, 4 May 2026 13:58:13 -0700 Subject: [PATCH 6/8] Add missing travelCardLink placeholder to es.ts --- src/languages/es.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/es.ts b/src/languages/es.ts index a607e98aebc3..7dac4a5d404e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7750,6 +7750,7 @@ ${amount} para ${merchant} - ${date}`, automaticActionThree: 'y creó un registro con éxito para', reimburseableLink: 'Exportar gastos por cuenta propia como', nonReimbursableLink: 'gastos de la tarjeta de empresa', + travelCardLink: 'travel card expenses', pending: (label) => `comenzó a exportar este informe a ${label}...`, }, integrationsMessage: (errorMessage, label, linkText, linkURL) => From 342ec15d40adf7d896294d378c2f05b8a960054c Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Mon, 4 May 2026 14:05:40 -0700 Subject: [PATCH 7/8] Use Oxford comma when listing three expense link types --- src/libs/ReportActionsUtils.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 743fcdb7b65b..738ab88233c2 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -2756,22 +2756,33 @@ function getExportIntegrationActionFragments(translate: LocalizedTranslate, repo }); } + const hasReimbursable = reimbursableUrls.length > 0; + const hasNonReimbursable = nonReimbursableUrls.length > 0; + const hasTravelInvoicing = travelInvoicingUrls.length > 0; + const linkItemCount = (hasReimbursable ? 1 : 0) + (hasNonReimbursable ? 1 : 0) + (hasTravelInvoicing ? 1 : 0); + if (reimbursableUrls.length === 1) { - const shouldAddPeriod = nonReimbursableUrls.length === 0; const reimbursableUrl = reimbursableUrls.at(0) ?? ''; + let suffix = ''; + if (linkItemCount === 1) { + suffix = '.'; + } else if (linkItemCount === 3) { + suffix = ','; + } result.push({ - text: translate('report.actions.type.exportedToIntegration.reimburseableLink') + (shouldAddPeriod ? '.' : ''), + text: translate('report.actions.type.exportedToIntegration.reimburseableLink') + suffix, url: reimbursableUrl.startsWith('https://') ? reimbursableUrl : '', }); } - if (reimbursableUrls.length === 1 && (nonReimbursableUrls.length || travelInvoicingUrls.length)) { + if (hasReimbursable && (hasNonReimbursable || hasTravelInvoicing) && linkItemCount === 2) { result.push({ text: translate('common.and'), url: '', }); } if (nonReimbursableUrls.length) { - const text = translate('report.actions.type.exportedToIntegration.nonReimbursableLink'); + const baseText = translate('report.actions.type.exportedToIntegration.nonReimbursableLink'); + const text = linkItemCount === 3 ? `${baseText},` : baseText; let url = ''; if (nonReimbursableUrls.length === 1) { From 674f8bfbbe5524664bb17d0ccc5b266061a40ac8 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Mon, 4 May 2026 15:20:47 -0700 Subject: [PATCH 8/8] Add Spanish translation for travel card expenses link --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 7dac4a5d404e..bdd3cb01432b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7750,7 +7750,7 @@ ${amount} para ${merchant} - ${date}`, automaticActionThree: 'y creó un registro con éxito para', reimburseableLink: 'Exportar gastos por cuenta propia como', nonReimbursableLink: 'gastos de la tarjeta de empresa', - travelCardLink: 'travel card expenses', + travelCardLink: 'gastos de la tarjeta de viaje', pending: (label) => `comenzó a exportar este informe a ${label}...`, }, integrationsMessage: (errorMessage, label, linkText, linkURL) =>