Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6359,7 +6359,12 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
defaultHourlyRate: 'Standardstundensatz',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `Um HR zu deaktivieren, trennen Sie bitte zuerst ${integration} von diesem Workspace.`},
vendors: {title: 'Lieferanten', subtitle: 'Ordnen Sie Kartenausgaben den aus Ihrer Buchhaltungssoftware importierten Lieferanten zu.'},
vendors: {
title: 'Lieferanten',
subtitle: 'Ordnen Sie Kartenausgaben den aus Ihrer Buchhaltungssoftware importierten Lieferanten zu.',
disabledTitle: 'Nicht so schnell...',
disabledMessage: 'Um diese Funktion zu aktivieren oder zu deaktivieren, müssen Sie Ihre Buchhaltungsimporteinstellungen ändern.',
},
},
reports: {
reportsCustomTitleExamples: 'Beispiele:',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6501,6 +6501,8 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ
vendors: {
title: 'Προμηθευτές',
subtitle: 'Ταιριάξτε τα έξοδα κάρτας με προμηθευτές που έχουν εισαχθεί από το λογιστικό σας λογισμικό.',
disabledTitle: 'Όχι τόσο γρήγορα...',
disabledMessage: 'Για να ενεργοποιήσετε ή να απενεργοποιήσετε αυτήν τη λειτουργία, θα πρέπει να αλλάξετε τις ρυθμίσεις εισαγωγής λογιστικής.',
},
},
reports: {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6409,6 +6409,8 @@ const translations = {
vendors: {
title: 'Vendors',
subtitle: 'Match card expenses to vendors imported from your accounting software.',
disabledTitle: 'Not so fast...',
disabledMessage: "To enable or disable this feature, you'll need to change your accounting import settings.",
},
reportFields: {
title: 'Report fields',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6197,7 +6197,12 @@ ${amount} para ${merchant} - ${date}`,
title: 'Impuestos',
subtitle: 'Documenta y reclama los impuestos aplicables.',
},
vendors: {title: 'Proveedores', subtitle: 'Asocia los gastos de tarjeta con los proveedores importados de tu software de contabilidad.'},
vendors: {
title: 'Proveedores',
subtitle: 'Asocia los gastos de tarjeta con los proveedores importados de tu software de contabilidad.',
disabledTitle: 'No tan rápido...',
disabledMessage: 'Para activar o desactivar esta función, deberás cambiar la configuración de importación de contabilidad.',
},
reportFields: {
title: 'Campos de informes',
subtitle: 'Configura campos personalizados para los gastos.',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6382,7 +6382,12 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.
defaultHourlyRate: 'Taux horaire par défaut',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `Pour désactiver les RH, veuillez d'abord déconnecter ${integration} de cet espace de travail.`},
vendors: {title: 'Fournisseurs', subtitle: 'Faites correspondre les dépenses de carte aux fournisseurs importés depuis votre logiciel de comptabilité.'},
vendors: {
title: 'Fournisseurs',
subtitle: 'Faites correspondre les dépenses de carte aux fournisseurs importés depuis votre logiciel de comptabilité.',
disabledTitle: 'Pas si vite...',
disabledMessage: 'Pour activer ou désactiver cette fonctionnalité, vous devrez modifier vos paramètres d’importation comptable.',
},
},
reports: {
reportsCustomTitleExamples: 'Exemples :',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6334,7 +6334,12 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST.
defaultHourlyRate: 'Tariffa oraria predefinita',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `Per disattivare HR, scollega prima ${integration} da questo workspace.`},
vendors: {title: 'Fornitori', subtitle: 'Abbina le spese della carta ai fornitori importati dal tuo software di contabilità.'},
vendors: {
title: 'Fornitori',
subtitle: 'Abbina le spese della carta ai fornitori importati dal tuo software di contabilità.',
disabledTitle: 'Non così in fretta...',
disabledMessage: 'Per abilitare o disabilitare questa funzione, dovrai modificare le impostazioni di importazione della contabilità.',
},
},
reports: {
reportsCustomTitleExamples: 'Esempi:',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6253,7 +6253,12 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO
defaultHourlyRate: 'デフォルトの時給率',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `HR を無効にするには、まずこのワークスペースから ${integration} を切断してください。`},
vendors: {title: 'ベンダー', subtitle: '会計ソフトからインポートした取引先にカード経費を照合します。'},
vendors: {
title: 'ベンダー',
subtitle: '会計ソフトからインポートした取引先にカード経費を照合します。',
disabledTitle: 'ちょっと待ってください...',
disabledMessage: 'この機能を有効または無効にするには、会計インポート設定を変更する必要があります。',
},
},
reports: {
reportsCustomTitleExamples: '例:',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6320,7 +6320,12 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_
defaultHourlyRate: 'Standaard uurtarief',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `Om HR uit te schakelen, koppel eerst ${integration} los van deze workspace.`},
vendors: {title: 'Leveranciers', subtitle: 'Koppel kaartuitgaven aan leveranciers die zijn geïmporteerd uit je boekhoudsoftware.'},
vendors: {
title: 'Leveranciers',
subtitle: 'Koppel kaartuitgaven aan leveranciers die zijn geïmporteerd uit je boekhoudsoftware.',
disabledTitle: 'Niet zo snel...',
disabledMessage: 'Om deze functie in of uit te schakelen, moet je je boekhoudimportinstellingen wijzigen.',
},
},
reports: {
reportsCustomTitleExamples: 'Voorbeelden:',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6299,7 +6299,12 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy
defaultHourlyRate: 'Domyślna stawka godzinowa',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `Aby wyłączyć HR, najpierw odłącz ${integration} od tego workspace’a.`},
vendors: {title: 'Dostawcy', subtitle: 'Dopasuj wydatki z karty do kontrahentów zaimportowanych z twojego oprogramowania księgowego.'},
vendors: {
title: 'Dostawcy',
subtitle: 'Dopasuj wydatki z karty do kontrahentów zaimportowanych z twojego oprogramowania księgowego.',
disabledTitle: 'Nie tak szybko...',
disabledMessage: 'Aby włączyć lub wyłączyć tę funkcję, musisz zmienić ustawienia importu księgowego.',
},
},
reports: {
reportsCustomTitleExamples: 'Przykłady:',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6320,7 +6320,12 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS
defaultHourlyRate: 'Taxa horária padrão',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `Para desativar RH, desconecte primeiro a integração ${integration} deste workspace.`},
vendors: {title: 'Fornecedores', subtitle: 'Combine as despesas do cartão com fornecedores importados do seu software de contabilidade.'},
vendors: {
title: 'Fornecedores',
subtitle: 'Combine as despesas do cartão com fornecedores importados do seu software de contabilidade.',
disabledTitle: 'Calma lá...',
disabledMessage: 'Para ativar ou desativar este recurso, você precisará alterar suas configurações de importação contábil.',
},
},
reports: {
reportsCustomTitleExamples: 'Exemplos:',
Expand Down
7 changes: 6 additions & 1 deletion src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6105,7 +6105,12 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
defaultHourlyRate: '默认时薪',
},
hrWarningModal: {disconnectText: ({integration}: {integration: string}) => `若要禁用人力资源功能,请先将此工作区与 ${integration} 断开连接。`},
vendors: {title: '供应商', subtitle: '将卡片报销与从您的会计软件导入的供应商进行匹配。'},
vendors: {
title: '供应商',
subtitle: '将卡片报销与从您的会计软件导入的供应商进行匹配。',
disabledTitle: '别急...',
disabledMessage: '要启用或禁用此功能,您需要更改会计导入设置。',
},
},
reports: {
reportsCustomTitleExamples: '示例:',
Expand Down
27 changes: 23 additions & 4 deletions src/pages/workspace/WorkspaceMoreFeaturesPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import {
arePolicyRulesEnabled,
canPolicyAccessFeature,
getConnectedIntegration,
getDistanceRateCustomUnit,
getPerDiemCustomUnit,
hasAccountingConnections,
Expand Down Expand Up @@ -158,9 +159,18 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
// connection, and when the data has already been fetched.
usePolicyConnectionsPrefetch(policy, true);

// Beta members see the row on any workspace so they can tell the feature exists. Everyone else
// only sees it once a connection actually scopes the vendor field, which post-GA means QBO.
const shouldShowVendorsFeature = isVendorMatchingEnabled || hasVendorFeature(policy, isVendorMatchingEnabled);
// Visibility is gated on a supported integration (QBO / Xero / Sage Intacct) being connected,
// not on the export config actually scoping vendors. That way members on a supported workspace
// still see the row so they can discover the feature even when the row is locked OFF (export
// config not yet set). NetSuite / QuickBooks Desktop / no connection hide the row.
// `hasVendorFeature` stays as the narrower `isActive` predicate (is the export config scoping
// vendors right now), so it can't double as the visibility gate.
//
// Beta gating mirrors `hasVendorFeature`: QBO (R1) is GA, so a connected QBO workspace always
// sees the row regardless of the `vendorMatching` beta. Sage Intacct (R2) and Xero (R3) haven't
// reached GA, so they only show the row while the beta is enabled.
const vendorMatchingConnection = getConnectedIntegration(policy, [CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT]);
const shouldShowVendorsFeature = vendorMatchingConnection === CONST.POLICY.CONNECTIONS.NAME.QBO || (isVendorMatchingEnabled && !!vendorMatchingConnection);

const warnAccountingManagesOrganizeFeature = async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QBO R1 is GA: hasVendorFeature() deliberately returns true for a QBO Credit/Debit Card configuration without consulting vendorMatching beta. This gate would hide the row for every non-beta QBO workspace, including the now-required locked-OFF discovery state. Please make QBO visible whenever it is connected, while retaining the beta requirement for Intacct/Xero.

🤖 Posted by Codex, an AI coding agent acting for @Beamanator.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in b17a19a.

QBO R1 is GA, so a connected QBO workspace now shows the Vendors row regardless of the vendorMatching beta, while Sage Intacct (R2) and Xero (R3) stay beta-gated. The gate is now:

const shouldShowVendorsFeature = vendorMatchingConnection === CONST.POLICY.CONNECTIONS.NAME.QBO || (isVendorMatchingEnabled && !!vendorMatchingConnection);

This matches the acceptance matrix: QBO visible whenever connected (locked ON when the export scopes vendors, locked OFF otherwise), Intacct/Xero visible only with the beta on, and NetSuite / QuickBooks Desktop / no connection hidden. See WorkspaceMoreFeaturesPage/index.tsx:171.

if (!hasAccountingConnection || !policyID) {
Expand All @@ -178,6 +188,15 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
Navigation.navigate(ROUTES.POLICY_ACCOUNTING.getRoute(policyID));
};

const warnVendorsManagedByAccounting = async () => {
await showConfirmModal({
title: translate('workspace.moreFeatures.vendors.disabledTitle'),
prompt: translate('workspace.moreFeatures.vendors.disabledMessage'),
confirmText: translate('common.buttonConfirm'),
shouldShowCancelButton: false,
});
};

const warnDisconnectAccountingFirst = async () => {
if (!hasAccountingConnection || !policyID) {
return;
Expand Down Expand Up @@ -476,7 +495,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
// get the read-only modal via withReadOnlyFallback(). Row-body navigation stays active when the
// feature is available. This will be unlocked in the follow-up PR that wires up the toggle.
disabled
disabledAction={withReadOnlyFallback()}
disabledAction={withReadOnlyFallback(warnVendorsManagedByAccounting)}
onToggle={() => {}}
onPress={() => {
if (!policyID) {
Expand Down
66 changes: 66 additions & 0 deletions tests/ui/WorkspaceMoreFeaturesPageTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -407,4 +407,70 @@ describe('WorkspaceMoreFeaturesPage', () => {
expect(navigateToConciergeChatSpy).not.toHaveBeenCalled();
});
});

describe('Vendors row (visibility gated on a supported integration)', () => {
const renderWithVendorMatching = async (connections: Record<string, unknown>, isBetaEnabled = true) => {
await TestHelper.signInWithTestUser();
await act(async () => {
await Onyx.merge(ONYXKEYS.BETAS, isBetaEnabled ? [CONST.BETAS.VENDOR_MATCHING] : []);
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, buildPolicy({id: POLICY_ID, connections}));
});
renderPage({policyID: POLICY_ID});
await waitForBatchedUpdatesWithAct();
};

const vendorsSwitchQuery = () => screen.queryByRole(CONST.ROLE.SWITCH, {name: new RegExp(escapeRegExp(TestHelper.translateLocal('workspace.moreFeatures.vendors.subtitle')), 'i')});

it('hides the Vendors row when no accounting connection is present', async () => {
await renderWithVendorMatching({});
expect(screen.queryByRole(CONST.ROLE.SWITCH, {name: new RegExp(escapeRegExp(TestHelper.translateLocal('workspace.moreFeatures.vendors.subtitle')), 'i')})).toBeNull();
});

it('hides the Vendors row for an unsupported integration (NetSuite)', async () => {
await renderWithVendorMatching({[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]: {config: {}}});
expect(screen.queryByRole(CONST.ROLE.SWITCH, {name: new RegExp(escapeRegExp(TestHelper.translateLocal('workspace.moreFeatures.vendors.subtitle')), 'i')})).toBeNull();
});

it('shows the Vendors row locked for a supported integration and explains the lock on press', async () => {
await renderWithVendorMatching({
[CONST.POLICY.CONNECTIONS.NAME.QBO]: {config: {nonReimbursableExpensesExportDestination: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD}},
});

fireEvent.press(await findLockedSwitch('workspace.moreFeatures.vendors.subtitle'));

await waitFor(() => {
expect(screen.getByText(TestHelper.translateLocal('workspace.moreFeatures.vendors.disabledMessage'))).toBeOnTheScreen();
});
});

it('shows the Vendors row for a supported integration even when its export config does not scope vendors (discovery state)', async () => {
await renderWithVendorMatching({
[CONST.POLICY.CONNECTIONS.NAME.QBO]: {config: {nonReimbursableExpensesExportDestination: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL}},
});
await expect(findLockedSwitch('workspace.moreFeatures.vendors.subtitle')).resolves.toBeOnTheScreen();
});

// QBO R1 is GA, so a connected QBO workspace shows the row regardless of the vendorMatching beta.
it('shows the Vendors row locked ON for QBO scoping vendors even with the beta disabled (QBO is GA)', async () => {
await renderWithVendorMatching(
{[CONST.POLICY.CONNECTIONS.NAME.QBO]: {config: {nonReimbursableExpensesExportDestination: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD}}},
false,
);
await expect(findLockedSwitch('workspace.moreFeatures.vendors.subtitle')).resolves.toBeOnTheScreen();
});

it('shows the Vendors row locked OFF for QBO not scoping vendors even with the beta disabled (discovery state, QBO is GA)', async () => {
await renderWithVendorMatching(
{[CONST.POLICY.CONNECTIONS.NAME.QBO]: {config: {nonReimbursableExpensesExportDestination: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL}}},
false,
);
await expect(findLockedSwitch('workspace.moreFeatures.vendors.subtitle')).resolves.toBeOnTheScreen();
});

// Sage Intacct (R2) and Xero (R3) are still beta-gated, so they stay hidden when the beta is off.
it('hides the Vendors row for a beta-gated integration (Xero) when the beta is disabled', async () => {
await renderWithVendorMatching({[CONST.POLICY.CONNECTIONS.NAME.XERO]: {config: {}}}, false);
expect(vendorsSwitchQuery()).toBeNull();
});
});
});
Loading