This commit is contained in:
a.bouhuolia
2021-02-22 17:00:57 +02:00
21 changed files with 432 additions and 267 deletions

View File

@@ -4,65 +4,65 @@ import { omit } from 'lodash';
import MultiSelect from 'components/MultiSelect'; import MultiSelect from 'components/MultiSelect';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
export default function CustomersMultiSelect({ export default function ContactsMultiSelect({
customers, contacts,
defaultText = <T id={'all_customers'} />, defaultText = <T id={'all_customers'} />,
buttonProps, buttonProps,
onCustomerSelected, onCustomerSelected: onContactSelected,
...selectProps ...selectProps
}) { }) {
const [selectedCustomers, setSelectedCustomers] = useState({}); const [selectedContacts, setSelectedContacts] = useState({});
const isCustomerSelect = useCallback( const isContactSelect = useCallback(
(id) => typeof selectedCustomers[id] !== 'undefined', (id) => typeof selectedContacts[id] !== 'undefined',
[selectedCustomers], [selectedContacts],
); );
const customerRenderer = useCallback( const contactRenderer = useCallback(
(customer, { handleClick }) => ( (contact, { handleClick }) => (
<MenuItem <MenuItem
icon={isCustomerSelect(customer.id) ? 'tick' : 'blank'} icon={isContactSelect(contact.id) ? 'tick' : 'blank'}
text={customer.display_name} text={contact.display_name}
key={customer.id} key={contact.id}
onClick={handleClick} onClick={handleClick}
/> />
), ),
[isCustomerSelect], [isContactSelect],
); );
const countSelected = useMemo(() => Object.values(selectedCustomers).length, [ const countSelected = useMemo(() => Object.values(selectedContacts).length, [
selectedCustomers, selectedContacts,
]); ]);
const onContactSelect = useCallback( const onContactSelect = useCallback(
({ id }) => { ({ id }) => {
const selected = { const selected = {
...(isCustomerSelect(id) ...(isContactSelect(id)
? { ? {
...omit(selectedCustomers, [id]), ...omit(selectedContacts, [id]),
} }
: { : {
...selectedCustomers, ...selectedContacts,
[id]: true, [id]: true,
}), }),
}; };
setSelectedCustomers({ ...selected }); setSelectedContacts({ ...selected });
onCustomerSelected && onCustomerSelected(selected); onContactSelected && onContactSelected(selected);
}, },
[ [
setSelectedCustomers, setSelectedContacts,
selectedCustomers, selectedContacts,
isCustomerSelect, isContactSelect,
onCustomerSelected, onContactSelected,
], ],
); );
return ( return (
<MultiSelect <MultiSelect
items={customers} items={contacts}
noResults={<MenuItem disabled={true} text="No results." />} noResults={<MenuItem disabled={true} text="No results." />}
itemRenderer={customerRenderer} itemRenderer={contactRenderer}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
filterable={true} filterable={true}
onItemSelect={onContactSelect} onItemSelect={onContactSelect}

View File

@@ -43,7 +43,7 @@ import DashboardCard from './Dashboard/DashboardCard';
import InputPrependText from './Forms/InputPrependText'; import InputPrependText from './Forms/InputPrependText';
import PageFormBigNumber from './PageFormBigNumber'; import PageFormBigNumber from './PageFormBigNumber';
import AccountsMultiSelect from './AccountsMultiSelect'; import AccountsMultiSelect from './AccountsMultiSelect';
import CustomersMultiSelect from './CustomersMultiSelect'; import ContactsMultiSelect from './ContactsMultiSelect';
import Skeleton from './Skeleton' import Skeleton from './Skeleton'
import ContextMenu from './ContextMenu' import ContextMenu from './ContextMenu'
import TableFastCell from './Datatable/TableFastCell'; import TableFastCell from './Datatable/TableFastCell';
@@ -99,7 +99,7 @@ export {
PageFormBigNumber, PageFormBigNumber,
AccountsMultiSelect, AccountsMultiSelect,
DataTableEditable, DataTableEditable,
CustomersMultiSelect, ContactsMultiSelect,
TableFastCell, TableFastCell,
Skeleton, Skeleton,
ContextMenu, ContextMenu,

View File

@@ -1,27 +1,20 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useState, useCallback, useEffect } from 'react';
import { useIntl } from 'react-intl';
import { queryCache, useQuery } from 'react-query';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/ARAgingSummary.scss';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import APAgingSummaryActionsBar from './APAgingSummaryActionsBar';
import APAgingSummaryHeader from './APAgingSummaryHeader'; import APAgingSummaryHeader from './APAgingSummaryHeader';
import APAgingSummaryActionsBar from './APAgingSummaryActionsBar';
import APAgingSummaryTable from './APAgingSummaryTable'; import APAgingSummaryTable from './APAgingSummaryTable';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { APAgingSummaryProvider } from './APAgingSummaryProvider';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withAPAgingSummaryActions from './withAPAgingSummaryActions'
import withAPAgingSummaryActions from './withAPAgingSummaryActions';
import withAPAgingSummary from './withAPAgingSummary';
import { transformFilterFormToQuery } from './common';
import { compose } from 'utils'; import { compose } from 'utils';
import 'style/pages/FinancialStatements/ARAgingSummary.scss';
/** /**
* AP aging summary report. * AP aging summary report.
*/ */
@@ -29,82 +22,47 @@ function APAgingSummary({
// #withSettings // #withSettings
organizationName, organizationName,
// #withDashboardActions
changePageTitle,
setDashboardBackLink,
// #withAPAgingSummary
APAgingSummaryRefresh,
// #withAPAgingSummaryActions // #withAPAgingSummaryActions
requestPayableAgingSummary, toggleAPAgingSummaryFilterDrawer: toggleDisplayFilterDrawer,
refreshAPAgingSummary,
toggleFilterAPAgingSummary,
}) { }) {
const { formatMessage } = useIntl(); const [filter, setFilter] = useState({
const [query, setQuery] = useState({
asDate: moment().endOf('day').format('YYYY-MM-DD'), asDate: moment().endOf('day').format('YYYY-MM-DD'),
agingBeforeDays: 30, agingBeforeDays: 30,
agingPeriods: 3, agingPeriods: 3,
}); });
// handle fetching payable aging summary report. // Handle filter submit.
const fetchAPAgingSummarySheet = useQuery( const handleFilterSubmit = useCallback((filter) => {
['payable-aging-summary', query],
(key, _query) =>
requestPayableAgingSummary({
...transformFilterFormToQuery(_query),
}),
{ enable: true },
);
useEffect(() => {
changePageTitle(formatMessage({ id: 'payable_aging_summary' }));
}, [changePageTitle, formatMessage]);
useEffect(() => {
if (APAgingSummaryRefresh) {
queryCache.invalidateQueries('payable-aging-summary');
refreshAPAgingSummary(false);
}
}, [APAgingSummaryRefresh, refreshAPAgingSummary]);
useEffect(() => {
setDashboardBackLink(true);
return () => {
setDashboardBackLink(false);
};
}, [setDashboardBackLink]);
const handleFilterSubmit = (filter) => {
const _filter = { const _filter = {
...filter, ...filter,
asDate: moment(filter.asDate).format('YYYY-MM-DD'), asDate: moment(filter.asDate).format('YYYY-MM-DD'),
}; };
setQuery(_filter); setFilter(_filter);
refreshAPAgingSummary(true); }, []);
toggleFilterAPAgingSummary(false);
};
// Handle number format submit.
const handleNumberFormatSubmit = (numberFormat) => { const handleNumberFormatSubmit = (numberFormat) => {
setQuery({ setFilter({
...query, ...filter,
numberFormat, numberFormat,
}); });
refreshAPAgingSummary(true);
}; };
// Hide the report filter drawer once the page unmount.
useEffect(() => () => {
toggleDisplayFilterDrawer(false);
}, [toggleDisplayFilterDrawer])
return ( return (
<DashboardInsider> <APAgingSummaryProvider filter={filter}>
<APAgingSummaryActionsBar <APAgingSummaryActionsBar
numberFormat={query.numberFormat} numberFormat={filter.numberFormat}
onNumberFormatSubmit={handleNumberFormatSubmit} onNumberFormatSubmit={handleNumberFormatSubmit}
/> />
<DashboardPageContent> <DashboardPageContent>
<FinancialStatement> <FinancialStatement>
<APAgingSummaryHeader <APAgingSummaryHeader
pageFilter={query} pageFilter={filter}
onSubmitFilter={handleFilterSubmit} onSubmitFilter={handleFilterSubmit}
/> />
<div className={'financial-statement__body'}> <div className={'financial-statement__body'}>
@@ -112,17 +70,13 @@ function APAgingSummary({
</div> </div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </APAgingSummaryProvider>
); );
} }
export default compose( export default compose(
withDashboardActions,
withAPAgingSummaryActions,
withSettings(({ organizationSettings }) => ({ withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name, organizationName: organizationSettings?.name,
})),
withAPAgingSummary(({ APAgingSummaryRefresh }) => ({
APAgingSummaryRefresh,
})), })),
withAPAgingSummaryActions
)(APAgingSummary); )(APAgingSummary);

View File

@@ -8,7 +8,6 @@ import {
PopoverInteractionKind, PopoverInteractionKind,
Position, Position,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { safeInvoke } from '@blueprintjs/core/lib/esm/common/utils';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -17,36 +16,42 @@ import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { Icon } from 'components'; import { Icon } from 'components';
import NumberFormatDropdown from 'components/NumberFormatDropdown'; import NumberFormatDropdown from 'components/NumberFormatDropdown';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import withAPAgingSummary from './withAPAgingSummary'; import withAPAgingSummary from './withAPAgingSummary';
import withARAgingSummaryActions from './withAPAgingSummaryActions'; import withAPAgingSummaryActions from './withAPAgingSummaryActions';
import { compose } from 'utils'; import { saveInvoke, compose } from 'utils';
/** /**
* AP Aging summary sheet - Actions bar. * AP Aging summary sheet - Actions bar.
*/ */
function APAgingSummaryActionsBar({ function APAgingSummaryActionsBar({
//#withPayableAgingSummary // #withPayableAgingSummary
payableAgingFilter, payableAgingFilter,
payableAgingLoading,
//#withARAgingSummaryActions // #withARAgingSummaryActions
toggleFilterAPAgingSummary, toggleAPAgingSummaryFilterDrawer: toggleFilterDrawerDisplay,
refreshAPAgingSummary,
//#ownProps //#ownProps
numberFormat, numberFormat,
onNumberFormatSubmit, onNumberFormatSubmit,
}) { }) {
const handleFilterToggleClick = () => toggleFilterAPAgingSummary(); const { isAPAgingFetching, refetch } = useAPAgingSummaryContext();
const handleFilterToggleClick = () => {
toggleFilterDrawerDisplay();
}
// handle recalculate report button. // handle recalculate report button.
const handleRecalculateReport = () => refreshAPAgingSummary(true); const handleRecalculateReport = () => {
refetch();
}
// handle number format submit. // handle number format submit.
const handleNumberFormatSubmit = (numberFormat) => const handleNumberFormatSubmit = (numberFormat) => {
safeInvoke(onNumberFormatSubmit, numberFormat); saveInvoke(onNumberFormatSubmit, numberFormat);
}
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
<NavbarGroup> <NavbarGroup>
@@ -76,7 +81,7 @@ function APAgingSummaryActionsBar({
<NumberFormatDropdown <NumberFormatDropdown
numberFormat={numberFormat} numberFormat={numberFormat}
onSubmit={handleNumberFormatSubmit} onSubmit={handleNumberFormatSubmit}
submitDisabled={payableAgingLoading} submitDisabled={isAPAgingFetching}
/> />
} }
minimal={true} minimal={true}
@@ -113,11 +118,8 @@ function APAgingSummaryActionsBar({
} }
export default compose( export default compose(
withARAgingSummaryActions, withAPAgingSummaryActions,
withAPAgingSummary( withAPAgingSummary(({ APAgingSummaryFilterDrawer }) => ({
({ payableAgingSummaryLoading, payableAgingSummaryFilter }) => ({ isFilterDrawerOpen: APAgingSummaryFilterDrawer
payableAgingLoading: payableAgingSummaryLoading, }))
payableAgingFilter: payableAgingSummaryFilter,
}),
),
)(APAgingSummaryActionsBar); )(APAgingSummaryActionsBar);

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { Formik, Form, validateYupSchema } from 'formik'; import { Formik, Form } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import moment from 'moment'; import moment from 'moment';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
@@ -17,12 +17,15 @@ import { compose } from 'utils';
* AP Aging Summary Report - Drawer Header. * AP Aging Summary Report - Drawer Header.
*/ */
function APAgingSummaryHeader({ function APAgingSummaryHeader({
// #ownProps
pageFilter, pageFilter,
onSubmitFilter, onSubmitFilter,
payableAgingFilter,
// #withPayableAgingSummaryActions // #withAPAgingSummaryActions
toggleFilterAPAgingSummary, toggleAPAgingSummaryFilterDrawer: toggleFilterDrawerDisplay,
// #withAPAgingSummary
isFilterDrawerOpen
}) { }) {
const validationSchema = Yup.object({ const validationSchema = Yup.object({
as_date: Yup.date().required().label('asDate'), as_date: Yup.date().required().label('asDate'),
@@ -38,23 +41,32 @@ function APAgingSummaryHeader({
.label('agingPeriods'), .label('agingPeriods'),
}); });
// initial values. // Initial values.
const initialValues = { const initialValues = {
as_date: moment(pageFilter.asDate).toDate(), as_date: moment(pageFilter.asDate).toDate(),
aging_days_before: 30, aging_days_before: 30,
aging_periods: 3, aging_periods: 3,
}; };
// handle form submit. // Handle form submit.
const handleSubmit = (values, { setSubmitting }) => { const handleSubmit = (values, { setSubmitting }) => {
onSubmitFilter(values); onSubmitFilter(values);
toggleFilterDrawerDisplay(false);
setSubmitting(false); setSubmitting(false);
}; };
// handle cancel button click. // handle cancel button click.
const handleCancelClick = () => toggleFilterAPAgingSummary(); const handleCancelClick = () => {
toggleFilterDrawerDisplay(false);
};
// Handle the drawer closing.
const handleDrawerClose = () => {
toggleFilterDrawerDisplay(false);
};
return ( return (
<FinancialStatementHeader isOpen={payableAgingFilter}> <FinancialStatementHeader isOpen={isFilterDrawerOpen} drawerProps={{ onClose: handleDrawerClose }}>
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
@@ -84,7 +96,7 @@ function APAgingSummaryHeader({
export default compose( export default compose(
withAPAgingSummaryActions, withAPAgingSummaryActions,
withAPAgingSummary(({ payableAgingSummaryFilter }) => ({ withAPAgingSummary(({ APAgingSummaryFilterDrawer }) => ({
payableAgingFilter: payableAgingSummaryFilter, isFilterDrawerOpen: APAgingSummaryFilterDrawer,
})), })),
)(APAgingSummaryHeader); )(APAgingSummaryHeader);

View File

@@ -1,9 +1,17 @@
import React from 'react'; import React from 'react';
import { FastField } from 'formik'; import { FastField } from 'formik';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { Intent, FormGroup, InputGroup, Position } from '@blueprintjs/core'; import {
Intent,
FormGroup,
InputGroup,
Position,
Classes,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { Row, Col, FieldHint } from 'components'; import classNames from 'classnames';
import { ContactsMultiSelect, Row, Col, FieldHint } from 'components';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { import {
momentFormatter, momentFormatter,
tansformDateValue, tansformDateValue,
@@ -15,6 +23,7 @@ import {
* AP Aging Summary - Drawer Header - General Fields. * AP Aging Summary - Drawer Header - General Fields.
*/ */
export default function APAgingSummaryHeaderGeneral() { export default function APAgingSummaryHeaderGeneral() {
const { vendors } = useAPAgingSummaryContext();
return ( return (
<div> <div>
<Row> <Row>
@@ -72,6 +81,19 @@ export default function APAgingSummaryHeaderGeneral() {
</FastField> </FastField>
</Col> </Col>
</Row> </Row>
<Row>
<Col xs={5}>
<FormGroup
label={<T id={'specific_vendors'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ContactsMultiSelect
defaultText={<T id={'all_vendors'} />}
contacts={vendors}
/>
</FormGroup>
</Col>
</Row>
</div> </div>
); );
} }

View File

@@ -0,0 +1,49 @@
import React, { useMemo, createContext, useContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useAPAgingSummaryReport,useARAgingSummaryReport , useVendors } from 'hooks/query';
import { transformFilterFormToQuery } from '../common';
const APAgingSummaryContext = createContext();
/**
* A/P aging summary provider.
*/
function APAgingSummaryProvider({ filter, ...props }) {
// Transformers the filter from to the Url query.
const query = useMemo(() => transformFilterFormToQuery(filter), [filter]);
const {
data: APAgingSummary,
isLoading: isAPAgingLoading,
isFetching: isAPAgingFetching,
refetch,
} = useAPAgingSummaryReport(query);
// Retrieve the vendors list.
const {
data: { vendors },
isFetching: isVendorsLoading,
} = useVendors();
const provider = {
APAgingSummary,
vendors,
isAPAgingLoading,
isAPAgingFetching,
isVendorsLoading,
refetch,
};
return (
<DashboardInsider name={'AP-Aging-Summary'}>
<APAgingSummaryContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useAPAgingSummaryContext = () => useContext(APAgingSummaryContext);
export { APAgingSummaryProvider, useAPAgingSummaryContext };

View File

@@ -1,71 +1,29 @@
import React, { useMemo, useCallback } from 'react'; import React, { useCallback } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { DataTable } from 'components'; import { DataTable } from 'components';
import FinancialSheet from 'components/FinancialSheet'; import FinancialSheet from 'components/FinancialSheet';
import withAPAgingSummary from './withAPAgingSummary'; import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { useAPAgingSummaryColumns } from './components';
import { compose, getColumnWidth } from 'utils';
/** /**
* AP aging summary table sheet. * AP aging summary table sheet.
*/ */
function APAgingSummaryTable({ export default function APAgingSummaryTable({
//#withPayableAgingSummary
payableAgingColumns,
payableAgingRows,
payableAgingLoading,
//#ownProps //#ownProps
organizationName, organizationName,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const agingColumns = useMemo(
() =>
payableAgingColumns.map((agingColumn) => {
return `${agingColumn.before_days} - ${
agingColumn.to_days || 'And Over'
}`;
}),
[payableAgingColumns],
);
const columns = useMemo( // AP aging summary report content.
() => [ const {
{ APAgingSummary: { tableRows },
Header: <T id={'vendor_name'} />, isAPAgingFetching,
accessor: 'name', } = useAPAgingSummaryContext();
className: 'name',
width: 240, // AP aging summary columns.
sticky: 'left', const columns = useAPAgingSummaryColumns();
textOverview: true,
},
{
Header: <T id={'current'} />,
accessor: 'current',
className: 'current',
width: getColumnWidth(payableAgingRows, `current`, {
minWidth: 120,
}),
},
...agingColumns.map((agingColumn, index) => ({
Header: agingColumn,
accessor: `aging-${index}`,
width: getColumnWidth(payableAgingRows, `aging-${index}`, {
minWidth: 120,
}),
})),
{
Header: <T id={'total'} />,
accessor: 'total',
width: getColumnWidth(payableAgingRows, 'total', {
minWidth: 120,
}),
},
],
[payableAgingRows],
);
const rowClassNames = (row) => [`row-type--${row.original.rowType}`]; const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
return ( return (
@@ -74,12 +32,12 @@ function APAgingSummaryTable({
name={'payable-aging-summary'} name={'payable-aging-summary'}
sheetType={formatMessage({ id: 'payable_aging_summary' })} sheetType={formatMessage({ id: 'payable_aging_summary' })}
asDate={new Date()} asDate={new Date()}
loading={payableAgingLoading} loading={isAPAgingFetching}
> >
<DataTable <DataTable
className={'bigcapital-datatable--financial-report'} className={'bigcapital-datatable--financial-report'}
columns={columns} columns={columns}
data={payableAgingRows} data={tableRows}
rowClassNames={rowClassNames} rowClassNames={rowClassNames}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}
@@ -87,17 +45,3 @@ function APAgingSummaryTable({
</FinancialSheet> </FinancialSheet>
); );
} }
export default compose(
withAPAgingSummary(
({
payableAgingSummaryLoading,
payableAgingSummaryColumns,
payableAgingSummaryRows,
}) => ({
payableAgingLoading: payableAgingSummaryLoading,
payableAgingColumns: payableAgingSummaryColumns,
payableAgingRows: payableAgingSummaryRows,
}),
),
)(APAgingSummaryTable);

View File

@@ -0,0 +1,56 @@
import React, { useMemo } from 'react';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { getColumnWidth } from 'utils';
import { FormattedMessage as T } from 'react-intl';
/**
* Retrieve AP aging summary columns.
*/
export const useAPAgingSummaryColumns = () => {
const {
APAgingSummary: { tableRows, columns },
} = useAPAgingSummaryContext();
const agingColumns = React.useMemo(() => {
return columns.map(
(agingColumn) =>
`${agingColumn.before_days} - ${agingColumn.to_days || 'And Over'}`,
);
}, [columns]);
return useMemo(
() => [
{
Header: <T id={'vendor_name'} />,
accessor: 'name',
className: 'name',
width: 240,
sticky: 'left',
textOverview: true,
},
{
Header: <T id={'current'} />,
accessor: 'current',
className: 'current',
width: getColumnWidth(tableRows, `current`, {
minWidth: 120,
}),
},
...agingColumns.map((agingColumn, index) => ({
Header: agingColumn,
accessor: `aging-${index}`,
width: getColumnWidth(tableRows, `aging-${index}`, {
minWidth: 120,
}),
})),
{
Header: <T id={'total'} />,
accessor: 'total',
width: getColumnWidth(tableRows, 'total', {
minWidth: 120,
}),
},
],
[tableRows, agingColumns],
);
};

View File

@@ -1,33 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
getFinancialSheetFactory, APAgingSummaryFilterDrawerSelector,
getFinancialSheetColumnsFactory,
getFinancialSheetTableRowsFactory,
} from 'store/financialStatement/financialStatements.selectors'; } from 'store/financialStatement/financialStatements.selectors';
export default (mapState) => { export default (mapState) => {
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const getAPAgingSheet = getFinancialSheetFactory('payableAgingSummary');
const getAPAgingSheetColumns = getFinancialSheetColumnsFactory(
'payableAgingSummary',
);
const getAPAgingSheetRows = getFinancialSheetTableRowsFactory(
'payableAgingSummary',
);
const {
loading,
filter,
refresh,
} = state.financialStatements.payableAgingSummary;
const mapped = { const mapped = {
payableAgingSummarySheet: getAPAgingSheet(state, props), APAgingSummaryFilterDrawer: APAgingSummaryFilterDrawerSelector(
payableAgingSummaryColumns: getAPAgingSheetColumns(state, props), state,
payableAgingSummaryRows: getAPAgingSheetRows(state, props), props,
payableAgingSummaryLoading: loading, ),
payableAgingSummaryFilter: filter,
APAgingSummaryRefresh: refresh,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -1,18 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { toggleAPAgingSummaryFilterDrawer } from 'store/financialStatement/financialStatements.actions';
fetchPayableAginSummary,
payableAgingSummaryRefresh,
} from 'store/financialStatement/financialStatements.actions';
const mapActionsToProps = (dispatch) => ({ const mapActionsToProps = (dispatch) => ({
requestPayableAgingSummary: (query) => toggleAPAgingSummaryFilterDrawer: (toggle) =>
dispatch(fetchPayableAginSummary({ query })), dispatch(toggleAPAgingSummaryFilterDrawer(toggle)),
refreshAPAgingSummary: (refresh) =>
dispatch(payableAgingSummaryRefresh(refresh)),
toggleFilterAPAgingSummary: () =>
dispatch({
type: 'PAYABLE_AGING_SUMMARY_FILTER_TOGGLE',
}),
}); });
export default connect(null, mapActionsToProps); export default connect(null, mapActionsToProps);

View File

@@ -39,7 +39,7 @@ function ARAgingSummaryActionsBar({
const { isARAgingFetching, refetch } = useARAgingSummaryContext(); const { isARAgingFetching, refetch } = useARAgingSummaryContext();
const handleFilterToggleClick = () => { const handleFilterToggleClick = () => {
toggleDisplayFilterDrawer(false); toggleDisplayFilterDrawer();
}; };
// Handles re-calculate report button. // Handles re-calculate report button.

View File

@@ -25,7 +25,7 @@ function ARAgingSummaryHeader({
toggleARAgingSummaryFilterDrawer: toggleFilterDrawerDisplay, toggleARAgingSummaryFilterDrawer: toggleFilterDrawerDisplay,
// #withARAgingSummary // #withARAgingSummary
isFilterDrawerOpen isFilterDrawerOpen,
}) { }) {
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
asDate: Yup.date().required().label('asDate'), asDate: Yup.date().required().label('asDate'),
@@ -53,13 +53,22 @@ function ARAgingSummaryHeader({
toggleFilterDrawerDisplay(false); toggleFilterDrawerDisplay(false);
setSubmitting(false); setSubmitting(false);
}; };
// Handle cancel button click. // Handle cancel button click.
const handleCancelClick = () => { const handleCancelClick = () => {
toggleFilterDrawerDisplay(false); toggleFilterDrawerDisplay(false);
}; };
// Handle the drawer close.
const handleDrawerClose = () => {
toggleFilterDrawerDisplay(false);
};
return ( return (
<FinancialStatementHeader isOpen={isFilterDrawerOpen}> <FinancialStatementHeader
isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }}
>
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}

View File

@@ -10,7 +10,7 @@ import {
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { CustomersMultiSelect, Row, Col, FieldHint } from 'components'; import { ContactsMultiSelect, Row, Col, FieldHint } from 'components';
import { momentFormatter } from 'utils'; import { momentFormatter } from 'utils';
import { useARAgingSummaryContext } from './ARAgingSummaryProvider'; import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
@@ -97,7 +97,7 @@ export default function ARAgingSummaryHeaderGeneral() {
label={<T id={'specific_customers'} />} label={<T id={'specific_customers'} />}
className={classNames('form-group--select-list', Classes.FILL)} className={classNames('form-group--select-list', Classes.FILL)}
> >
<CustomersMultiSelect customers={customers} /> <ContactsMultiSelect contacts={customers} />
</FormGroup> </FormGroup>
</Col> </Col>
</Row> </Row>

View File

@@ -261,3 +261,38 @@ export const ARAgingSummaryTableRowsMapper = (sheet, total) => {
}, },
]; ];
}; };
export const APAgingSummaryTableRowsMapper = (sheet, total) => {
const rows = [];
const mapAging = (agingPeriods) => {
return agingPeriods.reduce((acc, aging, index) => {
acc[`aging-${index}`] = aging.total.formatted_amount;
return acc;
}, {});
};
sheet.vendors.forEach((vendor) => {
const agingRow = mapAging(vendor.aging);
rows.push({
rowType: 'vendor',
name: vendor.vendor_name,
...agingRow,
current: vendor.current.formatted_amount,
total: vendor.total.formatted_amount,
});
});
if (rows.length <= 0) {
return [];
}
return [
...rows,
{
name: '',
rowType: 'total',
current: sheet.total.current.formatted_amount,
...mapAging(sheet.total.aging),
total: sheet.total.total.formatted_amount,
},
];
};

View File

@@ -6,10 +6,11 @@ import {
profitLossSheetReducer, profitLossSheetReducer,
generalLedgerTableRowsReducer, generalLedgerTableRowsReducer,
journalTableRowsReducer, journalTableRowsReducer,
ARAgingSummaryTableRowsMapper ARAgingSummaryTableRowsMapper,
APAgingSummaryTableRowsMapper
} from 'containers/FinancialStatements/reducers'; } from 'containers/FinancialStatements/reducers';
import useApiRequest from '../useRequest'; import useApiRequest from '../useRequest';
/** /**
* Retrieve balance sheet. * Retrieve balance sheet.
*/ */
@@ -27,7 +28,7 @@ export function useBalanceSheet(query, props) {
tableRows: balanceSheetRowsReducer(res.data.data), tableRows: balanceSheetRowsReducer(res.data.data),
...res.data, ...res.data,
}), }),
...props ...props,
}, },
); );
@@ -69,8 +70,8 @@ export function useTrialBalanceSheet(query, props) {
tableRows: [], tableRows: [],
data: [], data: [],
query: {}, query: {},
}) }),
} };
} }
/** /**
@@ -101,7 +102,7 @@ export function useProfitLossSheet(query, props) {
columns: [], columns: [],
query: {}, query: {},
}), }),
} };
} }
/** /**
@@ -132,7 +133,7 @@ export function useGeneralLedgerSheet(query, props) {
data: {}, data: {},
query: {}, query: {},
}), }),
} };
} }
/** /**
@@ -143,12 +144,11 @@ export function useJournalSheet(query, props) {
const states = useQuery( const states = useQuery(
['FINANCIAL-REPORT', 'JOURNAL', query], ['FINANCIAL-REPORT', 'JOURNAL', query],
() => () => apiRequest.get('/financial_statements/journal', { params: query }),
apiRequest.get('/financial_statements/journal', { params: query }),
{ {
select: (res) => ({ select: (res) => ({
tableRows: journalTableRowsReducer(res.data.data), tableRows: journalTableRowsReducer(res.data.data),
...res.data, ...res.data,
}), }),
...props, ...props,
}, },
@@ -160,8 +160,8 @@ export function useJournalSheet(query, props) {
data: {}, data: {},
tableRows: [], tableRows: [],
query: {}, query: {},
}) }),
} };
} }
/** /**
@@ -185,7 +185,7 @@ export function useARAgingSummaryReport(query, props) {
customers: res.data.data.customers, customers: res.data.data.customers,
total: res.data.data.total, total: res.data.data.total,
columns: res.data.columns, columns: res.data.columns,
}), }),
}), }),
initialData: { initialData: {
data: { data: {
@@ -194,11 +194,50 @@ export function useARAgingSummaryReport(query, props) {
total: {}, total: {},
}, },
columns: [], columns: [],
tableRows: [] tableRows: [],
} },
}, },
initialDataUpdatedAt: 0, initialDataUpdatedAt: 0,
...props ...props,
},
);
}
/**
* Retrieve AP aging summary report.
*/
export function useAPAgingSummaryReport(query, props) {
const apiRequest = useApiRequest();
return useQuery(
['FINANCIAL-REPORT', 'AP-AGING-SUMMARY', query],
() =>
apiRequest.get('/financial_statements/payable_aging_summary', {
params: query,
}),
{
select: (res) => ({
columns: res.data.columns,
data: res.data.data,
query: res.data.query,
tableRows: APAgingSummaryTableRowsMapper({
vendors: res.data.data.vendors,
total: res.data.data.total,
columns: res.data.columns,
}),
}),
initialData: {
data: {
data: {
vendors: [],
total: {},
},
columns: [],
tableRows: [],
},
},
initialDataUpdatedAt: 0,
...props,
}, },
); );
} }

View File

@@ -961,6 +961,7 @@ export default {
adjustment_reasons: 'Adjustment reasons', adjustment_reasons: 'Adjustment reasons',
specific_customers: 'Specific Customers', specific_customers: 'Specific Customers',
all_customers: 'All Customers', all_customers: 'All Customers',
all_vendors: 'All Vendors',
selected_customers: '{count} Selected Customers', selected_customers: '{count} Selected Customers',
transaction_number: 'Transaction #', transaction_number: 'Transaction #',
running_balance: 'Running balance', running_balance: 'Running balance',
@@ -976,4 +977,6 @@ export default {
receipt_paper: 'Receipt Paper', receipt_paper: 'Receipt Paper',
payable_aging_summary: 'Payable Aging Summary', payable_aging_summary: 'Payable Aging Summary',
payment_receive_paper: 'Payment Receive Paper', payment_receive_paper: 'Payment Receive Paper',
specific_vendors: 'Specific Vendors',
}; };

View File

@@ -159,13 +159,16 @@ export default [
backLink: true, backLink: true,
sidebarShrink: true, sidebarShrink: true,
}, },
// { {
// path: '/financial-reports/payable-aging-summary', path: '/financial-reports/payable-aging-summary',
// component: lazy(() => component: lazy(() =>
// import('containers/FinancialStatements/APAgingSummary/APAgingSummary'), import('containers/FinancialStatements/APAgingSummary/APAgingSummary'),
// ), ),
// breadcrumb: 'Payable Aging Summary', breadcrumb: 'Payable Aging Summary',
// }, pageTitle: formatMessage({ id: 'payable_aging_summary' }),
backLink: true,
sidebarShrink: true,
},
{ {
path: `/financial-reports/journal-sheet`, path: `/financial-reports/journal-sheet`,
component: lazy(() => component: lazy(() =>

View File

@@ -76,4 +76,17 @@ export function toggleARAgingSummaryFilterDrawer(toggle) {
toggle, toggle,
} }
}; };
}
/**
* Toggles display of the AP aging summary filter drawer.
* @param {boolean} toggle -
*/
export function toggleAPAgingSummaryFilterDrawer(toggle) {
return {
type: `${t.AP_AGING_SUMMARY}/${t.DISPLAY_FILTER_DRAWER_TOGGLE}`,
payload: {
toggle,
}
};
} }

View File

@@ -137,6 +137,41 @@ export const ARAgingSummaryTableRowsMapper = (sheet, total) => {
]; ];
}; };
export const APAgingSummaryTableRowsMapper = (sheet, total) => {
const rows = [];
const mapAging = (agingPeriods) => {
return agingPeriods.reduce((acc, aging, index) => {
acc[`aging-${index}`] = aging.total.formatted_amount;
return acc;
}, {});
};
sheet.vendors.forEach((vendor) => {
const agingRow = mapAging(vendor.aging);
rows.push({
rowType: 'vendor',
name: vendor.vendor_name,
...agingRow,
current: vendor.current.formatted_amount,
total: vendor.total.formatted_amount,
});
});
if (rows.length <= 0) {
return [];
}
return [
...rows,
{
name: '',
rowType: 'total',
current: sheet.total.current.formatted_amount,
...mapAging(sheet.total.aging),
total: sheet.total.total.formatted_amount,
},
];
};
export const mapTrialBalanceSheetToRows = (sheet) => { export const mapTrialBalanceSheetToRows = (sheet) => {
const results = []; const results = [];

View File

@@ -34,6 +34,11 @@ export const ARAgingSummaryFilterDrawerSelector = (state) => {
return filterDrawerByTypeSelector('ARAgingSummary')(state); return filterDrawerByTypeSelector('ARAgingSummary')(state);
}; };
export const APAgingSummaryFilterDrawerSelector = (state) => {
return filterDrawerByTypeSelector('APAgingSummary')(state);
}
/** /**
* Retrieve balance sheet filter drawer. * Retrieve balance sheet filter drawer.
*/ */
@@ -94,6 +99,17 @@ export const getARAgingSummaryFilterDrawer = createSelector(
}, },
); );
/**
* Retrieve whether display AR aging summary drawer filter.
*/
export const getAPAgingSummaryFilterDrawer = createSelector(
APAgingSummaryFilterDrawerSelector,
(isOpen) => {
return isOpen;
},
);
/** /**
* Retrieve financial statement query by the given sheet index. * Retrieve financial statement query by the given sheet index.
*/ */