diff --git a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsTable.ts b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsTable.ts
index 33c39ccd6..f876e9a02 100644
--- a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsTable.ts
+++ b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsTable.ts
@@ -7,8 +7,15 @@ import {
ITableRow,
} from '@/interfaces';
import { tableRowMapper } from '@/utils';
+import FinancialSheet from '../FinancialSheet';
+import { FinancialSheetStructure } from '../FinancialSheetStructure';
+import { FinancialTable } from '../FinancialTable';
+import { ROW_TYPE } from './constants';
-export class SalesByItemsTable {
+export class SalesByItemsTable extends R.compose(
+ FinancialTable,
+ FinancialSheetStructure
+)(FinancialSheet) {
private readonly data: ISalesByItemsSheetStatement;
/**
@@ -16,6 +23,7 @@ export class SalesByItemsTable {
* @param {ISalesByItemsSheetStatement} data
*/
constructor(data: ISalesByItemsSheetStatement) {
+ super();
this.data = data;
}
@@ -39,8 +47,9 @@ export class SalesByItemsTable {
*/
private itemMap = (item: ISalesByItemsItem): ITableRow => {
const columns = this.commonTableAccessors();
- const meta = {};
-
+ const meta = {
+ rowTypes: [ROW_TYPE.ITEM],
+ };
return tableRowMapper(item, columns, meta);
};
@@ -60,8 +69,9 @@ export class SalesByItemsTable {
*/
private totalMap = (total: ISalesByItemsTotal) => {
const columns = this.commonTableAccessors();
- const meta = {};
-
+ const meta = {
+ rowTypes: [ROW_TYPE.TOTAL],
+ };
return tableRowMapper(total, columns, meta);
};
@@ -81,11 +91,12 @@ export class SalesByItemsTable {
* @returns {ITableColumn[]}
*/
public tableColumns(): ITableColumn[] {
- return [
+ const columns = [
{ key: 'item_name', label: 'Item name' },
{ key: 'sold_quantity', label: 'Sold quantity' },
{ key: 'sold_amount', label: 'Sold amount' },
{ key: 'average_price', label: 'Average price' },
];
+ return R.compose(this.tableColumnsCellIndexing)(columns);
}
}
diff --git a/packages/server/src/services/FinancialStatements/SalesByItems/constants.ts b/packages/server/src/services/FinancialStatements/SalesByItems/constants.ts
new file mode 100644
index 000000000..0eb1e2311
--- /dev/null
+++ b/packages/server/src/services/FinancialStatements/SalesByItems/constants.ts
@@ -0,0 +1,6 @@
+
+
+export enum ROW_TYPE {
+ ITEM = 'ITEM',
+ TOTAL = 'TOTAL',
+}
\ No newline at end of file
diff --git a/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemProvider.tsx b/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemProvider.tsx
index 8b06996dc..16c2ad44f 100644
--- a/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemProvider.tsx
+++ b/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemProvider.tsx
@@ -1,7 +1,7 @@
// @ts-nocheck
-import React, { createContext, useContext } from 'react';
+import { createContext, useContext } from 'react';
import FinancialReportPage from '../FinancialReportPage';
-import { useSalesByItems } from '@/hooks/query';
+import { useSalesByItemsTable } from '@/hooks/query';
import { transformFilterFormToQuery } from '../common';
const SalesByItemsContext = createContext();
@@ -12,7 +12,7 @@ function SalesByItemProvider({ query, ...props }) {
isFetching,
isLoading,
refetch,
- } = useSalesByItems(
+ } = useSalesByItemsTable(
{
...transformFilterFormToQuery(query),
},
diff --git a/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsActionsBar.tsx b/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsActionsBar.tsx
index 56b025f35..9ac5a46a0 100644
--- a/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsActionsBar.tsx
+++ b/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsActionsBar.tsx
@@ -19,6 +19,7 @@ import withSalesByItemsActions from './withSalesByItemsActions';
import { compose, saveInvoke } from '@/utils';
import { useSalesByItemsContext } from './SalesByItemProvider';
+import { SalesByItemsSheetExportMenu } from './components';
function SalesByItemsActionsBar({
// #withSalesByItems
@@ -108,11 +109,18 @@ function SalesByItemsActionsBar({
icon={}
text={}
/>
- }
- text={}
- />
+ }
+ interactionKind={PopoverInteractionKind.CLICK}
+ placement="bottom-start"
+ minimal
+ >
+ }
+ text={}
+ />
+
);
diff --git a/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsTable.tsx b/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsTable.tsx
index 77e4da7e4..a3e439707 100644
--- a/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsTable.tsx
+++ b/packages/webapp/src/containers/FinancialStatements/SalesByItems/SalesByItemsTable.tsx
@@ -5,7 +5,7 @@ import styled from 'styled-components';
import { ReportDataTable, FinancialSheet } from '@/components';
import { useSalesByItemsContext } from './SalesByItemProvider';
-import { useSalesByItemsTableColumns } from './components';
+import { useSalesByItemsTableColumns } from './dynamicColumns';
import { tableRowTypesToClassnames } from '@/utils';
import { TableStyle } from '@/constants';
@@ -15,7 +15,7 @@ import { TableStyle } from '@/constants';
export default function SalesByItemsTable({ companyName }) {
// Sales by items context.
const {
- salesByItems: { tableRows, query },
+ salesByItems: { table, query },
isLoading,
} = useSalesByItemsContext();
@@ -32,7 +32,7 @@ export default function SalesByItemsTable({ companyName }) {
>
);
}
+
+/**
+ * Retrieves the sales by items export menu.
+ * @returns {JSX.Element}
+ */
+export const SalesByItemsSheetExportMenu = () => {
+ const toastKey = useRef(null);
+ const commonToastConfig = {
+ isCloseButtonShown: true,
+ timeout: 2000,
+ };
+ const { query } = useBalanceSheetContext();
+
+ const openProgressToast = (amount: number) => {
+ return (
+
+ The report has been exported successfully.
+ = 100,
+ })}
+ intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
+ value={amount / 100}
+ />
+
+ );
+ };
+
+ // Export the report to xlsx.
+ const { mutateAsync: xlsxExport } = useBalanceSheetXlsxExport(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 } = useBalanceSheetCsvExport(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().then(() => {});
+ };
+ // Handle xlsx export button click.
+ const handleXlsxExportBtnClick = () => {
+ xlsxExport().then(() => {});
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/webapp/src/containers/FinancialStatements/SalesByItems/dynamicColumns.ts b/packages/webapp/src/containers/FinancialStatements/SalesByItems/dynamicColumns.ts
new file mode 100644
index 000000000..769af9cce
--- /dev/null
+++ b/packages/webapp/src/containers/FinancialStatements/SalesByItems/dynamicColumns.ts
@@ -0,0 +1,77 @@
+// @ts-nocheck
+import { getColumnWidth } from '@/utils';
+import * as R from 'ramda';
+import { Align } from '@/constants';
+import { useSalesByItemsContext } from './SalesByItemProvider';
+
+const getTableCellValueAccessor = (index) => `cells[${index}].value`;
+
+const getReportColWidth = (data, accessor, headerText) => {
+ return getColumnWidth(
+ data,
+ accessor,
+ { magicSpacing: 10, minWidth: 100 },
+ headerText,
+ );
+};
+
+/**
+ * Account name column mapper.
+ */
+const commonColumnMapper = R.curry((data, column) => {
+ const accessor = getTableCellValueAccessor(column.cell_index);
+
+ return {
+ key: column.key,
+ Header: column.label,
+ accessor,
+ className: column.key,
+ textOverview: true,
+ };
+});
+
+/**
+ * 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,
+ };
+});
+
+const dynamiColumnMapper = R.curry((data, column) => {
+ const _numericColumnAccessor = numericColumnAccessor(data);
+
+ return R.compose(
+ R.when(R.pathEq(['key'], 'sold_quantity'), _numericColumnAccessor),
+ R.when(R.pathEq(['key'], 'sold_amount'), _numericColumnAccessor),
+ R.when(R.pathEq(['key'], 'average_price'), _numericColumnAccessor),
+ commonColumnMapper(data),
+ )(column);
+});
+
+/**
+ * Composes the dynamic columns that fetched from request to columns to table component.
+ */
+export const dynamicColumns = R.curry((data, columns) => {
+ return R.map(dynamiColumnMapper(data), columns);
+});
+
+/**
+ * Retrieves the G/L sheet table columns for table component.
+ */
+export const useSalesByItemsTableColumns = () => {
+ const { salesByItems } = useSalesByItemsContext();
+
+ if (!salesByItems) {
+ throw new Error('Sales by items context not found');
+ }
+ const { table } = salesByItems;
+
+ return dynamicColumns(table.rows, table.columns);
+};
diff --git a/packages/webapp/src/hooks/query/financialReports.tsx b/packages/webapp/src/hooks/query/financialReports.tsx
index e10951472..b3548c145 100644
--- a/packages/webapp/src/hooks/query/financialReports.tsx
+++ b/packages/webapp/src/hooks/query/financialReports.tsx
@@ -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.
*/
@@ -414,20 +413,60 @@ export function useSalesByItems(query, props) {
params: query,
},
{
- select: (res) => ({
- tableRows: salesByItemsReducer(res.data.data),
- ...res.data,
- }),
- defaultData: {
- tableRows: [],
- data: [],
- query: {},
- },
...props,
},
);
}
+/**
+ * Retrieves sales by items table format.
+ */
+export function useSalesByItemsTable(query, props) {
+ return useRequestQuery(
+ [t.FINANCIAL_REPORT, t.SALES_BY_ITEMS, query],
+ {
+ method: 'get',
+ url: '/financial_statements/sales-by-items',
+ params: query,
+ headers: {
+ Accept: 'application/json+table',
+ },
+ },
+ {
+ select: (res) => res.data,
+ ...props,
+ },
+ );
+}
+
+export const useSalesByItemsCsvExport = (query, args) => {
+ return useDownloadFile({
+ url: '/financial_statements/sales-by-items',
+ config: {
+ headers: {
+ accept: 'application/csv',
+ },
+ params: query,
+ },
+ filename: 'sales_by_items.csv',
+ ...args,
+ });
+};
+
+export const useSalesByItemsXlsxExport = (query, args) => {
+ return useDownloadFile({
+ url: '/financial_statements/sales-by-items',
+ config: {
+ headers: {
+ accept: 'application/xlsx',
+ },
+ params: query,
+ },
+ filename: 'sales_by_items.xlsx',
+ ...args,
+ });
+};
+
/**
* Retrieve customers balance summary report.
*/