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

[OldDot Rules Migration] Expense report rules #47468

Merged
Merged
Show file tree
Hide file tree
Changes from 75 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
3081420
Add ExpenseReportRulesSection
WojtekBoman Aug 8, 2024
3623d4a
menu setup
BrtqKr Aug 12, 2024
3e622bf
replace items with ToggleSettingOptionRow
BrtqKr Aug 12, 2024
e99bf88
add custom report names section content wip
BrtqKr Aug 12, 2024
bff54c3
add translations
BrtqKr Aug 12, 2024
94a0db2
setup rhp nav
BrtqKr Aug 12, 2024
b07708a
add bullet points, wire up form wip
BrtqKr Aug 13, 2024
2e82822
wire up custom name form
BrtqKr Aug 13, 2024
0d6aa6f
cleanup custom names, prevent from changing names wip
BrtqKr Aug 13, 2024
4d15101
cleanup custom names
BrtqKr Aug 13, 2024
66382f0
wire up prevent member created title
BrtqKr Aug 13, 2024
6be513d
wire up prevent self approval
BrtqKr Aug 13, 2024
2169b17
add remaining routes
BrtqKr Aug 14, 2024
132ccbb
auto approve reports under amount page wip
BrtqKr Aug 14, 2024
285cf5e
wire up auto approval
BrtqKr Aug 14, 2024
5797fb1
add percentage form
BrtqKr Aug 14, 2024
2e75abd
wire up auto approval audit rate
BrtqKr Aug 14, 2024
45b46b4
wire up auto pay
BrtqKr Aug 14, 2024
72c38a4
rule lock conditions wip
BrtqKr Aug 14, 2024
6f59541
wire up enablePolicyAutoReimbursementLimit
BrtqKr Aug 19, 2024
daa7c9c
wire up enablePolicyAutoReimbursementLimit, validate wip
BrtqKr Aug 19, 2024
b188339
wire up enableAutoApprovalOptions
BrtqKr Aug 19, 2024
36160d3
wire up auto approve reports under
BrtqKr Aug 19, 2024
7e72524
wire up random report audit
BrtqKr Aug 20, 2024
21549c6
wire up enable custom report names
BrtqKr Aug 20, 2024
8c8f270
wire up custom report name
BrtqKr Aug 20, 2024
269dedd
wire up custom report names
BrtqKr Aug 20, 2024
34a5c4b
wire up prevent self approval
BrtqKr Aug 20, 2024
9bc374a
add fallback subtitle, fix translations
BrtqKr Aug 20, 2024
b881703
add custom name validation
BrtqKr Aug 20, 2024
215a252
add validation, fix amount form validation
BrtqKr Aug 20, 2024
b1df169
add fallbacks
BrtqKr Aug 20, 2024
b5c7486
add side effects
BrtqKr Aug 20, 2024
0edff08
cleanup jsdoc
BrtqKr Aug 20, 2024
edc14fe
move actions to policy
BrtqKr Aug 20, 2024
67e9e83
Merge remote-tracking branch 'origin/main' into brtqkr/expense-report…
BrtqKr Aug 20, 2024
b89de0a
fix audit rate command
BrtqKr Aug 20, 2024
00b8175
add keys
BrtqKr Aug 20, 2024
05f9529
cleanup
BrtqKr Aug 21, 2024
34eb99a
review fixes
BrtqKr Aug 21, 2024
2af9b37
fix translations
BrtqKr Aug 21, 2024
d23fb35
add jsdoc
BrtqKr Aug 21, 2024
1f327fa
Merge remote-tracking branch 'origin/main' into brtqkr/expense-report…
BrtqKr Aug 23, 2024
7b73789
fix command
BrtqKr Aug 23, 2024
db3f8c7
add pending fields
BrtqKr Aug 23, 2024
4b044a0
Merge remote-tracking branch 'origin/main' into brtqkr/expense-report…
BrtqKr Aug 23, 2024
1ef024a
fix types
BrtqKr Aug 23, 2024
ea72e31
review fixes
BrtqKr Aug 26, 2024
6d06f5d
cleanup
BrtqKr Aug 26, 2024
7b8a235
cleanup
BrtqKr Aug 26, 2024
058773f
Merge remote-tracking branch 'origin/main' into brtqkr/expense-report…
BrtqKr Aug 28, 2024
6bcb2e4
cleanup
BrtqKr Aug 28, 2024
9fc1685
cleanup
BrtqKr Aug 28, 2024
79c5ae6
cleanup
BrtqKr Aug 28, 2024
c48575b
add offline with feedback
BrtqKr Aug 29, 2024
a22d5fb
add fallback values
BrtqKr Aug 29, 2024
db46fb0
cleanup
BrtqKr Aug 29, 2024
b9eb9ae
lint fix
BrtqKr Aug 29, 2024
721a0ec
Merge remote-tracking branch 'origin/main' into brtqkr/expense-report…
BrtqKr Sep 2, 2024
b229a26
remove wrapper
BrtqKr Sep 2, 2024
816deeb
make pending fields more direct
BrtqKr Sep 2, 2024
001041f
change permissions
BrtqKr Sep 2, 2024
927461f
block access when unavailable
BrtqKr Sep 2, 2024
8a46e91
cleanup
BrtqKr Sep 2, 2024
6cd2540
remove tmp permissions
BrtqKr Sep 2, 2024
ed21e12
modify pending fields
BrtqKr Sep 2, 2024
550939e
block api calls when the value is identical
BrtqKr Sep 2, 2024
5d81f1b
add fallback
BrtqKr Sep 2, 2024
1a008ba
add fallbacks
BrtqKr Sep 2, 2024
e7fdedf
move conditions higher
BrtqKr Sep 2, 2024
e921c73
policyID fallback changes, move conditon to utils function
BrtqKr Sep 2, 2024
3f0b321
cleanup
BrtqKr Sep 2, 2024
ab1a8cb
cleanup
BrtqKr Sep 2, 2024
020b1e3
reivew changes
BrtqKr Sep 3, 2024
c71279f
add conditional pending actions
BrtqKr Sep 3, 2024
efe9fe6
change error message
BrtqKr Sep 4, 2024
2d55ee0
Merge remote-tracking branch 'origin/main' into brtqkr/expense-report…
BrtqKr Sep 5, 2024
0ef8fb2
swap condition for workflow approvals unavailable
BrtqKr Sep 5, 2024
3f55bd3
run prettier
BrtqKr Sep 5, 2024
442d734
update translations
BrtqKr Sep 6, 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
9 changes: 8 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ const CONST = {
SAGE_INTACCT_INSTRUCTIONS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct',
HOW_TO_CONNECT_TO_SAGE_INTACCT: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct#how-to-connect-to-sage-intacct',
PRICING: `https://www.expensify.com/pricing`,

CUSTOM_REPORT_NAME_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates',
// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
OLDDOT_URLS: {
Expand Down Expand Up @@ -2042,11 +2042,18 @@ const CONST = {
// Often referred to as "collect" workspaces
TEAM: 'team',
},
FIELD_LIST_TITLE_FIELD_ID: 'text_title',
DEFAULT_REPORT_NAME_PATTERN: '{report:type} {report:startdate}',
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
ROLE: {
ADMIN: 'admin',
AUDITOR: 'auditor',
USER: 'user',
},
AUTO_REIMBURSEMENT_MAX_LIMIT_CENTS: 2000000,
AUTO_REIMBURSEMENT_DEFAULT_LIMIT_CENTS: 10000,
AUTO_APPROVE_REPORTS_UNDER_DEFAULT_CENTS: 10000,
RANDOM_AUDIT_DEFAULT_PERCENTAGE: 5,

AUTO_REPORTING_FREQUENCIES: {
INSTANT: 'instant',
IMMEDIATE: 'immediate',
Expand Down
12 changes: 12 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,14 @@ const ONYXKEYS = {
SEARCH_ADVANCED_FILTERS_FORM_DRAFT: 'searchAdvancedFiltersFormDraft',
TEXT_PICKER_MODAL_FORM: 'textPickerModalForm',
TEXT_PICKER_MODAL_FORM_DRAFT: 'textPickerModalFormDraft',
RULES_CUSTOM_NAME_MODAL_FORM: 'rulesCustomNameModalForm',
RULES_CUSTOM_NAME_MODAL_FORM_DRAFT: 'rulesCustomNameModalFormDraft',
RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM: 'rulesAutoApproveReportsUnderModalForm',
RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM_DRAFT: 'rulesAutoApproveReportsUnderModalFormDraft',
RULES_RANDOM_REPORT_AUDIT_MODAL_FORM: 'rulesRandomReportAuditModalForm',
RULES_RANDOM_REPORT_AUDIT_MODAL_FORM_DRAFT: 'rulesRandomReportAuditModalFormDraft',
RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM: 'rulesAutoPayReportsUnderModalForm',
RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM_DRAFT: 'rulesAutoPayReportsUnderModalFormDraft',
RULES_REQUIRED_RECEIPT_AMOUNT_FORM: 'rulesRequiredReceiptAmountForm',
RULES_REQUIRED_RECEIPT_AMOUNT_FORM_DRAFT: 'rulesRequiredReceiptAmountFormDraft',
RULES_MAX_EXPENSE_AMOUNT_FORM: 'rulesMaxExpenseAmountForm',
Expand Down Expand Up @@ -717,6 +725,10 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.SAGE_INTACCT_DIMENSION_TYPE_FORM]: FormTypes.SageIntacctDimensionForm;
[ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM]: FormTypes.SearchAdvancedFiltersForm;
[ONYXKEYS.FORMS.TEXT_PICKER_MODAL_FORM]: FormTypes.TextPickerModalForm;
[ONYXKEYS.FORMS.RULES_CUSTOM_NAME_MODAL_FORM]: FormTypes.RulesCustomNameModalForm;
[ONYXKEYS.FORMS.RULES_AUTO_APPROVE_REPORTS_UNDER_MODAL_FORM]: FormTypes.RulesAutoApproveReportsUnderModalForm;
[ONYXKEYS.FORMS.RULES_RANDOM_REPORT_AUDIT_MODAL_FORM]: FormTypes.RulesRandomReportAuditModalForm;
[ONYXKEYS.FORMS.RULES_AUTO_PAY_REPORTS_UNDER_MODAL_FORM]: FormTypes.RulesAutoPayReportsUnderModalForm;
[ONYXKEYS.FORMS.RULES_REQUIRED_RECEIPT_AMOUNT_FORM]: FormTypes.RulesRequiredReceiptAmountForm;
[ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AMOUNT_FORM]: FormTypes.RulesMaxExpenseAmountForm;
[ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AGE_FORM]: FormTypes.RulesMaxExpenseAgeForm;
Expand Down
16 changes: 16 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,22 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/distance-rates/:rateID/tax-rate/edit',
getRoute: (policyID: string, rateID: string) => `settings/workspaces/${policyID}/distance-rates/${rateID}/tax-rate/edit` as const,
},
RULES_CUSTOM_NAME: {
route: 'settings/workspaces/:policyID/rules/name',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/name` as const,
},
RULES_AUTO_APPROVE_REPORTS_UNDER: {
route: 'settings/workspaces/:policyID/rules/auto-approve',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/auto-approve` as const,
},
RULES_RANDOM_REPORT_AUDIT: {
route: 'settings/workspaces/:policyID/rules/audit',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/audit` as const,
},
RULES_AUTO_PAY_REPORTS_UNDER: {
route: 'settings/workspaces/:policyID/rules/auto-pay',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/auto-pay` as const,
},
RULES_RECEIPT_REQUIRED_AMOUNT: {
route: 'settings/workspaces/:policyID/rules/receipt-required-amount',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/receipt-required-amount` as const,
Expand Down
4 changes: 4 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@ const SCREENS = {
DISTANCE_RATE_TAX_RATE_EDIT: 'Distance_Rate_Tax_Rate_Edit',
UPGRADE: 'Workspace_Upgrade',
RULES: 'Policy_Rules',
RULES_CUSTOM_NAME: 'Rules_Custom_Name',
RULES_AUTO_APPROVE_REPORTS_UNDER: 'Rules_Auto_Approve_Reports_Under',
RULES_RANDOM_REPORT_AUDIT: 'Rules_Random_Report_Audit',
RULES_AUTO_PAY_REPORTS_UNDER: 'Rules_AutoPay_Reports_Under',
RULES_RECEIPT_REQUIRED_AMOUNT: 'Rules_Receipt_Required_Amount',
RULES_MAX_EXPENSE_AMOUNT: 'Rules_Max_Expense_Amount',
RULES_MAX_EXPENSE_AGE: 'Rules_Max_Expense_Age',
Expand Down
9 changes: 5 additions & 4 deletions src/components/AmountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ type AmountFormProps = {
/** Custom max amount length. It defaults to CONST.IOU.AMOUNT_MAX_LENGTH */
amountMaxLength?: number;

/** Custom label for the TextInput */
label?: string;

/** Whether the form should use a standard TextInput as a base */
displayAsTextInput?: boolean;
} & Pick<TextInputWithCurrencySymbolProps, 'hideCurrencySymbol' | 'extraSymbol'> &
Pick<BaseTextInputProps, 'autoFocus'>;
Pick<BaseTextInputProps, 'autoFocus' | 'hasError'>;

/**
* Returns the new selection object based on the updated amount's length
Expand All @@ -67,7 +69,6 @@ function AmountForm(
currency = CONST.CURRENCY.USD,
extraDecimals = 0,
amountMaxLength,
errorText,
onInputChange,
onCurrencyButtonPress,
displayAsTextInput = false,
Expand Down Expand Up @@ -296,11 +297,11 @@ function AmountForm(
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
/>
{!!errorText && (
{!!rest.errorText && (
<FormHelpMessage
style={[styles.pAbsolute, styles.b0, canUseTouchScreen ? styles.mb0 : styles.mb3, styles.ph5, styles.w100]}
isError
message={errorText}
message={rest.errorText}
Comment on lines -299 to +304
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this, on the other hand, was necessary, because the error handling was broken in general

/>
)}
</View>
Expand Down
52 changes: 52 additions & 0 deletions src/components/BulletList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type {ReactNode} from 'react';
import React from 'react';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import Text from './Text';

type BulletListItem = string;

type BulletListProps = {
/** List of items for the list. Each item will be rendered as a sepearte point. */
items: BulletListItem[];

/** Header section of the list */
header: string | ReactNode;
};

function BulletList({items, header}: BulletListProps) {
const styles = useThemeStyles();

const baseTextStyles = [styles.mutedNormalTextLabel];

const renderBulletListHeader = () => {
if (typeof header === 'string') {
return <Text style={baseTextStyles}>{header}</Text>;
}
return header;
};

const renderBulletPoint = (item: string) => {
return (
<Text
style={baseTextStyles}
key={item}
>
<Text style={[styles.ph2, baseTextStyles]}>•</Text>
{item}
</Text>
);
};

return (
<View style={[styles.w100, styles.mt2]}>
{renderBulletListHeader()}
<View>{items.map((item) => renderBulletPoint(item))}</View>
</View>
);
}

BulletList.displayName = 'BulletList';

export type {BulletListProps};
export default BulletList;
2 changes: 2 additions & 0 deletions src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type CountrySelector from '@components/CountrySelector';
import type CurrencySelector from '@components/CurrencySelector';
import type DatePicker from '@components/DatePicker';
import type EmojiPickerButtonDropdown from '@components/EmojiPicker/EmojiPickerButtonDropdown';
import type PercentageForm from '@components/PercentageForm';
import type Picker from '@components/Picker';
import type RadioButtons from '@components/RadioButtons';
import type RoomNameInput from '@components/RoomNameInput';
Expand Down Expand Up @@ -42,6 +43,7 @@ type ValidInputs =
| typeof CountrySelector
| typeof CurrencySelector
| typeof AmountForm
| typeof PercentageForm
| typeof BusinessTypePicker
| typeof DimensionTypeSelector
| typeof StateSelector
Expand Down
102 changes: 102 additions & 0 deletions src/components/PercentageForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useMemo, useRef, useState} from 'react';
import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import CONST from '@src/CONST';
import TextInput from './TextInput';
import type {BaseTextInputRef} from './TextInput/BaseTextInput/types';

type PercentageFormProps = {
/** Amount supplied by the FormProvider */
value?: string;

/** Error to display at the bottom of the component */
errorText?: string;

/** Callback to update the amount in the FormProvider */
onInputChange?: (value: string) => void;

/** Custom label for the TextInput */
label?: string;
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* Returns the new selection object based on the updated amount's length
*/
const getNewSelection = (oldSelection: {start: number; end: number}, prevLength: number, newLength: number) => {
const cursorPosition = oldSelection.end + (newLength - prevLength);
return {start: cursorPosition, end: cursorPosition};
};

function PercentageForm({value: amount, errorText, onInputChange, label, ...rest}: PercentageFormProps, forwardedRef: ForwardedRef<BaseTextInputRef>) {
const {toLocaleDigit, numberFormat} = useLocalize();

const textInput = useRef<BaseTextInputRef | null>(null);

const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]);

const [selection, setSelection] = useState({
start: currentAmount.length,
end: currentAmount.length,
});

const forwardDeletePressedRef = useRef(false);

/**
* Sets the selection and the amount accordingly to the value passed to the input
* @param newAmount - Changed amount from user input
*/
const setNewAmount = useCallback(
(newAmount: string) => {
// Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value
// More info: https://github.com/Expensify/App/issues/16974
const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount);
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
// Use a shallow copy of selection to trigger setSelection
// More info: https://github.com/Expensify/App/issues/16385
if (!MoneyRequestUtils.validatePercentage(newAmountWithoutSpaces)) {
setSelection((prevSelection) => ({...prevSelection}));
return;
}

const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces);
const isForwardDelete = currentAmount.length > strippedAmount.length && forwardDeletePressedRef.current;
setSelection(getNewSelection(selection, isForwardDelete ? strippedAmount.length : currentAmount.length, strippedAmount.length));
onInputChange?.(strippedAmount);
},
[currentAmount, onInputChange, selection],
);

const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit);

return (
<TextInput
label={label}
value={formattedAmount}
onChangeText={setNewAmount}
placeholder={numberFormat(0)}
ref={(ref: BaseTextInputRef) => {
if (typeof forwardedRef === 'function') {
forwardedRef(ref);
} else if (forwardedRef && 'current' in forwardedRef) {
// eslint-disable-next-line no-param-reassign
forwardedRef.current = ref;
}
textInput.current = ref;
}}
selection={selection}
onSelectionChange={(e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
setSelection(e.nativeEvent.selection);
}}
suffixCharacter="%"
keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
/>
);
}

PercentageForm.displayName = 'PercentageForm';

export default forwardRef(PercentageForm);
export type {PercentageFormProps};
Loading
Loading