Skip to content

Commit 23fa95c

Browse files
authored
Merge pull request #57475 from software-mansion-labs/jnowakow/simplified-actions-get-primary-action
[No QA] Implement report primary action getter
2 parents b649380 + e9d65e1 commit 23fa95c

13 files changed

+522
-82
lines changed

src/CONST.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,15 @@ const CONST = {
11091109
MIN_INITIAL_REPORT_ACTION_COUNT: 15,
11101110
UNREPORTED_REPORTID: '0',
11111111
SPLIT_REPORTID: '-2',
1112+
PRIMARY_ACTIONS: {
1113+
SUBMIT: 'submit',
1114+
APPROVE: 'approve',
1115+
PAY: 'pay',
1116+
EXPORT_TO_ACCOUNTING: 'exportToAccounting',
1117+
REMOVE_HOLD: 'removeHold',
1118+
REVIEW_DUPLICATES: 'reviewDuplicates',
1119+
MARK_AS_CASH: 'markAsCash',
1120+
},
11121121
ACTIONS: {
11131122
LIMIT: 50,
11141123
// OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts

src/components/MoneyReportHeader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
isPayAtEndExpense as isPayAtEndExpenseTransactionUtils,
4343
isPending,
4444
isReceiptBeingScanned,
45-
shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils,
45+
shouldShowBrokenConnectionViolationForMultipleTransactions,
4646
shouldShowRTERViolationMessage,
4747
} from '@libs/TransactionUtils';
4848
import variables from '@styles/variables';
@@ -162,7 +162,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
162162
// Check if there is pending rter violation in all transactionViolations with given transactionIDs.
163163
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations);
164164
// Check if user should see broken connection violation warning.
165-
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDs, moneyRequestReport, policy, violations);
165+
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, moneyRequestReport, policy, violations);
166166
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID);
167167
const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction);
168168
const isArchivedReport = isArchivedReportWithID(moneyRequestReport?.reportID);

src/components/MoneyRequestHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
8282

8383
const hasPendingRTERViolation = hasPendingRTERViolationTransactionUtils(transactionViolations);
8484

85-
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transaction, parentReport, policy, transactionViolations);
85+
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(parentReport, policy, transactionViolations);
8686
const shouldShowMarkAsCashButton = checkIfShouldShowMarkAsCashButton(hasPendingRTERViolation, shouldShowBrokenConnectionViolation, parentReport, policy);
8787

