-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix wrong total amount of hold expenses when approve/pay partially #49971
base: main
Are you sure you want to change the base?
Fix wrong total amount of hold expenses when approve/pay partially #49971
Conversation
src/libs/actions/IOU.ts
Outdated
@@ -6448,7 +6448,7 @@ function getReportFromHoldRequestsOnyxData( | |||
chatReport.reportID, | |||
chatReport.policyID ?? iouReport?.policyID ?? '', | |||
recipient.accountID ?? 1, | |||
holdTransactions.reduce((acc, transaction) => acc + transaction.amount, 0) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1), | |||
((iouReport?.total ?? 0) - ((iouReport?.unheldTotal ?? 0) + (iouReport?.nonReimbursableTotal ?? 0))) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the unheldTotal
doesn't include the nonReimbursableTotal
, I add them together. If I have 3 expenses,
A: $1 (track, non-reimbursable)
B: $2 (held)
C: $3
The total is $6, unheldTotal is $3, and nonReimbursableTotal is $1.
held total = $6 - ($3 + $1) = $2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty confident that we can/should add them together because of this logic.
Lines 6667 to 6670 in 76c5c81
let total = (iouReport?.total ?? 0) - (iouReport?.nonReimbursableTotal ?? 0); | |
if (ReportUtils.hasHeldExpenses(iouReport?.reportID ?? '') && !full && !!iouReport?.unheldTotal) { | |
total = iouReport?.unheldTotal; | |
} |
The logic above calculates the pay amount to show.
It subtracts the total from the nonReimbursableTotal. But if there are held expenses and it's paid partially, then we just use the unheldTotal which means the unheldTotal doesn't contain the nonReimbursableTotal.
unheldTotal = the total reimbursable amount that is not held
src/libs/actions/IOU.ts
Outdated
@@ -6448,7 +6448,7 @@ function getReportFromHoldRequestsOnyxData( | |||
chatReport.reportID, | |||
chatReport.policyID ?? iouReport?.policyID ?? '', | |||
recipient.accountID ?? 1, | |||
holdTransactions.reduce((acc, transaction) => acc + transaction.amount, 0) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1), | |||
((iouReport?.total ?? 0) - ((iouReport?.unheldTotal ?? 0) + (iouReport?.nonReimbursableTotal ?? 0))) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1), | |||
getCurrency(firstHoldTransaction), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you fix the currency to use the iou's currency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, forgot about it. Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I noticed that too, but that's how it's in prod too. I was thinking of adding the Before: Approve only $unheldTotal Do you agree with that? Or should we have someone from the team to confirm the behavior first? |
I think the calculated |
|
Ah, I think this needs to be fixed in BE then. cc @robertjchen Bug: Steps to reproduce:
Expected: the green button to state "Approve only DZD X.XX" Suggested solutions:
|
That won't work because we are not taking into account held non-reimbursable transactions |
Non reimbursable transaction can't be hold. |
Hm, is that true @JmillsExpensify @robertjchen? |
That should not be true, no. You should be able to hold any transaction. |
Oh, then it's a bug then. There is no option to Hold non-reimbursable in ND. |
So, to clarify- given this, the backend needs to be updated such so that: |
How do we expect this to work, @JmillsExpensify? Is it:
|
That's the expected.
In the approve modal we should show the unheldTotal which should include both reimbursable and non-reimbursable transactions. |
That's all fair feedback above, and on second thought, we struggled with this consideration in the design doc – enough that we put this in the out-of-scope section of the doc. In fact, there are kind of two cases to consider here:
This only matters for approval, as you can legitimately approve both reimbursable and non-reimbursable expenses. It is impossible to pay a non-reimbursable in 100% of cases. As a result, I think if we went this route, then we would only show the |
Ah right, then can we go with the following: BE:
FE: In the approve modal display:
In the pay modal display:
Does that make sense? |
If we're adding I think this question is still outstanding:
|
Sorry, we won't, I meant to write
They come off hold same as held reimbursable transactions. |
Gotcha. 👍
I think the UX might be a bit strange on that one as it's not communicated in the modal, but let's see I guess. |
@robertjchen Are we aligned with the changes here #49971 (comment) if so, can you please work on the BE changes? Those are breaking-changes (due to |
Ah that is true- from that perspective, the |
if (typeof iouReport.unheldTotal === 'number') { | ||
iouReport.unheldTotal -= amount; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a case where unheldTotal
wouldn't exist? If we have an expense report where all the transactions are reminbursable will we have:
unheldTotal
set tototal
unheldTotal
not set at all (undefined
)
cc @robertjchen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In main
, we don't add unheldTotal
and unheldNonReimbursableTotal
when creating the report optimistically (buildOptimisticExpenseReport only, IOU not yet, waiting for the IOU total bug to be fixed), so it will be undefined. But in this PR, I already added both optimistically, so it will never be undefined anymore optimistically. I don't know about the BE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For BE, we will always return these fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the confirmation. That means we could technically update the typing for the total
, unheldTotal
, nonReimbursableTotal
, and unheldNonReimbursableTotal
to be non-undefined/null, but if we want to do that, we need to update all report optimistic creation (buildOptimisticChatReport, buildOptimisticTaskReport, etc.) to include them all, which isn't the case for now.
@robertjchen thanks, it's now negative.
Now waiting for this one 😄 |
@bernhardoj Given how the backend is designed, I cannot change the |
Good news, I just retested and the IOU total issue doesn't happen anymore (hopefully it stays like this). |
} else { | ||
iouReportUpdate.total += isDeleting ? amount : -amount; | ||
if (!isOnhold) { | ||
iouReportUpdate.unheldTotal += isDeleting ? amount : -amount; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the optimistic unheldTotal for IOU too. However, there is a problem.
- User A submit $2 and $2 to user B (total = 4, unheldTotal = 4, owner = user A)
- User A hold $2 (total = 4, unheldTotal = 2, owner user A)
- User B submit $10
When user B submits, it will go into the else
block, subtracting total
from the amount (10). So, the new total will be -6. If it's < 0, we switch the owner with this logic and switch the sign too, so the new optimistic total is 6.
Lines 96 to 100 in 866ce4b
if (iouReportUpdate.total < 0) { | |
// The total sign has changed and hence we need to flip the manager and owner of the report. | |
iouReportUpdate.ownerAccountID = iouReport.managerID; | |
iouReportUpdate.managerID = iouReport.ownerAccountID; | |
iouReportUpdate.total = -iouReportUpdate.total; |
The problem I see is, that BE still keeps the sign, so the total
from BE is -6. Also, the unheldTotal
is -8, which looks like it comes from unheldTotal
(2) - amount (10). So, I apply this calculation for the optimistic one too as you can see above. I'm not sure if the unheldTotal
should be -8, but at least I match it with the BE. I think it will be another big problem to solve and decide the expected behavior.
Notice the image below shows Pay -8 (unheldTotal).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this looks tricky, i think we need to agree on the expected behaviour on what the total and the unheldTotal should be in this case and also in the case where we unhold the $2 request. (can we still unhold that request? and how can do it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can still unhold it and the unheldTotal
will be the same as total
(-6). If I hold it again, it will be -8 again.
User A submit $2 and $2 to user B (total = 4, unheldTotal = 4, owner = user A)
User A hold $2 (total = 4, unheldTotal = 2, owner user A)
User B submit $10
I think if we have this case, the new total should be 6 (number is correct already in prod, but it's in negative) and unheldTotal should be:
current held = total (4) - unheldTotal (2) = 2
new unheldTotal = new total (6) - current held (2) = 4
I suspect the BE already did this calculation, but because the total is negative (-6), subtracting with 2 will be -8.
cc: @robertjchen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unheldTotal
on the backend is simply the reimbursable total (excluding held transactions)
+ non reimbursable total (excluding held transactions)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the problem around IOUs and expense reports having different signage? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robertjchen According to this comment
Lines 63 to 71 in 95aab1f
/** | |
* The owner of the IOU report is the account who is owed money and the manager is the one who owes money! | |
* In case the owner/manager swap, we need to update the owner of the IOU report and the report total, since it is always positive. | |
* For example: if user1 owes user2 $10, then we have: {ownerAccountID: user2, managerID: user1, total: $10 (a positive amount, owed to user2)} | |
* If user1 requests $17 from user2, then we have: {ownerAccountID: user1, managerID: user2, total: $7 (still a positive amount, but now owed to user1)} | |
* | |
* @param isDeleting - whether the user is deleting the expense | |
* @param isUpdating - whether the user is updating the expense | |
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Gonals May be able to help if that logic is no longer true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK, we haven't changed that, but it has been a loooong while since I touched that area
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robertjchen Would you be able to check again so we can move forward here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robertjchen Just merged with the main and fixed some conflicts. I think we just need a confirmation whether it's necessarry need to do this or not.
} | ||
|
||
if (iouReportUpdate.total < 0) { | ||
// The total sign has changed and hence we need to flip the manager and owner of the report. | ||
iouReportUpdate.ownerAccountID = iouReport.managerID; | ||
iouReportUpdate.managerID = iouReport.ownerAccountID; | ||
iouReportUpdate.total = -iouReportUpdate.total; | ||
iouReportUpdate.unheldTotal = -iouReportUpdate.unheldTotal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though the BE stores it as negative, I still switch the sign here optimistically just to make it consistent with total
and I think it should be the correct amount, in positive.
@bernhardoj Can you please merge main, let's get this to the finish line |
Done |
Reviewer Checklist
Screenshots/VideosAndroid: Nativeandroid.movAndroid: mWeb Chromemweb-chrome.moviOS: Nativeios.moviOS: mWeb Safarimweb-safari.movMacOS: Chrome / Safariweb.movMacOS: Desktopdesktop.mov |
Can you check the failing lint and ts |
Fixed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but we have this bug that we should handle separately
Details
When approving/paying the report partially, the total held amount is wrong.
Fixed Issues
$ #48760
PROPOSAL: #48760 (comment)
Tests
Same as QA Steps
Offline tests
Same as QA Steps
QA Steps
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)Design
label and/or tagged@Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
android.mp4
Android: mWeb Chrome
android.mweb.mp4
iOS: Native
ios.mp4
iOS: mWeb Safari
ios.mweb.mp4
MacOS: Chrome / Safari
web.mp4
MacOS: Desktop
desktop.mp4