diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts index aeebc43632d7..b0a8554213c9 100644 --- a/src/libs/ReportPreviewActionUtils.ts +++ b/src/libs/ReportPreviewActionUtils.ts @@ -164,11 +164,11 @@ function canPay( } const parentReport = getParentReport(report); - if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL && reimbursableSpend > 0) { return parentReport?.invoiceReceiver?.accountID === getCurrentUserAccountID(); } - return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN; + return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN && reimbursableSpend > 0; } function canExport(report: Report, violations: OnyxCollection, policy?: Policy, reportActions?: OnyxEntry | ReportAction[]) { diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index caaf927bc49c..fb720cd54348 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -184,11 +184,11 @@ function isPrimaryPayAction(report: Report, policy?: Policy, reportNameValuePair } const parentReport = getParentReport(report); - if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL && reimbursableSpend > 0) { return parentReport?.invoiceReceiver?.accountID === getCurrentUserAccountID(); } - return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN; + return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN && reimbursableSpend > 0; } function isExportAction(report: Report, policy?: Policy, reportActions?: ReportAction[]) { diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index ab7f38235f0c..1839f28a839d 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -418,11 +418,16 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor const isOwner = transactionID ? getIOUActionForTransactionID(reportActions, transactionID)?.actorAccountID === getCurrentUserAccountID() : false; const isReportOpenOrProcessing = isOpenReportUtils(report) || isProcessingReportUtils(report); const isSingleTransaction = reportTransactions.length === 1; + const isInvoiceReport = isInvoiceReportUtils(report); if (isUnreported) { return isOwner; } + if (isInvoiceReport) { + return report?.ownerAccountID === getCurrentUserAccountID(); + } + // Users cannot delete a report in the unreported or IOU cases, but they can delete individual transactions. // So we check if the reportTransactions length is 1 which means they're viewing a single transaction and thus can delete it. if (isIOUReport) { diff --git a/tests/actions/ReportPreviewActionUtilsTest.ts b/tests/actions/ReportPreviewActionUtilsTest.ts index 09bf80d8d6b2..41fe58d66bf7 100644 --- a/tests/actions/ReportPreviewActionUtilsTest.ts +++ b/tests/actions/ReportPreviewActionUtilsTest.ts @@ -241,6 +241,7 @@ describe('getReportPreviewAction', () => { statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, isWaitingOnBankAccount: false, + total: 7, }; const policy = createRandomPolicy(0); @@ -260,6 +261,51 @@ describe('getReportPreviewAction', () => { expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, undefined, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY); }); + it('getReportPreviewAction should return VIEW action for zero value invoice', async () => { + const PARENT_REPORT_ID = (REPORT_ID + 1).toString(); + const parentReport: Report = { + ...createRandomReport(Number(PARENT_REPORT_ID)), + type: CONST.REPORT.TYPE.INVOICE, + invoiceReceiver: { + type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL, + accountID: CURRENT_USER_ACCOUNT_ID, + }, + policyID: '1', + }; + + const report: Report = { + ...createRandomReport(REPORT_ID), + type: CONST.REPORT.TYPE.INVOICE, + parentReportID: PARENT_REPORT_ID, + ownerAccountID: CURRENT_USER_ACCOUNT_ID + 1, // Different from current user + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + isWaitingOnBankAccount: false, + total: 0, + policyID: '1', + }; + + const policy = createRandomPolicy(0); + policy.role = CONST.POLICY.ROLE.ADMIN; + policy.type = CONST.POLICY.TYPE.CORPORATE; + policy.reimbursementChoice = CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; + policy.id = '1'; + + const invoiceReceiverPolicy = createRandomPolicy(0); + invoiceReceiverPolicy.role = CONST.POLICY.ROLE.ADMIN; + + const transaction = { + reportID: `${REPORT_ID}`, + } as unknown as Transaction; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${PARENT_REPORT_ID}`, parentReport); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + + const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.parentReportID)); + + expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, undefined, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW); + }); + it('canPay should return false for archived invoice', async () => { const report = { ...createRandomReport(REPORT_ID), @@ -268,6 +314,7 @@ describe('getReportPreviewAction', () => { statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, isWaitingOnBankAccount: false, + total: 7, }; const policy = createRandomPolicy(0); diff --git a/tests/unit/ReportPrimaryActionUtilsTest.ts b/tests/unit/ReportPrimaryActionUtilsTest.ts index a80af247c262..9742cd049770 100644 --- a/tests/unit/ReportPrimaryActionUtilsTest.ts +++ b/tests/unit/ReportPrimaryActionUtilsTest.ts @@ -159,6 +159,7 @@ describe('getPrimaryAction', () => { parentReportID: CHAT_REPORT_ID, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + total: 7, } as unknown as Report; const parentReport = { reportID: CHAT_REPORT_ID, @@ -180,6 +181,36 @@ describe('getPrimaryAction', () => { expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy, invoiceReceiverPolicy})).toBe(CONST.REPORT.PRIMARY_ACTIONS.PAY); }); + it('should not return PAY for zero value invoice report if paid as personal', async () => { + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.INVOICE, + ownerAccountID: INVOICE_SENDER_ACCOUNT_ID, + parentReportID: CHAT_REPORT_ID, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + total: 0, + } as unknown as Report; + const parentReport = { + reportID: CHAT_REPORT_ID, + invoiceReceiver: { + type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL, + accountID: CURRENT_USER_ACCOUNT_ID, + }, + } as unknown as Report; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${CHAT_REPORT_ID}`, parentReport); + const policy = {} as Policy; + const invoiceReceiverPolicy = { + role: CONST.POLICY.ROLE.ADMIN, + } as Policy; + const transaction = { + reportID: `${REPORT_ID}`, + } as unknown as Transaction; + + expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy, invoiceReceiverPolicy})).toBe(''); + }); + it('should return PAY for expense report with payments enabled', async () => { const report = { reportID: REPORT_ID, @@ -603,6 +634,7 @@ describe('getTransactionThreadPrimaryAction', () => { parentReportID: CHAT_REPORT_ID, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + total: 7, } as unknown as Report; const parentReport = { reportID: CHAT_REPORT_ID, @@ -630,4 +662,41 @@ describe('getTransactionThreadPrimaryAction', () => { }), ).toBe(CONST.REPORT.PRIMARY_ACTIONS.PAY); }); + + it('should not return PAY for zero value invoice report if paid as business and the payer is the policy admin', async () => { + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.INVOICE, + ownerAccountID: INVOICE_SENDER_ACCOUNT_ID, + parentReportID: CHAT_REPORT_ID, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + total: 0, + } as unknown as Report; + const parentReport = { + reportID: CHAT_REPORT_ID, + invoiceReceiver: { + type: CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS, + policyID: POLICY_ID, + }, + } as unknown as Report; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${CHAT_REPORT_ID}`, parentReport); + const invoiceReceiverPolicy = { + role: CONST.POLICY.ROLE.ADMIN, + }; + const transaction = { + reportID: `${REPORT_ID}`, + } as unknown as Transaction; + expect( + getReportPrimaryAction({ + report, + chatReport, + reportTransactions: [transaction], + violations: {}, + policy: {} as Policy, + invoiceReceiverPolicy: invoiceReceiverPolicy as Policy, + }), + ).toBe(''); + }); }); diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index 389209732e95..e38d03f787c7 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -795,6 +795,26 @@ describe('getSecondaryAction', () => { expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(true); }); + it('includes DELETE option for invoice report submitter when total is zero', async () => { + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.INVOICE, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + total: 0, + } as unknown as Report; + + const policy = { + role: CONST.POLICY.ROLE.USER, + } as unknown as Policy; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + + const result = getSecondaryTransactionThreadActions(report, {} as Transaction, [], policy); + expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(true); + }); + it('includes DELETE option for owner of unreported transaction', () => { const report = { reportID: REPORT_ID,