From f5c88d5424acf00cbb71d41eff185b61f1fe555b Mon Sep 17 00:00:00 2001 From: Linh Date: Tue, 2 Sep 2025 10:22:10 +0700 Subject: [PATCH 1/4] fix: hide delete button for unreported card expense imported with deleting disabled --- src/libs/ReportSecondaryActionUtils.ts | 9 ++++----- src/libs/ReportUtils.ts | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index 0aef88e03518..c72f9568f7ff 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -24,6 +24,7 @@ import {getIOUActionForReportID, getIOUActionForTransactionID, getOneTransaction import {getReportPrimaryAction, isPrimaryPayAction} from './ReportPrimaryActionUtils'; import { canAddTransaction, + canDeleteCardTransactionByLiabilityType, canEditReportPolicy, canHoldUnholdReportAction, getTransactionDetails, @@ -459,8 +460,9 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor return true; } + const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(transaction); if (isUnreported) { - return isOwner; + return isOwner && isCardTransactionCanBeDeleted; } if (isInvoiceReport) { @@ -474,10 +476,7 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor } if (isExpenseReport) { - const isCardTransactionWithCorporateLiability = - isSingleTransaction && isCardTransactionUtils(transaction) && transaction?.comment?.liabilityType === CONST.TRANSACTION.LIABILITY_TYPE.RESTRICT; - - if (isCardTransactionWithCorporateLiability) { + if (!isCardTransactionCanBeDeleted) { return false; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 161c29b653e7..b7c468794c44 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2581,9 +2581,13 @@ function canDeleteReportAction(reportAction: OnyxInputOrEntry, rep } if (isActionOwner) { + if (!isEmptyObject(report) && (isMoneyRequestReport(report) || isInvoiceReport(report))) { return canDeleteTransaction(report) && isCardTransactionCanBeDeleted; } + if(isTrackExpenseAction(reportAction)){ + return isCardTransactionCanBeDeleted + } return true; } } From 5a13d5235fc257c53f13758fb68bd0e7d625b472 Mon Sep 17 00:00:00 2001 From: Linh Date: Tue, 2 Sep 2025 10:30:35 +0700 Subject: [PATCH 2/4] fix: eslint fail --- src/libs/ReportSecondaryActionUtils.ts | 3 +-- src/libs/ReportUtils.ts | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index c72f9568f7ff..963d285f4277 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -56,7 +56,6 @@ import { allHavePendingRTERViolation, getOriginalTransactionWithSplitInfo, hasReceipt as hasReceiptTransactionUtils, - isCardTransaction as isCardTransactionUtils, isDemoTransaction, isDuplicate, isOnHold as isOnHoldTransactionUtils, @@ -462,7 +461,7 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(transaction); if (isUnreported) { - return isOwner && isCardTransactionCanBeDeleted; + return isOwner && isCardTransactionCanBeDeleted; } if (isInvoiceReport) { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b7c468794c44..58f25ad72096 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2581,12 +2581,11 @@ function canDeleteReportAction(reportAction: OnyxInputOrEntry, rep } if (isActionOwner) { - if (!isEmptyObject(report) && (isMoneyRequestReport(report) || isInvoiceReport(report))) { return canDeleteTransaction(report) && isCardTransactionCanBeDeleted; } - if(isTrackExpenseAction(reportAction)){ - return isCardTransactionCanBeDeleted + if (isTrackExpenseAction(reportAction)) { + return isCardTransactionCanBeDeleted; } return true; } From df1eca25088f68e45a1b038936ce808eb90f954e Mon Sep 17 00:00:00 2001 From: Linh Date: Tue, 2 Sep 2025 11:15:22 +0700 Subject: [PATCH 3/4] fix: write unit test for unreported card expense --- tests/unit/ReportSecondaryActionUtilsTest.ts | 42 ++++++++++++++++++ tests/unit/ReportUtilsTest.ts | 46 ++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index fd9cdd0c5730..f47d3b0329ea 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -1034,6 +1034,48 @@ describe('getSecondaryAction', () => { expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(false); }); + it('does not include DELETE option for unreported card expense imported with deleting disabled', async () => { + // Given the unreported card expense imported with deleting disabled + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.SELF_DM, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + } as unknown as Report; + + const TRANSACTION_ID = 'TRANSACTION_ID'; + + const transaction = { + transactionID: TRANSACTION_ID, + reportID: CONST.REPORT.UNREPORTED_REPORT_ID, + managedCard: true, + comment: { + liabilityType: CONST.TRANSACTION.LIABILITY_TYPE.RESTRICT, + }, + } as unknown as Transaction; + + const reportActions = [ + { + reportActionID: '1', + actorAccountID: EMPLOYEE_ACCOUNT_ID, + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + originalMessage: { + IOUTransactionID: TRANSACTION_ID, + IOUReportID: CONST.REPORT.UNREPORTED_REPORT_ID, + }, + }, + ] as unknown as ReportAction[]; + + const policy = {} as unknown as Policy; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + + // Then it should return false since the unreported card expense is imported with deleting disabled + const result = getSecondaryReportActions({report, chatReport, reportTransactions: [transaction], violations: {}, policy, reportActions}); + expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(false); + }); + it('does not include DELETE option for report that has been forwarded', async () => { const report = { reportID: REPORT_ID, diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 43aac315620c..258fe71bbc06 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -3210,6 +3210,52 @@ describe('ReportUtils', () => { expect(canDeleteReportAction(moneyRequestAction, '1', transaction)).toBe(true); }); + it('should return false for unreported card expense imported with deleting disabled', async () => { + // Given the unreported card expense import with deleting disabled + const selfDMReport = { + ...LHNTestUtils.getFakeReport(), + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.SELF_DM, + }; + + const transaction: Transaction = { + ...createRandomTransaction(1), + reportID: CONST.REPORT.UNREPORTED_REPORT_ID, + managedCard: true, + comment: { + liabilityType: CONST.TRANSACTION.LIABILITY_TYPE.RESTRICT, + }, + }; + + const trackExpenseAction: ReportAction = { + ...LHNTestUtils.getFakeReportAction(), + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + actorAccountID: currentUserAccountID, + originalMessage: { + IOUTransactionID: transaction.transactionID, + IOUReportID: CONST.REPORT.UNREPORTED_REPORT_ID, + amount: 100, + currency: CONST.CURRENCY.USD, + type: CONST.IOU.REPORT_ACTION_TYPE.TRACK, + }, + message: [ + { + type: 'COMMENT', + html: '$1.00 expense', + text: '$1.00 expense', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + }, + ], + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // Then it should return false since the unreported card expense is imported with deleting disabled + expect(canDeleteReportAction(trackExpenseAction, selfDMReport.reportID, transaction)).toBe(false); + }); + it("should return false for ADD_COMMENT report action the current user (admin of the personal policy) didn't comment", async () => { const adminPolicy = {...LHNTestUtils.getFakePolicy(), type: CONST.POLICY.TYPE.PERSONAL}; From 81881e3d49753f5725fef6d7eecae204ea375e7b Mon Sep 17 00:00:00 2001 From: Linh Date: Wed, 17 Sep 2025 17:36:33 +0700 Subject: [PATCH 4/4] chore: rename --- src/libs/ReportSecondaryActionUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index b22fe15c061e..4adf0b4e061b 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -458,9 +458,9 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor return true; } - const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(transaction); + const canCardTransactionBeDeleted = canDeleteCardTransactionByLiabilityType(transaction); if (isUnreported) { - return isOwner && isCardTransactionCanBeDeleted; + return isOwner && canCardTransactionBeDeleted; } if (isInvoiceReport) { @@ -474,7 +474,7 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor } if (isExpenseReport) { - if (!isCardTransactionCanBeDeleted) { + if (!canCardTransactionBeDeleted) { return false; }