diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js
index 6277ace96..61f694480 100644
--- a/client/src/components/DialogsContainer.js
+++ b/client/src/components/DialogsContainer.js
@@ -1,7 +1,6 @@
import React, { lazy } from 'react';
import AccountFormDialog from 'containers/Dialogs/AccountFormDialog';
-
import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog';
import CurrencyFormDialog from 'containers/Dialogs/CurrencyFormDialog';
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummary.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummary.js
new file mode 100644
index 000000000..3e1090854
--- /dev/null
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummary.js
@@ -0,0 +1,127 @@
+import React, { useEffect, useState, useCallback } from 'react';
+import { useIntl } from 'react-intl';
+import { queryCache, useQuery } from 'react-query';
+import moment from 'moment';
+
+import { FinancialStatement } from 'components';
+
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import ARAgingSummaryActionsBar from './ARAgingSummaryActionsBar';
+import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
+import ARAgingSummaryHeader from './ARAgingSummaryHeader';
+import ReceivableAgingSummaryTable from './ARAgingSummaryTable';
+
+import withSettings from 'containers/Settings/withSettings';
+import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+import withARAgingSummaryActions from './withARAgingSummaryActions';
+import withARAgingSummary from './withARAgingSummary';
+
+import { compose } from 'utils';
+import { transfromFilterFormToQuery } from './common';
+
+/**
+ * AR aging summary report.
+ */
+function ReceivableAgingSummarySheet({
+ // #withSettings
+ organizationName,
+
+ // #withDashboardActions
+ changePageTitle,
+ setDashboardBackLink,
+
+ // #withARAgingSummaryActions
+ requestReceivableAgingSummary,
+ refreshARAgingSummary,
+ toggleFilterARAgingSummary,
+
+ // #withARAgingSummary
+ ARAgingSummaryRefresh,
+}) {
+ const { formatMessage } = useIntl();
+ const [query, setQuery] = useState({
+ asDate: moment().endOf('day').format('YYYY-MM-DD'),
+ agingBeforeDays: 30,
+ agingPeriods: 3,
+ });
+
+ useEffect(() => {
+ changePageTitle(formatMessage({ id: 'receivable_aging_summary' }));
+ }, [changePageTitle, formatMessage]);
+
+ useEffect(() => {
+ if (ARAgingSummaryRefresh) {
+ queryCache.invalidateQueries('receivable-aging-summary');
+ refreshARAgingSummary(false);
+ }
+ }, [ARAgingSummaryRefresh, refreshARAgingSummary]);
+
+ useEffect(() => {
+ // Show the back link on dashboard topbar.
+ setDashboardBackLink(true);
+
+ return () => {
+ // Hide the back link on dashboard topbar.
+ setDashboardBackLink(false);
+ };
+ }, [setDashboardBackLink]);
+
+ // Handle fetching receivable aging summary report.
+ const fetchARAgingSummarySheet = useQuery(
+ ['receivable-aging-summary', query],
+ (key, q) =>
+ requestReceivableAgingSummary({
+ ...transfromFilterFormToQuery(q),
+ }),
+ { manual: true },
+ );
+
+ // Handle fetch the data of receivable aging summary sheet.
+ const handleFetchData = useCallback((...args) => {}, []);
+
+ const handleFilterSubmit = useCallback(
+ (filter) => {
+ const _filter = {
+ ...filter,
+ asDate: moment(filter.asDate).format('YYYY-MM-DD'),
+ };
+ setQuery(_filter);
+ refreshARAgingSummary(true);
+ toggleFilterARAgingSummary(false);
+ },
+ [refreshARAgingSummary, toggleFilterARAgingSummary],
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withDashboardActions,
+ withARAgingSummaryActions,
+ withSettings(({ organizationSettings }) => ({
+ organizationName: organizationSettings.name,
+ })),
+ withARAgingSummary(({ ARAgingSummaryRefresh }) => ({
+ ARAgingSummaryRefresh: ARAgingSummaryRefresh,
+ })),
+)(ReceivableAgingSummarySheet);
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js
new file mode 100644
index 000000000..cd826052c
--- /dev/null
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js
@@ -0,0 +1,94 @@
+import React from 'react';
+import {
+ NavbarDivider,
+ NavbarGroup,
+ Classes,
+ Button,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+} from '@blueprintjs/core';
+import { FormattedMessage as T } from 'react-intl';
+import classNames from 'classnames';
+
+import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
+import Icon from 'components/Icon';
+
+import withARAgingSummary from './withARAgingSummary';
+import withARAgingSummaryActions from './withARAgingSummaryActions';
+
+import { compose } from 'utils';
+
+/**
+ * AR Aging summary sheet - Actions bar.
+ */
+function ARAgingSummaryActionsBar({
+ // #withReceivableAging
+ receivableAgingFilter,
+
+ // #withReceivableAgingActions
+ toggleFilterARAgingSummary,
+ refreshARAgingSummary,
+}) {
+ const handleFilterToggleClick = () => {
+ toggleFilterARAgingSummary();
+ };
+ // Handles re-calculate report button.
+ const handleRecalcReport = () => {
+ refreshARAgingSummary(true);
+ };
+
+ return (
+
+
+ }
+ onClick={handleRecalcReport}
+ icon={}
+ />
+
+
+ }
+ text={
+ receivableAgingFilter ? (
+
+ ) : (
+
+ )
+ }
+ onClick={handleFilterToggleClick}
+ active={receivableAgingFilter}
+ />
+
+
+ }
+ icon={}
+ />
+
+
+ }
+ text={}
+ />
+ }
+ text={}
+ />
+
+
+ );
+}
+
+export default compose(
+ withARAgingSummaryActions,
+ withARAgingSummary(({ receivableAgingSummaryFilter }) => ({
+ receivableAgingFilter: receivableAgingSummaryFilter,
+ })),
+)(ARAgingSummaryActionsBar);
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeader.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeader.js
new file mode 100644
index 000000000..ad7649bad
--- /dev/null
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeader.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import { FormattedMessage as T } from 'react-intl';
+import { Formik, Form } from 'formik';
+import * as Yup from 'yup';
+import moment from 'moment';
+import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
+
+import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
+import ARAgingSummaryHeaderGeneral from './ARAgingSummaryHeaderGeneral';
+
+import withARAgingSummary from './withARAgingSummary';
+import withARAgingSummaryActions from './withARAgingSummaryActions';
+
+import { compose } from 'utils';
+
+/**
+ * AR Aging Summary Report - Drawer Header.
+ */
+function ARAgingSummaryHeader({
+ pageFilter,
+ onSubmitFilter,
+ receivableAgingFilter,
+
+ // #withReceivableAgingSummary
+ receivableAgingRefresh,
+
+ // #withReceivableAgingSummaryActions
+ refreshReceivableAgingSummary,
+ toggleFilterARAgingSummary,
+}) {
+ const validationSchema = Yup.object().shape({
+ asDate: Yup.date().required().label('asDate'),
+ agingBeforeDays: Yup.number()
+ .required()
+ .integer()
+ .positive()
+ .label('agingBeforeDays'),
+ agingPeriods: Yup.number()
+ .required()
+ .integer()
+ .positive()
+ .label('agingPeriods'),
+ });
+ // Initial values.
+ const initialValues = {
+ asDate: moment(pageFilter.asDate).toDate(),
+ agingBeforeDays: 30,
+ agingPeriods: 3,
+ };
+ // Handle form submit.
+ const handleSubmit = (values, { setSubmitting }) => {
+ onSubmitFilter(values);
+ setSubmitting(false);
+ };
+ // Handle cancel button click.
+ const handleCancelClick = () => {
+ toggleFilterARAgingSummary();
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withARAgingSummaryActions,
+ withARAgingSummary(
+ ({ receivableAgingSummaryFilter, receivableAgingSummaryRefresh }) => ({
+ receivableAgingFilter: receivableAgingSummaryFilter,
+ receivableAgingRefresh: receivableAgingSummaryRefresh,
+ }),
+ ),
+)(ARAgingSummaryHeader);
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeaderGeneral.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeaderGeneral.js
new file mode 100644
index 000000000..c90dd9554
--- /dev/null
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeaderGeneral.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { FastField } from 'formik';
+import { DateInput } from '@blueprintjs/datetime';
+import { Intent, FormGroup, InputGroup, Position } from '@blueprintjs/core';
+import { FormattedMessage as T } from 'react-intl';
+import { Row, Col, FieldHint } from 'components';
+import { momentFormatter } from 'utils';
+
+/**
+ * AR Aging Summary - Drawer Header - General Fields.
+ */
+export default function ARAgingSummaryHeaderGeneral({}) {
+ return (
+
+
+
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ fill={true}
+ intent={error && Intent.DANGER}
+ >
+ {
+ form.setFieldValue('asDate', selectedDate);
+ }}
+ popoverProps={{ position: Position.BOTTOM, minimal: true }}
+ minimal={true}
+ fill={true}
+ />
+
+ )}
+
+
+
+
+
+
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ className={'form-group--aging-before-days'}
+ intent={error && Intent.DANGER}
+ >
+
+
+ )}
+
+
+
+
+
+
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ className={'form-group--aging-periods'}
+ intent={error && Intent.DANGER}
+ >
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js
new file mode 100644
index 000000000..9e2db87b7
--- /dev/null
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js
@@ -0,0 +1,103 @@
+import React, { useMemo, useCallback } from 'react';
+import { FormattedMessage as T, useIntl } from 'react-intl';
+import DataTable from 'components/DataTable';
+import FinancialSheet from 'components/FinancialSheet';
+
+import withARAgingSummary from './withARAgingSummary';
+
+import { compose } from 'utils';
+
+/**
+ * AR aging summary table sheet.
+ */
+function ReceivableAgingSummaryTable({
+ // #withReceivableAgingSummary
+ receivableAgingRows,
+ receivableAgingLoading,
+ receivableAgingColumns,
+
+ // #ownProps
+ onFetchData,
+ organizationName,
+}) {
+ const { formatMessage } = useIntl();
+
+ const agingColumns = useMemo(() => {
+ return receivableAgingColumns.map((agingColumn) => {
+ return `${agingColumn.before_days} - ${
+ agingColumn.to_days || 'And Over'
+ }`;
+ });
+ }, [receivableAgingColumns]);
+
+ const columns = useMemo(
+ () => [
+ {
+ Header: ,
+ accessor: 'name',
+ className: 'customer_name',
+ sticky: 'left',
+ width: 200,
+ },
+ {
+ Header: ,
+ accessor: 'current',
+ className: 'current',
+ width: 120,
+ },
+ ...agingColumns.map((agingColumn, index) => ({
+ Header: agingColumn,
+ accessor: `aging-${index }`,
+ width: 120,
+ })),
+ {
+ Header: (),
+ id: 'total',
+ accessor: 'total',
+ className: 'total',
+ width: 140,
+ },
+ ],
+ [agingColumns],
+ );
+
+ const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
+
+ const handleFetchData = useCallback((...args) => {
+ // onFetchData && onFetchData(...args);
+ }, []);
+
+ return (
+
+
+
+ );
+}
+
+export default compose(
+ withARAgingSummary(
+ ({
+ receivableAgingSummaryLoading,
+ receivableAgingSummaryColumns,
+ receivableAgingSummaryRows,
+ }) => ({
+ receivableAgingLoading: receivableAgingSummaryLoading,
+ receivableAgingColumns: receivableAgingSummaryColumns,
+ receivableAgingRows: receivableAgingSummaryRows,
+ }),
+ ),
+)(ReceivableAgingSummaryTable);
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/common.js b/client/src/containers/FinancialStatements/ARAgingSummary/common.js
new file mode 100644
index 000000000..96bd39a7e
--- /dev/null
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/common.js
@@ -0,0 +1,5 @@
+import { mapKeys, snakeCase } from 'lodash';
+
+export const transfromFilterFormToQuery = (form) => {
+ return mapKeys(form, (v, k) => snakeCase(k));
+};
\ No newline at end of file
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/withARAgingSummary.js b/client/src/containers/FinancialStatements/ARAgingSummary/withARAgingSummary.js
new file mode 100644
index 000000000..c2a93c923
--- /dev/null
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/withARAgingSummary.js
@@ -0,0 +1,36 @@
+import { connect } from 'react-redux';
+import {
+ getFinancialSheetFactory,
+ getFinancialSheetAccountsFactory,
+ getFinancialSheetColumnsFactory,
+ getFinancialSheetQueryFactory,
+ getFinancialSheetTableRowsFactory,
+} from 'store/financialStatement/financialStatements.selectors';
+
+export default (mapState) => {
+ const mapStateToProps = (state, props) => {
+ const getARAgingSheet = getFinancialSheetFactory('receivableAgingSummary');
+ const getARAgingSheetColumns = getFinancialSheetColumnsFactory(
+ 'receivableAgingSummary',
+ );
+ const getARAgingSheetRows = getFinancialSheetTableRowsFactory(
+ 'receivableAgingSummary',
+ );
+ const {
+ loading,
+ filter,
+ refresh,
+ } = state.financialStatements.receivableAgingSummary;
+
+ const mapped = {
+ receivableAgingSummarySheet: getARAgingSheet(state, props),
+ receivableAgingSummaryColumns: getARAgingSheetColumns(state, props),
+ receivableAgingSummaryRows: getARAgingSheetRows(state, props),
+ receivableAgingSummaryLoading: loading,
+ receivableAgingSummaryFilter: filter,
+ ARAgingSummaryRefresh: refresh,
+ };
+ return mapState ? mapState(mapped, state, props) : mapped;
+ };
+ return connect(mapStateToProps);
+};
diff --git a/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummaryActions.js b/client/src/containers/FinancialStatements/ARAgingSummary/withARAgingSummaryActions.js
similarity index 84%
rename from client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummaryActions.js
rename to client/src/containers/FinancialStatements/ARAgingSummary/withARAgingSummaryActions.js
index 2e0923a12..228e233d6 100644
--- a/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummaryActions.js
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/withARAgingSummaryActions.js
@@ -7,11 +7,11 @@ import {
const mapActionsToProps = (dispatch) => ({
requestReceivableAgingSummary: (query) =>
dispatch(fetchReceivableAgingSummary({ query })),
- toggleFilterReceivableAgingSummary: () =>
+ toggleFilterARAgingSummary: () =>
dispatch({
type: 'RECEIVABLE_AGING_SUMMARY_FILTER_TOGGLE',
}),
- refreshReceivableAgingSummary: (refresh) =>
+ refreshARAgingSummary: (refresh) =>
dispatch(receivableAgingSummaryRefresh(refresh)),
});
diff --git a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js
index 5446c9d54..0e8eeffa3 100644
--- a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js
+++ b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js
@@ -71,7 +71,7 @@ function BalanceSheet({
// Hide the back link on dashboard topbar.
setDashboardBackLink(false);
};
- });
+ }, [setDashboardBackLink]);
// Handle re-fetch balance sheet after filter change.
const handleFilterSubmit = useCallback(
diff --git a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummary.js b/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummary.js
deleted file mode 100644
index 4f323a047..000000000
--- a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummary.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React, { useEffect, useState, useCallback } from 'react';
-import { useIntl } from 'react-intl';
-import { useQuery } from 'react-query';
-
-import moment from 'moment';
-import { FinancialStatement } from 'components';
-
-import DashboardInsider from 'components/Dashboard/DashboardInsider';
-import ReceivableAgingSummaryActionsBar from './ReceivableAgingSummaryActionsBar';
-import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
-import ReceivableAgingSummaryHeader from './ReceivableAgingSummaryHeader'
-import ReceivableAgingSummaryTable from './ReceivableAgingSummaryTable';
-
-import withDashboardActions from 'containers/Dashboard/withDashboardActions';
-import withReceivableAgingSummaryActions from './withReceivableAgingSummaryActions';
-import { compose } from 'utils';
-
-
-function ReceivableAgingSummarySheet({
- // #withDashboardActions
- changePageTitle,
-
- // #withReceivableAgingSummaryActions
- requestReceivableAgingSummary,
-}) {
- const { formatMessage } = useIntl();
- const [query, setQuery] = useState({
- as_date: moment().endOf('day').format('YYYY-MM-DD'),
- aging_before_days: 30,
- aging_periods: 3,
- });
- const [refresh, setRefresh] = useState(true);
-
- useEffect(() => {
- changePageTitle(formatMessage({ id: 'receivable_aging_summary' }));
- }, []);
-
- const fetchSheet = useQuery(
- ['receivable-aging-summary', query],
- (key, q) => requestReceivableAgingSummary(q),
- { manual: true });
-
- // Handle fetch the data of receivable aging summary sheet.
- const handleFetchData = useCallback((...args) => {
- setRefresh(true);
- }, []);
-
- const handleFilterSubmit = useCallback((filter) => {
- const _filter = {
- ...filter,
- as_date: moment(filter.as_date).format('YYYY-MM-DD'),
- };
- setQuery(_filter);
- setRefresh(true);
- }, []);
-
- useEffect(() => {
- if (refresh) {
- fetchSheet.refetch({ force: true });
- setRefresh(false);
- }
- }, [fetchSheet, refresh]);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default compose(
- withDashboardActions,
- withReceivableAgingSummaryActions
-)(ReceivableAgingSummarySheet);
\ No newline at end of file
diff --git a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryActionsBar.js b/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryActionsBar.js
deleted file mode 100644
index ae7a0b4cc..000000000
--- a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryActionsBar.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import React from 'react';
-import {
- NavbarDivider,
- NavbarGroup,
- Classes,
- Button,
- Popover,
- PopoverInteractionKind,
- Position,
-} from "@blueprintjs/core";
-import { FormattedMessage as T } from 'react-intl';
-import classNames from 'classnames';
-
-import DashboardActionsBar from "components/Dashboard/DashboardActionsBar";
-import Icon from 'components/Icon';
-import { If } from 'components';
-
-import withReceivableAging from './withReceivableAgingSummary';
-import withReceivableAgingActions from './withReceivableAgingSummaryActions';
-
-import { compose } from 'utils';
-
-
-function ReceivableAgingSummaryActionsBar({
- // #withReceivableAging
- receivableAgingFilter,
-
- // #withReceivableAgingActions
- toggleFilterReceivableAgingSummary,
- refreshReceivableAgingSummary,
-}) {
- const handleFilterToggleClick = () => {
- toggleFilterReceivableAgingSummary();
- };
-
- const handleRecalcReport = () => {
- refreshReceivableAgingSummary(true);
- };
-
- return (
-
-
- }
- text={}
- />
-
-
- }
- icon={}
- onClick={handleRecalcReport}
- />
-
- }
- onClick={handleFilterToggleClick}
- icon={}
- />
-
-
-
- }
- onClick={handleFilterToggleClick}
- icon={}
- />
-
-
-
- }
- icon={}
- />
-
-
-
-
- }
- text={}
- />
- }
- text={}
- />
-
-
- )
-}
-
-export default compose(
- withReceivableAgingActions,
- withReceivableAging(({ receivableAgingSummaryFilter }) => ({
- receivableAgingFilter: receivableAgingSummaryFilter,
- })),
-)(ReceivableAgingSummaryActionsBar)
\ No newline at end of file
diff --git a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryHeader.js b/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryHeader.js
deleted file mode 100644
index 59564d68f..000000000
--- a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryHeader.js
+++ /dev/null
@@ -1,148 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useIntl, FormattedMessage as T } from 'react-intl';
-import { useFormik } from 'formik';
-import { Row, Col } from 'react-grid-system';
-import * as Yup from 'yup';
-import {
- Intent,
- FormGroup,
- InputGroup,
- Position,
- Button,
-} from '@blueprintjs/core';
-import { DateInput } from '@blueprintjs/datetime';
-import moment from 'moment';
-import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
-import { ErrorMessage, FieldHint, FieldRequiredHint } from 'components';
-import { momentFormatter } from 'utils';
-
-import withReceivableAging from './withReceivableAgingSummary';
-import withReceivableAgingActions from './withReceivableAgingSummaryActions';
-
-import { compose } from 'utils';
-
-
-function ReceivableAgingSummaryHeader({
- pageFilter,
- onSubmitFilter,
- receivableAgingFilter,
-
- // #withReceivableAgingSummary
- receivableAgingRefresh,
-
- // #withReceivableAgingSummaryActions
- refreshReceivableAgingSummary
-}) {
- const { formatMessage } = useIntl();
-
- const {
- values,
- errors,
- touched,
- setFieldValue,
- getFieldProps,
- submitForm,
- isSubmitting,
- } = useFormik({
- enableReinitialize: true,
- initialValues: {
- as_date: moment(pageFilter.as_date).toDate(),
- aging_before_days: 30,
- aging_periods: 3,
- },
- validationSchema: Yup.object().shape({
- as_date: Yup.date().required().label('as_date'),
- aging_before_days: Yup.number()
- .required()
- .integer()
- .positive()
- .label('aging_before_days'),
- aging_periods: Yup.number()
- .required()
- .integer()
- .positive()
- .label('aging_periods'),
- }),
- onSubmit: (values, { setSubmitting }) => {
- onSubmitFilter(values);
- setSubmitting(false);
- },
- });
-
- const handleDateChange = useCallback(
- (name) => (date) => {
- setFieldValue(name, date);
- },
- [],
- );
-
- // Handle submit filter submit button.
- useEffect(() => {
- if (receivableAgingRefresh) {
- submitForm();
- refreshReceivableAgingSummary(false);
- }
- }, [submitForm, receivableAgingRefresh]);
-
- return (
-
-
-
- }
- fill={true}
- intent={errors.as_date && Intent.DANGER}
- >
-
-
-
-
-
- }
- labelInfo={}
- className={'form-group--aging-before-days'}
- intent={errors.aging_before_days && Intent.DANGER}
- >
-
-
-
-
-
- }
- labelInfo={}
- className={'form-group--aging-periods'}
- intent={errors.aging_before_days && Intent.DANGER}
- >
-
-
-
-
-
- );
-}
-
-export default compose(
- withReceivableAgingActions,
- withReceivableAging(({ receivableAgingSummaryFilter, receivableAgingSummaryRefresh }) => ({
- receivableAgingFilter: receivableAgingSummaryFilter,
- receivableAgingRefresh: receivableAgingSummaryRefresh
- })),
-)(ReceivableAgingSummaryHeader);
diff --git a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryTable.js b/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryTable.js
deleted file mode 100644
index c10bd09d2..000000000
--- a/client/src/containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummaryTable.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import React, { useMemo, useCallback } from 'react';
-import { FormattedMessage as T, useIntl } from 'react-intl';
-import DataTable from "components/DataTable";
-import FinancialSheet from 'components/FinancialSheet';
-import Money from 'components/Money';
-
-import withSettings from 'containers/Settings/withSettings';
-
-import { compose } from 'utils';
-import withReceivableAgingSummary from './withReceivableAgingSummary';
-import withReceivableAgingSummaryTable from './withReceivableAgingSummaryTable';
-
-function ReceivableAgingSummaryTable({
- // #withPreferences
- organizationSettings,
-
- // #withReceivableAgingSummary
- receivableAgingRows,
- receivableAgingLoading,
- receivableAgingColumns,
-
- // #ownProps
- onFetchData,
-}) {
- const { formatMessage } = useIntl();
-
- const agingColumns = useMemo(() => {
- return receivableAgingColumns.map((agingColumn) => {
- return `${agingColumn.before_days} - ${agingColumn.to_days || 'And Over'}`;
- });
- }, [receivableAgingColumns]);
-
- const columns = useMemo(() => ([
- {
- Header: (),
- accessor: 'customer_name',
- className: 'customer_name',
- sticky: 'left',
- },
- ...agingColumns.map((agingColumn, index) => ({
- Header: agingColumn,
- accessor: (row) => {
- const amount = row[`aging-${index}`];
- if (row.rowType === 'total') {
- return
- }
- return amount > 0 ? amount : '';
- },
- })),
- {
- Header: (),
- id: 'total',
- accessor: (row) => {
- return ;
- },
- className: 'total',
- },
- ]), [agingColumns]);
-
- const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
-
- const handleFetchData = useCallback((...args) => {
- onFetchData && onFetchData(...args);
- }, []);
-
- return (
-
-
-
-
- );
-}
-
-export default compose(
- withSettings,
- withReceivableAgingSummaryTable,
- withReceivableAgingSummary(({
- receivableAgingSummaryLoading,
- receivableAgingSummaryColumns,
- receivableAgingSummaryRows,
- }) => ({
- receivableAgingLoading: receivableAgingSummaryLoading,
- receivableAgingColumns: receivableAgingSummaryColumns,
- receivableAgingRows: receivableAgingSummaryRows,
- })),
-)(ReceivableAgingSummaryTable);
\ No newline at end of file
diff --git a/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummary.js b/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummary.js
deleted file mode 100644
index 95320b756..000000000
--- a/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummary.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { connect } from 'react-redux';
-import {
- getFinancialSheet,
- getFinancialSheetColumns,
- getFinancialSheetTableRows,
-} from 'store/financialStatement/financialStatements.selectors';
-
-export default (mapState) => {
- const mapStateToProps = (state, props) => {
- const { receivableAgingSummaryIndex } = props;
-
- const mapped = {
- receivableAgingSummarySheet: getFinancialSheet(
- state.financialStatements.receivableAgingSummary.sheets,
- receivableAgingSummaryIndex,
- ),
- receivableAgingSummaryColumns: getFinancialSheetColumns(
- state.financialStatements.receivableAgingSummary.sheets,
- receivableAgingSummaryIndex,
- ),
- receivableAgingSummaryRows: getFinancialSheetTableRows(
- state.financialStatements.receivableAgingSummary.sheets,
- receivableAgingSummaryIndex,
- ),
- receivableAgingSummaryLoading:
- state.financialStatements.receivableAgingSummary.loading,
- receivableAgingSummaryFilter:
- state.financialStatements.receivableAgingSummary.filter,
- receivableAgingSummaryRefresh:
- state.financialStatements.receivableAgingSummary.refresh,
- };
- return mapState ? mapState(mapped, state, props) : mapped;
- };
- return connect(mapStateToProps);
-}
\ No newline at end of file
diff --git a/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummaryTable.js b/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummaryTable.js
deleted file mode 100644
index 7f50ab420..000000000
--- a/client/src/containers/FinancialStatements/ReceivableAgingSummary/withReceivableAgingSummaryTable.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { getFinancialSheetIndexByQuery } from 'store/financialStatement/financialStatements.selectors';
-import { connect } from 'react-redux';
-
-const mapStateToProps = (state, props) => {
- const { receivableAgingSummaryQuery } = props;
-
- return {
- receivableAgingSummaryIndex: getFinancialSheetIndexByQuery(
- state.financialStatements.receivableAgingSummary.sheets,
- receivableAgingSummaryQuery,
- ),
- };
-}
-export default connect(mapStateToProps);
\ No newline at end of file
diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js
index 93aefba99..e00248503 100644
--- a/client/src/lang/en/index.js
+++ b/client/src/lang/en/index.js
@@ -968,4 +968,5 @@ export default {
you_could_not_delete_item_that_has_associated_inventory_adjustments_transacions:
'You could not delete item that has associated inventory adjustments transactions',
format: 'Format',
+ current: 'Current',
};
diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js
index 3873e51c2..fc245fae9 100644
--- a/client/src/routes/dashboard.js
+++ b/client/src/routes/dashboard.js
@@ -135,16 +135,16 @@ export default [
}),
breadcrumb: 'Profit Loss Sheet',
},
- // {
- // path: '/financial-reports/receivable-aging-summary',
- // component: LazyLoader({
- // loader: () =>
- // import(
- // 'containers/FinancialStatements/ReceivableAgingSummary/ReceivableAgingSummary'
- // ),
- // }),
- // breadcrumb: 'Receivable Aging Summary',
- // },
+ {
+ path: '/financial-reports/receivable-aging-summary',
+ component: LazyLoader({
+ loader: () =>
+ import(
+ 'containers/FinancialStatements/ARAgingSummary/ARAgingSummary'
+ ),
+ }),
+ breadcrumb: 'Receivable Aging Summary',
+ },
{
path: `/financial-reports/journal-sheet`,
component: LazyLoader({
@@ -236,7 +236,7 @@ export default [
breadcrumb: 'Vendors',
},
- //Estimates
+ // Estimates
{
path: `/estimates/:id/edit`,
component: LazyLoader({
diff --git a/client/src/store/financialStatement/financialStatements.actions.js b/client/src/store/financialStatement/financialStatements.actions.js
index 31ef7f47d..34bd31a9f 100644
--- a/client/src/store/financialStatement/financialStatements.actions.js
+++ b/client/src/store/financialStatement/financialStatements.actions.js
@@ -157,7 +157,8 @@ export const fetchReceivableAgingSummary = ({ query }) => {
dispatch({
type: t.RECEIVABLE_AGING_SUMMARY_SET,
payload: {
- aging: response.data.aging,
+ customers: response.data.data.customers,
+ total: response.data.data.total,
columns: response.data.columns,
query,
},
@@ -172,7 +173,7 @@ export const fetchReceivableAgingSummary = ({ query }) => {
})
.catch((error) => {
reject(error);
- })
+ });
});
}
diff --git a/client/src/store/financialStatement/financialStatements.mappers.js b/client/src/store/financialStatement/financialStatements.mappers.js
index b980c5875..a5f78c9e6 100644
--- a/client/src/store/financialStatement/financialStatements.mappers.js
+++ b/client/src/store/financialStatement/financialStatements.mappers.js
@@ -78,6 +78,51 @@ export const generalLedgerToTableRows = (accounts) => {
}, []);
};
+export const ARAgingSummaryTableRowsMapper = (sheet, total) => {
+ const rows = [];
+
+ const mapAging = (agingPeriods) => {
+ return agingPeriods.reduce((acc, aging, index) => {
+ acc[`aging-${index}`] = aging.total.formatted_amount;
+ return acc;
+ }, {});
+ };
+ sheet.customers.forEach((customer) => {
+ const agingRow = mapAging(customer.aging);
+
+ rows.push({
+ rowType: 'customer',
+ name: customer.customer_name,
+ ...agingRow,
+ current: customer.current.formatted_amount,
+ total: customer.total.formatted_amount,
+ });
+ });
+ if (rows.length <= 0) {
+ return [];
+ }
+ return [
+ ...rows,
+ {
+ name: 'TOTAL',
+ rowType: 'total',
+ current: sheet.total.current.formatted_amount,
+ ...mapAging(sheet.total.aging),
+ total: sheet.total.total.formatted_amount,
+ }
+ ];
+};
+
+export const mapTrialBalanceSheetToRows = (sheet) => {
+ return [
+ {
+ name: 'Total',
+ ...sheet.total,
+ },
+ ...sheet.accounts,
+ ];
+};
+
export const profitLossToTableRowsMapper = (profitLoss) => {
return [
diff --git a/client/src/store/financialStatement/financialStatements.reducer.js b/client/src/store/financialStatement/financialStatements.reducer.js
index 4e1a59502..c88cff6e9 100644
--- a/client/src/store/financialStatement/financialStatements.reducer.js
+++ b/client/src/store/financialStatement/financialStatements.reducer.js
@@ -1,11 +1,12 @@
import { createReducer } from '@reduxjs/toolkit';
import t from 'store/types';
-import { omit } from 'lodash';
import {
mapBalanceSheetToTableRows,
journalToTableRowsMapper,
generalLedgerToTableRows,
- profitLossToTableRowsMapper
+ profitLossToTableRowsMapper,
+ ARAgingSummaryTableRowsMapper,
+ mapTrialBalanceSheetToRows,
} from './financialStatements.mappers';
const initialState = {
@@ -41,7 +42,7 @@ const initialState = {
filter: true,
},
receivableAgingSummary: {
- sheets: [],
+ sheet: {},
loading: false,
tableRows: [],
filter: true,
@@ -49,38 +50,6 @@ const initialState = {
},
};
-
-
-
-const mapContactAgingSummary = (sheet) => {
- const rows = [];
-
- const mapAging = (agingPeriods) => {
- return agingPeriods.reduce((acc, aging, index) => {
- acc[`aging-${index}`] = aging.formatted_total;
- return acc;
- }, {});
- };
- sheet.customers.forEach((customer) => {
- const agingRow = mapAging(customer.aging);
-
- rows.push({
- rowType: 'customer',
- customer_name: customer.customer_name,
- ...agingRow,
- total: customer.total,
- });
- });
-
- rows.push({
- rowType: 'total',
- customer_name: 'Total',
- ...mapAging(sheet.total),
- total: 0,
- });
- return rows;
-};
-
const financialStatementFilterToggle = (financialName, statePath) => {
return {
[`${financialName}_FILTER_TOGGLE`]: (state, action) => {
@@ -112,8 +81,10 @@ export default createReducer(initialState, {
...financialStatementFilterToggle('BALANCE_SHEET', 'balanceSheet'),
[t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => {
+ debugger;
const trailBalanceSheet = {
- data: action.data.data,
+ sheet: action.data.data,
+ tableRows: mapTrialBalanceSheetToRows(action.data.data),
query: action.data.query,
};
state.trialBalance.sheet = trailBalanceSheet;
@@ -154,7 +125,7 @@ export default createReducer(initialState, {
accounts: action.data.data,
tableRows: generalLedgerToTableRows(action.data.data),
};
- state.generalLedger.sheet = generalLedger;
+ state.generalLedger.sheet = generalLedger;
},
[t.GENERAL_LEDGER_SHEET_LOADING]: (state, action) => {
@@ -173,7 +144,7 @@ export default createReducer(initialState, {
columns: action.columns,
tableRows: profitLossToTableRowsMapper(action.profitLoss),
};
- state.profitLoss.sheet = profitLossSheet;
+ state.profitLoss.sheet = profitLossSheet;
},
[t.PROFIT_LOSS_SHEET_LOADING]: (state, action) => {
@@ -187,34 +158,28 @@ export default createReducer(initialState, {
...financialStatementFilterToggle('PROFIT_LOSS', 'profitLoss'),
- // [t.RECEIVABLE_AGING_SUMMARY_LOADING]: (state, action) => {
- // const { loading } = action.payload;
- // state.receivableAgingSummary.loading = loading;
- // },
+
- // [t.RECEIVABLE_AGING_SUMMARY_SET]: (state, action) => {
- // const { aging, columns, query } = action.payload;
- // const index = getFinancialSheetIndexByQuery(
- // state.receivableAgingSummary.sheets,
- // query,
- // );
+ [t.RECEIVABLE_AGING_SUMMARY_SET]: (state, action) => {
+ const { customers, total, columns, query } = action.payload;
- // const receivableSheet = {
- // query,
- // columns,
- // aging,
- // tableRows: mapContactAgingSummary(aging),
- // };
- // if (index !== -1) {
- // state.receivableAgingSummary[index] = receivableSheet;
- // } else {
- // state.receivableAgingSummary.sheets.push(receivableSheet);
- // }
- // },
- // [t.RECEIVABLE_AGING_SUMMARY_REFRESH]: (state, action) => {
- // const { refresh } = action.payload;
- // state.receivableAgingSummary.refresh = !!refresh;
- // },
+ const receivableSheet = {
+ query,
+ columns,
+ customers,
+ total,
+ tableRows: ARAgingSummaryTableRowsMapper({ customers, columns, total }),
+ };
+ state.receivableAgingSummary.sheet = receivableSheet;
+ },
+ [t.RECEIVABLE_AGING_SUMMARY_REFRESH]: (state, action) => {
+ const { refresh } = action.payload;
+ state.receivableAgingSummary.refresh = !!refresh;
+ },
+ [t.RECEIVABLE_AGING_SUMMARY_LOADING]: (state, action) => {
+ const { loading } = action.payload;
+ state.receivableAgingSummary.loading = loading;
+ },
...financialStatementFilterToggle(
'RECEIVABLE_AGING_SUMMARY',
'receivableAgingSummary',
diff --git a/client/src/style/pages/financial-statements.scss b/client/src/style/pages/financial-statements.scss
index 2c791ef75..ad8661221 100644
--- a/client/src/style/pages/financial-statements.scss
+++ b/client/src/style/pages/financial-statements.scss
@@ -145,7 +145,7 @@
}
.financial-sheet{
- border: 2px solid #EBEBEB;
+ border: 2px solid #f0f0f0;
border-radius: 10px;
min-width: 640px;
width: auto;
@@ -366,15 +366,26 @@
&--receivable-aging-summary{
.financial-sheet__table{
- .tbody{
- .row-type--total{
- font-weight: 600;
- .td{
- background-color: #fafbff;
- border-bottom-color: #666;
- border-bottom-style: dotted;
- }
+ .bigcapital-datatable{
+ .tbody,
+ .thead{
+ .tr .td.customer_name ~ .td,
+ .tr .th.customer_name ~ .th{
+ justify-content: flex-end;
+ }
+ }
+ .tbody{
+
+ .row-type--total{
+ font-weight: 600;
+
+ .td{
+ border-top-color: #BBB;
+ border-top-style: solid;
+ border-bottom: 3px double #666;
+ }
+ }
}
}
}
diff --git a/server/package.json b/server/package.json
index da28bd13f..4fba092e0 100644
--- a/server/package.json
+++ b/server/package.json
@@ -16,6 +16,7 @@
"dependencies": {
"@hapi/boom": "^7.4.3",
"@types/i18n": "^0.8.7",
+ "accounting": "^0.4.1",
"agenda": "^3.1.0",
"agendash": "^1.0.0",
"app-root-path": "^3.0.0",
diff --git a/server/src/api/controllers/FinancialStatements/APAgingSummary.ts b/server/src/api/controllers/FinancialStatements/APAgingSummary.ts
index 76555235e..5f18f2555 100644
--- a/server/src/api/controllers/FinancialStatements/APAgingSummary.ts
+++ b/server/src/api/controllers/FinancialStatements/APAgingSummary.ts
@@ -1,11 +1,11 @@
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import { Inject } from 'typedi';
-import BaseController from 'api/controllers/BaseController';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
import APAgingSummaryReportService from 'services/FinancialStatements/AgingSummary/APAgingSummaryService';
+import BaseFinancialReportController from './BaseFinancialReportController';
-export default class APAgingSummaryReportController extends BaseController {
+export default class APAgingSummaryReportController extends BaseFinancialReportController {
@Inject()
APAgingSummaryService: APAgingSummaryReportService;
@@ -28,11 +28,10 @@ export default class APAgingSummaryReportController extends BaseController {
*/
get validationSchema() {
return [
+ ...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(),
query('aging_days_before').optional().isNumeric().toInt(),
query('aging_periods').optional().isNumeric().toInt(),
- query('number_format.no_cents').optional().isBoolean().toBoolean(),
- query('number_format.1000_divide').optional().isBoolean().toBoolean(),
query('vendors_ids').optional().isArray({ min: 1 }),
query('vendors_ids.*').isInt({ min: 1 }).toInt(),
query('none_zero').default(true).isBoolean().toBoolean(),
diff --git a/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts b/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts
index 1feb7741a..979eea0c5 100644
--- a/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts
+++ b/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts
@@ -1,13 +1,11 @@
import { Service, Inject } from 'typedi';
import { Router, Request, Response } from 'express';
-import { castArray } from 'lodash';
-import { query, oneOf } from 'express-validator';
-import { IARAgingSummaryQuery } from 'interfaces';
-import BaseController from '../BaseController';
+import { query } from 'express-validator';
import ARAgingSummaryService from 'services/FinancialStatements/AgingSummary/ARAgingSummaryService';
+import BaseFinancialReportController from './BaseFinancialReportController';
@Service()
-export default class ARAgingSummaryReportController extends BaseController {
+export default class ARAgingSummaryReportController extends BaseFinancialReportController {
@Inject()
ARAgingSummaryService: ARAgingSummaryService;
@@ -31,11 +29,11 @@ export default class ARAgingSummaryReportController extends BaseController {
*/
get validationSchema() {
return [
+ ...this.sheetNumberFormatValidationSchema,
+
query('as_date').optional().isISO8601(),
query('aging_days_before').optional().isInt({ max: 500 }).toInt(),
query('aging_periods').optional().isInt({ max: 12 }).toInt(),
- query('number_format.no_cents').optional().isBoolean().toBoolean(),
- query('number_format.1000_divide').optional().isBoolean().toBoolean(),
query('customers_ids').optional().isArray({ min: 1 }),
query('customers_ids.*').isInt({ min: 1 }).toInt(),
query('none_zero').default(true).isBoolean().toBoolean(),
diff --git a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts
index fe1562e27..46163c04b 100644
--- a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts
+++ b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts
@@ -3,11 +3,11 @@ import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
import { castArray } from 'lodash';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
-import BaseController from '../BaseController';
import BalanceSheetStatementService from 'services/FinancialStatements/BalanceSheet/BalanceSheetService';
+import BaseFinancialReportController from './BaseFinancialReportController';
@Service()
-export default class BalanceSheetStatementController extends BaseController {
+export default class BalanceSheetStatementController extends BaseFinancialReportController {
@Inject()
balanceSheetService: BalanceSheetStatementService;
@@ -32,6 +32,8 @@ export default class BalanceSheetStatementController extends BaseController {
*/
get balanceSheetValidationSchema(): ValidationChain[] {
return [
+ ...this.sheetNumberFormatValidationSchema,
+
query('accounting_method').optional().isIn(['cash', 'accural']),
query('from_date').optional(),
query('to_date').optional(),
@@ -39,8 +41,6 @@ export default class BalanceSheetStatementController extends BaseController {
query('display_columns_by')
.optional({ nullable: true, checkFalsy: true })
.isIn(['year', 'month', 'week', 'day', 'quarter']),
- query('number_format.no_cents').optional().isBoolean().toBoolean(),
- query('number_format.divide_1000').optional().isBoolean().toBoolean(),
query('account_ids').isArray().optional(),
query('account_ids.*').isNumeric().toInt(),
query('none_zero').optional().isBoolean().toBoolean(),
diff --git a/server/src/api/controllers/FinancialStatements/BaseFinancialReportController.ts b/server/src/api/controllers/FinancialStatements/BaseFinancialReportController.ts
new file mode 100644
index 000000000..7fccd76f1
--- /dev/null
+++ b/server/src/api/controllers/FinancialStatements/BaseFinancialReportController.ts
@@ -0,0 +1,26 @@
+import { query } from 'express-validator';
+import BaseController from "../BaseController";
+
+export default class BaseFinancialReportController extends BaseController {
+
+
+ get sheetNumberFormatValidationSchema() {
+ return [
+ query('number_format.precision')
+ .optional()
+ .isInt({ min: 0, max: 5 })
+ .toInt(),
+ query('number_format.divide_on_1000').optional().isBoolean().toBoolean(),
+ query('number_format.show_zero').optional().isBoolean().toBoolean(),
+ query('number_format.format_money')
+ .optional()
+ .isIn(['total', 'always', 'none'])
+ .trim(),
+ query('number_format.negative_format')
+ .optional()
+ .isIn(['parentheses', 'mines'])
+ .trim()
+ .escape(),
+ ];
+ }
+}
\ No newline at end of file
diff --git a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts
index b0357373d..071921a43 100644
--- a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts
+++ b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts
@@ -1,12 +1,12 @@
import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
-import asyncMiddleware from 'api/middleware/asyncMiddleware';
-import BaseController from '../BaseController';
import { Inject, Service } from 'typedi';
+import asyncMiddleware from 'api/middleware/asyncMiddleware';
import GeneralLedgerService from 'services/FinancialStatements/GeneralLedger/GeneralLedgerService';
+import BaseFinancialReportController from './BaseFinancialReportController';
@Service()
-export default class GeneralLedgerReportController extends BaseController {
+export default class GeneralLedgerReportController extends BaseFinancialReportController {
@Inject()
generalLedgetService: GeneralLedgerService;
diff --git a/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/server/src/api/controllers/FinancialStatements/JournalSheet.ts
index 6a4214da4..f832fedf6 100644
--- a/server/src/api/controllers/FinancialStatements/JournalSheet.ts
+++ b/server/src/api/controllers/FinancialStatements/JournalSheet.ts
@@ -2,11 +2,11 @@ import { Inject, Service } from 'typedi';
import { Request, Response, Router, NextFunction } from 'express';
import { castArray } from 'lodash';
import { query, oneOf } from 'express-validator';
+import BaseFinancialReportController from './BaseFinancialReportController';
import JournalSheetService from 'services/FinancialStatements/JournalSheet/JournalSheetService';
-import BaseController from '../BaseController';
@Service()
-export default class JournalSheetController extends BaseController {
+export default class JournalSheetController extends BaseFinancialReportController {
@Inject()
journalService: JournalSheetService;
diff --git a/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts
index be6720666..e5b5125fb 100644
--- a/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts
+++ b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts
@@ -1,11 +1,11 @@
import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
-import BaseController from '../BaseController';
import ProfitLossSheetService from 'services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService';
+import BaseFinancialReportController from './BaseFinancialReportController';
@Service()
-export default class ProfitLossSheetController extends BaseController {
+export default class ProfitLossSheetController extends BaseFinancialReportController {
@Inject()
profitLossSheetService: ProfitLossSheetService;
@@ -29,11 +29,10 @@ export default class ProfitLossSheetController extends BaseController {
*/
get validationSchema(): ValidationChain[] {
return [
+ ...this.sheetNumberFormatValidationSchema,
query('basis').optional(),
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
- query('number_format.no_cents').optional().isBoolean(),
- query('number_format.divide_1000').optional().isBoolean(),
query('basis').optional(),
query('none_zero').optional().isBoolean().toBoolean(),
query('none_transactions').optional().isBoolean().toBoolean(),
diff --git a/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts b/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts
index a9baf0997..9ea0f4113 100644
--- a/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts
+++ b/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts
@@ -1,13 +1,13 @@
import { Inject, Service } from 'typedi';
-import { Request, Response, Router, NextFunction } from 'express';
+import { Request, Response, Router, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
-import asyncMiddleware from 'api/middleware/asyncMiddleware';
-import BaseController from '../BaseController';
-import TrialBalanceSheetService from 'services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService';
import { castArray } from 'lodash';
+import asyncMiddleware from 'api/middleware/asyncMiddleware';
+import TrialBalanceSheetService from 'services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService';
+import BaseFinancialReportController from './BaseFinancialReportController';
@Service()
-export default class TrialBalanceSheetController extends BaseController {
+export default class TrialBalanceSheetController extends BaseFinancialReportController {
@Inject()
trialBalanceSheetService: TrialBalanceSheetService;
@@ -17,7 +17,8 @@ export default class TrialBalanceSheetController extends BaseController {
router() {
const router = Router();
- router.get('/',
+ router.get(
+ '/',
this.trialBalanceSheetValidationSchema,
this.validationResult,
asyncMiddleware(this.trialBalanceSheet.bind(this))
@@ -31,11 +32,10 @@ export default class TrialBalanceSheetController extends BaseController {
*/
get trialBalanceSheetValidationSchema(): ValidationChain[] {
return [
+ ...this.sheetNumberFormatValidationSchema,
query('basis').optional(),
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
- query('number_format.no_cents').optional().isBoolean().toBoolean(),
- query('number_format.1000_divide').optional().isBoolean().toBoolean(),
query('account_ids').isArray().optional(),
query('account_ids.*').isNumeric().toInt(),
query('basis').optional(),
@@ -46,7 +46,11 @@ export default class TrialBalanceSheetController extends BaseController {
/**
* Retrieve the trial balance sheet.
*/
- public async trialBalanceSheet(req: Request, res: Response, next: NextFunction) {
+ public async trialBalanceSheet(
+ req: Request,
+ res: Response,
+ next: NextFunction
+ ) {
const { tenantId, settings } = req;
let filter = this.matchedQueryData(req);
@@ -54,21 +58,32 @@ export default class TrialBalanceSheetController extends BaseController {
...filter,
accountsIds: castArray(filter.accountsIds),
};
- const organizationName = settings.get({ group: 'organization', key: 'name' });
- const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
+ const organizationName = settings.get({
+ group: 'organization',
+ key: 'name',
+ });
+ const baseCurrency = settings.get({
+ group: 'organization',
+ key: 'base_currency',
+ });
try {
- const { data, query } = await this.trialBalanceSheetService
- .trialBalanceSheet(tenantId, filter);
-
+ const {
+ data,
+ query,
+ } = await this.trialBalanceSheetService.trialBalanceSheet(
+ tenantId,
+ filter
+ );
+
return res.status(200).send({
organization_name: organizationName,
base_currency: baseCurrency,
data: this.transfromToResponse(data),
- query: this.transfromToResponse(query)
+ query: this.transfromToResponse(query),
});
} catch (error) {
next(error);
}
}
-}
\ No newline at end of file
+}
diff --git a/server/src/interfaces/APAgingSummaryReport.ts b/server/src/interfaces/APAgingSummaryReport.ts
index 490403338..dd62b4b1f 100644
--- a/server/src/interfaces/APAgingSummaryReport.ts
+++ b/server/src/interfaces/APAgingSummaryReport.ts
@@ -1,30 +1,32 @@
import {
IAgingPeriod,
- IAgingPeriodTotal
+ IAgingPeriodTotal,
+ IAgingAmount
} from './AgingReport';
+import {
+ INumberFormatQuery
+} from './FinancialStatements';
export interface IAPAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
- numberFormat: {
- noCents: boolean;
- divideOn1000: boolean;
- };
+ numberFormat: INumberFormatQuery;
vendorsIds: number[];
noneZero: boolean;
}
export interface IAPAgingSummaryVendor {
vendorName: string,
- current: IAgingPeriodTotal,
- aging: (IAgingPeriod & IAgingPeriodTotal)[],
- total: IAgingPeriodTotal,
+ current: IAgingAmount,
+ aging: IAgingPeriodTotal[],
+ total: IAgingAmount,
};
export interface IAPAgingSummaryTotal {
- current: IAgingPeriodTotal,
- aging: (IAgingPeriodTotal & IAgingPeriod)[],
+ current: IAgingAmount,
+ aging: IAgingPeriodTotal[],
+ total: IAgingAmount,
};
export interface IAPAgingSummaryData {
diff --git a/server/src/interfaces/ARAgingSummaryReport.ts b/server/src/interfaces/ARAgingSummaryReport.ts
index 10c09063d..20e3df42d 100644
--- a/server/src/interfaces/ARAgingSummaryReport.ts
+++ b/server/src/interfaces/ARAgingSummaryReport.ts
@@ -1,35 +1,31 @@
-import {
- IAgingPeriod,
- IAgingPeriodTotal
-} from './AgingReport';
+import { IAgingPeriod, IAgingPeriodTotal, IAgingAmount } from './AgingReport';
+import { INumberFormatQuery } from './FinancialStatements';
export interface IARAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
- numberFormat: {
- noCents: boolean;
- divideOn1000: boolean;
- };
+ numberFormat: INumberFormatQuery;
customersIds: number[];
noneZero: boolean;
}
export interface IARAgingSummaryCustomer {
customerName: string;
- current: IAgingPeriodTotal,
- aging: (IAgingPeriodTotal & IAgingPeriod)[];
- total: IAgingPeriodTotal;
+ current: IAgingAmount;
+ aging: IAgingPeriodTotal[];
+ total: IAgingAmount;
}
export interface IARAgingSummaryTotal {
- current: IAgingPeriodTotal,
- aging: (IAgingPeriodTotal & IAgingPeriod)[],
-};
+ current: IAgingAmount;
+ aging: IAgingPeriodTotal[];
+ total: IAgingAmount;
+}
export interface IARAgingSummaryData {
- customers: IARAgingSummaryCustomer[],
- total: IARAgingSummaryTotal,
-};
+ customers: IARAgingSummaryCustomer[];
+ total: IARAgingSummaryTotal;
+}
-export type IARAgingSummaryColumns = IAgingPeriod[];
\ No newline at end of file
+export type IARAgingSummaryColumns = IAgingPeriod[];
diff --git a/server/src/interfaces/AgingReport.ts b/server/src/interfaces/AgingReport.ts
index ca8d4bc01..65983d44f 100644
--- a/server/src/interfaces/AgingReport.ts
+++ b/server/src/interfaces/AgingReport.ts
@@ -1,12 +1,22 @@
-export interface IAgingPeriodTotal {
- total: number;
- formattedTotal: string;
+export interface IAgingPeriodTotal extends IAgingPeriod {
+ total: IAgingAmount;
+};
+
+export interface IAgingAmount {
+ amount: number;
+ formattedAmount: string;
currencyCode: string;
}
export interface IAgingPeriod {
- fromPeriod: Date|string;
- toPeriod: Date|string;
+ fromPeriod: Date | string;
+ toPeriod: Date | string;
beforeDays: number;
toDays: number;
-}
\ No newline at end of file
+}
+
+export interface IAgingSummaryContact {
+ current: IAgingAmount;
+ aging: IAgingPeriodTotal[];
+ total: IAgingAmount;
+}
diff --git a/server/src/interfaces/BalanceSheet.ts b/server/src/interfaces/BalanceSheet.ts
index a680fe13c..f9a8e631d 100644
--- a/server/src/interfaces/BalanceSheet.ts
+++ b/server/src/interfaces/BalanceSheet.ts
@@ -1,74 +1,79 @@
+import {
+ INumberFormatQuery,
+ IFormatNumberSettings,
+} from './FinancialStatements';
-export interface IBalanceSheetQuery{
- displayColumnsType: 'total' | 'date_periods',
- displayColumnsBy: string,
- fromDate: Date|string,
- toDate: Date|string,
- numberFormat: {
- noCents: boolean,
- divideOn1000: boolean,
- },
- noneZero: boolean,
- noneTransactions: boolean,
- basis: 'cash' | 'accural',
- accountIds: number[],
+export interface IBalanceSheetQuery {
+ displayColumnsType: 'total' | 'date_periods';
+ displayColumnsBy: string;
+ fromDate: Date | string;
+ toDate: Date | string;
+ numberFormat: INumberFormatQuery;
+ noneZero: boolean;
+ noneTransactions: boolean;
+ basis: 'cash' | 'accural';
+ accountIds: number[];
+}
+
+export interface IBalanceSheetFormatNumberSettings
+ extends IFormatNumberSettings {
+ type: string;
}
export interface IBalanceSheetStatementService {
- balanceSheet(tenantId: number, query: IBalanceSheetQuery): Promise;
+ balanceSheet(
+ tenantId: number,
+ query: IBalanceSheetQuery
+ ): Promise;
}
-export interface IBalanceSheetStatementColumns {
+export interface IBalanceSheetStatementColumns {}
-}
-
-export interface IBalanceSheetStatementData {
-
-}
+export interface IBalanceSheetStatementData {}
export interface IBalanceSheetStatement {
- query: IBalanceSheetQuery,
- columns: IBalanceSheetStatementColumns,
- data: IBalanceSheetStatementData,
+ query: IBalanceSheetQuery;
+ columns: IBalanceSheetStatementColumns;
+ data: IBalanceSheetStatementData;
}
export interface IBalanceSheetStructureSection {
- name: string,
- sectionType?: string,
- type: 'section' | 'accounts_section',
- children?: IBalanceSheetStructureSection[],
- accountsTypesRelated?: string[],
- alwaysShow?: boolean,
+ name: string;
+ sectionType?: string;
+ type: 'section' | 'accounts_section';
+ children?: IBalanceSheetStructureSection[];
+ accountsTypesRelated?: string[];
+ alwaysShow?: boolean;
}
export interface IBalanceSheetAccountTotal {
- amount: number,
- formattedAmount: string,
- currencyCode: string,
- date?: string|Date,
+ amount: number;
+ formattedAmount: string;
+ currencyCode: string;
+ date?: string | Date;
}
export interface IBalanceSheetAccount {
- id: number,
- index: number,
- name: string,
- code: string,
- parentAccountId: number,
- type: 'account',
- hasTransactions: boolean,
- children?: IBalanceSheetAccount[],
- total: IBalanceSheetAccountTotal,
- totalPeriods?: IBalanceSheetAccountTotal[],
+ id: number;
+ index: number;
+ name: string;
+ code: string;
+ parentAccountId: number;
+ type: 'account';
+ hasTransactions: boolean;
+ children?: IBalanceSheetAccount[];
+ total: IBalanceSheetAccountTotal;
+ totalPeriods?: IBalanceSheetAccountTotal[];
}
export interface IBalanceSheetSection {
- name: string,
- sectionType?: string,
- type: 'section' | 'accounts_section',
- children: IBalanceSheetAccount[] | IBalanceSheetSection[],
- total: IBalanceSheetAccountTotal,
+ name: string;
+ sectionType?: string;
+ type: 'section' | 'accounts_section';
+ children: IBalanceSheetAccount[] | IBalanceSheetSection[];
+ total: IBalanceSheetAccountTotal;
totalPeriods?: IBalanceSheetAccountTotal[];
- accountsTypesRelated?: string[],
- _forceShow?: boolean,
-}
\ No newline at end of file
+ accountsTypesRelated?: string[];
+ _forceShow?: boolean;
+}
diff --git a/server/src/interfaces/FinancialStatements.ts b/server/src/interfaces/FinancialStatements.ts
index 139597f9c..52ae49478 100644
--- a/server/src/interfaces/FinancialStatements.ts
+++ b/server/src/interfaces/FinancialStatements.ts
@@ -1,2 +1,19 @@
+export interface INumberFormatQuery {
+ precision: number;
+ divideOn1000: boolean;
+ showZero: boolean;
+ formatMoney: 'total' | 'always' | 'none';
+ negativeFormat: 'parentheses' | 'mines';
+}
-
+export interface IFormatNumberSettings {
+ precision?: number;
+ divideOn1000?: boolean;
+ excerptZero?: boolean;
+ negativeFormat?: 'parentheses' | 'mines';
+ thousand?: string;
+ decimal?: string;
+ zeroSign?: string;
+ symbol?: string;
+ money?: boolean,
+}
diff --git a/server/src/interfaces/Journal.ts b/server/src/interfaces/Journal.ts
index 147ee663f..3a13642e1 100644
--- a/server/src/interfaces/Journal.ts
+++ b/server/src/interfaces/Journal.ts
@@ -11,6 +11,8 @@ export interface IJournalEntry {
referenceType: string,
referenceId: number,
+ referenceTypeFormatted: string,
+
transactionType?: string,
note?: string,
userId?: number,
diff --git a/server/src/interfaces/ProfitLossSheet.ts b/server/src/interfaces/ProfitLossSheet.ts
index be4d2f16a..da0b81059 100644
--- a/server/src/interfaces/ProfitLossSheet.ts
+++ b/server/src/interfaces/ProfitLossSheet.ts
@@ -1,13 +1,12 @@
-
+import {
+ INumberFormatQuery,
+} from './FinancialStatements';
export interface IProfitLossSheetQuery {
basis: string,
fromDate: Date | string,
toDate: Date | string,
- numberFormat: {
- noCents: boolean,
- divideOn1000: boolean,
- },
+ numberFormat: INumberFormatQuery,
noneZero: boolean,
noneTransactions: boolean,
accountsIds: number[],
@@ -34,8 +33,8 @@ export interface IProfitLossSheetAccount {
};
export interface IProfitLossSheetAccountsSection {
- sectionTitle: string,
- entryNormal: 'credit',
+ name: string,
+ entryNormal: 'credit' | 'debit',
accounts: IProfitLossSheetAccount[],
total: IProfitLossSheetTotal,
totalPeriods?: IProfitLossSheetTotal[],
diff --git a/server/src/interfaces/TrialBalanceSheet.ts b/server/src/interfaces/TrialBalanceSheet.ts
index fcaa4fd6d..abd42e5a6 100644
--- a/server/src/interfaces/TrialBalanceSheet.ts
+++ b/server/src/interfaces/TrialBalanceSheet.ts
@@ -1,38 +1,41 @@
+import { INumberFormatQuery } from './FinancialStatements';
export interface ITrialBalanceSheetQuery {
- fromDate: Date|string,
- toDate: Date|string,
- numberFormat: {
- noCents: boolean,
- divideOn1000: boolean,
- },
- basis: 'cash' | 'accural',
- noneZero: boolean,
- noneTransactions: boolean,
- accountIds: number[],
+ fromDate: Date | string;
+ toDate: Date | string;
+ numberFormat: INumberFormatQuery;
+ basis: 'cash' | 'accural';
+ noneZero: boolean;
+ noneTransactions: boolean;
+ accountIds: number[];
}
-export interface ITrialBalanceAccount {
- id: number,
- parentAccountId: number,
- name: string,
- code: string,
- accountNormal: string,
- hasTransactions: boolean,
+export interface ITrialBalanceTotal {
+ credit: number;
+ debit: number;
+ balance: number;
+ currencyCode: string;
- credit: number,
- debit: number,
- balance: number,
- currencyCode: string,
-
- formattedCredit: string,
- formattedDebit: string,
- formattedBalance: string,
+ formattedCredit: string;
+ formattedDebit: string;
+ formattedBalance: string;
}
-export type ITrialBalanceSheetData = IBalanceSheetSection[];
+export interface ITrialBalanceAccount extends ITrialBalanceTotal {
+ id: number;
+ parentAccountId: number;
+ name: string;
+ code: string;
+ accountNormal: string;
+ hasTransactions: boolean;
+}
+
+export type ITrialBalanceSheetData = {
+ accounts: ITrialBalanceAccount[];
+ total: ITrialBalanceTotal;
+};
export interface ITrialBalanceStatement {
- data: ITrialBalanceSheetData,
- query: ITrialBalanceSheetQuery,
-}
\ No newline at end of file
+ data: ITrialBalanceSheetData;
+ query: ITrialBalanceSheetQuery;
+}
diff --git a/server/src/repositories/CustomerRepository.ts b/server/src/repositories/CustomerRepository.ts
index 8bfaa3633..59408909e 100644
--- a/server/src/repositories/CustomerRepository.ts
+++ b/server/src/repositories/CustomerRepository.ts
@@ -7,7 +7,7 @@ export default class CustomerRepository extends TenantRepository {
*/
constructor(knex, cache) {
super(knex, cache);
- this.repositoryName = 'ContactRepository';
+ this.repositoryName = 'CustomerRepository';
}
/**
diff --git a/server/src/repositories/VendorRepository.ts b/server/src/repositories/VendorRepository.ts
index b833840b3..085973e87 100644
--- a/server/src/repositories/VendorRepository.ts
+++ b/server/src/repositories/VendorRepository.ts
@@ -7,7 +7,7 @@ export default class VendorRepository extends TenantRepository {
*/
constructor(knex, cache) {
super(knex, cache);
- this.repositoryName = 'ContactRepository';
+ this.repositoryName = 'VendorRepository';
}
/**
@@ -17,6 +17,10 @@ export default class VendorRepository extends TenantRepository {
return Vendor.bindKnex(this.knex);
}
+ unpaid() {
+
+ }
+
changeBalance(vendorId: number, amount: number) {
return super.changeNumber({ id: vendorId }, 'balance', amount);
}
diff --git a/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts b/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts
index eebd8241a..ba1916b29 100644
--- a/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts
+++ b/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts
@@ -15,14 +15,17 @@ export default class PayableAgingSummaryService {
/**
* Default report query.
*/
- get defaultQuery() {
+ get defaultQuery(): IAPAgingSummaryQuery {
return {
asDate: moment().format('YYYY-MM-DD'),
agingDaysBefore: 30,
agingPeriods: 3,
numberFormat: {
- noCents: false,
+ precision: 2,
divideOn1000: false,
+ showZero: false,
+ formatMoney: 'total',
+ negativeFormat: 'mines'
},
vendorsIds: [],
noneZero: false,
diff --git a/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts b/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts
index 769be797c..da56ac415 100644
--- a/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts
+++ b/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts
@@ -7,9 +7,11 @@ import {
IVendor,
IAPAgingSummaryData,
IAPAgingSummaryVendor,
- IAPAgingSummaryColumns
+ IAPAgingSummaryColumns,
+ IAPAgingSummaryTotal
} from 'interfaces';
import { Dictionary } from 'tsyringe/dist/typings/types';
+
export default class APAgingSummarySheet extends AgingSummaryReport {
readonly tenantId: number;
readonly query: IAPAgingSummaryQuery;
@@ -56,6 +58,23 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
);
}
+ /**
+ * Retrieve the vendors aging and current total.
+ * @param {IAPAgingSummaryTotal} vendorsAgingPeriods
+ * @return {IAPAgingSummaryTotal}
+ */
+ getVendorsTotal(vendorsAgingPeriods): IAPAgingSummaryTotal {
+ const totalAgingPeriods = this.getTotalAgingPeriods(vendorsAgingPeriods);
+ const totalCurrent = this.getTotalCurrent(vendorsAgingPeriods);
+ const totalVendorsTotal = this.getTotalContactsTotals(vendorsAgingPeriods);
+
+ return {
+ current: this.formatTotalAmount(totalCurrent),
+ aging: totalAgingPeriods,
+ total: this.formatTotalAmount(totalVendorsTotal),
+ };
+ }
+
/**
* Retrieve the vendor section data.
* @param {IVendor} vendor
@@ -85,7 +104,7 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
.map((vendor) => this.vendorData(vendor))
.filter(
(vendor: IAPAgingSummaryVendor) =>
- !(vendor.total.total === 0 && this.query.noneZero)
+ !(vendor.total.amount === 0 && this.query.noneZero)
);
}
@@ -95,16 +114,12 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
*/
public reportData(): IAPAgingSummaryData {
const vendorsAgingPeriods = this.vendorsWalker(this.contacts);
- const totalAgingPeriods = this.getTotalAgingPeriods(vendorsAgingPeriods);
- const totalCurrent = this.getTotalCurrent(vendorsAgingPeriods);
+ const vendorsTotal = this.getVendorsTotal(vendorsAgingPeriods);
return {
vendors: vendorsAgingPeriods,
- total: {
- current: this.formatTotalAmount(totalCurrent),
- aging: totalAgingPeriods,
- },
- }
+ total: vendorsTotal,
+ };
}
/**
@@ -113,4 +128,4 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
public reportColumns(): IAPAgingSummaryColumns {
return this.agingPeriods;
}
-}
\ No newline at end of file
+}
diff --git a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts
index 37960a540..c1d715216 100644
--- a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts
+++ b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts
@@ -15,14 +15,17 @@ export default class ARAgingSummaryService {
/**
* Default report query.
*/
- get defaultQuery() {
+ get defaultQuery(): IARAgingSummaryQuery {
return {
asDate: moment().format('YYYY-MM-DD'),
agingDaysBefore: 30,
agingPeriods: 3,
numberFormat: {
- no_cents: false,
- divide_1000: false,
+ divideOn1000: false,
+ negativeFormat: 'mines',
+ showZero: false,
+ formatMoney: 'total',
+ precision: 2,
},
customersIds: [],
noneZero: false,
@@ -50,6 +53,7 @@ export default class ARAgingSummaryService {
});
// Settings tenant service.
const settings = this.tenancy.settings(tenantId);
+
const baseCurrency = settings.get({
group: 'organization',
key: 'base_currency',
diff --git a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts
index 39f0d18fa..3385c76bb 100644
--- a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts
+++ b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts
@@ -7,6 +7,7 @@ import {
ISaleInvoice,
IARAgingSummaryData,
IARAgingSummaryColumns,
+ IARAgingSummaryTotal,
} from 'interfaces';
import AgingSummaryReport from './AgingSummary';
import { Dictionary } from 'tsyringe/dist/typings/types';
@@ -44,8 +45,14 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
this.baseCurrency = baseCurrency;
this.numberFormat = this.query.numberFormat;
- this.overdueInvoicesByContactId = groupBy(overdueSaleInvoices, 'customerId');
- this.currentInvoicesByContactId = groupBy(currentSaleInvoices, 'customerId');
+ this.overdueInvoicesByContactId = groupBy(
+ overdueSaleInvoices,
+ 'customerId'
+ );
+ this.currentInvoicesByContactId = groupBy(
+ currentSaleInvoices,
+ 'customerId'
+ );
// Initializes the aging periods.
this.agingPeriods = this.agingRangePeriods(
@@ -68,7 +75,7 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
return {
customerName: customer.displayName,
- current: this.formatTotalAmount(currentTotal),
+ current: this.formatAmount(currentTotal),
aging: agingPeriods,
total: this.formatTotalAmount(amount),
};
@@ -84,25 +91,41 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
.map((customer) => this.customerData(customer))
.filter(
(customer: IARAgingSummaryCustomer) =>
- !(customer.total.total === 0 && this.query.noneZero)
+ !(customer.total.amount === 0 && this.query.noneZero)
);
}
+ /**
+ * Retrieve the customers aging and current total.
+ * @param {IARAgingSummaryCustomer} customersAgingPeriods
+ */
+ private getCustomersTotal(
+ customersAgingPeriods: IARAgingSummaryCustomer[]
+ ): IARAgingSummaryTotal {
+ const totalAgingPeriods = this.getTotalAgingPeriods(customersAgingPeriods);
+ const totalCurrent = this.getTotalCurrent(customersAgingPeriods);
+ const totalCustomersTotal = this.getTotalContactsTotals(
+ customersAgingPeriods
+ );
+
+ return {
+ current: this.formatTotalAmount(totalCurrent),
+ aging: totalAgingPeriods,
+ total: this.formatTotalAmount(totalCustomersTotal),
+ };
+ }
+
/**
* Retrieve A/R aging summary report data.
* @return {IARAgingSummaryData}
*/
public reportData(): IARAgingSummaryData {
const customersAgingPeriods = this.customersWalker(this.contacts);
- const totalAgingPeriods = this.getTotalAgingPeriods(customersAgingPeriods);
- const totalCurrent = this.getTotalCurrent(customersAgingPeriods);
+ const customersTotal = this.getCustomersTotal(customersAgingPeriods);
return {
customers: customersAgingPeriods,
- total: {
- current: this.formatTotalAmount(totalCurrent),
- aging: totalAgingPeriods,
- }
+ total: customersTotal,
};
}
diff --git a/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts b/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts
index b13ecaaa2..cdb6e2b4f 100644
--- a/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts
+++ b/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts
@@ -7,6 +7,9 @@ import {
IARAgingSummaryCustomer,
IContact,
IARAgingSummaryQuery,
+ IFormatNumberSettings,
+ IAgingAmount,
+ IAgingSummaryContact
} from 'interfaces';
import AgingReport from './AgingReport';
import { Dictionary } from 'tsyringe/dist/typings/types';
@@ -24,58 +27,61 @@ export default abstract class AgingSummaryReport extends AgingReport {
>;
/**
- * Setes initial aging periods to the given customer id.
- * @param {number} customerId - Customer id.
+ * Setes initial aging periods to the contact.
*/
- protected getInitialAgingPeriodsTotal() {
+ protected getInitialAgingPeriodsTotal(): IAgingPeriodTotal[] {
return this.agingPeriods.map((agingPeriod) => ({
...agingPeriod,
- ...this.formatTotalAmount(0),
+ total: this.formatAmount(0),
}));
}
/**
* Calculates the given contact aging periods.
- * @param {ICustomer} customer
- * @return {(IAgingPeriod & IAgingPeriodTotal)[]}
+ * @param {number} contactId - Contact id.
+ * @return {IAgingPeriodTotal[]}
*/
- protected getContactAgingPeriods(
- contactId: number
- ): (IAgingPeriod & IAgingPeriodTotal)[] {
+ protected getContactAgingPeriods(contactId: number): IAgingPeriodTotal[] {
const unpaidInvoices = this.getUnpaidInvoicesByContactId(contactId);
const initialAgingPeriods = this.getInitialAgingPeriodsTotal();
- return unpaidInvoices.reduce((agingPeriods, unpaidInvoice) => {
- const newAgingPeriods = this.getContactAgingDueAmount(
- agingPeriods,
- unpaidInvoice.dueAmount,
- unpaidInvoice.getOverdueDays(this.query.asDate)
- );
- return newAgingPeriods;
- }, initialAgingPeriods);
+ return unpaidInvoices.reduce(
+ (agingPeriods: IAgingPeriodTotal[], unpaidInvoice) => {
+ const newAgingPeriods = this.getContactAgingDueAmount(
+ agingPeriods,
+ unpaidInvoice.dueAmount,
+ unpaidInvoice.getOverdueDays(this.query.asDate)
+ );
+ return newAgingPeriods;
+ },
+ initialAgingPeriods
+ );
}
/**
- * Sets the customer aging due amount to the table. (Xx)
- * @param {number} customerId - Customer id.
+ * Sets the contact aging due amount to the table.
+ * @param {IAgingPeriodTotal} agingPeriods - Aging periods.
* @param {number} dueAmount - Due amount.
* @param {number} overdueDays - Overdue days.
+ * @return {IAgingPeriodTotal[]}
*/
protected getContactAgingDueAmount(
- agingPeriods: any,
+ agingPeriods: IAgingPeriodTotal[],
dueAmount: number,
overdueDays: number
- ): (IAgingPeriod & IAgingPeriodTotal)[] {
+ ): IAgingPeriodTotal[] {
const newAgingPeriods = agingPeriods.map((agingPeriod) => {
const isInAgingPeriod =
agingPeriod.beforeDays <= overdueDays &&
(agingPeriod.toDays > overdueDays || !agingPeriod.toDays);
+ const total: number = isInAgingPeriod
+ ? agingPeriod.total.amount + dueAmount
+ : agingPeriod.total.amount;
+
return {
...agingPeriod,
- total: isInAgingPeriod
- ? agingPeriod.total + dueAmount
- : agingPeriod.total,
+ total: this.formatAmount(total),
};
});
return newAgingPeriods;
@@ -84,16 +90,37 @@ export default abstract class AgingSummaryReport extends AgingReport {
/**
* Retrieve the aging period total object.
* @param {number} amount
- * @return {IAgingPeriodTotal}
+ * @param {IFormatNumberSettings} settings - Override the format number settings.
+ * @return {IAgingAmount}
*/
- protected formatTotalAmount(amount: number): IAgingPeriodTotal {
+ protected formatAmount(
+ amount: number,
+ settings: IFormatNumberSettings = {}
+ ): IAgingAmount {
return {
- total: amount,
- formattedTotal: this.formatNumber(amount),
+ amount,
+ formattedAmount: this.formatNumber(amount, settings),
currencyCode: this.baseCurrency,
};
}
+ /**
+ * Retrieve the aging period total object.
+ * @param {number} amount
+ * @param {IFormatNumberSettings} settings - Override the format number settings.
+ * @return {IAgingPeriodTotal}
+ */
+ protected formatTotalAmount(
+ amount: number,
+ settings: IFormatNumberSettings = {}
+ ): IAgingAmount {
+ return this.formatAmount(amount, {
+ money: true,
+ excerptZero: false,
+ ...settings,
+ });
+ }
+
/**
* Calculates the total of the aging period by the given index.
* @param {number} index
@@ -101,9 +128,9 @@ export default abstract class AgingSummaryReport extends AgingReport {
*/
protected getTotalAgingPeriodByIndex(
contactsAgingPeriods: any,
- index: number
+ index: number,
): number {
- return this.contacts.reduce((acc, customer) => {
+ return this.contacts.reduce((acc, contact) => {
const totalPeriod = contactsAgingPeriods[index]
? contactsAgingPeriods[index].total
: 0;
@@ -113,9 +140,9 @@ export default abstract class AgingSummaryReport extends AgingReport {
}
/**
- * Retrieve the due invoices by the given customer id.
- * @param {number} customerId -
- * @return {ISaleInvoice[]}
+ * Retrieve the due invoices by the given contact id.
+ * @param {number} contactId -
+ * @return {(ISaleInvoice | IBill)[]}
*/
protected getUnpaidInvoicesByContactId(
contactId: number
@@ -129,20 +156,30 @@ export default abstract class AgingSummaryReport extends AgingReport {
*/
protected getTotalAgingPeriods(
contactsAgingPeriods: IARAgingSummaryCustomer[]
- ): (IAgingPeriodTotal & IAgingPeriod)[] {
+ ): IAgingPeriodTotal[] {
return this.agingPeriods.map((agingPeriod, index) => {
- const total = sumBy(contactsAgingPeriods, `aging[${index}].total`);
+ const total = sumBy(
+ contactsAgingPeriods,
+ (summary: IARAgingSummaryCustomer) => {
+ const aging = summary.aging[index];
+
+ if (!aging) {
+ return 0;
+ }
+ return aging.total.amount;
+ }
+ );
return {
...agingPeriod,
- ...this.formatTotalAmount(total),
+ total: this.formatTotalAmount(total),
};
});
}
/**
* Retrieve the current invoices by the given contact id.
- * @param {number} contactId
+ * @param {number} contactId - Specific contact id.
* @return {(ISaleInvoice | IBill)[]}
*/
protected getCurrentInvoicesByContactId(
@@ -153,23 +190,23 @@ export default abstract class AgingSummaryReport extends AgingReport {
/**
* Retrieve the contact total due amount.
- * @param {number} contactId
+ * @param {number} contactId - Specific contact id.
* @return {number}
*/
protected getContactCurrentTotal(contactId: number): number {
const currentInvoices = this.getCurrentInvoicesByContactId(contactId);
- return sumBy(currentInvoices, invoice => invoice.dueAmount);
+ return sumBy(currentInvoices, (invoice) => invoice.dueAmount);
}
/**
- * Retrieve to total sumation of the given customers sections.
- * @param {IARAgingSummaryCustomer[]} contactsSections -
+ * Retrieve to total sumation of the given contacts summeries sections.
+ * @param {IARAgingSummaryCustomer[]} contactsSections -
* @return {number}
*/
protected getTotalCurrent(
- customersSummary: IARAgingSummaryCustomer[]
+ contactsSummaries: IAgingSummaryContact[]
): number {
- return sumBy(customersSummary, summary => summary.current.total);
+ return sumBy(contactsSummaries, (summary) => summary.current.amount);
}
/**
@@ -177,9 +214,17 @@ export default abstract class AgingSummaryReport extends AgingReport {
* @param {IAgingPeriodTotal[]} agingPeriods
* @return {number}
*/
- protected getAgingPeriodsTotal(
- agingPeriods: IAgingPeriodTotal[],
+ protected getAgingPeriodsTotal(agingPeriods: IAgingPeriodTotal[]): number {
+ return sumBy(agingPeriods, (period) => period.total.amount);
+ }
+
+ /**
+ * Retrieve total of contacts totals.
+ * @param {IAgingSummaryContact[]} contactsSummaries
+ */
+ protected getTotalContactsTotals(
+ contactsSummaries: IAgingSummaryContact[]
): number {
- return sumBy(agingPeriods, 'total');
+ return sumBy(contactsSummaries, (summary) => summary.total.amount);
}
}
diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts
index 87dcbdaf5..6c36c3c31 100644
--- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts
+++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts
@@ -73,7 +73,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
sections: IBalanceSheetSection[]
): IBalanceSheetAccountTotal {
const amount = sumBy(sections, 'total.amount');
- const formattedAmount = this.formatNumber(amount);
+ const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
return { amount, formattedAmount, currencyCode };
@@ -89,7 +89,25 @@ export default class BalanceSheetStatement extends FinancialSheet {
): IBalanceSheetAccountTotal[] {
return this.dateRangeSet.map((date, index) => {
const amount = sumBy(sections, `totalPeriods[${index}].amount`);
- const formattedAmount = this.formatNumber(amount);
+
+ const formattedAmount = this.formatTotalNumber(amount);
+ const currencyCode = this.baseCurrency;
+
+ return { date, amount, formattedAmount, currencyCode };
+ });
+ }
+
+ /**
+ * Retrieve accounts total periods.
+ * @param {Array} accounts -
+ * @return {IBalanceSheetAccountTotal[]}
+ */
+ private getAccountsTotalPeriods(
+ accounts: Array
+ ): IBalanceSheetAccountTotal[] {
+ return this.dateRangeSet.map((date, index) => {
+ const amount = sumBy(accounts, `totalPeriods[${index}].amount`);
+ const formattedAmount = this.formatNumber(amount)
const currencyCode = this.baseCurrency;
return { date, amount, formattedAmount, currencyCode };
@@ -190,12 +208,12 @@ export default class BalanceSheetStatement extends FinancialSheet {
}),
total: {
amount: totalAmount,
- formattedAmount: this.formatNumber(totalAmount),
+ formattedAmount: this.formatTotalNumber(totalAmount),
currencyCode: this.baseCurrency,
},
...(this.query.displayColumnsType === 'date_periods'
? {
- totalPeriods: this.getSectionTotalPeriods(filteredAccounts),
+ totalPeriods: this.getAccountsTotalPeriods(filteredAccounts),
}
: {}),
};
@@ -232,7 +250,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
*/
private balanceSheetStructureMapper(
structure: IBalanceSheetStructureSection,
- accounts: IAccount & { type: IAccountType }[]
+ accounts: IAccount & { type: IAccountType }[],
): IBalanceSheetSection {
const result = {
name: structure.name,
@@ -276,14 +294,9 @@ export default class BalanceSheetStatement extends FinancialSheet {
}
)
// Mappes the balance sheet scetions only
- .map(
- ([sheetSection, structure]: [
- IBalanceSheetSection,
- IBalanceSheetStructureSection
- ]) => {
- return sheetSection;
- }
- )
+ .map(([sheetSection]: [IBalanceSheetSection]) => {
+ return sheetSection;
+ })
);
}
diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts
index fc1e96212..8af288074 100644
--- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts
+++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts
@@ -29,8 +29,11 @@ export default class BalanceSheetStatementService
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
numberFormat: {
- noCents: false,
+ precision: 2,
divideOn1000: false,
+ showZero: false,
+ formatMoney: 'total',
+ negativeFormat: 'mines'
},
noneZero: false,
noneTransactions: false,
diff --git a/server/src/services/FinancialStatements/FinancialSheet.ts b/server/src/services/FinancialStatements/FinancialSheet.ts
index bab23900b..df78de504 100644
--- a/server/src/services/FinancialStatements/FinancialSheet.ts
+++ b/server/src/services/FinancialStatements/FinancialSheet.ts
@@ -1,16 +1,56 @@
-import {
- formatNumber
-} from 'utils';
+import { IFormatNumberSettings, INumberFormatQuery } from 'interfaces';
+import { formatNumber } from 'utils';
export default class FinancialSheet {
- numberFormat: { noCents: boolean, divideOn1000: boolean };
+ numberFormat: INumberFormatQuery;
+
+ /**
+ * Transformes the number format query to settings
+ */
+ protected transfromFormatQueryToSettings(): IFormatNumberSettings {
+ const { numberFormat } = this;
+
+ return {
+ precision: numberFormat.precision,
+ divideOn1000: numberFormat.divideOn1000,
+ excerptZero: !numberFormat.showZero,
+ negativeFormat: numberFormat.negativeFormat,
+ money: numberFormat.formatMoney === 'always',
+ };
+ }
/**
* Formating amount based on the given report query.
- * @param {number} number
+ * @param {number} number -
+ * @param {IFormatNumberSettings} overrideSettings -
* @return {string}
*/
- protected formatNumber(number): string {
- return formatNumber(number, this.numberFormat);
+ protected formatNumber(
+ number,
+ overrideSettings: IFormatNumberSettings = {}
+ ): string {
+ const settings = {
+ ...this.transfromFormatQueryToSettings(),
+ ...overrideSettings,
+ };
+ return formatNumber(number, settings);
}
-}
\ No newline at end of file
+
+ /**
+ * Formatting full amount with different format settings.
+ * @param {number} amount -
+ * @param {IFormatNumberSettings} settings -
+ */
+ protected formatTotalNumber(
+ amount: number,
+ settings: IFormatNumberSettings = {}
+ ): string {
+ const { numberFormat } = this;
+
+ return this.formatNumber(amount, {
+ money: numberFormat.formatMoney === 'none' ? false : true,
+ excerptZero: false,
+ ...settings
+ });
+ }
+}
diff --git a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts
index 751a87e20..98b6a3c2d 100644
--- a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts
+++ b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts
@@ -7,9 +7,9 @@ import {
IAccount,
IJournalPoster,
IAccountType,
- IJournalEntry
+ IJournalEntry,
} from 'interfaces';
-import FinancialSheet from "../FinancialSheet";
+import FinancialSheet from '../FinancialSheet';
export default class GeneralLedgerSheet extends FinancialSheet {
tenantId: number;
@@ -35,7 +35,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
transactions: IJournalPoster,
openingBalancesJournal: IJournalPoster,
closingBalancesJournal: IJournalPoster,
- baseCurrency: string,
+ baseCurrency: string
) {
super();
@@ -51,7 +51,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
/**
* Mapping the account transactions to general ledger transactions of the given account.
- * @param {IAccount} account
+ * @param {IAccount} account
* @return {IGeneralLedgerSheetAccountTransaction[]}
*/
private accountTransactionsMapper(
@@ -59,34 +59,45 @@ export default class GeneralLedgerSheet extends FinancialSheet {
): IGeneralLedgerSheetAccountTransaction[] {
const entries = this.transactions.getAccountEntries(account.id);
- return entries.map((transaction: IJournalEntry): IGeneralLedgerSheetAccountTransaction => {
- let amount = 0;
+ return entries.map(
+ (transaction: IJournalEntry): IGeneralLedgerSheetAccountTransaction => {
+ let amount = 0;
- if (account.type.normal === 'credit') {
- amount += transaction.credit - transaction.debit;
- } else if (account.type.normal === 'debit') {
- amount += transaction.debit - transaction.credit;
+ if (account.type.normal === 'credit') {
+ amount += transaction.credit - transaction.debit;
+ } else if (account.type.normal === 'debit') {
+ amount += transaction.debit - transaction.credit;
+ }
+ const formattedAmount = this.formatNumber(amount);
+
+ return {
+ ...pick(transaction, [
+ 'id',
+ 'note',
+ 'transactionType',
+ 'referenceType',
+ 'referenceId',
+ 'referenceTypeFormatted',
+ 'date',
+ ]),
+ amount,
+ formattedAmount,
+ currencyCode: this.baseCurrency,
+ };
}
- const formattedAmount = this.formatNumber(amount);
-
- return {
- ...pick(transaction, ['id', 'note', 'transactionType', 'referenceType',
- 'referenceId', 'date']),
- amount,
- formattedAmount,
- currencyCode: this.baseCurrency,
- };
- });
+ );
}
/**
* Retrieve account opening balance.
- * @param {IAccount} account
+ * @param {IAccount} account
* @return {IGeneralLedgerSheetAccountBalance}
*/
- private accountOpeningBalance(account: IAccount): IGeneralLedgerSheetAccountBalance {
+ private accountOpeningBalance(
+ account: IAccount
+ ): IGeneralLedgerSheetAccountBalance {
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
- const formattedAmount = this.formatNumber(amount);
+ const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
const date = this.query.fromDate;
@@ -95,12 +106,14 @@ export default class GeneralLedgerSheet extends FinancialSheet {
/**
* Retrieve account closing balance.
- * @param {IAccount} account
+ * @param {IAccount} account
* @return {IGeneralLedgerSheetAccountBalance}
*/
- private accountClosingBalance(account: IAccount): IGeneralLedgerSheetAccountBalance {
+ private accountClosingBalance(
+ account: IAccount
+ ): IGeneralLedgerSheetAccountBalance {
const amount = this.closingBalancesJournal.getAccountBalance(account.id);
- const formattedAmount = this.formatNumber(amount);
+ const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
const date = this.query.toDate;
@@ -109,35 +122,42 @@ export default class GeneralLedgerSheet extends FinancialSheet {
/**
* Retreive general ledger accounts sections.
- * @param {IAccount} account
+ * @param {IAccount} account
* @return {IGeneralLedgerSheetAccount}
*/
private accountMapper(
- account: IAccount & { type: IAccountType },
+ account: IAccount & { type: IAccountType }
): IGeneralLedgerSheetAccount {
return {
...pick(account, ['id', 'name', 'code', 'index', 'parentAccountId']),
opening: this.accountOpeningBalance(account),
transactions: this.accountTransactionsMapper(account),
closing: this.accountClosingBalance(account),
- }
+ };
}
/**
* Retrieve mapped accounts with general ledger transactions and opeing/closing balance.
- * @param {IAccount[]} accounts -
+ * @param {IAccount[]} accounts -
* @return {IGeneralLedgerSheetAccount[]}
*/
private accountsWalker(
accounts: IAccount & { type: IAccountType }[]
): IGeneralLedgerSheetAccount[] {
- return accounts
- .map((account: IAccount & { type: IAccountType }) => this.accountMapper(account))
-
- // Filter general ledger accounts that have no transactions when `noneTransactions` is on.
- .filter((generalLedgerAccount: IGeneralLedgerSheetAccount) => (
- !(generalLedgerAccount.transactions.length === 0 && this.query.noneTransactions)
- ));
+ return (
+ accounts
+ .map((account: IAccount & { type: IAccountType }) =>
+ this.accountMapper(account)
+ )
+ // Filter general ledger accounts that have no transactions when `noneTransactions` is on.
+ .filter(
+ (generalLedgerAccount: IGeneralLedgerSheetAccount) =>
+ !(
+ generalLedgerAccount.transactions.length === 0 &&
+ this.query.noneTransactions
+ )
+ )
+ );
}
/**
@@ -147,4 +167,4 @@ export default class GeneralLedgerSheet extends FinancialSheet {
public reportData(): IGeneralLedgerSheetAccount[] {
return this.accountsWalker(this.accounts);
}
-}
\ No newline at end of file
+}
diff --git a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts
index 57a28f2e7..071951cf4 100644
--- a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts
+++ b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts
@@ -85,8 +85,7 @@ export default class GeneralLedgerService {
group: 'organization',
key: 'base_currency',
});
-
- // Retrieve all accounts from the storage.
+ // Retrieve all accounts with associated type from the storage.
const accounts = await accountRepository.all('type');
const accountsGraph = await accountRepository.getDependencyGraph();
@@ -111,11 +110,13 @@ export default class GeneralLedgerService {
tenantId,
accountsGraph
);
+ // Accounts opening transactions.
const openingTransJournal = Journal.fromTransactions(
openingBalanceTrans,
tenantId,
accountsGraph
);
+ // Accounts closing transactions.
const closingTransJournal = Journal.fromTransactions(
closingBalanceTrans,
tenantId,
diff --git a/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts b/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts
index 27a7e2e0c..866bd1db2 100644
--- a/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts
+++ b/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts
@@ -57,7 +57,6 @@ export default class JournalSheetService {
group: 'organization',
key: 'base_currency',
});
-
// Retrieve all accounts on the storage.
const accountsGraph = await accountRepository.getDependencyGraph();
diff --git a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts
index 1ab120beb..9562acdf6 100644
--- a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts
+++ b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts
@@ -187,7 +187,7 @@ export default class ProfitLossSheet extends FinancialSheet {
accounts: IProfitLossSheetAccount[]
): IProfitLossSheetTotal {
const amount = sumBy(accounts, 'total.amount');
- const formattedAmount = this.formatNumber(amount);
+ const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
return { amount, formattedAmount, currencyCode };
@@ -203,7 +203,7 @@ export default class ProfitLossSheet extends FinancialSheet {
): IProfitLossSheetTotal[] {
return this.dateRangeSet.map((date, index) => {
const amount = sumBy(accounts, `totalPeriods[${index}].amount`);
- const formattedAmount = this.formatNumber(amount);
+ const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
return { amount, formattedAmount, currencyCode };
@@ -229,7 +229,7 @@ export default class ProfitLossSheet extends FinancialSheet {
*/
private get incomeSection(): IProfitLossSheetAccountsSection {
return {
- sectionTitle: 'Income accounts',
+ name: 'Income accounts',
entryNormal: 'credit',
...this.sectionMapper(this.incomeAccounts),
};
@@ -241,7 +241,7 @@ export default class ProfitLossSheet extends FinancialSheet {
*/
private get expensesSection(): IProfitLossSheetAccountsSection {
return {
- sectionTitle: 'Expense accounts',
+ name: 'Expense accounts',
entryNormal: 'debit',
...this.sectionMapper(this.expensesAccounts),
};
@@ -253,7 +253,7 @@ export default class ProfitLossSheet extends FinancialSheet {
*/
private get otherExpensesSection(): IProfitLossSheetAccountsSection {
return {
- sectionTitle: 'Other expenses accounts',
+ name: 'Other expenses accounts',
entryNormal: 'debit',
...this.sectionMapper(this.otherExpensesAccounts),
};
@@ -265,7 +265,7 @@ export default class ProfitLossSheet extends FinancialSheet {
*/
private get costOfSalesSection(): IProfitLossSheetAccountsSection {
return {
- sectionTitle: 'Cost of sales',
+ name: 'Cost of sales',
entryNormal: 'debit',
...this.sectionMapper(this.costOfSalesAccounts),
};
@@ -283,7 +283,7 @@ export default class ProfitLossSheet extends FinancialSheet {
const totalMines = sumBy(minesSections, `totalPeriods[${index}].amount`);
const amount = totalPositive - totalMines;
- const formattedAmount = this.formatNumber(amount);
+ const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
return { date, amount, formattedAmount, currencyCode };
@@ -298,7 +298,7 @@ export default class ProfitLossSheet extends FinancialSheet {
const totalMinesSections = sumBy(minesSections, 'total.amount');
const amount = totalPositiveSections - totalMinesSections;
- const formattedAmount = this.formatNumber(amount);
+ const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
return { amount, formattedAmount, currencyCode };
diff --git a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts
index af021cdda..fac09cc68 100644
--- a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts
+++ b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts
@@ -27,8 +27,11 @@ export default class ProfitLossSheetService {
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
numberFormat: {
- noCents: false,
divideOn1000: false,
+ negativeFormat: 'mines',
+ showZero: false,
+ formatMoney: 'total',
+ precision: 2,
},
basis: 'accural',
noneZero: false,
@@ -41,8 +44,8 @@ export default class ProfitLossSheetService {
/**
* Retrieve profit/loss sheet statement.
- * @param {number} tenantId
- * @param {IProfitLossSheetQuery} query
+ * @param {number} tenantId
+ * @param {IProfitLossSheetQuery} query
* @return { }
*/
async profitLossSheet(tenantId: number, query: IProfitLossSheetQuery) {
@@ -55,16 +58,24 @@ export default class ProfitLossSheetService {
...this.defaultQuery,
...query,
};
- this.logger.info('[profit_loss_sheet] trying to calculate the report.', { tenantId, filter });
+ this.logger.info('[profit_loss_sheet] trying to calculate the report.', {
+ tenantId,
+ filter,
+ });
// Get the given accounts or throw not found service error.
if (filter.accountsIds.length > 0) {
- await this.accountsService.getAccountsOrThrowError(tenantId, filter.accountsIds);
+ await this.accountsService.getAccountsOrThrowError(
+ tenantId,
+ filter.accountsIds
+ );
}
// Settings tenant service.
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.
const accounts = await accountRepository.all('type');
const accountsGraph = await accountRepository.getDependencyGraph();
@@ -75,8 +86,11 @@ export default class ProfitLossSheetService {
toDate: query.toDate,
});
// Transform transactions to journal collection.
- const transactionsJournal = Journal.fromTransactions(transactions, tenantId, accountsGraph);
-
+ const transactionsJournal = Journal.fromTransactions(
+ transactions,
+ tenantId,
+ accountsGraph
+ );
// Profit/Loss report instance.
const profitLossInstance = new ProfitLossSheet(
tenantId,
@@ -95,4 +109,4 @@ export default class ProfitLossSheetService {
query: filter,
};
}
-}
\ No newline at end of file
+}
diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts
index 0fe38c8a1..9c13915b1 100644
--- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts
+++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts
@@ -1,12 +1,19 @@
+import { sumBy } from 'lodash';
import {
ITrialBalanceSheetQuery,
ITrialBalanceAccount,
IAccount,
+ ITrialBalanceTotal,
IAccountType,
} from 'interfaces';
import FinancialSheet from '../FinancialSheet';
import { flatToNestedArray } from 'utils';
+const AMOUNT_TYPE = {
+ TOTAL: 'TOTAL',
+ SECTION_TOTAL: 'SECTION_TOTAL',
+};
+
export default class TrialBalanceSheet extends FinancialSheet {
tenantId: number;
query: ITrialBalanceSheetQuery;
@@ -103,10 +110,37 @@ export default class TrialBalanceSheet extends FinancialSheet {
});
}
+ /**
+ * Retrieve trial balance total section.
+ * @param {ITrialBalanceAccount[]} accountsBalances
+ * @return {ITrialBalanceTotal}
+ */
+ private tatalSection(
+ accountsBalances: ITrialBalanceAccount[]
+ ): ITrialBalanceTotal {
+ const credit = sumBy(accountsBalances, 'credit');
+ const debit = sumBy(accountsBalances, 'debit');
+ const balance = sumBy(accountsBalances, 'balance');
+ const currencyCode = this.baseCurrency;
+
+ return {
+ credit,
+ debit,
+ balance,
+ currencyCode,
+ formattedCredit: this.formatTotalNumber(credit),
+ formattedDebit: this.formatTotalNumber(debit),
+ formattedBalance: this.formatTotalNumber(balance),
+ };
+ }
+
/**
* Retrieve trial balance sheet statement data.
*/
public reportData() {
- return this.accountsWalker(this.accounts);
+ const accounts = this.accountsWalker(this.accounts);
+ const total = this.tatalSection(accounts);
+
+ return { accounts, total };
}
}
diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts
index d82b848f1..3fab2408e 100644
--- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts
+++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts
@@ -1,12 +1,13 @@
import { Service, Inject } from 'typedi';
import moment from 'moment';
import TenancyService from 'services/Tenancy/TenancyService';
-import { ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces';
-import TrialBalanceSheet from './TrialBalanceSheet';
import Journal from 'services/Accounting/JournalPoster';
+import { INumberFormatQuery, ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces';
+import TrialBalanceSheet from './TrialBalanceSheet';
+import FinancialSheet from '../FinancialSheet';
@Service()
-export default class TrialBalanceSheetService {
+export default class TrialBalanceSheetService extends FinancialSheet {
@Inject()
tenancy: TenancyService;
@@ -22,8 +23,11 @@ export default class TrialBalanceSheetService {
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
numberFormat: {
- noCents: false,
divideOn1000: false,
+ negativeFormat: 'mines',
+ showZero: false,
+ formatMoney: 'total',
+ precision: 2,
},
basis: 'accural',
noneZero: false,
@@ -42,7 +46,7 @@ export default class TrialBalanceSheetService {
*/
public async trialBalanceSheet(
tenantId: number,
- query: ITrialBalanceSheetQuery
+ query: ITrialBalanceSheetQuery,
): Promise {
const filter = {
...this.defaultQuery,
diff --git a/server/src/services/Inventory/InventoryAdjustmentService.ts b/server/src/services/Inventory/InventoryAdjustmentService.ts
index 3fed1f326..a2f07e841 100644
--- a/server/src/services/Inventory/InventoryAdjustmentService.ts
+++ b/server/src/services/Inventory/InventoryAdjustmentService.ts
@@ -220,8 +220,8 @@ export default class InventoryAdjustmentService {
/**
* Publish the inventory adjustment transaction.
- * @param tenantId
- * @param inventoryAdjustmentId
+ * @param tenantId
+ * @param inventoryAdjustmentId
*/
async publishInventoryAdjustment(
tenantId: number,
@@ -239,11 +239,9 @@ export default class InventoryAdjustmentService {
inventoryAdjustmentId,
});
// Publish the inventory adjustment transaction.
- await InventoryAdjustment.query()
- .findById(inventoryAdjustmentId)
- .patch({
- publishedAt: moment().toMySqlDateTime(),
- });
+ await InventoryAdjustment.query().findById(inventoryAdjustmentId).patch({
+ publishedAt: moment().toMySqlDateTime(),
+ });
// Retrieve the inventory adjustment after the modification.
const inventoryAdjustment = await InventoryAdjustment.query()
diff --git a/server/src/utils/index.js b/server/src/utils/index.js
index 8995bc7b6..688dc4b18 100644
--- a/server/src/utils/index.js
+++ b/server/src/utils/index.js
@@ -1,6 +1,7 @@
import bcrypt from 'bcryptjs';
import moment from 'moment';
import _ from 'lodash';
+import accounting from 'accounting';
import definedOptions from 'data/options';
const hashPassword = (password) =>
@@ -192,7 +193,7 @@ const entriesAmountDiff = (
.groupBy(idAttribute)
.mapValues((group) => _.sumBy(group, amountAttribute) || 0)
.mergeWith(oldEntriesTable, (objValue, srcValue) => {
- return (_.isNumber(objValue) ? objValue - srcValue : srcValue * -1);
+ return _.isNumber(objValue) ? objValue - srcValue : srcValue * -1;
})
.value();
@@ -214,27 +215,56 @@ const convertEmptyStringToNull = (value) => {
: value;
};
-const formatNumber = (balance, { noCents = false, divideOn1000 = false }) => {
+const getNegativeFormat = (formatName) => {
+ switch (formatName) {
+ case 'parentheses':
+ return '(%s%v)';
+ case 'mines':
+ return '-%s%v';
+ }
+};
+
+const formatNumber = (
+ balance,
+ {
+ precision = 2,
+ divideOn1000 = false,
+ excerptZero = false,
+ negativeFormat = 'mines',
+ thousand = ',',
+ decimal = '.',
+ zeroSign = '',
+ symbol = '$',
+ money = true,
+ }
+) => {
+ const negForamt = getNegativeFormat(negativeFormat);
+ const format = '%s%v';
+
let formattedBalance = parseFloat(balance);
- if (noCents) {
- formattedBalance = parseInt(formattedBalance, 10);
- }
if (divideOn1000) {
formattedBalance /= 1000;
}
- return formattedBalance + '';
+ return accounting.formatMoney(
+ formattedBalance,
+ money ? symbol : '',
+ precision,
+ thousand,
+ decimal,
+ {
+ pos: format,
+ neg: negForamt,
+ zero: excerptZero ? zeroSign : format,
+ }
+ );
};
const isBlank = (value) => {
- return _.isEmpty(value) && !_.isNumber(value) || _.isNaN(value);
-}
+ return (_.isEmpty(value) && !_.isNumber(value)) || _.isNaN(value);
+};
-function defaultToTransform(
- value,
- defaultOrTransformedValue,
- defaultValue,
-) {
+function defaultToTransform(value, defaultOrTransformedValue, defaultValue) {
const _defaultValue =
typeof defaultValue === 'undefined'
? defaultOrTransformedValue
@@ -248,7 +278,6 @@ function defaultToTransform(
: _transfromedValue;
}
-
export {
hashPassword,
origin,