Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3a0033a
Reapply Allow rejecting expenses in bulk in NewDot
lakchote Oct 31, 2025
00d2beb
fix pay button showing
lakchote Oct 31, 2025
fbf83d7
fix held expenses and already rejected transactions
lakchote Oct 31, 2025
9215e10
add `checkBulkRejectHydration()`
lakchote Oct 31, 2025
2b9de1f
fix hydration for Reject
lakchote Oct 31, 2025
2b297ff
remove logic that doesn't work for multi expenses
lakchote Oct 31, 2025
c570d36
fix: restore missing logic
truph01 Nov 12, 2025
5c90caf
fix: education modal isn't shown
truph01 Nov 13, 2025
5259dd2
fix: add new bulk reject API
truph01 Nov 13, 2025
bd85a35
fix: update bulk reject logic
truph01 Nov 13, 2025
475a378
fix: lint
truph01 Nov 13, 2025
9175e78
fix: type
truph01 Nov 13, 2025
3809cfe
fix: rename variable to non negative
truph01 Nov 13, 2025
4f66809
fix: conflict
truph01 Nov 18, 2025
9a715aa
merge main
truph01 Nov 19, 2025
4a31d37
fix: type
truph01 Nov 20, 2025
6dd1cd3
fix: remove redundant change
truph01 Nov 20, 2025
f406e66
fix: remove redundant changes
truph01 Nov 20, 2025
d5e3f3c
fix: conflicts
truph01 Nov 21, 2025
c56ae68
fix: remove redundant change
truph01 Nov 21, 2025
e1a469f
fix: handle optimistic data when bulk reject
truph01 Nov 21, 2025
1ba1c2e
Merge branch 'Expensify:main' into feat/bulk-reject-v2
truph01 Nov 24, 2025
97769b6
fix: lint
truph01 Nov 24, 2025
c45bf2a
fix: lint
truph01 Nov 24, 2025
a1edc60
fix: remove sound when reject
truph01 Nov 24, 2025
728aecc
fix: use reduce instead of foreach
truph01 Nov 24, 2025
782f69d
fix: missing education modal in small screen
truph01 Nov 24, 2025
142ef85
fix: remove disabled prop in submit option
truph01 Nov 24, 2025
f4783eb
fix: conflicts
truph01 Nov 24, 2025
7646239
fix: lint
truph01 Nov 24, 2025
9e04cc0
fix: prettier
truph01 Nov 24, 2025
da3dcbe
fix: remove redundant change
truph01 Nov 24, 2025
886b169
fix: conflicts
truph01 Nov 26, 2025
df7ebba
fix: remove redundant change
truph01 Nov 26, 2025
779420e
fix: lint
truph01 Nov 26, 2025
6f0ad36
fix: update param's type
truph01 Nov 26, 2025
d1b5450
Merge branch 'Expensify:main' into feat/bulk-reject-v2
truph01 Nov 27, 2025
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
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6634,6 +6634,7 @@ const CONST = {
HOLD: 'hold',
UNHOLD: 'unhold',
DELETE: 'delete',
REJECT: 'reject',
CHANGE_REPORT: 'changeReport',
},
TRANSACTION_TYPE: {
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const ROUTES = {
},
},
TRANSACTION_HOLD_REASON_RHP: 'search/hold',
SEARCH_REJECT_REASON_RHP: 'search/reject',
MOVE_TRANSACTIONS_SEARCH_RHP: 'search/move-transactions',

