Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#26793: Edit a tag in a money request #27950

Merged
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,7 @@ const CONST = {
MERCHANT: 'merchant',
CATEGORY: 'category',
RECEIPT: 'receipt',
TAG: 'tag',
},
FOOTER: {
EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`,
Expand Down
19 changes: 7 additions & 12 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import transactionPropTypes from './transactionPropTypes';
import DistanceRequestUtils from '../libs/DistanceRequestUtils';
import * as IOU from '../libs/actions/IOU';
import * as TransactionUtils from '../libs/TransactionUtils';
import * as PolicyUtils from '../libs/PolicyUtils';

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

/** Collection of tags attached to a policy */
policyTags: PropTypes.objectOf(
PropTypes.shape({
name: PropTypes.string,
required: PropTypes.bool,
tags: PropTypes.objectOf(tagPropTypes),
}),
),
policyTags: tagPropTypes,
};

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

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

// A flag for showing the billable field
const shouldShowBillable = canUseTags && !lodashGet(props.policy, 'disabledFields.defaultBillable', true);
Expand Down Expand Up @@ -541,7 +536,7 @@ function MoneyRequestConfirmationList(props) {
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly}
title={props.iouTag}
description={tagListName || translate('common.tag')}
description={policyTagListName}
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, props.reportID))}
style={[styles.moneyRequestMenuItem, styles.mb2]}
disabled={didConfirm || props.isReadOnly}
Expand Down
32 changes: 30 additions & 2 deletions src/components/ReportActionItem/MoneyRequestView.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as ReportUtils from '../../libs/ReportUtils';
import * as OptionsListUtils from '../../libs/OptionsListUtils';
import * as ReportActionsUtils from '../../libs/ReportActionsUtils';
import * as StyleUtils from '../../styles/StyleUtils';
import * as PolicyUtils from '../../libs/PolicyUtils';
import CONST from '../../CONST';
import * as Expensicons from '../Icon/Expensicons';
import iouReportPropTypes from '../../pages/iouReportPropTypes';
Expand All @@ -32,6 +33,7 @@ import * as TransactionUtils from '../../libs/TransactionUtils';
import OfflineWithFeedback from '../OfflineWithFeedback';
import categoryPropTypes from '../categoryPropTypes';
import SpacerView from '../SpacerView';
import tagPropTypes from '../tagPropTypes';

const propTypes = {
/** The report currently being looked at */
Expand All @@ -53,6 +55,9 @@ const propTypes = {
/** The transaction associated with the transactionThread */
transaction: transactionPropTypes,

/** Collection of tags attached to a policy */
policyTags: tagPropTypes,

...withCurrentUserPersonalDetailsPropTypes,
};

Expand All @@ -65,9 +70,10 @@ const defaultProps = {
currency: CONST.CURRENCY.USD,
comment: {comment: ''},
},
policyTags: {},
};

function MoneyRequestView({betas, report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) {
function MoneyRequestView({report, betas, parentReport, policyCategories, shouldShowHorizontalRule, transaction, policyTags}) {
const {isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();

Expand All @@ -80,6 +86,7 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should
comment: transactionDescription,
merchant: transactionMerchant,
category: transactionCategory,
tag: transactionTag,
} = ReportUtils.getTransactionDetails(transaction);
const isEmptyMerchant =
transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
Expand All @@ -89,8 +96,14 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should
const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction);
// A flag for verifying that the current report is a sub-report of a workspace chat
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
// A flag for showing categories

// Fetches only the first tag, for now
const policyTag = PolicyUtils.getTag(policyTags);
const policyTagsList = lodashGet(policyTag, 'tags', {});

// Flags for showing categories and tags
const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories)));
const shouldShowTag = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList)));

let description = `${translate('iou.amount')} • ${translate('iou.cash')}`;
if (isSettled) {
Expand Down Expand Up @@ -200,6 +213,18 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should
/>
</OfflineWithFeedback>
)}
{shouldShowTag && (
<OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.tag') || lodashGet(transaction, 'pendingAction')}>
<MenuItemWithTopDescription
description={lodashGet(policyTag, 'name', translate('common.tag'))}
title={transactionTag}
interactive={canEdit}
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))}
/>
</OfflineWithFeedback>
)}
<SpacerView
shouldShow={shouldShowHorizontalRule}
style={[shouldShowHorizontalRule ? styles.reportHorizontalRule : {}]}
Expand Down Expand Up @@ -237,5 +262,8 @@ export default compose(
return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
},
},
policyTags: {
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`,
},
}),
)(MoneyRequestView);
3 changes: 2 additions & 1 deletion src/components/TagPicker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ONYXKEYS from '../../ONYXKEYS';
import styles from '../../styles/styles';
import useLocalize from '../../hooks/useLocalize';
import * as OptionsListUtils from '../../libs/OptionsListUtils';
import * as PolicyUtils from '../../libs/PolicyUtils';
import OptionsSelector from '../OptionsSelector';
import {propTypes, defaultProps} from './tagPickerPropTypes';

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

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

