mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 15:50:32 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital
This commit is contained in:
@@ -49,7 +49,6 @@ const CLASSES = {
|
|||||||
|
|
||||||
SELECT_LIST_FILL_POPOVER: 'select-list--fill-popover',
|
SELECT_LIST_FILL_POPOVER: 'select-list--fill-popover',
|
||||||
|
|
||||||
|
|
||||||
PREFERENCES_PAGE: 'preferences-page',
|
PREFERENCES_PAGE: 'preferences-page',
|
||||||
PREFERENCES_PAGE_SIDEBAR: 'preferences-page__sidebar',
|
PREFERENCES_PAGE_SIDEBAR: 'preferences-page__sidebar',
|
||||||
PREFERENCES_PAGE_TOPBAR: 'preferences-page__topbar',
|
PREFERENCES_PAGE_TOPBAR: 'preferences-page__topbar',
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ export default function DataTable({
|
|||||||
initialState: {
|
initialState: {
|
||||||
pageIndex: initialPageIndex,
|
pageIndex: initialPageIndex,
|
||||||
pageSize: initialPageSize,
|
pageSize: initialPageSize,
|
||||||
|
expanded
|
||||||
},
|
},
|
||||||
manualPagination,
|
manualPagination,
|
||||||
pageCount: controlledPageCount,
|
pageCount: controlledPageCount,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import EmptyStatus from './EmptyStatus';
|
|||||||
import DashboardCard from './Dashboard/DashboardCard';
|
import DashboardCard from './Dashboard/DashboardCard';
|
||||||
import InputPrependText from './Forms/InputPrependText';
|
import InputPrependText from './Forms/InputPrependText';
|
||||||
import PageFormBigNumber from './PageFormBigNumber';
|
import PageFormBigNumber from './PageFormBigNumber';
|
||||||
|
import AccountsMultiSelect from './AccountsMultiSelect';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
@@ -87,5 +88,6 @@ export {
|
|||||||
EmptyStatus,
|
EmptyStatus,
|
||||||
DashboardCard,
|
DashboardCard,
|
||||||
InputPrependText,
|
InputPrependText,
|
||||||
PageFormBigNumber
|
PageFormBigNumber,
|
||||||
|
AccountsMultiSelect,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { compose } from 'utils';
|
|||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
import BalanceSheetHeader from './BalanceSheetHeader';
|
import BalanceSheetHeader from './BalanceSheetHeader';
|
||||||
import BalanceSheetTable from './BalanceSheetTable';
|
import BalanceSheetTable from './BalanceSheetTable';
|
||||||
@@ -18,66 +19,74 @@ import withSettings from 'containers/Settings/withSettings';
|
|||||||
import withBalanceSheetActions from './withBalanceSheetActions';
|
import withBalanceSheetActions from './withBalanceSheetActions';
|
||||||
import withBalanceSheetDetail from './withBalanceSheetDetail';
|
import withBalanceSheetDetail from './withBalanceSheetDetail';
|
||||||
|
|
||||||
|
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
|
||||||
|
|
||||||
function BalanceSheet({
|
function BalanceSheet({
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
setDashboardBackLink,
|
||||||
|
|
||||||
// #withBalanceSheetActions
|
// #withBalanceSheetActions
|
||||||
fetchBalanceSheet,
|
fetchBalanceSheet,
|
||||||
|
refreshBalanceSheet,
|
||||||
|
|
||||||
// #withBalanceSheetDetail
|
// #withBalanceSheetDetail
|
||||||
balanceSheetFilter,
|
balanceSheetRefresh,
|
||||||
|
|
||||||
// #withPreferences
|
// #withPreferences
|
||||||
organizationSettings,
|
organizationName,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const [filter, setFilter] = useState({
|
const [filter, setFilter] = useState({
|
||||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
basis: 'cash',
|
basis: 'cash',
|
||||||
display_columns_type: 'total',
|
displayColumnsType: 'total',
|
||||||
display_columns_by: '',
|
accountsFilter: 'all-accounts',
|
||||||
none_zero: false,
|
|
||||||
});
|
});
|
||||||
const [refresh, setRefresh] = useState(true);
|
|
||||||
|
|
||||||
const fetchHook = useQuery(
|
// Fetches the balance sheet.
|
||||||
['balance-sheet', filter],
|
const fetchHook = useQuery(['balance-sheet', filter], (key, query) =>
|
||||||
(key, query) => fetchBalanceSheet({ ...query }),
|
fetchBalanceSheet({ ...transformFilterFormToQuery(query) }),
|
||||||
{ manual: true },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle fetch the data of balance sheet.
|
|
||||||
const handleFetchData = useCallback(() => {
|
|
||||||
setRefresh(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(formatMessage({ id: 'balance_sheet' }));
|
changePageTitle(formatMessage({ id: 'balance_sheet' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
|
// Observes the balance sheet refresh to invalid the query to refresh it.
|
||||||
|
useEffect(() => {
|
||||||
|
if (balanceSheetRefresh) {
|
||||||
|
queryCache.invalidateQueries('balance-sheet');
|
||||||
|
refreshBalanceSheet(false);
|
||||||
|
}
|
||||||
|
}, [balanceSheetRefresh, refreshBalanceSheet]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Show the back link on dashboard topbar.
|
||||||
|
setDashboardBackLink(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Hide the back link on dashboard topbar.
|
||||||
|
setDashboardBackLink(false);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Handle re-fetch balance sheet after filter change.
|
// Handle re-fetch balance sheet after filter change.
|
||||||
const handleFilterSubmit = useCallback(
|
const handleFilterSubmit = useCallback(
|
||||||
(filter) => {
|
(filter) => {
|
||||||
const _filter = {
|
const _filter = {
|
||||||
...filter,
|
...filter,
|
||||||
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
|
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
|
||||||
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
|
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
|
||||||
};
|
};
|
||||||
setFilter({ ..._filter });
|
setFilter({ ..._filter });
|
||||||
setRefresh(true);
|
refreshBalanceSheet(true);
|
||||||
},
|
},
|
||||||
[setFilter],
|
[setFilter, refreshBalanceSheet],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (refresh) {
|
|
||||||
fetchHook.refetch({ force: true });
|
|
||||||
setRefresh(false);
|
|
||||||
}
|
|
||||||
}, [refresh]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider>
|
<DashboardInsider>
|
||||||
<BalanceSheetActionsBar />
|
<BalanceSheetActionsBar />
|
||||||
@@ -87,15 +96,9 @@ function BalanceSheet({
|
|||||||
<BalanceSheetHeader
|
<BalanceSheetHeader
|
||||||
pageFilter={filter}
|
pageFilter={filter}
|
||||||
onSubmitFilter={handleFilterSubmit}
|
onSubmitFilter={handleFilterSubmit}
|
||||||
show={balanceSheetFilter}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="financial-statement__body">
|
<div class="financial-statement__body">
|
||||||
<BalanceSheetTable
|
<BalanceSheetTable companyName={organizationName} />
|
||||||
companyName={organizationSettings.name}
|
|
||||||
balanceSheetQuery={filter}
|
|
||||||
onFetchData={handleFetchData}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</FinancialStatement>
|
</FinancialStatement>
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
@@ -106,8 +109,10 @@ function BalanceSheet({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withBalanceSheetActions,
|
withBalanceSheetActions,
|
||||||
withBalanceSheetDetail(({ balanceSheetFilter }) => ({
|
withBalanceSheetDetail(({ balanceSheetRefresh }) => ({
|
||||||
balanceSheetFilter,
|
balanceSheetRefresh,
|
||||||
|
})),
|
||||||
|
withSettings(({ organizationSettings }) => ({
|
||||||
|
organizationName: organizationSettings.name,
|
||||||
})),
|
})),
|
||||||
withSettings,
|
|
||||||
)(BalanceSheet);
|
)(BalanceSheet);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
Button,
|
Button,
|
||||||
@@ -13,26 +13,24 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import FilterDropdown from 'components/FilterDropdown';
|
|
||||||
import { If } from 'components';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import withBalanceSheetDetail from './withBalanceSheetDetail';
|
import withBalanceSheetDetail from './withBalanceSheetDetail';
|
||||||
import withBalanceSheetActions from './withBalanceSheetActions';
|
import withBalanceSheetActions from './withBalanceSheetActions';
|
||||||
|
|
||||||
|
|
||||||
function BalanceSheetActionsBar({
|
function BalanceSheetActionsBar({
|
||||||
// #withBalanceSheetDetail
|
// #withBalanceSheetDetail
|
||||||
balanceSheetFilter,
|
balanceSheetFilter,
|
||||||
|
|
||||||
// #withBalanceSheetActions
|
// #withBalanceSheetActions
|
||||||
toggleBalanceSheetFilter,
|
toggleBalanceSheetFilter,
|
||||||
refreshBalanceSheet
|
refreshBalanceSheet,
|
||||||
}) {
|
}) {
|
||||||
const handleFilterToggleClick = () => {
|
const handleFilterToggleClick = () => {
|
||||||
toggleBalanceSheetFilter();
|
toggleBalanceSheetFilter();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle recalculate the report button.
|
||||||
const handleRecalcReport = () => {
|
const handleRecalcReport = () => {
|
||||||
refreshBalanceSheet(true);
|
refreshBalanceSheet(true);
|
||||||
};
|
};
|
||||||
@@ -41,39 +39,21 @@ function BalanceSheetActionsBar({
|
|||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
|
||||||
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'} />}
|
text={<T id={'recalc_report'} />}
|
||||||
onClick={handleRecalcReport}
|
onClick={handleRecalcReport}
|
||||||
icon={<Icon icon="refresh-16" iconSize={16} />}
|
icon={<Icon icon="refresh-16" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
<If condition={balanceSheetFilter}>
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
text={<T id={'hide_filter'} />}
|
icon={<Icon icon="cog-16" iconSize={16} />}
|
||||||
|
text={!balanceSheetFilter ? <T id={'customize_report'} /> : <T id={'hide_customizer'} />}
|
||||||
onClick={handleFilterToggleClick}
|
onClick={handleFilterToggleClick}
|
||||||
icon={<Icon icon="arrow-to-top" />}
|
active={balanceSheetFilter}
|
||||||
/>
|
/>
|
||||||
</If>
|
<NavbarDivider />
|
||||||
|
|
||||||
<If condition={!balanceSheetFilter}>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
text={<T id={'show_filter'} />}
|
|
||||||
onClick={handleFilterToggleClick}
|
|
||||||
icon={<Icon icon="arrow-to-bottom" />}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
// content={}
|
// content={}
|
||||||
@@ -91,7 +71,7 @@ function BalanceSheetActionsBar({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='print-16' iconSize={16} />}
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
text={<T id={'print'} />}
|
text={<T id={'print'} />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,126 +1,105 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||||
import { Row, Col, Visible } from 'react-grid-system';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { FormGroup } from '@blueprintjs/core';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useFormik } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||||
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
|
|
||||||
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
|
||||||
import FinancialAccountsFilter from '../FinancialAccountsFilter';
|
|
||||||
|
|
||||||
import withBalanceSheet from './withBalanceSheetDetail';
|
import withBalanceSheet from './withBalanceSheetDetail';
|
||||||
import withBalanceSheetActions from './withBalanceSheetActions';
|
import withBalanceSheetActions from './withBalanceSheetActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
import BalanceSheetHeaderGeneralPanal from './BalanceSheetHeaderGeneralPanal';
|
||||||
|
|
||||||
function BalanceSheetHeader({
|
function BalanceSheetHeader({
|
||||||
|
// #ownProps
|
||||||
onSubmitFilter,
|
onSubmitFilter,
|
||||||
pageFilter,
|
pageFilter,
|
||||||
show,
|
|
||||||
refresh,
|
// #withBalanceSheet
|
||||||
|
balanceSheetFilter,
|
||||||
|
|
||||||
// #withBalanceSheetActions
|
// #withBalanceSheetActions
|
||||||
refreshBalanceSheet,
|
toggleBalanceSheetFilter,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const formik = useFormik({
|
// Filter form initial values.
|
||||||
enableReinitialize: true,
|
const initialValues = {
|
||||||
initialValues: {
|
|
||||||
...pageFilter,
|
|
||||||
basis: 'cash',
|
basis: 'cash',
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
...pageFilter,
|
||||||
to_date: moment(pageFilter.to_date).toDate(),
|
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||||
none_zero: false,
|
toDate: moment(pageFilter.toDate).toDate(),
|
||||||
},
|
};
|
||||||
validationSchema: Yup.object().shape({
|
|
||||||
from_date: Yup.date()
|
// Validation schema.
|
||||||
|
const validationSchema = Yup.object().shape({
|
||||||
|
dateRange: Yup.string().optional(),
|
||||||
|
fromDate: Yup.date()
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'from_data' })),
|
.label(formatMessage({ id: 'fromDate' })),
|
||||||
to_date: Yup.date()
|
toDate: Yup.date()
|
||||||
.min(Yup.ref('from_date'))
|
.min(Yup.ref('fromDate'))
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'to_date' })),
|
.label(formatMessage({ id: 'toDate' })),
|
||||||
none_zero: Yup.boolean(),
|
accountsFilter: Yup.string(),
|
||||||
}),
|
displayColumnsType: Yup.string(),
|
||||||
onSubmit: (values, actions) => {
|
|
||||||
onSubmitFilter(values);
|
|
||||||
actions.setSubmitting(false);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle item select of `display columns by` field.
|
// Handle form submit.
|
||||||
const onItemSelectDisplayColumns = useCallback(
|
const handleSubmit = (values, actions) => {
|
||||||
(item) => {
|
onSubmitFilter(values);
|
||||||
formik.setFieldValue('display_columns_type', item.type);
|
toggleBalanceSheetFilter();
|
||||||
formik.setFieldValue('display_columns_by', item.by);
|
actions.setSubmitting(false);
|
||||||
},
|
};
|
||||||
[formik],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAccountingBasisChange = useCallback(
|
// Handle cancel button click.
|
||||||
(value) => {
|
const handleCancelClick = () => {
|
||||||
formik.setFieldValue('basis', value);
|
toggleBalanceSheetFilter();
|
||||||
},
|
};
|
||||||
[formik],
|
// Handle drawer close action.
|
||||||
);
|
const handleDrawerClose = () => {
|
||||||
|
toggleBalanceSheetFilter();
|
||||||
useEffect(() => {
|
|
||||||
if (refresh) {
|
|
||||||
formik.submitForm();
|
|
||||||
refreshBalanceSheet(false);
|
|
||||||
}
|
|
||||||
}, [refresh]);
|
|
||||||
|
|
||||||
const handleAccountsFilterSelect = (filterType) => {
|
|
||||||
const noneZero = filterType.key === 'without-zero-balance' ? true : false;
|
|
||||||
formik.setFieldValue('none_zero', noneZero);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader show={show}>
|
<FinancialStatementHeader
|
||||||
<Row>
|
isOpen={balanceSheetFilter}
|
||||||
<FinancialStatementDateRange formik={formik} />
|
drawerProps={{ onClose: handleDrawerClose }}
|
||||||
|
|
||||||
<Visible xl>
|
|
||||||
<Col width={'100%'} />
|
|
||||||
</Visible>
|
|
||||||
|
|
||||||
<Col width={260} offset={10}>
|
|
||||||
<SelectDisplayColumnsBy onItemSelect={onItemSelectDisplayColumns} />
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col width={260}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'filter_accounts'} />}
|
|
||||||
className="form-group--select-list bp3-fill"
|
|
||||||
inline={false}
|
|
||||||
>
|
>
|
||||||
<FinancialAccountsFilter
|
<Formik
|
||||||
initialSelectedItem={'all-accounts'}
|
initialValues={initialValues}
|
||||||
onItemSelect={handleAccountsFilterSelect}
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<Tabs animate={true} vertical={true} renderActiveTabPanelOnly={true}>
|
||||||
|
<Tab
|
||||||
|
id="general"
|
||||||
|
title={<T id={'general'} />}
|
||||||
|
panel={<BalanceSheetHeaderGeneralPanal />}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</Tabs>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col width={260}>
|
<div class="financial-header-drawer__footer">
|
||||||
<RadiosAccountingBasis
|
<Button className={'mr1'} intent={Intent.PRIMARY} type={'submit'}>
|
||||||
selectedValue={formik.values.basis}
|
<T id={'calculate_report'} />
|
||||||
onChange={handleAccountingBasisChange}
|
</Button>
|
||||||
/>
|
<Button onClick={handleCancelClick} minimal={true}>
|
||||||
</Col>
|
<T id={'cancel'} />
|
||||||
</Row>
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
</FinancialStatementHeader>
|
</FinancialStatementHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withBalanceSheet(({ balanceSheetRefresh }) => ({
|
withBalanceSheet(({ balanceSheetFilter }) => ({
|
||||||
refresh: balanceSheetRefresh,
|
balanceSheetFilter,
|
||||||
})),
|
})),
|
||||||
withBalanceSheetActions,
|
withBalanceSheetActions,
|
||||||
)(BalanceSheetHeader);
|
)(BalanceSheetHeader);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
|
||||||
|
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
||||||
|
import FinancialAccountsFilter from '../FinancialAccountsFilter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Balance sheet header - General panal.
|
||||||
|
*/
|
||||||
|
export default function BalanceSheetHeaderGeneralTab({}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FinancialStatementDateRange />
|
||||||
|
<SelectDisplayColumnsBy />
|
||||||
|
<FinancialAccountsFilter
|
||||||
|
initialSelectedItem={'all-accounts'}
|
||||||
|
/>
|
||||||
|
<RadiosAccountingBasis key={'basis'} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,31 +1,56 @@
|
|||||||
import React, { useMemo, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
|
||||||
import Money from 'components/Money';
|
import Money from 'components/Money';
|
||||||
import FinancialSheet from 'components/FinancialSheet';
|
import FinancialSheet from 'components/FinancialSheet';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
|
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
|
||||||
import withBalanceSheetDetail from './withBalanceSheetDetail';
|
import withBalanceSheetDetail from './withBalanceSheetDetail';
|
||||||
import { getFinancialSheetIndexByQuery } from 'store/financialStatement/financialStatements.selectors';
|
|
||||||
|
|
||||||
import { compose, defaultExpanderReducer } from 'utils';
|
import { compose, defaultExpanderReducer, getColumnWidth } from 'utils';
|
||||||
|
|
||||||
|
// Total cell.
|
||||||
|
function TotalCell({ cell }) {
|
||||||
|
const row = cell.row.original;
|
||||||
|
|
||||||
|
if (row.total) {
|
||||||
|
return (
|
||||||
|
<Money
|
||||||
|
amount={row.total.formatted_amount}
|
||||||
|
currency={row.total.currency_code}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total period cell.
|
||||||
|
const TotalPeriodCell = (index) => ({ cell }) => {
|
||||||
|
const { original } = cell.row;
|
||||||
|
|
||||||
|
if (original.total_periods && original.total_periods[index]) {
|
||||||
|
const amount = original.total_periods[index].formatted_amount;
|
||||||
|
const currencyCode = original.total_periods[index].currency_code;
|
||||||
|
|
||||||
|
return <Money amount={amount} currency={currencyCode} />;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Balance sheet table.
|
||||||
|
*/
|
||||||
function BalanceSheetTable({
|
function BalanceSheetTable({
|
||||||
// #withPreferences
|
|
||||||
organizationSettings,
|
|
||||||
|
|
||||||
// #withBalanceSheetDetail
|
// #withBalanceSheetDetail
|
||||||
balanceSheetAccounts,
|
|
||||||
balanceSheetTableRows,
|
balanceSheetTableRows,
|
||||||
balanceSheetColumns,
|
balanceSheetColumns,
|
||||||
balanceSheetQuery,
|
balanceSheetQuery,
|
||||||
balanceSheetLoading,
|
balanceSheetLoading,
|
||||||
|
|
||||||
// #ownProps
|
// #ownProps
|
||||||
onFetchData,
|
companyName,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
@@ -33,35 +58,18 @@ function BalanceSheetTable({
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'account_name' }),
|
Header: formatMessage({ id: 'account_name' }),
|
||||||
accessor: 'name',
|
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
|
||||||
className: 'account_name',
|
className: 'account_name',
|
||||||
width: 120,
|
width: 240,
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'code' }),
|
|
||||||
accessor: 'code',
|
|
||||||
className: 'code',
|
|
||||||
width: 60,
|
|
||||||
},
|
},
|
||||||
...(balanceSheetQuery.display_columns_type === 'total'
|
...(balanceSheetQuery.display_columns_type === 'total'
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'total' }),
|
Header: formatMessage({ id: 'total' }),
|
||||||
accessor: 'balance.formatted_amount',
|
accessor: 'balance.formatted_amount',
|
||||||
Cell: ({ cell }) => {
|
Cell: TotalCell,
|
||||||
const row = cell.row.original;
|
|
||||||
if (row.total) {
|
|
||||||
return (
|
|
||||||
<Money
|
|
||||||
amount={row.total.formatted_amount}
|
|
||||||
currency={'USD'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
className: 'total',
|
className: 'total',
|
||||||
width: 80,
|
width: 140,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
@@ -70,44 +78,43 @@ function BalanceSheetTable({
|
|||||||
id: `date_period_${index}`,
|
id: `date_period_${index}`,
|
||||||
Header: column,
|
Header: column,
|
||||||
accessor: `total_periods[${index}]`,
|
accessor: `total_periods[${index}]`,
|
||||||
Cell: ({ cell }) => {
|
Cell: TotalPeriodCell(index),
|
||||||
const { original } = cell.row;
|
|
||||||
if (original.total_periods && original.total_periods[index]) {
|
|
||||||
const amount = original.total_periods[index].formatted_amount;
|
|
||||||
return <Money amount={amount} currency={'USD'} />;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
className: classNames('total-period', `total-periods-${index}`),
|
className: classNames('total-period', `total-periods-${index}`),
|
||||||
width: 80,
|
width: getColumnWidth(
|
||||||
|
balanceSheetTableRows,
|
||||||
|
`total_periods.${index}.formatted_amount`,
|
||||||
|
{ minWidth: 100 },
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
[balanceSheetQuery, balanceSheetColumns, formatMessage],
|
[balanceSheetQuery, balanceSheetColumns, balanceSheetTableRows, formatMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFetchData = useCallback(() => {
|
|
||||||
onFetchData && onFetchData();
|
|
||||||
}, [onFetchData]);
|
|
||||||
|
|
||||||
// Calculates the default expanded rows of balance sheet table.
|
// Calculates the default expanded rows of balance sheet table.
|
||||||
const expandedRows = useMemo(
|
const expandedRows = useMemo(
|
||||||
() => defaultExpanderReducer(balanceSheetTableRows, 3),
|
() => defaultExpanderReducer(balanceSheetTableRows, 4),
|
||||||
[balanceSheetTableRows],
|
[balanceSheetTableRows],
|
||||||
);
|
);
|
||||||
|
|
||||||
const rowClassNames = (row) => {
|
const rowClassNames = useCallback((row) => {
|
||||||
const { original } = row;
|
const { original } = row;
|
||||||
console.log(row);
|
const rowTypes = Array.isArray(original.row_types)
|
||||||
|
? original.row_types
|
||||||
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[`row_type--${original.row_type}`]: original.row_type,
|
...rowTypes.reduce((acc, rowType) => {
|
||||||
};
|
acc[`row_type--${rowType}`] = rowType;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
};
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialSheet
|
<FinancialSheet
|
||||||
name="balance-sheet"
|
name="balance-sheet"
|
||||||
companyName={organizationSettings.name}
|
companyName={companyName}
|
||||||
sheetType={formatMessage({ id: 'balance_sheet' })}
|
sheetType={formatMessage({ id: 'balance_sheet' })}
|
||||||
fromDate={balanceSheetQuery.from_date}
|
fromDate={balanceSheetQuery.from_date}
|
||||||
toDate={balanceSheetQuery.to_date}
|
toDate={balanceSheetQuery.to_date}
|
||||||
@@ -119,46 +126,29 @@ function BalanceSheetTable({
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
data={balanceSheetTableRows}
|
data={balanceSheetTableRows}
|
||||||
rowClassNames={rowClassNames}
|
rowClassNames={rowClassNames}
|
||||||
onFetchData={handleFetchData}
|
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
expanded={expandedRows}
|
|
||||||
expandable={true}
|
expandable={true}
|
||||||
|
expanded={expandedRows}
|
||||||
expandToggleColumn={1}
|
expandToggleColumn={1}
|
||||||
sticky={true}
|
|
||||||
expandColumnSpace={0.8}
|
expandColumnSpace={0.8}
|
||||||
|
sticky={true}
|
||||||
/>
|
/>
|
||||||
</FinancialSheet>
|
</FinancialSheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
|
||||||
const { balanceSheetQuery } = props;
|
|
||||||
return {
|
|
||||||
balanceSheetIndex: getFinancialSheetIndexByQuery(
|
|
||||||
state.financialStatements.balanceSheet.sheets,
|
|
||||||
balanceSheetQuery,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const withBalanceSheetTable = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withBalanceSheetTable,
|
|
||||||
withBalanceSheetDetail(
|
withBalanceSheetDetail(
|
||||||
({
|
({
|
||||||
balanceSheetAccounts,
|
|
||||||
balanceSheetTableRows,
|
balanceSheetTableRows,
|
||||||
balanceSheetColumns,
|
balanceSheetColumns,
|
||||||
balanceSheetQuery,
|
balanceSheetQuery,
|
||||||
balanceSheetLoading,
|
balanceSheetLoading,
|
||||||
}) => ({
|
}) => ({
|
||||||
balanceSheetAccounts,
|
|
||||||
balanceSheetTableRows,
|
balanceSheetTableRows,
|
||||||
balanceSheetColumns,
|
balanceSheetColumns,
|
||||||
balanceSheetQuery,
|
balanceSheetQuery,
|
||||||
balanceSheetLoading,
|
balanceSheetLoading,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
withSettings,
|
|
||||||
)(BalanceSheetTable);
|
)(BalanceSheetTable);
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getFinancialSheet,
|
getFinancialSheetFactory,
|
||||||
getFinancialSheetAccounts,
|
getFinancialSheetAccountsFactory,
|
||||||
getFinancialSheetColumns,
|
getFinancialSheetColumnsFactory,
|
||||||
getFinancialSheetQuery,
|
getFinancialSheetQueryFactory,
|
||||||
getFinancialSheetTableRows,
|
getFinancialSheetTableRowsFactory,
|
||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const { balanceSheetIndex } = props;
|
const getBalanceSheet = getFinancialSheetFactory('balanceSheet');
|
||||||
|
const getBalanceSheetAccounts = getFinancialSheetAccountsFactory(
|
||||||
|
'balanceSheet',
|
||||||
|
);
|
||||||
|
const getBalanceSheetTableRows = getFinancialSheetTableRowsFactory(
|
||||||
|
'balanceSheet',
|
||||||
|
);
|
||||||
|
const getBalanceSheetColumns = getFinancialSheetColumnsFactory('balanceSheet');
|
||||||
|
const getBalanceSheetQuery = getFinancialSheetQueryFactory('balanceSheet');
|
||||||
|
|
||||||
const mapped = {
|
const mapped = {
|
||||||
balanceSheet: getFinancialSheet(state.financialStatements.balanceSheet.sheets, balanceSheetIndex),
|
balanceSheet: getBalanceSheet(state, props),
|
||||||
balanceSheetAccounts: getFinancialSheetAccounts(state.financialStatements.balanceSheet.sheets, balanceSheetIndex),
|
balanceSheetAccounts: getBalanceSheetAccounts(state, props),
|
||||||
balanceSheetTableRows: getFinancialSheetTableRows(state.financialStatements.balanceSheet.sheets, balanceSheetIndex),
|
balanceSheetTableRows: getBalanceSheetTableRows(state, props),
|
||||||
balanceSheetColumns: getFinancialSheetColumns(state.financialStatements.balanceSheet.sheets, balanceSheetIndex),
|
balanceSheetColumns: getBalanceSheetColumns(state, props),
|
||||||
balanceSheetQuery: getFinancialSheetQuery(state.financialStatements.balanceSheet.sheets, balanceSheetIndex),
|
balanceSheetQuery: getBalanceSheetQuery(state, props),
|
||||||
balanceSheetLoading: state.financialStatements.balanceSheet.loading,
|
balanceSheetLoading: state.financialStatements.balanceSheet.loading,
|
||||||
balanceSheetFilter: state.financialStatements.balanceSheet.filter,
|
balanceSheetFilter: state.financialStatements.balanceSheet.filter,
|
||||||
balanceSheetRefresh: state.financialStatements.balanceSheet.refresh,
|
balanceSheetRefresh: state.financialStatements.balanceSheet.refresh,
|
||||||
@@ -25,4 +33,4 @@ export default (mapState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return connect(mapStateToProps);
|
return connect(mapStateToProps);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,46 +1,27 @@
|
|||||||
import React, { useMemo, useCallback } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Position,
|
Position,
|
||||||
|
FormGroup,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { useIntl } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { ListSelect, MODIFIER } from 'components';
|
import classNames from 'classnames';
|
||||||
|
import { FastField } from 'formik';
|
||||||
|
|
||||||
export default function FinancialAccountsFilter({
|
import { CLASSES } from 'common/classes';
|
||||||
...restProps
|
import { Col, Row, ListSelect, MODIFIER } from 'components';
|
||||||
}) {
|
import { filterAccountsOptions } from './common';
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const filterAccountsOptions = useMemo(
|
export default function FinancialAccountsFilter({ ...restProps }) {
|
||||||
() => [
|
|
||||||
{
|
|
||||||
key: 'all-accounts',
|
|
||||||
name: formatMessage({ id: 'all_accounts' }),
|
|
||||||
hint: formatMessage({ id: 'all_accounts_including_with_zero_balance' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'without-zero-balance',
|
|
||||||
name: formatMessage({ id: 'accounts_without_zero_balance' }),
|
|
||||||
hint: formatMessage({ id: 'include_accounts_and_exclude_zero_balance' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'with-transactions',
|
|
||||||
name: formatMessage({ id: 'accounts_with_transactions' }),
|
|
||||||
hint: formatMessage({ id: 'include_accounts_once_has_transactions_on_given_date_period' }),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[formatMessage],
|
|
||||||
);
|
|
||||||
const SUBMENU_POPOVER_MODIFIERS = {
|
const SUBMENU_POPOVER_MODIFIERS = {
|
||||||
flip: { boundariesElement: 'viewport', padding: 20 },
|
flip: { boundariesElement: 'viewport', padding: 20 },
|
||||||
offset: { offset: '0, 10' },
|
offset: { offset: '0, 10' },
|
||||||
preventOverflow: { boundariesElement: 'viewport', padding: 40 },
|
preventOverflow: { boundariesElement: 'viewport', padding: 40 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterAccountRenderer = useCallback(
|
const filterAccountRenderer = (item, { handleClick, modifiers, query }) => {
|
||||||
(item, { handleClick, modifiers, query }) => {
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
interactionKind={PopoverInteractionKind.HOVER}
|
interactionKind={PopoverInteractionKind.HOVER}
|
||||||
@@ -54,20 +35,36 @@ export default function FinancialAccountsFilter({
|
|||||||
<MenuItem text={item.name} key={item.key} onClick={handleClick} />
|
<MenuItem text={item.name} key={item.key} onClick={handleClick} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col xs={4}>
|
||||||
|
<FastField name={'accountsFilter'}>
|
||||||
|
{({ form: { setFieldValue }, field: { value } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'filter_accounts'} />}
|
||||||
|
className="form-group--select-list bp3-fill"
|
||||||
|
inline={false}
|
||||||
|
>
|
||||||
<ListSelect
|
<ListSelect
|
||||||
items={filterAccountsOptions}
|
items={filterAccountsOptions}
|
||||||
itemRenderer={filterAccountRenderer}
|
itemRenderer={filterAccountRenderer}
|
||||||
popoverProps={{ minimal: true, }}
|
popoverProps={{ minimal: true }}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
|
selectedItem={value}
|
||||||
selectedItemProp={'key'}
|
selectedItemProp={'key'}
|
||||||
labelProp={'name'}
|
labelProp={'name'}
|
||||||
// className={}
|
onItemSelect={(item) => {
|
||||||
|
setFieldValue('accountsFilter', item.key);
|
||||||
|
}}
|
||||||
|
className={classNames(CLASSES.SELECT_LIST_FILL_POPOVER)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,57 +1,30 @@
|
|||||||
import React, { useState, useCallback, useMemo } from 'react';
|
import React from 'react';
|
||||||
import { Row, Col } from 'react-grid-system';
|
import { FastField, ErrorMessage } from 'formik';
|
||||||
import { momentFormatter } from 'utils';
|
import { HTMLSelect, FormGroup, Intent, Position } from '@blueprintjs/core';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Row, Col, Hint } from 'components';
|
||||||
|
import { momentFormatter, parseDateRangeQuery } from 'utils';
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { HTMLSelect, FormGroup, Intent, Position } from '@blueprintjs/core';
|
import { dateRangeOptions } from 'containers/FinancialStatements/common';
|
||||||
import { Hint } from 'components';
|
|
||||||
import { parseDateRangeQuery } from 'utils';
|
|
||||||
|
|
||||||
export default function FinancialStatementDateRange({ formik }) {
|
/**
|
||||||
const intl = useIntl();
|
* Financial statement - Date range select.
|
||||||
const [reportDateRange, setReportDateRange] = useState('this_year');
|
*/
|
||||||
|
export default function FinancialStatementDateRange() {
|
||||||
const dateRangeOptions = useMemo(
|
const { formatMessage } = useIntl();
|
||||||
() => [
|
|
||||||
{ value: 'today', label: 'Today' },
|
|
||||||
{ value: 'this_week', label: 'This Week' },
|
|
||||||
{ value: 'this_month', label: 'This Month' },
|
|
||||||
{ value: 'this_quarter', label: 'This Quarter' },
|
|
||||||
{ value: 'this_year', label: 'This Year' },
|
|
||||||
{ value: 'custom', label: 'Custom Range' },
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDateChange = useCallback(
|
|
||||||
(name) => (date) => {
|
|
||||||
setReportDateRange('custom');
|
|
||||||
formik.setFieldValue(name, date);
|
|
||||||
},
|
|
||||||
[setReportDateRange, formik],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handles date range field change.
|
|
||||||
const handleDateRangeChange = useCallback(
|
|
||||||
(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
if (value !== 'custom') {
|
|
||||||
const dateRange = parseDateRangeQuery(value);
|
|
||||||
if (dateRange) {
|
|
||||||
formik.setFieldValue('from_date', dateRange.from_date);
|
|
||||||
formik.setFieldValue('to_date', dateRange.to_date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setReportDateRange(value);
|
|
||||||
},
|
|
||||||
[formik],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Col width={260}>
|
<Row>
|
||||||
|
<Col xs={4}>
|
||||||
|
<FastField name={'date_range'}>
|
||||||
|
{({
|
||||||
|
form: { setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
}) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={intl.formatMessage({ id: 'report_date_range' })}
|
label={formatMessage({ id: 'report_date_range' })}
|
||||||
labelInfo={<Hint />}
|
labelInfo={<Hint />}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
fill={true}
|
fill={true}
|
||||||
@@ -59,48 +32,89 @@ export default function FinancialStatementDateRange({ formik }) {
|
|||||||
<HTMLSelect
|
<HTMLSelect
|
||||||
fill={true}
|
fill={true}
|
||||||
options={dateRangeOptions}
|
options={dateRangeOptions}
|
||||||
value={reportDateRange}
|
value={value}
|
||||||
onChange={handleDateRangeChange}
|
onChange={(e) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
|
||||||
|
if (newValue !== 'custom') {
|
||||||
|
const dateRange = parseDateRangeQuery(newValue);
|
||||||
|
|
||||||
|
if (dateRange) {
|
||||||
|
setFieldValue('fromDate', moment(dateRange.fromDate).toDate());
|
||||||
|
setFieldValue('toDate', moment(dateRange.toDate).toDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFieldValue('dateRange', newValue);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
</Col>
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Col width={260}>
|
<Row>
|
||||||
|
<Col xs={4}>
|
||||||
|
<FastField name={'fromDate'}>
|
||||||
|
{({
|
||||||
|
form: { setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={intl.formatMessage({ id: 'from_date' })}
|
label={formatMessage({ id: 'from_date' })}
|
||||||
labelInfo={<Hint />}
|
labelInfo={<Hint />}
|
||||||
fill={true}
|
fill={true}
|
||||||
intent={formik.errors.from_date && Intent.DANGER}
|
intent={error && Intent.DANGER}
|
||||||
|
helperText={<ErrorMessage name={'fromDate'} />}
|
||||||
>
|
>
|
||||||
<DateInput
|
<DateInput
|
||||||
{...momentFormatter('YYYY/MM/DD')}
|
{...momentFormatter('YYYY-MM-DD')}
|
||||||
value={formik.values.from_date}
|
value={value}
|
||||||
onChange={handleDateChange('from_date')}
|
onChange={(selectedDate) => {
|
||||||
popoverProps={{ position: Position.BOTTOM }}
|
setFieldValue('fromDate', selectedDate);
|
||||||
|
}}
|
||||||
|
popoverProps={{ minimal: true, position: Position.BOTTOM }}
|
||||||
|
canClearSelection={false}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col width={260}>
|
<Col xs={4}>
|
||||||
|
<FastField name={'toDate'}>
|
||||||
|
{({
|
||||||
|
form: { setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error },
|
||||||
|
}) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={intl.formatMessage({ id: 'to_date' })}
|
label={formatMessage({ id: 'to_date' })}
|
||||||
labelInfo={<Hint />}
|
labelInfo={<Hint />}
|
||||||
fill={true}
|
fill={true}
|
||||||
intent={formik.errors.to_date && Intent.DANGER}
|
intent={error && Intent.DANGER}
|
||||||
|
helperText={<ErrorMessage name={'toDate'} />}
|
||||||
>
|
>
|
||||||
<DateInput
|
<DateInput
|
||||||
{...momentFormatter('YYYY/MM/DD')}
|
{...momentFormatter('YYYY-MM-DD')}
|
||||||
value={formik.values.to_date}
|
value={value}
|
||||||
onChange={handleDateChange('to_date')}
|
onChange={(selectedDate) => {
|
||||||
popoverProps={{ position: Position.BOTTOM }}
|
setFieldValue('toDate', selectedDate);
|
||||||
|
}}
|
||||||
|
popoverProps={{ minimal: true, position: Position.BOTTOM }}
|
||||||
|
canClearSelection={false}
|
||||||
fill={true}
|
fill={true}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
intent={formik.errors.to_date && Intent.DANGER}
|
intent={error && Intent.DANGER}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
</Col>
|
</Col>
|
||||||
|
</Row>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,59 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { Position, Drawer } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export default function FinancialStatementHeader({
|
||||||
|
children,
|
||||||
|
isOpen,
|
||||||
|
drawerProps,
|
||||||
|
}) {
|
||||||
|
const timeoutRef = React.useRef();
|
||||||
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
|
|
||||||
|
// Hides the content scrollbar and scroll to the top of the page once the drawer open.
|
||||||
|
useEffect(() => {
|
||||||
|
const contentPanel = document.querySelector('body');
|
||||||
|
contentPanel.classList.toggle('hide-scrollbar', isOpen);
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
document.querySelector('.Pane2').scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
contentPanel.classList.remove('hide-scrollbar');
|
||||||
|
};
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
setIsDrawerOpen(isOpen);
|
||||||
|
} else {
|
||||||
|
timeoutRef.current = setTimeout(() => setIsDrawerOpen(isOpen), 300);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
export default function FinancialStatementHeader({ show, children }) {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('financial-statement__header', {
|
className={classNames(
|
||||||
'is-hidden': !show,
|
'financial-statement__header',
|
||||||
})}
|
'financial-header-drawer',
|
||||||
|
{
|
||||||
|
'is-hidden': !isDrawerOpen,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Drawer
|
||||||
|
isOpen={isOpen}
|
||||||
|
usePortal={false}
|
||||||
|
hasBackdrop={true}
|
||||||
|
position={Position.TOP}
|
||||||
|
canOutsideClickClose={true}
|
||||||
|
canEscapeKeyClose={true}
|
||||||
|
{...drawerProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import React, { useEffect, useCallback, useState } from 'react';
|
import React, { useEffect, useCallback, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import GeneralLedgerTable from 'containers/FinancialStatements/GeneralLedger/GeneralLedgerTable';
|
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
|
import GeneralLedgerTable from 'containers/FinancialStatements/GeneralLedger/GeneralLedgerTable';
|
||||||
import GeneralLedgerHeader from './GeneralLedgerHeader';
|
import GeneralLedgerHeader from './GeneralLedgerHeader';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider'
|
|
||||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
import GeneralLedgerActionsBar from './GeneralLedgerActionsBar';
|
import GeneralLedgerActionsBar from './GeneralLedgerActionsBar';
|
||||||
|
|
||||||
@@ -17,26 +16,37 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
|||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
|
||||||
|
import withGeneralLedger from './withGeneralLedger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General Ledger (GL) sheet.
|
||||||
|
*/
|
||||||
function GeneralLedger({
|
function GeneralLedger({
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
setDashboardBackLink,
|
||||||
|
|
||||||
// #withGeneralLedgerActions
|
// #withGeneralLedgerActions
|
||||||
fetchGeneralLedger,
|
fetchGeneralLedger,
|
||||||
|
refreshGeneralLedgerSheet,
|
||||||
|
|
||||||
// #withAccountsActions
|
// #withAccountsActions
|
||||||
requestFetchAccounts,
|
requestFetchAccounts,
|
||||||
|
|
||||||
|
// #withGeneralLedger
|
||||||
|
generalLedgerSheetRefresh,
|
||||||
|
|
||||||
// #withSettings
|
// #withSettings
|
||||||
organizationSettings,
|
organizationName,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl()
|
const { formatMessage } = useIntl();
|
||||||
const [filter, setFilter] = useState({
|
const [filter, setFilter] = useState({
|
||||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
basis: 'accural',
|
basis: 'accural',
|
||||||
none_zero: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Change page title of the dashboard.
|
// Change page title of the dashboard.
|
||||||
@@ -44,46 +54,63 @@ function GeneralLedger({
|
|||||||
changePageTitle(formatMessage({ id: 'general_ledger' }));
|
changePageTitle(formatMessage({ id: 'general_ledger' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
const fetchAccounts = useQuery(['accounts-list'],
|
useEffect(() => {
|
||||||
() => requestFetchAccounts());
|
// Show the back link on dashboard topbar.
|
||||||
|
setDashboardBackLink(true);
|
||||||
|
|
||||||
const fetchSheet = useQuery(['general-ledger', filter],
|
return () => {
|
||||||
(key, query) => fetchGeneralLedger(query),
|
// Hide the back link on dashboard topbar.
|
||||||
{ manual: true });
|
setDashboardBackLink(false);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Handle fetch data of trial balance table.
|
// Observes the GL sheet refresh to invalid the query to refresh it.
|
||||||
const handleFetchData = useCallback(() => {
|
useEffect(() => {
|
||||||
fetchSheet.refetch({ force: true });
|
if (generalLedgerSheetRefresh) {
|
||||||
}, []);
|
queryCache.invalidateQueries('general-ledger');
|
||||||
|
refreshGeneralLedgerSheet(false);
|
||||||
|
}
|
||||||
|
}, [generalLedgerSheetRefresh, refreshGeneralLedgerSheet]);
|
||||||
|
|
||||||
|
// Fetches accounts list.
|
||||||
|
const fetchAccounts = useQuery(['accounts-list'], () =>
|
||||||
|
requestFetchAccounts(),
|
||||||
|
);
|
||||||
|
// Fetches the general ledger sheet.
|
||||||
|
const fetchSheet = useQuery(['general-ledger', filter], (key, q) =>
|
||||||
|
fetchGeneralLedger({ ...transformFilterFormToQuery(q) }),
|
||||||
|
);
|
||||||
|
|
||||||
// Handle financial statement filter change.
|
// Handle financial statement filter change.
|
||||||
const handleFilterSubmit = useCallback((filter) => {
|
const handleFilterSubmit = useCallback(
|
||||||
|
(filter) => {
|
||||||
const parsedFilter = {
|
const parsedFilter = {
|
||||||
...filter,
|
...filter,
|
||||||
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
|
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
|
||||||
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
|
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
|
||||||
};
|
};
|
||||||
setFilter(parsedFilter);
|
setFilter(parsedFilter);
|
||||||
}, [setFilter]);
|
refreshGeneralLedgerSheet(true);
|
||||||
|
},
|
||||||
const handleFilterChanged = () => { };
|
[setFilter, refreshGeneralLedgerSheet],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider>
|
<DashboardInsider>
|
||||||
<GeneralLedgerActionsBar
|
<GeneralLedgerActionsBar />
|
||||||
onFilterChanged={handleFilterChanged} />
|
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<div class="financial-statement financial-statement--general-ledger">
|
<div class="financial-statement financial-statement--general-ledger">
|
||||||
<GeneralLedgerHeader
|
<GeneralLedgerHeader
|
||||||
pageFilter={filter}
|
pageFilter={filter}
|
||||||
onSubmitFilter={handleFilterSubmit} />
|
onSubmitFilter={handleFilterSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="financial-statement__body">
|
<div class="financial-statement__body">
|
||||||
<GeneralLedgerTable
|
<GeneralLedgerTable
|
||||||
companyName={organizationSettings.name}
|
companyName={organizationName}
|
||||||
generalLedgerQuery={filter}
|
generalLedgerQuery={filter}
|
||||||
onFetchData={handleFetchData} />
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
@@ -95,5 +122,10 @@ export default compose(
|
|||||||
withGeneralLedgerActions,
|
withGeneralLedgerActions,
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withAccountsActions,
|
withAccountsActions,
|
||||||
withSettings,
|
withGeneralLedger(({ generalLedgerSheetRefresh }) => ({
|
||||||
|
generalLedgerSheetRefresh,
|
||||||
|
})),
|
||||||
|
withSettings(({ organizationSettings }) => ({
|
||||||
|
organizationName: organizationSettings.name,
|
||||||
|
})),
|
||||||
)(GeneralLedger);
|
)(GeneralLedger);
|
||||||
@@ -9,11 +9,10 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import Icon from 'components/Icon';
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'
|
|
||||||
import { If } from 'components';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import FilterDropdown from 'components/FilterDropdown';
|
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
import withGeneralLedger from './withGeneralLedger';
|
import withGeneralLedger from './withGeneralLedger';
|
||||||
import withGeneralLedgerActions from './withGeneralLedgerActions';
|
import withGeneralLedgerActions from './withGeneralLedgerActions';
|
||||||
@@ -21,7 +20,7 @@ import withGeneralLedgerActions from './withGeneralLedgerActions';
|
|||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger actions bar.
|
* General ledger - Actions bar.
|
||||||
*/
|
*/
|
||||||
function GeneralLedgerActionsBar({
|
function GeneralLedgerActionsBar({
|
||||||
// #withGeneralLedger
|
// #withGeneralLedger
|
||||||
@@ -29,12 +28,13 @@ function GeneralLedgerActionsBar({
|
|||||||
|
|
||||||
// #withGeneralLedgerActions
|
// #withGeneralLedgerActions
|
||||||
toggleGeneralLedgerSheetFilter,
|
toggleGeneralLedgerSheetFilter,
|
||||||
refreshGeneralLedgerSheet
|
refreshGeneralLedgerSheet,
|
||||||
}) {
|
}) {
|
||||||
const handleFilterClick = () => {
|
// Handle customize button click.
|
||||||
|
const handleCustomizeClick = () => {
|
||||||
toggleGeneralLedgerSheetFilter();
|
toggleGeneralLedgerSheetFilter();
|
||||||
};
|
};
|
||||||
|
// Handle re-calculate button click.
|
||||||
const handleRecalcReport = () => {
|
const handleRecalcReport = () => {
|
||||||
refreshGeneralLedgerSheet(true);
|
refreshGeneralLedgerSheet(true);
|
||||||
};
|
};
|
||||||
@@ -43,61 +43,49 @@ function GeneralLedgerActionsBar({
|
|||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
|
||||||
icon={<Icon icon='cog-16' iconSize={16} />}
|
text={<T id={'recalc_report'} />}
|
||||||
text={<T id={'customize_report'}/>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<NavbarDivider />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className={classNames(
|
|
||||||
Classes.MINIMAL,
|
|
||||||
'button--gray-highlight',
|
|
||||||
)}
|
|
||||||
text={'Re-calc Report'}
|
|
||||||
onClick={handleRecalcReport}
|
onClick={handleRecalcReport}
|
||||||
icon={<Icon icon="refresh-16" iconSize={16} />}
|
icon={<Icon icon="refresh-16" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
<If condition={generalLedgerSheetFilter}>
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
text={<T id={'hide_filter'} />}
|
icon={<Icon icon="cog-16" iconSize={16} />}
|
||||||
icon={<Icon icon="arrow-to-top" />}
|
text={
|
||||||
onClick={handleFilterClick}
|
generalLedgerSheetFilter ? (
|
||||||
|
<T id={'hide_customizer'} />
|
||||||
|
) : (
|
||||||
|
<T id={'customize_report'} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={handleCustomizeClick}
|
||||||
|
active={generalLedgerSheetFilter}
|
||||||
/>
|
/>
|
||||||
</If>
|
<NavbarDivider />
|
||||||
|
|
||||||
<If condition={!generalLedgerSheetFilter}>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
text={<T id={'show_filter'} />}
|
|
||||||
icon={<Icon icon="arrow-to-bottom" />}
|
|
||||||
onClick={handleFilterClick}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
position={Position.BOTTOM_LEFT}>
|
position={Position.BOTTOM_LEFT}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
text={<T id={'filter'} />}
|
text={<T id={'filter'} />}
|
||||||
icon={<Icon icon="filter-16" iconSize={16} /> } />
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='print-16' iconSize={16} />}
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
text={<T id={'print'} />}
|
text={<T id={'print'} />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='file-export-16' iconSize={16} />}
|
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||||
text={<T id={'export'} />}
|
text={<T id={'export'} />}
|
||||||
/>
|
/>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
@@ -106,6 +94,8 @@ function GeneralLedgerActionsBar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withGeneralLedger(({ generalLedgerSheetFilter }) => ({ generalLedgerSheetFilter })),
|
withGeneralLedger(({ generalLedgerSheetFilter }) => ({
|
||||||
|
generalLedgerSheetFilter,
|
||||||
|
})),
|
||||||
withGeneralLedgerActions,
|
withGeneralLedgerActions,
|
||||||
)(GeneralLedgerActionsBar);
|
)(GeneralLedgerActionsBar);
|
||||||
@@ -1,113 +1,100 @@
|
|||||||
import React, { useEffect, useCallback } from 'react';
|
import React from 'react';
|
||||||
import { Button, FormGroup, Classes } from '@blueprintjs/core';
|
|
||||||
import { Row, Col, Visible } from 'react-grid-system';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useFormik } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
|
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
|
||||||
import AccountsMultiSelect from 'components/AccountsMultiSelect';
|
|
||||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import GeneralLedgerHeaderGeneralPane from './GeneralLedgerHeaderGeneralPane';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
|
||||||
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
|
||||||
|
|
||||||
import withGeneralLedger from './withGeneralLedger';
|
import withGeneralLedger from './withGeneralLedger';
|
||||||
import withGeneralLedgerActions from './withGeneralLedgerActions';
|
import withGeneralLedgerActions from './withGeneralLedgerActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geenral Ledger (GL) - Header.
|
||||||
|
*/
|
||||||
function GeneralLedgerHeader({
|
function GeneralLedgerHeader({
|
||||||
|
// #ownProps
|
||||||
onSubmitFilter,
|
onSubmitFilter,
|
||||||
pageFilter,
|
pageFilter,
|
||||||
|
|
||||||
// #withAccounts
|
|
||||||
accountsList,
|
|
||||||
|
|
||||||
// #withGeneralLedgerActions
|
// #withGeneralLedgerActions
|
||||||
refreshGeneralLedgerSheet,
|
toggleGeneralLedgerSheetFilter,
|
||||||
|
|
||||||
// #withGeneralLedger
|
// #withGeneralLedger
|
||||||
generalLedgerSheetFilter,
|
generalLedgerSheetFilter,
|
||||||
generalLedgerSheetRefresh
|
|
||||||
}) {
|
}) {
|
||||||
const formik = useFormik({
|
// Initial values.
|
||||||
enableReinitialize: true,
|
const initialValues = {
|
||||||
initialValues: {
|
|
||||||
...pageFilter,
|
...pageFilter,
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||||
to_date: moment(pageFilter.to_date).toDate(),
|
toDate: moment(pageFilter.toDate).toDate(),
|
||||||
},
|
};
|
||||||
validationSchema: Yup.object().shape({
|
|
||||||
from_date: Yup.date().required(),
|
// Validation schema.
|
||||||
to_date: Yup.date().min(Yup.ref('from_date')).required(),
|
const validationSchema = Yup.object().shape({
|
||||||
}),
|
dateRange: Yup.string().optional(),
|
||||||
onSubmit(values, actions) {
|
fromDate: Yup.date().required(),
|
||||||
onSubmitFilter(values);
|
toDate: Yup.date().min(Yup.ref('fromDate')).required(),
|
||||||
actions.setSubmitting(false);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onAccountSelected = useCallback((selectedAccounts) => {
|
// Handle form submit.
|
||||||
formik.setFieldValue('accounts_ids', Object.keys(selectedAccounts));
|
const handleSubmit = (values, { setSubmitting }) => {
|
||||||
}, [formik.setFieldValue]);
|
onSubmitFilter(values);
|
||||||
|
toggleGeneralLedgerSheetFilter();
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleAccountingBasisChange = useCallback(
|
// Handle cancel button click.
|
||||||
(value) => {
|
const handleCancelClick = () => {
|
||||||
formik.setFieldValue('basis', value);
|
toggleGeneralLedgerSheetFilter(false);
|
||||||
},
|
};
|
||||||
[formik],
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle submit filter submit button.
|
// Handle drawer close action.
|
||||||
useEffect(() => {
|
const handleDrawerClose = () => {
|
||||||
if (generalLedgerSheetRefresh) {
|
toggleGeneralLedgerSheetFilter(false);
|
||||||
formik.submitForm();
|
};
|
||||||
refreshGeneralLedgerSheet(false);
|
|
||||||
}
|
|
||||||
}, [formik, generalLedgerSheetRefresh])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader show={generalLedgerSheetFilter}>
|
<FinancialStatementHeader
|
||||||
<Row>
|
isOpen={generalLedgerSheetFilter}
|
||||||
<FinancialStatementDateRange formik={formik} />
|
drawerProps={{ onClose: handleDrawerClose }}
|
||||||
|
|
||||||
<Visible xl><Col width={'100%'} /></Visible>
|
|
||||||
|
|
||||||
<Col width={260}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'specific_accounts'} />}
|
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
|
||||||
>
|
>
|
||||||
<AccountsMultiSelect
|
<Formik
|
||||||
accounts={accountsList}
|
validationSchema={validationSchema}
|
||||||
onAccountSelected={onAccountSelected}
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<Tabs animate={true} vertical={true} renderActiveTabPanelOnly={true}>
|
||||||
|
<Tab
|
||||||
|
id="general"
|
||||||
|
title={<T id={'general'} />}
|
||||||
|
panel={<GeneralLedgerHeaderGeneralPane />}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</Tabs>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col width={260}>
|
<div class="financial-header-drawer__footer">
|
||||||
<RadiosAccountingBasis
|
<Button className={'mr1'} intent={Intent.PRIMARY} type={'submit'}>
|
||||||
onChange={handleAccountingBasisChange}
|
<T id={'calculate_report'} />
|
||||||
selectedValue={formik.values.basis}
|
</Button>
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
</Row>
|
<Button onClick={handleCancelClick} minimal={true}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
</FinancialStatementHeader>
|
</FinancialStatementHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAccounts(({ accountsList }) => ({
|
withGeneralLedger(({ generalLedgerSheetFilter }) => ({
|
||||||
accountsList,
|
|
||||||
})),
|
|
||||||
withGeneralLedger(({ generalLedgerSheetFilter, generalLedgerSheetRefresh }) => ({
|
|
||||||
generalLedgerSheetFilter,
|
generalLedgerSheetFilter,
|
||||||
generalLedgerSheetRefresh,
|
|
||||||
})),
|
})),
|
||||||
withGeneralLedgerActions,
|
withGeneralLedgerActions,
|
||||||
)(GeneralLedgerHeader);
|
)(GeneralLedgerHeader);
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormGroup, Classes } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { AccountsMultiSelect, Row, Col } from 'components';
|
||||||
|
|
||||||
|
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
||||||
|
|
||||||
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
|
|
||||||
|
import { compose } from 'redux';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General ledger (GL) - Header - General panel.
|
||||||
|
*/
|
||||||
|
function GeneralLedgerHeaderGeneralPane({
|
||||||
|
// #withAccounts
|
||||||
|
accountsList,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FinancialStatementDateRange />
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col xs={4}>
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'specific_accounts'} />}
|
||||||
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
>
|
||||||
|
<AccountsMultiSelect accounts={accountsList} />
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<RadiosAccountingBasis key={'basis'} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withAccounts(({ accountsList }) => ({ accountsList })))(
|
||||||
|
GeneralLedgerHeaderGeneralPane,
|
||||||
|
);
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { defaultExpanderReducer, compose } from 'utils';
|
import { defaultExpanderReducer, compose } from 'utils';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
@@ -8,7 +7,6 @@ import FinancialSheet from 'components/FinancialSheet';
|
|||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
import Money from 'components/Money';
|
import Money from 'components/Money';
|
||||||
|
|
||||||
import { getFinancialSheetIndexByQuery } from 'store/financialStatement/financialStatements.selectors';
|
|
||||||
import withGeneralLedger from './withGeneralLedger';
|
import withGeneralLedger from './withGeneralLedger';
|
||||||
|
|
||||||
const ROW_TYPE = {
|
const ROW_TYPE = {
|
||||||
@@ -20,7 +18,6 @@ const ROW_TYPE = {
|
|||||||
|
|
||||||
function GeneralLedgerTable({
|
function GeneralLedgerTable({
|
||||||
companyName,
|
companyName,
|
||||||
onFetchData,
|
|
||||||
|
|
||||||
generalLedgerSheetLoading,
|
generalLedgerSheetLoading,
|
||||||
generalLedgerTableRows,
|
generalLedgerTableRows,
|
||||||
@@ -29,8 +26,7 @@ function GeneralLedgerTable({
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
// Account name column accessor.
|
// Account name column accessor.
|
||||||
const accountNameAccessor = useCallback(
|
const accountNameAccessor = (row) => {
|
||||||
(row) => {
|
|
||||||
switch (row.rowType) {
|
switch (row.rowType) {
|
||||||
case ROW_TYPE.OPENING_BALANCE:
|
case ROW_TYPE.OPENING_BALANCE:
|
||||||
return 'Opening Balance';
|
return 'Opening Balance';
|
||||||
@@ -39,13 +35,10 @@ function GeneralLedgerTable({
|
|||||||
default:
|
default:
|
||||||
return row.name;
|
return row.name;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
[ROW_TYPE],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Date accessor.
|
// Date accessor.
|
||||||
const dateAccessor = useCallback(
|
const dateAccessor = (row) => {
|
||||||
(row) => {
|
|
||||||
const TYPES = [
|
const TYPES = [
|
||||||
ROW_TYPE.OPENING_BALANCE,
|
ROW_TYPE.OPENING_BALANCE,
|
||||||
ROW_TYPE.CLOSING_BALANCE,
|
ROW_TYPE.CLOSING_BALANCE,
|
||||||
@@ -55,9 +48,7 @@ function GeneralLedgerTable({
|
|||||||
return TYPES.indexOf(row.rowType) !== -1
|
return TYPES.indexOf(row.rowType) !== -1
|
||||||
? moment(row.date).format('DD MMM YYYY')
|
? moment(row.date).format('DD MMM YYYY')
|
||||||
: '';
|
: '';
|
||||||
},
|
};
|
||||||
[moment, ROW_TYPE],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Amount cell
|
// Amount cell
|
||||||
const amountCell = useCallback(({ cell }) => {
|
const amountCell = useCallback(({ cell }) => {
|
||||||
@@ -73,10 +64,6 @@ function GeneralLedgerTable({
|
|||||||
return <Money amount={transaction.amount} currency={'USD'} />;
|
return <Money amount={transaction.amount} currency={'USD'} />;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const referenceLink = useCallback((row) => {
|
|
||||||
return <a href="">{row.referenceId}</a>;
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -99,7 +86,7 @@ function GeneralLedgerTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'trans_num' }),
|
Header: formatMessage({ id: 'trans_num' }),
|
||||||
accessor: referenceLink,
|
accessor: 'reference_id',
|
||||||
className: 'transaction_number',
|
className: 'transaction_number',
|
||||||
width: 110,
|
width: 110,
|
||||||
},
|
},
|
||||||
@@ -125,10 +112,6 @@ function GeneralLedgerTable({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFetchData = useCallback(() => {
|
|
||||||
onFetchData && onFetchData();
|
|
||||||
}, [onFetchData]);
|
|
||||||
|
|
||||||
// Default expanded rows of general ledger table.
|
// Default expanded rows of general ledger table.
|
||||||
const expandedRows = useMemo(
|
const expandedRows = useMemo(
|
||||||
() => defaultExpanderReducer(generalLedgerTableRows, 1),
|
() => defaultExpanderReducer(generalLedgerTableRows, 1),
|
||||||
@@ -140,12 +123,11 @@ function GeneralLedgerTable({
|
|||||||
return (
|
return (
|
||||||
<FinancialSheet
|
<FinancialSheet
|
||||||
companyName={companyName}
|
companyName={companyName}
|
||||||
// sheetType={formatMessage({ id: 'general_ledger_sheet' })}
|
sheetType={formatMessage({ id: 'general_ledger_sheet' })}
|
||||||
fromDate={generalLedgerQuery.from_date}
|
fromDate={generalLedgerQuery.from_date}
|
||||||
toDate={generalLedgerQuery.to_date}
|
toDate={generalLedgerQuery.to_date}
|
||||||
name="general-ledger"
|
name="general-ledger"
|
||||||
loading={generalLedgerSheetLoading}
|
loading={generalLedgerSheetLoading}
|
||||||
minimal={true}
|
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
>
|
>
|
||||||
<DataTable
|
<DataTable
|
||||||
@@ -155,7 +137,6 @@ function GeneralLedgerTable({
|
|||||||
})}
|
})}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={generalLedgerTableRows}
|
data={generalLedgerTableRows}
|
||||||
onFetchData={handleFetchData}
|
|
||||||
rowClassNames={rowClassNames}
|
rowClassNames={rowClassNames}
|
||||||
expanded={expandedRows}
|
expanded={expandedRows}
|
||||||
virtualizedRows={true}
|
virtualizedRows={true}
|
||||||
@@ -169,21 +150,7 @@ function GeneralLedgerTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
|
||||||
const { generalLedgerQuery } = props;
|
|
||||||
|
|
||||||
return {
|
|
||||||
generalLedgerIndex: getFinancialSheetIndexByQuery(
|
|
||||||
state.financialStatements.generalLedger.sheets,
|
|
||||||
generalLedgerQuery,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const withGeneralLedgerTable = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withGeneralLedgerTable,
|
|
||||||
withGeneralLedger(
|
withGeneralLedger(
|
||||||
({
|
({
|
||||||
generalLedgerTableRows,
|
generalLedgerTableRows,
|
||||||
|
|||||||
@@ -1,27 +1,20 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getFinancialSheet,
|
getFinancialSheetFactory,
|
||||||
getFinancialSheetQuery,
|
getFinancialSheetQueryFactory,
|
||||||
getFinancialSheetTableRows,
|
getFinancialSheetTableRowsFactory,
|
||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const { generalLedgerIndex } = props;
|
const getGeneralLedgerSheet = getFinancialSheetFactory('generalLedger');
|
||||||
|
const getSheetTableRows = getFinancialSheetTableRowsFactory('generalLedger');
|
||||||
|
const getSheetQuery = getFinancialSheetQueryFactory('generalLedger');
|
||||||
|
|
||||||
const mapped = {
|
const mapped = {
|
||||||
generalLedgerSheet: getFinancialSheet(
|
generalLedgerSheet: getGeneralLedgerSheet(state, props),
|
||||||
state.financialStatements.generalLedger.sheets,
|
generalLedgerTableRows: getSheetTableRows(state, props),
|
||||||
generalLedgerIndex,
|
generalLedgerQuery: getSheetQuery(state, props),
|
||||||
),
|
|
||||||
generalLedgerTableRows: getFinancialSheetTableRows(
|
|
||||||
state.financialStatements.generalLedger.sheets,
|
|
||||||
generalLedgerIndex,
|
|
||||||
),
|
|
||||||
generalLedgerQuery: getFinancialSheetQuery(
|
|
||||||
state.financialStatements.generalLedger.sheets,
|
|
||||||
generalLedgerIndex,
|
|
||||||
),
|
|
||||||
generalLedgerSheetLoading:
|
generalLedgerSheetLoading:
|
||||||
state.financialStatements.generalLedger.loading,
|
state.financialStatements.generalLedger.loading,
|
||||||
generalLedgerSheetFilter: state.financialStatements.generalLedger.filter,
|
generalLedgerSheetFilter: state.financialStatements.generalLedger.filter,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect } from 'react';
|
|||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import JournalTable from './JournalTable';
|
import JournalTable from './JournalTable';
|
||||||
@@ -12,79 +13,89 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
|||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withJournalActions from './withJournalActions';
|
import withJournalActions from './withJournalActions';
|
||||||
|
import withJournal from './withJournal';
|
||||||
|
|
||||||
|
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
|
||||||
|
|
||||||
function Journal({
|
function Journal({
|
||||||
// #withJournalActions
|
// #withJournalActions
|
||||||
requestFetchJournalSheet,
|
requestFetchJournalSheet,
|
||||||
|
refreshJournalSheet,
|
||||||
|
|
||||||
|
// #withJournal
|
||||||
|
journalSheetRefresh,
|
||||||
|
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
setDashboardBackLink,
|
||||||
|
|
||||||
// #withPreferences
|
// #withPreferences
|
||||||
organizationSettings,
|
organizationName,
|
||||||
}) {
|
}) {
|
||||||
const [filter, setFilter] = useState({
|
const [filter, setFilter] = useState({
|
||||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
basis: 'accural',
|
basis: 'accural',
|
||||||
});
|
});
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const fetchJournalSheet = useQuery(['journal-sheet', filter], (key, query) =>
|
||||||
|
requestFetchJournalSheet({
|
||||||
|
...transformFilterFormToQuery(filter),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(formatMessage({ id: 'journal_sheet' }));
|
changePageTitle(formatMessage({ id: 'journal_sheet' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
const fetchHook = useQuery(
|
useEffect(() => {
|
||||||
['journal', filter],
|
// Show the back link on dashboard topbar.
|
||||||
(key, query) => requestFetchJournalSheet(query),
|
setDashboardBackLink(true);
|
||||||
{ manual: 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.
|
// Handle financial statement filter change.
|
||||||
const handleFilterSubmit = useCallback(
|
const handleFilterSubmit = useCallback(
|
||||||
(filter) => {
|
(filter) => {
|
||||||
const _filter = {
|
const _filter = {
|
||||||
...filter,
|
...filter,
|
||||||
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
|
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
|
||||||
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
|
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
|
||||||
};
|
};
|
||||||
setFilter(_filter);
|
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 (
|
return (
|
||||||
<DashboardInsider>
|
<DashboardInsider>
|
||||||
<JournalActionsBar
|
<JournalActionsBar />
|
||||||
onSubmitFilter={handleFilterSubmit}
|
|
||||||
onPrintClick={handlePrintClick}
|
|
||||||
onExportClick={handleExportClick}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<div class="financial-statement financial-statement--journal">
|
<div class="financial-statement financial-statement--journal">
|
||||||
<JournalHeader
|
<JournalHeader
|
||||||
pageFilter={filter}
|
|
||||||
onSubmitFilter={handleFilterSubmit}
|
onSubmitFilter={handleFilterSubmit}
|
||||||
|
pageFilter={filter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="financial-statement__body">
|
<div class="financial-statement__body">
|
||||||
<JournalTable
|
<JournalTable
|
||||||
companyName={organizationSettings.name}
|
companyName={organizationName}
|
||||||
journalQuery={filter}
|
journalQuery={filter}
|
||||||
onFetchData={handleFetchData}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,5 +107,10 @@ function Journal({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withJournalActions,
|
withJournalActions,
|
||||||
withSettings,
|
withSettings(({ organizationSettings }) => ({
|
||||||
|
organizationName: organizationSettings.name,
|
||||||
|
})),
|
||||||
|
withJournal(({ journalSheetRefresh }) => ({
|
||||||
|
journalSheetRefresh,
|
||||||
|
})),
|
||||||
)(Journal);
|
)(Journal);
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ import {
|
|||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import FilterDropdown from 'components/FilterDropdown';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { If } from 'components';
|
|
||||||
|
|
||||||
import withJournalActions from './withJournalActions';
|
import withJournalActions from './withJournalActions';
|
||||||
import withJournal from './withJournal';
|
import withJournal from './withJournal';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Journal sheeet - Actions bar.
|
||||||
|
*/
|
||||||
function JournalActionsBar({
|
function JournalActionsBar({
|
||||||
// #withJournal
|
// #withJournal
|
||||||
journalSheetFilter,
|
journalSheetFilter,
|
||||||
@@ -40,36 +40,28 @@ function JournalActionsBar({
|
|||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<Button
|
|
||||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
|
||||||
icon={<Icon icon="cog-16" iconSize={16} />}
|
|
||||||
text={<T id={'customize_report'} />}
|
|
||||||
/>
|
|
||||||
<NavbarDivider />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
|
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
|
||||||
text={<T id={'recalc_report'} />}
|
text={<T id={'recalc_report'} />}
|
||||||
onClick={handleRecalcReport}
|
onClick={handleRecalcReport}
|
||||||
icon={<Icon icon="refresh-16" iconSize={16} />}
|
icon={<Icon icon="refresh-16" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
<If condition={journalSheetFilter}>
|
<NavbarDivider />
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
text={<T id={'hide_filter'} />}
|
|
||||||
icon={<Icon icon="arrow-to-top" />}
|
|
||||||
onClick={handleFilterToggleClick}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<If condition={!journalSheetFilter}>
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
text={<T id={'show_filter'} />}
|
icon={<Icon icon="cog-16" iconSize={16} />}
|
||||||
icon={<Icon icon="arrow-to-bottom" />}
|
text={
|
||||||
|
(journalSheetFilter) ? (
|
||||||
|
<T id={'hide_customizer'} />
|
||||||
|
) : (
|
||||||
|
<T id={'customize_report'} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
active={journalSheetFilter}
|
||||||
onClick={handleFilterToggleClick}
|
onClick={handleFilterToggleClick}
|
||||||
/>
|
/>
|
||||||
</If>
|
<NavbarDivider />
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React from 'react';
|
||||||
import { Row, Col } from 'react-grid-system';
|
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useFormik } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { Tab, Tabs, Button, Intent } from '@blueprintjs/core';
|
||||||
import * as Yup from 'yup';
|
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 FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||||
|
|
||||||
import withJournal from './withJournal';
|
import withJournal from './withJournal';
|
||||||
@@ -23,49 +23,75 @@ function JournalHeader({
|
|||||||
|
|
||||||
// #withJournalActions
|
// #withJournalActions
|
||||||
refreshJournalSheet,
|
refreshJournalSheet,
|
||||||
|
toggleJournalSheetFilter,
|
||||||
|
|
||||||
// #withJournal
|
// #withJournal
|
||||||
journalSheetFilter,
|
journalSheetFilter,
|
||||||
journalSheetRefresh,
|
journalSheetRefresh,
|
||||||
}) {
|
}) {
|
||||||
const formik = useFormik({
|
const initialValues = {
|
||||||
enableReinitialize: true,
|
|
||||||
initialValues: {
|
|
||||||
...pageFilter,
|
...pageFilter,
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||||
to_date: moment(pageFilter.to_date).toDate(),
|
toDate: moment(pageFilter.toDate).toDate(),
|
||||||
},
|
};
|
||||||
validationSchema: Yup.object().shape({
|
|
||||||
from_date: Yup.date().required(),
|
// Validation schema.
|
||||||
to_date: Yup.date().min(Yup.ref('from_date')).required(),
|
const validationSchema = Yup.object().shape({
|
||||||
}),
|
fromDate: Yup.date().required(),
|
||||||
onSubmit: (values, { setSubmitting }) => {
|
toDate: Yup.date().min(Yup.ref('fromDate')).required(),
|
||||||
onSubmitFilter(values);
|
|
||||||
setSubmitting(false);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
// Handle form submit.
|
||||||
if (journalSheetRefresh) {
|
const handleSubmit = (values, { setSubmitting }) => {
|
||||||
formik.submitForm();
|
onSubmitFilter(values);
|
||||||
refreshJournalSheet(false);
|
setSubmitting(false);
|
||||||
}
|
toggleJournalSheetFilter();
|
||||||
}, [formik, journalSheetRefresh]);
|
};
|
||||||
|
|
||||||
|
// Handle cancel journal drawer header.
|
||||||
|
const handleCancelClick = () => {
|
||||||
|
toggleJournalSheetFilter();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrawerClose = () => {
|
||||||
|
toggleJournalSheetFilter();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader show={journalSheetFilter}>
|
<FinancialStatementHeader
|
||||||
<Row>
|
isOpen={journalSheetFilter}
|
||||||
<FinancialStatementDateRange formik={formik} />
|
drawerProps={{ onClose: handleDrawerClose }}
|
||||||
</Row>
|
>
|
||||||
|
<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>
|
</FinancialStatementHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withJournal(({
|
withJournal(({ journalSheetFilter, journalSheetRefresh }) => ({
|
||||||
journalSheetFilter,
|
|
||||||
journalSheetRefresh
|
|
||||||
}) => ({
|
|
||||||
journalSheetFilter,
|
journalSheetFilter,
|
||||||
journalSheetRefresh,
|
journalSheetRefresh,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
|
||||||
|
export default function JournalSheetHeaderGeneralPanel({}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FinancialStatementDateRange />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
import FinancialSheet from 'components/FinancialSheet';
|
import FinancialSheet from 'components/FinancialSheet';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
import { compose, defaultExpanderReducer } from 'utils';
|
|
||||||
|
|
||||||
import Money from 'components/Money';
|
import Money from 'components/Money';
|
||||||
import { getFinancialSheetIndexByQuery } from 'store/financialStatement/financialStatements.selectors';
|
|
||||||
|
|
||||||
import withJournal from './withJournal';
|
import withJournal from './withJournal';
|
||||||
|
|
||||||
|
import { compose, defaultExpanderReducer } from 'utils';
|
||||||
|
|
||||||
function JournalSheetTable({
|
function JournalSheetTable({
|
||||||
// #withJournal
|
// #withJournal
|
||||||
journalSheetTableRows,
|
journalSheetTableRows,
|
||||||
@@ -106,12 +104,12 @@ function JournalSheetTable({
|
|||||||
return (
|
return (
|
||||||
<FinancialSheet
|
<FinancialSheet
|
||||||
companyName={companyName}
|
companyName={companyName}
|
||||||
// sheetType={formatMessage({ id: 'journal_sheet' })}
|
sheetType={formatMessage({ id: 'journal_sheet' })}
|
||||||
fromDate={journalSheetQuery.from_date}
|
fromDate={journalSheetQuery.from_date}
|
||||||
toDate={journalSheetQuery.to_date}
|
toDate={journalSheetQuery.to_date}
|
||||||
name="journal"
|
name="journal"
|
||||||
loading={journalSheetLoading}
|
loading={journalSheetLoading}
|
||||||
minimal={true}
|
// minimal={true}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
>
|
>
|
||||||
<DataTable
|
<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(
|
export default compose(
|
||||||
withJournalTable,
|
|
||||||
withJournal(
|
withJournal(
|
||||||
({ journalSheetTableRows, journalSheetLoading, journalSheetQuery }) => ({
|
({ journalSheetTableRows, journalSheetLoading, journalSheetQuery }) => ({
|
||||||
journalSheetTableRows,
|
journalSheetTableRows,
|
||||||
|
|||||||
@@ -1,34 +1,27 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getFinancialSheetIndexByQuery,
|
getFinancialSheetFactory,
|
||||||
getFinancialSheet,
|
getFinancialSheetTableRowsFactory,
|
||||||
getFinancialSheetTableRows,
|
getFinancialSheetQueryFactory,
|
||||||
getFinancialSheetQuery,
|
|
||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const { journalIndex } = props;
|
const getJournalSheet = getFinancialSheetFactory('journal');
|
||||||
|
const getJournalSheetTableRows = getFinancialSheetTableRowsFactory(
|
||||||
|
'journal',
|
||||||
|
);
|
||||||
|
const getJournalSheetQuery = getFinancialSheetQueryFactory('journal');
|
||||||
|
|
||||||
const mapped = {
|
const mapped = {
|
||||||
journalSheet: getFinancialSheet(
|
journalSheet: getJournalSheet(state, props),
|
||||||
state.financialStatements.journal.sheets,
|
journalSheetTableRows: getJournalSheetTableRows(state, props),
|
||||||
journalIndex
|
journalSheetQuery: getJournalSheetQuery(state, props),
|
||||||
),
|
|
||||||
journalSheetTableRows: getFinancialSheetTableRows(
|
|
||||||
state.financialStatements.journal.sheets,
|
|
||||||
journalIndex
|
|
||||||
),
|
|
||||||
journalSheetQuery: getFinancialSheetQuery(
|
|
||||||
state.financialStatements.journal.sheets,
|
|
||||||
journalIndex,
|
|
||||||
),
|
|
||||||
journalSheetLoading: state.financialStatements.journal.loading,
|
journalSheetLoading: state.financialStatements.journal.loading,
|
||||||
journalSheetFilter: state.financialStatements.journal.filter,
|
journalSheetFilter: state.financialStatements.journal.filter,
|
||||||
journalSheetRefresh: state.financialStatements.journal.refresh,
|
journalSheetRefresh: state.financialStatements.journal.refresh,
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|
||||||
return connect(mapStateToProps);
|
return connect(mapStateToProps);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavbarGroup, Button, Classes, NavbarDivider } from '@blueprintjs/core';
|
import {
|
||||||
|
NavbarGroup,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
NavbarDivider,
|
||||||
|
Popover,
|
||||||
|
Position,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
import { If } from 'components';
|
|
||||||
import withProfitLossActions from './withProfitLossActions';
|
import withProfitLossActions from './withProfitLossActions';
|
||||||
import withProfitLoss from './withProfitLoss';
|
import withProfitLoss from './withProfitLoss';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
function ProfitLossActionsBar({
|
function ProfitLossActionsBar({
|
||||||
// #withProfitLoss
|
// #withProfitLoss
|
||||||
profitLossSheetFilter,
|
profitLossSheetFilter,
|
||||||
@@ -33,45 +39,43 @@ function ProfitLossActionsBar({
|
|||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
|
||||||
icon={<Icon icon="cog-16" iconSize={16} />}
|
text={<T id={'recalc_report'} />}
|
||||||
text={<T id={'customize_report'} />}
|
|
||||||
/>
|
|
||||||
<NavbarDivider />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className={classNames(
|
|
||||||
Classes.MINIMAL,
|
|
||||||
'button--gray-highlight',
|
|
||||||
)}
|
|
||||||
text={'Re-calc Report'}
|
|
||||||
onClick={handleRecalcReport}
|
onClick={handleRecalcReport}
|
||||||
icon={<Icon icon="refresh-16" iconSize={16} />}
|
icon={<Icon icon="refresh-16" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<If condition={profitLossSheetFilter}>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
text={<T id={'hide_filter'} />}
|
|
||||||
icon={<Icon icon="arrow-to-top" />}
|
|
||||||
onClick={handleFilterClick}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<If condition={!profitLossSheetFilter}>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
text={<T id={'show_filter'} />}
|
|
||||||
icon={<Icon icon="arrow-to-bottom" />}
|
|
||||||
onClick={handleFilterClick}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
|
icon={<Icon icon="cog-16" iconSize={16} />}
|
||||||
|
text={
|
||||||
|
profitLossSheetFilter ? (
|
||||||
|
<T id={'hide_customizer'} />
|
||||||
|
) : (
|
||||||
|
<T id={'customize_report'} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={handleFilterClick}
|
||||||
|
active={profitLossSheetFilter}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
// content={}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
|
text={<T id={'filter'} />}
|
||||||
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='print-16' iconSize={16} />}
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
text={<T id={'print'} />}
|
text={<T id={'print'} />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React, {useState, useCallback, useEffect} from 'react';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {compose} from 'utils';
|
import {compose} from 'utils';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
import ProfitLossSheetHeader from './ProfitLossSheetHeader';
|
import ProfitLossSheetHeader from './ProfitLossSheetHeader';
|
||||||
import ProfitLossSheetTable from './ProfitLossSheetTable';
|
import ProfitLossSheetTable from './ProfitLossSheetTable';
|
||||||
@@ -13,59 +15,71 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent'
|
|||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withProfitLossActions from './withProfitLossActions';
|
import withProfitLossActions from './withProfitLossActions';
|
||||||
import withProfitLoss from './withProfitLoss';
|
import withProfitLoss from './withProfitLoss';
|
||||||
// import SettingsConnect from 'connectors/Settings.connect';
|
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
|
||||||
|
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
|
||||||
|
|
||||||
function ProfitLossSheet({
|
function ProfitLossSheet({
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
setDashboardBackLink,
|
||||||
|
|
||||||
|
// #withProfitLoss
|
||||||
|
profitLossSheetRefresh,
|
||||||
|
|
||||||
// #withProfitLossActions
|
// #withProfitLossActions
|
||||||
fetchProfitLossSheet,
|
fetchProfitLossSheet,
|
||||||
|
refreshProfitLossSheet,
|
||||||
|
|
||||||
// #withPreferences
|
// #withPreferences
|
||||||
organizationSettings,
|
organizationName,
|
||||||
}) {
|
}) {
|
||||||
const [filter, setFilter] = useState({
|
const [filter, setFilter] = useState({
|
||||||
basis: 'cash',
|
basis: 'cash',
|
||||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
|
displayColumnsType: 'total',
|
||||||
|
accountsFilter: 'all-accounts',
|
||||||
});
|
});
|
||||||
const [refresh, setRefresh] = useState(true);
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
// Change page title of the dashboard.
|
// Change page title of the dashboard.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle('Profit/Loss Sheet');
|
changePageTitle(formatMessage({ id: 'profit_loss_sheet' }));
|
||||||
}, [changePageTitle]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
|
// Observes the P&L sheet refresh to invalid the query to refresh it.
|
||||||
|
useEffect(() => {
|
||||||
|
if (profitLossSheetRefresh) {
|
||||||
|
refreshProfitLossSheet(false);
|
||||||
|
queryCache.invalidateQueries('profit-loss-sheet');
|
||||||
|
}
|
||||||
|
}, [profitLossSheetRefresh, refreshProfitLossSheet]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Show the back link on dashboard topbar.
|
||||||
|
setDashboardBackLink(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Hide the back link on dashboard topbar.
|
||||||
|
setDashboardBackLink(false);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Fetches profit/loss sheet.
|
// Fetches profit/loss sheet.
|
||||||
const fetchHook = useQuery(['profit-loss', filter],
|
const fetchSheetHook = useQuery(['profit-loss-sheet', filter],
|
||||||
(key, query) => fetchProfitLossSheet(query),
|
(key, query) => fetchProfitLossSheet({ ...transformFilterFormToQuery(query) }),
|
||||||
{ manual: true });
|
{ manual: true });
|
||||||
|
|
||||||
// Handle submit filter.
|
// Handle submit filter.
|
||||||
const handleSubmitFilter = useCallback((filter) => {
|
const handleSubmitFilter = useCallback((filter) => {
|
||||||
const _filter = {
|
const _filter = {
|
||||||
...filter,
|
...filter,
|
||||||
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
|
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
|
||||||
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
|
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
|
||||||
};
|
};
|
||||||
setFilter(_filter);
|
setFilter(_filter);
|
||||||
setRefresh(true);
|
}, [setFilter]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle fetch data of profit/loss sheet table.
|
|
||||||
const handleFetchData = useCallback(() => {
|
|
||||||
setRefresh(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (refresh) {
|
|
||||||
fetchHook.refetch({ force: true });
|
|
||||||
setRefresh(false);
|
|
||||||
}
|
|
||||||
}, [refresh, fetchHook]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider>
|
<DashboardInsider>
|
||||||
@@ -79,9 +93,8 @@ function ProfitLossSheet({
|
|||||||
|
|
||||||
<div class="financial-statement__body">
|
<div class="financial-statement__body">
|
||||||
<ProfitLossSheetTable
|
<ProfitLossSheetTable
|
||||||
companyName={organizationSettings.name}
|
companyName={organizationName}
|
||||||
profitLossQuery={filter}
|
profitLossQuery={filter} />
|
||||||
onFetchData={handleFetchData} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
@@ -92,5 +105,8 @@ function ProfitLossSheet({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withProfitLossActions,
|
withProfitLossActions,
|
||||||
withSettings,
|
withProfitLoss(({ profitLossSheetRefresh }) => ({ profitLossSheetRefresh })),
|
||||||
|
withSettings(({ organizationSettings }) => ({
|
||||||
|
organizationName: organizationSettings.name,
|
||||||
|
})),
|
||||||
)(ProfitLossSheet);
|
)(ProfitLossSheet);
|
||||||
@@ -1,126 +1,102 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Row, Col, Visible } from 'react-grid-system';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useFormik } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { FormGroup } from '@blueprintjs/core';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
|
||||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||||
import SelectsListColumnsBy from '../SelectDisplayColumnsBy';
|
import ProfitLossSheetHeaderGeneralPane from './ProfitLossSheetHeaderGeneralPane';
|
||||||
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
|
||||||
import FinancialAccountsFilter from '../FinancialAccountsFilter';
|
|
||||||
|
|
||||||
import withProfitLoss from './withProfitLoss';
|
import withProfitLoss from './withProfitLoss';
|
||||||
import withProfitLossActions from './withProfitLossActions';
|
import withProfitLossActions from './withProfitLossActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
function ProfitLossHeader({
|
function ProfitLossHeader({
|
||||||
|
// #ownProps
|
||||||
pageFilter,
|
pageFilter,
|
||||||
onSubmitFilter,
|
onSubmitFilter,
|
||||||
|
|
||||||
// #withProfitLoss
|
// #withProfitLoss
|
||||||
profitLossSheetFilter,
|
profitLossSheetFilter,
|
||||||
profitLossSheetRefresh,
|
|
||||||
|
|
||||||
// #withProfitLossActions
|
// #withProfitLossActions
|
||||||
refreshProfitLossSheet,
|
toggleProfitLossSheetFilter,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const formik = useFormik({
|
|
||||||
enableReinitialize: true,
|
// Validation schema.
|
||||||
initialValues: {
|
const validationSchema = Yup.object().shape({
|
||||||
...pageFilter,
|
fromDate: Yup.date()
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
|
||||||
to_date: moment(pageFilter.to_date).toDate(),
|
|
||||||
},
|
|
||||||
validationSchema: Yup.object().shape({
|
|
||||||
from_date: Yup.date()
|
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'from_date' })),
|
.label(formatMessage({ id: 'from_date' })),
|
||||||
to_date: Yup.date()
|
toDate: Yup.date()
|
||||||
.min(Yup.ref('from_date'))
|
.min(Yup.ref('fromDate'))
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'to_date' })),
|
.label(formatMessage({ id: 'to_date' })),
|
||||||
}),
|
accountsFilter: Yup.string(),
|
||||||
onSubmit: (values, actions) => {
|
displayColumnsType: Yup.string(),
|
||||||
onSubmitFilter(values);
|
|
||||||
actions.setSubmitting(false);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle item select of `display columns by` field.
|
// Initial values.
|
||||||
const handleItemSelectDisplayColumns = useCallback(
|
const initialValues = {
|
||||||
(item) => {
|
...pageFilter,
|
||||||
formik.setFieldValue('display_columns_type', item.type);
|
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||||
formik.setFieldValue('display_columns_by', item.by);
|
toDate: moment(pageFilter.toDate).toDate(),
|
||||||
},
|
};
|
||||||
[formik],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAccountingBasisChange = useCallback(
|
// Handle form submit.
|
||||||
(value) => {
|
const handleSubmit = (values, actions) => {
|
||||||
formik.setFieldValue('basis', value);
|
onSubmitFilter(values);
|
||||||
},
|
toggleProfitLossSheetFilter();
|
||||||
[formik],
|
};
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Handles the cancel button click.
|
||||||
if (profitLossSheetRefresh) {
|
const handleCancelClick = () => {
|
||||||
formik.submitForm();
|
toggleProfitLossSheetFilter();
|
||||||
refreshProfitLossSheet(false);
|
};
|
||||||
}
|
// Handles the drawer close action.
|
||||||
}, [profitLossSheetRefresh]);
|
const handleDrawerClose = () => {
|
||||||
|
toggleProfitLossSheetFilter();
|
||||||
const handleAccountsFilterSelect = (filterType) => {
|
|
||||||
const noneZero = filterType.key === 'without-zero-balance' ? true : false;
|
|
||||||
formik.setFieldValue('none_zero', noneZero);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader show={profitLossSheetFilter}>
|
<FinancialStatementHeader
|
||||||
<Row>
|
isOpen={profitLossSheetFilter}
|
||||||
<FinancialStatementDateRange formik={formik} />
|
drawerProps={{ onClose: handleDrawerClose }}
|
||||||
<Visible xl><Col width={'100%'} /></Visible>
|
|
||||||
|
|
||||||
<Col width={260}>
|
|
||||||
<SelectsListColumnsBy onItemSelect={handleItemSelectDisplayColumns} />
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col width={260}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'filter_accounts'} />}
|
|
||||||
className="form-group--select-list bp3-fill"
|
|
||||||
inline={false}
|
|
||||||
>
|
>
|
||||||
<FinancialAccountsFilter
|
<Formik
|
||||||
initialSelectedItem={'all-accounts'}
|
validationSchema={validationSchema}
|
||||||
onItemSelect={handleAccountsFilterSelect}
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<Tabs animate={true} vertical={true} renderActiveTabPanelOnly={true}>
|
||||||
|
<Tab
|
||||||
|
id="general"
|
||||||
|
title={<T id={'general'} />}
|
||||||
|
panel={<ProfitLossSheetHeaderGeneralPane />}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</Tabs>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col width={260}>
|
<div class="financial-header-drawer__footer">
|
||||||
<RadiosAccountingBasis
|
<Button className={'mr1'} intent={Intent.PRIMARY} type={'submit'}>
|
||||||
selectedValue={formik.values.basis}
|
<T id={'calculate_report'} />
|
||||||
onChange={handleAccountingBasisChange}
|
</Button>
|
||||||
/>
|
<Button onClick={handleCancelClick} minimal={true}>
|
||||||
</Col>
|
<T id={'cancel'} />
|
||||||
</Row>
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
</FinancialStatementHeader>
|
</FinancialStatementHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withProfitLoss(({
|
withProfitLoss(({ profitLossSheetFilter }) => ({
|
||||||
profitLossSheetFilter,
|
profitLossSheetFilter,
|
||||||
profitLossSheetRefresh,
|
|
||||||
}) => ({
|
|
||||||
profitLossSheetFilter,
|
|
||||||
profitLossSheetRefresh,
|
|
||||||
})),
|
})),
|
||||||
withProfitLossActions,
|
withProfitLossActions,
|
||||||
)(ProfitLossHeader);
|
)(ProfitLossHeader);
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
|
||||||
|
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
||||||
|
import FinancialAccountsFilter from '../FinancialAccountsFilter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profit/Loss sheet - Drawer header - General panel.
|
||||||
|
*/
|
||||||
|
export default function ProfitLossSheetHeaderGeneralPane({}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FinancialStatementDateRange />
|
||||||
|
<SelectDisplayColumnsBy />
|
||||||
|
<FinancialAccountsFilter initialSelectedItem={'all-accounts'} />
|
||||||
|
<RadiosAccountingBasis key={'basis'} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import React, { useMemo, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import FinancialSheet from 'components/FinancialSheet';
|
import FinancialSheet from 'components/FinancialSheet';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
import Money from 'components/Money';
|
import Money from 'components/Money';
|
||||||
|
|
||||||
import { compose, defaultExpanderReducer } from 'utils';
|
import { compose, defaultExpanderReducer, getColumnWidth } from 'utils';
|
||||||
import { getFinancialSheetIndexByQuery } from 'store/financialStatement/financialStatements.selectors';
|
|
||||||
import withProfitLossDetail from './withProfitLoss';
|
import withProfitLossDetail from './withProfitLoss';
|
||||||
|
|
||||||
function ProfitLossSheetTable({
|
function ProfitLossSheetTable({
|
||||||
@@ -18,7 +16,6 @@ function ProfitLossSheetTable({
|
|||||||
profitLossSheetLoading,
|
profitLossSheetLoading,
|
||||||
|
|
||||||
// #ownProps
|
// #ownProps
|
||||||
onFetchData,
|
|
||||||
companyName,
|
companyName,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@@ -26,14 +23,10 @@ function ProfitLossSheetTable({
|
|||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'account_name' }),
|
Header: formatMessage({ id: 'account' }),
|
||||||
accessor: 'name',
|
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
|
||||||
className: 'name',
|
className: 'name',
|
||||||
},
|
width: 240,
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'account_code' }),
|
|
||||||
accessor: 'code',
|
|
||||||
className: 'account_code',
|
|
||||||
},
|
},
|
||||||
...(profitLossQuery.display_columns_type === 'total'
|
...(profitLossQuery.display_columns_type === 'total'
|
||||||
? [
|
? [
|
||||||
@@ -45,13 +38,14 @@ function ProfitLossSheetTable({
|
|||||||
return (
|
return (
|
||||||
<Money
|
<Money
|
||||||
amount={row.total.formatted_amount}
|
amount={row.total.formatted_amount}
|
||||||
currency={'USD'}
|
currency={row.total.currency_code}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
className: 'total',
|
className: 'total',
|
||||||
|
width: 140,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
@@ -60,40 +54,44 @@ function ProfitLossSheetTable({
|
|||||||
id: `date_period_${index}`,
|
id: `date_period_${index}`,
|
||||||
Header: column,
|
Header: column,
|
||||||
accessor: (row) => {
|
accessor: (row) => {
|
||||||
if (row.periods && row.periods[index]) {
|
if (row.total_periods && row.total_periods[index]) {
|
||||||
const amount = row.periods[index].formatted_amount;
|
const amount = row.total_periods[index].formatted_amount;
|
||||||
return <Money amount={amount} currency={'USD'} />;
|
return <Money amount={amount} currency={'USD'} />;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
width: 100,
|
width: getColumnWidth(
|
||||||
|
profitLossTableRows,
|
||||||
|
`total_periods.${index}.formatted_amount`,
|
||||||
|
{ minWidth: 100 },
|
||||||
|
),
|
||||||
|
className: 'total-period',
|
||||||
}))
|
}))
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
[profitLossQuery.display_columns_type, profitLossColumns, formatMessage],
|
[profitLossQuery.display_columns_type, profitLossTableRows, profitLossColumns, formatMessage],
|
||||||
);
|
|
||||||
|
|
||||||
// Handle data table fetch data.
|
|
||||||
const handleFetchData = useCallback(
|
|
||||||
(...args) => {
|
|
||||||
onFetchData && onFetchData(...args);
|
|
||||||
},
|
|
||||||
[onFetchData],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Retrieve default expanded rows of balance sheet.
|
// Retrieve default expanded rows of balance sheet.
|
||||||
const expandedRows = useMemo(
|
const expandedRows = useMemo(
|
||||||
() => defaultExpanderReducer(profitLossTableRows, 1),
|
() => defaultExpanderReducer(profitLossTableRows, 3),
|
||||||
[profitLossTableRows],
|
[profitLossTableRows],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Retrieve conditional datatable row classnames.
|
// Retrieve conditional datatable row classnames.
|
||||||
const rowClassNames = useCallback(
|
const rowClassNames = useCallback((row) => {
|
||||||
(row) => ({
|
const { original } = row;
|
||||||
[`row--${row.rowType}`]: row.rowType,
|
const rowTypes = Array.isArray(original.rowTypes)
|
||||||
}),
|
? original.rowTypes
|
||||||
[],
|
: [];
|
||||||
);
|
|
||||||
|
return {
|
||||||
|
...rowTypes.reduce((acc, rowType) => {
|
||||||
|
acc[`row_type--${rowType}`] = rowType;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialSheet
|
<FinancialSheet
|
||||||
@@ -109,7 +107,6 @@ function ProfitLossSheetTable({
|
|||||||
className="bigcapital-datatable--financial-report"
|
className="bigcapital-datatable--financial-report"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={profitLossTableRows}
|
data={profitLossTableRows}
|
||||||
onFetchData={handleFetchData}
|
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
expanded={expandedRows}
|
expanded={expandedRows}
|
||||||
rowClassNames={rowClassNames}
|
rowClassNames={rowClassNames}
|
||||||
@@ -121,19 +118,14 @@ function ProfitLossSheetTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
|
||||||
profitLossIndex: getFinancialSheetIndexByQuery(
|
|
||||||
state.financialStatements.profitLoss.sheets,
|
|
||||||
props.profitLossQuery,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const withProfitLossTable = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withProfitLossTable,
|
|
||||||
withProfitLossDetail(
|
withProfitLossDetail(
|
||||||
({ profitLossQuery, profitLossColumns, profitLossTableRows, profitLossSheetLoading }) => ({
|
({
|
||||||
|
profitLossQuery,
|
||||||
|
profitLossColumns,
|
||||||
|
profitLossTableRows,
|
||||||
|
profitLossSheetLoading,
|
||||||
|
}) => ({
|
||||||
profitLossColumns,
|
profitLossColumns,
|
||||||
profitLossQuery,
|
profitLossQuery,
|
||||||
profitLossTableRows,
|
profitLossTableRows,
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getFinancialSheetIndexByQuery,
|
getFinancialSheetFactory,
|
||||||
getFinancialSheet,
|
getFinancialSheetColumnsFactory,
|
||||||
getFinancialSheetColumns,
|
getFinancialSheetQueryFactory,
|
||||||
getFinancialSheetQuery,
|
getFinancialSheetTableRowsFactory,
|
||||||
getFinancialSheetTableRows,
|
|
||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const { profitLossIndex } = props;
|
const getProfitLossSheet = getFinancialSheetFactory('profitLoss');
|
||||||
|
const getProfitLossColumns = getFinancialSheetColumnsFactory('profitLoss');
|
||||||
|
const getProfitLossQuery = getFinancialSheetQueryFactory('profitLoss');
|
||||||
|
const getProfitLossTableRows = getFinancialSheetTableRowsFactory('profitLoss');
|
||||||
|
|
||||||
const mapped = {
|
const mapped = {
|
||||||
profitLossSheet: getFinancialSheet(state.financialStatements.profitLoss.sheets, profitLossIndex),
|
profitLossSheet: getProfitLossSheet(state, props),
|
||||||
profitLossColumns: getFinancialSheetColumns(state.financialStatements.profitLoss.sheets, profitLossIndex),
|
profitLossColumns: getProfitLossColumns(state, props),
|
||||||
profitLossQuery: getFinancialSheetQuery(state.financialStatements.profitLoss.sheets, profitLossIndex),
|
profitLossQuery: getProfitLossQuery(state, props),
|
||||||
profitLossTableRows: getFinancialSheetTableRows(state.financialStatements.profitLoss.sheets, profitLossIndex),
|
profitLossTableRows: getProfitLossTableRows(state, props),
|
||||||
|
|
||||||
profitLossSheetLoading: state.financialStatements.profitLoss.loading,
|
profitLossSheetLoading: state.financialStatements.profitLoss.loading,
|
||||||
profitLossSheetFilter: state.financialStatements.profitLoss.filter,
|
profitLossSheetFilter: state.financialStatements.profitLoss.filter,
|
||||||
|
|||||||
@@ -1,28 +1,34 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FastField } from 'formik';
|
||||||
import { handleStringChange } from 'utils';
|
import { handleStringChange } from 'utils';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import {
|
import { RadioGroup, Radio } from '@blueprintjs/core';
|
||||||
RadioGroup,
|
|
||||||
Radio,
|
|
||||||
} from "@blueprintjs/core";
|
|
||||||
|
|
||||||
|
|
||||||
export default function RadiosAccountingBasis(props) {
|
export default function RadiosAccountingBasis(props) {
|
||||||
const { onChange, ...rest } = props;
|
const { key = 'basis', ...rest } = props;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<FastField name={'basis'}>
|
||||||
|
{({
|
||||||
|
form: { setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
}) => (
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
inline={true}
|
inline={true}
|
||||||
label={formatMessage({'id': 'accounting_basis'})}
|
label={formatMessage({ id: 'accounting_basis' })}
|
||||||
name="basis"
|
name="basis"
|
||||||
onChange={handleStringChange((value) => {
|
onChange={handleStringChange((value) => {
|
||||||
onChange && onChange(value);
|
setFieldValue(key, value);
|
||||||
})}
|
})}
|
||||||
className={'radio-group---accounting-basis'}
|
className={'radio-group---accounting-basis'}
|
||||||
{...rest}>
|
selectedValue={value}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
<Radio label={formatMessage({ id: 'cash' })} value="cash" />
|
<Radio label={formatMessage({ id: 'cash' })} value="cash" />
|
||||||
<Radio label={formatMessage({ id: 'accrual' })} value="accural" />
|
<Radio label={formatMessage({ id: 'accrual' })} value="accural" />
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,58 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormGroup } from '@blueprintjs/core';
|
||||||
import React, { useMemo, useState, useCallback } from 'react';
|
import { FastField } from 'formik';
|
||||||
import SelectList from 'components/SelectList';
|
|
||||||
import {
|
|
||||||
FormGroup,
|
|
||||||
MenuItem,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import { Row, Col, ListSelect } from 'components';
|
||||||
import { MODIFIER } from 'components';
|
import { displayColumnsByOptions } from 'containers/FinancialStatements/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Financial statement - Display columns by and type select.
|
||||||
|
*/
|
||||||
export default function SelectsListColumnsBy(props) {
|
export default function SelectsListColumnsBy(props) {
|
||||||
const { onItemSelect, formGroupProps, selectListProps } = props;
|
const { formGroupProps, selectListProps } = props;
|
||||||
const [itemSelected, setItemSelected] = useState(null);
|
|
||||||
|
|
||||||
const displayColumnsByOptions = useMemo(() => [
|
|
||||||
{key: 'total', name: 'Total', type: 'total', by: '', },
|
|
||||||
{key: 'year', name: 'Date/Year', type: 'date_periods', by: 'year'},
|
|
||||||
{key: 'month', name: 'Date/Month', type: 'date_periods', by: 'month'},
|
|
||||||
{key: 'week', name: 'Date/Week', type: 'date_periods', by: 'month'},
|
|
||||||
{key: 'day', name: 'Date/Day', type: 'date_periods', by: 'day'},
|
|
||||||
{key: 'quarter', name: 'Date/Quarter', type: 'date_periods', by: 'quarter'},
|
|
||||||
],[]);
|
|
||||||
|
|
||||||
const itemRenderer = useCallback((item, { handleClick, modifiers, query }) => {
|
|
||||||
return (<MenuItem text={item.name} key={item.id} onClick={handleClick} />);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleItemSelect = useCallback((item) => {
|
|
||||||
setItemSelected(item);
|
|
||||||
onItemSelect && onItemSelect(item);
|
|
||||||
}, [setItemSelected, onItemSelect]);
|
|
||||||
|
|
||||||
const buttonLabel = useMemo(() =>
|
|
||||||
itemSelected ? itemSelected.name : <T id={'select_display_columns_by'}/>,
|
|
||||||
[itemSelected]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col xs={4}>
|
||||||
|
<FastField name={'displayColumnsType'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'display_report_columns'} />}
|
label={<T id={'display_report_columns'} />}
|
||||||
className="form-group-display-columns-by form-group--select-list bp3-fill"
|
className="form-group-display-columns-by form-group--select-list bp3-fill"
|
||||||
inline={false}
|
inline={false}
|
||||||
{...formGroupProps}>
|
{...formGroupProps}
|
||||||
|
>
|
||||||
<SelectList
|
<ListSelect
|
||||||
items={displayColumnsByOptions}
|
items={displayColumnsByOptions}
|
||||||
noResults={<MenuItem disabled={true} text="No results." />}
|
|
||||||
filterable={false}
|
filterable={false}
|
||||||
itemRenderer={itemRenderer}
|
selectedItem={value}
|
||||||
popoverProps={{ minimal: true, usePortal: false, inline: true }}
|
selectedItemProp={'key'}
|
||||||
buttonLabel={buttonLabel}
|
labelProp={'name'}
|
||||||
onItemSelect={handleItemSelect}
|
onItemSelect={(item) => {
|
||||||
className={classNames(MODIFIER.SELECT_LIST_FILL_POPOVER)}
|
form.setFieldValue('displayColumnsType', item.key);
|
||||||
{...selectListProps} />
|
}}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
{...selectListProps}
|
||||||
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavbarGroup, Button, Classes, NavbarDivider } from '@blueprintjs/core';
|
import {
|
||||||
import Icon from 'components/Icon';
|
NavbarGroup,
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
Button,
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
Classes,
|
||||||
|
NavbarDivider,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
// import FilterDropdown from 'components/FilterDropdown';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
|
||||||
import { If } from 'components';
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
import withTrialBalance from './withTrialBalance';
|
import withTrialBalance from './withTrialBalance';
|
||||||
import withTrialBalanceActions from './withTrialBalanceActions';
|
import withTrialBalanceActions from './withTrialBalanceActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
function TrialBalanceActionsBar({
|
function TrialBalanceActionsBar({
|
||||||
|
|
||||||
// #withTrialBalance
|
// #withTrialBalance
|
||||||
trialBalanceSheetFilter,
|
trialBalanceSheetFilter,
|
||||||
|
|
||||||
@@ -22,7 +26,6 @@ function TrialBalanceActionsBar({
|
|||||||
toggleTrialBalanceFilter,
|
toggleTrialBalanceFilter,
|
||||||
refreshTrialBalance,
|
refreshTrialBalance,
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
const handleFilterToggleClick = () => {
|
const handleFilterToggleClick = () => {
|
||||||
toggleTrialBalanceFilter();
|
toggleTrialBalanceFilter();
|
||||||
};
|
};
|
||||||
@@ -35,45 +38,43 @@ function TrialBalanceActionsBar({
|
|||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
|
||||||
icon={<Icon icon="cog-16" iconSize={16} />}
|
|
||||||
text={<T id={'customize_report'} />}
|
|
||||||
/>
|
|
||||||
<NavbarDivider />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className={classNames(
|
|
||||||
Classes.MINIMAL,
|
|
||||||
'button--gray-highlight',
|
|
||||||
)}
|
|
||||||
text={'Re-calc Report'}
|
text={'Re-calc Report'}
|
||||||
onClick={handleRecalcReport}
|
onClick={handleRecalcReport}
|
||||||
icon={<Icon icon="refresh-16" iconSize={16} />}
|
icon={<Icon icon="refresh-16" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<If condition={trialBalanceSheetFilter}>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
text={<T id={'hide_filter'} />}
|
|
||||||
icon={<Icon icon="arrow-to-top" />}
|
|
||||||
onClick={handleFilterToggleClick}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<If condition={!trialBalanceSheetFilter}>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
text={<T id={'show_filter'} />}
|
|
||||||
icon={<Icon icon="arrow-to-bottom" />}
|
|
||||||
onClick={handleFilterToggleClick}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
|
icon={<Icon icon="cog-16" iconSize={16} />}
|
||||||
|
text={
|
||||||
|
trialBalanceSheetFilter ? (
|
||||||
|
<T id={'hide_customizer'} />
|
||||||
|
) : (
|
||||||
|
<T id={'customize_report'} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
active={trialBalanceSheetFilter}
|
||||||
|
onClick={handleFilterToggleClick}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
// content={}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
|
text={<T id={'filter'} />}
|
||||||
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='print-16' iconSize={16} />}
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
text={<T id={'print'} />}
|
text={<T id={'print'} />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@@ -87,6 +88,8 @@ function TrialBalanceActionsBar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withTrialBalance(({ trialBalanceSheetFilter }) => ({ trialBalanceSheetFilter })),
|
withTrialBalance(({ trialBalanceSheetFilter }) => ({
|
||||||
withTrialBalanceActions
|
trialBalanceSheetFilter,
|
||||||
|
})),
|
||||||
|
withTrialBalanceActions,
|
||||||
)(TrialBalanceActionsBar);
|
)(TrialBalanceActionsBar);
|
||||||
@@ -2,73 +2,93 @@ import React, { useEffect, useCallback, useState } from 'react';
|
|||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
|
import TrialBalanceActionsBar from './TrialBalanceActionsBar';
|
||||||
import TrialBalanceSheetHeader from './TrialBalanceSheetHeader';
|
import TrialBalanceSheetHeader from './TrialBalanceSheetHeader';
|
||||||
import TrialBalanceSheetTable from './TrialBalanceSheetTable';
|
import TrialBalanceSheetTable from './TrialBalanceSheetTable';
|
||||||
import TrialBalanceActionsBar from './TrialBalanceActionsBar';
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
|
||||||
|
|
||||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withTrialBalanceActions from './withTrialBalanceActions';
|
import withTrialBalanceActions from './withTrialBalanceActions';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withTrialBalance from './withTrialBalance';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trial balance sheet.
|
||||||
|
*/
|
||||||
function TrialBalanceSheet({
|
function TrialBalanceSheet({
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
setDashboardBackLink,
|
||||||
|
|
||||||
|
// #withTrialBalance
|
||||||
|
trialBalanceSheetRefresh,
|
||||||
|
|
||||||
// #withTrialBalanceActions
|
// #withTrialBalanceActions
|
||||||
fetchTrialBalanceSheet,
|
fetchTrialBalanceSheet,
|
||||||
|
refreshTrialBalance,
|
||||||
|
|
||||||
// #withPreferences
|
// #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'),
|
|
||||||
basis: 'accural',
|
|
||||||
none_zero: false,
|
|
||||||
});
|
|
||||||
const [refresh, setRefresh] = useState(true);
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const fetchHook = useQuery(
|
const [filter, setFilter] = useState({
|
||||||
['trial-balance', filter],
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
(key, query) => fetchTrialBalanceSheet(query),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
|
basis: 'accural',
|
||||||
|
accountsFilter: 'all-accounts',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetches trial balance sheet.
|
||||||
|
const fetchSheet = useQuery(
|
||||||
|
['trial-balance-sheet', filter],
|
||||||
|
(key, query) =>
|
||||||
|
fetchTrialBalanceSheet({
|
||||||
|
...transformFilterFormToQuery(query),
|
||||||
|
}),
|
||||||
{ manual: true },
|
{ manual: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// handle fetch data of trial balance table.
|
|
||||||
const handleFetchData = useCallback(() => {
|
|
||||||
setRefresh(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Change page title of the dashboard.
|
// Change page title of the dashboard.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(formatMessage({ id: 'trial_balance_sheet' }));
|
changePageTitle(formatMessage({ id: 'trial_balance_sheet' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Show the back link on dashboard topbar.
|
||||||
|
setDashboardBackLink(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Hide the back link on dashboard topbar.
|
||||||
|
setDashboardBackLink(false);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const handleFilterSubmit = useCallback(
|
const handleFilterSubmit = useCallback(
|
||||||
(filter) => {
|
(filter) => {
|
||||||
const parsedFilter = {
|
const parsedFilter = {
|
||||||
...filter,
|
...filter,
|
||||||
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
|
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
|
||||||
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
|
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
|
||||||
};
|
};
|
||||||
setFilter(parsedFilter);
|
setFilter(parsedFilter);
|
||||||
setRefresh(true);
|
refreshTrialBalance(true);
|
||||||
},
|
},
|
||||||
[fetchHook],
|
[setFilter, refreshTrialBalance],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Observes the trial balance sheet refresh to invaoid the query.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refresh) {
|
if (trialBalanceSheetRefresh) {
|
||||||
fetchHook.refetch({ force: true });
|
queryCache.invalidateQueries('trial-balance-sheet');
|
||||||
setRefresh(false);
|
refreshTrialBalance(false);
|
||||||
}
|
}
|
||||||
}, [refresh, fetchHook.refetch]);
|
}, [trialBalanceSheetRefresh, refreshTrialBalance]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider>
|
<DashboardInsider>
|
||||||
@@ -82,11 +102,7 @@ function TrialBalanceSheet({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="financial-statement__body">
|
<div class="financial-statement__body">
|
||||||
<TrialBalanceSheetTable
|
<TrialBalanceSheetTable companyName={organizationName} />
|
||||||
companyName={organizationSettings.name}
|
|
||||||
trialBalanceQuery={filter}
|
|
||||||
onFetchData={handleFetchData}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
@@ -97,5 +113,10 @@ function TrialBalanceSheet({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withTrialBalanceActions,
|
withTrialBalanceActions,
|
||||||
withSettings,
|
withTrialBalance(({ trialBalanceSheetRefresh }) => ({
|
||||||
|
trialBalanceSheetRefresh,
|
||||||
|
})),
|
||||||
|
withSettings(({ organizationSettings }) => ({
|
||||||
|
organizationName: organizationSettings.name,
|
||||||
|
})),
|
||||||
)(TrialBalanceSheet);
|
)(TrialBalanceSheet);
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import React, { useEffect, useCallback } from 'react';
|
import React from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Row, Col, Visible } from 'react-grid-system';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { FormGroup } from '@blueprintjs/core';
|
import { Formik, Form } from 'formik';
|
||||||
import { useFormik } from 'formik';
|
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||||
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
import TrialBalanceSheetHeaderGeneralPanel from './TrialBalanceSheetHeaderGeneralPanel';
|
||||||
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
|
||||||
import FinancialAccountsFilter from '../FinancialAccountsFilter';
|
|
||||||
|
|
||||||
import withTrialBalance from './withTrialBalance';
|
import withTrialBalance from './withTrialBalance';
|
||||||
import withTrialBalanceActions from './withTrialBalanceActions';
|
import withTrialBalanceActions from './withTrialBalanceActions';
|
||||||
@@ -17,6 +14,7 @@ import withTrialBalanceActions from './withTrialBalanceActions';
|
|||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
function TrialBalanceSheetHeader({
|
function TrialBalanceSheetHeader({
|
||||||
|
// #ownProps
|
||||||
pageFilter,
|
pageFilter,
|
||||||
onSubmitFilter,
|
onSubmitFilter,
|
||||||
|
|
||||||
@@ -26,78 +24,78 @@ function TrialBalanceSheetHeader({
|
|||||||
|
|
||||||
// #withTrialBalanceActions
|
// #withTrialBalanceActions
|
||||||
refreshTrialBalance,
|
refreshTrialBalance,
|
||||||
|
toggleTrialBalanceFilter
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const formik = useFormik({
|
|
||||||
enableReinitialize: true,
|
// Form validation schema.
|
||||||
initialValues: {
|
const validationSchema = Yup.object().shape({
|
||||||
...pageFilter,
|
fromDate: Yup.date()
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
|
||||||
to_date: moment(pageFilter.to_date).toDate(),
|
|
||||||
},
|
|
||||||
validationSchema: Yup.object().shape({
|
|
||||||
from_date: Yup.date()
|
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'from_date' })),
|
.label(formatMessage({ id: 'from_date' })),
|
||||||
to_date: Yup.date()
|
toDate: Yup.date()
|
||||||
.min(Yup.ref('from_date'))
|
.min(Yup.ref('fromDate'))
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'to_date' })),
|
.label(formatMessage({ id: 'to_date' })),
|
||||||
}),
|
|
||||||
onSubmit: (values, { setSubmitting }) => {
|
|
||||||
onSubmitFilter(values);
|
|
||||||
setSubmitting(false);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
// Initial values.
|
||||||
if (trialBalanceSheetRefresh) {
|
const initialValues = {
|
||||||
formik.submitForm();
|
...pageFilter,
|
||||||
refreshTrialBalance(false);
|
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||||
}
|
toDate: moment(pageFilter.toDate).toDate(),
|
||||||
}, [formik, trialBalanceSheetRefresh]);
|
};
|
||||||
|
|
||||||
const handleAccountingBasisChange = useCallback(
|
// Handle form submit.
|
||||||
(value) => {
|
const handleSubmit = (values, { setSubmitting }) => {
|
||||||
formik.setFieldValue('basis', value);
|
onSubmitFilter(values);
|
||||||
},
|
setSubmitting(false);
|
||||||
[formik],
|
toggleTrialBalanceFilter(false);
|
||||||
);
|
};
|
||||||
|
|
||||||
const handleAccountsFilterSelect = (filterType) => {
|
// Handle drawer close action.
|
||||||
const noneZero = filterType.key === 'without-zero-balance' ? true : false;
|
const handleDrawerClose = () => {
|
||||||
formik.setFieldValue('none_zero', noneZero);
|
toggleTrialBalanceFilter(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle cancel button click.
|
||||||
|
const handleCancelClick = () => {
|
||||||
|
toggleTrialBalanceFilter(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader show={trialBalanceSheetFilter}>
|
<FinancialStatementHeader
|
||||||
<Row>
|
isOpen={trialBalanceSheetFilter}
|
||||||
<FinancialStatementDateRange formik={formik} />
|
drawerProps={{ onClose: handleDrawerClose }}
|
||||||
|
|
||||||
<Visible xl>
|
|
||||||
<Col width={'100%'} />
|
|
||||||
</Visible>
|
|
||||||
|
|
||||||
<Col width={260}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'filter_accounts'} />}
|
|
||||||
className="form-group--select-list bp3-fill"
|
|
||||||
inline={false}
|
|
||||||
>
|
>
|
||||||
<FinancialAccountsFilter
|
<Formik
|
||||||
initialSelectedItem={'all-accounts'}
|
initialValues={initialValues}
|
||||||
onItemSelect={handleAccountsFilterSelect}
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<Tabs animate={true} vertical={true} renderActiveTabPanelOnly={true}>
|
||||||
|
<Tab
|
||||||
|
id="general"
|
||||||
|
title={<T id={'general'} />}
|
||||||
|
panel={<TrialBalanceSheetHeaderGeneralPanel />}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</Tabs>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col width={260}>
|
<div class="financial-header-drawer__footer">
|
||||||
<RadiosAccountingBasis
|
<Button
|
||||||
selectedValue={formik.values.basis}
|
className={'mr1'}
|
||||||
onChange={handleAccountingBasisChange}
|
intent={Intent.PRIMARY}
|
||||||
/>
|
type={'submit'}
|
||||||
</Col>
|
>
|
||||||
</Row>
|
<T id={'calculate_report'} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancelClick} minimal={true}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
</FinancialStatementHeader>
|
</FinancialStatementHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
||||||
|
import FinancialAccountsFilter from '../FinancialAccountsFilter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trial balance sheet - Drawer header - General panel.
|
||||||
|
*/
|
||||||
|
export default function TrialBalanceSheetHeaderGeneralPanel({
|
||||||
|
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FinancialStatementDateRange />
|
||||||
|
<FinancialAccountsFilter initialSelectedItem={'all-accounts'} />
|
||||||
|
<RadiosAccountingBasis />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
import FinancialSheet from 'components/FinancialSheet';
|
import FinancialSheet from 'components/FinancialSheet';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
import Money from 'components/Money';
|
import Money from 'components/Money';
|
||||||
import { getFinancialSheetIndexByQuery } from 'store/financialStatement/financialStatements.selectors';
|
|
||||||
|
|
||||||
import withTrialBalance from './withTrialBalance';
|
import withTrialBalance from './withTrialBalance';
|
||||||
|
|
||||||
@@ -13,14 +11,12 @@ import { compose } from 'utils';
|
|||||||
|
|
||||||
function TrialBalanceSheetTable({
|
function TrialBalanceSheetTable({
|
||||||
// #withTrialBalanceDetail
|
// #withTrialBalanceDetail
|
||||||
trialBalanceAccounts,
|
trialBalance,
|
||||||
trialBalanceSheetLoading,
|
trialBalanceSheetLoading,
|
||||||
|
|
||||||
// #withTrialBalanceTable
|
// #withTrialBalanceTable
|
||||||
trialBalanceIndex,
|
|
||||||
trialBalanceQuery,
|
trialBalanceQuery,
|
||||||
|
|
||||||
onFetchData,
|
|
||||||
companyName,
|
companyName,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@@ -29,55 +25,46 @@ function TrialBalanceSheetTable({
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'account_name' }),
|
Header: formatMessage({ id: 'account_name' }),
|
||||||
accessor: 'name',
|
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
|
||||||
className: 'name',
|
className: 'name',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
maxWidth: 150,
|
maxWidth: 150,
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'code' }),
|
|
||||||
accessor: 'code',
|
|
||||||
className: 'code',
|
|
||||||
minWidth: 80,
|
|
||||||
maxWidth: 80,
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'credit' }),
|
Header: formatMessage({ id: 'credit' }),
|
||||||
accessor: 'credit',
|
accessor: 'credit',
|
||||||
Cell: ({ cell }) => <Money amount={cell.row.original.credit} currency="USD" />,
|
Cell: ({ cell }) => {
|
||||||
|
const { currency_code, credit } = cell.row.original;
|
||||||
|
return (<Money amount={credit} currency={currency_code} />);
|
||||||
|
},
|
||||||
className: 'credit',
|
className: 'credit',
|
||||||
minWidth: 95,
|
|
||||||
maxWidth: 95,
|
|
||||||
width: 95,
|
width: 95,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'debit' }),
|
Header: formatMessage({ id: 'debit' }),
|
||||||
accessor: 'debit',
|
accessor: 'debit',
|
||||||
Cell: ({ cell }) => <Money amount={cell.row.original.debit} currency="USD" />,
|
Cell: ({ cell }) => {
|
||||||
|
const { currency_code, debit } = cell.row.original;
|
||||||
|
return (<Money amount={debit} currency={currency_code} />);
|
||||||
|
},
|
||||||
className: 'debit',
|
className: 'debit',
|
||||||
minWidth: 95,
|
|
||||||
maxWidth: 95,
|
|
||||||
width: 95,
|
width: 95,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'balance' }),
|
Header: formatMessage({ id: 'balance' }),
|
||||||
accessor: 'balance',
|
accessor: 'balance',
|
||||||
Cell: ({ cell }) => <Money amount={cell.row.original.balance} currency="USD" />,
|
Cell: ({ cell }) => {
|
||||||
|
const { currency_code, balance } = cell.row.original;
|
||||||
|
return (<Money amount={balance} currency={currency_code} />);
|
||||||
|
},
|
||||||
className: 'balance',
|
className: 'balance',
|
||||||
minWidth: 95,
|
|
||||||
maxWidth: 95,
|
|
||||||
width: 95,
|
width: 95,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[formatMessage],
|
[formatMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFetchData = useCallback(() => {
|
|
||||||
onFetchData && onFetchData();
|
|
||||||
}, [onFetchData]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialSheet
|
<FinancialSheet
|
||||||
companyName={companyName}
|
companyName={companyName}
|
||||||
@@ -86,12 +73,12 @@ function TrialBalanceSheetTable({
|
|||||||
toDate={trialBalanceQuery.to_date}
|
toDate={trialBalanceQuery.to_date}
|
||||||
name="trial-balance"
|
name="trial-balance"
|
||||||
loading={trialBalanceSheetLoading}
|
loading={trialBalanceSheetLoading}
|
||||||
|
basis={'cash'}
|
||||||
>
|
>
|
||||||
<DataTable
|
<DataTable
|
||||||
className="bigcapital-datatable--financial-report"
|
className="bigcapital-datatable--financial-report"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={trialBalanceAccounts}
|
data={trialBalance.data}
|
||||||
onFetchData={handleFetchData}
|
|
||||||
expandable={true}
|
expandable={true}
|
||||||
expandToggleColumn={1}
|
expandToggleColumn={1}
|
||||||
expandColumnSpace={1}
|
expandColumnSpace={1}
|
||||||
@@ -101,25 +88,14 @@ function TrialBalanceSheetTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
|
||||||
const { trialBalanceQuery } = props;
|
|
||||||
return {
|
|
||||||
trialBalanceIndex: getFinancialSheetIndexByQuery(
|
|
||||||
state.financialStatements.trialBalance.sheets,
|
|
||||||
trialBalanceQuery,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const withTrialBalanceTable = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withTrialBalanceTable,
|
|
||||||
withTrialBalance(({
|
withTrialBalance(({
|
||||||
trialBalanceAccounts,
|
trialBalance,
|
||||||
trialBalanceSheetLoading,
|
trialBalanceSheetLoading,
|
||||||
|
trialBalanceQuery
|
||||||
}) => ({
|
}) => ({
|
||||||
trialBalanceAccounts,
|
trialBalance,
|
||||||
trialBalanceSheetLoading
|
trialBalanceSheetLoading,
|
||||||
|
trialBalanceQuery
|
||||||
})),
|
})),
|
||||||
)(TrialBalanceSheetTable);
|
)(TrialBalanceSheetTable);
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getFinancialSheetAccounts,
|
getFinancialSheetFactory,
|
||||||
getFinancialSheetQuery,
|
getFinancialSheetQueryFactory,
|
||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const { trialBalanceIndex } = props;
|
const getTrialBalance = getFinancialSheetFactory('trialBalance');
|
||||||
|
const getBalanceSheetQuery = getFinancialSheetQueryFactory('trialBalance');
|
||||||
|
|
||||||
const mapped = {
|
const mapped = {
|
||||||
trialBalanceAccounts: getFinancialSheetAccounts(
|
trialBalance: getTrialBalance(state, props),
|
||||||
state.financialStatements.trialBalance.sheets,
|
trialBalanceQuery: getBalanceSheetQuery(state, props),
|
||||||
trialBalanceIndex
|
|
||||||
),
|
|
||||||
trialBalanceQuery: getFinancialSheetQuery(
|
|
||||||
state.financialStatements.trialBalance.sheets,
|
|
||||||
trialBalanceIndex
|
|
||||||
),
|
|
||||||
trialBalanceSheetLoading: state.financialStatements.trialBalance.loading,
|
trialBalanceSheetLoading: state.financialStatements.trialBalance.loading,
|
||||||
trialBalanceSheetFilter: state.financialStatements.trialBalance.filter,
|
trialBalanceSheetFilter: state.financialStatements.trialBalance.filter,
|
||||||
trialBalanceSheetRefresh: state.financialStatements.trialBalance.refresh,
|
trialBalanceSheetRefresh: state.financialStatements.trialBalance.refresh,
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|
||||||
return connect(mapStateToProps);
|
return connect(mapStateToProps);
|
||||||
};
|
};
|
||||||
|
|||||||
61
client/src/containers/FinancialStatements/common.js
Normal file
61
client/src/containers/FinancialStatements/common.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { mapKeys, omit, snakeCase } from 'lodash';
|
||||||
|
import { formatMessage } from 'services/intl';
|
||||||
|
|
||||||
|
export const displayColumnsByOptions = [
|
||||||
|
{ key: 'total', name: 'Total', type: 'total', by: '' },
|
||||||
|
{ key: 'year', name: 'Date/Year', type: 'date_periods', by: 'year' },
|
||||||
|
{ key: 'month', name: 'Date/Month', type: 'date_periods', by: 'month' },
|
||||||
|
{ key: 'week', name: 'Date/Week', type: 'date_periods', by: 'month' },
|
||||||
|
{ key: 'day', name: 'Date/Day', type: 'date_periods', by: 'day' },
|
||||||
|
{ key: 'quarter', name: 'Date/Quarter', type: 'date_periods', by: 'quarter' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const dateRangeOptions = [
|
||||||
|
{ value: 'today', label: 'Today' },
|
||||||
|
{ value: 'this_week', label: 'This Week' },
|
||||||
|
{ value: 'this_month', label: 'This Month' },
|
||||||
|
{ value: 'this_quarter', label: 'This Quarter' },
|
||||||
|
{ value: 'this_year', label: 'This Year' },
|
||||||
|
{ value: 'custom', label: 'Custom Range' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const filterAccountsOptions = [
|
||||||
|
{
|
||||||
|
key: 'all-accounts',
|
||||||
|
name: formatMessage({ id: 'all_accounts' }),
|
||||||
|
hint: formatMessage({ id: 'all_accounts_including_with_zero_balance' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'without-zero-balance',
|
||||||
|
name: formatMessage({ id: 'accounts_without_zero_balance' }),
|
||||||
|
hint: formatMessage({
|
||||||
|
id: 'include_accounts_and_exclude_zero_balance',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'with-transactions',
|
||||||
|
name: formatMessage({ id: 'accounts_with_transactions' }),
|
||||||
|
hint: formatMessage({
|
||||||
|
id: 'include_accounts_once_has_transactions_on_given_date_period',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const transformDisplayColumnsType = (form) => {
|
||||||
|
const columnType = displayColumnsByOptions.find(
|
||||||
|
(o) => o.key === form.displayColumnsType,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
displayColumnsBy: columnType ? columnType.by : '',
|
||||||
|
displayColumnsType: columnType ? columnType.type : 'total',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformFilterFormToQuery = (form) => {
|
||||||
|
return mapKeys({
|
||||||
|
...omit(form, ['accountsFilter']),
|
||||||
|
...transformDisplayColumnsType(form),
|
||||||
|
noneZero: form.accountsFilter === 'without-zero-balance',
|
||||||
|
noneTransactions: form.accountsFilter === 'with-transactions',
|
||||||
|
}, (v, k) => snakeCase(k));
|
||||||
|
};
|
||||||
@@ -9,6 +9,14 @@ import * as serviceWorker from 'serviceWorker';
|
|||||||
import { store, persistor } from 'store/createStore';
|
import { store, persistor } from 'store/createStore';
|
||||||
import AppProgress from 'components/NProgress/AppProgress';
|
import AppProgress from 'components/NProgress/AppProgress';
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||||
|
|
||||||
|
whyDidYouRender(React, {
|
||||||
|
trackAllPureComponents: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ export default {
|
|||||||
accrual: 'Accrual',
|
accrual: 'Accrual',
|
||||||
from: 'From',
|
from: 'From',
|
||||||
to: 'To',
|
to: 'To',
|
||||||
accounting_basis: 'Accounting Basis:',
|
accounting_basis: 'Accounting basis:',
|
||||||
general: 'General',
|
general: 'General',
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
currencies: 'Currencies',
|
currencies: 'Currencies',
|
||||||
@@ -282,7 +282,7 @@ export default {
|
|||||||
journal: 'Journal',
|
journal: 'Journal',
|
||||||
general_ledger: 'General Ledger',
|
general_ledger: 'General Ledger',
|
||||||
general_ledger_sheet: 'General Ledger Sheet',
|
general_ledger_sheet: 'General Ledger Sheet',
|
||||||
profit_loss_sheet: 'Profit Loss Sheet',
|
profit_loss_sheet: 'Profit/Loss Sheet',
|
||||||
expenses: 'Expenses',
|
expenses: 'Expenses',
|
||||||
expenses_list: 'Expenses List',
|
expenses_list: 'Expenses List',
|
||||||
new_expenses: 'New Expenses',
|
new_expenses: 'New Expenses',
|
||||||
@@ -335,8 +335,8 @@ export default {
|
|||||||
export: 'Export',
|
export: 'Export',
|
||||||
accounts_with_zero_balance: 'Accounts with Zero Balance',
|
accounts_with_zero_balance: 'Accounts with Zero Balance',
|
||||||
all_transactions: 'All Transactions',
|
all_transactions: 'All Transactions',
|
||||||
filter_accounts: 'Filter Accounts',
|
filter_accounts: 'Filter accounts',
|
||||||
calculate_report: 'Calculate Report',
|
calculate_report: 'Calculate report',
|
||||||
total: 'Total',
|
total: 'Total',
|
||||||
specific_accounts: 'Specific Accounts',
|
specific_accounts: 'Specific Accounts',
|
||||||
trans_num: 'Trans. NUM',
|
trans_num: 'Trans. NUM',
|
||||||
@@ -930,6 +930,7 @@ export default {
|
|||||||
inactivate_item: 'Inactivate Item',
|
inactivate_item: 'Inactivate Item',
|
||||||
activate_item: 'Activate Item',
|
activate_item: 'Activate Item',
|
||||||
all_payments:'All Payments',
|
all_payments:'All Payments',
|
||||||
|
hide_customizer: 'Hide Customizer',
|
||||||
opening_quantity_: 'Opening quantity',
|
opening_quantity_: 'Opening quantity',
|
||||||
opening_average_cost: 'Opening average cost',
|
opening_average_cost: 'Opening average cost',
|
||||||
opening_cost_: 'Opening cost ',
|
opening_cost_: 'Opening cost ',
|
||||||
|
|||||||
@@ -127,16 +127,16 @@ export default [
|
|||||||
}),
|
}),
|
||||||
breadcrumb: 'Profit Loss Sheet',
|
breadcrumb: 'Profit Loss Sheet',
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
path: '/financial-reports/receivable-aging-summary',
|
// path: '/financial-reports/receivable-aging-summary',
|
||||||
component: LazyLoader({
|
// component: LazyLoader({
|
||||||
loader: () =>
|
// loader: () =>
|
||||||
import(
|
// import(
|
||||||
'containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummary'
|
// 'containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummary'
|
||||||
),
|
// ),
|
||||||
}),
|
// }),
|
||||||
breadcrumb: 'Receivable Aging Summary',
|
// breadcrumb: 'Receivable Aging Summary',
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
path: `/financial-reports/journal-sheet`,
|
path: `/financial-reports/journal-sheet`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const fetchProfitLossSheet = ({ query }) => {
|
|||||||
ApiService.get('/financial_statements/profit_loss_sheet', { params: query }).then((response) => {
|
ApiService.get('/financial_statements/profit_loss_sheet', { params: query }).then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.PROFIT_LOSS_SHEET_SET,
|
type: t.PROFIT_LOSS_SHEET_SET,
|
||||||
profitLoss: response.data.profitLoss,
|
profitLoss: response.data.data,
|
||||||
columns: response.data.columns,
|
columns: response.data.columns,
|
||||||
query: response.data.query,
|
query: response.data.query,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
|
export const mapBalanceSheetToTableRows = (accounts) => {
|
||||||
|
return accounts.map((account) => {
|
||||||
|
const PRIMARY_SECTIONS = ['assets', 'liability', 'equity'];
|
||||||
|
const rowTypes = [
|
||||||
|
'total_row',
|
||||||
|
...(PRIMARY_SECTIONS.indexOf(account.section_type) !== -1
|
||||||
|
? ['total_assets']
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
...account,
|
||||||
|
children: mapBalanceSheetToTableRows([
|
||||||
|
...(account.children ? account.children : []),
|
||||||
|
...(account.total && account.children && account.children.length > 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: `Total ${account.name}`,
|
||||||
|
row_types: rowTypes,
|
||||||
|
total: { ...account.total },
|
||||||
|
...(account.total_periods && {
|
||||||
|
total_periods: account.total_periods,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const journalToTableRowsMapper = (journal) => {
|
||||||
|
return journal.reduce((rows, journal) => {
|
||||||
|
journal.entries.forEach((entry, index) => {
|
||||||
|
rows.push({
|
||||||
|
...entry,
|
||||||
|
rowType: index === 0 ? 'first_entry' : 'entry',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
rows.push({
|
||||||
|
credit: journal.credit,
|
||||||
|
debit: journal.debit,
|
||||||
|
rowType: 'entries_total',
|
||||||
|
});
|
||||||
|
rows.push({
|
||||||
|
rowType: 'space_entry',
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const generalLedgerToTableRows = (accounts) => {
|
||||||
|
return accounts.reduce((tableRows, account) => {
|
||||||
|
const children = [];
|
||||||
|
children.push({
|
||||||
|
...account.opening,
|
||||||
|
rowType: 'opening_balance',
|
||||||
|
});
|
||||||
|
account.transactions.map((transaction) => {
|
||||||
|
children.push({
|
||||||
|
...transaction,
|
||||||
|
...omit(account, ['transactions']),
|
||||||
|
rowType: 'transaction',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
children.push({
|
||||||
|
...account.closing,
|
||||||
|
rowType: 'closing_balance',
|
||||||
|
});
|
||||||
|
tableRows.push({
|
||||||
|
...omit(account, ['transactions']),
|
||||||
|
children,
|
||||||
|
rowType: 'account_name',
|
||||||
|
});
|
||||||
|
return tableRows;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const profitLossToTableRowsMapper = (profitLoss) => {
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'Income',
|
||||||
|
total: profitLoss.income.total,
|
||||||
|
children: [
|
||||||
|
...profitLoss.income.accounts,
|
||||||
|
{
|
||||||
|
name: 'Total Income',
|
||||||
|
total: profitLoss.income.total,
|
||||||
|
total_periods: profitLoss.income.total_periods,
|
||||||
|
rowTypes: ['income_total', 'section_total', 'total'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total_periods: profitLoss.income.total_periods,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cost of sales',
|
||||||
|
total: profitLoss.cost_of_sales.total,
|
||||||
|
children: [
|
||||||
|
...profitLoss.cost_of_sales.accounts,
|
||||||
|
{
|
||||||
|
name: 'Total cost of sales',
|
||||||
|
total: profitLoss.cost_of_sales.total,
|
||||||
|
total_periods: profitLoss.cost_of_sales.total_periods,
|
||||||
|
rowTypes: ['cogs_total', 'section_total', 'total'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total_periods: profitLoss.cost_of_sales.total_periods
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Gross profit',
|
||||||
|
total: profitLoss.gross_profit.total,
|
||||||
|
total_periods: profitLoss.gross_profit.total_periods,
|
||||||
|
rowTypes: ['gross_total', 'section_total', 'total'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Expenses',
|
||||||
|
total: profitLoss.expenses.total,
|
||||||
|
children: [
|
||||||
|
...profitLoss.expenses.accounts,
|
||||||
|
{
|
||||||
|
name: 'Total Expenses',
|
||||||
|
total: profitLoss.expenses.total,
|
||||||
|
total_periods: profitLoss.expenses.total_periods,
|
||||||
|
rowTypes: ['expenses_total', 'section_total', 'total'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total_periods: profitLoss.expenses.total_periods,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Net Operating income',
|
||||||
|
total: profitLoss.operating_profit.total,
|
||||||
|
total_periods: profitLoss.income.total_periods,
|
||||||
|
rowTypes: ['net_operating_total', 'section_total', 'total'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Other expenses',
|
||||||
|
total: profitLoss.other_expenses.total,
|
||||||
|
total_periods: profitLoss.other_expenses.total_periods,
|
||||||
|
children: [
|
||||||
|
...profitLoss.other_expenses.accounts,
|
||||||
|
{
|
||||||
|
name: 'Total other expenses',
|
||||||
|
total: profitLoss.other_expenses.total,
|
||||||
|
total_periods: profitLoss.other_expenses.total_periods,
|
||||||
|
rowTypes: ['expenses_total', 'section_total', 'total'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Net Income',
|
||||||
|
total: profitLoss.net_income.total,
|
||||||
|
total_periods: profitLoss.net_income.total_periods,
|
||||||
|
rowTypes: ['net_income_total', 'section_total', 'total'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
@@ -1,36 +1,41 @@
|
|||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
import { getFinancialSheetIndexByQuery } from './financialStatements.selectors';
|
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
import {
|
||||||
|
mapBalanceSheetToTableRows,
|
||||||
|
journalToTableRowsMapper,
|
||||||
|
generalLedgerToTableRows,
|
||||||
|
profitLossToTableRowsMapper
|
||||||
|
} from './financialStatements.mappers';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
balanceSheet: {
|
balanceSheet: {
|
||||||
sheets: [],
|
sheet: {},
|
||||||
loading: true,
|
loading: true,
|
||||||
filter: true,
|
filter: true,
|
||||||
refresh: false,
|
refresh: false,
|
||||||
},
|
},
|
||||||
trialBalance: {
|
trialBalance: {
|
||||||
sheets: [],
|
sheet: {},
|
||||||
loading: true,
|
loading: true,
|
||||||
filter: true,
|
filter: true,
|
||||||
refresh: false,
|
refresh: false,
|
||||||
},
|
},
|
||||||
generalLedger: {
|
generalLedger: {
|
||||||
sheets: [],
|
sheet: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
filter: true,
|
filter: true,
|
||||||
refresh: false,
|
refresh: false,
|
||||||
},
|
},
|
||||||
journal: {
|
journal: {
|
||||||
sheets: [],
|
sheet: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
tableRows: [],
|
tableRows: [],
|
||||||
filter: true,
|
filter: true,
|
||||||
refresh: true,
|
refresh: true,
|
||||||
},
|
},
|
||||||
profitLoss: {
|
profitLoss: {
|
||||||
sheets: [],
|
sheet: {},
|
||||||
loading: true,
|
loading: true,
|
||||||
tableRows: [],
|
tableRows: [],
|
||||||
filter: true,
|
filter: true,
|
||||||
@@ -44,52 +49,8 @@ const initialState = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapGeneralLedgerAccountsToRows = (accounts) => {
|
|
||||||
return accounts.reduce((tableRows, account) => {
|
|
||||||
const children = [];
|
|
||||||
children.push({
|
|
||||||
...account.opening,
|
|
||||||
rowType: 'opening_balance',
|
|
||||||
});
|
|
||||||
account.transactions.map((transaction) => {
|
|
||||||
children.push({
|
|
||||||
...transaction,
|
|
||||||
...omit(account, ['transactions']),
|
|
||||||
rowType: 'transaction',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
children.push({
|
|
||||||
...account.closing,
|
|
||||||
rowType: 'closing_balance',
|
|
||||||
});
|
|
||||||
tableRows.push({
|
|
||||||
...omit(account, ['transactions']),
|
|
||||||
children,
|
|
||||||
rowType: 'account_name',
|
|
||||||
});
|
|
||||||
return tableRows;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapJournalTableRows = (journal) => {
|
|
||||||
return journal.reduce((rows, journal) => {
|
|
||||||
journal.entries.forEach((entry, index) => {
|
|
||||||
rows.push({
|
|
||||||
...entry,
|
|
||||||
rowType: index === 0 ? 'first_entry' : 'entry',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
rows.push({
|
|
||||||
credit: journal.credit,
|
|
||||||
debit: journal.debit,
|
|
||||||
rowType: 'entries_total',
|
|
||||||
});
|
|
||||||
rows.push({
|
|
||||||
rowType: 'space_entry',
|
|
||||||
});
|
|
||||||
return rows;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapContactAgingSummary = (sheet) => {
|
const mapContactAgingSummary = (sheet) => {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -120,70 +81,6 @@ const mapContactAgingSummary = (sheet) => {
|
|||||||
return rows;
|
return rows;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapProfitLossToTableRows = (profitLoss) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'Income',
|
|
||||||
total: profitLoss.income.total,
|
|
||||||
children: [
|
|
||||||
...profitLoss.income.accounts,
|
|
||||||
{
|
|
||||||
name: 'Total Income',
|
|
||||||
total: profitLoss.income.total,
|
|
||||||
rowType: 'income_total',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Expenses',
|
|
||||||
total: profitLoss.expenses.total,
|
|
||||||
children: [
|
|
||||||
...profitLoss.expenses.accounts,
|
|
||||||
{
|
|
||||||
name: 'Total Expenses',
|
|
||||||
total: profitLoss.expenses.total,
|
|
||||||
rowType: 'expense_total',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Net Income',
|
|
||||||
total: profitLoss.net_income.total,
|
|
||||||
rowType: 'net_income',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapTotalToChildrenRows = (accounts) => {
|
|
||||||
return accounts.map((account) => {
|
|
||||||
return {
|
|
||||||
...account,
|
|
||||||
children: mapTotalToChildrenRows([
|
|
||||||
...(account.children ? account.children : []),
|
|
||||||
...(account.total &&
|
|
||||||
account.children &&
|
|
||||||
account.children.length > 0 &&
|
|
||||||
account.row_type !== 'total_row'
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: `Total ${account.name}`,
|
|
||||||
row_type: 'total_row',
|
|
||||||
total: { ...account.total },
|
|
||||||
...(account.total_periods && {
|
|
||||||
total_periods: account.total_periods,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapBalanceSheetRows = (balanceSheet) => {
|
|
||||||
return balanceSheet.map((section) => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
const financialStatementFilterToggle = (financialName, statePath) => {
|
const financialStatementFilterToggle = (financialName, statePath) => {
|
||||||
return {
|
return {
|
||||||
[`${financialName}_FILTER_TOGGLE`]: (state, action) => {
|
[`${financialName}_FILTER_TOGGLE`]: (state, action) => {
|
||||||
@@ -194,22 +91,13 @@ const financialStatementFilterToggle = (financialName, statePath) => {
|
|||||||
|
|
||||||
export default createReducer(initialState, {
|
export default createReducer(initialState, {
|
||||||
[t.BALANCE_SHEET_STATEMENT_SET]: (state, action) => {
|
[t.BALANCE_SHEET_STATEMENT_SET]: (state, action) => {
|
||||||
const index = getFinancialSheetIndexByQuery(
|
|
||||||
state.balanceSheet.sheets,
|
|
||||||
action.query,
|
|
||||||
);
|
|
||||||
|
|
||||||
const balanceSheet = {
|
const balanceSheet = {
|
||||||
sheet: action.data.balanceSheet,
|
sheet: action.data.data,
|
||||||
columns: Object.values(action.data.columns),
|
columns: action.data.columns,
|
||||||
query: action.data.query,
|
query: action.data.query,
|
||||||
tableRows: mapTotalToChildrenRows(action.data.balance_sheet),
|
tableRows: mapBalanceSheetToTableRows(action.data.data),
|
||||||
};
|
};
|
||||||
if (index !== -1) {
|
state.balanceSheet.sheet = balanceSheet;
|
||||||
state.balanceSheet.sheets[index] = balanceSheet;
|
|
||||||
} else {
|
|
||||||
state.balanceSheet.sheets.push(balanceSheet);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.BALANCE_SHEET_LOADING]: (state, action) => {
|
[t.BALANCE_SHEET_LOADING]: (state, action) => {
|
||||||
@@ -224,19 +112,11 @@ export default createReducer(initialState, {
|
|||||||
...financialStatementFilterToggle('BALANCE_SHEET', 'balanceSheet'),
|
...financialStatementFilterToggle('BALANCE_SHEET', 'balanceSheet'),
|
||||||
|
|
||||||
[t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => {
|
[t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => {
|
||||||
const index = getFinancialSheetIndexByQuery(
|
|
||||||
state.trialBalance.sheets,
|
|
||||||
action.query,
|
|
||||||
);
|
|
||||||
const trailBalanceSheet = {
|
const trailBalanceSheet = {
|
||||||
accounts: action.data.accounts,
|
data: action.data.data,
|
||||||
query: action.data.query,
|
query: action.data.query,
|
||||||
};
|
};
|
||||||
if (index !== -1) {
|
state.trialBalance.sheet = trailBalanceSheet;
|
||||||
state.trialBalance.sheets[index] = trailBalanceSheet;
|
|
||||||
} else {
|
|
||||||
state.trialBalance.sheets.push(trailBalanceSheet);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.TRIAL_BALANCE_SHEET_LOADING]: (state, action) => {
|
[t.TRIAL_BALANCE_SHEET_LOADING]: (state, action) => {
|
||||||
@@ -251,21 +131,12 @@ export default createReducer(initialState, {
|
|||||||
...financialStatementFilterToggle('TRIAL_BALANCE', 'trialBalance'),
|
...financialStatementFilterToggle('TRIAL_BALANCE', 'trialBalance'),
|
||||||
|
|
||||||
[t.JOURNAL_SHEET_SET]: (state, action) => {
|
[t.JOURNAL_SHEET_SET]: (state, action) => {
|
||||||
const index = getFinancialSheetIndexByQuery(
|
|
||||||
state.journal.sheets,
|
|
||||||
action.query,
|
|
||||||
);
|
|
||||||
|
|
||||||
const journal = {
|
const journal = {
|
||||||
query: action.data.query,
|
query: action.data.query,
|
||||||
journal: action.data.journal,
|
data: action.data.data,
|
||||||
tableRows: mapJournalTableRows(action.data.journal),
|
tableRows: journalToTableRowsMapper(action.data.data),
|
||||||
};
|
};
|
||||||
if (index !== -1) {
|
state.journal.sheet = journal;
|
||||||
state.journal.sheets[index] = journal;
|
|
||||||
} else {
|
|
||||||
state.journal.sheets.push(journal);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.JOURNAL_SHEET_LOADING]: (state, action) => {
|
[t.JOURNAL_SHEET_LOADING]: (state, action) => {
|
||||||
@@ -278,21 +149,12 @@ export default createReducer(initialState, {
|
|||||||
...financialStatementFilterToggle('JOURNAL', 'journal'),
|
...financialStatementFilterToggle('JOURNAL', 'journal'),
|
||||||
|
|
||||||
[t.GENERAL_LEDGER_STATEMENT_SET]: (state, action) => {
|
[t.GENERAL_LEDGER_STATEMENT_SET]: (state, action) => {
|
||||||
const index = getFinancialSheetIndexByQuery(
|
|
||||||
state.generalLedger.sheets,
|
|
||||||
action.query,
|
|
||||||
);
|
|
||||||
|
|
||||||
const generalLedger = {
|
const generalLedger = {
|
||||||
query: action.data.query,
|
query: action.data.query,
|
||||||
accounts: action.data.accounts,
|
accounts: action.data.data,
|
||||||
tableRows: mapGeneralLedgerAccountsToRows(action.data.accounts),
|
tableRows: generalLedgerToTableRows(action.data.data),
|
||||||
};
|
};
|
||||||
if (index !== -1) {
|
state.generalLedger.sheet = generalLedger;
|
||||||
state.generalLedger.sheets[index] = generalLedger;
|
|
||||||
} else {
|
|
||||||
state.generalLedger.sheets.push(generalLedger);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.GENERAL_LEDGER_SHEET_LOADING]: (state, action) => {
|
[t.GENERAL_LEDGER_SHEET_LOADING]: (state, action) => {
|
||||||
@@ -305,22 +167,13 @@ export default createReducer(initialState, {
|
|||||||
...financialStatementFilterToggle('GENERAL_LEDGER', 'generalLedger'),
|
...financialStatementFilterToggle('GENERAL_LEDGER', 'generalLedger'),
|
||||||
|
|
||||||
[t.PROFIT_LOSS_SHEET_SET]: (state, action) => {
|
[t.PROFIT_LOSS_SHEET_SET]: (state, action) => {
|
||||||
const index = getFinancialSheetIndexByQuery(
|
|
||||||
state.profitLoss.sheets,
|
|
||||||
action.query,
|
|
||||||
);
|
|
||||||
|
|
||||||
const profitLossSheet = {
|
const profitLossSheet = {
|
||||||
query: action.query,
|
query: action.query,
|
||||||
profitLoss: action.profitLoss,
|
profitLoss: action.profitLoss,
|
||||||
columns: action.columns,
|
columns: action.columns,
|
||||||
tableRows: mapProfitLossToTableRows(action.profitLoss),
|
tableRows: profitLossToTableRowsMapper(action.profitLoss),
|
||||||
};
|
};
|
||||||
if (index !== -1) {
|
state.profitLoss.sheet = profitLossSheet;
|
||||||
state.profitLoss.sheets[index] = profitLossSheet;
|
|
||||||
} else {
|
|
||||||
state.profitLoss.sheets.push(profitLossSheet);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.PROFIT_LOSS_SHEET_LOADING]: (state, action) => {
|
[t.PROFIT_LOSS_SHEET_LOADING]: (state, action) => {
|
||||||
@@ -334,34 +187,34 @@ export default createReducer(initialState, {
|
|||||||
|
|
||||||
...financialStatementFilterToggle('PROFIT_LOSS', 'profitLoss'),
|
...financialStatementFilterToggle('PROFIT_LOSS', 'profitLoss'),
|
||||||
|
|
||||||
[t.RECEIVABLE_AGING_SUMMARY_LOADING]: (state, action) => {
|
// [t.RECEIVABLE_AGING_SUMMARY_LOADING]: (state, action) => {
|
||||||
const { loading } = action.payload;
|
// const { loading } = action.payload;
|
||||||
state.receivableAgingSummary.loading = loading;
|
// state.receivableAgingSummary.loading = loading;
|
||||||
},
|
// },
|
||||||
|
|
||||||
[t.RECEIVABLE_AGING_SUMMARY_SET]: (state, action) => {
|
// [t.RECEIVABLE_AGING_SUMMARY_SET]: (state, action) => {
|
||||||
const { aging, columns, query } = action.payload;
|
// const { aging, columns, query } = action.payload;
|
||||||
const index = getFinancialSheetIndexByQuery(
|
// const index = getFinancialSheetIndexByQuery(
|
||||||
state.receivableAgingSummary.sheets,
|
// state.receivableAgingSummary.sheets,
|
||||||
query,
|
// query,
|
||||||
);
|
// );
|
||||||
|
|
||||||
const receivableSheet = {
|
// const receivableSheet = {
|
||||||
query,
|
// query,
|
||||||
columns,
|
// columns,
|
||||||
aging,
|
// aging,
|
||||||
tableRows: mapContactAgingSummary(aging),
|
// tableRows: mapContactAgingSummary(aging),
|
||||||
};
|
// };
|
||||||
if (index !== -1) {
|
// if (index !== -1) {
|
||||||
state.receivableAgingSummary[index] = receivableSheet;
|
// state.receivableAgingSummary[index] = receivableSheet;
|
||||||
} else {
|
// } else {
|
||||||
state.receivableAgingSummary.sheets.push(receivableSheet);
|
// state.receivableAgingSummary.sheets.push(receivableSheet);
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
[t.RECEIVABLE_AGING_SUMMARY_REFRESH]: (state, action) => {
|
// [t.RECEIVABLE_AGING_SUMMARY_REFRESH]: (state, action) => {
|
||||||
const { refresh } = action.payload;
|
// const { refresh } = action.payload;
|
||||||
state.receivableAgingSummary.refresh = !!refresh;
|
// state.receivableAgingSummary.refresh = !!refresh;
|
||||||
},
|
// },
|
||||||
...financialStatementFilterToggle(
|
...financialStatementFilterToggle(
|
||||||
'RECEIVABLE_AGING_SUMMARY',
|
'RECEIVABLE_AGING_SUMMARY',
|
||||||
'receivableAgingSummary',
|
'receivableAgingSummary',
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import {getObjectDiff} from 'utils';
|
import { createSelector } from 'reselect';
|
||||||
|
import { camelCase } from 'lodash';
|
||||||
|
|
||||||
|
const transformSheetType = (sheetType) => {
|
||||||
|
return camelCase(sheetType);
|
||||||
|
};
|
||||||
|
|
||||||
// Financial Statements selectors.
|
// Financial Statements selectors.
|
||||||
|
export const sheetByTypeSelector = (sheetType) => (state, props) => {
|
||||||
/**
|
const sheetName = transformSheetType(sheetType);
|
||||||
* Retrieve financial statement sheet by the given query.
|
return state.financialStatements[sheetName].sheet;
|
||||||
* @param {array} sheets
|
|
||||||
* @param {object} query
|
|
||||||
*/
|
|
||||||
export const getFinancialSheetIndexByQuery = (sheets, query) => {
|
|
||||||
return sheets.findIndex(balanceSheet => (
|
|
||||||
getObjectDiff(query, balanceSheet.query).length === 0
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,38 +16,56 @@ export const getFinancialSheetIndexByQuery = (sheets, query) => {
|
|||||||
* @param {array} sheets
|
* @param {array} sheets
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
*/
|
*/
|
||||||
export const getFinancialSheet = (sheets, index) => {
|
export const getFinancialSheetFactory = (sheetType) =>
|
||||||
return (typeof sheets[index] !== 'undefined') ? sheets[index] : null;
|
createSelector(
|
||||||
};
|
sheetByTypeSelector(sheetType),
|
||||||
|
(sheet) => {
|
||||||
|
return sheet;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve financial statement columns by the given sheet index.
|
* Retrieve financial statement columns by the given sheet index.
|
||||||
* @param {array} sheets
|
* @param {array} sheets
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
*/
|
*/
|
||||||
export const getFinancialSheetColumns = (sheets, index) => {
|
export const getFinancialSheetColumnsFactory = (sheetType) =>
|
||||||
const sheet = getFinancialSheet(sheets, index);
|
createSelector(
|
||||||
|
sheetByTypeSelector(sheetType),
|
||||||
|
(sheet) => {
|
||||||
return (sheet && sheet.columns) ? sheet.columns : [];
|
return (sheet && sheet.columns) ? sheet.columns : [];
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve financial statement query by the given sheet index.
|
* Retrieve financial statement query by the given sheet index.
|
||||||
* @param {array} sheets
|
|
||||||
* @param {number} index
|
|
||||||
*/
|
*/
|
||||||
export const getFinancialSheetQuery = (sheets, index) => {
|
export const getFinancialSheetQueryFactory = (sheetType) =>
|
||||||
const sheet = getFinancialSheet(sheets, index);
|
createSelector(
|
||||||
|
sheetByTypeSelector(sheetType),
|
||||||
|
(sheet) => {
|
||||||
return (sheet && sheet.query) ? sheet.query : {};
|
return (sheet && sheet.query) ? sheet.query : {};
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
export const getFinancialSheetAccounts = (sheets, index) => {
|
* Retrieve financial statement accounts by the given sheet index.
|
||||||
const sheet = getFinancialSheet(sheets, index);
|
*/
|
||||||
|
export const getFinancialSheetAccountsFactory = (sheetType) =>
|
||||||
|
createSelector(
|
||||||
|
sheetByTypeSelector(sheetType),
|
||||||
|
(sheet) => {
|
||||||
return (sheet && sheet.accounts) ? sheet.accounts : [];
|
return (sheet && sheet.accounts) ? sheet.accounts : [];
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
export const getFinancialSheetTableRows = (sheets, index) => {
|
* Retrieve financial statement table rows by the given sheet index.
|
||||||
const sheet = getFinancialSheet(sheets, index);
|
*/
|
||||||
|
export const getFinancialSheetTableRowsFactory = (sheetType) =>
|
||||||
|
createSelector(
|
||||||
|
sheetByTypeSelector(sheetType),
|
||||||
|
(sheet) => {
|
||||||
return (sheet && sheet.tableRows) ? sheet.tableRows : [];
|
return (sheet && sheet.tableRows) ? sheet.tableRows : [];
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ $button-background-color-hover: #CFDCEE !default;
|
|||||||
body.authentication {
|
body.authentication {
|
||||||
background-color: #fcfdff;
|
background-color: #fcfdff;
|
||||||
}
|
}
|
||||||
|
body.hide-scrollbar .Pane2{
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.bp3-toast {
|
.bp3-toast {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|||||||
@@ -296,6 +296,12 @@
|
|||||||
|
|
||||||
.tbody{
|
.tbody{
|
||||||
.tr .td{
|
.tr .td{
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
.tr:not(:first-child) .td{
|
||||||
|
border-top: 1px dotted #CCC;
|
||||||
|
}
|
||||||
|
.tr:last-child .td{
|
||||||
border-bottom: 1px dotted #CCC;
|
border-bottom: 1px dotted #CCC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,63 @@
|
|||||||
.financial-statement{
|
.financial-statement{
|
||||||
|
|
||||||
&__header{
|
&__header{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body{
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.financial-header-drawer{
|
||||||
padding: 25px 26px 25px;
|
padding: 25px 26px 25px;
|
||||||
background: #FDFDFD;
|
position: absolute;
|
||||||
|
top: 101px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&.is-hidden{
|
&.is-hidden{
|
||||||
display: none;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-form-group,
|
.row{
|
||||||
.radio-group---accounting-basis{
|
.col{
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bp3-label{
|
.bp3-drawer{
|
||||||
font-weight: 500;
|
box-shadow: 0 0 0 transparent;
|
||||||
font-size: 13px;
|
max-height: 550px;
|
||||||
color: #444;
|
height: 100%;
|
||||||
|
padding-bottom: 49px;
|
||||||
|
|
||||||
|
> form{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 0 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.bp3-drawer-backdrop{
|
||||||
|
background-color: rgba(2, 9, 19, 0.65);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-form-group{
|
||||||
|
margin-bottom: 22px;
|
||||||
|
|
||||||
|
label.bp3-label{
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bp3-button.button--submit-filter{
|
.bp3-button.button--submit-filter{
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
@@ -32,34 +73,89 @@
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-tabs{
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&.bp3-vertical > .bp3-tab-panel{
|
||||||
|
flex: 1 0 0;
|
||||||
|
border-top: 24px solid transparent;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__body{
|
.bp3-tabs.bp3-vertical{
|
||||||
padding-left: 15px;
|
flex: 1 0 0;
|
||||||
padding-right: 15px;
|
|
||||||
display: flex;
|
.bp3-tab-list{
|
||||||
justify-content: center;
|
width: 220px;
|
||||||
align-items: center;
|
border-right: 1px solid #c3cdd5;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
> *:not(:last-child){
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.bp3-tab-indicator-wrapper{
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.bp3-tab-indicator{
|
||||||
|
border-left: 3px solid #0350f8;
|
||||||
|
background-color: #edf5ff;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header.is-hidden + .financial-statement__body{
|
.bp3-tab{
|
||||||
.financial-sheet{
|
color: #333;
|
||||||
margin-top: 40px;
|
line-height: 45px;
|
||||||
|
border-radius: 0;
|
||||||
|
padding-left: 14px;
|
||||||
|
padding-right: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-panel{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer{
|
||||||
|
background-color: #ecf0f3;
|
||||||
|
border-top: 1px solid #c3cdd5;
|
||||||
|
padding: 8px;
|
||||||
|
padding-left: 230px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row{
|
||||||
|
margin-left: -0.85rem;
|
||||||
|
margin-right: -0.85rem;
|
||||||
|
|
||||||
|
.col{
|
||||||
|
padding-left: 0.85rem;
|
||||||
|
padding-right: 0.85rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.financial-sheet{
|
.financial-sheet{
|
||||||
border: 2px solid #F1F1F1;
|
border: 2px solid #EBEBEB;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
min-width: 640px;
|
min-width: 640px;
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: 30px 18px;
|
padding: 30px 18px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 15px auto 35px;
|
margin: 35px auto;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
&__title{
|
&__title{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -103,7 +199,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__inner{
|
&__inner{
|
||||||
&.is-loading{
|
&.is-loading{
|
||||||
display: none;
|
display: none;
|
||||||
@@ -113,8 +208,8 @@
|
|||||||
color: #888;
|
color: #888;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding-top: 16px;
|
padding-top: 18px;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.dashboard__loading-indicator{
|
.dashboard__loading-indicator{
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -143,7 +238,13 @@
|
|||||||
&--opening_balance,
|
&--opening_balance,
|
||||||
&--closing_balance{
|
&--closing_balance{
|
||||||
.td{
|
.td{
|
||||||
background-color: #fbfbfb;
|
border-top: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name,
|
||||||
|
.amount,
|
||||||
|
.balance{
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,18 +286,36 @@
|
|||||||
&--profit-loss-sheet{
|
&--profit-loss-sheet{
|
||||||
|
|
||||||
.financial-sheet__table{
|
.financial-sheet__table{
|
||||||
|
.thead,
|
||||||
.tbody{
|
.tbody{
|
||||||
.total.td {
|
.tr .td:not(:first-child),
|
||||||
border-bottom-color: #000;
|
.tr .th:not(:first-child) {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tbody{
|
||||||
|
.tr .td:not(:first-child) {
|
||||||
|
border-top-color: #000;
|
||||||
|
}
|
||||||
|
.tr.row_type--total{
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.tr.row_type--section_total .td{
|
||||||
|
border-top: 1px solid #BBB
|
||||||
|
}
|
||||||
|
.tr.row_type--section_total + .tr .td{
|
||||||
|
border-top: 1px solid #666;
|
||||||
|
}
|
||||||
|
.tr.row_type--section_total:last-child .td{
|
||||||
|
border-bottom: 1px solid #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row--income_total,
|
.tr.is-expanded{
|
||||||
.row--expense_total,
|
.td.total,
|
||||||
.row--net_income{
|
.td.total-period{
|
||||||
font-weight: 600;
|
> span{
|
||||||
|
display: none;
|
||||||
.total.td{
|
}
|
||||||
border-bottom-color: #555;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,13 +324,28 @@
|
|||||||
|
|
||||||
&--balance-sheet{
|
&--balance-sheet{
|
||||||
.financial-sheet__table{
|
.financial-sheet__table{
|
||||||
|
|
||||||
|
.thead,
|
||||||
.tbody{
|
.tbody{
|
||||||
.total.td{
|
.tr .td.account_name ~ .td,
|
||||||
border-bottom-color: #000;
|
.tr .th.account_name ~ .th{
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tbody{
|
||||||
|
.tr .total.td{
|
||||||
|
border-top-color: #000;
|
||||||
|
}
|
||||||
|
.tr.row_type--total_row .td{
|
||||||
|
border-top: 1px solid #BBB;
|
||||||
|
}
|
||||||
|
.tr.row_type--total_assets + .tr .td{
|
||||||
|
border-top: 1px solid #666;
|
||||||
}
|
}
|
||||||
.tr.row_type--total_row{
|
.tr.row_type--total_row{
|
||||||
.total.td,
|
.total.td,
|
||||||
.account_name.td{
|
.account_name.td,
|
||||||
|
.total-period.td{
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
@@ -267,6 +401,7 @@
|
|||||||
|
|
||||||
&.is-full-width{
|
&.is-full-width{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-top: 25px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,3 +445,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.financial-statement--journal{
|
||||||
|
|
||||||
|
|
||||||
|
.financial-header-drawer{
|
||||||
|
.bp3-drawer{
|
||||||
|
max-height: 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,24 +122,22 @@ export const parseDateRangeQuery = (keyword) => {
|
|||||||
const query = queries[keyword];
|
const query = queries[keyword];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
from_date: moment().startOf(query.range).toDate(),
|
fromDate: moment().startOf(query.range).toDate(),
|
||||||
to_date: moment().endOf(query.range).toDate(),
|
toDate: moment().endOf(query.range).toDate(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExpanderReducer = (tableRows, level) => {
|
export const defaultExpanderReducer = (tableRows, level) => {
|
||||||
let currentLevel = 1;
|
|
||||||
const expended = [];
|
const expended = [];
|
||||||
|
|
||||||
const walker = (rows, parentIndex = null) => {
|
const walker = (rows, parentIndex = null, currentLevel = 1) => {
|
||||||
return rows.forEach((row, index) => {
|
return rows.forEach((row, index) => {
|
||||||
const _index = parentIndex ? `${parentIndex}.${index}` : `${index}`;
|
const _index = parentIndex ? `${parentIndex}.${index}` : `${index}`;
|
||||||
expended[_index] = true;
|
expended[_index] = true;
|
||||||
|
|
||||||
if (row.children && currentLevel < level) {
|
if (row.children && currentLevel < level) {
|
||||||
walker(row.children, _index);
|
walker(row.children, _index, currentLevel + 1);
|
||||||
}
|
}
|
||||||
currentLevel++;
|
|
||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
walker(tableRows);
|
walker(tableRows);
|
||||||
@@ -372,3 +370,21 @@ export function defaultToTransform(
|
|||||||
export function isBlank(value) {
|
export function isBlank(value) {
|
||||||
return _.isEmpty(value) && !_.isNumber(value) || _.isNaN(value);
|
return _.isEmpty(value) && !_.isNumber(value) || _.isNaN(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getColumnWidth = (
|
||||||
|
rows,
|
||||||
|
accessor,
|
||||||
|
{ maxWidth, minWidth, magicSpacing = 14 },
|
||||||
|
) => {
|
||||||
|
const cellLength = Math.max(
|
||||||
|
...rows.map((row) => (`${_.get(row, accessor)}` || '').length),
|
||||||
|
);
|
||||||
|
let result = cellLength * magicSpacing;
|
||||||
|
|
||||||
|
result = minWidth ? Math.max(minWidth, result) : result;
|
||||||
|
result = maxWidth ? Math.min(maxWidth, result) : result;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export default class JournalSheetController extends BaseController {
|
|||||||
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.journalService.journalSheet(tenantId, filter);
|
const { data, query } = await this.journalService.journalSheet(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
organization_name: organizationName,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query, ValidationChain } from 'express-validator';
|
import { check, param, body, query, ValidationChain } from 'express-validator';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import ItemsService from 'services/Items/ItemsService';
|
import ItemsService from 'services/Items/ItemsService';
|
||||||
import BaseController from 'api/controllers/BaseController';
|
import BaseController from 'api/controllers/BaseController';
|
||||||
@@ -23,70 +23,62 @@ export default class ItemsController extends BaseController {
|
|||||||
router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/', [
|
router.post(
|
||||||
...this.validateItemSchema,
|
'/',
|
||||||
...this.validateNewItemSchema,
|
[...this.validateItemSchema, ...this.validateNewItemSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.newItem.bind(this)),
|
asyncMiddleware(this.newItem.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/activate', [
|
'/:id/activate',
|
||||||
...this.validateSpecificItemSchema,
|
[...this.validateSpecificItemSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.activateItem.bind(this)),
|
asyncMiddleware(this.activateItem.bind(this)),
|
||||||
this.handlerServiceErrors
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/inactivate', [
|
'/:id/inactivate',
|
||||||
...this.validateSpecificItemSchema,
|
[...this.validateSpecificItemSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.inactivateItem.bind(this)),
|
asyncMiddleware(this.inactivateItem.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
)
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id', [
|
'/:id',
|
||||||
...this.validateItemSchema,
|
[...this.validateItemSchema, ...this.validateSpecificItemSchema],
|
||||||
...this.validateSpecificItemSchema,
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editItem.bind(this)),
|
asyncMiddleware(this.editItem.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.delete('/', [
|
router.delete(
|
||||||
...this.validateBulkSelectSchema,
|
'/',
|
||||||
],
|
[...this.validateBulkSelectSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.bulkDeleteItems.bind(this)),
|
asyncMiddleware(this.bulkDeleteItems.bind(this)),
|
||||||
this.handlerServiceErrors
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id', [
|
'/:id',
|
||||||
...this.validateSpecificItemSchema,
|
[...this.validateSpecificItemSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deleteItem.bind(this)),
|
asyncMiddleware(this.deleteItem.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id', [
|
'/:id',
|
||||||
...this.validateSpecificItemSchema,
|
[...this.validateSpecificItemSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getItem.bind(this)),
|
asyncMiddleware(this.getItem.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/', [
|
'/',
|
||||||
...this.validateListQuerySchema,
|
[...this.validateListQuerySchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getItemsList.bind(this)),
|
asyncMiddleware(this.getItemsList.bind(this)),
|
||||||
this.dynamicListService.handlerErrorsToResponse,
|
this.dynamicListService.handlerErrorsToResponse,
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -97,8 +89,21 @@ export default class ItemsController extends BaseController {
|
|||||||
get validateNewItemSchema(): ValidationChain[] {
|
get validateNewItemSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('opening_quantity').default(0).isInt({ min: 0 }).toInt(),
|
check('opening_quantity').default(0).isInt({ min: 0 }).toInt(),
|
||||||
check('opening_cost').optional({ nullable: true }).isFloat({ min: 0 }).toFloat(),
|
check('opening_cost')
|
||||||
check('opening_date').optional({ nullable: true }).isISO8601(),
|
.if(body('opening_quantity').exists().isInt({ min: 1 }))
|
||||||
|
.exists()
|
||||||
|
.isFloat(),
|
||||||
|
check('opening_cost')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.isFloat({ min: 0 })
|
||||||
|
.toFloat(),
|
||||||
|
check('opening_date')
|
||||||
|
.if(
|
||||||
|
body('opening_quantity').exists().isFloat({ min: 1 }) ||
|
||||||
|
body('opening_cost').exists().isFloat({ min: 1 })
|
||||||
|
)
|
||||||
|
.exists(),
|
||||||
|
check('opening_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,8 +112,12 @@ export default class ItemsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
get validateItemSchema(): ValidationChain[] {
|
get validateItemSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('name').exists().isString().isLength({ max: DATATYPES_LENGTH.STRING }),
|
check('name')
|
||||||
check('type').exists()
|
.exists()
|
||||||
|
.isString()
|
||||||
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
|
check('type')
|
||||||
|
.exists()
|
||||||
.isString()
|
.isString()
|
||||||
.trim()
|
.trim()
|
||||||
.escape()
|
.escape()
|
||||||
@@ -127,12 +136,11 @@ export default class ItemsController extends BaseController {
|
|||||||
.toFloat()
|
.toFloat()
|
||||||
.if(check('purchasable').equals('true'))
|
.if(check('purchasable').equals('true'))
|
||||||
.exists(),
|
.exists(),
|
||||||
|
check('cost_account_id').if(check('purchasable').equals('true')).exists(),
|
||||||
check('cost_account_id')
|
check('cost_account_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
||||||
.toInt()
|
.toInt(),
|
||||||
.if(check('purchasable').equals('true'))
|
|
||||||
.exists(),
|
|
||||||
// Sell attributes.
|
// Sell attributes.
|
||||||
check('sellable').optional().isBoolean().toBoolean(),
|
check('sellable').optional().isBoolean().toBoolean(),
|
||||||
check('sell_price')
|
check('sell_price')
|
||||||
@@ -141,18 +149,18 @@ export default class ItemsController extends BaseController {
|
|||||||
.toFloat()
|
.toFloat()
|
||||||
.if(check('sellable').equals('true'))
|
.if(check('sellable').equals('true'))
|
||||||
.exists(),
|
.exists(),
|
||||||
|
check('sell_account_id').if(check('sellable').equals('true')).exists(),
|
||||||
check('sell_account_id')
|
check('sell_account_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
||||||
.toInt()
|
.toInt(),
|
||||||
.if(check('sellable').equals('true'))
|
check('inventory_account_id')
|
||||||
|
.if(check('type').equals('inventory'))
|
||||||
.exists(),
|
.exists(),
|
||||||
check('inventory_account_id')
|
check('inventory_account_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
||||||
.toInt()
|
.toInt(),
|
||||||
.if(check('type').equals('inventory'))
|
|
||||||
.exists(),
|
|
||||||
check('sell_description')
|
check('sell_description')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isString()
|
.isString()
|
||||||
@@ -187,9 +195,7 @@ export default class ItemsController extends BaseController {
|
|||||||
* @return {ValidationChain[]}
|
* @return {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get validateSpecificItemSchema(): ValidationChain[] {
|
get validateSpecificItemSchema(): ValidationChain[] {
|
||||||
return [
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
param('id').exists().isNumeric().toInt(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -216,7 +222,7 @@ export default class ItemsController extends BaseController {
|
|||||||
|
|
||||||
query('custom_view_id').optional().isNumeric().toInt(),
|
query('custom_view_id').optional().isNumeric().toInt(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -336,7 +342,7 @@ export default class ItemsController extends BaseController {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
||||||
next(error)
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +368,7 @@ export default class ItemsController extends BaseController {
|
|||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta
|
filterMeta,
|
||||||
} = await this.itemsService.itemsList(tenantId, filter);
|
} = await this.itemsService.itemsList(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@@ -404,7 +410,12 @@ export default class ItemsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
handlerServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
handlerServiceErrors(
|
||||||
|
error: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'NOT_FOUND') {
|
if (error.errorType === 'NOT_FOUND') {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
@@ -479,7 +490,7 @@ export default class ItemsController extends BaseController {
|
|||||||
if (error.errorType === 'ITEM_HAS_ASSOCIATED_TRANSACTINS') {
|
if (error.errorType === 'ITEM_HAS_ASSOCIATED_TRANSACTINS') {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
errors: [{ type: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', code: 320 }],
|
errors: [{ type: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', code: 320 }],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
|||||||
import PaymentReceiveService from 'services/Sales/PaymentsReceives';
|
import PaymentReceiveService from 'services/Sales/PaymentsReceives';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import HasItemEntries from 'services/Sales/HasItemsEntries';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Payments receives controller.
|
* Payments receives controller.
|
||||||
@@ -32,29 +31,28 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
this.editPaymentReceiveValidation,
|
this.editPaymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editPaymentReceive.bind(this)),
|
asyncMiddleware(this.editPaymentReceive.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/', [
|
'/',
|
||||||
...this.newPaymentReceiveValidation,
|
[...this.newPaymentReceiveValidation],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.newPaymentReceive.bind(this)),
|
asyncMiddleware(this.newPaymentReceive.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id/invoices',
|
'/:id/invoices',
|
||||||
this.paymentReceiveValidation,
|
this.paymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.paymentReceiveValidation,
|
this.paymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getPaymentReceive.bind(this)),
|
asyncMiddleware(this.getPaymentReceive.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
@@ -62,14 +60,14 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getPaymentReceiveList.bind(this)),
|
asyncMiddleware(this.getPaymentReceiveList.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
this.dynamicListService.handlerErrorsToResponse,
|
this.dynamicListService.handlerErrorsToResponse
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.paymentReceiveValidation,
|
this.paymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deletePaymentReceive.bind(this)),
|
asyncMiddleware(this.deletePaymentReceive.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -105,7 +103,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,10 +142,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storedPaymentReceive = await this.paymentReceiveService
|
const storedPaymentReceive = await this.paymentReceiveService.createPaymentReceive(
|
||||||
.createPaymentReceive(
|
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceive,
|
paymentReceive
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: storedPaymentReceive.id,
|
id: storedPaymentReceive.id,
|
||||||
@@ -172,11 +169,13 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.paymentReceiveService.editPaymentReceive(
|
await this.paymentReceiveService.editPaymentReceive(
|
||||||
tenantId, paymentReceiveId, paymentReceive,
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
paymentReceive
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: paymentReceiveId,
|
id: paymentReceiveId,
|
||||||
message: 'The payment receive has been edited successfully.'
|
message: 'The payment receive has been edited successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -193,7 +192,10 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.paymentReceiveService.deletePaymentReceive(tenantId, paymentReceiveId);
|
await this.paymentReceiveService.deletePaymentReceive(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: paymentReceiveId,
|
id: paymentReceiveId,
|
||||||
@@ -220,7 +222,8 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
receivableInvoices,
|
receivableInvoices,
|
||||||
paymentReceiveInvoices,
|
paymentReceiveInvoices,
|
||||||
} = await this.paymentReceiveService.getPaymentReceive(
|
} = await this.paymentReceiveService.getPaymentReceive(
|
||||||
tenantId, paymentReceiveId
|
tenantId,
|
||||||
|
paymentReceiveId
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@@ -239,13 +242,18 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async getPaymentReceiveInvoices(req: Request, res: Response, next: NextFunction) {
|
async getPaymentReceiveInvoices(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const invoices = await this.paymentReceiveService.getPaymentReceiveInvoices(
|
const invoices = await this.paymentReceiveService.getPaymentReceiveInvoices(
|
||||||
tenantId, paymentReceiveId,
|
tenantId,
|
||||||
|
paymentReceiveId
|
||||||
);
|
);
|
||||||
return res.status(200).send({ sale_invoices: invoices });
|
return res.status(200).send({ sale_invoices: invoices });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -278,7 +286,10 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
paymentReceives,
|
paymentReceives,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta,
|
filterMeta,
|
||||||
} = await this.paymentReceiveService.listPaymentReceives(tenantId, filter);
|
} = await this.paymentReceiveService.listPaymentReceives(
|
||||||
|
tenantId,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
payment_receives: paymentReceives,
|
payment_receives: paymentReceives,
|
||||||
@@ -297,7 +308,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param res
|
* @param res
|
||||||
* @param next
|
* @param next
|
||||||
*/
|
*/
|
||||||
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
handleServiceErrors(
|
||||||
|
error: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_FOUND') {
|
if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_FOUND') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
@@ -316,7 +332,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
}
|
}
|
||||||
if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE') {
|
if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
errors: [{ type: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE', code: 300 }],
|
errors: [
|
||||||
|
{ type: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE', code: 300 },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (error.errorType === 'INVALID_PAYMENT_AMOUNT_INVALID') {
|
if (error.errorType === 'INVALID_PAYMENT_AMOUNT_INVALID') {
|
||||||
@@ -346,14 +364,17 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
}
|
}
|
||||||
if (error.errorType === 'INVOICES_NOT_DELIVERED_YET') {
|
if (error.errorType === 'INVOICES_NOT_DELIVERED_YET') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
errors: [{
|
errors: [
|
||||||
type: 'INVOICES_NOT_DELIVERED_YET', code: 200,
|
{
|
||||||
|
type: 'INVOICES_NOT_DELIVERED_YET',
|
||||||
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
not_delivered_invoices_ids: error.payload.notDeliveredInvoices.map(
|
not_delivered_invoices_ids: error.payload.notDeliveredInvoices.map(
|
||||||
(invoice) => invoice.id
|
(invoice) => invoice.id
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface ITrialBalanceAccount {
|
|||||||
credit: number,
|
credit: number,
|
||||||
debit: number,
|
debit: number,
|
||||||
balance: number,
|
balance: number,
|
||||||
|
currencyCode: string,
|
||||||
|
|
||||||
formattedCredit: string,
|
formattedCredit: string,
|
||||||
formattedDebit: string,
|
formattedDebit: string,
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ import {
|
|||||||
IJournalPoster,
|
IJournalPoster,
|
||||||
IAccountType,
|
IAccountType,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import {
|
import { dateRangeCollection, flatToNestedArray } from 'utils';
|
||||||
dateRangeCollection,
|
|
||||||
flatToNestedArray,
|
|
||||||
} from 'utils';
|
|
||||||
import BalanceSheetStructure from 'data/BalanceSheetStructure';
|
import BalanceSheetStructure from 'data/BalanceSheetStructure';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
|
|
||||||
@@ -37,7 +34,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
query: IBalanceSheetQuery,
|
query: IBalanceSheetQuery,
|
||||||
accounts: IAccount & { type: IAccountType }[],
|
accounts: IAccount & { type: IAccountType }[],
|
||||||
journalFinancial: IJournalPoster,
|
journalFinancial: IJournalPoster,
|
||||||
baseCurrency: string,
|
baseCurrency: string
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -48,9 +45,8 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
this.journalFinancial = journalFinancial;
|
this.journalFinancial = journalFinancial;
|
||||||
this.baseCurrency = baseCurrency;
|
this.baseCurrency = baseCurrency;
|
||||||
|
|
||||||
this.comparatorDateType = query.displayColumnsType === 'total'
|
this.comparatorDateType =
|
||||||
? 'day'
|
query.displayColumnsType === 'total' ? 'day' : query.displayColumnsBy;
|
||||||
: query.displayColumnsBy;
|
|
||||||
|
|
||||||
this.initDateRangeCollection();
|
this.initDateRangeCollection();
|
||||||
}
|
}
|
||||||
@@ -73,20 +69,24 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
* @param {IBalanceSheetSection[]} sections -
|
* @param {IBalanceSheetSection[]} sections -
|
||||||
* @return {IBalanceSheetAccountTotal}
|
* @return {IBalanceSheetAccountTotal}
|
||||||
*/
|
*/
|
||||||
private getSectionTotal(sections: IBalanceSheetSection[]): IBalanceSheetAccountTotal {
|
private getSectionTotal(
|
||||||
|
sections: IBalanceSheetSection[]
|
||||||
|
): IBalanceSheetAccountTotal {
|
||||||
const amount = sumBy(sections, 'total.amount');
|
const amount = sumBy(sections, 'total.amount');
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
|
|
||||||
return { amount, formattedAmount, currencyCode };
|
return { amount, formattedAmount, currencyCode };
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve accounts total periods.
|
* Retrieve accounts total periods.
|
||||||
* @param {IBalanceSheetAccount[]} sections -
|
* @param {IBalanceSheetAccount[]} sections -
|
||||||
* @return {IBalanceSheetAccountTotal[]}
|
* @return {IBalanceSheetAccountTotal[]}
|
||||||
*/
|
*/
|
||||||
private getSectionTotalPeriods(sections: IBalanceSheetAccount[]): IBalanceSheetAccountTotal[] {
|
private getSectionTotalPeriods(
|
||||||
|
sections: Array<IBalanceSheetAccount|IBalanceSheetSection>
|
||||||
|
): IBalanceSheetAccountTotal[] {
|
||||||
return this.dateRangeSet.map((date, index) => {
|
return this.dateRangeSet.map((date, index) => {
|
||||||
const amount = sumBy(sections, `totalPeriods[${index}].amount`);
|
const amount = sumBy(sections, `totalPeriods[${index}].amount`);
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatNumber(amount);
|
||||||
@@ -101,7 +101,9 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IBalanceSheetAccountTotal[]}
|
* @return {IBalanceSheetAccountTotal[]}
|
||||||
*/
|
*/
|
||||||
private getAccountTotalPeriods (account: IAccount): IBalanceSheetAccountTotal[] {
|
private getAccountTotalPeriods(
|
||||||
|
account: IAccount
|
||||||
|
): IBalanceSheetAccountTotal[] {
|
||||||
return this.dateRangeSet.map((date) => {
|
return this.dateRangeSet.map((date) => {
|
||||||
const amount = this.journalFinancial.getAccountBalance(
|
const amount = this.journalFinancial.getAccountBalance(
|
||||||
account.id,
|
account.id,
|
||||||
@@ -124,19 +126,20 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
private balanceSheetAccountMapper(account: IAccount): IBalanceSheetAccount {
|
private balanceSheetAccountMapper(account: IAccount): IBalanceSheetAccount {
|
||||||
// Calculates the closing balance of the given account in the specific date point.
|
// Calculates the closing balance of the given account in the specific date point.
|
||||||
const amount = this.journalFinancial.getAccountBalance(
|
const amount = this.journalFinancial.getAccountBalance(
|
||||||
account.id, this.query.toDate,
|
account.id,
|
||||||
|
this.query.toDate
|
||||||
);
|
);
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatNumber(amount);
|
||||||
|
|
||||||
// Retrieve all entries that associated to the given account.
|
// Retrieve all entries that associated to the given account.
|
||||||
const entries = this.journalFinancial.getAccountEntries(account.id)
|
const entries = this.journalFinancial.getAccountEntries(account.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...pick(account, ['id', 'index', 'name', 'code', 'parentAccountId']),
|
...pick(account, ['id', 'index', 'name', 'code', 'parentAccountId']),
|
||||||
type: 'account',
|
type: 'account',
|
||||||
hasTransactions: entries.length > 0,
|
hasTransactions: entries.length > 0,
|
||||||
// Total date periods.
|
// Total date periods.
|
||||||
...this.query.displayColumnsType === 'date_periods' && ({
|
...(this.query.displayColumnsType === 'date_periods' && {
|
||||||
totalPeriods: this.getAccountTotalPeriods(account),
|
totalPeriods: this.getAccountTotalPeriods(account),
|
||||||
}),
|
}),
|
||||||
total: {
|
total: {
|
||||||
@@ -145,7 +148,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
currencyCode: this.baseCurrency,
|
currencyCode: this.baseCurrency,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strcuture accounts related mapper.
|
* Strcuture accounts related mapper.
|
||||||
@@ -155,10 +158,10 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
*/
|
*/
|
||||||
private structureRelatedAccountsMapper(
|
private structureRelatedAccountsMapper(
|
||||||
sectionAccountsTypes: string[],
|
sectionAccountsTypes: string[],
|
||||||
accounts: IAccount & { type: IAccountType }[],
|
accounts: IAccount & { type: IAccountType }[]
|
||||||
): {
|
): {
|
||||||
children: IBalanceSheetAccount[],
|
children: IBalanceSheetAccount[];
|
||||||
total: IBalanceSheetAccountTotal,
|
total: IBalanceSheetAccountTotal;
|
||||||
} {
|
} {
|
||||||
const filteredAccounts = accounts
|
const filteredAccounts = accounts
|
||||||
// Filter accounts that associated to the section accounts types.
|
// Filter accounts that associated to the section accounts types.
|
||||||
@@ -169,7 +172,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
// Filter accounts that have no transaction when `noneTransactions` is on.
|
// Filter accounts that have no transaction when `noneTransactions` is on.
|
||||||
.filter(
|
.filter(
|
||||||
(section: IBalanceSheetAccount) =>
|
(section: IBalanceSheetAccount) =>
|
||||||
!(!section.hasTransactions && this.query.noneTransactions),
|
!(!section.hasTransactions && this.query.noneTransactions)
|
||||||
)
|
)
|
||||||
// Filter accounts that have zero total amount when `noneZero` is on.
|
// Filter accounts that have zero total amount when `noneZero` is on.
|
||||||
.filter(
|
.filter(
|
||||||
@@ -181,10 +184,10 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
const totalAmount = sumBy(filteredAccounts, 'total.amount');
|
const totalAmount = sumBy(filteredAccounts, 'total.amount');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
children: flatToNestedArray(
|
children: flatToNestedArray(filteredAccounts, {
|
||||||
filteredAccounts,
|
id: 'id',
|
||||||
{ id: 'id', parentId: 'parentAccountId' }
|
parentId: 'parentAccountId',
|
||||||
),
|
}),
|
||||||
total: {
|
total: {
|
||||||
amount: totalAmount,
|
amount: totalAmount,
|
||||||
formattedAmount: this.formatNumber(totalAmount),
|
formattedAmount: this.formatNumber(totalAmount),
|
||||||
@@ -196,7 +199,31 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappes the structure sections.
|
||||||
|
* @param {IBalanceSheetStructureSection} structure
|
||||||
|
* @param {IAccount} accounts
|
||||||
|
*/
|
||||||
|
private structureSectionMapper(
|
||||||
|
structure: IBalanceSheetStructureSection,
|
||||||
|
accounts: IAccount[]
|
||||||
|
) {
|
||||||
|
const children = this.balanceSheetStructureWalker(
|
||||||
|
structure.children,
|
||||||
|
accounts
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
children,
|
||||||
|
total: this.getSectionTotal(children),
|
||||||
|
...(this.query.displayColumnsType === 'date_periods'
|
||||||
|
? {
|
||||||
|
totalPeriods: this.getSectionTotalPeriods(children),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Balance sheet structure mapper.
|
* Balance sheet structure mapper.
|
||||||
@@ -205,29 +232,18 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
*/
|
*/
|
||||||
private balanceSheetStructureMapper(
|
private balanceSheetStructureMapper(
|
||||||
structure: IBalanceSheetStructureSection,
|
structure: IBalanceSheetStructureSection,
|
||||||
accounts: IAccount & { type: IAccountType }[],
|
accounts: IAccount & { type: IAccountType }[]
|
||||||
): IBalanceSheetSection {
|
): IBalanceSheetSection {
|
||||||
const result = {
|
const result = {
|
||||||
name: structure.name,
|
name: structure.name,
|
||||||
sectionType: structure.sectionType,
|
sectionType: structure.sectionType,
|
||||||
type: structure.type,
|
type: structure.type,
|
||||||
...(structure.type === 'accounts_section'
|
...(structure.type === 'accounts_section')
|
||||||
? {
|
? this.structureRelatedAccountsMapper(
|
||||||
...this.structureRelatedAccountsMapper(
|
|
||||||
structure._accountsTypesRelated,
|
structure._accountsTypesRelated,
|
||||||
accounts,
|
accounts
|
||||||
),
|
)
|
||||||
}
|
: this.structureSectionMapper(structure, accounts),
|
||||||
: (() => {
|
|
||||||
const children = this.balanceSheetStructureWalker(
|
|
||||||
structure.children,
|
|
||||||
accounts,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
children,
|
|
||||||
total: this.getSectionTotal(children),
|
|
||||||
};
|
|
||||||
})()),
|
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -239,15 +255,18 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
*/
|
*/
|
||||||
private balanceSheetStructureWalker(
|
private balanceSheetStructureWalker(
|
||||||
reportStructure: IBalanceSheetStructureSection[],
|
reportStructure: IBalanceSheetStructureSection[],
|
||||||
balanceSheetAccounts: IAccount & { type: IAccountType }[],
|
balanceSheetAccounts: IAccount & { type: IAccountType }[]
|
||||||
): IBalanceSheetSection[] {
|
): IBalanceSheetSection[] {
|
||||||
return reportStructure
|
return (
|
||||||
|
reportStructure
|
||||||
.map((structure: IBalanceSheetStructureSection) =>
|
.map((structure: IBalanceSheetStructureSection) =>
|
||||||
this.balanceSheetStructureMapper(structure, balanceSheetAccounts)
|
this.balanceSheetStructureMapper(structure, balanceSheetAccounts)
|
||||||
)
|
)
|
||||||
// Filter the structure sections that have no children.
|
// Filter the structure sections that have no children.
|
||||||
.filter((structure: IBalanceSheetSection) =>
|
.filter(
|
||||||
|
(structure: IBalanceSheetSection) =>
|
||||||
structure.children.length > 0 || structure._forceShow
|
structure.children.length > 0 || structure._forceShow
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +297,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
|
|||||||
public reportData(): IBalanceSheetSection[] {
|
public reportData(): IBalanceSheetSection[] {
|
||||||
return this.balanceSheetStructureWalker(
|
return this.balanceSheetStructureWalker(
|
||||||
BalanceSheetStructure,
|
BalanceSheetStructure,
|
||||||
this.accounts,
|
this.accounts
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,8 @@ export default class BalanceSheetStatementService
|
|||||||
|
|
||||||
// Retrieve all journal transactions based on the given query.
|
// Retrieve all journal transactions based on the given query.
|
||||||
const transactions = await transactionsRepository.journal({
|
const transactions = await transactionsRepository.journal({
|
||||||
fromDate: query.toDate,
|
fromDate: query.fromDate,
|
||||||
|
toDate: query.toDate,
|
||||||
});
|
});
|
||||||
// Transform transactions to journal collection.
|
// Transform transactions to journal collection.
|
||||||
const transactionsJournal = Journal.fromTransactions(
|
const transactionsJournal = Journal.fromTransactions(
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ export default class JournalSheet extends FinancialSheet {
|
|||||||
* @return {IJournalReport}
|
* @return {IJournalReport}
|
||||||
*/
|
*/
|
||||||
reportData(): IJournalReport {
|
reportData(): IJournalReport {
|
||||||
return {
|
return this.entriesWalker(this.journal.entries);
|
||||||
entries: this.entriesWalker(this.journal.entries),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Service, Inject } from "typedi";
|
import { Service, Inject } from 'typedi';
|
||||||
import { IJournalReportQuery } from 'interfaces';
|
import { IJournalReportQuery } from 'interfaces';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import JournalSheet from "./JournalSheet";
|
import JournalSheet from './JournalSheet';
|
||||||
import TenancyService from "services/Tenancy/TenancyService";
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import Journal from "services/Accounting/JournalPoster";
|
import Journal from 'services/Accounting/JournalPoster';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class JournalSheetService {
|
export default class JournalSheetService {
|
||||||
@@ -36,10 +36,7 @@ export default class JournalSheetService {
|
|||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IJournalSheetFilterQuery} query
|
* @param {IJournalSheetFilterQuery} query
|
||||||
*/
|
*/
|
||||||
async journalSheet(
|
async journalSheet(tenantId: number, query: IJournalReportQuery) {
|
||||||
tenantId: number,
|
|
||||||
query: IJournalReportQuery,
|
|
||||||
) {
|
|
||||||
const {
|
const {
|
||||||
accountRepository,
|
accountRepository,
|
||||||
transactionsRepository,
|
transactionsRepository,
|
||||||
@@ -49,11 +46,17 @@ export default class JournalSheetService {
|
|||||||
...this.defaultQuery,
|
...this.defaultQuery,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
this.logger.info('[journal] trying to calculate the report.', { tenantId, filter });
|
this.logger.info('[journal] trying to calculate the report.', {
|
||||||
|
tenantId,
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
|
||||||
// Settings service.
|
// Settings service.
|
||||||
const settings = this.tenancy.settings(tenantId);
|
const settings = this.tenancy.settings(tenantId);
|
||||||
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
// Retrieve all accounts on the storage.
|
// Retrieve all accounts on the storage.
|
||||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
@@ -66,10 +69,12 @@ export default class JournalSheetService {
|
|||||||
fromAmount: filter.fromRange,
|
fromAmount: filter.fromRange,
|
||||||
toAmount: filter.toRange,
|
toAmount: filter.toRange,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Transform the transactions array to journal collection.
|
// Transform the transactions array to journal collection.
|
||||||
const transactionsJournal = Journal.fromTransactions(transactions, tenantId, accountsGraph);
|
const transactionsJournal = Journal.fromTransactions(
|
||||||
|
transactions,
|
||||||
|
tenantId,
|
||||||
|
accountsGraph
|
||||||
|
);
|
||||||
// Journal report instance.
|
// Journal report instance.
|
||||||
const journalSheetInstance = new JournalSheet(
|
const journalSheetInstance = new JournalSheet(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -80,6 +85,9 @@ export default class JournalSheetService {
|
|||||||
// Retrieve journal report columns.
|
// Retrieve journal report columns.
|
||||||
const journalSheetData = journalSheetInstance.reportData();
|
const journalSheetData = journalSheetInstance.reportData();
|
||||||
|
|
||||||
return journalSheetData;
|
return {
|
||||||
|
data: journalSheetData,
|
||||||
|
query: filter,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { flatten, pick, sumBy } from 'lodash';
|
import { flatten, pick, sumBy } from 'lodash';
|
||||||
import { IProfitLossSheetQuery } from "interfaces/ProfitLossSheet";
|
import { IProfitLossSheetQuery } from 'interfaces/ProfitLossSheet';
|
||||||
import FinancialSheet from "../FinancialSheet";
|
import FinancialSheet from '../FinancialSheet';
|
||||||
import {
|
import {
|
||||||
IAccount,
|
IAccount,
|
||||||
IAccountType,
|
IAccountType,
|
||||||
@@ -34,7 +34,7 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
query: IProfitLossSheetQuery,
|
query: IProfitLossSheetQuery,
|
||||||
accounts: IAccount & { type: IAccountType }[],
|
accounts: IAccount & { type: IAccountType }[],
|
||||||
journal: IJournalPoster,
|
journal: IJournalPoster,
|
||||||
baseCurrency: string,
|
baseCurrency: string
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -44,9 +44,8 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
this.journal = journal;
|
this.journal = journal;
|
||||||
this.baseCurrency = baseCurrency;
|
this.baseCurrency = baseCurrency;
|
||||||
this.comparatorDateType = query.displayColumnsType === 'total'
|
this.comparatorDateType =
|
||||||
? 'day'
|
query.displayColumnsType === 'total' ? 'day' : query.displayColumnsBy;
|
||||||
: query.displayColumnsBy;
|
|
||||||
|
|
||||||
this.initDateRangeCollection();
|
this.initDateRangeCollection();
|
||||||
}
|
}
|
||||||
@@ -56,7 +55,7 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
* @return {IAccount & { type: IAccountType }[]}
|
* @return {IAccount & { type: IAccountType }[]}
|
||||||
*/
|
*/
|
||||||
get incomeAccounts() {
|
get incomeAccounts() {
|
||||||
return this.accounts.filter(a => a.type.key === 'income');
|
return this.accounts.filter((a) => a.type.key === 'income');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,7 +63,7 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
* @return {IAccount & { type: IAccountType }[]}
|
* @return {IAccount & { type: IAccountType }[]}
|
||||||
*/
|
*/
|
||||||
get expensesAccounts() {
|
get expensesAccounts() {
|
||||||
return this.accounts.filter(a => a.type.key === 'expense');
|
return this.accounts.filter((a) => a.type.key === 'expense');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,7 +71,7 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
* @return {IAccount & { type: IAccountType }[]}}
|
* @return {IAccount & { type: IAccountType }[]}}
|
||||||
*/
|
*/
|
||||||
get otherExpensesAccounts() {
|
get otherExpensesAccounts() {
|
||||||
return this.accounts.filter(a => a.type.key === 'other_expense');
|
return this.accounts.filter((a) => a.type.key === 'other_expense');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,7 +79,7 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
* @return {IAccount & { type: IAccountType }[]}
|
* @return {IAccount & { type: IAccountType }[]}
|
||||||
*/
|
*/
|
||||||
get costOfSalesAccounts() {
|
get costOfSalesAccounts() {
|
||||||
return this.accounts.filter(a => a.type.key === 'cost_of_goods_sold');
|
return this.accounts.filter((a) => a.type.key === 'cost_of_goods_sold');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +104,7 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
const amount = this.journal.getAccountBalance(
|
const amount = this.journal.getAccountBalance(
|
||||||
account.id,
|
account.id,
|
||||||
this.query.toDate,
|
this.query.toDate,
|
||||||
this.comparatorDateType,
|
this.comparatorDateType
|
||||||
);
|
);
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
@@ -123,13 +122,13 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
const amount = this.journal.getAccountBalance(
|
const amount = this.journal.getAccountBalance(
|
||||||
account.id,
|
account.id,
|
||||||
date,
|
date,
|
||||||
this.comparatorDateType,
|
this.comparatorDateType
|
||||||
);
|
);
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
|
|
||||||
return { date, amount, formattedAmount, currencyCode };
|
return { date, amount, formattedAmount, currencyCode };
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,22 +156,26 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
* @param {IAccount[]} accounts -
|
* @param {IAccount[]} accounts -
|
||||||
* @return {IProfitLossSheetAccount[]}
|
* @return {IProfitLossSheetAccount[]}
|
||||||
*/
|
*/
|
||||||
private accountsWalker(accounts: IAccount & { type: IAccountType }[]): IProfitLossSheetAccount[] {
|
private accountsWalker(
|
||||||
|
accounts: IAccount & { type: IAccountType }[]
|
||||||
|
): IProfitLossSheetAccount[] {
|
||||||
const flattenAccounts = accounts
|
const flattenAccounts = accounts
|
||||||
.map(this.accountMapper.bind(this))
|
.map(this.accountMapper.bind(this))
|
||||||
// Filter accounts that have no transaction when `noneTransactions` is on.
|
// Filter accounts that have no transaction when `noneTransactions` is on.
|
||||||
.filter((account: IProfitLossSheetAccount) =>
|
.filter(
|
||||||
!(!account.hasTransactions && this.query.noneTransactions),
|
(account: IProfitLossSheetAccount) =>
|
||||||
|
!(!account.hasTransactions && this.query.noneTransactions)
|
||||||
)
|
)
|
||||||
// Filter accounts that have zero total amount when `noneZero` is on.
|
// Filter accounts that have zero total amount when `noneZero` is on.
|
||||||
.filter((account: IProfitLossSheetAccount) =>
|
.filter(
|
||||||
|
(account: IProfitLossSheetAccount) =>
|
||||||
!(account.total.amount === 0 && this.query.noneZero)
|
!(account.total.amount === 0 && this.query.noneZero)
|
||||||
);
|
);
|
||||||
|
|
||||||
return flatToNestedArray(
|
return flatToNestedArray(flattenAccounts, {
|
||||||
flattenAccounts,
|
id: 'id',
|
||||||
{ id: 'id', parentId: 'parentAccountId' },
|
parentId: 'parentAccountId',
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,7 +183,9 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
* @param {IAccount[]} accounts -
|
* @param {IAccount[]} accounts -
|
||||||
* @return {IProfitLossSheetTotal}
|
* @return {IProfitLossSheetTotal}
|
||||||
*/
|
*/
|
||||||
private gatTotalSection(accounts: IProfitLossSheetAccount[]): IProfitLossSheetTotal {
|
private gatTotalSection(
|
||||||
|
accounts: IProfitLossSheetAccount[]
|
||||||
|
): IProfitLossSheetTotal {
|
||||||
const amount = sumBy(accounts, 'total.amount');
|
const amount = sumBy(accounts, 'total.amount');
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
@@ -193,7 +198,9 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
* @param {IAccount} accounts -
|
* @param {IAccount} accounts -
|
||||||
* @return {IProfitLossSheetTotal[]}
|
* @return {IProfitLossSheetTotal[]}
|
||||||
*/
|
*/
|
||||||
private getTotalPeriodsSection(accounts: IProfitLossSheetAccount[]): IProfitLossSheetTotal[] {
|
private getTotalPeriodsSection(
|
||||||
|
accounts: IProfitLossSheetAccount[]
|
||||||
|
): IProfitLossSheetTotal[] {
|
||||||
return this.dateRangeSet.map((date, index) => {
|
return this.dateRangeSet.map((date, index) => {
|
||||||
const amount = sumBy(accounts, `totalPeriods[${index}].amount`);
|
const amount = sumBy(accounts, `totalPeriods[${index}].amount`);
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatNumber(amount);
|
||||||
@@ -213,7 +220,7 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
...(this.query.displayColumnsType === 'date_periods' && {
|
...(this.query.displayColumnsType === 'date_periods' && {
|
||||||
totalPeriods: this.getTotalPeriodsSection(accounts),
|
totalPeriods: this.getTotalPeriodsSection(accounts),
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,10 +273,13 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
|
|
||||||
private getSummarySectionDatePeriods(
|
private getSummarySectionDatePeriods(
|
||||||
positiveSections: IProfitLossSheetTotalSection[],
|
positiveSections: IProfitLossSheetTotalSection[],
|
||||||
minesSections: IProfitLossSheetTotalSection[],
|
minesSections: IProfitLossSheetTotalSection[]
|
||||||
) {
|
) {
|
||||||
return this.dateRangeSet.map((date, index: number) => {
|
return this.dateRangeSet.map((date, index: number) => {
|
||||||
const totalPositive = sumBy(positiveSections, `totalPeriods[${index}].amount`);
|
const totalPositive = sumBy(
|
||||||
|
positiveSections,
|
||||||
|
`totalPeriods[${index}].amount`
|
||||||
|
);
|
||||||
const totalMines = sumBy(minesSections, `totalPeriods[${index}].amount`);
|
const totalMines = sumBy(minesSections, `totalPeriods[${index}].amount`);
|
||||||
|
|
||||||
const amount = totalPositive - totalMines;
|
const amount = totalPositive - totalMines;
|
||||||
@@ -278,11 +288,11 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
|
|
||||||
return { date, amount, formattedAmount, currencyCode };
|
return { date, amount, formattedAmount, currencyCode };
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
private getSummarySectionTotal(
|
private getSummarySectionTotal(
|
||||||
positiveSections: IProfitLossSheetTotalSection[],
|
positiveSections: IProfitLossSheetTotalSection[],
|
||||||
minesSections: IProfitLossSheetTotalSection[],
|
minesSections: IProfitLossSheetTotalSection[]
|
||||||
) {
|
) {
|
||||||
const totalPositiveSections = sumBy(positiveSections, 'total.amount');
|
const totalPositiveSections = sumBy(positiveSections, 'total.amount');
|
||||||
const totalMinesSections = sumBy(minesSections, 'total.amount');
|
const totalMinesSections = sumBy(minesSections, 'total.amount');
|
||||||
@@ -300,22 +310,23 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
*/
|
*/
|
||||||
private getSummarySection(
|
private getSummarySection(
|
||||||
sections: IProfitLossSheetTotalSection | IProfitLossSheetTotalSection[],
|
sections: IProfitLossSheetTotalSection | IProfitLossSheetTotalSection[],
|
||||||
subtractSections: IProfitLossSheetTotalSection|IProfitLossSheetTotalSection[]
|
subtractSections:
|
||||||
|
| IProfitLossSheetTotalSection
|
||||||
|
| IProfitLossSheetTotalSection[]
|
||||||
): IProfitLossSheetTotalSection {
|
): IProfitLossSheetTotalSection {
|
||||||
const positiveSections = Array.isArray(sections) ? sections : [sections];
|
const positiveSections = Array.isArray(sections) ? sections : [sections];
|
||||||
const minesSections = Array.isArray(subtractSections) ? subtractSections : [subtractSections];
|
const minesSections = Array.isArray(subtractSections)
|
||||||
|
? subtractSections
|
||||||
|
: [subtractSections];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
total: this.getSummarySectionTotal(positiveSections, minesSections),
|
total: this.getSummarySectionTotal(positiveSections, minesSections),
|
||||||
...(this.query.displayColumnsType === 'date_periods' && {
|
...(this.query.displayColumnsType === 'date_periods' && {
|
||||||
totalPeriods: [
|
totalPeriods: [
|
||||||
...this.getSummarySectionDatePeriods(
|
...this.getSummarySectionDatePeriods(positiveSections, minesSections),
|
||||||
positiveSections,
|
|
||||||
minesSections,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -341,7 +352,10 @@ export default class ProfitLossSheet extends FinancialSheet {
|
|||||||
const grossProfit = this.getSummarySection(income, costOfSales);
|
const grossProfit = this.getSummarySection(income, costOfSales);
|
||||||
|
|
||||||
// - Operating profit = Gross profit - Expenses.
|
// - Operating profit = Gross profit - Expenses.
|
||||||
const operatingProfit = this.getSummarySection(grossProfit, [expenses, costOfSales]);
|
const operatingProfit = this.getSummarySection(grossProfit, [
|
||||||
|
expenses,
|
||||||
|
costOfSales,
|
||||||
|
]);
|
||||||
|
|
||||||
// - Net income = Operating profit - Other expenses.
|
// - Net income = Operating profit - Other expenses.
|
||||||
const netIncome = this.getSummarySection(operatingProfit, otherExpenses);
|
const netIncome = this.getSummarySection(operatingProfit, otherExpenses);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default class TrialBalanceSheet extends FinancialSheet{
|
|||||||
query: ITrialBalanceSheetQuery;
|
query: ITrialBalanceSheetQuery;
|
||||||
accounts: IAccount & { type: IAccountType }[];
|
accounts: IAccount & { type: IAccountType }[];
|
||||||
journalFinancial: any;
|
journalFinancial: any;
|
||||||
|
baseCurrency: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -25,7 +26,8 @@ export default class TrialBalanceSheet extends FinancialSheet{
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: ITrialBalanceSheetQuery,
|
query: ITrialBalanceSheetQuery,
|
||||||
accounts: IAccount & { type: IAccountType }[],
|
accounts: IAccount & { type: IAccountType }[],
|
||||||
journalFinancial: any
|
journalFinancial: any,
|
||||||
|
baseCurrency: string,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@ export default class TrialBalanceSheet extends FinancialSheet{
|
|||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
this.journalFinancial = journalFinancial;
|
this.journalFinancial = journalFinancial;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
|
this.baseCurrency = baseCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,6 +61,7 @@ export default class TrialBalanceSheet extends FinancialSheet{
|
|||||||
credit: trial.credit,
|
credit: trial.credit,
|
||||||
debit: trial.debit,
|
debit: trial.debit,
|
||||||
balance: trial.balance,
|
balance: trial.balance,
|
||||||
|
currencyCode: this.baseCurrency,
|
||||||
|
|
||||||
formattedCredit: this.formatNumber(trial.credit),
|
formattedCredit: this.formatNumber(trial.credit),
|
||||||
formattedDebit: this.formatNumber(trial.debit),
|
formattedDebit: this.formatNumber(trial.debit),
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ export default class TrialBalanceSheetService {
|
|||||||
transactionsRepository,
|
transactionsRepository,
|
||||||
} = this.tenancy.repositories(tenantId);
|
} = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
// Settings tenant service.
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
||||||
|
|
||||||
this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { tenantId, filter });
|
this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { tenantId, filter });
|
||||||
|
|
||||||
// Retrieve all accounts on the storage.
|
// Retrieve all accounts on the storage.
|
||||||
@@ -76,6 +80,7 @@ export default class TrialBalanceSheetService {
|
|||||||
filter,
|
filter,
|
||||||
accounts,
|
accounts,
|
||||||
transactionsJournal,
|
transactionsJournal,
|
||||||
|
baseCurrency
|
||||||
);
|
);
|
||||||
// Trial balance sheet data.
|
// Trial balance sheet data.
|
||||||
const trialBalanceSheetData = trialBalanceInstance.reportData();
|
const trialBalanceSheetData = trialBalanceInstance.reportData();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
IPaymentReceiveEntry,
|
IPaymentReceiveEntry,
|
||||||
IPaymentReceiveEntryDTO,
|
IPaymentReceiveEntryDTO,
|
||||||
IPaymentReceivesFilter,
|
IPaymentReceivesFilter,
|
||||||
ISaleInvoice
|
ISaleInvoice,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import AccountsService from 'services/Accounts/AccountsService';
|
import AccountsService from 'services/Accounts/AccountsService';
|
||||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||||
@@ -34,11 +34,12 @@ const ERRORS = {
|
|||||||
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
|
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
|
||||||
PAYMENT_RECEIVE_NOT_EXISTS: 'PAYMENT_RECEIVE_NOT_EXISTS',
|
PAYMENT_RECEIVE_NOT_EXISTS: 'PAYMENT_RECEIVE_NOT_EXISTS',
|
||||||
DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND',
|
DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND',
|
||||||
DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE',
|
DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE:
|
||||||
|
'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE',
|
||||||
INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT',
|
INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT',
|
||||||
INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND',
|
INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND',
|
||||||
ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS',
|
ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS',
|
||||||
INVOICES_NOT_DELIVERED_YET: 'INVOICES_NOT_DELIVERED_YET'
|
INVOICES_NOT_DELIVERED_YET: 'INVOICES_NOT_DELIVERED_YET',
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Payment receive service.
|
* Payment receive service.
|
||||||
@@ -81,7 +82,8 @@ export default class PaymentReceiveService {
|
|||||||
notPaymentReceiveId?: number
|
notPaymentReceiveId?: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
const paymentReceive = await PaymentReceive.query().findOne('payment_receive_no', paymentReceiveNo)
|
const paymentReceive = await PaymentReceive.query()
|
||||||
|
.findOne('payment_receive_no', paymentReceiveNo)
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
if (notPaymentReceiveId) {
|
if (notPaymentReceiveId) {
|
||||||
builder.whereNot('id', notPaymentReceiveId);
|
builder.whereNot('id', notPaymentReceiveId);
|
||||||
@@ -118,13 +120,23 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {number} depositAccountId -
|
* @param {number} depositAccountId -
|
||||||
*/
|
*/
|
||||||
async getDepositAccountOrThrowError(tenantId: number, depositAccountId: number): Promise<IAccount> {
|
async getDepositAccountOrThrowError(
|
||||||
const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId);
|
tenantId: number,
|
||||||
|
depositAccountId: number
|
||||||
|
): Promise<IAccount> {
|
||||||
|
const {
|
||||||
|
accountTypeRepository,
|
||||||
|
accountRepository,
|
||||||
|
} = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
const currentAssetTypes = await accountTypeRepository.getByChildType('current_asset');
|
const currentAssetTypes = await accountTypeRepository.getByChildType(
|
||||||
const depositAccount = await accountRepository.findOneById(depositAccountId);
|
'current_asset'
|
||||||
|
);
|
||||||
|
const depositAccount = await accountRepository.findOneById(
|
||||||
|
depositAccountId
|
||||||
|
);
|
||||||
|
|
||||||
const currentAssetTypesIds = currentAssetTypes.map(type => type.id);
|
const currentAssetTypesIds = currentAssetTypes.map((type) => type.id);
|
||||||
|
|
||||||
if (!depositAccount) {
|
if (!depositAccount) {
|
||||||
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
|
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
|
||||||
@@ -140,10 +152,16 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {} paymentReceiveEntries -
|
* @param {} paymentReceiveEntries -
|
||||||
*/
|
*/
|
||||||
async validateInvoicesIDsExistance(tenantId: number, customerId: number, paymentReceiveEntries: IPaymentReceiveEntryDTO[]): Promise<void> {
|
async validateInvoicesIDsExistance(
|
||||||
|
tenantId: number,
|
||||||
|
customerId: number,
|
||||||
|
paymentReceiveEntries: IPaymentReceiveEntryDTO[]
|
||||||
|
): Promise<void> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const invoicesIds = paymentReceiveEntries.map((e: IPaymentReceiveEntryDTO) => e.invoiceId);
|
const invoicesIds = paymentReceiveEntries.map(
|
||||||
|
(e: IPaymentReceiveEntryDTO) => e.invoiceId
|
||||||
|
);
|
||||||
const storedInvoices = await SaleInvoice.query()
|
const storedInvoices = await SaleInvoice.query()
|
||||||
.whereIn('id', invoicesIds)
|
.whereIn('id', invoicesIds)
|
||||||
.where('customer_id', customerId);
|
.where('customer_id', customerId);
|
||||||
@@ -155,10 +173,14 @@ export default class PaymentReceiveService {
|
|||||||
throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND);
|
throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND);
|
||||||
}
|
}
|
||||||
// Filters the not delivered invoices.
|
// Filters the not delivered invoices.
|
||||||
const notDeliveredInvoices = storedInvoices.filter((invoice) => !invoice.isDelivered);
|
const notDeliveredInvoices = storedInvoices.filter(
|
||||||
|
(invoice) => !invoice.isDelivered
|
||||||
|
);
|
||||||
|
|
||||||
if (notDeliveredInvoices.length > 0) {
|
if (notDeliveredInvoices.length > 0) {
|
||||||
throw new ServiceError(ERRORS.INVOICES_NOT_DELIVERED_YET, null, { notDeliveredInvoices });
|
throw new ServiceError(ERRORS.INVOICES_NOT_DELIVERED_YET, null, {
|
||||||
|
notDeliveredInvoices,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return storedInvoices;
|
return storedInvoices;
|
||||||
}
|
}
|
||||||
@@ -172,32 +194,38 @@ export default class PaymentReceiveService {
|
|||||||
async validateInvoicesPaymentsAmount(
|
async validateInvoicesPaymentsAmount(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveEntries: IPaymentReceiveEntryDTO[],
|
paymentReceiveEntries: IPaymentReceiveEntryDTO[],
|
||||||
oldPaymentEntries: IPaymentReceiveEntry[] = [],
|
oldPaymentEntries: IPaymentReceiveEntry[] = []
|
||||||
) {
|
) {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const invoicesIds = paymentReceiveEntries.map((e: IPaymentReceiveEntryDTO) => e.invoiceId);
|
const invoicesIds = paymentReceiveEntries.map(
|
||||||
|
(e: IPaymentReceiveEntryDTO) => e.invoiceId
|
||||||
|
);
|
||||||
|
|
||||||
const storedInvoices = await SaleInvoice.query().whereIn('id', invoicesIds);
|
const storedInvoices = await SaleInvoice.query().whereIn('id', invoicesIds);
|
||||||
|
|
||||||
const storedInvoicesMap = new Map(
|
const storedInvoicesMap = new Map(
|
||||||
storedInvoices
|
storedInvoices.map((invoice: ISaleInvoice) => {
|
||||||
.map((invoice: ISaleInvoice) => {
|
const oldEntries = oldPaymentEntries.filter((entry) => entry.invoiceId);
|
||||||
const oldEntries = oldPaymentEntries.filter(entry => entry.invoiceId);
|
const oldPaymentAmount = sumBy(oldEntries, 'paymentAmount') || 0;
|
||||||
const oldPaymentAmount = sumBy(oldEntries, 'paymentAmount') || 0,
|
|
||||||
|
|
||||||
return [invoice.id, { ...invoice, dueAmount: invoice.dueAmount + oldPaymentAmount }];
|
return [
|
||||||
|
invoice.id,
|
||||||
|
{ ...invoice, dueAmount: invoice.dueAmount + oldPaymentAmount },
|
||||||
|
];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const hasWrongPaymentAmount: any[] = [];
|
const hasWrongPaymentAmount: any[] = [];
|
||||||
|
|
||||||
paymentReceiveEntries.forEach((entry: IPaymentReceiveEntryDTO, index: number) => {
|
paymentReceiveEntries.forEach(
|
||||||
|
(entry: IPaymentReceiveEntryDTO, index: number) => {
|
||||||
const entryInvoice = storedInvoicesMap.get(entry.invoiceId);
|
const entryInvoice = storedInvoicesMap.get(entry.invoiceId);
|
||||||
const { dueAmount } = entryInvoice;
|
const { dueAmount } = entryInvoice;
|
||||||
|
|
||||||
if (dueAmount < entry.paymentAmount) {
|
if (dueAmount < entry.paymentAmount) {
|
||||||
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (hasWrongPaymentAmount.length > 0) {
|
if (hasWrongPaymentAmount.length > 0) {
|
||||||
throw new ServiceError(ERRORS.INVALID_PAYMENT_AMOUNT);
|
throw new ServiceError(ERRORS.INVALID_PAYMENT_AMOUNT);
|
||||||
}
|
}
|
||||||
@@ -212,7 +240,7 @@ export default class PaymentReceiveService {
|
|||||||
private async validateEntriesIdsExistance(
|
private async validateEntriesIdsExistance(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveId: number,
|
paymentReceiveId: number,
|
||||||
paymentReceiveEntries: IPaymentReceiveEntryDTO[],
|
paymentReceiveEntries: IPaymentReceiveEntryDTO[]
|
||||||
) {
|
) {
|
||||||
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
|
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -220,8 +248,10 @@ export default class PaymentReceiveService {
|
|||||||
.filter((entry) => entry.id)
|
.filter((entry) => entry.id)
|
||||||
.map((entry) => entry.id);
|
.map((entry) => entry.id);
|
||||||
|
|
||||||
const storedEntries = await PaymentReceiveEntry.query()
|
const storedEntries = await PaymentReceiveEntry.query().where(
|
||||||
.where('payment_receive_id', paymentReceiveId);
|
'payment_receive_id',
|
||||||
|
paymentReceiveId
|
||||||
|
);
|
||||||
|
|
||||||
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
|
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
|
||||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
||||||
@@ -238,31 +268,51 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {IPaymentReceive} paymentReceive
|
* @param {IPaymentReceive} paymentReceive
|
||||||
*/
|
*/
|
||||||
public async createPaymentReceive(tenantId: number, paymentReceiveDTO: IPaymentReceiveCreateDTO) {
|
public async createPaymentReceive(
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceiveDTO: IPaymentReceiveCreateDTO
|
||||||
|
) {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
||||||
|
|
||||||
// Validate payment receive number uniquiness.
|
// Validate payment receive number uniquiness.
|
||||||
if (paymentReceiveDTO.paymentReceiveNo) {
|
if (paymentReceiveDTO.paymentReceiveNo) {
|
||||||
await this.validatePaymentReceiveNoExistance(tenantId, paymentReceiveDTO.paymentReceiveNo);
|
await this.validatePaymentReceiveNoExistance(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.paymentReceiveNo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Validate customer existance.
|
// Validate customer existance.
|
||||||
await this.customersService.getCustomerByIdOrThrowError(tenantId, paymentReceiveDTO.customerId);
|
await this.customersService.getCustomerByIdOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.customerId
|
||||||
|
);
|
||||||
|
|
||||||
// Validate the deposit account existance and type.
|
// Validate the deposit account existance and type.
|
||||||
await this.getDepositAccountOrThrowError(tenantId, paymentReceiveDTO.depositAccountId);
|
await this.getDepositAccountOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.depositAccountId
|
||||||
|
);
|
||||||
|
|
||||||
// Validate payment receive invoices IDs existance.
|
// Validate payment receive invoices IDs existance.
|
||||||
await this.validateInvoicesIDsExistance(tenantId, paymentReceiveDTO.customerId, paymentReceiveDTO.entries);
|
await this.validateInvoicesIDsExistance(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.customerId,
|
||||||
|
paymentReceiveDTO.entries
|
||||||
|
);
|
||||||
|
|
||||||
// Validate invoice payment amount.
|
// Validate invoice payment amount.
|
||||||
await this.validateInvoicesPaymentsAmount(tenantId, paymentReceiveDTO.entries);
|
await this.validateInvoicesPaymentsAmount(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.entries
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.info('[payment_receive] inserting to the storage.');
|
this.logger.info('[payment_receive] inserting to the storage.');
|
||||||
const paymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query().insertGraphAndFetch({
|
||||||
.insertGraphAndFetch({
|
|
||||||
amount: paymentAmount,
|
amount: paymentAmount,
|
||||||
...formatDateFields(omit(paymentReceiveDTO, ['entries']), ['paymentDate']),
|
...formatDateFields(omit(paymentReceiveDTO, ['entries']), [
|
||||||
|
'paymentDate',
|
||||||
|
]),
|
||||||
|
|
||||||
entries: paymentReceiveDTO.entries.map((entry) => ({
|
entries: paymentReceiveDTO.entries.map((entry) => ({
|
||||||
...omit(entry, ['id']),
|
...omit(entry, ['id']),
|
||||||
@@ -270,9 +320,14 @@ export default class PaymentReceiveService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.eventDispatcher.dispatch(events.paymentReceive.onCreated, {
|
await this.eventDispatcher.dispatch(events.paymentReceive.onCreated, {
|
||||||
tenantId, paymentReceive, paymentReceiveId: paymentReceive.id,
|
tenantId,
|
||||||
|
paymentReceive,
|
||||||
|
paymentReceiveId: paymentReceive.id,
|
||||||
|
});
|
||||||
|
this.logger.info('[payment_receive] updated successfully.', {
|
||||||
|
tenantId,
|
||||||
|
paymentReceive,
|
||||||
});
|
});
|
||||||
this.logger.info('[payment_receive] updated successfully.', { tenantId, paymentReceive });
|
|
||||||
|
|
||||||
return paymentReceive;
|
return paymentReceive;
|
||||||
}
|
}
|
||||||
@@ -295,45 +350,78 @@ export default class PaymentReceiveService {
|
|||||||
public async editPaymentReceive(
|
public async editPaymentReceive(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveId: number,
|
paymentReceiveId: number,
|
||||||
paymentReceiveDTO: IPaymentReceiveEditDTO,
|
paymentReceiveDTO: IPaymentReceiveEditDTO
|
||||||
) {
|
) {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
||||||
|
|
||||||
this.logger.info('[payment_receive] trying to edit payment receive.', { tenantId, paymentReceiveId, paymentReceiveDTO });
|
this.logger.info('[payment_receive] trying to edit payment receive.', {
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
paymentReceiveDTO,
|
||||||
|
});
|
||||||
|
|
||||||
// Validate the payment receive existance.
|
// Validate the payment receive existance.
|
||||||
const oldPaymentReceive = await this.getPaymentReceiveOrThrowError(tenantId, paymentReceiveId);
|
const oldPaymentReceive = await this.getPaymentReceiveOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId
|
||||||
|
);
|
||||||
|
|
||||||
// Validate payment receive number uniquiness.
|
// Validate payment receive number uniquiness.
|
||||||
if (paymentReceiveDTO.paymentReceiveNo) {
|
if (paymentReceiveDTO.paymentReceiveNo) {
|
||||||
await this.validatePaymentReceiveNoExistance(tenantId, paymentReceiveDTO.paymentReceiveNo, paymentReceiveId);
|
await this.validatePaymentReceiveNoExistance(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.paymentReceiveNo,
|
||||||
|
paymentReceiveId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Validate the deposit account existance and type.
|
// Validate the deposit account existance and type.
|
||||||
this.getDepositAccountOrThrowError(tenantId, paymentReceiveDTO.depositAccountId);
|
this.getDepositAccountOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.depositAccountId
|
||||||
|
);
|
||||||
|
|
||||||
// Validate the entries ids existance on payment receive type.
|
// Validate the entries ids existance on payment receive type.
|
||||||
await this.validateEntriesIdsExistance(tenantId, paymentReceiveId, paymentReceiveDTO.entries);
|
await this.validateEntriesIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
paymentReceiveDTO.entries
|
||||||
|
);
|
||||||
|
|
||||||
// Validate payment receive invoices IDs existance and associated to the given customer id.
|
// Validate payment receive invoices IDs existance and associated to the given customer id.
|
||||||
await this.validateInvoicesIDsExistance(tenantId, oldPaymentReceive.customerId, paymentReceiveDTO.entries);
|
await this.validateInvoicesIDsExistance(
|
||||||
|
tenantId,
|
||||||
|
oldPaymentReceive.customerId,
|
||||||
|
paymentReceiveDTO.entries
|
||||||
|
);
|
||||||
|
|
||||||
// Validate invoice payment amount.
|
// Validate invoice payment amount.
|
||||||
await this.validateInvoicesPaymentsAmount(tenantId, paymentReceiveDTO.entries, oldPaymentReceive.entries);
|
await this.validateInvoicesPaymentsAmount(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveDTO.entries,
|
||||||
|
oldPaymentReceive.entries
|
||||||
|
);
|
||||||
|
|
||||||
// Update the payment receive transaction.
|
// Update the payment receive transaction.
|
||||||
const paymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query().upsertGraphAndFetch({
|
||||||
.upsertGraphAndFetch({
|
|
||||||
id: paymentReceiveId,
|
id: paymentReceiveId,
|
||||||
amount: paymentAmount,
|
amount: paymentAmount,
|
||||||
...formatDateFields(omit(paymentReceiveDTO, ['entries']), ['paymentDate']),
|
...formatDateFields(omit(paymentReceiveDTO, ['entries']), [
|
||||||
|
'paymentDate',
|
||||||
|
]),
|
||||||
entries: paymentReceiveDTO.entries,
|
entries: paymentReceiveDTO.entries,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.eventDispatcher.dispatch(events.paymentReceive.onEdited, {
|
await this.eventDispatcher.dispatch(events.paymentReceive.onEdited, {
|
||||||
tenantId, paymentReceiveId, paymentReceive, oldPaymentReceive
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
paymentReceive,
|
||||||
|
oldPaymentReceive,
|
||||||
|
});
|
||||||
|
this.logger.info('[payment_receive] upserted successfully.', {
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
});
|
});
|
||||||
this.logger.info('[payment_receive] upserted successfully.', { tenantId, paymentReceiveId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -351,20 +439,32 @@ export default class PaymentReceiveService {
|
|||||||
* @param {IPaymentReceive} paymentReceive - Payment receive object.
|
* @param {IPaymentReceive} paymentReceive - Payment receive object.
|
||||||
*/
|
*/
|
||||||
async deletePaymentReceive(tenantId: number, paymentReceiveId: number) {
|
async deletePaymentReceive(tenantId: number, paymentReceiveId: number) {
|
||||||
const { PaymentReceive, PaymentReceiveEntry } = this.tenancy.models(tenantId);
|
const { PaymentReceive, PaymentReceiveEntry } = this.tenancy.models(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
|
||||||
const oldPaymentReceive = await this.getPaymentReceiveOrThrowError(tenantId, paymentReceiveId);
|
const oldPaymentReceive = await this.getPaymentReceiveOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId
|
||||||
|
);
|
||||||
|
|
||||||
// Deletes the payment receive associated entries.
|
// Deletes the payment receive associated entries.
|
||||||
await PaymentReceiveEntry.query().where('payment_receive_id', paymentReceiveId).delete();
|
await PaymentReceiveEntry.query()
|
||||||
|
.where('payment_receive_id', paymentReceiveId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
// Deletes the payment receive transaction.
|
// Deletes the payment receive transaction.
|
||||||
await PaymentReceive.query().findById(paymentReceiveId).delete();
|
await PaymentReceive.query().findById(paymentReceiveId).delete();
|
||||||
|
|
||||||
await this.eventDispatcher.dispatch(events.paymentReceive.onDeleted, {
|
await this.eventDispatcher.dispatch(events.paymentReceive.onDeleted, {
|
||||||
tenantId, paymentReceiveId, oldPaymentReceive,
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
oldPaymentReceive,
|
||||||
|
});
|
||||||
|
this.logger.info('[payment_receive] deleted successfully.', {
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
});
|
});
|
||||||
this.logger.info('[payment_receive] deleted successfully.', { tenantId, paymentReceiveId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -376,9 +476,9 @@ export default class PaymentReceiveService {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveId: number
|
paymentReceiveId: number
|
||||||
): Promise<{
|
): Promise<{
|
||||||
paymentReceive: IPaymentReceive,
|
paymentReceive: IPaymentReceive;
|
||||||
receivableInvoices: ISaleInvoice[],
|
receivableInvoices: ISaleInvoice[];
|
||||||
paymentReceiveInvoices: ISaleInvoice[],
|
paymentReceiveInvoices: ISaleInvoice[];
|
||||||
}> {
|
}> {
|
||||||
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
|
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const paymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query()
|
||||||
@@ -401,8 +501,8 @@ export default class PaymentReceiveService {
|
|||||||
|
|
||||||
// Retrieve all payment receive associated invoices.
|
// Retrieve all payment receive associated invoices.
|
||||||
const paymentReceiveInvoices = paymentReceive.entries.map((entry) => ({
|
const paymentReceiveInvoices = paymentReceive.entries.map((entry) => ({
|
||||||
...(entry.invoice),
|
...entry.invoice,
|
||||||
dueAmount: (entry.invoice.dueAmount + entry.paymentAmount),
|
dueAmount: entry.invoice.dueAmount + entry.paymentAmount,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return { paymentReceive, receivableInvoices, paymentReceiveInvoices };
|
return { paymentReceive, receivableInvoices, paymentReceiveInvoices };
|
||||||
@@ -414,13 +514,24 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} paymentReceiveId - Payment receive id.
|
* @param {number} paymentReceiveId - Payment receive id.
|
||||||
* @return {Promise<ISaleInvoice>}
|
* @return {Promise<ISaleInvoice>}
|
||||||
*/
|
*/
|
||||||
public async getPaymentReceiveInvoices(tenantId: number, paymentReceiveId: number) {
|
public async getPaymentReceiveInvoices(
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceiveId: number
|
||||||
|
) {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const paymentReceive = await this.getPaymentReceiveOrThrowError(tenantId, paymentReceiveId);
|
const paymentReceive = await this.getPaymentReceiveOrThrowError(
|
||||||
const paymentReceiveInvoicesIds = paymentReceive.entries.map(entry => entry.invoiceId);
|
tenantId,
|
||||||
|
paymentReceiveId
|
||||||
|
);
|
||||||
|
const paymentReceiveInvoicesIds = paymentReceive.entries.map(
|
||||||
|
(entry) => entry.invoiceId
|
||||||
|
);
|
||||||
|
|
||||||
const saleInvoices = await SaleInvoice.query().whereIn('id', paymentReceiveInvoicesIds);
|
const saleInvoices = await SaleInvoice.query().whereIn(
|
||||||
|
'id',
|
||||||
|
paymentReceiveInvoicesIds
|
||||||
|
);
|
||||||
|
|
||||||
return saleInvoices;
|
return saleInvoices;
|
||||||
}
|
}
|
||||||
@@ -432,18 +543,28 @@ export default class PaymentReceiveService {
|
|||||||
*/
|
*/
|
||||||
public async listPaymentReceives(
|
public async listPaymentReceives(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceivesFilter: IPaymentReceivesFilter,
|
paymentReceivesFilter: IPaymentReceivesFilter
|
||||||
): Promise<{ paymentReceives: IPaymentReceive[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
|
): Promise<{
|
||||||
|
paymentReceives: IPaymentReceive[];
|
||||||
|
pagination: IPaginationMeta;
|
||||||
|
filterMeta: IFilterMeta;
|
||||||
|
}> {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, PaymentReceive, paymentReceivesFilter);
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
PaymentReceive,
|
||||||
|
paymentReceivesFilter
|
||||||
|
);
|
||||||
|
|
||||||
const { results, pagination } = await PaymentReceive.query().onBuild((builder) => {
|
const { results, pagination } = await PaymentReceive.query()
|
||||||
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('customer');
|
builder.withGraphFetched('customer');
|
||||||
builder.withGraphFetched('depositAccount');
|
builder.withGraphFetched('depositAccount');
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
}).pagination(
|
})
|
||||||
|
.pagination(
|
||||||
paymentReceivesFilter.page - 1,
|
paymentReceivesFilter.page - 1,
|
||||||
paymentReceivesFilter.pageSize,
|
paymentReceivesFilter.pageSize
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
paymentReceives: results,
|
paymentReceives: results,
|
||||||
@@ -456,7 +577,10 @@ export default class PaymentReceiveService {
|
|||||||
* Retrieve the payment receive details with associated invoices.
|
* Retrieve the payment receive details with associated invoices.
|
||||||
* @param {Integer} paymentReceiveId
|
* @param {Integer} paymentReceiveId
|
||||||
*/
|
*/
|
||||||
async getPaymentReceiveWithInvoices(tenantId: number, paymentReceiveId: number) {
|
async getPaymentReceiveWithInvoices(
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceiveId: number
|
||||||
|
) {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
return PaymentReceive.query()
|
return PaymentReceive.query()
|
||||||
.where('id', paymentReceiveId)
|
.where('id', paymentReceiveId)
|
||||||
@@ -484,7 +608,9 @@ export default class PaymentReceiveService {
|
|||||||
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
|
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
||||||
const formattedDate = moment(paymentReceive.payment_date).format('YYYY-MM-DD');
|
const formattedDate = moment(paymentReceive.payment_date).format(
|
||||||
|
'YYYY-MM-DD'
|
||||||
|
);
|
||||||
const receivableAccount = await this.accountsService.getAccountByType(
|
const receivableAccount = await this.accountsService.getAccountByType(
|
||||||
tenantId,
|
tenantId,
|
||||||
'accounts_receivable'
|
'accounts_receivable'
|
||||||
@@ -540,7 +666,7 @@ export default class PaymentReceiveService {
|
|||||||
public async saveChangeInvoicePaymentAmount(
|
public async saveChangeInvoicePaymentAmount(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
newPaymentReceiveEntries: IPaymentReceiveEntryDTO[],
|
newPaymentReceiveEntries: IPaymentReceiveEntryDTO[],
|
||||||
oldPaymentReceiveEntries?: IPaymentReceiveEntryDTO[],
|
oldPaymentReceiveEntries?: IPaymentReceiveEntryDTO[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const opers: Promise<void>[] = [];
|
const opers: Promise<void>[] = [];
|
||||||
@@ -549,10 +675,12 @@ export default class PaymentReceiveService {
|
|||||||
newPaymentReceiveEntries,
|
newPaymentReceiveEntries,
|
||||||
oldPaymentReceiveEntries,
|
oldPaymentReceiveEntries,
|
||||||
'paymentAmount',
|
'paymentAmount',
|
||||||
'invoiceId',
|
'invoiceId'
|
||||||
);
|
);
|
||||||
diffEntries.forEach((diffEntry: any) => {
|
diffEntries.forEach((diffEntry: any) => {
|
||||||
if (diffEntry.paymentAmount === 0) { return; }
|
if (diffEntry.paymentAmount === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const oper = SaleInvoice.changePaymentAmount(
|
const oper = SaleInvoice.changePaymentAmount(
|
||||||
diffEntry.invoiceId,
|
diffEntry.invoiceId,
|
||||||
|
|||||||
Reference in New Issue
Block a user