feat: balance sheet report.

feat: trial balance sheet.
feat: general ledger report.
feat: journal report.
feat: profit/loss report.
This commit is contained in:
a.bouhuolia
2020-12-30 20:39:17 +02:00
parent de9f6d9521
commit 7ae73ed6cd
62 changed files with 2403 additions and 1850 deletions

View File

@@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect } from 'react';
import { useQuery } from 'react-query';
import moment from 'moment';
import { useIntl } from 'react-intl';
import { queryCache } from 'react-query';
import { compose } from 'utils';
import JournalTable from './JournalTable';
@@ -12,79 +13,89 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withSettings from 'containers/Settings/withSettings';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withJournalActions from './withJournalActions';
import withJournal from './withJournal';
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
function Journal({
// #withJournalActions
requestFetchJournalSheet,
refreshJournalSheet,
// #withJournal
journalSheetRefresh,
// #withDashboardActions
changePageTitle,
setDashboardBackLink,
// #withPreferences
organizationSettings,
organizationName,
}) {
const [filter, setFilter] = useState({
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
});
const { formatMessage } = useIntl();
const fetchJournalSheet = useQuery(['journal-sheet', filter], (key, query) =>
requestFetchJournalSheet({
...transformFilterFormToQuery(filter),
}),
);
useEffect(() => {
changePageTitle(formatMessage({ id: 'journal_sheet' }));
}, [changePageTitle, formatMessage]);
const fetchHook = useQuery(
['journal', filter],
(key, query) => requestFetchJournalSheet(query),
{ manual: true },
);
useEffect(() => {
// Show the back link on dashboard topbar.
setDashboardBackLink(true);
return () => {
// Hide the back link on dashboard topbar.
setDashboardBackLink(false);
};
});
useEffect(() => {
if (journalSheetRefresh) {
queryCache.invalidateQueries('journal-sheet');
refreshJournalSheet(false);
}
}, [journalSheetRefresh, refreshJournalSheet]);
// Handle financial statement filter change.
const handleFilterSubmit = useCallback(
(filter) => {
const _filter = {
...filter,
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
};
setFilter(_filter);
fetchHook.refetch({ force: true });
queryCache.invalidateQueries('journal-sheet');
},
[fetchHook],
[setFilter],
);
const handlePrintClick = useCallback(() => {}, []);
const handleExportClick = useCallback(() => {}, []);
const handleFetchData = useCallback(({ sortBy, pageIndex, pageSize }) => {
fetchHook.refetch({ force: true });
}, []);
return (
<DashboardInsider>
<JournalActionsBar
onSubmitFilter={handleFilterSubmit}
onPrintClick={handlePrintClick}
onExportClick={handleExportClick}
/>
<JournalActionsBar />
<DashboardPageContent>
<div class="financial-statement financial-statement--journal">
<JournalHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit}
pageFilter={filter}
/>
<div class="financial-statement__body">
<JournalTable
companyName={organizationSettings.name}
companyName={organizationName}
journalQuery={filter}
onFetchData={handleFetchData}
/>
</div>
</div>
@@ -96,5 +107,10 @@ function Journal({
export default compose(
withDashboardActions,
withJournalActions,
withSettings,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
})),
withJournal(({ journalSheetRefresh }) => ({
journalSheetRefresh,
})),
)(Journal);

View File

