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
13 changes: 13 additions & 0 deletions src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,18 @@ function allHavePendingRTERViolation(transactionIds: string[], transactionViolat
return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true);
}

/**
* Check if there is any transaction without RTER violation within the given transactionIDs.
*/
function hasAnyTransactionWithoutRTERViolation(transactionIds: string[], transactionViolations: OnyxCollection<TransactionViolations> | undefined): boolean {
return (
transactionIds.length > 0 &&
transactionIds.some((transactionId) => {
return !hasBrokenConnectionViolation(transactionId, transactionViolations);
})
);
}

/**
* Check if the transaction is pending or has a pending rter violation.
*/
Expand Down Expand Up @@ -1539,6 +1551,7 @@ export {
getOriginalAmount,
getFormattedAttendees,
getMerchant,
hasAnyTransactionWithoutRTERViolation,
getMerchantOrDescription,
getMCCGroup,
getCreated,
Expand Down
7 changes: 3 additions & 4 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ import {
getMerchant,
getTransaction,
getUpdatedTransaction,
hasAnyTransactionWithoutRTERViolation,
hasDuplicateTransactions,
hasReceipt as hasReceiptTransactionUtils,
isAmountMissing,
Expand All @@ -174,7 +175,6 @@ import {
isReceiptBeingScanned as isReceiptBeingScannedTransactionUtils,
isScanRequest as isScanRequestTransactionUtils,
removeSettledAndApprovedTransactions,
shouldShowBrokenConnectionViolationForMultipleTransactions,
} from '@libs/TransactionUtils';
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
import type {IOUAction, IOUActionParams, IOUType} from '@src/CONST';
Expand Down Expand Up @@ -8588,8 +8588,7 @@ function canSubmitReport(
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
const transactionIDList = transactions.map((transaction) => transaction.transactionID);
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allViolations);
const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDList, report, policy, allViolations);

const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactionIDList, allViolations);
const hasOnlyPendingCardOrScanFailTransactions =
transactions.length > 0 &&
transactions.every((t) => (isExpensifyCardTransaction(t) && isPending(t)) || (isPartialMerchant(getMerchant(t)) && isAmountMissing(t)) || isReceiptBeingScannedTransactionUtils(t));
Expand All @@ -8600,7 +8599,7 @@ function canSubmitReport(
!isArchived &&
!hasOnlyPendingCardOrScanFailTransactions &&
!hasAllPendingRTERViolations &&
!hasBrokenConnectionViolation &&
hasTransactionWithoutRTERViolation &&
(report?.ownerAccountID === currentUserAccountID || isAdmin || report?.managerID === currentUserAccountID)
);
}
Expand Down
155 changes: 155 additions & 0 deletions tests/unit/IOUUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import Onyx from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
import DateUtils from '@libs/DateUtils';
import {canSubmitReport} from '@userActions/IOU';
import CONST from '@src/CONST';
import * as IOUUtils from '@src/libs/IOUUtils';
import * as ReportUtils from '@src/libs/ReportUtils';
import * as TransactionUtils from '@src/libs/TransactionUtils';
import {hasAnyTransactionWithoutRTERViolation} from '@src/libs/TransactionUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, Report, Transaction, TransactionViolations} from '@src/types/onyx';
import type {TransactionCollectionDataSet} from '@src/types/onyx/Transaction';
import createRandomPolicy from '../utils/collections/policies';
import createRandomReport from '../utils/collections/reports';
import createRandomTransaction from '../utils/collections/transaction';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
import currencyList from './currencyList.json';

const testDate = DateUtils.getDBTime();
const currentUserAccountID = 5;

