Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: policy add distance rate #38035

Merged
merged 22 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c14fbf8
feat: PolicyNewDistanceRatePage
MrMuzyk Mar 7, 2024
b20f519
Merge branch 'feat/policy-distance-rates-page' of github.com:MrMuzyk/…
MrMuzyk Mar 7, 2024
729c746
Merge branch 'feat/policy-distance-rates-page' of github.com:MrMuzyk/…
MrMuzyk Mar 8, 2024
84115b4
Merge branch 'feat/policy-distance-rates-page' of github.com:MrMuzyk/…
MrMuzyk Mar 8, 2024
f7567c3
feat: adding delete
MrMuzyk Mar 8, 2024
c74c9fb
Merge branch 'main' of https://github.com/Expensify/App into feat/pol…
MrMuzyk Mar 11, 2024
d0d8d2c
feat: add
MrMuzyk Mar 11, 2024
d27b9fe
fix: ts error
MrMuzyk Mar 11, 2024
cfa9145
fix: scrap delete from this PR
MrMuzyk Mar 11, 2024
2b1baf7
fix: one line
MrMuzyk Mar 11, 2024
1b53b39
feat: add validation
MrMuzyk Mar 11, 2024
f24b378
Merge branch 'main' of https://github.com/Expensify/App into feat/pol…
MrMuzyk Mar 12, 2024
bf5ba1d
Merge branch 'main' of https://github.com/Expensify/App into feat/pol…
MrMuzyk Mar 13, 2024
f6df548
fix: cr fixes
MrMuzyk Mar 13, 2024
ba3f3b3
fix: error handling
MrMuzyk Mar 13, 2024
ddeabce
Merge branch 'main' of https://github.com/Expensify/App into feat/pol…
MrMuzyk Mar 13, 2024
94638fc
Merge branch 'main' of https://github.com/Expensify/App into feat/pol…
MrMuzyk Mar 13, 2024
0fd5258
fix: cr fixes
MrMuzyk Mar 14, 2024
d04d2a3
fix: last cr fixes
MrMuzyk Mar 15, 2024
9522399
Merge branch 'main' of https://github.com/Expensify/App into feat/pol…
MrMuzyk Mar 15, 2024
78bd350
fix: move validate to util
MrMuzyk Mar 15, 2024
e096ce3
fix: remove extra const
MrMuzyk Mar 15, 2024
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
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 @@ -177,4 +177,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 @@ -174,6 +174,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 @@ -346,6 +347,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
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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We miss to check the duplicated value and it caused this problem #51769

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 @@ -3621,6 +3622,83 @@ function openPolicyMoreFeaturesPage(policyID: string) {
API.read(READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE, params);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case missed when updating workspace currency distance rates are reset based on old logic which had one distance rate: #30156

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 @@ -3693,6 +3771,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
Loading