diff --git a/src/CONST.ts b/src/CONST.ts index 8e356be6df298..66b51184852f0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -55,6 +55,7 @@ const chatTypes = { POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', SELF_DM: 'selfDM', + INVOICE: 'invoice', } as const; // Explicit type annotation is required @@ -805,6 +806,7 @@ const CONST = { EXPENSE: 'expense', IOU: 'iou', TASK: 'task', + INVOICE: 'invoice', }, CHAT_TYPE: chatTypes, WORKSPACE_CHAT_ROOMS: { @@ -854,6 +856,10 @@ const CONST = { SHARE: 'share', OWN: 'own', }, + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + BUSINESS: 'policy', + }, }, NEXT_STEP: { FINISHED: 'Finished!', @@ -1470,6 +1476,7 @@ const CONST = { PAY: 'pay', SPLIT: 'split', REQUEST: 'request', + INVOICE: 'invoice', SUBMIT: 'submit', TRACK: 'track', }, diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index c7a4ece0de972..8942bf97a7dde 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -60,7 +60,8 @@ function AvatarWithDisplayName({ const title = ReportUtils.getReportName(report); const subtitle = ReportUtils.getChatRoomSubtitle(report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); - const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report) || ReportUtils.isTrackExpenseReport(report); + const isMoneyRequestOrReport = + ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report) || ReportUtils.isTrackExpenseReport(report) || ReportUtils.isInvoiceReport(report); const icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy); const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails) as PersonalDetails[], false); diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index d61bd5186ecc6..710f2da5b7de4 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -38,7 +38,8 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); const isChatRoom = ReportUtils.isChatRoom(report); const isSelfDM = ReportUtils.isSelfDM(report); - const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM); + const isInvoiceRoom = ReportUtils.isInvoiceRoom(report); + const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM || isInvoiceRoom); const participantAccountIDs = report?.participantAccountIDs ?? []; const isMultipleParticipant = participantAccountIDs.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); @@ -58,6 +59,10 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP }; const welcomeHeroText = useMemo(() => { + if (isInvoiceRoom) { + return translate('reportActionsView.sayHello'); + } + if (isChatRoom) { return translate('reportActionsView.welcomeToRoom', {roomName: reportName}); } @@ -67,7 +72,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP } return translate('reportActionsView.sayHello'); - }, [isChatRoom, isSelfDM, translate, reportName]); + }, [isChatRoom, isInvoiceRoom, isSelfDM, translate, reportName]); return ( <> diff --git a/src/languages/en.ts b/src/languages/en.ts index 610603e227e7c..8b7737f008125 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -504,6 +504,7 @@ export default { beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` to chat about anything ${workspaceName} related.`, beginningOfChatHistoryUserRoomPartOne: 'Collaboration starts here! 🎉\nUse this space to chat about anything ', beginningOfChatHistoryUserRoomPartTwo: ' related.', + beginningOfChatHistoryInvoiceRoom: 'Collaboration starts here! 🎉 Use this room to view, discuss, and pay invoices.', beginningOfChatHistory: 'This is the beginning of your chat with ', beginningOfChatHistoryPolicyExpenseChatPartOne: 'Collaboration between ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' and ', @@ -519,6 +520,7 @@ export default { split: 'split an expense', submit: 'submit an expense', track: 'track an expense', + invoice: 'invoice an expense', }, }, reportAction: { diff --git a/src/languages/es.ts b/src/languages/es.ts index f9063fb560acb..82b5505b18f4b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -497,6 +497,7 @@ export default { beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` para chatear sobre cualquier cosa relacionada con ${workspaceName}.`, beginningOfChatHistoryUserRoomPartOne: '¡Este es el lugar para colaborar! 🎉\nUsa este espacio para chatear sobre cualquier cosa relacionada con ', beginningOfChatHistoryUserRoomPartTwo: '.', + beginningOfChatHistoryInvoiceRoom: '¡Este es el lugar para colaborar! 🎉 Utilice esta sala para ver, discutir y pagar facturas.', beginningOfChatHistory: 'Aquí comienzan tus conversaciones con ', beginningOfChatHistoryPolicyExpenseChatPartOne: '¡La colaboración entre ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ', @@ -512,6 +513,7 @@ export default { split: 'dividir un gasto', submit: 'presentar un gasto', track: 'rastrear un gasto', + invoice: 'facturar un gasto', }, }, reportAction: { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 56fca4bb7c0bb..71d66a5d2a313 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -227,9 +227,9 @@ function isTransactionThread(parentReportAction: OnyxEntry | Empty * Returns the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions with a childReportID. Returns a reportID if there is exactly one transaction thread for the report, and null otherwise. */ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): string | null { - // If the report is not an IOU or Expense report, it shouldn't be treated as one-transaction report. + // If the report is not an IOU, Expense report or an Invoice, it shouldn't be treated as one-transaction report. const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE) { + if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) { return null; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 764105f8c175b..a0da34416aad3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -288,6 +288,7 @@ type OptimisticChatReport = Pick< | 'description' | 'writeCapability' | 'avatarUrl' + | 'invoiceReceiver' > & { isOptimisticReport: true; }; @@ -673,6 +674,10 @@ function isChatReport(report: OnyxEntry | EmptyObject): boolean { return report?.type === CONST.REPORT.TYPE.CHAT; } +function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { + return report?.type === CONST.REPORT.TYPE.INVOICE; +} + /** * Checks if a report is an Expense report. */ @@ -860,6 +865,10 @@ function isPolicyExpenseChat(report: OnyxEntry | Participant | EmptyObje return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false); } +function isInvoiceRoom(report: OnyxEntry): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; +} + /** * Whether the provided report belongs to a Control policy and is an expense chat */ @@ -904,11 +913,18 @@ function isPaidGroupPolicyExpenseReport(report: OnyxEntry): boolean { return isExpenseReport(report) && isPaidGroupPolicy(report); } +/** + * Checks if the supplied report is an invoice report in Open state and status. + */ +function isOpenInvoiceReport(report: OnyxEntry | EmptyObject): boolean { + return isInvoiceReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN; +} + /** * Whether the provided report is a chat room */ function isChatRoom(report: OnyxEntry): boolean { - return isUserCreatedPolicyRoom(report) || isDefaultRoom(report); + return isUserCreatedPolicyRoom(report) || isDefaultRoom(report) || isInvoiceRoom(report); } /** @@ -1499,6 +1515,9 @@ function getRoomWelcomeMessage(report: OnyxEntry, isUserPolicyAdmin: boo } else if (isAnnounceRoom(report)) { welcomeMessage.phrase1 = Localize.translateLocal('reportActionsView.beginningOfChatHistoryAnnounceRoomPartOne', {workspaceName}); welcomeMessage.phrase2 = Localize.translateLocal('reportActionsView.beginningOfChatHistoryAnnounceRoomPartTwo', {workspaceName}); + } else if (isInvoiceRoom(report)) { + welcomeMessage.showReportName = false; + welcomeMessage.phrase1 = Localize.translateLocal('reportActionsView.beginningOfChatHistoryInvoiceRoom'); } else { // Message for user created rooms or other room types. welcomeMessage.phrase1 = Localize.translateLocal('reportActionsView.beginningOfChatHistoryUserRoomPartOne'); @@ -1955,6 +1974,25 @@ function getIcons( return [groupChatIcon]; } + if (isInvoiceReport(report)) { + const invoiceRoomReport = getReport(report.chatReportID); + const icons = [getWorkspaceIcon(invoiceRoomReport, policy)]; + + if (invoiceRoomReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + icons.push(...getIconsForParticipants([invoiceRoomReport?.invoiceReceiver.accountID], personalDetails)); + + return icons; + } + + const receiverPolicy = getPolicy(invoiceRoomReport?.invoiceReceiver?.policyID); + + if (!isEmptyObject(receiverPolicy)) { + icons.push(getWorkspaceIcon(invoiceRoomReport, receiverPolicy)); + } + + return icons; + } + return getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails); } @@ -2189,7 +2227,7 @@ function hasNonReimbursableTransactions(iouReportID: string | undefined): boolea function getMoneyRequestSpendBreakdown(report: OnyxEntry, allReportsDict: OnyxCollection = null): SpendBreakdown { const allAvailableReports = allReportsDict ?? allReports; let moneyRequestReport; - if (isMoneyRequestReport(report)) { + if (isMoneyRequestReport(report) || isInvoiceReport(report)) { moneyRequestReport = report; } if (allAvailableReports && report?.iouReportID) { @@ -2401,7 +2439,7 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry< return Localize.translateLocal('iou.payerSpentAmount', {payer: payerOrApproverName, amount: formattedAmount}); } - if (isProcessingReport(report) || isOpenExpenseReport(report) || moneyRequestTotal === 0) { + if (isProcessingReport(report) || isOpenExpenseReport(report) || isOpenInvoiceReport(report) || moneyRequestTotal === 0) { return Localize.translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount}); } @@ -2948,6 +2986,22 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; } +/** + * Get the invoice payer name based on its type: + * - Individual - a receiver display name. + * - Policy - a receiver policy name. + */ +function getInvoicePayerName(report: OnyxEntry): string { + const invoiceReceiver = report?.invoiceReceiver; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + + if (isIndividual) { + return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiver.accountID]); + } + + return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); +} + /** * Get the report action message for a report action. */ @@ -2964,6 +3018,29 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, parent return Str.removeSMSDomain(reportAction?.message?.[0]?.text ?? ''); } +/** + * Get the title for an invoice room. + */ +function getInvoicesChatName(report: OnyxEntry): string { + const invoiceReceiver = report?.invoiceReceiver; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; + const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + const isCurrentUserReceiver = + (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); + + if (isCurrentUserReceiver) { + return getPolicyName(report); + } + + if (isIndividual) { + return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); + } + + // TODO: Check this flow in a scope of the Invoice V0.3 + return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]); +} + /** * Get the title for a report. */ @@ -3027,7 +3104,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu formattedName = getPolicyExpenseChatName(report, policy); } - if (isMoneyRequestReport(report)) { + if (isMoneyRequestReport(report) || isInvoiceReport(report)) { formattedName = getMoneyRequestReportName(report, policy); } @@ -3039,6 +3116,10 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu formattedName = getDisplayNameForParticipant(currentUserAccountID, undefined, undefined, true); } + if (isInvoiceRoom(report)) { + formattedName = getInvoicesChatName(report); + } + if (formattedName) { return formattedName; } @@ -3073,6 +3154,9 @@ function getPayeeName(report: OnyxEntry): string | undefined { * Get either the policyName or domainName the chat is tied to */ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { + if (isInvoiceRoom(report)) { + return Localize.translateLocal('workspace.common.invoices'); + } if (isChatThread(report)) { return ''; } @@ -3092,6 +3176,9 @@ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { if (isArchivedRoom(report)) { return report?.oldPolicyName ?? ''; } + if (isInvoiceRoom(report)) { + return Localize.translateLocal('workspace.common.invoices'); + } return getPolicyName(report); } @@ -3112,6 +3199,10 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio return {}; } + if (isInvoiceReport(report)) { + return {reportName: `${getPolicyName(parentReport)} & ${getInvoicePayerName(parentReport)}`}; + } + return { reportName: getReportName(parentReport), workspaceName: getPolicyName(parentReport, true), @@ -4814,7 +4905,8 @@ function shouldReportBeInOptionList({ !isMoneyRequestReport(report) && !isTaskReport(report) && !isSelfDM(report) && - !isGroupChat(report)) + !isGroupChat(report) && + !isInvoiceRoom(report)) ) { return false; } @@ -5192,7 +5284,7 @@ function isGroupChatAdmin(report: OnyxEntry, accountID: number) { */ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, reportParticipants: number[], canUseTrackExpense = true, filterDeprecatedTypes = false): IOUType[] { // In any thread or task report, we do not allow any new expenses yet - if (isChatThread(report) || isTaskReport(report) || (!canUseTrackExpense && isSelfDM(report))) { + if (isChatThread(report) || isTaskReport(report) || (!canUseTrackExpense && isSelfDM(report)) || isInvoiceRoom(report) || isInvoiceReport(report)) { return []; } @@ -5272,8 +5364,36 @@ function temporary_getMoneyRequestOptions( * `domain` - Nobody can leave (it's auto-shared with domain members) * `dm` - Nobody can leave (it's auto-shared with users) * `private` - Anybody can leave (though you can only be invited to join) + * `invoice` - Invoice sender, invoice receiver and auto-invited admins cannot leave */ function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boolean { + if (isInvoiceRoom(report)) { + const invoiceReport = getReport(report?.iouReportID ?? ''); + + if (invoiceReport?.ownerAccountID === currentUserAccountID) { + return false; + } + + if (invoiceReport?.managerID === currentUserAccountID) { + return false; + } + + const isSenderPolicyAdmin = getPolicy(report?.policyID)?.role === CONST.POLICY.ROLE.ADMIN; + + if (isSenderPolicyAdmin) { + return false; + } + + const isReceiverPolicyAdmin = + report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; + + if (isReceiverPolicyAdmin) { + return false; + } + + return true; + } + if (!report?.visibility) { if ( report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS || @@ -5343,6 +5463,10 @@ function shouldReportShowSubscript(report: OnyxEntry): boolean { return true; } + if (isInvoiceRoom(report)) { + return true; + } + return false; } @@ -5721,7 +5845,9 @@ function isReportParticipant(accountID: number, report: OnyxEntry): bool } function shouldUseFullTitleToDisplay(report: OnyxEntry): boolean { - return isMoneyRequestReport(report) || isPolicyExpenseChat(report) || isChatRoom(report) || isChatThread(report) || isTaskReport(report) || isGroupChat(report); + return ( + isMoneyRequestReport(report) || isPolicyExpenseChat(report) || isChatRoom(report) || isChatThread(report) || isTaskReport(report) || isGroupChat(report) || isInvoiceReport(report) + ); } function getRoom(type: ValueOf, policyID: string): OnyxEntry | undefined { @@ -6330,6 +6456,8 @@ export { getWorkspaceChats, getWorkspaceIcon, goBackToDetailsPage, + getInvoicePayerName, + getInvoicesChatName, getPayeeName, hasActionsWithErrors, hasAutomatedExpensifyAccountIDs, @@ -6418,6 +6546,9 @@ export { isValidReport, isValidReportIDFromPath, isWaitingForAssigneeToCompleteTask, + isInvoiceRoom, + isInvoiceReport, + isOpenInvoiceReport, canWriteInReport, navigateToDetailsPage, navigateToPrivateNotes, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 65e2e4f35c4a9..07ff9420b60ef 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5309,6 +5309,14 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat return false; } + if (ReportUtils.isInvoiceReport(iouReport)) { + if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + return chatReport?.invoiceReceiver?.accountID === userAccountID; + } + + return PolicyUtils.getPolicy(chatReport?.invoiceReceiver?.policyID).role === CONST.POLICY.ROLE.ADMIN; + } + const isPayer = ReportUtils.isPayer( { email: currentUserEmail, diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 3a7f4ad6f28dd..5ea00361d8c41 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -75,6 +75,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD const isChatThread = useMemo(() => ReportUtils.isChatThread(report), [report]); const isArchivedRoom = useMemo(() => ReportUtils.isArchivedRoom(report), [report]); const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(report), [report]); + const isInvoiceReport = useMemo(() => ReportUtils.isInvoiceReport(report), [report]); const canEditReportDescription = useMemo(() => ReportUtils.canEditReportDescription(report, policy), [report, policy]); const shouldShowReportDescription = isChatRoom && (canEditReportDescription || report.description !== ''); @@ -183,7 +184,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD }); // Prevent displaying private notes option for threads and task reports - if (!isChatThread && !isMoneyRequestReport && !ReportUtils.isTaskReport(report)) { + if (!isChatThread && !isMoneyRequestReport && !isInvoiceReport && !ReportUtils.isTaskReport(report)) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.PRIVATE_NOTES, translationKey: 'privateNotes.title', @@ -196,19 +197,20 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD return items; }, [ + isSelfDM, + isGroupDMChat, isArchivedRoom, - participants.length, + isGroupChat, + isDefaultRoom, isChatThread, - isMoneyRequestReport, - report, - isGroupDMChat, isPolicyEmployee, isUserCreatedPolicyRoom, - session, - isSelfDM, - isDefaultRoom, + participants.length, + report, + isMoneyRequestReport, + isInvoiceReport, activeChatMembers.length, - isGroupChat, + session, ]); const displayNamesWithTooltips = useMemo(() => { @@ -266,7 +268,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD - {isMoneyRequestReport ? ( + {isMoneyRequestReport || isInvoiceReport ? ( IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? ''), }, + [CONST.IOU.TYPE.INVOICE]: { + icon: Expensicons.Invoice, + text: translate('workspace.invoices.sendInvoice'), + onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? ''), + }, }; return ReportUtils.temporary_getMoneyRequestOptions(report, policy, reportParticipantIDs ?? [], canUseTrackExpense).map((option) => ({ diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 1600bf67d2301..84a689c6f03c0 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -805,7 +805,7 @@ function ReportActionItem({ ); } - if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) { + if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report) || ReportUtils.isInvoiceReport(report)) { return ( {transactionThreadReport && !isEmptyObject(transactionThreadReport) ? ( diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index dda17e1e83d37..042f217e057db 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -84,8 +84,11 @@ function ReportActionItemSingle({ const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID ?? -1] ?? {}; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); - const displayAllActors = useMemo(() => action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && iouReport, [action?.actionName, iouReport]); - const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors); + const displayAllActors = useMemo( + () => action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && iouReport && !ReportUtils.isInvoiceReport(iouReport), + [action?.actionName, iouReport], + ); + const isWorkspaceActor = ReportUtils.isInvoiceReport(iouReport ?? {}) || (ReportUtils.isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors)); let avatarSource = UserUtils.getAvatar(avatar ?? '', actorAccountID); if (isWorkspaceActor) { diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 95c7b09ce1c1c..db58e4220cba1 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -67,6 +67,7 @@ function IOURequestStartPage({ [CONST.IOU.TYPE.PAY]: translate('iou.paySomeone', {name: ReportUtils.getPayeeName(report)}), [CONST.IOU.TYPE.SPLIT]: translate('iou.splitExpense'), [CONST.IOU.TYPE.TRACK]: translate('iou.trackExpense'), + [CONST.IOU.TYPE.INVOICE]: translate('workspace.invoices.sendInvoice'), }; const transactionRequestType = useRef(TransactionUtils.getRequestType(transaction)); const {canUseP2PDistanceRequests} = usePermissions(iouType); diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 6963d2eba8b99..f37a0c3655f30 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -29,6 +29,16 @@ type Participant = { role?: 'admin' | 'member'; }; +type InvoiceReceiver = + | { + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + accountID: number; + } + | { + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS; + policyID: string; + }; + type Participants = Record; type Report = OnyxCommon.OnyxValueWithOfflineFeedback< @@ -129,6 +139,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** Report cached total */ cachedTotal?: string; + /** Invoice room receiver data */ + invoiceReceiver?: InvoiceReceiver; + lastMessageTranslationKey?: string; parentReportID?: string; parentReportActionID?: string;