Skip to content

Commit

Permalink
Merge pull request #40240 from Expensify/monil-splitBillTaxUpdate
Browse files Browse the repository at this point in the history
Handle tax for split requests
  • Loading branch information
jasperhuangg authored May 24, 2024
2 parents 6b6ed03 + c671c7a commit c5c1120
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 72 deletions.
63 changes: 35 additions & 28 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,15 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps &

type MoneyRequestConfirmationListItem = Participant | ReportUtils.OptionData;

const getTaxAmount = (transaction: OnyxEntry<OnyxTypes.Transaction>, policy: OnyxEntry<OnyxTypes.Policy>) => {
/**
* Calculate and set tax amount in transaction draft
*/
const setTaxAmountInDraft = (transaction: OnyxEntry<OnyxTypes.Transaction>, policy: OnyxEntry<OnyxTypes.Policy>) => {
const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, transaction) ?? '';

const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, transaction?.taxCode ?? defaultTaxCode) ?? '';
return TransactionUtils.calculateTaxAmount(taxPercentage, transaction?.amount ?? 0);
const taxAmount = TransactionUtils.calculateTaxAmount(taxPercentage, transaction?.amount ?? 0);
const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount.toString()));
IOU.setMoneyRequestTaxAmount(transaction?.transactionID ?? '', taxAmountInSmallestCurrencyUnits, true);
};

