fix(preferences): currencies preferences.

This commit is contained in:
a.bouhuolia
2021-03-24 13:09:56 +02:00
parent 542110fdf3
commit ac5e88f558
16 changed files with 346 additions and 285 deletions

View File

@@ -2,7 +2,9 @@ import React from 'react';
import { Classes, FormGroup, InputGroup } from '@blueprintjs/core'; import { Classes, FormGroup, InputGroup } from '@blueprintjs/core';
import { FastField } from 'formik'; import { FastField } from 'formik';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useCurrencyFormContext } from './CurrencyFormProvider'; import { useCurrencyFormContext } from './CurrencyFormProvider';
import { ErrorMessage, FieldRequiredHint, ListSelect } from 'components'; import { ErrorMessage, FieldRequiredHint, ListSelect } from 'components';
@@ -25,7 +27,7 @@ export default function CurrencyFormFields() {
field: { value }, field: { value },
meta: { error, touched }, meta: { error, touched },
}) => ( }) => (
<FormGroup label={'Currency code'}> <FormGroup label={'Currency code'} className={classNames(CLASSES.FILL, 'form-group--type')}>
<ListSelect <ListSelect
items={currenciesOptions} items={currenciesOptions}
selectedItemProp={'currency_code'} selectedItemProp={'currency_code'}
@@ -38,6 +40,7 @@ export default function CurrencyFormFields() {
setFieldValue('currency_sign', currency.symbol); setFieldValue('currency_sign', currency.symbol);
}} }}
disabled={isEditMode} disabled={isEditMode}
popoverProps={{ minimal: true }}
/> />
</FormGroup> </FormGroup>
)} )}

View File

