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
13 changes: 13 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4084,6 +4084,7 @@ const CONST = {
ARE_DISTANCE_RATES_ENABLED: 'areDistanceRatesEnabled',
ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled',
ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled',
ARE_INVOICE_FIELDS_ENABLED: 'areInvoiceFieldsEnabled',
ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled',
ARE_RECEIPT_PARTNERS_ENABLED: 'receiptPartners',
ARE_COMPANY_CARDS_ENABLED: 'areCompanyCardsEnabled',
Expand Down Expand Up @@ -7565,6 +7566,14 @@ const CONST = {
description: 'workspace.upgrade.reportFields.description' as const,
icon: 'Pencil',
},
invoiceFields: {
id: 'invoiceFields' as const,
alias: 'invoice-fields',
name: 'Invoice Fields',
title: 'workspace.upgrade.invoiceFields.title' as const,
description: 'workspace.upgrade.invoiceFields.description' as const,
icon: 'Pencil',
},
policyPreventMemberChangingTitle: {
id: 'policyPreventMemberChangingTitle' as const,
alias: 'policy-prevent-member-changing-title',
Expand Down Expand Up @@ -7843,6 +7852,10 @@ const CONST = {
LIST: 'dropdown',
FORMULA: 'formula',
},
REPORT_FIELD_TARGETS: {
EXPENSE: 'expense',
INVOICE: 'invoice',
},

NAVIGATION_ACTIONS: {
RESET: 'RESET',
Expand Down
37 changes: 37 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,10 @@ const DYNAMIC_ROUTES = {
path: 'initial-list-value',
entryScreens: [SCREENS.WORKSPACE.REPORT_FIELDS_CREATE],
},
WORKSPACE_INVOICE_FIELDS_INITIAL_LIST_VALUE: {
path: 'invoice-initial-list-value',
entryScreens: [SCREENS.WORKSPACE.INVOICE_FIELDS_CREATE],
},
} as const satisfies DynamicRoutes;

const ROUTES = {
Expand Down Expand Up @@ -2552,6 +2556,39 @@ const ROUTES = {
route: 'workspaces/:policyID/invoices/company-website',
getRoute: (policyID: string) => `workspaces/${policyID}/invoices/company-website` as const,
},
WORKSPACE_INVOICE_FIELDS_CREATE: {
route: 'workspaces/:policyID/invoices/newInvoiceField',
getRoute: (policyID: string) => `workspaces/${policyID}/invoices/newInvoiceField` as const,
},
WORKSPACE_INVOICE_FIELDS_SETTINGS: {
route: 'workspaces/:policyID/invoices/:reportFieldID/edit',
getRoute: (policyID: string, reportFieldID: string) => `workspaces/${policyID}/invoices/${encodeURIComponent(reportFieldID)}/edit` as const,
},
WORKSPACE_INVOICE_FIELDS_LIST_VALUES: {
route: 'workspaces/:policyID/invoices/listValues/:reportFieldID?',
getRoute: (policyID: string, reportFieldID?: string) => `workspaces/${policyID}/invoices/listValues/${reportFieldID ? encodeURIComponent(reportFieldID) : ''}` as const,
},
WORKSPACE_INVOICE_FIELDS_TYPE_SELECTOR: {
route: 'workspaces/:policyID/invoices/typeSelector/:currentType?',
getRoute: (policyID: string, currentType?: PolicyReportFieldType) => `workspaces/${policyID}/invoices/typeSelector/${currentType ? encodeURIComponent(currentType) : ''}` as const,
},
WORKSPACE_INVOICE_FIELDS_ADD_VALUE: {
route: 'workspaces/:policyID/invoices/addValue/:reportFieldID?',
getRoute: (policyID: string, reportFieldID?: string) => `workspaces/${policyID}/invoices/addValue/${reportFieldID ? encodeURIComponent(reportFieldID) : ''}` as const,
},
WORKSPACE_INVOICE_FIELDS_VALUE_SETTINGS: {
route: 'workspaces/:policyID/invoices/:valueIndex/:reportFieldID?',
getRoute: (policyID: string, valueIndex: number, reportFieldID?: string) =>
`workspaces/${policyID}/invoices/${valueIndex}/${reportFieldID ? encodeURIComponent(reportFieldID) : ''}` as const,
},
WORKSPACE_INVOICE_FIELDS_EDIT_VALUE: {
route: 'workspaces/:policyID/invoices/newInvoiceField/:valueIndex/edit',
getRoute: (policyID: string, valueIndex: number) => `workspaces/${policyID}/invoices/newInvoiceField/${valueIndex}/edit` as const,
},
WORKSPACE_INVOICE_FIELDS_EDIT_INITIAL_VALUE: {
route: 'workspaces/:policyID/invoices/:reportFieldID/edit/initialValue',
getRoute: (policyID: string, reportFieldID: string) => `workspaces/${policyID}/invoices/${encodeURIComponent(reportFieldID)}/edit/initialValue` as const,
},
WORKSPACE_MEMBERS: {
route: 'workspaces/:policyID/members',
getRoute: (policyID: string | undefined) => {
Expand Down
9 changes: 9 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,15 @@ const SCREENS = {
INVOICES_VERIFY_ACCOUNT: 'Workspace_Invoices_Verify_Account',
INVOICES_COMPANY_NAME: 'Workspace_Invoices_Company_Name',
INVOICES_COMPANY_WEBSITE: 'Workspace_Invoices_Company_Website',
INVOICE_FIELDS_CREATE: 'Workspace_InvoiceFields_Create',
INVOICE_FIELDS_SETTINGS: 'Workspace_InvoiceFields_Settings',
INVOICE_FIELDS_LIST_VALUES: 'Workspace_InvoiceFields_ListValues',
INVOICE_FIELDS_ADD_VALUE: 'Workspace_InvoiceFields_AddValue',
INVOICE_FIELDS_VALUE_SETTINGS: 'Workspace_InvoiceFields_ValueSettings',
INVOICE_FIELDS_EDIT_VALUE: 'Workspace_InvoiceFields_EditValue',
INVOICE_FIELDS_EDIT_INITIAL_VALUE: 'Workspace_InvoiceFields_EditInitialValue',
DYNAMIC_INVOICE_FIELDS_INITIAL_LIST_VALUE: 'Dynamic_Invoice_Fields_Initial_List_Value',
INVOICE_FIELDS_TYPE_SELECTOR: 'Workspace_InvoiceFields_TypeSelector',
MEMBERS: 'Workspace_Members',
ROOMS: 'Workspace_Rooms',
ROOM_CREATE: 'Workspace_Room_Create',
Expand Down
4 changes: 4 additions & 0 deletions src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import type DimensionTypeSelector from '@pages/workspace/accounting/intacct/impo
import type NetSuiteCustomFieldMappingPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker';
import type NetSuiteCustomListPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker';
import type NetSuiteMenuWithTopDescriptionForm from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm';
import type InitialListValueSelector from '@pages/workspace/reports/InitialListValueSelector';
import type TypeSelector from '@pages/workspace/reports/TypeSelector';
import type TaxValuePicker from '@pages/workspace/taxes/TaxValuePicker';

import type {Country} from '@src/CONST';
Expand Down Expand Up @@ -74,6 +76,8 @@ type ValidInputs =
| typeof NetSuiteCustomListPicker
| typeof NetSuiteCustomFieldMappingPicker
| typeof NetSuiteMenuWithTopDescriptionForm
| typeof InitialListValueSelector
| typeof TypeSelector
| typeof CountryPicker
| typeof StatePicker
| typeof ConstantSelector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isGroupPolicyExpenseReport as isGroupPolicyExpenseReportUtils,
isInvoiceReport as isInvoiceReportUtils,
isReportFieldDisabledForUser,
isReportFieldTargetMatchingReport,
shouldHideSingleReportField,
} from '@libs/ReportUtils';

Expand Down Expand Up @@ -93,7 +94,7 @@ function MoneyRequestViewReportFields({report, policy, pendingAction}: MoneyRequ
const fields = Object.values(fieldsByName);

return fields
.filter((field) => field.target === report?.type)
.filter((field) => isReportFieldTargetMatchingReport(report, field))
.filter((reportField) => !shouldHideSingleReportField(reportField))
.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight)
.map((field): EnrichedPolicyReportField => {
Expand All @@ -118,8 +119,11 @@ function MoneyRequestViewReportFields({report, policy, pendingAction}: MoneyRequ

const isGroupPolicyExpenseReport = isGroupPolicyExpenseReportUtils(report, policy?.type);
const isInvoiceReport = isInvoiceReportUtils(report);
const areFieldsEnabledForReport = isInvoiceReport ? policy?.areInvoiceFieldsEnabled : policy?.areReportFieldsEnabled;

const shouldDisplayReportFields = (isGroupPolicyExpenseReport || isInvoiceReport) && !!policy?.areReportFieldsEnabled;
// `sortedPolicyReportFields` already excludes fields hidden by `shouldHideSingleReportField`, including the title field.
// If no displayable custom fields remain, the early return below hides the section.
const shouldDisplayReportFields = (isGroupPolicyExpenseReport || isInvoiceReport) && !!areFieldsEnabledForReport;

if (!shouldDisplayReportFields || !sortedPolicyReportFields.length) {
return null;
Expand Down
8 changes: 5 additions & 3 deletions src/components/ReportActionItem/MoneyReportView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
isGroupPolicyExpenseReport as isGroupPolicyExpenseReportUtils,
isInvoiceReport as isInvoiceReportUtils,
isReportFieldDisabledForUser,
isReportFieldTargetMatchingReport,
isSettled as isSettledReportUtils,
shouldHideSingleReportField,
} from '@libs/ReportUtils';
Expand Down Expand Up @@ -146,7 +147,7 @@ function MoneyReportView({
const {sortedPolicyReportFields, fieldValues, fieldsByName} = useMemo(() => {
const {fieldValues: values, fieldsByName: byName} = getReportFieldMaps(report, policy?.fieldList ?? {});
const sorted = Object.values(byName)
.filter((field) => field.target === report?.type)
.filter((field) => isReportFieldTargetMatchingReport(report, field))
.sort(({orderWeight: a}, {orderWeight: b}) => a - b);
return {sortedPolicyReportFields: sorted, fieldValues: values, fieldsByName: byName};
}, [policy?.fieldList, report]);
Expand All @@ -156,7 +157,8 @@ function MoneyReportView({
const isGroupPolicyExpenseReport = isGroupPolicyExpenseReportUtils(report, policy?.type);
const isInvoiceReport = isInvoiceReportUtils(report);

const shouldShowReportField = !isClosedExpenseReportWithNoExpenses && (isGroupPolicyExpenseReport || isInvoiceReport) && !!policy?.areReportFieldsEnabled && !isOnlyTitleFieldEnabled;
const areFieldsEnabledForReport = isInvoiceReport ? policy?.areInvoiceFieldsEnabled : policy?.areReportFieldsEnabled;
const shouldShowReportField = !isClosedExpenseReportWithNoExpenses && (isGroupPolicyExpenseReport || isInvoiceReport) && !!areFieldsEnabledForReport && !isOnlyTitleFieldEnabled;

const hasPendingAction = transactions.some(getTransactionPendingAction);

Expand Down Expand Up @@ -184,7 +186,7 @@ function MoneyReportView({
{!isClosedExpenseReportWithNoExpenses && (
<>
{(isGroupPolicyExpenseReport || isInvoiceReport) &&
!!policy?.areReportFieldsEnabled &&
areFieldsEnabledForReport &&
(!isCombinedReport || !isOnlyTitleFieldEnabled) &&
sortedPolicyReportFields.map((reportField) => {
if (shouldHideSingleReportField(reportField)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ type WorkspaceReportFieldListValuesTableProps = {
selectionEnabled: boolean;
selectedKeys: string[];
onRowSelectionChange: (selectedRowKeys: string[]) => void;
isInvoicePage: boolean;
};

export default function WorkspaceReportFieldListValuesTable({listValues, selectionEnabled, selectedKeys, onRowSelectionChange}: WorkspaceReportFieldListValuesTableProps) {
export default function WorkspaceReportFieldListValuesTable({listValues, selectionEnabled, selectedKeys, onRowSelectionChange, isInvoicePage}: WorkspaceReportFieldListValuesTableProps) {
const styles = useThemeStyles();
const {translate, localeCompare} = useLocalize();
const illustrations = useMemoizedLazyIllustrations(['FolderWithPapers']);
const findFieldKey = isInvoicePage ? 'workspace.invoiceFields.findInvoiceField' : 'workspace.reportFields.findReportField';
const emptyValuesSubtitleKey = isInvoicePage ? 'workspace.invoiceFields.emptyInvoiceFieldsValues.subtitle' : 'workspace.reportFields.emptyReportFieldsValues.subtitle';

const columns: Array<TableColumn<ReportFieldListValueColumnKey>> = [
{
Expand Down Expand Up @@ -103,10 +106,10 @@ export default function WorkspaceReportFieldListValuesTable({listValues, selecti
keyExtractor={(item) => item.keyForList}
onRowSelectionChange={onRowSelectionChange}
>
<Table.FilterBar label={translate('workspace.reportFields.findReportField')} />
<Table.FilterBar label={translate(findFieldKey)} />
<Table.EmptyState
title={translate('workspace.reportFields.emptyReportFieldsValues.title')}
subtitle={translate('workspace.reportFields.emptyReportFieldsValues.subtitle')}
subtitle={translate(emptyValuesSubtitleKey)}
headerMedia={illustrations.FolderWithPapers}
headerStyles={styles.emptyStateCardIllustrationContainer}
headerContentStyles={styles.emptyStateFolderWithPaperIconSize}
Expand Down
30 changes: 30 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4469,6 +4469,7 @@ ${amount} für ${merchant} – ${date}`,
customFieldHint: 'Füge benutzerdefinierte Codierung hinzu, die für alle Ausgaben dieses Mitglieds gilt.',
reports: 'Berichte',
reportFields: 'Berichtsfelder',
invoiceFields: 'Rechnungsfelder',
reportTitle: 'Berichtstitel',
reportField: 'Berichts­feld',
taxes: 'Steuern',
Expand Down Expand Up @@ -6294,6 +6295,29 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
reportFieldInitialValueRequiredError: 'Bitte wähle einen Anfangswert für ein Berichtsfeld aus',
genericFailureMessage: 'Beim Aktualisieren des Berichtfelds ist ein Fehler aufgetreten. Bitte versuche es erneut.',
},
invoiceFields: {
subtitle: 'Rechnungsfelder können hilfreich sein, wenn du zusätzliche Informationen einfügen möchtest.',
importedFromAccountingSoftware: 'Die folgenden Rechnungsfelder werden importiert aus Ihrem',
disableInvoiceFields: 'Rechnungsfelder deaktivieren',
disableInvoiceFieldsConfirmation: 'Sind Sie sicher? Rechnungsfelder werden auf Rechnungen deaktiviert.',
delete: 'Rechnungsfeld löschen',
deleteConfirmation: 'Sind Sie sicher, dass Sie dieses Rechnungsfeld löschen möchten?',
findInvoiceField: 'Rechnungsfeld suchen',
nameInputSubtitle: 'Wähle einen Namen für das Rechnungsfeld.',
typeInputSubtitle: 'Wähle aus, welcher Rechnungsfeldtyp verwendet werden soll.',
initialValueInputSubtitle: 'Gib einen Startwert ein, der im Rechnungsfeld angezeigt werden soll.',
listValuesInputSubtitle: 'Diese Werte werden im Dropdown-Menü des Rechnungsfelds angezeigt. Aktivierte Werte können von Mitgliedern ausgewählt werden.',
listInputSubtitle: 'Diese Werte werden in der Rechnungsfeldliste angezeigt. Aktivierte Werte können von Mitgliedern ausgewählt werden.',
emptyInvoiceFieldsValues: {
title: 'Noch keine Listenwerte',
subtitle: 'Füge benutzerdefinierte Werte hinzu, die auf Rechnungen angezeigt werden.',
},
existingInvoiceFieldNameError: 'Ein Rechnungsfeld mit diesem Namen existiert bereits',
invoiceFieldNameRequiredError: 'Bitte gib einen Namen für das Rechnungsfeld ein',
invoiceFieldTypeRequiredError: 'Bitte wähle einen Rechnungsfeldtyp aus',
invoiceFieldInitialValueRequiredError: 'Bitte wähle einen Anfangswert für ein Rechnungsfeld aus',
addField: 'Feld hinzufügen',
},
tags: {
tagName: 'Tag-Name',
requiresTag: 'Mitglieder müssen alle Ausgaben taggen',
Expand Down Expand Up @@ -7151,6 +7175,12 @@ ${reportName}`,
onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) =>
`<muted-text>Berichtsfelder sind nur im Control-Tarif verfügbar, ab <strong>${formattedPrice}</strong> ${hasTeam2025Pricing ? `pro Mitglied und Monat.` : `pro aktivem Mitglied und Monat.`}</muted-text>`,
},
invoiceFields: {
title: 'Rechnungsfelder',
description: `Mit Rechnungsfeldern kannst du zusätzliche Details auf Rechnungsebene in Rechnungen aufnehmen.`,
onlyAvailableOnPlan: ({formattedPrice, hasTeam2025Pricing}: {formattedPrice: string; hasTeam2025Pricing: boolean}) =>
`<muted-text>Rechnungsfelder sind nur im Control-Tarif verfügbar, ab <strong>${formattedPrice}</strong> ${hasTeam2025Pricing ? `pro Mitglied und Monat.` : `pro aktivem Mitglied und Monat.`}</muted-text>`,
},
[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]: {
title: 'NetSuite',
description: `Profitiere von automatischer Synchronisierung und reduziere manuelle Eingaben mit der Expensify + NetSuite-Integration. Gewinne detaillierte Finanzanalysen in Echtzeit mit nativer und benutzerdefinierter Segmentunterstützung, einschließlich Projekt- und Kundenzuordnung.`,
Expand Down
Loading
Loading