Skip to content

Commit

Permalink
Merge pull request #32550 from teneeto/feat/31672/add-new-input-field…
Browse files Browse the repository at this point in the history
…s-for-tax-tracking

feat: add new input fields for tax tracking
  • Loading branch information
MonilBhavsar authored Jan 3, 2024
2 parents 2fe1174 + 70f3b99 commit 27a51a4
Show file tree
Hide file tree
Showing 26 changed files with 1,044 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,7 @@ const CONST = {
PARENT_CHILD_SEPARATOR: ': ',
CATEGORY_LIST_THRESHOLD: 8,
TAG_LIST_THRESHOLD: 8,
TAX_RATES_LIST_THRESHOLD: 8,
COLON: ':',
MAPBOX: {
PADDING: 50,
Expand Down
1 change: 1 addition & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ const ONYXKEYS = {
POLICY_CATEGORIES: 'policyCategories_',
POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_',
POLICY_TAGS: 'policyTags_',
POLICY_TAX_RATE: 'policyTaxRates_',
POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_',
POLICY_REPORT_FIELDS: 'policyReportFields_',
POLICY_RECENTLY_USED_REPORT_FIELDS: 'policyRecentlyUsedReportFields_',
Expand Down
10 changes: 10 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,16 @@ const ROUTES = {
getRoute: (iouType: ValueOf<typeof CONST.IOU.TYPE>, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`create/${iouType}/amount/${transactionID}/${reportID}/`, backTo),
},
MONEY_REQUEST_STEP_TAX_RATE: {
route: 'create/:iouType/taxRate/:transactionID/:reportID?',
getRoute: (iouType: ValueOf<typeof CONST.IOU.TYPE>, transactionID: string, reportID: string, backTo: string) =>
getUrlWithBackToParam(`create/${iouType}/taxRate/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_TAX_AMOUNT: {
route: 'create/:iouType/taxAmount/:transactionID/:reportID?',
getRoute: (iouType: ValueOf<typeof CONST.IOU.TYPE>, transactionID: string, reportID: string, backTo: string) =>
getUrlWithBackToParam(`create/${iouType}/taxAmount/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_CATEGORY: {
route: 'create/:iouType/category/:transactionID/:reportID/',
getRoute: (iouType: ValueOf<typeof CONST.IOU.TYPE>, transactionID: string, reportID: string, backTo = '') =>
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ const SCREENS = {
STEP_SCAN: 'Money_Request_Step_Scan',
STEP_TAG: 'Money_Request_Step_Tag',
STEP_WAYPOINT: 'Money_Request_Step_Waypoint',
STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount',
STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate',
ROOT: 'Money_Request',
AMOUNT: 'Money_Request_Amount',
PARTICIPANTS: 'Money_Request_Participants',
Expand Down
53 changes: 52 additions & 1 deletion src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import SettlementButton from './SettlementButton';
import ShowMoreButton from './ShowMoreButton';
import Switch from './Switch';
import tagPropTypes from './tagPropTypes';
import taxPropTypes from './taxPropTypes';
import Text from './Text';
import transactionPropTypes from './transactionPropTypes';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails';
Expand Down Expand Up @@ -164,6 +165,10 @@ const propTypes = {
/** Collection of tags attached to a policy */
policyTags: tagPropTypes,

/* Onyx Props */
/** Collection of tax rates attached to a policy */
policyTaxRates: taxPropTypes,

/** Holds data related to Money Request view state, rather than the underlying Money Request data. */
iou: iouPropTypes,
};
Expand Down Expand Up @@ -200,6 +205,7 @@ const defaultProps = {
shouldShowSmartScanFields: true,
isPolicyExpenseChat: false,
iou: iouDefaultProps,
policyTaxRates: {},
};

function MoneyRequestConfirmationList(props) {
Expand Down Expand Up @@ -241,6 +247,9 @@ function MoneyRequestConfirmationList(props) {
// A flag for showing the tags field
const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledOptions(_.values(policyTagList)));

// A flag for showing tax fields - tax rate and tax amount
const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled;

// A flag for showing the billable field
const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true);

Expand All @@ -252,6 +261,11 @@ function MoneyRequestConfirmationList(props) {
shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount,
props.isDistanceRequest ? currency : props.iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode);

const defaultTaxKey = props.policyTaxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${props.policyTaxRates.taxes[defaultTaxKey].name} (${props.policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
const taxRateTitle = (props.transaction.taxRate && props.transaction.taxRate.text) || defaultTaxName;

const isFocused = useIsFocused();
const [formError, setFormError] = useState('');
Expand Down Expand Up @@ -741,6 +755,40 @@ function MoneyRequestConfirmationList(props) {
/>
)}

{shouldShowTax && (
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly}
title={taxRateTitle}
description={props.policyTaxRates.name}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(
ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(props.iouType, props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams()),
)
}
disabled={didConfirm}
interactive={!props.isReadOnly}
/>
)}

{shouldShowTax && (
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly}
title={formattedTaxAmount}
description={props.policyTaxRates.name}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(
ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(props.iouType, props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams()),
)
}
disabled={didConfirm}
interactive={!props.isReadOnly}
/>
)}

{shouldShowBillable && (
<View style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.ml5, styles.mr8, styles.optionRow]}>
<Text color={!props.iouIsBillable ? theme.textSupporting : undefined}>{translate('common.billable')}</Text>
Expand Down Expand Up @@ -777,12 +825,15 @@ export default compose(
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
selector: DistanceRequestUtils.getDefaultMileageRate,
},
draftTransaction: {
splitTransactionDraft: {
key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`,
},
policy: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
},
policyTaxRates: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`,
},
iou: {
key: ONYXKEYS.IOU,
},
Expand Down
47 changes: 47 additions & 0 deletions src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import OptionsSelector from './OptionsSelector';
import SettlementButton from './SettlementButton';
import Switch from './Switch';
import tagPropTypes from './tagPropTypes';
import taxPropTypes from './taxPropTypes';
import Text from './Text';
import transactionPropTypes from './transactionPropTypes';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails';
Expand Down Expand Up @@ -160,6 +161,10 @@ const propTypes = {
/** Collection of tags attached to a policy */
policyTags: tagPropTypes,

/* Onyx Props */
/** Collection of tax rates attached to a policy */
policyTaxRates: taxPropTypes,

/** Transaction that represents the money request */
transaction: transactionPropTypes,
};
Expand Down Expand Up @@ -194,6 +199,7 @@ const defaultProps = {
isDistanceRequest: false,
shouldShowSmartScanFields: true,
isPolicyExpenseChat: false,
policyTaxRates: {},
};

function MoneyTemporaryForRefactorRequestConfirmationList({
Expand Down Expand Up @@ -235,6 +241,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
session: {accountID},
shouldShowSmartScanFields,
transaction,
policyTaxRates,
}) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -269,6 +276,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
// A flag for showing the tags field
const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(policyTagList));

// A flag for showing tax rate
const shouldShowTax = isPolicyExpenseChat && policy.isTaxTrackingEnabled;

// A flag for showing the billable field
const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true);

Expand All @@ -280,6 +290,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount,
isDistanceRequest ? currency : iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode);

const defaultTaxKey = policyTaxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
const taxRateTitle = (transaction.taxRate && transaction.taxRate.text) || defaultTaxName;

const isFocused = useIsFocused();
const [formError, setFormError] = useState('');
Expand Down Expand Up @@ -796,6 +811,35 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
rightLabel={canUseViolations && Boolean(policy.requiresTag) ? translate('common.required') : ''}
/>
)}
{shouldShowTax && (
<MenuItemWithTopDescription
shouldShowRightIcon={!isReadOnly}
title={taxRateTitle}
description={policyTaxRates.name}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))
}
disabled={didConfirm}
interactive={!isReadOnly}
/>
)}

