diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js
index e0ba0c858..6f261d044 100644
--- a/client/src/components/DialogsContainer.js
+++ b/client/src/components/DialogsContainer.js
@@ -7,13 +7,14 @@ import AccountFormDialog from 'containers/Dialogs/AccountFormDialog';
// import CurrencyDialog from 'containers/Dialogs/CurrencyDialog';
// import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
// import ExchangeRateDialog from 'containers/Dialogs/ExchangeRateDialog';
-// import JournalNumberDailog from 'containers/Dialogs/JournalNumberDailog';
+import JournalNumberDialog from 'containers/Dialogs/JournalNumberDialog';
export default function DialogsContainer() {
return (
);
}
diff --git a/client/src/components/Forms/InputPrependButton.js b/client/src/components/Forms/InputPrependButton.js
new file mode 100644
index 000000000..be48c7abe
--- /dev/null
+++ b/client/src/components/Forms/InputPrependButton.js
@@ -0,0 +1,27 @@
+import React, { useMemo } from 'react';
+import classNames from 'classnames';
+import { Button, Tooltip, Classes } from '@blueprintjs/core';
+
+export default function InputPrependButton({
+ buttonProps = {},
+ tooltip = false,
+ tooltipProps = {},
+}) {
+ const appendButton = useMemo(() => (
+
+ ), [buttonProps]);
+
+ const appendButtonWithTooltip = useMemo(
+ () => ({ appendButton }),
+ [tooltipProps, appendButton],
+ );
+
+ return (
+
+ { tooltip ? appendButtonWithTooltip : appendButton }
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/components/index.js b/client/src/components/index.js
index 0fb4dd6c2..45bacda7c 100644
--- a/client/src/components/index.js
+++ b/client/src/components/index.js
@@ -24,6 +24,7 @@ import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
import Dialog from './Dialog/Dialog';
import DialogContent from './Dialog/DialogContent';
import DialogSuspense from './Dialog/DialogSuspense';
+import InputPrependButton from './Forms/InputPrependButton';
const Hint = FieldHint;
export {
@@ -53,5 +54,6 @@ export {
AppToaster,
Dialog,
DialogContent,
- DialogSuspense
+ DialogSuspense,
+ InputPrependButton
};
\ No newline at end of file
diff --git a/client/src/containers/Accounting/MakeJournalEntriesForm.js b/client/src/containers/Accounting/MakeJournalEntriesForm.js
index 5d7d0ddc6..12a90a314 100644
--- a/client/src/containers/Accounting/MakeJournalEntriesForm.js
+++ b/client/src/containers/Accounting/MakeJournalEntriesForm.js
@@ -20,6 +20,8 @@ import withJournalsActions from 'containers/Accounting/withJournalsActions';
import withManualJournalDetail from 'containers/Accounting/withManualJournalDetail';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+import withSettings from 'containers/Settings/withSettings';
+import withManualJournals from './withManualJournals';
import AppToaster from 'components/AppToaster';
import Dragzone from 'components/Dragzone';
@@ -27,6 +29,7 @@ import withMediaActions from 'containers/Media/withMediaActions';
import useMedia from 'hooks/useMedia';
import { compose, repeatValue } from 'utils';
+import withManualJournalsActions from './withManualJournalsActions';
const ERROR = {
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
@@ -54,6 +57,15 @@ function MakeJournalEntriesForm({
changePageTitle,
changePageSubtitle,
+ // #withSettings
+ journalNextNumber,
+ journalNumberPrefix,
+
+ // #withManualJournals
+ nextJournalNumberChanged,
+
+ setJournalNumberChanged,
+
manualJournalId,
manualJournal,
onFormSubmit,
@@ -149,16 +161,19 @@ function MakeJournalEntriesForm({
[],
);
+ const journalNumber = (journalNumberPrefix) ?
+ `${journalNumberPrefix}-${journalNextNumber}` : journalNextNumber;
+
const defaultInitialValues = useMemo(
() => ({
- journal_number: '',
+ journal_number: journalNumber,
journal_type: 'Journal',
date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
reference: '',
entries: [...repeatValue(defaultEntry, 4)],
}),
- [defaultEntry],
+ [defaultEntry, journalNumber],
);
const initialValues = useMemo(
@@ -255,7 +270,6 @@ function MakeJournalEntriesForm({
};
const formik = useFormik({
- enableReinitialize: true,
validationSchema,
initialValues: {
...initialValues,
@@ -354,6 +368,11 @@ function MakeJournalEntriesForm({
},
});
+ useEffect(() => {
+ formik.setFieldValue('journal_number', journalNumber);
+ setJournalNumberChanged(false);
+ }, [nextJournalNumberChanged, journalNumber]);
+
const handleSubmitClick = useCallback(
(payload) => {
setPayload(payload);
@@ -430,7 +449,15 @@ function MakeJournalEntriesForm({
export default compose(
withJournalsActions,
withManualJournalDetail,
+ withManualJournals(({ nextJournalNumberChanged }) => ({
+ nextJournalNumberChanged,
+ })),
withAccountsActions,
withDashboardActions,
withMediaActions,
+ withSettings(({ manualJournalsSettings }) => ({
+ journalNextNumber: manualJournalsSettings.next_number,
+ journalNumberPrefix: manualJournalsSettings.number_prefix
+ })),
+ withManualJournalsActions,
)(MakeJournalEntriesForm);
diff --git a/client/src/containers/Accounting/MakeJournalEntriesHeader.js b/client/src/containers/Accounting/MakeJournalEntriesHeader.js
index ab9e33622..c386582d6 100644
--- a/client/src/containers/Accounting/MakeJournalEntriesHeader.js
+++ b/client/src/containers/Accounting/MakeJournalEntriesHeader.js
@@ -5,7 +5,6 @@ import {
Intent,
Position,
Classes,
- Button,
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl';
@@ -20,6 +19,7 @@ import {
FieldHint,
FieldRequiredHint,
Icon,
+ InputPrependButton
} from 'components';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -40,7 +40,7 @@ function MakeJournalEntriesHeader({
[setFieldValue],
);
const handleJournalNumberChange = useCallback(() => {
- openDialog('journalNumber-form', {});
+ openDialog('journal-number-form', {});
}, [openDialog]);
return (
@@ -63,25 +63,29 @@ function MakeJournalEntriesHeader({
}
fill={true}
+
>
)
+ }}
+ tooltip={true}
+ tooltipProps={{
+ content: 'Setting your auto-generated journal number',
+ position: Position.BOTTOM_LEFT,
+ }}
+ />}
{...getFieldProps('journal_number')}
/>
-
- {/* */}
- }
- onClick={handleJournalNumberChange}
- />
- {/* */}
-
+
}
@@ -173,4 +177,6 @@ function MakeJournalEntriesHeader({
);
}
-export default compose(withDialogActions)(MakeJournalEntriesHeader);
+export default compose(
+ withDialogActions,
+)(MakeJournalEntriesHeader);
diff --git a/client/src/containers/Accounting/MakeJournalEntriesPage.js b/client/src/containers/Accounting/MakeJournalEntriesPage.js
index b8e4a4d2d..6e7279013 100644
--- a/client/src/containers/Accounting/MakeJournalEntriesPage.js
+++ b/client/src/containers/Accounting/MakeJournalEntriesPage.js
@@ -8,6 +8,7 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
+import withSettingsActions from 'containers/Settings/withSettingsActions';
import { compose } from 'utils';
@@ -20,6 +21,9 @@ function MakeJournalEntriesPage({
// #withManualJournalActions
requestFetchManualJournal,
+
+ // #withSettingsActions
+ requestFetchOptions,
}) {
const history = useHistory();
const { id } = useParams();
@@ -32,6 +36,8 @@ function MakeJournalEntriesPage({
requestFetchCustomers(),
);
+ const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
+
const fetchJournal = useQuery(
['manual-journal', id],
(key, journalId) => requestFetchManualJournal(journalId),
@@ -71,4 +77,5 @@ export default compose(
withAccountsActions,
withCustomersActions,
withManualJournalsActions,
+ withSettingsActions
)(MakeJournalEntriesPage);
diff --git a/client/src/containers/Accounting/withManualJournals.js b/client/src/containers/Accounting/withManualJournals.js
index e7f5955d8..04a0233cd 100644
--- a/client/src/containers/Accounting/withManualJournals.js
+++ b/client/src/containers/Accounting/withManualJournals.js
@@ -19,6 +19,8 @@ export default (mapState) => {
manualJournalsPagination: getManualJournalsPagination(state, props, query),
manualJournalsLoading: state.manualJournals.loading,
+
+ nextJournalNumberChanged: state.manualJournals.nextJournalNumberChanged,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
diff --git a/client/src/containers/Accounting/withManualJournalsActions.js b/client/src/containers/Accounting/withManualJournalsActions.js
index 6c75d6a54..0e1600164 100644
--- a/client/src/containers/Accounting/withManualJournalsActions.js
+++ b/client/src/containers/Accounting/withManualJournalsActions.js
@@ -24,6 +24,10 @@ const mapActionsToProps = (dispatch) => ({
type: t.MANUAL_JOURNALS_TABLE_QUERIES_ADD,
queries,
}),
+ setJournalNumberChanged: (isChanged) => dispatch({
+ type: t.MANUAL_JOURNAL_NUMBER_CHANGED,
+ payload: { isChanged },
+ }),
});
export default connect(null, mapActionsToProps);
diff --git a/client/src/containers/Dialogs/JournalNumberDailog.js b/client/src/containers/Dialogs/JournalNumberDailog.js
deleted file mode 100644
index 71bd214b5..000000000
--- a/client/src/containers/Dialogs/JournalNumberDailog.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React, { useEffect, useCallback, useMemo } from 'react';
-import { FormattedMessage as T, useIntl } from 'react-intl';
-import { useQuery, queryCache } from 'react-query';
-import { Dialog } from 'components';
-import withDialogActions from 'containers/Dialog/withDialogActions';
-import withDialogRedux from 'components/DialogReduxConnect';
-import withSettingsActions from 'containers/Settings/withSettingsActions';
-import withSettings from 'containers/Settings/withSettings';
-import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
-import classNames from 'classnames';
-
-import { connect } from 'react-redux';
-import { compose, optionsMapToArray } from 'utils';
-
-function JournalNumberDailog({
- dialogName,
- payload,
- isOpen,
-
- // #withSettingsActions
- requestSubmitOptions,
-
- // #withDialogActions
- closeDialog,
-}) {
- // Handles dialog close.
- const handleClose = useCallback(() => {
- closeDialog(dialogName);
- }, [closeDialog, dialogName]);
-
- const handleSubmitForm = useCallback(() => {});
-
- // Handle dialog on closed.
- // const onDialogClosed = useCallback(() => {
- // resetForm();
- // }, [resetForm]);
-
- return (
- }
- autoFocus={true}
- canEscapeKeyClose={true}
- isOpen={isOpen}
- onClose={handleClose}
- >
-
-
- );
-}
-
-const mapStateToProps = (state, props) => ({
- dialogName: 'journalNumber-form',
- journalNumberId: props?.payload?.id || null,
-});
-
-const withJournalNumberDailog = connect(mapStateToProps);
-
-export default compose(
- withDialogRedux(null, 'journalNumber-form'),
- withJournalNumberDailog,
- withDialogActions,
- withSettingsActions,
-)(JournalNumberDailog);
diff --git a/client/src/containers/Dialogs/JournalNumberDialog/JournalNumberDialogContent.js b/client/src/containers/Dialogs/JournalNumberDialog/JournalNumberDialogContent.js
new file mode 100644
index 000000000..27ef5206e
--- /dev/null
+++ b/client/src/containers/Dialogs/JournalNumberDialog/JournalNumberDialogContent.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import { useQuery, queryCache } from 'react-query';
+
+import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import withSettingsActions from 'containers/Settings/withSettingsActions';
+import withSettings from 'containers/Settings/withSettings';
+import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
+
+import { compose, optionsMapToArray } from 'utils';
+
+/**
+ * Journal number dialog's content.
+ */
+function JournalNumberDialogContent({
+ // #withSettings
+ nextNumber,
+ numberPrefix,
+
+ // #withSettingsActions
+ requestFetchOptions,
+ requestSubmitOptions,
+
+ // #withDialogActions
+ closeDialog,
+
+ // #withManualJournalsActions
+ setJournalNumberChanged,
+}) {
+ const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
+
+ const handleSubmitForm = (values, { setSubmitting }) => {
+ const options = optionsMapToArray(values).map((option) => {
+ return { key: option.key, ...option, group: 'manual_journals' };
+ });
+
+ requestSubmitOptions({ options }).then(() => {
+ setSubmitting(false);
+ closeDialog('journal-number-form');
+ setJournalNumberChanged(true);
+
+ setTimeout(() => {
+ queryCache.invalidateQueries('settings');
+ }, 250);
+ }).catch(() => {
+ setSubmitting(false);
+ });
+ };
+
+ const handleClose = () => {
+ closeDialog('journal-number-form');
+ };
+
+ return (
+
+
+
+ )
+}
+
+export default compose(
+ withDialogActions,
+ withSettingsActions,
+ withSettings(({ manualJournalsSettings }) => ({
+ nextNumber: manualJournalsSettings?.next_number,
+ numberPrefix: manualJournalsSettings?.number_prefix,
+ })),
+ withManualJournalsActions,
+)(JournalNumberDialogContent);
\ No newline at end of file
diff --git a/client/src/containers/Dialogs/JournalNumberDialog/index.js b/client/src/containers/Dialogs/JournalNumberDialog/index.js
new file mode 100644
index 000000000..cf83bf035
--- /dev/null
+++ b/client/src/containers/Dialogs/JournalNumberDialog/index.js
@@ -0,0 +1,35 @@
+import React, { lazy } from 'react';
+import { FormattedMessage as T } from 'react-intl';
+import { Dialog, DialogSuspense } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose } from 'utils';
+
+const JournalNumberDialogContent = lazy(() => import('./JournalNumberDialogContent'));
+
+function JournalNumberDialog({
+ dialogName,
+ payload = { id: null },
+ isOpen,
+}) {
+ return (
+ }
+ autoFocus={true}
+ canEscapeKeyClose={true}
+ isOpen={isOpen}
+ className={'dialog--journal-number-settings'}
+ >
+
+
+
+
+ );
+}
+
+
+export default compose(
+ withDialogRedux(),
+)(JournalNumberDialog);
diff --git a/client/src/containers/JournalNumber/ReferenceNumberForm.js b/client/src/containers/JournalNumber/ReferenceNumberForm.js
index 0c64ce65d..0794ce2aa 100644
--- a/client/src/containers/JournalNumber/ReferenceNumberForm.js
+++ b/client/src/containers/JournalNumber/ReferenceNumberForm.js
@@ -1,51 +1,43 @@
-import React, { useMemo, useCallback } from 'react';
+import React, { useMemo } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Row, Col } from 'react-grid-system';
-import { FormattedMessage as T, useIntl } from 'react-intl';
-import { ErrorMessage, AppToaster } from 'components';
-
+import { FormattedMessage as T } from 'react-intl';
+import { ErrorMessage } from 'components';
import {
Button,
Classes,
FormGroup,
InputGroup,
Intent,
- Position,
} from '@blueprintjs/core';
-import { compose, optionsMapToArray } from 'utils';
import withSettingsActions from 'containers/Settings/withSettingsActions';
-import withSettings from 'containers/Settings/withSettings';
-function ReferenceNumberForm({
+import { compose } from 'utils';
+
+
+export default function ReferenceNumberForm({
onSubmit,
onClose,
initialPrefix,
initialNumber,
- groupName,
- requestSubmitOptions,
}) {
- const { formatMessage } = useIntl();
-
const validationSchema = Yup.object().shape({
- prefix: Yup.string(),
+ number_prefix: Yup.string(),
next_number: Yup.number(),
});
const initialValues = useMemo(
() => ({
- prefix: initialPrefix || '',
+ number_prefix: initialPrefix || '',
next_number: initialNumber || '',
}),
- [],
+ [initialPrefix, initialNumber],
);
const {
errors,
- values,
touched,
- setFieldValue,
- resetForm,
handleSubmit,
isSubmitting,
getFieldProps,
@@ -56,33 +48,9 @@ function ReferenceNumberForm({
},
validationSchema,
onSubmit: (values, { setSubmitting, setErrors }) => {
- const options = optionsMapToArray(values).map((option) => {
- return { key: option.key, ...option, group: groupName };
- });
-
- onSubmit(
- requestSubmitOptions({ options })
- .then(() => {
- setSubmitting(false);
- })
-
- .catch((erros) => {
- setSubmitting(false);
- }),
- );
+ onSubmit(values, { setSubmitting, setErrors });
},
});
-
- // Handles dialog close.
- // const handleClose = useCallback(() => {
- // closeDialog(dialogName);
- // }, [closeDialog, dialogName]);
-
- // Handle dialog on closed.
- const onClosed = useCallback(() => {
- resetForm();
- }, [resetForm]);
-
return (
);
-}
-
-export default compose(withSettingsActions)(ReferenceNumberForm);
+}
\ No newline at end of file
diff --git a/client/src/index.js b/client/src/index.js
index 4db7f18b1..3408e641f 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -2,6 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
+import 'services/yup';
import App from 'components/App';
import * as serviceWorker from 'serviceWorker';
import createStore from 'store/createStore';
diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js
index 597c732d9..eb4c3a4b5 100644
--- a/client/src/lang/en/index.js
+++ b/client/src/lang/en/index.js
@@ -774,5 +774,5 @@ export default {
bigger_or_equals: 'Bigger or equals',
prefix: 'Prefix',
next_number: 'Next Number',
- journal_number_settings: 'Journal Number Settings',
+ journal_number_settings: 'Journal number settings',
};
diff --git a/client/src/services/yup.js b/client/src/services/yup.js
index ec84ca0bd..410c3c9e0 100644
--- a/client/src/services/yup.js
+++ b/client/src/services/yup.js
@@ -1,6 +1,5 @@
import * as Yup from 'yup';
-
Yup.addMethod(Yup.string, 'digits', function () {
return this.test(
'is-digits',
diff --git a/client/src/static/json/icons.js b/client/src/static/json/icons.js
index c6cd62d7c..bfe440098 100644
--- a/client/src/static/json/icons.js
+++ b/client/src/static/json/icons.js
@@ -275,4 +275,10 @@ export default {
],
viewBox: '0 0 16 16',
},
+ 'settings-18': {
+ path: [
+ 'M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z',
+ ],
+ viewBox: '0 0 24 24',
+ },
};
diff --git a/client/src/store/manualJournals/manualJournals.reducers.js b/client/src/store/manualJournals/manualJournals.reducers.js
index 9ae29bb11..eb76130b5 100644
--- a/client/src/store/manualJournals/manualJournals.reducers.js
+++ b/client/src/store/manualJournals/manualJournals.reducers.js
@@ -14,7 +14,8 @@ const initialState = {
},
paginationMeta: {
total: 0,
- }
+ },
+ nextJournalNumberChanged: false,
};
const defaultJournal = {
@@ -114,6 +115,11 @@ const reducer = createReducer(initialState, {
paginationMeta,
},
};
+ },
+
+ [t.MANUAL_JOURNAL_NUMBER_CHANGED]: (state, action) => {
+ const { isChanged } = action.payload;
+ state.nextJournalNumberChanged = isChanged;
}
});
diff --git a/client/src/store/manualJournals/manualJournals.types.js b/client/src/store/manualJournals/manualJournals.types.js
index 74e857995..b6452a051 100644
--- a/client/src/store/manualJournals/manualJournals.types.js
+++ b/client/src/store/manualJournals/manualJournals.types.js
@@ -12,4 +12,6 @@ export default {
MANUAL_JOURNAL_PUBLISH: 'MANUAL_JOURNAL_PUBLISH',
MANUAL_JOURNALS_BULK_DELETE: 'MANUAL_JOURNALS_BULK_DELETE',
MANUAL_JOURNALS_PAGINATION_SET: 'MANUAL_JOURNALS_PAGINATION_SET',
+
+ MANUAL_JOURNAL_NUMBER_CHANGED: 'MANUAL_JOURNAL_NUMBER_CHANGED'
};
diff --git a/client/src/store/settings/settings.reducer.js b/client/src/store/settings/settings.reducer.js
index a11834310..0f8cac695 100644
--- a/client/src/store/settings/settings.reducer.js
+++ b/client/src/store/settings/settings.reducer.js
@@ -12,7 +12,16 @@ const initialState = {
export default createReducer(initialState, {
[t.SETTING_SET]: (state, action) => {
const { options } = action;
-
- state.data.organization = optionsArrayToMap(options);
+ const _data = {
+ ...state.data,
+ };
+ options.forEach((option) => {
+ const { key, group, value } = option;
+ if (!_data[group]) {
+ _data[group] = {};
+ }
+ _data[group][key] = value;
+ });
+ state.data = _data;
},
});
diff --git a/client/src/style/objects/form.scss b/client/src/style/objects/form.scss
index b34e3f34c..10fb360ef 100644
--- a/client/src/style/objects/form.scss
+++ b/client/src/style/objects/form.scss
@@ -341,4 +341,42 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,