fix: Delete option is missing in More menu of an unpaid invoice report#73914
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 23 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
|
||
| if (isInvoiceReport(report)) { | ||
| return report?.ownerAccountID === currentUserAccountID && isReportOpenOrProcessing && policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; | ||
| return report?.ownerAccountID === currentUserAccountID && isReportOpenOrProcessing; |
There was a problem hiding this comment.
Question: Invoice deletion vs Expense Report deletion logic
The change simplifies invoice deletion by removing the policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL check. This makes sense as the previous logic was counterintuitive - it required approval workflows to be enabled to delete invoices, which seems backwards.
However, comparing this to the expense report deletion logic (lines 2756-2766), I notice expense reports have protection against deleting forwarded reports:
const isForwarded = isProcessingReport(report) && isApprovalEnabled && !isAwaitingFirstLevelApproval(report);
return isReportSubmitter && isReportOpenOrProcessing && !isForwarded;Question for reviewers: Should invoices have similar protection against deletion once they're in an approval workflow? Or are invoices intentionally different because they don't follow the same approval workflow as expense reports?
The current change allows invoice deletion based solely on:
- Owner check
- Report status (open or processing)
This seems appropriate if invoices don't participate in approval workflows the same way expense reports do. But if they do, we might want to add the !isForwarded check here as well for consistency.
There was a problem hiding this comment.
The delete invoice flow doesn’t depend on the approval mode as mentioned.
|
|
||
| if (isInvoiceReport(report)) { | ||
| return report?.ownerAccountID === currentUserAccountID && isReportOpenOrProcessing && policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; | ||
| return report?.ownerAccountID === currentUserAccountID && isReportOpenOrProcessing; |
There was a problem hiding this comment.
✅ Change looks correct - Removes illogical constraint
The change fixes a bug where invoices could ONLY be deleted when workflows were ENABLED (approvalMode !== OPTIONAL), which prevented deletion when workflows were disabled. This was backwards logic.
After the change, invoice deletion now follows the same pattern as IOU reports (line 2752-2753):
- ✅ Owner check:
report?.ownerAccountID === currentUserAccountID - ✅ State protection:
isReportOpenOrProcessing(prevents deleting approved/reimbursed invoices) - ✅ No approval mode dependency (consistent with IOU reports)
Security note: The isReportOpenOrProcessing check (line 2733) provides adequate protection by ensuring only OPEN or SUBMITTED invoices can be deleted. Once an invoice is approved or paid, it cannot be deleted.
The change is logically sound and aligns with the existing codebase patterns.
|
LGTM 👍 Thank you for your hard work! |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2025-11-01.at.17.09.03.mp4iOS: HybridAppScreen.Recording.2025-11-01.at.17.11.57.mp4iOS: mWeb SafariScreen.Recording.2025-11-01.at.17.13.44.mp4MacOS: Chrome / SafariScreen.Recording.2025-11-01.at.17.05.37.mp4 |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/cristipaval in version: 9.2.44-0 🚀
|
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.2.44-5 🚀
|



Explanation of Change
Fixed Issues
$ #72212
PROPOSAL: #72212 (comment)
Tests
Offline tests
Same as QA steps
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Precondition:
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.Screen.Recording.2025-10-31.at.3.06.48.in.the.afternoon.mov
Android: mWeb Chrome
android.chrome.Screen.Recording.2025-10-31.at.2.56.47.in.the.afternoon.mp4
iOS: Native
Ios.Screen.Recording.2025-10-31.at.2.39.29.in.the.afternoon.mp4
iOS: mWeb Safari
ios.safari.Screen.Recording.2025-10-31.at.2.42.43.in.the.afternoon.mp4
MacOS: Chrome / Safari
Web.Screen.Recording.2025-10-31.at.12.23.21.in.the.afternoon.mp4
MacOS: Desktop
Desktop.Screen.Recording.2025-10-31.at.12.31.52.in.the.afternoon.mp4