From 9b74993203a781dc7f043a29ba734a9914f1c98e Mon Sep 17 00:00:00 2001 From: aswin-s Date: Sat, 9 May 2026 21:44:58 +0530 Subject: [PATCH 1/3] Fix View payment history to show only billing owner's receipts Add from:me filter to scope results to the current user and explicitly list all expense statuses including Deleted so billing owner's own receipts are not excluded. Fixes https://github.com/Expensify/App/issues/89402 --- .../settings/Subscription/CardSection/CardSection.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 074e3b68dea1..701daf0ee83f 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -100,8 +100,17 @@ function CardSection() { const viewPurchases = () => { const query = buildQueryStringFromFilterFormValues({ type: CONST.SEARCH.DATA_TYPES.EXPENSE, - status: CONST.SEARCH.STATUS.EXPENSE.ALL, + status: [ + CONST.SEARCH.STATUS.EXPENSE.UNREPORTED, + CONST.SEARCH.STATUS.EXPENSE.DRAFTS, + CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING, + CONST.SEARCH.STATUS.EXPENSE.APPROVED, + CONST.SEARCH.STATUS.EXPENSE.DONE, + CONST.SEARCH.STATUS.EXPENSE.PAID, + CONST.SEARCH.STATUS.EXPENSE.DELETED, + ], merchant: CONST.EXPENSIFY_MERCHANT, + from: [CONST.SEARCH.ME], }); Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query, rawQuery: query})); From 0220235138f0a7631510bf749806bc9fe8b76535 Mon Sep 17 00:00:00 2001 From: aswin-s Date: Tue, 12 May 2026 01:12:02 +0530 Subject: [PATCH 2/3] Resolve from:me filter display in search chips and Filters panel The 'me' keyword in user-based filters (from, to, assignee, exporter) was not being resolved to the current user's account ID when populating the Filters form and display chips, causing the From field to appear empty. This resolves 'me' to the current user's account ID in buildFilterFormValuesFromQuery and handles the 'me' keyword directly in getFilterDisplayValue. --- src/hooks/useFilterFormValues.tsx | 15 ++++++++++++++- src/libs/SearchQueryUtils.ts | 7 ++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/hooks/useFilterFormValues.tsx b/src/hooks/useFilterFormValues.tsx index dddb5db7b76e..90f711cad151 100644 --- a/src/hooks/useFilterFormValues.tsx +++ b/src/hooks/useFilterFormValues.tsx @@ -11,6 +11,7 @@ import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {Policy, PolicyCategories, PolicyTagLists, Report} from '@src/types/onyx'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; import {useCurrencyListState} from './useCurrencyList'; +import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; import useExportedToFilterOptions from './useExportedToFilterOptions'; import useOnyx from './useOnyx'; @@ -84,6 +85,7 @@ function policyTagsSelector(tags: OnyxCollection): OnyxCollectio const useFilterFormValues = (queryJSON?: SearchQueryJSON) => { const personalDetails = usePersonalDetails(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const {currencyList} = useCurrencyListState(); const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST); @@ -99,7 +101,18 @@ const useFilterFormValues = (queryJSON?: SearchQueryJSON) => { const {exportedToFilterOptions} = useExportedToFilterOptions(); const formValues = queryJSON - ? buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, allCards, allReports, taxRates, exportedToFilterOptions) + ? buildFilterFormValuesFromQuery( + queryJSON, + policyCategories, + policyTagsLists, + currencyList, + personalDetails, + allCards, + allReports, + taxRates, + exportedToFilterOptions, + currentUserPersonalDetails.accountID, + ) : getEmptyObject>(); return formValues; diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index a1812094e501..fa63e7f4d20f 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -1062,6 +1062,7 @@ function buildFilterFormValuesFromQuery( reports: OnyxCollection, taxRates: Record, exportedToFilterOptions?: string[], + currentUserAccountID?: number, ) { const filters = queryJSON.flatFilters; const filtersForm = {} as Partial; @@ -1128,7 +1129,8 @@ function buildFilterFormValuesFromQuery( filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.ASSIGNEE || filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTER ) { - filtersForm[key as typeof filterKey] = filterValues.filter((id) => personalDetails?.[id]); + const resolvedValues = filterValues.map((id) => (id === CONST.SEARCH.ME && currentUserAccountID ? currentUserAccountID.toString() : id)); + filtersForm[key as typeof filterKey] = resolvedValues.filter((id) => personalDetails?.[id]); } if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.ATTENDEE) { // Don't filter attendee values by personalDetails - they can be accountIDs OR display names for name-only attendees @@ -1437,6 +1439,9 @@ function getFilterDisplayValue({ filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTER || filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.ATTENDEE ) { + if (filterValue === CONST.SEARCH.ME) { + return CONST.SEARCH.ME; + } return filterValue === currentUserAccountID.toString() ? CONST.SEARCH.ME : getDisplayNameOrDefault(personalDetails?.[filterValue], filterValue, false); } if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID) { From f4ed6b9e0bcde33e561477553bab6208e6288a01 Mon Sep 17 00:00:00 2001 From: aswin-s Date: Tue, 12 May 2026 01:58:06 +0530 Subject: [PATCH 3/3] Add unit tests for from:me resolution in search filters Tests buildFilterFormValuesFromQuery resolving 'me' to account ID for from/to filters, and getFilterDisplayValue returning 'me' for the literal keyword. --- tests/unit/Search/SearchQueryUtilsTest.ts | 123 ++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/tests/unit/Search/SearchQueryUtilsTest.ts b/tests/unit/Search/SearchQueryUtilsTest.ts index 71f3d71667e6..27fe0613224f 100644 --- a/tests/unit/Search/SearchQueryUtilsTest.ts +++ b/tests/unit/Search/SearchQueryUtilsTest.ts @@ -1353,6 +1353,111 @@ describe('SearchQueryUtils', () => { // invalid should be filtered out, cash and card are valid CONST.SEARCH.TRANSACTION_TYPE values expect(result.expenseType).toEqual(['cash', 'card']); }); + + test('from:me resolves to current user account ID when currentUserAccountID is provided', () => { + const currentUserAccountID = 12345; + const queryString = 'type:expense from:me'; + const queryJSON = buildSearchQueryJSON(queryString); + + const policyCategories = {}; + const policyTags = {}; + const currencyList = {}; + const personalDetails = { + '12345': { + accountID: 12345, + login: 'testuser@example.com', + displayName: 'Test User', + }, + }; + const cardList = {}; + const reports = {}; + const taxRates = {}; + + if (!queryJSON) { + throw new Error('Failed to parse query string'); + } + + const result = buildFilterFormValuesFromQuery( + queryJSON, + policyCategories, + policyTags, + currencyList, + personalDetails, + cardList, + reports, + taxRates, + undefined, + currentUserAccountID, + ); + + expect(result.from).toEqual(['12345']); + }); + + test('from:me is filtered out when currentUserAccountID is not provided', () => { + const queryString = 'type:expense from:me'; + const queryJSON = buildSearchQueryJSON(queryString); + + const policyCategories = {}; + const policyTags = {}; + const currencyList = {}; + const personalDetails = { + '12345': { + accountID: 12345, + login: 'testuser@example.com', + displayName: 'Test User', + }, + }; + const cardList = {}; + const reports = {}; + const taxRates = {}; + + if (!queryJSON) { + throw new Error('Failed to parse query string'); + } + + const result = buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTags, currencyList, personalDetails, cardList, reports, taxRates); + + expect(result.from).toEqual([]); + }); + + test('to:me resolves to current user account ID', () => { + const currentUserAccountID = 99999; + const queryString = 'type:expense to:me'; + const queryJSON = buildSearchQueryJSON(queryString); + + const policyCategories = {}; + const policyTags = {}; + const currencyList = {}; + const personalDetails = { + '99999': { + accountID: 99999, + login: 'otheruser@example.com', + displayName: 'Other User', + }, + }; + const cardList = {}; + const reports = {}; + const taxRates = {}; + + if (!queryJSON) { + throw new Error('Failed to parse query string'); + } + + const result = buildFilterFormValuesFromQuery( + queryJSON, + policyCategories, + policyTags, + currencyList, + personalDetails, + cardList, + reports, + taxRates, + undefined, + currentUserAccountID, + ); + + expect(result.to).toEqual(['99999']); + }); }); describe('shouldHighlight', () => { @@ -1699,6 +1804,24 @@ describe('SearchQueryUtils', () => { expect(result).toBe(CONST.SEARCH.ME); }); + it('should return "me" when filterValue is the literal "me" keyword', () => { + const personalDetails = {}; + + const result = getFilterDisplayValue({ + filterName: CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM, + filterValue: CONST.SEARCH.ME, + personalDetails, + reports: mockReports, + cardList: mockCardList, + cardFeeds: mockCardFeeds, + policies: mockPolicies, + currentUserAccountID, + translate: translateLocal, + }); + + expect(result).toBe(CONST.SEARCH.ME); + }); + it('should return fallback value when personal details not found', () => { const personalDetails = {};