Skip to content
Open
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
16 changes: 8 additions & 8 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2145,15 +2145,15 @@ function pushTransactionViolationsOnyxData(
for (const {transactions, violations} of nonInvoiceReportTransactionsAndViolations) {
for (const transaction of Object.values(transactions)) {
const existingViolations = violations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`];
const optimisticViolations = ViolationsUtils.getViolationsOnyxData(
transaction,
existingViolations ?? [],
optimisticPolicy,
optimisticTagLists,
optimisticCategories,
const optimisticViolations = ViolationsUtils.getViolationsOnyxData({
updatedTransaction: transaction,
transactionViolations: existingViolations ?? [],
policy: optimisticPolicy,
policyTagList: optimisticTagLists,
policyCategories: optimisticCategories,
hasDependentTags,
false,
);
isInvoiceTransaction: false,
});

if (!isEmptyObject(optimisticViolations)) {
onyxData.optimisticData?.push(optimisticViolations);
Expand Down
42 changes: 28 additions & 14 deletions src/libs/Violations/ViolationsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,18 +339,31 @@ const ViolationsUtils = {
* Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction
* violations.
*/
getViolationsOnyxData(
updatedTransaction: Transaction,
transactionViolations: TransactionViolation[],
policy: Policy,
policyTagList: PolicyTagLists,
policyCategories: PolicyCategories,
hasDependentTags: boolean,
isInvoiceTransaction: boolean,
isSelfDM?: boolean,
iouReport?: OnyxEntry<Report>,
isFromExpenseReport?: boolean,
): OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS> {
getViolationsOnyxData({
updatedTransaction,
transactionViolations,
policy,
policyTagList,
policyCategories,
hasDependentTags,
isInvoiceTransaction,
isSelfDM,
iouReport,
isFromExpenseReport,
shouldRemoveRejectedExpenseViolation,
}: {
updatedTransaction: Transaction;
transactionViolations: TransactionViolation[];
policy: Policy;
policyTagList: PolicyTagLists;
policyCategories: PolicyCategories;
hasDependentTags: boolean;
isInvoiceTransaction: boolean;
isSelfDM?: boolean;
iouReport?: OnyxEntry<Report>;
isFromExpenseReport?: boolean;
shouldRemoveRejectedExpenseViolation?: boolean;
}): OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS> {
const isScanning = TransactionUtils.isScanning(updatedTransaction);
const isScanRequest = TransactionUtils.isScanRequest(updatedTransaction);
const isPartialTransaction = TransactionUtils.isPartialTransaction(updatedTransaction);
Expand All @@ -364,8 +377,9 @@ const ViolationsUtils = {

let newTransactionViolations = [...transactionViolations];

// Remove AUTO_REPORTED_REJECTED_EXPENSE violation when the submitter edits the expense
if (iouReport && isFromExpenseReport && isCurrentUserSubmitter(iouReport)) {
// Remove AUTO_REPORTED_REJECTED_EXPENSE violation when the submitter edits the expense, when the transaction is moved to a different report,
// or when explicitly requested (e.g. from changeTransactionsReport)
if (shouldRemoveRejectedExpenseViolation || (iouReport && isFromExpenseReport && isCurrentUserSubmitter(iouReport))) {
const hasRejectedExpenseViolation = newTransactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.AUTO_REPORTED_REJECTED_EXPENSE);
if (hasRejectedExpenseViolation) {
newTransactionViolations = newTransactionViolations.filter((violation) => violation.name !== CONST.VIOLATIONS.AUTO_REPORTED_REJECTED_EXPENSE);
Expand Down
18 changes: 9 additions & 9 deletions src/libs/actions/IOU/BulkEdit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,18 +328,18 @@ function updateMultipleMoneyRequests({
: optimisticViolations;
const transactionPolicyTagList = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${transactionPolicy?.id}`] ?? {};
const transactionPolicyCategories = policyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${transactionPolicy?.id}`] ?? {};
optimisticViolationsData = ViolationsUtils.getViolationsOnyxData(
optimisticViolationsData = ViolationsUtils.getViolationsOnyxData({
updatedTransaction,
optimisticViolations,
transactionPolicy,
transactionPolicyTagList,
transactionPolicyCategories,
hasDependentTags(transactionPolicy, transactionPolicyTagList),
isInvoiceReportReportUtils(iouReport),
isSelfDM(iouReport),
transactionViolations: optimisticViolations,
policy: transactionPolicy,
policyTagList: transactionPolicyTagList,
policyCategories: transactionPolicyCategories,
hasDependentTags: hasDependentTags(transactionPolicy, transactionPolicyTagList),
isInvoiceTransaction: isInvoiceReportReportUtils(iouReport),
isSelfDM: isSelfDM(iouReport),
iouReport,
isFromExpenseReport,
);
});
optimisticData.push(optimisticViolationsData);
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down
16 changes: 8 additions & 8 deletions src/libs/actions/IOU/MoneyRequestBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,15 +996,15 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR
if (!policy || !isPaidGroupPolicy(policy) || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
return onyxData;
}
const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(
transaction,
[],
const violationsOnyxData = ViolationsUtils.getViolationsOnyxData({
updatedTransaction: transaction,
transactionViolations: [],
policy,
policyTagList ?? {},
policyCategories ?? {},
hasDependentTags(policy, policyTagList ?? {}),
false,
);
policyTagList: policyTagList ?? {},
policyCategories: policyCategories ?? {},
hasDependentTags: hasDependentTags(policy, policyTagList ?? {}),
isInvoiceTransaction: false,
});

if (violationsOnyxData) {
const reportTransactions = [...getReportTransactions(iou.report.reportID).filter((reportTransaction) => reportTransaction.transactionID !== transaction.transactionID), transaction];
Expand Down
36 changes: 18 additions & 18 deletions src/libs/actions/IOU/Receipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,15 @@ function detachReceipt(

if (transactionPolicy && isPaidGroupPolicy(transactionPolicy) && newTransaction) {
const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? [];
const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(
newTransaction,
currentTransactionViolations,
transactionPolicy,
transactionPolicyTagList ?? {},
transactionPolicyCategories ?? {},
hasDependentTags(transactionPolicy, transactionPolicyTagList ?? {}),
isInvoiceReportReportUtils(expenseReport),
);
const violationsOnyxData = ViolationsUtils.getViolationsOnyxData({
updatedTransaction: newTransaction,
transactionViolations: currentTransactionViolations,
policy: transactionPolicy,
policyTagList: transactionPolicyTagList ?? {},
policyCategories: transactionPolicyCategories ?? {},
hasDependentTags: hasDependentTags(transactionPolicy, transactionPolicyTagList ?? {}),
isInvoiceTransaction: isInvoiceReportReportUtils(expenseReport),
});
optimisticData.push(violationsOnyxData);
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -237,15 +237,15 @@ function replaceReceipt({transactionID, file, source, state, transactionPolicy,

if (transactionPolicy && isPaidGroupPolicy(transactionPolicy) && newTransaction) {
const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? [];
const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(
newTransaction,
currentTransactionViolations,
transactionPolicy,
transactionPolicyTagList ?? {},
transactionPolicyCategories ?? {},
hasDependentTags(transactionPolicy, transactionPolicyTagList ?? {}),
isInvoiceReportReportUtils(expenseReport),
);
const violationsOnyxData = ViolationsUtils.getViolationsOnyxData({
updatedTransaction: newTransaction,
transactionViolations: currentTransactionViolations,
policy: transactionPolicy,
policyTagList: transactionPolicyTagList ?? {},
policyCategories: transactionPolicyCategories ?? {},
hasDependentTags: hasDependentTags(transactionPolicy, transactionPolicyTagList ?? {}),
isInvoiceTransaction: isInvoiceReportReportUtils(expenseReport),
});
optimisticData.push(violationsOnyxData);
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down
16 changes: 8 additions & 8 deletions src/libs/actions/IOU/UpdateMoneyRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1540,18 +1540,18 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
);
}

const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(
const violationsOnyxData = ViolationsUtils.getViolationsOnyxData({
updatedTransaction,
optimisticViolations,
transactionViolations: optimisticViolations,
policy,
policyTagList ?? {},
policyCategories ?? {},
hasDependentTags(policy, policyTagList ?? {}),
isInvoice,
isSelfDM(iouReport),
policyTagList: policyTagList ?? {},
policyCategories: policyCategories ?? {},
hasDependentTags: hasDependentTags(policy, policyTagList ?? {}),
isInvoiceTransaction: isInvoice,
isSelfDM: isSelfDM(iouReport),
iouReport,
isFromExpenseReport,
);
});
optimisticData.push(violationsOnyxData);
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down
33 changes: 17 additions & 16 deletions src/libs/actions/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1270,15 +1270,16 @@ function changeTransactionsReport({
let transactionReimbursable = transaction.reimbursable;
// 2. Calculate transaction violations if moving transaction to a workspace
if (isPaidGroupPolicy(policy) && policy?.id) {
const violationData = ViolationsUtils.getViolationsOnyxData(
transactionForViolations,
currentTransactionViolations[transaction.transactionID] ?? [],
const violationData = ViolationsUtils.getViolationsOnyxData({
updatedTransaction: transactionForViolations,
transactionViolations: currentTransactionViolations[transaction.transactionID] ?? [],
policy,
policyTagList ?? {},
policyCategories ?? {},
policyHasDependentTags,
false,
);
policyTagList: policyTagList ?? {},
policyCategories: policyCategories ?? {},
hasDependentTags: policyHasDependentTags,
isInvoiceTransaction: false,
shouldRemoveRejectedExpenseViolation: true,
});
optimisticData.push(violationData);
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -1668,15 +1669,15 @@ function changeTransactionsReport({
if (!isPaidGroupPolicy(policy) || !policy?.id) {
continue;
}
const violationData = ViolationsUtils.getViolationsOnyxData(
transaction,
currentTransactionViolations[transaction.transactionID] ?? [],
const violationData = ViolationsUtils.getViolationsOnyxData({
updatedTransaction: transaction,
transactionViolations: currentTransactionViolations[transaction.transactionID] ?? [],
policy,
policyTagList ?? {},
policyCategories ?? {},
policyHasDependentTags,
false,
);
policyTagList: policyTagList ?? {},
policyCategories: policyCategories ?? {},
hasDependentTags: policyHasDependentTags,
isInvoiceTransaction: false,
});
if (Array.isArray(violationData.value) && hasSubmissionBlockingViolationInList(violationData.value)) {
shouldFixViolations = true;
}
Expand Down
46 changes: 45 additions & 1 deletion tests/unit/TransactionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {ReportCollectionDataSet} from '@src/types/onyx/Report';
import type {OnyxData} from '@src/types/onyx/Request';
import type {UpdateMoneyRequestDataKeys} from '../../src/libs/actions/IOU/UpdateMoneyRequest';
import * as TransactionUtils from '../../src/libs/TransactionUtils';
import type {PersonalDetails, PolicyTagLists, RecentWaypoint, Report, ReportAction, ReportActions, Transaction} from '../../src/types/onyx';
import type {PersonalDetails, Policy, PolicyTagLists, RecentWaypoint, Report, ReportAction, ReportActions, Transaction} from '../../src/types/onyx';
import createRandomPolicy from '../utils/collections/policies';
import createRandomPolicyCategories from '../utils/collections/policyCategory';
import {createExpenseReport, createRandomReport} from '../utils/collections/reports';
Expand Down Expand Up @@ -1336,6 +1336,50 @@ describe('Transaction', () => {
expect(missingTagViolations.some((violation) => violation.data?.tagName === 'City')).toBe(true);
});

it('removes AUTO_REPORTED_REJECTED_EXPENSE from transaction violations when moving to a paid group policy', async () => {
const policyID = '1001';
const transaction = generateTransaction({reportID: FAKE_OLD_REPORT_ID});
const oldIOUAction = createIOUAction(transaction);
const newExpenseReport = {
...createExpenseReport(Number(FAKE_NEW_REPORT_ID)),
policyID,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
ownerAccountID: CURRENT_USER_ID,
};
const policy: Policy = {
...createRandomPolicy(Number(policyID), CONST.POLICY.TYPE.TEAM),
requiresTag: false,
requiresCategory: false,
};

await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction);
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${FAKE_OLD_REPORT_ID}`, {[oldIOUAction.reportActionID]: oldIOUAction});
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`, [
{name: CONST.VIOLATIONS.AUTO_REPORTED_REJECTED_EXPENSE, type: CONST.VIOLATION_TYPES.VIOLATION, showInReview: true},
]);
await waitForBatchedUpdates();

const allTransactions = {
[`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction,
};

changeTransactionsReport({
transactionIDs: [transaction.transactionID],
isASAPSubmitBetaEnabled: false,
accountID: CURRENT_USER_ID,
email: 'test@example.com',
newReport: newExpenseReport,
policy,
allTransactions,
policyTagList: {},
});
await waitForBatchedUpdates();

const updatedViolations = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`);
expect(updatedViolations?.some((violation) => violation.name === CONST.VIOLATIONS.AUTO_REPORTED_REJECTED_EXPENSE)).toBe(false);
});

it('should auto-select a valid distance rate when moving a distance expense with an invalid P2P rate to a workspace', async () => {
const policyID = '100';
const validRateID = 'valid_rate_1';
Expand Down
Loading
Loading