diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 5784be21bac3..31e7792a7310 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -8,7 +8,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as HeaderUtils from '@libs/HeaderUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; @@ -51,13 +50,9 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const {translate} = useLocalize(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport); - const isApproved = ReportUtils.isReportApproved(moneyRequestReport); const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport); const policyType = policy?.type; - const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(moneyRequestReport, policy); - const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicy(moneyRequestReport); - const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && session?.accountID === moneyRequestReport.managerID; const isPayer = ReportUtils.isPayer(session, moneyRequestReport); const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); @@ -70,22 +65,12 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money setIsConfirmModalVisible(false); }, [moneyRequestReport, chatReport]); - const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy); - const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy); - const shouldShowPayButton = useMemo( - () => isPayer && !isDraft && !isSettled && !moneyRequestReport.isWaitingOnBankAccount && reimbursableSpend !== 0 && !ReportUtils.isArchivedRoom(chatReport) && !isAutoReimbursable, - [isPayer, isDraft, isSettled, moneyRequestReport, reimbursableSpend, chatReport, isAutoReimbursable], - ); - const shouldShowApproveButton = useMemo(() => { - if (!isPaidGroupPolicy) { - return false; - } - if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) { - return false; - } - return isManager && !isDraft && !isApproved && !isSettled; - }, [isPaidGroupPolicy, isManager, isDraft, isApproved, isSettled, isOnInstantSubmitPolicy, isOnSubmitAndClosePolicy]); + const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]); + + const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]); + const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 381302489699..c78a3cedebbd 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -19,7 +19,6 @@ import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -30,7 +29,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Policy, Report, ReportAction, Session, Transaction, TransactionViolations} from '@src/types/onyx'; +import type {Policy, Report, ReportAction, Transaction, TransactionViolations} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import ReportActionItemImages from './ReportActionItemImages'; @@ -44,9 +43,6 @@ type ReportPreviewOnyxProps = { /** Active IOU Report for current report */ iouReport: OnyxEntry; - /** Session info for the currently logged in user. */ - session: OnyxEntry; - /** All the transactions, used to update ReportPreview label and status */ transactions: OnyxCollection; @@ -85,7 +81,6 @@ type ReportPreviewProps = ReportPreviewOnyxProps & { function ReportPreview({ iouReport, - session, policy, iouReportID, policyID, @@ -118,12 +113,9 @@ function ReportPreview({ ); const managerID = iouReport?.managerID ?? 0; - const isCurrentUserManager = managerID === session?.accountID; const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); - const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy); const iouSettled = ReportUtils.isSettled(iouReportID); - const iouCanceled = ReportUtils.isArchivedRoom(chatReport); const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(action); const moneyRequestComment = action?.childLastMoneyRequestComment ?? ''; const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); @@ -208,23 +200,10 @@ function ReportPreview({ const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); - const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport); - const isPayer = ReportUtils.isPayer(session, iouReport); - const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy); - const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy); - const shouldShowPayButton = useMemo( - () => isPayer && !isDraftExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable, - [isPayer, isDraftExpenseReport, iouSettled, reimbursableSpend, iouCanceled, isAutoReimbursable, iouReport], - ); - const shouldShowApproveButton = useMemo(() => { - if (!isPaidGroupPolicy) { - return false; - } - if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) { - return false; - } - return isCurrentUserManager && !isDraftExpenseReport && !isApproved && !iouSettled; - }, [isPaidGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, isOnInstantSubmitPolicy, isOnSubmitAndClosePolicy, iouSettled]); + const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(iouReport, chatReport, policy), [iouReport, chatReport, policy]); + + const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, chatReport, policy), [iouReport, chatReport, policy]); + const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; /* @@ -353,9 +332,6 @@ export default withOnyx({ iouReport: { key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, }, - session: { - key: ONYXKEYS.SESSION, - }, transactions: { key: ONYXKEYS.COLLECTION.TRANSACTION, }, diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index f6534e075773..9f5b0ada4cb1 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -236,7 +236,7 @@ function isInstantSubmitEnabled(policy: OnyxEntry | EmptyObject): boolea /** * Checks if policy's approval mode is "optional", a.k.a. "Submit & Close" */ -function isSubmitAndClose(policy: OnyxEntry): boolean { +function isSubmitAndClose(policy: OnyxEntry | EmptyObject): boolean { return policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e062a4857e19..f84baee47c92 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5160,8 +5160,8 @@ function getAllAncestorReportActionIDs(report: Report | null | undefined): Ances return allAncestorIDs; } -function canBeAutoReimbursed(report: OnyxEntry, policy: OnyxEntry = null): boolean { - if (!policy) { +function canBeAutoReimbursed(report: OnyxEntry, policy: OnyxEntry | EmptyObject): boolean { + if (isEmptyObject(policy)) { return false; } type CurrencyType = (typeof CONST.DIRECT_REIMBURSEMENT_CURRENCIES)[number]; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cb3aa20ab6a7..1c0d7fef2729 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3677,10 +3677,73 @@ function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency: Report.notifyNewAction(params.chatReportID, managerID); } +function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) { + const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); + const iouCanceled = ReportUtils.isArchivedRoom(chatReport); + + if (isEmptyObject(iouReport)) { + return false; + } + + const isPayer = ReportUtils.isPayer( + { + email: currentUserEmail, + accountID: userAccountID, + }, + iouReport, + ); + + const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport); + const iouSettled = ReportUtils.isSettled(iouReport?.reportID); + + const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); + const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy); + return isPayer && !isDraftExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable; +} + +function canApproveIOU(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) { + if (isEmptyObject(chatReport)) { + return false; + } + const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport); + if (!isPaidGroupPolicy) { + return false; + } + + const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy); + const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy); + if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) { + return false; + } + + const managerID = iouReport?.managerID ?? 0; + const isCurrentUserManager = managerID === userAccountID; + const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); + + const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport); + const isApproved = ReportUtils.isReportApproved(iouReport); + const iouSettled = ReportUtils.isSettled(iouReport?.reportID); + + return isCurrentUserManager && !isDraftExpenseReport && !isApproved && !iouSettled; +} + +function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObject, excludedIOUReportID: string): boolean { + const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? ''); + + return Object.values(chatReportActions).some((action) => { + const iouReport = ReportUtils.getReport(action.childReportID ?? ''); + const policy = ReportUtils.getPolicy(iouReport?.policyID); + + const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy) || canApproveIOU(iouReport, chatReport, policy); + return action.childReportID?.toString() !== excludedIOUReportID && action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && shouldShowSettlementButton; + }); +} + function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) { const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID); const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.APPROVED); + const chatReport = ReportUtils.getReport(expenseReport.chatReportID); const optimisticReportActionsData: OnyxUpdate = { onyxMethod: Onyx.METHOD.MERGE, @@ -3703,12 +3766,21 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) { statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }, }; + + const optimisticChatReportData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.chatReportID}`, + value: { + hasOutstandingChildRequest: hasIOUToApproveOrPay(chatReport, expenseReport?.reportID ?? ''), + }, + }; + const optimisticNextStepData: OnyxUpdate = { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, value: optimisticNextStep, }; - const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionsData, optimisticNextStepData]; + const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionsData, optimisticNextStepData, optimisticChatReportData]; const successData: OnyxUpdate[] = [ { @@ -3732,6 +3804,13 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) { }, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.chatReportID}`, + value: { + hasOutstandingChildRequest: chatReport?.hasOutstandingChildRequest, + }, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, @@ -4331,4 +4410,6 @@ export { cancelPayment, navigateToStartStepIfScanFileCannotBeRead, savePreferredPaymentMethod, + canIOUBePaid, + canApproveIOU, };