Skip to content

Commit

Permalink
Merge pull request #38035 from MrMuzyk/feat/policy-add-distance-rate
Browse files Browse the repository at this point in the history
feat: policy add distance rate
  • Loading branch information
luacmartins authored Mar 15, 2024
2 parents 9e1ca5c + e096ce3 commit 1f232f2
Show file tree
Hide file tree
Showing 17 changed files with 285 additions and 38 deletions.
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ const ONYXKEYS = {
WORKSPACE_DESCRIPTION_FORM_DRAFT: 'workspaceDescriptionFormDraft',
WORKSPACE_RATE_AND_UNIT_FORM: 'workspaceRateAndUnitForm',
WORKSPACE_RATE_AND_UNIT_FORM_DRAFT: 'workspaceRateAndUnitFormDraft',
POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm',
POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft',
CLOSE_ACCOUNT_FORM: 'closeAccount',
CLOSE_ACCOUNT_FORM_DRAFT: 'closeAccountDraft',
PROFILE_SETTINGS_FORM: 'profileSettingsForm',
Expand Down Expand Up @@ -456,6 +458,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm;
[ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm;
[ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm;
[ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM]: FormTypes.PolicyCreateDistanceRateForm;
};

type OnyxFormDraftValuesMapping = {
Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,10 @@ const ROUTES = {
route: 'workspace/:policyID/distance-rates',
getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const,
},
WORKSPACE_CREATE_DISTANCE_RATE: {
route: 'workspace/:policyID/distance-rates/new',
getRoute: (policyID: string) => `workspace/${policyID}/distance-rates/new` as const,
},
// Referral program promotion
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ const SCREENS = {
MEMBER_DETAILS: 'Workspace_Member_Details',
MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection',
DISTANCE_RATES: 'Distance_Rates',
CREATE_DISTANCE_RATE: 'Create_Distance_Rate',
},

EDIT_REQUEST: {
Expand Down
7 changes: 7 additions & 0 deletions src/libs/API/parameters/CreatePolicyDistanceRateParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type CreatePolicyDistanceRateParams = {
policyID: string;
customUnitID: string;
customUnitRate: string;
};

export default CreatePolicyDistanceRateParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,5 @@ export type {default as OpenPolicyWorkflowsPageParams} from './OpenPolicyWorkflo
export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams';
export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams';
export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMoreFeaturesPageParams';
export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams';
export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ const WRITE_COMMANDS = {
JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink',
ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest',
DECLINE_JOIN_REQUEST: 'DeclineJoinRequest',
CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate',
} as const;

type WriteCommand = ValueOf<typeof WRITE_COMMANDS>;
Expand Down Expand Up @@ -348,6 +349,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams;
[WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams;
[WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams;
};

const READ_COMMANDS = {
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORY_EDIT]: () => require('../../../pages/workspace/categories/EditCategoryPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('@pages/workspace/distanceRates/CreateDistanceRatePage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAGS_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAG_CREATE]: () => require('../../../pages/workspace/tags/WorkspaceCreateTagPage').default as React.ComponentType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
],
[SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT, SCREENS.WORKSPACE.TAG_CREATE],
[SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS, SCREENS.WORKSPACE.CATEGORY_EDIT],
[SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE],
};

export default FULL_SCREEN_TO_RHP_MAPPING;
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
categoryName: (categoryName: string) => decodeURI(categoryName),
},
},
[SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: {
path: ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.route,
},
[SCREENS.WORKSPACE.TAGS_SETTINGS]: {
path: ROUTES.WORKSPACE_TAGS_SETTINGS.route,
},
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ type SettingsNavigatorParamList = {
accountID: string;
backTo: Routes;
};
[SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: {
policyID: string;
};
[SCREENS.GET_ASSISTANCE]: {
backTo: Routes;
};
Expand Down
25 changes: 25 additions & 0 deletions src/libs/PolicyDistanceRatesUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import type ONYXKEYS from '@src/ONYXKEYS';
import * as CurrencyUtils from './CurrencyUtils';
import getPermittedDecimalSeparator from './getPermittedDecimalSeparator';
import * as MoneyRequestUtils from './MoneyRequestUtils';
import * as NumberUtils from './NumberUtils';

type RateValueForm = typeof ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM | typeof ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM;

function validateRateValue(values: FormOnyxValues<RateValueForm>, currency: string, toLocaleDigit: (arg: string) => string): FormInputErrors<RateValueForm> {
const errors: FormInputErrors<RateValueForm> = {};
const parsedRate = MoneyRequestUtils.replaceAllDigits(values.rate, toLocaleDigit);
const decimalSeparator = toLocaleDigit('.');

// Allow one more decimal place for accuracy
const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(currency) + 1}})?$`, 'i');
if (!rateValueRegex.test(parsedRate) || parsedRate === '') {
errors.rate = 'workspace.reimburse.invalidRateError';
} else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) {
errors.rate = 'workspace.reimburse.lowRateError';
}
return errors;
}

export default validateRateValue;
81 changes: 81 additions & 0 deletions src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {ValueOf} from 'type-fest';
import * as API from '@libs/API';
import type {
AddMembersToWorkspaceParams,
CreatePolicyDistanceRateParams,
CreateWorkspaceFromIOUPaymentParams,
CreateWorkspaceParams,
DeleteMembersFromWorkspaceParams,
Expand Down Expand Up @@ -3620,6 +3621,83 @@ function openPolicyMoreFeaturesPage(policyID: string) {
API.read(READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE, params);
}

function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
customUnits: {
[customUnitID]: {
rates: {
[customUnitRate.customUnitRateID ?? '']: {
...customUnitRate,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
},
},
},
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
customUnits: {
[customUnitID]: {
rates: {
[customUnitRate.customUnitRateID ?? '']: {
pendingAction: null,
},
},
},
},
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
customUnits: {
[customUnitID]: {
rates: {
[customUnitRate.customUnitRateID ?? '']: {
errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'),
},
},
},
},
},
},
];

const params: CreatePolicyDistanceRateParams = {
policyID,
customUnitID,
customUnitRate: JSON.stringify(customUnitRate),
};

API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, successData, failureData});
}

function clearCreateDistanceRateItemAndError(policyID: string, customUnitID: string, customUnitRateIDToClear: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
customUnits: {
[customUnitID]: {
rates: {
[customUnitRateIDToClear]: null,
},
},
},
});
}

export {
removeMembers,
updateWorkspaceMembersRole,
Expand Down Expand Up @@ -3692,6 +3770,9 @@ export {
enablePolicyWorkflows,
openPolicyDistanceRatesPage,
openPolicyMoreFeaturesPage,
generateCustomUnitID,
createPolicyDistanceRate,
clearCreateDistanceRateItemAndError,
createPolicyTag,
clearPolicyTagErrors,
clearWorkspaceReimbursementErrors,
Expand Down
101 changes: 101 additions & 0 deletions src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import AmountForm from '@components/AmountForm';
import FormProvider from '@components/Form/FormProvider';
import InputWrapperWithRef from '@components/Form/InputWrapper';
import type {FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import validateRateValue from '@libs/PolicyDistanceRatesUtils';
import Navigation from '@navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import {createPolicyDistanceRate, generateCustomUnitID} from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/PolicyCreateDistanceRateForm';
import type {Rate} from '@src/types/onyx/Policy';
import type Policy from '@src/types/onyx/Policy';

type CreateDistanceRatePageOnyxProps = {
policy: OnyxEntry<Policy>;
};

type CreateDistanceRatePageProps = CreateDistanceRatePageOnyxProps & StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.CREATE_DISTANCE_RATE>;

function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) {
const styles = useThemeStyles();
const {translate, toLocaleDigit} = useLocalize();
const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD;
const customUnits = policy?.customUnits ?? {};
const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? '';
const customUnitRateID = generateCustomUnitID();
const {inputCallbackRef} = useAutoFocusInput();

const validate = useCallback(
(values: FormOnyxValues<typeof ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM>) => validateRateValue(values, currency, toLocaleDigit),
[currency, toLocaleDigit],
);

const submit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM>) => {
const newRate: Rate = {
currency,
name: CONST.CUSTOM_UNITS.DEFAULT_RATE,
rate: parseFloat(values.rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET,
customUnitRateID,
enabled: true,
};

createPolicyDistanceRate(route.params.policyID, customUnitID, newRate);
Navigation.goBack();
};

return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={CreateDistanceRatePage.displayName}
>
<HeaderWithBackButton title={translate('workspace.distanceRates.addRate')} />
<FormProvider
formID={ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM}
submitButtonText={translate('common.save')}
onSubmit={submit}
validate={validate}
enabledWhenOffline
style={[styles.flexGrow1]}
shouldHideFixErrorsAlert
submitFlexEnabled={false}
submitButtonStyles={[styles.mh5, styles.mt0]}
>
<InputWrapperWithRef
InputComponent={AmountForm}
inputID={INPUT_IDS.RATE}
extraDecimals={1}
isCurrencyPressable={false}
currency={currency}
ref={inputCallbackRef}
/>
</FormProvider>
</ScreenWrapper>
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
}

CreateDistanceRatePage.displayName = 'CreateDistanceRatePage';

export default withOnyx<CreateDistanceRatePageProps, CreateDistanceRatePageOnyxProps>({
policy: {
key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`,
},
})(CreateDistanceRatePage);
Loading

0 comments on commit 1f232f2

Please sign in to comment.