diff --git a/client/src/components/JournalEntry/ManualJournalActionsBar.js b/client/src/components/JournalEntry/ManualJournalActionsBar.js
new file mode 100644
index 000000000..dbdd4c815
--- /dev/null
+++ b/client/src/components/JournalEntry/ManualJournalActionsBar.js
@@ -0,0 +1,111 @@
+import React, { useMemo, useState } from 'react';
+import Icon from 'components/Icon';
+import {
+ Button,
+ NavbarGroup,
+ Classes,
+ NavbarDivider,
+ MenuItem,
+ Menu,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+ Intent
+} from '@blueprintjs/core';
+import classNames from 'classnames';
+import { useRouteMatch, useHistory } from 'react-router-dom';
+import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
+import DialogConnect from 'connectors/Dialog.connector';
+import { compose } from 'utils';
+import FilterDropdown from 'components/FilterDropdown';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import ResourceConnect from 'connectors/Resource.connector';
+
+function ManualJournalActionsBar({
+ views,
+ getResourceFields,
+ addManualJournalsTableQueries,
+ onFilterChanged
+}) {
+ const { path } = useRouteMatch();
+ const history = useHistory();
+
+ const manualJournalFields = getResourceFields('manual_journals');
+ const viewsMenuItems = views.map(view => {
+ return (
+
+ );
+ });
+
+ const onClickNewManualJournal = () => {
+ history.push('/dashboard/accounting/make-journal-entry');
+ };
+ const filterDropdown = FilterDropdown({
+ fields: manualJournalFields,
+ onFilterChange: filterConditions => {
+ addManualJournalsTableQueries({
+ filter_roles: filterConditions || ''
+ });
+ onFilterChanged && onFilterChanged(filterConditions);
+ }
+ });
+ return (
+
+
+ {viewsMenuItems}}
+ minimal={true}
+ interactionKind={PopoverInteractionKind.HOVER}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ text='Table Views'
+ rightIcon={'caret-down'}
+ />
+
+
+ }
+ text='New Journal'
+ onClick={onClickNewManualJournal}
+ />
+
+ }
+ />
+
+ }
+ text='Delete'
+ intent={Intent.DANGER}
+ />
+ }
+ text='Import'
+ />
+ }
+ text='Export'
+ />
+
+
+ );
+}
+
+export default compose(
+ DialogConnect,
+ ManualJournalsConnect,
+ ResourceConnect
+)(ManualJournalActionsBar);
diff --git a/client/src/components/JournalEntry/ManualJournalsDataTable.js b/client/src/components/JournalEntry/ManualJournalsDataTable.js
new file mode 100644
index 000000000..71f86cefe
--- /dev/null
+++ b/client/src/components/JournalEntry/ManualJournalsDataTable.js
@@ -0,0 +1,167 @@
+import React, { useEffect, useCallback, useState, useMemo } from 'react';
+import {
+ Intent,
+ Button,
+ Popover,
+ Menu,
+ MenuItem,
+ MenuDivider,
+ Position,
+} from '@blueprintjs/core';
+import { useParams, useHistory } from 'react-router-dom';
+import Icon from 'components/Icon';
+import { compose } from 'utils';
+import moment from 'moment';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import DialogConnect from 'connectors/Dialog.connector';
+import DashboardConnect from 'connectors/Dashboard.connector';
+import ViewConnect from 'connectors/View.connector';
+import LoadingIndicator from 'components/LoadingIndicator';
+import DataTable from 'components/DataTable';
+import Money from 'components/Money';
+
+function ManualJournalsDataTable({
+ manualJournals,
+ changeCurrentView,
+ changePageSubtitle,
+ getViewItem,
+ setTopbarEditView,
+ manualJournalsLoading,
+ onFetchData,
+ onEditJournal,
+ onDeleteJournal,
+ onPublishJournal,
+}) {
+ const { custom_view_id: customViewId } = useParams();
+
+ useEffect(() => {
+ const viewMeta = getViewItem(customViewId);
+
+ if (customViewId) {
+ changeCurrentView(customViewId);
+ setTopbarEditView(customViewId);
+ }
+ changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
+ }, [customViewId]);
+
+ const actionMenuList = (journal) => (
+
+ );
+
+ const columns = useMemo(() => [
+ {
+ id: 'date',
+ Header: 'Date',
+ accessor: r => moment().format('YYYY-MM-DD'),
+ disableResizing: true,
+ width: 150,
+ className: 'date',
+ },
+ {
+ id: 'amount',
+ Header: 'Amount',
+ accessor: r => (),
+ disableResizing: true,
+ className: 'amount',
+ },
+ {
+ id: 'journal_number',
+ Header: 'Journal No.',
+ accessor: 'journal_number',
+ disableResizing: true,
+ className: 'journal_number',
+ },
+ {
+ id: 'status',
+ Header: 'Status',
+ accessor: (r) => {
+ return r.status ? 'Published' : 'Draft';
+ },
+ disableResizing: true,
+ width: 100,
+ className: 'status',
+ },
+ {
+ id: 'note',
+ Header: 'Note',
+ accessor: r => (),
+ disableResizing: true,
+ width: 100,
+ className: 'note',
+ },
+ {
+ id: 'transaction_type',
+ Header: 'Transaction type ',
+ accessor: 'transaction_type',
+ width: 100,
+ className: 'transaction_type',
+ },
+ {
+ id: 'created_at',
+ Header: 'Created At',
+ accessor: r => moment().format('YYYY-MM-DD'),
+ disableResizing: true,
+ width: 150,
+ className: 'created_at',
+ },
+ {
+ id: 'actions',
+ Header: '',
+ Cell: ({ cell }) => (
+
+ } />
+
+ ),
+ className: 'actions',
+ width: 50,
+ disableResizing: true,
+ },
+ ], []);
+
+ const handleDataTableFetchData = useCallback(() => {
+ onFetchData && onFetchData();
+ }, [onFetchData]);
+
+ return (
+
+
+
+ );
+}
+
+export default compose(
+ DialogConnect,
+ DashboardConnect,
+ ViewConnect,
+ ManualJournalsConnect,
+)(ManualJournalsDataTable);
diff --git a/client/src/components/JournalEntry/ManualJournalsViewTabs.js b/client/src/components/JournalEntry/ManualJournalsViewTabs.js
new file mode 100644
index 000000000..8be539d18
--- /dev/null
+++ b/client/src/components/JournalEntry/ManualJournalsViewTabs.js
@@ -0,0 +1,98 @@
+import React, { useEffect } from 'react';
+import { useHistory } from 'react-router';
+import {
+ Alignment,
+ Navbar,
+ NavbarGroup,
+ Tabs,
+ Tab,
+ Button,
+} from '@blueprintjs/core';
+import { useParams } from 'react-router-dom';
+import Icon from 'components/Icon';
+import { Link } from 'react-router-dom';
+import { compose } from 'utils';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import DashboardConnect from 'connectors/Dashboard.connector';
+import { useUpdateEffect } from 'hooks';
+
+function ManualJournalsViewTabs({
+ views,
+ manualJournals,
+ setTopbarEditView,
+ customViewChanged,
+ addManualJournalsTableQueries,
+ onViewChanged,
+}) {
+ const history = useHistory();
+ const { custom_view_id: customViewId } = useParams();
+
+ const handleClickNewView = () => {
+ setTopbarEditView(null);
+ history.push('/dashboard/custom_views/manual_journals/new');
+ };
+ const handleViewLinkClick = () => {
+ setTopbarEditView(customViewId);
+ };
+
+ useUpdateEffect(() => {
+ customViewChanged && customViewChanged(customViewId);
+
+ addManualJournalsTableQueries({
+ custom_view_id: customViewId || null,
+ });
+ onViewChanged && onViewChanged(customViewId);
+ }, [customViewId]);
+
+ useEffect(() => {
+ addManualJournalsTableQueries({
+ custom_view_id: customViewId,
+ });
+ }, [customViewId]);
+
+ const tabs = views.map((view) => {
+ //FIXME: dashboard/accounting/make-journal-entry
+
+ const baseUrl = '/dashboard/accounting/manual-journals';
+ const link = (
+
+ {view.name}
+
+ );
+ return ;
+ });
+
+ return (
+
+
+
+ All
+ }
+ />
+ {tabs}
+ }
+ onClick={handleClickNewView}
+ />
+
+
+
+ );
+}
+
+export default compose(
+ ManualJournalsConnect,
+ DashboardConnect
+)(ManualJournalsViewTabs);
diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js
index 522fa6077..0ccdddd1e 100644
--- a/client/src/config/sidebarMenu.js
+++ b/client/src/config/sidebarMenu.js
@@ -43,10 +43,14 @@ export default [
text: 'Accounts Chart',
href: '/dashboard/accounts'
},
+ {
+ text: 'Manual Journal',
+ href: '/dashboard/accounting/manual-journals'
+ },
{
text: 'Make Journal',
href: '/dashboard/accounting/make-journal-entry'
- }
+ },
]
},
{
diff --git a/client/src/connectors/MakeJournalEntries.connect.js b/client/src/connectors/MakeJournalEntries.connect.js
index 41ef4aa43..645ef4e2a 100644
--- a/client/src/connectors/MakeJournalEntries.connect.js
+++ b/client/src/connectors/MakeJournalEntries.connect.js
@@ -3,10 +3,10 @@ import {
makeJournalEntries,
fetchManualJournal,
editManualJournal,
-} from 'store/accounting/accounting.actions';
+} from 'store/manualJournals/manualJournals.actions';
import {
getManualJournal,
-} from 'store/accounting/accounting.reducers';
+} from 'store/manualJournals/manualJournals.reducers';
export const mapStateToProps = (state, props) => ({
getManualJournal: (id) => getManualJournal(state, id),
diff --git a/client/src/connectors/ManualJournals.connect.js b/client/src/connectors/ManualJournals.connect.js
new file mode 100644
index 000000000..2cce4bd42
--- /dev/null
+++ b/client/src/connectors/ManualJournals.connect.js
@@ -0,0 +1,39 @@
+import { connect } from 'react-redux';
+import {
+ deleteManualJournal,
+ fetchManualJournalsTable,
+ publishManualJournal,
+} from 'store/manualJournals/manualJournals.actions';
+import { getResourceViews } from 'store/customViews/customViews.selectors';
+import t from 'store/types';
+import {
+ getManualJournalsItems,
+} from 'store/manualJournals/manualJournals.selectors'
+
+const mapStateToProps = (state, props) => ({
+ views: getResourceViews(state, 'manual_journals'),
+ manualJournals: getManualJournalsItems(state, state.manualJournals.currentViewId),
+ tableQuery: state.manualJournals.tableQuery,
+ manualJournalsLoading: state.manualJournals.loading,
+});
+
+const mapActionsToProps = (dispatch) => ({
+ requestDeleteManualJournal: (id) => dispatch(deleteManualJournal({ id })),
+ changeCurrentView: (id) =>
+ dispatch({
+ type: t.MANUAL_JOURNALS_SET_CURRENT_VIEW,
+ currentViewId: parseInt(id, 10),
+ }),
+ addManualJournalsTableQueries: (queries) =>
+ dispatch({
+ type: 'MANUAL_JOURNALS_TABLE_QUERIES_ADD',
+ queries,
+ }),
+ fetchManualJournalsTable: (query = {}) =>
+ dispatch(fetchManualJournalsTable({ query: { ...query } })),
+
+ requestPublishManualJournal: (id) =>
+ dispatch(publishManualJournal({ id })),
+});
+
+export default connect(mapStateToProps, mapActionsToProps);
diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js
index f1a1d1c06..f66e9320b 100644
--- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js
+++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js
@@ -7,6 +7,8 @@ import { FormattedList } from 'react-intl';
export default function MakeJournalEntriesFooter({
formik,
+ onSubmitClick,
+ onCancelClick,
}) {
return (
@@ -14,28 +16,38 @@ export default function MakeJournalEntriesFooter({
diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js
index c32079c79..ea75b5563 100644
--- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js
+++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js
@@ -1,6 +1,4 @@
-
-
-import React, {useMemo, useEffect} from 'react';
+import React, {useMemo, useState, useEffect, useCallback} from 'react';
import * as Yup from 'yup';
import MakeJournalEntriesHeader from './MakeJournalEntriesHeader';
import MakeJournalEntriesFooter from './MakeJournalEntriesFooter';
@@ -10,10 +8,9 @@ import MakeJournalEntriesConnect from 'connectors/MakeJournalEntries.connect';
import AccountsConnect from 'connectors/Accounts.connector';
import DashboardConnect from 'connectors/Dashboard.connector';
import {compose} from 'utils';
-import useAsync from 'hooks/async';
import moment from 'moment';
import AppToaster from 'components/AppToaster';
-import {pick, omit} from 'lodash';
+import {pick} from 'lodash';
function MakeJournalEntriesForm({
requestMakeJournalEntries,
@@ -21,6 +18,8 @@ function MakeJournalEntriesForm({
changePageTitle,
changePageSubtitle,
editJournal,
+ onFormSubmit,
+ onCancelForm,
}) {
useEffect(() => {
if (editJournal && editJournal.id) {
@@ -49,6 +48,12 @@ function MakeJournalEntriesForm({
)
});
+ const saveInvokeSubmit = useCallback((payload) => {
+ onFormSubmit && onFormSubmit(payload)
+ }, [onFormSubmit]);
+
+ const [payload, setPayload] = useState({});
+
const defaultEntry = useMemo(() => ({
account_id: null,
credit: 0,
@@ -83,11 +88,11 @@ function MakeJournalEntriesForm({
}
},
onSubmit: (values, actions) => {
- const form = values.entries.filter((entry) => (
+ const entries = values.entries.filter((entry) => (
(entry.credit || entry.debit)
));
const getTotal = (type = 'credit') => {
- return form.reduce((total, item) => {
+ return entries.reduce((total, item) => {
return item[type] ? item[type] + total : total;
}, 0);
}
@@ -101,24 +106,27 @@ function MakeJournalEntriesForm({
actions.setSubmitting(false);
return;
}
-
+ const form = { ...values, status: payload.publish, entries };
+
if (editJournal && editJournal.id) {
- requestEditManualJournal(editJournal.id, { ...values, entries: form })
+ requestEditManualJournal(editJournal.id, form)
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_edited',
});
actions.setSubmitting(false);
+ saveInvokeSubmit({ action: 'update', ...payload });
}).catch((error) => {
actions.setSubmitting(false);
});
} else {
- requestMakeJournalEntries({ ...values, entries: form })
+ requestMakeJournalEntries(form)
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_submit',
});
actions.setSubmitting(false);
+ saveInvokeSubmit({ action: 'new', ...payload });
}).catch((error) => {
actions.setSubmitting(false);
});
@@ -126,12 +134,24 @@ function MakeJournalEntriesForm({
},
});
+ const handleSubmitClick = useCallback((payload) => {
+ setPayload(payload);
+ formik.handleSubmit();
+ }, [setPayload, formik]);
+
+ const handleCancelClick = useCallback((payload) => {
+ onCancelForm && onCancelForm(payload);
+ }, [onCancelForm]);
+
return (
);
diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js
index 761c8f696..8f5e5cbaf 100644
--- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js
+++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js
@@ -1,13 +1,11 @@
import React, {useMemo, useCallback} from 'react';
-import * as Yup from 'yup';
import {
InputGroup,
FormGroup,
Intent,
Position,
} from '@blueprintjs/core';
-import {DatePicker, DateInput} from '@blueprintjs/datetime';
-import {Formik, useFormik} from "formik";
+import {DateInput} from '@blueprintjs/datetime';
import {useIntl} from 'react-intl';
import {Row, Col} from 'react-grid-system';
import moment from 'moment';
diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesPage.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesPage.js
index 5d05a85ce..ebd59ddbd 100644
--- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesPage.js
+++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesPage.js
@@ -1,5 +1,5 @@
-import React, {useMemo} from 'react';
-import { useParams } from 'react-router-dom';
+import React, {useMemo, useCallback} from 'react';
+import { useParams, useHistory } from 'react-router-dom';
import { useAsync } from 'react-use';
import MakeJournalEntriesForm from './MakeJournalEntriesForm';
import LoadingIndicator from 'components/LoadingIndicator';
@@ -13,6 +13,7 @@ function MakeJournalEntriesPage({
getManualJournal,
fetchAccounts,
}) {
+ const history = useHistory();
const { id } = useParams();
const fetchJournal = useAsync(() => {
@@ -25,9 +26,21 @@ function MakeJournalEntriesPage({
getManualJournal(id) || null,
[getManualJournal, id]);
+ const handleFormSubmit = useCallback((payload) => {
+ payload.redirect &&
+ history.push('/dashboard/accounting/manual-journals');
+ }, [history]);
+
+ const handleCancel = useCallback(() => {
+ history.push('/dashboard/accounting/manual-journals');
+ }, [history]);
+
return (
-
+
);
}
diff --git a/client/src/containers/Dashboard/Accounting/ManualJournalsTable.js b/client/src/containers/Dashboard/Accounting/ManualJournalsTable.js
new file mode 100644
index 000000000..87bc75308
--- /dev/null
+++ b/client/src/containers/Dashboard/Accounting/ManualJournalsTable.js
@@ -0,0 +1,126 @@
+import React, { useEffect, useState, useCallback } from 'react';
+import { Route, Switch, useHistory } from 'react-router-dom';
+import useAsync from 'hooks/async';
+import { Alert, Intent } from '@blueprintjs/core';
+import AppToaster from 'components/AppToaster';
+import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import ManualJournalsViewTabs from 'components/JournalEntry/ManualJournalsViewTabs';
+import ManualJournalsDataTable from 'components/JournalEntry/ManualJournalsDataTable';
+import DashboardActionsBar from 'components/JournalEntry/ManualJournalActionsBar';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import DashboardConnect from 'connectors/Dashboard.connector';
+import CustomViewConnect from 'connectors/CustomView.connector';
+import ResourceConnect from 'connectors/Resource.connector';
+import { compose } from 'utils';
+
+function ManualJournalsTable({
+ changePageTitle,
+ fetchResourceViews,
+ fetchManualJournalsTable,
+ requestDeleteManualJournal,
+ requestPublishManualJournal,
+}) {
+ const history = useHistory();
+ const [deleteManualJournal, setDeleteManualJournal] = useState(false);
+
+ const fetchHook = useAsync(async () => {
+ await Promise.all([
+ fetchResourceViews('manual_journals'),
+ ]);
+ });
+
+ const fetchManualJournalsHook = useAsync(async () => {
+ return fetchManualJournalsTable();
+ }, false);
+
+ useEffect(() => {
+ changePageTitle('Manual Journals');
+ }, []);
+
+ const handleCancelManualJournalDelete = () => {
+ setDeleteManualJournal(false);
+ };
+
+ const handleConfirmManualJournalDelete = useCallback(() => {
+ requestDeleteManualJournal(deleteManualJournal.id).then(() => {
+ setDeleteManualJournal(false);
+ AppToaster.show({ message: 'the_manual_Journal_has_been_deleted' });
+ });
+ }, [deleteManualJournal, requestDeleteManualJournal]);
+
+ const handleEditJournal = useCallback((journal) => {
+ history.push(`/dashboard/accounting/manual-journals/${journal.id}/edit`);
+ }, [history]);
+
+ const handleDeleteJournal = useCallback((journal) => {
+ setDeleteManualJournal(journal);
+ }, []);
+
+ const handleFilterChanged = useCallback(() => {
+ fetchManualJournalsHook.execute();
+ }, []);
+
+ const handleViewChanged = useCallback(() => {
+ fetchManualJournalsHook.execute();
+ }, []);
+
+ const handleFetchData = useCallback(() => {
+ fetchManualJournalsHook.execute();
+ }, []);
+
+ const handlePublishJournal = useCallback((journal) => {
+ requestPublishManualJournal(journal.id).then(() => {
+ AppToaster.show({ message: 'the_manual_journal_id_has_been_published' });
+ })
+ }, [requestPublishManualJournal]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Are you sure you want to move filename to Trash? You will be
+ able to restore it later, but it will become private to you.
+
+
+
+
+ );
+}
+
+export default compose(
+ ManualJournalsConnect,
+ CustomViewConnect,
+ ResourceConnect,
+ DashboardConnect
+)(ManualJournalsTable);
diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js
index e97152b4f..a5cdfd673 100644
--- a/client/src/routes/dashboard.js
+++ b/client/src/routes/dashboard.js
@@ -67,7 +67,7 @@ export default [
},
{
- path: `${BASE_URL}/accounting/manual-journal/:id`,
+ path: `${BASE_URL}/accounting/manual-journals/:id/edit`,
name: 'dashboard.manual.journal.edit',
component: LazyLoader({
loader: () =>
@@ -75,6 +75,15 @@ export default [
}),
},
+ {
+ path: `${BASE_URL}/accounting/manual-journals`,
+ component: LazyLoader({
+ loader: () =>
+ import('containers/Dashboard/Accounting/ManualJournalsTable')
+ }),
+ text: 'Manual Journals'
+ },
+
// Items
{
path: `${BASE_URL}/items/list`,
@@ -95,14 +104,16 @@ export default [
loader: () => import('containers/Dashboard/Items/ItemsCategoryList')
})
},
- ,
+
// Financial Reports.
{
path: `${BASE_URL}/accounting/general-ledger`,
name: 'dashboard.accounting.general.ledger',
component: LazyLoader({
loader: () =>
- import('containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger')
+ import(
+ 'containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger'
+ )
})
},
{
diff --git a/client/src/static/json/icons.js b/client/src/static/json/icons.js
index bbc23219f..fd09923db 100644
--- a/client/src/static/json/icons.js
+++ b/client/src/static/json/icons.js
@@ -98,5 +98,9 @@ export default {
"times-circle": {
path: ['M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 464c-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216 0 118.7-96.1 216-216 216zm94.8-285.3L281.5 256l69.3 69.3c4.7 4.7 4.7 12.3 0 17l-8.5 8.5c-4.7 4.7-12.3 4.7-17 0L256 281.5l-69.3 69.3c-4.7 4.7-12.3 4.7-17 0l-8.5-8.5c-4.7-4.7-4.7-12.3 0-17l69.3-69.3-69.3-69.3c-4.7-4.7-4.7-12.3 0-17l8.5-8.5c4.7-4.7 12.3-4.7 17 0l69.3 69.3 69.3-69.3c4.7-4.7 12.3-4.7 17 0l8.5 8.5c4.6 4.7 4.6 12.3 0 17z'],
viewBox: '0 0 512 512',
- }
+ },
+ "file-alt": {
+ path: ['M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zm-22.6 22.7c2.1 2.1 3.5 4.6 4.2 7.4H256V32.5c2.8.7 5.3 2.1 7.4 4.2l83.9 83.9zM336 480H48c-8.8 0-16-7.2-16-16V48c0-8.8 7.2-16 16-16h176v104c0 13.3 10.7 24 24 24h104v304c0 8.8-7.2 16-16 16zm-48-244v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm0 64v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm0 64v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12z'],
+ viewBox: '0 0 384 512'
+ },
}
\ No newline at end of file
diff --git a/client/src/store/accounting/accounting.actions.js b/client/src/store/accounting/accounting.actions.js
deleted file mode 100644
index a2be95e35..000000000
--- a/client/src/store/accounting/accounting.actions.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import ApiService from 'services/ApiService';
-import t from 'store/types';
-
-export const makeJournalEntries = ({ form }) => {
- return (dispatch) => new Promise((resolve, reject) => {
- ApiService.post('accounting/make-journal-entries', form).then((response) => {
- 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); });
- });
-}
\ No newline at end of file
diff --git a/client/src/store/accounting/accounting.reducers.js b/client/src/store/accounting/accounting.reducers.js
deleted file mode 100644
index 8d47a87cb..000000000
--- a/client/src/store/accounting/accounting.reducers.js
+++ /dev/null
@@ -1,19 +0,0 @@
-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];
-}
\ No newline at end of file
diff --git a/client/src/store/accounting/accounting.types.js b/client/src/store/accounting/accounting.types.js
deleted file mode 100644
index 84eae0701..000000000
--- a/client/src/store/accounting/accounting.types.js
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-export default {
- MAKE_JOURNAL_ENTRIES: 'MAKE_JOURNAL_ENTRIES',
- MANUAL_JOURNAL_SET: 'MANUAL_JOURNAL_SET',
-}
\ No newline at end of file
diff --git a/client/src/store/accounts/accounts.selectors.js b/client/src/store/accounts/accounts.selectors.js
index 56ff4c07f..6192243a2 100644
--- a/client/src/store/accounts/accounts.selectors.js
+++ b/client/src/store/accounts/accounts.selectors.js
@@ -1,9 +1,11 @@
-import {pickItemsFromIds} from 'store/selectors';
+import { pickItemsFromIds } from 'store/selectors';
export const getAccountsItems = (state, viewId) => {
- const accountsView = state.accounts.views[(viewId || -1)];
+
+ const accountsView = state.accounts.views[viewId || -1];
const accountsItems = state.accounts.items;
- return (typeof accountsView === 'object')
- ? (pickItemsFromIds(accountsItems, accountsView.ids) || []) : [];
-}
\ No newline at end of file
+ return typeof accountsView === 'object'
+ ? pickItemsFromIds(accountsItems, accountsView.ids) || []
+ : [];
+};
diff --git a/client/src/store/manualJournals/manualJournals.actions.js b/client/src/store/manualJournals/manualJournals.actions.js
new file mode 100644
index 000000000..4eeb4749c
--- /dev/null
+++ b/client/src/store/manualJournals/manualJournals.actions.js
@@ -0,0 +1,97 @@
+import ApiService from 'services/ApiService';
+import t from 'store/types';
+
+export const makeJournalEntries = ({ form }) => {
+ return (dispatch) => new Promise((resolve, reject) => {
+ ApiService.post('accounting/make-journal-entries', form).then((response) => {
+ 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); });
+ });
+};
+
+export const deleteManualJournal = ({ id }) => {
+ return (dispatch) =>
+ new Promise((resolve, reject) => {
+ ApiService.delete(`accounting/manual-journals/${id}`)
+ .then((response) => {
+ dispatch({
+ type: t.MANUAL_JOURNAL_REMOVE,
+ payload: { id },
+ });
+ resolve(response);
+ })
+ .catch((error) => { reject(error); });
+ });
+};
+
+export const publishManualJournal = ({ id }) => {
+ return (dispatch) =>
+ new Promise((resolve, reject) => {
+ ApiService.post(`accounting/manual-journals/${id}/publish`)
+ .then((response) => {
+ dispatch({
+ type: t.MANUAL_JOURNAL_PUBLISH,
+ payload: { id },
+ });
+ resolve(response);
+ })
+ .catch((error) => { reject(error); });
+ });
+}
+
+export const fetchManualJournalsTable = ({ query } = {}) => {
+ return (dispatch, getState) =>
+ new Promise((resolve, reject) => {
+ const pageQuery = getState().manualJournals.tableQuery;
+ dispatch({
+ type: t.MANUAL_JOURNALS_TABLE_LOADING,
+ loading: true,
+ });
+ ApiService.get('accounting/manual-journals', {
+ params: { ...pageQuery, ...query },
+ })
+ .then((response) => {
+
+ dispatch({
+ type: t.MANUAL_JOURNALS_PAGE_SET,
+ manual_journals: response.data.manualJournals,
+ customViewId: response.data.customViewId,
+ });
+ dispatch({
+ type: t.MANUAL_JOURNALS_ITEMS_SET,
+ manual_journals: response.data.manualJournals,
+ });
+ dispatch({
+ type: t.MANUAL_JOURNALS_TABLE_LOADING,
+ loading: false,
+ });
+ resolve(response);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+};
diff --git a/client/src/store/manualJournals/manualJournals.reducers.js b/client/src/store/manualJournals/manualJournals.reducers.js
new file mode 100644
index 000000000..25c94c6be
--- /dev/null
+++ b/client/src/store/manualJournals/manualJournals.reducers.js
@@ -0,0 +1,68 @@
+import t from 'store/types';
+import { createReducer } from '@reduxjs/toolkit';
+import { omit } from 'lodash';
+
+const initialState = {
+ items: {},
+ views: {},
+ loading: false,
+ currentViewId: -1,
+ tableQuery: {},
+};
+
+export default createReducer(initialState, {
+
+ [t.MANUAL_JOURNAL_SET]: (state, action) => {
+ const { id, manualJournal } = action.payload;
+ state.items[id] = manualJournal;
+ },
+
+ [t.MANUAL_JOURNAL_PUBLISH]: (state, action) => {
+ const { id } = action.payload;
+ const item = state.items[id] || {};
+
+ state.items[id] = {
+ ...item, status: 1,
+ };
+ },
+
+ [t.MANUAL_JOURNALS_ITEMS_SET]: (state, action) => {
+ const _manual_journals = {};
+
+ action.manual_journals.forEach((manual_journal) => {
+ _manual_journals[manual_journal.id] = manual_journal;
+ });
+ state.items = {
+ ...state.items,
+ ..._manual_journals,
+ };
+ },
+
+ [t.MANUAL_JOURNALS_PAGE_SET]: (state, action) => {
+ const viewId = action.customViewId || -1;
+ const view = state.views[viewId] || {};
+
+ state.views[viewId] = {
+ ...view,
+ ids: action.manual_journals.map((i) => i.id),
+ };
+ },
+
+ [t.MANUAL_JOURNALS_TABLE_LOADING]: (state, action) => {
+ state.loading = action.loading;
+ },
+
+ [t.MANUAL_JOURNALS_SET_CURRENT_VIEW]: (state, action) => {
+ state.currentViewId = action.currentViewId;
+ },
+
+ [t.MANUAL_JOURNAL_REMOVE]: (state, action) => {
+ const { id } = action.payload;
+ state.items = omit(state.items, [id]);
+ }
+});
+
+
+export const getManualJournal = (state, id) => {
+ return state.manualJournals.items[id];
+}
\ No newline at end of file
diff --git a/client/src/store/manualJournals/manualJournals.selectors.js b/client/src/store/manualJournals/manualJournals.selectors.js
new file mode 100644
index 000000000..d8dc48789
--- /dev/null
+++ b/client/src/store/manualJournals/manualJournals.selectors.js
@@ -0,0 +1,10 @@
+import { pickItemsFromIds } from 'store/selectors';
+
+export const getManualJournalsItems = (state, viewId) => {
+ const accountsView = state.manualJournals.views[viewId || -1];
+ const accountsItems = state.manualJournals.items;
+
+ return typeof accountsView === 'object'
+ ? pickItemsFromIds(accountsItems, accountsView.ids) || []
+ : [];
+};
diff --git a/client/src/store/manualJournals/manualJournals.types.js b/client/src/store/manualJournals/manualJournals.types.js
new file mode 100644
index 000000000..1bff8414d
--- /dev/null
+++ b/client/src/store/manualJournals/manualJournals.types.js
@@ -0,0 +1,13 @@
+export default {
+ MAKE_JOURNAL_ENTRIES: 'MAKE_JOURNAL_ENTRIES',
+ MANUAL_JOURNAL_SET: 'MANUAL_JOURNAL_SET',
+
+ MANUAL_JOURNALS_TABLE_LOADING: 'MANUAL_JOURNALS_TABLE_LOADING',
+ MANUAL_JOURNALS_PAGE_SET: 'MANUAL_JOURNALS_PAGE_SET',
+ MANUAL_JOURNALS_ITEMS_SET: 'MANUAL_JOURNALS_ITEMS_SET',
+ MANUAL_JOURNALS_SET_CURRENT_VIEW: 'MANUAL_JOURNALS_SET_CURRENT_VIEW',
+ MANUAL_JOURNALS_TABLE_QUERIES_ADD: 'MANUAL_JOURNALS_TABLE_QUERIES_ADD',
+ MANUAL_JOURNAL_REMOVE: 'MANUAL_JOURNAL_REMOVE',
+
+ MANUAL_JOURNAL_PUBLISH: 'MANUAL_JOURNAL_PUBLISH',
+};
diff --git a/client/src/store/reducers.js b/client/src/store/reducers.js
index e050e2d9f..a41666779 100644
--- a/client/src/store/reducers.js
+++ b/client/src/store/reducers.js
@@ -11,16 +11,16 @@ import expenses from './expenses/expenses.reducer';
import currencies from './currencies/currencies.reducer';
import resources from './resources/resources.reducer';
import financialStatements from './financialStatement/financialStatements.reducer';
-import itemCategories from './itemCategories/itemsCateory.reducer';
+import itemCategories from './itemCategories/itemsCategory.reducer';
import settings from './settings/settings.reducer';
-import accounting from './accounting/accounting.reducers';
+import manualJournals from './manualJournals/manualJournals.reducers';
export default combineReducers({
authentication,
dashboard,
users,
accounts,
- accounting,
+ manualJournals,
fields,
views,
expenses,
diff --git a/client/src/store/types.js b/client/src/store/types.js
index 29adce107..a157d63db 100644
--- a/client/src/store/types.js
+++ b/client/src/store/types.js
@@ -1,6 +1,6 @@
import authentication from './authentication/authentication.types';
import accounts from './accounts/accounts.types';
-import accounting from './accounting/accounting.types'
+import accounting from './manualJournals/manualJournals.types'
import currencies from './currencies/currencies.types';
import customFields from './customFields/customFields.types';
import customViews from './customViews/customViews.types';
diff --git a/client/src/style/App.scss b/client/src/style/App.scss
index 5ebc46caf..5d0c45e8d 100644
--- a/client/src/style/App.scss
+++ b/client/src/style/App.scss
@@ -35,6 +35,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import "pages/make-journal-entries";
@import "pages/preferences";
@import "pages/view-form";
+@import "pages/manual-journals";
// Views
@import "views/filter-dropdown";
diff --git a/client/src/style/components/data-table.scss b/client/src/style/components/data-table.scss
index f607ab572..f06e908c0 100644
--- a/client/src/style/components/data-table.scss
+++ b/client/src/style/components/data-table.scss
@@ -16,7 +16,7 @@
overflow-x: hidden;
.th{
- padding: 1rem 1.5rem;
+ padding: 1rem 0.5rem;
background: #F8FAFA;
font-size: 14px;
color: #666;
@@ -54,7 +54,6 @@
.bp3-control{
margin-bottom: 0;
}
-
.resizer {
display: inline-block;
background: transparent;
@@ -90,8 +89,8 @@
.tr .td{
border-bottom: 1px solid #E0E2E2;
- }
-
+ align-items: center;
+ }
.td.actions .#{$ns}-button{
background: #E6EFFB;
border: 0;
diff --git a/client/src/style/pages/manual-journals.scss b/client/src/style/pages/manual-journals.scss
new file mode 100644
index 000000000..5e130f152
--- /dev/null
+++ b/client/src/style/pages/manual-journals.scss
@@ -0,0 +1,26 @@
+
+
+.dashboard__insider--manual-journals{
+
+ .bigcapital-datatable{
+
+ .thead{
+
+ }
+
+ .tbody{
+ .amount > span{
+ font-weight: 600;
+ }
+ .note{
+ .bp3-icon{
+ color: #666;
+ }
+ }
+ .status{
+ font-size: 13px;
+ text-transform: uppercase;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/src/http/controllers/Accounting.js b/server/src/http/controllers/Accounting.js
index dc7165a69..78d59150e 100644
--- a/server/src/http/controllers/Accounting.js
+++ b/server/src/http/controllers/Accounting.js
@@ -433,11 +433,15 @@ export default {
errors: [{ type: 'MANUAL.JOURNAL.NOT.FOUND', code: 100 }],
});
}
- if (!manualJournal.status) {
+ if (manualJournal.status) {
return res.status(400).send({
errors: [{ type: 'MANUAL.JOURNAL.PUBLISHED.ALREADY', code: 200 }],
});
}
+ const updateJournalTransactionOper = ManualJournal.query()
+ .where('id', manualJournal.id)
+ .update({ status: 1 });
+
const transactions = await AccountTransaction.query()
.whereIn('reference_type', ['Journal', 'ManualJournal'])
.where('reference_id', manualJournal.id)
@@ -452,6 +456,7 @@ export default {
.update({ draft: 0 });
await Promise.all([
+ updateJournalTransactionOper,
updateAccountsTransactionsOper,
journal.saveBalance(),
]);