Skip to content

Commit

Permalink
Merge pull request #73 from infinitered/trevorcoleman/violations/viol…
Browse files Browse the repository at this point in the history
…ation-utils

Implement ViolationUtils lib Expensify#31083
  • Loading branch information
trevor-coleman authored Nov 21, 2023
2 parents ff4c947 + ccb4206 commit 6a67fb8
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ const ONYXKEYS = {
REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_',
SECURITY_GROUP: 'securityGroup_',
TRANSACTION: 'transactions_',
TRANSACTION_VIOLATIONS: 'transactionViolations_',
SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_',
PRIVATE_NOTES_DRAFT: 'privateNotesDraft_',
NEXT_STEP: 'reportNextStep_',
Expand Down
69 changes: 69 additions & 0 deletions src/libs/Violations/ViolationsUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import reject from 'lodash/reject';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx';
import possibleViolationsByField, {ViolationField} from './possibleViolationsByField';

const ViolationsUtils = {
getViolationForField(transactionViolations: TransactionViolation[], field: ViolationField, translate: (key: string) => string): string[] {
return transactionViolations.filter((violation) => possibleViolationsByField[field]?.includes(violation.name)).map((violation) => translate(violation.name));
},

getViolationsOnyxData(
/** The transaction to check for policy violations. */
transaction: Transaction,
/** An array of existing transaction violations. */
transactionViolations: TransactionViolation[],
/** Indicates if the policy requires tags. */
policyRequiresTags: boolean,
/** Collection of policy tags and their enabled states. */
policyTags: PolicyTags,
/** Indicates if the policy requires categories. */
policyRequiresCategories: boolean,
/** Collection of policy categories and their enabled states. */
policyCategories: PolicyCategories,
): {
onyxMethod: string;
key: string;
value: TransactionViolation[];
} {
let newTransactionViolations = [...transactionViolations];

if (policyRequiresCategories) {
const categoryViolationExists = transactionViolations.some((violation) => violation.name === 'categoryOutOfPolicy');
const categoryIsInPolicy = policyCategories[transaction.category]?.enabled;

// Add 'categoryOutOfPolicy' violation if category is not in policy
if (!categoryViolationExists && transaction.category && !categoryIsInPolicy) {
newTransactionViolations.push({name: 'categoryOutOfPolicy', type: 'violation', userMessage: ''});
}

// Remove 'missingCategory' violation if category is valid according to policy
if (categoryIsInPolicy) {
newTransactionViolations = reject(newTransactionViolations, {name: 'missingCategory'});
}
}

if (policyRequiresTags) {
// Add 'tagOutOfPolicy' violation if tag is not in policy
const tagViolationExists = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy');
const tagInPolicy = policyTags[transaction.tag]?.enabled;
if (!tagViolationExists && transaction.tag && !tagInPolicy) {
newTransactionViolations.push({name: 'tagOutOfPolicy', type: 'violation', userMessage: ''});
}

// Remove 'missingTag' violation if tag is valid according to policy
if (tagInPolicy) {
newTransactionViolations = reject(newTransactionViolations, {name: 'missingTag'});
}
}

return {
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`,
value: newTransactionViolations,
};
},
};

export default ViolationsUtils;
3 changes: 3 additions & 0 deletions src/libs/Violations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as ViolationsUtils from './ViolationsUtils';

export default ViolationsUtils;
41 changes: 41 additions & 0 deletions src/libs/Violations/possibleViolationsByField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import invertBy from 'lodash/invertBy';
import {ViolationName} from '@src/types/onyx';

/**
* Map from Violation Names to the field where that violation can occur
*/
const violationFields: Record<ViolationName, ViolationField> = {
perDayLimit: 'amount',
maxAge: 'date',
overLimit: 'amount',
overLimitAttendee: 'amount',
overCategoryLimit: 'amount',
receiptRequired: 'receipt',
missingCategory: 'category',
categoryOutOfPolicy: 'category',
missingTag: 'tag',
tagOutOfPolicy: 'tag',
missingComment: 'comment',
taxRequired: 'tax',
taxOutOfPolicy: 'tax',
billableExpense: 'billable',
};

/**
* Names of Fields where violations can occur
*/
type ViolationField = 'merchant' | 'amount' | 'category' | 'date' | 'tag' | 'comment' | 'billable' | 'receipt' | 'tax';

/**
* Map from field name to array of violation types that can occur on that field.
* @example
* {
* // ...
* category: ['missingCategory', 'categoryOutOfPolicy']
* // ...
* }
*/
const possibleViolationsByField = invertBy(violationFields, (value) => value) as Record<ViolationField, ViolationName[]>;

export default possibleViolationsByField;
export type {ViolationField};
2 changes: 2 additions & 0 deletions src/types/onyx/PolicyCategory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ type PolicyCategory = {
origin: string;
};

type PolicyCategories = Record<string, PolicyCategory>;
export default PolicyCategory;
export type {PolicyCategories};
34 changes: 34 additions & 0 deletions src/types/onyx/TransactionViolation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @module TransactionViolation
* @description Transaction Violation
*/

/**
* Names of the various Transaction Violation types
*/
type ViolationName =
| 'perDayLimit'
| 'maxAge'
| 'overLimit'
| 'overLimitAttendee'
| 'overCategoryLimit'
| 'receiptRequired'
| 'missingCategory'
| 'categoryOutOfPolicy'
| 'missingTag'
| 'tagOutOfPolicy'
| 'missingComment'
| 'taxRequired'
| 'taxOutOfPolicy'
| 'billableExpense';

type ViolationType = string;

type TransactionViolation = {
type: ViolationType;
name: ViolationName;
userMessage: string;
data?: Record<string, string>;
};

export type {TransactionViolation, ViolationName, ViolationType};
7 changes: 6 additions & 1 deletion src/types/onyx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import PersonalBankAccount from './PersonalBankAccount';
import PersonalDetails from './PersonalDetails';
import PlaidData from './PlaidData';
import Policy from './Policy';
import PolicyCategory from './PolicyCategory';
import PolicyCategory, {PolicyCategories} from './PolicyCategory';
import PolicyMember, {PolicyMembers} from './PolicyMember';
import PolicyTag, {PolicyTags} from './PolicyTag';
import PrivatePersonalDetails from './PrivatePersonalDetails';
Expand All @@ -42,6 +42,7 @@ import SecurityGroup from './SecurityGroup';
import Session from './Session';
import Task from './Task';
import Transaction from './Transaction';
import {TransactionViolation, ViolationName, ViolationType} from './TransactionViolation';
import User from './User';
import UserWallet from './UserWallet';
import WalletAdditionalDetails from './WalletAdditionalDetails';
Expand Down Expand Up @@ -78,6 +79,7 @@ export type {
PlaidData,
Policy,
PolicyCategory,
PolicyCategories,
PolicyMember,
PolicyMembers,
PolicyTag,
Expand All @@ -101,8 +103,11 @@ export type {
Session,
Task,
Transaction,
TransactionViolation,
User,
UserWallet,
ViolationName,
ViolationType,
WalletAdditionalDetails,
WalletOnfido,
WalletStatement,
Expand Down

0 comments on commit 6a67fb8

Please sign in to comment.