Merge pull request #310 from bigcapitalhq/big-99-purchases-by-items

feat: sales by items export csv & xlsx
This commit is contained in:
Ahmed Bouhuolia
2024-01-19 11:41:56 +02:00
committed by GitHub
15 changed files with 598 additions and 147 deletions

View File

@@ -0,0 +1,74 @@
import { Inject, Service } from 'typedi';
import {
ISalesByItemsReportQuery,
ISalesByItemsSheet,
ISalesByItemsSheetData,
ISalesByItemsTable,
} from '@/interfaces';
import { SalesByItemsReportService } from './SalesByItemsService';
import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable';
import { SalesByItemsExport } from './SalesByItemsExport';
@Service()
export class SalesByItemsApplication {
@Inject()
private salesByItemsSheet: SalesByItemsReportService;
@Inject()
private salesByItemsTable: SalesByItemsTableInjectable;
@Inject()
private salesByItemsExport: SalesByItemsExport;
/**
* Retrieves the sales by items report in json format.
* @param {number} tenantId
* @param {ISalesByItemsReportQuery} filter
* @returns {Promise<ISalesByItemsSheetData>}
*/
public sheet(
tenantId: number,
filter: ISalesByItemsReportQuery
): Promise<ISalesByItemsSheet> {
return this.salesByItemsSheet.salesByItems(tenantId, filter);
}
/**
* Retrieves the sales by items report in table format.
* @param {number} tenantId
* @param {ISalesByItemsReportQuery} filter
* @returns {Promise<ISalesByItemsTable>}
*/
public table(
tenantId: number,
filter: ISalesByItemsReportQuery
): Promise<ISalesByItemsTable> {
return this.salesByItemsTable.table(tenantId, filter);
}
/**
* Retrieves the sales by items report in csv format.
* @param {number} tenantId
* @param {ISalesByItemsReportQuery} filter
* @returns {Promise<string>}
*/
public csv(
tenantId: number,
filter: ISalesByItemsReportQuery
): Promise<string> {
return this.salesByItemsExport.csv(tenantId, filter);
}
/**
* Retrieves the sales by items report in xlsx format.
* @param {number} tenantId
* @param {ISalesByItemsReportQuery} filter
* @returns {Promise<Buffer>}
*/
public xlsx(
tenantId: number,
filter: ISalesByItemsReportQuery
): Promise<Buffer> {
return this.salesByItemsExport.xlsx(tenantId, filter);
}
}

View File

@@ -0,0 +1,43 @@
import { Inject, Service } from 'typedi';
import { TableSheet } from '@/lib/Xlsx/TableSheet';
import { ISalesByItemsReportQuery } from '@/interfaces';
import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable';
@Service()
export class SalesByItemsExport {
@Inject()
private salesByItemsTable: SalesByItemsTableInjectable;
/**
* Retrieves the trial balance sheet in XLSX format.
* @param {number} tenantId
* @param {ISalesByItemsReportQuery} query
* @returns {Promise<Buffer>}
*/
public async xlsx(tenantId: number, query: ISalesByItemsReportQuery) {
const table = await this.salesByItemsTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToXLSX();
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
}
/**
* Retrieves the trial balance sheet in CSV format.
* @param {number} tenantId
* @param {ISalesByItemsReportQuery} query
* @returns {Promise<Buffer>}
*/
public async csv(
tenantId: number,
query: ISalesByItemsReportQuery
): Promise<string> {
const table = await this.salesByItemsTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToCSV();
return tableCsv;
}
}

View File

