You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] Pay is shown when one of the reports contains only nonreimbursable expense during bulk select #104388
Version Number: 9.5.14-0 Reproducible in staging?: Yes Reproducible in production?: N/A - new feature, doesn't exist in prod If this was caught during regression testing, add the test name, ID and link from BrowserStack:#100569 Email or phone of affected tester (no customers):josdjojnsosi@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Mac 26.5 / Chrome App Component: Search
Action Performed:
Precondition:
Go to workspace settings > Workflows and enable Prevent marking non-reimbursable reports as paid
Go to staging.new.expensify.com
Go to workspace chat.
Create a reimbursable expense.
Submit and approve the report.
Create a non-reimbursable expense.
Submit and approve the report.
Go to Spend > Reports.
Bulk select both reports.
Expected Result:
Pay button will not be shown when one of the reports cannot be paid.
Actual Result:
Pay button is shown when one of the reports containing only non-reimbursable expense cannot be paid.
👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:
Identify the pull request that introduced this issue and revert it.
Verdict: This looks like expected behavior, not a regression from Add workspace setting to control marking non-reimbursable reports as paid. Bulk Pay is designed to show when at least one selected report is payable. It then pays only the payable reports. I recommend demoting or closing this issue after QA runs the two checks below. I don't recommend a revert.
QA check: The new PR is only at fault if the non-reimbursable report is still treated as payable. Please confirm both of these:
With both reports selected, the Pay total equals only the reimbursable report's amount. After you pay, the non-reimbursable report stays unpaid.
When you select only the non-reimbursable report, its row shows View and the bulk menu has no Pay option.
If either check fails, the likely cause is that the search results don't include preventPayoutNonReimbursableReports for the policy. The fix would be a small follow-up PR, not a revert.
Investigation details
The new PR adds !(policy?.preventPayoutNonReimbursableReports && hasOnlyNonReimbursableTransactions(...)) to all three pay checks:
The issue's expected result assumes every selected report must be payable. That conflicts with the current bulk Pay design. This analysis is based on reading the code. I didn't test it in the app.
Let's get this fixed and make sure we don't reintroduce the original issue #93699 (hiding pay buttons when it should not be hidden i.e. when all reports are payable)
changed the title [-]Pay is shown when one of the reports contains only nonreimbursable expense during bulk select[/-][+][$175] Pay is shown when one of the reports contains only nonreimbursable expense during bulk select[/+]on Oct 19, 2026
Please re-state the problem that we are trying to solve in this issue.
When selecting multiple expense reports in Search/Reports view where one or more selected reports cannot be paid (e.g. contains only non-reimbursable expenses, or is pending approval, or view-only), the Bulk Actions menu still displays the "Pay" option instead of hiding it.
This creates an inconsistency with the "Approve" bulk action (which requires selectedReports.every(report => report.canApprove)) and allows users to trigger payment flow when not all selected reports are payable.
What is the root cause of that problem?
In src/libs/actions/Search.ts, getPayOption filters selectedReports to payableReports = selectedReports.filter((report) => report.canPay) and evaluates:
Because payableReports.every(...) was introduced in PR #94075 to degrade bulk pay to the payable subset, it only requires at least one report to be payable (payableReports.length > 0) rather than requiring all selected reports to be payable.
Consequently, when a non-reimbursable (or unpayable) report is selected alongside a payable report, shouldShowBulkPayOption returns true, causing the "Pay" bulk action to appear unexpectedly.
What changes do you think we should make in order to solve the problem?
In src/libs/actions/Search.ts, update getPayOption so that for bulk report selection, every selected report must be payable (report.canPay), making it consistent with canApprove and transaction-level selections (every(action === PAY)):
When any selected report cannot be paid (such as a report with only non-reimbursable expenses), the Pay option is hidden.
Behavior is strictly aligned with the "Approve" bulk action (selectedReports.every(report => report.canApprove)).
What alternative solutions did you consider?
Handling the condition inside useSearchBulkActions.ts by checking selectedReports.every(report => report.canPay) before rendering payButtonOption. However, keeping the validation centralized inside getPayOption in Search.ts is cleaner, adheres to existing architecture, and directly protects all callers and unit tests.
Please re-state the problem that we are trying to solve in this issue.
When you bulk select reports in Search and at least one can't be paid, the Pay option still shows. It then pays only the payable reports. Approve works differently: it shows only when every selected report can be approved.
What changes do you think we should make in order to solve the problem?
In getPayOption, restore the all-or-nothing check for whole-report selections. Show Pay only if every selected report has canPay, all reports share the first report's type, and shouldShowBulkOptionForRemainingTransactions passes. Compute hasLastPaymentMethod over all selected reports, not just the payable ones. Keep getSelectedReportType so the snapshot fix stays.
Flip "keeps bulk pay enabled when only some of the selected reports are payable" (tests/unit/SearchActionsTest.ts:690) so it expects Pay to be hidden.
Add a test for a payable report plus an unapproved report, which should also hide Pay.
Keep the existing test that expects Pay to show when all selected reports are payable, so the bug from that earlier issue doesn't come back.
The payableSelectedReports filter in useSearchBulkActions can stay. Once every selected report must be payable, that filter has no effect on manual selections. It's still useful for Select all.
What alternative solutions did you explore? (Optional)
You could also apply the all-or-nothing rule to Select all, which currently shows Pay if any loaded report is payable (src/hooks/useSearchBulkActions.ts:2588-2589). I left it out because this issue covers manual selection only, and that Select all behavior was a deliberate part of the earlier fix. s77rt and FitseTLT, let me know if you want it included.
The core change in step 1 matches the earlier proposal from janiya2. This proposal adds the hold-check alignment and states which earlier fixes to keep. I haven't tested it in the app.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/104388) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Activity
applause-bot commentedon Oct 19, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/e08fe4d5977a106d49b391837850fde37fda07d1dd72e80ff2b9d7e168686692
melvin-bot commentedon Oct 19, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #100569
melvin-bot commentedon Oct 19, 2026
@stitesExpensify @FitseTLT you have been assigned to this deploy blocker because you reviewed the offending PR: #100569
github-actions commentedon Oct 19, 2026
👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:
melvin-bot commentedon Oct 19, 2026
@s77rt you have been assigned to this deploy blocker because you authored the offending PR: #100569
melvin-bot commentedon Oct 19, 2026
💬 A slack conversation has been started in #expensify-open-source
MelvinBot commentedon Oct 19, 2026
Deploy Blocker Analysis
Verdict: This looks like expected behavior, not a regression from Add workspace setting to control marking non-reimbursable reports as paid. Bulk Pay is designed to show when at least one selected report is payable. It then pays only the payable reports. I recommend demoting or closing this issue after QA runs the two checks below. I don't recommend a revert.
Why Pay shows: Fix bulk Pay/Mark as Paid disappearing for whole-report selections changed bulk Pay to use only the payable reports instead of hiding Pay. That PR merged on 2026-09-08 and is already in production.
getPayOptionfilters the selection tocanPayreports and shows Pay if any remain (src/libs/actions/Search.ts:2423-2438). A unit test covers this behavior (tests/unit/SearchActionsTest.ts:690-699). You can see the same result in production today: select one approved report and one report still waiting for approval.QA check: The new PR is only at fault if the non-reimbursable report is still treated as payable. Please confirm both of these:
If either check fails, the likely cause is that the search results don't include
preventPayoutNonReimbursableReportsfor the policy. The fix would be a small follow-up PR, not a revert.Investigation details
The new PR adds
!(policy?.preventPayoutNonReimbursableReports && hasOnlyNonReimbursableTransactions(...))to all three pay checks:canIOUBePaid: src/libs/ReportUtils.ts:3393-3396canPay: src/libs/ReportPreviewActionUtils.ts:173isPrimaryPayAction: src/libs/ReportPrimaryActionUtils.ts:262Search rows get their action from
canIOUBePaid(src/libs/SearchUIUtils.ts:2219-2248). Each row'scanPaycomes from that action (src/libs/SearchUIUtils.ts:2355). As a result, the non-reimbursable report should havecanPay: false.The bulk payment and the displayed total also use only the payable subset (src/hooks/useSearchBulkActions.ts:805-807, src/hooks/useSearchBulkActions.ts:2586-2590). The partial-Pay behavior came from commit
e54017a, "Degrade bulk Pay to the payable subset instead of hiding it."The issue's expected result assumes every selected report must be payable. That conflicts with the current bulk Pay design. This analysis is based on reading the code. I didn't test it in the app.
view run
5 remaining items
s77rt commentedon Oct 19, 2026
Let's get this fixed and make sure we don't reintroduce the original issue #93699 (hiding pay buttons when it should not be hidden i.e. when all reports are payable)
melvin-bot commentedon Oct 19, 2026
Current assignee @FitseTLT is eligible for the External assigner, not assigning anyone new.
[-]Pay is shown when one of the reports contains only nonreimbursable expense during bulk select[/-][+][$175] Pay is shown when one of the reports contains only nonreimbursable expense during bulk select[/+]melvin-bot commentedon Oct 19, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022112284799078923331
FitseTLT commentedon Oct 19, 2026
@MelvinBot any proposal you have got
janiya2 commentedon Oct 19, 2026
Contributor details
Your Expensify account email: janithad2@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/janithad
Proposal
Please re-state the problem that we are trying to solve in this issue.
When selecting multiple expense reports in Search/Reports view where one or more selected reports cannot be paid (e.g. contains only non-reimbursable expenses, or is pending approval, or view-only), the Bulk Actions menu still displays the "Pay" option instead of hiding it.
This creates an inconsistency with the "Approve" bulk action (which requires
selectedReports.every(report => report.canApprove)) and allows users to trigger payment flow when not all selected reports are payable.What is the root cause of that problem?
In
src/libs/actions/Search.ts,getPayOptionfiltersselectedReportstopayableReports = selectedReports.filter((report) => report.canPay)and evaluates:Because
payableReports.every(...)was introduced in PR #94075 to degrade bulk pay to the payable subset, it only requires at least one report to be payable (payableReports.length > 0) rather than requiring all selected reports to be payable.Consequently, when a non-reimbursable (or unpayable) report is selected alongside a payable report,
shouldShowBulkPayOptionreturnstrue, causing the "Pay" bulk action to appear unexpectedly.What changes do you think we should make in order to solve the problem?
In
src/libs/actions/Search.ts, updategetPayOptionso that for bulk report selection, every selected report must be payable (report.canPay), making it consistent withcanApproveand transaction-level selections (every(action === PAY)):Also, update the unit test in
tests/unit/SearchActionsTest.tsto assert that mixing payable and non-payable reports disables the bulk pay button:This ensures:
selectedReports.every(report => report.canApprove)).What alternative solutions did you consider?
Handling the condition inside
useSearchBulkActions.tsby checkingselectedReports.every(report => report.canPay)before renderingpayButtonOption. However, keeping the validation centralized insidegetPayOptioninSearch.tsis cleaner, adheres to existing architecture, and directly protects all callers and unit tests.melvin-bot commentedon Oct 19, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
FitseTLT commentedon Oct 19, 2026
@janiya2 do not post proposal before help wanted label is applied
MelvinBot commentedon Oct 19, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When you bulk select reports in Search and at least one can't be paid, the Pay option still shows. It then pays only the payable reports. Approve works differently: it shows only when every selected report can be approved.
What is the root cause of that problem?
In Fix bulk Pay/Mark as Paid disappearing for whole-report selections, commit
e54017a("Degrade bulk Pay to the payable subset instead of hiding it") changedgetPayOption. It now filters the selection down tocanPayreports and shows Pay if any report remains. It no longer requires every selected report to be payable (src/libs/actions/Search.ts:2423-2438).That commit wasn't needed to fix [Due for payment 2026-09-17] [$250] Bulk Pay / Mark as Paid options missing when selecting multiple reports (across workspaces/currencies). The same PR fixed that issue by deriving
selectedPolicyIDsfrom the selected reports (src/hooks/useSearchBulkActions.ts:824-827). It also made two other fixes: it reads the report type from the search snapshot, and it lets Mark as Paid show without a report or policy ID.What changes do you think we should make in order to solve the problem?
getPayOption, restore the all-or-nothing check for whole-report selections. Show Pay only if every selected report hascanPay, all reports share the first report's type, andshouldShowBulkOptionForRemainingTransactionspasses. ComputehasLastPaymentMethodover all selected reports, not just the payable ones. KeepgetSelectedReportTypeso the snapshot fix stays.useSearchBulkActions, gate Pay onisAnyTransactionOnHoldinstead ofisAnyPayableTransactionOnHold, the same way Approve does (src/hooks/useSearchBulkActions.ts:2590 compared with src/hooks/useSearchBulkActions.ts:2733-2740). After that, remove the unusedisAnyPayableTransactionOnHold.The
payableSelectedReportsfilter inuseSearchBulkActionscan stay. Once every selected report must be payable, that filter has no effect on manual selections. It's still useful for Select all.What alternative solutions did you explore? (Optional)
You could also apply the all-or-nothing rule to Select all, which currently shows Pay if any loaded report is payable (src/hooks/useSearchBulkActions.ts:2588-2589). I left it out because this issue covers manual selection only, and that Select all behavior was a deliberate part of the earlier fix.
s77rtandFitseTLT, let me know if you want it included.The core change in step 1 matches the earlier proposal from
janiya2. This proposal adds the hold-check alignment and states which earlier fixes to keep. I haven't tested it in the app.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/104388)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Oct 19, 2026
If you are the assigned CME please investigate whether the linked PR caused a regression and leave a comment with the results.
If a regression has occurred and you are the assigned CM follow the instructions here.
If this regression could have been avoided please consider also proposing a recommendation to the PR checklist so that we can avoid it in the future.
stitesExpensify commentedon Oct 22, 2026
@allgandalf are you taking this one on since you authored #94075 ?
#104388 (comment)