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] Individual expense rules #47409

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
82be201
Add IndividualExpenseRulesSection
WojtekBoman Aug 8, 2024
7078618
Merge branch 'rules/toggle' into rules/individual-expense-rules
WojtekBoman Aug 12, 2024
f973e79
Add links to categories and tags pages
WojtekBoman Aug 12, 2024
5cf7c48
Add individual expense rules rhp screens templates
WojtekBoman Aug 12, 2024
8e10e14
Merge branch 'rules/toggle' into rules/individual-expense-rules
WojtekBoman Aug 12, 2024
85e89df
Add types for individual expense rules number forms
WojtekBoman Aug 13, 2024
18cb8b6
Handle displaying amount form as text input
WojtekBoman Aug 13, 2024
c356109
Add types for individual expense rules actions
WojtekBoman Aug 13, 2024
c61f3ca
Handle setPolicyMaxExpenseAmountNoReceipt action
WojtekBoman Aug 13, 2024
57d8e9c
Handle setting billable mode
WojtekBoman Aug 19, 2024
d786f8f
Rename DEFAULT_MAX_EXPENSE_AMOUNT_NO_RECEIPT const to DISABLED_MAX_EX…
WojtekBoman Aug 19, 2024
fe1e10f
Handle setting max expense amount
WojtekBoman Aug 19, 2024
3e6f02e
Handle toggling eReceipts
WojtekBoman Aug 19, 2024
7f8c373
Cleanup rules forms
WojtekBoman Aug 19, 2024
d52c8be
Add suffixCharacter prop to BaseTextInput
WojtekBoman Aug 19, 2024
cdde66b
Add setPolicyMaxExpenseAge and cleanup rules actions
WojtekBoman Aug 19, 2024
d9aad75
Handle setting maxExpenseAge
WojtekBoman Aug 20, 2024
dc57a47
Handle link to Tags page on RulesBillableDefaultPage
WojtekBoman Aug 20, 2024
f62182d
Add maxLength to RulesMaxExpenseAgePage
WojtekBoman Aug 21, 2024
706eec6
Adjust paddings on Rules pages
WojtekBoman Aug 21, 2024
8af85ba
Cleanup rules actions
WojtekBoman Aug 21, 2024
60f743d
Remove unused imports
WojtekBoman Aug 21, 2024
70baaf1
Add missing docs for policy actions
WojtekBoman Aug 21, 2024
d58b903
Refactor onSubmit in RulesMaxExpenseAgePage
WojtekBoman Aug 21, 2024
e919113
Rename SetPolicyEReceiptsEnabled to SetWorkspaceEReceiptsEnabled
WojtekBoman Aug 21, 2024
b9b94b0
Add IndividualExpenseRulesSectionSubtitle
WojtekBoman Aug 21, 2024
6d7b7a1
Refactor destructuring route params on rules pages
WojtekBoman Aug 21, 2024
71b51f2
Add rules pages fixes
WojtekBoman Aug 21, 2024
b646d12
Add IndividualExpenseRulesMenuItems
WojtekBoman Aug 21, 2024
1fd5711
Merge branch 'main' into rules/individual-expense-rules
WojtekBoman Aug 23, 2024
a5fb07b
Remove submit button from RulesBillableDefaultPage
WojtekBoman Aug 23, 2024
12cf26b
Fix eReceipts accessibility label
WojtekBoman Aug 23, 2024
9ced023
Add es translations for Individual Expense Rules section
WojtekBoman Aug 23, 2024
fee02c9
Adjust AmountForm for es locale
WojtekBoman Aug 23, 2024
1739e7c
Wrap section items in OfflineWithFeedback
WojtekBoman Aug 23, 2024
897b590
Add rules draft forms to ONYXKEYS
WojtekBoman Aug 23, 2024
6804e6e
Fix AmountForm when is display as TextInput
WojtekBoman Aug 23, 2024
3750539
Fix individual expense rules es translations
WojtekBoman Aug 26, 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
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ const CONST = {
DEFAULT_ONYX_DUMP_FILE_NAME: 'onyx-state.txt',
DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL],
DISABLED_MAX_EXPENSE_VALUE: 10000000000,
POLICY_BILLABLE_MODES: {
BILLABLE: 'billable',
NON_BILLABLE: 'nonBillable',
},

