Skip to content
Open
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
5 changes: 4 additions & 1 deletion src/components/KYCWall/BaseKYCWall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Dimensions} from 'react-native';
import type {EmitterSubscription, View} from 'react-native';
import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
import useAllPolicyExpenseChatReportActions from '@hooks/useAllPolicyExpenseChatReportActions';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -74,6 +75,7 @@ function KYCWall({
const personalDetails = usePersonalDetails();
const employeeEmail = personalDetails?.[iouReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID]?.login ?? '';
const reportTransactions = useReportTransactions(iouReport?.reportID);
const {filteredReportActions} = useAllPolicyExpenseChatReportActions();
const anchorRef = useRef<HTMLDivElement | View>(null);
const transferBalanceButtonRef = useRef<HTMLDivElement | View | null>(null);

Expand Down Expand Up @@ -139,7 +141,7 @@ function KYCWall({
if (iouReport && isIOUReport(iouReport)) {
const adminPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`];
if (adminPolicy) {
const inviteResult = moveIOUReportToPolicyAndInviteSubmitter(iouReport, adminPolicy, formatPhoneNumber, reportTransactions);
const inviteResult = moveIOUReportToPolicyAndInviteSubmitter(iouReport, adminPolicy, formatPhoneNumber, filteredReportActions, reportTransactions);
if (inviteResult?.policyExpenseChatReportID) {
setNavigationActionToMicrotaskQueue(() => {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(inviteResult.policyExpenseChatReportID));
Expand Down Expand Up @@ -214,6 +216,7 @@ function KYCWall({
introSelected,
formatPhoneNumber,
reportTransactions,
filteredReportActions,
lastPaymentMethod,
isSelfTourViewed,
betas,
Expand Down
54 changes: 54 additions & 0 deletions src/hooks/useAllPolicyExpenseChatReportActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {useCallback, useMemo} from 'react';
import type {OnyxCollection} from 'react-native-onyx';
import {isPolicyExpenseChat, isThread} from '@libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Report, ReportActions} from '@src/types/onyx';
import mapOnyxCollectionItems from '@src/utils/mapOnyxCollectionItems';
import useOnyx from './useOnyx';

const policyExpenseChatReportsSelector = (reports: OnyxCollection<Report>) => {
return mapOnyxCollectionItems(reports, (report) => {
if (!report || !isPolicyExpenseChat(report) || isThread(report)) {
return undefined;
}
return report;
});
};

/**
* Returns all reports that satisfy isPolicyExpenseChat && !isThread (via selector),
* along with reportActions filtered to only those matching reports.
*/
function useAllPolicyExpenseChatReportActions() {
const [policyExpenseChatReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: policyExpenseChatReportsSelector});

const policyExpenseChatReportIDs = useMemo(() => {
const ids = new Set<string>();
for (const report of Object.values(policyExpenseChatReports ?? {})) {
if (report?.reportID) {
ids.add(report.reportID);
}
}
return ids;
}, [policyExpenseChatReports]);

const reportActionsSelector = useCallback(
(reportActions: OnyxCollection<ReportActions>) => {
const result: NonNullable<OnyxCollection<ReportActions>> = {};
for (const [key, actions] of Object.entries(reportActions ?? {})) {
const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, '');
if (policyExpenseChatReportIDs.has(reportID)) {
result[key] = actions;
}
}
return result;
},
[policyExpenseChatReportIDs],
);

const [filteredReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {selector: reportActionsSelector}, [reportActionsSelector]);

return {policyExpenseChatReports, filteredReportActions};
}

export default useAllPolicyExpenseChatReportActions;
23 changes: 13 additions & 10 deletions src/hooks/useSearchBulkActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {BillingGraceEndPeriod, Policy, Report, SearchResults, Transaction, TransactionViolations} from '@src/types/onyx';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import useAllPolicyExpenseChatReportActions from './useAllPolicyExpenseChatReportActions';
import useAllTransactions from './useAllTransactions';
import useBulkPayOptions from './useBulkPayOptions';
import useConfirmModal from './useConfirmModal';
Expand Down Expand Up @@ -110,6 +111,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
const allTransactions = useAllTransactions();
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const {filteredReportActions: policyExpenseChatReportActions} = useAllPolicyExpenseChatReportActions();
const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS);
const selfDMReport = useSelfDMReport();
const [lastPaymentMethods] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD);
Expand Down Expand Up @@ -631,7 +633,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
);
return;
}
const invite = moveIOUReportToPolicyAndInviteSubmitter(itemReport, adminPolicy, formatPhoneNumber, reportTransactions);
const invite = moveIOUReportToPolicyAndInviteSubmitter(itemReport, adminPolicy, formatPhoneNumber, policyExpenseChatReportActions, reportTransactions);
if (!invite?.policyExpenseChatReportID) {
moveIOUReportToPolicy(itemReport, adminPolicy, false, reportTransactions);
}
Expand Down Expand Up @@ -680,23 +682,24 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
});
},
[
clearSelectedTransactions,
hash,
isOffline,
lastPaymentMethods,
isDelegateAccessRestricted,
selectedReports,
selectedTransactions,
userBillingGracePeriodEnds,
ownerBillingGracePeriodEnd,
amountOwed,
policies,
formatPhoneNumber,
policyIDsWithVBBA,
isDelegateAccessRestricted,
showDelegateNoAccessModal,
allReports,
personalPolicyID,
lastPaymentMethods,
allTransactions,
allReports,
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
policyIDsWithVBBA,
policyExpenseChatReportActions,
formatPhoneNumber,
clearSelectedTransactions,
],
);

Expand Down
3 changes: 2 additions & 1 deletion src/libs/actions/Policy/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
};

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 46 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => (allReportActions = actions),
Expand Down Expand Up @@ -869,7 +869,8 @@
const announceRoomChat = optimisticAnnounceChat.announceChatData;

// create onyx data for policy expense chats for each new member
const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs, undefined, policyExpenseChatNotificationPreference);
// TODO: Update to include reportActionsList later (https://github.com/Expensify/App/issues/66578)
const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs, undefined, undefined, policyExpenseChatNotificationPreference);

const optimisticMembersState: OnyxCollectionInputValue<PolicyEmployee> = {};
const successMembersState: OnyxCollectionInputValue<PolicyEmployee> = {};
Expand Down
15 changes: 12 additions & 3 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@
};

const deprecatedAllPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 255 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (val, key) => {
if (!key) {
Expand All @@ -268,7 +268,7 @@
});

let deprecatedAllReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 271 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -278,7 +278,7 @@

let deprecatedSessionEmail = '';
let deprecatedSessionAccountID = 0;
Onyx.connect({

Check warning on line 281 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (val) => {
deprecatedSessionEmail = val?.email ?? '';
Expand All @@ -287,7 +287,7 @@
});

let deprecatedAllPersonalDetails: OnyxEntry<PersonalDetailsList>;
Onyx.connect({

Check warning on line 290 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (val) => (deprecatedAllPersonalDetails = val),
});
Expand Down Expand Up @@ -1536,6 +1536,8 @@
function createPolicyExpenseChats(
policyID: string,
invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs,
// TODO: Remove optional (?) once all is updated (https://github.com/Expensify/App/issues/66578)
reportActionsList?: OnyxCollection<ReportActions>,
hasOutstandingChildRequest = false,
notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
): WorkspaceMembersChats {
Expand Down Expand Up @@ -1575,7 +1577,7 @@
},
});
const currentTime = DateUtils.getDBTime();
const reportActions = deprecatedAllReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldChat.reportID}`] ?? {};
const reportActions = (reportActionsList ?? deprecatedAllReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldChat.reportID}`] ?? {};
for (const action of Object.values(reportActions)) {
if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) {
continue;
Expand Down Expand Up @@ -2934,7 +2936,13 @@
}

if (adminParticipant?.login) {
const employeeWorkspaceChat = createPolicyExpenseChats(policyID, {[adminParticipant.login]: adminParticipant.accountID ?? CONST.DEFAULT_NUMBER_ID}, hasOutstandingChildRequest);
// TODO: Update to include reportActionsList later (https://github.com/Expensify/App/issues/66578)
const employeeWorkspaceChat = createPolicyExpenseChats(
policyID,
{[adminParticipant.login]: adminParticipant.accountID ?? CONST.DEFAULT_NUMBER_ID},
undefined,
hasOutstandingChildRequest,
);
params.memberData = JSON.stringify({
accountID: Number(adminParticipant.accountID),
email: adminParticipant.login,
Expand Down Expand Up @@ -3973,7 +3981,8 @@
}

// Create the expense chat for the employee whose IOU is being paid
const employeeWorkspaceChat = createPolicyExpenseChats(policyID, {[iouReportOwnerEmail]: employeeAccountID}, true);
// TODO: Update to include reportActionsList later (https://github.com/Expensify/App/issues/66578)
const employeeWorkspaceChat = createPolicyExpenseChats(policyID, {[iouReportOwnerEmail]: employeeAccountID}, undefined, true);
const newWorkspace = {
id: policyID,

Expand Down
3 changes: 2 additions & 1 deletion src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@
// map of reportID to all reportActions for that report
const allReportActions: OnyxCollection<ReportActions> = {};

Onyx.connect({

Check warning on line 376 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
callback: (actions, key) => {
if (!key || !actions) {
Expand All @@ -385,7 +385,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 388 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -394,7 +394,7 @@
});

let allPersonalDetails: OnyxEntry<PersonalDetailsList> = {};
Onyx.connect({

Check warning on line 397 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
allPersonalDetails = value ?? {};
Expand All @@ -409,7 +409,7 @@
});

let onboarding: OnyxEntry<Onboarding>;
Onyx.connect({

Check warning on line 412 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_ONBOARDING,
callback: (val) => {
if (Array.isArray(val)) {
Expand Down Expand Up @@ -6106,6 +6106,7 @@
iouReport: OnyxEntry<Report>,
policy: Policy,
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
reportActions: OnyxCollection<ReportActions>,
reportTransactions: Transaction[] = [],
): {policyExpenseChatReportID?: string} | undefined {
if (!policy || !iouReport) {
Expand Down Expand Up @@ -6179,7 +6180,7 @@
const announceRoomMembers = buildRoomMembersOnyxData(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID, [submitterAccountID]);

// Create policy expense chat for the submitter
const policyExpenseChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs);
const policyExpenseChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs, reportActions);
const optimisticPolicyExpenseChatReportID = policyExpenseChats.reportCreationData[submitterEmail].reportID;
const optimisticPolicyExpenseChatCreatedReportActionID = policyExpenseChats.reportCreationData[submitterEmail].reportActionID;

Expand Down
5 changes: 4 additions & 1 deletion src/pages/ReportChangeWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import type {WorkspaceListItemType} from '@components/SelectionList/ListItem/types';
import UserListItem from '@components/SelectionList/ListItem/UserListItem';
import useAllPolicyExpenseChatReportActions from '@hooks/useAllPolicyExpenseChatReportActions';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
Expand Down Expand Up @@ -74,6 +75,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro
const hasViolations = hasViolationsReportUtils(report?.reportID, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? '');
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const [userBillingGracePeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const {filteredReportActions} = useAllPolicyExpenseChatReportActions();

const selectPolicy = useCallback(
(policyID?: string) => {
Expand All @@ -88,7 +90,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro
const {backTo} = route.params;
Navigation.goBack(backTo);
if (isIOUReport(reportID)) {
const invite = moveIOUReportToPolicyAndInviteSubmitter(report, policy, formatPhoneNumber, reportTransactions);
const invite = moveIOUReportToPolicyAndInviteSubmitter(report, policy, formatPhoneNumber, filteredReportActions, reportTransactions);
if (!invite?.policyExpenseChatReportID) {
moveIOUReportToPolicy(report, policy, false, reportTransactions);
}
Expand Down Expand Up @@ -134,6 +136,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro
parentReport,
formatPhoneNumber,
reportTransactions,
filteredReportActions,
isReportLastVisibleArchived,
session?.accountID,
session?.email,
Expand Down
96 changes: 94 additions & 2 deletions tests/actions/ReportTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3523,7 +3523,7 @@ describe('actions/Report', () => {
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy);

// When moving iou to a workspace and invite the submitter
Report.moveIOUReportToPolicyAndInviteSubmitter(iouReport, policy, (phone: string) => phone);
Report.moveIOUReportToPolicyAndInviteSubmitter(iouReport, policy, (phone: string) => phone, {});
await waitForBatchedUpdates();

// Then MOVED report action should be added to the expense report
Expand Down Expand Up @@ -3590,7 +3590,7 @@ describe('actions/Report', () => {

// Call moveIOUReportToPolicyAndInviteSubmitter
const formatPhoneNumber = (phoneNumber: string) => phoneNumber;
Report.moveIOUReportToPolicyAndInviteSubmitter(iouReport, policy, formatPhoneNumber);
Report.moveIOUReportToPolicyAndInviteSubmitter(iouReport, policy, formatPhoneNumber, {});
await waitForBatchedUpdates();

// Simulate network failure
Expand All @@ -3617,6 +3617,98 @@ describe('actions/Report', () => {
// Cleanup
mockFetch.succeed?.();
});

it('should negate transaction amounts when reportTransactions are provided', async () => {
const ownerAccountID = 1;
const ownerEmail = 'owner@gmail.com';
const transactionID = 'txn123';
const iouReport: OnyxTypes.Report = {
...createRandomReport(1, undefined),
type: CONST.REPORT.TYPE.IOU,
ownerAccountID,
total: 5000,
};
const policy: OnyxTypes.Policy = {...createRandomPolicy(1), role: CONST.POLICY.ROLE.ADMIN};
const transaction: OnyxTypes.Transaction = {
...createRandomTransaction(1),
transactionID,
reportID: iouReport.reportID,
amount: 5000,
modifiedAmount: 6000,
};

await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[ownerAccountID]: {
login: ownerEmail,
},
});
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport);
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy);
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);

// When moving IOU to a workspace with reportTransactions
Report.moveIOUReportToPolicyAndInviteSubmitter(iouReport, policy, (phone: string) => phone, {}, [transaction]);
await waitForBatchedUpdates();

// Then the transaction amounts should be negated optimistically
const updatedTransaction = await new Promise<OnyxEntry<OnyxTypes.Transaction>>((resolve) => {
const connection = Onyx.connect({
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
callback: (val) => {
resolve(val);
Onyx.disconnect(connection);
},
});
});
expect(updatedTransaction?.amount).toBe(-5000);
expect(updatedTransaction?.modifiedAmount).toBe(-6000);
});

it('should convert IOU report to expense report with correct policyID when reportTransactions are provided', async () => {
const ownerAccountID = 1;
const ownerEmail = 'owner@gmail.com';
const transactionID = 'txn456';
const iouReport: OnyxTypes.Report = {
...createRandomReport(1, undefined),
type: CONST.REPORT.TYPE.IOU,
ownerAccountID,
total: 3000,
};
const policy: OnyxTypes.Policy = {...createRandomPolicy(1), role: CONST.POLICY.ROLE.ADMIN};
const transaction: OnyxTypes.Transaction = {
...createRandomTransaction(1),
transactionID,
reportID: iouReport.reportID,
amount: 3000,
};

await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[ownerAccountID]: {
login: ownerEmail,
},
});
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport);
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy);
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);

// When moving IOU to a workspace with transactions
Report.moveIOUReportToPolicyAndInviteSubmitter(iouReport, policy, (phone: string) => phone, {}, [transaction]);
await waitForBatchedUpdates();

// Then the report should be converted to an expense report with the new policyID
const updatedReport = await new Promise<OnyxEntry<OnyxTypes.Report>>((resolve) => {
const connection = Onyx.connect({
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
callback: (val) => {
resolve(val);
Onyx.disconnect(connection);
},
});
});
expect(updatedReport?.type).toBe(CONST.REPORT.TYPE.EXPENSE);
expect(updatedReport?.policyID).toBe(policy.id);
expect(updatedReport?.total).toBe(-3000);
});
});

describe('buildOptimisticChangePolicyData', () => {
Expand Down
Loading