{shouldShowTax && (
<MenuItemWithTopDescription
shouldShowRightIcon={!isReadOnly}
title={formattedTaxAmount}
description={policyTaxRates.name}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))
}
disabled={didConfirm}
interactive={!isReadOnly}
/>
)}
{shouldShowBillable && (
<View style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.ml5, styles.mr8, styles.optionRow]}>
<Text color={!iouIsBillable ? theme.textSupporting : undefined}>{translate('common.billable')}</Text>
Expand Down Expand Up @@ -835,5 +879,8 @@ export default compose(
policy: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
},
policyTaxRates: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`,
},
}),
)(MoneyTemporaryForRefactorRequestConfirmationList);
90 changes: 90 additions & 0 deletions src/components/TaxPicker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import lodashGet from 'lodash/get';
import React, {useMemo, useState} from 'react';
import _ from 'underscore';
import OptionsSelector from '@components/OptionsSelector';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import {defaultProps, propTypes} from './taxPickerPropTypes';

function TaxPicker({selectedTaxRate, policyTaxRates, insets, onSubmit}) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const [searchValue, setSearchValue] = useState('');

const policyTaxRatesCount = TransactionUtils.getEnabledTaxRateCount(policyTaxRates.taxes);
const isTaxRatesCountBelowThreshold = policyTaxRatesCount < CONST.TAX_RATES_LIST_THRESHOLD;

const shouldShowTextInput = !isTaxRatesCountBelowThreshold;

const selectedOptions = useMemo(() => {
if (!selectedTaxRate) {
return [];
}

return [
{
name: selectedTaxRate,
enabled: true,
accountID: null,
},
];
}, [selectedTaxRate]);

const sections = useMemo(() => {
const {policyTaxRatesOptions} = OptionsListUtils.getFilteredOptions(
{},
{},
[],
searchValue,
selectedOptions,
[],
false,
false,
false,
{},
[],
false,
{},
[],
false,
false,
true,
policyTaxRates,
);
return policyTaxRatesOptions;
}, [policyTaxRates, searchValue, selectedOptions]);

const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (taxRate) => taxRate.searchText === selectedTaxRate)[0], 'keyForList');

return (
<OptionsSelector
contentContainerStyles={[{paddingBottom: StyleUtils.getSafeAreaMargins(insets).marginBottom}]}
optionHoveredStyle={styles.hoveredComponentBG}
sectionHeaderStyle={styles.mt5}
sections={sections}
selectedOptions={selectedOptions}
value={searchValue}
// Focus the first option when searching
focusedIndex={0}
initiallyFocusedOptionKey={selectedOptionKey}
textInputLabel={translate('common.search')}
boldStyle
highlightSelectedOptions
isRowMultilineSupported
shouldShowTextInput={shouldShowTextInput}
onChangeText={setSearchValue}
onSelectRow={onSubmit}
/>
);
}

TaxPicker.displayName = 'TaxPicker';
TaxPicker.propTypes = propTypes;
TaxPicker.defaultProps = defaultProps;

export default TaxPicker;
21 changes: 21 additions & 0 deletions src/components/TaxPicker/taxPickerPropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import PropTypes from 'prop-types';
import taxPropTypes from '@components/taxPropTypes';

const propTypes = {
/** The selected tax rate of an expense */
selectedTaxRate: PropTypes.string,

/** Callback to fire when a tax is pressed */
onSubmit: PropTypes.func.isRequired,

/* Onyx Props */
/** Collection of tax rates attached to a policy */
policyTaxRates: taxPropTypes,
};

const defaultProps = {
selectedTaxRate: '',
policyTaxRates: {},
};

export {propTypes, defaultProps};
29 changes: 29 additions & 0 deletions src/components/taxPropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import PropTypes from 'prop-types';

const taxPropTypes = PropTypes.shape({
/** Name of a tax */
name: PropTypes.string,

/** The value of a tax */
value: PropTypes.string,

/** Whether the tax is disabled */
isDisabled: PropTypes.bool,
});

export default PropTypes.shape({
/** Name of the tax */
name: PropTypes.string,

/** Default policy tax ID */
defaultExternalID: PropTypes.string,

/** Default value of taxes */
defaultValue: PropTypes.string,

/** Default foreign policy tax ID */
foreignTaxDefault: PropTypes.string,

/** List of tax names and values */
taxes: PropTypes.objectOf(taxPropTypes),
});
Loading

0 comments on commit 27a51a4

Please sign in to comment.