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
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo
outstandingReportsByPolicyID,
isChatReportArchived,
isProduction,
isOffline,
})
: [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ function MoneyReportHeaderSelectionDropdown({reportID, primaryAction, isReportIn
outstandingReportsByPolicyID,
isChatReportArchived,
isProduction,
isOffline,
})
: [];

Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useReportPrimaryAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import type {ValueOf} from 'type-fest';

import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useNetwork from './useNetwork';
import useOnyx from './useOnyx';
import useReportIsArchived from './useReportIsArchived';
import useTransactionsAndViolationsForReport from './useTransactionsAndViolationsForReport';

function useReportPrimaryAction(reportID: string | undefined): ValueOf<typeof CONST.REPORT.PRIMARY_ACTIONS> | '' {
const {isPaidAnimationRunning, isApprovedAnimationRunning, isSubmittingAnimationRunning} = usePaymentAnimationsContext();
const {login: currentUserLogin, accountID} = useCurrentUserPersonalDetails();
const {isOffline} = useNetwork();

const [moneyRequestReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(moneyRequestReport?.chatReportID)}`);
Expand Down Expand Up @@ -66,6 +68,7 @@ function useReportPrimaryAction(reportID: string | undefined): ValueOf<typeof CO
isChatReportArchived,
invoiceReceiverPolicy,
ownerLogin,
isOffline,
});
}

Expand Down
2 changes: 2 additions & 0 deletions src/hooks/useSelectionModeReportActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ function useSelectionModeReportActions({
isChatReportArchived,
invoiceReceiverPolicy,
ownerLogin: submitterLogin,
isOffline,
});

const secondaryActions = (() => {
Expand All @@ -167,6 +168,7 @@ function useSelectionModeReportActions({
outstandingReportsByPolicyID,
isChatReportArchived,
isProduction,
isOffline,
});
})();

Expand Down
9 changes: 6 additions & 3 deletions src/libs/ReportPrimaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ type GetReportPrimaryActionParams = {
isChatReportArchived: boolean;
invoiceReceiverPolicy?: Policy;
ownerLogin: string | undefined;
/** Whether the client is currently offline. Will become required once #66407 lands. */
isOffline?: boolean;
};

type IsPrimaryPayActionParams = {
Expand Down Expand Up @@ -318,7 +320,7 @@ function isExportAction(report: Report, currentUserLogin: string, policy?: Polic
return false;
}

function isRemoveHoldAction(report: Report, chatReport: OnyxEntry<Report>, reportTransactions: Transaction[]) {
function isRemoveHoldAction(report: Report, chatReport: OnyxEntry<Report>, reportTransactions: Transaction[], isOffline?: boolean) {
const isClosedReport = isClosedReportUtils(report);
if (isClosedReport) {
return false;
Expand All @@ -331,7 +333,7 @@ function isRemoveHoldAction(report: Report, chatReport: OnyxEntry<Report>, repor
}

const reportActions = getAllReportActions(report.reportID);
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions);
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions, isOffline);

if (!transactionThreadReportID) {
return false;
Expand Down Expand Up @@ -483,6 +485,7 @@ function getReportPrimaryAction(params: GetReportPrimaryActionParams): ValueOf<t
chatReport,
invoiceReceiverPolicy,
ownerLogin,
isOffline,
} = params;

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

if (isRemoveHoldAction(report, chatReport, reportTransactions) || (isPayActionWithAllExpensesHeld && expensesToHold.length)) {
if (isRemoveHoldAction(report, chatReport, reportTransactions, isOffline) || (isPayActionWithAllExpensesHeld && expensesToHold.length)) {
return CONST.REPORT.PRIMARY_ACTIONS.REMOVE_HOLD;
}

Expand Down
14 changes: 10 additions & 4 deletions src/libs/ReportSecondaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,8 +586,9 @@ function isHoldAction(
reportActions: ReportAction[] | undefined,
policy: OnyxEntry<Policy>,
currentUserAccountID: number | undefined,
isOffline?: boolean,
): boolean {
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions);
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions, isOffline);
const isOneExpenseReport = reportTransactions.length === 1;
const transaction = reportTransactions.at(0);

Expand Down Expand Up @@ -839,6 +840,7 @@ function isRemoveHoldAction(
reportActions?: ReportAction[],
policy?: Policy,
primaryAction?: ValueOf<typeof CONST.REPORT.PRIMARY_ACTIONS> | '',
isOffline?: boolean,
Comment thread
thelullabyy marked this conversation as resolved.
): boolean {
const isClosedReport = isClosedReportUtils(report);
if (isClosedReport) {
Expand All @@ -851,7 +853,7 @@ function isRemoveHoldAction(
return false;
}

const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions);
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions, isOffline);

if (!transactionThreadReportID) {
return false;
Expand Down Expand Up @@ -931,6 +933,7 @@ function getSecondaryReportActions({
isChatReportArchived = false,
parentReport,
isProduction,
isOffline,
}: {
currentUserLogin: string;
currentUserAccountID: number;
Expand All @@ -951,6 +954,8 @@ function getSecondaryReportActions({
isChatReportArchived?: boolean;
parentReport?: OnyxEntry<Report>;
isProduction: boolean;
/** Whether the client is currently offline. Will become required once #66407 lands. */
isOffline?: boolean;
}): Array<ValueOf<typeof CONST.REPORT.SECONDARY_ACTIONS>> {
const options: Array<ValueOf<typeof CONST.REPORT.SECONDARY_ACTIONS>> = [];

Expand Down Expand Up @@ -995,6 +1000,7 @@ function getSecondaryReportActions({
reportMetadata,
isChatReportArchived,
ownerLogin: submitterLogin,
isOffline,
});

if (
Expand Down Expand Up @@ -1040,11 +1046,11 @@ function getSecondaryReportActions({
options.push(CONST.REPORT.SECONDARY_ACTIONS.REOPEN);
}

if (isHoldAction(report, chatReport, reportTransactions, reportActions, policy, currentUserAccountID)) {
if (isHoldAction(report, chatReport, reportTransactions, reportActions, policy, currentUserAccountID, isOffline)) {
options.push(CONST.REPORT.SECONDARY_ACTIONS.HOLD);
}

if (isRemoveHoldAction(report, chatReport, reportTransactions, reportActions, policy, primaryAction)) {
if (isRemoveHoldAction(report, chatReport, reportTransactions, reportActions, policy, primaryAction, isOffline)) {
options.push(CONST.REPORT.SECONDARY_ACTIONS.REMOVE_HOLD);
}

Expand Down
83 changes: 83 additions & 0 deletions tests/unit/ReportPrimaryActionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,89 @@ describe('getPrimaryAction', () => {
).toBe(CONST.REPORT.PRIMARY_ACTIONS.REMOVE_HOLD);
});

it('should thread isOffline into the one-transaction thread lookup when detecting REMOVE HOLD', async () => {
const report = createMock<Report>({
reportID: REPORT_ID,
type: CONST.REPORT.TYPE.EXPENSE,
});
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
const policy = createMock<Policy>({});
const HOLD_ACTION_ID = 'HOLD_ACTION_ID';
const REPORT_ACTION_ID = 'REPORT_ACTION_ID';
const PENDING_DELETE_REPORT_ACTION_ID = 'PENDING_DELETE_REPORT_ACTION_ID';
const TRANSACTION_ID = 'TRANSACTION_ID';
const PENDING_DELETE_TRANSACTION_ID = 'PENDING_DELETE_TRANSACTION_ID';
const CHILD_REPORT_ID = 'CHILD_REPORT_ID';
const transaction = createMock<Transaction>({
transactionID: TRANSACTION_ID,
comment: {
hold: HOLD_ACTION_ID,
},
});

const reportAction = createMock<ReportAction>({
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
reportActionID: REPORT_ACTION_ID,
actorAccountID: CURRENT_USER_ACCOUNT_ID,
childReportID: CHILD_REPORT_ID,
message: [
{
html: 'html',
},
],
originalMessage: {
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
IOUTransactionID: TRANSACTION_ID,
},
});

// A second IOU action that has been deleted while offline. When offline, it is still counted as a
// transaction, so the report is no longer a one-transaction report and REMOVE HOLD is not offered.
const pendingDeleteReportAction = createMock<ReportAction>({
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
reportActionID: PENDING_DELETE_REPORT_ACTION_ID,
actorAccountID: CURRENT_USER_ACCOUNT_ID,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
message: [
{
html: '',
},
],
originalMessage: {
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
IOUTransactionID: PENDING_DELETE_TRANSACTION_ID,
},
});

const holdAction = {
reportActionID: HOLD_ACTION_ID,
reportID: CHILD_REPORT_ID,
actorAccountID: CURRENT_USER_ACCOUNT_ID,
};

await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, {
[REPORT_ACTION_ID]: reportAction,
[PENDING_DELETE_REPORT_ACTION_ID]: pendingDeleteReportAction,
});
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${CHILD_REPORT_ID}`, {[HOLD_ACTION_ID]: holdAction});

const params = {
currentUserLogin: CURRENT_USER_EMAIL,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
ownerLogin: '',
chatReport,
reportTransactions: [transaction],
violations: {},
bankAccountList: {},
policy,
isChatReportArchived: false,
};

expect(getReportPrimaryAction({...params, isOffline: false})).toBe(CONST.REPORT.PRIMARY_ACTIONS.REMOVE_HOLD);
expect(getReportPrimaryAction({...params, isOffline: true})).not.toBe(CONST.REPORT.PRIMARY_ACTIONS.REMOVE_HOLD);
});

it('should return REMOVE HOLD over APPROVE when all expenses are held and the manager can unhold', async () => {
const MEMBER_ACCOUNT_ID = 2;
const HOLD_ACTION_ID = 'HOLD_ACTION_ID';
Expand Down
Loading