diff --git a/src/languages/de.ts b/src/languages/de.ts index bdb59e445f34..1a30de34bcd6 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -1406,6 +1406,10 @@ const translations: TranslationDeepObject = { one: 'Erkläre, warum du diese Ausgabe zurückhältst.', other: 'Erklären Sie, warum Sie diese Ausgaben zurückhalten.', }), + explainHoldApprover: () => ({ + one: 'Erkläre, was du vor der Genehmigung dieser Ausgabe benötigst.', + other: 'Erkläre, was du vor der Genehmigung dieser Ausgaben benötigst.', + }), retracted: 'zurückgezogen', retract: 'Zurückziehen', reopened: 'wiedereröffnet', @@ -1498,7 +1502,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: 'Zurückgehaltene Ausgaben werden ausgelassen, wenn du einen gesamten Bericht genehmigst.', rejectExpenseTitle: 'Lehne eine Ausgabe ab, die du nicht genehmigen oder bezahlen möchtest.', reasonPageTitle: 'Ausgabe ablehnen', - reasonPageDescription: 'Erkläre, warum du diese Ausgabe ablehnst.', + reasonPageDescription: 'Erkläre, warum du diese Ausgabe nicht genehmigen wirst.', rejectReason: 'Ablehnungsgrund', markAsResolved: 'Als gelöst markieren', rejectedStatus: 'Diese Ausgabe wurde abgelehnt. Wir warten darauf, dass du die Probleme behebst und als gelöst markierst, um das Einreichen zu ermöglichen.', diff --git a/src/languages/en.ts b/src/languages/en.ts index 3c02f0097a8c..abd567ac85fb 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1424,6 +1424,10 @@ const translations = { one: "Explain why you're holding this expense.", other: "Explain why you're holding these expenses.", }), + explainHoldApprover: () => ({ + one: 'Explain what you need before approving this expense.', + other: 'Explain what you need before approving these expenses.', + }), retracted: 'retracted', retract: 'Retract', reopened: 'reopened', @@ -1516,7 +1520,7 @@ const translations = { heldExpenseLeftBehindTitle: 'Held expenses are left behind when you approve an entire report.', rejectExpenseTitle: "Reject an expense that you don't intend to approve or pay.", reasonPageTitle: 'Reject expense', - reasonPageDescription: "Explain why you're rejecting this expense.", + reasonPageDescription: 'Explain why you will not approve this expense.', rejectReason: 'Rejection reason', markAsResolved: 'Mark as resolved', rejectedStatus: 'This expense was rejected. Waiting on you to fix the issues and mark as resolved to enable submission.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 091cfe1e11a0..f578500d138d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1255,6 +1255,10 @@ const translations: TranslationDeepObject = { one: 'Explica la razón para retener esta solicitud.', other: 'Explica la razón para retener estas solicitudes.', }), + explainHoldApprover: () => ({ + one: 'Explica qué necesitas antes de aprobar este gasto.', + other: 'Explica qué necesitas antes de aprobar estos gastos.', + }), retract: 'Retractar', reopened: 'reabrir', reopenReport: 'Reabrir informe', @@ -1349,7 +1353,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: 'Los gastos retenidos se dejan atrás cuando apruebas un informe completo.', rejectExpenseTitle: 'Rechaza un gasto que no tengos intención de aprobar o pagar.', reasonPageTitle: 'Rechazar gasto', - reasonPageDescription: 'Explica por qué estás rechazando este gasto.', + reasonPageDescription: 'Explica por qué no aprobarás este gasto.', rejectReason: 'Motivo del rechazo', markAsResolved: 'Marcar como resuelto', rejectedStatus: 'Este gasto fue rechazado. Estamos esperando que soluciones los problemas y lo marques como resuelto para poder enviarlo.', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index b031aa989d38..d912f6807d5d 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -1410,6 +1410,10 @@ const translations: TranslationDeepObject = { one: 'Expliquez pourquoi vous retenez cette dépense.', other: 'Expliquez pourquoi vous retenez ces dépenses.', }), + explainHoldApprover: () => ({ + one: "Expliquez ce dont vous avez besoin avant d'approuver cette dépense.", + other: "Expliquez ce dont vous avez besoin avant d'approuver ces dépenses.", + }), retracted: 'Retiré', retract: 'Retirer', reopened: 'rouvert', @@ -1502,7 +1506,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: 'Les dépenses en attente sont laissées de côté lorsque vous approuvez une note de frais entière.', rejectExpenseTitle: 'Rejetez une dépense que vous n’avez pas l’intention d’approuver ou de payer.', reasonPageTitle: 'Rejeter la dépense', - reasonPageDescription: 'Expliquez pourquoi vous rejetez cette dépense.', + reasonPageDescription: "Expliquez pourquoi vous n'approuverez pas cette dépense.", rejectReason: 'Motif de rejet', markAsResolved: 'Marquer comme résolu', rejectedStatus: 'Cette dépense a été rejetée. Nous attendons que vous corrigiez les problèmes et la marquiez comme résolue pour permettre la soumission.', diff --git a/src/languages/it.ts b/src/languages/it.ts index d2eaee2bad69..a47cbcd11761 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -1403,6 +1403,10 @@ const translations: TranslationDeepObject = { one: 'Spiega perché stai trattenendo questa spesa.', other: 'Spiega perché stai trattenendo queste spese.', }), + explainHoldApprover: () => ({ + one: 'Spiega cosa ti serve prima di approvare questa spesa.', + other: 'Spiega cosa ti serve prima di approvare queste spese.', + }), retracted: 'ritirato', retract: 'Revoca', reopened: 'riaperto', @@ -1495,7 +1499,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: 'Le spese in sospeso vengono escluse quando approvi l’intero report.', rejectExpenseTitle: 'Rifiuta una spesa che non intendi approvare o pagare.', reasonPageTitle: 'Rifiuta spesa', - reasonPageDescription: 'Spiega perché stai rifiutando questa spesa.', + reasonPageDescription: 'Spiega perché non approverai questa spesa.', rejectReason: 'Motivo del rifiuto', markAsResolved: 'Segna come risolto', rejectedStatus: 'Questa spesa è stata rifiutata. In attesa che tu risolva i problemi e la contrassegni come risolta per consentirne l’invio.', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 492e1d1a2cd0..6c5233842f57 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -1393,6 +1393,10 @@ const translations: TranslationDeepObject = { one: 'この経費を保留している理由を説明してください。', other: 'これらの経費を保留している理由を説明してください。', }), + explainHoldApprover: () => ({ + one: 'この経費を承認する前に必要なことを説明してください。', + other: 'これらの経費を承認する前に必要なことを説明してください。', + }), retracted: '取り消し済み', retract: '撤回', reopened: '再開しました', @@ -1485,7 +1489,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: '保留中の経費は、レポート全体を承認してもそのまま残ります。', rejectExpenseTitle: '承認や支払いを行う予定のない経費を却下します。', reasonPageTitle: '経費を却下', - reasonPageDescription: 'この経費を却下する理由を説明してください。', + reasonPageDescription: 'この経費を承認しない理由を説明してください。', rejectReason: '却下理由', markAsResolved: '解決済みにする', rejectedStatus: 'この経費は却下されました。問題を修正して解決済みにすると、再提出できるようになります。', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 4ba410ef1f33..6d13dfe81db1 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -1401,6 +1401,10 @@ const translations: TranslationDeepObject = { one: 'Leg uit waarom je deze uitgave aanhoudt.', other: 'Leg uit waarom je deze uitgaven vasthoudt.', }), + explainHoldApprover: () => ({ + one: 'Leg uit wat je nodig hebt voordat je deze uitgave goedkeurt.', + other: 'Leg uit wat je nodig hebt voordat je deze uitgaven goedkeurt.', + }), retracted: 'ingetrokken', retract: 'Intrekken', reopened: 'heropend', @@ -1493,7 +1497,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: 'Vaste uitgaven blijven achter wanneer je een volledig rapport goedkeurt.', rejectExpenseTitle: 'Wijs een uitgave af die je niet van plan bent goed te keuren of te betalen.', reasonPageTitle: 'Uitgave afwijzen', - reasonPageDescription: 'Leg uit waarom je deze declaratie afwijst.', + reasonPageDescription: 'Leg uit waarom je deze uitgave niet zult goedkeuren.', rejectReason: 'Reden van afwijzing', markAsResolved: 'Markeren als opgelost', rejectedStatus: 'Deze uitgave is afgewezen. We wachten tot jij de problemen oplost en als opgelost markeert, zodat je deze opnieuw kunt indienen.', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 5d3866abbecf..2d590cdd3cf3 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -1400,6 +1400,10 @@ const translations: TranslationDeepObject = { one: 'Wyjaśnij, dlaczego wstrzymujesz ten wydatek.', other: 'Wyjaśnij, dlaczego wstrzymujesz te wydatki.', }), + explainHoldApprover: () => ({ + one: 'Wyjaśnij, czego potrzebujesz przed zatwierdzeniem tego wydatku.', + other: 'Wyjaśnij, czego potrzebujesz przed zatwierdzeniem tych wydatków.', + }), retracted: 'wycofano', retract: 'Wycofaj', reopened: 'ponownie otwarto', @@ -1492,7 +1496,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: 'Wstrzymane wydatki są pomijane, gdy zatwierdzasz cały raport.', rejectExpenseTitle: 'Odrzuć wydatek, którego nie zamierzasz zatwierdzić ani opłacić.', reasonPageTitle: 'Odrzuć wydatek', - reasonPageDescription: 'Wyjaśnij, dlaczego odrzucasz ten wydatek.', + reasonPageDescription: 'Wyjaśnij, dlaczego nie zatwierdzisz tego wydatku.', rejectReason: 'Powód odrzucenia', markAsResolved: 'Oznacz jako rozwiązane', rejectedStatus: 'Ten wydatek został odrzucony. Czekamy, aż naprawisz problemy i oznaczysz go jako rozwiązany, aby umożliwić przesłanie.', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 3b62c36315de..159e879249ff 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -1398,6 +1398,10 @@ const translations: TranslationDeepObject = { one: 'Explique por que você está retendo esta despesa.', other: 'Explique por que você está segurando essas despesas.', }), + explainHoldApprover: () => ({ + one: 'Explique o que você precisa antes de aprovar esta despesa.', + other: 'Explique o que você precisa antes de aprovar estas despesas.', + }), retracted: 'retraído', retract: 'Retrair', reopened: 'reaberto', @@ -1490,7 +1494,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: 'Despesas retidas são deixadas para trás quando você aprova um relatório inteiro.', rejectExpenseTitle: 'Rejeite uma despesa que você não pretende aprovar ou pagar.', reasonPageTitle: 'Rejeitar despesa', - reasonPageDescription: 'Explique por que você está rejeitando esta despesa.', + reasonPageDescription: 'Explique por que você não aprovará esta despesa.', rejectReason: 'Motivo da rejeição', markAsResolved: 'Marcar como resolvido', rejectedStatus: 'Esta despesa foi rejeitada. Estamos aguardando você corrigir os problemas e marcá-la como resolvida para permitir o envio.', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 7c303102c2dc..c550becf8aa0 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -1374,6 +1374,10 @@ const translations: TranslationDeepObject = { one: '请解释你为何暂时保留这笔报销。', other: '请说明你为何保留这些报销。', }), + explainHoldApprover: () => ({ + one: '请说明在批准这笔报销之前你需要什么。', + other: '请说明在批准这些报销之前你需要什么。', + }), retracted: '已撤回', retract: '撤回', reopened: '已重新打开', @@ -1464,7 +1468,7 @@ const translations: TranslationDeepObject = { heldExpenseLeftBehindTitle: '当你批准整份报销报告时,已搁置的报销会被保留在报告之外。', rejectExpenseTitle: '拒绝你不打算批准或报销的报销单。', reasonPageTitle: '拒绝报销', - reasonPageDescription: '请说明你拒绝此报销的原因。', + reasonPageDescription: '请说明你为何不会批准这笔报销。', rejectReason: '拒绝原因', markAsResolved: '标记为已解决', rejectedStatus: '该报销已被拒绝。请先修复问题并标记为已解决,然后才能提交。', diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index d23a2068cf43..d8d6a62d37d9 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -3,6 +3,7 @@ import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import {useSearchActionsContext, useSearchStateContext} from '@components/Search/SearchContext'; import useAncestors from '@hooks/useAncestors'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import {clearErrorFields, clearErrors} from '@libs/actions/FormActions'; @@ -26,10 +27,14 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { const {backTo = '', reportID} = route.params ?? {}; const {selectedTransactionIDs, selectedTransactions, currentSearchHash} = useSearchStateContext(); const {clearSelectedTransactions} = useSearchActionsContext(); + const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); - const ancestors = useAncestors(report); + const selectedTransactionsList = Object.values(selectedTransactions); + const isSubmitter = report ? report.ownerAccountID === currentUserAccountID : selectedTransactionsList.some((t) => t.ownerAccountID === currentUserAccountID); + + const ancestors = useAncestors(report); const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); const {isDelegateAccessRestricted} = useDelegateNoAccessState(); const {showDelegateNoAccessModal} = useDelegateNoAccessActions(); @@ -91,6 +96,7 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { validate={validate} expenseCount={expenseCount} backTo={backTo} + isSubmitter={isSubmitter} /> ); } diff --git a/src/pages/iou/HoldReasonFormView.tsx b/src/pages/iou/HoldReasonFormView.tsx index 481747ac2704..7eaf279a5b72 100644 --- a/src/pages/iou/HoldReasonFormView.tsx +++ b/src/pages/iou/HoldReasonFormView.tsx @@ -27,9 +27,12 @@ type HoldReasonFormViewProps = { /** Number of expenses being held (defaults to 1) */ expenseCount?: number; + + /** Whether the current user is the submitter of the expense */ + isSubmitter?: boolean; }; -function HoldReasonFormView({backTo, validate, onSubmit, expenseCount = 1}: HoldReasonFormViewProps) { +function HoldReasonFormView({backTo, validate, onSubmit, expenseCount = 1, isSubmitter = true}: HoldReasonFormViewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); @@ -53,7 +56,7 @@ function HoldReasonFormView({backTo, validate, onSubmit, expenseCount = 1}: Hold enabledWhenOffline shouldHideFixErrorsAlert > - {translate('iou.explainHold', {count: expenseCount})} + {translate(isSubmitter ? 'iou.explainHold' : 'iou.explainHoldApprover', {count: expenseCount})} ); } diff --git a/src/selectors/Report.ts b/src/selectors/Report.ts index a0c60472d6e0..2d569570b3ba 100644 --- a/src/selectors/Report.ts +++ b/src/selectors/Report.ts @@ -19,4 +19,8 @@ function getReportPolicyID(report: OnyxEntry) { return report?.policyID; } -export {getArchiveReason, getReportPolicyID}; +function getReportOwnerAccountID(report: OnyxEntry) { + return report?.ownerAccountID; +} + +export {getArchiveReason, getReportOwnerAccountID, getReportPolicyID}; diff --git a/tests/ui/components/HoldReasonFormViewTest.tsx b/tests/ui/components/HoldReasonFormViewTest.tsx index b1ecd569900e..ff9f1e0eb4e7 100644 --- a/tests/ui/components/HoldReasonFormViewTest.tsx +++ b/tests/ui/components/HoldReasonFormViewTest.tsx @@ -51,4 +51,64 @@ describe('HoldReasonFormView', () => { const holdExpenseElements = screen.getAllByText(translateLocal('iou.holdExpense', {count: 2})); expect(holdExpenseElements.length).toBeGreaterThanOrEqual(2); // Title and button }); + + it('renders submitter copy when isSubmitter is true', () => { + render( + + + , + ); + + expect(screen.getByText(translateLocal('iou.explainHold', {count: 1}))).toBeTruthy(); + }); + + it('renders approver copy when isSubmitter is false', () => { + render( + + + , + ); + + expect(screen.getByText(translateLocal('iou.explainHoldApprover', {count: 1}))).toBeTruthy(); + }); + + it('renders approver plural copy when isSubmitter is false with multiple expenses', () => { + render( + + + , + ); + + expect(screen.getByText(translateLocal('iou.explainHoldApprover', {count: 3}))).toBeTruthy(); + }); + + it('defaults to submitter copy when isSubmitter is not provided', () => { + render( + + + , + ); + + expect(screen.getByText(translateLocal('iou.explainHold', {count: 1}))).toBeTruthy(); + }); });