feat: Edit make journal entries.

This commit is contained in:
Ahmed Bouhuolia
2020-04-08 17:50:35 +02:00
parent 8ba96e7343
commit 4920d5f419
16 changed files with 225 additions and 75 deletions

View File

@@ -1,29 +1,23 @@
import React, {useMemo, useCallback, useState} from 'react';
import {omit} from 'lodash';
import React, {useCallback, useState} from 'react';
import {
MenuItem,
FormGroup,
Button,
Intent,
} from '@blueprintjs/core';
import {Select} from '@blueprintjs/select';
// import MultiSelect from 'components/MultiSelect';
export default function AccountsMultiSelect({
accounts,
onAccountSelected,
error,
initialAccount,
}) {
const [selectedAccount, setSelectedAccount] = useState(null);
const [selectedAccount, setSelectedAccount] = useState(
initialAccount || null
);
// Account item of select accounts field.
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
return (
<MenuItem
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick} />
<MenuItem text={item.name} label={item.code} key={item.id} onClick={handleClick} />
);
}, []);
@@ -33,20 +27,17 @@ export default function AccountsMultiSelect({
}, [setSelectedAccount, onAccountSelected]);
return (
<Select
items={accounts}
noResults={<MenuItem disabled={true} text='No results.' />}
itemRenderer={accountItem}
popoverProps={{ minimal: true }}
filterable={true}
onItemSelect={onAccountSelect}
>
onItemSelect={onAccountSelect}>
<Button
rightIcon='caret-down'
text={selectedAccount ? selectedAccount.name : 'Select account'}
/>
</Select>
);
}

View File

@@ -1,4 +1,4 @@
import React, {useCallback} from 'react';
import React, {useCallback, useMemo} from 'react';
import AccountsSelectList from 'components/AccountsSelectList';
import classNames from 'classnames';
import {
@@ -20,6 +20,10 @@ const AccountCellRenderer = ({
const { account_id = false } = (errors[index] || {});
const initialAccount = useMemo(() =>
accounts.find(a => a.id === initialValue),
[accounts, initialValue]);
return (
<FormGroup
intent={account_id ? Intent.DANGER : ''}
@@ -31,7 +35,8 @@ const AccountCellRenderer = ({
<AccountsSelectList
accounts={accounts}
onAccountSelected={handleAccountSelected}
error={account_id} />
error={account_id}
initialAccount={initialAccount} />
</FormGroup>
);
};

View File

@@ -64,7 +64,9 @@ export default function MoneyFieldGroup({
const options = useMemo(() => ({
prefix, suffix, thousands, decimal, precision,
}), []);
}), [
prefix, suffix, thousands, decimal, precision,
]);
const handleChange = useCallback((event) => {
const formatted = formatter(event.target.value, options);
@@ -72,12 +74,12 @@ export default function MoneyFieldGroup({
setState(formatted);
onChange && onChange(event, value);
}, []);
}, [onChange, options]);
useEffect(() => {
const formatted = formatter(value, options);
setState(formatted)
}, []);
}, [value, options, setState]);
return (
<InputGroup

View File

@@ -1,17 +1,21 @@
import {connect} from 'react-redux';
import {
makeJournalEntries,
fetchManualJournal,
editManualJournal,
} from 'store/accounting/accounting.actions';
import t from 'store/types';
import {
getManualJournal,
} from 'store/accounting/accounting.reducers';
export const mapStateToProps = (state, props) => ({
getManualJournal: (id) => getManualJournal(state, id),
});
export const mapDispatchToProps = (dispatch) => ({
makeJournalEntries: (form) => dispatch(makeJournalEntries({ form })),
requestMakeJournalEntries: (form) => dispatch(makeJournalEntries({ form })),
fetchManualJournal: (id) => dispatch(fetchManualJournal({ id })),
requestEditManualJournal: (id, form) => dispatch(editManualJournal({ id, form }))
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -13,21 +13,23 @@ import {compose} from 'utils';
import useAsync from 'hooks/async';
import moment from 'moment';
import AppToaster from 'components/AppToaster';
import {pick, omit} from 'lodash';
function MakeJournalEntriesForm({
makeJournalEntries,
fetchAccounts,
requestMakeJournalEntries,
requestEditManualJournal,
changePageTitle,
changePageSubtitle,
editJournal,
}) {
useEffect(() => {
changePageTitle('New Journal');
}, []);
const fetchHook = useAsync(async () => {
await Promise.all([
fetchAccounts(),
]);
});
if (editJournal && editJournal.id) {
changePageTitle('Edit Journal');
changePageSubtitle(`No. ${editJournal.journal_number}`);
} else {
changePageTitle('New Journal');
}
}, [changePageTitle, changePageSubtitle, editJournal]);
const validationSchema = Yup.object().shape({
journal_number: Yup.string().required(),
@@ -54,22 +56,31 @@ function MakeJournalEntriesForm({
note: '',
}), []);
const initialValues = useMemo(() => ({
journal_number: '',
date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
reference: '',
entries: [
defaultEntry,
defaultEntry,
defaultEntry,
defaultEntry,
],
}), [defaultEntry]);
const formik = useFormik({
enableReinitialize: true,
validationSchema,
initialValues: {
journal_number: '',
date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
reference: '',
entries: [
defaultEntry,
defaultEntry,
defaultEntry,
defaultEntry,
defaultEntry,
defaultEntry,
],
...(editJournal) ? {
...pick(editJournal, Object.keys(initialValues)),
entries: editJournal.entries.map((entry) => ({
...pick(entry, Object.keys(defaultEntry)),
}))
} : {
...initialValues,
}
},
onSubmit: (values, actions) => {
const form = values.entries.filter((entry) => (
@@ -87,18 +98,31 @@ function MakeJournalEntriesForm({
AppToaster.show({
message: 'credit_and_debit_not_equal',
});
actions.setSubmitting(false);
return;
}
makeJournalEntries({ ...values, entries: form })
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_submit',
});
actions.setSubmitting(false);
}).catch((error) => {
actions.setSubmitting(false);
});
if (editJournal && editJournal.id) {
requestEditManualJournal(editJournal.id, { ...values, entries: form })
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_edited',
});
actions.setSubmitting(false);
}).catch((error) => {
actions.setSubmitting(false);
});
} else {
requestMakeJournalEntries({ ...values, entries: form })
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_submit',
});
actions.setSubmitting(false);
}).catch((error) => {
actions.setSubmitting(false);
});
}
},
});