// Note: Group and Self-DM excluded as these are not tied to a Workspace
WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT],
Expand Down Expand Up @@ -593,6 +597,7 @@ const CONST = {
CONCIERGE_ICON_URL: `${CLOUDFRONT_URL}/images/icons/concierge_2022.png`,
UPWORK_URL: 'https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22',
DEEP_DIVE_EXPENSIFY_CARD: 'https://community.expensify.com/discussion/4848/deep-dive-expensify-card-and-quickbooks-online-auto-reconciliation-how-it-works',
DEEP_DIVE_ERECEIPTS: 'https://community.expensify.com/discussion/5542/deep-dive-what-are-ereceipts/',
GITHUB_URL: 'https://github.com/Expensify/App',
TERMS_URL: `${USE_EXPENSIFY_URL}/terms`,
PRIVACY_URL: `${USE_EXPENSIFY_URL}/privacy`,
Expand Down
9 changes: 9 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,12 @@ const ONYXKEYS = {
SEARCH_ADVANCED_FILTERS_FORM_DRAFT: 'searchAdvancedFiltersFormDraft',
TEXT_PICKER_MODAL_FORM: 'textPickerModalForm',
TEXT_PICKER_MODAL_FORM_DRAFT: 'textPickerModalFormDraft',
RULES_REQUIRED_RECEIPT_AMOUNT_FORM: 'rulesRequiredReceiptAmountForm',
RULES_REQUIRED_RECEIPT_AMOUNT_FORM_DRAFT: 'rulesRequiredReceiptAmountFormDraft',
RULES_MAX_EXPENSE_AMOUNT_FORM: 'rulesMaxExpenseAmountForm',
RULES_MAX_EXPENSE_AMOUNT_FORM_DRAFT: 'rulesMaxExpenseAmountFormDraft',
RULES_MAX_EXPENSE_AGE_FORM: 'rulesMaxExpenseAgeForm',
RULES_MAX_EXPENSE_AGE_FORM_DRAFT: 'rulesMaxExpenseAgeFormDraft',
},
} as const;

Expand Down Expand Up @@ -702,6 +708,9 @@ 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_REQUIRED_RECEIPT_AMOUNT_FORM]: FormTypes.RulesRequiredReceiptAmountForm;
[ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AMOUNT_FORM]: FormTypes.RulesMaxExpenseAmountForm;
[ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AGE_FORM]: FormTypes.RulesMaxExpenseAgeForm;
};

