diff --git a/client/src/components/Dashboard/Dashboard.js b/client/src/components/Dashboard/Dashboard.js
index 6ead8488c..c00bb07d4 100644
--- a/client/src/components/Dashboard/Dashboard.js
+++ b/client/src/components/Dashboard/Dashboard.js
@@ -2,6 +2,8 @@ import React from 'react';
import { Switch, Route } from 'react-router';
import { useQuery } from 'react-query';
+import 'style/pages/Dashboard/Dashboard.scss';
+
import DashboardLoadingIndicator from './DashboardLoadingIndicator';
import Sidebar from 'components/Sidebar/Sidebar';
@@ -15,8 +17,6 @@ import withSettingsActions from 'containers/Settings/withSettingsActions';
import { compose } from 'utils';
-import 'style/pages/Dashboard/Dashboard.scss';
-
/**
* Dashboard page.
*/
diff --git a/client/src/components/DataTable.js b/client/src/components/DataTable.js
index 6dd95ff0a..f808755d9 100644
--- a/client/src/components/DataTable.js
+++ b/client/src/components/DataTable.js
@@ -191,7 +191,7 @@ export default function DataTable({
// Renders table cell.
const RenderCell = useCallback(
- ({ row, cell, index }) => (
+ ({ row, cell, column, index }) => (
(
@@ -199,6 +199,7 @@ export default function DataTable({
style={{
'padding-left': `${row.depth * expandColumnSpace}rem`,
}}
+ className={'expend-padding'}
>
{children}
@@ -224,7 +225,14 @@ export default function DataTable({
/>
- {cell.render('Cell')}
+
+ (
+ { children }
+ )}>
+ {cell.render('Cell')}
+
),
[expandable, expandToggleColumn, expandColumnSpace],
@@ -276,7 +284,13 @@ export default function DataTable({
return (
diff --git a/client/src/components/Datatable/Cells.js b/client/src/components/Datatable/Cells.js
new file mode 100644
index 000000000..8c5efd370
--- /dev/null
+++ b/client/src/components/Datatable/Cells.js
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export function CellTextSpan({ cell: { value } }) {
+ return (
{ value })
+}
diff --git a/client/src/components/FinancialSheet.js b/client/src/components/FinancialSheet.js
index a46eafdf6..181747f35 100644
--- a/client/src/components/FinancialSheet.js
+++ b/client/src/components/FinancialSheet.js
@@ -3,9 +3,10 @@ import moment from 'moment';
import classnames from 'classnames';
import { FormattedMessage as T, useIntl } from 'react-intl';
+import 'style/pages/FinancialStatements/FinancialSheet.scss';
+
import { If, LoadingIndicator, MODIFIER } from 'components';
-import 'style/pages/FinancialStatements/FinancialSheet.scss';
export default function FinancialSheet({
companyName,
@@ -20,7 +21,8 @@ export default function FinancialSheet({
className,
basis,
minimal = false,
- fullWidth = false
+ fullWidth = false,
+ currentDate = true,
}) {
const { formatMessage } = useIntl();
const format = 'DD MMMM YYYY';
@@ -84,11 +86,19 @@ export default function FinancialSheet({
{children}
{accountingBasis}
- {basisLabel && (
-
- {basisLabel}
-
- )}
+
);
diff --git a/client/src/components/index.js b/client/src/components/index.js
index a11744c50..2b1316f8b 100644
--- a/client/src/components/index.js
+++ b/client/src/components/index.js
@@ -91,5 +91,5 @@ export {
InputPrependText,
PageFormBigNumber,
AccountsMultiSelect,
- DataTableEditable
+ DataTableEditable,
};
diff --git a/client/src/containers/Accounts/AccountsDataTable.js b/client/src/containers/Accounts/AccountsDataTable.js
index 2b91620d2..14ab2c163 100644
--- a/client/src/containers/Accounts/AccountsDataTable.js
+++ b/client/src/containers/Accounts/AccountsDataTable.js
@@ -27,8 +27,6 @@ import withAccounts from 'containers/Accounts/withAccounts';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withCurrentView from 'containers/Views/withCurrentView';
-import { accountNameAccessor } from './utils';
-
function AccountsDataTable({
// #withDashboardActions
accountsTable,
@@ -136,7 +134,7 @@ function AccountsDataTable({
{
id: 'name',
Header: formatMessage({ id: 'account_name' }),
- accessor: accountNameAccessor,
+ accessor: 'name',
className: 'account_name',
width: 220,
},
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js
index d3d74a593..88dc2f23f 100644
--- a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js
+++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js
@@ -38,6 +38,7 @@ function ReceivableAgingSummaryTable({
className: 'customer_name',
sticky: 'left',
width: 240,
+ textOverview: true,
},
{
Header: ,
diff --git a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js
index d1d14fac8..c3c2044d5 100644
--- a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js
+++ b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js
@@ -4,33 +4,12 @@ import classNames from 'classnames';
import FinancialSheet from 'components/FinancialSheet';
import DataTable from 'components/DataTable';
+import { CellTextSpan } from 'components/Datatable/Cells';
import withBalanceSheetDetail from './withBalanceSheetDetail';
import { compose, defaultExpanderReducer, getColumnWidth } from 'utils';
-// Total cell.
-function TotalCell({ cell }) {
- const row = cell.row.original;
-
- if (row.total) {
- return row.total.formatted_amount;
- }
- return '';
-}
-
-// Total period cell.
-const TotalPeriodCell = (index) => ({ cell }) => {
- const { original } = cell.row;
-
- if (original.total_periods && original.total_periods[index]) {
- const amount = original.total_periods[index].formatted_amount;
-
- return amount;
- }
- return '';
-};
-
/**
* Balance sheet table.
*/
@@ -52,14 +31,15 @@ function BalanceSheetTable({
Header: formatMessage({ id: 'account_name' }),
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
className: 'account_name',
+ textOverview: true,
width: 240,
},
...(balanceSheetQuery.display_columns_type === 'total'
? [
{
Header: formatMessage({ id: 'total' }),
- accessor: 'balance.formatted_amount',
- Cell: TotalCell,
+ accessor: 'total.formatted_amount',
+ Cell: CellTextSpan,
className: 'total',
width: 140,
},
@@ -69,8 +49,8 @@ function BalanceSheetTable({
? balanceSheetColumns.map((column, index) => ({
id: `date_period_${index}`,
Header: column,
- accessor: `total_periods[${index}]`,
- Cell: TotalPeriodCell(index),
+ Cell: CellTextSpan,
+ accessor: `total_periods[${index}].formatted_amount`,
className: classNames('total-period', `total-periods-${index}`),
width: getColumnWidth(
balanceSheetTableRows,
@@ -93,7 +73,7 @@ function BalanceSheetTable({
const { original } = row;
const rowTypes = Array.isArray(original.row_types)
? original.row_types
- : [];
+ : [original.row_types];
return {
...rowTypes.reduce((acc, rowType) => {
diff --git a/client/src/containers/FinancialStatements/FinancialAccountsFilter.js b/client/src/containers/FinancialStatements/FinancialAccountsFilter.js
index 233865ad0..68c6da790 100644
--- a/client/src/containers/FinancialStatements/FinancialAccountsFilter.js
+++ b/client/src/containers/FinancialStatements/FinancialAccountsFilter.js
@@ -14,6 +14,8 @@ import { CLASSES } from 'common/classes';
import { Col, Row, ListSelect, MODIFIER } from 'components';
import { filterAccountsOptions } from './common';
+
+
export default function FinancialAccountsFilter({ ...restProps }) {
const SUBMENU_POPOVER_MODIFIERS = {
flip: { boundariesElement: 'viewport', padding: 20 },
diff --git a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedger.js b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedger.js
index b36a53803..927582c62 100644
--- a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedger.js
+++ b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedger.js
@@ -49,6 +49,7 @@ function GeneralLedger({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
+ accountsFilter: 'with-transactions',
});
// Change page title of the dashboard.
diff --git a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js
index b9d3cccc8..7f7f455d1 100644
--- a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js
+++ b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js
@@ -1,16 +1,21 @@
import React from 'react';
-import { FormGroup, Classes } from '@blueprintjs/core';
+import {
+ FormGroup,
+ Classes,
+} from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
+import { compose } from 'redux';
import { AccountsMultiSelect, Row, Col } from 'components';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import RadiosAccountingBasis from '../RadiosAccountingBasis';
+import FinancialAccountsFilter from '../FinancialAccountsFilter';
import withAccounts from 'containers/Accounts/withAccounts';
-import { compose } from 'redux';
+import { filterAccountsOptions } from './common';
/**
* General ledger (GL) - Header - General panel.
@@ -22,7 +27,10 @@ function GeneralLedgerHeaderGeneralPane({
return (
-
+
{
- switch (row.rowType) {
- case ROW_TYPE.OPENING_BALANCE:
- return 'Opening Balance';
- case ROW_TYPE.CLOSING_BALANCE:
- return 'Closing Balance';
- default:
- return row.name;
- }
- };
-
- // Date accessor.
- const dateAccessor = (row) => {
- const TYPES = [
- ROW_TYPE.OPENING_BALANCE,
- ROW_TYPE.CLOSING_BALANCE,
- ROW_TYPE.TRANSACTION,
- ];
-
- return TYPES.indexOf(row.rowType) !== -1
- ? moment(row.date).format('DD MMM YYYY')
- : '';
- };
-
- // Amount cell
- const amountCell = useCallback(({ cell }) => {
- const transaction = cell.row.original;
-
- if (transaction.rowType === ROW_TYPE.ACCOUNT) {
- return !cell.row.isExpanded ? (
-
- ) : (
- ''
- );
- }
- return ;
- }, []);
-
const columns = useMemo(
() => [
{
- Header: formatMessage({ id: 'account_name' }),
- accessor: accountNameAccessor,
- className: 'name',
- width: 225,
+ Header: formatMessage({ id: 'date' }),
+ accessor: (row) => {
+ if (row.rowType === 'ACCOUNT_ROW') {
+ return (
+
+ {row.date}
+
+ );
+ }
+ return row.date;
+ },
+ className: 'date',
+ width: 120,
},
{
- Header: formatMessage({ id: 'date' }),
- accessor: dateAccessor,
- className: 'date',
- width: 115,
+ Header: formatMessage({ id: 'account_name' }),
+ accessor: 'name',
+ className: 'name',
+ textOverview: true,
+ // width: 200,
},
{
Header: formatMessage({ id: 'transaction_type' }),
- accessor: 'referenceType',
+ accessor: 'reference_type_formatted',
className: 'transaction_type',
- width: 145,
+ width: 125 ,
},
{
- Header: formatMessage({ id: 'trans_num' }),
+ Header: formatMessage({ id: 'transaction_number' }),
accessor: 'reference_id',
className: 'transaction_number',
- width: 110,
+ width: 100,
},
{
Header: formatMessage({ id: 'description' }),
accessor: 'note',
className: 'description',
- width: 145,
+ // width: 145,
+ },
+ {
+ Header: formatMessage({ id: 'credit' }),
+ accessor: 'formatted_credit',
+ className: 'credit',
+ width: getColumnWidth(generalLedgerTableRows, 'formatted_credit', {
+ minWidth: 100,
+ magicSpacing: 10,
+ }),
+ },
+ {
+ Header: formatMessage({ id: 'debit' }),
+ accessor: 'formatted_debit',
+ className: 'debit',
+ width: getColumnWidth(generalLedgerTableRows, 'formatted_debit', {
+ minWidth: 100,
+ magicSpacing: 10,
+ }),
},
{
Header: formatMessage({ id: 'amount' }),
- Cell: amountCell,
+ accessor: 'formatted_amount',
className: 'amount',
- width: 150,
+ width: getColumnWidth(generalLedgerTableRows, 'formatted_amount', {
+ minWidth: 100,
+ magicSpacing: 10,
+ }),
},
{
- Header: formatMessage({ id: 'balance' }),
- Cell: amountCell,
- className: 'balance',
- width: 150,
+ Header: formatMessage({ id: 'running_balance' }),
+ accessor: 'formatted_running_balance',
+ className: 'running_balance',
+ width: getColumnWidth(generalLedgerTableRows, 'formatted_running_balance', {
+ minWidth: 100,
+ magicSpacing: 10,
+ }),
},
],
- [],
+ [formatMessage, generalLedgerTableRows],
);
// Default expanded rows of general ledger table.
@@ -140,7 +139,7 @@ function GeneralLedgerTable({
rowClassNames={rowClassNames}
expanded={expandedRows}
virtualizedRows={true}
- fixedItemSize={37}
+ fixedItemSize={30}
fixedSizeHeight={1000}
expandable={true}
expandToggleColumn={1}
diff --git a/client/src/containers/FinancialStatements/GeneralLedger/common.js b/client/src/containers/FinancialStatements/GeneralLedger/common.js
new file mode 100644
index 000000000..76c57d092
--- /dev/null
+++ b/client/src/containers/FinancialStatements/GeneralLedger/common.js
@@ -0,0 +1,16 @@
+import { formatMessage } from 'services/intl';
+
+export const filterAccountsOptions = [
+ {
+ key: 'all-accounts',
+ name: formatMessage({ id: 'all_accounts' }),
+ hint: formatMessage({ id: 'all_accounts_including_with_zero_balance' }),
+ },
+ {
+ key: 'with-transactions',
+ name: formatMessage({ id: 'accounts_with_transactions' }),
+ hint: formatMessage({
+ id: 'include_accounts_once_has_transactions_on_given_date_period',
+ }),
+ },
+];
diff --git a/client/src/containers/FinancialStatements/Journal/JournalTable.js b/client/src/containers/FinancialStatements/Journal/JournalTable.js
index 93779678f..0c36b2661 100644
--- a/client/src/containers/FinancialStatements/Journal/JournalTable.js
+++ b/client/src/containers/FinancialStatements/Journal/JournalTable.js
@@ -8,7 +8,7 @@ import Money from 'components/Money';
import withJournal from './withJournal';
-import { compose, defaultExpanderReducer } from 'utils';
+import { compose, defaultExpanderReducer, getForceWidth } from 'utils';
function JournalSheetTable({
// #withJournal
@@ -22,70 +22,52 @@ function JournalSheetTable({
}) {
const { formatMessage } = useIntl();
- const rowTypeFilter = (rowType, value, types) => {
- return types.indexOf(rowType) === -1 ? '' : value;
- };
-
- const exceptRowTypes = (rowType, value, types) => {
- return types.indexOf(rowType) !== -1 ? '' : value;
- };
-
const columns = useMemo(
() => [
{
Header: formatMessage({ id: 'date' }),
- accessor: (r) =>
- rowTypeFilter(r.rowType, moment(r.date).format('YYYY MMM DD'), [
- 'first_entry',
- ]),
+ accessor: row => row.date ? moment(row.date).format('YYYY MMM DD') : '',
className: 'date',
- width: 85,
+ width: 100,
},
{
Header: formatMessage({ id: 'transaction_type' }),
- accessor: (r) =>
- rowTypeFilter(r.rowType, r.transaction_type, ['first_entry']),
+ accessor: 'reference_type_formatted',
className: 'reference_type_formatted',
- width: 145,
+ width: 120,
},
{
Header: formatMessage({ id: 'num' }),
- accessor: (r) =>
- rowTypeFilter(r.rowType, r.reference_id, ['first_entry']),
+ accessor: 'reference_id',
className: 'reference_id',
width: 70,
},
{
Header: formatMessage({ id: 'description' }),
accessor: 'note',
+ className: 'note'
},
{
Header: formatMessage({ id: 'acc_code' }),
- accessor: 'account.code',
+ accessor: 'account_code',
width: 95,
className: 'account_code',
},
{
Header: formatMessage({ id: 'account' }),
- accessor: 'account.name',
+ accessor: 'account_name',
+ className: 'account_name',
+ textOverview: true,
},
{
Header: formatMessage({ id: 'credit' }),
- accessor: (r) =>
- exceptRowTypes(
- r.rowType,
- ,
- ['space_entry'],
- ),
+ accessor: 'formatted_credit',
+ className: 'credit'
},
{
Header: formatMessage({ id: 'debit' }),
- accessor: (r) =>
- exceptRowTypes(
- r.rowType,
- ,
- ['space_entry'],
- ),
+ accessor: 'formatted_debit',
+ className: 'debit'
},
],
[formatMessage],
@@ -101,6 +83,20 @@ function JournalSheetTable({
// Default expanded rows of general journal table.
const expandedRows = useMemo(() => defaultExpanderReducer([], 1), []);
+ const rowClassNames = useCallback((row) => {
+ const { original } = row;
+ const rowTypes = Array.isArray(original.rowType)
+ ? original.rowType
+ : [original.rowType];
+
+ return {
+ ...rowTypes.reduce((acc, rowType) => {
+ acc[`row_type--${rowType}`] = rowType;
+ return acc;
+ }, {}),
+ };
+ }, []);
+
return (
+ >
(row.code ? `${row.name} - ${row.code}` : row.name),
className: 'name',
+ textOverview: true,
width: 240,
},
...(profitLossQuery.display_columns_type === 'total'
? [
{
Header: formatMessage({ id: 'total' }),
+ Cell: CellTextSpan,
accessor: 'total.formatted_amount',
className: 'total',
width: 140,
@@ -42,6 +44,7 @@ function ProfitLossSheetTable({
? profitLossColumns.map((column, index) => ({
id: `date_period_${index}`,
Header: column,
+ Cell: CellTextSpan,
accessor: `total_periods[${index}].formatted_amount`,
width: getColumnWidth(
profitLossTableRows,
diff --git a/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.js b/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.js
index cf60323a0..73395aac3 100644
--- a/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.js
+++ b/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.js
@@ -3,7 +3,7 @@ import { useIntl } from 'react-intl';
import FinancialSheet from 'components/FinancialSheet';
import DataTable from 'components/DataTable';
-import Money from 'components/Money';
+import { CellTextSpan } from 'components/Datatable/Cells';
import withTrialBalance from './withTrialBalance';
@@ -28,9 +28,11 @@ function TrialBalanceSheetTable({
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
className: 'name',
width: 160,
+ textOverview: true,
},
{
Header: formatMessage({ id: 'credit' }),
+ Cell: CellTextSpan,
accessor: 'formatted_credit',
className: 'credit',
width: getColumnWidth(trialBalanceTableRows, `credit`, {
@@ -39,11 +41,13 @@ function TrialBalanceSheetTable({
},
{
Header: formatMessage({ id: 'debit' }),
+ Cell: CellTextSpan,
accessor: 'formatted_debit',
width: getColumnWidth(trialBalanceTableRows, `debit`, { minWidth: 95 }),
},
{
Header: formatMessage({ id: 'balance' }),
+ Cell: CellTextSpan,
accessor: 'formatted_balance',
className: 'balance',
width: getColumnWidth(trialBalanceTableRows, `balance`, {
@@ -56,7 +60,7 @@ function TrialBalanceSheetTable({
const rowClassNames = (row) => {
const { original } = row;
- const rowTypes = Array.isArray(original.rowTypes) ? original.rowTypes : [];
+ const rowTypes = Array.isArray(original.rowType) ? original.rowType : [original.rowType];
return {
...rowTypes.reduce((acc, rowType) => {
diff --git a/client/src/containers/FinancialStatements/common.js b/client/src/containers/FinancialStatements/common.js
index a4ea8fc64..216e06ab4 100644
--- a/client/src/containers/FinancialStatements/common.js
+++ b/client/src/containers/FinancialStatements/common.js
@@ -1,4 +1,4 @@
-import { mapKeys, omit, snakeCase } from 'lodash';
+import { omit } from 'lodash';
import { transformToCamelCase, flatObject } from 'utils';
import { formatMessage } from 'services/intl';
diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js
index 02bb0bb5b..c9d834ca3 100644
--- a/client/src/lang/en/index.js
+++ b/client/src/lang/en/index.js
@@ -970,5 +970,7 @@ export default {
'You could not delete item that has associated inventory adjustments transactions',
format: 'Format',
current: 'Current',
- adjustment_reasons: 'Adjustment reasons'
+ adjustment_reasons: 'Adjustment reasons',
+ transaction_number: 'Transaction #',
+ running_balance: 'Running balance'
};
diff --git a/client/src/store/financialStatement/financialStatements.mappers.js b/client/src/store/financialStatement/financialStatements.mappers.js
index dd8d4e419..bc21867f5 100644
--- a/client/src/store/financialStatement/financialStatements.mappers.js
+++ b/client/src/store/financialStatement/financialStatements.mappers.js
@@ -1,14 +1,8 @@
-import { omit } from 'lodash';
+import { omit, chain } from 'lodash';
+import moment from 'moment';
export const mapBalanceSheetToTableRows = (accounts) => {
return accounts.map((account) => {
- const PRIMARY_SECTIONS = ['assets', 'liability', 'equity'];
- const rowTypes = [
- 'total_row',
- ...(PRIMARY_SECTIONS.indexOf(account.section_type) !== -1
- ? ['total_assets']
- : []),
- ];
return {
...account,
children: mapBalanceSheetToTableRows([
@@ -31,51 +25,79 @@ export const mapBalanceSheetToTableRows = (accounts) => {
};
export const journalToTableRowsMapper = (journal) => {
- return journal.reduce((rows, journal) => {
- journal.entries.forEach((entry, index) => {
- rows.push({
- ...entry,
- rowType: index === 0 ? 'first_entry' : 'entry',
- });
- });
- rows.push({
- credit: journal.credit,
- debit: journal.debit,
- rowType: 'entries_total',
- });
- rows.push({
- rowType: 'space_entry',
- });
- return rows;
- }, []);
+ const TYPES = {
+ ENTRY: 'ENTRY',
+ TOTAL_ENTRIES: 'TOTAL_ENTRIES',
+ EMPTY_ROW: 'EMPTY_ROW',
+ };
+
+ const entriesMapper = (transaction) => {
+ return transaction.entries.map((entry, index) => ({
+ ...(index === 0
+ ? {
+ date: transaction.date,
+ reference_type: transaction.reference_type,
+ reference_id: transaction.reference_id,
+ reference_type_formatted: transaction.reference_type_formatted,
+ }
+ : {}),
+ rowType: TYPES.ENTRY,
+ ...entry,
+ }));
+ };
+
+ return chain(journal)
+ .map((transaction) => {
+ const entries = entriesMapper(transaction);
+
+ return [
+ ...entries,
+ {
+ rowType: TYPES.TOTAL_ENTRIES,
+ currency_code: transaction.currency_code,
+ credit: transaction.credit,
+ debit: transaction.debit,
+ formatted_credit: transaction.formatted_credit,
+ formatted_debit: transaction.formatted_debit,
+ },
+ {
+ rowType: TYPES.EMPTY_ROW,
+ },
+ ];
+ })
+ .flatten()
+ .value();
};
-
export const generalLedgerToTableRows = (accounts) => {
- return accounts.reduce((tableRows, account) => {
- const children = [];
- children.push({
- ...account.opening,
- rowType: 'opening_balance',
- });
- account.transactions.map((transaction) => {
- children.push({
- ...transaction,
- ...omit(account, ['transactions']),
- rowType: 'transaction',
- });
- });
- children.push({
- ...account.closing,
- rowType: 'closing_balance',
- });
- tableRows.push({
- ...omit(account, ['transactions']),
- children,
- rowType: 'account_name',
- });
- return tableRows;
- }, []);
+ return chain(accounts)
+ .map((account) => {
+ return {
+ name: '',
+ code: account.code,
+ rowType: 'ACCOUNT_ROW',
+ date: account.name,
+ children: [
+ {
+ ...account.opening_balance,
+ name: 'Opening balance',
+ rowType: 'OPENING_BALANCE',
+ },
+ ...account.transactions.map((transaction) => ({
+ ...transaction,
+ name: account.name,
+ code: account.code,
+ date: moment(transaction.date).format('DD MMM YYYY'),
+ })),
+ {
+ ...account.closing_balance,
+ name: 'Closing balance',
+ rowType: 'CLOSING_BALANCE',
+ },
+ ],
+ };
+ })
+ .value();
};
export const ARAgingSummaryTableRowsMapper = (sheet, total) => {
@@ -109,25 +131,32 @@ export const ARAgingSummaryTableRowsMapper = (sheet, total) => {
current: sheet.total.current.formatted_amount,
...mapAging(sheet.total.aging),
total: sheet.total.total.formatted_amount,
- }
- ];
-};
-
-export const mapTrialBalanceSheetToRows = (sheet) => {
- return [
- ...sheet.accounts,
- {
- name: 'Total',
- rowTypes: ['total'],
- ...sheet.total,
},
];
};
-export const profitLossToTableRowsMapper = (profitLoss) => {
+export const mapTrialBalanceSheetToRows = (sheet) => {
+ const results = [];
- return [
- {
+ if (sheet.accounts) {
+ sheet.accounts.forEach((account) => {
+ results.push(account);
+ });
+ }
+ if (sheet.total) {
+ results.push({
+ rowType: 'total',
+ ...sheet.total,
+ });
+ }
+ return results;
+};
+
+export const profitLossToTableRowsMapper = (profitLoss) => {
+ const results = [];
+
+ if (profitLoss.income) {
+ results.push({
name: 'Income',
total: profitLoss.income.total,
children: [
@@ -140,8 +169,10 @@ export const profitLossToTableRowsMapper = (profitLoss) => {
},
],
total_periods: profitLoss.income.total_periods,
- },
- {
+ });
+ }
+ if (profitLoss.cost_of_sales) {
+ results.push({
name: 'Cost of sales',
total: profitLoss.cost_of_sales.total,
children: [
@@ -153,15 +184,19 @@ export const profitLossToTableRowsMapper = (profitLoss) => {
rowTypes: ['cogs_total', 'section_total', 'total'],
},
],
- total_periods: profitLoss.cost_of_sales.total_periods
- },
- {
+ total_periods: profitLoss.cost_of_sales.total_periods,
+ });
+ }
+ if (profitLoss.gross_profit) {
+ results.push({
name: 'Gross profit',
total: profitLoss.gross_profit.total,
total_periods: profitLoss.gross_profit.total_periods,
rowTypes: ['gross_total', 'section_total', 'total'],
- },
- {
+ })
+ }
+ if (profitLoss.expenses) {
+ results.push({
name: 'Expenses',
total: profitLoss.expenses.total,
children: [
@@ -174,14 +209,34 @@ export const profitLossToTableRowsMapper = (profitLoss) => {
},
],
total_periods: profitLoss.expenses.total_periods,
- },
- {
+ })
+ }
+ if (profitLoss.operating_profit) {
+ results.push({
name: 'Net Operating income',
total: profitLoss.operating_profit.total,
total_periods: profitLoss.income.total_periods,
rowTypes: ['net_operating_total', 'section_total', 'total'],
- },
- {
+ })
+ }
+ if (profitLoss.other_income) {
+ results.push({
+ name: 'Other Income',
+ total: profitLoss.other_income.total,
+ total_periods: profitLoss.other_income.total_periods,
+ children: [
+ ...profitLoss.other_income.accounts,
+ {
+ name: 'Total other income',
+ total: profitLoss.other_income.total,
+ total_periods: profitLoss.other_income.total_periods,
+ rowTypes: ['expenses_total', 'section_total', 'total'],
+ },
+ ],
+ });
+ }
+ if (profitLoss.other_expenses) {
+ results.push({
name: 'Other expenses',
total: profitLoss.other_expenses.total,
total_periods: profitLoss.other_expenses.total_periods,
@@ -194,12 +249,15 @@ export const profitLossToTableRowsMapper = (profitLoss) => {
rowTypes: ['expenses_total', 'section_total', 'total'],
},
],
- },
- {
+ });
+ }
+ if (profitLoss.net_income) {
+ results.push({
name: 'Net Income',
total: profitLoss.net_income.total,
total_periods: profitLoss.net_income.total_periods,
rowTypes: ['net_income_total', 'section_total', 'total'],
- },
- ];
-};
\ No newline at end of file
+ })
+ };
+ return results;
+};
diff --git a/client/src/style/components/DataTable/DataTable.scss b/client/src/style/components/DataTable/DataTable.scss
index c9fb01445..1724bb489 100644
--- a/client/src/style/components/DataTable/DataTable.scss
+++ b/client/src/style/components/DataTable/DataTable.scss
@@ -27,6 +27,12 @@
color: #58667b;
font-weight: 500;
border-bottom: 1px solid rgb(224, 224, 224);
+
+ > div{
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
}
.sort-icon {
width: 0;
@@ -141,10 +147,23 @@
.placeholder {
color: #a0a0a0;
}
+ .text-overview{
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ }
.bp3-form-group {
width: 100%;
}
+
+ &.is-text-overview {
+ .expend-padding{
+ display: flex;
+ width: 100%;
+ }
+ }
}
.tr:hover .td {
background: #f3f7fc;
diff --git a/client/src/style/containers/FinancialStatements/FinancialSheet.scss b/client/src/style/containers/FinancialStatements/FinancialSheet.scss
index b2bf7c1e4..18b3c7957 100644
--- a/client/src/style/containers/FinancialStatements/FinancialSheet.scss
+++ b/client/src/style/containers/FinancialStatements/FinancialSheet.scss
@@ -60,12 +60,17 @@
display: none;
}
}
- &__basis {
+ &__footer {
color: #888;
text-align: center;
margin-top: auto;
padding-top: 18px;
font-size: 13px;
+
+
+ > span + span{
+ padding-left: 10px;
+ }
}
.dashboard__loading-indicator {
margin: auto;
diff --git a/client/src/style/pages/FinancialStatements/ARAgingSummary.scss b/client/src/style/pages/FinancialStatements/ARAgingSummary.scss
index c331c9511..afdc364cc 100644
--- a/client/src/style/pages/FinancialStatements/ARAgingSummary.scss
+++ b/client/src/style/pages/FinancialStatements/ARAgingSummary.scss
@@ -13,20 +13,22 @@
}
}
.tbody{
- .tr .td{
- border-bottom: 0;
- padding-top: 0.4rem;
- padding-bottom: 0.4rem;
- }
- .tr:not(:first-child) .td{
- border-top: 1px solid transparent;
- }
- .tr.row-type--total{
- font-weight: 500;
-
+ .tr:not(.no-results) {
.td{
- border-top: 1px solid #333;
- border-bottom: 3px double #333;
+ border-bottom: 0;
+ padding-top: 0.4rem;
+ padding-bottom: 0.4rem;
+ }
+ &.row-type--total{
+ font-weight: 500;
+
+ .td{
+ border-top: 1px solid #333;
+ border-bottom: 3px double #333;
+ }
+ }
+ &:not(:first-child) .td{
+ border-top: 1px solid transparent;
}
}
}
diff --git a/client/src/style/pages/FinancialStatements/BalanceSheet.scss b/client/src/style/pages/FinancialStatements/BalanceSheet.scss
index 35e909077..e145fd445 100644
--- a/client/src/style/pages/FinancialStatements/BalanceSheet.scss
+++ b/client/src/style/pages/FinancialStatements/BalanceSheet.scss
@@ -36,7 +36,7 @@
.tr.is-expanded{
.td.total,
.td.total-period{
- > span{
+ > span.cell-text{
display: none;
}
}
diff --git a/client/src/style/pages/FinancialStatements/FinancialSheet.scss b/client/src/style/pages/FinancialStatements/FinancialSheet.scss
index f3e7530ba..5c37bfdd3 100644
--- a/client/src/style/pages/FinancialStatements/FinancialSheet.scss
+++ b/client/src/style/pages/FinancialStatements/FinancialSheet.scss
@@ -3,9 +3,17 @@
&--financial-report{
.table {
+ .tbody{
+
+ .tr.no-results {
+ .td{
+ border-bottom: 1px solid #DDD;
+ }
+ }
+ }
.thead{
.tr .th{
- background: transparent;
+ background-color: #fff;
border-top: 1px solid #666;
border-bottom: 1px solid #666;
diff --git a/client/src/style/pages/FinancialStatements/GeneralLedger.scss b/client/src/style/pages/FinancialStatements/GeneralLedger.scss
index f3fd1fa6f..96dd9a8a9 100644
--- a/client/src/style/pages/FinancialStatements/GeneralLedger.scss
+++ b/client/src/style/pages/FinancialStatements/GeneralLedger.scss
@@ -2,25 +2,64 @@
.financial-sheet{
&--general-ledger{
.financial-sheet__table{
+ .tbody,
+ .thead{
+ .tr .td,
+ .tr .th{
+ &.credit,
+ &.debit,
+ &.running_balance,
+ &.amount{
+ justify-content: flex-end;
+ }
+ }
+ }
.tbody{
+
+ .tr .td{
+ padding-top: 0.2rem;
+ padding-bottom: 0.2rem;
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+
+ &.date{
+ > div{
+ display: flex;
+ }
+ span.force-width{
+ position: relative;
+ }
+ }
+ }
+ .tr:not(.no-results) .td{
+ border-left: 1px solid #ececec;
+ }
.tr.row-type{
- &--opening_balance,
- &--closing_balance{
+
+ &--ACCOUNT_ROW{
.td{
- border-top: 1px solid #333;
+ &.date{
+ font-weight: 500;
+ }
+ &.name{
+ border-left-color: transparent;
+ }
}
- .name,
- .amount,
- .balance{
+ &:not(:first-child).is-expanded .td{
+ border-top: 1px solid #DDD;
+ }
+ }
+ &--OPENING_BALANCE,
+ &--CLOSING_BALANCE{
+ .amount{
font-weight: 500;
}
}
- &--closing_balance .td{
- border-bottom-color: #666;
- }
- &--account_name .td.name{
- font-weight: 500;
+ &--CLOSING_BALANCE{
+ .name{
+ font-weight: 500;
+ }
}
}
}
diff --git a/client/src/style/pages/FinancialStatements/Journal.scss b/client/src/style/pages/FinancialStatements/Journal.scss
index 7447d752b..cd7792e9b 100644
--- a/client/src/style/pages/FinancialStatements/Journal.scss
+++ b/client/src/style/pages/FinancialStatements/Journal.scss
@@ -3,19 +3,39 @@
&--journal{
.financial-sheet__table{
-
+ .tr .td.credit,
+ .tr .th.credit,
+ .tr .td.debit,
+ .tr .th.debit{
+ justify-content: flex-end;
+ }
.tbody{
.tr:not(.no-results) .td{
- padding: 0.4rem;
+ padding: 0.3rem 0.4rem;
color: #000;
border-bottom-color: transparent;
- min-height: 32px;
+ min-height: 28px;
border-left: 1px solid #ececec;
&:first-of-type{
border-left: 0;
}
+ &.account_name,
+ &.reference_type_formatted{
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow:ellipsis;
+ }
}
+ .tr:not(.no-results):last-child{
+ .td{
+ border-bottom: 1px solid #dbdbdb;
+ }
+ }
+ .tr.row_type--TOTAL_ENTRIES{
+ font-weight: 600;
+ }
+
}
}
}
diff --git a/client/src/utils.js b/client/src/utils.js
index 0ba62b39f..a68ea7bae 100644
--- a/client/src/utils.js
+++ b/client/src/utils.js
@@ -389,6 +389,16 @@ export const getColumnWidth = (
return result;
};
+export const getForceWidth = (
+ text,
+ magicSpacing = 14,
+) => {
+ const textLength = text.length;
+ const result = textLength * magicSpacing
+
+ return result;
+}
+
export const toSafeNumber = (number) => {
return _.toNumber(_.defaultTo(number, 0));
};
diff --git a/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/server/src/api/controllers/FinancialStatements/JournalSheet.ts
index f832fedf6..350a80dbb 100644
--- a/server/src/api/controllers/FinancialStatements/JournalSheet.ts
+++ b/server/src/api/controllers/FinancialStatements/JournalSheet.ts
@@ -16,7 +16,8 @@ export default class JournalSheetController extends BaseFinancialReportControlle
router() {
const router = Router();
- router.get('/',
+ router.get(
+ '/',
this.journalValidationSchema,
this.validationResult,
this.asyncMiddleware(this.journal.bind(this))
@@ -31,18 +32,20 @@ export default class JournalSheetController extends BaseFinancialReportControlle
return [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
- oneOf([
- query('transaction_types').optional().isArray({ min: 1 }),
- query('transaction_types.*').optional().isNumeric().toInt(),
- ], [
- query('transaction_types').optional().trim().escape(),
- ]),
- oneOf([
- query('account_ids').optional().isArray({ min: 1 }),
- query('account_ids.*').optional().isNumeric().toInt(),
- ], [
- query('account_ids').optional().isNumeric().toInt(),
- ]),
+ oneOf(
+ [
+ query('transaction_types').optional().isArray({ min: 1 }),
+ query('transaction_types.*').optional().isNumeric().toInt(),
+ ],
+ [query('transaction_types').optional().trim().escape()]
+ ),
+ oneOf(
+ [
+ query('account_ids').optional().isArray({ min: 1 }),
+ query('account_ids.*').optional().isNumeric().toInt(),
+ ],
+ [query('account_ids').optional().isNumeric().toInt()]
+ ),
query('from_range').optional().isNumeric().toInt(),
query('to_range').optional().isNumeric().toInt(),
query('number_format.no_cents').optional().isBoolean().toBoolean(),
@@ -52,7 +55,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle
/**
* Retrieve the ledger report of the given account.
- * @param {Request} req -
+ * @param {Request} req -
* @param {Response} res -
*/
async journal(req: Request, res: Response, next: NextFunction) {
@@ -63,11 +66,20 @@ export default class JournalSheetController extends BaseFinancialReportControlle
...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.journalService.journalSheet(tenantId, filter);
+ const { data, query } = await this.journalService.journalSheet(
+ tenantId,
+ filter
+ );
return res.status(200).send({
organization_name: organizationName,
@@ -79,4 +91,4 @@ export default class JournalSheetController extends BaseFinancialReportControlle
next(error);
}
}
-}
\ No newline at end of file
+}
diff --git a/server/src/data/BalanceSheetStructure.ts b/server/src/data/BalanceSheetStructure.ts
index d57a58d9c..bdb785000 100644
--- a/server/src/data/BalanceSheetStructure.ts
+++ b/server/src/data/BalanceSheetStructure.ts
@@ -8,19 +8,42 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [
children: [
{
name: 'Current Asset',
- type: 'accounts_section',
- accountsTypesRelated: ['current_asset'],
+ sectionType: 'assets',
+ type: 'section',
+ children: [
+ {
+ name: 'Cash and cash equivalents',
+ type: 'accounts_section',
+ accountsTypes: ['cash', 'bank'],
+ },
+ {
+ name: 'Accounts Receivable',
+ type: 'accounts_section',
+ accountsTypes: ['accounts_receivable'],
+ },
+ {
+ name: 'Inventories',
+ type: 'accounts_section',
+ accountsTypes: ['inventory'],
+ },
+ {
+ name: 'Other current assets',
+ type: 'accounts_section',
+ accountsTypes: ['other_current_asset'],
+ },
+ ],
+ alwaysShow: true,
},
{
name: 'Fixed Asset',
type: 'accounts_section',
- accountsTypesRelated: ['fixed_asset'],
+ accountsTypes: ['fixed_asset'],
},
{
- name: 'Other Asset',
+ name: 'Non-Current Assets',
type: 'accounts_section',
- accountsTypesRelated: ['other_asset'],
- },
+ accountsTypes: ['non_current_asset'],
+ }
],
alwaysShow: true,
},
@@ -35,27 +58,32 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [
type: 'section',
children: [
{
- name: 'Current Liability',
+ name: 'Current Liabilties',
type: 'accounts_section',
- accountsTypesRelated: ['current_liability'],
+ accountsTypes: [
+ 'accounts_payable',
+ 'tax_payable',
+ 'credit_card',
+ 'other_current_liability'
+ ],
},
{
- name: 'Long Term Liability',
+ name: 'Long-Term Liabilities',
type: 'accounts_section',
- accountsTypesRelated: ['long_term_liability'],
+ accountsTypes: ['long_term_liability'],
},
{
- name: 'Other Liability',
+ name: 'Non-Current Liabilities',
type: 'accounts_section',
- accountsTypesRelated: ['other_liability'],
- },
+ accountsTypes: ['non_current_liability'],
+ }
],
},
{
name: 'Equity',
sectionType: 'equity',
type: 'accounts_section',
- accountsTypesRelated: ['equity'],
+ accountsTypes: ['equity'],
},
],
alwaysShow: true,
diff --git a/server/src/database/seeds/data/accounts_types.js b/server/src/database/seeds/data/accounts_types.js
index 11c2a5f13..b38da463a 100644
--- a/server/src/database/seeds/data/accounts_types.js
+++ b/server/src/database/seeds/data/accounts_types.js
@@ -53,7 +53,7 @@ export default [
key: 'non_current_asset',
normal: 'debit',
root_type: 'asset',
- child_type: 'non_current_asset',
+ child_type: 'fixed_asset',
balance_sheet: true,
income_sheet: false,
},
@@ -81,14 +81,6 @@ export default [
balance_sheet: true,
income_sheet: false,
},
- {
- key: 'long_term_liability',
- normal: 'credit',
- root_type: 'liability',
- child_type: 'long_term_liability',
- balance_sheet: false,
- income_sheet: true,
- },
{
key: 'other_current_liability',
normal: 'credit',
@@ -97,6 +89,22 @@ export default [
balance_sheet: false,
income_sheet: true,
},
+ {
+ key: 'non_current_liability',
+ normal: 'credit',
+ root_type: 'liability',
+ child_type: 'current_liability',
+ balance_sheet: false,
+ income_sheet: true,
+ },
+ {
+ key: 'long_term_liability',
+ normal: 'credit',
+ root_type: 'liability',
+ child_type: 'long_term_liability',
+ balance_sheet: false,
+ income_sheet: true,
+ },
{
key: 'equity',
normal: 'credit',
diff --git a/server/src/interfaces/BalanceSheet.ts b/server/src/interfaces/BalanceSheet.ts
index f9a8e631d..cf330b1dc 100644
--- a/server/src/interfaces/BalanceSheet.ts
+++ b/server/src/interfaces/BalanceSheet.ts
@@ -42,7 +42,7 @@ export interface IBalanceSheetStructureSection {
sectionType?: string;
type: 'section' | 'accounts_section';
children?: IBalanceSheetStructureSection[];
- accountsTypesRelated?: string[];
+ accountsTypes?: string[];
alwaysShow?: boolean;
}
@@ -74,6 +74,6 @@ export interface IBalanceSheetSection {
total: IBalanceSheetAccountTotal;
totalPeriods?: IBalanceSheetAccountTotal[];
- accountsTypesRelated?: string[];
+ accountsTypes?: string[];
_forceShow?: boolean;
}
diff --git a/server/src/interfaces/GeneralLedgerSheet.ts b/server/src/interfaces/GeneralLedgerSheet.ts
index 5c75a1137..84d0cc607 100644
--- a/server/src/interfaces/GeneralLedgerSheet.ts
+++ b/server/src/interfaces/GeneralLedgerSheet.ts
@@ -14,13 +14,26 @@ export interface IGeneralLedgerSheetQuery {
export interface IGeneralLedgerSheetAccountTransaction {
id: number,
+
amount: number,
+ runningBalance: number,
+ credit: number,
+ debit: number,
+
formattedAmount: string,
+ formattedCredit: string,
+ formattedDebit: string,
+ formattedRunningBalance: string,
+
currencyCode: string,
note?: string,
+
transactionType?: string,
+ transactionNumber: string,
+
referenceId?: number,
referenceType?: string,
+
date: Date|string,
};
@@ -38,8 +51,8 @@ export interface IGeneralLedgerSheetAccount {
index: number,
parentAccountId: number,
transactions: IGeneralLedgerSheetAccountTransaction[],
- opening: IGeneralLedgerSheetAccountBalance,
- closing: IGeneralLedgerSheetAccountBalance,
+ openingBalance: IGeneralLedgerSheetAccountBalance,
+ closingBalance: IGeneralLedgerSheetAccountBalance,
}
export interface IAccountTransaction {
diff --git a/server/src/interfaces/ProfitLossSheet.ts b/server/src/interfaces/ProfitLossSheet.ts
index da0b81059..f070c1f1b 100644
--- a/server/src/interfaces/ProfitLossSheet.ts
+++ b/server/src/interfaces/ProfitLossSheet.ts
@@ -50,7 +50,7 @@ export interface IProfitLossSheetStatement {
costOfSales: IProfitLossSheetAccountsSection,
expenses: IProfitLossSheetAccountsSection,
otherExpenses: IProfitLossSheetAccountsSection,
-
+ otherIncome: IProfitLossSheetAccountsSection,
netIncome: IProfitLossSheetTotalSection;
operatingProfit: IProfitLossSheetTotalSection;
grossProfit: IProfitLossSheetTotalSection;
diff --git a/server/src/models/AccountType.js b/server/src/models/AccountType.js
index 452ac71d9..8d16b1dee 100644
--- a/server/src/models/AccountType.js
+++ b/server/src/models/AccountType.js
@@ -58,23 +58,23 @@ export default class AccountType extends TenantModel {
static get labels() {
return {
inventory: 'Inventory',
- other_current_asset: 'Other current asset',
- bank: 'Bank account',
+ other_current_asset: 'Other Current Asset',
+ bank: 'Bank Account',
cash: 'Cash',
- fixed_asset: 'Fixed asset',
- non_current_asset: 'Non-current asset',
- accounts_payable: 'Accounts payable (A/P)',
- accounts_receivable: 'Accounts receivable (A/R)',
- credit_card: 'Credit card',
- long_term_liability: 'Long term liability',
- other_current_liability: 'Other current liability',
- other_liability: 'Other liability',
+ fixed_asset: 'Fixed Asset',
+ non_current_asset: 'Non-Current Asset',
+ accounts_payable: 'Accounts Payable (A/P)',
+ accounts_receivable: 'Accounts Receivable (A/R)',
+ credit_card: 'Credit Card',
+ long_term_liability: 'Long Term Liability',
+ other_current_liability: 'Other Current Liability',
+ other_liability: 'Other Liability',
equity: "Equity",
expense: "Expense",
income: "Income",
- other_income: "Other income",
- other_expense: "Other expense",
- cost_of_goods_sold: "Cost of goods sold (COGS)",
+ other_income: "Other Income",
+ other_expense: "Other Expense",
+ cost_of_goods_sold: "Cost of Goods Sold (COGS)",
};
}
}
diff --git a/server/src/services/Accounting/JournalPoster.ts b/server/src/services/Accounting/JournalPoster.ts
index 9d33e8461..ddf8e855a 100644
--- a/server/src/services/Accounting/JournalPoster.ts
+++ b/server/src/services/Accounting/JournalPoster.ts
@@ -71,6 +71,13 @@ export default class JournalPoster implements IJournalPoster {
}
}
+ /**
+ *
+ */
+ public isEmpty() {
+ return this.entries.length === 0;
+ }
+
/**
* Writes the credit entry for the given account.
* @param {IJournalEntry} entry -
diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts
index 6c36c3c31..62141bb7a 100644
--- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts
+++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts
@@ -184,7 +184,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
const filteredAccounts = accounts
// Filter accounts that associated to the section accounts types.
.filter(
- (account) => sectionAccountsTypes.indexOf(account.type.childType) !== -1
+ (account) => sectionAccountsTypes.indexOf(account.type.key) !== -1
)
.map((account) => this.balanceSheetAccountMapper(account))
// Filter accounts that have no transaction when `noneTransactions` is on.
@@ -258,7 +258,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
type: structure.type,
...(structure.type === 'accounts_section'
? this.structureRelatedAccountsMapper(
- structure.accountsTypesRelated,
+ structure.accountsTypes,
accounts
)
: this.structureSectionMapper(structure, accounts)),
diff --git a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts
index 98b6a3c2d..48df05ef8 100644
--- a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts
+++ b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts
@@ -1,4 +1,4 @@
-import { pick } from 'lodash';
+import { pick, get, last } from 'lodash';
import {
IGeneralLedgerSheetQuery,
IGeneralLedgerSheetAccount,
@@ -8,9 +8,13 @@ import {
IJournalPoster,
IAccountType,
IJournalEntry,
+ IContact,
} from 'interfaces';
import FinancialSheet from '../FinancialSheet';
+/**
+ * General ledger sheet.
+ */
export default class GeneralLedgerSheet extends FinancialSheet {
tenantId: number;
accounts: IAccount[];
@@ -18,6 +22,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
openingBalancesJournal: IJournalPoster;
closingBalancesJournal: IJournalPoster;
transactions: IJournalPoster;
+ contactsMap: Map;
baseCurrency: string;
/**
@@ -32,6 +37,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
tenantId: number,
query: IGeneralLedgerSheetQuery,
accounts: IAccount[],
+ contactsByIdMap: Map,
transactions: IJournalPoster,
openingBalancesJournal: IJournalPoster,
closingBalancesJournal: IJournalPoster,
@@ -43,48 +49,100 @@ export default class GeneralLedgerSheet extends FinancialSheet {
this.query = query;
this.numberFormat = this.query.numberFormat;
this.accounts = accounts;
+ this.contactsMap = contactsByIdMap;
this.transactions = transactions;
this.openingBalancesJournal = openingBalancesJournal;
this.closingBalancesJournal = closingBalancesJournal;
this.baseCurrency = baseCurrency;
}
+ /**
+ * Retrieve the transaction amount.
+ * @param {number} credit - Credit amount.
+ * @param {number} debit - Debit amount.
+ * @param {string} normal - Credit or debit.
+ */
+ getAmount(credit: number, debit: number, normal: string) {
+ return normal === 'credit' ? credit - debit : debit - credit;
+ }
+
+ /**
+ * Entry mapper.
+ * @param {IJournalEntry} entry -
+ * @return {IGeneralLedgerSheetAccountTransaction}
+ */
+ entryReducer(
+ entries: IGeneralLedgerSheetAccountTransaction[],
+ entry: IJournalEntry,
+ index: number
+ ): IGeneralLedgerSheetAccountTransaction[] {
+ const lastEntry = last(entries);
+ const openingBalance = 0;
+
+ const contact = this.contactsMap.get(entry.contactId);
+ const amount = this.getAmount(
+ entry.credit,
+ entry.debit,
+ entry.accountNormal
+ );
+ const runningBalance =
+ (entries.length === 0
+ ? openingBalance
+ : lastEntry
+ ? lastEntry.runningBalance
+ : 0) + amount;
+
+ const newEntry = {
+ date: entry.date,
+ entryId: entry.id,
+
+ referenceType: entry.referenceType,
+ referenceId: entry.referenceId,
+ referenceTypeFormatted: entry.referenceTypeFormatted,
+
+ contactName: get(contact, 'displayName'),
+ contactType: get(contact, 'contactService'),
+
+ transactionType: entry.transactionType,
+ index: entry.index,
+ note: entry.note,
+
+ credit: entry.credit,
+ debit: entry.debit,
+ amount,
+ runningBalance,
+
+ formattedAmount: this.formatNumber(amount),
+ formattedCredit: this.formatNumber(entry.credit),
+ formattedDebit: this.formatNumber(entry.debit),
+ formattedRunningBalance: this.formatNumber(runningBalance),
+
+ currencyCode: this.baseCurrency,
+ };
+ entries.push(newEntry);
+
+ return entries;
+ }
+
/**
* Mapping the account transactions to general ledger transactions of the given account.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccountTransaction[]}
*/
private accountTransactionsMapper(
- account: IAccount & { type: IAccountType }
+ account: IAccount & { type: IAccountType },
+ openingBalance: number
): IGeneralLedgerSheetAccountTransaction[] {
const entries = this.transactions.getAccountEntries(account.id);
- 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;
- }
- const formattedAmount = this.formatNumber(amount);
-
- return {
- ...pick(transaction, [
- 'id',
- 'note',
- 'transactionType',
- 'referenceType',
- 'referenceId',
- 'referenceTypeFormatted',
- 'date',
- ]),
- amount,
- formattedAmount,
- currencyCode: this.baseCurrency,
- };
- }
+ return entries.reduce(
+ (
+ entries: IGeneralLedgerSheetAccountTransaction[],
+ entry: IJournalEntry
+ ) => {
+ return this.entryReducer(entries, entry, openingBalance);
+ },
+ []
);
}
@@ -128,11 +186,21 @@ export default class GeneralLedgerSheet extends FinancialSheet {
private accountMapper(
account: IAccount & { type: IAccountType }
): IGeneralLedgerSheetAccount {
+ const openingBalance = this.accountOpeningBalance(account);
+ const closingBalance = this.accountClosingBalance(account);
+
return {
- ...pick(account, ['id', 'name', 'code', 'index', 'parentAccountId']),
- opening: this.accountOpeningBalance(account),
- transactions: this.accountTransactionsMapper(account),
- closing: this.accountClosingBalance(account),
+ id: account.id,
+ name: account.name,
+ code: account.code,
+ index: account.index,
+ parentAccountId: account.parentAccountId,
+ openingBalance,
+ transactions: this.accountTransactionsMapper(
+ account,
+ openingBalance.amount
+ ),
+ closingBalance,
};
}
@@ -149,7 +217,8 @@ export default class GeneralLedgerSheet extends FinancialSheet {
.map((account: IAccount & { type: IAccountType }) =>
this.accountMapper(account)
)
- // Filter general ledger accounts that have no transactions when `noneTransactions` is on.
+ // Filter general ledger accounts that have no transactions
+ // when`noneTransactions` is on.
.filter(
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
!(
diff --git a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts
index 071951cf4..8fd09ac5f 100644
--- a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts
+++ b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts
@@ -7,6 +7,8 @@ import TenancyService from 'services/Tenancy/TenancyService';
import Journal from 'services/Accounting/JournalPoster';
import GeneralLedgerSheet from 'services/FinancialStatements/GeneralLedger/GeneralLedger';
+import { transformToMap } from 'utils';
+
const ERRORS = {
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
};
@@ -70,6 +72,7 @@ export default class GeneralLedgerService {
const {
accountRepository,
transactionsRepository,
+ contactRepository
} = this.tenancy.repositories(tenantId);
const settings = this.tenancy.settings(tenantId);
@@ -89,6 +92,10 @@ export default class GeneralLedgerService {
const accounts = await accountRepository.all('type');
const accountsGraph = await accountRepository.getDependencyGraph();
+ // Retrieve all contacts on the storage.
+ const contacts = await contactRepository.all();
+ const contactsByIdMap = transformToMap(contacts, 'id');
+
// Retreive journal transactions from/to the given date.
const transactions = await transactionsRepository.journal({
fromDate: filter.fromDate,
@@ -127,6 +134,7 @@ export default class GeneralLedgerService {
tenantId,
filter,
accounts,
+ contactsByIdMap,
transactionsJournal,
openingTransJournal,
closingTransJournal,
diff --git a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts
index 718b75076..313eb58f9 100644
--- a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts
+++ b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts
@@ -1,19 +1,20 @@
-import { sumBy, chain, omit } from 'lodash';
+import { sumBy, chain, get, head } from 'lodash';
import {
IJournalEntry,
IJournalPoster,
IJournalReportEntriesGroup,
IJournalReportQuery,
IJournalReport,
+ IContact,
} from 'interfaces';
import FinancialSheet from '../FinancialSheet';
-import { AccountTransaction } from 'models';
export default class JournalSheet extends FinancialSheet {
tenantId: number;
journal: IJournalPoster;
query: IJournalReportQuery;
baseCurrency: string;
+ readonly contactsById: Map;
/**
* Constructor method.
@@ -24,6 +25,8 @@ export default class JournalSheet extends FinancialSheet {
tenantId: number,
query: IJournalReportQuery,
journal: IJournalPoster,
+ accountsGraph: any,
+ contactsById: Map,
baseCurrency: string
) {
super();
@@ -32,22 +35,48 @@ export default class JournalSheet extends FinancialSheet {
this.journal = journal;
this.query = query;
this.numberFormat = this.query.numberFormat;
+ this.accountsGraph = accountsGraph;
+ this.contactsById = contactsById;
this.baseCurrency = baseCurrency;
}
/**
- * Mappes the journal entries.
- * @param {IJournalEntry[]} entries -
+ * Entry mapper.
+ * @param {IJournalEntry} entry
*/
- entriesMapper(
- entries: IJournalEntry[],
- ) {
- return entries.map((entry: IJournalEntry) => {
- return {
- ...omit(entry, 'account'),
- currencyCode: this.baseCurrency,
- };
- })
+ entryMapper(entry: IJournalEntry) {
+ const account = this.accountsGraph.getNodeData(entry.accountId);
+ const contact = this.contactsById.get(entry.contactId);
+
+ return {
+ entryId: entry.id,
+ index: entry.index,
+ note: entry.note,
+
+ contactName: get(contact, 'displayName'),
+ contactType: get(contact, 'contactService'),
+
+ accountName: account.name,
+ accountCode: account.code,
+ transactionNumber: entry.transactionNumber,
+
+ currencyCode: this.baseCurrency,
+ formattedCredit: this.formatNumber(entry.credit),
+ formattedDebit: this.formatNumber(entry.debit),
+
+ credit: entry.credit,
+ debit: entry.debit,
+
+ createdAt: entry.createdAt,
+ };
+ }
+
+ /**
+ * Mappes the journal entries.
+ * @param {IJournalEntry[]} entries -
+ */
+ entriesMapper(entries: IJournalEntry[]) {
+ return entries.map(this.entryMapper.bind(this));
}
/**
@@ -58,13 +87,17 @@ export default class JournalSheet extends FinancialSheet {
*/
entriesGroupsMapper(
entriesGroup: IJournalEntry[],
- key: string
+ groupEntry: IJournalEntry
): IJournalReportEntriesGroup {
const totalCredit = sumBy(entriesGroup, 'credit');
const totalDebit = sumBy(entriesGroup, 'debit');
return {
- id: key,
+ date: groupEntry.date,
+ referenceType: groupEntry.referenceType,
+ referenceId: groupEntry.referenceId,
+ referenceTypeFormatted: groupEntry.referenceTypeFormatted,
+
entries: this.entriesMapper(entriesGroup),
currencyCode: this.baseCurrency,
@@ -72,8 +105,8 @@ export default class JournalSheet extends FinancialSheet {
credit: totalCredit,
debit: totalDebit,
- formattedCredit: this.formatNumber(totalCredit),
- formattedDebit: this.formatNumber(totalDebit),
+ formattedCredit: this.formatTotalNumber(totalCredit),
+ formattedDebit: this.formatTotalNumber(totalDebit),
};
}
@@ -85,9 +118,10 @@ export default class JournalSheet extends FinancialSheet {
entriesWalker(entries: IJournalEntry[]): IJournalReportEntriesGroup[] {
return chain(entries)
.groupBy((entry) => `${entry.referenceId}-${entry.referenceType}`)
- .map((entriesGroup: IJournalEntry[], key: string) =>
- this.entriesGroupsMapper(entriesGroup, key)
- )
+ .map((entriesGroup: IJournalEntry[], key: string) => {
+ const headEntry = head(entriesGroup);
+ return this.entriesGroupsMapper(entriesGroup, headEntry);
+ })
.value();
}
diff --git a/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts b/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts
index 866bd1db2..686399c53 100644
--- a/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts
+++ b/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts
@@ -1,10 +1,13 @@
import { Service, Inject } from 'typedi';
import { IJournalReportQuery } from 'interfaces';
import moment from 'moment';
+
import JournalSheet from './JournalSheet';
import TenancyService from 'services/Tenancy/TenancyService';
import Journal from 'services/Accounting/JournalPoster';
+import { transformToMap } from 'utils';
+
@Service()
export default class JournalSheetService {
@Inject()
@@ -40,6 +43,7 @@ export default class JournalSheetService {
const {
accountRepository,
transactionsRepository,
+ contactRepository,
} = this.tenancy.repositories(tenantId);
const filter = {
@@ -50,7 +54,6 @@ export default class JournalSheetService {
tenantId,
filter,
});
-
// Settings service.
const settings = this.tenancy.settings(tenantId);
const baseCurrency = settings.get({
@@ -60,6 +63,10 @@ export default class JournalSheetService {
// Retrieve all accounts on the storage.
const accountsGraph = await accountRepository.getDependencyGraph();
+ // Retrieve all contacts on the storage.
+ const contacts = await contactRepository.all();
+ const contactsByIdMap = transformToMap(contacts, 'id');
+
// Retrieve all journal transactions based on the given query.
const transactions = await transactionsRepository.journal({
fromDate: filter.fromDate,
@@ -79,6 +86,8 @@ export default class JournalSheetService {
tenantId,
filter,
transactionsJournal,
+ accountsGraph,
+ contactsByIdMap,
baseCurrency
);
// Retrieve journal report columns.
diff --git a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts
index 9562acdf6..d5d16232b 100644
--- a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts
+++ b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts
@@ -50,6 +50,10 @@ export default class ProfitLossSheet extends FinancialSheet {
this.initDateRangeCollection();
}
+ get otherIncomeAccounts() {
+ return this.accounts.filter((a) => a.type.key === 'other_income');
+ }
+
/**
* Filtering income accounts.
* @return {IAccount & { type: IAccountType }[]}
@@ -235,6 +239,14 @@ export default class ProfitLossSheet extends FinancialSheet {
};
}
+ private get otherIncomeSection(): any {
+ return {
+ name: 'Other Income',
+ entryNormal: 'credit',
+ ...this.sectionMapper(this.otherIncomeAccounts)
+ }
+ }
+
/**
* Retreive expenses section.
* @return {IProfitLossSheetLossSection}
@@ -343,10 +355,14 @@ export default class ProfitLossSheet extends FinancialSheet {
* @return {IProfitLossSheetStatement}
*/
public reportData(): IProfitLossSheetStatement {
+ if (this.journal.isEmpty()) {
+ return null;
+ }
const income = this.incomeSection;
const costOfSales = this.costOfSalesSection;
const expenses = this.expensesSection;
const otherExpenses = this.otherExpensesSection;
+ const otherIncome = this.otherIncomeSection;
// - Gross profit = Total income - COGS.
const grossProfit = this.getSummarySection(income, costOfSales);
@@ -356,7 +372,6 @@ export default class ProfitLossSheet extends FinancialSheet {
expenses,
costOfSales,
]);
-
// - Net income = Operating profit - Other expenses.
const netIncome = this.getSummarySection(operatingProfit, otherExpenses);
@@ -365,6 +380,7 @@ export default class ProfitLossSheet extends FinancialSheet {
costOfSales,
grossProfit,
expenses,
+ otherIncome,
otherExpenses,
netIncome,
operatingProfit,
diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts
index 9c13915b1..86020ea0b 100644
--- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts
+++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts
@@ -4,6 +4,7 @@ import {
ITrialBalanceAccount,
IAccount,
ITrialBalanceTotal,
+ ITrialBalanceSheetData,
IAccountType,
} from 'interfaces';
import FinancialSheet from '../FinancialSheet';
@@ -49,6 +50,7 @@ export default class TrialBalanceSheet extends FinancialSheet {
/**
* Account mapper.
* @param {IAccount} account
+ * @return {ITrialBalanceAccount}
*/
private accountMapper(
account: IAccount & { type: IAccountType }
@@ -80,6 +82,7 @@ export default class TrialBalanceSheet extends FinancialSheet {
/**
* Accounts walker.
* @param {IAccount[]} accounts
+ * @return {ITrialBalanceAccount[]}
*/
private accountsWalker(
accounts: IAccount & { type: IAccountType }[]
@@ -136,8 +139,15 @@ export default class TrialBalanceSheet extends FinancialSheet {
/**
* Retrieve trial balance sheet statement data.
+ * Note: Retruns null in case there is no transactions between the given date periods.
+ *
+ * @return {ITrialBalanceSheetData}
*/
- public reportData() {
+ public reportData(): ITrialBalanceSheetData {
+ // Don't return noting if the journal has no transactions.
+ if (this.journalFinancial.isEmpty()) {
+ return null;
+ }
const accounts = this.accountsWalker(this.accounts);
const total = this.tatalSection(accounts);
diff --git a/server/src/utils/index.js b/server/src/utils/index.js
index 688dc4b18..09d648265 100644
--- a/server/src/utils/index.js
+++ b/server/src/utils/index.js
@@ -278,6 +278,15 @@ function defaultToTransform(value, defaultOrTransformedValue, defaultValue) {
: _transfromedValue;
}
+const transformToMap = (objects, key) => {
+ const map = new Map();
+
+ objects.forEach(object => {
+ map.set(object[key], object);
+ });
+ return map;
+}
+
export {
hashPassword,
origin,
@@ -299,4 +308,5 @@ export {
formatNumber,
isBlank,
defaultToTransform,
+ transformToMap
};