Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/libs/ReportPreviewActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransactionViolation[]>, policy?: Policy, reportActions?: OnyxEntry<ReportActions> | ReportAction[]) {
Expand Down
4 changes: 2 additions & 2 deletions src/libs/ReportPrimaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]) {
Expand Down
5 changes: 5 additions & 0 deletions src/libs/ReportSecondaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
47 changes: 47 additions & 0 deletions tests/actions/ReportPreviewActionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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),
Expand All @@ -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);
Expand Down
69 changes: 69 additions & 0 deletions tests/unit/ReportPrimaryActionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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('');
});
});
20 changes: 20 additions & 0 deletions tests/unit/ReportSecondaryActionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading