Skip to content

Commit 132a6d2

Browse files
committed
fix: bulk edit, disable tags edit for non WS expenses
1 parent cc258b5 commit 132a6d2

3 files changed

Lines changed: 65 additions & 10 deletions

File tree

src/pages/Search/SearchEditMultiple/SearchEditMultiplePage.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
2525
import ROUTES from '@src/ROUTES';
2626
import type {Route} from '@src/ROUTES';
2727
import type {TransactionChanges} from '@src/types/onyx/Transaction';
28-
import {getTransactionEditContext, withSnapshotReportActions, withSnapshotReports, withSnapshotTransactions} from './SearchEditMultipleUtils';
28+
import {areAllTransactionsExpenseCompatible, getTransactionEditContext, withSnapshotReportActions, withSnapshotReports, withSnapshotTransactions} from './SearchEditMultipleUtils';
2929

3030
function SearchEditMultiplePage() {
3131
const {translate} = useLocalize();
@@ -102,14 +102,9 @@ function SearchEditMultiplePage() {
102102
const policyTagLists = getTagLists(policyTags);
103103

104104
const isTaxTrackingEnabled = !hasPerDiemOrTimeTransaction && selectedTransactionContexts.every(({transactionPolicy}) => !!transactionPolicy?.tax?.trackingEnabled);
105-
const areSelectedTransactionsExpenses = selectedTransactionContexts.every(({transaction, report}) => {
106-
if (!transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
107-
return true;
108-
}
109-
return !isIOUReport(report);
110-
});
105+
const areSelectedTransactionsExpenses = areAllTransactionsExpenseCompatible(selectedTransactionContexts);
111106
const areCategoriesEnabled = areSelectedTransactionsExpenses && !!policy?.areCategoriesEnabled && hasEnabledOptions(policyCategories ?? {});
112-
const areTagsEnabled = !!policy?.areTagsEnabled && hasEnabledTags(policyTagLists);
107+
const areTagsEnabled = areSelectedTransactionsExpenses && !!policy?.areTagsEnabled && hasEnabledTags(policyTagLists);
113108

114109
useEffect(() => {
115110
return () => {

src/pages/Search/SearchEditMultiple/SearchEditMultipleUtils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
22
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
3+
import {isIOUReport} from '@libs/ReportUtils';
34
import {getTagArrayFromName} from '@libs/TransactionUtils';
5+
import CONST from '@src/CONST';
46
import ONYXKEYS from '@src/ONYXKEYS';
57
import type {Policy, Report, ReportActions, SearchResults, Transaction} from '@src/types/onyx';
68

@@ -60,6 +62,19 @@ function getTransactionEditContext(
6062
return {transaction, report, reportAction, transactionPolicy};
6163
}
6264

65+
/**
66+
* Category/Tag/Tax only apply to expense/invoice reports and unreported (track) expenses.
67+
* Returns true only when every selected transaction is eligible.
68+
*/
69+
function areAllTransactionsExpenseCompatible(selectedTransactionContexts: Array<{transaction: Transaction; report: OnyxEntry<Report>}>): boolean {
70+
return selectedTransactionContexts.every(({transaction, report}) => {
71+
if (!transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
72+
return true;
73+
}
74+
return !isIOUReport(report);
75+
});
76+
}
77+
6378
/**
6479
* After a hard refresh, transaction/report/reportAction data may only exist in the search snapshot,
6580
* not in the main Onyx collections. These helpers fill gaps from the snapshot so bulk edit can work.
@@ -113,4 +128,4 @@ function withSnapshotReports(onyxReports: OnyxCollection<Report> | undefined, sn
113128
return merged;
114129
}
115130

116-
export {getCommonDependentTag, getTransactionEditContext, withSnapshotTransactions, withSnapshotReportActions, withSnapshotReports};
131+
export {areAllTransactionsExpenseCompatible, getCommonDependentTag, getTransactionEditContext, withSnapshotTransactions, withSnapshotReportActions, withSnapshotReports};

tests/unit/SearchEditMultipleUtilsTest.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {getSearchBulkEditPolicyID} from '@libs/SearchUIUtils';
2+
import CONST from '@src/CONST';
23
import ONYXKEYS from '@src/ONYXKEYS';
34
import type {Report, SearchResults, Transaction} from '@src/types/onyx';
4-
import {withSnapshotReports, withSnapshotTransactions} from '../../src/pages/Search/SearchEditMultiple/SearchEditMultipleUtils';
5+
import {areAllTransactionsExpenseCompatible, withSnapshotReports, withSnapshotTransactions} from '../../src/pages/Search/SearchEditMultiple/SearchEditMultipleUtils';
56

67
const POLICY_A = 'policyA';
78
const POLICY_B = 'policyB';
@@ -130,4 +131,48 @@ describe('SearchEditMultipleUtils', () => {
130131
expect(result).toBe(POLICY_A);
131132
});
132133
});
134+
135+
describe('areAllTransactionsExpenseCompatible', () => {
136+
const expenseReport = {reportID: 'expenseReport1', type: CONST.REPORT.TYPE.EXPENSE} as Report;
137+
const iouReport = {reportID: 'iouReport1', type: CONST.REPORT.TYPE.IOU} as Report;
138+
const invoiceReport = {reportID: 'invoiceReport1', type: CONST.REPORT.TYPE.INVOICE} as Report;
139+
140+
it('returns true when every reported transaction is on an expense report', () => {
141+
const contexts = [
142+
{transaction: makeTransaction(TRANSACTION_ID_1, 'expenseReport1'), report: expenseReport},
143+
{transaction: makeTransaction(TRANSACTION_ID_2, 'expenseReport1'), report: expenseReport},
144+
];
145+
expect(areAllTransactionsExpenseCompatible(contexts)).toBe(true);
146+
});
147+
148+
it('returns true for unreported (track) transactions', () => {
149+
const contexts = [{transaction: makeTransaction(TRANSACTION_ID_1, CONST.REPORT.UNREPORTED_REPORT_ID), report: undefined}];
150+
expect(areAllTransactionsExpenseCompatible(contexts)).toBe(true);
151+
});
152+
153+
it('returns false when any reported transaction is on an IOU report', () => {
154+
const contexts = [
155+
{transaction: makeTransaction(TRANSACTION_ID_1, 'expenseReport1'), report: expenseReport},
156+
{transaction: makeTransaction(TRANSACTION_ID_2, 'iouReport1'), report: iouReport},
157+
];
158+
expect(areAllTransactionsExpenseCompatible(contexts)).toBe(false);
159+
});
160+
161+
it('returns false when a mix of unreported and IOU transactions is selected', () => {
162+
const contexts = [
163+
{transaction: makeTransaction(TRANSACTION_ID_1, CONST.REPORT.UNREPORTED_REPORT_ID), report: undefined},
164+
{transaction: makeTransaction(TRANSACTION_ID_2, 'iouReport1'), report: iouReport},
165+
];
166+
expect(areAllTransactionsExpenseCompatible(contexts)).toBe(false);
167+
});
168+
169+
it('returns true for invoice reports (not IOU)', () => {
170+
const contexts = [{transaction: makeTransaction(TRANSACTION_ID_1, 'invoiceReport1'), report: invoiceReport}];
171+
expect(areAllTransactionsExpenseCompatible(contexts)).toBe(true);
172+
});
173+
174+
it('returns true for an empty selection', () => {
175+
expect(areAllTransactionsExpenseCompatible([])).toBe(true);
176+
});
177+
});
133178
});

0 commit comments

Comments
 (0)