Skip to content

Commit 2359dcc

Browse files
authored
Merge pull request Expensify#65371 from ijmalik/64590
2 parents e29b652 + 8a7bcac commit 2359dcc

6 files changed

Lines changed: 145 additions & 4 deletions

src/libs/ReportPreviewActionUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,11 @@ function canPay(
164164
}
165165

166166
const parentReport = getParentReport(report);
167-
if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) {
167+
if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL && reimbursableSpend > 0) {
168168
return parentReport?.invoiceReceiver?.accountID === getCurrentUserAccountID();
169169
}
170170

171-
return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN;
171+
return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN && reimbursableSpend > 0;
172172
}
173173

174174
function canExport(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy, reportActions?: OnyxEntry<ReportActions> | ReportAction[]) {

src/libs/ReportPrimaryActionUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,11 @@ function isPrimaryPayAction(report: Report, policy?: Policy, reportNameValuePair
184184
}
185185

186186
const parentReport = getParentReport(report);
187-
if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) {
187+
if (parentReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL && reimbursableSpend > 0) {
188188
return parentReport?.invoiceReceiver?.accountID === getCurrentUserAccountID();
189189
}
190190

191-
return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN;
191+
return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN && reimbursableSpend > 0;
192192
}
193193

194194
function isExportAction(report: Report, policy?: Policy, reportActions?: ReportAction[]) {

src/libs/ReportSecondaryActionUtils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,11 +418,16 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor
418418
const isOwner = transactionID ? getIOUActionForTransactionID(reportActions, transactionID)?.actorAccountID === getCurrentUserAccountID() : false;
419419
const isReportOpenOrProcessing = isOpenReportUtils(report) || isProcessingReportUtils(report);
420420
const isSingleTransaction = reportTransactions.length === 1;
421+
const isInvoiceReport = isInvoiceReportUtils(report);
421422

422423
if (isUnreported) {
423424
return isOwner;
424425
}
425426

427+
if (isInvoiceReport) {
428+
return report?.ownerAccountID === getCurrentUserAccountID();
429+
}
430+
426431
// Users cannot delete a report in the unreported or IOU cases, but they can delete individual transactions.
427432
// So we check if the reportTransactions length is 1 which means they're viewing a single transaction and thus can delete it.
428433
if (isIOUReport) {

tests/actions/ReportPreviewActionUtilsTest.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ describe('getReportPreviewAction', () => {
241241
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
242242
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
243243
isWaitingOnBankAccount: false,
244+
total: 7,
244245
};
245246

246247
const policy = createRandomPolicy(0);
@@ -260,6 +261,51 @@ describe('getReportPreviewAction', () => {
260261
expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, undefined, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
261262
});
262263

264+
it('getReportPreviewAction should return VIEW action for zero value invoice', async () => {
265+
const PARENT_REPORT_ID = (REPORT_ID + 1).toString();
266+
const parentReport: Report = {
267+
...createRandomReport(Number(PARENT_REPORT_ID)),
268+
type: CONST.REPORT.TYPE.INVOICE,
269+
invoiceReceiver: {
270+
type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL,
271+
accountID: CURRENT_USER_ACCOUNT_ID,
272+
},
273+
policyID: '1',
274+
};
275+
276+
const report: Report = {
277+
...createRandomReport(REPORT_ID),
278+
type: CONST.REPORT.TYPE.INVOICE,
279+
parentReportID: PARENT_REPORT_ID,
280+
ownerAccountID: CURRENT_USER_ACCOUNT_ID + 1, // Different from current user
281+
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
282+
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
283+
isWaitingOnBankAccount: false,
284+
total: 0,
285+
policyID: '1',
286+
};
287+
288+
const policy = createRandomPolicy(0);
289+
policy.role = CONST.POLICY.ROLE.ADMIN;
290+
policy.type = CONST.POLICY.TYPE.CORPORATE;
291+
policy.reimbursementChoice = CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO;
292+
policy.id = '1';
293+
294+
const invoiceReceiverPolicy = createRandomPolicy(0);
295+
invoiceReceiverPolicy.role = CONST.POLICY.ROLE.ADMIN;
296+
297+
const transaction = {
298+
reportID: `${REPORT_ID}`,
299+
} as unknown as Transaction;
300+
301+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${PARENT_REPORT_ID}`, parentReport);
302+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
303+
304+
const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.parentReportID));
305+
306+
expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, undefined, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW);
307+
});
308+
263309
it('canPay should return false for archived invoice', async () => {
264310
const report = {
265311
...createRandomReport(REPORT_ID),
@@ -268,6 +314,7 @@ describe('getReportPreviewAction', () => {
268314
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
269315
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
270316
isWaitingOnBankAccount: false,
317+
total: 7,
271318
};
272319

273320
const policy = createRandomPolicy(0);

tests/unit/ReportPrimaryActionUtilsTest.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ describe('getPrimaryAction', () => {
159159
parentReportID: CHAT_REPORT_ID,
160160
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
161161
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
162+
total: 7,
162163
} as unknown as Report;
163164
const parentReport = {
164165
reportID: CHAT_REPORT_ID,
@@ -180,6 +181,36 @@ describe('getPrimaryAction', () => {
180181
expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy, invoiceReceiverPolicy})).toBe(CONST.REPORT.PRIMARY_ACTIONS.PAY);
181182
});
182183

184+
it('should not return PAY for zero value invoice report if paid as personal', async () => {
185+
const report = {
186+
reportID: REPORT_ID,
187+
type: CONST.REPORT.TYPE.INVOICE,
188+
ownerAccountID: INVOICE_SENDER_ACCOUNT_ID,
189+
parentReportID: CHAT_REPORT_ID,
190+
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
191+
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
192+
total: 0,
193+
} as unknown as Report;
194+
const parentReport = {
195+
reportID: CHAT_REPORT_ID,
196+
invoiceReceiver: {
197+
type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL,
198+
accountID: CURRENT_USER_ACCOUNT_ID,
199+
},
200+
} as unknown as Report;
201+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
202+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${CHAT_REPORT_ID}`, parentReport);
203+
const policy = {} as Policy;
204+
const invoiceReceiverPolicy = {
205+
role: CONST.POLICY.ROLE.ADMIN,
206+
} as Policy;
207+
const transaction = {
208+
reportID: `${REPORT_ID}`,
209+
} as unknown as Transaction;
210+
211+
expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy, invoiceReceiverPolicy})).toBe('');
212+
});
213+
183214
it('should return PAY for expense report with payments enabled', async () => {
184215
const report = {
185216
reportID: REPORT_ID,
@@ -603,6 +634,7 @@ describe('getTransactionThreadPrimaryAction', () => {
603634
parentReportID: CHAT_REPORT_ID,
604635
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
605636
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
637+
total: 7,
606638
} as unknown as Report;
607639
const parentReport = {
608640
reportID: CHAT_REPORT_ID,
@@ -630,4 +662,41 @@ describe('getTransactionThreadPrimaryAction', () => {
630662
}),
631663
).toBe(CONST.REPORT.PRIMARY_ACTIONS.PAY);
632664
});
665+
666+
it('should not return PAY for zero value invoice report if paid as business and the payer is the policy admin', async () => {
667+
const report = {
668+
reportID: REPORT_ID,
669+
type: CONST.REPORT.TYPE.INVOICE,
670+
ownerAccountID: INVOICE_SENDER_ACCOUNT_ID,
671+
parentReportID: CHAT_REPORT_ID,
672+
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
673+
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
674+
total: 0,
675+
} as unknown as Report;
676+
const parentReport = {
677+
reportID: CHAT_REPORT_ID,
678+
invoiceReceiver: {
679+
type: CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS,
680+
policyID: POLICY_ID,
681+
},
682+
} as unknown as Report;
683+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
684+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${CHAT_REPORT_ID}`, parentReport);
685+
const invoiceReceiverPolicy = {
686+
role: CONST.POLICY.ROLE.ADMIN,
687+
};
688+
const transaction = {
689+
reportID: `${REPORT_ID}`,
690+
} as unknown as Transaction;
691+
expect(
692+
getReportPrimaryAction({
693+
report,
694+
chatReport,
695+
reportTransactions: [transaction],
696+
violations: {},
697+
policy: {} as Policy,
698+
invoiceReceiverPolicy: invoiceReceiverPolicy as Policy,
699+
}),
700+
).toBe('');
701+
});
633702
});

tests/unit/ReportSecondaryActionUtilsTest.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,26 @@ describe('getSecondaryAction', () => {
795795
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(true);
796796
});
797797

798+
it('includes DELETE option for invoice report submitter when total is zero', async () => {
799+
const report = {
800+
reportID: REPORT_ID,
801+
type: CONST.REPORT.TYPE.INVOICE,
802+
ownerAccountID: EMPLOYEE_ACCOUNT_ID,
803+
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
804+
stateNum: CONST.REPORT.STATE_NUM.OPEN,
805+
total: 0,
806+
} as unknown as Report;
807+
808+
const policy = {
809+
role: CONST.POLICY.ROLE.USER,
810+
} as unknown as Policy;
811+
812+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
813+
814+
const result = getSecondaryTransactionThreadActions(report, {} as Transaction, [], policy);
815+
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(true);
816+
});
817+
798818
it('includes DELETE option for owner of unreported transaction', () => {
799819
const report = {
800820
reportID: REPORT_ID,

0 commit comments

Comments
 (0)