Skip to content

Commit f0c9bbe

Browse files
authored
Merge pull request #27950 from BeeMargarida/feat/26793-edit-money-request-tag
#26793: Edit a tag in a money request
2 parents 41e1da3 + 02c218f commit f0c9bbe

13 files changed

+252
-41
lines changed

src/CONST.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,7 @@ const CONST = {
13591359
MERCHANT: 'merchant',
13601360
CATEGORY: 'category',
13611361
RECEIPT: 'receipt',
1362+
TAG: 'tag',
13621363
},
13631364
FOOTER: {
13641365
EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`,

src/components/MoneyRequestConfirmationList.js

+7-12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import transactionPropTypes from './transactionPropTypes';
3838
import DistanceRequestUtils from '../libs/DistanceRequestUtils';
3939
import * as IOU from '../libs/actions/IOU';
4040
import * as TransactionUtils from '../libs/TransactionUtils';
41+
import * as PolicyUtils from '../libs/PolicyUtils';
4142

4243
const propTypes = {
4344
/** Callback to inform parent modal of success */
@@ -142,13 +143,7 @@ const propTypes = {
142143
policyCategories: PropTypes.objectOf(categoryPropTypes),
143144

144145
/** Collection of tags attached to a policy */
145-
policyTags: PropTypes.objectOf(
146-
PropTypes.shape({
147-
name: PropTypes.string,
148-
required: PropTypes.bool,
149-
tags: PropTypes.objectOf(tagPropTypes),
150-
}),
151-
),
146+
policyTags: tagPropTypes,
152147
};
153148

154149
const defaultProps = {
@@ -202,12 +197,12 @@ function MoneyRequestConfirmationList(props) {
202197
const shouldShowCategories = isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories));
203198

204199
// Fetches the first tag list of the policy
205-
const tagListKey = _.first(_.keys(props.policyTags));
206-
const tagList = lodashGet(props.policyTags, [tagListKey, 'tags'], []);
207-
const tagListName = lodashGet(props.policyTags, [tagListKey, 'name'], '');
200+
const policyTag = PolicyUtils.getTag(props.policyTags);
201+
const policyTagList = lodashGet(policyTag, 'tags', {});
202+
const policyTagListName = lodashGet(policyTag, 'name', translate('common.tag'));
208203
const canUseTags = Permissions.canUseTags(props.betas);
209204
// A flag for showing the tags field
210-
const shouldShowTags = isPolicyExpenseChat && canUseTags && _.any(tagList, (tag) => tag.enabled);
205+
const shouldShowTags = isPolicyExpenseChat && canUseTags && OptionsListUtils.hasEnabledOptions(_.values(policyTagList));
211206

212207
// A flag for showing the billable field
213208
const shouldShowBillable = canUseTags && !lodashGet(props.policy, 'disabledFields.defaultBillable', true);
@@ -541,7 +536,7 @@ function MoneyRequestConfirmationList(props) {
541536
<MenuItemWithTopDescription
542537
shouldShowRightIcon={!props.isReadOnly}
543538
title={props.iouTag}
544-
description={tagListName || translate('common.tag')}
539+
description={policyTagListName}
545540
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, props.reportID))}
546541
style={[styles.moneyRequestMenuItem, styles.mb2]}
547542
disabled={didConfirm || props.isReadOnly}

src/components/ReportActionItem/MoneyRequestView.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as ReportUtils from '../../libs/ReportUtils';
1717
import * as OptionsListUtils from '../../libs/OptionsListUtils';
1818
import * as ReportActionsUtils from '../../libs/ReportActionsUtils';
1919
import * as StyleUtils from '../../styles/StyleUtils';
20+
import * as PolicyUtils from '../../libs/PolicyUtils';
2021
import CONST from '../../CONST';
2122
import * as Expensicons from '../Icon/Expensicons';
2223
import iouReportPropTypes from '../../pages/iouReportPropTypes';
@@ -32,6 +33,7 @@ import * as TransactionUtils from '../../libs/TransactionUtils';
3233
import OfflineWithFeedback from '../OfflineWithFeedback';
3334
import categoryPropTypes from '../categoryPropTypes';
3435
import SpacerView from '../SpacerView';
36+
import tagPropTypes from '../tagPropTypes';
3537

3638
const propTypes = {
3739
/** The report currently being looked at */
@@ -53,6 +55,9 @@ const propTypes = {
5355
/** The transaction associated with the transactionThread */
5456
transaction: transactionPropTypes,
5557

58+
/** Collection of tags attached to a policy */
59+
policyTags: tagPropTypes,
60+
5661
...withCurrentUserPersonalDetailsPropTypes,
5762
};
5863

@@ -65,9 +70,10 @@ const defaultProps = {
6570
currency: CONST.CURRENCY.USD,
6671
comment: {comment: ''},
6772
},
73+
policyTags: {},
6874
};
6975

70-
function MoneyRequestView({betas, report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) {
76+
function MoneyRequestView({report, betas, parentReport, policyCategories, shouldShowHorizontalRule, transaction, policyTags}) {
7177
const {isSmallScreenWidth} = useWindowDimensions();
7278
const {translate} = useLocalize();
7379

@@ -80,6 +86,7 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should
8086
comment: transactionDescription,
8187
merchant: transactionMerchant,
8288
category: transactionCategory,
89+
tag: transactionTag,
8390
} = ReportUtils.getTransactionDetails(transaction);
8491
const isEmptyMerchant =
8592
transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
@@ -89,8 +96,14 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should
8996
const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction);
9097
// A flag for verifying that the current report is a sub-report of a workspace chat
9198
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
92-
// A flag for showing categories
99+
100+
// Fetches only the first tag, for now
101+
const policyTag = PolicyUtils.getTag(policyTags);
102+
const policyTagsList = lodashGet(policyTag, 'tags', {});
103+
104+
// Flags for showing categories and tags
93105
const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories)));
106+
const shouldShowTag = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList)));
94107

95108
let description = `${translate('iou.amount')}${translate('iou.cash')}`;
96109
if (isSettled) {
@@ -200,6 +213,18 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should
200213
/>
201214
</OfflineWithFeedback>
202215
)}
216+
{shouldShowTag && (
217+
<OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.tag') || lodashGet(transaction, 'pendingAction')}>
218+
<MenuItemWithTopDescription
219+
description={lodashGet(policyTag, 'name', translate('common.tag'))}
220+
title={transactionTag}
221+
interactive={canEdit}
222+
shouldShowRightIcon={canEdit}
223+
titleStyle={styles.flex1}
224+
onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))}
225+
/>
226+
</OfflineWithFeedback>
227+
)}
203228
<SpacerView
204229
shouldShow={shouldShowHorizontalRule}
205230
style={[shouldShowHorizontalRule ? styles.reportHorizontalRule : {}]}
@@ -237,5 +262,8 @@ export default compose(
237262
return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
238263
},
239264
},
265+
policyTags: {
266+
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`,
267+
},
240268
}),
241269
)(MoneyRequestView);

