App: Implement ViolationUtils lib

  • Design Doc
  • Implement a new ViolationUtils lib (src/libs/ViolationUtils.js or src/libs/ViolationUtils.ts)
    1. Add a method getViolationForField(object transactionViolation, string field) : string
      • getViolationForField will check transactionViolation and return an an array of strings with the translated copies of the violations for that field, eg
      • When field is ‘category’, the method will pull the violations that apply to the category field, and then call translate on the violation names, like missingCategory and return that
      • See code example below
    2. Add a method getViolationsOnyxData(Transaction transaction, object transactionViolations, bool policyRequiresTags, object[] policyTags, bool policyRequiresCategories, object[] policyCategories) that calculates the new transaction violations (by adding/removing those that have been addressed with the current user changes), and returns an object that we can save into onyx. The updatedTransactionViolations variable shown in the code snippet below will be set like so:
      • If policyRequiresCategories === true, the transactionViolations array doesn’t have one with name: categoryOutOfPolicy and the passed transaction contains a category, and the policyCategories collection doesn’t have that category with its enabled value set to true, then it’ll add the object {‘name’:’categoryOutOfPolicy’, ‘type’: ‘violation’} to updatedTransactionViolations
      • If policyRequiresCategories === true, the passed transaction contains a category that is enabled in the policy, and the onyx data had a missingCategory violation, we’ll clear it from updatedTransactionViolations using _.reject(transactionViolations, e => e.name === 'missingCategory')
      • If policyRequiresTag === true, the transactionViolations array doesn’t have one with name: tagOutOfPolicy and the passed transaction contains a tag, and the policyTags collection doesn’t have that tag with its enabled value set to true, then it’ll add the object {‘name’:’categoryOutOfPolicy’, ‘type’: ‘violation’} to updatedTransactionViolations
      • If policyRequiresTag === true, the passed transaction contains a tag that is enabled in the policy, and the onyx data had a missingTag violation, we’ll clear it from updatedTransactionViolations using `_.reject(transactionViolations, e => e.name === ‘missingTag’)
    3. Add const formFields = {merchant: 'merchant', amount: 'amount', category: 'category', ...}
    4. Add a const violationField = {missingCategory: 'category', categoryOutOfPolicy: 'category', ...} with a mapping of violation names to the name of the field where the violation is shown (more in code snippet below
    5. Feel free to add dummy translations to the EN and ES files for now for the violations
function getViolationForField(transactionViolations, field) {
    const {translate} = useLocalize();
    const fieldViolations = _.filter(transactionViolations, (violation) => violations[violation.name] === field);
    return fieldViolations.map(violation => translate(violation.name));
}
// Example object returned by getViolationsOnyxData
{
    onyxMethod: Onyx.METHOD.SET, 
    key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`
    value: updatedTransactionViolations
}
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,
};

Example

getViolationForField([{name: 'overLimit', type: 'violation'}], 'amount') 
// should return => translate('overLimit')

getViolationForField([{name: 'maxAge', type: 'violation'}], 'amount') 
// should return => ''

getViolationForField([{name: 'maxAge', type: 'violation'}], 'date') 
// should return => translate('maxAge')
getViolationsOnyxData(
    transaction = {category: '', ...}, 
    transactionViolations = [], 
    policyRequiresTags = false,
    policyTags = [],
    policyRequiresCategories = true, 
    policyCategories = [{Entertainment: {name: 'Entertainment', enabled: true}}]
)
// should return 
{
    onyxMethod: Onyx.METHOD.SET, 
    key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`,
    value: [{name: 'missingCategory', type: 'violation'}],
}


getViolationsOnyxData(
    transaction = {tag: '', ...}, 
    transactionViolations = [], 
    policyRequiresTags = true,
    policyTags = [{Entertainment: {name: 'Engineering', enabled: true}}],
    policyRequiresCategories = true, 
    policyCategories = []
)
// should return
{
    onyxMethod: Onyx.METHOD.SET, 
    key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`,
    value: [{name: 'missingTag', type: 'violation'}],
}

About this issue

  • Original URL
  • State: closed
  • Created 8 months ago
  • Comments: 28 (22 by maintainers)

Commits related to this issue

Most upvoted comments

I agree with @trevor-coleman. Your approach to use an union of string literals seems much simpler approach. Let’s go with that plan 👍

I replied in the other issue. Feel free to bring up these questions on slack and tag me, since I imagine I’ll be quicker to respond that way

@trevor-coleman nvm. @marcaaron is internal engineer and was assigned just to review PR

Ah, ok – then should we also update the code on the display side to display multiple violations per field? And if so, how do we handle that.

We could either create multiple ‘rows’ each with one violation, or do something simpler like just violations.join(', ') to list them separated by commas.

Edit: Going to ask this question on the display ticket (see this comment)