Skip to content

Reports - Invoice is accessible when account does not have invoice, which opens not here page #62735

Closed

Description

@mitarachim

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: 9.1.51-0
Reproducible in staging?: Yes
Reproducible in production?: No
If this was caught during regression testing, add the test name, ID and link from TestRail: #61170
Email or phone of affected tester (no customers): applausetester+sjdoidwsn111j12@applause.expensifail.com
Issue reported by: Applause Internal Team
Device used: Mac 15.4 / Chrome
App Component: Search

Action Performed:

Precondition:

  • Account has no workspace, or has workspace without invoicing feature.
  1. Go to staging.new.expensify.com
  2. Go to Reports.
  3. Click Type:Expense.
  4. Select Invoice.
  5. Click Apply.
  6. Click Send invoice button on the empty state screen.

Expected Result:

In Step 4, Invoice should be hidden when the account does not have invoice expense or does not have workspace with invoicing feature.

Actual Result:

In Step 4, Invoice is accessible when the account does not have invoice expense or does not have workspace with invoicing feature.
This leads to Step 6 where app opens not here page after clicking Send invoice button on the empty state screen.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari
  • MacOS: Desktop

Screenshots/Videos

Bug6841231_1748093099013.20250524_212038.mp4

View all open jobs on GitHub

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    DeployBlockerIndicates it should block deploying the API
    on May 24, 2025
  2. melvin-bot commented on May 24, 2025

    @melvin-bot

    Triggered auto assignment to @RachCHopkins (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

  3. melvin-bot commented on May 24, 2025

    @melvin-bot

    Triggered auto assignment to @MarioExpensify (DeployBlockerCash), see https://stackoverflowteams.com/c/expensify/questions/9980/ for more details.

  4. melvin-bot commented on May 24, 2025

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  5. github-actions commented on May 24, 2025

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  6. BlameGPT commented on May 25, 2025

    @BlameGPT
    Contributor

    Possible culprit PRs for this issue

    • Fix change workspace condition #62062: This PR refactors logic for displaying the 'Change workspace' secondary action option. It modifies the behavior based on the report state and user roles, similar to the conditions described in the issue where the invoice option should only be shown if certain criteria are met (account having invoice feature).
    • Suggested Search & Filter Revamp #61170: This PR introduces changes to the filters and displayed content in reports. As the issue involves displaying invoices based on specific account features, any changes to the report's logic could affect whether invoices appear correctly for users without invoice capabilities.
  7. jaydamani commented on May 26, 2025

    @jaydamani
    Contributor

    🚨 Edited by proposal-police: This proposal was edited at 2025-05-26 04:49:39 UTC.

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    Invoice is accessible when account does not have invoice, which opens not here page

    What is the root cause of that problem?

    Before the changes from #61170 , the invoice type was only showed based on below conditions:

    if (canSendInvoice(allPolicies, email) || hasInvoiceReports()) {

    After search revamp, transaction type is showed inside search filter bar but there is no check like before so invoice type is visible even when user can not create invoice

    What changes do you think we should make in order to solve the problem?

    Add filter in below code to remove invoice type based on previous conditions

    const typeComponent = useCallback(
    ({closeOverlay}: PopoverComponentProps) => {
    const value = typeOptions.find((option) => option.value === type) ?? null;
    const onChange = (item: SingleSelectItem<SearchDataTypes> | null) => {
    const hasTypeChanged = item?.value !== type;
    const newType = item?.value ?? CONST.SEARCH.DATA_TYPES.EXPENSE;
    // If the type has changed, reset the status so we dont have an invalid status selected
    const newStatus = hasTypeChanged ? CONST.SEARCH.STATUS.EXPENSE.ALL : status;
    const query = buildSearchQueryString({...queryJSON, type: newType, status: newStatus});
    Navigation.setParams({q: query});
    };
    return (
    <SingleSelectPopup
    label={translate('common.type')}
    value={value}
    items={typeOptions}
    closeOverlay={closeOverlay}
    onChange={onChange}
    />
    );
    },
    [queryJSON, status, translate, type],
    );

    Updated code:

                // previous code
                let visibleTypeOptions = typeOptions;
                if (!canSendInvoice(allPolicies, session?.email) && !hasInvoiceReports()) {
                    visibleTypeOptions = visibleTypeOptions.filter(typeOption => typeOption.value !== CONST.SEARCH.DATA_TYPES.INVOICE)
                }
                return (
                    <SingleSelectPopup
                        label={translate('common.type')}
                        value={value}
                        items={visibleTypeOptions}
                        closeOverlay={closeOverlay}
                        onChange={onChange}
                    />
                );
                // previous code
    Git diff for testing
    diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx
    index 7ed6fe5c0b9..52dadaf120d 100644
    --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx
    +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx
    @@ -30,12 +30,13 @@ import {updateAdvancedFilters} from '@libs/actions/Search';
     import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils';
     import DateUtils from '@libs/DateUtils';
     import Navigation from '@libs/Navigation/Navigation';
    -import {getAllTaxRates} from '@libs/PolicyUtils';
    +import {canSendInvoice, getAllTaxRates} from '@libs/PolicyUtils';
     import {buildFilterFormValuesFromQuery, buildQueryStringFromFilterFormValues, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
     import CONST from '@src/CONST';
     import ONYXKEYS from '@src/ONYXKEYS';
     import ROUTES from '@src/ROUTES';
     import type {SearchDataTypes} from '@src/types/onyx/SearchResults';
    +import { hasInvoiceReports } from '@libs/ReportUtils';
     import type {SearchHeaderOptionValue} from './SearchPageHeader';
     
     type SearchFiltersBarProps = {
    @@ -129,6 +130,8 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
         const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true});
         const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true});
         const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true});
    +    const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
    +    const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false});
     
         const taxRates = getAllTaxRates();
         const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
    @@ -163,18 +166,21 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
                     const query = buildSearchQueryString({...queryJSON, type: newType, status: newStatus});
                     Navigation.setParams({q: query});
                 };
    -
    +            let visibleTypeOptions = typeOptions;
    +            if (!canSendInvoice(allPolicies, session?.email) && !hasInvoiceReports()) {
    +                visibleTypeOptions = visibleTypeOptions.filter(typeOption => typeOption.value !== CONST.SEARCH.DATA_TYPES.INVOICE)
    +            }
                 return (
                     <SingleSelectPopup
                         label={translate('common.type')}
                         value={value}
    -                    items={typeOptions}
    +                    items={visibleTypeOptions}
                         closeOverlay={closeOverlay}
                         onChange={onChange}
                     />
                 );
             },
    -        [queryJSON, status, translate, type],
    +        [queryJSON, status, translate, type, allPolicies, session],
         );
     
         const statusComponent = useCallback(
    

    What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?

    What alternative solutions did you explore? (Optional)

    Move typeOptions definition to be inside component and only push invoice when conditions are met

        const typeOptions: Array<SingleSelectItem<SearchDataTypes>> = useMemo(() => {
            const typeOptionsToShow: Array<SingleSelectItem<SearchDataTypes>> = [
                {translation: 'common.expense', value: CONST.SEARCH.DATA_TYPES.EXPENSE},
                {translation: 'common.chat', value: CONST.SEARCH.DATA_TYPES.CHAT},
            ];
    
            if (canSendInvoice(allPolicies, session?.email) && hasInvoiceReports()) {
                typeOptionsToShow.push({translation: 'common.invoice', value: CONST.SEARCH.DATA_TYPES.INVOICE});
            }
    
    
            typeOptionsToShow.push({translation: 'common.trip', value: CONST.SEARCH.DATA_TYPES.TRIP});
            typeOptionsToShow.push({translation: 'common.task', value: CONST.SEARCH.DATA_TYPES.TASK});
            return typeOptionsToShow
        }, [allPolicies, session?.email])
    

    Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.

  8. github-actions commented on May 26, 2025

    @github-actions
    Contributor

    ⚠️ @jaydamani Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).

  9. removed
    DeployBlockerIndicates it should block deploying the API
    on May 26, 2025
  10. MarioExpensify commented on May 26, 2025

    @MarioExpensify
    Contributor

    @jaydamani thanks for your proposal but this was reverted here and will be fixed by the original author.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.EngineeringHourlyKSv2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions