Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GBR and Settlement button for the receiver on the invoice report preview #41859

Merged
merged 25 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
665b508
updated settlement button, added invoice payment
waterim May 8, 2024
22edb59
remove unused activeUserID
waterim May 8, 2024
5e56c7f
fixed payments and ts issues
waterim May 9, 2024
1ea1fc3
remove unused canIOUbePaid part, remove unused useState
waterim May 10, 2024
8afc6fc
added translations
waterim May 10, 2024
a4daf61
Merge remote-tracking branch 'upstream/main' into feat-40437-invoice
waterim May 13, 2024
faaa568
update params in payInvoice
waterim May 13, 2024
1505652
remove PERSONAL & BUSINESS payment types
waterim May 13, 2024
a52e68d
update isSettled
waterim May 14, 2024
bff509d
Merge remote-tracking branch 'upstream/main' into feat-40437-invoice
waterim May 16, 2024
6c2b1a1
new view of the settlement button
waterim May 17, 2024
86f6fb2
fix modal, show amount, update text
waterim May 21, 2024
524d640
update hover, remove business button
waterim May 21, 2024
e0ed718
Revert "draft fix"
waterim May 21, 2024
6da0e8e
Revert "Revert "draft fix""
waterim May 21, 2024
7ca70bf
update wallet for invoiceReport
waterim May 22, 2024
97a41aa
stylings change
waterim May 23, 2024
cba930e
updated stylings
waterim May 23, 2024
35da70f
fixed focus
waterim May 24, 2024
e5e73cc
translations
waterim May 24, 2024
1003662
Merge branch 'main' into feat-40437-invoice
VickyStash May 29, 2024
bb88d41
Update back title inside the submenu
VickyStash May 29, 2024
e600f02
Merge branch 'main' into feat-40437-invoice
VickyStash May 31, 2024
9b7056f
Minor UI fix
VickyStash May 31, 2024
54de5b5
Merge branch 'main' into feat-40437-invoice
VickyStash Jun 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions assets/images/checkmark-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import ChatBubbleUnread from '@assets/images/chatbubble-unread.svg';
import ChatBubble from '@assets/images/chatbubble.svg';
import ChatBubbles from '@assets/images/chatbubbles.svg';
import CheckCircle from '@assets/images/check-circle.svg';
import CheckmarkCircle from '@assets/images/checkmark-circle.svg';
import Checkmark from '@assets/images/checkmark.svg';
import Close from '@assets/images/close.svg';
import ClosedSign from '@assets/images/closed-sign.svg';
Expand Down Expand Up @@ -350,4 +351,5 @@ export {
DocumentPlus,
Clear,
CheckCircle,
CheckmarkCircle,
};
2 changes: 1 addition & 1 deletion src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ function MenuItem(
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const combinedStyle = [style, styles.popoverMenuItem];
const combinedStyle = [styles.popoverMenuItem, style];
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {isExecuting, singleExecution, waitForNavigate} = useContext(MenuItemGroupContext) ?? {};

Expand Down
8 changes: 5 additions & 3 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea

const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport);

const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation;
const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation;

const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
Expand All @@ -120,14 +120,16 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const isMoreContentShown = shouldShowNextStep || hasScanningReceipt || (shouldShowAnyButton && shouldUseNarrowLayout);

