feat: inventory valuation csv and xlsx export (#308)

* feat: inventory valuation csv and xlsx export

* feat(server): inventory valuation sheet exporting

* feat(webapp): inventory valuation sheet dyanmic columns

* feat: inventory valuation dynamic columns

* feat: inventory valuation TS types
This commit is contained in:
Ahmed Bouhuolia
2024-01-18 20:16:29 +02:00
committed by GitHub
parent 8495990ec2
commit 3672abe7a5
15 changed files with 622 additions and 65 deletions

View File

@@ -19,6 +19,7 @@ import withInventoryValuationActions from './withInventoryValuationActions';
import { useInventoryValuationContext } from './InventoryValuationProvider';
import { compose, saveInvoke } from '@/utils';
import { InventoryValuationExportMenu } from './components';
function InventoryValuationActionsBar({
// #withInventoryValuation
@@ -109,11 +110,18 @@ function InventoryValuationActionsBar({
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Popover
content={<InventoryValuationExportMenu />}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
minimal
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</Popover>
</NavbarGroup>
</DashboardActionsBar>
);

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import React from 'react';
import FinancialReportPage from '../FinancialReportPage';
import { useInventoryValuation } from '@/hooks/query';
import { useInventoryValuationTable } from '@/hooks/query';
import { transformFilterFormToQuery } from '../common';
const InventoryValuationContext = React.createContext();
@@ -21,7 +21,7 @@ function InventoryValuationProvider({ query, ...props }) {
isFetching,
isLoading,
refetch,
} = useInventoryValuation(requestQuery, {
} = useInventoryValuationTable(requestQuery, {
keepPreviousData: true,
});

View File

@@ -8,23 +8,23 @@ import { ReportDataTable, FinancialSheet } from '@/components';
import { tableRowTypesToClassnames } from '@/utils';
import { useInventoryValuationContext } from './InventoryValuationProvider';
import { useInventoryValuationTableColumns } from './components';
import { useInventoryValuationColumns } from './dynamicColumns';
/**
* inventory valuation data table.
* Inventory valuation data table.
*/
export default function InventoryValuationTable({
//#ownProps
// #ownProps
companyName,
}) {
// inventory valuation context.
// Inventory valuation context.
const {
inventoryValuation: { tableRows, query },
inventoryValuation: { table, query },
isLoading,
} = useInventoryValuationContext();
// inventory valuation table columns.
const columns = useInventoryValuationTableColumns();
// Inventory valuation table columns.
const columns = useInventoryValuationColumns();
return (
<InventoryValuationSheet
@@ -35,7 +35,7 @@ export default function InventoryValuationTable({
>
<InventoryValuationDataTable
columns={columns}
data={tableRows}
data={table.rows}
expandable={true}
expandToggleColumn={1}
expandColumnSpace={1}
@@ -62,7 +62,7 @@ const InventoryValuationDataTable = styled(ReportDataTable)`
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
.tr.row_type--total .td {
.tr.row_type--TOTAL .td {
border-top: 1px solid #bbb;
font-weight: 500;
border-bottom: 3px double #000;

View File

@@ -1,13 +1,25 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import { useMemo, useRef } from 'react';
import intl from 'react-intl-universal';
import { If } from '@/components';
import classNames from 'classnames';
import { AppToaster, If, Stack } from '@/components';
import { Align } from '@/constants';
import { getColumnWidth } from '@/utils';
import { CellTextSpan } from '@/components/Datatable/Cells';
import { useInventoryValuationContext } from './InventoryValuationProvider';
import FinancialLoadingBar from '../FinancialLoadingBar';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import {
useInventoryValuationCsvExport,
useInventoryValuationXlsxExport,
} from '@/hooks/query';
/**
* Retrieve inventory valuation table columns.
@@ -77,3 +89,87 @@ export function InventoryValuationLoadingBar() {
</If>
);
}
/**
* Retrieves the inventory valuation sheet export menu.
* @returns {JSX.Element}
*/
export const InventoryValuationExportMenu = () => {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = useInventoryValuationContext();
const openProgressToast = (amount: number) => {
return (
<Stack spacing={8}>
<Text>The report has been exported successfully.</Text>
<ProgressBar
className={classNames('toast-progress', {
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
})}
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
value={amount / 100}
/>
</Stack>
);
};
// Export the report to xlsx.
const { mutateAsync: xlsxExport } = useInventoryValuationXlsxExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Export the report to csv.
const { mutateAsync: csvExport } = useInventoryValuationCsvExport(query, {
onDownloadProgress: (xlsxExportProgress: number) => {
if (!toastKey.current) {
toastKey.current = AppToaster.show({
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
});
} else {
AppToaster.show(
{
message: openProgressToast(xlsxExportProgress),
...commonToastConfig,
},
toastKey.current,
);
}
},
});
// Handle csv export button click.
const handleCsvExportBtnClick = () => {
csvExport();
};
// Handle xlsx export button click.
const handleXlsxExportBtnClick = () => {
xlsxExport();
};
return (
<Menu>
<MenuItem
text={'XLSX (Microsoft Excel)'}
onClick={handleXlsxExportBtnClick}
/>
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
</Menu>
);
};

View File

@@ -0,0 +1,97 @@
// @ts-nocheck
import { Align } from '@/constants';
import { getColumnWidth } from '@/utils';
import * as R from 'ramda';
import { useInventoryValuationContext } from './InventoryValuationProvider';
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
const getReportColWidth = (data, accessor, headerText) => {
return getColumnWidth(
data,
accessor,
{ magicSpacing: 10, minWidth: 100 },
headerText,
);
};
/**
* Common column mapper.
*/
const commonAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
key: column.key,
Header: column.label,
accessor,
className: column.key,
textOverview: true,
align: Align.Left,
};
});
/**
* Numeric columns accessor.
*/
const numericColumnAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
...column,
align: Align.Right,
width,
};
});
/**
* Item name column accessor.
*/
const itemNameColumnAccessor = R.curry((data, column) => {
return {
...column,
width: 240,
}
});
/**
* Dynamic column mapper.
* @param {} data -
* @param {} column -
*/
const dynamicColumnMapper = R.curry((data, column) => {
const _commonAccessor = commonAccessor(data);
const _numericColumnAccessor = numericColumnAccessor(data);
const _itemNameColumnAccessor = itemNameColumnAccessor(data);
return R.compose(
R.when(R.pathEq(['key'], 'item_name'), _itemNameColumnAccessor),
R.when(R.pathEq(['key'], 'quantity'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'valuation'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'average'), _numericColumnAccessor),
_commonAccessor,
)(column);
});
/**
* Composes the fetched dynamic columns from the server to the columns to pass it
* to the table component.
*/
export const dynamicColumns = (columns, data) => {
return R.map(dynamicColumnMapper(data), columns);
};
/**
* Retrieves the table columns of inventory valuation sheet.
*/
export const useInventoryValuationColumns = () => {
const { inventoryValuation } = useInventoryValuationContext();
if (!inventoryValuation) {
throw new Error('The inventory valuation is not loaded');
}
const { table } = inventoryValuation;
return dynamicColumns(table.columns, table.rows);
};

View File

@@ -176,34 +176,33 @@ export function useGeneralLedgerSheet(query, props) {
);
}
export const useGeneralLedgerSheetXlsxExport = (query, args) => {
return useDownloadFile({
url: '/financial_statements/general_ledger',
config: {
headers: {
accept: 'application/xlsx',
return useDownloadFile({
url: '/financial_statements/general_ledger',
config: {
headers: {
accept: 'application/xlsx',
},
params: query,
},
params: query,
},
filename: 'general_ledger.xlsx',
...args,
});
filename: 'general_ledger.xlsx',
...args,
});
};
export const useGeneralLedgerSheetCsvExport = (query, args) => {
return useDownloadFile({
url: '/financial_statements/general_ledger',
config: {
headers: {
accept: 'application/csv',
return useDownloadFile({
url: '/financial_statements/general_ledger',
config: {
headers: {
accept: 'application/csv',
},
params: query,
},
params: query,
},
filename: 'general_ledger.csv',
...args,
});
filename: 'general_ledger.csv',
...args,
});
};
/**
* Retrieve journal sheet.
*/
@@ -376,6 +375,56 @@ export function useInventoryValuation(query, props) {
},
);
}
/**
* Retrieve inventory valuation.
*/
export function useInventoryValuationTable(query, props) {
return useRequestQuery(
[t.FINANCIAL_REPORT, t.INVENTORY_VALUATION, query],
{
method: 'get',
url: '/financial_statements/inventory-valuation',
params: query,
headers: {
Accept: 'application/json+table',
},
},
{
select: (res) => res.data,
...props,
},
);
}
export const useInventoryValuationXlsxExport = (query, args) => {
return useDownloadFile({
url: '/financial_statements/inventory-valuation',
config: {
headers: {
accept: 'application/xlsx',
},
params: query,
},
filename: 'inventory_valuation.xlsx',
...args,
});
};
export const useInventoryValuationCsvExport = (query, args) => {
return useDownloadFile({
url: '/financial_statements/inventory-valuation',
config: {
headers: {
accept: 'application/csv',
},
params: query,
},
filename: 'inventory_valuation.csv',
...args,
});
};
/**
* Retrieve purchases by items.
*/