function initCurrencyList() {
Onyx.init({
keys: ONYXKEYS,
Expand Down Expand Up @@ -171,6 +182,150 @@ describe('isValidMoneyRequestType', () => {
});
});

describe('hasRTERWithoutViolation', () => {
test('Return true if there is at least one rter without violation in transactionViolations with given transactionIDs.', async () => {
const transactionIDWithViolation = 1;
const transactionIDWithoutViolation = 2;
const currentReportId = '';
const transactionWithViolation: Transaction = {
...createRandomTransaction(transactionIDWithViolation),
category: '',
tag: '',
created: testDate,
reportID: currentReportId,
};
const transactionWithoutViolation: Transaction = {
...createRandomTransaction(transactionIDWithoutViolation),
category: '',
tag: '',
created: testDate,
reportID: currentReportId,
};
const transactionViolations = `transactionViolations_${transactionIDWithViolation}`;
const violations: OnyxCollection<TransactionViolations> = {
[transactionViolations]: [
{
type: 'warning',
name: 'rter',
data: {
tooltip: "Personal Cards: Fix your card from Account Settings. Corporate Cards: ask your Expensify admin to fix your company's card connection.",
rterType: 'brokenCardConnection',
},
showInReview: true,
},
],
};

await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation);
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithoutViolation}`, transactionWithoutViolation);
expect(hasAnyTransactionWithoutRTERViolation([String(transactionIDWithoutViolation), String(transactionIDWithViolation)], violations)).toBe(true);
});

test('Return false if there is no rter without violation in all transactionViolations with given transactionIDs.', async () => {
const transactionIDWithViolation = 1;
const currentReportId = '';
const transactionWithViolation: Transaction = {
...createRandomTransaction(transactionIDWithViolation),
category: '',
tag: '',
created: testDate,
reportID: currentReportId,
};
const transactionViolations = `transactionViolations_${transactionIDWithViolation}`;
const violations: OnyxCollection<TransactionViolations> = {
[transactionViolations]: [
{
type: 'warning',
name: 'rter',
data: {
tooltip: "Personal Cards: Fix your card from Account Settings. Corporate Cards: ask your Expensify admin to fix your company's card connection.",
rterType: 'brokenCardConnection',
},
showInReview: true,
},
],
};

await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation);
expect(hasAnyTransactionWithoutRTERViolation([String(transactionIDWithViolation)], violations)).toBe(false);
});
});

describe('canSubmitReport', () => {
test('Return true if report can be submitted', async () => {
await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID});
const fakePolicy: Policy = {
...createRandomPolicy(6),
ownerAccountID: currentUserAccountID,
areRulesEnabled: true,
preventSelfApproval: false,
};
const expenseReport: Report = {
...createRandomReport(6),
type: CONST.REPORT.TYPE.EXPENSE,
managerID: currentUserAccountID,
ownerAccountID: currentUserAccountID,
policyID: fakePolicy.id,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
};

const transactionIDWithViolation = 1;
const transactionIDWithoutViolation = 2;
const transactionWithViolation: Transaction = {
...createRandomTransaction(transactionIDWithViolation),
category: '',
tag: '',
created: testDate,
reportID: expenseReport?.reportID,
};
const transactionWithoutViolation: Transaction = {
...createRandomTransaction(transactionIDWithoutViolation),
category: '',
tag: '',
created: testDate,
reportID: expenseReport?.reportID,
};
const transactionViolations = `transactionViolations_${transactionIDWithViolation}`;
const violations: OnyxCollection<TransactionViolations> = {
[transactionViolations]: [
{
type: 'warning',
name: 'rter',
data: {
tooltip: "Personal Cards: Fix your card from Account Settings. Corporate Cards: ask your Expensify admin to fix your company's card connection.",
rterType: 'brokenCardConnection',
},
showInReview: true,
},
],
};

await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation);
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithoutViolation}`, transactionWithoutViolation);
expect(canSubmitReport(expenseReport, fakePolicy, [transactionWithViolation, transactionWithoutViolation], violations)).toBe(true);
});

test('Return false if report can not be submitted', async () => {
await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID});
const fakePolicy: Policy = {
...createRandomPolicy(6),
ownerAccountID: currentUserAccountID,
areRulesEnabled: true,
preventSelfApproval: false,
};
const expenseReport: Report = {
...createRandomReport(6),
type: CONST.REPORT.TYPE.EXPENSE,
managerID: currentUserAccountID,
ownerAccountID: currentUserAccountID,
policyID: fakePolicy.id,
};

expect(canSubmitReport(expenseReport, fakePolicy, [], undefined)).toBe(false);
});
});

describe('Check valid amount for IOU/Expense request', () => {
test('IOU amount should be positive', () => {
const iouReport = ReportUtils.buildOptimisticIOUReport(1, 2, 100, '1', 'USD');
Expand Down