Add bulk export to accounting integration#83527
Conversation
…ccounting-integrations Add bulk export to accounting integration
Screen.Recording.2026-02-26.at.11.09.48.mp4@arosiclair @ahmedGaber93 I’ve fixed the deploy blocker issue. Could you please check again? |
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f552e1598
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/pages/Search/SearchPage.tsx
Outdated
| { | ||
| text: connectionNameFriendly, | ||
| icon: integrationIcon, | ||
| onSelected: () => handleExportAction(() => exportToIntegrationOnSearch(hash, selectedReportIDs, connectedIntegration)), |
There was a problem hiding this comment.
Use all-matching query when bulk-exporting integration reports
In SearchPage this menu action exports with selectedReportIDs, so when the user has enabled “all matching items selected” it still sends only the currently loaded report IDs to exportToIntegrationOnSearch. That differs from the existing CSV/template export paths (which switch to query-based export in handleBasicExport/beginExportWithTemplate) and causes partial exports on paginated result sets while the UI implies all matches are selected.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@arosiclair @ahmedGaber93 Is this out of scope for this PR?
There was a problem hiding this comment.
So with the current implementation, we're only exporting the page of reports that you can see? I think that's fine for now, but we'll probably want to improve that to do a full export as mentioned. cc @trjExpensify
|
@ahmedGaber93 please jump on this! |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safari20260308175546018.mp420260308175238372.mp4 |
|
Bug: The warning modal appears starting from the third time. Steps to reproduce:
20260226180711810.mp4@huult If you’re unable to reproduce the issue, try using a slow network connection. I believe this happens because we rely on the backend response when refetching the |
|
@arosiclair Just to confirm: we will fix the regression for bulk exporting only, but we won’t address the existing issue on the Expense Report page in production, correct? |
If they both have the same root cause, let's fix both. Otherwise, just fixing the bulk export is fine. |
|
Sounds good 👍 @huult Could you please handle the other case as well if it turns out to have the same root cause? |
|
#83476 The search bulk actions logic has been moved to a custom hook, so I need to update this accordingly. |
Screen.Recording.2026-02-27.at.15.49.16.mov#83527 (comment) It's done |
|
@huult I am still able to reproduce the issue with the same steps here #83527 (comment) 20260227203828451.mp4 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 34374acba6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@ahmedGaber93 it's done |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db6e1cdbe6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| exportOptions.push({ | ||
| text: translate('workspace.common.markAsExported'), | ||
| icon: integrationIcon, | ||
| onSelected: () => handleExportAction(() => markAsManuallyExported(selectedReportIDs, connectedIntegration)), |
There was a problem hiding this comment.
Update Export snapshot after bulk mark-as-exported
When bulk Mark as exported is triggered from Search, this path calls markAsManuallyExported(selectedReportIDs, connectedIntegration) without passing hash/currentSearchKey, so it cannot remove rows from ONYXKEYS.COLLECTION.SNAPSHOT for the Export suggested search. In the EXPORT search context, users will see reports remain in the list until a refetch even though the action succeeded, unlike exportToIntegrationOnSearch which explicitly nulls snapshot entries for this case.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This works as expected without the above change. I think it’s because we set isExportedToIntegration to true optimistically in this case.
There was a problem hiding this comment.
I'm not sure why we made that change. In either case, should we pass the hash anyway?
There was a problem hiding this comment.
We passed it to exportToIntegrationOnSearch to address this issue in the "Export" tab: #83527 (comment), but it hasn’t been fixed yet.
should we pass the hash anyway?
It works as expected with markAsManuallyExported, the item disappears immediately after being marked as exported. But can pass it to avoid unexpected issues.
20260310133123396.mp4
|
We can merge as soon as C+ is able to do a final review. |
|
NAB: Removing rows from the Export tab after exporting works, but there is a slight delay while waiting for the Pusher update that confirms the export success. @huult Shouldn’t the last change fix it? 20260310132550975.mp4 |
I think yes, because the previous function didn’t need it |
…rt-accounting-integrations-version-2
…rt-accounting-integrations-version-2
| pendingFields: { | ||
| export: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, | ||
| }, | ||
| isExportedToIntegration: true, |
There was a problem hiding this comment.
Why are we not using pendingFields in this case anymore?
There was a problem hiding this comment.
@arosiclair We’re discussing this here: #83527 (comment), but no final decision has been made yet. WDYT?
There was a problem hiding this comment.
Using pendingFields here makes most sense to me
| exportOptions.push({ | ||
| text: translate('workspace.common.markAsExported'), | ||
| icon: integrationIcon, | ||
| onSelected: () => handleExportAction(() => markAsManuallyExported(selectedReportIDs, connectedIntegration)), |
There was a problem hiding this comment.
I'm not sure why we made that change. In either case, should we pass the hash anyway?
|
@huult please keep on top of the comments and let's get this one over the line. Thanks! |
…rt-accounting-integrations-version-2
@arosiclair For a single “mark as exported”, we don’t need a hash. For multiple “mark as exported”, we don’t need a hash either, I think. |
|
@huult any update on this #83527 (comment)? |
@ahmedGaber93 I think this is currently a product issue. If you use the export button, the same issue still occurs, right? |
|
@huult Yes, it reproduces on production. I just want to confirm that this is not related to BE changes that send back The previous behavior on production, before the BE change, was to keep the item in the list and not remove it. After the BE change, it is now removed once the Pusher data is received. I think The correct behavior should be removing the item immediately once the request is sent. @trjExpensify @arosiclair If this is considered an issue, we can handle it separately in a new issue. video here #83527 (comment) |
|
@arosiclair LGTM! All yours! |
|
🚧 @arosiclair has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/arosiclair in version: 9.3.37-0 🚀
|
|
🚀 Deployed to production by https://github.com/cristipaval in version: 9.3.37-10 🚀
|
Explanation of Change
Fixed Issues
$ #79515
PROPOSAL: #79515 (comment)
Tests
Same QA step
Offline tests
QA Steps
Precondition: The workspace is connected to an accounting integration (for example, QuickBooks Online).
“Exported to QuickBooks Online and successfully created a record for out-of-pocket expenses.”
If exported via QuickBooks Online:
“Exported to QuickBooks Online and successfully created a record for out-of-pocket expenses.”
If marked via Mark as exported:
“Marked this report as manually exported to QuickBooks Online.”
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Screen.Recording.2026-02-18.at.15.07.19.mov
Android: mWeb Chrome
Screen.Recording.2026-02-18.at.15.08.52.mov
iOS: Native
Screen.Recording.2026-02-18.at.14.53.45.mov
iOS: mWeb Safari
Screen.Recording.2026-02-18.at.15.03.47.mov
MacOS: Chrome / Safari
Screen.Recording.2026-02-18.at.15.09.55.mov