View File

@@ -21,12 +21,13 @@ export default function MakeJournalEntriesHeader({
}) {
const intl = useIntl();
const handleDateChange = (date) => {
const handleDateChange = useCallback((date) => {
const formatted = moment(date).format('YYYY-MM-DD');
formik.setFieldValue('date', formatted);
};
}, [formik]);
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
const infoIcon = useMemo(() =>
(<Icon icon="info-circle" iconSize={12} />), []);
return (
<div class="make-journal-entries__header">

View File

@@ -1,14 +1,39 @@
import React from 'react';
import React, {useMemo} from 'react';
import { useParams } from 'react-router-dom';
import { useAsync } from 'react-use';
import MakeJournalEntriesForm from './MakeJournalEntriesForm';
import LoadingIndicator from 'components/LoadingIndicator';
import DashboardConnect from 'connectors/Dashboard.connector';
import {compose} from 'utils';
import MakeJournalEntriesConnect from 'connectors/MakeJournalEntries.connect';
import AccountsConnect from 'connectors/Accounts.connector';
function MakeJournalEntriesPage({
fetchManualJournal,
getManualJournal,
fetchAccounts,
}) {
const { id } = useParams();
const fetchJournal = useAsync(() => {
return Promise.all([
fetchAccounts(),
(id) && fetchManualJournal(id),
]);
});
const editJournal = useMemo(() =>
getManualJournal(id) || null,
[getManualJournal, id]);
function MakeJournalEntriesPage() {
return (
<MakeJournalEntriesForm />
<LoadingIndicator loading={fetchJournal.loading} mount={false}>
<MakeJournalEntriesForm editJournal={editJournal} />
</LoadingIndicator>
);
}
export default compose(
DashboardConnect,
AccountsConnect,
MakeJournalEntriesConnect,
)(MakeJournalEntriesPage);

View File

@@ -70,6 +70,9 @@ const NoteCellRenderer = (chainedComponent) => (props) => {
return chainedComponent(props);
};
/**
* Make journal entries table component.
*/
function MakeJournalEntriesTable({
formik,
accounts,
@@ -79,6 +82,8 @@ function MakeJournalEntriesTable({
}) {
const [rows, setRow] = useState([
...formik.values.entries.map((e) => ({ ...e, rowType: 'editor'})),
defaultRow,
defaultRow,
]);
// Handles update datatable data.
@@ -101,11 +106,15 @@ function MakeJournalEntriesTable({
// Handles click remove datatable row.
const handleRemoveRow = useCallback((rowIndex) => {
const removeIndex = parseInt(rowIndex, 10);
setRow([
...rows.filter((row, index) => index !== removeIndex),
]);
const newRows = rows.filter((row, index) => index !== removeIndex);
setRow([ ...newRows ]);
formik.setFieldValue('entries', newRows
.filter(row => row.rowType === 'editor')
.map(row => ({ ...omit(row, ['rowType']) })
));
onClickRemoveRow && onClickRemoveRow(removeIndex);
}, [rows, onClickRemoveRow]);
}, [rows, formik, onClickRemoveRow]);
// Memorized data table columns.
const columns = useMemo(() => [
@@ -123,7 +132,7 @@ function MakeJournalEntriesTable({
{
Header: 'Account',
id: 'account_id',
accessor: 'account',
accessor: 'account_id',
Cell: TotalAccountCellRenderer(AccountsListFieldCell),
className: "account",
disableSortBy: true,

View File

@@ -66,6 +66,15 @@ export default [
text: 'Make Journal Entry'
},
{
path: `${BASE_URL}/accounting/manual-journal/:id`,
name: 'dashboard.manual.journal.edit',
component: LazyLoader({
loader: () =>
import('containers/Dashboard/Accounting/MakeJournalEntriesPage')
}),
},
// Items
{
path: `${BASE_URL}/items/list`,

View File

@@ -7,4 +7,27 @@ export const makeJournalEntries = ({ form }) => {
resolve(response);
}).catch((error) => { reject(error); });
});
};
export const fetchManualJournal = ({ id }) => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.get(`accounting/manual-journals/${id}`).then((response) => {
dispatch({
type: t.MANUAL_JOURNAL_SET,
payload: {
id,
manualJournal: response.data.manual_journal,
}
});
resolve(response);
}).catch((error) => { reject(error); });
});
};
export const editManualJournal = ({ form, id }) => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.post(`accounting/manual-journals/${id}`, form).then((response) => {
resolve(response);
}).catch((error) => { reject(error); });
});
}

View File

@@ -0,0 +1,19 @@
import t from 'store/types';
import { createReducer } from '@reduxjs/toolkit';
const initialState = {
manualJournals: {},
};
export default createReducer(initialState, {
[t.MANUAL_JOURNAL_SET]: (state, action) => {
const { id, manualJournal } = action.payload;
state.manualJournals[id] = manualJournal;
},
});
export const getManualJournal = (state, id) => {
return state.accounting.manualJournals[id];
}

View File

@@ -2,4 +2,5 @@
export default {
MAKE_JOURNAL_ENTRIES: 'MAKE_JOURNAL_ENTRIES',
MANUAL_JOURNAL_SET: 'MANUAL_JOURNAL_SET',
}

View File

@@ -13,12 +13,14 @@ import resources from './resources/resources.reducer';
import financialStatements from './financialStatement/financialStatements.reducer';
import itemCategories from './itemCategories/itemsCateory.reducer';
import settings from './settings/settings.reducer';
import accounting from './accounting/accounting.reducers';
export default combineReducers({
authentication,
dashboard,
users,
accounts,
accounting,
fields,
views,
expenses,

View File

@@ -1,5 +1,6 @@
import authentication from './authentication/authentication.types';
import accounts from './accounts/accounts.types';
import accounting from './accounting/accounting.types'
import currencies from './currencies/currencies.types';
import customFields from './customFields/customFields.types';
import customViews from './customViews/customViews.types';
@@ -27,5 +28,6 @@ export default {
...users,
...financialStatements,
...itemCategories,
...settings
...settings,
...accounting,
};