// This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const SCREENS = {
MONEY_REQUEST_REPORT: 'Search_Money_Request_Report',
MONEY_REQUEST_REPORT_VERIFY_ACCOUNT: 'Search_Money_Request_Report_Verify_Account',
MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS: 'Search_Money_Request_Report_Hold_Transactions',
MONEY_REQUEST_REPORT_REJECT_TRANSACTIONS: 'Search_Money_Request_Report_Reject_Transactions',
REPORT_RHP: 'Search_Report_RHP',
REPORT_VERIFY_ACCOUNT: 'Search_Report_Verify_Account',
ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP',
Expand Down Expand Up @@ -91,6 +92,7 @@ const SCREENS = {
SAVED_SEARCH_RENAME_RHP: 'Search_Saved_Search_Rename_RHP',
ADVANCED_FILTERS_IN_RHP: 'Search_Advanced_Filters_In_RHP',
TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP',
SEARCH_REJECT_REASON_RHP: 'Search_Reject_Reason_RHP',
TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP: 'Search_Transactions_Change_Report_RHP',
},
SETTINGS: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6412,6 +6412,7 @@ ${
delete: 'Löschen',
hold: 'Halten',
unhold: 'Halten entfernen',
reject: 'Ablehnen',
noOptionsAvailable: 'Keine Optionen verfügbar für die ausgewählte Gruppe von Ausgaben.',
},
filtersHeader: 'Filter',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6487,6 +6487,7 @@ const translations = {
delete: 'Delete',
hold: 'Hold',
unhold: 'Remove hold',
reject: 'Reject',
noOptionsAvailable: 'No options available for the selected group of expenses.',
},
filtersHeader: 'Filters',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6137,6 +6137,7 @@ ${amount} para ${merchant} - ${date}`,
delete: 'Eliminar',
hold: 'Retener',
unhold: 'Desbloquear',
reject: 'Rechazar',
noOptionsAvailable: 'No hay opciones disponibles para el grupo de gastos seleccionado.',
},
filtersHeader: 'Filtros',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6420,6 +6420,7 @@ ${
delete: 'Supprimer',
hold: 'Attente',
unhold: 'Supprimer la suspension',
reject: 'Refuser',
noOptionsAvailable: 'Aucune option disponible pour le groupe de dépenses sélectionné.',
},
filtersHeader: 'Filtres',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6400,6 +6400,7 @@ ${
delete: 'Elimina',
hold: 'Attendere',
unhold: 'Rimuovi blocco',
reject: 'Rifiuta',
noOptionsAvailable: 'Nessuna opzione disponibile per il gruppo di spese selezionato.',
},
filtersHeader: 'Filtri',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6335,6 +6335,7 @@ ${
delete: '削除',
hold: '保留',
unhold: '保留を解除',
reject: '却下',
noOptionsAvailable: '選択した経費グループには利用可能なオプションがありません。',
},
filtersHeader: 'フィルター',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6382,6 +6382,7 @@ ${
delete: 'Verwijderen',
hold: 'Vasthouden',
unhold: 'Verwijder blokkering',
reject: 'Afwijzen',
noOptionsAvailable: 'Geen opties beschikbaar voor de geselecteerde groep uitgaven.',
},
filtersHeader: 'Filters',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6369,6 +6369,7 @@ ${
delete: 'Usuń',
hold: 'Trzymaj',
unhold: 'Usuń blokadę',
reject: 'Odrzuć',
noOptionsAvailable: 'Brak dostępnych opcji dla wybranej grupy wydatków.',
},
filtersHeader: 'Filtry',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6381,6 +6381,7 @@ ${
delete: 'Excluir',
hold: 'Manter',
unhold: 'Remover retenção',
reject: 'Rejeitar',
noOptionsAvailable: 'Nenhuma opção disponível para o grupo de despesas selecionado.',
},
filtersHeader: 'Filtros',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6247,6 +6247,7 @@ ${
delete: '删除',
hold: '保持',
unhold: '移除保留',
reject: '拒绝',
noOptionsAvailable: '所选费用组没有可用选项。',
},
filtersHeader: '筛选器',
Expand Down
7 changes: 7 additions & 0 deletions src/libs/API/parameters/RejectMoneyRequestInBulkParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type RejectMoneyRequestInBulkParams = {
reportID: string;
comment: string;
transactionIDToRejectReportAction: string;
};

export default RejectMoneyRequestInBulkParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ export type {default as MarkAsExportedParams} from './MarkAsExportedParams';
export type {default as UpgradeToCorporateParams} from './UpgradeToCorporateParams';
export type {default as DowngradeToTeamParams} from './DowngradeToTeamParams';
export type {default as DeleteMoneyRequestOnSearchParams} from './DeleteMoneyRequestOnSearchParams';
export type {default as RejectMoneyRequestInBulkParams} from './RejectMoneyRequestInBulkParams';
export type {default as HoldMoneyRequestOnSearchParams} from './HoldMoneyRequestOnSearchParams';
export type {default as ApproveMoneyRequestOnSearchParams} from './ApproveMoneyRequestOnSearchParams';
export type {default as PayMoneyRequestOnSearchParams} from './PayMoneyRequestOnSearchParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ const WRITE_COMMANDS = {
UPGRADE_TO_CORPORATE: 'UpgradeToCorporate',
DOWNGRADE_TO_TEAM: 'Policy_DowngradeToTeam',
DELETE_MONEY_REQUEST_ON_SEARCH: 'DeleteMoneyRequestOnSearch',
REJECT_MONEY_REQUEST_IN_BULK: 'RejectMoneyRequestInBulk',
HOLD_MONEY_REQUEST_ON_SEARCH: 'HoldMoneyRequestOnSearch',
APPROVE_MONEY_REQUEST_ON_SEARCH: 'ApproveMoneyRequestOnSearch',
UNHOLD_MONEY_REQUEST_ON_SEARCH: 'UnholdMoneyRequestOnSearch',
Expand Down Expand Up @@ -899,6 +900,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SEND_SCHEDULE_CALL_NUDGE]: Parameters.SendScheduleCallNudgeParams;

[WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH]: Parameters.DeleteMoneyRequestOnSearchParams;
[WRITE_COMMANDS.REJECT_MONEY_REQUEST_IN_BULK]: Parameters.RejectMoneyRequestInBulkParams;
[WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.HoldMoneyRequestOnSearchParams;
[WRITE_COMMANDS.APPROVE_MONEY_REQUEST_ON_SEARCH]: Parameters.ApproveMoneyRequestOnSearchParams;
[WRITE_COMMANDS.UNHOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.UnholdMoneyRequestOnSearchParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,15 @@ const OPTIONS_PER_SCREEN: Partial<Record<Screen, PlatformStackNavigationOptions>
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: {
animation: Animations.NONE,
},
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_REJECT_TRANSACTIONS]: {
animation: Animations.NONE,
},
[SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: {
animation: Animations.NONE,
},
[SCREENS.SEARCH.SEARCH_REJECT_REASON_RHP]: {
animation: Animations.NONE,
},
[SCREENS.SEARCH.TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP]: {
animation: Animations.NONE,
},
Expand Down Expand Up @@ -848,7 +854,9 @@ const SearchReportModalStackNavigator = createModalStackNavigator<SearchReportPa
[SCREENS.SEARCH.ROOT_VERIFY_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/Search/SearchRootVerifyAccountPage').default,
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_VERIFY_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/Search/SearchMoneyRequestReportVerifyAccountPage').default,
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: () => require<ReactComponentModule>('../../../../pages/Search/SearchHoldReasonPage').default,
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_REJECT_TRANSACTIONS]: () => require<ReactComponentModule>('../../../../pages/Search/SearchRejectReasonPage').default,
[SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchHoldReasonPage').default,
[SCREENS.SEARCH.SEARCH_REJECT_REASON_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchRejectReasonPage').default,
[SCREENS.SEARCH.TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchTransactionsChangeReport').default,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const SEARCH_TO_RHP: Partial<Record<keyof SearchFullscreenNavigatorParamList, st
SCREENS.SEARCH.REPORT_RHP,
SCREENS.SEARCH.REPORT_VERIFY_ACCOUNT,
SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP,
SCREENS.SEARCH.SEARCH_REJECT_REASON_RHP,
SCREENS.SEARCH.TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,7 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_VERIFY_ACCOUNT]: ROUTES.SEARCH_MONEY_REQUEST_REPORT_VERIFY_ACCOUNT.route,
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.route,
[SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: ROUTES.TRANSACTION_HOLD_REASON_RHP,
[SCREENS.SEARCH.SEARCH_REJECT_REASON_RHP]: ROUTES.SEARCH_REJECT_REASON_RHP,
[SCREENS.SEARCH.TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP]: ROUTES.MOVE_TRANSACTIONS_SEARCH_RHP,
},
},
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2629,6 +2629,11 @@ type SearchReportParamList = {
/** Selected transactions' report ID */
reportID: string;
};
[SCREENS.SEARCH.SEARCH_REJECT_REASON_RHP]: Record<string, never>;
[SCREENS.SEARCH.MONEY_REQUEST_REPORT_REJECT_TRANSACTIONS]: {
/** Selected transactions' report ID */
reportID: string;
};
};

type SearchFullscreenNavigatorParamList = {
Expand Down
48 changes: 48 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@
const parsedReportActionMessageCache: Record<string, string> = {};

let conciergeReportID: OnyxEntry<string>;
Onyx.connect({

Check warning on line 963 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CONCIERGE_REPORT_ID,
callback: (value) => {
conciergeReportID = value;
Expand All @@ -968,7 +968,7 @@
});

const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';
Onyx.connect({

Check warning on line 971 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, val is undefined
Expand All @@ -986,7 +986,7 @@
let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
let allPersonalDetailLogins: string[];
let currentUserPersonalDetails: OnyxEntry<PersonalDetails>;
Onyx.connect({

Check warning on line 989 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
if (currentUserAccountID) {
Expand All @@ -998,14 +998,14 @@
});

let allReportsDraft: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 1001 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
waitForCollectionCallback: true,
callback: (value) => (allReportsDraft = value),
});

let allPolicies: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 1008 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => (allPolicies = value),
Expand All @@ -1020,7 +1020,7 @@

let allReports: OnyxCollection<Report>;
let reportsByPolicyID: ReportByPolicyMap;
Onyx.connect({

Check warning on line 1023 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand Down Expand Up @@ -1058,14 +1058,14 @@
});

let allBetas: OnyxEntry<Beta[]>;
Onyx.connect({

Check warning on line 1061 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.BETAS,
callback: (value) => (allBetas = value),
});

let allTransactions: OnyxCollection<Transaction> = {};
let reportsTransactions: Record<string, Transaction[]> = {};
Onyx.connect({

Check warning on line 1068 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -1091,7 +1091,7 @@
});

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 1094 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -1104,7 +1104,7 @@

let allReportMetadata: OnyxCollection<ReportMetadata>;
const allReportMetadataKeyValue: Record<string, ReportMetadata> = {};
Onyx.connect({

Check warning on line 1107 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_METADATA,
waitForCollectionCallback: true,
callback: (value) => {
Expand Down Expand Up @@ -12649,10 +12649,58 @@
return `${environmentURL}/${relativePath}`;
}

/**
* Checks if all selected items have the necessary Onyx data hydrated for bulk rejection.
*/
function checkBulkRejectHydration(
selectedTransactions: Record<string, {reportID: string; policyID?: string}>,
policies: Record<string, unknown> | null | undefined,
): {areHydrated: boolean; missingReportIDs: string[]; missingPolicyIDs: string[]} {
const transactionIDs = Object.keys(selectedTransactions);
const missingReportIDs: string[] = [];
const missingPolicyIDs: string[] = [];

for (const transactionID of transactionIDs) {
const transaction = selectedTransactions[transactionID];
const reportID = transaction?.reportID;

// Check if we have the report data
const report = getReportOrDraftReport(reportID);
if (!report) {
missingReportIDs.push(reportID);
continue;
}

const effectivePolicyID = transaction?.policyID ?? report.policyID;

// Check if we have the policy data (required for canRejectReportAction check)
if (effectivePolicyID) {
const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${effectivePolicyID}`];
if (!policy) {
missingPolicyIDs.push(effectivePolicyID);
continue;
}
}