@@ -11,16 +11,16 @@ import {
import { FormattedMessage as T } from 'react-intl';
import Icon from 'components/Icon';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import FilterDropdown from 'components/FilterDropdown';
import classNames from 'classnames';
import { If } from 'components';
import withJournalActions from './withJournalActions';
import withJournal from './withJournal';
import { compose } from 'utils';
/**
* Journal sheeet - Actions bar.
*/
function JournalActionsBar({
// #withJournal
journalSheetFilter,
@@ -40,36 +40,28 @@ function JournalActionsBar({
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={classNames(Classes.MINIMAL, 'button--table-views')}
icon={<Icon icon="cog-16" iconSize={16} />}
text={<T id={'customize_report'} />}
/>
<NavbarDivider />
<Button
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
text={<T id={'recalc_report'} />}
onClick={handleRecalcReport}
icon={<Icon icon="refresh-16" iconSize={16} />}
/>
<If condition={journalSheetFilter}>
<Button
className={Classes.MINIMAL}
text={<T id={'hide_filter'} />}
icon={<Icon icon="arrow-to-top" />}
onClick={handleFilterToggleClick}
/>
</If>
<NavbarDivider />
<If condition={!journalSheetFilter}>
<Button
className={Classes.MINIMAL}
text={<T id={'show_filter'} />}
icon={<Icon icon="arrow-to-bottom" />}
onClick={handleFilterToggleClick}
/>
</If>
<Button
className={classNames(Classes.MINIMAL, 'button--table-views')}
icon={<Icon icon="cog-16" iconSize={16} />}
text={
(journalSheetFilter) ? (
<T id={'hide_customizer'} />
) : (
<T id={'customize_report'} />
)
}
active={journalSheetFilter}
onClick={handleFilterToggleClick}
/>
<NavbarDivider />
<Popover
interactionKind={PopoverInteractionKind.CLICK}

View File

@@ -1,12 +1,12 @@
import React, { useCallback, useEffect } from 'react';
import { Row, Col } from 'react-grid-system';
import { Button } from '@blueprintjs/core';
import React from 'react';
import moment from 'moment';
import { useFormik } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { Formik, Form } from 'formik';
import { Tab, Tabs, Button, Intent } from '@blueprintjs/core';
import * as Yup from 'yup';
import { FormattedMessage as T } from 'react-intl';
import JournalSheetHeaderGeneralPanel from './JournalSheetHeaderGeneralPanel';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import withJournal from './withJournal';
@@ -23,49 +23,75 @@ function JournalHeader({
// #withJournalActions
refreshJournalSheet,
toggleJournalSheetFilter,
// #withJournal
journalSheetFilter,
journalSheetRefresh,
}) {
const formik = useFormik({
enableReinitialize: true,
initialValues: {
...pageFilter,
from_date: moment(pageFilter.from_date).toDate(),
to_date: moment(pageFilter.to_date).toDate(),
},
validationSchema: Yup.object().shape({
from_date: Yup.date().required(),
to_date: Yup.date().min(Yup.ref('from_date')).required(),
}),
onSubmit: (values, { setSubmitting }) => {
onSubmitFilter(values);
setSubmitting(false);
},
const initialValues = {
...pageFilter,
fromDate: moment(pageFilter.fromDate).toDate(),
toDate: moment(pageFilter.toDate).toDate(),
};
// Validation schema.
const validationSchema = Yup.object().shape({
fromDate: Yup.date().required(),
toDate: Yup.date().min(Yup.ref('fromDate')).required(),
});
useEffect(() => {
if (journalSheetRefresh) {
formik.submitForm();
refreshJournalSheet(false);
}
}, [formik, journalSheetRefresh]);
// Handle form submit.
const handleSubmit = (values, { setSubmitting }) => {
onSubmitFilter(values);
setSubmitting(false);
toggleJournalSheetFilter();
};
// Handle cancel journal drawer header.
const handleCancelClick = () => {
toggleJournalSheetFilter();
};
const handleDrawerClose = () => {
toggleJournalSheetFilter();
};
return (
<FinancialStatementHeader show={journalSheetFilter}>
<Row>
<FinancialStatementDateRange formik={formik} />
</Row>
<FinancialStatementHeader
isOpen={journalSheetFilter}
drawerProps={{ onClose: handleDrawerClose }}
>
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validationSchema={validationSchema}
>
<Form>
<Tabs animate={true} vertical={true} renderActiveTabPanelOnly={true}>
<Tab
id="general"
title={'General'}
panel={<JournalSheetHeaderGeneralPanel />}
/>
</Tabs>
<div class="financial-header-drawer__footer">
<Button className={'mr1'} intent={Intent.PRIMARY} type={'submit'}>
<T id={'calculate_report'} />
</Button>
<Button onClick={handleCancelClick} minimal={true}>
<T id={'cancel'} />
</Button>
</div>
</Form>
</Formik>
</FinancialStatementHeader>
);
}
export default compose(
withJournal(({
journalSheetFilter,
journalSheetRefresh
}) => ({
withJournal(({ journalSheetFilter, journalSheetRefresh }) => ({
journalSheetFilter,
journalSheetRefresh,
})),

View File

@@ -0,0 +1,10 @@
import React from 'react';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
export default function JournalSheetHeaderGeneralPanel({}) {
return (
<div>
<FinancialStatementDateRange />
</div>
);
}

View File

@@ -1,17 +1,15 @@
import React, { useCallback, useMemo } from 'react';
import { connect } from 'react-redux';
import moment from 'moment';
import { useIntl } from 'react-intl';
import FinancialSheet from 'components/FinancialSheet';
import DataTable from 'components/DataTable';
import { compose, defaultExpanderReducer } from 'utils';
import Money from 'components/Money';
import { getFinancialSheetIndexByQuery } from 'store/financialStatement/financialStatements.selectors';
import withJournal from './withJournal';
import { compose, defaultExpanderReducer } from 'utils';
function JournalSheetTable({
// #withJournal
journalSheetTableRows,
@@ -106,12 +104,12 @@ function JournalSheetTable({
return (
<FinancialSheet
companyName={companyName}
// sheetType={formatMessage({ id: 'journal_sheet' })}
sheetType={formatMessage({ id: 'journal_sheet' })}
fromDate={journalSheetQuery.from_date}
toDate={journalSheetQuery.to_date}
name="journal"
loading={journalSheetLoading}
minimal={true}
// minimal={true}
fullWidth={true}
>
<DataTable
@@ -129,20 +127,7 @@ function JournalSheetTable({
);
}
const mapStateToProps = (state, props) => {
const { journalQuery } = props;
return {
journalIndex: getFinancialSheetIndexByQuery(
state.financialStatements.journal.sheets,
journalQuery,
),
};
};
const withJournalTable = connect(mapStateToProps);
export default compose(
withJournalTable,
withJournal(
({ journalSheetTableRows, journalSheetLoading, journalSheetQuery }) => ({
journalSheetTableRows,

View File

@@ -1,34 +1,27 @@
import {connect} from 'react-redux';
import { connect } from 'react-redux';
import {
getFinancialSheetIndexByQuery,
getFinancialSheet,
getFinancialSheetTableRows,
getFinancialSheetQuery,
getFinancialSheetFactory,
getFinancialSheetTableRowsFactory,
getFinancialSheetQueryFactory,
} from 'store/financialStatement/financialStatements.selectors';
export default (mapState) => {
const mapStateToProps = (state, props) => {
const { journalIndex } = props;
const getJournalSheet = getFinancialSheetFactory('journal');
const getJournalSheetTableRows = getFinancialSheetTableRowsFactory(
'journal',
);
const getJournalSheetQuery = getFinancialSheetQueryFactory('journal');
const mapped = {
journalSheet: getFinancialSheet(
state.financialStatements.journal.sheets,
journalIndex
),
journalSheetTableRows: getFinancialSheetTableRows(
state.financialStatements.journal.sheets,
journalIndex
),
journalSheetQuery: getFinancialSheetQuery(
state.financialStatements.journal.sheets,
journalIndex,
),
journalSheet: getJournalSheet(state, props),
journalSheetTableRows: getJournalSheetTableRows(state, props),
journalSheetQuery: getJournalSheetQuery(state, props),
journalSheetLoading: state.financialStatements.journal.loading,
journalSheetFilter: state.financialStatements.journal.filter,
journalSheetRefresh: state.financialStatements.journal.refresh,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};