8888
const markAsCash = useCallback(() => {

src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ function MoneyRequestPreviewContent({
282282
};
283283

284284
const getPendingMessageProps: () => PendingMessageProps = () => {
285-
if (shouldShowBrokenConnectionViolation(transaction, iouReport, policy, violations)) {
285+
if (shouldShowBrokenConnectionViolation(iouReport, policy, violations)) {
286286
return {shouldShow: true, messageIcon: Hourglass, messageDescription: translate('violations.brokenConnection530Error')};
287287
}
288288
return {shouldShow: false};

src/components/ReportActionItem/ReportPreview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ import {
7878
isPartialMerchant,
7979
isPending,
8080
isReceiptBeingScanned,
81-
shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils,
81+
shouldShowBrokenConnectionViolationForMultipleTransactions,
8282
} from '@libs/TransactionUtils';
8383
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
8484
import variables from '@styles/variables';
@@ -236,7 +236,7 @@ function ReportPreview({
236236
const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction}));
237237
const lastTransactionViolations = useTransactionViolations(lastTransaction?.transactionID);
238238
const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, lastTransactionViolations);
239-
const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy, violations);
239+
const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDList, iouReport, policy, violations);
240240
let formattedMerchant = numberOfRequests === 1 ? getMerchant(lastTransaction) : null;
241241
const formattedDescription = numberOfRequests === 1 ? getDescription(lastTransaction) : null;
242242

src/libs/PolicyUtils.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import type PolicyEmployee from '@src/types/onyx/PolicyEmployee';
3333
import type {SearchPolicy} from '@src/types/onyx/SearchResults';
3434
import {isEmptyObject} from '@src/types/utils/EmptyObject';
3535
import {hasSynchronizationErrorMessage} from './actions/connections';
36-
import {getCurrentUserAccountID} from './actions/Report';
36+
import {getCurrentUserAccountID, getCurrentUserEmail} from './actions/Report';
3737
import {getCategoryApproverRule} from './CategoryUtils';
3838
import {translateLocal} from './Localize';
3939
import Navigation from './Navigation/Navigation';
@@ -1354,6 +1354,33 @@ function canEnablePreventSelfApprovals(policy: OnyxEntry<Policy>): boolean {
13541354
return employeeEmails.length > 1;
13551355
}
13561356

1357+
function isPrefferedExporter(policy: Policy) {
1358+
const user = getCurrentUserEmail();
1359+
const exporters = [
1360+
policy.connections?.intacct?.config?.export?.exporter,
1361+
policy.connections?.netsuite?.options?.config?.exporter,
1362+
policy.connections?.netsuiteQuickStart?.config?.exporter,
1363+
policy.connections?.quickbooksDesktop?.config?.export?.exporter,
1364+
policy.connections?.quickbooksOnline?.config?.export?.exporter,
1365+
policy.connections?.xero?.config?.export?.exporter,
1366+
];
1367+
1368+
return exporters.some((exporter) => exporter && exporter === user);
1369+
}
1370+
1371+
function isAutoSyncEnabled(policy: Policy) {
1372+
const values = [
1373+
policy.connections?.intacct?.config?.autoSync?.enabled,
1374+
policy.connections?.netsuite?.config?.autoSync?.enabled,
1375+
policy.connections?.netsuiteQuickStart?.config?.autoSync?.enabled,
1376+
policy.connections?.quickbooksDesktop?.config?.autoSync?.enabled,
1377+
policy.connections?.quickbooksOnline?.config?.autoSync?.enabled,
1378+
policy.connections?.xero?.config?.autoSync?.enabled,
1379+
];
1380+
1381+
return values.some((value) => !!value);
1382+
}
1383+
13571384
export {
13581385
canEditTaxRate,
13591386
canEnablePreventSelfApprovals,
@@ -1492,6 +1519,8 @@ export {
14921519
getPolicyNameByID,
14931520
getMostFrequentEmailDomain,
14941521
getDescriptionForPolicyDomainCard,
1522+
isPrefferedExporter,
1523+
isAutoSyncEnabled,
14951524
};
14961525

14971526
export type {MemberEmailsToAccountIDs};

src/libs/ReportPrimaryActionUtils.ts

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import type {OnyxCollection} from 'react-native-onyx';
2+
import type {ValueOf} from 'type-fest';
3+
import CONST from '@src/CONST';
4+
import type {Policy, Report, Transaction, TransactionViolation} from '@src/types/onyx';
5+
import {isApprover as isApprovedMember} from './actions/Policy/Member';
6+
import {getCurrentUserAccountID} from './actions/Report';
7+
import {arePaymentsEnabled, getCorrectedAutoReportingFrequency, hasAccountingConnections, isAutoSyncEnabled, isPrefferedExporter} from './PolicyUtils';
8+
import {
9+
isClosedReport,
10+
isCurrentUserSubmitter,
11+
isExpenseReport,
12+
isHoldCreator,
13+
isInvoiceReport,
14+
isIOUReport,
15+
isOpenReport,
16+
isPayer,
17+
isProcessingReport,
18+
isReportApproved,
19+
isSettled,
20+
} from './ReportUtils';
21+
import {getSession} from './SessionUtils';
22+
import {allHavePendingRTERViolation, isDuplicate, isOnHold as isOnHoldTransactionUtils, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils';
23+
24+
function isSubmitAction(report: Report, policy: Policy) {
25+
const isExpense = isExpenseReport(report);
26+
const isSubmitter = isCurrentUserSubmitter(report.reportID);
27+
const isOpen = isOpenReport(report);
28+
const isManualSubmitEnabled = getCorrectedAutoReportingFrequency(policy) === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL;
29+
30+
return isExpense && isSubmitter && isOpen && isManualSubmitEnabled;
31+
}
32+
33+
function isApproveAction(report: Report, policy: Policy, reportTransactions: Transaction[]) {
34+
const isExpense = isExpenseReport(report);
35+
const isApprover = isApprovedMember(policy, getCurrentUserAccountID());
36+
const isApprovalEnabled = policy.approvalMode && policy.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL;
37+
38+
if (!isExpense || !isApprover || !isApprovalEnabled) {
39+
return false;
40+
}
41+
42+
const isOneExpenseReport = isExpense && reportTransactions.length === 1;
43+
const isOnHold = reportTransactions.some(isOnHoldTransactionUtils);
44+
const isProcessing = isProcessingReport(report);
45+
const isOneExpenseReportOnHold = isOneExpenseReport && isOnHold;
46+
47+
if (isProcessing || isOneExpenseReportOnHold) {
48+
return true;
49+
}
50+
51+
return false;
52+
}
53+
54+
function isPayAction(report: Report, policy: Policy) {
55+
const isExpense = isExpenseReport(report);
56+
const isReportPayer = isPayer(getSession(), report, false, policy);
57+
const isPaymentsEnabled = arePaymentsEnabled(policy);
58+
const isApproved = isReportApproved({report});
59+
const isClosed = isClosedReport(report);
60+
const isFinished = isApproved || isClosed;
61+
62+
if (isReportPayer && isExpense && isPaymentsEnabled && isFinished) {
63+
return true;
64+
}
65+
66+
const isProcessing = isProcessingReport(report);
67+
const isInvoice = isInvoiceReport(report);
68+
const isIOU = isIOUReport(report);
69+
70+
if ((isInvoice || isIOU) && isProcessing) {
71+
return true;
72+
}
73+
74+
return false;
75+
}
76+
77+
function isExportAction(report: Report, policy: Policy) {
78+
const hasAccountingConnection = hasAccountingConnections(policy);
79+
if (!hasAccountingConnection) {
80+
return false;
81+
}
82+
83+
const isExporter = isPrefferedExporter(policy);
84+
if (!isExporter) {
85+
return false;
86+
}
87+
88+
const syncEnabled = isAutoSyncEnabled(policy);
89+
if (syncEnabled) {
90+
return false;
91+
}
92+
93+
const isReimbursed = isSettled(report);
94+
const isApproved = isReportApproved({report});
95+
const isClosed = isClosedReport(report);
96+
97+
if (isApproved || isReimbursed || isClosed) {
98+
return true;
99+
}
100+
101+
return false;
102+
}
103+
104+
function isRemoveHoldAction(report: Report, reportTransactions: Transaction[]) {
105+
const isOnHold = reportTransactions.some(isOnHoldTransactionUtils);
106+
const isHolder = reportTransactions.some((transaction) => isHoldCreator(transaction, report.reportID));
107+
108+
return isOnHold && isHolder;
109+
}
110+
111+
function isReviewDuplicatesAction(report: Report, policy: Policy, reportTransactions: Transaction[]) {
112+
const hasDuplicates = reportTransactions.some((transaction) => isDuplicate(transaction.transactionID));
113+
114+
if (!hasDuplicates) {
115+
return false;
116+
}
117+
118+
const isApprover = isApprovedMember(policy, getCurrentUserAccountID());
119+
const isSubmitter = isCurrentUserSubmitter(report.reportID);
120+
const isProcessing = isProcessingReport(report);
121+
const isOpen = isOpenReport(report);
122+
123+
const isSubmitterOrApprover = isSubmitter || isApprover;
124+
const isActive = isOpen || isProcessing;
125+
126+
if (isSubmitterOrApprover && isActive) {
127+
return true;
128+
}
129+
130+
return false;
131+
}
132+
133+
function isMarkAsCashAction(report: Report, policy: Policy, reportTransactions: Transaction[], violations: OnyxCollection<TransactionViolation[]>) {
134+
const transactionIDs = reportTransactions.map((t) => t.transactionID);
135+
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations);
136+
137+
if (hasAllPendingRTERViolations) {
138+
return true;
139+
}
140+
141+
const isSubmitter = isCurrentUserSubmitter(report.reportID);
142+
const isApprover = isApprovedMember(policy, getCurrentUserAccountID());
143+
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
144+
145+
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, report, policy, violations);
146+
147+
const userControlsReport = isSubmitter || isApprover || isAdmin;
148+
return userControlsReport && shouldShowBrokenConnectionViolation;
149+
}
150+
151+
function getPrimaryAction(
152+
report: Report,
153+
policy: Policy,
154+
reportTransactions: Transaction[],
155+
violations: OnyxCollection<TransactionViolation[]>,
156+
): ValueOf<typeof CONST.REPORT.PRIMARY_ACTIONS> | '' {
157+
if (isSubmitAction(report, policy)) {
158+
return CONST.REPORT.PRIMARY_ACTIONS.SUBMIT;
159+
}
160+
161+
if (isApproveAction(report, policy, reportTransactions)) {
162+
return CONST.REPORT.PRIMARY_ACTIONS.APPROVE;
163+
}
164+
165+
if (isPayAction(report, policy)) {
166+
return CONST.REPORT.PRIMARY_ACTIONS.PAY;
167+
}
168+
169+
if (isExportAction(report, policy)) {
170+
return CONST.REPORT.PRIMARY_ACTIONS.EXPORT_TO_ACCOUNTING;
171+
}
172+
173+
if (isRemoveHoldAction(report, reportTransactions)) {
174+
return CONST.REPORT.PRIMARY_ACTIONS.REMOVE_HOLD;
175+
}
176+
177+
if (isReviewDuplicatesAction(report, policy, reportTransactions)) {
178+
return CONST.REPORT.PRIMARY_ACTIONS.REVIEW_DUPLICATES;
179+
}
180+
181+
if (isMarkAsCashAction(report, policy, reportTransactions, violations)) {
182+
return CONST.REPORT.PRIMARY_ACTIONS.MARK_AS_CASH;
183+
}
184+
185+
return '';
186+
}
187+
188+
export default getPrimaryAction;

src/libs/ReportUtils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,10 @@ function isProcessingReport(report: OnyxEntry<Report>): boolean {
15891589
return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS_NUM.SUBMITTED;
15901590
}
15911591

1592+
function isOpenReport(report: OnyxEntry<Report>): boolean {
1593+
return report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN;
1594+
}
1595+
15921596
function isAwaitingFirstLevelApproval(report: OnyxEntry<Report>): boolean {
15931597
if (!report) {
15941598
return false;
@@ -9446,6 +9450,7 @@ export {
94469450
isPolicyExpenseChat,
94479451
isPolicyExpenseChatAdmin,
94489452
isProcessingReport,
9453+
isOpenReport,
94499454
isReportIDApproved,
94509455
isAwaitingFirstLevelApproval,
94519456
isPublicAnnounceRoom,

0 commit comments

Comments
 (0)