src/components/TagPicker/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ONYXKEYS from '../../ONYXKEYS';
77
import styles from '../../styles/styles';
88
import useLocalize from '../../hooks/useLocalize';
99
import * as OptionsListUtils from '../../libs/OptionsListUtils';
10+
import * as PolicyUtils from '../../libs/PolicyUtils';
1011
import OptionsSelector from '../OptionsSelector';
1112
import {propTypes, defaultProps} from './tagPickerPropTypes';
1213

@@ -15,7 +16,7 @@ function TagPicker({selectedTag, tag, policyTags, policyRecentlyUsedTags, onSubm
1516
const [searchValue, setSearchValue] = useState('');
1617

1718
const policyRecentlyUsedTagsList = lodashGet(policyRecentlyUsedTags, tag, []);
18-
const policyTagList = lodashGet(policyTags, [tag, 'tags'], {});
19+
const policyTagList = PolicyUtils.getTagList(policyTags, tag);
1920
const policyTagsCount = _.size(_.filter(policyTagList, (policyTag) => policyTag.enabled));
2021
const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD;
2122

src/components/TagPicker/tagPickerPropTypes.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ const propTypes = {
1616

1717
/* Onyx Props */
1818
/** Collection of tags attached to a policy */
19-
policyTags: PropTypes.objectOf(
20-
PropTypes.shape({
21-
name: PropTypes.string,
22-
tags: PropTypes.objectOf(tagPropTypes),
23-
}),
24-
),
19+
policyTags: tagPropTypes,
2520

2621
/** List of recently used tags */
2722
policyRecentlyUsedTags: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),

src/components/tagPropTypes.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import PropTypes from 'prop-types';
22

3-
export default PropTypes.shape({
3+
const tagListPropTypes = PropTypes.shape({
44
/** Name of a tag */
55
name: PropTypes.string.isRequired,
66

@@ -10,3 +10,11 @@ export default PropTypes.shape({
1010
/** "General Ledger code" that corresponds to this tag in an accounting system. Similar to an ID. */
1111
'GL Code': PropTypes.string,
1212
});
13+
14+
export default PropTypes.objectOf(
15+
PropTypes.shape({
16+
name: PropTypes.string,
17+
required: PropTypes.bool,
18+
tags: PropTypes.objectOf(tagListPropTypes),
19+
}),
20+
);

src/libs/PolicyUtils.js

+53
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,56 @@ function getIneligibleInvitees(policyMembers, personalDetails) {
203203
return memberEmailsToExclude;
204204
}
205205

206+
/**
207+
* Gets the tag from policy tags, defaults to the first if no key is provided.
208+
*
209+
* @param {Object} policyTags
210+
* @param {String} [tagKey]
211+
* @returns {Object}
212+
*/
213+
function getTag(policyTags, tagKey) {
214+
if (_.isEmpty(policyTags)) {
215+
return {};
216+
}
217+
218+
const policyTagKey = tagKey || _.first(_.keys(policyTags));
219+
220+
return lodashGet(policyTags, policyTagKey, {});
221+
}
222+
223+
/**
224+
* Gets the first tag name from policy tags.
225+
*
226+
* @param {Object} policyTags
227+
* @returns {String}
228+
*/
229+
function getTagListName(policyTags) {
230+
if (_.isEmpty(policyTags)) {
231+
return '';
232+
}
233+
234+
const policyTagKeys = _.keys(policyTags) || [];
235+
236+
return lodashGet(policyTags, [_.first(policyTagKeys), 'name'], '');
237+
}
238+
239+
/**
240+
* Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided.
241+
*
242+
* @param {Object} policyTags
243+
* @param {String} [tagKey]
244+
* @returns {String}
245+
*/
246+
function getTagList(policyTags, tagKey) {
247+
if (_.isEmpty(policyTags)) {
248+
return {};
249+
}
250+
251+
const policyTagKey = tagKey || _.first(_.keys(policyTags));
252+
253+
return lodashGet(policyTags, [policyTagKey, 'tags'], {});
254+
}
255+
206256
/**
207257
* @param {Object} policy
208258
* @returns {Boolean}
@@ -226,5 +276,8 @@ export {
226276
isPolicyAdmin,
227277
getMemberAccountIDsForWorkspace,
228278
getIneligibleInvitees,
279+
getTag,
280+
getTagListName,
281+
getTagList,
229282
isPendingDeletePolicy,
230283
};

src/libs/ReportUtils.js

+11
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,7 @@ function getTransactionDetails(transaction) {
13411341
comment: TransactionUtils.getDescription(transaction),
13421342
merchant: TransactionUtils.getMerchant(transaction),
13431343
category: TransactionUtils.getCategory(transaction),
1344+
tag: TransactionUtils.getTag(transaction),
13441345
};
13451346
}
13461347

@@ -1592,6 +1593,11 @@ function getModifiedExpenseMessage(reportAction) {
15921593
if (hasModifiedCategory) {
15931594
return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.category, reportActionOriginalMessage.oldCategory, Localize.translateLocal('common.category'), true);
15941595
}
1596+
1597+
const hasModifiedTag = _.has(reportActionOriginalMessage, 'oldTag') && _.has(reportActionOriginalMessage, 'tag');
1598+
if (hasModifiedTag) {
1599+
return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.tag, reportActionOriginalMessage.oldTag, Localize.translateLocal('common.tag'), true);
1600+
}
15951601
}
15961602

15971603
/**
@@ -1637,6 +1643,11 @@ function getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, i
16371643
originalMessage.category = transactionChanges.category;
16381644
}
16391645

1646+
if (_.has(transactionChanges, 'tag')) {
1647+
originalMessage.oldTag = TransactionUtils.getTag(oldTransaction);
1648+
originalMessage.tag = transactionChanges.tag;
1649+
}
1650+
16401651
return originalMessage;
16411652
}
16421653

src/libs/TransactionUtils.js

+16
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep
151151
updatedTransaction.category = transactionChanges.category;
152152
}
153153

154+
if (_.has(transactionChanges, 'tag')) {
155+
updatedTransaction.tag = transactionChanges.tag;
156+
}
157+
154158
if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) {
155159
updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN;
156160
}
@@ -162,6 +166,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep
162166
...(_.has(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
163167
...(_.has(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
164168
...(_.has(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
169+
...(_.has(transactionChanges, 'tag') && {tag: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
165170
};
166171

167172
return updatedTransaction;
@@ -253,6 +258,16 @@ function getCategory(transaction) {
253258
return lodashGet(transaction, 'category', '');
254259
}
255260

261+
/**
262+
* Return the tag from the transaction. This "tag" field has no "modified" complement.
263+
*
264+
* @param {Object} transaction
265+
* @return {String}
266+
*/
267+
function getTag(transaction) {
268+
return lodashGet(transaction, 'tag', '');
269+
}
270+
256271
/**
257272
* Return the created field from the transaction, return the modifiedCreated if present.
258273
*
@@ -399,6 +414,7 @@ export {
399414
getMerchant,
400415
getCreated,
401416
getCategory,
417+
getTag,
402418
getLinkedTransaction,
403419
getAllReportTransactions,
404420
hasReceipt,

src/libs/actions/IOU.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,17 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
11361136
updatedChatReport.lastMessageHtml = messageText;
11371137
}
11381138

1139+
const optimisticPolicyRecentlyUsedTags = {};
1140+
if (_.has(transactionChanges, 'tag')) {
1141+
const tagListName = transactionChanges.tagListName;
1142+
const recentlyUsedPolicyTags = allRecentlyUsedTags[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`];
1143+
1144+
if (recentlyUsedPolicyTags) {
1145+
const uniquePolicyRecentlyUsedTags = _.filter(recentlyUsedPolicyTags[tagListName], (recentlyUsedPolicyTag) => recentlyUsedPolicyTag !== transactionChanges.tag);
1146+
optimisticPolicyRecentlyUsedTags[tagListName] = [transactionChanges.tag, ...uniquePolicyRecentlyUsedTags];
1147+
}
1148+
}
1149+
11391150
// STEP 4: Compose the optimistic data
11401151
const currentTime = DateUtils.getDBTime();
11411152
const optimisticData = [
@@ -1171,6 +1182,14 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
11711182
},
11721183
];
11731184

1185+
if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) {
1186+
optimisticData.push({
1187+
onyxMethod: Onyx.METHOD.MERGE,
1188+
key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`,
1189+
value: optimisticPolicyRecentlyUsedTags,
1190+
});
1191+
}
1192+
11741193
const successData = [
11751194
{
11761195
onyxMethod: Onyx.METHOD.MERGE,
@@ -1190,6 +1209,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
11901209
currency: null,
11911210
merchant: null,
11921211
category: null,
1212+
tag: null,
11931213
},
11941214
},
11951215
},
@@ -1236,7 +1256,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
12361256
];
12371257

12381258
// STEP 6: Call the API endpoint
1239-
const {created, amount, currency, comment, merchant, category} = ReportUtils.getTransactionDetails(updatedTransaction);
1259+
const {created, amount, currency, comment, merchant, category, tag} = ReportUtils.getTransactionDetails(updatedTransaction);
12401260
API.write(
12411261
'EditMoneyRequest',
12421262
{
@@ -1248,6 +1268,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
12481268
comment,
12491269
merchant,
12501270
category,
1271+
tag,
12511272
},
12521273
{optimisticData, successData, failureData},
12531274
);

0 commit comments

Comments
 (0)