From f25197834cad73b816672fcaf87ba7687bd15ec8 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 17 Nov 2023 14:07:47 -0500 Subject: [PATCH 1/7] feat(Violations): Add PolicyCategories type to match PolicyTags --- src/types/onyx/PolicyCategory.ts | 2 ++ src/types/onyx/index.ts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/PolicyCategory.ts b/src/types/onyx/PolicyCategory.ts index adaf16e1acec..b6dfb7bbab9a 100644 --- a/src/types/onyx/PolicyCategory.ts +++ b/src/types/onyx/PolicyCategory.ts @@ -19,4 +19,6 @@ type PolicyCategory = { origin: string; }; +type PolicyCategories = Record; export default PolicyCategory; +export type {PolicyCategories}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 87cf24d6dec7..ca1cde7fcc61 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -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'; @@ -78,6 +78,7 @@ export type { PlaidData, Policy, PolicyCategory, + PolicyCategories, PolicyMember, PolicyMembers, PolicyTag, From 51c2e7e920da7533c605af35f32b0309d127aaa2 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 17 Nov 2023 14:08:30 -0500 Subject: [PATCH 2/7] feat(Violations): Create stub for ViolationUtils --- src/libs/Violations/ViolationsUtils.ts | 88 ++++++++++++++++++++++++++ src/libs/Violations/index.ts | 3 + 2 files changed, 91 insertions(+) create mode 100644 src/libs/Violations/ViolationsUtils.ts create mode 100644 src/libs/Violations/index.ts diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts new file mode 100644 index 000000000000..4152573a1f11 --- /dev/null +++ b/src/libs/Violations/ViolationsUtils.ts @@ -0,0 +1,88 @@ +// TODO: String literal for Violation Type +import {PolicyTags, Transaction} from '@src/types/onyx'; +import {PolicyCategories} from '@src/types/onyx/PolicyCategory'; + +type ViolationType =string; + +// TODO: String literal for Violation Name +type ViolationName = string; + +type ViolationField = 'merchant' | 'amount' | 'category' | 'date' | 'tag' | 'comment' | 'billable' | 'receipt' | 'tax'; + +type TransactionViolation = { + type: ViolationType; + name: ViolationName; + userMessage: string; + data?: Record +}; + +const formFields = { + merchant: 'merchant', + amount: 'amount', + category: 'category', + date: 'date', + tag: 'tag', + comment: 'comment', + billable: 'billable', + receipt: 'receipt', + tax: 'tax', +}; + +const violationFields = { + perDayLimit: formFields.amount, + maxAge: formFields.date, + overLimit: formFields.amount, + overLimitAttendee: formFields.amount, + overCategoryLimit: formFields.amount, + receiptRequired: formFields.receipt, + missingCategory: formFields.category, + categoryOutOfPolicy: formFields.category, + missingTag: formFields.tag, + tagOutOfPolicy: formFields.tag, + missingComment: formFields.comment, + taxRequired: formFields.tax, + taxOutOfPolicy: formFields.tax, + billableExpense: formFields.billable, +}; + + + +const ViolationsUtils = { + getViolationForField(transactionViolation:TransactionViolation, field:ViolationField +) :string { + console.log('getViolationsForField()', {transactionViolation, field}); + throw new Error('Not implemented: getViolationsForField'); + }, + getViolationsOnyxData( + { + transaction, + transactionViolations, + policyRequiresCategories, + policyRequiresTags, + policyCategories, + policyTags + }:{ + transaction: Transaction, + transactionViolations: TransactionViolation, + policyRequiresTags: boolean, + policyTags: PolicyTags, + policyRequiresCategories:boolean, + policyCategories: PolicyCategories + }){ + + console.log('getViolationsOnyxData()', { + transaction, + transactionViolations, + policyRequiresCategories, + policyRequiresTags, + policyCategories, + policyTags + }); + + throw new Error('Not implemented: getViolationsOnyxData()'); + }, + + +} + +export default ViolationsUtils; diff --git a/src/libs/Violations/index.ts b/src/libs/Violations/index.ts new file mode 100644 index 000000000000..3ee08a4000d0 --- /dev/null +++ b/src/libs/Violations/index.ts @@ -0,0 +1,3 @@ +import * as ViolationsUtils from './ViolationsUtils'; + +export default ViolationsUtils; From f0010663f1297a0fe702983daaf7a5313d8f9e68 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 17 Nov 2023 16:32:23 -0500 Subject: [PATCH 3/7] feat(Violations): Add TRANSACTION_VIOLATION to ONYXKEYS.COLLECTIONS --- src/ONYXKEYS.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a5a969adb833..414570cafe5c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -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_', From 27052809ba17434b8982aabc7e02c909af678527 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 17 Nov 2023 16:34:36 -0500 Subject: [PATCH 4/7] feat(Violations): Add TransactionViolation and related types to src/types/onyx --- src/types/onyx/TransactionViolation.ts | 39 ++++++++++++++++++++++++++ src/types/onyx/index.ts | 8 ++++++ 2 files changed, 47 insertions(+) create mode 100644 src/types/onyx/TransactionViolation.ts diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts new file mode 100644 index 000000000000..732c7e3f58d7 --- /dev/null +++ b/src/types/onyx/TransactionViolation.ts @@ -0,0 +1,39 @@ +/** + * @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 +}; + +export type { + TransactionViolation, + ViolationName, + ViolationType, +}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index ca1cde7fcc61..42aa028ead0c 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -49,6 +49,11 @@ import WalletOnfido from './WalletOnfido'; import WalletStatement from './WalletStatement'; import WalletTerms from './WalletTerms'; import WalletTransfer from './WalletTransfer'; +import { + TransactionViolation, + ViolationName, + ViolationType, +} from './TransactionViolation'; export type { Account, @@ -102,8 +107,11 @@ export type { Session, Task, Transaction, + TransactionViolation, User, UserWallet, + ViolationName, + ViolationType, WalletAdditionalDetails, WalletOnfido, WalletStatement, From e2aaa6a1707a114d2b0c075c09cf372f99c55a67 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 17 Nov 2023 16:35:16 -0500 Subject: [PATCH 5/7] feat(Violations): create possibleViolationsByField lookup --- .../Violations/possibleViolationsByField.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/libs/Violations/possibleViolationsByField.ts diff --git a/src/libs/Violations/possibleViolationsByField.ts b/src/libs/Violations/possibleViolationsByField.ts new file mode 100644 index 000000000000..d52e19851c72 --- /dev/null +++ b/src/libs/Violations/possibleViolationsByField.ts @@ -0,0 +1,52 @@ +import {ViolationName} from '@src/types/onyx'; +import invertBy from 'lodash/invertBy'; + + +/** + * Map from Violation Names to the field where that violation can occur + */ +const violationFields: Record = { + 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; + + +export default possibleViolationsByField +export type {ViolationField} From 7f39f5a0a54cba08ff0450af87365f1c62d1ccba Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 17 Nov 2023 16:36:28 -0500 Subject: [PATCH 6/7] feat(Violations): implement getViolationForField and getViolationsOnyxData --- src/libs/Violations/ViolationsUtils.ts | 144 ++++++++++++------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 4152573a1f11..2a888814bce3 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,88 +1,84 @@ -// TODO: String literal for Violation Type -import {PolicyTags, Transaction} from '@src/types/onyx'; -import {PolicyCategories} from '@src/types/onyx/PolicyCategory'; +import ONYXKEYS from '@src/ONYXKEYS'; +import { + Transaction, + TransactionViolation, + PolicyTags, + PolicyCategories, +} from '@src/types/onyx'; +import Onyx from 'react-native-onyx'; +import reject from 'lodash/reject'; +import possibleViolationsByField, {ViolationField} from './possibleViolationsByField'; -type ViolationType =string; -// TODO: String literal for Violation Name -type ViolationName = string; -type ViolationField = 'merchant' | 'amount' | 'category' | 'date' | 'tag' | 'comment' | 'billable' | 'receipt' | 'tax'; +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)); + }, -type TransactionViolation = { - type: ViolationType; - name: ViolationName; - userMessage: string; - data?: Record -}; + 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]; -const formFields = { - merchant: 'merchant', - amount: 'amount', - category: 'category', - date: 'date', - tag: 'tag', - comment: 'comment', - billable: 'billable', - receipt: 'receipt', - tax: 'tax', -}; -const violationFields = { - perDayLimit: formFields.amount, - maxAge: formFields.date, - overLimit: formFields.amount, - overLimitAttendee: formFields.amount, - overCategoryLimit: formFields.amount, - receiptRequired: formFields.receipt, - missingCategory: formFields.category, - categoryOutOfPolicy: formFields.category, - missingTag: formFields.tag, - tagOutOfPolicy: formFields.tag, - missingComment: formFields.comment, - taxRequired: formFields.tax, - taxOutOfPolicy: formFields.tax, - billableExpense: formFields.billable, -}; + 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'}); + } + } -const ViolationsUtils = { - getViolationForField(transactionViolation:TransactionViolation, field:ViolationField -) :string { - console.log('getViolationsForField()', {transactionViolation, field}); - throw new Error('Not implemented: getViolationsForField'); - }, - getViolationsOnyxData( - { - transaction, - transactionViolations, - policyRequiresCategories, - policyRequiresTags, - policyCategories, - policyTags - }:{ - transaction: Transaction, - transactionViolations: TransactionViolation, - policyRequiresTags: boolean, - policyTags: PolicyTags, - policyRequiresCategories:boolean, - policyCategories: PolicyCategories - }){ - - console.log('getViolationsOnyxData()', { - transaction, - transactionViolations, - policyRequiresCategories, - policyRequiresTags, - policyCategories, - policyTags - }); - throw new Error('Not implemented: getViolationsOnyxData()'); - }, + 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; From ccb4206ff8e635e566fb632670f7f9e6fc6018f1 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 20 Nov 2023 13:35:28 -0500 Subject: [PATCH 7/7] prettier --- src/libs/Violations/ViolationsUtils.ts | 37 ++++++------------- .../Violations/possibleViolationsByField.ts | 19 ++-------- src/types/onyx/TransactionViolation.ts | 37 ++++++++----------- src/types/onyx/index.ts | 6 +-- 4 files changed, 32 insertions(+), 67 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 2a888814bce3..28f5aedf10b9 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,25 +1,12 @@ -import ONYXKEYS from '@src/ONYXKEYS'; -import { - Transaction, - TransactionViolation, - PolicyTags, - PolicyCategories, -} from '@src/types/onyx'; -import Onyx from 'react-native-onyx'; 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)); + 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( @@ -36,15 +23,14 @@ const ViolationsUtils = { /** Collection of policy categories and their enabled states. */ policyCategories: PolicyCategories, ): { - onyxMethod: string, - key: string, - value: TransactionViolation[] + onyxMethod: string; + key: string; + value: TransactionViolation[]; } { let newTransactionViolations = [...transactionViolations]; - if (policyRequiresCategories) { - const categoryViolationExists = transactionViolations.some(violation => violation.name === 'categoryOutOfPolicy'); + const categoryViolationExists = transactionViolations.some((violation) => violation.name === 'categoryOutOfPolicy'); const categoryIsInPolicy = policyCategories[transaction.category]?.enabled; // Add 'categoryOutOfPolicy' violation if category is not in policy @@ -58,10 +44,9 @@ const ViolationsUtils = { } } - if (policyRequiresTags) { // Add 'tagOutOfPolicy' violation if tag is not in policy - const tagViolationExists = transactionViolations.some(violation => violation.name === 'tagOutOfPolicy'); + 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: ''}); @@ -76,7 +61,7 @@ const ViolationsUtils = { return { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`, - value: newTransactionViolations + value: newTransactionViolations, }; }, }; diff --git a/src/libs/Violations/possibleViolationsByField.ts b/src/libs/Violations/possibleViolationsByField.ts index d52e19851c72..4dfc1c22b054 100644 --- a/src/libs/Violations/possibleViolationsByField.ts +++ b/src/libs/Violations/possibleViolationsByField.ts @@ -1,6 +1,5 @@ -import {ViolationName} from '@src/types/onyx'; import invertBy from 'lodash/invertBy'; - +import {ViolationName} from '@src/types/onyx'; /** * Map from Violation Names to the field where that violation can occur @@ -25,16 +24,7 @@ const violationFields: Record = { /** * Names of Fields where violations can occur */ -type ViolationField = - | 'merchant' - | 'amount' - | 'category' - | 'date' - | 'tag' - | 'comment' - | 'billable' - | 'receipt' - | 'tax'; +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. @@ -47,6 +37,5 @@ type ViolationField = */ const possibleViolationsByField = invertBy(violationFields, (value) => value) as Record; - -export default possibleViolationsByField -export type {ViolationField} +export default possibleViolationsByField; +export type {ViolationField}; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 732c7e3f58d7..eb0e67cc1e14 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -7,21 +7,20 @@ * Names of the various Transaction Violation types */ type ViolationName = - | "perDayLimit" - | "maxAge" - | "overLimit" - | "overLimitAttendee" - | "overCategoryLimit" - | "receiptRequired" - | "missingCategory" - | "categoryOutOfPolicy" - | "missingTag" - | "tagOutOfPolicy" - | "missingComment" - | "taxRequired" - | "taxOutOfPolicy" - | "billableExpense"; - + | 'perDayLimit' + | 'maxAge' + | 'overLimit' + | 'overLimitAttendee' + | 'overCategoryLimit' + | 'receiptRequired' + | 'missingCategory' + | 'categoryOutOfPolicy' + | 'missingTag' + | 'tagOutOfPolicy' + | 'missingComment' + | 'taxRequired' + | 'taxOutOfPolicy' + | 'billableExpense'; type ViolationType = string; @@ -29,11 +28,7 @@ type TransactionViolation = { type: ViolationType; name: ViolationName; userMessage: string; - data?: Record + data?: Record; }; -export type { - TransactionViolation, - ViolationName, - ViolationType, -}; +export type {TransactionViolation, ViolationName, ViolationType}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 42aa028ead0c..adeba62b08c6 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -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'; @@ -49,11 +50,6 @@ import WalletOnfido from './WalletOnfido'; import WalletStatement from './WalletStatement'; import WalletTerms from './WalletTerms'; import WalletTransfer from './WalletTransfer'; -import { - TransactionViolation, - ViolationName, - ViolationType, -} from './TransactionViolation'; export type { Account,