mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat: journal and general ledger report.
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -191,7 +191,7 @@ export default function DataTable({
|
||||
|
||||
// Renders table cell.
|
||||
const RenderCell = useCallback(
|
||||
({ row, cell, index }) => (
|
||||
({ row, cell, column, index }) => (
|
||||
<ConditionalWrapper
|
||||
condition={expandToggleColumn === index && expandable}
|
||||
wrapper={(children) => (
|
||||
@@ -199,6 +199,7 @@ export default function DataTable({
|
||||
style={{
|
||||
'padding-left': `${row.depth * expandColumnSpace}rem`,
|
||||
}}
|
||||
className={'expend-padding'}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
@@ -224,7 +225,14 @@ export default function DataTable({
|
||||
/>
|
||||
</span>
|
||||
</If>
|
||||
{cell.render('Cell')}
|
||||
|
||||
<ConditionalWrapper
|
||||
condition={cell.column.textOverview}
|
||||
wrapper={(children) => (
|
||||
<span class="text-overview">{ children }</span>
|
||||
)}>
|
||||
{cell.render('Cell')}
|
||||
</ConditionalWrapper>
|
||||
</ConditionalWrapper>
|
||||
),
|
||||
[expandable, expandToggleColumn, expandColumnSpace],
|
||||
@@ -276,7 +284,13 @@ export default function DataTable({
|
||||
return (
|
||||
<div
|
||||
{...cell.getCellProps({
|
||||
className: classnames(cell.column.className || '', 'td'),
|
||||
className: classnames(
|
||||
cell.column.className,
|
||||
'td',
|
||||
{
|
||||
'is-text-overview': cell.column.textOverview,
|
||||
}
|
||||
),
|
||||
})}
|
||||
onContextMenu={handleRowContextMenu(cell, row)}
|
||||
>
|
||||
|
||||
5
client/src/components/Datatable/Cells.js
Normal file
5
client/src/components/Datatable/Cells.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export function CellTextSpan({ cell: { value } }) {
|
||||
return (<span class="cell-text">{ value }</span>)
|
||||
}
|
||||
@@ -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({
|
||||
<div class="financial-sheet__table">{children}</div>
|
||||
<div class="financial-sheet__accounting-basis">{accountingBasis}</div>
|
||||
|
||||
{basisLabel && (
|
||||
<div class="financial-sheet__basis">
|
||||
<T id={'accounting_basis'} /> {basisLabel}
|
||||
</div>
|
||||
)}
|
||||
<div class="financial-sheet__footer">
|
||||
<If condition={basisLabel}>
|
||||
<span class="financial-sheet__basis">
|
||||
<T id={'accounting_basis'} /> {basisLabel}
|
||||
</span>
|
||||
</If>
|
||||
|
||||
<If condition={currentDate}>
|
||||
<span class="financial-sheet__current-date">
|
||||
{moment().format('YYYY MMM DD HH:MM')}
|
||||
</span>
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -91,5 +91,5 @@ export {
|
||||
InputPrependText,
|
||||
PageFormBigNumber,
|
||||
AccountsMultiSelect,
|
||||
DataTableEditable
|
||||
DataTableEditable,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -38,6 +38,7 @@ function ReceivableAgingSummaryTable({
|
||||
className: 'customer_name',
|
||||
sticky: 'left',
|
||||
width: 240,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: <T id={'current'} />,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<FinancialStatementDateRange />
|
||||
|
||||
<FinancialAccountsFilter
|
||||
items={filterAccountsOptions}
|
||||
initialSelectedItem={'all-accounts'}
|
||||
/>
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<FormGroup
|
||||
|
||||
@@ -8,6 +8,7 @@ import DataTable from 'components/DataTable';
|
||||
import Money from 'components/Money';
|
||||
|
||||
import withGeneralLedger from './withGeneralLedger';
|
||||
import { getForceWidth, getColumnWidth } from 'utils';
|
||||
|
||||
const ROW_TYPE = {
|
||||
CLOSING_BALANCE: 'closing_balance',
|
||||
@@ -25,91 +26,89 @@ function GeneralLedgerTable({
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// Account name column accessor.
|
||||
const accountNameAccessor = (row) => {
|
||||
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 ? (
|
||||
<Money amount={transaction.closing.amount} currency={'USD'} />
|
||||
) : (
|
||||
''
|
||||
);
|
||||
}
|
||||
return <Money amount={transaction.amount} currency={'USD'} />;
|
||||
}, []);
|
||||
|
||||
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 (
|
||||
<span
|
||||
className={'force-width'}
|
||||
style={{ minWidth: getForceWidth(row.date) }}
|
||||
>
|
||||
{row.date}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
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}
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
},
|
||||
];
|
||||
@@ -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,
|
||||
<Money amount={r.credit} currency={'USD'} />,
|
||||
['space_entry'],
|
||||
),
|
||||
accessor: 'formatted_credit',
|
||||
className: 'credit'
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'debit' }),
|
||||
accessor: (r) =>
|
||||
exceptRowTypes(
|
||||
r.rowType,
|
||||
<Money amount={r.debit} currency={'USD'} />,
|
||||
['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 (
|
||||
<FinancialSheet
|
||||
companyName={companyName}
|
||||
@@ -111,11 +107,12 @@ function JournalSheetTable({
|
||||
loading={journalSheetLoading}
|
||||
// minimal={true}
|
||||
fullWidth={true}
|
||||
>
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
columns={columns}
|
||||
data={journalSheetTableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
onFetchData={handleFetchData}
|
||||
noResults={formatMessage({
|
||||
id: 'this_report_does_not_contain_any_data_between_date_period',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FormattedMessage as T, 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 { compose, defaultExpanderReducer, getColumnWidth } from 'utils';
|
||||
import withProfitLossDetail from './withProfitLoss';
|
||||
@@ -26,12 +26,14 @@ function ProfitLossSheetTable({
|
||||
Header: formatMessage({ id: 'account' }),
|
||||
accessor: (row) => (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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mapKeys, omit, snakeCase } from 'lodash';
|
||||
import { omit } from 'lodash';
|
||||
import { transformToCamelCase, flatObject } from 'utils';
|
||||
import { formatMessage } from 'services/intl';
|
||||
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
];
|
||||
};
|
||||
})
|
||||
};
|
||||
return results;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
.tr.is-expanded{
|
||||
.td.total,
|
||||
.td.total-period{
|
||||
> span{
|
||||
> span.cell-text{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -50,7 +50,7 @@ export interface IProfitLossSheetStatement {
|
||||
costOfSales: IProfitLossSheetAccountsSection,
|
||||
expenses: IProfitLossSheetAccountsSection,
|
||||
otherExpenses: IProfitLossSheetAccountsSection,
|
||||
|
||||
otherIncome: IProfitLossSheetAccountsSection,
|
||||
netIncome: IProfitLossSheetTotalSection;
|
||||
operatingProfit: IProfitLossSheetTotalSection;
|
||||
grossProfit: IProfitLossSheetTotalSection;
|
||||
|
||||
@@ -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)",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 -
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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<number, IContact>;
|
||||
baseCurrency: string;
|
||||
|
||||
/**
|
||||
@@ -32,6 +37,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery,
|
||||
accounts: IAccount[],
|
||||
contactsByIdMap: Map<number, IContact>,
|
||||
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) =>
|
||||
!(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<number | string, IContact>;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -24,6 +25,8 @@ export default class JournalSheet extends FinancialSheet {
|
||||
tenantId: number,
|
||||
query: IJournalReportQuery,
|
||||
journal: IJournalPoster,
|
||||
accountsGraph: any,
|
||||
contactsById: Map<number | string, IContact>,
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user