Fix infinite amount and pending rate when submitting a self DM distance expense quickly - #99665
Conversation
…s none Submitting a self DM distance expense before it finishes being written snapshots a draft with no customUnitRateID and no quantity. The confirmation page then cannot resolve a rate, so the rate row is stuck on "pending" and the amount is back-calculated against a missing quantity, rendering as an infinity symbol. Keep only an explicit p2p rate when moving a tracked expense onto a workspace, and fall back to routeDistanceMeters when no other distance is available.
…ates loaded getCustomUnitRateID falls back to the p2p rate when the destination policy has no distance custom unit yet. Only the default workspace is guaranteed full customUnits from OpenApp, so selecting against an unloaded one would replace a real workspace rate ID with the p2p sentinel.
|
@ZhenjaHorbach Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eea0ac2553
ℹ️ 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".
A tracked per diem carries a per-diem rate ID that is never among the destination workspace's mileage rates, so the replacement would have swapped it for a mileage rate the per-diem custom unit cannot resolve.
|
@ZhenjaHorbach this is ready.. let's move forward. thanks |
|
@codex |
|
@MelvinBot |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safari |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
|
🤖 Reviewed. The fix works — I reproduced the flow on web and the Rate row resolves the workspace rate and the Amount is finite. CI is green and the unit tests cover the interesting branches (per diem, time, explicit p2p, unloaded rates, already-owned rate). Four things worth 1. There is already a self-heal path for this on the confirmation page
Relaxing that one guard would fix the bug in the place that already owns this decision. Instead the PR adds a second decision in 2. The unloaded-rates escape hatch can strand a foreign rate
Not a regression (the old code always kept the rate), but it is inside the stated scope of the fix. 3. The
|
|
ohh reviews much improved 👍 |
|
Yeah |
can you psl approve and we can continue? tjanks |
Maybe you should add these changes in this PR?😅 |
|
oh sorry, got a bit confused with multiple ✅ emojis. let me check them |
Okay
CC: @JmillsExpensify When we are trying to send an expense to a friend to a non-default workspace after clearing cache or relogging in 2026-09-07.11.17.37.mov |
|
My preference is that we deprecate support for this entire flow, so I'm fine blocking on the Confirm details case for now. This just isn't a mainline flow that matters. |
|
@mukhrr |
|
@ZhenjaHorbach done |
|
🚧 chuckdries has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/chuckdries in version: 9.4.75-0 🚀
|
|
🤖 No help site changes are required for this PR, so I did not create a docs PR. This is a defect fix, not a behavior or copy change. It stops the confirmation page from showing a stuck pending Rate row and a What I checkedChanged files — all logic and tests, no user-facing strings and no
Articles reviewed — the only two in
Two passages I looked at closely, and why both still hold:
@mukhrr, please confirm you agree that no help site update is needed here. If you think one of the two articles above should be updated anyway — for example, if you want the Move-expense FAQ reworded to be explicit that it does not cover submitting a tracked expense — reply with |
|
PR failed due to an original issue: Amount shows the infinity symbol, while Rate shows “Pending” when submitting a distance expense from a self-DM to a non-default workspace. 1788993811059.Recording__1568.mp4 |
@IuliiaHerets i think you either wrongly commented or attached wrong video record. I don't see bug on video you uploaded. can you psl check it once more? thanks |
|
Just checked the video |
|
@mukhrr @ZhenjaHorbach asked the tester to recheck. I'll be back soon |
|
@mukhrr @ZhenjaHorbach Step 10 says that the Rate should be shown as invalid and the user should be able to manually select an available rate before submitting.
Recording.1572.1.mp4 |
|
@IuliiaHerets i suggest reading this and this. It's expected disabled Rate field. It seems I mistyped last step ( sorry for that). It should have been:
@ZhenjaHorbach did I miss anything? Correct me if I am wrong, pls. Thanks |
|
Yes |
|
So we can check it off, right? |
Yes |
|
🚀 Deployed to production by https://github.com/mountiny in version: 9.4.75-3 🚀
Bundle Size Analysis (Sentry): |
|
🚀 Deployed to production by https://github.com/mountiny in version: 9.4.75-3 🚀
Bundle Size Analysis (Sentry): |
@ZhenjaHorbach while this is expected, I am a bit worried about UX. I mean we show "... pls select a rate.." as error while it is not selectable.
so I have a suggestion here:
result online: same as warm cache, just a sub second delay. unit matches → rate shown, no error. unit differs → rate shown, row tappable, error stays and the user can actually pick one. I understand we don't have data to show optimistically so offlime stays as-is now. what d oyuo think? cc @JmillsExpensify |
|
@MelvinBot
|
|
🤖 I held off creating the issue — there's already an open one for this exact symptom, and this specific case was explicitly deprioritized on this PR. Likely duplicate: [$250] DEV: Rate not valid error is displayed when submitting a distance expense, but the Rate field is not editable ( Also worth weighing: Your call:
Code the suggestion touches
|











Explanation of Change
Clicking "Submit to a friend" snapshots the tracked expense into a draft with
Onyx.set. Submit before that expense has finished being written and the snapshot has nocustomUnitRateIDand noquantity, and because it is asetit never catches up. With no rate ID the confirmation page cannot resolve a rate, so the Rate row is stuck on "pending" and the amount is back-calculated by dividing by the missing quantity, which renders as♾️.Three changes, the first two reading data already in Onyx so the rate and amount fill in without waiting on the backend:
addParticipantselects the destination workspace's rate unless the draft holds an explicitFAKE_P2Prate or one that workspace already owns. Previously the whole block was skipped for any tracked-expense move onto a workspace, so a draft carrying a missing or foreign rate ID was never repaired. Non-distance drafts are left alone: a per diem rate ID, and a time expense's absent one, are not mileage rates.getDistanceInMetersfalls back tocomment.customUnit.routeDistanceMeterswhen there is no quantity and no route.quantitystill takes priority, so manual distance edits are unaffected.hasRoutedeliberately does not take the same fallback, and now carries a comment saying why.useDistanceRequestStateandMoneyRequestViewno longer back-calculate a rate from a quantity that has not been written yet. Dividing by0is what produced the♾️; substituting1only defers it, because the amount then becomes the rate and compounds back into the amount on every render.Fixed Issues
$ #99315
PROPOSAL: #99315 (comment)
Tests
OpenAppreturns on sign in).$∞.OpenAppdoes not return that workspace's rates, so the following is expected behaviour and not a bug:Offline tests
$∞. A non-default workspace whose rates were never cached still shows the "rate not valid" error, as in step 10 of the Tests section.QA Steps
Same as tests.
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
android_app.mp4
Android: mWeb Chrome
android_web.mp4
iOS: Native
IOS_app.mp4
iOS: mWeb Safari
IOS_web.mp4
MacOS: Chrome / Safari
web.mp4