diff --git a/client/package.json b/client/package.json index c0b3f4f2c..909ba049c 100644 --- a/client/package.json +++ b/client/package.json @@ -71,6 +71,7 @@ "react-sortablejs": "^2.0.11", "react-table": "^7.0.0", "react-use": "^13.26.1", + "react-window": "^1.8.5", "redux": "^4.0.5", "redux-thunk": "^2.3.0", "resolve": "1.15.0", diff --git a/client/src/components/DataTable.js b/client/src/components/DataTable.js index 3cd9a97ae..25bb97c7d 100644 --- a/client/src/components/DataTable.js +++ b/client/src/components/DataTable.js @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useMemo, useCallback} from 'react'; import { useTable, useExpanded, @@ -11,6 +11,7 @@ import { } from 'react-table' import {Checkbox} from '@blueprintjs/core'; import classnames from 'classnames'; +import { FixedSizeList } from 'react-window' import Icon from 'components/Icon'; const IndeterminateCheckbox = React.forwardRef( @@ -34,8 +35,15 @@ export default function DataTable({ onSelectedRowsChange, manualSortBy = 'false', selectionColumn = false, + expandSubRows = true, className, - noResults = 'This report does not contain any data.' + noResults = 'This report does not contain any data.', + expanded = {}, + rowClassNames, + stickyHeader = true, + virtualizedRows = false, + fixedSizeHeight = 100, + fixedItemSize = 30, }) { const { getTableProps, @@ -43,6 +51,7 @@ export default function DataTable({ headerGroups, prepareRow, page, + rows, canPreviousPage, canNextPage, pageOptions, @@ -52,21 +61,23 @@ export default function DataTable({ previousPage, setPageSize, selectedFlatRows, + totalColumnsWidth, // Get the state from the instance state: { pageIndex, pageSize, sortBy, selectedRowIds }, } = useTable( { columns, - data, - initialState: { pageIndex: 0 }, // Pass our hoisted table state + data: data, + initialState: { pageIndex: 0, expanded }, // Pass our hoisted table state manualPagination: true, // Tell the usePagination // hook that we'll handle our own data fetching // This means we'll also have to provide our own // pageCount. // pageCount: controlledPageCount, getSubRows: row => row.children, - manualSortBy + manualSortBy, + expandSubRows, }, useSortBy, useExpanded, @@ -102,14 +113,50 @@ export default function DataTable({ ]) } ); - + // When these table states change, fetch new data! useEffect(() => { onFetchData && onFetchData({ pageIndex, pageSize, sortBy }) }, [pageIndex, pageSize, sortBy]); + // Renders table row. + const RenderRow = useCallback(({ style = {}, row }) => { + prepareRow(row); + return ( +
+ {row.cells.map((cell) => { + return
{ cell.render('Cell') }
+ })} +
); + }, [prepareRow]); + + // Renders virtualize circle table rows. + const RenderVirtualizedRows = useCallback(({ index, style }) => { + const row = rows[index]; + return RenderRow({ row, style }); + }, [RenderRow, rows]); + + const RenderPage = useCallback(({ style, index } = {}) => { + return page.map((row, index) => RenderRow({ row })); + }, [RenderRow, page]); + + const RenderTBody = useCallback(() => { + return (virtualizedRows) ? ( + + {RenderVirtualizedRows} + + ) : RenderPage(); + }, [fixedSizeHeight, rows, fixedItemSize, virtualizedRows, + RenderVirtualizedRows, RenderPage]) + return ( -
+
{headerGroups.map(headerGroup => ( @@ -144,19 +191,8 @@ export default function DataTable({ ))}
- {page.map((row, i) => { - prepareRow(row); - - return ( -
- {row.cells.map((cell) => { - return
{ cell.render('Cell') }
- })} -
) - })} - + { RenderTBody() } + { (page.length === 0) && (
{ noResults }
diff --git a/client/src/components/FinancialSheet.js b/client/src/components/FinancialSheet.js index a506fef14..af78c3165 100644 --- a/client/src/components/FinancialSheet.js +++ b/client/src/components/FinancialSheet.js @@ -11,12 +11,13 @@ export default function FinancialSheet({ accountingBasis, name, loading, + className, }) { const formattedDate = moment(date).format('DD MMMM YYYY') const nameModifer = name ? `financial-sheet--${name}` : ''; return ( -
+

{ companyTitle }

{ sheetType }
@@ -29,6 +30,10 @@ export default function FinancialSheet({
{ accountingBasis }
+ +
+ Accounting Basis: Accural +
); diff --git a/client/src/connectors/BalanceSheet.connect.js b/client/src/connectors/BalanceSheet.connect.js new file mode 100644 index 000000000..15b0fa2cb --- /dev/null +++ b/client/src/connectors/BalanceSheet.connect.js @@ -0,0 +1,27 @@ +import {connect} from 'react-redux'; +import { + fetchBalanceSheet, +} from 'store/financialStatement/financialStatements.actions'; +import { + getFinancialSheetIndexByQuery, + getFinancialSheet, + getFinancialSheetAccounts, + getFinancialSheetColumns, + getFinancialSheetQuery, +} from 'store/financialStatement/financialStatements.selectors'; + + +export const mapStateToProps = (state, props) => ({ + getBalanceSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.balanceSheet.sheets, query), + getBalanceSheet: (index) => getFinancialSheet(state.financialStatements.balanceSheet.sheets, index), + getBalanceSheetAccounts: (index) => getFinancialSheetAccounts(state.financialStatements.balanceSheet.sheets, index), + getBalanceSheetColumns:(index) => getFinancialSheetColumns(state.financialStatements.balanceSheet.sheets, index), + getBalanceSheetQuery: (index) => getFinancialSheetQuery(state.financialStatements.balanceSheet.sheets, index), + balanceSheetLoading: state.financialStatements.balanceSheet.loading, +}); + +export const mapDispatchToProps = (dispatch) => ({ + fetchBalanceSheet: (query = {}) => dispatch(fetchBalanceSheet({ query })), +}); + +export default connect(mapStateToProps, mapDispatchToProps); \ No newline at end of file diff --git a/client/src/connectors/BalanceSheetTable.connect.js b/client/src/connectors/BalanceSheetTable.connect.js new file mode 100644 index 000000000..58d8d52d1 --- /dev/null +++ b/client/src/connectors/BalanceSheetTable.connect.js @@ -0,0 +1,27 @@ +import {connect} from 'react-redux'; +import { + fetchBalanceSheet, +} from 'store/financialStatement/financialStatements.actions'; +import { + getFinancialSheetIndexByQuery, + getFinancialSheet, + getFinancialSheetsAccounts, + getFinancialSheetsColumns, +} from 'store/financialStatement/financialStatements.selectors'; + + +export const mapStateToProps = (state, props) => { + const sheetIndex = props.balanceSheetIndex; + + return { + balanceSheetAccounts: props.getBalanceSheetAccounts(sheetIndex), + balanceSheetQuery: props.getBalanceSheetQuery(sheetIndex), + balanceSheetColumns: props.getBalanceSheetColumns(sheetIndex), + }; +}; + +export const mapDispatchToProps = (dispatch) => ({ + +}); + +export default connect(mapStateToProps, mapDispatchToProps); \ No newline at end of file diff --git a/client/src/connectors/ProfitLossSheet.connect.js b/client/src/connectors/ProfitLossSheet.connect.js index 8d006d480..e7ad67afd 100644 --- a/client/src/connectors/ProfitLossSheet.connect.js +++ b/client/src/connectors/ProfitLossSheet.connect.js @@ -5,11 +5,19 @@ import { import { getFinancialSheetIndexByQuery, getFinancialSheet, + getFinancialSheetColumns, + getFinancialSheetQuery, + getFinancialSheetTableRows, } from 'store/financialStatement/financialStatements.selectors'; export const mapStateToProps = (state, props) => ({ getProfitLossSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.profitLoss.sheets, query), getProfitLossSheet: (index) => getFinancialSheet(state.financialStatements.profitLoss.sheets, index), + getProfitLossColumns: (index) => getFinancialSheetColumns(state.financialStatements.profitLoss.sheets, index), + getProfitLossQuery: (index) => getFinancialSheetQuery(state.financialStatements.profitLoss.sheets, index), + getProfitLossTableRows: (index) => getFinancialSheetTableRows(state.financialStatements.profitLoss.sheets, index), + + profitLossSheetLoading: state.financialStatements.profitLoss.loading, }); export const mapDispatchToProps = (dispatch) => ({ diff --git a/client/src/connectors/ProfitLossTable.connect.js b/client/src/connectors/ProfitLossTable.connect.js new file mode 100644 index 000000000..02970c30c --- /dev/null +++ b/client/src/connectors/ProfitLossTable.connect.js @@ -0,0 +1,17 @@ +import {connect} from 'react-redux'; + +export const mapStateToProps = (state, props) => { + const sheetIndex = props.profitLossSheetIndex; + + return { + profitLossTableRows: props.getProfitLossTableRows(sheetIndex), + profitLossColumns: props.getProfitLossColumns(sheetIndex), + profitLossQuery: props.getProfitLossQuery(sheetIndex), + }; +}; + +export const mapDispatchToProps = (dispatch) => ({ + +}); + +export default connect(mapStateToProps, mapDispatchToProps); \ No newline at end of file diff --git a/client/src/connectors/TrialBalanceSheet.connect.js b/client/src/connectors/TrialBalanceSheet.connect.js index 5755b4869..01a63e9f2 100644 --- a/client/src/connectors/TrialBalanceSheet.connect.js +++ b/client/src/connectors/TrialBalanceSheet.connect.js @@ -3,16 +3,15 @@ import { fetchTrialBalanceSheet } from 'store/financialStatement/financialStatements.actions'; import { - getTrialBalanceSheetIndex, - getTrialBalanceAccounts, - getTrialBalanceQuery, getFinancialSheetIndexByQuery, + getFinancialSheetAccounts, + getFinancialSheetQuery, } from 'store/financialStatement/financialStatements.selectors'; export const mapStateToProps = (state, props) => ({ getTrialBalanceSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.trialBalance.sheets, query), - getTrialBalanceAccounts: (sheetIndex) => getTrialBalanceAccounts(state.financialStatements.trialBalance.sheets, sheetIndex), - getTrialBalanceQuery: (sheetIndex) => getTrialBalanceQuery(state.financialStatements.trialBalance.sheets, sheetIndex), + getTrialBalanceAccounts: (sheetIndex) => getFinancialSheetAccounts(state.financialStatements.trialBalance.sheets, sheetIndex), + getTrialBalanceQuery: (sheetIndex) => getFinancialSheetQuery(state.financialStatements.trialBalance.sheets, sheetIndex), trialBalanceSheetLoading: state.financialStatements.trialBalance.loading, }); diff --git a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheet.js b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheet.js index fbb5d6fbb..94cdfed07 100644 --- a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheet.js +++ b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheet.js @@ -2,7 +2,7 @@ import React, {useEffect, useMemo, useCallback, useState} from 'react'; import DashboardConnect from 'connectors/Dashboard.connector'; import {compose} from 'utils'; import useAsync from 'hooks/async'; -import FinancialStatementConnect from 'connectors/FinancialStatements.connector'; +import BalanceSheetConnect from 'connectors/BalanceSheet.connect'; import {useIntl} from 'react-intl'; import BalanceSheetHeader from './BalanceSheetHeader'; import LoadingIndicator from 'components/LoadingIndicator'; @@ -15,10 +15,9 @@ import BalanceSheetActionsBar from './BalanceSheetActionsBar'; function BalanceSheet({ fetchBalanceSheet, changePageTitle, - getBalanceSheetByQuery, - getBalanceSheetIndexByQuery, - getBalanceSheetByIndex, - balanceSheets + balanceSheetLoading, + getBalanceSheetIndex, + getBalanceSheet, }) { const intl = useIntl(); const [filter, setFilter] = useState({ @@ -45,13 +44,8 @@ function BalanceSheet({ // Retrieve balance sheet index by the given filter query. const balanceSheetIndex = useMemo(() => - getBalanceSheetIndexByQuery(filter), - [filter, getBalanceSheetIndexByQuery]); - - // Retreive balance sheet by the given sheet index. - const balanceSheet = useMemo(() => - getBalanceSheetByIndex(balanceSheetIndex), - [balanceSheetIndex, getBalanceSheetByIndex]); + getBalanceSheetIndex(filter), + [filter, getBalanceSheetIndex]); // Handle re-fetch balance sheet after filter change. const handleFilterSubmit = useCallback((filter) => { @@ -75,13 +69,10 @@ function BalanceSheet({ onSubmitFilter={handleFilterSubmit} />
- - - +
@@ -91,5 +82,5 @@ function BalanceSheet({ export default compose( DashboardConnect, - FinancialStatementConnect, + BalanceSheetConnect, )(BalanceSheet); \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js index b62b7fe76..09ef4a543 100644 --- a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js +++ b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js @@ -39,7 +39,7 @@ export default function BalanceSheetHeader({ actions.setSubmitting(false); }, }); - + // Handle item select of `display columns by` field. const onItemSelectDisplayColumns = useCallback((item) => { formik.setFieldValue('display_columns_type', item.type); @@ -73,7 +73,8 @@ export default function BalanceSheetHeader({ - + @@ -102,7 +103,7 @@ export default function BalanceSheetHeader({ type="submit" onClick={handleSubmitClick} disabled={formik.isSubmitting} - className={'button--submit-filter'}> + className={'button--submit-filter mt2'}> { 'Calculate Report' } diff --git a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetTable.js b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetTable.js index d76f27fe7..0925a53c0 100644 --- a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetTable.js +++ b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetTable.js @@ -1,37 +1,28 @@ import React, {useMemo, useState, useCallback, useEffect} from 'react'; -import FinancialSheet from 'components/FinancialSheet'; -import DataTable from 'components/DataTable'; -import FinancialStatementConnect from 'connectors/FinancialStatements.connector'; -import {compose} from 'utils'; import moment from 'moment'; import Money from 'components/Money'; +import FinancialSheet from 'components/FinancialSheet'; +import DataTable from 'components/DataTable'; +import BalanceSheetConnect from 'connectors/BalanceSheet.connect'; +import BalanceSheetTableConnect from 'connectors/BalanceSheetTable.connect'; +import { + compose, + defaultExpanderReducer, +} from 'utils'; function BalanceSheetTable({ - balanceSheet, - balanceSheetIndex, - getBalanceSheetColumns, - - getBalanceSheetAssetsAccounts, - getBalanceSheetLiabilitiesAccounts, - - getBalanceSheetQuery, - + balanceSheetAccounts, + balanceSheetColumns, + balanceSheetQuery, onFetchData, asDate, + loading, }) { - const balanceSheetColumns = useMemo(() => - getBalanceSheetColumns(balanceSheetIndex), - [getBalanceSheetColumns, balanceSheetIndex]); - - const balanceSheetQuery = useMemo(() => - getBalanceSheetQuery(balanceSheetIndex), - [getBalanceSheetQuery, balanceSheetIndex]) - const columns = useMemo(() => [ { // Build our expander column id: 'expander', // Make sure it has an ID - className: 'expander', + className: 'expander', Header: ({ getToggleAllRowsExpandedProps, isAllRowsExpanded @@ -77,63 +68,65 @@ function BalanceSheetTable({ accessor: 'code', className: "code", }, - ...(balanceSheetQuery && - balanceSheetQuery.display_columns_by === 'total') ? [ + ...(balanceSheetQuery.display_columns_type === 'total') ? [ { Header: 'Total', accessor: 'balance.formatted_amount', Cell: ({ cell }) => { const row = cell.row.original; - if (!row.balance) { return ''; } - return (); + if (row.total) { + return (); + } + return ''; }, className: "credit", } - ] : (balanceSheetColumns.map((column, index) => ({ - Header: column, - accessor: (row) => { - if (row.periods_balance && row.periods_balance[index]) { - return row.periods_balance[index].formatted_amount; - } - }, - }))), - ], [balanceSheetColumns, balanceSheetQuery]); - - const [data, setData] = useState([]); - - useEffect(() => { - if (!balanceSheet) { return; } - setData([ - { - name: 'Assets', - children: getBalanceSheetAssetsAccounts(balanceSheetIndex), - }, - { - name: 'Liabilies & Equity', - children: getBalanceSheetLiabilitiesAccounts(balanceSheetIndex), - } - ]) - }, []); - + ] : [], + ...(balanceSheetQuery.display_columns_type === 'date_periods') ? + (balanceSheetColumns.map((column, index) => ({ + id: `date_period_${index}`, + Header: column, + accessor: (row) => { + if (row.total_periods && row.total_periods[index]) { + const amount = row.total_periods[index].formatted_amount; + return (); + } + return ''; + }, + width: 100, + }))) + : [], + ], [balanceSheetQuery, balanceSheetColumns]); + const handleFetchData = useCallback(() => { onFetchData && onFetchData(); }, [onFetchData]); + + // Calculates the default expanded rows of balance sheet table. + const expandedRows = useMemo(() => + defaultExpanderReducer(balanceSheetAccounts, 1), + [balanceSheetAccounts]); + return ( + date={asDate} + loading={loading}> + data={balanceSheetAccounts} + onFetchData={handleFetchData} + expandSubRows={true} + expanded={expandedRows} /> ); } export default compose( - FinancialStatementConnect, + BalanceSheetConnect, + BalanceSheetTableConnect, )(BalanceSheetTable); \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js index d6b9dd57d..1486897f2 100644 --- a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js +++ b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js @@ -83,7 +83,7 @@ function GeneralLedgerHeader({ type="submit" onClick={handleSubmitClick} disabled={formik.isSubmitting} - className={'button--submit-filter'}> + className={'button--submit-filter mt2'}> { 'Calculate Report' } diff --git a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js index 2ba961b16..966903245 100644 --- a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js +++ b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js @@ -3,6 +3,10 @@ import FinancialSheet from 'components/FinancialSheet'; import DataTable from 'components/DataTable'; import Money from 'components/Money'; import moment from 'moment'; +import { + defaultExpanderReducer, +} from 'utils'; + const ROW_TYPE = { CLOSING_BALANCE: 'closing_balance', @@ -135,6 +139,9 @@ export default function GeneralLedgerTable({ onFetchData && onFetchData(); }, [onFetchData]); + // Default expanded rows of general ledger table. + const expandedRows = useMemo(() => defaultExpanderReducer(data, 1), [data]); + return ( + onFetchData={handleFetchData} + expanded={expandedRows} + virtualizedRows={true} + fixedItemSize={37} + fixedSizeHeight={1000} /> ); } \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/Journal/JournalHeader.js b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalHeader.js index e0ba8dfd2..363a5b9db 100644 --- a/client/src/containers/Dashboard/FinancialStatements/Journal/JournalHeader.js +++ b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalHeader.js @@ -47,7 +47,7 @@ export default function JournalHeader({ diff --git a/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js index 28485da84..f2c2bbfbf 100644 --- a/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js +++ b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js @@ -1,7 +1,7 @@ import React, {useState, useEffect, useCallback, useMemo} from 'react'; import FinancialSheet from 'components/FinancialSheet'; import DataTable from 'components/DataTable'; -import {compose} from 'utils'; +import {compose, defaultExpanderReducer} from 'utils'; import moment from 'moment'; import JournalConnect from 'connectors/Journal.connect'; import { @@ -71,6 +71,9 @@ function JournalSheetTable({ onFetchData && onFetchData(...args) }, [onFetchData]); + // Default expanded rows of general journal table. + const expandedRows = useMemo(() => defaultExpanderReducer(data, 1), [data]); + return ( + noResults={"This report does not contain any data."} + expanded={expandedRows} /> ); } diff --git a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js index 398eb61a5..7e9b327d8 100644 --- a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js +++ b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js @@ -14,11 +14,11 @@ import moment from 'moment'; function ProfitLossSheet({ changePageTitle, fetchProfitLossSheet, - getProfitLossSheetIndex, - getProfitLossSheet, + profitLossSheetLoading, }) { const [filter, setFilter] = useState({ + basis: 'cash', from_date: moment().startOf('year').format('YYYY-MM-DD'), to_date: moment().endOf('year').format('YYYY-MM-DD'), }); @@ -26,7 +26,7 @@ function ProfitLossSheet({ // Change page title of the dashboard. useEffect(() => { changePageTitle('Profit/Loss Sheet'); - }, []); + }, [changePageTitle]); // Fetches profit/loss sheet. const fetchHook = useAsync((query = filter) => { @@ -35,14 +35,12 @@ function ProfitLossSheet({ ]); }, false); + // Retrieve profit/loss sheet index based on the given filter query. const profitLossSheetIndex = useMemo(() => getProfitLossSheetIndex(filter), - [getProfitLossSheetIndex, filter]) - - const profitLossSheet = useMemo(() => - getProfitLossSheet(profitLossSheetIndex), - [getProfitLossSheet, profitLossSheetIndex]); + [getProfitLossSheetIndex, filter]); + // Handle submit filter. const handleSubmitFilter = useCallback((filter) => { const _filter = { ...filter, @@ -51,30 +49,29 @@ function ProfitLossSheet({ }; setFilter(_filter); fetchHook.execute(_filter); - }, []); + }, [fetchHook]); // Handle fetch data of profit/loss sheet table. - const handleFetchData = useCallback(() => { - fetchHook.execute(); - }, [fetchHook]); + const handleFetchData = useCallback(() => { fetchHook.execute(); }, [fetchHook]); return ( + + +
+ -
- - -
- +
- -
-
+ profitLossSheetIndex={profitLossSheetIndex} + onFetchData={handleFetchData} + loading={profitLossSheetLoading} /> +
+
+
); } diff --git a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js index 76c7aeea7..a8d997e7b 100644 --- a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js +++ b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js @@ -39,7 +39,7 @@ export default function JournalHeader({ const handleItemSelectDisplayColumns = useCallback((item) => { formik.setFieldValue('display_columns_type', item.type); formik.setFieldValue('display_columns_by', item.by); - }, []); + }, [formik]); const handleSubmitClick = useCallback(() => { formik.submitForm(); @@ -68,7 +68,7 @@ export default function JournalHeader({ diff --git a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js index a72a6a739..224edd270 100644 --- a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js +++ b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js @@ -2,14 +2,59 @@ import React, {useState, useMemo, useCallback} from 'react'; import FinancialSheet from 'components/FinancialSheet'; import DataTable from 'components/DataTable'; import Money from 'components/Money'; +import ProfitLossSheetConnect from 'connectors/ProfitLossSheet.connect'; +import ProfitLossSheetTableConnect from 'connectors/ProfitLossTable.connect'; +import { compose, defaultExpanderReducer } from 'utils'; -export default function ProfitLossSheetTable({ +function ProfitLossSheetTable({ loading, data, onFetchData, + profitLossTableRows, + profitLossQuery, + profitLossColumns }) { const columns = useMemo(() => [ + { + // Build our expander column + id: 'expander', // Make sure it has an ID + className: 'expander', + Header: ({ + getToggleAllRowsExpandedProps, + isAllRowsExpanded + }) => ( + + {isAllRowsExpanded ? + () : + () + } + + ), + Cell: ({ row }) => + // Use the row.canExpand and row.getToggleRowExpandedProps prop getter + // to build the toggle for expanding a row + row.canExpand ? ( + + {row.isExpanded ? + () : + () + } + + ) : null, + width: 20, + disableResizing: true, + }, { Header: 'Account Name', accessor: 'name', @@ -20,12 +65,52 @@ export default function ProfitLossSheetTable({ accessor: 'code', className: "account_code", }, - ]) + ...(profitLossQuery.display_columns_type === 'total') ? [ + { + Header: 'Total', + Cell: ({ cell }) => { + const row = cell.row.original; + if (row.total) { + return (); + } + return ''; + }, + className: "total", + } + ] : [], + ...(profitLossQuery.display_columns_type === 'date_periods') ? + (profitLossColumns.map((column, index) => ({ + id: `date_period_${index}`, + Header: column, + accessor: (row) => { + if (row.periods && row.periods[index]) { + const amount = row.periods[index].formatted_amount; + return (); + } + return ''; + }, + width: 100, + }))) + : [], + ], [profitLossQuery.display_columns_type, profitLossColumns]); + // Handle data table fetch data. const handleFetchData = useCallback((...args) => { onFetchData && onFetchData(...args); }, [onFetchData]); + // Retrieve default expanded rows of balance sheet. + const expandedRows = useMemo(() => + defaultExpanderReducer(profitLossTableRows, 1), + [profitLossTableRows]); + + // Retrieve conditional datatable row classnames. + const rowClassNames = useCallback((row) => { + return { + [`row--${row.rowType}`]: row.rowType, + }; + }, []); + return ( + data={profitLossTableRows} + onFetchData={handleFetchData} + expanded={expandedRows} + rowClassNames={rowClassNames} /> - ) -} \ No newline at end of file + ); +} + +export default compose( + ProfitLossSheetConnect, + ProfitLossSheetTableConnect, +)(ProfitLossSheetTable); \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/RadiosAccountingBasis.js b/client/src/containers/Dashboard/FinancialStatements/RadiosAccountingBasis.js index a957a8781..ac3eee2e0 100644 --- a/client/src/containers/Dashboard/FinancialStatements/RadiosAccountingBasis.js +++ b/client/src/containers/Dashboard/FinancialStatements/RadiosAccountingBasis.js @@ -19,6 +19,7 @@ export default function RadiosAccountingBasis(props) { onChange={handleStringChange((value) => { onChange && onChange(value); })} + className={'radio-group---accounting-basis'} {...rest}> diff --git a/client/src/containers/Dashboard/FinancialStatements/SelectDisplayColumnsBy.js b/client/src/containers/Dashboard/FinancialStatements/SelectDisplayColumnsBy.js index 0faf79071..54ab6aa74 100644 --- a/client/src/containers/Dashboard/FinancialStatements/SelectDisplayColumnsBy.js +++ b/client/src/containers/Dashboard/FinancialStatements/SelectDisplayColumnsBy.js @@ -1,6 +1,6 @@ -import React, {useMemo, useCallback} from 'react'; +import React, { useMemo, useState, useCallback } from 'react'; import SelectList from 'components/SelectList'; import { FormGroup, @@ -8,21 +8,31 @@ import { } from '@blueprintjs/core'; export default function SelectsListColumnsBy(props) { - const { formGroupProps, selectListProps } = props; + const { onItemSelect, formGroupProps, selectListProps } = props; + const [itemSelected, setItemSelected] = useState(null); const displayColumnsByOptions = useMemo(() => [ {key: 'total', name: 'Total', type: 'total', by: '', }, - {key: 'year', name: 'Year', type: 'date', by: 'year'}, - {key: 'month', name: 'Month', type: 'date', by: 'month'}, - {key: 'week', name: 'Week', type: 'date', by: 'month'}, - {key: 'day', name: 'Day', type: 'date', by: 'day'}, - {key: 'quarter', name: 'Quarter', type: 'date', by: 'quarter'}, + {key: 'year', name: 'Date/Year', type: 'date_periods', by: 'year'}, + {key: 'month', name: 'Date/Month', type: 'date_periods', by: 'month'}, + {key: 'week', name: 'Date/Week', type: 'date_periods', by: 'month'}, + {key: 'day', name: 'Date/Day', type: 'date_periods', by: 'day'}, + {key: 'quarter', name: 'Date/Quarter', type: 'date_periods', by: 'quarter'}, ]); const itemRenderer = useCallback((item, { handleClick, modifiers, query }) => { return (); }, []); + const handleItemSelect = useCallback((item) => { + setItemSelected(item); + onItemSelect && onItemSelect(item); + }, [setItemSelected, onItemSelect]); + + const buttonLabel = useMemo(() => + itemSelected ? itemSelected.name : 'Select display columns by...', + [itemSelected]); + return ( ); diff --git a/client/src/containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetHeader.js b/client/src/containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetHeader.js index 13be76113..8ca9d3bed 100644 --- a/client/src/containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetHeader.js +++ b/client/src/containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetHeader.js @@ -55,7 +55,8 @@ export default function TrialBalanceSheetHeader({ diff --git a/client/src/store/financialStatement/financialStatements.actions.js b/client/src/store/financialStatement/financialStatements.actions.js index 6f884c455..8ddeb9749 100644 --- a/client/src/store/financialStatement/financialStatements.actions.js +++ b/client/src/store/financialStatement/financialStatements.actions.js @@ -3,11 +3,19 @@ import t from 'store/types'; export const fetchGeneralLedger = ({ query }) => { return (dispatch) => new Promise((resolve, reject) => { + dispatch({ + type: t.GENERAL_LEDGER_SHEET_LOADING, + loading: true, + }) ApiService.get('/financial_statements/general_ledger', { params: query }).then((response) => { dispatch({ type: t.GENERAL_LEDGER_STATEMENT_SET, data: response.data, }); + dispatch({ + type: t.GENERAL_LEDGER_SHEET_LOADING, + loading: false, + }); resolve(response); }).catch((error) => { reject(error); }); }); @@ -15,12 +23,20 @@ export const fetchGeneralLedger = ({ query }) => { export const fetchBalanceSheet = ({ query }) => { return (dispatch) => new Promise((resolve, reject) => { + dispatch({ + type: t.BALANCE_SHEET_LOADING, + loading: true, + }); ApiService.get('/financial_statements/balance_sheet', { params: query }).then((response) => { dispatch({ type: t.BALANCE_SHEET_STATEMENT_SET, data: response.data, query: query, }); + dispatch({ + type: t.BALANCE_SHEET_LOADING, + loading: false, + }); resolve(response); }).catch((error) => { reject(error); }); }); @@ -50,9 +66,9 @@ export const fetchProfitLossSheet = ({ query }) => { return (dispatch) => new Promise((resolve, reject) => { dispatch({ type: t.PROFIT_LOSS_SHEET_LOADING, - loading: false, + loading: true, }); - ApiService.get('/financial_statements/profit_loss_sheet').then((response) => { + ApiService.get('/financial_statements/profit_loss_sheet', { params: query }).then((response) => { dispatch({ type: t.PROFIT_LOSS_SHEET_SET, profitLoss: response.data.profitLoss, diff --git a/client/src/store/financialStatement/financialStatements.reducer.js b/client/src/store/financialStatement/financialStatements.reducer.js index fce244cd4..9ea91e6d4 100644 --- a/client/src/store/financialStatement/financialStatements.reducer.js +++ b/client/src/store/financialStatement/financialStatements.reducer.js @@ -1,14 +1,17 @@ import { createReducer } from '@reduxjs/toolkit'; import t from 'store/types'; import { - getBalanceSheetIndexByQuery, + // getBalanceSheetIndexByQuery, getFinancialSheetIndexByQuery, // getFinancialSheetIndexByQuery, } from './financialStatements.selectors'; import {omit} from 'lodash'; const initialState = { - balanceSheets: [], + balanceSheet: { + sheets: [], + loading: false, + }, trialBalance: { sheets: [], loading: false, @@ -72,22 +75,61 @@ const mapJournalTableRows = (journal) => { }, []); } + +const mapProfitLossToTableRows = (profitLoss) => { + return [ + { + name: 'Income', + total: profitLoss.income.total, + children: [ + ...profitLoss.income.accounts, + { + name: 'Total Income', + total: profitLoss.income.total, + rowType: 'income_total', + } + ], + }, + { + name: 'Expenses', + total: profitLoss.expenses.total, + children: [ + ...profitLoss.expenses.accounts, + { + name: 'Total Expenses', + total: profitLoss.expenses.total, + rowType: 'expense_total', + } + ], + }, + { + name: 'Net Income', + total: profitLoss.net_income.total, + rowType: 'net_income', + } + ] +}; + export default createReducer(initialState, { [t.BALANCE_SHEET_STATEMENT_SET]: (state, action) => { - const index = getBalanceSheetIndexByQuery(state.balanceSheets, action.query); - + const index = getFinancialSheetIndexByQuery(state.balanceSheet.sheets, action.query); + const balanceSheet = { - balances: action.data.balance_sheet, + accounts: action.data.accounts, columns: Object.values(action.data.columns), query: action.data.query, }; if (index !== -1) { - state.balanceSheets[index] = balanceSheet; + state.balanceSheet.sheets[index] = balanceSheet; } else { - state.balanceSheets.push(balanceSheet); + state.balanceSheet.sheets.push(balanceSheet); } }, + [t.BALANCE_SHEET_LOADING]: (state, action) => { + state.balanceSheet.loading = !!action.loading; + }, + [t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => { const index = getFinancialSheetIndexByQuery(state.trialBalance.sheets, action.query); const trailBalanceSheet = { @@ -139,13 +181,18 @@ export default createReducer(initialState, { } }, + [t.GENERAL_LEDGER_SHEET_LOADING]: (state, action) => { + state.generalLedger.loading = !!action.loading; + }, + [t.PROFIT_LOSS_SHEET_SET]: (state, action) => { const index = getFinancialSheetIndexByQuery(state.profitLoss.sheets, action.query); - + const profitLossSheet = { query: action.query, profitLoss: action.profitLoss, columns: action.columns, + tableRows: mapProfitLossToTableRows(action.profitLoss), }; if (index !== -1) { state.profitLoss.sheets[index] = profitLossSheet; diff --git a/client/src/store/financialStatement/financialStatements.selectors.js b/client/src/store/financialStatement/financialStatements.selectors.js index f458a5a53..9c2183e97 100644 --- a/client/src/store/financialStatement/financialStatements.selectors.js +++ b/client/src/store/financialStatement/financialStatements.selectors.js @@ -38,97 +38,19 @@ export const getFinancialSheetColumns = (sheets, index) => { * @param {array} sheets * @param {number} index */ -export const getFinancialSheetsQuery = (sheets, index) => { +export const getFinancialSheetQuery = (sheets, index) => { const sheet = getFinancialSheet(sheets, index); - return (sheet && sheet.query) ? sheet.columns : {}; + return (sheet && sheet.query) ? sheet.query : {}; }; -// Balance Sheet. -export const getBalanceSheetByQuery = (balanceSheets, query) => { - return balanceSheets.find(balanceSheet => { - return getObjectDiff(query, balanceSheet.query).length === 0; - }); -}; - -export const getBalanceSheetIndexByQuery = (balanceSheets, query) => { - return balanceSheets.findIndex((balanceSheet) => { - return getObjectDiff(query, balanceSheet.query).length === 0; - }); -}; - -export const getBalanceSheetByIndex = (balanceSheets, sheetIndex) => { - return balanceSheets[sheetIndex]; -}; - -export const getBalanceSheetQuery = (balanceSheets, sheetIndex) => { - if (typeof balanceSheets[sheetIndex] === 'object') { - return balanceSheets[sheetIndex].query || {}; - } - return {}; -}; - -export const getBalanceSheetAssetsAccounts = (balanceSheets, sheetIndex) => { - if (typeof balanceSheets[sheetIndex] === 'object') { - return balanceSheets[sheetIndex].balances.assets.accounts || []; - } - return []; -}; - -export const getBalanceSheetLiabilitiesAccounts = (balanceSheets, sheetIndex) => { - if (typeof balanceSheets[sheetIndex] === 'object') { - return balanceSheets[sheetIndex].balances.liabilities_equity.accounts || []; - } - return []; -}; - -export const getBalanceSheetColumns = (balanceSheets, sheetIndex) => { - if (typeof balanceSheets[sheetIndex] === 'object') { - return balanceSheets[sheetIndex].columns; - } - return []; +export const getFinancialSheetAccounts = (sheets, index) => { + const sheet = getFinancialSheet(sheets, index); + return (sheet && sheet.accounts) ? sheet.accounts : []; }; -// Trial Balance Sheet. -export const getTrialBalanceSheetIndex = (trialBalanceSheets, query) => { - return trialBalanceSheets.find((trialBalanceSheet) => { - return getObjectDiff(query, trialBalanceSheet.query).length === 0; - }); -}; - -export const getTrialBalanceAccounts = (trialBalanceSheets, sheetIndex) => { - if (typeof trialBalanceSheets[sheetIndex] === 'object') { - return trialBalanceSheets[sheetIndex].accounts; - } - return []; -}; - -export const getTrialBalanceQuery = (trialBalanceSheets, sheetIndex) => { - if (typeof trialBalanceSheets[sheetIndex] === 'object') { - return trialBalanceSheets[sheetIndex].query; - } - return []; -}; - -// Profit/Loss Sheet selectors. -export const getProfitLossSheetIndex = (profitLossSheets, query) => { - return profitLossSheets.find((profitLossSheet) => { - return getObjectDiff(query, profitLossSheet.query).length === 0; - }); -} - -export const getProfitLossSheet = (profitLossSheets, index) => { - return (typeof profitLossSheets[index] !== 'undefined') ? - profitLossSheets[index] : null; -}; - -export const getProfitLossSheetColumns = (profitLossSheets, index) => { - const sheet = getProfitLossSheet(profitLossSheets, index); - return (sheet) ? sheet.columns : []; -}; - -export const getProfitLossSheetAccounts = (profitLossSheets, index) => { - const sheet = getProfitLossSheet(profitLossSheets, index); - return (sheet) ? sheet.accounts : []; +export const getFinancialSheetTableRows = (sheets, index) => { + const sheet = getFinancialSheet(sheets, index); + return (sheet && sheet.tableRows) ? sheet.tableRows : []; }; \ No newline at end of file diff --git a/client/src/store/financialStatement/financialStatements.types.js b/client/src/store/financialStatement/financialStatements.types.js index 0804852b3..7d969c316 100644 --- a/client/src/store/financialStatement/financialStatements.types.js +++ b/client/src/store/financialStatement/financialStatements.types.js @@ -2,9 +2,14 @@ export default { GENERAL_LEDGER_STATEMENT_SET: 'GENERAL_LEDGER_STATEMENT_SET', + GENERAL_LEDGER_SHEET_LOADING: 'GENERAL_LEDGER_SHEET_LOADING', + BALANCE_SHEET_STATEMENT_SET: 'BALANCE_SHEET_STATEMENT_SET', + BALANCE_SHEET_LOADING: 'BALANCE_SHEET_LOADING', + TRAIL_BALANCE_STATEMENT_SET: 'TRAIL_BALANCE_STATEMENT_SET', TRIAL_BALANCE_SHEET_LOADING: 'TRIAL_BALANCE_SHEET_LOADING', + JOURNAL_SHEET_SET: 'JOURNAL_SHEET_SET', JOURNAL_SHEET_LOADING: 'JOURNAL_SHEET_LOADING', diff --git a/client/src/style/components/data-table.scss b/client/src/style/components/data-table.scss index ec124e6c3..2706f6237 100644 --- a/client/src/style/components/data-table.scss +++ b/client/src/style/components/data-table.scss @@ -1,5 +1,5 @@ -.bigcapital-datatable{ +.bigcapital-datatable{ display: block; overflow-x: auto; overflow-y: hidden; @@ -138,8 +138,7 @@ } } - .no-results{ - + .no-results{ color: #666; .td{ @@ -149,6 +148,15 @@ } } + &.has-sticky-header{ + + .thead{ + .tr .th{ + position: sticky; + } + } + } + &--financial-report{ .thead{ diff --git a/client/src/style/objects/buttons.scss b/client/src/style/objects/buttons.scss index 4fc099465..c41e73483 100644 --- a/client/src/style/objects/buttons.scss +++ b/client/src/style/objects/buttons.scss @@ -30,7 +30,7 @@ min-height: 32px; background-color: #E6EFFB; color: #555555; - box-shadow: 0 0 0; + box-shadow: 0 0 0 transparent; .form-group--select-list &{ border-radius: 0; @@ -38,7 +38,7 @@ &, &:hover{ background: #fff; - box-shadow: 0 0 0; + box-shadow: 0 0 0 transparent; border: 1px solid #ced4da; } } @@ -53,7 +53,7 @@ &, &:hover{ - box-shadow: 0 0 0; + box-shadow: 0 0 0 transparent; } } } diff --git a/client/src/style/objects/form.scss b/client/src/style/objects/form.scss index 15efd2542..cd1bb465c 100644 --- a/client/src/style/objects/form.scss +++ b/client/src/style/objects/form.scss @@ -94,4 +94,110 @@ label{ .#{$ns}-icon-caret-down{ color: #8D8D8D; } +} + + + + +///@extend + + + +.#{$ns}-control { + + input:checked ~ .#{$ns}-control-indicator { + box-shadow: 0 0 0 transparent; + background-color: transparent; + background-image: none; + } + &:hover input:checked ~ .#{$ns}-control-indicator { + box-shadow: 0 0 0 transparent; + background-color: transparent; + } + input:not(:disabled):active:checked ~ .#{$ns}-control-indicator { + box-shadow: 0 0 0 transparent; + background: transparent; + } + input:disabled:checked ~ .#{$ns}-control-indicator { + box-shadow: none; + background: transparent; + } + + &.#{$ns}-disabled { + cursor: not-allowed; + color: $pt-text-color-disabled; + } + + &.#{$ns}-inline { + display: inline-block; + margin-right: $pt-grid-size * 2; + } + + .#{$ns}-control-indicator { + box-shadow: 0 0 0 transparent; + background-clip: padding-box; + background-color: transparent; + background-image: none; + width: 18px; + height: 18px; + + &::before { + width: 18px; + height: 18px; + } + } + + &:hover .#{$ns}-control-indicator { + background-color: transparent; + } + + input:not(:disabled):active ~ .#{$ns}-control-indicator { + box-shadow: 0 0 0 transparent; + background: transparent; + } + + /* + Radio + + Markup: + + + :checked - Selected + :disabled - Disabled. Also add .#{$ns}-disabled to .#{$ns}-control to change text color (not shown below). + .#{$ns}-align-right - Right-aligned indicator + .#{$ns}-large - Large + + Styleguide radio + */ + &.#{$ns}-radio { + + .#{$ns}-control-indicator{ + border: 2px solid #cecece; + + &::before{ + height: 14px; + width: 14px; + } + } + + input:checked ~ .#{$ns}-control-indicator{ + border-color: #137cbd; + + &::before { + background-image: radial-gradient(#137cbd 40%, transparent 40%); + } + } + + input:checked:disabled ~ .#{$ns}-control-indicator::before { + opacity: 0.5; + } + + input:focus ~ .#{$ns}-control-indicator { + -moz-outline-radius: $control-indicator-size; + } + } } \ No newline at end of file diff --git a/client/src/style/pages/financial-statements.scss b/client/src/style/pages/financial-statements.scss index 8963cfef4..8690ef6a1 100644 --- a/client/src/style/pages/financial-statements.scss +++ b/client/src/style/pages/financial-statements.scss @@ -7,26 +7,46 @@ padding: 25px 26px 25px; background: #FDFDFD; - .bp3-form-group .bp3-label{ - font-weight: 500; - font-size: 13px; - color: #444; + .bp3-form-group, + .radio-group---accounting-basis{ + + .bp3-label{ + font-weight: 500; + font-size: 13px; + color: #444; + } + } + .bp3-button.button--submit-filter{ + min-height: 34px; + padding-left: 16px; + padding-right: 16px; + } + .radio-group---accounting-basis{ + .bp3-label{ + margin-bottom: 12px; + } } } &__body{ padding-left: 20px; padding-right: 20px; + display: flex; + justify-content: center; + align-items: center; } } .financial-sheet{ border: 1px solid #E2E2E2; min-width: 640px; - width: 0; - padding: 20px; - padding-top: 30px; + width: auto; + padding: 30px 20px; + max-width: 100%; margin: 35px auto; + min-height: 400px; + display: flex; + flex-direction: column; &__title{ margin: 0; @@ -57,14 +77,24 @@ } } } - &__accounting-basis{ - + &__basis{ + color: #888; + text-align: center; + margin-top: auto; + padding-top: 16px; + font-size: 12px; + } + .dashboard__loading-indicator{ + margin-left: auto; + margin-right: auto; + } + + &--expended{ + width: auto; } - &--trial-balance{ min-width: 720px; } - &--general-ledger, &--journal{ width: auto; @@ -73,11 +103,8 @@ margin-top: 10px; border-color: #EEEDED; } - &--journal{ - .financial-sheet__table{ - .tbody{ .tr .td{ padding: 0.4rem; @@ -96,4 +123,25 @@ } } } + + &--profit-loss-sheet{ + + .financial-sheet__table{ + .tbody{ + .account_code.td{ + color: #666; + } + + .row--income_total, + .row--expense_total, + .row--net_income{ + font-weight: 600; + + .total.td{ + border-bottom-color: #555; + } + } + } + } + } } \ No newline at end of file diff --git a/client/src/utils.js b/client/src/utils.js index 8f6f0936b..77ade104c 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -111,4 +111,24 @@ export const parseDateRangeQuery = (keyword) => { from_date: moment().startOf(query.range).toDate(), to_date: moment().endOf(query.range).toDate(), }; -}; \ No newline at end of file +}; + + +export const defaultExpanderReducer = (tableRows, level) => { + let currentLevel = 1; + const expended = []; + + const walker = (rows, parentIndex = null) => { + return rows.forEach((row, index) => { + const _index = parentIndex ? `${parentIndex}.${index}` : `${index}`; + expended[_index] = true; + + if (row.children && currentLevel < level) { + walker(row.children, _index); + } + currentLevel++; + }, {}); + }; + walker(tableRows); + return expended; +} \ No newline at end of file diff --git a/server/src/http/controllers/FinancialStatements.js b/server/src/http/controllers/FinancialStatements.js index be387033c..dd3c2e522 100644 --- a/server/src/http/controllers/FinancialStatements.js +++ b/server/src/http/controllers/FinancialStatements.js @@ -274,7 +274,9 @@ export default { query('accounting_method').optional().isIn(['cash', 'accural']), query('from_date').optional(), query('to_date').optional(), - query('display_columns_by').optional().isIn(['total', 'year', 'month', 'week', 'day', 'quarter']), + query('display_columns_type').optional().isIn(['date_periods', 'total']), + query('display_columns_by').optional({ nullable: true, checkFalsy: true }) + .isIn(['year', 'month', 'week', 'day', 'quarter']), query('number_format.no_cents').optional().isBoolean().toBoolean(), query('number_format.divide_1000').optional().isBoolean().toBoolean(), query('none_zero').optional().isBoolean().toBoolean(), @@ -288,7 +290,8 @@ export default { }); } const filter = { - display_columns_by: 'total', + display_columns_type: 'total', + display_columns_by: '', from_date: moment().startOf('year').format('YYYY-MM-DD'), to_date: moment().endOf('year').format('YYYY-MM-DD'), number_format: { @@ -299,7 +302,6 @@ export default { basis: 'cash', ...req.query, }; - const balanceSheetTypes = await AccountType.query().where('balance_sheet', true); // Fetch all balance sheet accounts. @@ -317,91 +319,76 @@ export default { // Account balance formmatter based on the given query. const balanceFormatter = formatNumberClosure(filter.number_format); - const filterDateType = filter.display_columns_by === 'total' + const comparatorDateType = filter.display_columns_type === 'total' ? 'day' : filter.display_columns_by; - // Gets the date range set from start to end date. - const dateRangeSet = dateRangeCollection( - filter.from_date, - filter.to_date, - filterDateType, - ); - // Retrieve the asset balance sheet. - const assets = accounts - .filter((account) => ( - account.type.normal === 'debit' - && (account.transactions.length > 0 || !filter.none_zero) - )) - .map((account) => { + const dateRangeSet = (filter.display_columns_type === 'date_periods') + ? dateRangeCollection( + filter.from_date, filter.to_date, comparatorDateType, + ) : []; + + const totalPeriods = (account) => { + // Gets the date range set from start to end date. + return { + total_periods: dateRangeSet.map((date) => { + const balance = journalEntries.getClosingBalance(account.id, date, comparatorDateType); + return { + date, + formatted_amount: balanceFormatter(balance), + amount: balance, + }; + }), + }; + }; + + const accountsMapper = (balanceSheetAccounts) => { + return balanceSheetAccounts.map((account) => { // Calculates the closing balance to the given date. const closingBalance = journalEntries.getClosingBalance(account.id, filter.to_date); - const type = filter.display_columns_by; return { ...pick(account, ['id', 'index', 'name', 'code']), - ...(type !== 'total') ? { - periods_balance: dateRangeSet.map((date) => { - const balance = journalEntries.getClosingBalance(account.id, date, filterDateType); - return { - date, - formatted_amount: balanceFormatter(balance), - amount: balance, - }; - }), - } : {}, - balance: { + // Date periods when display columns. + ...(filter.display_columns_type === 'date_periods') && totalPeriods(account), + + total: { formatted_amount: balanceFormatter(closingBalance), amount: closingBalance, date: filter.to_date, }, }; }); + }; + // Retrieve all assets accounts. + const assetsAccounts = accounts.filter((account) => ( + account.type.normal === 'debit' + && (account.transactions.length > 0 || !filter.none_zero))); + + // Retrieve all liability accounts. + const liabilitiesAccounts = accounts.filter((account) => ( + account.type.normal === 'credit' + && (account.transactions.length > 0 || !filter.none_zero))); + + // Retrieve the asset balance sheet. + const assets = accountsMapper(assetsAccounts); // Retrieve liabilities and equity balance sheet. - const liabilitiesEquity = accounts - .filter((account) => ( - account.type.normal === 'credit' - && (account.transactions.length > 0 || !filter.none_zero) - )) - .map((account) => { - // Calculates the closing balance to the given date. - const closingBalance = journalEntries.getClosingBalance(account.id, filter.to_date); - const type = filter.display_columns_by; - - return { - ...pick(account, ['id', 'index', 'name', 'code']), - ...(type !== 'total') ? { - periods_balance: dateRangeSet.map((date) => { - const balance = journalEntries.getClosingBalance(account.id, date, filterDateType); - return { - date, - formatted_amount: balanceFormatter(balance), - amount: balance, - }; - }), - } : {}, - balance: { - formatted_amount: balanceFormatter(closingBalance), - amount: closingBalance, - date: filter.to_date, - }, - }; - }); + const liabilitiesEquity = accountsMapper(liabilitiesAccounts); return res.status(200).send({ query: { ...filter }, columns: { ...dateRangeSet }, - balance_sheet: { - assets: { - title: 'Assets', - accounts: [...assets], + accounts: [ + { + name: 'Assets', + children: [...assets], }, - liabilities_equity: { - title: 'Liabilities & Equity', - accounts: [...liabilitiesEquity], + { + name: 'Liabilities & Equity', + children: [...liabilitiesEquity], }, - }, + ], }); }, }, @@ -492,9 +479,8 @@ export default { query('display_columns_type').optional().isIn([ 'total', 'date_periods', ]), - query('display_columns_by').optional().isIn([ - 'year', 'month', 'week', 'day', 'quarter', - ]), + query('display_columns_by').optional({ nullable: true, checkFalsy: true }) + .isIn(['year', 'month', 'week', 'day', 'quarter']), ], async handler(req, res) { const validationErrors = validationResult(req); diff --git a/server/tests/routes/financial_statements.test.js b/server/tests/routes/financial_statements.test.js index b86b593e3..7928b8d49 100644 --- a/server/tests/routes/financial_statements.test.js +++ b/server/tests/routes/financial_statements.test.js @@ -363,7 +363,7 @@ describe('routes: `/financial_statements`', () => { }); }); - describe('routes: `financial_statements/balance_sheet`', () => { + describe.only('routes: `financial_statements/balance_sheet`', () => { it('Should response unauthorzied in case the user was not authorized.', async () => { const res = await request() .get('/api/financial_statements/balance_sheet') @@ -406,21 +406,23 @@ describe('routes: `/financial_statements`', () => { expect(res.body.balance_sheet.liabilities_equity.accounts).to.be.a('array'); }); - it('Should retrieve assets/liabilities total balance between the given date range.', async () => { + it.only('Should retrieve assets/liabilities total balance between the given date range.', async () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) .query({ - display_columns_by: 'total', + display_columns_type: 'total', from_date: '2012-01-01', to_date: '2032-02-02', }) .send(); - expect(res.body.balance_sheet.assets.accounts[0].balance).deep.equals({ + console.log(res.body.balance_sheet.assets.accounts); + + expect(res.body.balance_sheet.assets.accounts[0].total).deep.equals({ amount: 4000, formatted_amount: 4000, date: '2032-02-02', }); - expect(res.body.balance_sheet.liabilities_equity.accounts[0].balance).deep.equals({ + expect(res.body.balance_sheet.liabilities_equity.accounts[0].total).deep.equals({ amount: 2000, formatted_amount: 2000, date: '2032-02-02', }); }); diff --git a/server/tests/routes/itemsCategories.test.js b/server/tests/routes/itemsCategories.test.js index 7f20624d6..2f0ae0ad1 100644 --- a/server/tests/routes/itemsCategories.test.js +++ b/server/tests/routes/itemsCategories.test.js @@ -8,7 +8,7 @@ import knex from '@/database/knex'; let loginRes; -describe.only('routes: /item_categories/', () => { +describe('routes: /item_categories/', () => { beforeEach(async () => { loginRes = await login(); });