Expand Down
7 changes: 1 addition & 6 deletions src/components/TagPicker/tagPickerPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ const propTypes = {

/* Onyx Props */
/** Collection of tags attached to a policy */
policyTags: PropTypes.objectOf(
PropTypes.shape({
name: PropTypes.string,
tags: PropTypes.objectOf(tagPropTypes),
}),
),
policyTags: tagPropTypes,

/** List of recently used tags */
policyRecentlyUsedTags: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
Expand Down
10 changes: 9 additions & 1 deletion src/components/tagPropTypes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';

export default PropTypes.shape({
const tagListPropTypes = PropTypes.shape({
/** Name of a tag */
name: PropTypes.string.isRequired,

Expand All @@ -10,3 +10,11 @@ export default PropTypes.shape({
/** "General Ledger code" that corresponds to this tag in an accounting system. Similar to an ID. */
'GL Code': PropTypes.string,
});

export default PropTypes.objectOf(
PropTypes.shape({
name: PropTypes.string,
required: PropTypes.bool,
tags: PropTypes.objectOf(tagListPropTypes),
}),
);
53 changes: 53 additions & 0 deletions src/libs/PolicyUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,56 @@ function getIneligibleInvitees(policyMembers, personalDetails) {
return memberEmailsToExclude;
}

/**
* Gets the tag from policy tags, defaults to the first if no key is provided.
*
* @param {Object} policyTags
* @param {String} [tagKey]
* @returns {Object}
*/
function getTag(policyTags, tagKey) {
if (_.isEmpty(policyTags)) {
return {};
}

const policyTagKey = tagKey || _.first(_.keys(policyTags));

return lodashGet(policyTags, policyTagKey, {});
}

/**
* Gets the first tag name from policy tags.
*
* @param {Object} policyTags
* @returns {String}
*/
function getTagListName(policyTags) {
if (_.isEmpty(policyTags)) {
return '';
}

const policyTagKeys = _.keys(policyTags) || [];

return lodashGet(policyTags, [_.first(policyTagKeys), 'name'], '');
}

/**
* Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided.
*
* @param {Object} policyTags
* @param {String} [tagKey]
* @returns {String}
*/
function getTagList(policyTags, tagKey) {
if (_.isEmpty(policyTags)) {
return {};
}

const policyTagKey = tagKey || _.first(_.keys(policyTags));

return lodashGet(policyTags, [policyTagKey, 'tags'], {});
}

/**
* @param {Object} policy
* @returns {Boolean}
Expand All @@ -226,5 +276,8 @@ export {
isPolicyAdmin,
getMemberAccountIDsForWorkspace,
getIneligibleInvitees,
getTag,
getTagListName,
getTagList,
isPendingDeletePolicy,
};
11 changes: 11 additions & 0 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,7 @@ function getTransactionDetails(transaction) {
comment: TransactionUtils.getDescription(transaction),
merchant: TransactionUtils.getMerchant(transaction),
category: TransactionUtils.getCategory(transaction),
tag: TransactionUtils.getTag(transaction),
};
}

Expand Down Expand Up @@ -1592,6 +1593,11 @@ function getModifiedExpenseMessage(reportAction) {
if (hasModifiedCategory) {
return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.category, reportActionOriginalMessage.oldCategory, Localize.translateLocal('common.category'), true);
}

const hasModifiedTag = _.has(reportActionOriginalMessage, 'oldTag') && _.has(reportActionOriginalMessage, 'tag');
if (hasModifiedTag) {
return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.tag, reportActionOriginalMessage.oldTag, Localize.translateLocal('common.tag'), true);
}
}

/**
Expand Down Expand Up @@ -1637,6 +1643,11 @@ function getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, i
originalMessage.category = transactionChanges.category;
}

if (_.has(transactionChanges, 'tag')) {
originalMessage.oldTag = TransactionUtils.getTag(oldTransaction);
originalMessage.tag = transactionChanges.tag;
}

return originalMessage;
}

Expand Down
16 changes: 16 additions & 0 deletions src/libs/TransactionUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep
updatedTransaction.category = transactionChanges.category;
}

if (_.has(transactionChanges, 'tag')) {
updatedTransaction.tag = transactionChanges.tag;
}

if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) {
updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN;
}
Expand All @@ -162,6 +166,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep
...(_.has(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(_.has(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(_.has(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(_.has(transactionChanges, 'tag') && {tag: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
};

return updatedTransaction;
Expand Down Expand Up @@ -253,6 +258,16 @@ function getCategory(transaction) {
return lodashGet(transaction, 'category', '');
}

/**
* Return the tag from the transaction. This "tag" field has no "modified" complement.
*
* @param {Object} transaction
* @return {String}
*/
function getTag(transaction) {
return lodashGet(transaction, 'tag', '');
}

/**
* Return the created field from the transaction, return the modifiedCreated if present.
*
Expand Down Expand Up @@ -399,6 +414,7 @@ export {
getMerchant,
getCreated,
getCategory,
getTag,
getLinkedTransaction,
getAllReportTransactions,
hasReceipt,
Expand Down
23 changes: 22 additions & 1 deletion src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,17 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
updatedChatReport.lastMessageHtml = messageText;
}

const optimisticPolicyRecentlyUsedTags = {};
if (_.has(transactionChanges, 'tag')) {
const tagListName = transactionChanges.tagListName;
const recentlyUsedPolicyTags = allRecentlyUsedTags[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`];

if (recentlyUsedPolicyTags) {
const uniquePolicyRecentlyUsedTags = _.filter(recentlyUsedPolicyTags[tagListName], (recentlyUsedPolicyTag) => recentlyUsedPolicyTag !== transactionChanges.tag);
optimisticPolicyRecentlyUsedTags[tagListName] = [transactionChanges.tag, ...uniquePolicyRecentlyUsedTags];
}
}

// STEP 4: Compose the optimistic data
const currentTime = DateUtils.getDBTime();
const optimisticData = [
Expand Down Expand Up @@ -1171,6 +1182,14 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
},
];

if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`,
value: optimisticPolicyRecentlyUsedTags,
});
}

const successData = [
{
onyxMethod: Onyx.METHOD.MERGE,
Expand All @@ -1190,6 +1209,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
currency: null,
merchant: null,
category: null,
tag: null,
},
},
},
Expand Down Expand Up @@ -1236,7 +1256,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
];

// STEP 6: Call the API endpoint
const {created, amount, currency, comment, merchant, category} = ReportUtils.getTransactionDetails(updatedTransaction);
const {created, amount, currency, comment, merchant, category, tag} = ReportUtils.getTransactionDetails(updatedTransaction);
API.write(
'EditMoneyRequest',
{
Expand All @@ -1248,6 +1268,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
comment,
merchant,
category,
tag,
},
{optimisticData, successData, failureData},
);
Expand Down
Loading
Loading