diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e7504f58e8e72..e8a8120009ab8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11613,6 +11613,8 @@ function prepareOnboardingOnyxData({ let createWorkspaceTaskReportID; let addExpenseApprovalsTaskReportID; + let setupTagsTaskReportID; + let setupCategoriesAndTagsTaskReportID; const tasksData = onboardingMessage.tasks .filter((task) => { if (engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM) { @@ -11694,6 +11696,12 @@ function prepareOnboardingOnyxData({ if (task.type === CONST.ONBOARDING_TASK_TYPE.ADD_EXPENSE_APPROVALS) { addExpenseApprovalsTaskReportID = currentTask.reportID; } + if (task.type === CONST.ONBOARDING_TASK_TYPE.SETUP_TAGS) { + setupTagsTaskReportID = currentTask.reportID; + } + if (task.type === CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES_AND_TAGS) { + setupCategoriesAndTagsTaskReportID = currentTask.reportID; + } return { task, @@ -11901,6 +11909,8 @@ function prepareOnboardingOnyxData({ choice: engagementChoice, createWorkspace: createWorkspaceTaskReportID, addExpenseApprovals: addExpenseApprovalsTaskReportID, + setupTags: setupTagsTaskReportID, + setupCategoriesAndTags: setupCategoriesAndTagsTaskReportID, }, }, ); @@ -11970,6 +11980,7 @@ function prepareOnboardingOnyxData({ choice: null, createWorkspace: null, addExpenseApprovals: null, + setupCategoriesAndTags: null, }, }, ); diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 0b2f8b64dccff..4fa96f04b42c3 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -39,6 +39,40 @@ import type {ApprovalRule, ExpenseRule, MccGroup} from '@src/types/onyx/Policy'; import type {PolicyCategoryExpenseLimitType} from '@src/types/onyx/PolicyCategory'; import type {OnyxData} from '@src/types/onyx/Request'; +type CreatePolicyCategoryParams = { + policyID: string; + categoryName: string; + isSetupCategoriesTaskParentReportArchived: boolean; + setupCategoryTaskReport: OnyxEntry; + setupCategoryTaskParentReport: OnyxEntry; + currentUserAccountID: number; + hasOutstandingChildTask: boolean; + parentReportAction: OnyxEntry; + setupCategoriesAndTagsTaskReport?: OnyxEntry; + setupCategoriesAndTagsTaskParentReport?: OnyxEntry; + isSetupCategoriesAndTagsTaskParentReportArchived?: boolean; + setupCategoriesAndTagsHasOutstandingChildTask?: boolean; + setupCategoriesAndTagsParentReportAction?: OnyxEntry; + policyHasTags?: boolean; +}; + +type SetWorkspaceCategoryEnabledParams = { + policyData: PolicyData; + categoriesToUpdate: Record; + isSetupCategoriesTaskParentReportArchived: boolean; + setupCategoryTaskReport: OnyxEntry; + setupCategoryTaskParentReport: OnyxEntry; + currentUserAccountID: number; + hasOutstandingChildTask: boolean; + parentReportAction: OnyxEntry | undefined; + setupCategoriesAndTagsTaskReport?: OnyxEntry; + setupCategoriesAndTagsTaskParentReport?: OnyxEntry; + isSetupCategoriesAndTagsTaskParentReportArchived?: boolean; + setupCategoriesAndTagsHasOutstandingChildTask?: boolean; + setupCategoriesAndTagsParentReportAction?: OnyxEntry; + policyHasTags?: boolean; +}; + function appendSetupCategoriesOnboardingData( onyxData: OnyxData< typeof ONYXKEYS.COLLECTION.POLICY_CATEGORIES | typeof ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS @@ -328,16 +362,22 @@ function getPolicyCategories(policyID: string) { API.read(READ_COMMANDS.GET_POLICY_CATEGORIES, params); } -function setWorkspaceCategoryEnabled( - policyData: PolicyData, - categoriesToUpdate: Record, - isSetupCategoriesTaskParentReportArchived: boolean, - setupCategoryTaskReport: OnyxEntry, - setupCategoryTaskParentReport: OnyxEntry, - currentUserAccountID: number, - hasOutstandingChildTask: boolean, - parentReportAction: OnyxEntry | undefined, -) { +function setWorkspaceCategoryEnabled({ + policyData, + categoriesToUpdate, + isSetupCategoriesTaskParentReportArchived, + setupCategoryTaskReport, + setupCategoryTaskParentReport, + currentUserAccountID, + hasOutstandingChildTask, + parentReportAction, + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, +}: SetWorkspaceCategoryEnabledParams) { const policyID = policyData.policy?.id; const policyCategoriesOptimisticData = { ...Object.keys(categoriesToUpdate).reduce((acc, key) => { @@ -414,6 +454,18 @@ function setWorkspaceCategoryEnabled( parentReportAction, ); + if (setupCategoriesAndTagsTaskReport && policyHasTags) { + appendSetupCategoriesOnboardingData( + onyxData, + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived ?? false, + currentUserAccountID, + setupCategoriesAndTagsHasOutstandingChildTask ?? false, + setupCategoriesAndTagsParentReportAction, + ); + } + const parameters = { policyID, categories: JSON.stringify(Object.keys(categoriesToUpdate).map((key) => categoriesToUpdate[key])), @@ -620,16 +672,22 @@ function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryNa API.write(WRITE_COMMANDS.REMOVE_POLICY_CATEGORY_RECEIPTS_REQUIRED, parameters, onyxData); } -function createPolicyCategory( - policyID: string, - categoryName: string, - isSetupCategoriesTaskParentReportArchived: boolean, - setupCategoryTaskReport: OnyxEntry, - setupCategoryTaskParentReport: OnyxEntry, - currentUserAccountID: number, - hasOutstandingChildTask: boolean, - parentReportAction: OnyxEntry, -) { +function createPolicyCategory({ + policyID, + categoryName, + isSetupCategoriesTaskParentReportArchived, + setupCategoryTaskReport, + setupCategoryTaskParentReport, + currentUserAccountID, + hasOutstandingChildTask, + parentReportAction, + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, +}: CreatePolicyCategoryParams) { const onyxData = buildOptimisticPolicyCategories(policyID, [categoryName]); appendSetupCategoriesOnboardingData( onyxData, @@ -640,6 +698,18 @@ function createPolicyCategory( hasOutstandingChildTask, parentReportAction, ); + // Complete the combined "Set up categories and tags" task only if tags already exist + if (setupCategoriesAndTagsTaskReport && policyHasTags) { + appendSetupCategoriesOnboardingData( + onyxData, + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived ?? false, + currentUserAccountID, + setupCategoriesAndTagsHasOutstandingChildTask ?? false, + setupCategoriesAndTagsParentReportAction, + ); + } const parameters = { policyID, categories: JSON.stringify([{name: categoryName}]), diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index 4ff64a78db170..6200afd76846c 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -29,9 +29,10 @@ import {goBackWhenEnableFeature} from '@libs/PolicyUtils'; import {pushTransactionViolationsOnyxData} from '@libs/ReportUtils'; import {getTagArrayFromName} from '@libs/TransactionUtils'; import type {PolicyTagList} from '@pages/workspace/tags/types'; +import {completeTask} from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ImportedSpreadsheet, Policy, PolicyTag, PolicyTagLists, PolicyTags, RecentlyUsedTags} from '@src/types/onyx'; +import type {ImportedSpreadsheet, Policy, PolicyTag, PolicyTagLists, PolicyTags, RecentlyUsedTags, Report} from '@src/types/onyx'; import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon'; import type {ApprovalRule} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; @@ -126,7 +127,14 @@ function updateImportSpreadsheetData(tagsLength: number): OnyxData, + setupCategoriesAndTagsTaskReport?: OnyxEntry, + policyHasCustomCategories?: boolean, +) { const policyTag = PolicyUtils.getTagLists(policyTags)?.at(0) ?? ({} as PolicyTagList); const newTagName = PolicyUtils.escapeTagName(tagName); @@ -188,6 +196,19 @@ function createPolicyTag(policyID: string, tagName: string, policyTags: PolicyTa }; API.write(WRITE_COMMANDS.CREATE_POLICY_TAG, parameters, onyxData); + + if (setupTagsTaskReport && (setupTagsTaskReport.stateNum !== CONST.REPORT.STATE_NUM.APPROVED || setupTagsTaskReport.statusNum !== CONST.REPORT.STATUS_NUM.APPROVED)) { + completeTask(setupTagsTaskReport, false, false, undefined); + } + + // Complete the combined "Set up categories and tags" task only if categories already exist + if ( + setupCategoriesAndTagsTaskReport && + policyHasCustomCategories && + (setupCategoriesAndTagsTaskReport.stateNum !== CONST.REPORT.STATE_NUM.APPROVED || setupCategoriesAndTagsTaskReport.statusNum !== CONST.REPORT.STATUS_NUM.APPROVED) + ) { + completeTask(setupCategoriesAndTagsTaskReport, false, false, undefined); + } } function importPolicyTags(policyID: string, tags: PolicyTag[]) { diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx index 1cb19e136ca61..1c9ec4c9a28cb 100644 --- a/src/pages/workspace/categories/CategorySettingsPage.tsx +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -15,6 +15,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useOnboardingTaskInformation from '@hooks/useOnboardingTaskInformation'; +import useOnyx from '@hooks/useOnyx'; import usePolicyData from '@hooks/usePolicyData'; import useThemeStyles from '@hooks/useThemeStyles'; import {formatRequiredFieldsTitle} from '@libs/AttendeeUtils'; @@ -25,12 +26,13 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {isDisablingOrDeletingLastEnabledCategory} from '@libs/OptionsListUtils'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; -import {getWorkflowApprovalsUnavailable, isControlPolicy} from '@libs/PolicyUtils'; +import {getTagLists, getWorkflowApprovalsUnavailable, isControlPolicy} from '@libs/PolicyUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import {clearCategoryErrors, deleteWorkspaceCategories, setWorkspaceCategoryEnabled} from '@userActions/Policy/Category'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -70,6 +72,21 @@ function CategorySettingsPage({ parentReportAction, } = useOnboardingTaskInformation(CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES); + const { + taskReport: setupCategoriesAndTagsTaskReport, + taskParentReport: setupCategoriesAndTagsTaskParentReport, + isOnboardingTaskParentReportArchived: isSetupCategoriesAndTagsTaskParentReportArchived, + hasOutstandingChildTask: setupCategoriesAndTagsHasOutstandingChildTask, + parentReportAction: setupCategoriesAndTagsParentReportAction, + } = useOnboardingTaskInformation(CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES_AND_TAGS); + + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); + + const policyHasTags = useMemo(() => { + const tagLists = getTagLists(policyTags); + return tagLists.some((tagList) => Object.keys(tagList.tags ?? {}).length > 0); + }, [policyTags]); + const navigateBack = () => { Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_CATEGORIES_ROOT.getRoute(policyID, backTo) : undefined); }; @@ -138,26 +155,47 @@ function CategorySettingsPage({ return policyCategory?.pendingFields?.areCommentsRequired; }, [policyCategory?.pendingFields, policy?.isAttendeeTrackingEnabled]); - if (!policyCategory) { - return ; - } - - const updateWorkspaceCategoryEnabled = (value: boolean) => { - if (shouldPreventDisableOrDelete) { - setIsCannotDeleteOrDisableLastCategoryModalVisible(true); - return; - } - setWorkspaceCategoryEnabled( + const updateWorkspaceCategoryEnabled = useCallback( + (value: boolean) => { + if (shouldPreventDisableOrDelete) { + setIsCannotDeleteOrDisableLastCategoryModalVisible(true); + return; + } + setWorkspaceCategoryEnabled({ + policyData, + categoriesToUpdate: {[policyCategory.name]: {name: policyCategory.name, enabled: value}}, + isSetupCategoriesTaskParentReportArchived: isSetupCategoryTaskParentReportArchived, + setupCategoryTaskReport, + setupCategoryTaskParentReport, + currentUserAccountID: currentUserPersonalDetails.accountID, + hasOutstandingChildTask, + parentReportAction, + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, + }); + }, + [ + shouldPreventDisableOrDelete, policyData, - {[policyCategory.name]: {name: policyCategory.name, enabled: value}}, + policyCategory.name, isSetupCategoryTaskParentReportArchived, setupCategoryTaskReport, setupCategoryTaskParentReport, currentUserPersonalDetails.accountID, hasOutstandingChildTask, parentReportAction, - ); - }; + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, + ], + ); const navigateToEditCategory = () => { Navigation.navigate( @@ -184,6 +222,10 @@ function CategorySettingsPage({ const workflowApprovalsUnavailable = getWorkflowApprovalsUnavailable(policy); const approverDisabled = !policy?.areWorkflowsEnabled || workflowApprovalsUnavailable; + if (!policyCategory) { + return ; + } + return ( { + const tagLists = getTagLists(policyTags); + return tagLists.some((tagList) => Object.keys(tagList.tags ?? {}).length > 0); + }, [policyTags]); + const createCategory = useCallback( (values: FormOnyxValues) => { - createPolicyCategory( - route.params.policyID, - values.categoryName.trim(), - isSetupCategoryTaskParentReportArchived, + createPolicyCategory({ + policyID: route.params.policyID, + categoryName: values.categoryName.trim(), + isSetupCategoriesTaskParentReportArchived: isSetupCategoryTaskParentReportArchived, setupCategoryTaskReport, setupCategoryTaskParentReport, - currentUserPersonalDetails.accountID, + currentUserAccountID: currentUserPersonalDetails.accountID, hasOutstandingChildTask, parentReportAction, - ); + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, + }); Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_CATEGORIES_ROOT.getRoute(route.params.policyID, backTo) : undefined); }, [ @@ -61,6 +82,12 @@ function CreateCategoryPage({route}: CreateCategoryPageProps) { backTo, hasOutstandingChildTask, parentReportAction, + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, ], ); diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index e0b7dbcb658f5..3ad15eac00dcc 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -47,7 +47,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; import {isDisablingOrDeletingLastEnabledCategory} from '@libs/OptionsListUtils'; -import {getConnectedIntegration, getCurrentConnectionName, hasAccountingConnections, isControlPolicy, shouldShowSyncError} from '@libs/PolicyUtils'; +import {getConnectedIntegration, getCurrentConnectionName, getTagLists, hasAccountingConnections, isControlPolicy, shouldShowSyncError} from '@libs/PolicyUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import variables from '@styles/variables'; @@ -102,6 +102,21 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { parentReportAction, } = useOnboardingTaskInformation(CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES); + const { + taskReport: setupCategoriesAndTagsTaskReport, + taskParentReport: setupCategoriesAndTagsTaskParentReport, + isOnboardingTaskParentReportArchived: isSetupCategoriesAndTagsTaskParentReportArchived, + hasOutstandingChildTask: setupCategoriesAndTagsHasOutstandingChildTask, + parentReportAction: setupCategoriesAndTagsParentReportAction, + } = useOnboardingTaskInformation(CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES_AND_TAGS); + + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyId}`, {canBeMissing: true}); + + const policyHasTags = useMemo(() => { + const tagLists = getTagLists(policyTags); + return tagLists.some((tagList) => Object.keys(tagList.tags ?? {}).length > 0); + }, [policyTags]); + const fetchCategories = useCallback(() => { openPolicyCategoriesPage(policyId); }, [policyId]); @@ -151,16 +166,22 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const updateWorkspaceCategoryEnabled = useCallback( (value: boolean, categoryName: string) => { - setWorkspaceCategoryEnabled( + setWorkspaceCategoryEnabled({ policyData, - {[categoryName]: {name: categoryName, enabled: value}}, - isSetupCategoryTaskParentReportArchived, + categoriesToUpdate: {[categoryName]: {name: categoryName, enabled: value}}, + isSetupCategoriesTaskParentReportArchived: isSetupCategoryTaskParentReportArchived, setupCategoryTaskReport, setupCategoryTaskParentReport, - currentUserPersonalDetails.accountID, + currentUserAccountID: currentUserPersonalDetails.accountID, hasOutstandingChildTask, parentReportAction, - ); + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, + }); }, [ policyData, @@ -170,6 +191,12 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { currentUserPersonalDetails.accountID, hasOutstandingChildTask, parentReportAction, + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, ], ); @@ -459,16 +486,22 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { return; } setSelectedCategories([]); - setWorkspaceCategoryEnabled( + setWorkspaceCategoryEnabled({ policyData, - categoriesToDisable, - isSetupCategoryTaskParentReportArchived, + categoriesToUpdate: categoriesToDisable, + isSetupCategoriesTaskParentReportArchived: isSetupCategoryTaskParentReportArchived, setupCategoryTaskReport, setupCategoryTaskParentReport, - currentUserPersonalDetails.accountID, + currentUserAccountID: currentUserPersonalDetails.accountID, hasOutstandingChildTask, parentReportAction, - ); + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, + }); }, }); } @@ -490,16 +523,22 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.ENABLE, onSelected: () => { setSelectedCategories([]); - setWorkspaceCategoryEnabled( + setWorkspaceCategoryEnabled({ policyData, - categoriesToEnable, - isSetupCategoryTaskParentReportArchived, + categoriesToUpdate: categoriesToEnable, + isSetupCategoriesTaskParentReportArchived: isSetupCategoryTaskParentReportArchived, setupCategoryTaskReport, setupCategoryTaskParentReport, - currentUserPersonalDetails.accountID, + currentUserAccountID: currentUserPersonalDetails.accountID, hasOutstandingChildTask, parentReportAction, - ); + setupCategoriesAndTagsTaskReport, + setupCategoriesAndTagsTaskParentReport, + isSetupCategoriesAndTagsTaskParentReportArchived, + setupCategoriesAndTagsHasOutstandingChildTask, + setupCategoriesAndTagsParentReportAction, + policyHasTags, + }); }, }); } diff --git a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx index 57757b028822e..e42dd9019964c 100644 --- a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx +++ b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {Keyboard} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -8,6 +8,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; +import useOnboardingTaskInformation from '@hooks/useOnboardingTaskInformation'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; @@ -28,15 +29,26 @@ type WorkspaceCreateTagPageProps = | PlatformStackScreenProps | PlatformStackScreenProps; +const DEFAULT_CATEGORIES_AMOUNT = 19; + function WorkspaceCreateTagPage({route}: WorkspaceCreateTagPageProps) { const policyID = route.params.policyID; const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true}); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); + const setupTagsTaskReportID = introSelected?.setupTags; + const [setupTagsTaskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${setupTagsTaskReportID}`, {canBeMissing: true}); + const {taskReport: setupCategoriesAndTagsTaskReport} = useOnboardingTaskInformation(CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES_AND_TAGS); const styles = useThemeStyles(); const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const backTo = route.params.backTo; const isQuickSettingsFlow = route.name === SCREENS.SETTINGS_TAGS.SETTINGS_TAG_CREATE; + const policyHasCustomCategories = useMemo(() => { + return Object.keys(policyCategories ?? {}).length > DEFAULT_CATEGORIES_AMOUNT; + }, [policyCategories]); + const validate = useCallback( (values: FormOnyxValues) => { const errors: FormInputErrors = {}; @@ -61,11 +73,11 @@ function WorkspaceCreateTagPage({route}: WorkspaceCreateTagPageProps) { const createTag = useCallback( (values: FormOnyxValues) => { - createPolicyTag(policyID, values.tagName.trim(), policyTags); + createPolicyTag(policyID, values.tagName.trim(), policyTags, setupTagsTaskReport, setupCategoriesAndTagsTaskReport, policyHasCustomCategories); Keyboard.dismiss(); Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); }, - [policyID, policyTags, isQuickSettingsFlow, backTo], + [policyID, policyTags, isQuickSettingsFlow, backTo, setupTagsTaskReport, setupCategoriesAndTagsTaskReport, policyHasCustomCategories], ); return ( diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index 6c0c63aa98ed8..0ff0b7bdcd407 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -2,7 +2,7 @@ import type {OnboardingInvite} from '@src/CONST'; import type {OnboardingPurpose} from './index'; /** The tasks of IntroSelected model */ -type IntroSelectedTask = 'viewTour' | 'createWorkspace' | 'setupCategories'; +type IntroSelectedTask = 'viewTour' | 'createWorkspace' | 'setupCategories' | 'setupCategoriesAndTags'; /** Model of onboarding */ type IntroSelected = { @@ -30,6 +30,12 @@ type IntroSelected = { /** Task reportID for 'addExpenseApprovals' type */ addExpenseApprovals?: string; + /** Task reportID for 'setupTags' type */ + setupTags?: string; + + /** Task reportID for 'setupCategoriesAndTags' type */ + setupCategoriesAndTags?: string; + /** The previous onboarding choices of the user */ previousChoices?: OnboardingPurpose[]; }; diff --git a/tests/actions/PolicyCategoryTest.ts b/tests/actions/PolicyCategoryTest.ts index 3ad2c66a99e40..13e7d43440b4b 100644 --- a/tests/actions/PolicyCategoryTest.ts +++ b/tests/actions/PolicyCategoryTest.ts @@ -87,7 +87,16 @@ describe('actions/PolicyCategory', () => { mockFetch?.pause?.(); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); - createPolicyCategory(fakePolicy.id, newCategoryName, false, undefined, undefined, CONST.DEFAULT_NUMBER_ID, false, undefined); + createPolicyCategory({ + policyID: fakePolicy.id, + categoryName: newCategoryName, + isSetupCategoriesTaskParentReportArchived: false, + setupCategoryTaskReport: undefined, + setupCategoryTaskParentReport: undefined, + currentUserAccountID: CONST.DEFAULT_NUMBER_ID, + hasOutstandingChildTask: false, + parentReportAction: undefined, + }); await waitForBatchedUpdates(); await new Promise((resolve) => { const connection = Onyx.connect({ @@ -189,7 +198,16 @@ describe('actions/PolicyCategory', () => { Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); - setWorkspaceCategoryEnabled(policyData.current, categoriesToUpdate, false, undefined, undefined, CONST.DEFAULT_NUMBER_ID, false, undefined); + setWorkspaceCategoryEnabled({ + policyData: policyData.current, + categoriesToUpdate, + isSetupCategoriesTaskParentReportArchived: false, + setupCategoryTaskReport: undefined, + setupCategoryTaskParentReport: undefined, + currentUserAccountID: CONST.DEFAULT_NUMBER_ID, + hasOutstandingChildTask: false, + parentReportAction: undefined, + }); await waitForBatchedUpdates(); await new Promise((resolve) => { const connection = Onyx.connect({ @@ -497,4 +515,64 @@ describe('actions/PolicyCategory', () => { await waitForBatchedUpdates(); }); }); + + describe('createPolicyCategory with onboarding task completion', () => { + it('should complete SETUP_CATEGORIES_AND_TAGS task when creating category and tags already exist', async () => { + const fakePolicy = createRandomPolicy(0); + const fakeCategories = createRandomPolicyCategories(3); + const fakeTags = createRandomPolicyTags('TestTagList', 2); + const newCategoryName = 'New category'; + + // Create a fake task report for SETUP_CATEGORIES_AND_TAGS + const fakeTaskReportID = '123456'; + const fakeTaskReport = { + reportID: fakeTaskReportID, + type: CONST.REPORT.TYPE.TASK, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + }; + + mockFetch?.pause?.(); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakeTags); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeTaskReportID}`, fakeTaskReport); + + createPolicyCategory({ + policyID: fakePolicy.id, + categoryName: newCategoryName, + isSetupCategoriesTaskParentReportArchived: false, + setupCategoryTaskReport: undefined, + setupCategoryTaskParentReport: undefined, + currentUserAccountID: CONST.DEFAULT_NUMBER_ID, + hasOutstandingChildTask: false, + parentReportAction: undefined, + setupCategoriesAndTagsTaskReport: fakeTaskReport, + setupCategoriesAndTagsTaskParentReport: undefined, + isSetupCategoriesAndTagsTaskParentReportArchived: false, + setupCategoriesAndTagsHasOutstandingChildTask: false, + setupCategoriesAndTagsParentReportAction: undefined, + policyHasTags: true, + }); + + await waitForBatchedUpdates(); + + // Verify the category was created + await new Promise((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policyCategories) => { + Onyx.disconnect(connection); + const newCategory = policyCategories?.[newCategoryName]; + expect(newCategory?.name).toBe(newCategoryName); + resolve(); + }, + }); + }); + + await mockFetch?.resume?.(); + await waitForBatchedUpdates(); + }); + }); }); diff --git a/tests/actions/PolicyTagTest.ts b/tests/actions/PolicyTagTest.ts index 73f4574cb5d7a..4dc41b27e95e8 100644 --- a/tests/actions/PolicyTagTest.ts +++ b/tests/actions/PolicyTagTest.ts @@ -2222,4 +2222,132 @@ describe('actions/Policy', () => { } }); }); + + describe('createPolicyTag with onboarding task completion', () => { + it('should create a new tag and complete SETUP_TAGS task', async () => { + const fakePolicy = createRandomPolicy(0); + const fakeTags = createRandomPolicyTags('TestTagList', 2); + const newTagName = 'New tag'; + + // Create a fake task report for SETUP_TAGS + const fakeTaskReportID = '123456'; + const fakeTaskReport = { + reportID: fakeTaskReportID, + type: CONST.REPORT.TYPE.TASK, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + }; + + mockFetch?.pause?.(); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakeTags); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeTaskReportID}`, fakeTaskReport); + + createPolicyTag(fakePolicy.id, newTagName, fakeTags, fakeTaskReport, undefined, false); + + await waitForBatchedUpdates(); + + // Verify the tag was created + await new Promise((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policyTags) => { + Onyx.disconnect(connection); + const tagList = Object.values(policyTags ?? {}).at(0); + const newTag = tagList?.tags?.[newTagName]; + expect(newTag?.name).toBe(newTagName); + resolve(); + }, + }); + }); + + await mockFetch?.resume?.(); + await waitForBatchedUpdates(); + }); + + it('should complete SETUP_CATEGORIES_AND_TAGS task when creating tag and categories already exist', async () => { + const fakePolicy = createRandomPolicy(0); + const fakeTags = createRandomPolicyTags('TestTagList', 2); + const newTagName = 'New tag with categories'; + + // Create a fake task report for SETUP_CATEGORIES_AND_TAGS + const fakeTaskReportID = '789012'; + const fakeTaskReport = { + reportID: fakeTaskReportID, + type: CONST.REPORT.TYPE.TASK, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + }; + + mockFetch?.pause?.(); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakeTags); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeTaskReportID}`, fakeTaskReport); + + createPolicyTag(fakePolicy.id, newTagName, fakeTags, undefined, fakeTaskReport, true); + + await waitForBatchedUpdates(); + + // Verify the tag was created + await new Promise((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policyTags) => { + Onyx.disconnect(connection); + const tagList = Object.values(policyTags ?? {}).at(0); + const newTag = tagList?.tags?.[newTagName]; + expect(newTag?.name).toBe(newTagName); + resolve(); + }, + }); + }); + + await mockFetch?.resume?.(); + await waitForBatchedUpdates(); + }); + + it('should NOT complete SETUP_CATEGORIES_AND_TAGS task when creating tag but no custom categories exist', async () => { + const fakePolicy = createRandomPolicy(0); + const fakeTags = createRandomPolicyTags('TestTagList', 2); + const newTagName = 'New tag without categories'; + + // Create a fake task report for SETUP_CATEGORIES_AND_TAGS + const fakeTaskReportID = '345678'; + const fakeTaskReport = { + reportID: fakeTaskReportID, + type: CONST.REPORT.TYPE.TASK, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + }; + + mockFetch?.pause?.(); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakeTags); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeTaskReportID}`, fakeTaskReport); + + createPolicyTag(fakePolicy.id, newTagName, fakeTags, undefined, fakeTaskReport, false); + + await waitForBatchedUpdates(); + + // Verify the tag was created + await new Promise((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policyTags) => { + Onyx.disconnect(connection); + const tagList = Object.values(policyTags ?? {}).at(0); + const newTag = tagList?.tags?.[newTagName]; + expect(newTag?.name).toBe(newTagName); + resolve(); + }, + }); + }); + + await mockFetch?.resume?.(); + await waitForBatchedUpdates(); + }); + }); });