diff --git a/src/CONST/index.ts b/src/CONST/index.ts index e2e24b5c03fd..17590ad13af4 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -7793,6 +7793,15 @@ const CONST = { icon: 'Rules', requiredPlan: this.POLICY.TYPE.CORPORATE, }, + publicReceiptVisibility: { + id: 'publicReceiptVisibility' as const, + alias: 'public-receipt-visibility', + name: 'Public receipt visibility', + title: 'workspace.upgrade.publicReceiptVisibility.title' as const, + description: 'workspace.upgrade.publicReceiptVisibility.description' as const, + icon: 'ReportReceipt', + requiredPlan: this.POLICY.TYPE.CORPORATE, + }, perDiem: { id: 'perDiem' as const, alias: 'per-diem', diff --git a/src/languages/de.ts b/src/languages/de.ts index 32be93979750..332ef66d877f 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7487,6 +7487,13 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und `Spezialisierte Arbeitsbereichsrollen sind nur im Control-Tarif verfügbar, beginnend bei ${formattedPrice} ${hasTeam2025Pricing ? `pro Mitglied und Monat.` : `pro aktivem Mitglied und Monat.`}`, }, unlockFeatures: 'Schalten Sie diese Funktionen frei!', + publicReceiptVisibility: { + title: 'Öffentliche Belegsichtbarkeit', + description: + 'Wenn Sie Ihre Beleglinks für alle zugänglich machen möchten, zum Beispiel für einen Kunden oder eine externe Buchhalterin bzw. einen externen Buchhalter, ist diese Funktion genau das Richtige für Sie.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `Die öffentliche Belegsichtbarkeit ist nur im Control-Tarif verfügbar, ab ${formattedPrice} ${hasTeam2025Pricing ? `pro Mitglied und Monat.` : `pro aktivem Mitglied und Monat.`}`, + }, }, downgrade: { commonFeatures: { @@ -7593,6 +7600,9 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und requireCompanyCard: 'Firmenkarten für alle Käufe vorschreiben', requireCompanyCardDescription: 'Kennzeichne alle Barausgaben, einschließlich Kilometer- und Tagegeldspesen.', requireCompanyCardDisabledTooltip: 'Aktiviere Firmenkarten (unter Weitere Funktionen), um dies freizuschalten.', + publicReceiptVisibility: 'Öffentliche Belegsichtbarkeit', + publicReceiptVisibilityHintEnabled: 'Belege sind für alle sichtbar, die über die URL verfügen. Der Zugriff auf den Bericht mit dem Beleg ist nicht erforderlich.', + publicReceiptVisibilityHintDisabled: 'Belege können nur von Expensify-Mitgliedern eingesehen werden, die Zugriff auf den Bericht haben, der den Beleg enthält.', enableTagsToUnlockTitle: 'Tags aktivieren?', enableTagsToUnlockPrompt: 'Aktivieren Sie Tags (unter Weitere Funktionen), um dies freizuschalten.', enableTagsAndRequirePrompt: 'Sind Sie sicher, dass Sie Tags aktivieren und für alle Ausgaben verpflichtend machen möchten?', diff --git a/src/languages/en.ts b/src/languages/en.ts index 58d25bd3b932..55e3d7730a0f 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7540,6 +7540,12 @@ const translations = { onlyAvailableOnPlanUnlimited: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => `Unlimited access to rules is only available on the Control plan, starting at ${formattedPrice} ${hasTeam2025Pricing ? `per member per month.` : `per active member per month.`}`, }, + publicReceiptVisibility: { + title: 'Public receipt visibility', + description: 'If you want to make your receipt links accessible to anyone, such as a client or external accountant, this feature is for you.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `Public receipt visibility is only available on the Control plan, starting at ${formattedPrice} ${hasTeam2025Pricing ? `per member per month.` : `per active member per month.`}`, + }, perDiem: { title: 'Per diem', description: @@ -7824,6 +7830,9 @@ const translations = { eReceiptsHint: `eReceipts are auto-created [for most USD credit transactions](${CONST.DEEP_DIVE_ERECEIPTS}).`, attendeeTracking: 'Attendee tracking', attendeeTrackingHint: 'Track the per-person cost for every expense.', + publicReceiptVisibility: 'Public receipt visibility', + publicReceiptVisibilityHintEnabled: 'Receipts are viewable by anyone with the URL. Access to the report with the receipt is not required.', + publicReceiptVisibilityHintDisabled: 'Receipts are only viewable by Expensify members with access to the report containing the receipt.', prohibitedDefaultDescription: 'Flag receipts with these line items for manual review.', prohibitedExpenses: 'Prohibited expenses', alcohol: 'Alcohol', diff --git a/src/languages/es.ts b/src/languages/es.ts index f42499f831fb..c48247bdd014 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7423,6 +7423,12 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`, `Los roles especializados del espacio de trabajo solo están disponibles en el plan Controlar, a partir de ${formattedPrice} ${hasTeam2025Pricing ? `por miembro al mes.` : `por miembro activo al mes.`}`, }, unlockFeatures: '¡Desbloquea estas funciones!', + publicReceiptVisibility: { + title: 'Visibilidad pública del recibo', + description: 'Si quieres que los enlaces de tus recibos sean accesibles para cualquiera, como un cliente o un contable externo, esta función es para ti.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `La visibilidad pública de recibos solo está disponible en el plan Controlar, desde ${formattedPrice} ${hasTeam2025Pricing ? `por miembro al mes.` : `por miembro activo al mes.`}`, + }, }, downgrade: { commonFeatures: { @@ -7529,6 +7535,9 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`, requireCompanyCard: 'Requerir que todas las compras se hagan con la tarjeta de empresa', requireCompanyCardDescription: 'Marca todo gasto en efectivo, incluyendo kilometraje y gastos per diem.', requireCompanyCardDisabledTooltip: 'Habilita las tarjetas de empresa (bajo Más características) para desbloquearlo.', + publicReceiptVisibility: 'Visibilidad pública del recibo', + publicReceiptVisibilityHintEnabled: 'Cualquier persona con la URL puede ver los recibos. No es necesario tener acceso al informe con el recibo.', + publicReceiptVisibilityHintDisabled: 'Solo los miembros de Expensify con acceso al informe que contiene el recibo pueden ver los recibos.', enableTagsToUnlockTitle: '¿Habilitar etiquetas?', enableTagsToUnlockPrompt: 'Habilita las etiquetas (en Más funciones) para desbloquear.', enableTagsAndRequirePrompt: '¿Seguro que quieres habilitar las etiquetas y hacerlas obligatorias para todos los gastos?', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 959aecc89d52..35bcdf3f1b7e 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7514,6 +7514,13 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip `Les rôles spécialisés dans l’espace de travail sont uniquement disponibles avec l’offre Control, à partir de ${formattedPrice} ${hasTeam2025Pricing ? `par membre et par mois.` : `par membre actif et par mois.`}`, }, unlockFeatures: 'Débloquez ces fonctionnalités !', + publicReceiptVisibility: { + title: 'Visibilité publique du reçu', + description: + 'Si vous souhaitez rendre les liens de vos reçus accessibles à toute personne, comme un client ou un comptable externe, cette fonctionnalité est faite pour vous.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `La visibilité publique des reçus est uniquement disponible avec l’offre Control, à partir de ${formattedPrice} ${hasTeam2025Pricing ? `par membre et par mois.` : `par membre actif et par mois.`}`, + }, }, downgrade: { commonFeatures: { @@ -7619,6 +7626,9 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip requireCompanyCard: "Exiger l'utilisation de cartes d'entreprise pour tous les achats", requireCompanyCardDescription: 'Signaler toutes les dépenses en espèces, y compris le kilométrage et les indemnités journalières.', requireCompanyCardDisabledTooltip: 'Activez les cartes d’entreprise (dans Plus de fonctionnalités) pour déverrouiller.', + publicReceiptVisibility: 'Visibilité publique du reçu', + publicReceiptVisibilityHintEnabled: 'Les reçus sont visibles par toute personne disposant de l’URL. L’accès à la note de frais contenant le reçu n’est pas requis.', + publicReceiptVisibilityHintDisabled: 'Les reçus ne sont visibles que par les membres Expensify ayant accès à la note de frais contenant le reçu.', enableTagsToUnlockTitle: 'Activer les tags ?', enableTagsToUnlockPrompt: 'Activez les tags (sous Plus de fonctionnalités) pour débloquer.', enableTagsAndRequirePrompt: 'Voulez-vous vraiment activer les tags et les rendre obligatoires pour toutes les dépenses ?', diff --git a/src/languages/it.ts b/src/languages/it.ts index aee696db203c..14169ebf3dce 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7459,6 +7459,12 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo `I ruoli specializzati dello spazio di lavoro sono disponibili solo con il piano Control, a partire da ${formattedPrice} ${hasTeam2025Pricing ? `per membro al mese.` : `per membro attivo al mese.`}`, }, unlockFeatures: 'Sblocca queste funzionalità!', + publicReceiptVisibility: { + title: 'Visibilità pubblica della ricevuta', + description: 'Se vuoi rendere i link alle tue ricevute accessibili a chiunque, ad esempio a un cliente o a un commercialista esterno, questa funzione fa per te.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `La visibilità pubblica delle ricevute è disponibile solo con il piano Control, a partire da ${formattedPrice} ${hasTeam2025Pricing ? `per membro al mese.` : `per membro attivo al mese.`}`, + }, }, downgrade: { commonFeatures: { @@ -7565,6 +7571,9 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo requireCompanyCard: 'Richiedi le carte aziendali per tutti gli acquisti', requireCompanyCardDescription: 'Contrassegna tutte le spese in contanti, inclusi chilometraggio e indennità giornaliere.', requireCompanyCardDisabledTooltip: 'Abilita Carte aziendali (in Altre funzionalità) per sbloccare.', + publicReceiptVisibility: 'Visibilità pubblica della ricevuta', + publicReceiptVisibilityHintEnabled: 'Le ricevute sono visibili a chiunque abbia l’URL. Non è richiesto l’accesso al report che contiene la ricevuta.', + publicReceiptVisibilityHintDisabled: 'Le ricevute possono essere visualizzate solo dai membri di Expensify che hanno accesso al report che contiene la ricevuta.', enableTagsToUnlockTitle: 'Abilitare i tag?', enableTagsToUnlockPrompt: 'Attiva i Tag (in Altre funzionalità) per sbloccare.', enableTagsAndRequirePrompt: 'Sei sicuro di voler abilitare le etichette e renderle obbligatorie per tutte le spese?', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 50a4ba04e4b4..4469d51655b4 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7368,6 +7368,12 @@ ${reportName}`, `特別なワークスペースロールは Control プランでのみご利用いただけます(${formattedPrice} から、${hasTeam2025Pricing ? `メンバー1人あたり月額。` : `アクティブメンバー1人あたり/月`})。`, }, unlockFeatures: 'これらの機能をアンロックしましょう!', + publicReceiptVisibility: { + title: '公開領収書の可視性', + description: 'クライアントや外部の会計士など、誰でも領収書リンクにアクセスできるようにしたい場合に便利な機能です。', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `領収書の公開可視性は、${formattedPrice} ${hasTeam2025Pricing ? `メンバー1人あたり月額` : `有効メンバー1人あたり1か月ごとに。`}からのControlプランでのみご利用いただけます。`, + }, }, downgrade: { commonFeatures: { @@ -7473,6 +7479,9 @@ ${reportName}`, requireCompanyCard: 'すべての購入に会社カードを必須にする', requireCompanyCardDescription: 'マイレージや日当経費を含む、すべての現金支出にフラグを付ける。', requireCompanyCardDisabledTooltip: 'ロック解除するには、「その他の機能」内の「会社カード」を有効にしてください。', + publicReceiptVisibility: '公開領収書の可視性', + publicReceiptVisibilityHintEnabled: 'レシートは、URL を知っている人であれば誰でも閲覧できます。レシートが含まれるレポートへのアクセスは不要です。', + publicReceiptVisibilityHintDisabled: 'レシートは、そのレシートを含むレポートへのアクセス権を持つ Expensify のメンバーのみが閲覧できます。', enableTagsToUnlockTitle: 'タグを有効にしますか?', enableTagsToUnlockPrompt: '有効にするには、「その他の機能」でタグを有効化してください。', enableTagsAndRequirePrompt: 'タグを有効にし、すべての経費でタグを必須にしてもよろしいですか?', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index e015943b5c2d..94f231cf6e88 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7443,6 +7443,12 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar `Gespecialiseerde werkruimterollen zijn alleen beschikbaar in het Control-abonnement, vanaf ${formattedPrice} ${hasTeam2025Pricing ? `per lid per maand.` : `per actief lid per maand.`}`, }, unlockFeatures: 'Ontgrendel deze functies!', + publicReceiptVisibility: { + title: 'Openbare bonzichtbaarheid', + description: 'Als je je bonlinks toegankelijk wilt maken voor iedereen, zoals een klant of externe boekhouder, dan is deze functie iets voor jou.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `Publieke bonzichtbaarheid is alleen beschikbaar in het Control-abonnement, vanaf ${formattedPrice} ${hasTeam2025Pricing ? `per lid per maand.` : `per actieve deelnemer per maand.`}`, + }, }, downgrade: { commonFeatures: { @@ -7549,6 +7555,9 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar requireCompanyCard: 'Verplicht bedrijfskaarten voor alle aankopen', requireCompanyCardDescription: 'Markeer alle contante uitgaven, inclusief kilometer- en dagvergoedingen.', requireCompanyCardDisabledTooltip: 'Schakel Bedrijfskaarten in (onder Meer functies) om te ontgrendelen.', + publicReceiptVisibility: 'Openbare bonzichtbaarheid', + publicReceiptVisibilityHintEnabled: 'Bonnetjes zijn zichtbaar voor iedereen met de URL. Toegang tot het rapport met het bonnetje is niet vereist.', + publicReceiptVisibilityHintDisabled: 'Bonnetjes zijn alleen zichtbaar voor Expensify-leden met toegang tot het rapport dat het bonnetje bevat.', enableTagsToUnlockTitle: 'Tags inschakelen?', enableTagsToUnlockPrompt: 'Schakel Labels in (onder Meer functies) om te ontgrendelen.', enableTagsAndRequirePrompt: 'Weet je zeker dat je tags wilt inschakelen en ze verplicht wilt maken voor alle uitgaven?', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index a22fd414fa2f..48d35d8c751d 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7425,6 +7425,12 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i `Specjalistyczne role w przestrzeni roboczej są dostępne tylko w planie Control, od ${formattedPrice} ${hasTeam2025Pricing ? `za członka miesięcznie.` : `za aktywnego członka miesięcznie.`}`, }, unlockFeatures: 'Odblokuj te funkcje!', + publicReceiptVisibility: { + title: 'Publiczna widoczność paragonu', + description: 'Jeśli chcesz udostępniać linki do swoich paragonów każdemu, na przykład klientowi lub zewnętrznemu księgowemu, ta funkcja jest dla ciebie.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `Publiczna widoczność rachunków jest dostępna tylko w planie Control, zaczynającym się od ${formattedPrice} ${hasTeam2025Pricing ? `za członka miesięcznie.` : `za aktywnego członka miesięcznie.`}`, + }, }, downgrade: { commonFeatures: { @@ -7531,6 +7537,9 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i requireCompanyCard: 'Wymagaj kart służbowych dla wszystkich zakupów', requireCompanyCardDescription: 'Oznacz wszystkie wydatki gotówkowe, w tym koszty za przejechane kilometry i ryczałty dzienne.', requireCompanyCardDisabledTooltip: 'Włącz karty firmowe (w sekcji Więcej funkcji), aby odblokować.', + publicReceiptVisibility: 'Publiczna widoczność paragonu', + publicReceiptVisibilityHintEnabled: 'Paragony są widoczne dla każdej osoby mającej link URL. Dostęp do raportu zawierającego paragon nie jest wymagany.', + publicReceiptVisibilityHintDisabled: 'Paragony są widoczne tylko dla członków Expensify, którzy mają dostęp do raportu zawierającego paragon.', enableTagsToUnlockTitle: 'Włączyć tagi?', enableTagsToUnlockPrompt: 'Włącz Tagi (w sekcji Więcej funkcji), aby odblokować.', enableTagsAndRequirePrompt: 'Na pewno chcesz włączyć tagi i wymagać ich dla wszystkich wydatków?', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f8f3c8773cb9..764d3b1295b1 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7438,6 +7438,12 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e `Funções especializadas no espaço de trabalho estão disponíveis apenas no plano Control, a partir de ${formattedPrice} ${hasTeam2025Pricing ? `por membro por mês.` : `por membro ativo por mês.`}`, }, unlockFeatures: 'Desbloqueie estes recursos!', + publicReceiptVisibility: { + title: 'Visibilidade pública do recibo', + description: 'Se você quer tornar os links dos seus recibos acessíveis para qualquer pessoa, como um cliente ou contador externo, este recurso é para você.', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `A visibilidade pública de recibos está disponível apenas no plano Control, a partir de ${formattedPrice} ${hasTeam2025Pricing ? `por membro por mês.` : `por membro ativo por mês.`}`, + }, }, downgrade: { commonFeatures: { @@ -7544,6 +7550,9 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e requireCompanyCard: 'Exigir cartões corporativos para todas as compras', requireCompanyCardDescription: 'Sinalize todos os gastos em dinheiro, incluindo despesas com quilometragem e diárias.', requireCompanyCardDisabledTooltip: 'Ative Cartões corporativos (em Mais recursos) para desbloquear.', + publicReceiptVisibility: 'Visibilidade pública do recibo', + publicReceiptVisibilityHintEnabled: 'Comprovantes podem ser visualizados por qualquer pessoa que tenha a URL. Não é necessário ter acesso ao relatório com o comprovante.', + publicReceiptVisibilityHintDisabled: 'Os recibos só podem ser vistos por membros da Expensify com acesso ao relatório que contém o recibo.', enableTagsToUnlockTitle: 'Ativar tags?', enableTagsToUnlockPrompt: 'Ative as Tags (em Mais recursos) para desbloquear.', enableTagsAndRequirePrompt: 'Tem certeza de que quer ativar etiquetas e exigi-las para todas as despesas?', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 84066a6a4774..9c46664602fb 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7207,6 +7207,12 @@ ${reportName}`, `专用工作区角色仅在 Control 方案中提供,起价为 ${formattedPrice} ${hasTeam2025Pricing ? `每位成员每月。` : `每位活跃成员每月。`}`, }, unlockFeatures: '解锁这些功能!', + publicReceiptVisibility: { + title: '公开报销单可见性', + description: '如果你希望让你的收据链接对任何人可访问,例如客户或外部会计,那么这个功能就是为你准备的。', + onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) => + `公开收据可见性仅适用于 Control 方案,起价为${formattedPrice} ${hasTeam2025Pricing ? `每位成员每月。` : `每位活跃成员每月。`}`, + }, }, downgrade: { commonFeatures: { @@ -7309,6 +7315,9 @@ ${reportName}`, requireCompanyCard: '所有消费均需使用公司卡', requireCompanyCardDescription: '标记所有现金支出,包括里程和每日津贴报销。', requireCompanyCardDisabledTooltip: '启用“公司卡”(位于“更多功能”下)以解锁。', + publicReceiptVisibility: '公开报销单可见性', + publicReceiptVisibilityHintEnabled: '任何拥有该 URL 的人都可以查看收据。无需访问包含该收据的报表。', + publicReceiptVisibilityHintDisabled: '只有拥有包含该收据的报表访问权限的 Expensify 成员才能查看收据。', enableTagsToUnlockTitle: '启用标签?', enableTagsToUnlockPrompt: '启用“标签”(位于“更多功能”下)以解锁。', enableTagsAndRequirePrompt: '确定要启用标签,并将其设为所有报销的必填项吗?', diff --git a/src/libs/API/parameters/SetPolicyPublicReceiptVisibilityEnabledParams.ts b/src/libs/API/parameters/SetPolicyPublicReceiptVisibilityEnabledParams.ts new file mode 100644 index 000000000000..3bbf3196e821 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyPublicReceiptVisibilityEnabledParams.ts @@ -0,0 +1,6 @@ +type SetPolicyPublicReceiptVisibilityEnabledParams = { + enabled: boolean; + policyID: string; +}; + +export default SetPolicyPublicReceiptVisibilityEnabledParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 3e9798dc6a52..320747ba7c8f 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -400,6 +400,7 @@ export type {default as SetPolicyCashExpenseModeParams} from './SetPolicyCashExp export type {default as DisablePolicyBillableModeParams} from './DisablePolicyBillableModeParams'; export type {default as SetWorkspaceEReceiptsEnabled} from './SetWorkspaceEReceiptsEnabled'; export type {default as SetPolicyAttendeeTrackingEnabledParams} from './SetPolicyAttendeeTrackingEnabledParams'; +export type {default as SetPolicyPublicReceiptVisibilityEnabledParams} from './SetPolicyPublicReceiptVisibilityEnabledParams'; export type {default as ConfigureExpensifyCardsForPolicyParams} from './ConfigureExpensifyCardsForPolicyParams'; export type {default as SetExpensifyCardRuleParams} from './SetExpensifyCardRuleParams'; export type {default as CreateExpensifyCardParams} from './CreateExpensifyCardParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index c76bfa7abaeb..efb2a9a63fd3 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -294,6 +294,7 @@ const WRITE_COMMANDS = { DISABLE_POLICY_BILLABLE_MODE: 'DisablePolicyBillableExpenses', SET_WORKSPACE_ERECEIPTS_ENABLED: 'SetWorkspaceEReceiptsEnabled', SET_POLICY_ATTENDEE_TRACKING_ENABLED: 'SetPolicyAttendeeTrackingEnabled', + SET_POLICY_PUBLIC_RECEIPT_VISIBILITY_ENABLED: 'SetPolicyPublicReceiptVisibilityEnabled', SET_POLICY_REQUIRE_COMPANY_CARDS_ENABLED: 'SetPolicyRequireCompanyCardsEnabled', SET_POLICY_CATEGORY_DESCRIPTION_REQUIRED: 'SetPolicyCategoryDescriptionRequired', SET_POLICY_CATEGORY_ATTENDEES_REQUIRED: 'SetPolicyCategoryAttendeesRequired', @@ -988,6 +989,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.DISABLE_POLICY_BILLABLE_MODE]: Parameters.DisablePolicyBillableModeParams; [WRITE_COMMANDS.SET_WORKSPACE_ERECEIPTS_ENABLED]: Parameters.SetWorkspaceEReceiptsEnabled; [WRITE_COMMANDS.SET_POLICY_ATTENDEE_TRACKING_ENABLED]: Parameters.SetPolicyAttendeeTrackingEnabledParams; + [WRITE_COMMANDS.SET_POLICY_PUBLIC_RECEIPT_VISIBILITY_ENABLED]: Parameters.SetPolicyPublicReceiptVisibilityEnabledParams; [WRITE_COMMANDS.UPDATE_QUICKBOOKS_ONLINE_ENABLE_NEW_CATEGORIES]: Parameters.UpdateQuickbooksOnlineGenericTypeParams; [WRITE_COMMANDS.UPDATE_QUICKBOOKS_ONLINE_AUTO_CREATE_VENDOR]: Parameters.UpdateQuickbooksOnlineAutoCreateVendorParams; [WRITE_COMMANDS.UPDATE_QUICKBOOKS_ONLINE_SYNC_TAX]: Parameters.UpdateQuickbooksOnlineGenericTypeParams; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 2fd5cddf289c..93095354dc9c 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -6931,6 +6931,53 @@ function setPolicyAttendeeTrackingEnabled(policyID: string, isAttendeeTrackingEn API.write(WRITE_COMMANDS.SET_POLICY_ATTENDEE_TRACKING_ENABLED, parameters, onyxData); } +function setPolicyReceiptVisibilityPublic(policyID: string, isReceiptVisibilityPublic: boolean, currentIsReceiptVisibilityPublic: boolean | undefined) { + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + isReceiptVisibilityPublic, + pendingFields: { + isReceiptVisibilityPublic: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: { + isReceiptVisibilityPublic: null, + }, + errorFields: null, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + isReceiptVisibilityPublic: currentIsReceiptVisibilityPublic ?? null, + pendingFields: {isReceiptVisibilityPublic: null}, + errorFields: {isReceiptVisibilityPublic: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + ], + }; + + const parameters = { + policyID, + enabled: isReceiptVisibilityPublic, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_PUBLIC_RECEIPT_VISIBILITY_ENABLED, parameters, onyxData); +} + /** * Call the API to set default report title pattern for the given policy * @param policyID - id of the policy to apply the naming pattern to @@ -7877,6 +7924,7 @@ export { openPolicyReceiptPartnersPage, setIsComingFromGlobalReimbursementsFlow, setPolicyAttendeeTrackingEnabled, + setPolicyReceiptVisibilityPublic, setPolicyReimbursableMode, getCashExpenseReimbursableMode, clearPolicyTitleFieldError, diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx index af041f4c6896..661e2f0b5020 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx @@ -26,6 +26,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; +import PublicReceiptVisibilityToggle from './PublicReceiptVisibilityToggle'; + type IndividualExpenseRulesSectionProps = { policyID: string; canWriteRules: boolean; @@ -249,7 +251,7 @@ function IndividualExpenseRulesSection({policyID, canWriteRules, withReadOnlyFal disabledAction={withReadOnlyFallback()} showLockIcon={!canWriteRules || disableRequireCompanyCardToggle} disabledText={translate('workspace.rules.individualExpenseRules.requireCompanyCardDisabledTooltip')} - wrapperStyle={[styles.mt3]} + wrapperStyle={[styles.pv3]} titleStyle={styles.pv2} subtitleStyle={styles.pt1} isActive={requireCompanyCardsEnabled} @@ -262,8 +264,7 @@ function IndividualExpenseRulesSection({policyID, canWriteRules, withReadOnlyFal subtitle={translate('workspace.rules.individualExpenseRules.eReceiptsHint')} switchAccessibilityLabel={translate('workspace.rules.individualExpenseRules.eReceipts')} shouldParseSubtitle - wrapperStyle={[styles.mt3]} - shouldPlaceSubtitleBelowSwitch + wrapperStyle={[styles.pv3]} titleStyle={styles.pv2} subtitleStyle={styles.pt1} isActive={areEReceiptsEnabled} @@ -277,8 +278,7 @@ function IndividualExpenseRulesSection({policyID, canWriteRules, withReadOnlyFal title={translate('workspace.rules.individualExpenseRules.attendeeTracking')} subtitle={translate('workspace.rules.individualExpenseRules.attendeeTrackingHint')} switchAccessibilityLabel={translate('workspace.rules.individualExpenseRules.attendeeTracking')} - wrapperStyle={[styles.mt3]} - shouldPlaceSubtitleBelowSwitch + wrapperStyle={[styles.pv3]} titleStyle={styles.pv2} subtitleStyle={styles.pt1} isActive={isAttendeeTrackingEnabledForPolicy} @@ -288,6 +288,13 @@ function IndividualExpenseRulesSection({policyID, canWriteRules, withReadOnlyFal onToggle={() => (canWriteRules ? handleAttendeeTrackingToggle(!isAttendeeTrackingEnabledForPolicy) : undefined)} pendingAction={policy?.pendingFields?.isAttendeeTrackingEnabled} /> + ); diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSectionRevamp.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSectionRevamp.tsx index 3b1e9518c67c..c794be13d246 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSectionRevamp.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSectionRevamp.tsx @@ -25,6 +25,8 @@ import type IconAsset from '@src/types/utils/IconAsset'; import React, {useMemo} from 'react'; import {View} from 'react-native'; +import PublicReceiptVisibilityToggle from './PublicReceiptVisibilityToggle'; + type IndividualExpenseRulesSectionRevampProps = { policyID: string; canWriteRules: boolean; @@ -56,7 +58,7 @@ function IndividualExpenseRulesSectionRevamp({policyID, canWriteRules}: Individu const {translate} = useLocalize(); const styles = useThemeStyles(); const policy = usePolicy(policyID); - const icons = useMemoizedLazyExpensifyIcons(['CalendarSolid', 'Coins', 'Receipt', 'ReceiptCheck', 'Task', 'Cash', 'Users']); + const icons = useMemoizedLazyExpensifyIcons(['CalendarSolid', 'Coins', 'Receipt', 'ReceiptCheck', 'Task', 'Cash', 'Users', 'Eye']); const policyCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; @@ -233,9 +235,7 @@ function IndividualExpenseRulesSectionRevamp({policyID, canWriteRules}: Individu subtitle={translate('workspace.rules.individualExpenseRules.eReceiptsHint')} switchAccessibilityLabel={translate('workspace.rules.individualExpenseRules.eReceipts')} shouldParseSubtitle - wrapperStyle={[styles.mt3]} - shouldPlaceSubtitleBelowSwitch - shouldUseCompactSubtitleSpacing + wrapperStyle={[styles.pv3]} isActive={areEReceiptsEnabled} disabled={!canWriteRules || policyCurrency !== CONST.CURRENCY.USD || isCollect} showLockIcon={!canWriteRules || policyCurrency !== CONST.CURRENCY.USD || isCollect} @@ -248,9 +248,7 @@ function IndividualExpenseRulesSectionRevamp({policyID, canWriteRules}: Individu title={translate('workspace.rules.individualExpenseRules.attendeeTracking')} subtitle={translate('workspace.rules.individualExpenseRules.attendeeTrackingHint')} switchAccessibilityLabel={translate('workspace.rules.individualExpenseRules.attendeeTracking')} - wrapperStyle={[styles.mt3]} - shouldPlaceSubtitleBelowSwitch - shouldUseCompactSubtitleSpacing + wrapperStyle={[styles.pv3]} isActive={isAttendeeTrackingEnabledForPolicy} disabled={!canWriteRules || isCollect} showLockIcon={!canWriteRules || isCollect} @@ -259,6 +257,11 @@ function IndividualExpenseRulesSectionRevamp({policyID, canWriteRules}: Individu pendingAction={policy?.pendingFields?.isAttendeeTrackingEnabled} rowIcon={icons.Users} /> + ); diff --git a/src/pages/workspace/rules/PublicReceiptVisibilityToggle.tsx b/src/pages/workspace/rules/PublicReceiptVisibilityToggle.tsx new file mode 100644 index 000000000000..216344a1efc7 --- /dev/null +++ b/src/pages/workspace/rules/PublicReceiptVisibilityToggle.tsx @@ -0,0 +1,83 @@ +/** + * Shared Public receipt visibility toggle for classic and revamp Rules General sections. + */ +import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; +import useThemeStyles from '@hooks/useThemeStyles'; + +import {setPolicyReceiptVisibilityPublic} from '@libs/actions/Policy/Policy'; +import Navigation from '@libs/Navigation/Navigation'; +import {isControlPolicy} from '@libs/PolicyUtils'; + +import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; + +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type IconAsset from '@src/types/utils/IconAsset'; + +import type {StyleProp, TextStyle} from 'react-native'; + +import React from 'react'; + +type PublicReceiptVisibilityToggleProps = { + /** Workspace policy ID */ + policyID: string; + + /** Whether the current user can write Rules settings */ + canWriteRules: boolean; + + /** Wraps disabled actions for read-only Rules users (classic Rules page) */ + withReadOnlyFallback?: (disabledAction?: () => void | Promise) => (() => void | Promise) | undefined; + + /** Optional leading row icon (Rules Revamp) */ + rowIcon?: IconAsset; + + /** Optional title style overrides (classic Rules) */ + titleStyle?: StyleProp; + + /** Optional subtitle style overrides (classic Rules) */ + subtitleStyle?: StyleProp; +}; + +function PublicReceiptVisibilityToggle({policyID, canWriteRules, withReadOnlyFallback, rowIcon, titleStyle, subtitleStyle}: PublicReceiptVisibilityToggleProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const policy = usePolicy(policyID); + + const isEnabled = !!policy?.isReceiptVisibilityPublic; + const isUnavailable = !isControlPolicy(policy); + + const navigateToUpgrade = () => { + Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.publicReceiptVisibility.alias, ROUTES.WORKSPACE_RULES.getRoute(policyID))); + }; + + const upgradeDisabledAction = isUnavailable ? navigateToUpgrade : undefined; + let disabledAction: (() => void | Promise) | undefined; + if (withReadOnlyFallback) { + disabledAction = withReadOnlyFallback(upgradeDisabledAction); + } else if (canWriteRules) { + disabledAction = upgradeDisabledAction; + } + + return ( + (canWriteRules ? setPolicyReceiptVisibilityPublic(policyID, !isEnabled, policy?.isReceiptVisibilityPublic) : undefined)} + pendingAction={policy?.pendingFields?.isReceiptVisibilityPublic} + rowIcon={rowIcon} + /> + ); +} + +export default PublicReceiptVisibilityToggle; diff --git a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx index fc0ff12a9827..bed5f0a5a126 100644 --- a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx +++ b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx @@ -45,6 +45,7 @@ import { isCurrencySupportedForDirectReimbursement, setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, + setPolicyReceiptVisibilityPublic, setWorkspaceApprovalMode, setWorkspaceReimbursement, upgradeSubmit, @@ -250,6 +251,9 @@ function WorkspaceUpgradePage({route}: WorkspaceUpgradePageProps) { case CONST.UPGRADE_FEATURE_INTRO_MAPPING.rules.id: enablePolicyRules(policy, true, false, policyDataRef.current); break; + case CONST.UPGRADE_FEATURE_INTRO_MAPPING.publicReceiptVisibility.id: + setPolicyReceiptVisibilityPublic(policyID, true, policy?.isReceiptVisibilityPublic); + break; case CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCards.id: case CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCardSubmit.id: enableCompanyCards(policyID, true, false); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 50dbe8cb05f7..e74ce510118c 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -2830,6 +2830,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether Attendee Tracking is enabled */ isAttendeeTrackingEnabled?: boolean; + /** Whether receipts are publicly viewable via URL without report access */ + isReceiptVisibilityPublic?: boolean; + /** Whether the policy requires purchases to be on a company card */ requireCompanyCardsEnabled?: boolean; } & Partial,