-
Notifications
You must be signed in to change notification settings - Fork 3.9k
feat: Add Tax, Billable, and Reimbursable selectors in the Split details page #94434
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -258,6 +258,9 @@ function updateSplitTransactions({ | |
| }, | ||
| reimbursable: split?.reimbursable, | ||
| billable: split?.billable, | ||
| taxCode: split?.taxCode, | ||
| taxAmount: split?.taxAmount, | ||
| taxValue: split?.taxValue, | ||
|
Comment on lines
+261
to
+263
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These per-split tax fields are now sent to the API, but the optimistic transaction built later in this same function still uses Useful? React with 👍 / 👎. |
||
| quantity: split.customUnit?.quantity ?? undefined, | ||
| customUnitRateID: split.customUnit?.customUnitRateID, | ||
| odometerStart: split.odometerStart, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| import ScreenWrapper from '@components/ScreenWrapper'; | ||
| import ScrollView from '@components/ScrollView'; | ||
| import {useSearchResultsContext} from '@components/Search/SearchContext'; | ||
| import Switch from '@components/Switch'; | ||
| import Text from '@components/Text'; | ||
| import useAllTransactions from '@hooks/useAllTransactions'; | ||
| import {useCurrencyListActions} from '@hooks/useCurrencyList'; | ||
| import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; | ||
|
|
@@ -24,24 +26,25 @@ | |
| import useSplitEffectivePolicy from '@hooks/useSplitEffectivePolicy'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import type {ViolationField} from '@hooks/useViolations'; | ||
| import {initDraftSplitExpenseDataForEdit, removeSplitExpenseField, updateSplitExpenseField} from '@libs/actions/IOU/SplitExpenseItems'; | ||
| import {initDraftSplitExpenseDataForEdit, removeSplitExpenseField, updateSplitExpenseDraftField, updateSplitExpenseField} from '@libs/actions/IOU/SplitExpenseItems'; | ||
| import {openPolicyCategoriesPage} from '@libs/actions/Policy/Category'; | ||
| import {openPolicyTagsPage} from '@libs/actions/Policy/Tag'; | ||
| import {getDecodedLeafCategoryName, isCategoryDescriptionRequired, isCategoryMissing} from '@libs/CategoryUtils'; | ||
| import DistanceRequestUtils from '@libs/DistanceRequestUtils'; | ||
| import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; | ||
| import {isBillableEnabledOnPolicy} from '@libs/MoneyRequestReportUtils'; | ||
| import Navigation from '@libs/Navigation/Navigation'; | ||
| import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; | ||
| import type {SplitExpenseParamList} from '@libs/Navigation/types'; | ||
| import {hasEnabledOptions} from '@libs/OptionsListUtils'; | ||
| import Parser from '@libs/Parser'; | ||
| import {getDistanceRateCustomUnitRate, getTagLists, hasAnyPaidPolicy, isGroupPolicyByType} from '@libs/PolicyUtils'; | ||
| import {getDistanceRateCustomUnitRate, getTagLists, hasAnyPaidPolicy, isGroupPolicyByType, isTaxTrackingEnabled} from '@libs/PolicyUtils'; | ||
| import {getReportName} from '@libs/ReportNameUtils'; | ||
| import {isSplitAction} from '@libs/ReportSecondaryActionUtils'; | ||
| import type {TransactionDetails} from '@libs/ReportUtils'; | ||
| import {getParsedComment, getReportOrDraftReport, getTransactionDetails, isSelfDM} from '@libs/ReportUtils'; | ||
| import {getTagVisibility, hasEnabledTags} from '@libs/TagsOptionsListUtils'; | ||
| import {getDistanceInMeters, getRateID, getTag, getTagForDisplay, isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest} from '@libs/TransactionUtils'; | ||
| import {getDistanceInMeters, getRateID, getTag, getTagForDisplay, getTaxName, isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest} from '@libs/TransactionUtils'; | ||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import ROUTES from '@src/ROUTES'; | ||
|
|
@@ -63,7 +66,7 @@ | |
| const [originalTransactionDraft] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${splitExpenseDraftTransaction?.comment?.originalTransactionID}`, undefined, [ | ||
| splitExpenseDraftTransaction?.comment?.originalTransactionID, | ||
| ]); | ||
|
|
||
| console.log('>>>>>>>>>>>>>>>>>>', splitExpenseDraftTransaction); | ||
|
Check failure on line 69 in src/pages/iou/SplitExpenseEditPage.tsx
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This Useful? React with 👍 / 👎. |
||
| const splitExpenseDraftTransactionDetails = useMemo<Partial<TransactionDetails>>(() => getTransactionDetails(splitExpenseDraftTransaction) ?? {}, [splitExpenseDraftTransaction]); | ||
| const allTransactions = useAllTransactions(); | ||
|
|
||
|
|
@@ -164,6 +167,13 @@ | |
|
|
||
| const previousTagsVisibility = usePrevious(tagVisibility.map((v) => v.shouldShow)) ?? []; | ||
|
|
||
| const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat || isExpenseUnreported, effectivePolicy, isDistanceRequest(splitExpenseDraftTransaction), false, false); | ||
| const taxRatesDescription = effectivePolicy?.taxRates?.name; | ||
| const taxRateTitle = getTaxName(effectivePolicy, splitExpenseDraftTransaction); | ||
|
|
||
| const shouldShowBillable = (isPolicyExpenseChat || isExpenseUnreported) && (!!splitExpenseDraftTransactionDetails?.billable || isBillableEnabledOnPolicy(effectivePolicy)); | ||
| const shouldShowReimbursable = (isPolicyExpenseChat || (isExpenseUnreported && !!effectivePolicy)) && effectivePolicy?.disabledFields?.reimbursable !== true; | ||
|
|
||
| const isDistance = isDistanceRequest(splitExpenseDraftTransaction); | ||
| const isManualDistance = isManualDistanceRequest(splitExpenseDraftTransaction); | ||
| const isOdometerDistance = isOdometerDistanceRequest(splitExpenseDraftTransaction); | ||
|
|
@@ -424,6 +434,74 @@ | |
| style={[styles.moneyRequestMenuItem]} | ||
| titleStyle={styles.flex1} | ||
| /> | ||
| {shouldShowTax && ( | ||
| <MenuItemWithTopDescription | ||
| shouldShowRightIcon | ||
| key={translate('common.tax')} | ||
| description={taxRatesDescription ?? translate('common.tax')} | ||
| title={taxRateTitle} | ||
| numberOfLinesTitle={2} | ||
| onPress={() => { | ||
| Navigation.navigate( | ||
| ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute( | ||
| CONST.IOU.ACTION.EDIT, | ||
| CONST.IOU.TYPE.SPLIT, | ||
| CONST.IOU.OPTIMISTIC_TRANSACTION_ID, | ||
| reportID, | ||
| Navigation.getActiveRoute(), | ||
| ), | ||
| ); | ||
| }} | ||
| style={[styles.moneyRequestMenuItem]} | ||
| titleStyle={styles.flex1} | ||
| /> | ||
| )} | ||
| {shouldShowTax && ( | ||
| <MenuItemWithTopDescription | ||
| shouldShowRightIcon | ||
| key={translate('iou.taxAmount')} | ||
| description={translate('iou.taxAmount')} | ||
| title={convertToDisplayString(Math.abs(splitExpenseDraftTransaction?.taxAmount ?? 0), currency)} | ||
| numberOfLinesTitle={2} | ||
| onPress={() => { | ||
| Navigation.navigate( | ||
| ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute( | ||
| CONST.IOU.ACTION.EDIT, | ||
| CONST.IOU.TYPE.SPLIT, | ||
| CONST.IOU.OPTIMISTIC_TRANSACTION_ID, | ||
| reportID, | ||
| Navigation.getActiveRoute(), | ||
| ), | ||
| ); | ||
| }} | ||
| style={[styles.moneyRequestMenuItem]} | ||
| titleStyle={styles.flex1} | ||
| /> | ||
| )} | ||
| {shouldShowReimbursable && ( | ||
| <View style={[styles.flexRow, styles.optionRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.mh5]}> | ||
| <Text>{translate('common.reimbursable')}</Text> | ||
| <Switch | ||
| accessibilityLabel={translate('common.reimbursable')} | ||
| isOn={splitExpenseDraftTransaction?.reimbursable ?? true} | ||
| onToggle={(value) => { | ||
| updateSplitExpenseDraftField({reimbursable: value}); | ||
| }} | ||
| /> | ||
| </View> | ||
| )} | ||
| {shouldShowBillable && ( | ||
| <View style={[styles.flexRow, styles.optionRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.mh5]}> | ||
| <Text>{translate('common.billable')}</Text> | ||
| <Switch | ||
| accessibilityLabel={translate('common.billable')} | ||
| isOn={splitExpenseDraftTransaction?.billable ?? false} | ||
| onToggle={(value) => { | ||
| updateSplitExpenseDraftField({billable: value}); | ||
| }} | ||
| /> | ||
| </View> | ||
| )} | ||
| <MenuItemWithTopDescription | ||
| key={translate('common.report')} | ||
| description={translate('common.report')} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a taxable expense is split into multiple new split expenses and the user saves without opening each tax row, every
SplitExpenseinitialized here carries the original transaction's fulltaxAmount; the newupdateSplitTransactionspayload then sends that value for each split. For example, splitting an expense with $10 tax into two lines sends $10 tax on both child transactions, corrupting tax totals unless the user manually edits each one. This should be prorated/recomputed for the split amount or omitted until explicitly set.Useful? React with 👍 / 👎.