Skip to content

Commit f7c1995

Browse files
authored
Merge pull request #60891 from software-mansion-labs/borys3kk-59999-follow-ups
Borys3kk 59999 follow ups
2 parents 61b0588 + 6cbe195 commit f7c1995

10 files changed

+107
-51
lines changed

src/components/MoneyReportHeader.tsx

+43-29
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
isReceiptBeingScanned,
5050
shouldShowBrokenConnectionViolationForMultipleTransactions,
5151
} from '@libs/TransactionUtils';
52+
import type {ExportType} from '@pages/home/report/ReportDetailsExportPage';
5253
import variables from '@styles/variables';
5354
import {
5455
approveMoneyRequest,
@@ -146,13 +147,14 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
146147
const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult);
147148

148149
const isExported = isExportedUtils(reportActions);
149-
const [markAsExportedModalVisible, setMarkAsExportedModalVisible] = useState(false);
150150

151151
const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
152152
const [isCancelPaymentModalVisible, setIsCancelPaymentModalVisible] = useState(false);
153153
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
154154
const [isUnapproveModalVisible, setIsUnapproveModalVisible] = useState(false);
155155

156+
const [exportModalStatus, setExportModalStatus] = useState<ExportType | null>(null);
157+
156158
const {isPaidAnimationRunning, isApprovedAnimationRunning, startAnimation, stopAnimation, startApprovedAnimation} = usePaymentAnimations();
157159
const styles = useThemeStyles();
158160
const theme = useTheme();
@@ -286,14 +288,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
286288
markAsCashAction(iouTransactionID, reportID);
287289
}, [requestParentReportAction, transactionThreadReport?.reportID]);
288290

289-
const confirmManualExport = useCallback(() => {
290-
if (!connectedIntegration || !moneyRequestReport) {
291-
throw new Error('Missing data');
292-
}
293-
294-
markAsManuallyExported(moneyRequestReport.reportID, connectedIntegration);
295-
}, [connectedIntegration, moneyRequestReport]);
296-
297291
const getStatusIcon: (src: IconAsset) => React.ReactNode = (src) => (
298292
<Icon
299293
src={src}
@@ -363,7 +357,19 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
363357
return '';
364358
}
365359
return getReportPrimaryAction(moneyRequestReport, transactions, violations, policy, reportNameValuePairs);
366-
}, [isPaidAnimationRunning, moneyRequestReport, policy, reportNameValuePairs, transactions, violations]);
360+
}, [isPaidAnimationRunning, moneyRequestReport, reportNameValuePairs, policy, transactions, violations]);
361+
362+
const confirmExport = useCallback(() => {
363+
setExportModalStatus(null);
364+
if (!moneyRequestReport?.reportID || !connectedIntegration) {
365+
return;
366+
}
367+
if (exportModalStatus === CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION) {
368+
exportToIntegration(moneyRequestReport?.reportID, connectedIntegration);
369+
} else if (exportModalStatus === CONST.REPORT.EXPORT_OPTIONS.MARK_AS_EXPORTED) {
370+
markAsManuallyExported(moneyRequestReport?.reportID, connectedIntegration);
371+
}
372+
}, [connectedIntegration, exportModalStatus, moneyRequestReport?.reportID]);
367373