const confirmPayment = (type?: PaymentMethodType | undefined) => {
if (!type) {
if (!type || !chatReport) {
return;
}
setPaymentType(type);
setRequestType('pay');
if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) {
setIsHoldMenuVisible(true);
} else if (chatReport) {
} else if (ReportUtils.isInvoiceReport(moneyRequestReport)) {
IOU.payInvoice(type, chatReport, moneyRequestReport);
} else {
IOU.payMoneyRequest(type, chatReport, moneyRequestReport, true);
}
};
Expand Down
36 changes: 32 additions & 4 deletions src/components/PopoverMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import lodashIsEqual from 'lodash/isEqual';
import type {RefObject} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import type {ModalProps} from 'react-native-modal';
import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type {AnchorPosition} from '@src/styles';
Expand All @@ -26,6 +28,9 @@ type PopoverMenuItem = MenuItemProps & {
/** Sub menu items to be rendered after a menu item is selected */
subMenuItems?: PopoverMenuItem[];

/** Back button text to be shown if sub menu items are opened */
backButtonText?: string;

/** Determines whether the menu item is disabled or not */
disabled?: boolean;
};
Expand Down Expand Up @@ -98,6 +103,7 @@ function PopoverMenu({
shouldEnableNewFocusManagement,
}: PopoverMenuProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {isSmallScreenWidth} = useResponsiveLayout();
const selectedItemIndex = useRef<number | null>(null);

Expand All @@ -111,6 +117,7 @@ function PopoverMenu({
if (selectedItem?.subMenuItems) {
setCurrentMenuItems([...selectedItem.subMenuItems]);
setEnteredSubMenuIndexes([...enteredSubMenuIndexes, index]);
setFocusedIndex(-1);
} else {
selectedItemIndex.current = index;
onItemSelected(selectedItem, index);
Expand All @@ -132,17 +139,22 @@ function PopoverMenu({
const renderBackButtonItem = () => {
const previousMenuItems = getPreviousSubMenu();
const previouslySelectedItem = previousMenuItems[enteredSubMenuIndexes[enteredSubMenuIndexes.length - 1]];
const hasBackButtonText = !!previouslySelectedItem.backButtonText;

return (
<MenuItem
key={previouslySelectedItem.text}
icon={Expensicons.BackArrow}
iconFill="gray"
title={previouslySelectedItem.text}
iconFill={theme.icon}
style={hasBackButtonText ? styles.pv0 : undefined}
title={hasBackButtonText ? previouslySelectedItem.backButtonText : previouslySelectedItem.text}
titleStyle={hasBackButtonText ? styles.createMenuHeaderText : undefined}
shouldShowBasicTitle={hasBackButtonText}
shouldCheckActionAllowedOnPress={false}
description={previouslySelectedItem.description}
onPress={() => {
setCurrentMenuItems(previousMenuItems);
setFocusedIndex(-1);
enteredSubMenuIndexes.splice(-1);
}}
/>
Expand Down Expand Up @@ -199,7 +211,7 @@ function PopoverMenu({
shouldEnableNewFocusManagement={shouldEnableNewFocusManagement}
>
<View style={isSmallScreenWidth ? {} : styles.createMenuContainer}>
{!!headerText && <Text style={[styles.createMenuHeaderText, styles.ml3]}>{headerText}</Text>}
{!!headerText && enteredSubMenuIndexes.length === 0 && <Text style={[styles.createMenuHeaderText, styles.ph5, styles.pv3]}>{headerText}</Text>}
{enteredSubMenuIndexes.length > 0 && renderBackButtonItem()}
{currentMenuItems.map((item, menuIndex) => (
<FocusableMenuItem
Expand Down Expand Up @@ -237,5 +249,21 @@ function PopoverMenu({

PopoverMenu.displayName = 'PopoverMenu';

export default React.memo(PopoverMenu);
export default React.memo(
PopoverMenu,
(prevProps, nextProps) =>
!lodashIsEqual(prevProps.menuItems, nextProps.menuItems) &&
prevProps.isVisible === nextProps.isVisible &&
lodashIsEqual(prevProps.anchorPosition, nextProps.anchorPosition) &&
prevProps.anchorRef === nextProps.anchorRef &&
prevProps.headerText === nextProps.headerText &&
prevProps.fromSidebarMediumScreen === nextProps.fromSidebarMediumScreen &&
lodashIsEqual(prevProps.anchorAlignment, nextProps.anchorAlignment) &&
prevProps.animationIn === nextProps.animationIn &&
prevProps.animationOut === nextProps.animationOut &&
prevProps.animationInTiming === nextProps.animationInTiming &&
prevProps.disableAnimation === nextProps.disableAnimation &&
prevProps.withoutOverlay === nextProps.withoutOverlay &&
prevProps.shouldSetModalVisibility === nextProps.shouldSetModalVisibility,
);
export type {PopoverMenuItem, PopoverMenuProps};
16 changes: 14 additions & 2 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ function ReportPreview({

const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport);

const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(iouReport) && (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage;
const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage;

const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID);
const shouldShowRBR = !iouSettled && hasErrors;
Expand Down Expand Up @@ -280,6 +280,17 @@ function ReportPreview({
};
}, [formattedMerchant, formattedDescription, moneyRequestComment, translate, numberOfRequests, numberOfScanningReceipts, numberOfPendingRequests]);

const confirmPayment = (paymentMethodType?: PaymentMethodType) => {
if (!paymentMethodType || !chatReport || !iouReport) {
return;
}
if (ReportUtils.isInvoiceReport(iouReport)) {
IOU.payInvoice(paymentMethodType, chatReport, iouReport);
} else {
IOU.payMoneyRequest(paymentMethodType, chatReport, iouReport);
}
};

return (
<OfflineWithFeedback
pendingAction={iouReport?.pendingFields?.preview}
Expand Down Expand Up @@ -364,11 +375,12 @@ function ReportPreview({
</View>
{shouldShowSettlementButton && (
<SettlementButton
formattedAmount={getDisplayAmount() ?? ''}
currency={iouReport?.currency}
policyID={policyID}
chatReportID={chatReportID}
iouReport={iouReport}
onPress={(paymentType?: PaymentMethodType) => chatReport && iouReport && paymentType && IOU.payMoneyRequest(paymentType, chatReport, iouReport)}
onPress={confirmPayment}
enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
addBankAccountRoute={bankAccountRoute}
shouldHidePaymentOptions={!shouldShowPayButton}
Expand Down
28 changes: 26 additions & 2 deletions src/components/SettlementButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {LastPaymentMethod, Policy, Report} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
import type {PaymentType} from './ButtonWithDropdownMenu/types';
import * as Expensicons from './Icon/Expensicons';
Expand Down Expand Up @@ -149,9 +150,10 @@ function SettlementButton({

const session = useSession();
const chatReport = ReportUtils.getReport(chatReportID);
const isInvoiceReport = (!isEmptyObject(iouReport) && ReportUtils.isInvoiceReport(iouReport)) || false;
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport);
const shouldShowPaywithExpensifyOption = !isPaidGroupPolicy || (!shouldHidePaymentOptions && ReportUtils.isPayer(session, iouReport as OnyxEntry<Report>));
const shouldShowPayElsewhereOption = !isPaidGroupPolicy || policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL;
const shouldShowPayElsewhereOption = (!isPaidGroupPolicy || policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL) && !isInvoiceReport;
const paymentButtonOptions = useMemo(() => {
const buttonOptions = [];
const isExpenseReport = ReportUtils.isExpenseReport(iouReport);
Expand All @@ -178,7 +180,7 @@ function SettlementButton({
value: CONST.IOU.REPORT_ACTION_TYPE.APPROVE,
disabled: !!shouldDisableApproveButton,
};
const canUseWallet = !isExpenseReport && currency === CONST.CURRENCY.USD;
const canUseWallet = !isExpenseReport && !isInvoiceReport && currency === CONST.CURRENCY.USD;

// Only show the Approve button if the user cannot pay the expense
if (shouldHidePaymentOptions && shouldShowApproveButton) {
Expand All @@ -199,6 +201,23 @@ function SettlementButton({
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);
}

if (isInvoiceReport) {
buttonOptions.push({
text: translate('iou.settlePersonal', {formattedAmount}),
icon: Expensicons.User,
value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
backButtonText: translate('iou.individual'),
subMenuItems: [
{
text: translate('iou.payElsewhere', {formattedAmount: ''}),
icon: Expensicons.Cash,
value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.ELSEWHERE),
},
],
});
}

if (shouldShowApproveButton) {
buttonOptions.push(approveButtonOption);
}
Expand All @@ -211,6 +230,7 @@ function SettlementButton({
// We don't want to reorder the options when the preferred payment method changes while the button is still visible
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currency, formattedAmount, iouReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton, shouldDisableApproveButton]);

const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) {
triggerKYCFlow(event, iouPaymentType);
Expand Down Expand Up @@ -252,6 +272,10 @@ function SettlementButton({
<ButtonWithDropdownMenu<PaymentType>
success
buttonRef={buttonRef}
shouldAlwaysShowDropdownMenu={isInvoiceReport}
customText={isInvoiceReport ? translate('iou.settlePayment', {formattedAmount}) : undefined}
menuHeaderText={isInvoiceReport ? translate('workspace.invoices.paymentMethods.chooseInvoiceMethod') : undefined}
isSplitButton={!isInvoiceReport}
isDisabled={isDisabled}
isLoading={isLoading}
onPress={(event, iouPaymentType) => selectPaymentType(event, iouPaymentType, triggerKYCFlow)}
Expand Down
12 changes: 12 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,11 @@ export default {
deleteConfirmation: 'Are you sure that you want to delete this expense?',
settledExpensify: 'Paid',
settledElsewhere: 'Paid elsewhere',
individual: 'Individual',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} with Expensify` : `Pay with Expensify`),
settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as an individual` : `Pay as an individual`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount}`,
settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as a business` : `Pay as a business`),
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} elsewhere` : `Pay elsewhere`),
nextStep: 'Next Steps',
finished: 'Finished',
Expand Down Expand Up @@ -2497,6 +2501,14 @@ export default {
viewUnpaidInvoices: 'View unpaid invoices',
sendInvoice: 'Send invoice',
sendFrom: 'Send from',
paymentMethods: {
personal: 'Personal',
business: 'Business',
chooseInvoiceMethod: 'Choose a payment method below:',
addBankAccount: 'Add bank account',
payingAsIndividual: 'Paying as an individual',
payingAsBusiness: 'Paying as a business',
},
},
travel: {
unlockConciergeBookingTravel: 'Unlock Concierge travel booking',
Expand Down
12 changes: 12 additions & 0 deletions src/languages/es.ts
waterim marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,11 @@ export default {
deleteConfirmation: '¿Estás seguro de que quieres eliminar esta solicitud?',
settledExpensify: 'Pagado',
settledElsewhere: 'Pagado de otra forma',
individual: 'Individual',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} con Expensify` : `Pagar con Expensify`),
settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pago ${formattedAmount} como individuo` : `Pago individual`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount}`,
settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} como negocio` : `Pagar como empresa`),
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} de otra forma` : `Pagar de otra forma`),
nextStep: 'Pasos Siguientes',
finished: 'Finalizado',
Expand Down Expand Up @@ -2536,6 +2540,14 @@ export default {
viewUnpaidInvoices: 'Ver facturas emitidas pendientes',
sendInvoice: 'Enviar factura',
sendFrom: 'Enviar desde',
paymentMethods: {
personal: 'Personal',
business: 'Empresas',
chooseInvoiceMethod: 'Elija un método de pago:',
addBankAccount: 'Añadir cuenta bancaria',
payingAsIndividual: 'Pago individual',
payingAsBusiness: 'Pagar como una empresa',
},
},
travel: {
unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge',
Expand Down
9 changes: 9 additions & 0 deletions src/libs/API/parameters/PayInvoiceParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';

type PayInvoiceParams = {
reportID: string;
reportActionID: string;
paymentMethodType: PaymentMethodType;
};

export default PayInvoiceParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,5 @@ export type {default as LeavePolicyParams} from './LeavePolicyParams';
export type {default as OpenPolicyAccountingPageParams} from './OpenPolicyAccountingPageParams';
export type {default as SearchParams} from './Search';
export type {default as SendInvoiceParams} from './SendInvoiceParams';
export type {default as PayInvoiceParams} from './PayInvoiceParams';
export type {default as MarkAsCashParams} from './MarkAsCashParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ const WRITE_COMMANDS = {
LEAVE_POLICY: 'LeavePolicy',
ACCEPT_SPOTNANA_TERMS: 'AcceptSpotnanaTerms',
SEND_INVOICE: 'SendInvoice',
PAY_INVOICE: 'PayInvoice',
MARK_AS_CASH: 'MarkAsCash',
} as const;

Expand Down Expand Up @@ -433,6 +434,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.LEAVE_POLICY]: Parameters.LeavePolicyParams;
[WRITE_COMMANDS.ACCEPT_SPOTNANA_TERMS]: EmptyObject;
[WRITE_COMMANDS.SEND_INVOICE]: Parameters.SendInvoiceParams;
[WRITE_COMMANDS.PAY_INVOICE]: Parameters.PayInvoiceParams;
[WRITE_COMMANDS.MARK_AS_CASH]: Parameters.MarkAsCashParams;
};

Expand Down
2 changes: 1 addition & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ function isPolicyExpenseChat(report: OnyxEntry<Report> | Participant | EmptyObje
return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false);
}

function isInvoiceRoom(report: OnyxEntry<Report>): boolean {
function isInvoiceRoom(report: OnyxEntry<Report> | EmptyObject): boolean {
return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE;
}

Expand Down
Loading
Loading