@@ -30,7 +30,7 @@ function CurrencyFormDialog({
isOpen={isOpen} isOpen={isOpen}
autoFocus={true} autoFocus={true}
canEscapeKeyClose={true} canEscapeKeyClose={true}
style={{ width: '450px' }} style={{ width: '400px' }}
> >
<DialogSuspense> <DialogSuspense>
<CurrencyFormDialogContent <CurrencyFormDialogContent

View File

@@ -11,11 +11,13 @@ import {
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { AccountsSelectList, FieldRequiredHint } from 'components'; import { AccountsSelectList, FieldRequiredHint } from 'components';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
import { handleStringChange, inputIntent } from 'utils'; import { handleStringChange, inputIntent } from 'utils';
import { useAccountantFormContext } from './AccountantFormProvider'; import { useAccountantFormContext } from './AccountantFormProvider';
/**
* Accountant form.
*/
export default function AccountantForm() { export default function AccountantForm() {
const history = useHistory(); const history = useHistory();

View File

@@ -1,9 +1,8 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import classNames from 'classnames';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { CLASSES } from 'common/classes';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@@ -50,6 +49,7 @@ function AccountantFormPage({
const handleFormSubmit = (values, { setSubmitting }) => { const handleFormSubmit = (values, { setSubmitting }) => {
const options = transformToOptions(values); const options = transformToOptions(values);
setSubmitting(true); setSubmitting(true);
const onSuccess = () => { const onSuccess = () => {
AppToaster.show({ AppToaster.show({
@@ -68,21 +68,12 @@ function AccountantFormPage({
}; };
return ( return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<div className={classNames(CLASSES.CARD)}>
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={AccountantSchema} validationSchema={AccountantSchema}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
component={AccountantForm} component={AccountantForm}
/> />
</div>
</div>
); );
} }

View File

@@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import { LoadingIndicator } from 'components'; import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useAccounts, useSaveSettings, useSettings } from 'hooks/query'; import { useAccounts, useSaveSettings, useSettings } from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
const AccountantFormContext = React.createContext(); const AccountantFormContext = React.createContext();
@@ -24,10 +26,23 @@ function AccountantFormProvider({ ...props }) {
saveSettingMutate, saveSettingMutate,
}; };
const isLoading = isSettingsLoading || isAccountsLoading;
return ( return (
<LoadingIndicator loading={isSettingsLoading || isAccountsLoading}> <div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<div className={classNames(CLASSES.CARD)}>
{isLoading ? (
<PreferencesPageLoader />
) : (
<AccountantFormContext.Provider value={provider} {...props} /> <AccountantFormContext.Provider value={provider} {...props} />
</LoadingIndicator> )}
</div>
</div>
); );
} }

View File

@@ -59,7 +59,9 @@ export default function PreferencesGeneralForm({}) {
inline={true} inline={true}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
className={classNames('form-group--select-list', CLASSES.FILL)} className={classNames('form-group--select-list', CLASSES.FILL)}
helperText={'For reporting, you can specify any month as the start of your financial year (also called your financial reporting year or accounting year).'} helperText={
'For reporting, you can specify any month as the start of your financial year (also called your financial reporting year or accounting year).'
}
> >
<DateInput <DateInput
{...momentFormatter('MMMM Do YYYY')} {...momentFormatter('MMMM Do YYYY')}
@@ -122,7 +124,9 @@ export default function PreferencesGeneralForm({}) {
className={classNames('form-group--base-currency', CLASSES.FILL)} className={classNames('form-group--base-currency', CLASSES.FILL)}
inline={true} inline={true}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
helperText={"You can't change the base currency as there are transactions recorded in your organization."} helperText={
"You can't change the base currency as there are transactions recorded in your organization."
}
> >
<ListSelect <ListSelect
items={currencies} items={currencies}

View File

@@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { mapKeys, snakeCase } from 'lodash'; import { mapKeys, snakeCase } from 'lodash';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import classNames from 'classnames';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { CLASSES } from 'common/classes';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import GeneralForm from './GeneralForm'; import GeneralForm from './GeneralForm';
@@ -46,6 +44,7 @@ function GeneralFormPage({
const options = optionsMapToArray(values).map((option) => { const options = optionsMapToArray(values).map((option) => {
return { key: option.key, ...option, group: 'organization' }; return { key: option.key, ...option, group: 'organization' };
}); });
// Handle request success.
const onSuccess = (response) => { const onSuccess = (response) => {
AppToaster.show({ AppToaster.show({
message: 'The general preferences has been saved.', message: 'The general preferences has been saved.',
@@ -54,6 +53,7 @@ function GeneralFormPage({
setSubmitting(false); setSubmitting(false);
resetForm(); resetForm();
}; };
// Handle request error.
const onError = (errors) => { const onError = (errors) => {
setSubmitting(false); setSubmitting(false);
}; };
@@ -61,21 +61,12 @@ function GeneralFormPage({
}; };
return ( return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_GENERAL,
)}
>
<div className={classNames(CLASSES.CARD)}>
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={PreferencesGeneralSchema} validationSchema={PreferencesGeneralSchema}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
component={GeneralForm} component={GeneralForm}
/> />
</div>
</div>
); );
} }

View File

@@ -1,6 +1,8 @@
import React, { createContext } from 'react'; import React, { createContext } from 'react';
import { LoadingIndicator } from 'components'; import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useSaveSettings, useSettings } from 'hooks/query'; import { useSaveSettings, useSettings } from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
const GeneralFormContext = createContext(); const GeneralFormContext = createContext();
@@ -8,8 +10,8 @@ const GeneralFormContext = createContext();
* General form provider. * General form provider.
*/ */
function GeneralFormProvider({ ...props }) { function GeneralFormProvider({ ...props }) {
//Fetches Organization Settings. // Fetches Organization Settings.
const { isFetching: isSettingsLoading } = useSettings(); const { isLoading: isSettingsLoading } = useSettings();
// Save Organization Settings. // Save Organization Settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings(); const { mutateAsync: saveSettingMutate } = useSaveSettings();
@@ -20,10 +22,23 @@ function GeneralFormProvider({ ...props }) {
saveSettingMutate, saveSettingMutate,
}; };
const loading = isSettingsLoading;
return ( return (
<LoadingIndicator loading={isSettingsLoading} spinnerSize={28}> <div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_GENERAL,
)}
>
<div className={classNames(CLASSES.CARD)}>
{loading ? (
<PreferencesPageLoader />
) : (
<GeneralFormContext.Provider value={provider} {...props} /> <GeneralFormContext.Provider value={provider} {...props} />
</LoadingIndicator> )}
</div>
</div>
); );
} }

View File

@@ -1,9 +1,9 @@
import * as Yup from 'yup'; import * as Yup from 'yup';
const Schema = Yup.object().shape({ const Schema = Yup.object().shape({
sell_account: Yup.number().nullable().required(), sell_account: Yup.number().nullable(),
cost_account: Yup.number().nullable().required(), cost_account: Yup.number().nullable(),
inventory_account: Yup.number().nullable().required(), inventory_account: Yup.number().nullable(),
}); });
export const ItemPreferencesSchema = Schema; export const ItemPreferencesSchema = Schema;

View File

@@ -1,8 +1,6 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { ItemPreferencesSchema } from './Item.schema'; import { ItemPreferencesSchema } from './Item.schema';
@@ -19,24 +17,28 @@ import 'style/pages/Preferences/Accounting.scss';
function ItemFormPage({ function ItemFormPage({
// #withSettings // #withSettings
itemsSettings, itemsSettings,
//# withDashboardActions
// #withDashboardActions
changePreferencesPageTitle, changePreferencesPageTitle,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { saveSettingMutate } = useItemFormContext(); const { saveSettingMutate } = useItemFormContext();
const initialValues = { const initialValues = {
sell_account: '',
cost_account: '',
inventory_account: '',
...transformGeneralSettings(itemsSettings), ...transformGeneralSettings(itemsSettings),
}; };
useEffect(() => { useEffect(() => {
changePreferencesPageTitle(formatMessage({ id: 'items' })); changePreferencesPageTitle(formatMessage({ id: 'items' }));
}, [changePreferencesPageTitle]); }, [formatMessage, changePreferencesPageTitle]);
// Handle form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => { const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const options = optionsMapToArray(values).map((option) => { const options = optionsMapToArray(values)
return { key: option.key, ...option, group: 'items' }; .map((option) => ({ ...option, group: 'items' }));
});
const onSuccess = () => { const onSuccess = () => {
AppToaster.show({ AppToaster.show({
@@ -55,21 +57,12 @@ function ItemFormPage({
}; };
return ( return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<div className={classNames(CLASSES.CARD)}>
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={ItemPreferencesSchema} validationSchema={ItemPreferencesSchema}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
component={ItemForm} component={ItemForm}
/> />
</div>
</div>
); );
} }

View File

@@ -1,7 +1,9 @@
import React, { useContext, createContext } from 'react'; import React, { useContext, createContext } from 'react';
import { LoadingIndicator } from 'components'; import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useAccounts, useSaveSettings } from 'hooks/query'; import { useAccounts, useSaveSettings } from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
const ItemFormContext = createContext(); const ItemFormContext = createContext();
@@ -22,10 +24,23 @@ function ItemFormProvider({ ...props }) {
saveSettingMutate, saveSettingMutate,
}; };
const isLoading = isAccountsLoading;
return ( return (
<LoadingIndicator loading={isAccountsLoading}> <div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<div className={classNames(CLASSES.CARD)}>
{isLoading ? (
<PreferencesPageLoader />
) : (
<ItemFormContext.Provider value={provider} {...props} /> <ItemFormContext.Provider value={provider} {...props} />
</LoadingIndicator> )}
</div>
</div>
); );
} }

View File

@@ -0,0 +1,23 @@
import React from 'react';
import ContentLoader from 'react-content-loader';
export default function PreferencesPageLoader(props) {
return (
<ContentLoader
speed={2}
width={400}
height={250}
viewBox="0 0 400 250"
backgroundColor="#f3f3f3"
foregroundColor="#e6e6e6"
{...props}
>
<rect x="0" y="82" rx="2" ry="2" width="200" height="20" />
<rect x="0" y="112" rx="2" ry="2" width="385" height="30" />
<rect x="0" y="0" rx="2" ry="2" width="200" height="20" />
<rect x="-1" y="30" rx="2" ry="2" width="385" height="30" />
<rect x="0" y="164" rx="2" ry="2" width="200" height="20" />
<rect x="0" y="194" rx="2" ry="2" width="385" height="30" />
</ContentLoader>
);
}

View File

@@ -1,5 +1,7 @@
.dialog--currency-form { .dialog--currency-form {
.bp3-dialog-body { .bp3-dialog-body {
margin-bottom: 30px;
.bp3-form-group.bp3-inline { .bp3-form-group.bp3-inline {
.bp3-label { .bp3-label {
min-width: 140px; min-width: 140px;

View File

@@ -41,7 +41,7 @@ export default class SettingsController extends BaseController {
return [ return [
body('options').isArray({ min: 1 }), body('options').isArray({ min: 1 }),
body('options.*.key').exists().trim().isLength({ min: 1 }), body('options.*.key').exists().trim().isLength({ min: 1 }),
body('options.*.value').exists().trim().isLength({ min: 1 }), body('options.*.value').exists().trim(),
body('options.*.group').exists().trim().isLength({ min: 1 }), body('options.*.group').exists().trim().isLength({ min: 1 }),
]; ];
} }

View File

@@ -5,6 +5,12 @@ exports.up = (knex) => {
const tenancyService = Container.get(TenancyService); const tenancyService = Container.get(TenancyService);
const settings = tenancyService.settings(knex.userParams.tenantId); const settings = tenancyService.settings(knex.userParams.tenantId);
// Orgnization settings.
settings.set({ group: 'organization', key: 'accounting_basis', value: 'accural' });
// Accounts settings.
settings.set({ group: 'accounts', key: 'account_code_unique', value: true });
// Manual journals settings. // Manual journals settings.
settings.set({ group: 'manual_journals', key: 'next_number', value: '00001' }); settings.set({ group: 'manual_journals', key: 'next_number', value: '00001' });
settings.set({ group: 'manual_journals', key: 'auto_increment', value: true }); settings.set({ group: 'manual_journals', key: 'auto_increment', value: true });

View File

@@ -261,6 +261,7 @@ export default class CurrenciesService implements ICurrenciesService {
await Currency.query().insert({ await Currency.query().insert({
currency_code: currencyMeta.code, currency_code: currencyMeta.code,
currency_name: currencyMeta.name, currency_name: currencyMeta.name,
currency_sign: currencyMeta.symbol,
}); });
} }
} }