368374
const primaryActionsImplementation = {
369375
[CONST.REPORT.PRIMARY_ACTIONS.SUBMIT]: (
@@ -417,7 +423,11 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
417423
if (!connectedIntegration || !moneyRequestReport) {
418424
return;
419425
}
420-
exportToIntegration(moneyRequestReport.reportID, connectedIntegration);
426+
if (isExported) {
427+
setExportModalStatus(CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION);
428+
return;
429+
}
430+
exportToIntegration(moneyRequestReport?.reportID, connectedIntegration);
421431
}}
422432
/>
423433
),
@@ -554,22 +564,28 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
554564
value: CONST.REPORT.SECONDARY_ACTIONS.EXPORT_TO_ACCOUNTING,
555565
onSelected: () => {
556566
if (!connectedIntegration || !moneyRequestReport) {
557-
throw new Error('Missing data');
567+
return;
558568
}
559-
560-
exportToIntegration(moneyRequestReport.reportID, connectedIntegration);
569+
if (isExported) {
570+
setExportModalStatus(CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION);
571+
return;
572+
}
573+
exportToIntegration(moneyRequestReport?.reportID, connectedIntegration);
561574
},
562575
},
563576
[CONST.REPORT.SECONDARY_ACTIONS.MARK_AS_EXPORTED]: {
564577
text: translate('workspace.common.markAsExported'),
565578
icon: Expensicons.CheckCircle,
566579
value: CONST.REPORT.SECONDARY_ACTIONS.MARK_AS_EXPORTED,
567580
onSelected: () => {
581+
if (!connectedIntegration || !moneyRequestReport) {
582+
return;
583+
}
568584
if (isExported) {
569-
setMarkAsExportedModalVisible(true);
585+
setExportModalStatus(CONST.REPORT.EXPORT_OPTIONS.MARK_AS_EXPORTED);
570586
return;
571587
}
572-
confirmManualExport();
588+
markAsManuallyExported(moneyRequestReport?.reportID, connectedIntegration);
573589
},
574590
},
575591
[CONST.REPORT.SECONDARY_ACTIONS.HOLD]: {
@@ -799,19 +815,17 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
799815
danger
800816
shouldEnableNewFocusManagement
801817
/>
802-
<ConfirmModal
803-
title={translate('workspace.exportAgainModal.title')}
804-
onConfirm={() => {
805-
confirmManualExport();
806-
setMarkAsExportedModalVisible(false);
807-
}}
808-
onCancel={() => setMarkAsExportedModalVisible(false)}
809-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
810-
prompt={translate('workspace.exportAgainModal.description', {connectionName: connectedIntegration!, reportName: moneyRequestReport?.reportName ?? ''})}
811-
confirmText={translate('workspace.exportAgainModal.confirmText')}
812-
cancelText={translate('workspace.exportAgainModal.cancelText')}
813-
isVisible={markAsExportedModalVisible}
814-
/>
818+
{!!connectedIntegration && (
819+
<ConfirmModal
820+
title={translate('workspace.exportAgainModal.title')}
821+
onConfirm={confirmExport}
822+
onCancel={() => setExportModalStatus(null)}
823+
prompt={translate('workspace.exportAgainModal.description', {connectionName: connectedIntegration, reportName: moneyRequestReport?.reportName ?? ''})}
824+
confirmText={translate('workspace.exportAgainModal.confirmText')}
825+
cancelText={translate('workspace.exportAgainModal.cancelText')}
826+
isVisible={!!exportModalStatus}
827+
/>
828+
)}
815829
<ConfirmModal
816830
title={translate('iou.unapproveReport')}
817831
isVisible={isUnapproveModalVisible}

src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
22
import {FlatList, View} from 'react-native';
33
import type {LayoutChangeEvent, ListRenderItemInfo, ViewToken} from 'react-native';
4+
import {useOnyx} from 'react-native-onyx';
45
import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withTiming} from 'react-native-reanimated';
56
import type {LayoutRectangle} from 'react-native/Libraries/Types/CoreEventTypes';
67
import Button from '@components/Button';
@@ -62,6 +63,7 @@ import {approveMoneyRequest, canApproveIOU, canIOUBePaid as canIOUBePaidIOUActio
6263
import Timing from '@userActions/Timing';
6364
import CONST from '@src/CONST';
6465
import type {TranslationPaths} from '@src/languages/types';
66+
import ONYXKEYS from '@src/ONYXKEYS';
6567
import ROUTES from '@src/ROUTES';
6668
import type {Transaction} from '@src/types/onyx';
6769
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
@@ -188,9 +190,10 @@ function MoneyRequestReportPreviewContent({
188190

189191
// The submit button should be success green color only if the user is submitter and the policy does not have Scheduled Submit turned on
190192
const isWaitingForSubmissionFromCurrentUser = useMemo(() => isWaitingForSubmissionFromCurrentUserReportUtils(chatReport, policy), [chatReport, policy]);
191-
192193
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
193194

195+
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${iouReportID}`, {canBeMissing: true});
196+
194197
const confirmPayment = useCallback(
195198
(type: PaymentMethodType | undefined, payAsBusiness?: boolean) => {
196199
if (!type) {
@@ -452,8 +455,8 @@ function MoneyRequestReportPreviewContent({
452455
if (isPaidAnimationRunning) {
453456
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
454457
}
455-
return getReportPreviewAction(violations, iouReport, policy, transactions);
456-
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions]);
458+
return getReportPreviewAction(violations, iouReport, policy, transactions, reportNameValuePairs);
459+
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions, reportNameValuePairs]);
457460

458461
const reportPreviewActions = {
459462
[CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT]: (

src/components/ReportActionItem/ReportPreview.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import truncate from 'lodash/truncate';
22
import React, {useCallback, useEffect, useMemo, useState} from 'react';
33
import type {StyleProp, ViewStyle} from 'react-native';
44
import {View} from 'react-native';
5+
import {useOnyx} from 'react-native-onyx';
56
import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withTiming} from 'react-native-reanimated';
67
import Button from '@components/Button';
78
import {getButtonRole} from '@components/Button/utils';
@@ -20,7 +21,6 @@ import Text from '@components/Text';
2021
import useDelegateUserDetails from '@hooks/useDelegateUserDetails';
2122
import useLocalize from '@hooks/useLocalize';
2223
import useNetwork from '@hooks/useNetwork';
23-
import useOnyx from '@hooks/useOnyx';
2424
import usePaymentAnimations from '@hooks/usePaymentAnimations';
2525
import usePolicy from '@hooks/usePolicy';
2626
import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations';
@@ -203,6 +203,7 @@ function ReportPreview({
203203
const managerID = iouReport?.managerID ?? action.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
204204
const {totalDisplaySpend, reimbursableSpend} = getMoneyRequestSpendBreakdown(iouReport);
205205
const [reports] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}`, {canBeMissing: false});
206+
206207
const iouSettled = isSettled(iouReportID, isOnSearch ? reports : undefined) || action?.childStatusNum === CONST.REPORT.STATUS_NUM.REIMBURSED;
207208
const previewMessageOpacity = useSharedValue(1);
208209
const previewMessageStyle = useAnimatedStyle(() => ({
@@ -258,6 +259,8 @@ function ReportPreview({
258259
const {isDelegateAccessRestricted} = useDelegateUserDetails();
259260
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
260261

262+
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${iouReport?.reportID}`, {canBeMissing: true});
263+
261264
const confirmPayment = useCallback(
262265
(type: PaymentMethodType | undefined, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod) => {
263266
if (!type) {
@@ -512,8 +515,8 @@ function ReportPreview({
512515
if (isPaidAnimationRunning) {
513516
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
514517
}
515-
return getReportPreviewAction(violations, iouReport, policy, transactions);
516-
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions]);
518+
return getReportPreviewAction(violations, iouReport, policy, transactions, reportNameValuePairs);
519+
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions, reportNameValuePairs]);
517520

518521
const reportPreviewActions = {
519522
[CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT]: (

src/libs/ReportActionsUtils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ function getOriginalMessage<T extends ReportActionName>(reportAction: OnyxInputO
273273
}
274274

275275
function isExportIntegrationAction(reportAction: OnyxInputOrEntry<ReportAction>): boolean {
276-
return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.INTEGRATIONS_MESSAGE && !!getOriginalMessage(reportAction as ReportAction<'INTEGRATIONSMESSAGE'>)?.result?.success;
276+
return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION;
277277
}
278278

279279
/**

src/libs/ReportPreviewActionUtils.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {OnyxCollection} from 'react-native-onyx';
22
import type {ValueOf} from 'type-fest';
33
import CONST from '@src/CONST';
4-
import type {Policy, Report, Transaction, TransactionViolation} from '@src/types/onyx';
4+
import type {Policy, Report, ReportNameValuePairs, Transaction, TransactionViolation} from '@src/types/onyx';
55
import {isApprover as isApproverMember} from './actions/Policy/Member';
66
import {getCurrentUserAccountID} from './actions/Report';
77
import {
@@ -16,7 +16,6 @@ import {
1616
import {
1717
getMoneyRequestSpendBreakdown,
1818
getParentReport,
19-
getReportNameValuePairs,
2019
getReportTransactions,
2120
hasMissingSmartscanFields,
2221
hasNoticeTypeViolations,
@@ -59,10 +58,13 @@ function canSubmit(report: Report, violations: OnyxCollection<TransactionViolati
5958
}
6059

6160
function canApprove(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy, transactions?: Transaction[]) {
61+
const currentUserID = getCurrentUserAccountID();
6262
const isExpense = isExpenseReport(report);
63-
const isApprover = isApproverMember(policy, getCurrentUserAccountID());
63+
const isApprover = isApproverMember(policy, currentUserID);
6464
const isProcessing = isProcessingReport(report);
6565
const isApprovalEnabled = policy ? policy.approvalMode && policy.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL : false;
66+
const managerID = report?.managerID ?? CONST.DEFAULT_NUMBER_ID;
67+
const isCurrentUserManager = managerID === currentUserID;
6668
const hasAnyViolations =
6769
hasMissingSmartscanFields(report.reportID, transactions) ||
6870
hasViolations(report.reportID, violations) ||
@@ -77,11 +79,10 @@ function canApprove(report: Report, violations: OnyxCollection<TransactionViolat
7779
return false;
7880
}
7981

80-
return isExpense && isApprover && isProcessing && isApprovalEnabled && !hasAnyViolations && reportTransactions.length > 0;
82+
return isExpense && isApprover && isProcessing && isApprovalEnabled && !hasAnyViolations && reportTransactions.length > 0 && isCurrentUserManager;
8183
}
8284

83-
function canPay(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy) {
84-
const reportNameValuePairs = getReportNameValuePairs(report.chatReportID);
85+
function canPay(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy, reportNameValuePairs?: ReportNameValuePairs) {
8586
const isChatReportArchived = isArchivedReport(reportNameValuePairs);
8687

8788
if (isChatReportArchived) {
@@ -113,7 +114,7 @@ function canPay(report: Report, violations: OnyxCollection<TransactionViolation[
113114

114115
const isIOU = isIOUReport(report);
115116

116-
if (isIOU && isReportPayer && !isReimbursed) {
117+
if (isIOU && isReportPayer && !isReimbursed && reimbursableSpend > 0) {
117118
return true;
118119
}
119120

@@ -180,6 +181,7 @@ function getReportPreviewAction(
180181
report?: Report,
181182
policy?: Policy,
182183
transactions?: Transaction[],
184+
reportNameValuePairs?: ReportNameValuePairs,
183185
): ValueOf<typeof CONST.REPORT.REPORT_PREVIEW_ACTIONS> {
184186
if (!report) {
185187
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW;
@@ -190,7 +192,7 @@ function getReportPreviewAction(
190192
if (canApprove(report, violations, policy, transactions)) {
191193
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.APPROVE;
192194
}
193-
if (canPay(report, violations, policy)) {
195+
if (canPay(report, violations, policy, reportNameValuePairs)) {
194196
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
195197
}
196198
if (canExport(report, violations, policy)) {

src/libs/ReportPrimaryActionUtils.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,14 @@ function isSubmitAction(report: Report, reportTransactions: Transaction[], polic
7373
}
7474

7575
function isApproveAction(report: Report, reportTransactions: Transaction[], policy?: Policy) {
76+
const currentUserAccountID = getCurrentUserAccountID();
77+
const managerID = report?.managerID ?? CONST.DEFAULT_NUMBER_ID;
78+
const isCurrentUserManager = managerID === currentUserAccountID;
79+
if (!isCurrentUserManager) {
80+
return false;
81+
}
7682
const isExpenseReport = isExpenseReportUtils(report);
77-
const isReportApprover = isApproverUtils(policy, getCurrentUserAccountID());
83+
const isReportApprover = isApproverUtils(policy, currentUserAccountID);
7884
const isApprovalEnabled = policy?.approvalMode && policy.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL;
7985

8086
if (!isExpenseReport || !isReportApprover || !isApprovalEnabled || reportTransactions.length === 0) {
@@ -130,7 +136,7 @@ function isPayAction(report: Report, policy?: Policy, reportNameValuePairs?: Rep
130136

131137
const isIOUReport = isIOUReportUtils(report);
132138

133-
if (isIOUReport && isReportPayer) {
139+
if (isIOUReport && isReportPayer && reimbursableSpend > 0) {
134140
return true;
135141
}
136142

src/libs/ReportSecondaryActionUtils.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,14 @@ function isSubmitAction(report: Report, reportTransactions: Transaction[], polic
8787
}
8888

8989
function isApproveAction(report: Report, reportTransactions: Transaction[], violations: OnyxCollection<TransactionViolation[]>, policy?: Policy): boolean {
90+
const currentUserAccountID = getCurrentUserAccountID();
91+
const managerID = report?.managerID ?? CONST.DEFAULT_NUMBER_ID;
92+
const isCurrentUserManager = managerID === currentUserAccountID;
93+
if (!isCurrentUserManager) {
94+
return false;
95+
}
9096
const isExpenseReport = isExpenseReportUtils(report);
91-
const isReportApprover = isApproverUtils(policy, getCurrentUserAccountID());
97+
const isReportApprover = isApproverUtils(policy, currentUserAccountID);
9298
const isProcessingReport = isProcessingReportUtils(report);
9399
const reportHasDuplicatedTransactions = reportTransactions.some((transaction) => isDuplicate(transaction.transactionID));
94100

@@ -254,15 +260,15 @@ function isMarkAsExportedAction(report: Report, policy?: Policy): boolean {
254260
const syncEnabled = hasIntegrationAutoSync(policy, connectedIntegration);
255261
const isReportFinished = isReportClosedOrApproved || isReportReimbursed;
256262

257-
if (!isReportFinished || !syncEnabled) {
263+
if (!isReportFinished) {
258264
return false;
259265
}
260266

261267
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
262268

263269
const isExporter = isPrefferedExporter(policy);
264270

265-
return isAdmin || isExporter;
271+
return (isAdmin && syncEnabled) || (isExporter && !syncEnabled);
266272
}
267273

268274
function isHoldAction(report: Report, reportTransactions: Transaction[]): boolean {

tests/actions/ReportPreviewActionUtilsTest.ts

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ describe('getReportPreviewAction', () => {
8080
ownerAccountID: CURRENT_USER_ACCOUNT_ID,
8181
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
8282
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
83+
managerID: CURRENT_USER_ACCOUNT_ID,
8384
};
8485

8586
const policy = createRandomPolicy(0);

tests/unit/ReportPrimaryActionUtilsTest.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ describe('getPrimaryAction', () => {
5656
ownerAccountID: CURRENT_USER_ACCOUNT_ID,
5757
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
5858
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
59+
managerID: CURRENT_USER_ACCOUNT_ID,
5960
} as unknown as Report;
6061
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
6162
const policy = {
@@ -326,7 +327,6 @@ describe('getTransactionThreadPrimaryAction', () => {
326327
},
327328
} as unknown as TransactionViolation;
328329

329-
getTransactionThreadPrimaryAction({} as Report, report, transaction, [violation], policy as Policy);
330330
expect(getTransactionThreadPrimaryAction({} as Report, report, transaction, [violation], policy as Policy)).toBe(CONST.REPORT.TRANSACTION_PRIMARY_ACTIONS.MARK_AS_CASH);
331331
});
332332

0 commit comments

Comments
 (0)