@@ -2,15 +2,15 @@ import { Service, Inject } from 'typedi';
import moment from 'moment';
import {
ISalesByItemsReportQuery,
ISalesByItemsSheetStatement,
ISalesByItemsSheetMeta
ISalesByItemsSheetMeta,
ISalesByItemsSheet,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import SalesByItems from './SalesByItems';
import { Tenant } from '@/system/models';
@Service()
export default class SalesByItemsReportService {
export class SalesByItemsReportService {
@Inject()
tenancy: TenancyService;
@@ -63,20 +63,14 @@ export default class SalesByItemsReportService {
/**
* Retrieve balance sheet statement.
* -------------
* @param {number} tenantId
* @param {IBalanceSheetQuery} query
*
* @return {IBalanceSheetStatement}
* @return {Promise<ISalesByItemsSheet>}
*/
public async salesByItems(
tenantId: number,
query: ISalesByItemsReportQuery
): Promise<{
data: ISalesByItemsSheetStatement,
query: ISalesByItemsReportQuery,
meta: ISalesByItemsSheetMeta,
}> {
): Promise<ISalesByItemsSheet> {
const { Item, InventoryTransaction } = this.tenancy.models(tenantId);
const tenant = await Tenant.query()
@@ -107,20 +101,19 @@ export default class SalesByItemsReportService {
builder.whereIn('itemId', inventoryItemsIds);
// Filter the date range of the sheet.
builder.modify('filterDateRange', filter.fromDate, filter.toDate)
builder.modify('filterDateRange', filter.fromDate, filter.toDate);
}
);
const purchasesByItemsInstance = new SalesByItems(
const sheet = new SalesByItems(
filter,
inventoryItems,
inventoryTransactions,
tenant.metadata.baseCurrency,
tenant.metadata.baseCurrency
);
const purchasesByItemsData = purchasesByItemsInstance.reportData();
const salesByItemsData = sheet.reportData();
return {
data: purchasesByItemsData,
data: salesByItemsData,
query: filter,
meta: this.reportMetadata(tenantId),
};

View File

@@ -0,0 +1,102 @@
import * as R from 'ramda';
import {
ISalesByItemsItem,
ISalesByItemsSheetStatement,
ISalesByItemsTotal,
ITableColumn,
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 extends R.compose(
FinancialTable,
FinancialSheetStructure
)(FinancialSheet) {
private readonly data: ISalesByItemsSheetStatement;
/**
* Constructor method.
* @param {ISalesByItemsSheetStatement} data
*/
constructor(data: ISalesByItemsSheetStatement) {
super();
this.data = data;
}
/**
* Retrieves the common table accessors.
* @returns {ITableColumn[]}
*/
private commonTableAccessors() {
return [
{ key: 'item_name', accessor: 'name' },
{ key: 'sold_quantity', accessor: 'quantitySoldFormatted' },
{ key: 'sold_amount', accessor: 'soldCostFormatted' },
{ key: 'average_price', accessor: 'averageSellPriceFormatted' },
];
}
/**
* Maps the given item node to table row.
* @param {ISalesByItemsItem} item
* @returns {ITableRow}
*/
private itemMap = (item: ISalesByItemsItem): ITableRow => {
const columns = this.commonTableAccessors();
const meta = {
rowTypes: [ROW_TYPE.ITEM],
};
return tableRowMapper(item, columns, meta);
};
/**
* Maps the given items nodes to table rows.
* @param {ISalesByItemsItem[]} items
* @returns {ITableRow[]}
*/
private itemsMap = (items: ISalesByItemsItem[]): ITableRow[] => {
return R.map(this.itemMap, items);
};
/**
* Maps the given total node to table row.
* @param {ISalesByItemsTotal} total
* @returns {ITableRow[]}
*/
private totalMap = (total: ISalesByItemsTotal) => {
const columns = this.commonTableAccessors();
const meta = {
rowTypes: [ROW_TYPE.TOTAL],
};
return tableRowMapper(total, columns, meta);
};
/**
* Retrieves the table rows.
* @returns {ITableRow[]}
*/
public tableData(): ITableRow[] {
const itemsRows = this.itemsMap(this.data.items);
const totalRow = this.totalMap(this.data.total);
return [...itemsRows, totalRow];
}
/**
* Retrieves the table columns.
* @returns {ITableColumn[]}
*/
public tableColumns(): ITableColumn[] {
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);
}
}

View File

@@ -0,0 +1,33 @@
import { Inject, Service } from 'typedi';
import { ISalesByItemsReportQuery } from '@/interfaces';
import { SalesByItemsReportService } from './SalesByItemsService';
import { SalesByItemsTable } from './SalesByItemsTable';
@Service()
export class SalesByItemsTableInjectable {
@Inject()
private salesByItemSheet: SalesByItemsReportService;
/**
* Retrieves the sales by items report in table format.
* @param {number} tenantId
* @param {ISalesByItemsReportQuery} filter
* @returns {Promise<ISalesByItemsTable>}
*/
public async table(tenantId: number, filter: ISalesByItemsReportQuery) {
const { data, query, meta } = await this.salesByItemSheet.salesByItems(
tenantId,
filter
);
const table = new SalesByItemsTable(data);
return {
table: {
columns: table.tableColumns(),
rows: table.tableData(),
},
meta,
query,
};
}
}

View File

@@ -0,0 +1,6 @@
export enum ROW_TYPE {
ITEM = 'ITEM',
TOTAL = 'TOTAL',
}