// Check if essential report fields are present
if (report.managerID == null || report.stateNum == null || report.statusNum == null) {
missingReportIDs.push(reportID);
continue;
}
}

return {
areHydrated: missingReportIDs.length === 0 && missingPolicyIDs.length === 0,
missingReportIDs: [...new Set(missingReportIDs)],
missingPolicyIDs: [...new Set(missingPolicyIDs)],
};
}

export {
areAllRequestsBeingSmartScanned,
buildOptimisticAddCommentReportAction,
buildOptimisticApprovedReportAction,
checkBulkRejectHydration,
buildOptimisticUnapprovedReportAction,
buildOptimisticCancelPaymentReportAction,
buildOptimisticChangedTaskAssigneeReportAction,
Expand Down
46 changes: 37 additions & 9 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,14 @@ type MoneyRequestInformation = {
reimbursable?: boolean;
};

type RejectMoneyRequestData = {
optimisticData: OnyxUpdate[];
successData: OnyxUpdate[];
failureData: OnyxUpdate[];
parameters: RejectMoneyRequestParams;
urlToNavigateBack: Route | undefined;
};

type TrackExpenseInformation = {
createdWorkspaceParams?: CreateWorkspaceParams;
iouReport?: OnyxTypes.Report;
Expand Down Expand Up @@ -13035,13 +13043,21 @@ function dismissRejectUseExplanation() {
}

/**
* Reject a money request
* Retrieve the reject money request data
* @param transactionID - The ID of the transaction to reject
* @param reportID - The ID of the expense report to reject
* @param comment - The comment to add to the reject action
* @returns The route to navigate back to
* @param options
* - sharedRejectedToReportID: When rejecting multiple expenses sequentially, pass a single shared destination reportID so all rejections land in the same new report.
* @returns optimisticData, successData, failureData, parameters, urlToNavigateBack
*/
function rejectMoneyRequest(transactionID: string, reportID: string, comment: string): Route | undefined {
function prepareRejectMoneyRequestData(
transactionID: string,
reportID: string,
comment: string,
options?: {sharedRejectedToReportID?: string},
shouldUseBulkAction?: boolean,
): RejectMoneyRequestData | undefined {
const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
const transactionAmount = getAmount(transaction);
const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
Expand All @@ -13063,7 +13079,7 @@ function rejectMoneyRequest(transactionID: string, reportID: string, comment: st
const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${childReportID}`];

let movedToReport;
let rejectedToReportID;
let rejectedToReportID = options?.sharedRejectedToReportID;
let urlToNavigateBack;
let reportPreviewAction: OnyxTypes.ReportAction | undefined;
let createdIOUReportActionID;
Expand All @@ -13086,7 +13102,7 @@ function rejectMoneyRequest(transactionID: string, reportID: string, comment: st
const successData: OnyxUpdate[] = [];
const failureData: OnyxUpdate[] = [];

if (!isPolicyDelayedSubmissionEnabled || isIOU) {
if ((!isPolicyDelayedSubmissionEnabled || isIOU) && !shouldUseBulkAction) {
if (hasMultipleExpenses) {
// For reports with multiple expenses: Update report total
optimisticData.push(
Expand Down Expand Up @@ -13225,7 +13241,7 @@ function rejectMoneyRequest(transactionID: string, reportID: string, comment: st
urlToNavigateBack = ROUTES.REPORT_WITH_ID.getRoute(report.chatReportID);
}
}
} else if (hasMultipleExpenses) {
} else if (hasMultipleExpenses && !shouldUseBulkAction) {
if (isUserOnSearchPage || isUserOnSearchMoneyRequestReport) {
// Navigate to the existing Reports > Expense view.
urlToNavigateBack = undefined;
Expand Down Expand Up @@ -13321,8 +13337,10 @@ function rejectMoneyRequest(transactionID: string, reportID: string, comment: st
},
);
} else {
// Create optimistic report for the rejected transaction
rejectedToReportID = generateReportID();
// When no existing open report is found, use the sharedRejectedToReportID
// so multiple sequential rejections land in the same destination report
// Fallback to generating a fresh ID if not provided
rejectedToReportID = rejectedToReportID ?? generateReportID();
const newExpenseReport = buildOptimisticExpenseReport(
report.chatReportID,
report?.policyID,
Expand Down Expand Up @@ -13709,10 +13727,19 @@ function rejectMoneyRequest(transactionID: string, reportID: string, comment: st
expenseCreatedReportActionID,
};

return {optimisticData, successData, failureData, parameters, urlToNavigateBack: urlToNavigateBack as Route};
}

function rejectMoneyRequest(transactionID: string, reportID: string, comment: string, options?: {sharedRejectedToReportID?: string}): Route | undefined {
const data = prepareRejectMoneyRequestData(transactionID, reportID, comment, options);
if (!data) {
return;
}
const {urlToNavigateBack, optimisticData, successData, failureData, parameters} = data;
// Make API call
API.write(WRITE_COMMANDS.REJECT_MONEY_REQUEST, parameters, {optimisticData, successData, failureData});

return urlToNavigateBack as Route;
return urlToNavigateBack;
}

function markRejectViolationAsResolved(transactionID: string, reportID?: string) {
Expand Down Expand Up @@ -14807,6 +14834,7 @@ export {
calculateDiffAmount,
dismissRejectUseExplanation,
rejectMoneyRequest,
prepareRejectMoneyRequestData,
markRejectViolationAsResolved,
setMoneyRequestReimbursable,
computePerDiemExpenseAmount,
Expand Down
Loading
Loading