diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 086580d60e57..06000716b851 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -599,7 +599,7 @@ function MoneyRequestConfirmationList({ const formattedSelectedParticipants = selectedParticipants.map((participant) => ({ ...participant, isSelected: false, - isDisabled: !participant.isPolicyExpenseChat && !participant.isSelfDM && ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), + isDisabled: !participant.isInvoiceRoom && !participant.isPolicyExpenseChat && !participant.isSelfDM && ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); options.push({ title: translate('common.to'), @@ -1148,7 +1148,7 @@ function MoneyRequestConfirmationList({ style={styles.moneyRequestMenuItem} labelStyle={styles.mt2} titleStyle={styles.flex1} - disabled={didConfirm || !canUpdateSenderWorkspace} + disabled={didConfirm} /> )} {isDistanceRequest && ( diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f1bdac4a2494..5f72a8712893 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -694,6 +694,7 @@ function createOption( result.isDefaultRoom = ReportUtils.isDefaultRoom(report); result.isArchivedRoom = ReportUtils.isArchivedRoom(report); result.isExpenseReport = ReportUtils.isExpenseReport(report); + result.isInvoiceRoom = ReportUtils.isInvoiceRoom(report); result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); result.isThread = ReportUtils.isChatThread(report); result.isTaskReport = ReportUtils.isTaskReport(report); @@ -806,6 +807,9 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { // Update text & alternateText because createOption returns workspace name only if report is owned by the user if (option.isSelfDM) { option.alternateText = Localize.translateLocal('reportActionsView.yourSpace'); + } else if (option.isInvoiceRoom) { + option.text = ReportUtils.getReportName(report); + option.alternateText = Localize.translateLocal('workspace.common.invoices'); } else { option.text = ReportUtils.getPolicyName(report); option.alternateText = Localize.translateLocal('workspace.common.workspace'); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 295ea99a57aa..8911a0623479 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -433,6 +433,7 @@ type OptionData = { parentReportAction?: OnyxEntry; displayNamesWithTooltips?: DisplayNameWithTooltips | null; isDefaultRoom?: boolean; + isInvoiceRoom?: boolean; isExpenseReport?: boolean; isOptimisticPersonalDetail?: boolean; selected?: boolean; @@ -5515,13 +5516,21 @@ function isGroupChatAdmin(report: OnyxEntry, accountID: number) { * - Self DMs * - own policy expense chats * - open and processing expense reports tied to own policy expense chat - * + * - Send invoice option should show for: + * - invoice rooms if the user is an admin of the sender workspace * None of the options should show in chat threads or if there is some special Expensify account * as a participant of the report. */ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, reportParticipants: number[], filterDeprecatedTypes = false): IOUType[] { // In any thread or task report, we do not allow any new expenses yet - if (isChatThread(report) || isTaskReport(report) || isInvoiceRoom(report) || isInvoiceReport(report)) { + if (isChatThread(report) || isTaskReport(report) || isInvoiceReport(report)) { + return []; + } + + if (isInvoiceRoom(report)) { + if (isPolicyAdmin(report?.policyID ?? '', allPolicies)) { + return [CONST.IOU.TYPE.INVOICE]; + } return []; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c949c54ecc18..2464dbff7dbd 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1650,8 +1650,8 @@ function getSendInvoiceInformation( const {amount = 0, currency = '', created = '', merchant = '', category = '', tag = '', taxCode = '', taxAmount = 0, billable, comment, participants} = transaction ?? {}; const trimmedComment = (comment?.comment ?? '').trim(); const senderWorkspaceID = participants?.find((participant) => participant?.isSender)?.policyID ?? ''; - const receiverParticipant = participants?.find((participant) => participant?.accountID); - const receiverAccountID = receiverParticipant?.accountID ?? -1; + const receiverParticipant = participants?.find((participant) => participant?.accountID) ?? invoiceChatReport?.invoiceReceiver; + const receiverAccountID = receiverParticipant && 'accountID' in receiverParticipant && receiverParticipant.accountID ? receiverParticipant.accountID : -1; let receiver = ReportUtils.getPersonalDetailsForAccountID(receiverAccountID); let optimisticPersonalDetailListAction = {}; @@ -1704,11 +1704,12 @@ function getSendInvoiceInformation( // STEP 4: Add optimistic personal details for participant const shouldCreateOptimisticPersonalDetails = isNewChatReport && !allPersonalDetails[receiverAccountID]; if (shouldCreateOptimisticPersonalDetails) { + const receiverLogin = receiverParticipant && 'login' in receiverParticipant && receiverParticipant.login ? receiverParticipant.login : ''; receiver = { accountID: receiverAccountID, avatar: UserUtils.getDefaultAvatarURL(receiverAccountID), - displayName: LocalePhoneNumber.formatPhoneNumber(receiverParticipant?.login ?? ''), - login: receiverParticipant?.login, + displayName: LocalePhoneNumber.formatPhoneNumber(receiverLogin), + login: receiverLogin, isOptimisticPersonalDetail: true, }; @@ -6497,22 +6498,20 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On if (ReportUtils.isPolicyExpenseChat(chatReport) || shouldAddAsReport) { participants = [{accountID: 0, reportID: chatReport?.reportID, isPolicyExpenseChat: ReportUtils.isPolicyExpenseChat(chatReport), selected: true}]; + } else if (ReportUtils.isInvoiceRoom(chatReport)) { + participants = [ + {reportID: chatReport?.reportID, selected: true}, + { + policyID: chatReport?.policyID, + isSender: true, + selected: false, + }, + ]; } else { const chatReportOtherParticipants = Object.keys(chatReport?.participants ?? {}) .map(Number) .filter((accountID) => accountID !== currentUserAccountID); participants = chatReportOtherParticipants.map((accountID) => ({accountID, selected: true})); - - if (ReportUtils.isInvoiceRoom(chatReport)) { - participants = [ - ...participants, - { - policyID: chatReport?.policyID, - isSender: true, - selected: false, - }, - ]; - } } Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index df5d9c3b31f9..26aefe24bb20 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -147,7 +147,7 @@ function AttachmentPickerWithMenuItems({ onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? ''), }, [CONST.IOU.TYPE.INVOICE]: { - icon: Expensicons.Invoice, + icon: Expensicons.InvoiceGeneric, text: translate('workspace.invoices.sendInvoice'), onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? ''), }, diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index 82bf4d6efc04..8b0ca935f4b3 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -7,6 +7,7 @@ type Participant = { login?: string; displayName?: string; isPolicyExpenseChat?: boolean; + isInvoiceRoom?: boolean; isOwnPolicyExpenseChat?: boolean; chatType?: ValueOf; reportID?: string;