From 54dcde657f40e5d9e28ed89de124afeed85f3247 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 1 Sep 2023 01:39:16 +0200 Subject: [PATCH] feat(webapp): wip sales tax liability summary report --- .../webapp/src/constants/abilityOption.tsx | 1 + .../src/constants/financialReportsMenu.tsx | 7 + .../SalesTaxLiabilitySummary.tsx | 72 ++++++++++ .../SalesTaxLiabilitySummaryActionsBar.tsx | 133 ++++++++++++++++++ .../SalesTaxLiabilitySummaryBody.tsx | 37 +++++ .../SalesTaxLiabilitySummaryBoot.tsx | 45 ++++++ .../SalesTaxLiabilitySummaryHeader.tsx | 116 +++++++++++++++ .../SalesTaxLiabilitySummaryTable.tsx | 93 ++++++++++++ .../SalesTaxLiabilitySummary/components.tsx | 17 +++ .../dynamicColumns.ts | 55 ++++++++ .../SalesTaxLiabilitySummary/utils.ts | 89 ++++++++++++ .../withSalesTaxLiabilitySummary.ts | 15 ++ .../withSalesTaxLiabilitySummaryActions.ts | 10 ++ .../src/hooks/query/financialReports.tsx | 22 +++ packages/webapp/src/routes/dashboard.tsx | 18 ++- .../financialStatements.selectors.tsx | 12 ++ 16 files changed, 741 insertions(+), 1 deletion(-) create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummary.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryActionsBar.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryBody.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryBoot.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryHeader.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryTable.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/components.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/dynamicColumns.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/utils.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummary.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummaryActions.ts diff --git a/packages/webapp/src/constants/abilityOption.tsx b/packages/webapp/src/constants/abilityOption.tsx index fa460ff6b..85168e352 100644 --- a/packages/webapp/src/constants/abilityOption.tsx +++ b/packages/webapp/src/constants/abilityOption.tsx @@ -169,6 +169,7 @@ export const ReportsAction = { READ_INVENTORY_VALUATION_SUMMARY: 'read-inventory-valuation-summary', READ_INVENTORY_ITEM_DETAILS: 'read-inventory-item-details', READ_CASHFLOW_ACCOUNT_TRANSACTION: 'read-cashflow-account-transactions', + READ_SALES_TAX_LIABILITY_SUMMARY: 'read-sales-tax-liability-summary', }; export const PreferencesAbility = { diff --git a/packages/webapp/src/constants/financialReportsMenu.tsx b/packages/webapp/src/constants/financialReportsMenu.tsx index e7b300bd2..8f749728f 100644 --- a/packages/webapp/src/constants/financialReportsMenu.tsx +++ b/packages/webapp/src/constants/financialReportsMenu.tsx @@ -85,6 +85,13 @@ export const financialReportMenus = [ subject: AbilitySubject.Report, ability: ReportsAction.READ_PROFIT_LOSS, }, + { + title: 'Sales Tax Liability Summary', + desc: 'Reports the total amount of sales tax collected from customers', + link: '/financial-reports/sales-tax-liability-summary', + subject: AbilitySubject.Report, + ability: ReportsAction.READ_SALES_TAX_LIABILITY_SUMMARY, + } ], }, ]; diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummary.tsx b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummary.tsx new file mode 100644 index 000000000..5a187c1da --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummary.tsx @@ -0,0 +1,72 @@ +// @ts-nocheck +import React, { useEffect } from 'react'; +import moment from 'moment'; + +import { SalesTaxLiabilitySummaryLoadingBar } from './components'; +import { FinancialStatement, DashboardPageContent } from '@/components'; + +import SalesTaxLiabilitySummaryHeader from './SalesTaxLiabilitySummaryHeader'; +import SalesTaxLiabilitySummaryActionsBar from './SalesTaxLiabilitySummaryActionsBar'; +import { SalesTaxLiabilitySummaryBoot } from './SalesTaxLiabilitySummaryBoot'; +import { SalesTaxLiabilitySummaryBody } from './SalesTaxLiabilitySummaryBody'; +import { useSalesTaxLiabilitySummaryQuery } from './utils'; +import withSalesTaxLiabilitySummaryActions from './withSalesTaxLiabilitySummaryActions'; +import { compose } from '@/utils'; + +/** + * Sales tax liability summary. + * @returns {React.JSX} + */ +function SalesTaxLiabilitySummary({ + // #withSalesTaxLiabilitySummaryActions + toggleSalesTaxLiabilitySummaryFilterDrawer, +}) { + const [query, setQuery] = useSalesTaxLiabilitySummaryQuery(); + + const handleFilterSubmit = (filter) => { + const newFilter = { + ...filter, + fromDate: moment(filter.fromDate).format('YYYY-MM-DD'), + toDate: moment(filter.toDate).format('YYYY-MM-DD'), + }; + setQuery({ ...newFilter }); + }; + // Handle number format submit. + const handleNumberFormatSubmit = (values) => { + setQuery({ + ...query, + numberFormat: values, + }); + }; + // Hides the filter drawer once the page unmount. + useEffect( + () => () => { + toggleSalesTaxLiabilitySummaryFilterDrawer(false); + }, + [toggleSalesTaxLiabilitySummaryFilterDrawer], + ); + + return ( + + + + + + + + + + + + ); +} + +export default compose(withSalesTaxLiabilitySummaryActions)( + SalesTaxLiabilitySummary, +); diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryActionsBar.tsx b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryActionsBar.tsx new file mode 100644 index 000000000..2fc716be0 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryActionsBar.tsx @@ -0,0 +1,133 @@ +// @ts-nocheck +import React from 'react'; +import { + NavbarGroup, + Button, + Classes, + NavbarDivider, + Popover, + PopoverInteractionKind, + Position, +} from '@blueprintjs/core'; +import classNames from 'classnames'; +import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components'; + +import NumberFormatDropdown from '@/components/NumberFormatDropdown'; + +import { compose, saveInvoke } from '@/utils'; +import { useSalesTaxLiabilitySummaryContext } from './SalesTaxLiabilitySummaryBoot'; +import withSalesTaxLiabilitySummary from './withSalesTaxLiabilitySummary'; +import withSalesTaxLiabilitySummaryActions from './withSalesTaxLiabilitySummaryActions'; + +/** + * Sales tax liability summary - actions bar. + */ +function SalesTaxLiabilitySummaryActionsBar({ + // #withSalesTaxLiabilitySummary + salesTaxLiabilitySummaryFilter, + + // #withSalesTaxLiabilitySummaryActions + toggleBalanceSheetFilterDrawer: toggleFilterDrawer, + + // #ownProps + numberFormat, + onNumberFormatSubmit, +}) { + const { isLoading, refetchBalanceSheet } = + useSalesTaxLiabilitySummaryContext(); + + // Handle filter toggle click. + const handleFilterToggleClick = () => { + toggleFilterDrawer(); + }; + + // Handle recalculate the report button. + const handleRecalcReport = () => { + refetchBalanceSheet(); + }; + + // Handle number format form submit. + const handleNumberFormatSubmit = (values) => { + saveInvoke(onNumberFormatSubmit, values); + }; + + return ( + + + + + + + + + ); +} + +export default compose( + withSalesTaxLiabilitySummary(({ salesTaxLiabilitySummaryFilter }) => ({ + salesTaxLiabilitySummaryFilter, + })), + withSalesTaxLiabilitySummaryActions, +)(SalesTaxLiabilitySummaryHeader); + +const BalanceSheetFinancialHeader = styled(FinancialStatementHeader)` + .bp3-drawer { + max-height: 520px; + } +`; diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryTable.tsx b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryTable.tsx new file mode 100644 index 000000000..60b102354 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryTable.tsx @@ -0,0 +1,93 @@ +// @ts-nocheck +import React from 'react'; +import styled from 'styled-components'; +import intl from 'react-intl-universal'; + +import { TableStyle } from '@/constants'; +import { ReportDataTable, FinancialSheet } from '@/components'; +import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils'; +import { useSalesTaxLiabilitySummaryContext } from './SalesTaxLiabilitySummaryBoot'; +import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization'; +import { useSalesTaxLiabilitySummaryColumns } from './utils'; +import { compose } from 'ramda'; + +/** + * Balance sheet table. + */ +function SalesTaxLiabilitySummaryTableRoot({ + // #ownProps + organizationName, +}) { + // Balance sheet context. + const { + salesTaxLiabilitySummary: { table }, + } = useSalesTaxLiabilitySummaryContext(); + + // Retrieve the database columns. + const columns = useSalesTaxLiabilitySummaryColumns(); + + // Retrieve default expanded rows of balance sheet. + const expandedRows = React.useMemo( + () => defaultExpanderReducer(table.rows, 3), + [table], + ); + + return ( + + + + ); +} + +const SalesTaxLiabilitySummaryDataTable = styled(ReportDataTable)` + .table { + .tbody .tr { + .td { + border-bottom: 0; + padding-top: 0.32rem; + padding-bottom: 0.32rem; + } + &:not(.no-results) { + .td { + border-bottom: 0; + padding-top: 0.4rem; + padding-bottom: 0.4rem; + } + &:not(:first-child) .td { + border-top: 1px solid transparent; + } + &.row_type--Total { + font-weight: 500; + + .td { + border-top: 1px solid #bbb; + border-bottom: 3px double #333; + } + } + } + } + } +`; + +export const SalesTaxLiabilitySummaryTable = compose( + withCurrentOrganization(({ organization }) => ({ + organizationName: organization.name, + })), +)(SalesTaxLiabilitySummaryTableRoot); diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/components.tsx b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/components.tsx new file mode 100644 index 000000000..8a3c1d793 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/components.tsx @@ -0,0 +1,17 @@ +// @ts-nocheck +import React from 'react'; + +import { useSalesTaxLiabilitySummaryContext } from './SalesTaxLiabilitySummaryBoot'; +import FinancialLoadingBar from '../FinancialLoadingBar'; + +/** + * Balance sheet loading bar. + */ +export function SalesTaxLiabilitySummaryLoadingBar() { + const { isFetching } = useSalesTaxLiabilitySummaryContext(); + + if (!isFetching) { + return null; + } + return ; +} diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/dynamicColumns.ts b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/dynamicColumns.ts new file mode 100644 index 000000000..76c51f811 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/dynamicColumns.ts @@ -0,0 +1,55 @@ +// @ts-nocheck +import React, { useMemo } from 'react'; +import * as R from 'ramda'; +import { getColumnWidth } from '@/utils'; +import { Align } from '@/constants'; + +const getTableCellValueAccessor = (index) => `cells[${index}].value`; + +const taxNameAccessor = R.curry((data, column) => ({ + key: column.key, + Header: column.label, + accessor: getTableCellValueAccessor(column.cell_index), + sticky: 'left', + width: 240, + textOverview: true, +})); + +const taxCodeAccessor = R.curry((data, column) => ({ + key: column.key, + Header: column.label, + accessor: getTableCellValueAccessor(column.cell_index), + sticky: 'left', + width: 240, + textOverview: true, +})); + +const taxableAmountAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + id: column.key, + accessor: getTableCellValueAccessor(column.cell_index), + className: column.key, + width: getColumnWidth(data, accessor, { minWidth: 120 }), + align: Align.Right, + }; +}); + +const dynamicColumnMapper = R.curry((data, column) => { + const taxNameAccessorColumn = taxNameAccessor(data); + const taxCodeAccessorColumn = taxCodeAccessor(data); + const taxableAmountColumn = taxableAmountAccessor(data); + + return R.compose( + R.when(R.pathEq(['key'], 'taxName'), taxNameAccessorColumn), + R.when(R.pathEq(['key'], 'taxCode'), taxCodeAccessorColumn), + R.when(R.pathEq(['key'], 'taxableAmount'), taxableAmountColumn), + R.when(R.pathEq(['key'], 'taxRate'), taxableAmountColumn), + )(column); +}); + +export const salesTaxLiabilitySummaryDynamicColumns = (columns, data) => { + return R.map(dynamicColumnMapper(data), columns); +}; diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/utils.ts b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/utils.ts new file mode 100644 index 000000000..951cdc6c5 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/utils.ts @@ -0,0 +1,89 @@ +// @ts-nocheck +import React from 'react'; +import moment from 'moment'; +import * as Yup from 'yup'; +import { castArray } from 'lodash'; +import intl from 'react-intl-universal'; +import { transformToForm } from '@/utils'; +import { useAppQueryString } from '@/hooks'; +import { salesTaxLiabilitySummaryDynamicColumns } from './dynamicColumns'; +import { useSalesTaxLiabilitySummaryContext } from './SalesTaxLiabilitySummaryBoot'; + +/** + * Retrieves the default sales tax liability summary query. + * @returns {} + */ +export const getDefaultSalesTaxLiablitySummaryQuery = () => ({ + fromDate: moment().startOf('month').format('YYYY-MM-DD'), + toDate: moment().format('YYYY-MM-DD'), + basis: 'cash', +}); + +/** + * Parses the sales tax liability summary query. + */ +const parseSalesTaxLiabilitySummaryQuery = (locationQuery) => { + const defaultQuery = getDefaultSalesTaxLiablitySummaryQuery(); + + const transformed = { + ...defaultQuery, + ...transformToForm(locationQuery, defaultQuery), + }; + return { + ...transformed, + + // Ensures the branches ids is always array. + branchesIds: castArray(transformed.branchesIds), + }; +}; + +/** + * Retrieves the sales tax liability summary query. + */ +export const useSalesTaxLiabilitySummaryQuery = () => { + // Retrieves location query. + const [locationQuery, setLocationQuery] = useAppQueryString(); + + // Merges the default filter query with location URL query. + const parsedQuery = React.useMemo( + () => parseSalesTaxLiabilitySummaryQuery(locationQuery), + [locationQuery], + ); + return [parsedQuery, setLocationQuery]; +}; + +/** + * Retrieves the sales tax liability summary default query. + */ +export const getSalesTaxLiabilitySummaryDefaultQuery = () => { + return { + basic: 'cash', + fromDate: moment().toDate(), + toDate: moment().toDate(), + }; +}; + +/** + * Retrieves the sales tax liability summary query validation. + */ +export const getSalesTaxLiabilitySummaryQueryValidation = () => + Yup.object().shape({ + dateRange: Yup.string().optional(), + fromDate: Yup.date().required().label(intl.get('fromDate')), + toDate: Yup.date() + .min(Yup.ref('fromDate')) + .required() + .label(intl.get('toDate')), + }); + +/** + * Retrieves the sales tax liability summary columns. + * @returns {ITableColumn[]} + */ +export const useSalesTaxLiabilitySummaryColumns = () => { + const { + salesTaxLiabilitySummary: { table }, + } = useSalesTaxLiabilitySummaryContext(); + + return salesTaxLiabilitySummaryDynamicColumns(table.columns, table.rows); +}; diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummary.ts b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummary.ts new file mode 100644 index 000000000..32a5d1552 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummary.ts @@ -0,0 +1,15 @@ +// @ts-nocheck +import { connect } from 'react-redux'; +import { getSalesTaxLiabilitySummaryFilterDrawer } from '@/store/financialStatement/financialStatements.selectors'; + +export default (mapState) => { + const mapStateToProps = (state, props) => { + const mapped = { + salesTaxLiabilitySummaryFilter: + getSalesTaxLiabilitySummaryFilterDrawer(state), + }; + return mapState ? mapState(mapped, state, props) : mapped; + }; + + return connect(mapStateToProps); +}; diff --git a/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummaryActions.ts b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummaryActions.ts new file mode 100644 index 000000000..3ce7d3046 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/SalesTaxLiabilitySummary/withSalesTaxLiabilitySummaryActions.ts @@ -0,0 +1,10 @@ +// @ts-nocheck +import { connect } from 'react-redux'; +import { toggleBalanceSheetFilterDrawer } from '@/store/financialStatement/financialStatements.actions'; + +const mapDispatchToProps = (dispatch) => ({ + toggleSalesTaxLiabilitySummaryFilterDrawer: (toggle) => + dispatch(toggleBalanceSheetFilterDrawer(toggle)), +}); + +export default connect(null, mapDispatchToProps); diff --git a/packages/webapp/src/hooks/query/financialReports.tsx b/packages/webapp/src/hooks/query/financialReports.tsx index 6941fb1d2..68afef7c0 100644 --- a/packages/webapp/src/hooks/query/financialReports.tsx +++ b/packages/webapp/src/hooks/query/financialReports.tsx @@ -444,3 +444,25 @@ export function useTransactionsByReference(query, props) { }, ); } + + +/** + * + */ +export function useSalesTaxLiabilitySummary(query, props) { + return useRequestQuery( + [t.FINANCIAL_REPORT, t.BALANCE_SHEET, query], + { + method: 'get', + url: '/financial_statements/sales-tax-liability-summary', + params: query, + headers: { + Accept: 'application/json+table', + }, + }, + { + select: (res) => res.data, + ...props, + }, + ); +} diff --git a/packages/webapp/src/routes/dashboard.tsx b/packages/webapp/src/routes/dashboard.tsx index ed66b4373..45260bcb7 100644 --- a/packages/webapp/src/routes/dashboard.tsx +++ b/packages/webapp/src/routes/dashboard.tsx @@ -440,7 +440,9 @@ export const getDashboardRoutes = () => [ path: `/financial-reports/project-profitability-summary`, component: lazy( () => - import('@/containers/FinancialStatements/ProjectProfitabilitySummary/ProjectProfitabilitySummary'), + import( + '@/containers/FinancialStatements/ProjectProfitabilitySummary/ProjectProfitabilitySummary' + ), ), breadcrumb: intl.get('project_profitability_summary'), pageTitle: intl.get('project_profitability_summary'), @@ -448,6 +450,20 @@ export const getDashboardRoutes = () => [ sidebarExpand: false, subscriptionActive: [SUBSCRIPTION_TYPE.MAIN], }, + { + path: '/financial-reports/sales-tax-liability-summary', + component: lazy( + () => + import( + '@/containers/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummary' + ), + ), + breadcrumb: 'Sales Tax Liability Summary', + pageTitle: 'Sales Tax Liability Summary', + backLink: true, + sidebarExpand: false, + subscriptionActive: [SUBSCRIPTION_TYPE.MAIN], + }, { path: '/financial-reports', component: lazy( diff --git a/packages/webapp/src/store/financialStatement/financialStatements.selectors.tsx b/packages/webapp/src/store/financialStatement/financialStatements.selectors.tsx index b5f0c8d10..b46241aa1 100644 --- a/packages/webapp/src/store/financialStatement/financialStatements.selectors.tsx +++ b/packages/webapp/src/store/financialStatement/financialStatements.selectors.tsx @@ -86,6 +86,10 @@ export const projectProfitabilitySummaryFilterDrawerSelector = (state) => { return filterDrawerByTypeSelector('projectProfitabilitySummary')(state); }; +export const salesTaxLiabilitySummaryFilterDrawerSelector = (state) => { + return filterDrawerByTypeSelector('projectProfitabilitySummary')(state); +}; + /** * Retrieve balance sheet filter drawer. */ @@ -278,3 +282,11 @@ export const getProjectProfitabilitySummaryFilterDrawer = createSelector( return isOpen; }, ); + +/** + * Retrieve sales tax liability summary filter drawer. + */ +export const getSalesTaxLiabilitySummaryFilterDrawer = createSelector( + salesTaxLiabilitySummaryFilterDrawerSelector, + (isOpen) => isOpen, +);