Skip to content

Commit 8756f7a

Browse files
authored
Merge pull request Expensify#64819 from nkdengineer/fix/64128
fix: This report has no expenses after splitting expense
2 parents 15aa1ad + eb08ca8 commit 8756f7a

4 files changed

Lines changed: 104 additions & 34 deletions

File tree

src/libs/SearchQueryUtils.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import {isValidDate} from './ValidationUtils';
4040

4141
type FilterKeys = keyof typeof CONST.SEARCH.SYNTAX_FILTER_KEYS;
4242

43+
type TodoSearchType = typeof CONST.SEARCH.SEARCH_KEYS.SUBMIT | typeof CONST.SEARCH.SEARCH_KEYS.APPROVE | typeof CONST.SEARCH.SEARCH_KEYS.PAY | typeof CONST.SEARCH.SEARCH_KEYS.EXPORT;
44+
4345
// This map contains chars that match each operator
4446
const operatorToCharMap = {
4547
[CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO]: ':' as const,
@@ -960,6 +962,43 @@ function getCurrentSearchQueryJSON() {
960962
return queryJSON;
961963
}
962964

965+
function getTodoSearchQuery(action: TodoSearchType, userAccountID: number | undefined) {
966+
switch (action) {
967+
case CONST.SEARCH.SEARCH_LIST.SUBMIT:
968+
return buildQueryStringFromFilterFormValues({
969+
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
970+
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
971+
status: CONST.SEARCH.STATUS.EXPENSE.DRAFTS,
972+
from: [`${userAccountID}`],
973+
});
974+
case CONST.SEARCH.SEARCH_LIST.APPROVE:
975+
return buildQueryStringFromFilterFormValues({
976+
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
977+
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
978+
action: CONST.SEARCH.ACTION_FILTERS.APPROVE,
979+
to: [`${userAccountID}`],
980+
});
981+
case CONST.SEARCH.SEARCH_LIST.PAY:
982+
return buildQueryStringFromFilterFormValues({
983+
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
984+
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
985+
action: CONST.SEARCH.ACTION_FILTERS.PAY,
986+
reimbursable: CONST.SEARCH.BOOLEAN.YES,
987+
payer: userAccountID?.toString(),
988+
});
989+
case CONST.SEARCH.SEARCH_LIST.EXPORT:
990+
return buildQueryStringFromFilterFormValues({
991+
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
992+
action: CONST.SEARCH.ACTION_FILTERS.EXPORT,
993+
exporter: [`${userAccountID}`],
994+
exportedOn: CONST.SEARCH.DATE_PRESETS.NEVER,
995+
});
996+
997+
default:
998+
return '';
999+
}
1000+
}
1001+
9631002
/**
9641003
* Extracts the query text without the filter parts.
9651004
* This is used to determine if a user's core search terms have changed,
@@ -1028,4 +1067,5 @@ export {
10281067
sortOptionsWithEmptyValue,
10291068
shouldHighlight,
10301069
getAllPolicyValues,
1070+
getTodoSearchQuery,
10311071
};

src/libs/SearchUIUtils.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ import {
7878
isOpenExpenseReport,
7979
isSettled,
8080
} from './ReportUtils';
81-
import {buildCannedSearchQuery, buildQueryStringFromFilterFormValues, buildSearchQueryJSON} from './SearchQueryUtils';
81+
import {buildCannedSearchQuery, buildQueryStringFromFilterFormValues, buildSearchQueryJSON, getTodoSearchQuery} from './SearchQueryUtils';
8282
import StringUtils from './StringUtils';
8383
import {shouldRestrictUserBillableActions} from './SubscriptionUtils';
8484
import {
@@ -268,12 +268,7 @@ function getSuggestedSearches(defaultFeedID: string | undefined, accountID: numb
268268
translationPath: 'common.submit',
269269
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
270270
icon: Expensicons.Pencil,
271-
searchQuery: buildQueryStringFromFilterFormValues({
272-
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
273-
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
274-
status: CONST.SEARCH.STATUS.EXPENSE.DRAFTS,
275-
from: [`${accountID}`],
276-
}),
271+
searchQuery: getTodoSearchQuery(CONST.SEARCH.SEARCH_LIST.SUBMIT, accountID),
277272
get hash() {
278273
return buildSearchQueryJSON(this.searchQuery)?.hash ?? CONST.DEFAULT_NUMBER_ID;
279274
},
@@ -283,12 +278,7 @@ function getSuggestedSearches(defaultFeedID: string | undefined, accountID: numb
283278
translationPath: 'search.bulkActions.approve',
284279
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
285280
icon: Expensicons.ThumbsUp,
286-
searchQuery: buildQueryStringFromFilterFormValues({
287-
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
288-
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
289-
action: CONST.SEARCH.ACTION_FILTERS.APPROVE,
290-
to: [`${accountID}`],
291-
}),
281+
searchQuery: getTodoSearchQuery(CONST.SEARCH.SEARCH_LIST.APPROVE, accountID),
292282
get hash() {
293283
return buildSearchQueryJSON(this.searchQuery)?.hash ?? CONST.DEFAULT_NUMBER_ID;
294284
},
@@ -298,13 +288,7 @@ function getSuggestedSearches(defaultFeedID: string | undefined, accountID: numb
298288
translationPath: 'search.bulkActions.pay',
299289
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
300290
icon: Expensicons.MoneyBag,
301-
searchQuery: buildQueryStringFromFilterFormValues({
302-
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
303-
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
304-
action: CONST.SEARCH.ACTION_FILTERS.PAY,
305-
reimbursable: CONST.SEARCH.BOOLEAN.YES,
306-
payer: `${accountID}`,
307-
}),
291+
searchQuery: getTodoSearchQuery(CONST.SEARCH.SEARCH_LIST.PAY, accountID),
308292
get hash() {
309293
return buildSearchQueryJSON(this.searchQuery)?.hash ?? CONST.DEFAULT_NUMBER_ID;
310294
},
@@ -314,13 +298,7 @@ function getSuggestedSearches(defaultFeedID: string | undefined, accountID: numb
314298
translationPath: 'common.export',
315299
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
316300
icon: Expensicons.CheckCircle,
317-
searchQuery: buildQueryStringFromFilterFormValues({
318-
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
319-
groupBy: CONST.SEARCH.GROUP_BY.REPORTS,
320-
action: CONST.SEARCH.ACTION_FILTERS.EXPORT,
321-
exporter: [`${accountID}`],
322-
exportedOn: CONST.SEARCH.DATE_PRESETS.NEVER,
323-
}),
301+
searchQuery: getTodoSearchQuery(CONST.SEARCH.SEARCH_LIST.EXPORT, accountID),
324302
get hash() {
325303
return buildSearchQueryJSON(this.searchQuery)?.hash ?? CONST.DEFAULT_NUMBER_ID;
326304
},

src/libs/actions/IOU.ts

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Onyx from 'react-native-onyx';
77
import type {PartialDeep, SetRequired, ValueOf} from 'type-fest';
88
import ReceiptGeneric from '@assets/images/receipt-generic.png';
99
import type {PaymentMethod} from '@components/KYCWall/types';
10+
import type {SearchQueryJSON} from '@components/Search/types';
1011
import * as API from '@libs/API';
1112
import type {
1213
ApproveMoneyRequestParams,
@@ -178,7 +179,7 @@ import {
178179
shouldCreateNewMoneyRequestReport as shouldCreateNewMoneyRequestReportReportUtils,
179180
updateReportPreview,
180181
} from '@libs/ReportUtils';
181-
import {getCurrentSearchQueryJSON} from '@libs/SearchQueryUtils';
182+
import {buildSearchQueryJSON, getCurrentSearchQueryJSON, getTodoSearchQuery} from '@libs/SearchQueryUtils';
182183
import {getSession} from '@libs/SessionUtils';
183184
import playSound, {SOUNDS} from '@libs/Sound';
184185
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
@@ -11503,6 +11504,59 @@ function resolveDuplicates(params: MergeDuplicatesParams) {
1150311504
API.write(WRITE_COMMANDS.RESOLVE_DUPLICATES, parameters, {optimisticData, failureData});
1150411505
}
1150511506

11507+
const expenseReportStatusFilterMapping = {
11508+
[CONST.SEARCH.STATUS.EXPENSE.DRAFTS]: (expenseReport: OnyxEntry<OnyxTypes.Report>) =>
11509+
expenseReport?.stateNum === CONST.REPORT.STATE_NUM.OPEN && expenseReport?.statusNum === CONST.REPORT.STATUS_NUM.OPEN,
11510+
[CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING]: (expenseReport: OnyxEntry<OnyxTypes.Report>) =>
11511+
expenseReport?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && expenseReport?.statusNum === CONST.REPORT.STATUS_NUM.SUBMITTED,
11512+
[CONST.SEARCH.STATUS.EXPENSE.APPROVED]: (expenseReport: OnyxEntry<OnyxTypes.Report>) =>
11513+
expenseReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && expenseReport?.statusNum === CONST.REPORT.STATUS_NUM.APPROVED,
11514+
[CONST.SEARCH.STATUS.EXPENSE.PAID]: (expenseReport: OnyxEntry<OnyxTypes.Report>) =>
11515+
(expenseReport?.stateNum ?? 0) >= CONST.REPORT.STATE_NUM.APPROVED && expenseReport?.statusNum === CONST.REPORT.STATUS_NUM.REIMBURSED,
11516+
[CONST.SEARCH.STATUS.EXPENSE.DONE]: (expenseReport: OnyxEntry<OnyxTypes.Report>) =>
11517+
expenseReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && expenseReport?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED,
11518+
[CONST.SEARCH.STATUS.EXPENSE.UNREPORTED]: (expenseReport: OnyxEntry<OnyxTypes.Report>) => !expenseReport,
11519+
[CONST.SEARCH.STATUS.EXPENSE.ALL]: () => true,
11520+
};
11521+
11522+
// Determines whether the current search results should be optimistically updated
11523+
function shouldOptimisticallyUpdateSearch(currentSearchQueryJSON: SearchQueryJSON, iouReport: OnyxEntry<OnyxTypes.Report>, isInvoice: boolean | undefined) {
11524+
if (currentSearchQueryJSON.type !== CONST.SEARCH.DATA_TYPES.INVOICE && currentSearchQueryJSON.type !== CONST.SEARCH.DATA_TYPES.EXPENSE) {
11525+
return false;
11526+
}
11527+
let shouldOptimisticallyUpdateByStatus;
11528+
const status = currentSearchQueryJSON.status;
11529+
if (Array.isArray(status)) {
11530+
shouldOptimisticallyUpdateByStatus = status.some((val) => {
11531+
const expenseStatus = val as ValueOf<typeof CONST.SEARCH.STATUS.EXPENSE>;
11532+
return expenseReportStatusFilterMapping[expenseStatus](iouReport);
11533+
});
11534+
} else {
11535+
const expenseStatus = status as ValueOf<typeof CONST.SEARCH.STATUS.EXPENSE>;
11536+
shouldOptimisticallyUpdateByStatus = expenseReportStatusFilterMapping[expenseStatus](iouReport);
11537+
}
11538+
11539+
if (!shouldOptimisticallyUpdateByStatus) {
11540+
return false;
11541+
}
11542+
11543+
const submitQueryString = getTodoSearchQuery(CONST.SEARCH.SEARCH_LIST.SUBMIT, userAccountID);
11544+
11545+
const submitQueryJSON = buildSearchQueryJSON(submitQueryString);
11546+
11547+
const approveQueryString = getTodoSearchQuery(CONST.SEARCH.SEARCH_LIST.APPROVE, userAccountID);
11548+
const approveQueryJSON = buildSearchQueryJSON(approveQueryString);
11549+
11550+
const validSearchTypes =
11551+
(!isInvoice && currentSearchQueryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE) || (isInvoice && currentSearchQueryJSON.type === CONST.SEARCH.DATA_TYPES.INVOICE);
11552+
11553+
return (
11554+
shouldOptimisticallyUpdateByStatus &&
11555+
validSearchTypes &&
11556+
(currentSearchQueryJSON.flatFilters.length === 0 || [submitQueryJSON?.hash, approveQueryJSON?.hash].includes(currentSearchQueryJSON.hash))
11557+
);
11558+
}
11559+
1150611560
function getSearchOnyxUpdate({
1150711561
participant,
1150811562
transaction,
@@ -11517,11 +11571,7 @@ function getSearchOnyxUpdate({
1151711571
const currentSearchQueryJSON = getCurrentSearchQueryJSON();
1151811572

1151911573
if (currentSearchQueryJSON && toAccountID != null && fromAccountID != null) {
11520-
const validSearchTypes =
11521-
(!isInvoice && currentSearchQueryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE) || (isInvoice && currentSearchQueryJSON.type === CONST.SEARCH.DATA_TYPES.INVOICE);
11522-
const shouldOptimisticallyUpdate = currentSearchQueryJSON.status === CONST.SEARCH.STATUS.EXPENSE.ALL && validSearchTypes && currentSearchQueryJSON.flatFilters.length === 0;
11523-
11524-
if (shouldOptimisticallyUpdate) {
11574+
if (shouldOptimisticallyUpdateSearch(currentSearchQueryJSON, iouReport, isInvoice)) {
1152511575
const isOptimisticToAccountData = isOptimisticPersonalDetail(toAccountID);
1152611576
const successData = [];
1152711577
if (isOptimisticToAccountData) {

tests/actions/IOUTest.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,11 @@ jest.mock('@src/libs/SearchQueryUtils', () => ({
126126
hash: 12345,
127127
query: 'test',
128128
type: 'invoice',
129-
status: 'all',
129+
status: '',
130130
flatFilters: [],
131131
})),
132+
getTodoSearchQuery: jest.fn(),
133+
buildSearchQueryJSON: jest.fn(),
132134
}));
133135

134136
const CARLOS_EMAIL = 'cmartins@expensifail.com';

0 commit comments

Comments
 (0)