diff --git a/src/CONST.ts b/src/CONST.ts index dd6eafc7f0e6..eb895b2ef554 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -600,6 +600,7 @@ const CONST = { ADMINS: '#admins', }, STATE: { + OPEN: 'OPEN', SUBMITTED: 'SUBMITTED', PROCESSING: 'PROCESSING', }, diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index 09a5130adc0b..880e46b2592a 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -77,7 +77,7 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(policy, 'role') === CONST.POLICY.ROLE.ADMIN; const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(session, 'accountID', null) === moneyRequestReport.managerID; const isPayer = policyType === CONST.POLICY.TYPE.CORPORATE ? isPolicyAdmin && isApproved : isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager); - const isDraft = ReportUtils.isReportDraft(moneyRequestReport); + const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport); const shouldShowSettlementButton = useMemo( () => isPayer && !isDraft && !isSettled && !moneyRequestReport.isWaitingOnBankAccount && reimbursableTotal !== 0 && !ReportUtils.isArchivedRoom(chatReport), [isPayer, isDraft, isSettled, moneyRequestReport, reimbursableTotal, chatReport], @@ -89,7 +89,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt return isManager && !isDraft && !isApproved && !isSettled; }, [policyType, isManager, isDraft, isApproved, isSettled]); const shouldShowSubmitButton = isDraft && reimbursableTotal !== 0; - const shouldShowNextSteps = isDraft && nextStep && !_.isEmpty(nextStep.message); + const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; + const shouldShowNextSteps = isFromPaidPolicy && nextStep && !_.isEmpty(nextStep.message); const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextSteps; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableTotal, moneyRequestReport.currency); diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index f04029182d45..8cb8406e8a0d 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -120,7 +120,7 @@ function ReportPreview(props) { const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(props.action); const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment', ''); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.chatReport); - const isReportDraft = isPolicyExpenseChat && ReportUtils.isReportDraft(props.iouReport); + const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(props.iouReport); const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(props.iouReportID); const numberOfScanningReceipts = _.filter(transactionsWithReceipts, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length; @@ -143,7 +143,7 @@ function ReportPreview(props) { scanningReceipts: numberOfScanningReceipts, }); - const shouldShowSubmitButton = isReportDraft && reimbursableSpend !== 0; + const shouldShowSubmitButton = isDraftExpenseReport && reimbursableSpend !== 0; const getDisplayAmount = () => { if (hasPendingWaypoints) { @@ -189,7 +189,7 @@ function ReportPreview(props) { const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); const shouldShowSettlementButton = ReportUtils.isControlPolicyExpenseChat(props.chatReport) ? props.policy.role === CONST.POLICY.ROLE.ADMIN && ReportUtils.isReportApproved(props.iouReport) && !iouSettled && !iouCanceled - : !_.isEmpty(props.iouReport) && isCurrentUserManager && !isReportDraft && !iouSettled && !iouCanceled && !props.iouReport.isWaitingOnBankAccount && reimbursableSpend !== 0; + : !_.isEmpty(props.iouReport) && isCurrentUserManager && !isDraftExpenseReport && !iouSettled && !iouCanceled && !props.iouReport.isWaitingOnBankAccount && reimbursableSpend !== 0; return ( diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d93661778b83..e2b883a735bf 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -110,6 +110,7 @@ type OptimisticExpenseReport = Pick< | 'reportName' | 'state' | 'stateNum' + | 'statusNum' | 'total' | 'notificationPreference' | 'parentReportID' @@ -545,6 +546,13 @@ function isReportApproved(report: OnyxEntry): boolean { return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS.APPROVED; } +/** + * Checks if the supplied report is an expense report in Open state and status. + */ +function isDraftExpenseReport(report: OnyxEntry): boolean { + return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; +} + /** * Given a collection of reports returns them sorted by last read */ @@ -1669,7 +1677,6 @@ function getPolicyExpenseChatName(report: OnyxEntry, policy: OnyxEntry

, policy: OnyxEntry | undefined = undefined): string { const moneyRequestTotal = getMoneyRequestReimbursableTotal(report); @@ -1688,7 +1695,7 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry< return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName, amount: formattedAmount}); } - if (!!report?.hasOutstandingIOU || moneyRequestTotal === 0) { + if (!!report?.hasOutstandingIOU || isDraftExpenseReport(report) || moneyRequestTotal === 0) { return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName, amount: formattedAmount}); } @@ -2545,9 +2552,16 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa const storedTotal = total * -1; const policyName = getPolicyName(allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`]); const formattedTotal = CurrencyUtils.convertToDisplayString(storedTotal, currency); + const policy = getPolicy(policyID); // The expense report is always created with the policy's output currency - const outputCurrency = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.outputCurrency ?? CONST.CURRENCY.USD; + const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; + const isFree = policy?.type === CONST.POLICY.TYPE.FREE; + + // Define the state and status of the report based on whether the policy is free or paid + const state = isFree ? CONST.REPORT.STATE.SUBMITTED : CONST.REPORT.STATE.OPEN; + const stateNum = isFree ? CONST.REPORT.STATE_NUM.PROCESSING : CONST.REPORT.STATE_NUM.OPEN; + const statusNum = isFree ? CONST.REPORT.STATUS.SUBMITTED : CONST.REPORT.STATUS.OPEN; return { reportID: generateReportID(), @@ -2560,8 +2574,9 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa // We don't translate reportName because the server response is always in English reportName: `${policyName} owes ${formattedTotal}`, - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, + state, + stateNum, + statusNum, total: storedTotal, notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, parentReportID: chatReportID, @@ -4134,10 +4149,6 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry) }); } -function isReportDraft(report: OnyxEntry): boolean { - return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; -} - /** * Return room channel log display message */ @@ -4372,7 +4383,7 @@ export { getIOUReportActionDisplayMessage, isWaitingForAssigneeToCompleteTask, isGroupChat, - isReportDraft, + isDraftExpenseReport, shouldUseFullTitleToDisplay, parseReportRouteParams, getReimbursementQueuedActionMessage, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 3daf6b362935..c0466f8c2604 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -459,6 +459,15 @@ function getMoneyRequestInformation( const isNewIOUReport = !chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport); let iouReport = isNewIOUReport ? null : allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]; + // If the linked expense report on paid policy is not draft, we need to create a new draft expense report + if (isPolicyExpenseChat && iouReport) { + const policyType = ReportUtils.getPolicy(iouReport.policyID).type || ''; + const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; + if (isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport)) { + iouReport = null; + } + } + if (iouReport) { if (isPolicyExpenseChat) { iouReport = {...iouReport}; @@ -2659,30 +2668,46 @@ function approveMoneyRequest(expenseReport) { */ function submitReport(expenseReport) { const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport.total, expenseReport.currency, expenseReport.reportID); + const parentReport = ReportUtils.getReport(expenseReport.parentReportID); - const optimisticReportActionsData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, - value: { - [optimisticSubmittedReportAction.reportActionID]: { - ...optimisticSubmittedReportAction, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [optimisticSubmittedReportAction.reportActionID]: { + ...optimisticSubmittedReportAction, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, }, }, - }; - const optimisticIOUReportData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, - value: { - ...expenseReport, - lastMessageText: optimisticSubmittedReportAction.message[0].text, - lastMessageHtml: optimisticSubmittedReportAction.message[0].html, - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, + value: { + ...expenseReport, + lastMessageText: lodashGet(optimisticSubmittedReportAction, 'message.0.text', ''), + lastMessageHtml: lodashGet(optimisticSubmittedReportAction, 'message.0.html', ''), + state: CONST.REPORT.STATE.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.PROCESSING, + statusNum: CONST.REPORT.STATUS.SUBMITTED, + }, }, - }; - const optimisticData = [optimisticIOUReportData, optimisticReportActionsData]; + ...(parentReport.reportID + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, + value: { + ...parentReport, + hasOutstandingIOU: false, + hasOutstandingChildRequest: false, + iouReportID: null, + }, + }, + ] + : []), + ]; const successData = [ { @@ -2714,6 +2739,19 @@ function submitReport(expenseReport) { stateNum: CONST.REPORT.STATE_NUM.OPEN, }, }, + ...(parentReport.reportID + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, + value: { + hasOutstandingIOU: parentReport.hasOutstandingIOU, + hasOutstandingChildRequest: parentReport.hasOutstandingChildRequest, + iouReportID: expenseReport.reportID, + }, + }, + ] + : []), ]; API.write(