mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { ListSelect } from 'components';
|
import { ListSelect } from 'components';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
export default function AccountsTypesSelect({
|
export default function AccountsTypesSelect({
|
||||||
accountsTypes,
|
accountsTypes,
|
||||||
@@ -7,6 +9,7 @@ export default function AccountsTypesSelect({
|
|||||||
defaultSelectText = 'Select account type',
|
defaultSelectText = 'Select account type',
|
||||||
onTypeSelected,
|
onTypeSelected,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
popoverFill = false,
|
||||||
...restProps
|
...restProps
|
||||||
}) {
|
}) {
|
||||||
// Filters accounts types items.
|
// Filters accounts types items.
|
||||||
@@ -41,6 +44,9 @@ export default function AccountsTypesSelect({
|
|||||||
onItemSelect={handleItemSelected}
|
onItemSelect={handleItemSelected}
|
||||||
itemPredicate={filterAccountTypeItems}
|
itemPredicate={filterAccountTypeItems}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
className={classNames('form-group--select-list', {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const Schema = Yup.object().shape({
|
|||||||
.min(3)
|
.min(3)
|
||||||
.max(DATATYPES_LENGTH.STRING)
|
.max(DATATYPES_LENGTH.STRING)
|
||||||
.label(formatMessage({ id: 'account_name_' })),
|
.label(formatMessage({ id: 'account_name_' })),
|
||||||
code: Yup.string().digits().min(3).max(6),
|
code: Yup.string().nullable().min(3).max(6),
|
||||||
account_type_id: Yup.number()
|
account_type_id: Yup.number()
|
||||||
.nullable()
|
.nullable()
|
||||||
.required()
|
.required()
|
||||||
|
|||||||
@@ -1,49 +1,37 @@
|
|||||||
import React, { useCallback, useMemo, useEffect } from 'react';
|
import React, { useCallback, useMemo, useEffect } from 'react';
|
||||||
import {
|
import { Intent } from '@blueprintjs/core';
|
||||||
Button,
|
import { Formik } from 'formik';
|
||||||
Classes,
|
|
||||||
FormGroup,
|
|
||||||
InputGroup,
|
|
||||||
Intent,
|
|
||||||
TextArea,
|
|
||||||
Checkbox,
|
|
||||||
Position,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { useFormik } from 'formik';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { pick, omit } from 'lodash';
|
import { pick, omit } from 'lodash';
|
||||||
import { useQuery, queryCache } from 'react-query';
|
import { useQuery, queryCache } from 'react-query';
|
||||||
import classNames from 'classnames';
|
import { AppToaster, DialogContent } from 'components';
|
||||||
import Yup from 'services/yup';
|
|
||||||
import {
|
import AccountFormDialogFields from './AccountFormDialogFields';
|
||||||
If,
|
|
||||||
ErrorMessage,
|
|
||||||
AppToaster,
|
|
||||||
FieldRequiredHint,
|
|
||||||
Hint,
|
|
||||||
AccountsSelectList,
|
|
||||||
AccountsTypesSelect,
|
|
||||||
DialogContent,
|
|
||||||
} from 'components';
|
|
||||||
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
import withAccountDetail from 'containers/Accounts/withAccountDetail';
|
import withAccountDetail from 'containers/Accounts/withAccountDetail';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import {
|
import {
|
||||||
EditAccountFormSchema,
|
EditAccountFormSchema,
|
||||||
CreateAccountFormSchema,
|
CreateAccountFormSchema,
|
||||||
} from './AccountForm.schema';
|
} from './AccountForm.schema';
|
||||||
import { compose } from 'utils';
|
|
||||||
|
import { compose, transformToForm } from 'utils';
|
||||||
|
import { transformApiErrors, transformAccountToForm } from './utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
account_type_id: '',
|
||||||
|
parent_account_id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
description: '',
|
||||||
|
subaccount: false,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account form dialog content.
|
* Account form dialog content.
|
||||||
*/
|
*/
|
||||||
function AccountFormDialogContent({
|
function AccountFormDialogContent({
|
||||||
// #withAccounts
|
|
||||||
accountsTypes,
|
|
||||||
accounts,
|
|
||||||
|
|
||||||
// #withAccountDetail
|
// #withAccountDetail
|
||||||
account,
|
account,
|
||||||
|
|
||||||
@@ -57,132 +45,80 @@ function AccountFormDialogContent({
|
|||||||
closeDialog,
|
closeDialog,
|
||||||
|
|
||||||
// #ownProp
|
// #ownProp
|
||||||
|
dialogName,
|
||||||
accountId,
|
accountId,
|
||||||
action,
|
action,
|
||||||
dialogName,
|
|
||||||
parentAccountId,
|
parentAccountId,
|
||||||
accountTypeId,
|
accountTypeId,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const isNewMode = !accountId;
|
const isNewMode = !accountId;
|
||||||
|
|
||||||
|
// Form validation schema in create and edit mode.
|
||||||
const validationSchema = isNewMode
|
const validationSchema = isNewMode
|
||||||
? CreateAccountFormSchema
|
? CreateAccountFormSchema
|
||||||
: EditAccountFormSchema;
|
: EditAccountFormSchema;
|
||||||
|
|
||||||
const initialValues = useMemo(
|
// Callbacks handles form submit.
|
||||||
() => ({
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
account_type_id: '',
|
const form = omit(values, ['subaccount']);
|
||||||
name: '',
|
const toastAccountName = values.code
|
||||||
code: '',
|
? `${values.code} - ${values.name}`
|
||||||
description: '',
|
: values.name;
|
||||||
}),
|
|
||||||
[],
|
// Handle request success.
|
||||||
);
|
const handleSuccess = () => {
|
||||||
const transformApiErrors = (errors) => {
|
closeDialog(dialogName);
|
||||||
const fields = {};
|
queryCache.invalidateQueries('accounts-table');
|
||||||
if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) {
|
queryCache.invalidateQueries('accounts-list');
|
||||||
fields.code = formatMessage({ id: 'account_code_is_not_unique' });
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage(
|
||||||
|
{
|
||||||
|
id: isNewMode
|
||||||
|
? 'service_has_been_successful_created'
|
||||||
|
: 'service_has_been_successful_edited',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: toastAccountName,
|
||||||
|
service: formatMessage({ id: 'account' }),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Handle request error.
|
||||||
|
const handleError = (errors) => {
|
||||||
|
const errorsTransformed = transformApiErrors(errors);
|
||||||
|
setErrors({ ...errorsTransformed });
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
if (accountId) {
|
||||||
|
requestEditAccount(accountId, form)
|
||||||
|
.then(handleSuccess)
|
||||||
|
.catch(handleError);
|
||||||
|
} else {
|
||||||
|
requestSubmitAccount({ form }).then(handleSuccess).catch(handleError);
|
||||||
}
|
}
|
||||||
if (errors.find((e) => e.type === 'ACCOUNT.NAME.NOT.UNIQUE')) {
|
|
||||||
fields.name = formatMessage({ id: 'account_name_is_already_used' });
|
|
||||||
}
|
|
||||||
return fields;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Formik
|
// Form initial values in create and edit mode.
|
||||||
const {
|
const initialValues = {
|
||||||
errors,
|
...defaultInitialValues,
|
||||||
values,
|
/**
|
||||||
touched,
|
* We only care about the fields in the form. Previously unfilled optional
|
||||||
setFieldValue,
|
* values such as `notes` come back from the API as null, so remove those
|
||||||
resetForm,
|
* as well.
|
||||||
handleSubmit,
|
*/
|
||||||
isSubmitting,
|
...transformToForm(
|
||||||
getFieldProps,
|
transformAccountToForm(account, {
|
||||||
} = useFormik({
|
action,
|
||||||
enableReinitialize: true,
|
parentAccountId,
|
||||||
initialValues: {
|
accountTypeId,
|
||||||
...initialValues,
|
}),
|
||||||
...(accountId && pick(account, Object.keys(initialValues))),
|
defaultInitialValues,
|
||||||
},
|
),
|
||||||
validationSchema,
|
};
|
||||||
onSubmit: (values, { setSubmitting, setErrors }) => {
|
|
||||||
const form = omit(values, ['subaccount']);
|
|
||||||
const toastAccountName = values.code
|
|
||||||
? `${values.code} - ${values.name}`
|
|
||||||
: values.name;
|
|
||||||
|
|
||||||
const afterSubmit = () => {
|
|
||||||
closeDialog(dialogName);
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
queryCache.invalidateQueries('accounts-list');
|
|
||||||
};
|
|
||||||
const afterErrors = (errors) => {
|
|
||||||
const errorsTransformed = transformApiErrors(errors);
|
|
||||||
setErrors({ ...errorsTransformed });
|
|
||||||
setSubmitting(false);
|
|
||||||
};
|
|
||||||
if (accountId) {
|
|
||||||
requestEditAccount(accountId, form)
|
|
||||||
.then((response) => {
|
|
||||||
afterSubmit(response);
|
|
||||||
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage(
|
|
||||||
{ id: 'service_has_been_successful_edited' },
|
|
||||||
{
|
|
||||||
name: toastAccountName,
|
|
||||||
service: formatMessage({ id: 'account' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(afterErrors);
|
|
||||||
} else {
|
|
||||||
requestSubmitAccount({ form })
|
|
||||||
.then((response) => {
|
|
||||||
afterSubmit(response);
|
|
||||||
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage(
|
|
||||||
{ id: 'service_has_been_successful_created' },
|
|
||||||
{
|
|
||||||
name: toastAccountName,
|
|
||||||
service: formatMessage({ id: 'account' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
position: Position.BOTTOM,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(afterErrors);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (values.parent_account_id) {
|
|
||||||
setFieldValue('subaccount', true);
|
|
||||||
}
|
|
||||||
}, [values.parent_account_id]);
|
|
||||||
|
|
||||||
// Reset `parent account id` after change `account type`.
|
|
||||||
useEffect(() => {
|
|
||||||
setFieldValue('parent_account_id', null);
|
|
||||||
}, [values.account_type_id]);
|
|
||||||
|
|
||||||
// Filtered accounts based on the given account type.
|
|
||||||
const filteredAccounts = useMemo(
|
|
||||||
() =>
|
|
||||||
accounts.filter(
|
|
||||||
(account) =>
|
|
||||||
account.account_type_id === values.account_type_id ||
|
|
||||||
!values.account_type_id,
|
|
||||||
),
|
|
||||||
[accounts, values.account_type_id],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handles dialog close.
|
// Handles dialog close.
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
@@ -195,9 +131,9 @@ function AccountFormDialogContent({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Fetches accounts types.
|
// Fetches accounts types.
|
||||||
const fetchAccountsTypes = useQuery('accounts-types-list', async () => {
|
const fetchAccountsTypes = useQuery('accounts-types-list', () =>
|
||||||
await requestFetchAccountTypes();
|
requestFetchAccountTypes(),
|
||||||
});
|
);
|
||||||
|
|
||||||
// Fetch the given account id on edit mode.
|
// Fetch the given account id on edit mode.
|
||||||
const fetchAccount = useQuery(
|
const fetchAccount = useQuery(
|
||||||
@@ -211,176 +147,15 @@ function AccountFormDialogContent({
|
|||||||
fetchAccountsTypes.isFetching ||
|
fetchAccountsTypes.isFetching ||
|
||||||
fetchAccount.isFetching;
|
fetchAccount.isFetching;
|
||||||
|
|
||||||
// Fetch requests on dialog opening.
|
|
||||||
const onDialogOpening = useCallback(() => {
|
|
||||||
fetchAccountsList.refetch();
|
|
||||||
fetchAccountsTypes.refetch();
|
|
||||||
|
|
||||||
if (action === 'edit' && accountId) {
|
|
||||||
fetchAccount.refetch();
|
|
||||||
}
|
|
||||||
if (action === 'new_child') {
|
|
||||||
setFieldValue('parent_account_id', parentAccountId);
|
|
||||||
setFieldValue('account_type_id', accountTypeId);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
parentAccountId,
|
|
||||||
accountTypeId,
|
|
||||||
fetchAccount,
|
|
||||||
fetchAccountsList,
|
|
||||||
fetchAccountsTypes,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Handle account type change.
|
|
||||||
const onChangeAccountType = useCallback(
|
|
||||||
(accountType) => {
|
|
||||||
setFieldValue('account_type_id', accountType.id);
|
|
||||||
},
|
|
||||||
[setFieldValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handles change sub-account.
|
|
||||||
const onChangeSubaccount = useCallback(
|
|
||||||
(account) => {
|
|
||||||
setFieldValue('parent_account_id', account.id);
|
|
||||||
},
|
|
||||||
[setFieldValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle dialog on closed.
|
|
||||||
const onDialogClosed = useCallback(() => {
|
|
||||||
resetForm();
|
|
||||||
}, [resetForm]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent isLoading={isFetching}>
|
<DialogContent isLoading={isFetching}>
|
||||||
<form onSubmit={handleSubmit}>
|
<Formik
|
||||||
<div className={Classes.DIALOG_BODY}>
|
validationSchema={validationSchema}
|
||||||
<FormGroup
|
initialValues={initialValues}
|
||||||
label={<T id={'account_type'} />}
|
onSubmit={handleFormSubmit}
|
||||||
labelInfo={<FieldRequiredHint />}
|
>
|
||||||
className={classNames(
|
<AccountFormDialogFields isNewMode={isNewMode} onClose={handleClose} />
|
||||||
'form-group--account-type',
|
</Formik>
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name="account_type_id" {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
intent={
|
|
||||||
errors.account_type_id && touched.account_type_id && Intent.DANGER
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AccountsTypesSelect
|
|
||||||
accountsTypes={accountsTypes}
|
|
||||||
selectedTypeId={values.account_type_id}
|
|
||||||
defaultSelectText={<T id={'select_account_type'} />}
|
|
||||||
onTypeSelected={onChangeAccountType}
|
|
||||||
disabled={action === 'edit'}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account_name'} />}
|
|
||||||
labelInfo={<FieldRequiredHint />}
|
|
||||||
className={'form-group--account-name'}
|
|
||||||
intent={errors.name && touched.name && Intent.DANGER}
|
|
||||||
helperText={<ErrorMessage name="name" {...{ errors, touched }} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
medium={true}
|
|
||||||
intent={errors.name && touched.name && Intent.DANGER}
|
|
||||||
{...getFieldProps('name')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account_code'} />}
|
|
||||||
className={'form-group--account-code'}
|
|
||||||
intent={errors.code && touched.code && Intent.DANGER}
|
|
||||||
helperText={<ErrorMessage name="code" {...{ errors, touched }} />}
|
|
||||||
inline={true}
|
|
||||||
labelInfo={<Hint content={<T id="account_code_hint" />} />}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
medium={true}
|
|
||||||
intent={errors.code && touched.code && Intent.DANGER}
|
|
||||||
{...getFieldProps('code')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={' '}
|
|
||||||
className={classNames('form-group--subaccount')}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
inline={true}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<T id={'sub_account'} />
|
|
||||||
<Hint />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
{...getFieldProps('subaccount')}
|
|
||||||
checked={values.subaccount}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<If condition={values.subaccount}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'parent_account'} />}
|
|
||||||
className={classNames(
|
|
||||||
'form-group--parent-account',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<AccountsSelectList
|
|
||||||
accounts={filteredAccounts}
|
|
||||||
onAccountSelected={onChangeSubaccount}
|
|
||||||
defaultSelectText={<T id={'select_parent_account'} />}
|
|
||||||
selectedAccountId={values.parent_account_id}
|
|
||||||
popoverFill={true}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'description'} />}
|
|
||||||
className={'form-group--description'}
|
|
||||||
intent={errors.description && Intent.DANGER}
|
|
||||||
helperText={errors.description && errors.credential}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
growVertically={true}
|
|
||||||
height={280}
|
|
||||||
{...getFieldProps('description')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
|
||||||
<Button onClick={handleClose} style={{ minWidth: '75px' }}>
|
|
||||||
<T id={'close'} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
intent={Intent.PRIMARY}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
style={{ minWidth: '75px' }}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{action === 'edit' ? <T id={'edit'} /> : <T id={'submit'} />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -388,9 +163,5 @@ function AccountFormDialogContent({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAccountsActions,
|
withAccountsActions,
|
||||||
withAccountDetail,
|
withAccountDetail,
|
||||||
withAccounts(({ accountsTypes, accountsList }) => ({
|
|
||||||
accountsTypes,
|
|
||||||
accounts: accountsList,
|
|
||||||
})),
|
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
)(AccountFormDialogContent);
|
)(AccountFormDialogContent);
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form, FastField, Field, ErrorMessage, useFormikContext } from 'formik';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
Intent,
|
||||||
|
TextArea,
|
||||||
|
Checkbox,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import {
|
||||||
|
If,
|
||||||
|
FieldRequiredHint,
|
||||||
|
Hint,
|
||||||
|
AccountsSelectList,
|
||||||
|
AccountsTypesSelect,
|
||||||
|
} from 'components';
|
||||||
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
|
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
import { compose } from 'redux';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account form dialogs fields.
|
||||||
|
*/
|
||||||
|
function AccountFormDialogFields({
|
||||||
|
// #ownProps
|
||||||
|
onClose,
|
||||||
|
isNewMode,
|
||||||
|
|
||||||
|
// #withAccounts
|
||||||
|
accounts,
|
||||||
|
accountsTypes,
|
||||||
|
}) {
|
||||||
|
const { values, isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<FastField name={'account_type_id'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'account_type'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={classNames('form-group--account-type', Classes.FILL)}
|
||||||
|
inline={true}
|
||||||
|
helperText={<ErrorMessage name="account_type_id" />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
>
|
||||||
|
<AccountsTypesSelect
|
||||||
|
accountsTypes={accountsTypes}
|
||||||
|
selectedTypeId={value}
|
||||||
|
defaultSelectText={<T id={'select_account_type'} />}
|
||||||
|
onTypeSelected={(account) => {
|
||||||
|
form.setFieldValue('account_type_id', account.id);
|
||||||
|
}}
|
||||||
|
disabled={!isNewMode}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
popoverFill={true}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<FastField name={'name'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'account_name'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={'form-group--account-name'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="name" />}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<InputGroup medium={true} {...field} />
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<FastField name={'code'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'account_code'} />}
|
||||||
|
className={'form-group--account-code'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="code" />}
|
||||||
|
inline={true}
|
||||||
|
labelInfo={<Hint content={<T id="account_code_hint" />} />}
|
||||||
|
>
|
||||||
|
<InputGroup medium={true} {...field} />
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<Field name={'subaccount'} type={'checkbox'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={' '}
|
||||||
|
className={classNames('form-group--subaccount')}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
inline={true}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<T id={'sub_account'} />
|
||||||
|
<Hint />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
name={'subaccount'}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<If condition={values.subaccount}>
|
||||||
|
<FastField name={'parent_account_id'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'parent_account'} />}
|
||||||
|
className={classNames(
|
||||||
|
'form-group--parent-account',
|
||||||
|
Classes.FILL,
|
||||||
|
)}
|
||||||
|
inline={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
>
|
||||||
|
<AccountsSelectList
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={(account) => {
|
||||||
|
form.setFieldValue('parent_account_id', account.id);
|
||||||
|
}}
|
||||||
|
defaultSelectText={<T id={'select_parent_account'} />}
|
||||||
|
selectedAccountId={value}
|
||||||
|
popoverFill={true}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<TextArea growVertically={true} height={280} {...field} />
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button onClick={onClose} style={{ minWidth: '75px' }}>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{!isNewMode ? <T id={'edit'} /> : <T id={'submit'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAccounts(({ accountsTypes, accountsList }) => ({
|
||||||
|
accountsTypes,
|
||||||
|
accounts: accountsList,
|
||||||
|
})),
|
||||||
|
)(AccountFormDialogFields);
|
||||||
25
client/src/containers/Dialogs/AccountFormDialog/utils.js
Normal file
25
client/src/containers/Dialogs/AccountFormDialog/utils.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { formatMessage } from 'services/intl';
|
||||||
|
|
||||||
|
export const transformApiErrors = (errors) => {
|
||||||
|
const fields = {};
|
||||||
|
if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) {
|
||||||
|
fields.code = formatMessage({ id: 'account_code_is_not_unique' });
|
||||||
|
}
|
||||||
|
if (errors.find((e) => e.type === 'ACCOUNT.NAME.NOT.UNIQUE')) {
|
||||||
|
fields.name = formatMessage({ id: 'account_name_is_already_used' });
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformAccountToForm = (account, {
|
||||||
|
action,
|
||||||
|
parentAccountId,
|
||||||
|
accountTypeId
|
||||||
|
}) => {
|
||||||
|
return {
|
||||||
|
parent_account_id: action === 'new_child' ? parentAccountId : '',
|
||||||
|
account_type_id: action === 'new_child'? accountTypeId : '',
|
||||||
|
subaccount: action === 'new_child' ? true : false,
|
||||||
|
...account,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,6 +128,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
check('reference')
|
check('reference')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
|
.isString()
|
||||||
.trim()
|
.trim()
|
||||||
.escape()
|
.escape()
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
@@ -144,15 +145,11 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
.isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(),
|
.isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(),
|
||||||
check('entries.*.credit')
|
check('entries.*.credit')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isFloat({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 })
|
||||||
.isDecimal()
|
|
||||||
.isFloat({ max: DATATYPES_LENGTH.INT_13_3 }) // 13, 3
|
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.debit')
|
check('entries.*.debit')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isFloat({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 })
|
||||||
.isDecimal()
|
|
||||||
.isFloat({ max: DATATYPES_LENGTH.INT_13_3 }) // 13, 3
|
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.account_id').isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(),
|
check('entries.*.account_id').isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(),
|
||||||
check('entries.*.note')
|
check('entries.*.note')
|
||||||
@@ -245,7 +242,11 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.manualJournalsService.deleteManualJournal(tenantId, manualJournalId);
|
await this.manualJournalsService.deleteManualJournal(tenantId, manualJournalId);
|
||||||
return res.status(200).send();
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: manualJournalId,
|
||||||
|
message: 'Manual journal has been deleted successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -263,7 +264,11 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.manualJournalsService.deleteManualJournals(tenantId, manualJournalsIds);
|
await this.manualJournalsService.deleteManualJournals(tenantId, manualJournalsIds);
|
||||||
return res.status(200).send();
|
|
||||||
|
return res.status(200).send({
|
||||||
|
ids: manualJournalsIds,
|
||||||
|
message: 'Manual journal have been delete successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -280,7 +285,8 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
const manualJournalDTO = this.matchedBodyData(req);
|
const manualJournalDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { manualJournal } = await this.manualJournalsService.makeJournalEntries(tenantId, manualJournalDTO, user);
|
const { manualJournal } = await this.manualJournalsService
|
||||||
|
.makeJournalEntries(tenantId, manualJournalDTO, user);
|
||||||
|
|
||||||
return res.status(200).send({ id: manualJournal.id });
|
return res.status(200).send({ id: manualJournal.id });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
await this.validateManualJournalsExistance(tenantId, manualJournalsIds);
|
await this.validateManualJournalsExistance(tenantId, manualJournalsIds);
|
||||||
|
|
||||||
this.logger.info('[manual_journal] trying to delete the manual journals.', { tenantId, manualJournalsIds });
|
this.logger.info('[manual_journal] trying to delete the manual journals.', { tenantId, manualJournalsIds });
|
||||||
await ManualJournal.query().where('id', manualJournalsIds).delete();
|
await ManualJournal.query().whereIn('id', manualJournalsIds).delete();
|
||||||
|
|
||||||
// Triggers `onManualJournalDeletedBulk` event.
|
// Triggers `onManualJournalDeletedBulk` event.
|
||||||
this.eventDispatcher.dispatch(events.manualJournals.onDeletedBulk, {
|
this.eventDispatcher.dispatch(events.manualJournals.onDeletedBulk, {
|
||||||
|
|||||||
Reference in New Issue
Block a user