Skip to content
Closed
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
3 changes: 2 additions & 1 deletion src/hooks/useSearchBulkActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,8 @@ function useSearchBulkActions({queryJSON, deleteTransactionsOnSearch}: UseSearch
const selectedTransactionsList = Object.values(selectedTransactions)
.map((transaction) => transaction.transaction)
.filter((transaction): transaction is Transaction => !!transaction);
const canEditMultiple = canEditMultipleTransactions(selectedTransactionsList, allReportActions, allReports, policies, isExpenseReportSearch) && isBetaEnabled(CONST.BETAS.BULK_EDIT);
const canEditMultiple =
canEditMultipleTransactions(selectedTransactionsList, allReportActions, allReports, policies, isExpenseReportSearch, searchResults?.data) && isBetaEnabled(CONST.BETAS.BULK_EDIT);

if (canEditMultiple) {
options.push({
Expand Down
6 changes: 5 additions & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import type {
ReportMetadata,
ReportNameValuePairs,
ReportViolationName,
SearchResults,
Task,
Transaction,
TransactionViolation,
Expand Down Expand Up @@ -4876,6 +4877,7 @@ function canEditMultipleTransactions(
reports: OnyxCollection<Report>,
policies: OnyxCollection<Policy>,
areReportsSelected = false,
searchSnapshotData?: SearchResults['data'],
): boolean {
if (areReportsSelected) {
return false;
Expand All @@ -4896,7 +4898,9 @@ function canEditMultipleTransactions(
return false;
}

const reportAction = getIOUActionForTransactionID(Object.values(reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction.reportID}`] ?? {}), transaction.transactionID);
const reportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction.reportID}` as const;
const actionsForReport = {...(searchSnapshotData?.[reportActionsKey] ?? {}), ...(reportActions?.[reportActionsKey] ?? {})};
const reportAction = getIOUActionForTransactionID(Object.values(actionsForReport), transaction.transactionID);
const report = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction.reportID}`];
const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`];

Expand Down
4 changes: 2 additions & 2 deletions src/libs/Violations/ViolationsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,12 @@ const ViolationsUtils = {
}

// Remove 'missingCategory' violation if category is valid according to policy
if (hasMissingCategoryViolation && (isCategoryInPolicy || isSelfDM)) {
if (hasMissingCategoryViolation && (isCategoryInPolicy || isSelfDM || isInvoiceTransaction)) {
newTransactionViolations = reject(newTransactionViolations, {name: 'missingCategory'});
}

// Add 'missingCategory' violation if category is required and not set
if (!hasMissingCategoryViolation && policyRequiresCategories && !categoryKey && !isSelfDM) {
if (!hasMissingCategoryViolation && policyRequiresCategories && !categoryKey && !isSelfDM && !isInvoiceTransaction) {
newTransactionViolations.push({name: 'missingCategory', type: CONST.VIOLATION_TYPES.VIOLATION, showInReview: true});
}
}
Expand Down
52 changes: 47 additions & 5 deletions src/pages/Search/SearchEditMultiple/SearchEditMultiplePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import type {OnyxCollection} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand All @@ -24,13 +25,50 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type {ReportActions, SearchResults, Transaction} from '@src/types/onyx';
import type {TransactionChanges} from '@src/types/onyx/Transaction';
import {getTransactionEditContext} from './SearchEditMultipleUtils';

/**
* After a hard refresh, invoice transaction and report action data may only exist in the search snapshot,
* not in the main Onyx collections. These helpers fill gaps from the snapshot so bulk edit can work.
*/
function withSnapshotTransactions(onyxTransactions: OnyxCollection<Transaction> | undefined, snapshotData: SearchResults['data'] | undefined): OnyxCollection<Transaction> | undefined {
if (!snapshotData) {
return onyxTransactions;
}
const merged = {...onyxTransactions};
for (const key of Object.keys(snapshotData)) {
if (!key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)) {
continue;
}
const typedKey = key as `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`;
if (!merged[typedKey]) {
merged[typedKey] = snapshotData[typedKey] ?? null;
}
}
return merged;
}

function withSnapshotReportActions(onyxReportActions: OnyxCollection<ReportActions> | undefined, snapshotData: SearchResults['data'] | undefined): OnyxCollection<ReportActions> | undefined {
if (!snapshotData) {
return onyxReportActions;
}
const merged = {...onyxReportActions};
for (const key of Object.keys(snapshotData)) {
if (!key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS)) {
continue;
}
const typedKey = key as `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS}${string}`;
merged[typedKey] = {...(snapshotData[typedKey] ?? {}), ...(merged[typedKey] ?? {})};
}
return merged;
}

function SearchEditMultiplePage() {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {currentSearchHash} = useSearchStateContext();
const {currentSearchHash, currentSearchResults} = useSearchStateContext();
const {clearSelectedTransactions} = useSearchActionsContext();
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
Expand All @@ -39,10 +77,14 @@ function SearchEditMultiplePage() {
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);

const snapshotData = currentSearchResults?.data;
const mergedTransactions = withSnapshotTransactions(allTransactions, snapshotData);
const mergedReportActions = withSnapshotReportActions(allReportActions, snapshotData);

const selectedTransactionIDs = draftTransaction?.selectedTransactionIDs ?? [];

const selectedTransactionContexts = selectedTransactionIDs.flatMap((transactionID) => {
const context = getTransactionEditContext(transactionID, allTransactions, allReports, allReportActions, policies);
const context = getTransactionEditContext(transactionID, mergedTransactions, allReports, mergedReportActions, policies);
return context ? [context] : [];
});

Expand Down Expand Up @@ -85,7 +127,7 @@ function SearchEditMultiplePage() {
return !isIOUReport(report) && !isInvoiceReport(report) && transactionPolicy?.disabledFields?.reimbursable === false && !isManagedCardTransaction(transaction);
});

const policyID = getSearchBulkEditPolicyID(selectedTransactionIDs, activePolicyID, allTransactions, allReports);
const policyID = getSearchBulkEditPolicyID(selectedTransactionIDs, activePolicyID, mergedTransactions, allReports);

const policy = policyID ? policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] : undefined;
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`);
Expand Down Expand Up @@ -152,8 +194,8 @@ function SearchEditMultiplePage() {
changes,
policy,
reports: allReports,
transactions: allTransactions,
reportActions: allReportActions,
transactions: mergedTransactions,
reportActions: mergedReportActions,
policyCategories,
hash: currentSearchHash,
allPolicies: policies,
Expand Down
Loading