From a551927b2bf0b97860a5a4efa8a73f9659c6ea24 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Tue, 9 Jan 2024 17:27:41 -0800 Subject: [PATCH 01/27] Add optimistic violations to money request edits Update everywhere that accesses an arg on policy to use {} as default arg for safety, since we can't use optional chaining --- src/libs/actions/IOU.js | 88 +++++++++++++++++++++++++++++------- src/pages/EditRequestPage.js | 30 +++++++----- 2 files changed, 90 insertions(+), 28 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index f2584cb8accd..31fee6d2b9d7 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -341,7 +341,7 @@ function buildOnyxDataForMoneyRequest( optimisticPolicyRecentlyUsedTags, isNewChatReport, isNewIOUReport, - policy, + policy = {}, policyTags, policyCategories, hasOutstandingChildRequest = false, @@ -918,13 +918,16 @@ function createDistanceRequest(report, participant, comment, created, category, * @param {String} transactionThreadReportID * @param {Object} transactionChanges * @param {String} [transactionChanges.created] Present when updated the date field + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories * @param {Boolean} onlyIncludeChangedFields * When 'true', then the returned params will only include the transaction details for the fields that were changed. * When `false`, then the returned params will include all the transaction details, regardless of which fields were changed. * This setting is necessary while the UpdateDistanceRequest API is refactored to be fully 1:1:1 in https://github.com/Expensify/App/issues/28358 * @returns {object} */ -function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, onlyIncludeChangedFields) { +function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy = {}, policyTags, policyCategories, onlyIncludeChangedFields) { const optimisticData = []; const successData = []; const failureData = []; @@ -1050,6 +1053,13 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t } } + // Add optimistic transaction violations + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), + }); + // Clear out the error fields and loading states on success successData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -1088,6 +1098,13 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t value: iouReport, }); + // Reset transaction violations to their original state + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`], + }); + return { params, onyxData: {optimisticData, successData, failureData}, @@ -1100,12 +1117,15 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t * @param {String} transactionID * @param {String} transactionThreadReportID * @param {String} val + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) { +function updateMoneyRequestDate(transactionID, transactionThreadReportID, val, policy, policyTags, policyCategories) { const transactionChanges = { created: val, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true); API.write('UpdateMoneyRequestDate', params, onyxData); } @@ -1115,12 +1135,15 @@ function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) { * @param {String} transactionID * @param {Number} transactionThreadReportID * @param {String} val + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateMoneyRequestMerchant(transactionID, transactionThreadReportID, val) { +function updateMoneyRequestMerchant(transactionID, transactionThreadReportID, val, policy, policyTags, policyCategories) { const transactionChanges = { merchant: val, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true); API.write('UpdateMoneyRequestMerchant', params, onyxData); } @@ -1130,12 +1153,15 @@ function updateMoneyRequestMerchant(transactionID, transactionThreadReportID, va * @param {String} transactionID * @param {Number} transactionThreadReportID * @param {String} tag + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) { +function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag, policy, policyTags, policyCategories) { const transactionChanges = { tag, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true); API.write('UpdateMoneyRequestTag', params, onyxData); } @@ -1149,10 +1175,12 @@ function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) { * @param {Number} [transactionChanges.amount] * @param {Object} [transactionChanges.comment] * @param {Object} [transactionChanges.waypoints] - * + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateDistanceRequest(transactionID, transactionThreadReportID, transactionChanges) { - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, false); +function updateDistanceRequest(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories) { + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, false); API.write('UpdateDistanceRequest', params, onyxData); } @@ -2170,8 +2198,11 @@ function setDraftSplitTransaction(transactionID, transactionChanges = {}) { * @param {String} transactionID * @param {Number} transactionThreadReportID * @param {Object} transactionChanges + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function editRegularMoneyRequest(transactionID, transactionThreadReportID, transactionChanges) { +function editRegularMoneyRequest(transactionID, transactionThreadReportID, transactionChanges, policy = {}, policyTags, policyCategories) { // STEP 1: Get all collections we're updating const transactionThread = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`]; const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; @@ -2225,6 +2256,13 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // STEP 4: Compose the optimistic data const currentTime = DateUtils.getDBTime(); + const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories); + // TODO + const previousViolationsOnyxData = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`], + }; const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2256,6 +2294,11 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans lastVisibleActionCreated: currentTime, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: updatedViolationsOnyxData, + }, ...(!isScanning ? [ { @@ -2379,6 +2422,11 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans lastVisibleActionCreated: transactionThread.lastVisibleActionCreated, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: previousViolationsOnyxData, + }, ]; // STEP 6: Call the API endpoint @@ -2405,12 +2453,15 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans * @param {object} transaction * @param {String} transactionThreadReportID * @param {Object} transactionChanges + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function editMoneyRequest(transaction, transactionThreadReportID, transactionChanges) { +function editMoneyRequest(transaction, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories) { if (TransactionUtils.isDistanceRequest(transaction)) { - updateDistanceRequest(transaction.transactionID, transactionThreadReportID, transactionChanges); + updateDistanceRequest(transaction.transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories); } else { - editRegularMoneyRequest(transaction.transactionID, transactionThreadReportID, transactionChanges); + editRegularMoneyRequest(transaction.transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories); } } @@ -2421,13 +2472,16 @@ function editMoneyRequest(transaction, transactionThreadReportID, transactionCha * @param {String} transactionThreadReportID * @param {String} currency * @param {Number} amount + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateMoneyRequestAmountAndCurrency(transactionID, transactionThreadReportID, currency, amount) { +function updateMoneyRequestAmountAndCurrency(transactionID, transactionThreadReportID, currency, amount, policy, policyTags, policyCategories) { const transactionChanges = { amount, currency, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true); API.write('UpdateMoneyRequestAmountAndCurrency', params, onyxData); } diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index b322f4eb106c..57fe1e7957a7 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -29,6 +29,7 @@ import EditRequestReceiptPage from './EditRequestReceiptPage'; import EditRequestTagPage from './EditRequestTagPage'; import reportActionPropTypes from './home/report/reportActionPropTypes'; import reportPropTypes from './reportPropTypes'; +import {policyPropTypes} from './workspace/withPolicy'; const propTypes = { /** Route from navigation */ @@ -47,6 +48,9 @@ const propTypes = { /** The report object for the thread report */ report: reportPropTypes, + /** The policy of the report */ + policy: policyPropTypes.policy, + /** Collection of categories attached to a policy */ policyCategories: PropTypes.objectOf(categoryPropTypes), @@ -62,13 +66,14 @@ const propTypes = { const defaultProps = { report: {}, + policy: {}, policyCategories: {}, policyTags: {}, parentReportActions: {}, transaction: {}, }; -function EditRequestPage({report, route, policyCategories, policyTags, parentReportActions, transaction}) { +function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); const { @@ -112,7 +117,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep // Update the transaction object and close the modal function editMoneyRequest(transactionChanges) { - IOU.editMoneyRequest(transaction, report.reportID, transactionChanges); + IOU.editMoneyRequest(transaction, report.reportID, transactionChanges, policy, policyTags, policyCategories); Navigation.dismissModal(report.reportID); } @@ -126,10 +131,10 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep return; } - IOU.updateMoneyRequestAmountAndCurrency(transaction.transactionID, report.reportID, newCurrency, newAmount); + IOU.updateMoneyRequestAmountAndCurrency(transaction.transactionID, report.reportID, newCurrency, newAmount, policy, policyTags, policyCategories); Navigation.dismissModal(); }, - [transaction, report], + [transaction, report, policy, policyTags, policyCategories], ); const saveCreated = useCallback( @@ -139,10 +144,10 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep Navigation.dismissModal(); return; } - IOU.updateMoneyRequestDate(transaction.transactionID, report.reportID, newCreated); + IOU.updateMoneyRequestDate(transaction.transactionID, report.reportID, newCreated, policy, policyTags, policyCategories); Navigation.dismissModal(); }, - [transaction, report], + [transaction, report, policy, policyTags, policyCategories], ); const saveMerchant = useCallback( @@ -158,14 +163,14 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep // This is possible only in case of IOU requests. if (newTrimmedMerchant === '') { - IOU.updateMoneyRequestMerchant(transaction.transactionID, report.reportID, CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT); + IOU.updateMoneyRequestMerchant(transaction.transactionID, report.reportID, CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, policy, policyTags, policyCategories); return; } - IOU.updateMoneyRequestMerchant(transaction.transactionID, report.reportID, newMerchant); + IOU.updateMoneyRequestMerchant(transaction.transactionID, report.reportID, newMerchant, policy, policyTags, policyCategories); Navigation.dismissModal(); }, - [transactionMerchant, transaction, report], + [transactionMerchant, transaction, report, policy, policyTags, policyCategories], ); const saveTag = useCallback( @@ -175,10 +180,10 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep // In case the same tag has been selected, reset the tag. updatedTag = ''; } - IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, updatedTag); + IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, updatedTag, policy, policyTags, policyCategories); Navigation.dismissModal(); }, - [transactionTag, transaction.transactionID, report.reportID], + [transactionTag, transaction.transactionID, report.reportID, policy, policyTags, policyCategories], ); if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) { @@ -300,6 +305,9 @@ export default compose( }), // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, policyCategories: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, }, From 56dd74d8898a29fc50a922d16f601ca6733d7662 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Tue, 9 Jan 2024 22:51:29 -0800 Subject: [PATCH 02/27] Lint fix --- src/pages/EditRequestPage.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 8e867c150b90..7e0577fa86b9 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -162,7 +162,14 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p } // An empty newTrimmedMerchant is only possible for the P2P IOU case - IOU.updateMoneyRequestMerchant(transaction.transactionID, report.reportID, newTrimmedMerchant || CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, policy, policyTags, policyCategories); + IOU.updateMoneyRequestMerchant( + transaction.transactionID, + report.reportID, + newTrimmedMerchant || CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, + policy, + policyTags, + policyCategories, + ); Navigation.dismissModal(); }, [transactionMerchant, transaction, report, policy, policyTags, policyCategories], From fe4b1788b7c5adc4ed32246b32fbc3693e89e997 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 10 Jan 2024 10:17:43 -0800 Subject: [PATCH 03/27] Fix bugs --- src/libs/actions/IOU.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 27326f896f14..b1f0b00d7f7e 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1054,11 +1054,7 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t } // Add optimistic transaction violations - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), - }); + optimisticData.push(ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories)); // Clear out the error fields and loading states on success successData.push({ @@ -2272,7 +2268,6 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // STEP 4: Compose the optimistic data const currentTime = DateUtils.getDBTime(); const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories); - // TODO const previousViolationsOnyxData = { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, @@ -2309,11 +2304,7 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans lastVisibleActionCreated: currentTime, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: updatedViolationsOnyxData, - }, + updatedViolationsOnyxData, ...(!isScanning ? [ { From a1899701ed39638bfedd73ea840f8d193b15e4b5 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 10 Jan 2024 10:50:46 -0800 Subject: [PATCH 04/27] Fix data passing --- src/libs/actions/IOU.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b1f0b00d7f7e..474028af01f0 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1054,7 +1054,8 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t } // Add optimistic transaction violations - optimisticData.push(ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories)); + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; + optimisticData.push(ViolationsUtils.getViolationsOnyxData(transaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories)); // Clear out the error fields and loading states on success successData.push({ @@ -1098,7 +1099,7 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`], + value: currentTransactionViolations, }); return { @@ -2267,12 +2268,15 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // STEP 4: Compose the optimistic data const currentTime = DateUtils.getDBTime(); - const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories); - const previousViolationsOnyxData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`], - }; + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; + const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( + transaction, + currentTransactionViolations, + policy.requiresTag, + policyTags, + policy.requiresCategory, + policyCategories, + ); const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2431,7 +2435,7 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: previousViolationsOnyxData, + value: currentTransactionViolations, }, ]; From 69ad6950c34bf5d2b6f6da84e5b4dc097a178e2e Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Thu, 11 Jan 2024 11:47:23 -0800 Subject: [PATCH 05/27] Temp fix for tag issue --- src/libs/ViolationsUtils.ts | 10 ++++++++-- src/types/onyx/PolicyTag.ts | 12 +++++++++++- src/types/onyx/index.ts | 3 ++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/libs/ViolationsUtils.ts b/src/libs/ViolationsUtils.ts index 2637686e726b..f7b5482c560a 100644 --- a/src/libs/ViolationsUtils.ts +++ b/src/libs/ViolationsUtils.ts @@ -2,8 +2,9 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {PolicyCategories, PolicyTagList, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Phrase, PhraseParameters} from './Localize'; +import * as PolicyUtils from './PolicyUtils'; const ViolationsUtils = { /** @@ -14,7 +15,7 @@ const ViolationsUtils = { transaction: Transaction, transactionViolations: TransactionViolation[], policyRequiresTags: boolean, - policyTags: PolicyTags, + policyTagList: PolicyTagList, policyRequiresCategories: boolean, policyCategories: PolicyCategories, ): { @@ -50,7 +51,12 @@ const ViolationsUtils = { } } + if (policyRequiresTags) { + // TODO, this fixes it but TS rightly complains about + // @ts-ignore + const tagListName: string = PolicyUtils.getTagListName(policyTagList); + const policyTags = policyTagList[tagListName].tags; const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); const isTagInPolicy = Boolean(policyTags[transaction.tag]?.enabled); diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 58a21dcf4df5..149da7eb3341 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -12,4 +12,14 @@ type PolicyTag = { type PolicyTags = Record; -export type {PolicyTag, PolicyTags}; +type PolicyTagList = Record; + +export type {PolicyTag, PolicyTags, PolicyTagList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 7bd9c321be5e..d307c61b0baa 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -31,7 +31,7 @@ import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type PolicyReportField from './PolicyReportField'; -import type {PolicyTag, PolicyTags} from './PolicyTag'; +import type {PolicyTag, PolicyTags, PolicyTagList} from './PolicyTag'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -103,6 +103,7 @@ export type { PolicyMembers, PolicyTag, PolicyTags, + PolicyTagList, PrivatePersonalDetails, RecentWaypoint, RecentlyUsedCategories, From 41e87caeb581dd95d5b8ef8b8be8886281089626 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Thu, 11 Jan 2024 16:18:24 -0800 Subject: [PATCH 06/27] Use full policyTags data as ViolationsUtils input --- src/libs/ViolationsUtils.ts | 11 ++++------- src/types/onyx/PolicyTag.ts | 8 ++++++-- tests/unit/ViolationUtilsTest.js | 8 +++++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/libs/ViolationsUtils.ts b/src/libs/ViolationsUtils.ts index f7b5482c560a..6db4eb58cfa9 100644 --- a/src/libs/ViolationsUtils.ts +++ b/src/libs/ViolationsUtils.ts @@ -2,9 +2,8 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyCategories, PolicyTagList, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Phrase, PhraseParameters} from './Localize'; -import * as PolicyUtils from './PolicyUtils'; const ViolationsUtils = { /** @@ -15,7 +14,7 @@ const ViolationsUtils = { transaction: Transaction, transactionViolations: TransactionViolation[], policyRequiresTags: boolean, - policyTagList: PolicyTagList, + policyTagList: PolicyTagList, policyRequiresCategories: boolean, policyCategories: PolicyCategories, ): { @@ -53,10 +52,8 @@ const ViolationsUtils = { if (policyRequiresTags) { - // TODO, this fixes it but TS rightly complains about - // @ts-ignore - const tagListName: string = PolicyUtils.getTagListName(policyTagList); - const policyTags = policyTagList[tagListName].tags; + const policyTagListName = Object.keys(policyTagList)[0]; + const policyTags = policyTagList[policyTagListName].tags; const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); const isTagInPolicy = Boolean(policyTags[transaction.tag]?.enabled); diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 149da7eb3341..a2872971a7f5 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -12,7 +12,9 @@ type PolicyTag = { type PolicyTags = Record; -type PolicyTagList = Record = Record = Record; + }>; + + type PolicyTagList = PolicyTagListGeneric; export type {PolicyTag, PolicyTags, PolicyTagList}; diff --git a/tests/unit/ViolationUtilsTest.js b/tests/unit/ViolationUtilsTest.js index cc84c547da2e..1f16d41f598e 100644 --- a/tests/unit/ViolationUtilsTest.js +++ b/tests/unit/ViolationUtilsTest.js @@ -128,7 +128,13 @@ describe('getViolationsOnyxData', () => { describe('policyRequiresTags', () => { beforeEach(() => { policyRequiresTags = true; - policyTags = {Lunch: {enabled: true}, Dinner: {enabled: true}}; + policyTags = { + Tag: { + name: 'Tag', + required: true, + tags: {Lunch: {enabled: true}, Dinner: {enabled: true}}, + }, + }; transaction.tag = 'Lunch'; }); From a09e4ac881643945daefd623724a2be3273aa017 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Thu, 11 Jan 2024 16:54:15 -0800 Subject: [PATCH 07/27] getViolationsOnyxData requires new transaction data but previous transaction violations. Fix and make that more clear with argument names --- src/libs/ViolationsUtils.ts | 21 ++++++++++----------- src/libs/actions/IOU.js | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/libs/ViolationsUtils.ts b/src/libs/ViolationsUtils.ts index 6db4eb58cfa9..97891235b5d3 100644 --- a/src/libs/ViolationsUtils.ts +++ b/src/libs/ViolationsUtils.ts @@ -11,7 +11,7 @@ const ViolationsUtils = { * violations. */ getViolationsOnyxData( - transaction: Transaction, + updatedTransaction: Transaction, transactionViolations: TransactionViolation[], policyRequiresTags: boolean, policyTagList: PolicyTagList, @@ -27,15 +27,15 @@ const ViolationsUtils = { if (policyRequiresCategories) { const hasCategoryOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'categoryOutOfPolicy'); const hasMissingCategoryViolation = transactionViolations.some((violation) => violation.name === 'missingCategory'); - const isCategoryInPolicy = Boolean(policyCategories[transaction.category]?.enabled); + const isCategoryInPolicy = Boolean(policyCategories[updatedTransaction.category]?.enabled); // Add 'categoryOutOfPolicy' violation if category is not in policy - if (!hasCategoryOutOfPolicyViolation && transaction.category && !isCategoryInPolicy) { + if (!hasCategoryOutOfPolicyViolation && updatedTransaction.category && !isCategoryInPolicy) { newTransactionViolations.push({name: 'categoryOutOfPolicy', type: 'violation', userMessage: ''}); } // Remove 'categoryOutOfPolicy' violation if category is in policy - if (hasCategoryOutOfPolicyViolation && transaction.category && isCategoryInPolicy) { + if (hasCategoryOutOfPolicyViolation && updatedTransaction.category && isCategoryInPolicy) { newTransactionViolations = reject(newTransactionViolations, {name: 'categoryOutOfPolicy'}); } @@ -45,26 +45,25 @@ const ViolationsUtils = { } // Add 'missingCategory' violation if category is required and not set - if (!hasMissingCategoryViolation && policyRequiresCategories && !transaction.category) { + if (!hasMissingCategoryViolation && policyRequiresCategories && !updatedTransaction.category) { newTransactionViolations.push({name: 'missingCategory', type: 'violation', userMessage: ''}); } } - if (policyRequiresTags) { const policyTagListName = Object.keys(policyTagList)[0]; const policyTags = policyTagList[policyTagListName].tags; const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); - const isTagInPolicy = Boolean(policyTags[transaction.tag]?.enabled); + const isTagInPolicy = Boolean(policyTags[updatedTransaction.tag]?.enabled); // Add 'tagOutOfPolicy' violation if tag is not in policy - if (!hasTagOutOfPolicyViolation && transaction.tag && !isTagInPolicy) { + if (!hasTagOutOfPolicyViolation && updatedTransaction.tag && !isTagInPolicy) { newTransactionViolations.push({name: 'tagOutOfPolicy', type: 'violation', userMessage: ''}); } // Remove 'tagOutOfPolicy' violation if tag is in policy - if (hasTagOutOfPolicyViolation && transaction.tag && isTagInPolicy) { + if (hasTagOutOfPolicyViolation && updatedTransaction.tag && isTagInPolicy) { newTransactionViolations = reject(newTransactionViolations, {name: 'tagOutOfPolicy'}); } @@ -74,14 +73,14 @@ const ViolationsUtils = { } // Add 'missingTag violation' if tag is required and not set - if (!hasMissingTagViolation && !transaction.tag && policyRequiresTags) { + if (!hasMissingTagViolation && !updatedTransaction.tag && policyRequiresTags) { newTransactionViolations.push({name: 'missingTag', type: 'violation', userMessage: ''}); } } return { onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${updatedTransaction.transactionID}`, value: newTransactionViolations, }; }, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 474028af01f0..550b7176a103 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1055,7 +1055,7 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t // Add optimistic transaction violations const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; - optimisticData.push(ViolationsUtils.getViolationsOnyxData(transaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories)); + optimisticData.push(ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories)); // Clear out the error fields and loading states on success successData.push({ @@ -2270,7 +2270,7 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans const currentTime = DateUtils.getDBTime(); const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( - transaction, + updatedTransaction, currentTransactionViolations, policy.requiresTag, policyTags, From 72cfa451cb7dc4fe5a9943139cb587c462026cec Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Fri, 12 Jan 2024 14:37:08 -0800 Subject: [PATCH 08/27] Improve types --- src/types/onyx/PolicyTag.ts | 25 ++++++++++++------------- src/types/onyx/index.ts | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index a2872971a7f5..1171ade17006 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -12,18 +12,17 @@ type PolicyTag = { type PolicyTags = Record; -// Using a generic to indicate that the top-level key and name should be the -// same value. Not meant for direct use, just used by the alias below. -type PolicyTagListGeneric = Record; - - type PolicyTagList = PolicyTagListGeneric; +type PolicyTagList = Record< + T, + { + /** Name of the tag list */ + name: T; + + /** Flag that determines if tags are required */ + required: boolean; + + tags: PolicyTags; + } +>; export type {PolicyTag, PolicyTags, PolicyTagList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index d307c61b0baa..e4a9123af56f 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -31,7 +31,7 @@ import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type PolicyReportField from './PolicyReportField'; -import type {PolicyTag, PolicyTags, PolicyTagList} from './PolicyTag'; +import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; From 8ff7c692224605c4d616b671799a7b0ed85da822 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Fri, 12 Jan 2024 14:52:44 -0800 Subject: [PATCH 09/27] Fix case of passing empty object to policyTags --- src/libs/ViolationsUtils.ts | 8 ++++---- src/types/onyx/PolicyTag.ts | 6 +++++- src/types/onyx/index.ts | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/libs/ViolationsUtils.ts b/src/libs/ViolationsUtils.ts index 97891235b5d3..09b3b0632723 100644 --- a/src/libs/ViolationsUtils.ts +++ b/src/libs/ViolationsUtils.ts @@ -2,7 +2,7 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {MaybePolicyTagList, PolicyCategories, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Phrase, PhraseParameters} from './Localize'; const ViolationsUtils = { @@ -14,7 +14,7 @@ const ViolationsUtils = { updatedTransaction: Transaction, transactionViolations: TransactionViolation[], policyRequiresTags: boolean, - policyTagList: PolicyTagList, + policyTagList: MaybePolicyTagList, policyRequiresCategories: boolean, policyCategories: PolicyCategories, ): { @@ -52,10 +52,10 @@ const ViolationsUtils = { if (policyRequiresTags) { const policyTagListName = Object.keys(policyTagList)[0]; - const policyTags = policyTagList[policyTagListName].tags; + const policyTags = policyTagList[policyTagListName]?.tags; const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); - const isTagInPolicy = Boolean(policyTags[updatedTransaction.tag]?.enabled); + const isTagInPolicy = Boolean(policyTags?.[updatedTransaction.tag]?.enabled); // Add 'tagOutOfPolicy' violation if tag is not in policy if (!hasTagOutOfPolicyViolation && updatedTransaction.tag && !isTagInPolicy) { diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 1171ade17006..ca8545775a5c 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -25,4 +25,8 @@ type PolicyTagList = Record< } >; -export type {PolicyTag, PolicyTags, PolicyTagList}; +// When queried from Onyx, if there is no matching policy tag list, the data +// returned will be an empty object. +type MaybePolicyTagList = PolicyTagList | Record; + +export type {PolicyTag, PolicyTags, PolicyTagList, MaybePolicyTagList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index e4a9123af56f..20cf1ae69897 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -31,7 +31,7 @@ import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type PolicyReportField from './PolicyReportField'; -import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; +import type {MaybePolicyTagList, PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -104,6 +104,7 @@ export type { PolicyTag, PolicyTags, PolicyTagList, + MaybePolicyTagList, PrivatePersonalDetails, RecentWaypoint, RecentlyUsedCategories, From f2942ea411fd776f58639334416ebfa417c69ecd Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Fri, 12 Jan 2024 15:29:16 -0800 Subject: [PATCH 10/27] We should only run getViolationsOnyxData if there's a policy --- src/libs/actions/IOU.js | 52 ++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 550b7176a103..ab3bf0882e54 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1053,9 +1053,13 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t } } - // Add optimistic transaction violations + // Add optimistic transaction violations if there is a policy const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; - optimisticData.push(ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories)); + if (policy && policy.id) { + optimisticData.push( + ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), + ); + } // Clear out the error fields and loading states on success successData.push({ @@ -1095,12 +1099,14 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t value: iouReport, }); - // Reset transaction violations to their original state - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: currentTransactionViolations, - }); + // If there is a policy, restore transaction violations to their original state + if (policy && policy.id) { + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: currentTransactionViolations, + }); + } return { params, @@ -2268,15 +2274,6 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // STEP 4: Compose the optimistic data const currentTime = DateUtils.getDBTime(); - const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; - const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( - updatedTransaction, - currentTransactionViolations, - policy.requiresTag, - policyTags, - policy.requiresCategory, - policyCategories, - ); const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2308,7 +2305,6 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans lastVisibleActionCreated: currentTime, }, }, - updatedViolationsOnyxData, ...(!isScanning ? [ { @@ -2432,12 +2428,26 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans lastVisibleActionCreated: transactionThread.lastVisibleActionCreated, }, }, - { + ]; + + // Add transaction violations if there is a policy + if (policy && policy.id) { + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; + const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( + updatedTransaction, + currentTransactionViolations, + policy.requiresTag, + policyTags, + policy.requiresCategory, + policyCategories, + ); + optimisticData.push(updatedViolationsOnyxData); + failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, value: currentTransactionViolations, - }, - ]; + }); + } // STEP 6: Call the API endpoint const {created, amount, currency, comment, merchant, category, billable, tag} = ReportUtils.getTransactionDetails(updatedTransaction); From 37bbacc60158ac87db4c357bbffc868244197264 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 15 Jan 2024 13:14:36 -0800 Subject: [PATCH 11/27] Feedback: Don't use Maybe type --- src/libs/ViolationsUtils.ts | 4 ++-- src/types/onyx/PolicyTag.ts | 10 ++++------ src/types/onyx/index.ts | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/libs/ViolationsUtils.ts b/src/libs/ViolationsUtils.ts index 09b3b0632723..dd128a68c703 100644 --- a/src/libs/ViolationsUtils.ts +++ b/src/libs/ViolationsUtils.ts @@ -2,7 +2,7 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {MaybePolicyTagList, PolicyCategories, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {PolicyTagList, PolicyCategories, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Phrase, PhraseParameters} from './Localize'; const ViolationsUtils = { @@ -14,7 +14,7 @@ const ViolationsUtils = { updatedTransaction: Transaction, transactionViolations: TransactionViolation[], policyRequiresTags: boolean, - policyTagList: MaybePolicyTagList, + policyTagList: PolicyTagList, policyRequiresCategories: boolean, policyCategories: PolicyCategories, ): { diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index ca8545775a5c..7c3636551746 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -12,6 +12,8 @@ type PolicyTag = { type PolicyTags = Record; +// When queried from Onyx, if there is no matching policy tag list, the data +// returned will be an empty object, represented by Record. type PolicyTagList = Record< T, { @@ -23,10 +25,6 @@ type PolicyTagList = Record< tags: PolicyTags; } ->; - -// When queried from Onyx, if there is no matching policy tag list, the data -// returned will be an empty object. -type MaybePolicyTagList = PolicyTagList | Record; +> | Record; -export type {PolicyTag, PolicyTags, PolicyTagList, MaybePolicyTagList}; +export type {PolicyTag, PolicyTags, PolicyTagList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 20cf1ae69897..e4a9123af56f 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -31,7 +31,7 @@ import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type PolicyReportField from './PolicyReportField'; -import type {MaybePolicyTagList, PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; +import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -104,7 +104,6 @@ export type { PolicyTag, PolicyTags, PolicyTagList, - MaybePolicyTagList, PrivatePersonalDetails, RecentWaypoint, RecentlyUsedCategories, From 0a3e81856b1a2550ca714d22b13fbb5a7779a508 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 15 Jan 2024 13:21:48 -0800 Subject: [PATCH 12/27] Ensure an array is always passed to getViolationsOnyxData --- src/libs/actions/IOU.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index ab3bf0882e54..ff16336b97dd 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1057,7 +1057,7 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; if (policy && policy.id) { optimisticData.push( - ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), + ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations || [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), ); } @@ -2435,7 +2435,7 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( updatedTransaction, - currentTransactionViolations, + currentTransactionViolations || [], policy.requiresTag, policyTags, policy.requiresCategory, From ede3bbe35c4564871666fea79d55b946d0a8afa4 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Tue, 16 Jan 2024 16:06:24 -0800 Subject: [PATCH 13/27] More prettier changes --- src/libs/ViolationsUtils.ts | 2 +- src/types/onyx/PolicyTag.ts | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/libs/ViolationsUtils.ts b/src/libs/ViolationsUtils.ts index dd128a68c703..e24ac5277283 100644 --- a/src/libs/ViolationsUtils.ts +++ b/src/libs/ViolationsUtils.ts @@ -2,7 +2,7 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyTagList, PolicyCategories, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Phrase, PhraseParameters} from './Localize'; const ViolationsUtils = { diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 7c3636551746..cea555a2a0f9 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -14,17 +14,19 @@ type PolicyTags = Record; // When queried from Onyx, if there is no matching policy tag list, the data // returned will be an empty object, represented by Record. -type PolicyTagList = Record< - T, - { - /** Name of the tag list */ - name: T; +type PolicyTagList = + | Record< + T, + { + /** Name of the tag list */ + name: T; - /** Flag that determines if tags are required */ - required: boolean; + /** Flag that determines if tags are required */ + required: boolean; - tags: PolicyTags; - } -> | Record; + tags: PolicyTags; + } + > + | Record; export type {PolicyTag, PolicyTags, PolicyTagList}; From 371e1665a81aa89ad8a02593b70c2ac0fa184fe5 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 24 Jan 2024 16:34:32 -0800 Subject: [PATCH 14/27] Support client-side violations in new commands --- src/libs/actions/IOU.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 574a1a027440..06084fc0704f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1167,12 +1167,15 @@ function updateMoneyRequestDate(transactionID, transactionThreadReportID, val, p * @param {String} transactionID * @param {Number} transactionThreadReportID * @param {String} val + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateMoneyRequestBillable(transactionID, transactionThreadReportID, val) { +function updateMoneyRequestBillable(transactionID, transactionThreadReportID, val, policy, policyTags, policyCategories) { const transactionChanges = { billable: val, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true); API.write('UpdateMoneyRequestBillable', params, onyxData); } @@ -1218,12 +1221,15 @@ function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag, po * @param {String} transactionID * @param {Number} transactionThreadReportID * @param {String} category + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateMoneyRequestCategory(transactionID, transactionThreadReportID, category) { +function updateMoneyRequestCategory(transactionID, transactionThreadReportID, category, policy, policyTags, policyCategories) { const transactionChanges = { category, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true); API.write('UpdateMoneyRequestCategory', params, onyxData); } @@ -1233,12 +1239,15 @@ function updateMoneyRequestCategory(transactionID, transactionThreadReportID, ca * @param {String} transactionID * @param {Number} transactionThreadReportID * @param {String} comment + * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) + * @param {Array} policyTags + * @param {Array} policyCategories */ -function updateMoneyRequestDescription(transactionID, transactionThreadReportID, comment) { +function updateMoneyRequestDescription(transactionID, transactionThreadReportID, comment, policy, policyTags, policyCategories) { const transactionChanges = { comment, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true); API.write('UpdateMoneyRequestDescription', params, onyxData); } From 38ffff6f91b8895a20c9c53fc2fd0aac730f5cb7 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Fri, 26 Jan 2024 11:47:43 -0800 Subject: [PATCH 15/27] Pass policy args to all commands --- src/pages/EditRequestPage.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 18f10e09bdbc..3b71e7046158 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -186,21 +186,21 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p ({category: newCategory}) => { // In case the same category has been selected, reset the category. const updatedCategory = newCategory === transactionCategory ? '' : newCategory; - IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory); + IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories); Navigation.dismissModal(); }, - [transactionCategory, transaction.transactionID, report.reportID], + [transactionCategory, transaction.transactionID, report.reportID, policy, policyTags, policyCategories], ); const saveComment = useCallback( ({comment: newComment}) => { // Only update comment if it has changed if (newComment.trim() !== transactionDescription) { - IOU.updateMoneyRequestDescription(transaction.transactionID, report.reportID, newComment.trim()); + IOU.updateMoneyRequestDescription(transaction.transactionID, report.reportID, newComment.trim(), policy, policyTags, policyCategories); } Navigation.dismissModal(); }, - [transactionDescription, transaction.transactionID, report.reportID], + [transactionDescription, transaction.transactionID, report.reportID, policy, policyTags, policyCategories], ); if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) { From 210cfeab4539bf16c8983f1b5ca71bc9e7683ab4 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 31 Jan 2024 11:23:50 -0800 Subject: [PATCH 16/27] Make currentTransactionViolations always an empty array if falsy --- src/libs/actions/IOU.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 06084fc0704f..430773eba231 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1083,10 +1083,10 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t } // Add optimistic transaction violations if there is a policy - const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] || []; if (policy && policy.id) { optimisticData.push( - ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations || [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), + ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), ); } @@ -2526,10 +2526,10 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // Add transaction violations if there is a policy if (policy && policy.id) { - const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] || []; const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( updatedTransaction, - currentTransactionViolations || [], + currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, From 938c6bccea9d3cb37f410b3fe9d22473a94dcb64 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 5 Feb 2024 11:00:07 -0800 Subject: [PATCH 17/27] Manage MoneyRequestView type error This component was typed before the actions file, and so it's not inferring the correct type for the policy arg. Should be fixed when IOU.js is typed. --- src/components/ReportActionItem/MoneyRequestView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2ce02d598de2..5139f2407db6 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -156,10 +156,11 @@ function MoneyRequestView({ Navigation.dismissModal(); return; } - IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '', report?.reportID, newBillable); + // @ts-expect-error: updateMoneyRequestBillable is not typed and the compiler is inferring the wrong type for policy + IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '', report?.reportID, newBillable, policy, policyTags, policyCategories); Navigation.dismissModal(); }, - [transaction, report], + [transaction, report, policy, policyTags, policyCategories], ); if (isCardTransaction) { From b8248efb7af185163926ca2ffa309053e4ce1ccd Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 5 Feb 2024 14:14:52 -0800 Subject: [PATCH 18/27] Remove obsolete ts-expect-error --- src/components/ReportActionItem/MoneyRequestView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 5deb19d389f2..de00440e87d3 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -156,7 +156,6 @@ function MoneyRequestView({ Navigation.dismissModal(); return; } - // @ts-expect-error: updateMoneyRequestBillable is not typed and the compiler is inferring the wrong type for policy IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '', report?.reportID, newBillable, policy, policyTags, policyCategories); Navigation.dismissModal(); }, From 32d6617457b98fbd13ae72953d47759ddde1788e Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 5 Feb 2024 17:42:20 -0800 Subject: [PATCH 19/27] First stab at fixing typing Problems remaining: 1. Inconsistent types 2. Mismatch between Onyx types and what's actually sent, and my code based on what's actually sent doesn't account for bad types: ``` > new.expensify@1.4.36-5 typecheck > tsc src/components/ReportActionItem/MoneyRequestView.tsx:159:109 - error TS2345: Argument of type 'OnyxEntry' is not assignable to parameter of type 'Policy'. Type 'null' is not assignable to type 'Policy'. 159 IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '', report?.reportID, newBillable, policy, policyTags, policyCategories); ~~~~~~ Found 1 error in src/components/ReportActionItem/MoneyRequestView.tsx:159 ``` It's never actually null, it's empty object 3. Need to double-check that everything is set by default when it needs to be --- src/libs/Violations/ViolationsUtils.ts | 9 +- src/libs/actions/IOU.ts | 147 ++++++++++++++++++------- 2 files changed, 115 insertions(+), 41 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index ea92532fe54d..54b198496fa5 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -24,10 +24,11 @@ const ViolationsUtils = { if (policyRequiresCategories) { const hasCategoryOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'categoryOutOfPolicy'); const hasMissingCategoryViolation = transactionViolations.some((violation) => violation.name === 'missingCategory'); - const isCategoryInPolicy = !!policyCategories[updatedTransaction.category ?? '']?.enabled; + const categoryKey = updatedTransaction.category; + const isCategoryInPolicy = categoryKey ? policyCategories?.[categoryKey]?.enabled : undefined; // Add 'categoryOutOfPolicy' violation if category is not in policy - if (!hasCategoryOutOfPolicyViolation && updatedTransaction.category && !isCategoryInPolicy) { + if (!hasCategoryOutOfPolicyViolation && categoryKey && !isCategoryInPolicy) { newTransactionViolations.push({name: 'categoryOutOfPolicy', type: 'violation', userMessage: ''}); } @@ -42,7 +43,7 @@ const ViolationsUtils = { } // Add 'missingCategory' violation if category is required and not set - if (!hasMissingCategoryViolation && policyRequiresCategories && !updatedTransaction.category) { + if (!hasMissingCategoryViolation && policyRequiresCategories && !categoryKey) { newTransactionViolations.push({name: 'missingCategory', type: 'violation', userMessage: ''}); } } @@ -52,7 +53,7 @@ const ViolationsUtils = { const policyTags = policyTagList[policyTagListName]?.tags; const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); - const isTagInPolicy = !!policyTags[updatedTransaction.tag ?? '']?.enabled; + const isTagInPolicy = policyTags ? !!policyTags[updatedTransaction.tag ?? '']?.enabled : undefined; // Add 'tagOutOfPolicy' violation if tag is not in policy if (!hasTagOutOfPolicyViolation && updatedTransaction.tag && !isTagInPolicy) { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 1f68e9f10f73..50f6c11bfde2 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -392,8 +392,8 @@ function buildOnyxDataForMoneyRequest( isNewChatReport: boolean, shouldCreateNewMoneyRequestReport: boolean, policy: OnyxTypes.Policy | EmptyObject = {}, - policyTags?: OnyxTypes.PolicyTags, - policyCategories?: OnyxTypes.PolicyCategories, + policyTags: OnyxTypes.PolicyTagList = {}, + policyCategories: OnyxTypes.PolicyCategories | EmptyObject = {}, needsToBeManuallySubmitted = true, ): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { const isScanRequest = TransactionUtils.isScanRequest(transaction); @@ -700,8 +700,8 @@ function getMoneyRequestInformation( tag: string | undefined, billable: boolean | undefined, policy: OnyxTypes.Policy | EmptyObject | undefined, - policyTags: OnyxTypes.PolicyTags | undefined, - policyCategories: OnyxTypes.PolicyCategories | undefined, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories | EmptyObject, payeeAccountID = userAccountID, payeeEmail = currentUserEmail, moneyRequestReportID = '', @@ -914,7 +914,7 @@ function createDistanceRequest( billable: boolean | undefined, validWaypoints: WaypointCollection, policy: OnyxTypes.Policy | EmptyObject | undefined, - policyTags: OnyxTypes.PolicyTags, + policyTags: OnyxTypes.PolicyTagList, policyCategories: OnyxTypes.PolicyCategories, ) { // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function @@ -986,9 +986,9 @@ function getUpdateMoneyRequestParams( transactionID: string, transactionThreadReportID: string, transactionChanges: TransactionChanges, - policy: any = {}, - policyTags: any, - policyCategories: any, + policy: OnyxTypes.Policy | EmptyObject, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, onlyIncludeChangedFields: boolean, ): UpdateMoneyRequestData { const optimisticData: OnyxUpdate[] = []; @@ -1144,14 +1144,6 @@ function getUpdateMoneyRequestParams( } } - // Add optimistic transaction violations if there is a policy - const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] || []; - if (policy && policy.id) { - optimisticData.push( - ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, policy.requiresTag, policyTags, policy.requiresCategory, policyCategories), - ); - } - // Clear out the error fields and loading states on success successData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -1192,8 +1184,11 @@ function getUpdateMoneyRequestParams( }); } - // If there is a policy, restore transaction violations to their original state - if (policy && policy.id) { + if (policy?.id && updatedTransaction) { + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; + optimisticData.push( + ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, !!policy.requiresTag, policyTags, !!policy.requiresCategory, policyCategories), + ); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, @@ -1208,7 +1203,14 @@ function getUpdateMoneyRequestParams( } /** Updates the created date of a money request */ -function updateMoneyRequestDate(transactionID: string, transactionThreadReportID: string, value: string, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestDate( + transactionID: string, + transactionThreadReportID: string, + value: string, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges: TransactionChanges = { created: value, }; @@ -1217,7 +1219,14 @@ function updateMoneyRequestDate(transactionID: string, transactionThreadReportID } /** Updates the billable field of a money request */ -function updateMoneyRequestBillable(transactionID: string, transactionThreadReportID: string, value: boolean, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestBillable( + transactionID: string, + transactionThreadReportID: string, + value: boolean, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges: TransactionChanges = { billable: value, }; @@ -1226,7 +1235,14 @@ function updateMoneyRequestBillable(transactionID: string, transactionThreadRepo } /** Updates the merchant field of a money request */ -function updateMoneyRequestMerchant(transactionID: string, transactionThreadReportID: string, value: string, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestMerchant( + transactionID: string, + transactionThreadReportID: string, + value: string, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges: TransactionChanges = { merchant: value, }; @@ -1235,7 +1251,14 @@ function updateMoneyRequestMerchant(transactionID: string, transactionThreadRepo } /** Updates the tag of a money request */ -function updateMoneyRequestTag(transactionID: string, transactionThreadReportID: string, tag: string, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestTag( + transactionID: string, + transactionThreadReportID: string, + tag: string, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges: TransactionChanges = { tag, }; @@ -1244,7 +1267,14 @@ function updateMoneyRequestTag(transactionID: string, transactionThreadReportID: } /** Updates the waypoints of a distance money request */ -function updateMoneyRequestDistance(transactionID: string, transactionThreadReportID: string, waypoints: WaypointCollection, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestDistance( + transactionID: string, + transactionThreadReportID: string, + waypoints: WaypointCollection, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges: TransactionChanges = { waypoints, }; @@ -1253,7 +1283,14 @@ function updateMoneyRequestDistance(transactionID: string, transactionThreadRepo } /** Updates the category of a money request */ -function updateMoneyRequestCategory(transactionID: string, transactionThreadReportID: string, category: string, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestCategory( + transactionID: string, + transactionThreadReportID: string, + category: string, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges: TransactionChanges = { category, }; @@ -1262,7 +1299,14 @@ function updateMoneyRequestCategory(transactionID: string, transactionThreadRepo } /** Updates the description of a money request */ -function updateMoneyRequestDescription(transactionID: string, transactionThreadReportID: string, comment: string, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestDescription( + transactionID: string, + transactionThreadReportID: string, + comment: string, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges: TransactionChanges = { comment, }; @@ -1271,7 +1315,14 @@ function updateMoneyRequestDescription(transactionID: string, transactionThreadR } /** Edits an existing distance request */ -function updateDistanceRequest(transactionID: string, transactionThreadReportID: string, transactionChanges: TransactionChanges, policy: any, policyTags: any, policyCategories: any) { +function updateDistanceRequest( + transactionID: string, + transactionThreadReportID: string, + transactionChanges: TransactionChanges, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, false); API.write(WRITE_COMMANDS.UPDATE_DISTANCE_REQUEST, params, onyxData); } @@ -1296,9 +1347,9 @@ function requestMoney( taxCode = '', taxAmount = 0, billable?: boolean, - policy = undefined, - policyTags = undefined, - policyCategories = undefined, + policy: OnyxTypes.Policy | EmptyObject = {}, + policyTags: OnyxTypes.PolicyTagList = {}, + policyCategories: OnyxTypes.PolicyCategories | EmptyObject = {}, ) { // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); @@ -2334,7 +2385,14 @@ function setDraftSplitTransaction(transactionID: string, transactionChanges: Tra Onyx.merge(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, updatedTransaction); } -function editRegularMoneyRequest(transactionID: string, transactionThreadReportID: string, transactionChanges: TransactionChanges, policy: any = {}, policyTags: any, policyCategories: any) { +function editRegularMoneyRequest( + transactionID: string, + transactionThreadReportID: string, + transactionChanges: TransactionChanges, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { // STEP 1: Get all collections we're updating const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null; const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; @@ -2547,15 +2605,15 @@ function editRegularMoneyRequest(transactionID: string, transactionThreadReportI }, ]; - // Add transaction violations if there is a policy - if (policy && policy.id) { - const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] || []; + // Add transaction violations if there is a policy and updated transaaction + if (policy?.id && updatedTransaction) { + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( updatedTransaction, currentTransactionViolations, - policy.requiresTag, + !!policy.requiresTag, policyTags, - policy.requiresCategory, + !!policy.requiresCategory, policyCategories, ); optimisticData.push(updatedViolationsOnyxData); @@ -2585,7 +2643,14 @@ function editRegularMoneyRequest(transactionID: string, transactionThreadReportI API.write(WRITE_COMMANDS.EDIT_MONEY_REQUEST, parameters, {optimisticData, successData, failureData}); } -function editMoneyRequest(transaction: OnyxTypes.Transaction, transactionThreadReportID: string, transactionChanges: TransactionChanges, policy: any, policyTags: any, policyCategories: any) { +function editMoneyRequest( + transaction: OnyxTypes.Transaction, + transactionThreadReportID: string, + transactionChanges: TransactionChanges, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { if (TransactionUtils.isDistanceRequest(transaction)) { updateDistanceRequest(transaction.transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories); } else { @@ -2594,7 +2659,15 @@ function editMoneyRequest(transaction: OnyxTypes.Transaction, transactionThreadR } /** Updates the amount and currency fields of a money request */ -function updateMoneyRequestAmountAndCurrency(transactionID: string, transactionThreadReportID: string, currency: string, amount: number, policy: any, policyTags: any, policyCategories: any) { +function updateMoneyRequestAmountAndCurrency( + transactionID: string, + transactionThreadReportID: string, + currency: string, + amount: number, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { const transactionChanges = { amount, currency, From 28a1f954e998673f391c9d85f7671a8d0952ebf1 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 5 Feb 2024 21:08:42 -0800 Subject: [PATCH 20/27] WIP update Partway through updating code, realizing that `updateMoneyRequest` is now a completely dead code branch --- src/libs/actions/IOU.ts | 24 ++++++++-------- .../step/IOURequestStepConfirmation.js | 6 ++-- .../iou/request/step/IOURequestStepTag.js | 23 +++++++++++++-- src/types/onyx/PolicyTag.ts | 28 ++++++++----------- 4 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 50f6c11bfde2..5ff77c348a76 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -391,9 +391,9 @@ function buildOnyxDataForMoneyRequest( optimisticPolicyRecentlyUsedTags: OnyxTypes.RecentlyUsedTags, isNewChatReport: boolean, shouldCreateNewMoneyRequestReport: boolean, - policy: OnyxTypes.Policy | EmptyObject = {}, - policyTags: OnyxTypes.PolicyTagList = {}, - policyCategories: OnyxTypes.PolicyCategories | EmptyObject = {}, + policy?: OnyxEntry, + policyTags?: OnyxEntry, + policyCategories?: OnyxEntry, needsToBeManuallySubmitted = true, ): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { const isScanRequest = TransactionUtils.isScanRequest(transaction); @@ -699,9 +699,9 @@ function getMoneyRequestInformation( category: string | undefined, tag: string | undefined, billable: boolean | undefined, - policy: OnyxTypes.Policy | EmptyObject | undefined, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories | EmptyObject, + policy: OnyxEntry | undefined, + policyTags: OnyxEntry | undefined, + policyCategories: OnyxEntry | undefined, payeeAccountID = userAccountID, payeeEmail = currentUserEmail, moneyRequestReportID = '', @@ -913,9 +913,9 @@ function createDistanceRequest( merchant: string, billable: boolean | undefined, validWaypoints: WaypointCollection, - policy: OnyxTypes.Policy | EmptyObject | undefined, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); @@ -1347,9 +1347,9 @@ function requestMoney( taxCode = '', taxAmount = 0, billable?: boolean, - policy: OnyxTypes.Policy | EmptyObject = {}, - policyTags: OnyxTypes.PolicyTagList = {}, - policyCategories: OnyxTypes.PolicyCategories | EmptyObject = {}, + policy?: OnyxEntry, + policyTags?: OnyxEntry, + policyCategories?: OnyxEntry, ) { // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 2efba59e0acc..4ea3f4a88c6a 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -64,9 +64,9 @@ const propTypes = { }; const defaultProps = { personalDetails: {}, - policy: {}, - policyCategories: {}, - policyTags: {}, + policy: null, + policyCategories: null, + policyTags: null, report: {}, transaction: {}, ...withCurrentUserPersonalDetailsDefaultProps, diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index 1297b98a0814..d3cf73847b73 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -1,6 +1,8 @@ +import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import categoryPropTypes from '@components/categoryPropTypes'; import TagPicker from '@components/TagPicker'; import tagPropTypes from '@components/tagPropTypes'; import Text from '@components/Text'; @@ -11,6 +13,7 @@ import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import reportPropTypes from '@pages/reportPropTypes'; +import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -31,17 +34,27 @@ const propTypes = { /** The report currently being used */ report: reportPropTypes, + /** The policy of the report */ + policy: policyPropTypes.policy, + + /** The category configuration of the report's policy */ + policyCategories: PropTypes.objectOf(categoryPropTypes), + /** Collection of tags attached to a policy */ policyTags: tagPropTypes, }; const defaultProps = { report: {}, - policyTags: {}, + policy: null, + policyTags: null, + policyCategories: null, transaction: {}, }; function IOURequestStepTag({ + policy, + policyCategories, policyTags, report, route: { @@ -75,7 +88,7 @@ function IOURequestStepTag({ return; } if (isEditing) { - IOU.updateMoneyRequestTag(transactionID, report.reportID, updatedTag); + IOU.updateMoneyRequestTag(transactionID, report.reportID, updatedTag, policy, policyTags, policyCategories); Navigation.dismissModal(); return; } @@ -114,6 +127,12 @@ export default compose( withWritableReportOrNotFound, withFullTransactionOrNotFound, withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, + policyCategories: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, + }, policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index cec03565f7f4..ff688419605d 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -15,21 +15,17 @@ type PolicyTag = { type PolicyTags = Record; -// When queried from Onyx, if there is no matching policy tag list, the data -// returned will be an empty object, represented by Record. -type PolicyTagList = - | Record< - T, - { - /** Name of the tag list */ - name: T; - - /** Flag that determines if tags are required */ - required: boolean; - - tags: PolicyTags; - } - > - | Record; +type PolicyTagList = Record< + T, + { + /** Name of the tag list */ + name: T; + + /** Flag that determines if tags are required */ + required: boolean; + + tags: PolicyTags; + } +>; export type {PolicyTag, PolicyTags, PolicyTagList}; From 34930401a0b74bc3144a000b05fb3da620d2a865 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 5 Feb 2024 21:15:04 -0800 Subject: [PATCH 21/27] editMoneyRequest is no longer used, delete --- .../API/parameters/EditMoneyRequestParams.ts | 14 - src/libs/API/parameters/index.ts | 1 - src/libs/API/types.ts | 2 - src/libs/actions/IOU.ts | 288 ------------------ tests/actions/IOUTest.js | 276 ----------------- 5 files changed, 581 deletions(-) delete mode 100644 src/libs/API/parameters/EditMoneyRequestParams.ts diff --git a/src/libs/API/parameters/EditMoneyRequestParams.ts b/src/libs/API/parameters/EditMoneyRequestParams.ts deleted file mode 100644 index 6d320510e267..000000000000 --- a/src/libs/API/parameters/EditMoneyRequestParams.ts +++ /dev/null @@ -1,14 +0,0 @@ -type EditMoneyRequestParams = { - transactionID: string; - reportActionID: string; - created?: string; - amount?: number; - currency?: string; - comment?: string; - merchant?: string; - category?: string; - billable?: boolean; - tag?: string; -}; - -export default EditMoneyRequestParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 4d784463c2f8..d3a730dd64a2 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -134,7 +134,6 @@ export type {default as CreateDistanceRequestParams} from './CreateDistanceReque export type {default as StartSplitBillParams} from './StartSplitBillParams'; export type {default as SendMoneyParams} from './SendMoneyParams'; export type {default as ApproveMoneyRequestParams} from './ApproveMoneyRequestParams'; -export type {default as EditMoneyRequestParams} from './EditMoneyRequestParams'; export type {default as ReplaceReceiptParams} from './ReplaceReceiptParams'; export type {default as SubmitReportParams} from './SubmitReportParams'; export type {default as DetachReceiptParams} from './DetachReceiptParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a4ab3db9a7cd..679d7af63cd5 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -135,7 +135,6 @@ const WRITE_COMMANDS = { SEND_MONEY_ELSEWHERE: 'SendMoneyElsewhere', SEND_MONEY_WITH_WALLET: 'SendMoneyWithWallet', APPROVE_MONEY_REQUEST: 'ApproveMoneyRequest', - EDIT_MONEY_REQUEST: 'EditMoneyRequest', REPLACE_RECEIPT: 'ReplaceReceipt', SUBMIT_REPORT: 'SubmitReport', DETACH_RECEIPT: 'DetachReceipt', @@ -273,7 +272,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SEND_MONEY_ELSEWHERE]: Parameters.SendMoneyParams; [WRITE_COMMANDS.SEND_MONEY_WITH_WALLET]: Parameters.SendMoneyParams; [WRITE_COMMANDS.APPROVE_MONEY_REQUEST]: Parameters.ApproveMoneyRequestParams; - [WRITE_COMMANDS.EDIT_MONEY_REQUEST]: Parameters.EditMoneyRequestParams; [WRITE_COMMANDS.REPLACE_RECEIPT]: Parameters.ReplaceReceiptParams; [WRITE_COMMANDS.SUBMIT_REPORT]: Parameters.SubmitReportParams; [WRITE_COMMANDS.DETACH_RECEIPT]: Parameters.DetachReceiptParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5ff77c348a76..4b15f9b4e0ef 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -13,7 +13,6 @@ import type { CreateDistanceRequestParams, DeleteMoneyRequestParams, DetachReceiptParams, - EditMoneyRequestParams, PayMoneyRequestParams, ReplaceReceiptParams, RequestMoneyParams, @@ -1314,19 +1313,6 @@ function updateMoneyRequestDescription( API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DESCRIPTION, params, onyxData); } -/** Edits an existing distance request */ -function updateDistanceRequest( - transactionID: string, - transactionThreadReportID: string, - transactionChanges: TransactionChanges, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, -) { - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, false); - API.write(WRITE_COMMANDS.UPDATE_DISTANCE_REQUEST, params, onyxData); -} - /** * Request money from another user * @param amount - always in the smallest unit of the currency @@ -2385,279 +2371,6 @@ function setDraftSplitTransaction(transactionID: string, transactionChanges: Tra Onyx.merge(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, updatedTransaction); } -function editRegularMoneyRequest( - transactionID: string, - transactionThreadReportID: string, - transactionChanges: TransactionChanges, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, -) { - // STEP 1: Get all collections we're updating - const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null; - const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null; - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`] ?? null; - const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); - - // STEP 2: Build new modified expense report action. - const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport); - const updatedTransaction = transaction ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) : null; - - // STEP 3: Compute the IOU total and update the report preview message so LHN amount owed is correct - // Should only update if the transaction matches the currency of the report, else we wait for the update - // from the server with the currency conversion - let updatedMoneyRequestReport = {...iouReport}; - const updatedChatReport = {...chatReport}; - const diff = TransactionUtils.getAmount(transaction, true) - TransactionUtils.getAmount(updatedTransaction, true); - if (updatedTransaction?.currency === iouReport?.currency && updatedTransaction?.modifiedAmount && diff !== 0) { - if (ReportUtils.isExpenseReport(iouReport) && typeof updatedMoneyRequestReport.total === 'number') { - updatedMoneyRequestReport.total += diff; - } else { - updatedMoneyRequestReport = iouReport - ? IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID ?? -1, diff, TransactionUtils.getCurrency(transaction), false) - : {}; - } - - updatedMoneyRequestReport.cachedTotal = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedTransaction.currency); - - // Update the last message of the IOU report - const lastMessage = ReportUtils.getIOUReportActionMessage( - iouReport?.reportID ?? '', - CONST.IOU.REPORT_ACTION_TYPE.CREATE, - updatedMoneyRequestReport.total ?? 0, - '', - updatedTransaction.currency, - '', - false, - ); - updatedMoneyRequestReport.lastMessageText = lastMessage[0].text; - updatedMoneyRequestReport.lastMessageHtml = lastMessage[0].html; - - // Update the last message of the chat report - const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport?.reportID); - const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { - payer: ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID ?? -1).login ?? '', - amount: CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency), - }); - updatedChatReport.lastMessageText = messageText; - updatedChatReport.lastMessageHtml = messageText; - } - - const isScanning = TransactionUtils.hasReceipt(updatedTransaction) && TransactionUtils.isReceiptBeingScanned(updatedTransaction); - - // STEP 4: Compose the optimistic data - const currentTime = DateUtils.getDBTime(); - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`, - value: { - [updatedReportAction.reportActionID]: updatedReportAction as OnyxTypes.ReportAction, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: updatedTransaction, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`, - value: updatedMoneyRequestReport, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`, - value: updatedChatReport, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, - value: { - lastReadTime: currentTime, - lastVisibleActionCreated: currentTime, - }, - }, - ]; - - if (!isScanning) { - optimisticData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`, - value: { - [transactionThread?.parentReportActionID ?? '']: { - whisperedToAccountIDs: [], - }, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.parentReportID}`, - value: { - [iouReport?.parentReportActionID ?? '']: { - whisperedToAccountIDs: [], - }, - }, - }, - ); - } - - // Update recently used categories if the category is changed - if ('category' in transactionChanges) { - const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport?.policyID, transactionChanges.category); - if (optimisticPolicyRecentlyUsedCategories.length) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport?.policyID}`, - value: optimisticPolicyRecentlyUsedCategories, - }); - } - } - - // Update recently used categories if the tag is changed - if ('tag' in transactionChanges) { - const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport?.policyID, transactionChanges.tag); - if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport?.policyID}`, - value: optimisticPolicyRecentlyUsedTags, - }); - } - } - - const successData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`, - value: { - [updatedReportAction.reportActionID]: {pendingAction: null}, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: { - pendingFields: { - comment: null, - amount: null, - created: null, - currency: null, - merchant: null, - billable: null, - category: null, - tag: null, - }, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`, - value: {pendingAction: null}, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`, - value: { - [updatedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'), - }, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: { - ...transaction, - modifiedCreated: transaction?.modifiedCreated ? transaction.modifiedCreated : null, - modifiedAmount: transaction?.modifiedAmount ? transaction.modifiedAmount : null, - modifiedCurrency: transaction?.modifiedCurrency ? transaction.modifiedCurrency : null, - modifiedMerchant: transaction?.modifiedMerchant ? transaction.modifiedMerchant : null, - modifiedWaypoints: transaction?.modifiedWaypoints ? transaction.modifiedWaypoints : null, - pendingFields: null, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`, - value: { - ...iouReport, - cachedTotal: iouReport?.cachedTotal ? iouReport?.cachedTotal : null, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`, - value: chatReport, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, - value: { - lastReadTime: transactionThread?.lastReadTime, - lastVisibleActionCreated: transactionThread?.lastVisibleActionCreated, - }, - }, - ]; - - // Add transaction violations if there is a policy and updated transaaction - if (policy?.id && updatedTransaction) { - const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; - const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( - updatedTransaction, - currentTransactionViolations, - !!policy.requiresTag, - policyTags, - !!policy.requiresCategory, - policyCategories, - ); - optimisticData.push(updatedViolationsOnyxData); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: currentTransactionViolations, - }); - } - - // STEP 6: Call the API endpoint - const {created, amount, currency, comment, merchant, category, billable, tag} = ReportUtils.getTransactionDetails(updatedTransaction) ?? {}; - - const parameters: EditMoneyRequestParams = { - transactionID, - reportActionID: updatedReportAction.reportActionID, - created, - amount, - currency, - comment, - merchant, - category, - billable, - tag, - }; - - API.write(WRITE_COMMANDS.EDIT_MONEY_REQUEST, parameters, {optimisticData, successData, failureData}); -} - -function editMoneyRequest( - transaction: OnyxTypes.Transaction, - transactionThreadReportID: string, - transactionChanges: TransactionChanges, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, -) { - if (TransactionUtils.isDistanceRequest(transaction)) { - updateDistanceRequest(transaction.transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories); - } else { - editRegularMoneyRequest(transaction.transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories); - } -} - /** Updates the amount and currency fields of a money request */ function updateMoneyRequestAmountAndCurrency( transactionID: string, @@ -3843,7 +3556,6 @@ export { replaceReceipt, detachReceipt, getIOUReportID, - editMoneyRequest, navigateToStartStepIfScanFileCannotBeRead, savePreferredPaymentMethod, }; diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index 92b39fc3ac50..d45e60d1999e 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -1390,282 +1390,6 @@ describe('actions/IOU', () => { }); }); - describe('edit money request', () => { - const amount = 10000; - const comment = '💸💸💸💸'; - const merchant = 'NASDAQ'; - - afterEach(() => { - fetch.resume(); - }); - - it('updates the IOU request and IOU report when offline', () => { - let thread = {}; - let iouReport = {}; - let iouAction = {}; - let transaction = {}; - - fetch.pause(); - IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); - return waitForBatchedUpdates() - .then(() => { - Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}); - return waitForBatchedUpdates(); - }) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (allReports) => { - Onyx.disconnect(connectionID); - iouReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); - - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, - waitForCollectionCallback: true, - callback: (reportActionsForIOUReport) => { - Onyx.disconnect(connectionID); - - [iouAction] = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (allTransactions) => { - Onyx.disconnect(connectionID); - - transaction = _.find(allTransactions, (t) => !_.isEmpty(t)); - resolve(); - }, - }); - }), - ) - .then(() => { - thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID); - Onyx.set(`report_${thread.reportID}`, thread); - return waitForBatchedUpdates(); - }) - .then(() => { - IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'}); - return waitForBatchedUpdates(); - }) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (allTransactions) => { - Onyx.disconnect(connectionID); - - const updatedTransaction = _.find(allTransactions, (t) => !_.isEmpty(t)); - expect(updatedTransaction.modifiedAmount).toBe(20000); - expect(updatedTransaction.comment).toMatchObject({comment: 'Double the amount!'}); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, - waitForCollectionCallback: true, - callback: (allActions) => { - Onyx.disconnect(connectionID); - const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction)); - expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE'); - expect(updatedAction.originalMessage).toEqual( - expect.objectContaining({amount: 20000, newComment: 'Double the amount!', oldAmount: amount, oldComment: comment}), - ); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (allReports) => { - Onyx.disconnect(connectionID); - const updatedIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); - const updatedChatReport = _.find(allReports, (report) => report.reportID === iouReport.chatReportID); - expect(updatedIOUReport).toEqual( - expect.objectContaining({ - total: 20000, - cachedTotal: '$200.00', - lastMessageHtml: 'requested $200.00', - lastMessageText: 'requested $200.00', - }), - ); - expect(updatedChatReport).toEqual( - expect.objectContaining({ - lastMessageHtml: `${CARLOS_EMAIL} owes $200.00`, - lastMessageText: `${CARLOS_EMAIL} owes $200.00`, - }), - ); - resolve(); - }, - }); - }), - ) - .then(() => { - fetch.resume(); - }); - }); - - it('resets the IOU request and IOU report when api returns an error', () => { - let thread = {}; - let iouReport = {}; - let iouAction = {}; - let transaction = {}; - - IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); - return waitForBatchedUpdates() - .then(() => { - Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}); - return waitForBatchedUpdates(); - }) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (allReports) => { - Onyx.disconnect(connectionID); - [iouReport] = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, - waitForCollectionCallback: true, - callback: (reportActionsForIOUReport) => { - Onyx.disconnect(connectionID); - - [iouAction] = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (allTransactions) => { - Onyx.disconnect(connectionID); - - transaction = _.find(allTransactions, (t) => !_.isEmpty(t)); - resolve(); - }, - }); - }), - ) - .then(() => { - thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID); - Onyx.set(`report_${thread.reportID}`, thread); - return waitForBatchedUpdates(); - }) - .then(() => { - fetch.fail(); - IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'}); - return waitForBatchedUpdates(); - }) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (allTransactions) => { - Onyx.disconnect(connectionID); - - const updatedTransaction = _.find(allTransactions, (t) => !_.isEmpty(t)); - expect(updatedTransaction.modifiedAmount).toBe(undefined); - expect(updatedTransaction.amount).toBe(10000); - expect(updatedTransaction.comment).toMatchObject({comment}); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, - waitForCollectionCallback: true, - callback: (allActions) => { - Onyx.disconnect(connectionID); - const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction)); - expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE'); - expect(_.values(updatedAction.errors)).toEqual(expect.arrayContaining(['iou.error.genericEditFailureMessage'])); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (allReports) => { - Onyx.disconnect(connectionID); - const updatedIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); - const updatedChatReport = _.find(allReports, (report) => report.reportID === iouReport.chatReportID); - expect(updatedIOUReport).toEqual( - expect.objectContaining({ - total: 10000, - cachedTotal: '$100.00', - lastMessageHtml: `requested $${amount / 100}.00 for ${comment}`, - lastMessageText: `requested $${amount / 100}.00 for ${comment}`, - }), - ); - expect(updatedChatReport).toEqual( - expect.objectContaining({ - lastMessageHtml: '', - }), - ); - resolve(); - }, - }); - }), - ); - }); - }); - describe('pay expense report via ACH', () => { const amount = 10000; const comment = '💸💸💸💸'; From 86ea25f5cb9340fdfea61173e153927674d88ac9 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 5 Feb 2024 21:46:26 -0800 Subject: [PATCH 22/27] Finish typing IOU --- src/libs/actions/IOU.ts | 56 ++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4b15f9b4e0ef..beb5df6d12fb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -985,9 +985,9 @@ function getUpdateMoneyRequestParams( transactionID: string, transactionThreadReportID: string, transactionChanges: TransactionChanges, - policy: OnyxTypes.Policy | EmptyObject, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, onlyIncludeChangedFields: boolean, ): UpdateMoneyRequestData { const optimisticData: OnyxUpdate[] = []; @@ -1186,7 +1186,7 @@ function getUpdateMoneyRequestParams( if (policy?.id && updatedTransaction) { const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; optimisticData.push( - ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, !!policy.requiresTag, policyTags, !!policy.requiresCategory, policyCategories), + ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, !!policy.requiresTag, policyTags ?? {}, !!policy.requiresCategory, policyCategories ?? {}), ); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -1206,9 +1206,9 @@ function updateMoneyRequestDate( transactionID: string, transactionThreadReportID: string, value: string, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges: TransactionChanges = { created: value, @@ -1222,9 +1222,9 @@ function updateMoneyRequestBillable( transactionID: string, transactionThreadReportID: string, value: boolean, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges: TransactionChanges = { billable: value, @@ -1238,9 +1238,9 @@ function updateMoneyRequestMerchant( transactionID: string, transactionThreadReportID: string, value: string, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges: TransactionChanges = { merchant: value, @@ -1254,9 +1254,9 @@ function updateMoneyRequestTag( transactionID: string, transactionThreadReportID: string, tag: string, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges: TransactionChanges = { tag, @@ -1270,9 +1270,9 @@ function updateMoneyRequestDistance( transactionID: string, transactionThreadReportID: string, waypoints: WaypointCollection, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges: TransactionChanges = { waypoints, @@ -1286,9 +1286,9 @@ function updateMoneyRequestCategory( transactionID: string, transactionThreadReportID: string, category: string, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges: TransactionChanges = { category, @@ -1302,9 +1302,9 @@ function updateMoneyRequestDescription( transactionID: string, transactionThreadReportID: string, comment: string, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges: TransactionChanges = { comment, @@ -2377,9 +2377,9 @@ function updateMoneyRequestAmountAndCurrency( transactionThreadReportID: string, currency: string, amount: number, - policy: OnyxTypes.Policy, - policyTags: OnyxTypes.PolicyTagList, - policyCategories: OnyxTypes.PolicyCategories, + policy: OnyxEntry, + policyTags: OnyxEntry, + policyCategories: OnyxEntry, ) { const transactionChanges = { amount, From 352f5b136ed31089348c2c30fe586e825a5e1991 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Tue, 6 Feb 2024 11:01:26 -0800 Subject: [PATCH 23/27] Expect error in MoneyRequestView --- src/components/ReportActionItem/MoneyRequestView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index de00440e87d3..774503023e6a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -156,6 +156,7 @@ function MoneyRequestView({ Navigation.dismissModal(); return; } + // @ts-expect-error: the type used across the app for policyTags is not what is returned by Onyx, PolicyTagList represents that, but existing policy tag utils need a refactor to fix this IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '', report?.reportID, newBillable, policy, policyTags, policyCategories); Navigation.dismissModal(); }, From 934bab49c7bd9ce10b8301ebc7f4c14eb5102514 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Tue, 6 Feb 2024 11:12:53 -0800 Subject: [PATCH 24/27] Random lint change --- src/libs/actions/IOU.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index da0ed2421867..766f35e4e58b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1184,7 +1184,14 @@ function getUpdateMoneyRequestParams( if (policy?.id && updatedTransaction) { const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; optimisticData.push( - ViolationsUtils.getViolationsOnyxData(updatedTransaction, currentTransactionViolations, !!policy.requiresTag, policyTags ?? {}, !!policy.requiresCategory, policyCategories ?? {}), + ViolationsUtils.getViolationsOnyxData( + updatedTransaction, + currentTransactionViolations, + !!policy.requiresTag, + policyTags ?? {}, + !!policy.requiresCategory, + policyCategories ?? {}, + ), ); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, From fbbcdae27919e54642ca4e1fe80eba3966251e64 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Tue, 6 Feb 2024 11:13:00 -0800 Subject: [PATCH 25/27] Revert "editMoneyRequest is no longer used, delete" This reverts commit 34930401a0b74bc3144a000b05fb3da620d2a865. --- .../API/parameters/EditMoneyRequestParams.ts | 14 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/IOU.ts | 288 ++++++++++++++++++ tests/actions/IOUTest.js | 276 +++++++++++++++++ 5 files changed, 581 insertions(+) create mode 100644 src/libs/API/parameters/EditMoneyRequestParams.ts diff --git a/src/libs/API/parameters/EditMoneyRequestParams.ts b/src/libs/API/parameters/EditMoneyRequestParams.ts new file mode 100644 index 000000000000..6d320510e267 --- /dev/null +++ b/src/libs/API/parameters/EditMoneyRequestParams.ts @@ -0,0 +1,14 @@ +type EditMoneyRequestParams = { + transactionID: string; + reportActionID: string; + created?: string; + amount?: number; + currency?: string; + comment?: string; + merchant?: string; + category?: string; + billable?: boolean; + tag?: string; +}; + +export default EditMoneyRequestParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index d3a730dd64a2..4d784463c2f8 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -134,6 +134,7 @@ export type {default as CreateDistanceRequestParams} from './CreateDistanceReque export type {default as StartSplitBillParams} from './StartSplitBillParams'; export type {default as SendMoneyParams} from './SendMoneyParams'; export type {default as ApproveMoneyRequestParams} from './ApproveMoneyRequestParams'; +export type {default as EditMoneyRequestParams} from './EditMoneyRequestParams'; export type {default as ReplaceReceiptParams} from './ReplaceReceiptParams'; export type {default as SubmitReportParams} from './SubmitReportParams'; export type {default as DetachReceiptParams} from './DetachReceiptParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 679d7af63cd5..a4ab3db9a7cd 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -135,6 +135,7 @@ const WRITE_COMMANDS = { SEND_MONEY_ELSEWHERE: 'SendMoneyElsewhere', SEND_MONEY_WITH_WALLET: 'SendMoneyWithWallet', APPROVE_MONEY_REQUEST: 'ApproveMoneyRequest', + EDIT_MONEY_REQUEST: 'EditMoneyRequest', REPLACE_RECEIPT: 'ReplaceReceipt', SUBMIT_REPORT: 'SubmitReport', DETACH_RECEIPT: 'DetachReceipt', @@ -272,6 +273,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SEND_MONEY_ELSEWHERE]: Parameters.SendMoneyParams; [WRITE_COMMANDS.SEND_MONEY_WITH_WALLET]: Parameters.SendMoneyParams; [WRITE_COMMANDS.APPROVE_MONEY_REQUEST]: Parameters.ApproveMoneyRequestParams; + [WRITE_COMMANDS.EDIT_MONEY_REQUEST]: Parameters.EditMoneyRequestParams; [WRITE_COMMANDS.REPLACE_RECEIPT]: Parameters.ReplaceReceiptParams; [WRITE_COMMANDS.SUBMIT_REPORT]: Parameters.SubmitReportParams; [WRITE_COMMANDS.DETACH_RECEIPT]: Parameters.DetachReceiptParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 766f35e4e58b..e1d4b2dbf51f 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -13,6 +13,7 @@ import type { CreateDistanceRequestParams, DeleteMoneyRequestParams, DetachReceiptParams, + EditMoneyRequestParams, PayMoneyRequestParams, ReplaceReceiptParams, RequestMoneyParams, @@ -1318,6 +1319,19 @@ function updateMoneyRequestDescription( API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DESCRIPTION, params, onyxData); } +/** Edits an existing distance request */ +function updateDistanceRequest( + transactionID: string, + transactionThreadReportID: string, + transactionChanges: TransactionChanges, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, false); + API.write(WRITE_COMMANDS.UPDATE_DISTANCE_REQUEST, params, onyxData); +} + /** * Request money from another user */ @@ -2379,6 +2393,279 @@ function setDraftSplitTransaction(transactionID: string, transactionChanges: Tra Onyx.merge(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, updatedTransaction); } +function editRegularMoneyRequest( + transactionID: string, + transactionThreadReportID: string, + transactionChanges: TransactionChanges, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { + // STEP 1: Get all collections we're updating + const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null; + const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null; + const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`] ?? null; + const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); + + // STEP 2: Build new modified expense report action. + const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport); + const updatedTransaction = transaction ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) : null; + + // STEP 3: Compute the IOU total and update the report preview message so LHN amount owed is correct + // Should only update if the transaction matches the currency of the report, else we wait for the update + // from the server with the currency conversion + let updatedMoneyRequestReport = {...iouReport}; + const updatedChatReport = {...chatReport}; + const diff = TransactionUtils.getAmount(transaction, true) - TransactionUtils.getAmount(updatedTransaction, true); + if (updatedTransaction?.currency === iouReport?.currency && updatedTransaction?.modifiedAmount && diff !== 0) { + if (ReportUtils.isExpenseReport(iouReport) && typeof updatedMoneyRequestReport.total === 'number') { + updatedMoneyRequestReport.total += diff; + } else { + updatedMoneyRequestReport = iouReport + ? IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID ?? -1, diff, TransactionUtils.getCurrency(transaction), false) + : {}; + } + + updatedMoneyRequestReport.cachedTotal = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedTransaction.currency); + + // Update the last message of the IOU report + const lastMessage = ReportUtils.getIOUReportActionMessage( + iouReport?.reportID ?? '', + CONST.IOU.REPORT_ACTION_TYPE.CREATE, + updatedMoneyRequestReport.total ?? 0, + '', + updatedTransaction.currency, + '', + false, + ); + updatedMoneyRequestReport.lastMessageText = lastMessage[0].text; + updatedMoneyRequestReport.lastMessageHtml = lastMessage[0].html; + + // Update the last message of the chat report + const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport?.reportID); + const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { + payer: ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID ?? -1).login ?? '', + amount: CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency), + }); + updatedChatReport.lastMessageText = messageText; + updatedChatReport.lastMessageHtml = messageText; + } + + const isScanning = TransactionUtils.hasReceipt(updatedTransaction) && TransactionUtils.isReceiptBeingScanned(updatedTransaction); + + // STEP 4: Compose the optimistic data + const currentTime = DateUtils.getDBTime(); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`, + value: { + [updatedReportAction.reportActionID]: updatedReportAction as OnyxTypes.ReportAction, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: updatedTransaction, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`, + value: updatedMoneyRequestReport, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`, + value: updatedChatReport, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + value: { + lastReadTime: currentTime, + lastVisibleActionCreated: currentTime, + }, + }, + ]; + + if (!isScanning) { + optimisticData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`, + value: { + [transactionThread?.parentReportActionID ?? '']: { + whisperedToAccountIDs: [], + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.parentReportID}`, + value: { + [iouReport?.parentReportActionID ?? '']: { + whisperedToAccountIDs: [], + }, + }, + }, + ); + } + + // Update recently used categories if the category is changed + if ('category' in transactionChanges) { + const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport?.policyID, transactionChanges.category); + if (optimisticPolicyRecentlyUsedCategories.length) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport?.policyID}`, + value: optimisticPolicyRecentlyUsedCategories, + }); + } + } + + // Update recently used categories if the tag is changed + if ('tag' in transactionChanges) { + const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport?.policyID, transactionChanges.tag); + if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport?.policyID}`, + value: optimisticPolicyRecentlyUsedTags, + }); + } + } + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`, + value: { + [updatedReportAction.reportActionID]: {pendingAction: null}, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + pendingFields: { + comment: null, + amount: null, + created: null, + currency: null, + merchant: null, + billable: null, + category: null, + tag: null, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`, + value: {pendingAction: null}, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`, + value: { + [updatedReportAction.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'), + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + ...transaction, + modifiedCreated: transaction?.modifiedCreated ? transaction.modifiedCreated : null, + modifiedAmount: transaction?.modifiedAmount ? transaction.modifiedAmount : null, + modifiedCurrency: transaction?.modifiedCurrency ? transaction.modifiedCurrency : null, + modifiedMerchant: transaction?.modifiedMerchant ? transaction.modifiedMerchant : null, + modifiedWaypoints: transaction?.modifiedWaypoints ? transaction.modifiedWaypoints : null, + pendingFields: null, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`, + value: { + ...iouReport, + cachedTotal: iouReport?.cachedTotal ? iouReport?.cachedTotal : null, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`, + value: chatReport, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + value: { + lastReadTime: transactionThread?.lastReadTime, + lastVisibleActionCreated: transactionThread?.lastVisibleActionCreated, + }, + }, + ]; + + // Add transaction violations if there is a policy and updated transaaction + if (policy?.id && updatedTransaction) { + const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; + const updatedViolationsOnyxData = ViolationsUtils.getViolationsOnyxData( + updatedTransaction, + currentTransactionViolations, + !!policy.requiresTag, + policyTags, + !!policy.requiresCategory, + policyCategories, + ); + optimisticData.push(updatedViolationsOnyxData); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: currentTransactionViolations, + }); + } + + // STEP 6: Call the API endpoint + const {created, amount, currency, comment, merchant, category, billable, tag} = ReportUtils.getTransactionDetails(updatedTransaction) ?? {}; + + const parameters: EditMoneyRequestParams = { + transactionID, + reportActionID: updatedReportAction.reportActionID, + created, + amount, + currency, + comment, + merchant, + category, + billable, + tag, + }; + + API.write(WRITE_COMMANDS.EDIT_MONEY_REQUEST, parameters, {optimisticData, successData, failureData}); +} + +function editMoneyRequest( + transaction: OnyxTypes.Transaction, + transactionThreadReportID: string, + transactionChanges: TransactionChanges, + policy: OnyxTypes.Policy, + policyTags: OnyxTypes.PolicyTagList, + policyCategories: OnyxTypes.PolicyCategories, +) { + if (TransactionUtils.isDistanceRequest(transaction)) { + updateDistanceRequest(transaction.transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories); + } else { + editRegularMoneyRequest(transaction.transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories); + } +} + /** Updates the amount and currency fields of a money request */ function updateMoneyRequestAmountAndCurrency( transactionID: string, @@ -3565,6 +3852,7 @@ export { replaceReceipt, detachReceipt, getIOUReportID, + editMoneyRequest, navigateToStartStepIfScanFileCannotBeRead, savePreferredPaymentMethod, }; diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index d45e60d1999e..92b39fc3ac50 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -1390,6 +1390,282 @@ describe('actions/IOU', () => { }); }); + describe('edit money request', () => { + const amount = 10000; + const comment = '💸💸💸💸'; + const merchant = 'NASDAQ'; + + afterEach(() => { + fetch.resume(); + }); + + it('updates the IOU request and IOU report when offline', () => { + let thread = {}; + let iouReport = {}; + let iouAction = {}; + let transaction = {}; + + fetch.pause(); + IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); + return waitForBatchedUpdates() + .then(() => { + Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}); + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + iouReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); + + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, + waitForCollectionCallback: true, + callback: (reportActionsForIOUReport) => { + Onyx.disconnect(connectionID); + + [iouAction] = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (allTransactions) => { + Onyx.disconnect(connectionID); + + transaction = _.find(allTransactions, (t) => !_.isEmpty(t)); + resolve(); + }, + }); + }), + ) + .then(() => { + thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID); + Onyx.set(`report_${thread.reportID}`, thread); + return waitForBatchedUpdates(); + }) + .then(() => { + IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'}); + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (allTransactions) => { + Onyx.disconnect(connectionID); + + const updatedTransaction = _.find(allTransactions, (t) => !_.isEmpty(t)); + expect(updatedTransaction.modifiedAmount).toBe(20000); + expect(updatedTransaction.comment).toMatchObject({comment: 'Double the amount!'}); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, + waitForCollectionCallback: true, + callback: (allActions) => { + Onyx.disconnect(connectionID); + const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction)); + expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE'); + expect(updatedAction.originalMessage).toEqual( + expect.objectContaining({amount: 20000, newComment: 'Double the amount!', oldAmount: amount, oldComment: comment}), + ); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + const updatedIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); + const updatedChatReport = _.find(allReports, (report) => report.reportID === iouReport.chatReportID); + expect(updatedIOUReport).toEqual( + expect.objectContaining({ + total: 20000, + cachedTotal: '$200.00', + lastMessageHtml: 'requested $200.00', + lastMessageText: 'requested $200.00', + }), + ); + expect(updatedChatReport).toEqual( + expect.objectContaining({ + lastMessageHtml: `${CARLOS_EMAIL} owes $200.00`, + lastMessageText: `${CARLOS_EMAIL} owes $200.00`, + }), + ); + resolve(); + }, + }); + }), + ) + .then(() => { + fetch.resume(); + }); + }); + + it('resets the IOU request and IOU report when api returns an error', () => { + let thread = {}; + let iouReport = {}; + let iouAction = {}; + let transaction = {}; + + IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); + return waitForBatchedUpdates() + .then(() => { + Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}); + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + [iouReport] = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, + waitForCollectionCallback: true, + callback: (reportActionsForIOUReport) => { + Onyx.disconnect(connectionID); + + [iouAction] = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (allTransactions) => { + Onyx.disconnect(connectionID); + + transaction = _.find(allTransactions, (t) => !_.isEmpty(t)); + resolve(); + }, + }); + }), + ) + .then(() => { + thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID); + Onyx.set(`report_${thread.reportID}`, thread); + return waitForBatchedUpdates(); + }) + .then(() => { + fetch.fail(); + IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'}); + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (allTransactions) => { + Onyx.disconnect(connectionID); + + const updatedTransaction = _.find(allTransactions, (t) => !_.isEmpty(t)); + expect(updatedTransaction.modifiedAmount).toBe(undefined); + expect(updatedTransaction.amount).toBe(10000); + expect(updatedTransaction.comment).toMatchObject({comment}); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, + waitForCollectionCallback: true, + callback: (allActions) => { + Onyx.disconnect(connectionID); + const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction)); + expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE'); + expect(_.values(updatedAction.errors)).toEqual(expect.arrayContaining(['iou.error.genericEditFailureMessage'])); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + const updatedIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU); + const updatedChatReport = _.find(allReports, (report) => report.reportID === iouReport.chatReportID); + expect(updatedIOUReport).toEqual( + expect.objectContaining({ + total: 10000, + cachedTotal: '$100.00', + lastMessageHtml: `requested $${amount / 100}.00 for ${comment}`, + lastMessageText: `requested $${amount / 100}.00 for ${comment}`, + }), + ); + expect(updatedChatReport).toEqual( + expect.objectContaining({ + lastMessageHtml: '', + }), + ); + resolve(); + }, + }); + }), + ); + }); + }); + describe('pay expense report via ACH', () => { const amount = 10000; const comment = '💸💸💸💸'; From 30a2f24c713438d2428271d0454bc7d8842f9bc9 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 7 Feb 2024 09:57:51 -0800 Subject: [PATCH 26/27] Update command in new description component --- .../request/step/IOURequestStepDescription.js | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepDescription.js b/src/pages/iou/request/step/IOURequestStepDescription.js index 25477170f505..d3e0ca72991c 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.js +++ b/src/pages/iou/request/step/IOURequestStepDescription.js @@ -1,11 +1,14 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import lodashIsEmpty from 'lodash/isEmpty'; +import PropTypes from 'prop-types'; import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import categoryPropTypes from '@components/categoryPropTypes'; import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; +import tagPropTypes from '@components/tagPropTypes'; import TextInput from '@components/TextInput'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; @@ -17,6 +20,7 @@ import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {policyPropTypes} from '@src/pages/workspace/withPolicy'; import ROUTES from '@src/ROUTES'; import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; import StepScreenWrapper from './StepScreenWrapper'; @@ -33,11 +37,23 @@ const propTypes = { /** The draft transaction that holds data to be persisted on the current transaction */ splitDraftTransaction: transactionPropTypes, + + /** The policy of the report */ + policy: policyPropTypes.policy, + + /** Collection of categories attached to a policy */ + policyCategories: PropTypes.objectOf(categoryPropTypes), + + /** Collection of tags attached to a policy */ + policyTags: tagPropTypes, }; const defaultProps = { transaction: {}, splitDraftTransaction: {}, + policy: null, + policyTags: null, + policyCategories: null, }; function IOURequestStepDescription({ @@ -46,6 +62,9 @@ function IOURequestStepDescription({ }, transaction, splitDraftTransaction, + policy, + policyTags, + policyCategories, }) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -98,7 +117,7 @@ function IOURequestStepDescription({ IOU.setMoneyRequestDescription(transaction.transactionID, newComment, action === CONST.IOU.ACTION.CREATE); if (action === CONST.IOU.ACTION.EDIT) { - IOU.updateMoneyRequestDescription(transaction.transactionID, reportID, newComment); + IOU.updateMoneyRequestDescription(transaction.transactionID, reportID, newComment, policy, policyTags, policyCategories); } navigateBack(); @@ -158,5 +177,14 @@ export default compose( return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`; }, }, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, + policyCategories: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, + }, + policyTags: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, + }, }), )(IOURequestStepDescription); From d528580c6319edceba89c1ec59a7c86752a442d7 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Fri, 9 Feb 2024 10:46:35 -0800 Subject: [PATCH 27/27] Apply suggestions from code review Co-authored-by: Carlos Alvarez --- src/libs/Violations/ViolationsUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 54b198496fa5..992449267934 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -25,7 +25,7 @@ const ViolationsUtils = { const hasCategoryOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'categoryOutOfPolicy'); const hasMissingCategoryViolation = transactionViolations.some((violation) => violation.name === 'missingCategory'); const categoryKey = updatedTransaction.category; - const isCategoryInPolicy = categoryKey ? policyCategories?.[categoryKey]?.enabled : undefined; + const isCategoryInPolicy = categoryKey ? policyCategories?.[categoryKey]?.enabled : false; // Add 'categoryOutOfPolicy' violation if category is not in policy if (!hasCategoryOutOfPolicyViolation && categoryKey && !isCategoryInPolicy) { @@ -53,7 +53,7 @@ const ViolationsUtils = { const policyTags = policyTagList[policyTagListName]?.tags; const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); - const isTagInPolicy = policyTags ? !!policyTags[updatedTransaction.tag ?? '']?.enabled : undefined; + const isTagInPolicy = policyTags ? !!policyTags[updatedTransaction.tag ?? '']?.enabled : false; // Add 'tagOutOfPolicy' violation if tag is not in policy if (!hasTagOutOfPolicyViolation && updatedTransaction.tag && !isTagInPolicy) {