diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 56006d599d6f..d78fbc27189d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -704,6 +704,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/categories/:categoryName/edit', getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/edit` as const, }, + WORKSPACE_CATEGORY_PAYROLL_CODE: { + route: 'settings/workspaces/:policyID/categories/:categoryName/payroll-code', + getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/payroll-code` as const, + }, WORKSPACE_CATEGORY_GL_CODE: { route: 'settings/workspaces/:policyID/categories/:categoryName/gl-code', getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/gl-code` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 4790ac3c6a32..6e5a9b272ebf 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -385,6 +385,7 @@ const SCREENS = { NAME: 'Workspace_Profile_Name', CATEGORY_CREATE: 'Category_Create', CATEGORY_EDIT: 'Category_Edit', + CATEGORY_PAYROLL_CODE: 'Category_Payroll_Code', CATEGORY_GL_CODE: 'Category_GL_Code', CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', diff --git a/src/languages/en.ts b/src/languages/en.ts index 3170d0ebb777..944bdf02e774 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2670,6 +2670,8 @@ export default { existingCategoryError: 'A category with this name already exists.', invalidCategoryName: 'Invalid category name.', importedFromAccountingSoftware: 'The categories below are imported from your', + payrollCode: 'Payroll code', + updatePayrollCodeFailureMessage: 'An error occurred while updating the payroll code, please try again.', glCode: 'GL code', updateGLCodeFailureMessage: 'An error occurred while updating the GL code, please try again.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 75fb44a5c394..5c169b1cce38 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2718,6 +2718,8 @@ export default { existingCategoryError: 'Ya existe una categoría con este nombre.', invalidCategoryName: 'Lo nombre de la categoría es invalido.', importedFromAccountingSoftware: 'Categorías importadas desde', + payrollCode: 'Código de nómina', + updatePayrollCodeFailureMessage: 'Se produjo un error al actualizar el código de nómina, por favor intente nuevamente.', glCode: 'Código GL', updateGLCodeFailureMessage: 'Se produjo un error al actualizar el código GL. Inténtelo nuevamente.', }, diff --git a/src/libs/API/parameters/UpdatePolicyCategoryPayrollCodeParams.ts b/src/libs/API/parameters/UpdatePolicyCategoryPayrollCodeParams.ts new file mode 100644 index 000000000000..8751832f7cad --- /dev/null +++ b/src/libs/API/parameters/UpdatePolicyCategoryPayrollCodeParams.ts @@ -0,0 +1,7 @@ +type UpdatePolicyCategoryPayrollCodeParams = { + policyID: string; + categoryName: string; + payrollCode: string; +}; + +export default UpdatePolicyCategoryPayrollCodeParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 6510be4940d4..7826ed55e5d1 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -169,6 +169,7 @@ export type {default as CreateWorkspaceCategoriesParams} from './CreateWorkspace export type {default as RenameWorkspaceCategoriesParams} from './RenameWorkspaceCategoriesParams'; export type {default as SetWorkspaceRequiresCategoryParams} from './SetWorkspaceRequiresCategoryParams'; export type {default as DeleteWorkspaceCategoriesParams} from './DeleteWorkspaceCategoriesParams'; +export type {default as UpdatePolicyCategoryPayrollCodeParams} from './UpdatePolicyCategoryPayrollCodeParams'; export type {default as UpdatePolicyCategoryGLCodeParams} from './UpdatePolicyCategoryGLCodeParams'; export type {default as SetWorkspaceAutoReportingFrequencyParams} from './SetWorkspaceAutoReportingFrequencyParams'; export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './SetWorkspaceAutoReportingMonthlyOffsetParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 341f7c5033e6..1554b6164c98 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -130,6 +130,7 @@ const WRITE_COMMANDS = { CREATE_POLICY_TAG: 'CreatePolicyTag', RENAME_POLICY_TAG: 'RenamePolicyTag', SET_WORKSPACE_REQUIRES_CATEGORY: 'SetWorkspaceRequiresCategory', + UPDATE_POLICY_CATEGORY_PAYROLL_CODE: 'UpdatePolicyCategoryPayrollCode', UPDATE_POLICY_CATEGORY_GL_CODE: 'UpdatePolicyCategoryGLCode', DELETE_WORKSPACE_CATEGORIES: 'DeleteWorkspaceCategories', DELETE_POLICY_REPORT_FIELD: 'DeletePolicyReportField', @@ -426,6 +427,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY]: Parameters.RenameWorkspaceCategoriesParams; [WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams; [WRITE_COMMANDS.DELETE_WORKSPACE_CATEGORIES]: Parameters.DeleteWorkspaceCategoriesParams; + [WRITE_COMMANDS.UPDATE_POLICY_CATEGORY_PAYROLL_CODE]: Parameters.UpdatePolicyCategoryPayrollCodeParams; [WRITE_COMMANDS.UPDATE_POLICY_CATEGORY_GL_CODE]: Parameters.UpdatePolicyCategoryGLCodeParams; [WRITE_COMMANDS.DELETE_POLICY_REPORT_FIELD]: Parameters.DeletePolicyReportField; [WRITE_COMMANDS.SET_POLICY_REQUIRES_TAG]: Parameters.SetPolicyRequiresTag; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index ce4028b87ea8..37fc01dce82e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -237,6 +237,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/members/WorkspaceOwnerChangeErrorPage').default, [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../../pages/workspace/categories/CreateCategoryPage').default, [SCREENS.WORKSPACE.CATEGORY_EDIT]: () => require('../../../../pages/workspace/categories/EditCategoryPage').default, + [SCREENS.WORKSPACE.CATEGORY_PAYROLL_CODE]: () => require('../../../../pages/workspace/categories/CategoryPayrollCodePage').default, [SCREENS.WORKSPACE.CATEGORY_GL_CODE]: () => require('../../../../pages/workspace/categories/CategoryGLCodePage').default, [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../../pages/workspace/distanceRates/CreateDistanceRatePage').default, [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage').default, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index 0833d6c9f195..fec821dc8fb9 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -140,6 +140,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.CATEGORIES_SETTINGS, SCREENS.WORKSPACE.CATEGORY_EDIT, SCREENS.WORKSPACE.CATEGORY_GL_CODE, + SCREENS.WORKSPACE.CATEGORY_PAYROLL_CODE, ], [SCREENS.WORKSPACE.DISTANCE_RATES]: [ SCREENS.WORKSPACE.CREATE_DISTANCE_RATE, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 9d5f1355b74c..8d60a48b40e5 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -520,6 +520,12 @@ const config: LinkingOptions['config'] = { categoryName: (categoryName: string) => decodeURIComponent(categoryName), }, }, + [SCREENS.WORKSPACE.CATEGORY_PAYROLL_CODE]: { + path: ROUTES.WORKSPACE_CATEGORY_PAYROLL_CODE.route, + parse: { + categoryName: (categoryName: string) => decodeURIComponent(categoryName), + }, + }, [SCREENS.WORKSPACE.CATEGORY_GL_CODE]: { path: ROUTES.WORKSPACE_CATEGORY_GL_CODE.route, parse: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index a9bc53b01a2c..a71620db5cd5 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -207,6 +207,10 @@ type SettingsNavigatorParamList = { categoryName: string; backTo?: Routes; }; + [SCREENS.WORKSPACE.CATEGORY_PAYROLL_CODE]: { + policyID: string; + categoryName: string; + }; [SCREENS.WORKSPACE.CATEGORY_GL_CODE]: { policyID: string; categoryName: string; diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 3af4749569c9..ae8ed41f22c5 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -331,7 +331,75 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string API.write(WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY, parameters, onyxData); } -function updatePolicyCategoryGLCode(policyID: string, categoryName: string, glCode: string) { +function setPolicyCategoryPayrollCode(policyID: string, categoryName: string, payrollCode: string) { + const policyCategoryToUpdate = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]?.[categoryName] ?? {}; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + [categoryName]: { + ...policyCategoryToUpdate, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + pendingFields: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Payroll Code': CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Payroll Code': payrollCode, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + [categoryName]: { + ...policyCategoryToUpdate, + pendingAction: null, + pendingFields: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Payroll Code': null, + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Payroll Code': payrollCode, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + [categoryName]: { + ...policyCategoryToUpdate, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.updatePayrollCodeFailureMessage'), + pendingAction: null, + pendingFields: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Payroll Code': null, + }, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + categoryName, + payrollCode, + }; + + API.write(WRITE_COMMANDS.UPDATE_POLICY_CATEGORY_PAYROLL_CODE, parameters, onyxData); +} + +function setPolicyCategoryGLCode(policyID: string, categoryName: string, glCode: string) { const policyCategoryToUpdate = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]?.[categoryName] ?? {}; const onyxData: OnyxData = { @@ -684,9 +752,10 @@ export { buildOptimisticPolicyRecentlyUsedCategories, setWorkspaceCategoryEnabled, setWorkspaceRequiresCategory, + setPolicyCategoryPayrollCode, createPolicyCategory, renamePolicyCategory, - updatePolicyCategoryGLCode, + setPolicyCategoryGLCode, clearCategoryErrors, enablePolicyCategories, setPolicyDistanceRatesDefaultCategory, diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index 770358335680..351bd36fbb34 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -23,8 +23,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; const ACCESS_VARIANTS = { [CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => PolicyUtils.isPaidGroupPolicy(policy), - [CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry, login: string) => PolicyUtils.isPolicyAdmin(policy, login), [CONST.POLICY.ACCESS_VARIANTS.CONTROL]: (policy: OnyxEntry) => PolicyUtils.isControlPolicy(policy), + [CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry, login: string) => PolicyUtils.isPolicyAdmin(policy, login), [CONST.IOU.ACCESS_VARIANTS.CREATE]: ( policy: OnyxEntry, login: string, diff --git a/src/pages/workspace/categories/CategoryGLCodePage.tsx b/src/pages/workspace/categories/CategoryGLCodePage.tsx index 050ced2e497c..2a56184b7ba8 100644 --- a/src/pages/workspace/categories/CategoryGLCodePage.tsx +++ b/src/pages/workspace/categories/CategoryGLCodePage.tsx @@ -36,7 +36,7 @@ function CategoryGLCodePage({route}: EditCategoryPageProps) { (values: FormOnyxValues) => { const newGLCode = values.glCode.trim(); if (newGLCode !== glCode) { - Category.updatePolicyCategoryGLCode(route.params.policyID, categoryName, newGLCode); + Category.setPolicyCategoryGLCode(route.params.policyID, categoryName, newGLCode); } Navigation.goBack(); }, diff --git a/src/pages/workspace/categories/CategoryPayrollCodePage.tsx b/src/pages/workspace/categories/CategoryPayrollCodePage.tsx new file mode 100644 index 000000000000..22143add373a --- /dev/null +++ b/src/pages/workspace/categories/CategoryPayrollCodePage.tsx @@ -0,0 +1,87 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as Category from '@userActions/Policy/Category'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceCategoryForm'; + +type EditCategoryPageProps = StackScreenProps; + +function CategoryPayrollCodePage({route}: EditCategoryPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const policyId = route.params.policyID ?? '-1'; + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`); + + const categoryName = route.params.categoryName; + const payrollCode = policyCategories?.[categoryName]?.['Payroll Code']; + const {inputCallbackRef} = useAutoFocusInput(); + + const editPayrollCode = useCallback( + (values: FormOnyxValues) => { + const newPayrollCode = values.payrollCode.trim(); + if (newPayrollCode !== payrollCode) { + Category.setPolicyCategoryPayrollCode(route.params.policyID, categoryName, newPayrollCode); + } + Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, route.params.categoryName)); + }, + [categoryName, payrollCode, route.params.categoryName, route.params.policyID], + ); + + return ( + + + Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, route.params.categoryName))} + /> + + + + + + ); +} + +CategoryPayrollCodePage.displayName = 'CategoryPayrollCodePage'; + +export default CategoryPayrollCodePage; diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx index 54450a1117cd..959347b621fb 100644 --- a/src/pages/workspace/categories/CategorySettingsPage.tsx +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -16,6 +16,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {isControlPolicy} from '@libs/PolicyUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; @@ -40,6 +41,7 @@ function CategorySettingsPage({route, policyCategories, navigation}: CategorySet const [deleteCategoryConfirmModalVisible, setDeleteCategoryConfirmModalVisible] = useState(false); const backTo = route.params?.backTo; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`); + const shouldDisablePayrollCode = !isControlPolicy(policy); const policyCategory = policyCategories?.[route.params.categoryName] ?? Object.values(policyCategories ?? {}).find((category) => category.previousCategoryName === route.params.categoryName); @@ -142,6 +144,15 @@ function CategorySettingsPage({route, policyCategories, navigation}: CategorySet shouldShowRightIcon /> + + Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_PAYROLL_CODE.getRoute(route.params.policyID, policyCategory.name))} + shouldShowRightIcon + disabled={shouldDisablePayrollCode} + /> + {!isThereAnyAccountingConnection && ( ; diff --git a/src/types/onyx/PolicyCategory.ts b/src/types/onyx/PolicyCategory.ts index c9191ae5b8d9..18a714208819 100644 --- a/src/types/onyx/PolicyCategory.ts +++ b/src/types/onyx/PolicyCategory.ts @@ -18,6 +18,10 @@ type PolicyCategory = OnyxCommon.OnyxValueWithOfflineFeedback<{ // eslint-disable-next-line @typescript-eslint/naming-convention 'GL Code'?: string; + /** Payroll code is used to keep track of taxes, deductions, and an employee’s earnings */ + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Payroll Code'?: string; + /** An ID for this category from an external accounting system */ externalID?: string;