type OnyxFormDraftValuesMapping = {
Expand Down
16 changes: 16 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,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_RECEIPT_REQUIRED_AMOUNT: {
route: 'settings/workspaces/:policyID/rules/receipt-required-amount',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/receipt-required-amount` as const,
},
RULES_MAX_EXPENSE_AMOUNT: {
route: 'settings/workspaces/:policyID/rules/max-expense-amount',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/max-expense-amount` as const,
},
RULES_MAX_EXPENSE_AGE: {
route: 'settings/workspaces/:policyID/rules/max-expense-age',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/max-expense-age` as const,
},
RULES_BILLABLE_DEFAULT: {
route: 'settings/workspaces/:policyID/rules/billable',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/billable` as const,
},
// Referral program promotion
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
Expand Down
4 changes: 4 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,10 @@ const SCREENS = {
DISTANCE_RATE_TAX_RATE_EDIT: 'Distance_Rate_Tax_Rate_Edit',
UPGRADE: 'Workspace_Upgrade',
RULES: 'Policy_Rules',
RULES_RECEIPT_REQUIRED_AMOUNT: 'Rules_Receipt_Required_Amount',
RULES_MAX_EXPENSE_AMOUNT: 'Rules_Max_Expense_Amount',
RULES_MAX_EXPENSE_AGE: 'Rules_Max_Expense_Age',
RULES_BILLABLE_DEFAULT: 'Rules_Billable_Default',
},

EDIT_REQUEST: {
Expand Down
69 changes: 67 additions & 2 deletions src/components/AmountForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Browser from '@libs/Browser';
Expand All @@ -12,6 +12,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import CONST from '@src/CONST';
import BigNumberPad from './BigNumberPad';
import FormHelpMessage from './FormHelpMessage';
import TextInput from './TextInput';
import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused';
import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types';
import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol';
Expand Down Expand Up @@ -41,6 +42,10 @@ type AmountFormProps = {

/** Custom max amount length. It defaults to CONST.IOU.AMOUNT_MAX_LENGTH */
amountMaxLength?: number;

label?: string;

displayAsTextInput?: boolean;
Comment on lines +46 to +48
Copy link
Contributor

Choose a reason for hiding this comment

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

I'll add descriptions for both in my PR

} & Pick<TextInputWithCurrencySymbolProps, 'hideCurrencySymbol' | 'extraSymbol'> &
Pick<BaseTextInputProps, 'autoFocus'>;

Expand All @@ -57,7 +62,19 @@ const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView';
const NUM_PAD_VIEW_ID = 'numPadView';

function AmountForm(
{value: amount, currency = CONST.CURRENCY.USD, extraDecimals = 0, amountMaxLength, errorText, onInputChange, onCurrencyButtonPress, isCurrencyPressable = true, ...rest}: AmountFormProps,
{
value: amount,
currency = CONST.CURRENCY.USD,
extraDecimals = 0,
amountMaxLength,
errorText,
onInputChange,
onCurrencyButtonPress,
displayAsTextInput = false,
isCurrencyPressable = true,
label,
...rest
}: AmountFormProps,
forwardedRef: ForwardedRef<BaseTextInputRef>,
) {
const styles = useThemeStyles();
Expand Down Expand Up @@ -124,6 +141,29 @@ function AmountForm(
[amountMaxLength, currentAmount, decimals, onInputChange, selection],
);

/**
* Set a new amount value properly formatted
*
* @param text - Changed text from user input
*/
const setFormattedAmount = (text: 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(text);
const replacedCommasAmount = MoneyRequestUtils.replaceCommasWithPeriod(newAmountWithoutSpaces);
const withLeadingZero = MoneyRequestUtils.addLeadingZero(replacedCommasAmount);

if (!MoneyRequestUtils.validateAmount(withLeadingZero, decimals, amountMaxLength)) {
setSelection((prevSelection) => ({...prevSelection}));
return;
}

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

// Modifies the amount to match the decimals for changed currency.
useEffect(() => {
// If the changed currency supports decimals, we can return
Expand Down Expand Up @@ -195,6 +235,31 @@ function AmountForm(
const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit);
const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();

if (displayAsTextInput) {
return (
<TextInput
label={label}
value={formattedAmount}
onChangeText={setFormattedAmount}
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;
}}
prefixCharacter={currency}
prefixStyle={styles.colorMuted}
keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD}
inputMode={CONST.INPUT_MODE.DECIMAL}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
/>
);
}

return (
<>
<View
Expand Down
24 changes: 20 additions & 4 deletions src/components/TextInput/BaseTextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,14 @@ function BaseTextInput(
shouldInterceptSwipe = false,
autoCorrect = true,
prefixCharacter = '',
suffixCharacter = '',
inputID,
isMarkdownEnabled = false,
shouldShowClearButton = false,
prefixContainerStyle = [],
prefixStyle = [],
suffixContainerStyle = [],
suffixStyle = [],
contentWidth,
...inputProps
}: BaseTextInputProps,
Expand All @@ -83,7 +86,7 @@ function BaseTextInput(
// Disabling this line for saftiness as nullish coalescing works only if value is undefined or null
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const initialValue = value || defaultValue || '';
const initialActiveLabel = !!forceActiveLabel || initialValue.length > 0 || !!prefixCharacter;
const initialActiveLabel = !!forceActiveLabel || initialValue.length > 0 || !!prefixCharacter || !!suffixCharacter;

const [isFocused, setIsFocused] = useState(false);
const [passwordHidden, setPasswordHidden] = useState(inputProps.secureTextEntry);
Expand Down Expand Up @@ -142,13 +145,13 @@ function BaseTextInput(
const deactivateLabel = useCallback(() => {
const newValue = value ?? '';

if (!!forceActiveLabel || newValue.length !== 0 || prefixCharacter) {
if (!!forceActiveLabel || newValue.length !== 0 || prefixCharacter || suffixCharacter) {
return;
}

animateLabel(styleConst.INACTIVE_LABEL_TRANSLATE_Y, styleConst.INACTIVE_LABEL_SCALE);
isLabelActive.current = false;
}, [animateLabel, forceActiveLabel, prefixCharacter, value]);
}, [animateLabel, forceActiveLabel, prefixCharacter, suffixCharacter, value]);

const onFocus = (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
inputProps.onFocus?.(event);
Expand Down Expand Up @@ -245,7 +248,7 @@ function BaseTextInput(
// Disabling this line for safeness as nullish coalescing works only if the value is undefined or null, and errorText can be an empty string
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const inputHelpText = errorText || hint;
const newPlaceholder = !!prefixCharacter || isFocused || !hasLabel || (hasLabel && forceActiveLabel) ? placeholder : undefined;
const newPlaceholder = !!prefixCharacter || !!suffixCharacter || isFocused || !hasLabel || (hasLabel && forceActiveLabel) ? placeholder : undefined;
const newTextInputContainerStyles: StyleProp<ViewStyle> = StyleSheet.flatten([
styles.textInputContainer,
textInputContainerStyles,
Expand Down Expand Up @@ -276,6 +279,7 @@ function BaseTextInput(
}, [inputStyle]);

const inputPaddingLeft = !!prefixCharacter && StyleUtils.getPaddingLeft(StyleUtils.getCharacterPadding(prefixCharacter) + styles.pl1.paddingLeft);
const inputPaddingRight = !!suffixCharacter && StyleUtils.getPaddingRight(StyleUtils.getCharacterPadding(suffixCharacter) + styles.pr1.paddingRight);

return (
<>
Expand Down Expand Up @@ -366,6 +370,7 @@ function BaseTextInput(
inputStyle,
(!hasLabel || isMultiline) && styles.pv0,
inputPaddingLeft,
inputPaddingRight,
inputProps.secureTextEntry && styles.secureInput,

// Explicitly remove `lineHeight` from single line inputs so that long text doesn't disappear
Expand Down Expand Up @@ -402,6 +407,17 @@ function BaseTextInput(
defaultValue={defaultValue}
markdownStyle={markdownStyle}
/>
{!!suffixCharacter && (
Copy link
Contributor

Choose a reason for hiding this comment

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

We missed adding suffix handling to native (#54363), so we added it in #54549

<View style={[styles.textInputSuffixWrapper, suffixContainerStyle]}>
<Text
tabIndex={-1}
style={[styles.textInputSuffix, !hasLabel && styles.pv0, styles.pointerEventsNone, suffixStyle]}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{suffixCharacter}
</Text>
</View>
)}
{isFocused && !isReadOnly && shouldShowClearButton && !!value && <TextInputClearButton onPressButton={() => setValue('')} />}
{inputProps.isLoading && (
<ActivityIndicator
Expand Down
9 changes: 9 additions & 0 deletions src/components/TextInput/BaseTextInput/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type CustomBaseTextInputProps = {
/** Prefix character */
prefixCharacter?: string;

/** Suffix character */
suffixCharacter?: string;

/** Whether autoCorrect functionality should enable */
autoCorrect?: boolean;

Expand Down Expand Up @@ -121,6 +124,12 @@ type CustomBaseTextInputProps = {
/** Style for the prefix container */
prefixContainerStyle?: StyleProp<ViewStyle>;

/** Style for the suffix */
suffixStyle?: StyleProp<TextStyle>;

/** Style for the suffix container */
suffixContainerStyle?: StyleProp<ViewStyle>;

/** The width of inner content */
contentWidth?: number;
};
Expand Down
18 changes: 18 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ export default {
filterLogs: 'Filter Logs',
network: 'Network',
reportID: 'Report ID',
days: 'days',
},
location: {
useCurrent: 'Use current location',
Expand Down Expand Up @@ -3599,6 +3600,23 @@ export default {
individualExpenseRules: {
title: 'Expenses',
subtitle: 'Set spend controls and defaults for individual expenses. You can also create rules for',
receiptRequiredAmount: 'Receipt required amount',
receiptRequiredAmountDescription: 'Require receipts when spend exceeds this amount, unless overridden by a category rule.',
maxExpenseAmount: 'Max expense amount',
maxExpenseAmountDescription: 'Flag spend that exceeds this amount, unless overridden by a category rule.',
maxAge: 'Max age',
maxExpenseAge: 'Max expense age',
maxExpenseAgeDescription: 'Flag spend older than a specific number of days.',
maxExpenseAgeDays: (age: number) => `${age} ${Str.pluralize('day', 'days', age)}`,
billableDefault: 'Billable default',
billableDefaultDescription: 'Choose whether cash and credit card expenses should be billable by default. Billable expenses are enabled or disabled in',
billable: 'Billable',
billableDescription: 'Expenses are most often re-billed to clients',
nonBillable: 'Non-billable',
nonBillableDescription: 'Expenses are occasionally re-billed to clients',
eReceipts: 'eReceipts',
eReceiptsHint: 'eReceipts are auto-created',
eReceiptsHintLink: 'for most USD credit transactions',
},
expenseReportRules: {
title: 'Expense reports',
Expand Down
18 changes: 18 additions & 0 deletions src/languages/es.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pecanoro @marcaaron
The same request as for the previous PR :) Please review the translations

Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ export default {
filterLogs: 'Registros de filtrado',
network: 'La red',
reportID: 'ID del informe',
days: 'días',
},
connectionComplete: {
title: 'Conexión completa',
Expand Down Expand Up @@ -3648,6 +3649,23 @@ export default {
individualExpenseRules: {
title: 'Gastos',
subtitle: 'Establece controles y valores predeterminados para gastos individuales. También puedes crear reglas para',
receiptRequiredAmount: 'Cantidad requerida para los recibos',
receiptRequiredAmountDescription: 'Exige recibos cuando los gastos superen este importe, a menos que lo anule una regla de categoría.',
maxExpenseAmount: 'Importe máximo del gasto',
maxExpenseAmountDescription: 'Marca los gastos que superen este importe, a menos que una regla de categoría lo anule.',
maxAge: 'Antigüedad máxima',
maxExpenseAge: 'Antigüedad máxima de los gastos',
maxExpenseAgeDescription: 'Marca los gastos de más de un número determinado de días.',
maxExpenseAgeDays: (age: number) => `${age} ${Str.pluralize('día', 'días', age)}`,
billableDefault: 'Valor predeterminado facturable',
billableDefaultDescription: 'Elige si los gastos en efectivo y con tarjeta de crédito deben ser facturables por defecto. Los gastos facturables se activan o desactivan en',
billable: 'Facturable',
billableDescription: 'Los gastos se vuelven a facturar a los clientes en la mayoría de los casos',
nonBillable: 'No facturable',
nonBillableDescription: 'Los gastos se vuelven a facturar a los clientes en ocasiones',
eReceipts: 'Recibos electrónicos',
eReceiptsHint: 'Los recibos electrónicos se crean automáticamente',
eReceiptsHintLink: 'para la mayoría de las transacciones en USD',
},
expenseReportRules: {
title: 'Informes de gastos',
Expand Down
12 changes: 12 additions & 0 deletions src/libs/API/parameters/SetPolicyBillableMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type SetPolicyBillableMode = {
defaultBillable: boolean;
/**
* Stringified JSON object with type of following structure:
* disabledFields: {
* defaultBillable: boolean;
* };
*/
disabledFields: string;
};

export default SetPolicyBillableMode;
6 changes: 6 additions & 0 deletions src/libs/API/parameters/SetPolicyExpenseMaxAge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type SetPolicyExpenseMaxAge = {
policyID: string;
maxExpenseAge: number;
};

export default SetPolicyExpenseMaxAge;
6 changes: 6 additions & 0 deletions src/libs/API/parameters/SetPolicyExpenseMaxAmount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type SetPolicyExpenseMaxAmount = {
policyID: string;
maxExpenseAmount: number;
};

export default SetPolicyExpenseMaxAmount;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type SetPolicyExpenseMaxAmountNoReceipt = {
policyID: string;
maxExpenseAmountNoReceipt: number;
};

export default SetPolicyExpenseMaxAmountNoReceipt;
5 changes: 5 additions & 0 deletions src/libs/API/parameters/SetWorkspaceEReceiptsEnabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type SetWorkspaceEReceiptsEnabled = {
eReceipts: boolean;
};

export default SetWorkspaceEReceiptsEnabled;
Loading
Loading