diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index d01b69ed5649..008c1353cd68 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -170,7 +170,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea shouldShowExportIntegrationButton; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport?.currency); - const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); + const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton); const isAnyTransactionOnHold = ReportUtils.hasHeldExpenses(moneyRequestReport?.reportID); const displayedAmount = isAnyTransactionOnHold && canAllowSettlement && hasValidNonHeldAmount ? nonHeldAmount : formattedAmount; const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index e3ddb91d0528..63edd29ef4c3 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -102,6 +102,9 @@ function ReportPreview({ const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + const [invoiceReceiverPolicy] = useOnyx( + `${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : -1}`, + ); const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -123,13 +126,21 @@ function ReportPreview({ const [isPaidAnimationRunning, setIsPaidAnimationRunning] = useState(false); const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [requestType, setRequestType] = useState(); - const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); - const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID ?? ''); + const [paymentType, setPaymentType] = useState(); - const [invoiceReceiverPolicy] = useOnyx( - `${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : -1}`, + + const getCanIOUBePaid = useCallback( + (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere), + [iouReport, chatReport, policy, allTransactions], ); + const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); + const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); + const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; + + const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = ReportUtils.getNonHeldAndFullAmount(iouReport, shouldShowPayButton); + const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID ?? ''); + const managerID = iouReport?.managerID ?? action.childManagerAccountID ?? 0; const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); @@ -329,14 +340,6 @@ function ReportPreview({ ]); const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); - const getCanIOUBePaid = useCallback( - (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere), - [iouReport, chatReport, policy, allTransactions], - ); - - const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); - const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); - const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]); const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index bb50156c0231..5687333370f0 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -488,6 +488,7 @@ function validateReportDraftProperty(key: keyof Report, value: string) { case 'total': case 'unheldTotal': case 'nonReimbursableTotal': + case 'unheldNonReimbursableTotal': return validateNumber(value); case 'chatType': return validateConstantEnum(value, CONST.REPORT.CHAT_TYPE); @@ -615,6 +616,7 @@ function validateReportDraftProperty(key: keyof Report, value: string) { participants: CONST.RED_BRICK_ROAD_PENDING_ACTION, total: CONST.RED_BRICK_ROAD_PENDING_ACTION, unheldTotal: CONST.RED_BRICK_ROAD_PENDING_ACTION, + unheldNonReimbursableTotal: CONST.RED_BRICK_ROAD_PENDING_ACTION, isWaitingOnBankAccount: CONST.RED_BRICK_ROAD_PENDING_ACTION, isCancelledIOU: CONST.RED_BRICK_ROAD_PENDING_ACTION, iouReportID: CONST.RED_BRICK_ROAD_PENDING_ACTION, diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index e0fd37db5b3b..cb6e0cc32818 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -76,6 +76,7 @@ function updateIOUOwnerAndTotal>( currency: string, isDeleting = false, isUpdating = false, + isOnhold = false, ): TReport { // For the update case, we have calculated the diff amount in the calculateDiffAmount function so there is no need to compare currencies here if ((currency !== iouReport?.currency && !isUpdating) || !iouReport) { @@ -87,11 +88,18 @@ function updateIOUOwnerAndTotal>( // Let us ensure a valid value before updating the total amount. iouReportUpdate.total = iouReportUpdate.total ?? 0; + iouReportUpdate.unheldTotal = iouReportUpdate.unheldTotal ?? 0; if (actorAccountID === iouReport.ownerAccountID) { iouReportUpdate.total += isDeleting ? -amount : amount; + if (!isOnhold) { + iouReportUpdate.unheldTotal += isDeleting ? -amount : amount; + } } else { iouReportUpdate.total += isDeleting ? amount : -amount; + if (!isOnhold) { + iouReportUpdate.unheldTotal += isDeleting ? amount : -amount; + } } if (iouReportUpdate.total < 0) { @@ -99,6 +107,7 @@ function updateIOUOwnerAndTotal>( iouReportUpdate.ownerAccountID = iouReport.managerID; iouReportUpdate.managerID = iouReport.ownerAccountID; iouReportUpdate.total = -iouReportUpdate.total; + iouReportUpdate.unheldTotal = -iouReportUpdate.unheldTotal; } return iouReportUpdate; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b8a59fffdf38..56744174a501 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -156,7 +156,9 @@ type OptimisticExpenseReport = Pick< | 'stateNum' | 'statusNum' | 'total' + | 'unheldTotal' | 'nonReimbursableTotal' + | 'unheldNonReimbursableTotal' | 'parentReportID' | 'lastVisibleActionCreated' | 'parentReportActionID' @@ -452,6 +454,9 @@ type OptimisticIOUReport = Pick< | 'stateNum' | 'statusNum' | 'total' + | 'unheldTotal' + | 'nonReimbursableTotal' + | 'unheldNonReimbursableTotal' | 'reportName' | 'parentReportID' | 'lastVisibleActionCreated' @@ -4564,6 +4569,9 @@ function buildOptimisticIOUReport( stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: isSendingMoney ? CONST.REPORT.STATUS_NUM.REIMBURSED : CONST.REPORT.STATE_NUM.SUBMITTED, total, + unheldTotal: total, + nonReimbursableTotal: 0, + unheldNonReimbursableTotal: 0, // We don't translate reportName because the server response is always in English reportName: `${payerEmail} owes ${formattedTotal}`, @@ -4681,11 +4689,12 @@ function buildOptimisticExpenseReport( payeeAccountID: number, total: number, currency: string, - reimbursable = true, + nonReimbursableTotal = 0, parentReportActionID?: string, ): OptimisticExpenseReport { // The amount for Expense reports are stored as negative value in the database const storedTotal = total * -1; + const storedNonReimbursableTotal = nonReimbursableTotal * -1; const policyName = getPolicyName(ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`]); const formattedTotal = CurrencyUtils.convertToDisplayString(storedTotal, currency); const policy = getPolicy(policyID); @@ -4704,7 +4713,9 @@ function buildOptimisticExpenseReport( stateNum, statusNum, total: storedTotal, - nonReimbursableTotal: reimbursable ? 0 : storedTotal, + unheldTotal: storedTotal, + nonReimbursableTotal: storedNonReimbursableTotal, + unheldNonReimbursableTotal: storedNonReimbursableTotal, participants: { [payeeAccountID]: { notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, @@ -7756,27 +7767,21 @@ function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEn /** * Return held and full amount formatted with used currency */ -function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry): NonHeldAndFullAmount { - const reportTransactions = reportsTransactions[iouReport?.reportID ?? ''] ?? []; - const hasPendingTransaction = reportTransactions.some((transaction) => !!transaction.pendingAction); - +function getNonHeldAndFullAmount(iouReport: OnyxEntry, shouldExcludeNonReimbursables: boolean): NonHeldAndFullAmount { // if the report is an expense report, the total amount should be negated const coefficient = isExpenseReport(iouReport) ? -1 : 1; - if (hasUpdatedTotal(iouReport, policy) && hasPendingTransaction) { - const unheldTotal = reportTransactions.reduce((currentVal, transaction) => currentVal + (!TransactionUtils.isOnHold(transaction) ? transaction.amount : 0), 0); - - return { - nonHeldAmount: CurrencyUtils.convertToDisplayString(unheldTotal * coefficient, iouReport?.currency), - fullAmount: CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * coefficient, iouReport?.currency), - hasValidNonHeldAmount: unheldTotal * coefficient >= 0, - }; + let total = iouReport?.total ?? 0; + let unheldTotal = iouReport?.unheldTotal ?? 0; + if (shouldExcludeNonReimbursables) { + total -= iouReport?.nonReimbursableTotal ?? 0; + unheldTotal -= iouReport?.unheldNonReimbursableTotal ?? 0; } return { - nonHeldAmount: CurrencyUtils.convertToDisplayString((iouReport?.unheldTotal ?? 0) * coefficient, iouReport?.currency), - fullAmount: CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * coefficient, iouReport?.currency), - hasValidNonHeldAmount: (iouReport?.unheldTotal ?? 0) * coefficient >= 0, + nonHeldAmount: CurrencyUtils.convertToDisplayString(unheldTotal * coefficient, iouReport?.currency), + fullAmount: CurrencyUtils.convertToDisplayString(total * coefficient, iouReport?.currency), + hasValidNonHeldAmount: unheldTotal * coefficient >= 0, }; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 90719ffeed55..709344a5ff45 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -54,7 +54,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as SessionUtils from '@libs/SessionUtils'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {getCurrency, getTransaction} from '@libs/TransactionUtils'; +import {getTransaction} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; @@ -2110,9 +2110,15 @@ function getMoneyRequestInformation( : ReportUtils.buildOptimisticIOUReport(payeeAccountID, payerAccountID, amount, chatReport.reportID, currency); } else if (isPolicyExpenseChat) { iouReport = {...iouReport}; - if (iouReport?.currency === currency && typeof iouReport.total === 'number') { - // Because of the Expense reports are stored as negative values, we subtract the total from the amount - iouReport.total -= amount; + // Because of the Expense reports are stored as negative values, we subtract the total from the amount + if (iouReport?.currency === currency) { + if (typeof iouReport.total === 'number') { + iouReport.total -= amount; + } + + if (typeof iouReport.unheldTotal === 'number') { + iouReport.unheldTotal -= amount; + } } } else { iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, payeeAccountID, amount, currency); @@ -2335,13 +2341,20 @@ function getTrackExpenseInformation( shouldCreateNewMoneyRequestReport = ReportUtils.shouldCreateNewMoneyRequestReport(iouReport, chatReport); if (!iouReport || shouldCreateNewMoneyRequestReport) { - iouReport = ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID ?? '-1', payeeAccountID, amount, currency, false); + iouReport = ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID ?? '-1', payeeAccountID, amount, currency, amount); } else { iouReport = {...iouReport}; - if (iouReport?.currency === currency && typeof iouReport.total === 'number' && typeof iouReport.nonReimbursableTotal === 'number') { - // Because of the Expense reports are stored as negative values, we subtract the total from the amount - iouReport.total -= amount; - iouReport.nonReimbursableTotal -= amount; + // Because of the Expense reports are stored as negative values, we subtract the total from the amount + if (iouReport?.currency === currency) { + if (typeof iouReport.total === 'number' && typeof iouReport.nonReimbursableTotal === 'number') { + iouReport.total -= amount; + iouReport.nonReimbursableTotal -= amount; + } + + if (typeof iouReport.unheldTotal === 'number' && typeof iouReport.unheldNonReimbursableTotal === 'number') { + iouReport.unheldTotal -= amount; + iouReport.unheldNonReimbursableTotal -= amount; + } } } } @@ -2537,6 +2550,7 @@ function getUpdateMoneyRequestParams( // Step 2: Get all the collections being updated const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const isTransactionOnHold = TransactionUtils.isOnHold(transaction); const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null; const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); @@ -2654,8 +2668,24 @@ function getUpdateMoneyRequestParams( if (!transaction?.reimbursable && typeof updatedMoneyRequestReport.nonReimbursableTotal === 'number') { updatedMoneyRequestReport.nonReimbursableTotal -= diff; } + if (!isTransactionOnHold) { + if (typeof updatedMoneyRequestReport.unheldTotal === 'number') { + updatedMoneyRequestReport.unheldTotal -= diff; + } + if (!transaction?.reimbursable && typeof updatedMoneyRequestReport.unheldNonReimbursableTotal === 'number') { + updatedMoneyRequestReport.unheldNonReimbursableTotal -= diff; + } + } } else { - updatedMoneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID ?? -1, diff, TransactionUtils.getCurrency(transaction), false, true); + updatedMoneyRequestReport = IOUUtils.updateIOUOwnerAndTotal( + iouReport, + updatedReportAction.actorAccountID ?? -1, + diff, + TransactionUtils.getCurrency(transaction), + false, + true, + isTransactionOnHold, + ); } optimisticData.push( @@ -4281,9 +4311,15 @@ function createSplitsAndOnyxData( ? ReportUtils.buildOptimisticExpenseReport(oneOnOneChatReport.reportID, oneOnOneChatReport.policyID ?? '-1', currentUserAccountID, splitAmount, currency) : ReportUtils.buildOptimisticIOUReport(currentUserAccountID, accountID, splitAmount, oneOnOneChatReport.reportID, currency); } else if (isOwnPolicyExpenseChat) { - if (typeof oneOnOneIOUReport?.total === 'number') { - // Because of the Expense reports are stored as negative values, we subtract the total from the amount - oneOnOneIOUReport.total -= splitAmount; + // Because of the Expense reports are stored as negative values, we subtract the total from the amount + if (oneOnOneIOUReport?.currency === currency) { + if (typeof oneOnOneIOUReport.total === 'number') { + oneOnOneIOUReport.total -= splitAmount; + } + + if (typeof oneOnOneIOUReport.unheldTotal === 'number') { + oneOnOneIOUReport.unheldTotal -= splitAmount; + } } } else { oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(oneOnOneIOUReport, currentUserAccountID, splitAmount, currency); @@ -5405,6 +5441,7 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const reportPreviewAction = getReportPreviewAction(iouReport?.chatReportID ?? '-1', iouReport?.reportID ?? '-1')!; const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const isTransactionOnHold = TransactionUtils.isOnHold(transaction); const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; const transactionThreadID = reportAction.childReportID; let transactionThread = null; @@ -5464,9 +5501,27 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT if (!transaction?.reimbursable && typeof updatedIOUReport.nonReimbursableTotal === 'number') { updatedIOUReport.nonReimbursableTotal += amountDiff; } + + if (!isTransactionOnHold) { + if (typeof updatedIOUReport.unheldTotal === 'number') { + updatedIOUReport.unheldTotal += amountDiff; + } + + if (!transaction?.reimbursable && typeof updatedIOUReport.unheldNonReimbursableTotal === 'number') { + updatedIOUReport.unheldNonReimbursableTotal += amountDiff; + } + } } } else { - updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, reportAction.actorAccountID ?? -1, TransactionUtils.getAmount(transaction, false), currency, true); + updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal( + iouReport, + reportAction.actorAccountID ?? -1, + TransactionUtils.getAmount(transaction, false), + currency, + true, + false, + isTransactionOnHold, + ); } if (updatedIOUReport) { @@ -6321,24 +6376,26 @@ function getReportFromHoldRequestsOnyxData( const firstHoldTransaction = holdTransactions.at(0); const newParentReportActionID = rand64(); + const coefficient = ReportUtils.isExpenseReport(iouReport) ? -1 : 1; const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); - const holdTransactionAmount = holdTransactions.reduce((acc, transaction) => acc + TransactionUtils.getAmount(transaction), 0); + const holdAmount = ((iouReport?.total ?? 0) - (iouReport?.unheldTotal ?? 0)) * coefficient; + const holdNonReimbursableAmount = ((iouReport?.nonReimbursableTotal ?? 0) - (iouReport?.unheldNonReimbursableTotal ?? 0)) * coefficient; const optimisticExpenseReport = isPolicyExpenseChat ? ReportUtils.buildOptimisticExpenseReport( chatReport.reportID, chatReport.policyID ?? iouReport?.policyID ?? '', recipient.accountID ?? 1, - holdTransactionAmount, - getCurrency(firstHoldTransaction), - false, + holdAmount, + iouReport?.currency ?? '', + holdNonReimbursableAmount, newParentReportActionID, ) : ReportUtils.buildOptimisticIOUReport( iouReport?.ownerAccountID ?? -1, iouReport?.managerID ?? -1, - holdTransactionAmount, + holdAmount, chatReport.reportID, - getCurrency(firstHoldTransaction), + iouReport?.currency ?? '', false, newParentReportActionID, ); @@ -6415,6 +6472,7 @@ function getReportFromHoldRequestsOnyxData( value: { ...optimisticExpenseReport, unheldTotal: 0, + unheldNonReimbursableTotal: 0, }, }, // add preview report action to main chat @@ -7869,6 +7927,8 @@ function putOnHold(transactionID: string, comment: string, reportID: string, sea const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; const updatedViolations = [...transactionViolations, newViolation]; const parentReportActionOptimistic = ReportUtils.getOptimisticDataForParentReportAction(reportID, createdReportActionComment.created, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const iouReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`]; const optimisticData: OnyxUpdate[] = [ { @@ -7896,6 +7956,20 @@ function putOnHold(transactionID: string, comment: string, reportID: string, sea }, ]; + if (iouReport && iouReport.currency === transaction?.currency) { + const isExpenseReport = ReportUtils.isExpenseReport(iouReport); + const coefficient = isExpenseReport ? -1 : 1; + const transactionAmount = TransactionUtils.getAmount(transaction, isExpenseReport) * coefficient; + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: { + unheldTotal: (iouReport.unheldTotal ?? 0) - transactionAmount, + unheldNonReimbursableTotal: !transaction?.reimbursable ? (iouReport.unheldNonReimbursableTotal ?? 0) - transactionAmount : iouReport.unheldNonReimbursableTotal, + }, + }); + } + parentReportActionOptimistic.forEach((parentActionData) => { if (!parentActionData) { return; @@ -7976,6 +8050,8 @@ function putOnHold(transactionID: string, comment: string, reportID: string, sea function unholdRequest(transactionID: string, reportID: string, searchHash?: number) { const createdReportAction = ReportUtils.buildOptimisticUnHoldReportAction(); const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; + const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const iouReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`]; const optimisticData: OnyxUpdate[] = [ { @@ -8002,6 +8078,20 @@ function unholdRequest(transactionID: string, reportID: string, searchHash?: num }, ]; + if (iouReport && iouReport.currency === transaction?.currency) { + const isExpenseReport = ReportUtils.isExpenseReport(iouReport); + const coefficient = isExpenseReport ? -1 : 1; + const transactionAmount = TransactionUtils.getAmount(transaction, isExpenseReport) * coefficient; + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: { + unheldTotal: (iouReport.unheldTotal ?? 0) + transactionAmount, + unheldNonReimbursableTotal: !transaction?.reimbursable ? (iouReport.unheldNonReimbursableTotal ?? 0) + transactionAmount : iouReport.unheldNonReimbursableTotal, + }, + }); + } + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index cd0433da7353..2b3f88b92795 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -211,6 +211,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro ownerAccountID: reportOnyx.ownerAccountID, currency: reportOnyx.currency, unheldTotal: reportOnyx.unheldTotal, + unheldNonReimbursableTotal: reportOnyx.unheldNonReimbursableTotal, participants: reportOnyx.participants, isWaitingOnBankAccount: reportOnyx.isWaitingOnBankAccount, iouReportID: reportOnyx.iouReportID, diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 42be2a8c3613..4020137f5300 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -190,6 +190,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** For expense reports, this is the total amount requested */ unheldTotal?: number; + /** Total amount of unheld non-reimbursable transactions in an expense report */ + unheldNonReimbursableTotal?: number; + /** For expense reports, this is the currency of the expense */ currency?: string; diff --git a/src/types/utils/whitelistedReportKeys.ts b/src/types/utils/whitelistedReportKeys.ts index d556a51cd3de..45b4a11ed052 100644 --- a/src/types/utils/whitelistedReportKeys.ts +++ b/src/types/utils/whitelistedReportKeys.ts @@ -47,6 +47,7 @@ type WhitelistedReport = OnyxCommon.OnyxValueWithOfflineFeedback< participants: unknown; total: unknown; unheldTotal: unknown; + unheldNonReimbursableTotal: unknown; currency: unknown; errorFields: unknown; isWaitingOnBankAccount: unknown;