diff --git a/src/CONST.ts b/src/CONST.ts index 4d7049271e78..7b1d941bb5ed 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3717,6 +3717,7 @@ const CONST = { TAG: 'tag', TAX_RATE: 'taxRate', TAX_AMOUNT: 'taxAmount', + REIMBURSABLE: 'reimbursable', REPORT: 'report', }, FOOTER: { diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 99f5da95537c..06d59c8c4c3f 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -183,6 +183,12 @@ type MoneyRequestConfirmationListProps = { /** The PDF password callback */ onPDFPassword?: () => void; + + /** Function to toggle reimbursable */ + onToggleReimbursable?: (isOn: boolean) => void; + + /** Flag indicating if the IOU is reimbursable */ + iouIsReimbursable?: boolean; }; type MoneyRequestConfirmationListItem = Participant | OptionData; @@ -224,6 +230,8 @@ function MoneyRequestConfirmationList({ isConfirming, onPDFLoadError, onPDFPassword, + iouIsReimbursable = true, + onToggleReimbursable, }: MoneyRequestConfirmationListProps) { const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true}); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); @@ -1089,6 +1097,8 @@ function MoneyRequestConfirmationList({ unit={unit} onPDFLoadError={onPDFLoadError} onPDFPassword={onPDFPassword} + iouIsReimbursable={iouIsReimbursable} + onToggleReimbursable={onToggleReimbursable} isReceiptEditable={isReceiptEditable} /> ); diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 15b5cf32d0bd..cc6401dde660 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -25,6 +25,7 @@ import { getTaxAmount, getTaxName, isAmountMissing, + isCardTransaction, isCreatedMissing, isFetchingWaypointsFromServer, shouldShowAttendees as shouldShowAttendeesTransactionUtils, @@ -196,6 +197,12 @@ type MoneyRequestConfirmationListFooterProps = { /** The PDF password callback */ onPDFPassword?: () => void; + + /** Function to toggle reimbursable */ + onToggleReimbursable?: (isOn: boolean) => void; + + /** Flag indicating if the IOU is reimbursable */ + iouIsReimbursable: boolean; }; function MoneyRequestConfirmationListFooter({ @@ -246,6 +253,8 @@ function MoneyRequestConfirmationListFooter({ unit, onPDFLoadError, onPDFPassword, + iouIsReimbursable, + onToggleReimbursable, isReceiptEditable = false, }: MoneyRequestConfirmationListFooterProps) { const styles = useThemeStyles(); @@ -312,6 +321,7 @@ function MoneyRequestConfirmationListFooter({ const canModifyTaxFields = !isReadOnly && !isDistanceRequest && !isPerDiemRequest; // A flag for showing the billable field const shouldShowBillable = policy?.disabledFields?.defaultBillable === false; + const shouldShowReimbursable = policy?.disabledFields?.reimbursable === false && !isCardTransaction(transaction); // Do not hide fields in case of paying someone const shouldShowAllFields = !!isPerDiemRequest || !!isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || !!isEditingSplitBill; // Calculate the formatted tax amount based on the transaction's tax amount and the IOU currency code @@ -636,6 +646,25 @@ function MoneyRequestConfirmationListFooter({ shouldShow: shouldShowAttendees, isSupplementary: true, }, + { + item: ( + + onToggleReimbursable?.(isOn)} + isActive={iouIsReimbursable} + disabled={isReadOnly} + wrapperStyle={styles.flex1} + /> + + ), + shouldShow: shouldShowReimbursable, + isSupplementary: true, + }, { item: ( shouldShowAttendeesTransactionUtils(iouType, policy), [iouType, policy]); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest, isPerDiemRequest); @@ -279,6 +285,17 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals [transaction, report, policy, policyTagList, policyCategories], ); + const saveReimbursable = useCallback( + (newReimbursable: boolean) => { + // If the value hasn't changed, don't request to save changes on the server and just close the modal + if (newReimbursable === getReimbursable(transaction) || !transaction?.transactionID || !report?.reportID) { + return; + } + updateMoneyRequestReimbursable(transaction.transactionID, report?.reportID, newReimbursable, policy, policyTagList, policyCategories); + }, + [transaction, report, policy, policyTagList, policyCategories], + ); + if (isCardTransaction) { if (transactionPostedDate) { dateDescription += ` ${CONST.DOT_SEPARATOR} ${translate('iou.posted')} ${transactionPostedDate}`; @@ -800,6 +817,19 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals /> )} + {shouldShowReimbursable && ( + + + {Str.UCFirst(translate('iou.reimbursable'))} + + + + )} {/* Note: "Billable" toggle and "View trip details" should be always the last two items */} {shouldShowBillable && ( diff --git a/src/libs/API/parameters/CreateDistanceRequestParams.ts b/src/libs/API/parameters/CreateDistanceRequestParams.ts index 73f08deb6c4f..738393903c36 100644 --- a/src/libs/API/parameters/CreateDistanceRequestParams.ts +++ b/src/libs/API/parameters/CreateDistanceRequestParams.ts @@ -15,6 +15,7 @@ type CreateDistanceRequestParams = { taxCode?: string; taxAmount?: number; billable?: boolean; + reimbursable?: boolean; transactionThreadReportID?: string; createdReportActionIDForThread?: string; payerEmail?: string; diff --git a/src/libs/API/parameters/CreatePerDiemRequestParams.ts b/src/libs/API/parameters/CreatePerDiemRequestParams.ts index 480acd9ca23d..85ff52eadaf1 100644 --- a/src/libs/API/parameters/CreatePerDiemRequestParams.ts +++ b/src/libs/API/parameters/CreatePerDiemRequestParams.ts @@ -20,6 +20,7 @@ type CreatePerDiemRequestParams = { transactionThreadReportID: string; createdReportActionIDForThread: string | undefined; billable?: boolean; + reimbursable?: boolean; attendees?: string; }; diff --git a/src/libs/API/parameters/RequestMoneyParams.ts b/src/libs/API/parameters/RequestMoneyParams.ts index 74a435600cd2..d059aec8e597 100644 --- a/src/libs/API/parameters/RequestMoneyParams.ts +++ b/src/libs/API/parameters/RequestMoneyParams.ts @@ -27,7 +27,7 @@ type RequestMoneyParams = { receiptGpsPoints?: string; transactionThreadReportID: string; createdReportActionIDForThread: string | undefined; - reimbursible?: boolean; + reimbursable?: boolean; description?: string; attendees?: string; }; diff --git a/src/libs/API/parameters/SplitBillParams.ts b/src/libs/API/parameters/SplitBillParams.ts index 3fda11b9ca98..bc99bf176e2d 100644 --- a/src/libs/API/parameters/SplitBillParams.ts +++ b/src/libs/API/parameters/SplitBillParams.ts @@ -9,6 +9,7 @@ type SplitBillParams = { category: string; tag: string; billable: boolean; + reimbursable: boolean; transactionID: string; reportActionID: string; createdReportActionID?: string; diff --git a/src/libs/API/parameters/StartSplitBillParams.ts b/src/libs/API/parameters/StartSplitBillParams.ts index 10f1029a0fba..031d75aca525 100644 --- a/src/libs/API/parameters/StartSplitBillParams.ts +++ b/src/libs/API/parameters/StartSplitBillParams.ts @@ -13,6 +13,7 @@ type StartSplitBillParams = { isFromGroupDM: boolean; createdReportActionID?: string; billable: boolean; + reimbursable: boolean; chatType?: string; taxCode?: string; taxAmount?: number; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 950c15dc3af9..bd38bc2e77f8 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -188,6 +188,7 @@ const WRITE_COMMANDS = { COMPLETE_SPLIT_BILL: 'CompleteSplitBill', UPDATE_MONEY_REQUEST_ATTENDEES: 'UpdateMoneyRequestAttendees', UPDATE_MONEY_REQUEST_DATE: 'UpdateMoneyRequestDate', + UPDATE_MONEY_REQUEST_REIMBURSABLE: 'UpdateMoneyRequestReimbursable', UPDATE_MONEY_REQUEST_BILLABLE: 'UpdateMoneyRequestBillable', UPDATE_MONEY_REQUEST_MERCHANT: 'UpdateMoneyRequestMerchant', UPDATE_MONEY_REQUEST_TAG: 'UpdateMoneyRequestTag', @@ -655,6 +656,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_ATTENDEES]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DATE]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_MERCHANT]: Parameters.UpdateMoneyRequestParams; + [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_REIMBURSABLE]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_AMOUNT]: Parameters.UpdateMoneyRequestParams; diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index c4a9028118dc..0f438053f807 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -36,13 +36,6 @@ Onyx.connect({ callback: (value) => (allReports = value), }); -/** - * Utility to get message based on boolean literal value. - */ -function getBooleanLiteralMessage(value: string | undefined, truthyMessage: string, falsyMessage: string): string { - return value === 'true' ? truthyMessage : falsyMessage; -} - /** * Builds the partial message fragment for a modified field on the expense. */ @@ -334,8 +327,8 @@ function getForReportAction({ const hasModifiedReimbursable = isReportActionOriginalMessageAnObject && 'oldReimbursable' in reportActionOriginalMessage && 'reimbursable' in reportActionOriginalMessage; if (hasModifiedReimbursable) { buildMessageFragmentForValue( - getBooleanLiteralMessage(reportActionOriginalMessage?.reimbursable, translateLocal('iou.reimbursable'), translateLocal('iou.nonReimbursable')), - getBooleanLiteralMessage(reportActionOriginalMessage?.oldReimbursable, translateLocal('iou.reimbursable'), translateLocal('iou.nonReimbursable')), + reportActionOriginalMessage?.reimbursable ?? '', + reportActionOriginalMessage?.oldReimbursable ?? '', translateLocal('iou.expense'), true, setFragments, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 11882161be61..65ed4ac9d293 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -660,6 +660,7 @@ type TransactionDetails = { customUnitRateID?: string; comment: string; category: string; + reimbursable: boolean; billable: boolean; tag: string; mccGroup?: ValueOf; @@ -3734,6 +3735,7 @@ function getTransactionDetails( waypoints: getWaypoints(transaction), customUnitRateID: getRateID(transaction), category: getCategory(transaction), + reimbursable: getReimbursable(transaction), billable: getBillable(transaction), tag: getTag(transaction), mccGroup: getMCCGroup(transaction), @@ -3837,6 +3839,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxInputOrEntry CONST.EDIT_REQUEST_FIELD.RECEIPT, CONST.EDIT_REQUEST_FIELD.DISTANCE, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE, + CONST.EDIT_REQUEST_FIELD.REIMBURSABLE, CONST.EDIT_REQUEST_FIELD.REPORT, ]; @@ -4378,6 +4381,12 @@ function getModifiedExpenseOriginalMessage( originalMessage.currency = getCurrency(oldTransaction); } + if ('reimbursable' in transactionChanges) { + const oldReimbursable = getReimbursable(oldTransaction); + originalMessage.oldReimbursable = oldReimbursable ? translateLocal('common.reimbursable').toLowerCase() : translateLocal('iou.nonReimbursable').toLowerCase(); + originalMessage.reimbursable = transactionChanges?.reimbursable ? translateLocal('common.reimbursable').toLowerCase() : translateLocal('iou.nonReimbursable').toLowerCase(); + } + if ('billable' in transactionChanges) { const oldBillable = getBillable(oldTransaction); originalMessage.oldBillable = oldBillable ? translateLocal('common.billable').toLowerCase() : translateLocal('common.nonBillable').toLowerCase(); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index beba9b35c280..6b031ddb7320 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -486,6 +486,10 @@ function getUpdatedTransaction({ updatedTransaction.taxCode = transactionChanges.taxCode; } + if (Object.hasOwn(transactionChanges, 'reimbursable') && typeof transactionChanges.reimbursable === 'boolean') { + updatedTransaction.reimbursable = transactionChanges.reimbursable; + } + if (Object.hasOwn(transactionChanges, 'billable') && typeof transactionChanges.billable === 'boolean') { updatedTransaction.billable = transactionChanges.billable; } @@ -526,6 +530,7 @@ function getUpdatedTransaction({ ...(Object.hasOwn(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'waypoints') && {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(Object.hasOwn(transactionChanges, 'reimbursable') && {reimbursable: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'billable') && {billable: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'tag') && {tag: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), @@ -684,7 +689,7 @@ function getFormattedAttendees(modifiedAttendees?: Attendee[], attendees?: Atten /** * Return the reimbursable value. Defaults to true to match BE logic. */ -function getReimbursable(transaction: Transaction): boolean { +function getReimbursable(transaction: OnyxInputOrEntry): boolean { return transaction?.reimbursable ?? true; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0fdb551ae682..35405a7d9672 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -234,6 +234,7 @@ type BaseTransactionParams = { taxCode?: string; taxAmount?: number; billable?: boolean; + reimbursable?: boolean; customUnitRateID?: string; }; @@ -251,6 +252,7 @@ type MoneyRequestInformation = { createdReportActionIDForThread: string | undefined; onyxData: OnyxData; billable?: boolean; + reimbursable?: boolean; }; type TrackExpenseInformation = { @@ -404,7 +406,6 @@ type RequestMoneyInformation = { policyParams?: BasePolicyParams; gpsPoints?: GPSPoint; action?: IOUAction; - reimbursible?: boolean; transactionParams: RequestMoneyTransactionParams; isRetry?: boolean; shouldHandleNavigation?: boolean; @@ -605,6 +606,7 @@ type StartSplitBilActionParams = { receipt: Receipt; existingSplitChatReportID?: string; billable?: boolean; + reimbursable?: boolean; category: string | undefined; tag: string | undefined; currency: string; @@ -1016,6 +1018,10 @@ function setMoneyRequestBillable(transactionID: string, billable: boolean) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {billable}); } +function setMoneyRequestReimbursable(transactionID: string, reimbursable: boolean) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {reimbursable}); +} + function setMoneyRequestParticipants(transactionID: string, participants: Participant[] = [], isTestTransaction = false) { // We should change the reportID and isFromGlobalCreate of the test transaction since this flow can start inside an existing report return Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { @@ -3342,7 +3348,7 @@ function getPerDiemExpenseInformation(perDiemExpenseInformation: PerDiemExpenseI const {parentChatReport, transactionParams, participantParams, policyParams = {}, moneyRequestReportID = ''} = perDiemExpenseInformation; const {payeeAccountID = userAccountID, payeeEmail = currentUserEmail, participant} = participantParams; const {policy, policyCategories, policyTagList} = policyParams; - const {comment = '', currency, created, category, tag, customUnit, billable, attendees} = transactionParams; + const {comment = '', currency, created, category, tag, customUnit, billable, attendees, reimbursable} = transactionParams; const amount = computePerDiemExpenseAmount(customUnit); const merchant = computePerDiemExpenseMerchant(customUnit, policy); @@ -3420,6 +3426,7 @@ function getPerDiemExpenseInformation(perDiemExpenseInformation: PerDiemExpenseI tag, customUnit, billable, + reimbursable, pendingFields: {subRates: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, attendees, }, @@ -3537,6 +3544,7 @@ function getPerDiemExpenseInformation(perDiemExpenseInformation: PerDiemExpenseI failureData, }, billable, + reimbursable, }; } @@ -4389,6 +4397,24 @@ function updateMoneyRequestBillable( API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE, params, onyxData); } +function updateMoneyRequestReimbursable( + transactionID: string | undefined, + transactionThreadReportID: string | undefined, + value: boolean, + policy: OnyxEntry, + policyTagList: OnyxEntry, + policyCategories: OnyxEntry, +) { + if (!transactionID || !transactionThreadReportID) { + return; + } + const transactionChanges: TransactionChanges = { + reimbursable: value, + }; + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories); + API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_REIMBURSABLE, params, onyxData); +} + /** Updates the merchant field of an expense */ function updateMoneyRequestMerchant( transactionID: string, @@ -4714,6 +4740,7 @@ type ConvertTrackedWorkspaceParams = { receipt: Receipt | undefined; waypoints?: string; customUnitRateID?: string; + reimbursable?: boolean; }; type AddTrackedExpenseToPolicyParam = { @@ -4999,7 +5026,7 @@ function shareTrackedExpense(trackedExpenseParams: TrackedExpenseParams) { * Submit expense to another user */ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { - const {report, participantParams, policyParams = {}, transactionParams, gpsPoints, action, reimbursible, shouldHandleNavigation = true, backToReport} = requestMoneyInformation; + const {report, participantParams, policyParams = {}, transactionParams, gpsPoints, action, shouldHandleNavigation = true, backToReport} = requestMoneyInformation; const {payeeAccountID} = participantParams; const parsedComment = getParsedComment(transactionParams.comment ?? ''); transactionParams.comment = parsedComment; @@ -5014,6 +5041,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { taxCode = '', taxAmount = 0, billable, + reimbursable, created, attendees, actionableWhisperReportActionID, @@ -5093,6 +5121,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { policyID: chatReport.policyID, waypoints: sanitizedWaypoints, customUnitRateID, + reimbursable, } : undefined; convertTrackedExpenseToRequest({ @@ -5155,7 +5184,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { receiptGpsPoints: gpsPoints ? JSON.stringify(gpsPoints) : undefined, transactionThreadReportID, createdReportActionIDForThread, - reimbursible, + reimbursable, description: parsedComment, attendees: attendees ? JSON.stringify(attendees) : undefined, }; @@ -5217,6 +5246,7 @@ function submitPerDiemExpense(submitPerDiemExpenseInformation: PerDiemExpenseInf createdReportActionIDForThread, onyxData, billable, + reimbursable, } = getPerDiemExpenseInformation({ parentChatReport: currentChatReport, participantParams, @@ -5248,6 +5278,7 @@ function submitPerDiemExpense(submitPerDiemExpenseInformation: PerDiemExpenseInf transactionThreadReportID, createdReportActionIDForThread, billable, + reimbursable, attendees: attendees ? JSON.stringify(attendees) : undefined, }; @@ -6115,6 +6146,7 @@ type SplitBillActionsParams = { category?: string; tag?: string; billable?: boolean; + reimbursable?: boolean; iouRequestType?: IOURequestType; existingSplitChatReportID?: string; splitShares?: SplitShares; @@ -6140,6 +6172,7 @@ function splitBill({ category = '', tag = '', billable = false, + reimbursable = false, iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL, existingSplitChatReportID, splitShares = {}, @@ -6163,6 +6196,7 @@ function splitBill({ tag, splitShares, billable, + reimbursable, iouRequestType, taxCode, taxAmount, @@ -6180,6 +6214,7 @@ function splitBill({ created, tag, billable, + reimbursable, transactionID: splitData.transactionID, reportActionID: splitData.reportActionID, createdReportActionID: splitData.createdReportActionID, @@ -6214,6 +6249,7 @@ function splitBillAndOpenReport({ category = '', tag = '', billable = false, + reimbursable = false, iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL, splitShares = {}, splitPayerAccountIDs = [], @@ -6237,6 +6273,7 @@ function splitBillAndOpenReport({ tag, splitShares, billable, + reimbursable, iouRequestType, taxCode, taxAmount, @@ -6254,6 +6291,7 @@ function splitBillAndOpenReport({ category, tag, billable, + reimbursable, transactionID: splitData.transactionID, reportActionID: splitData.reportActionID, createdReportActionID: splitData.createdReportActionID, @@ -6285,6 +6323,7 @@ function startSplitBill({ receipt, existingSplitChatReportID, billable = false, + reimbursable = false, category = '', tag = '', currency, @@ -6314,6 +6353,7 @@ function startSplitBill({ taxCode, taxAmount, billable, + reimbursable, filename, }, }); @@ -6449,6 +6489,7 @@ function startSplitBill({ receipt: receiptObject, existingSplitChatReportID, billable, + reimbursable, category, tag, currency, @@ -6598,6 +6639,7 @@ function startSplitBill({ currency, isFromGroupDM: !existingSplitChatReport, billable, + reimbursable, ...(existingSplitChatReport ? {} : {createdReportActionID: splitChatCreatedReportAction.reportActionID}), chatType: splitChatReport?.chatType, taxCode, @@ -6912,7 +6954,23 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest const {policy, policyCategories, policyTagList} = policyParams; const parsedComment = getParsedComment(transactionParams.comment); transactionParams.comment = parsedComment; - const {amount, comment, currency, created, category, tag, taxAmount, taxCode, merchant, billable, validWaypoints, customUnitRateID = '', splitShares = {}, attendees} = transactionParams; + const { + amount, + comment, + currency, + created, + category, + tag, + taxAmount, + taxCode, + merchant, + billable, + reimbursable, + validWaypoints, + customUnitRateID = '', + splitShares = {}, + attendees, + } = transactionParams; // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = isMoneyRequestReportReportUtils(report); @@ -6971,6 +7029,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest taxCode, taxAmount, billable, + reimbursable, splits: JSON.stringify(splits), chatType: splitData.chatType, description: parsedComment, @@ -7016,6 +7075,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest taxCode, taxAmount, billable, + reimbursable, attendees, }, }); @@ -7038,6 +7098,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest taxCode, taxAmount, billable, + reimbursable, transactionThreadReportID, createdReportActionIDForThread, payerEmail, @@ -11011,6 +11072,7 @@ export { unholdRequest, updateMoneyRequestAttendees, updateMoneyRequestAmountAndCurrency, + updateMoneyRequestReimbursable, updateMoneyRequestBillable, updateMoneyRequestCategory, updateMoneyRequestDate, @@ -11030,5 +11092,6 @@ export { canSubmitReport, submitPerDiemExpense, calculateDiffAmount, + setMoneyRequestReimbursable, }; export type {GPSPoint as GpsPoint, IOURequestType, StartSplitBilActionParams, CreateTrackExpenseParams, RequestMoneyInformation, ReplaceReceipt}; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 5faebc8c7323..a1db72bcc3dc 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1760,7 +1760,8 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol }, areWorkflowsEnabled: shouldEnableWorkflowsByDefault, defaultBillable: false, - disabledFields: {defaultBillable: true}, + defaultReimbursable: true, + disabledFields: {defaultBillable: true, reimbursable: false}, requiresCategory: true, }, }, @@ -1867,7 +1868,8 @@ function buildPolicyData( type: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, defaultBillable: false, - disabledFields: {defaultBillable: true}, + defaultReimbursable: true, + disabledFields: {defaultBillable: true, reimbursable: false}, avatarURL: file?.uri, originalFileName: file?.name, ...optimisticMccGroupData.optimisticData, diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 55a015e4d9e6..5e982980079c 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -47,6 +47,7 @@ import { setMoneyRequestBillable, setMoneyRequestCategory, setMoneyRequestReceipt, + setMoneyRequestReimbursable, splitBill, splitBillAndOpenReport, startMoneyRequest, @@ -235,6 +236,13 @@ function IOURequestStepConfirmation({ }); }, [transactionIDs, defaultBillable]); + const defaultReimbursable = !!policy?.defaultReimbursable; + useEffect(() => { + transactionIDs.forEach((transactionID) => { + setMoneyRequestReimbursable(transactionID, defaultReimbursable); + }); + }, [transactionIDs, defaultReimbursable]); + useEffect(() => { // Exit early if the transaction is still loading if (isLoadingTransaction) { @@ -475,6 +483,7 @@ function IOURequestStepConfirmation({ taxCode: transactionTaxCode, taxAmount: transactionTaxAmount, billable: item.billable, + reimbursable: item.reimbursable, actionableWhisperReportActionID: item.actionableWhisperReportActionID, linkedTrackedExpenseReportAction: item.linkedTrackedExpenseReportAction, linkedTrackedExpenseReportID: item.linkedTrackedExpenseReportID, @@ -533,6 +542,7 @@ function IOURequestStepConfirmation({ tag: transaction.tag, customUnit: transaction.comment?.customUnit, billable: transaction.billable, + reimbursable: transaction.reimbursable, attendees: transaction.comment?.attendees, }, }); @@ -639,6 +649,7 @@ function IOURequestStepConfirmation({ splitShares: transaction.splitShares, validWaypoints: getValidWaypoints(transaction.comment?.waypoints, true), billable: transaction.billable, + reimbursable: transaction.reimbursable, attendees: transaction.comment?.attendees, }, backToReport, @@ -704,6 +715,7 @@ function IOURequestStepConfirmation({ receipt: currentTransactionReceiptFile, existingSplitChatReportID: report?.reportID, billable: transaction.billable, + reimbursable: transaction.reimbursable, category: transaction.category, tag: transaction.tag, currency: transaction.currency, @@ -731,6 +743,7 @@ function IOURequestStepConfirmation({ tag: transaction.tag, existingSplitChatReportID: report?.reportID, billable: transaction.billable, + reimbursable: transaction.reimbursable, iouRequestType: transaction.iouRequestType, splitShares: transaction.splitShares, splitPayerAccountIDs: transaction.splitPayerAccountIDs ?? [], @@ -756,6 +769,7 @@ function IOURequestStepConfirmation({ category: transaction.category, tag: transaction.tag, billable: !!transaction.billable, + reimbursable: !!transaction.reimbursable, iouRequestType: transaction.iouRequestType, splitShares: transaction.splitShares, splitPayerAccountIDs: transaction.splitPayerAccountIDs, @@ -919,6 +933,13 @@ function IOURequestStepConfirmation({ [currentTransactionID], ); + const setReimbursable = useCallback( + (reimbursable: boolean) => { + setMoneyRequestReimbursable(currentTransactionID, reimbursable); + }, + [currentTransactionID], + ); + // This loading indicator is shown because the transaction originalCurrency is being updated later than the component mounts. // To prevent the component from rendering with the wrong currency, we show a loading indicator until the correct currency is set. const isLoading = !!transaction?.originalCurrency; @@ -1082,6 +1103,8 @@ function IOURequestStepConfirmation({ shouldPlaySound={iouType === CONST.IOU.TYPE.PAY} isConfirmed={isConfirmed} isConfirming={isConfirming} + iouIsReimbursable={transaction?.reimbursable} + onToggleReimbursable={setReimbursable} isReceiptEditable /> diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 3c39fee66263..bb523775fca2 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -353,6 +353,7 @@ function IOURequestStepDistance({ currency: transaction?.currency ?? 'USD', merchant: translate('iou.fieldPending'), billable: !!policy?.defaultBillable, + reimbursable: !!policy?.defaultReimbursable, validWaypoints: getValidWaypoints(waypoints, true), customUnitRateID: DistanceRequestUtils.getCustomUnitRateID(report.reportID), splitShares: transaction?.splitShares, diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index eadece5d418a..08d5877c86e0 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1740,6 +1740,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether transactions should be billable by default */ defaultBillable?: boolean; + /** Whether transactions should be reimbursable by default */ + defaultReimbursable?: boolean; + /** The workspace description */ description?: string;