function MoneyRequestConfirmationList({
Expand Down Expand Up @@ -244,16 +248,6 @@ function MoneyRequestConfirmationList({
const transactionID = transaction?.transactionID ?? '';
const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '';

useEffect(() => {
if (customUnitRateID || !canUseP2PDistanceRequests) {
return;
}
if (!customUnitRateID) {
const rateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? defaultMileageRate?.customUnitRateID ?? '';
IOU.setCustomUnitRateID(transactionID, rateID);
}
}, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID]);

const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD;

const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction)
Expand Down Expand Up @@ -314,9 +308,9 @@ function MoneyRequestConfirmationList({
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode);
const taxRateTitle = TransactionUtils.getTaxName(policy, transaction);

const previousTransactionAmount = usePrevious(transaction?.amount);
const previousTransactionCurrency = usePrevious(transaction?.currency);
const prevTaxAmount = usePrevious(transaction?.taxAmount);
const prevAmount = usePrevious(transaction?.amount);
const prevCurrency = usePrevious(transaction?.currency);

const isFocused = useIsFocused();
const [formError, debouncedFormError, setFormError] = useDebouncedState('');
Expand Down Expand Up @@ -345,6 +339,31 @@ function MoneyRequestConfirmationList({

const isCategoryRequired = !!policy?.requiresCategory;

useEffect(() => {
if (!shouldShowTax) {
return;
}
setTaxAmountInDraft(transaction, policy);
// eslint-disable-next-line react-hooks/exhaustive-deps -- we want to call this function when component is mounted
}, []);

useEffect(() => {
if (!shouldShowTax || prevTaxAmount !== transaction?.taxAmount || (prevAmount === transaction?.amount && prevCurrency === transaction?.currency)) {
return;
}
setTaxAmountInDraft(transaction, policy);
}, [policy, shouldShowTax, prevTaxAmount, prevAmount, prevCurrency, transaction]);

useEffect(() => {
if (customUnitRateID || !canUseP2PDistanceRequests) {
return;
}
if (!customUnitRateID) {
const rateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? defaultMileageRate?.customUnitRateID ?? '';
IOU.setCustomUnitRateID(transactionID, rateID);
}
}, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID]);

useEffect(() => {
if (shouldDisplayFieldError && hasSmartScanFailed) {
setFormError('iou.receiptScanningFailed');
Expand All @@ -369,18 +388,6 @@ function MoneyRequestConfirmationList({
IOU.setMoneyRequestAmount(transactionID, amount, currency ?? '');
}, [shouldCalculateDistanceAmount, distance, rate, unit, transactionID, currency]);

// Calculate and set tax amount in transaction draft
useEffect(() => {
const taxAmount = getTaxAmount(transaction, policy).toString();
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount));

if (transaction?.taxAmount && previousTransactionAmount === transaction?.amount && previousTransactionCurrency === transaction?.currency) {
return IOU.setMoneyRequestTaxAmount(transactionID, transaction?.taxAmount ?? 0, true);
}

IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true);
}, [policy, transaction, transactionID, previousTransactionAmount, previousTransactionCurrency]);

// If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again
if (isEditingSplitBill && didConfirm) {
setDidConfirm(false);
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/parameters/SplitBillParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type SplitBillParams = {
policyID: string | undefined;
chatType: string | undefined;
splitPayerAccountIDs: number[];
taxCode: string;
taxAmount: number;
};

export default SplitBillParams;
29 changes: 24 additions & 5 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3726,6 +3726,8 @@ function createSplitsAndOnyxData(
existingSplitChatReportID = '',
billable = false,
iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
taxCode = '',
taxAmount = 0,
): SplitsAndOnyxData {
const currentUserEmailForIOUSplit = PhoneNumber.addSMSDomainIfPhoneNumber(currentUserLogin);
const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
Expand All @@ -3747,8 +3749,8 @@ function createSplitsAndOnyxData(
undefined,
category,
tag,
undefined,
undefined,
taxCode,
taxAmount,
billable,
);

Expand Down Expand Up @@ -3903,14 +3905,16 @@ function createSplitsAndOnyxData(

// Loop through participants creating individual chats, iouReports and reportActionIDs as needed
const currentUserAmount = splitShares?.[currentUserAccountID]?.amount ?? IOUUtils.calculateAmount(participants.length, amount, currency, true);
const currentUserTaxAmount = IOUUtils.calculateAmount(participants.length, taxAmount, currency, true);

const splits: Split[] = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID, amount: currentUserAmount}];
const splits: Split[] = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID, amount: currentUserAmount, taxAmount: currentUserTaxAmount}];

const hasMultipleParticipants = participants.length > 1;
participants.forEach((participant) => {
// In a case when a participant is a workspace, even when a current user is not an owner of the workspace
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(participant);
const splitAmount = splitShares?.[participant.accountID ?? -1]?.amount ?? IOUUtils.calculateAmount(participants.length, amount, currency, false);
const splitTaxAmount = IOUUtils.calculateAmount(participants.length, taxAmount, currency, false);

// To exclude someone from a split, the amount can be 0. The scenario for this is when creating a split from a group chat, we have remove the option to deselect users to exclude them.
// We can input '0' next to someone we want to exclude.
Expand Down Expand Up @@ -3980,8 +3984,8 @@ function createSplitsAndOnyxData(
undefined,
category,
tag,
undefined,
undefined,
taxCode,
ReportUtils.isExpenseReport(oneOnOneIOUReport) ? -splitTaxAmount : splitTaxAmount,
billable,
);

Expand Down Expand Up @@ -4073,6 +4077,7 @@ function createSplitsAndOnyxData(
reportPreviewReportActionID: oneOnOneReportPreviewAction.reportActionID,
transactionThreadReportID: optimisticTransactionThread.reportID,
createdReportActionIDForThread: optimisticCreatedActionForTransactionThread.reportActionID,
taxAmount: splitTaxAmount,
};

splits.push(individualSplit);
Expand Down Expand Up @@ -4126,6 +4131,8 @@ type SplitBillActionsParams = {
existingSplitChatReportID?: string;
splitShares?: SplitShares;
splitPayerAccountIDs?: number[];
taxCode?: string;
taxAmount?: number;
};

/**
Expand All @@ -4148,6 +4155,8 @@ function splitBill({
existingSplitChatReportID = '',
splitShares = {},
splitPayerAccountIDs = [],
taxCode = '',
taxAmount = 0,
}: SplitBillActionsParams) {
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const {splitData, splits, onyxData} = createSplitsAndOnyxData(
Expand All @@ -4165,6 +4174,8 @@ function splitBill({
existingSplitChatReportID,
billable,
iouRequestType,
taxCode,
taxAmount,
);

const parameters: SplitBillParams = {
Expand All @@ -4184,6 +4195,8 @@ function splitBill({
policyID: splitData.policyID,
chatType: splitData.chatType,
splitPayerAccountIDs,
taxCode,
taxAmount,
};

API.write(WRITE_COMMANDS.SPLIT_BILL, parameters, onyxData);
Expand All @@ -4210,6 +4223,8 @@ function splitBillAndOpenReport({
iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
splitShares = {},
splitPayerAccountIDs = [],
taxCode = '',
taxAmount = 0,
}: SplitBillActionsParams) {
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const {splitData, splits, onyxData} = createSplitsAndOnyxData(
Expand All @@ -4227,6 +4242,8 @@ function splitBillAndOpenReport({
'',
billable,
iouRequestType,
taxCode,
taxAmount,
);

const parameters: SplitBillParams = {
Expand All @@ -4246,6 +4263,8 @@ function splitBillAndOpenReport({
policyID: splitData.policyID,
chatType: splitData.chatType,
splitPayerAccountIDs,
taxCode,
taxAmount,
};

API.write(WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT, parameters, onyxData);
Expand Down
35 changes: 13 additions & 22 deletions src/pages/iou/request/step/IOURequestStepAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ type IOURequestStepAmountOnyxProps = {
/** Whether the confirmation step should be skipped */
skipConfirmation: OnyxEntry<boolean>;

/** The draft transaction object being modified in Onyx */
draftTransaction: OnyxEntry<OnyxTypes.Transaction>;

/** Personal details of all users */
personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;

Expand All @@ -67,7 +64,6 @@ function IOURequestStepAmount({
currentUserPersonalDetails,
splitDraftTransaction,
skipConfirmation,
draftTransaction,
}: IOURequestStepAmountProps) {
const {translate} = useLocalize();
const textInput = useRef<BaseTextInputRef | null>(null);
Expand All @@ -78,8 +74,9 @@ function IOURequestStepAmount({
const isEditing = action === CONST.IOU.ACTION.EDIT;
const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT;
const isEditingSplitBill = isEditing && isSplitBill;
const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction) ?? {amount: 0};
const {currency: originalCurrency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction) ?? {currency: CONST.CURRENCY.USD};
const currentTransaction = isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction;
const {amount: transactionAmount} = ReportUtils.getTransactionDetails(currentTransaction) ?? {amount: 0};
const {currency: originalCurrency} = ReportUtils.getTransactionDetails(currentTransaction) ?? {currency: CONST.CURRENCY.USD};
const currency = CurrencyUtils.isValidCurrencyCode(selectedCurrency) ? selectedCurrency : originalCurrency;

// For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace request, as
Expand Down Expand Up @@ -269,25 +266,25 @@ function IOURequestStepAmount({
}

// If the value hasn't changed, don't request to save changes on the server and just close the modal
const transactionCurrency = TransactionUtils.getCurrency(transaction);
if (newAmount === TransactionUtils.getAmount(transaction) && currency === transactionCurrency) {
const transactionCurrency = TransactionUtils.getCurrency(currentTransaction);
if (newAmount === TransactionUtils.getAmount(currentTransaction) && currency === transactionCurrency) {
Navigation.dismissModal();
return;
}

// If currency has changed, then we get the default tax rate based on currency, otherwise we use the current tax rate selected in transaction, if we have it.
const transactionTaxCode = ReportUtils.getTransactionDetails(currentTransaction)?.taxCode;
const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, currentTransaction, currency) ?? '';
const taxCode = (currency !== transactionCurrency ? defaultTaxCode : transactionTaxCode) ?? defaultTaxCode;
const taxPercentage = TransactionUtils.getTaxValue(policy, currentTransaction, taxCode) ?? '';
const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, newAmount));

if (isSplitBill) {
IOU.setDraftSplitTransaction(transactionID, {amount: newAmount, currency});
IOU.setDraftSplitTransaction(transactionID, {amount: newAmount, currency, taxCode, taxAmount});
Navigation.goBack(backTo);
return;
}

// If currency has changed, then we get the default tax rate based on currency, otherwise we use the current tax rate selected in transaction, if we have it.
const transactionTaxCode = transaction?.taxCode ?? '';
const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, transaction, currency) ?? '';
const taxCode = (currency !== transactionCurrency ? defaultTaxCode : transactionTaxCode) ?? defaultTaxCode;
const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxCode) ?? '';
const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, newAmount));

IOU.updateMoneyRequestAmountAndCurrency({transactionID, transactionThreadReportID: reportID, currency, amount: newAmount, taxAmount, policy, taxCode});
Navigation.dismissModal();
};
Expand Down Expand Up @@ -326,12 +323,6 @@ const IOURequestStepAmountWithOnyx = withOnyx<IOURequestStepAmountProps, IOURequ
return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`;
},
},
draftTransaction: {
key: ({route}) => {
const transactionID = route.params.transactionID ?? 0;
return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`;
},
},
skipConfirmation: {
key: ({route}) => {
const transactionID = route.params.transactionID ?? 0;
Expand Down
10 changes: 9 additions & 1 deletion src/pages/iou/request/step/IOURequestStepConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ function IOURequestStepConfirmation({
const participantsWithAmount = Object.keys(transaction.splitShares ?? {})
.filter((accountID: string): boolean => (transaction?.splitShares?.[Number(accountID)]?.amount ?? 0) > 0)
.map((accountID) => Number(accountID));
splitParticipants = selectedParticipants.filter((participant) => participantsWithAmount.includes(participant.accountID ?? -1));
splitParticipants = selectedParticipants.filter((participant) =>
participantsWithAmount.includes(participant.isPolicyExpenseChat ? participant?.ownerAccountID ?? -1 : participant.accountID ?? -1),
);
}
const trimmedComment = (transaction?.comment.comment ?? '').trim();

Expand Down Expand Up @@ -375,6 +377,8 @@ function IOURequestStepConfirmation({
iouRequestType: transaction.iouRequestType,
splitShares: transaction.splitShares,
splitPayerAccountIDs: transaction.splitPayerAccountIDs ?? [],
taxCode: transactionTaxCode,
taxAmount: transactionTaxAmount,
});
}
return;
Expand All @@ -398,6 +402,8 @@ function IOURequestStepConfirmation({
iouRequestType: transaction.iouRequestType,
splitShares: transaction.splitShares,
splitPayerAccountIDs: transaction.splitPayerAccountIDs,
taxCode: transactionTaxCode,
taxAmount: transactionTaxAmount,
});
}
return;
Expand Down Expand Up @@ -493,6 +499,8 @@ function IOURequestStepConfirmation({
policy,
policyTags,
policyCategories,
transactionTaxAmount,
transactionTaxCode,
],
);

Expand Down
4 changes: 3 additions & 1 deletion src/pages/iou/request/step/IOURequestStepParticipants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ function IOURequestStepParticipants({
const participants = transaction?.participants;
const {translate} = useLocalize();
const styles = useThemeStyles();
const selectedReportID = useRef<string>(reportID);

// We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant
const selectedReportID = useRef<string>(participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID);
const numberOfParticipants = useRef(participants?.length ?? 0);
const iouRequestType = TransactionUtils.getRequestType(transaction);
const isSplitRequest = iouType === CONST.IOU.TYPE.SPLIT;
Expand Down
Loading

0 comments on commit c5c1120

Please sign in to comment.