Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import variables from '@styles/variables';
import {canIOUBePaid as canIOUBePaidIOUActions} from '@userActions/IOU/ReportWorkflow';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft';
import type {Report, Transaction} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
Expand Down Expand Up @@ -84,6 +85,7 @@ function ReportPreviewActionButton({
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [iouReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${iouReportID}`);
const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(iouReport?.ownerAccountID)}, [iouReport?.ownerAccountID]);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`);
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
Expand Down Expand Up @@ -141,6 +143,7 @@ function ReportPreviewActionButton({
isDEWSubmitPending,
violationsData: transactionViolations,
reportMetadata: iouReportMetadata,
ownerLogin,
});

const renderButton = () => {
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useReportPrimaryAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {isExpenseReport as isExpenseReportUtils} from '@libs/ReportUtils';
import {isTransactionPendingDelete} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useOnyx from './useOnyx';
import useReportIsArchived from './useReportIsArchived';
Expand All @@ -23,6 +24,7 @@ function useReportPrimaryAction(reportID: string | undefined): ValueOf<typeof CO
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getNonEmptyStringOnyxID(moneyRequestReport?.reportID)}`);
const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${getNonEmptyStringOnyxID(moneyRequestReport?.reportID)}`);
const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(moneyRequestReport?.ownerAccountID)}, [moneyRequestReport?.ownerAccountID]);
const [invoiceReceiverPolicy] = useOnyx(
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined}`,
);
Expand Down Expand Up @@ -59,6 +61,7 @@ function useReportPrimaryAction(reportID: string | undefined): ValueOf<typeof CO
reportMetadata,
isChatReportArchived,
invoiceReceiverPolicy,
ownerLogin,
});
}

Expand Down
1 change: 1 addition & 0 deletions src/hooks/useSelectionModeReportActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ function useSelectionModeReportActions({
reportMetadata,
isChatReportArchived,
invoiceReceiverPolicy,
ownerLogin: submitterLogin,
});

const secondaryActions = (() => {
Expand Down
4 changes: 2 additions & 2 deletions src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1319,7 +1319,7 @@ function getReportOption(
option.alternateText = translateLocal('workspace.common.workspace');

if (report?.policyID) {
const submitToAccountID = getSubmitToAccountID(policy, report);
const submitToAccountID = getSubmitToAccountID(policy, report, getLoginByAccountID(report?.ownerAccountID, personalDetails));
const submitsToAccountDetails = personalDetails?.[submitToAccountID];
const subtitle = submitsToAccountDetails?.displayName ?? submitsToAccountDetails?.login;

Expand Down Expand Up @@ -2434,7 +2434,7 @@ function prepareReportOptionsForDisplay(
newReportOption.alternateText = translateLocal('workspace.common.workspace');

if (report?.policyID) {
const submitToAccountID = getSubmitToAccountID(policy, report);
const submitToAccountID = getSubmitToAccountID(policy, report, getLoginByAccountID(report?.ownerAccountID, personalDetails));
const submitsToAccountDetails = personalDetails?.[submitToAccountID];
const subtitle = submitsToAccountDetails?.displayName ?? submitsToAccountDetails?.login;

Expand Down
43 changes: 19 additions & 24 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import Navigation from './Navigation/Navigation';
import {getIsOffline} from './NetworkState';
import {formatMemberForList} from './OptionsListUtils';
import type {MemberForList} from './OptionsListUtils';
import {getAccountIDsByLogins, getLoginByAccountID, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils';
import {getAccountIDsByLogins, getPersonalDetailByEmail} from './PersonalDetailsUtils';
import {getAllSortedTransactions, getCategory, getTag, getTagArrayFromName} from './TransactionUtils';
import {isPublicDomain, isValidAccountRoute} from './ValidationUtils';

Expand Down Expand Up @@ -1473,7 +1473,7 @@ function getRuleApprovers(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Re
return [...new Set([...categoryApprovers, ...tagApprovers])];
}

function getFirstRuleApprover(approvalRules: ApprovalRule[], expenseReport: OnyxEntry<Report>) {
function getFirstRuleApprover(approvalRules: ApprovalRule[], expenseReport: OnyxEntry<Report>, ownerLogin: string | undefined) {
// Pre-build a lookup map of { category: { value → approver }, tag: { value → approver } }
// from the policy's approval rules so that each transaction's category/tag can be resolved in O(1).
const rulesMap: Record<'category' | 'tag', Record<string, string>> = {category: {}, tag: {}};
Expand Down Expand Up @@ -1504,9 +1504,6 @@ function getFirstRuleApprover(approvalRules: ApprovalRule[], expenseReport: Onyx
return '';
}

const employeeAccountID = expenseReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID;
const employeeLogin = getLoginByAccountID(employeeAccountID);

let firstCategoryApprover = '';
let firstTagApprover = '';

Expand All @@ -1517,7 +1514,7 @@ function getFirstRuleApprover(approvalRules: ApprovalRule[], expenseReport: Onyx

// Category approvers take strict priority over tag approvers.
// Break immediately on the first match so we don't keep scanning transactions unnecessarily.
if (categoryApprover && categoryApprover !== employeeLogin) {
if (categoryApprover && categoryApprover !== ownerLogin) {
firstCategoryApprover = categoryApprover;
break;
}
Expand All @@ -1527,7 +1524,7 @@ function getFirstRuleApprover(approvalRules: ApprovalRule[], expenseReport: Onyx
const tag = getTag(transaction);
const tagApprover = rulesMap.tag[tag];

if (tagApprover && tagApprover !== employeeLogin) {
if (tagApprover && tagApprover !== ownerLogin) {
firstTagApprover = tagApprover;
}
}
Expand All @@ -1536,46 +1533,49 @@ function getFirstRuleApprover(approvalRules: ApprovalRule[], expenseReport: Onyx
return firstCategoryApprover || firstTagApprover;
}

function getManagerAccountID(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report> | {ownerAccountID: number}) {
const employeeAccountID = expenseReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID;
const employeeLogin = getLoginByAccountID(employeeAccountID) ?? '';
function getManagerAccountEmail(policy: OnyxEntry<Policy>, ownerLogin: string | undefined): string {
const defaultApprover = getDefaultApprover(policy);

// For policy using the optional or basic workflow, the manager is the policy default approver.
if (([CONST.POLICY.APPROVAL_MODE.OPTIONAL, CONST.POLICY.APPROVAL_MODE.BASIC] as Array<ValueOf<typeof CONST.POLICY.APPROVAL_MODE>>).includes(getApprovalWorkflow(policy))) {
return getAccountIDsByLogins([defaultApprover]).at(0) ?? -1;
return defaultApprover;
}

const employee = policy?.employeeList?.[employeeLogin];
const employee = policy?.employeeList?.[ownerLogin ?? ''];
if (!employee && !defaultApprover) {
return -1;
return '';
}

return getAccountIDsByLogins([employee?.submitsTo ?? defaultApprover]).at(0) ?? -1;
return employee?.submitsTo ?? defaultApprover ?? '';
}

function getManagerAccountID(policy: OnyxEntry<Policy>, ownerLogin: string | undefined) {
const managerEmail = getManagerAccountEmail(policy, ownerLogin);
return managerEmail ? (getAccountIDsByLogins([managerEmail]).at(0) ?? -1) : -1;
}

/**
* Returns the accountID to whom the given expenseReport submits reports to in the given Policy.
*/
function getSubmitToAccountID(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>): number {
function getSubmitToAccountID(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>, ownerLogin: string | undefined): number {
const approvalRules = policy?.rules?.approvalRules;

if (!isSubmitAndClose(policy) && approvalRules?.length) {
const ruleApprover = getFirstRuleApprover(approvalRules, expenseReport);
const ruleApprover = getFirstRuleApprover(approvalRules, expenseReport, ownerLogin);
if (ruleApprover) {
return getAccountIDsByLogins([ruleApprover]).at(0) ?? -1;
}
}

return getManagerAccountID(policy, expenseReport);
return getManagerAccountID(policy, ownerLogin);
}

function getSubmitReportManagerAccountID(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>, submitterLogin: string | undefined): number | undefined {
const ownerAccountID = expenseReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID;
const existingManagerID = expenseReport?.managerID;
const approvalRules = policy?.rules?.approvalRules;
const ruleApprover = !isSubmitAndClose(policy) && approvalRules?.length ? getFirstRuleApprover(approvalRules, expenseReport) : '';
const submitToAccountID = ruleApprover ? (getAccountIDsByLogins([ruleApprover]).at(0) ?? -1) : getManagerAccountID(policy, expenseReport);
const ruleApprover = !isSubmitAndClose(policy) && approvalRules?.length ? getFirstRuleApprover(approvalRules, expenseReport, submitterLogin) : '';
const submitToAccountID = getSubmitToAccountID(policy, expenseReport, submitterLogin);
const isValidSubmitToAccountID = isValidAccountRoute(submitToAccountID);
const isValidExistingManagerID = isValidAccountRoute(existingManagerID ?? CONST.DEFAULT_NUMBER_ID) && existingManagerID !== ownerAccountID;
const hasReliablePolicyRoute =
Expand All @@ -1594,11 +1594,6 @@ function getSubmitReportManagerAccountID(policy: OnyxEntry<Policy>, expenseRepor
return isValidSubmitToAccountID ? submitToAccountID : existingManagerID;
}

function getManagerAccountEmail(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>): string {
const managerAccountID = getManagerAccountID(policy, expenseReport);
return getLoginsByAccountIDs([managerAccountID]).at(0) ?? '';
}

/**
* Returns the email of the account to forward the report to depending on the approver's approval limit.
* Used for advanced approval mode only.
Expand Down
7 changes: 5 additions & 2 deletions src/libs/ReportPreviewActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function canSubmit(
isReportArchived: boolean,
currentUserAccountID: number,
currentUserEmail: string,
ownerLogin: string | undefined,
violations?: OnyxCollection<TransactionViolation[]>,
policy?: Policy,
transactions?: Transaction[],
Expand All @@ -59,7 +60,7 @@ function canSubmit(
return false;
}

const submitToAccountID = getSubmitToAccountID(policy, report);
const submitToAccountID = getSubmitToAccountID(policy, report, ownerLogin);

if (submitToAccountID === report.ownerAccountID && policy?.preventSelfApproval) {
return false;
Expand Down Expand Up @@ -213,6 +214,7 @@ function getReportPreviewAction({
isDEWSubmitPending,
violationsData,
reportMetadata,
ownerLogin,
}: {
isReportArchived: boolean;
currentUserAccountID: number;
Expand All @@ -228,6 +230,7 @@ function getReportPreviewAction({
isDEWSubmitPending?: boolean;
violationsData?: OnyxCollection<TransactionViolation[]>;
reportMetadata: OnyxEntry<ReportMetadata>;
ownerLogin: string | undefined;
}): ValueOf<typeof CONST.REPORT.REPORT_PREVIEW_ACTIONS> {
if (!report) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW;
Expand All @@ -250,7 +253,7 @@ function getReportPreviewAction({
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW;
}

if (canSubmit(report, isReportArchived, currentUserAccountID, currentUserLogin, violationsData, policy, transactions)) {
if (canSubmit(report, isReportArchived, currentUserAccountID, currentUserLogin, ownerLogin, violationsData, policy, transactions)) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT;
}
if (canApprove(report, currentUserAccountID, reportMetadata, policy, transactions)) {
Expand Down
7 changes: 5 additions & 2 deletions src/libs/ReportPrimaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type GetReportPrimaryActionParams = {
reportMetadata?: OnyxEntry<ReportMetadata>;
isChatReportArchived: boolean;
invoiceReceiverPolicy?: Policy;
ownerLogin: string | undefined;
};

type IsPrimaryPayActionParams = {
Expand Down Expand Up @@ -108,6 +109,7 @@ function isSubmitAction(
report: Report,
reportTransactions: Transaction[],
reportMetadata: OnyxEntry<ReportMetadata>,
ownerLogin: string | undefined,
policy?: Policy,
reportNameValuePairs?: ReportNameValuePairs,
violations?: OnyxCollection<TransactionViolation[]>,
Expand Down Expand Up @@ -144,7 +146,7 @@ function isSubmitAction(
}
}

const submitToAccountID = getSubmitToAccountID(policy, report);
const submitToAccountID = getSubmitToAccountID(policy, report, ownerLogin);

if (submitToAccountID === report.ownerAccountID && policy?.preventSelfApproval && !isReportSubmitter) {
return false;
Expand Down Expand Up @@ -467,6 +469,7 @@ function getReportPrimaryAction(params: GetReportPrimaryActionParams): ValueOf<t
isChatReportArchived,
chatReport,
invoiceReceiverPolicy,
ownerLogin,
} = params;

// The expense report of personal policy shouldn't have any action
Expand Down Expand Up @@ -514,7 +517,7 @@ function getReportPrimaryAction(params: GetReportPrimaryActionParams): ValueOf<t
return CONST.REPORT.PRIMARY_ACTIONS.MARK_AS_RESOLVED;
}

if (isSubmitAction(report, reportTransactions, reportMetadata, policy, reportNameValuePairs, violations, currentUserLogin, currentUserAccountID) && !allExpensesHeld) {
if (isSubmitAction(report, reportTransactions, reportMetadata, ownerLogin, policy, reportNameValuePairs, violations, currentUserLogin, currentUserAccountID) && !allExpensesHeld) {
return CONST.REPORT.PRIMARY_ACTIONS.SUBMIT;
}

Expand Down
6 changes: 5 additions & 1 deletion src/libs/ReportSecondaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ function isSubmitAction({
violations,
currentUserLogin,
currentUserAccountID,
ownerLogin,
}: {
report: Report;
reportTransactions: Transaction[];
Expand All @@ -210,6 +211,7 @@ function isSubmitAction({
violations?: OnyxCollection<TransactionViolation[]>;
currentUserLogin?: string;
currentUserAccountID: number;
ownerLogin: string | undefined;
}): boolean {
if (isArchivedReport(reportNameValuePairs) || isChatReportArchived) {
return false;
Expand Down Expand Up @@ -258,7 +260,7 @@ function isSubmitAction({
return false;
}

const submitToAccountID = getSubmitToAccountID(policy, report);
const submitToAccountID = getSubmitToAccountID(policy, report, ownerLogin);
if (submitToAccountID === report.ownerAccountID && policy?.preventSelfApproval) {
return false;
}
Expand Down Expand Up @@ -978,6 +980,7 @@ function getSecondaryReportActions({
reportActions,
reportMetadata,
isChatReportArchived,
ownerLogin: submitterLogin,
});

if (
Expand All @@ -993,6 +996,7 @@ function getSecondaryReportActions({
violations,
currentUserLogin,
currentUserAccountID,
ownerLogin: submitterLogin,
})
) {
options.push(CONST.REPORT.SECONDARY_ACTIONS.SUBMIT);
Expand Down
Loading
Loading