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

pay conditions based on reimbursement settings #36814

6 changes: 3 additions & 3 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1373,9 +1373,9 @@ const CONST = {
OWNER_EMAIL_FAKE: '_FAKE_',
OWNER_ACCOUNT_ID_FAKE: 0,
REIMBURSEMENT_CHOICES: {
REIMBURSEMENT_YES: 'reimburseYes',
REIMBURSEMENT_NO: 'reimburseNo',
REIMBURSEMENT_MANUAL: 'reimburseManual',
REIMBURSEMENT_YES: 'reimburseYes', // Direct
REIMBURSEMENT_NO: 'reimburseNo', // None
REIMBURSEMENT_MANUAL: 'reimburseManual', // Indirect
},
ID_FAKE: '_FAKE_',
EMPTY: 'EMPTY',
Expand Down
5 changes: 1 addition & 4 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(moneyRequestReport, policy);
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicy(moneyRequestReport);
const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && session?.accountID === moneyRequestReport.managerID;
const isPayer = isPaidGroupPolicy
? // In a group policy, the admin approver can pay the report directly by skipping the approval step
isPolicyAdmin && (isApproved || isManager)
: isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager);
const isPayer = PolicyUtils.isPolicyPayer(policy, session, isApproved, isManager, isPolicyAdmin);
const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);

Expand Down
6 changes: 1 addition & 5 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ function ReportPreview({

const isApproved = ReportUtils.isReportApproved(iouReport);
const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport);
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(iouReport);
const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID);
const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length;

Expand Down Expand Up @@ -210,10 +209,7 @@ function ReportPreview({

const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport);
const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && policy?.role === CONST.POLICY.ROLE.ADMIN;
const isPayer = isPaidGroupPolicy
? // In a paid group policy, the admin approver can pay the report directly by skipping the approval step
isPolicyAdmin && (isApproved || isCurrentUserManager)
: isPolicyAdmin || (isMoneyRequestReport && isCurrentUserManager);
const isPayer = PolicyUtils.isPolicyPayer(policy, session, isApproved, isCurrentUserManager, isPolicyAdmin);
const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
const shouldShowPayButton = useMemo(
Expand Down
7 changes: 6 additions & 1 deletion src/components/SettlementButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject';
import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
import * as Expensicons from './Icon/Expensicons';
import KYCWall from './KYCWall';
import {useSession} from './OnyxProvider';

type KYCFlowEvent = GestureResponderEvent | KeyboardEvent | undefined;

Expand Down Expand Up @@ -133,6 +134,10 @@ function SettlementButton({
PaymentMethods.openWalletPage();
}, []);

const policy = ReportUtils.getPolicy(policyID);
const session = useSession();
const shouldShowPaywithExpensifyOption = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? policy?.reimbursersEmail === session?.email : false;
c3024 marked this conversation as resolved.
Show resolved Hide resolved

const paymentButtonOptions = useMemo(() => {
const buttonOptions = [];
const isExpenseReport = ReportUtils.isExpenseReport(iouReport);
Expand Down Expand Up @@ -172,7 +177,7 @@ function SettlementButton({
if (canUseWallet) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.EXPENSIFY]);
}
if (isExpenseReport) {
if (isExpenseReport && shouldShowPaywithExpensifyOption) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]);
}
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);
Expand Down
13 changes: 12 additions & 1 deletion src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx';
import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags, Session} from '@src/types/onyx';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

Expand Down Expand Up @@ -110,6 +110,16 @@ const isPolicyAdmin = (policy: OnyxEntry<Policy> | EmptyObject): boolean => poli

const isPolicyMember = (policyID: string, policies: OnyxCollection<Policy>): boolean => Object.values(policies ?? {}).some((policy) => policy?.id === policyID);

const isPolicyPayer = (policy: OnyxEntry<Policy> | EmptyObject, session: OnyxEntry<Session>, isApproved: boolean, isManager: boolean, isAdmin: boolean): boolean => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change the name to isPayer since it would be used for IOUs too to avoid a regression

Suggested change
const isPolicyPayer = (policy: OnyxEntry<Policy> | EmptyObject, session: OnyxEntry<Session>, isApproved: boolean, isManager: boolean, isAdmin: boolean): boolean => {
const isPayer = (policy: OnyxEntry<Policy> | EmptyObject, session: OnyxEntry<Session>, isApproved: boolean, isManager: boolean, isAdmin: boolean): boolean => {

if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES) {
const isReimburser = session?.email === policy?.reimbursersEmail;
c3024 marked this conversation as resolved.
Show resolved Hide resolved
return isReimburser && (isApproved || isManager);
}
if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL) {
return isAdmin && (isApproved || isManager);
}
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to show the Pay button for IOUs (and free workspaces for now), I think we need to check if the user is manager and if the policy is not a group policy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcochavezf

Thank you! Focused too much only on policies and let this blunder slide. Fixed now.

One last question, we show only Pay elsewhere for free workspaces, right? Or do we show Pay with Expensify as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good question, we should show both Pay elsewhere and Pay with Expensify for non-group policies (free workspaces and IOUs). In theory for free workspaces the admin and the manager should be the same, so we should probably show both buttons to the manager if policy is a non-group policy/workspace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Done. 👍

};
/**
* Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID.
*
Expand Down Expand Up @@ -269,6 +279,7 @@ export {
getCountOfEnabledTagsOfList,
isPendingDeletePolicy,
isPolicyMember,
isPolicyPayer,
isPaidGroupPolicy,
extractPolicyIDFromPath,
getPathWithoutPolicyID,
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ type Policy = {
trackingEnabled: boolean;
};

/** Email of the reimburser when reimbursement is set direct */
reimbursersEmail?: string;
c3024 marked this conversation as resolved.
Show resolved Hide resolved

/** ReportID of the admins room for this workspace */
chatReportIDAdmins?: number;

Expand Down
Loading