From 93292f7ef05da5c5d2b1ae404b378dca72fbc89a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 16:43:50 +0200 Subject: [PATCH 01/53] add invoice chat type --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index 74e722cdba59d..7f26b9ecaf368 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -52,6 +52,7 @@ const chatTypes = { POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', SELF_DM: 'selfDM', + INVOICE: 'invoiceRoom', } as const; // Explicit type annotation is required From 1c17dd6eae4add9972ed335118a926ff453e4ade Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 16:44:30 +0200 Subject: [PATCH 02/53] add invoice report type --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index 7f26b9ecaf368..c30f94db6b836 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -791,6 +791,7 @@ const CONST = { EXPENSE: 'expense', IOU: 'iou', TASK: 'task', + INVOICE: 'invoice', }, CHAT_TYPE: chatTypes, WORKSPACE_CHAT_ROOMS: { From 08c640b4e670e62bbb478459bda79d1e2af20f8d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 16:45:40 +0200 Subject: [PATCH 03/53] create isInvoiceRoom and isInvoiceReport --- src/libs/ReportUtils.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f7b160bd67e24..9f1ebb40c0c28 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5898,6 +5898,20 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } +/** + * Check if Report is an invoice room + */ +function isInvoiceRoom(report: OnyxEntry): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; +} + +/** + * Check if Report is an invoice report + */ +function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { + return report?.type === CONST.REPORT.TYPE.INVOICE; +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -6134,6 +6148,8 @@ export { buildParticipantsFromAccountIDs, canReportBeMentionedWithinPolicy, getAllHeldTransactions, + isInvoiceRoom, + isInvoiceReport, }; export type { From 10c2d2fb9f4427b06995771abe6c52397cdcc0ee Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 16:47:31 +0200 Subject: [PATCH 04/53] check isInvoiceReport in report screen --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 332e9b080558e..7e31b69779c26 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -343,7 +343,7 @@ function ReportScreen({ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(route.params.reportID)); }, [transactionThreadReportID, route.params.reportActionID, route.params.reportID]); - if (ReportUtils.isMoneyRequestReport(report)) { + if (ReportUtils.isMoneyRequestReport(report) || ReportUtils.isInvoiceReport(report)) { headerView = ( Date: Wed, 10 Apr 2024 16:49:26 +0200 Subject: [PATCH 05/53] check that invoice room is chat room --- src/libs/ReportUtils.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9f1ebb40c0c28..b92c7d4b9b50f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -905,11 +905,25 @@ function isPaidGroupPolicyExpenseReport(report: OnyxEntry): boolean { return isExpenseReport(report) && isPaidGroupPolicy(report); } +/** + * Check if Report is an invoice room + */ +function isInvoiceRoom(report: OnyxEntry): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; +} + +/** + * Check if Report is an invoice report + */ +function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { + return report?.type === CONST.REPORT.TYPE.INVOICE; +} + /** * 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); } /** @@ -5898,20 +5912,6 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } -/** - * Check if Report is an invoice room - */ -function isInvoiceRoom(report: OnyxEntry): boolean { - return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; -} - -/** - * Check if Report is an invoice report - */ -function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { - return report?.type === CONST.REPORT.TYPE.INVOICE; -} - export { getReportParticipantsTitle, isReportMessageAttachment, @@ -5929,6 +5929,8 @@ export { isAdminsOnlyPostingRoom, isAnnounceRoom, isUserCreatedPolicyRoom, + isInvoiceRoom, + isInvoiceReport, isChatRoom, getChatRoomSubtitle, getParentNavigationSubtitle, @@ -6148,8 +6150,6 @@ export { buildParticipantsFromAccountIDs, canReportBeMentionedWithinPolicy, getAllHeldTransactions, - isInvoiceRoom, - isInvoiceReport, }; export type { From 86f52ca2cea996d56e5acfcaf66fac4413f5af95 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 16:55:17 +0200 Subject: [PATCH 06/53] implement invoice room welcome message --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/ReportUtils.ts | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 9451407c822fc..20779fa561a65 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -519,6 +519,7 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'track an expense', }, + beginningOfChatHistoryInvoiceRoom: 'Collaboration starts here! 🎉 \nUse this room to view, discuss, and pay invoices.', }, reportAction: { asCopilot: 'as copilot for', diff --git a/src/languages/es.ts b/src/languages/es.ts index a56c8ac2739d8..0d55f4a902dda 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -515,6 +515,7 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'rastrear un gasto', }, + beginningOfChatHistoryInvoiceRoom: '¡Este es el lugar para colaborar! 🎉\nUsa esta sala para ver, discutir y pagar facturas.', }, reportAction: { asCopilot: 'como copiloto de', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b92c7d4b9b50f..123610acbf341 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1497,6 +1497,8 @@ 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.phrase1 = Localize.translateLocal('reportActionsView.beginningOfChatHistoryInvoiceRoom'); } else { // Message for user created rooms or other room types. welcomeMessage.phrase1 = Localize.translateLocal('reportActionsView.beginningOfChatHistoryUserRoomPartOne'); From f9243bbde5a65a264b46620fb83cf8ef86806574 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 17:00:11 +0200 Subject: [PATCH 07/53] accept invoice report in AvatarWithDisplayName --- src/components/AvatarWithDisplayName.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index f6afb4dae2d6c..4171a3170534e 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); From c15f33d6442772f84ae03394f018049e06ac6ae8 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 17:05:08 +0200 Subject: [PATCH 08/53] support invoice report in getChatRoomSubtitle --- src/libs/ReportUtils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 123610acbf341..1669a619d14df 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3062,6 +3062,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); } From e08889ed8dd3c3e7c75a1abb8496d8d40468b020 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 17:07:40 +0200 Subject: [PATCH 09/53] accept invoice room in shouldReportShowSubscript --- src/libs/ReportUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1669a619d14df..2f054cc60788d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5112,6 +5112,10 @@ function shouldReportShowSubscript(report: OnyxEntry): boolean { return true; } + if (isInvoiceRoom(report)) { + return true; + } + return false; } From 2d67b99043a1194c72959c7736fc69874f91e75e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 17:54:33 +0200 Subject: [PATCH 10/53] integrate invoice room in canLeaveRoom --- src/libs/ReportUtils.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2f054cc60788d..0b30e1d9676e3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5041,8 +5041,15 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, isPolicyMember: boolean): boolean { + if (report?.chatType === CONST.REPORT.CHAT_TYPE.INVOICE) { + const isAdmin = !!Object.entries(report.participants ?? {}).find(([participantID, {role}]) => Number(participantID) === currentUserAccountID && role !== CONST.POLICY.ROLE.ADMIN); + + return report.managerID !== currentUserAccountID && report.ownerAccountID !== currentUserAccountID && !isAdmin; + } + if (!report?.visibility) { if ( report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS || From 3e946169042201531aee923417eff56e7bf9b5af Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 10 Apr 2024 18:17:48 +0200 Subject: [PATCH 11/53] integrate invoice icon in getIcons --- src/libs/ReportUtils.ts | 17 +++++++++++++++++ src/types/onyx/Report.ts | 2 ++ 2 files changed, 19 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0b30e1d9676e3..97c6f59ed26f4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1955,6 +1955,23 @@ function getIcons( return [groupChatIcon]; } + if (isInvoiceRoom(report)) { + const workspaceIcon = getWorkspaceIcon(report, policy); + + const receiverPolicyID = Object.values(report.participants ?? {})?.find((participant) => participant.type === 'policy')?.policyID ?? ''; + const receiverPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${receiverPolicyID}`]; + const isWorkspaceToWorkspace = !!receiverPolicyID && receiverPolicy; + const icons = []; + + if (isWorkspaceToWorkspace) { + icons.push(getWorkspaceIcon(report, receiverPolicy)); + } else { + icons.push(...getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails)); + } + + return [workspaceIcon, ...icons]; + } + return getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails); } diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e6..cf39d1f0d1bda 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -26,6 +26,8 @@ type PendingChatMember = { type Participant = { hidden?: boolean; role?: 'admin' | 'member'; + type?: 'policy' | 'individual'; + policyID?: string; }; type Participants = Record; From 8a41a366cd6b76515b1b2cd6809b9e4fe69ccaad Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 11 Apr 2024 17:56:20 +0200 Subject: [PATCH 12/53] create getInvoicesChatName --- src/libs/ReportUtils.ts | 23 +++++++++++++++++++++++ src/types/onyx/Report.ts | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 97c6f59ed26f4..937d54d77702e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2974,6 +2974,24 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, parent return reportAction?.message?.[0]?.text ?? ''; } +/** + * Get the title for an invoice room. + */ +function getInvoicesChatName(report: OnyxEntry, policy: OnyxEntry): string { + const policyName = getPolicyName(report, false, policy); + let receiverName = ''; + + if (report?.invoiceReceiverAccountID) { + receiverName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[report.invoiceReceiverAccountID]); + } + + if (report?.invoiceReceiverPolicyID) { + receiverName = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.invoiceReceiverPolicyID}`]); + } + + return `${receiverName} & ${policyName}`; +} + /** * Get the title for a report. */ @@ -3045,6 +3063,10 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu formattedName = getDisplayNameForParticipant(currentUserAccountID, undefined, undefined, true); } + if (isInvoiceRoom(report)) { + formattedName = getInvoicesChatName(report, policy); + } + if (formattedName) { return formattedName; } @@ -5999,6 +6021,7 @@ export { getIcons, getRoomWelcomeMessage, getDisplayNamesWithTooltips, + getInvoicesChatName, getReportName, getReport, getReportNotificationPreference, diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index cf39d1f0d1bda..9aba3f2c2a34a 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -188,6 +188,10 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< transactionThreadReportID?: string; fieldList?: Record; + + // TODO: Confirm + invoiceReceiverAccountID?: number; + invoiceReceiverPolicyID?: string; }, PolicyReportField['fieldID'] >; From 24460570f31f6f00717315b306b26fbfd0e0412c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 11:11:25 +0200 Subject: [PATCH 13/53] add awaiting payment translation --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/languages/types.ts | 3 +++ 3 files changed, 7 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 20779fa561a65..df87737eb504c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -9,6 +9,7 @@ import type { AlreadySignedInParams, AmountEachParams, ApprovedAmountParams, + AwaitingPaymentParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, @@ -736,6 +737,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', + awaitingPayment: ({payerName}: AwaitingPaymentParams) => `Awaiting payment by ${payerName}`, }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 0d55f4a902dda..280852813fb51 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7,6 +7,7 @@ import type { AlreadySignedInParams, AmountEachParams, ApprovedAmountParams, + AwaitingPaymentParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, @@ -734,6 +735,7 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', + awaitingPayment: ({payerName}: AwaitingPaymentParams) => `A la espera de pago por ${payerName}`, }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/languages/types.ts b/src/languages/types.ts index c365363f84af5..59e1bfe40af20 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -135,6 +135,8 @@ type PayerSettledParams = {amount: number | string}; type WaitingOnBankAccountParams = {submitterDisplayName: string}; +type AwaitingPaymentParams = {payerName: string}; + type CanceledRequestParams = {amount: string; submitterDisplayName: string}; type AdminCanceledRequestParams = {manager: string; amount: string}; @@ -391,6 +393,7 @@ export type { ViolationsTagOutOfPolicyParams, ViolationsTaxOutOfPolicyParams, WaitingOnBankAccountParams, + AwaitingPaymentParams, WalletProgramParams, UsePlusButtonParams, WeSentYouMagicSignInLinkParams, From 8e667005d581a6bb9cccb7d6f398f754f6b97ade Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 11:15:32 +0200 Subject: [PATCH 14/53] create PaymentWaitingBanner --- src/components/PaymentWaitingBanner/index.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/components/PaymentWaitingBanner/index.tsx diff --git a/src/components/PaymentWaitingBanner/index.tsx b/src/components/PaymentWaitingBanner/index.tsx new file mode 100644 index 0000000000000..af13711f08e04 --- /dev/null +++ b/src/components/PaymentWaitingBanner/index.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type PaymentWaitingBannerProps = { + payerName: string; +}; + +function PaymentWaitingBanner({payerName}: PaymentWaitingBannerProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + + + + {translate('iou.awaitingPayment', {payerName})} + + ); +} + +export default PaymentWaitingBanner; From fd8a5bd6bfc9076a1be48454c84fb289ed58837f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 11:17:31 +0200 Subject: [PATCH 15/53] create isInvoiceAwaitingPayment --- src/libs/ReportUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 937d54d77702e..f92c0ad7b5efb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5967,6 +5967,13 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } +/** + * Check if a invoice report is awaiting for payment + */ +function isInvoiceAwaitingPayment(report: OnyxEntry): boolean { + return !isSettled(report?.reportID ?? '') && isInvoiceReport(report); +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -6206,6 +6213,7 @@ export { buildParticipantsFromAccountIDs, canReportBeMentionedWithinPolicy, getAllHeldTransactions, + isInvoiceAwaitingPayment, }; export type { From 19175e973c0084c56fcc305fb91b15b7f95904e6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 11:19:10 +0200 Subject: [PATCH 16/53] render MoneyRequestView in ReportActionItem for invoice --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index b2922556b71d4..06dc68bf8fae8 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -752,7 +752,7 @@ function ReportActionItem({ ); } - if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) { + if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report) || ReportUtils.isInvoiceReport(report)) { return ( {transactionThreadReport && !isEmptyObject(transactionThreadReport) ? ( From 21fb170d9bd79da63f39c7e902d9c219d31c30c3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 11:59:31 +0200 Subject: [PATCH 17/53] add invoiceReceiver to report --- src/CONST.ts | 5 +++++ src/types/onyx/Report.ts | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index c30f94db6b836..9f4fd2c4128bb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4339,6 +4339,11 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, + + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + POLICY: 'policy', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 9aba3f2c2a34a..56980094cd718 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -189,9 +189,15 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< fieldList?: Record; - // TODO: Confirm - invoiceReceiverAccountID?: number; - invoiceReceiverPolicyID?: string; + invoiceReceiver?: + | { + type: typeof CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + accountID: number; + } + | { + type: typeof CONST.INVOICE_RECEIVER_TYPE.POLICY; + policyID: string; + }; }, PolicyReportField['fieldID'] >; From 32536ffaee59a86a1312869d084b40c075e524bb Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 12:04:07 +0200 Subject: [PATCH 18/53] integrate invoiceReceiver usage --- src/libs/ReportUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f92c0ad7b5efb..5321ae30d0756 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2981,12 +2981,12 @@ function getInvoicesChatName(report: OnyxEntry, policy: OnyxEntry Date: Fri, 12 Apr 2024 12:06:00 +0200 Subject: [PATCH 19/53] add invoice banner to MoneyReportHeader --- src/components/MoneyReportHeader.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 14227d6a20510..befcaf470901b 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -22,6 +22,7 @@ import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import * as Expensicons from './Icon/Expensicons'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; +import PaymentWaitingBanner from './PaymentWaitingBanner'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import SettlementButton from './SettlementButton'; @@ -99,6 +100,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; + const shouldShowWaitingNote = ReportUtils.isInvoiceAwaitingPayment(moneyRequestReport); + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; @@ -108,7 +111,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount; - const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && isSmallScreenWidth); + const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && isSmallScreenWidth) || shouldShowWaitingNote; const confirmPayment = (type?: PaymentMethodType | undefined) => { if (!type) { @@ -263,6 +266,12 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money )} + {shouldShowWaitingNote && ( + + )} {isHoldMenuVisible && requestType !== undefined && ( Date: Fri, 12 Apr 2024 12:11:07 +0200 Subject: [PATCH 20/53] integrate deleted invoice message --- src/components/ReportActionItem/MoneyRequestAction.tsx | 2 ++ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/pages/home/report/ReportActionItem.tsx | 2 ++ 4 files changed, 6 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 7d9ba2697c7a0..9bef637bf2924 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -116,6 +116,8 @@ function MoneyRequestAction({ message = 'parentReportAction.reversedTransaction'; } else if (isTrackExpenseAction) { message = 'parentReportAction.deletedExpense'; + } else if (action.childType === CONST.REPORT.TYPE.INVOICE) { + message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } diff --git a/src/languages/en.ts b/src/languages/en.ts index df87737eb504c..b38601420c2df 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2535,6 +2535,7 @@ export default { reversedTransaction: '[Reversed transaction]', deletedTask: '[Deleted task]', hiddenMessage: '[Hidden message]', + deletedInvoice: '[Deleted invoice]', }, threads: { thread: 'Thread', diff --git a/src/languages/es.ts b/src/languages/es.ts index 280852813fb51..2263170425c3d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3027,6 +3027,7 @@ export default { reversedTransaction: '[Transacción anulada]', deletedTask: '[Tarea eliminada]', hiddenMessage: '[Mensaje oculto]', + deletedInvoice: '[Factura eliminada]', }, threads: { thread: 'Hilo', diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 06dc68bf8fae8..afc66d15c1f1b 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -689,6 +689,8 @@ function ReportActionItem({ message = 'parentReportAction.reversedTransaction'; } else if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { message = 'parentReportAction.deletedExpense'; + } else if (parentReportAction?.childType === CONST.REPORT.TYPE.INVOICE) { + message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } From 19b54e34b25b9d9ddcb7c27ae4b237cdcac744ff Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 12:12:58 +0200 Subject: [PATCH 21/53] integrate deleted invoice message in getTransactionReportName --- src/libs/ReportUtils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5321ae30d0756..7aa2d4a7278b3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -190,6 +190,7 @@ type OptimisticIOUReportAction = Pick< | 'receipt' | 'whisperedToAccountIDs' | 'childReportID' + | 'childType' >; type ReportRouteParams = { @@ -2676,6 +2677,10 @@ function getTransactionReportName(reportAction: OnyxEntry Date: Fri, 12 Apr 2024 12:16:09 +0200 Subject: [PATCH 22/53] add invoice banner to ReportPreview --- src/components/ReportActionItem/ReportPreview.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 190343e48abd0..9ff09ad33ec98 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -8,6 +8,7 @@ import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import PaymentWaitingBanner from '@components/PaymentWaitingBanner'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import RenderHTML from '@components/RenderHTML'; import SettlementButton from '@components/SettlementButton'; @@ -215,6 +216,8 @@ function ReportPreview({ const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID); const shouldShowRBR = !iouSettled && hasErrors; + const shouldShowWaitingNote = ReportUtils.isInvoiceAwaitingPayment(iouReport); + /* Show subtitle if at least one of the money requests is not being smart scanned, and either: - There is more than one money request – in this case, the "X requests, Y scanning" subtitle is shown; @@ -352,6 +355,12 @@ function ReportPreview({ isDisabled={shouldDisableSubmitButton} /> )} + {shouldShowWaitingNote && ( + + )} From 89e2eedf79ca35828eb6216d1a12cced528b1155 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Apr 2024 12:17:42 +0200 Subject: [PATCH 23/53] add todo --- src/types/onyx/Report.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 56980094cd718..2bad31bc372c3 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -26,6 +26,7 @@ type PendingChatMember = { type Participant = { hidden?: boolean; role?: 'admin' | 'member'; + // TODO: Confirm type?: 'policy' | 'individual'; policyID?: string; }; From d83d22aee6806b16db5513a5b2089cc6f2fb335f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 15 Apr 2024 14:41:11 +0200 Subject: [PATCH 24/53] invoice is not a default for ReportWelcomeText --- src/components/ReportWelcomeText.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 219199c25bc38..24d7747a8fc7e 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); From b9ed19d390825fe254c4f36e64d799e85f017e9d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 15 Apr 2024 14:41:21 +0200 Subject: [PATCH 25/53] fix welcome message --- src/libs/ReportUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7aa2d4a7278b3..8305e90d20d3e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1499,6 +1499,7 @@ function getRoomWelcomeMessage(report: OnyxEntry, isUserPolicyAdmin: boo 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. From 697de5e4abb4d115b1fbb7897de2cde886df52f6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 15 Apr 2024 14:44:58 +0200 Subject: [PATCH 26/53] render proper welcome hero text --- src/components/ReportWelcomeText.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 24d7747a8fc7e..77c40354ea806 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -59,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}); } @@ -68,7 +72,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP } return translate('reportActionsView.sayHello'); - }, [isChatRoom, isSelfDM, translate, reportName]); + }, [isChatRoom, isInvoiceRoom, isSelfDM, translate, reportName]); return ( <> From 60462fd0c3af94813877902a069446633bb3f318 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 15 Apr 2024 15:35:31 +0200 Subject: [PATCH 27/53] render proper invoice room title --- src/libs/ReportUtils.ts | 29 ++++++++++++++++++++--------- src/pages/home/ReportScreen.tsx | 2 ++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8305e90d20d3e..c8163a5e29476 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2983,19 +2983,30 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, parent /** * Get the title for an invoice room. */ -function getInvoicesChatName(report: OnyxEntry, policy: OnyxEntry): string { - const policyName = getPolicyName(report, false, policy); - let receiverName = ''; +function getInvoicesChatName(report: OnyxEntry): string { + const invoiceReceiver = report?.invoiceReceiver; + const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; + const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + let isReceiver = false; + + if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { + isReceiver = true; + } + + if (!isIndividual && PolicyUtils.isPolicyMember(policyID, allPolicies)) { + isReceiver = true; + } - if (report?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { - receiverName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[report.invoiceReceiver.accountID]); + if (isReceiver) { + return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); } - if (report?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.POLICY) { - receiverName = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.invoiceReceiver.policyID}`]); + if (isIndividual) { + return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); } - return `${receiverName} & ${policyName}`; + return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); } /** @@ -3070,7 +3081,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isInvoiceRoom(report)) { - formattedName = getInvoicesChatName(report, policy); + formattedName = getInvoicesChatName(report); } if (formattedName) { diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7e31b69779c26..0f32c6a8b5dc2 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -204,6 +204,7 @@ function ReportScreen({ isOptimisticReport: reportProp?.isOptimisticReport, lastMentionedTime: reportProp?.lastMentionedTime, avatarUrl: reportProp?.avatarUrl, + invoiceReceiver: reportProp?.invoiceReceiver, }), [ reportProp?.lastReadTime, @@ -243,6 +244,7 @@ function ReportScreen({ reportProp?.isOptimisticReport, reportProp?.lastMentionedTime, reportProp?.avatarUrl, + reportProp?.invoiceReceiver, ], ); From a524346f9402a98b9b6900a167c9ca15a1569078 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 15 Apr 2024 15:53:33 +0200 Subject: [PATCH 28/53] integrate report subtitle for invoice room --- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ src/languages/types.ts | 6 ++++++ src/libs/ReportUtils.ts | 39 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b38601420c2df..275d578b8813f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -30,6 +30,8 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, + InvoicesFromParams, + InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2144,6 +2146,8 @@ export default { unlockVBACopy: "You're all set to accept payments by ACH or credit card!", viewUnpaidInvoices: 'View unpaid invoices', sendInvoice: 'Send invoice', + invoicesFrom: ({sender}: InvoicesFromParams) => `Invoices from ${sender}`, + invoicesTo: ({receiver}: InvoicesToParams) => `Invoices to ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Unlock Concierge travel booking', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2263170425c3d..0ed33dd533a6c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -29,6 +29,8 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, + InvoicesFromParams, + InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2172,6 +2174,8 @@ export default { unlockVBACopy: '¡Todo listo para recibir pagos por transferencia o con tarjeta!', viewUnpaidInvoices: 'Ver facturas emitidas pendientes', sendInvoice: 'Enviar factura', + invoicesFrom: ({sender}: InvoicesFromParams) => `Facturas de ${sender}`, + invoicesTo: ({receiver}: InvoicesToParams) => `Facturas a ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', diff --git a/src/languages/types.ts b/src/languages/types.ts index 59e1bfe40af20..8c4d287b7dfce 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -251,6 +251,10 @@ type ViolationsTaxOutOfPolicyParams = {taxName?: string}; type TaskCreatedActionParams = {title: string}; +type InvoicesFromParams = {sender: string}; + +type InvoicesToParams = {receiver: string}; + /* Translation Object types */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type TranslationBaseValue = string | string[] | ((...args: any[]) => string); @@ -403,4 +407,6 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, + InvoicesFromParams, + InvoicesToParams, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c8163a5e29476..db20ba2a45c30 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3009,6 +3009,39 @@ function getInvoicesChatName(report: OnyxEntry): string { return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); } +/** + * Get the subtitle for an invoice room. + */ +function getInvoicesChatSubtitle(report: OnyxEntry): string { + const invoiceReceiver = report?.invoiceReceiver; + const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; + const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + let isReceiver = false; + + if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { + isReceiver = true; + } + + if (!isIndividual && PolicyUtils.isPolicyMember(policyID, allPolicies)) { + isReceiver = true; + } + + if (isReceiver) { + let receiver = ''; + + if (isIndividual) { + receiver = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); + } else { + receiver = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); + } + + return Localize.translateLocal('workspace.invoices.invoicesTo', {receiver}); + } + + return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`])}); +} + /** * Get the title for a report. */ @@ -3102,6 +3135,9 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu * Get either the policyName or domainName the chat is tied to */ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { + if (isInvoiceRoom(report)) { + return getInvoicesChatSubtitle(report); + } if (isChatThread(report)) { return ''; } @@ -3118,9 +3154,6 @@ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { if (isArchivedRoom(report)) { return report?.oldPolicyName ?? ''; } - if (isInvoiceRoom(report)) { - return Localize.translateLocal('workspace.common.invoices'); - } return getPolicyName(report); } From 933499e25c29b7708abe7008f6d014e24a7b5545 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 15 Apr 2024 16:05:15 +0200 Subject: [PATCH 29/53] integrate payer name --- src/components/MoneyReportHeader.tsx | 9 +++------ src/components/ReportActionItem/ReportPreview.tsx | 9 +++------ src/libs/ReportUtils.ts | 13 +++++++++++++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index befcaf470901b..4f66d3f959b49 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -102,6 +102,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const shouldShowWaitingNote = ReportUtils.isInvoiceAwaitingPayment(moneyRequestReport); + const invoicePayerName = ReportUtils.getInvoicePayerName(chatReport); + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; @@ -266,12 +268,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money )} - {shouldShowWaitingNote && ( - - )} + {shouldShowWaitingNote && } {isHoldMenuVisible && requestType !== undefined && ( )} - {shouldShowWaitingNote && ( - - )} + {shouldShowWaitingNote && } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index db20ba2a45c30..d4d85be0d933e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2964,6 +2964,17 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; } +function getInvoicePayerName(report: OnyxEntry): string { + const invoiceReceiver = report?.invoiceReceiver; + const isIndividual = invoiceReceiver?.type === CONST.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. */ @@ -6078,7 +6089,9 @@ export { getIcons, getRoomWelcomeMessage, getDisplayNamesWithTooltips, + getInvoicePayerName, getInvoicesChatName, + getInvoicesChatSubtitle, getReportName, getReport, getReportNotificationPreference, From 80dbeef598dae218b7d43d238701478c8f61651b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 15 Apr 2024 16:28:01 +0200 Subject: [PATCH 30/53] render proper actor of invoice --- src/pages/home/report/ReportActionItemSingle.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 1e0dc432b3fc6..7d0b22b22f527 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.REPORTPREVIEW && iouReport, [action?.actionName, iouReport]); - const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors); + const displayAllActors = useMemo( + () => action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && 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) { From 2f5a0be8a68191c54b94c1c9abbbfda789f67304 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 16 Apr 2024 16:55:12 +0200 Subject: [PATCH 31/53] Support one transaction view for invoices --- src/components/MoneyReportHeader.tsx | 8 ++++++-- src/libs/ReportActionsUtils.ts | 2 +- src/libs/ReportUtils.ts | 15 +++++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 4f66d3f959b49..dbc73973c274b 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -193,7 +193,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(undefined, false, true)} // Shows border if no buttons or next steps are showing below the header - shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextStep && !isSmallScreenWidth)} + shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextStep && !isSmallScreenWidth) && !shouldShowWaitingNote} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} @@ -268,7 +268,11 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money )} - {shouldShowWaitingNote && } + {shouldShowWaitingNote && ( + + + + )} {isHoldMenuVisible && requestType !== undefined && ( | Empty function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[]): string | null { // If the report is not an IOU or Expense report, 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 d4d85be0d933e..630f6f70c0934 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -920,6 +920,13 @@ function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { return report?.type === CONST.REPORT.TYPE.INVOICE; } +/** + * 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 */ @@ -2414,7 +2421,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}); } @@ -3112,7 +3119,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu formattedName = getPolicyExpenseChatName(report, policy); } - if (isMoneyRequestReport(report)) { + if (isMoneyRequestReport(report) || isInvoiceReport(report)) { formattedName = getMoneyRequestReportName(report, policy); } @@ -3185,6 +3192,10 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio return {}; } + if (isInvoiceReport(report)) { + return {reportName: `${getPolicyName(parentReport)} & ${getInvoicePayerName(parentReport)}`}; + } + return { reportName: getReportName(parentReport), workspaceName: getPolicyName(parentReport, true), From 875d321e48b2cb0e3518ca5891ec704107f2f27f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 16 Apr 2024 17:34:09 +0200 Subject: [PATCH 32/53] restrict room leaving --- src/libs/ReportUtils.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 630f6f70c0934..ec34ae4046069 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5155,10 +5155,31 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, isPolicyMember: boolean): boolean { - if (report?.chatType === CONST.REPORT.CHAT_TYPE.INVOICE) { - const isAdmin = !!Object.entries(report.participants ?? {}).find(([participantID, {role}]) => Number(participantID) === currentUserAccountID && role !== CONST.POLICY.ROLE.ADMIN); + 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; - return report.managerID !== currentUserAccountID && report.ownerAccountID !== currentUserAccountID && !isAdmin; + if (isSenderPolicyAdmin) { + return false; + } + + const isReceiverPolicyAdmin = + report?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.POLICY ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; + + if (isReceiverPolicyAdmin) { + return false; + } + + return true; } if (!report?.visibility) { From babdb9acb0882a7e82959539d8b14dd1042af360 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 16 Apr 2024 18:05:46 +0200 Subject: [PATCH 33/53] configure ReportDetailsPage for invoices --- src/libs/ReportUtils.ts | 28 ++++++++++++++++------------ src/pages/ReportDetailsPage.tsx | 24 +++++++++++++----------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ec34ae4046069..77c177bcee9a7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1964,21 +1964,23 @@ function getIcons( return [groupChatIcon]; } - if (isInvoiceRoom(report)) { - const workspaceIcon = getWorkspaceIcon(report, policy); + if (isInvoiceReport(report)) { + const invoiceRoomReport = getReport(report.chatReportID); + const icons = [getWorkspaceIcon(invoiceRoomReport, policy)]; - const receiverPolicyID = Object.values(report.participants ?? {})?.find((participant) => participant.type === 'policy')?.policyID ?? ''; - const receiverPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${receiverPolicyID}`]; - const isWorkspaceToWorkspace = !!receiverPolicyID && receiverPolicy; - const icons = []; + if (invoiceRoomReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + icons.push(...getIconsForParticipants([invoiceRoomReport?.invoiceReceiver.accountID], personalDetails)); - if (isWorkspaceToWorkspace) { - icons.push(getWorkspaceIcon(report, receiverPolicy)); - } else { - icons.push(...getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails)); + return icons; } - return [workspaceIcon, ...icons]; + const receiverPolicy = getPolicy(invoiceRoomReport?.invoiceReceiver?.policyID); + + if (!isEmptyObject(receiverPolicy)) { + icons.push(getWorkspaceIcon(invoiceRoomReport, receiverPolicy)); + } + + return icons; } return getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails); @@ -5642,7 +5644,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 { diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 7f1dadab8c0e1..048b1215787fa 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 !== ''); @@ -182,7 +183,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', @@ -195,19 +196,20 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD return items; }, [ + isSelfDM, + isGroupDMChat, isArchivedRoom, - participants.length, + isGroupChat, + isDefaultRoom, isChatThread, - isMoneyRequestReport, - report, - isGroupDMChat, isPolicyMember, isUserCreatedPolicyRoom, - session, - isSelfDM, - isDefaultRoom, + participants.length, + report, + isMoneyRequestReport, + isInvoiceReport, activeChatMembers.length, - isGroupChat, + session, ]); const displayNamesWithTooltips = useMemo(() => { @@ -261,7 +263,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD - {isMoneyRequestReport ? ( + {isMoneyRequestReport || isInvoiceReport ? ( Date: Tue, 16 Apr 2024 19:00:21 +0200 Subject: [PATCH 34/53] check if the user can pay an invoice --- src/libs/actions/IOU.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 85f4b74f34362..e98fac17bee80 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4857,6 +4857,14 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat return false; } + if (ReportUtils.isInvoiceReport(iouReport)) { + if (chatReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + return chatReport?.invoiceReceiver?.accountID === userAccountID; + } + + return getPolicy(chatReport?.invoiceReceiver?.policyID).role === CONST.POLICY.ROLE.ADMIN; + } + const isPayer = ReportUtils.isPayer( { email: currentUserEmail, From 23dd180b89f7b656b5c2138b49bac1c0a1a4c3ac Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:48:47 +0200 Subject: [PATCH 35/53] improve getInvoicesChatName --- src/libs/ReportUtils.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 28edbd79a9ba4..4f50562de698d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3011,26 +3011,20 @@ function getInvoicesChatName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - let isReceiver = false; - - if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { - isReceiver = true; - } + const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + const isCurrentUserReceiver = + (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - if (!isIndividual && PolicyUtils.isPolicyEmployee(policyID, allPolicies)) { - isReceiver = true; - } - - if (isReceiver) { - return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); + if (isCurrentUserReceiver) { + return getPolicyName(report); } if (isIndividual) { return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); } - return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); + // TODO: Check this flow in a scope of the Invoice V0.3 + return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]); } /** From 12bd9c484f46510441726eb5626cb974153b6f5b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:52:24 +0200 Subject: [PATCH 36/53] add comment --- src/libs/ReportUtils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4f50562de698d..8a0b1d43adf62 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2977,6 +2977,11 @@ 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.INVOICE_RECEIVER_TYPE.INDIVIDUAL; From 7b711429ea0a4ff3a965b531a7097aacd3e945bc Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:53:03 +0200 Subject: [PATCH 37/53] Revert "integrate deleted invoice message" This reverts commit 4e81a6c86a7a00ffbaa7692f17cf82d29f0cfb24. --- src/components/ReportActionItem/MoneyRequestAction.tsx | 2 -- src/languages/en.ts | 1 - src/languages/es.ts | 1 - src/pages/home/report/ReportActionItem.tsx | 2 -- 4 files changed, 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 9bef637bf2924..7d9ba2697c7a0 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -116,8 +116,6 @@ function MoneyRequestAction({ message = 'parentReportAction.reversedTransaction'; } else if (isTrackExpenseAction) { message = 'parentReportAction.deletedExpense'; - } else if (action.childType === CONST.REPORT.TYPE.INVOICE) { - message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } diff --git a/src/languages/en.ts b/src/languages/en.ts index b6de47a3aaf0d..05313113c5216 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2541,7 +2541,6 @@ export default { reversedTransaction: '[Reversed transaction]', deletedTask: '[Deleted task]', hiddenMessage: '[Hidden message]', - deletedInvoice: '[Deleted invoice]', }, threads: { thread: 'Thread', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6b963a8745995..f99eaa4eef106 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3033,7 +3033,6 @@ export default { reversedTransaction: '[Transacción anulada]', deletedTask: '[Tarea eliminada]', hiddenMessage: '[Mensaje oculto]', - deletedInvoice: '[Factura eliminada]', }, threads: { thread: 'Hilo', diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 06e222d86928d..6a6eca9fb7343 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -738,8 +738,6 @@ function ReportActionItem({ message = 'parentReportAction.reversedTransaction'; } else if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { message = 'parentReportAction.deletedExpense'; - } else if (parentReportAction?.childType === CONST.REPORT.TYPE.INVOICE) { - message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } From 78f5f642773d697474736dbe2cbcf4eea6384d8b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:54:01 +0200 Subject: [PATCH 38/53] Revert "integrate deleted invoice message in getTransactionReportName" This reverts commit 19b54e34b25b9d9ddcb7c27ae4b237cdcac744ff. --- src/libs/ReportUtils.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a0b1d43adf62..4f8a16246d612 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -191,9 +191,6 @@ type OptimisticIOUReportAction = Pick< | 'receipt' | 'whisperedToAccountIDs' | 'childReportID' - | 'childType' - | 'childVisibleActionCount' - | 'childCommenterCount' >; type ReportRouteParams = { @@ -2691,10 +2688,6 @@ function getTransactionReportName(reportAction: OnyxEntry Date: Wed, 17 Apr 2024 11:54:30 +0200 Subject: [PATCH 39/53] clear redundant participant props --- src/types/onyx/Report.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 2bad31bc372c3..36f124a4b826f 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -26,9 +26,6 @@ type PendingChatMember = { type Participant = { hidden?: boolean; role?: 'admin' | 'member'; - // TODO: Confirm - type?: 'policy' | 'individual'; - policyID?: string; }; type Participants = Record; From 5625582f11ab9b8a4c136651fce4319b09ad6689 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:16:41 +0200 Subject: [PATCH 40/53] sync changes --- src/CONST.ts | 3 ++- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/libs/ReportUtils.ts | 32 +++++++++++++++++--------------- src/types/onyx/Report.ts | 23 +++++++++++++---------- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0dbf26851ead0..95056970457be 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -52,7 +52,7 @@ const chatTypes = { POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', SELF_DM: 'selfDM', - INVOICE: 'invoiceRoom', + INVOICE: 'invoice', } as const; // Explicit type annotation is required @@ -1423,6 +1423,7 @@ const CONST = { SPLIT: 'split', REQUEST: 'request', TRACK_EXPENSE: 'track-expense', + INVOICE: 'invoice', }, REQUEST_TYPE: { DISTANCE: 'distance', diff --git a/src/languages/en.ts b/src/languages/en.ts index 05313113c5216..f9c845b42a5aa 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -505,6 +505,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 ', @@ -522,7 +523,6 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'track an expense', }, - beginningOfChatHistoryInvoiceRoom: 'Collaboration starts here! 🎉 \nUse this room to view, discuss, and pay invoices.', }, reportAction: { asCopilot: 'as copilot for', diff --git a/src/languages/es.ts b/src/languages/es.ts index f99eaa4eef106..568e14e6b241b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -501,6 +501,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 ', @@ -518,7 +519,6 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'rastrear un gasto', }, - beginningOfChatHistoryInvoiceRoom: '¡Este es el lugar para colaborar! 🎉\nUsa esta sala para ver, discutir y pagar facturas.', }, reportAction: { asCopilot: 'como copiloto de', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4f8a16246d612..76693877b4627 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -141,6 +141,7 @@ type OptimisticAddCommentReportAction = Pick< | 'childStatusNum' | 'childStateNum' | 'errors' + | 'whisperedToAccountIDs' > & {isOptimisticAction: boolean}; type OptimisticReportAction = { @@ -283,6 +284,7 @@ type OptimisticChatReport = Pick< | 'description' | 'writeCapability' | 'avatarUrl' + | 'invoiceReceiver' > & { isOptimisticReport: true; }; @@ -675,6 +677,13 @@ function isChatReport(report: OnyxEntry | EmptyObject): boolean { return report?.type === CONST.REPORT.TYPE.CHAT; } +/** + * Checks if a report is an invoice report. + */ +function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { + return report?.type === CONST.REPORT.TYPE.INVOICE; +} + /** * Checks if a report is an Expense report. */ @@ -862,6 +871,13 @@ function isPolicyExpenseChat(report: OnyxEntry | Participant | EmptyObje return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false); } +/** + * Whether the provided report is an invoice room chat. + */ +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 */ @@ -906,20 +922,6 @@ function isPaidGroupPolicyExpenseReport(report: OnyxEntry): boolean { return isExpenseReport(report) && isPaidGroupPolicy(report); } -/** - * Check if Report is an invoice room - */ -function isInvoiceRoom(report: OnyxEntry): boolean { - return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; -} - -/** - * Check if Report is an invoice report - */ -function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { - return report?.type === CONST.REPORT.TYPE.INVOICE; -} - /** * Checks if the supplied report is an invoice report in Open state and status. */ @@ -2218,7 +2220,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) { diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 36f124a4b826f..344b7df5b2ebb 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -28,6 +28,16 @@ type Participant = { role?: 'admin' | 'member'; }; +type InvoiceReceiver = + | { + type: 'individual'; + accountID: number; + } + | { + type: 'policy'; + policyID: string; + }; + type Participants = Record; type Report = OnyxCommon.OnyxValueWithOfflineFeedback< @@ -128,6 +138,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** Report cached total */ cachedTotal?: string; + /** Invoice room receiver data */ + invoiceReceiver?: InvoiceReceiver; + lastMessageTranslationKey?: string; parentReportID?: string; parentReportActionID?: string; @@ -186,16 +199,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< transactionThreadReportID?: string; fieldList?: Record; - - invoiceReceiver?: - | { - type: typeof CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; - accountID: number; - } - | { - type: typeof CONST.INVOICE_RECEIVER_TYPE.POLICY; - policyID: string; - }; }, PolicyReportField['fieldID'] >; From 1b92e52f10d874af91ab346590e315aa640be244 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:18:30 +0200 Subject: [PATCH 41/53] update invoice receiver const --- src/CONST.ts | 9 ++++----- src/libs/ReportUtils.ts | 10 +++++----- src/libs/actions/IOU.ts | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 95056970457be..6467769263dd0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -839,6 +839,10 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + BUSINESS: 'policy', + }, }, NEXT_STEP: { FINISHED: 'Finished!', @@ -4353,11 +4357,6 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, - - INVOICE_RECEIVER_TYPE: { - INDIVIDUAL: 'individual', - POLICY: 'policy', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 76693877b4627..b2a1fd53afeb1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1971,7 +1971,7 @@ function getIcons( const invoiceRoomReport = getReport(report.chatReportID); const icons = [getWorkspaceIcon(invoiceRoomReport, policy)]; - if (invoiceRoomReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (invoiceRoomReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { icons.push(...getIconsForParticipants([invoiceRoomReport?.invoiceReceiver.accountID], personalDetails)); return icons; @@ -2979,7 +2979,7 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco */ function getInvoicePayerName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; if (isIndividual) { return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiver.accountID]); @@ -3009,7 +3009,7 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, parent */ function getInvoicesChatName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; const isCurrentUserReceiver = @@ -3032,7 +3032,7 @@ function getInvoicesChatName(report: OnyxEntry): string { */ function getInvoicesChatSubtitle(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; let isReceiver = false; @@ -5236,7 +5236,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boo } const isReceiverPolicyAdmin = - report?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.POLICY ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; + report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; if (isReceiverPolicyAdmin) { return false; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 947a800f5ee57..ae93ec7413ac7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5328,7 +5328,7 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat } if (ReportUtils.isInvoiceReport(iouReport)) { - if (chatReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { return chatReport?.invoiceReceiver?.accountID === userAccountID; } From fb69172a697a9892cc07f7a6a80581f3f3ee99e6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:22:27 +0200 Subject: [PATCH 42/53] improve getInvoicesChatSubtitle --- src/libs/ReportUtils.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b2a1fd53afeb1..efd51ef4af990 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3034,18 +3034,11 @@ function getInvoicesChatSubtitle(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - let isReceiver = false; - - if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { - isReceiver = true; - } - - if (!isIndividual && PolicyUtils.isPolicyEmployee(policyID, allPolicies)) { - isReceiver = true; - } + const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + const isCurrentUserReceiver = + (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - if (isReceiver) { + if (isCurrentUserReceiver) { let receiver = ''; if (isIndividual) { @@ -3057,7 +3050,8 @@ function getInvoicesChatSubtitle(report: OnyxEntry): string { return Localize.translateLocal('workspace.invoices.invoicesTo', {receiver}); } - return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`])}); + // TODO: Check this flow in a scope of the Invoice V0.3 + return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`])}); } /** From 2610f14920e41ec6a9b067bab9c9c4dab4c7fa77 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:25:59 +0200 Subject: [PATCH 43/53] revert extra changes --- src/libs/ReportUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index efd51ef4af990..f41b310c2f2ec 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -141,7 +141,6 @@ type OptimisticAddCommentReportAction = Pick< | 'childStatusNum' | 'childStateNum' | 'errors' - | 'whisperedToAccountIDs' > & {isOptimisticAction: boolean}; type OptimisticReportAction = { @@ -192,6 +191,8 @@ type OptimisticIOUReportAction = Pick< | 'receipt' | 'whisperedToAccountIDs' | 'childReportID' + | 'childVisibleActionCount' + | 'childCommenterCount' >; type ReportRouteParams = { From b8d94a7681ffe4d84aa94aa24ca0893d910b449f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:32:51 +0200 Subject: [PATCH 44/53] simplify invoice room subtitle --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f41b310c2f2ec..417cfdc09bf94 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3149,7 +3149,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu */ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { if (isInvoiceRoom(report)) { - return getInvoicesChatSubtitle(report); + return Localize.translateLocal('workspace.common.invoices'); } if (isChatThread(report)) { return ''; From 04ee7b1fb55945358135fcb907271a11a34be45e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:33:55 +0200 Subject: [PATCH 45/53] Revert "integrate report subtitle for invoice room" This reverts commit a524346f9402a98b9b6900a167c9ca15a1569078. --- src/languages/en.ts | 4 ---- src/languages/es.ts | 4 ---- src/languages/types.ts | 6 ------ src/libs/ReportUtils.ts | 31 +++---------------------------- 4 files changed, 3 insertions(+), 42 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f9c845b42a5aa..d2f76ee4ed44f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -30,8 +30,6 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - InvoicesFromParams, - InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2148,8 +2146,6 @@ export default { unlockVBACopy: "You're all set to accept payments by ACH or credit card!", viewUnpaidInvoices: 'View unpaid invoices', sendInvoice: 'Send invoice', - invoicesFrom: ({sender}: InvoicesFromParams) => `Invoices from ${sender}`, - invoicesTo: ({receiver}: InvoicesToParams) => `Invoices to ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Unlock Concierge travel booking', diff --git a/src/languages/es.ts b/src/languages/es.ts index 568e14e6b241b..141e3ad3db918 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -29,8 +29,6 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - InvoicesFromParams, - InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2176,8 +2174,6 @@ export default { unlockVBACopy: '¡Todo listo para recibir pagos por transferencia o con tarjeta!', viewUnpaidInvoices: 'Ver facturas emitidas pendientes', sendInvoice: 'Enviar factura', - invoicesFrom: ({sender}: InvoicesFromParams) => `Facturas de ${sender}`, - invoicesTo: ({receiver}: InvoicesToParams) => `Facturas a ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', diff --git a/src/languages/types.ts b/src/languages/types.ts index 8c4d287b7dfce..59e1bfe40af20 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -251,10 +251,6 @@ type ViolationsTaxOutOfPolicyParams = {taxName?: string}; type TaskCreatedActionParams = {title: string}; -type InvoicesFromParams = {sender: string}; - -type InvoicesToParams = {receiver: string}; - /* Translation Object types */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type TranslationBaseValue = string | string[] | ((...args: any[]) => string); @@ -407,6 +403,4 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, - InvoicesFromParams, - InvoicesToParams, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 417cfdc09bf94..0ba38d8f91dea 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3028,33 +3028,6 @@ function getInvoicesChatName(report: OnyxEntry): string { return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]); } -/** - * Get the subtitle for an invoice room. - */ -function getInvoicesChatSubtitle(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) { - let receiver = ''; - - if (isIndividual) { - receiver = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); - } else { - receiver = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); - } - - return Localize.translateLocal('workspace.invoices.invoicesTo', {receiver}); - } - - // TODO: Check this flow in a scope of the Invoice V0.3 - return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`])}); -} - /** * Get the title for a report. */ @@ -3167,6 +3140,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); } @@ -6208,7 +6184,6 @@ export { getDisplayNamesWithTooltips, getInvoicePayerName, getInvoicesChatName, - getInvoicesChatSubtitle, getReportName, getReport, getReportNotificationPreference, From 3e448b6425e8a7de5f0d3bcfaeeaffcdc438b21b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 22 Apr 2024 14:41:29 +0200 Subject: [PATCH 46/53] use proper type --- src/types/onyx/Report.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 05aa6bc153b95..ead526bba987d 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -30,11 +30,11 @@ type Participant = { type InvoiceReceiver = | { - type: 'individual'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; accountID: number; } | { - type: 'policy'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS; policyID: string; }; From 4b5db30ee86f443b66da0a9151ac7a661046d9e5 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 24 Apr 2024 16:52:22 +0200 Subject: [PATCH 47/53] fix translations, CONST --- src/CONST.ts | 1 - src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/actions/IOU.ts | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index c477cb735c0f2..78ef4e1873c44 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1454,7 +1454,6 @@ const CONST = { PAY: 'pay', SPLIT: 'split', REQUEST: 'request', - TRACK_EXPENSE: 'track-expense', INVOICE: 'invoice', SUBMIT: 'submit', TRACK: 'track', diff --git a/src/languages/en.ts b/src/languages/en.ts index 824e1eca5722f..b1c3a45e9db7d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -518,6 +518,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 529084aa20614..53c631036ab5c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -511,6 +511,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/actions/IOU.ts b/src/libs/actions/IOU.ts index 2e5a633304559..45bcf7c2fad1d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5337,7 +5337,7 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat return chatReport?.invoiceReceiver?.accountID === userAccountID; } - return getPolicy(chatReport?.invoiceReceiver?.policyID).role === CONST.POLICY.ROLE.ADMIN; + return PolicyUtils.getPolicy(chatReport?.invoiceReceiver?.policyID).role === CONST.POLICY.ROLE.ADMIN; } const isPayer = ReportUtils.isPayer( From 7d05ac2e4c4ed420af245f443da22b6839c51627 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 25 Apr 2024 14:31:14 +0200 Subject: [PATCH 48/53] remove payment waiting banner --- src/components/MoneyReportHeader.tsx | 14 ++------- src/components/PaymentWaitingBanner/index.tsx | 31 ------------------- .../ReportActionItem/ReportPreview.tsx | 6 ---- src/languages/en.ts | 2 -- src/languages/es.ts | 2 -- src/languages/types.ts | 3 -- src/libs/ReportUtils.ts | 8 ----- 7 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 src/components/PaymentWaitingBanner/index.tsx diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 0f5d74f71d31b..56dc6bf0075d1 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -22,7 +22,6 @@ import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import * as Expensicons from './Icon/Expensicons'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; -import PaymentWaitingBanner from './PaymentWaitingBanner'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import SettlementButton from './SettlementButton'; @@ -100,10 +99,6 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; - const shouldShowWaitingNote = ReportUtils.isInvoiceAwaitingPayment(moneyRequestReport); - - const invoicePayerName = ReportUtils.getInvoicePayerName(chatReport); - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; @@ -113,7 +108,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount; - const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && isSmallScreenWidth) || shouldShowWaitingNote; + const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && isSmallScreenWidth); const confirmPayment = (type?: PaymentMethodType | undefined) => { if (!type) { @@ -193,7 +188,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(undefined, false, true)} // Shows border if no buttons or next steps are showing below the header - shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextStep && !isSmallScreenWidth) && !shouldShowWaitingNote} + shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextStep && !isSmallScreenWidth)} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} @@ -268,11 +263,6 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money )} - {shouldShowWaitingNote && ( - - - - )} {isHoldMenuVisible && requestType !== undefined && ( - - - {translate('iou.awaitingPayment', {payerName})} - - ); -} - -export default PaymentWaitingBanner; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 4cba437b0d80d..5c78e1e2604ef 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -8,7 +8,6 @@ import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import PaymentWaitingBanner from '@components/PaymentWaitingBanner'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import RenderHTML from '@components/RenderHTML'; import SettlementButton from '@components/SettlementButton'; @@ -216,10 +215,6 @@ function ReportPreview({ const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID); const shouldShowRBR = !iouSettled && hasErrors; - const shouldShowWaitingNote = ReportUtils.isInvoiceAwaitingPayment(iouReport); - - const invoicePayerName = ReportUtils.getInvoicePayerName(chatReport); - /* Show subtitle if at least one of the expenses is not being smart scanned, and either: - There is more than one expense – in this case, the "X expenses, Y scanning" subtitle is shown; @@ -357,7 +352,6 @@ function ReportPreview({ isDisabled={shouldDisableSubmitButton} /> )} - {shouldShowWaitingNote && } diff --git a/src/languages/en.ts b/src/languages/en.ts index d9c6e7f0d5b36..2c3d58c171fe1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -9,7 +9,6 @@ import type { AlreadySignedInParams, AmountEachParams, ApprovedAmountParams, - AwaitingPaymentParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, @@ -740,7 +739,6 @@ export default { set: 'set', changed: 'changed', removed: 'removed', - awaitingPayment: ({payerName}: AwaitingPaymentParams) => `Awaiting payment by ${payerName}`, chooseARate: ({unit}: ReimbursementRateParams) => `Select a workspace reimbursement rate per ${unit}`, }, notificationPreferencesPage: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 91462121f52ca..e56fd67db883a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7,7 +7,6 @@ import type { AlreadySignedInParams, AmountEachParams, ApprovedAmountParams, - AwaitingPaymentParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, @@ -735,7 +734,6 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', - awaitingPayment: ({payerName}: AwaitingPaymentParams) => `A la espera de pago por ${payerName}`, chooseARate: ({unit}: ReimbursementRateParams) => `Seleccione una tasa de reembolso del espacio de trabajo por ${unit}`, }, notificationPreferencesPage: { diff --git a/src/languages/types.ts b/src/languages/types.ts index c81720f1773bd..9426e343bbf0a 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -136,8 +136,6 @@ type PayerSettledParams = {amount: number | string}; type WaitingOnBankAccountParams = {submitterDisplayName: string}; -type AwaitingPaymentParams = {payerName: string}; - type CanceledRequestParams = {amount: string; submitterDisplayName: string}; type AdminCanceledRequestParams = {manager: string; amount: string}; @@ -398,7 +396,6 @@ export type { ViolationsTagOutOfPolicyParams, ViolationsTaxOutOfPolicyParams, WaitingOnBankAccountParams, - AwaitingPaymentParams, WalletProgramParams, UsePlusButtonParams, WeSentYouMagicSignInLinkParams, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 12df7ba20a52a..781cf13d23f64 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6286,13 +6286,6 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } -/** - * Check if a invoice report is awaiting for payment - */ -function isInvoiceAwaitingPayment(report: OnyxEntry): boolean { - return !isSettled(report?.reportID ?? '') && isInvoiceReport(report); -} - export { addDomainToShortMention, areAllRequestsBeingSmartScanned, @@ -6519,7 +6512,6 @@ export { isInvoiceRoom, isInvoiceReport, isOpenInvoiceReport, - isInvoiceAwaitingPayment, navigateToDetailsPage, navigateToPrivateNotes, parseReportRouteParams, From 4264b50f4c90d38f42cae81ab6d1b12c5254d333 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 25 Apr 2024 14:36:44 +0200 Subject: [PATCH 49/53] show invoice room in sidebar --- src/libs/ReportUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 781cf13d23f64..7589c7963a0f1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4896,7 +4896,8 @@ function shouldReportBeInOptionList({ !isMoneyRequestReport(report) && !isTaskReport(report) && !isSelfDM(report) && - !isGroupChat(report)) + !isGroupChat(report) && + !isInvoiceRoom(report)) ) { return false; } From c852145ed838a9b4b2caffef35cfeed01f2379a6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 26 Apr 2024 10:22:49 +0200 Subject: [PATCH 50/53] clarify comments --- src/libs/ReportActionsUtils.ts | 2 +- src/libs/ReportUtils.ts | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index a666d81a180e4..9491b61415365 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -226,7 +226,7 @@ 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 && report?.type !== CONST.REPORT.TYPE.INVOICE) { return null; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 686808f016113..d61945f506c8b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -675,9 +675,6 @@ function isChatReport(report: OnyxEntry | EmptyObject): boolean { return report?.type === CONST.REPORT.TYPE.CHAT; } -/** - * Checks if a report is an invoice report. - */ function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { return report?.type === CONST.REPORT.TYPE.INVOICE; } @@ -869,9 +866,6 @@ function isPolicyExpenseChat(report: OnyxEntry | Participant | EmptyObje return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false); } -/** - * Whether the provided report is an invoice room chat. - */ function isInvoiceRoom(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; } From 1a24b88541e18566469ced08faec407f7a2e5f2c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 26 Apr 2024 10:26:38 +0200 Subject: [PATCH 51/53] restrict for request options invoice room --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d61945f506c8b..150c9c7aebafd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5270,7 +5270,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)) { return []; } From b024fe556c59a05dba78b8f79566a9199c1f6a56 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 26 Apr 2024 10:34:15 +0200 Subject: [PATCH 52/53] restrict for request options invoice room --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 150c9c7aebafd..a558dd9b941b1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5270,7 +5270,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)) || isInvoiceRoom(report)) { + if (isChatThread(report) || isTaskReport(report) || (!canUseTrackExpense && isSelfDM(report)) || isInvoiceRoom(report) || isInvoiceReport(report)) { return []; } From 396447f1b601e7eb29063542dd7e1b74b2a86fc0 Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Fri, 26 Apr 2024 22:57:35 +0200 Subject: [PATCH 53/53] Fix TS issues --- .../ReportActionCompose/AttachmentPickerWithMenuItems.tsx | 5 +++++ src/pages/iou/request/IOURequestStartPage.tsx | 1 + 2 files changed, 6 insertions(+) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index 1adb161a92e95..924d9c5f1cd96 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -147,6 +147,11 @@ function AttachmentPickerWithMenuItems({ text: translate('iou.trackExpense'), onSelected: () => 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/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);