diff --git a/assets/images/invoice-generic.svg b/assets/images/invoice-generic.svg
new file mode 100644
index 000000000000..d0e2662c4084
--- /dev/null
+++ b/assets/images/invoice-generic.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 88a7adcbcb84..28c854d46203 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -314,6 +314,11 @@ const ROUTES = {
route: ':action/:iouType/start/:transactionID/:reportID',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `${action as string}/${iouType as string}/start/${transactionID}/${reportID}` as const,
},
+ MONEY_REQUEST_STEP_SEND_FROM: {
+ route: 'create/:iouType/from/:transactionID/:reportID',
+ getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
+ getUrlWithBackToParam(`create/${iouType as string}/from/${transactionID}/${reportID}`, backTo),
+ },
MONEY_REQUEST_STEP_CONFIRMATION: {
route: ':action/:iouType/confirmation/:transactionID/:reportID',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) =>
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index daf2a9791930..bfe2935eeb7f 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -161,6 +161,7 @@ const SCREENS = {
STEP_WAYPOINT: 'Money_Request_Step_Waypoint',
STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount',
STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate',
+ STEP_SEND_FROM: 'Money_Request_Step_Send_From',
CURRENCY: 'Money_Request_Currency',
WAYPOINT: 'Money_Request_Waypoint',
EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint',
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index 208543b2d9c2..822ae0a04a42 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -88,6 +88,7 @@ import ImageCropSquareMask from '@assets/images/image-crop-square-mask.svg';
import Info from '@assets/images/info.svg';
import QBOSquare from '@assets/images/integrationicons/qbo-icon-square.svg';
import XeroSquare from '@assets/images/integrationicons/xero-icon-square.svg';
+import InvoiceGeneric from '@assets/images/invoice-generic.svg';
import Invoice from '@assets/images/invoice.svg';
import Key from '@assets/images/key.svg';
import Keyboard from '@assets/images/keyboard.svg';
@@ -250,6 +251,7 @@ export {
ImageCropSquareMask,
Info,
Invoice,
+ InvoiceGeneric,
Key,
Keyboard,
Link,
diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index 9946dea9d5a7..850173433cf0 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -202,6 +202,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
!!optionItem.isTaskReport ||
!!optionItem.isThread ||
!!optionItem.isMoneyRequestReport ||
+ !!optionItem.isInvoiceReport ||
ReportUtils.isGroupChat(report)
}
/>
diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx
index 1afadd8ea856..2bfb78cf9340 100644
--- a/src/components/MenuItem.tsx
+++ b/src/components/MenuItem.tsx
@@ -87,6 +87,9 @@ type MenuItemBaseProps = {
/** Any additional styles to apply on the badge element */
badgeStyle?: ViewStyle;
+ /** Any additional styles to apply to the label */
+ labelStyle?: StyleProp;
+
/** Any adjustments to style when menu item is hovered or pressed */
hoverAndPressStyle?: StyleProp>;
@@ -267,6 +270,7 @@ function MenuItem(
outerWrapperStyle,
containerStyle,
titleStyle,
+ labelStyle,
hoverAndPressStyle,
descriptionTextStyle,
badgeStyle,
@@ -424,7 +428,7 @@ function MenuItem(
return (
{!!label && !isLabelHoverable && (
-
+
{label}
)}
@@ -460,7 +464,7 @@ function MenuItem(
<>
{!!label && isLabelHoverable && (
-
+
{label}
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 522158b8edb7..7e0139b147fd 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -109,7 +109,7 @@ function MoneyReportHeader({
const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport);
- const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton;
+ const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton);
const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx
index b76ca99751af..59d7e9bb7fcd 100755
--- a/src/components/MoneyRequestConfirmationList.tsx
+++ b/src/components/MoneyRequestConfirmationList.tsx
@@ -5,7 +5,7 @@ import React, {useCallback, useEffect, useMemo, useReducer, useState} from 'reac
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
@@ -25,6 +25,7 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import {isTaxTrackingEnabled} from '@libs/PolicyUtils';
import * as ReceiptUtils from '@libs/ReceiptUtils';
import * as ReportUtils from '@libs/ReportUtils';
+import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils';
import playSound, {SOUNDS} from '@libs/Sound';
import * as TransactionUtils from '@libs/TransactionUtils';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
@@ -42,6 +43,7 @@ import type {DropdownOption} from './ButtonWithDropdownMenu/types';
import ConfirmedRoute from './ConfirmedRoute';
import ConfirmModal from './ConfirmModal';
import FormHelpMessage from './FormHelpMessage';
+import MenuItem from './MenuItem';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
import OptionsSelector from './OptionsSelector';
import PDFThumbnail from './PDFThumbnail';
@@ -73,6 +75,9 @@ type MoneyRequestConfirmationListOnyxProps = {
/** Last selected distance rates */
lastSelectedDistanceRates: OnyxEntry>;
+
+ /** The list of all policies */
+ allPolicies: OnyxCollection;
};
type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & {
@@ -217,6 +222,7 @@ function MoneyRequestConfirmationList({
reportActionID,
defaultMileageRate,
lastSelectedDistanceRates,
+ allPolicies,
action = CONST.IOU.ACTION.CREATE,
}: MoneyRequestConfirmationListProps) {
const theme = useTheme();
@@ -230,6 +236,7 @@ function MoneyRequestConfirmationList({
const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT;
const isTypeSend = iouType === CONST.IOU.TYPE.PAY;
const isTypeTrackExpense = iouType === CONST.IOU.TYPE.TRACK;
+ const isTypeInvoice = iouType === CONST.IOU.TYPE.INVOICE;
const transactionID = transaction?.transactionID ?? '';
const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '';
@@ -275,6 +282,13 @@ function MoneyRequestConfirmationList({
const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]);
+ const senderWorkspace = useMemo(() => {
+ const senderWorkspaceParticipant = selectedParticipantsProp.find((participant) => participant.isSender);
+ return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`];
+ }, [allPolicies, selectedParticipantsProp]);
+
+ const canUpdateSenderWorkspace = useMemo(() => PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate, [allPolicies, transaction?.isFromGlobalCreate]);
+
// A flag for showing the tags field
const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]);
@@ -376,7 +390,9 @@ function MoneyRequestConfirmationList({
const splitOrRequestOptions: Array> = useMemo(() => {
let text;
- if (isTypeTrackExpense) {
+ if (isTypeInvoice) {
+ text = translate('iou.sendInvoice', {amount: formattedAmount});
+ } else if (isTypeTrackExpense) {
text = translate('iou.trackExpense');
} else if (isTypeSplit && iouAmount === 0) {
text = translate('iou.splitExpense');
@@ -395,7 +411,7 @@ function MoneyRequestConfirmationList({
value: iouType,
},
];
- }, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount]);
+ }, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount, isTypeInvoice]);
const selectedParticipants = useMemo(() => selectedParticipantsProp.filter((participant) => participant.selected), [selectedParticipantsProp]);
const payeePersonalDetails = useMemo(() => payeePersonalDetailsProp ?? currentUserPersonalDetails, [payeePersonalDetailsProp, currentUserPersonalDetails]);
@@ -984,6 +1000,26 @@ function MoneyRequestConfirmationList({
)}
+ {isTypeInvoice && (
+