From ebecd03526adeb110ecbecae152ab90a8f62c94f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 8 Jul 2024 16:00:01 +0200 Subject: [PATCH 1/9] Implement Pay as business functionality --- src/components/MoneyReportHeader.tsx | 4 +- .../ReportActionItem/ReportPreview.tsx | 16 ++++- src/components/SettlementButton.tsx | 58 +++++++++++++------ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/API/parameters/PayInvoiceParams.ts | 1 + src/libs/ReportUtils.ts | 43 ++++++++++++-- src/libs/actions/IOU.ts | 48 +++++++++++---- src/libs/actions/Policy/Policy.ts | 2 +- .../home/report/ReportActionItemSingle.tsx | 36 +++++++----- 10 files changed, 157 insertions(+), 53 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 80ad2890afaa..1a6fa5326234 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -137,7 +137,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount; const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); - const confirmPayment = (type?: PaymentMethodType | undefined) => { + const confirmPayment = (type?: PaymentMethodType | undefined, payAsBusiness?: boolean) => { if (!type || !chatReport) { return; } @@ -146,7 +146,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { setIsHoldMenuVisible(true); } else if (ReportUtils.isInvoiceReport(moneyRequestReport)) { - IOU.payInvoice(type, chatReport, moneyRequestReport); + IOU.payInvoice(type, chatReport, moneyRequestReport, payAsBusiness); } else { IOU.payMoneyRequest(type, chatReport, moneyRequestReport, true); } diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index d986af8f5cf3..9693b982ec4a 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -138,6 +138,7 @@ function ReportPreview({ const moneyRequestComment = action?.childLastMoneyRequestComment ?? ''; const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); + const isInvoiceRoom = ReportUtils.isInvoiceRoom(chatReport); const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport); const isApproved = ReportUtils.isReportApproved(iouReport, action); @@ -177,7 +178,7 @@ function ReportPreview({ [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled], ); - const confirmPayment = (type: PaymentMethodType | undefined) => { + const confirmPayment = (type: PaymentMethodType | undefined, payAsBusiness?: boolean) => { if (!type) { return; } @@ -187,7 +188,7 @@ function ReportPreview({ setIsHoldMenuVisible(true); } else if (chatReport && iouReport) { if (ReportUtils.isInvoiceReport(iouReport)) { - IOU.payInvoice(type, chatReport, iouReport); + IOU.payInvoice(type, chatReport, iouReport, payAsBusiness); } else { IOU.payMoneyRequest(type, chatReport, iouReport); } @@ -246,7 +247,16 @@ function ReportPreview({ if (isScanning) { return translate('common.receipt'); } - let payerOrApproverName = isPolicyExpenseChat ? ReportUtils.getPolicyName(chatReport) : ReportUtils.getDisplayNameForParticipant(managerID, true); + + let payerOrApproverName; + if (isPolicyExpenseChat) { + payerOrApproverName = ReportUtils.getPolicyName(chatReport); + } else if (isInvoiceRoom) { + payerOrApproverName = ReportUtils.getInvoicePayerName(chatReport); + } else { + payerOrApproverName = ReportUtils.getDisplayNameForParticipant(managerID, true); + } + if (isApproved) { return translate('iou.managerApproved', {manager: payerOrApproverName}); } diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index 7a7e4e584363..3cda457fb219 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -5,11 +5,13 @@ import {useOnyx, withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as BankAccounts from '@userActions/BankAccounts'; import * as IOU from '@userActions/IOU'; +import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; @@ -41,7 +43,7 @@ type SettlementButtonOnyxProps = { type SettlementButtonProps = SettlementButtonOnyxProps & { /** Callback to execute when this button is pressed. Receives a single payment type argument. */ - onPress: (paymentType?: PaymentMethodType) => void; + onPress: (paymentType?: PaymentMethodType, payAsBusiness?: boolean) => void; /** The route to redirect if user does not have a payment method setup */ enablePaymentsRoute: EnablePaymentsRoute; @@ -143,6 +145,9 @@ function SettlementButton({ }: SettlementButtonProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + + const primaryPolicy = useMemo(() => PolicyActions.getPrimaryPolicy(activePolicyID), [activePolicyID]); const session = useSession(); // The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here. @@ -199,20 +204,39 @@ function SettlementButton({ } if (isInvoiceReport) { - buttonOptions.push({ - text: translate('iou.settlePersonal', {formattedAmount}), - icon: Expensicons.User, - value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, - backButtonText: translate('iou.individual'), - subMenuItems: [ - { - text: translate('iou.payElsewhere', {formattedAmount: ''}), - icon: Expensicons.Cash, - value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, - onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.ELSEWHERE), - }, - ], - }); + if (ReportUtils.isIndividualInvoiceRoom(chatReport)) { + buttonOptions.push({ + text: translate('iou.settlePersonal', {formattedAmount}), + icon: Expensicons.User, + value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + backButtonText: translate('iou.individual'), + subMenuItems: [ + { + text: translate('iou.payElsewhere', {formattedAmount: ''}), + icon: Expensicons.Cash, + value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.ELSEWHERE), + }, + ], + }); + } + + if (PolicyUtils.isPolicyAdmin(primaryPolicy) && PolicyUtils.isPaidGroupPolicy(primaryPolicy)) { + buttonOptions.push({ + text: translate('iou.settleBusiness', {formattedAmount}), + icon: Expensicons.Building, + value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + backButtonText: translate('iou.business'), + subMenuItems: [ + { + text: translate('iou.payElsewhere', {formattedAmount: ''}), + icon: Expensicons.Cash, + value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, true), + }, + ], + }); + } } if (shouldShowApproveButton) { @@ -226,7 +250,7 @@ function SettlementButton({ return buttonOptions; // We don't want to reorder the options when the preferred payment method changes while the button is still visible // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currency, formattedAmount, iouReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton, shouldDisableApproveButton]); + }, [currency, formattedAmount, iouReport, chatReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton, shouldDisableApproveButton]); const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => { if (policy && SubscriptionUtils.shouldRestrictUserBillableActions(policy.id)) { @@ -259,7 +283,7 @@ function SettlementButton({ return ( onPress(paymentType)} enablePaymentsRoute={enablePaymentsRoute} addBankAccountRoute={addBankAccountRoute} addDebitCardRoute={addDebitCardRoute} diff --git a/src/languages/en.ts b/src/languages/en.ts index e3e080f26201..94dbf7d33e04 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -705,6 +705,7 @@ export default { settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', individual: 'Individual', + business: 'Business', settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} with Expensify` : `Pay with Expensify`), settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as an individual` : `Pay as an individual`), settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 9e104ca9b1bb..1a4632f2a1a7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -698,6 +698,7 @@ export default { settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', individual: 'Individual', + business: 'Empresa', settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} con Expensify` : `Pagar con Expensify`), settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pago ${formattedAmount} como individuo` : `Pago individual`), settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount}`, diff --git a/src/libs/API/parameters/PayInvoiceParams.ts b/src/libs/API/parameters/PayInvoiceParams.ts index 4c6633749adb..a6b9746d87bc 100644 --- a/src/libs/API/parameters/PayInvoiceParams.ts +++ b/src/libs/API/parameters/PayInvoiceParams.ts @@ -4,6 +4,7 @@ type PayInvoiceParams = { reportID: string; reportActionID: string; paymentMethodType: PaymentMethodType; + payAsBusiness: boolean; }; export default PayInvoiceParams; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 342e2439ed66..1e1f72adb37d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -902,11 +902,20 @@ function isTripRoom(report: OnyxEntry): boolean { return isChatReport(report) && getChatType(report) === CONST.REPORT.CHAT_TYPE.TRIP_ROOM; } +function isIndividualInvoiceRoom(report: OnyxEntry): boolean { + return isInvoiceRoom(report) && report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; +} + function isCurrentUserInvoiceReceiver(report: OnyxEntry): boolean { if (report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { return currentUserAccountID === report.invoiceReceiver.accountID; } + if (report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS) { + const policy = PolicyUtils.getPolicy(report.invoiceReceiver.policyID); + return PolicyUtils.isPolicyAdmin(policy); + } + return false; } @@ -2050,9 +2059,15 @@ function getIcons( if (report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { icons.push(...getIconsForParticipants([report?.invoiceReceiver.accountID], personalDetails)); } else { - const receiverPolicy = getPolicy(report?.invoiceReceiver?.policyID); + const receiverPolicyID = report?.invoiceReceiver?.policyID; + const receiverPolicy = getPolicy(receiverPolicyID); if (!isEmptyObject(receiverPolicy)) { - icons.push(getWorkspaceIcon(report, receiverPolicy)); + icons.push({ + source: receiverPolicy?.avatarURL ?? getDefaultWorkspaceAvatar(receiverPolicy.name), + type: CONST.ICON_TYPE_WORKSPACE, + name: receiverPolicy.name, + id: receiverPolicyID, + }); } } } @@ -2124,10 +2139,16 @@ function getIcons( return icons; } - const receiverPolicy = getPolicy(invoiceRoomReport?.invoiceReceiver?.policyID); + const receiverPolicyID = invoiceRoomReport?.invoiceReceiver?.policyID; + const receiverPolicy = getPolicy(receiverPolicyID); if (!isEmptyObject(receiverPolicy)) { - icons.push(getWorkspaceIcon(invoiceRoomReport, receiverPolicy)); + icons.push({ + source: receiverPolicy?.avatarURL ?? getDefaultWorkspaceAvatar(receiverPolicy.name), + type: CONST.ICON_TYPE_WORKSPACE, + name: receiverPolicy.name, + id: receiverPolicyID, + }); } return icons; @@ -2592,7 +2613,17 @@ function getMoneyRequestReportName(report: OnyxEntry, policy?: OnyxEntry const moneyRequestTotal = getMoneyRequestSpendBreakdown(report).totalDisplaySpend; const formattedAmount = CurrencyUtils.convertToDisplayString(moneyRequestTotal, report?.currency); - let payerOrApproverName = isExpenseReport(report) ? getPolicyName(report, false, policy) : getDisplayNameForParticipant(report?.managerID) ?? ''; + + let payerOrApproverName; + if (isExpenseReport(report)) { + payerOrApproverName = getPolicyName(report, false, policy); + } else if (isInvoiceReport(report)) { + const chatReport = getReportOrDraftReport(report?.chatReportID); + payerOrApproverName = getInvoicePayerName(chatReport); + } else { + payerOrApproverName = getDisplayNameForParticipant(report?.managerID) ?? ''; + } + const payerPaidAmountMessage = Localize.translateLocal('iou.payerPaidAmount', { payer: payerOrApproverName, amount: formattedAmount, @@ -5522,6 +5553,7 @@ function getChatByParticipants(newParticipantList: number[], reports: OnyxCollec isChatThread(report) || isTaskReport(report) || isMoneyRequestReport(report) || + isInvoiceReport(report) || isChatRoom(report) || isPolicyExpenseChat(report) || (isGroupChat(report) && !shouldIncludeGroupChats) @@ -7313,6 +7345,7 @@ export { getChatUsedForOnboarding, findPolicyExpenseChatByPolicyID, hasOnlyNonReimbursableTransactions, + isIndividualInvoiceRoom, }; export type { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 48c70021cacc..42381d9008a7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -291,6 +291,12 @@ Onyx.connect({ }, }); +let primaryPolicyID: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, + callback: (value) => (primaryPolicyID = value), +}); + /** * Get the report or draft report given a reportID */ @@ -5938,13 +5944,22 @@ function getSendMoneyParams( } function getPayMoneyRequestParams( - chatReport: OnyxTypes.Report, + initialChatReport: OnyxTypes.Report, iouReport: OnyxTypes.Report, recipient: Participant, paymentMethodType: PaymentMethodType, full: boolean, + payAsBusiness?: boolean, ): PayMoneyRequestData { const isInvoiceReport = ReportUtils.isInvoiceReport(iouReport); + let chatReport = initialChatReport; + + if (ReportUtils.isIndividualInvoiceRoom(chatReport) && payAsBusiness && primaryPolicyID) { + const existingB2BInvoiceRoom = ReportUtils.getInvoiceChatByParticipants(chatReport.policyID ?? '', primaryPolicyID); + if (existingB2BInvoiceRoom) { + chatReport = existingB2BInvoiceRoom; + } + } let total = (iouReport.total ?? 0) - (iouReport.nonReimbursableTotal ?? 0); if (ReportUtils.hasHeldExpenses(iouReport.reportID) && !full && !!iouReport.unheldTotal) { @@ -5977,19 +5992,27 @@ function getPayMoneyRequestParams( optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithExpensify: paymentMethodType === CONST.IOU.PAYMENT_TYPE.VBBA}); } + const optimisticChatReport = { + ...chatReport, + lastReadTime: DateUtils.getDBTime(), + lastVisibleActionCreated: optimisticIOUReportAction.created, + hasOutstandingChildRequest: false, + iouReportID: null, + lastMessageText: ReportActionsUtils.getReportActionText(optimisticIOUReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticIOUReportAction), + }; + if (ReportUtils.isIndividualInvoiceRoom(chatReport) && payAsBusiness && primaryPolicyID) { + optimisticChatReport.invoiceReceiver = { + type: CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS, + policyID: primaryPolicyID, + }; + } + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastReadTime: DateUtils.getDBTime(), - lastVisibleActionCreated: optimisticIOUReportAction.created, - hasOutstandingChildRequest: false, - iouReportID: null, - lastMessageText: ReportActionsUtils.getReportActionText(optimisticIOUReportAction), - lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticIOUReportAction), - }, + value: optimisticChatReport, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -6611,19 +6634,20 @@ function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.R Navigation.dismissModalWithReport(chatReport); } -function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes.Report, invoiceReport: OnyxTypes.Report) { +function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes.Report, invoiceReport: OnyxTypes.Report, payAsBusiness = false) { const recipient = {accountID: invoiceReport.ownerAccountID}; const { optimisticData, successData, failureData, params: {reportActionID}, - } = getPayMoneyRequestParams(chatReport, invoiceReport, recipient, paymentMethodType, true); + } = getPayMoneyRequestParams(chatReport, invoiceReport, recipient, paymentMethodType, true, payAsBusiness); const params: PayInvoiceParams = { reportID: invoiceReport.reportID, reportActionID, paymentMethodType, + payAsBusiness, }; API.write(WRITE_COMMANDS.PAY_INVOICE, params, {optimisticData, successData, failureData}); diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index acd42b6202c7..0111c2e406c6 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -190,7 +190,7 @@ function getPolicy(policyID: string | undefined): OnyxEntry { */ function getPrimaryPolicy(activePolicyID?: OnyxEntry): Policy | undefined { const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies); - const primaryPolicy: Policy | null | undefined = allPolicies?.[activePolicyID ?? '-1']; + const primaryPolicy: Policy | null | undefined = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`]; return primaryPolicy ?? activeAdminWorkspaces[0]; } diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 7b0db3e0d844..53527e85b215 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -19,6 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; import {getReportActionMessage} from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -112,21 +113,30 @@ function ReportActionItemSingle({ let secondaryAvatar: Icon; const primaryDisplayName = displayName; if (displayAllActors) { - // The ownerAccountID and actorAccountID can be the same if a user submits an expense back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice - const secondaryAccountId = ownerAccountID === actorAccountID || isInvoiceReport ? actorAccountID : ownerAccountID; - const secondaryUserAvatar = personalDetails?.[secondaryAccountId ?? -1]?.avatar ?? FallbackAvatar; - const secondaryDisplayName = ReportUtils.getDisplayNameForParticipant(secondaryAccountId); + if (ReportUtils.isInvoiceRoom(report) && !ReportUtils.isIndividualInvoiceRoom(report)) { + const secondaryPolicyID = report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : '-1'; + const secondaryPolicy = PolicyUtils.getPolicy(secondaryPolicyID); + const secondaryPolicyAvatar = secondaryPolicy?.avatarURL ?? ReportUtils.getDefaultWorkspaceAvatar(secondaryPolicy?.name); - if (!isInvoiceReport) { - displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; - } + secondaryAvatar = { + source: secondaryPolicyAvatar, + type: CONST.ICON_TYPE_WORKSPACE, + name: secondaryPolicy?.name, + id: secondaryPolicyID, + }; + } else { + // The ownerAccountID and actorAccountID can be the same if a user submits an expense back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice + const secondaryAccountId = ownerAccountID === actorAccountID || isInvoiceReport ? actorAccountID : ownerAccountID; + const secondaryUserAvatar = personalDetails?.[secondaryAccountId ?? -1]?.avatar ?? FallbackAvatar; + const secondaryDisplayName = ReportUtils.getDisplayNameForParticipant(secondaryAccountId); - secondaryAvatar = { - source: secondaryUserAvatar, - type: CONST.ICON_TYPE_AVATAR, - name: secondaryDisplayName ?? '', - id: secondaryAccountId, - }; + secondaryAvatar = { + source: secondaryUserAvatar, + type: CONST.ICON_TYPE_AVATAR, + name: secondaryDisplayName ?? '', + id: secondaryAccountId, + }; + } } else if (!isWorkspaceActor) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const avatarIconIndex = report.isOwnPolicyExpenseChat || ReportUtils.isPolicyExpenseChat(report) ? 0 : 1; From 85c45ddc9e65aaecb97ec9f90b5efb0fa4e337c8 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 9 Jul 2024 15:13:29 +0200 Subject: [PATCH 2/9] Fix workspace details display --- src/ROUTES.ts | 4 ++-- src/components/RoomHeaderAvatars.tsx | 4 ++-- src/libs/Navigation/types.ts | 1 + src/pages/ReportAvatar.tsx | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index d548297cb854..9e83c59e5e4b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -240,8 +240,8 @@ const ROUTES = { }, }, REPORT_AVATAR: { - route: 'r/:reportID/avatar', - getRoute: (reportID: string) => `r/${reportID}/avatar` as const, + route: 'r/:reportID/avatar/:policyID', + getRoute: (reportID: string, policyID: string) => `r/${reportID}/avatar/${policyID}` as const, }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index ac3b9c4d1396..0c14588afc30 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -17,8 +17,8 @@ type RoomHeaderAvatarsProps = { function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { const navigateToAvatarPage = (icon: Icon) => { - if (icon.type === CONST.ICON_TYPE_WORKSPACE) { - Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID)); + if (icon.type === CONST.ICON_TYPE_WORKSPACE && icon.id) { + Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID, icon.id.toString())); return; } diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index a60316fb7768..a8241ce20a21 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1087,6 +1087,7 @@ type AuthScreensParamList = CentralPaneScreensParamList & }; [SCREENS.REPORT_AVATAR]: { reportID: string; + policyID: string; }; [SCREENS.NOT_FOUND]: undefined; [NAVIGATORS.LEFT_MODAL_NAVIGATOR]: NavigatorScreenParams; diff --git a/src/pages/ReportAvatar.tsx b/src/pages/ReportAvatar.tsx index 5ec34e3b71dc..560c4dccdb9c 100644 --- a/src/pages/ReportAvatar.tsx +++ b/src/pages/ReportAvatar.tsx @@ -20,8 +20,9 @@ type ReportAvatarOnyxProps = { type ReportAvatarProps = ReportAvatarOnyxProps & StackScreenProps; -function ReportAvatar({report = {} as Report, policies, isLoadingApp = true}: ReportAvatarProps) { - const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '-1'}`]; +function ReportAvatar({report = {} as Report, route, policies, isLoadingApp = true}: ReportAvatarProps) { + const policyID = route.params.policyID; + const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; const policyName = ReportUtils.getPolicyName(report, false, policy); const avatarURL = ReportUtils.getWorkspaceAvatar(report); From e422516da689141a11f233ff504f19f1ca44c266 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 22 Jul 2024 15:31:32 +0200 Subject: [PATCH 3/9] Fixes after merging main --- src/ROUTES.ts | 9 ++++++--- src/components/RoomHeaderAvatars.tsx | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 484767dd5e43..7bee4c1a6c94 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -254,11 +254,14 @@ const ROUTES = { }, REPORT_AVATAR: { route: 'r/:reportID/avatar', - getRoute: (reportID: string, policyID: string, isNewGroupChat?: boolean) => { + getRoute: (reportID: string, isNewGroupChat?: boolean, policyID?: string) => { if (isNewGroupChat) { - return `r/${reportID}/avatar/${policyID}?isNewGroupChat=${isNewGroupChat}` as const; + return `r/${reportID}/avatar?isNewGroupChat=${isNewGroupChat}` as const; } - return `r/${reportID}/avatar/${policyID}` as const; + if (policyID) { + return `r/${reportID}/avatar?policyID=${policyID}` as const; + } + return `r/${reportID}/avatar` as const; }, }, EDIT_CURRENCY_REQUEST: { diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index 0c14588afc30..2e50d7337fac 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -18,7 +18,7 @@ type RoomHeaderAvatarsProps = { function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { const navigateToAvatarPage = (icon: Icon) => { if (icon.type === CONST.ICON_TYPE_WORKSPACE && icon.id) { - Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID, icon.id.toString())); + Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID, false, icon.id.toString())); return; } From 2811559ee1d9d4d30bd2f52841d4938c76327dc8 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 22 Jul 2024 16:09:45 +0200 Subject: [PATCH 4/9] Fix report avatar displays for invoice chats --- src/pages/ReportAvatar.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportAvatar.tsx b/src/pages/ReportAvatar.tsx index 5608970311a5..cc6138a16079 100644 --- a/src/pages/ReportAvatar.tsx +++ b/src/pages/ReportAvatar.tsx @@ -42,9 +42,17 @@ function ReportAvatar({report = {} as Report, policies, isLoadingApp = true, rou title = groupChatDraft?.reportName || ReportUtils.getGroupChatName(groupChatDraft?.participants.map((participant) => participant.accountID) ?? [], true); shouldShowNotFoundPage = !isLoadingApp && !groupChatDraft; } else { - avatarURL = policy ? ReportUtils.getWorkspaceAvatar(report) : report?.avatarUrl; + if (policy) { + avatarURL = policy.avatarURL ? policy.avatarURL : ReportUtils.getDefaultWorkspaceAvatar(policy.name); + } else { + avatarURL = report.policyID ? ReportUtils.getWorkspaceAvatar(report) : report?.avatarUrl; + } // In the case of default workspace avatar, originalFileName prop takes policyID as value to get the color of the avatar - fileName = policy ? policy?.originalFileName ?? policy?.id ?? report?.policyID : report?.avatarFileName; + if (policy) { + fileName = policy?.originalFileName ?? policyID; + } else { + fileName = report?.policyID ?? report?.avatarFileName; + } title = policy ? ReportUtils.getPolicyName(report, false, policy) : ReportUtils.getReportName(report); shouldShowNotFoundPage = !isLoadingApp && !report?.reportID; } From cf518922c0d2047d696428612abe0ac005a1082f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 23 Jul 2024 09:08:33 +0200 Subject: [PATCH 5/9] Fix after merging main --- src/components/RoomHeaderAvatars.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index 2e50d7337fac..0c14588afc30 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -18,7 +18,7 @@ type RoomHeaderAvatarsProps = { function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { const navigateToAvatarPage = (icon: Icon) => { if (icon.type === CONST.ICON_TYPE_WORKSPACE && icon.id) { - Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID, false, icon.id.toString())); + Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID, icon.id.toString())); return; } From 6c4ef69b34cb9cad49d7b81abf6b41a5fead29b8 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 24 Jul 2024 09:49:07 +0200 Subject: [PATCH 6/9] Minor improvement --- src/pages/home/report/ReportActionItemCreated.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index 7d9d95e54ba5..c8ed6147c429 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -1,3 +1,4 @@ +import lodashIsEqual from 'lodash/isEqual'; import React, {memo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -122,6 +123,7 @@ export default withOnyx Date: Thu, 1 Aug 2024 14:29:17 +0200 Subject: [PATCH 7/9] Fix invoice room and report display after receiver policy is added --- src/components/AvatarWithDisplayName.tsx | 10 +++-- .../LHNOptionsList/LHNOptionsList.tsx | 11 +++++ .../LHNOptionsList/OptionRowLHNData.tsx | 3 ++ src/components/LHNOptionsList/types.ts | 3 ++ .../ReportActionItem/ReportPreview.tsx | 7 +++- src/libs/ReportUtils.ts | 40 +++++++++---------- src/libs/SidebarUtils.ts | 6 ++- src/pages/home/HeaderView.tsx | 8 ++-- .../home/report/ReportActionItemCreated.tsx | 27 +++++++------ .../home/report/ReportActionItemSingle.tsx | 12 +++--- 10 files changed, 78 insertions(+), 49 deletions(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 38bf3912ae4b..187acf2536c5 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -58,12 +58,16 @@ function AvatarWithDisplayName({ const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const title = ReportUtils.getReportName(report); + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`); + const [invoiceReceiverPolicy] = useOnyx( + `${ONYXKEYS.COLLECTION.POLICY}${parentReport?.invoiceReceiver && 'policyID' in parentReport.invoiceReceiver ? parentReport.invoiceReceiver.policyID : -1}`, + ); + const title = ReportUtils.getReportName(report, undefined, undefined, invoiceReceiverPolicy); const subtitle = ReportUtils.getChatRoomSubtitle(report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(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 icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails) as PersonalDetails[], false); const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report); diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 5f06fb78049a..4c5625ec72a2 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -116,10 +116,20 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const renderItem = useCallback( ({item: reportID}: RenderItemProps): ReactElement => { const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const itemParentReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${itemFullReport?.parentReportID ?? '-1'}`]; const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '-1']; + let invoiceReceiverPolicyID = '-1'; + if (itemFullReport?.invoiceReceiver && 'policyID' in itemFullReport.invoiceReceiver) { + invoiceReceiverPolicyID = itemFullReport.invoiceReceiver.policyID; + } + if (itemParentReport?.invoiceReceiver && 'policyID' in itemParentReport.invoiceReceiver) { + invoiceReceiverPolicyID = itemParentReport.invoiceReceiver.policyID; + } + const itemInvoiceReceiverPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]; + const iouReportIDOfLastAction = OptionsListUtils.getIOUReportIDOfLastAction(itemFullReport); const itemIouReportReportActions = iouReportIDOfLastAction ? reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportIDOfLastAction}`] : undefined; @@ -148,6 +158,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio parentReportAction={itemParentReportAction} iouReportReportActions={itemIouReportReportActions} policy={itemPolicy} + invoiceReceiverPolicy={itemInvoiceReceiverPolicy} personalDetails={personalDetails ?? {}} transaction={itemTransaction} lastReportActionTransaction={lastReportActionTransaction} diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 30ea32642190..dd2848e1ef62 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -21,6 +21,7 @@ function OptionRowLHNData({ personalDetails = {}, preferredLocale = CONST.LOCALES.DEFAULT, policy, + invoiceReceiverPolicy, receiptTransactions, parentReportAction, iouReportReportActions, @@ -50,6 +51,7 @@ function OptionRowLHNData({ parentReportAction, hasViolations: !!shouldDisplayViolations || shouldDisplayReportViolations, transactionViolations, + invoiceReceiverPolicy, }); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; @@ -74,6 +76,7 @@ function OptionRowLHNData({ transactionViolations, canUseViolations, receiptTransactions, + invoiceReceiverPolicy, shouldDisplayReportViolations, ]); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 2c3637fa5c8e..6e37b6a54646 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -50,6 +50,9 @@ type OptionRowLHNDataProps = { /** The policy which the user has access to and which the report could be tied to */ policy?: OnyxEntry; + /** Invoice receiver policy */ + invoiceReceiverPolicy?: OnyxEntry; + /** The action from the parent report */ parentReportAction?: OnyxEntry; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 8e97eb0d6157..7a2e9155d1c7 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -3,7 +3,7 @@ import React, {useMemo, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -140,6 +140,9 @@ function ReportPreview({ const [nonHeldAmount, fullAmount] = 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 managerID = iouReport?.managerID ?? action.childManagerAccountID ?? 0; const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); @@ -275,7 +278,7 @@ function ReportPreview({ if (isPolicyExpenseChat) { payerOrApproverName = ReportUtils.getPolicyName(chatReport); } else if (isInvoiceRoom) { - payerOrApproverName = ReportUtils.getInvoicePayerName(chatReport); + payerOrApproverName = ReportUtils.getInvoicePayerName(chatReport, invoiceReceiverPolicy); } else { payerOrApproverName = ReportUtils.getDisplayNameForParticipant(managerID, true); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6cb833bb4a39..d0211306f23f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2052,6 +2052,7 @@ function getIcons( defaultName = '', defaultAccountID = -1, policy?: OnyxInputOrEntry, + invoiceReceiverPolicy?: OnyxInputOrEntry, ): Icon[] { if (isEmptyObject(report)) { const fallbackIcon: Icon = { @@ -2130,7 +2131,7 @@ function getIcons( icons.push(...getIconsForParticipants([report?.invoiceReceiver.accountID], personalDetails)); } else { const receiverPolicyID = report?.invoiceReceiver?.policyID; - const receiverPolicy = getPolicy(receiverPolicyID); + const receiverPolicy = invoiceReceiverPolicy ?? getPolicy(receiverPolicyID); if (!isEmptyObject(receiverPolicy)) { icons.push({ source: receiverPolicy?.avatarURL ?? getDefaultWorkspaceAvatar(receiverPolicy.name), @@ -2210,7 +2211,7 @@ function getIcons( } const receiverPolicyID = invoiceRoomReport?.invoiceReceiver?.policyID; - const receiverPolicy = getPolicy(receiverPolicyID); + const receiverPolicy = invoiceReceiverPolicy ?? getPolicy(receiverPolicyID); if (!isEmptyObject(receiverPolicy)) { icons.push({ @@ -2664,7 +2665,7 @@ function getAvailableReportFields(report: Report, policyReportFields: PolicyRepo /** * Get the title for an IOU or expense chat which will be showing the payer and the amount */ -function getMoneyRequestReportName(report: OnyxEntry, policy?: OnyxEntry): string { +function getMoneyRequestReportName(report: OnyxEntry, policy?: OnyxEntry, invoiceReceiverPolicy?: OnyxEntry): string { const isReportSettled = isSettled(report?.reportID ?? '-1'); const reportFields = isReportSettled ? report?.fieldList : getReportFieldsByPolicyID(report?.policyID ?? '-1'); const titleReportField = getFormulaTypeReportField(reportFields ?? {}); @@ -2681,7 +2682,7 @@ function getMoneyRequestReportName(report: OnyxEntry, policy?: OnyxEntry payerOrApproverName = getPolicyName(report, false, policy); } else if (isInvoiceReport(report)) { const chatReport = getReportOrDraftReport(report?.chatReportID); - payerOrApproverName = getInvoicePayerName(chatReport); + payerOrApproverName = getInvoicePayerName(chatReport, invoiceReceiverPolicy); } else { payerOrApproverName = getDisplayNameForParticipant(report?.managerID) ?? ''; } @@ -3359,7 +3360,7 @@ function getAdminRoomInvitedParticipants(parentReportAction: OnyxEntry): string { +function getInvoicePayerName(report: OnyxEntry, invoiceReceiverPolicy?: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; @@ -3367,7 +3368,7 @@ function getInvoicePayerName(report: OnyxEntry): string { return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiver.accountID]); } - return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); + return getPolicyName(report, false, invoiceReceiverPolicy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); } /** @@ -3443,13 +3444,13 @@ function getReportActionMessage(reportAction: OnyxEntry, reportID? /** * Get the title for an invoice room. */ -function getInvoicesChatName(report: OnyxEntry): string { +function getInvoicesChatName(report: OnyxEntry, receiverPolicy: 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 ?? '-1'; - const isCurrentUserReceiver = - (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); + const invoiceReceiverPolicy = receiverPolicy ?? getPolicy(invoiceReceiverPolicyID); + const isCurrentUserReceiver = (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyAdmin(invoiceReceiverPolicy)); if (isCurrentUserReceiver) { return getPolicyName(report); @@ -3459,14 +3460,13 @@ function getInvoicesChatName(report: OnyxEntry): string { 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}`]); + return getPolicyName(report, false, invoiceReceiverPolicy); } /** * Get the title for a report. */ -function getReportName(report: OnyxEntry, policy?: OnyxEntry, parentReportActionParam?: OnyxInputOrEntry): string { +function getReportName(report: OnyxEntry, policy?: OnyxEntry, parentReportActionParam?: OnyxInputOrEntry, invoiceReceiverPolicy?: OnyxEntry): string { let formattedName: string | undefined; const parentReportAction = parentReportActionParam ?? ReportActionsUtils.getParentReportAction(report); const parentReportActionMessage = ReportActionsUtils.getReportActionMessage(parentReportAction); @@ -3537,12 +3537,16 @@ function getReportName(report: OnyxEntry, policy?: OnyxEntry, pa formattedName = getPolicyExpenseChatName(report, policy); } - if (isMoneyRequestReport(report) || isInvoiceReport(report)) { + if (isMoneyRequestReport(report)) { formattedName = getMoneyRequestReportName(report, policy); } + if (isInvoiceReport(report)) { + formattedName = getMoneyRequestReportName(report, policy, invoiceReceiverPolicy); + } + if (isInvoiceRoom(report)) { - formattedName = getInvoicesChatName(report); + formattedName = getInvoicesChatName(report, invoiceReceiverPolicy); } if (isArchivedRoom(report, getReportNameValuePairs(report?.reportID))) { @@ -3553,10 +3557,6 @@ function getReportName(report: OnyxEntry, policy?: OnyxEntry, pa formattedName = getDisplayNameForParticipant(currentUserAccountID, undefined, undefined, true); } - if (isInvoiceRoom(report)) { - formattedName = getInvoicesChatName(report); - } - if (formattedName) { return formatReportLastMessageText(formattedName); } @@ -3631,14 +3631,14 @@ function getPendingChatMembers(accountIDs: number[], previousPendingChatMembers: /** * Gets the parent navigation subtitle for the report */ -function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigationSummaryParams { +function getParentNavigationSubtitle(report: OnyxEntry, invoiceReceiverPolicy?: OnyxEntry): ParentNavigationSummaryParams { const parentReport = getParentReport(report); if (isEmptyObject(parentReport)) { return {}; } if (isInvoiceReport(report) || isInvoiceRoom(parentReport)) { - let reportName = `${getPolicyName(parentReport)} & ${getInvoicePayerName(parentReport)}`; + let reportName = `${getPolicyName(parentReport)} & ${getInvoicePayerName(parentReport, invoiceReceiverPolicy)}`; if (isArchivedRoom(parentReport, getReportNameValuePairs(parentReport?.reportID))) { reportName += ` (${Localize.translateLocal('common.archived')})`; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index eb13915c6f47..cd581da141d2 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -244,6 +244,7 @@ function getOptionData({ parentReportAction, hasViolations, transactionViolations, + invoiceReceiverPolicy, }: { report: OnyxEntry; reportActions: OnyxEntry; @@ -252,6 +253,7 @@ function getOptionData({ policy: OnyxEntry | undefined; parentReportAction: OnyxEntry | undefined; hasViolations: boolean; + invoiceReceiverPolicy?: OnyxEntry; transactionViolations?: OnyxCollection; }): ReportUtils.OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for @@ -462,13 +464,13 @@ function getOptionData({ result.phoneNumber = personalDetail?.phoneNumber; } - const reportName = ReportUtils.getReportName(report, policy); + const reportName = ReportUtils.getReportName(report, policy, undefined, invoiceReceiverPolicy); result.text = reportName; result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, personalDetail?.login, personalDetail?.accountID, policy); + result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, personalDetail?.login, personalDetail?.accountID, policy, invoiceReceiverPolicy); result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 481a497fe2a7..120b5d8f5f9f 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -1,7 +1,7 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; import Button from '@components/Button'; import CaretWrapper from '@components/CaretWrapper'; @@ -64,6 +64,8 @@ type HeaderViewProps = HeaderViewOnyxProps & { function HeaderView({report, personalDetails, parentReport, parentReportAction, policy, reportID, onNavigationMenuButtonClicked, shouldUseNarrowLayout = false}: HeaderViewProps) { const [isDeleteTaskConfirmModalVisible, setIsDeleteTaskConfirmModalVisible] = React.useState(false); + const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`); + const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -82,7 +84,7 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction, const isTaskReport = ReportUtils.isTaskReport(report); const reportHeaderData = !isTaskReport && !isChatThread && report.parentReportID ? parentReport : report; // Use sorted display names for the title for group chats on native small screen widths - const title = ReportUtils.getReportName(reportHeaderData, undefined, parentReportAction); + const title = ReportUtils.getReportName(reportHeaderData, undefined, parentReportAction, invoiceReceiverPolicy); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(reportHeaderData); const reportDescription = ReportUtils.getReportDescriptionText(report); @@ -129,7 +131,7 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction, const shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); const defaultSubscriptSize = ReportUtils.isExpenseRequest(report) ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - const icons = ReportUtils.getIcons(reportHeaderData, personalDetails); + const icons = ReportUtils.getIcons(reportHeaderData, personalDetails, null, '', -1, undefined, invoiceReceiverPolicy); const brickRoadIndicator = ReportUtils.hasReportNameError(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; const shouldShowBorderBottom = !isTaskReport || !shouldUseNarrowLayout; const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index 1d7c0d5c27e8..ec9f5ea9915f 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -2,7 +2,7 @@ import lodashIsEqual from 'lodash/isEqual'; import React, {memo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -36,29 +36,30 @@ type ReportActionItemCreatedProps = ReportActionItemCreatedOnyxProps & { // eslint-disable-next-line react/no-unused-prop-types policyID: string | undefined; }; -function ReportActionItemCreated(props: ReportActionItemCreatedProps) { +function ReportActionItemCreated({report, personalDetails, policy, reportID}: ReportActionItemCreatedProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {shouldUseNarrowLayout, isLargeScreenWidth} = useResponsiveLayout(); + const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`); - if (!ReportUtils.isChatReport(props.report)) { + if (!ReportUtils.isChatReport(report)) { return null; } - let icons = ReportUtils.getIcons(props.report, props.personalDetails); - const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(props.report); + let icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, undefined, invoiceReceiverPolicy); + const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); - if (ReportUtils.isInvoiceRoom(props.report) && ReportUtils.isCurrentUserInvoiceReceiver(props.report)) { + if (ReportUtils.isInvoiceRoom(report) && ReportUtils.isCurrentUserInvoiceReceiver(report)) { icons = [...icons].reverse(); } return ( navigateToConciergeChatAndDeleteReport(props.report?.reportID ?? props.reportID, undefined, true)} + onClose={() => navigateToConciergeChatAndDeleteReport(report?.reportID ?? reportID, undefined, true)} > @@ -66,9 +67,9 @@ function ReportActionItemCreated(props: ReportActionItemCreatedProps) { accessibilityLabel={translate('accessibilityHints.chatWelcomeMessage')} style={[styles.p5]} > - + ReportUtils.navigateToDetailsPage(props.report)} + onPress={() => ReportUtils.navigateToDetailsPage(report)} style={[styles.mh5, styles.mb3, styles.alignSelfStart, shouldDisableDetailPage && styles.cursorDefault]} accessibilityLabel={translate('common.details')} role={CONST.ROLE.BUTTON} @@ -85,8 +86,8 @@ function ReportActionItemCreated(props: ReportActionItemCreatedProps) { diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 4d7041e7d3c3..95a7332f0606 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -2,6 +2,7 @@ import React, {useCallback, useMemo} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import Avatar from '@components/Avatar'; import {FallbackAvatar} from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; @@ -19,10 +20,10 @@ import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; import {getReportActionMessage} from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Report, ReportAction} from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; @@ -81,6 +82,7 @@ function ReportActionItemSingle({ const {translate} = useLocalize(); const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; const actorAccountID = ReportUtils.getReportActionActorAccountID(action, iouReport); + const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`); let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID); const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID ?? -1] ?? {}; @@ -118,15 +120,13 @@ function ReportActionItemSingle({ const primaryDisplayName = displayName; if (displayAllActors) { if (ReportUtils.isInvoiceRoom(report) && !ReportUtils.isIndividualInvoiceRoom(report)) { - const secondaryPolicyID = report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : '-1'; - const secondaryPolicy = PolicyUtils.getPolicy(secondaryPolicyID); - const secondaryPolicyAvatar = secondaryPolicy?.avatarURL ?? ReportUtils.getDefaultWorkspaceAvatar(secondaryPolicy?.name); + const secondaryPolicyAvatar = invoiceReceiverPolicy?.avatarURL ?? ReportUtils.getDefaultWorkspaceAvatar(invoiceReceiverPolicy?.name); secondaryAvatar = { source: secondaryPolicyAvatar, type: CONST.ICON_TYPE_WORKSPACE, - name: secondaryPolicy?.name, - id: secondaryPolicyID, + name: invoiceReceiverPolicy?.name, + id: invoiceReceiverPolicy?.id, }; } else { // The ownerAccountID and actorAccountID can be the same if a user submits an expense back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice From 7d1945540d9ffa245b79ac280f318312bb3728f2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 6 Aug 2024 19:02:00 +0200 Subject: [PATCH 8/9] Fixes after merging main --- src/components/AvatarWithDisplayName.tsx | 2 +- src/libs/SidebarUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 187acf2536c5..2ccdd47c3205 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -62,7 +62,7 @@ function AvatarWithDisplayName({ const [invoiceReceiverPolicy] = useOnyx( `${ONYXKEYS.COLLECTION.POLICY}${parentReport?.invoiceReceiver && 'policyID' in parentReport.invoiceReceiver ? parentReport.invoiceReceiver.policyID : -1}`, ); - const title = ReportUtils.getReportName(report, undefined, undefined, invoiceReceiverPolicy); + const title = ReportUtils.getReportName(report, undefined, undefined, undefined, invoiceReceiverPolicy); const subtitle = ReportUtils.getChatRoomSubtitle(report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); const isMoneyRequestOrReport = diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 06804cdf5881..2d226339ee52 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -464,7 +464,7 @@ function getOptionData({ result.phoneNumber = personalDetail?.phoneNumber; } - const reportName = ReportUtils.getReportName(report, policy, undefined, invoiceReceiverPolicy); + const reportName = ReportUtils.getReportName(report, policy, undefined, undefined, invoiceReceiverPolicy); result.text = reportName; result.subtitle = subtitle; From a39aa22306310a41d0333b46cdc8a52de6cf91d9 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 6 Aug 2024 19:57:41 +0200 Subject: [PATCH 9/9] Show invoice room under receiver workspace reports --- src/libs/ReportUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7b26870458c7..41560ee98341 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1168,10 +1168,8 @@ function findSelfDMReportID(): string | undefined { * In this case report and workspace members must be compared to determine whether the report belongs to the workspace. */ function doesReportBelongToWorkspace(report: OnyxEntry, policyMemberAccountIDs: number[], policyID?: string) { - return ( - isConciergeChatReport(report) || - (report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : report?.policyID === policyID) - ); + const isPolicyRelatedReport = report?.policyID === policyID || !!(report?.invoiceReceiver && 'policyID' in report.invoiceReceiver && report.invoiceReceiver.policyID === policyID); + return isConciergeChatReport(report) || (report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : isPolicyRelatedReport); } /**