mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
feat(server): general ledger exporting to csv/xlsx
This commit is contained in:
@@ -2,15 +2,16 @@ import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { query, ValidationChain } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import GeneralLedgerService from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerService';
|
||||
import BaseFinancialReportController from './BaseFinancialReportController';
|
||||
import { AbilitySubject, ReportsAction } from '@/interfaces';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||
import { GeneralLedgerApplication } from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication';
|
||||
|
||||
@Service()
|
||||
export default class GeneralLedgerReportController extends BaseFinancialReportController {
|
||||
@Inject()
|
||||
generalLedgetService: GeneralLedgerService;
|
||||
private generalLedgerApplication: GeneralLedgerApplication;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
@@ -61,20 +62,43 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
|
||||
* @param {Response} res -
|
||||
*/
|
||||
async generalLedger(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId, settings } = req;
|
||||
const { tenantId } = req;
|
||||
const filter = this.matchedQueryData(req);
|
||||
const accept = this.accepts(req);
|
||||
|
||||
try {
|
||||
const { data, query, meta } =
|
||||
await this.generalLedgetService.generalLedger(tenantId, filter);
|
||||
const acceptType = accept.types([
|
||||
ACCEPT_TYPE.APPLICATION_JSON,
|
||||
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
|
||||
ACCEPT_TYPE.APPLICATION_XLSX,
|
||||
ACCEPT_TYPE.APPLICATION_CSV,
|
||||
]);
|
||||
// Retrieves the table format.
|
||||
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
|
||||
const table = await this.generalLedgerApplication.table(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
meta: this.transfromToResponse(meta),
|
||||
data: this.transfromToResponse(data),
|
||||
query: this.transfromToResponse(query),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
return res.status(200).send(table);
|
||||
// Retrieves the csv format.
|
||||
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
|
||||
const buffer = await this.generalLedgerApplication.csv(tenantId, filter);
|
||||
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
|
||||
return res.send(buffer);
|
||||
// Retrieves the xlsx format.
|
||||
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
|
||||
const buffer = this.generalLedgerApplication.xlsx(tenantId, filter);
|
||||
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||
res.setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
);
|
||||
return res.send(buffer);
|
||||
// Retrieves the json format.
|
||||
} else {
|
||||
const sheet = await this.generalLedgerApplication.sheet(tenantId, filter);
|
||||
return res.status(200).send(sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@ import { Request, Response, Router, NextFunction } from 'express';
|
||||
import { castArray } from 'lodash';
|
||||
import { query, oneOf } from 'express-validator';
|
||||
import BaseFinancialReportController from './BaseFinancialReportController';
|
||||
import JournalSheetService from '@/services/FinancialStatements/JournalSheet/JournalSheetService';
|
||||
import { AbilitySubject, ReportsAction } from '@/interfaces';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||
import { JournalSheetApplication } from '@/services/FinancialStatements/JournalSheet/JournalSheetApplication';
|
||||
|
||||
@Service()
|
||||
export default class JournalSheetController extends BaseFinancialReportController {
|
||||
@Inject()
|
||||
journalService: JournalSheetService;
|
||||
private journalSheetApp: JournalSheetApplication;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
@@ -57,28 +58,49 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
async journal(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId, settings } = req;
|
||||
private async journal(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
let filter = this.matchedQueryData(req);
|
||||
|
||||
filter = {
|
||||
...filter,
|
||||
accountsIds: castArray(filter.accountsIds),
|
||||
};
|
||||
const accept = this.accepts(req);
|
||||
const acceptType = accept.types([
|
||||
ACCEPT_TYPE.APPLICATION_JSON,
|
||||
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
|
||||
ACCEPT_TYPE.APPLICATION_XLSX,
|
||||
ACCEPT_TYPE.APPLICATION_CSV,
|
||||
]);
|
||||
|
||||
try {
|
||||
const { data, query, meta } = await this.journalService.journalSheet(
|
||||
tenantId,
|
||||
filter
|
||||
// Retrieves the json table format.
|
||||
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
|
||||
const table = await this.journalSheetApp.table(tenantId, filter);
|
||||
return res.status(200).send(table);
|
||||
// Retrieves the csv format.
|
||||
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
|
||||
const buffer = this.journalSheetApp.csv(tenantId, filter);
|
||||
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
|
||||
return res.send(buffer);
|
||||
// Retrieves the xlsx format.
|
||||
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
|
||||
const buffer = await this.journalSheetApp.xlsx(tenantId, filter);
|
||||
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||
res.setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
);
|
||||
return res.send(buffer);
|
||||
// Retrieves the json format.
|
||||
} else {
|
||||
const sheet = await this.journalSheetApp.sheet(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
data: this.transfromToResponse(data),
|
||||
query: this.transfromToResponse(query),
|
||||
meta: this.transfromToResponse(meta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
return res.status(200).send(sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IFinancialTable } from "./Table";
|
||||
|
||||
|
||||
export interface IGeneralLedgerSheetQuery {
|
||||
@@ -56,6 +57,8 @@ export interface IGeneralLedgerSheetAccount {
|
||||
closingBalance: IGeneralLedgerSheetAccountBalance,
|
||||
}
|
||||
|
||||
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
||||
|
||||
export interface IAccountTransaction {
|
||||
id: number,
|
||||
index: number,
|
||||
@@ -78,4 +81,9 @@ export interface IGeneralLedgerMeta {
|
||||
isCostComputeRunning: boolean,
|
||||
organizationName: string,
|
||||
baseCurrency: string,
|
||||
};
|
||||
};
|
||||
|
||||
export interface IGeneralLedgerTableData extends IFinancialTable {
|
||||
meta: IGeneralLedgerMeta;
|
||||
query: IGeneralLedgerSheetQuery;
|
||||
}
|
||||
@@ -1,36 +1,45 @@
|
||||
import { IJournalEntry } from './Journal';
|
||||
import { IFinancialTable } from './Table';
|
||||
|
||||
export interface IJournalReportQuery {
|
||||
fromDate: Date | string,
|
||||
toDate: Date | string,
|
||||
fromDate: Date | string;
|
||||
toDate: Date | string;
|
||||
numberFormat: {
|
||||
noCents: boolean,
|
||||
divideOn1000: boolean,
|
||||
},
|
||||
transactionType: string,
|
||||
transactionId: string,
|
||||
noCents: boolean;
|
||||
divideOn1000: boolean;
|
||||
};
|
||||
transactionType: string;
|
||||
transactionId: string;
|
||||
|
||||
accountsIds: number | number[],
|
||||
fromRange: number,
|
||||
toRange: number,
|
||||
accountsIds: number | number[];
|
||||
fromRange: number;
|
||||
toRange: number;
|
||||
}
|
||||
|
||||
export interface IJournalReportEntriesGroup {
|
||||
id: string,
|
||||
entries: IJournalEntry[],
|
||||
currencyCode: string,
|
||||
credit: number,
|
||||
debit: number,
|
||||
formattedCredit: string,
|
||||
formattedDebit: string,
|
||||
id: string;
|
||||
entries: IJournalEntry[];
|
||||
currencyCode: string;
|
||||
credit: number;
|
||||
debit: number;
|
||||
formattedCredit: string;
|
||||
formattedDebit: string;
|
||||
}
|
||||
|
||||
export interface IJournalReport {
|
||||
entries: IJournalReportEntriesGroup[],
|
||||
entries: IJournalReportEntriesGroup[];
|
||||
}
|
||||
|
||||
export interface IJournalSheetMeta {
|
||||
isCostComputeRunning: boolean,
|
||||
organizationName: string,
|
||||
baseCurrency: string,
|
||||
}
|
||||
isCostComputeRunning: boolean;
|
||||
organizationName: string;
|
||||
baseCurrency: string;
|
||||
}
|
||||
|
||||
export interface IJournalTable extends IFinancialTable {
|
||||
query: IJournalReportQuery;
|
||||
meta: IJournalSheetMeta;
|
||||
}
|
||||
|
||||
|
||||
export type IJournalTableData = IJournalReportEntriesGroup[];
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Inject } from 'typedi';
|
||||
import {
|
||||
IGeneralLedgerSheetQuery,
|
||||
IGeneralLedgerTableData,
|
||||
} from '@/interfaces';
|
||||
import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable';
|
||||
import { GeneralLedgerExportInjectable } from './GeneralLedgerExport';
|
||||
import { GeneralLedgerService } from './GeneralLedgerService';
|
||||
|
||||
export class GeneralLedgerApplication {
|
||||
@Inject()
|
||||
private GLTable: GeneralLedgerTableInjectable;
|
||||
|
||||
@Inject()
|
||||
private GLExport: GeneralLedgerExportInjectable;
|
||||
|
||||
@Inject()
|
||||
private GLSheet: GeneralLedgerService;
|
||||
|
||||
/**
|
||||
* Retrieves the G/L sheet in json format.
|
||||
* @param {number} tenantId
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
*/
|
||||
public sheet(tenantId: number, query: IGeneralLedgerSheetQuery) {
|
||||
return this.GLSheet.generalLedger(tenantId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the G/L sheet in table format.
|
||||
* @param {number} tenantId
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
* @returns {Promise<IGeneralLedgerTableData>}
|
||||
*/
|
||||
public table(
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery
|
||||
): Promise<IGeneralLedgerTableData> {
|
||||
return this.GLTable.table(tenantId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the G/L sheet in xlsx format.
|
||||
* @param {number} tenantId
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
* @returns {}
|
||||
*/
|
||||
public xlsx(
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery
|
||||
): Promise<Buffer> {
|
||||
return this.GLExport.xlsx(tenantId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the G/L sheet in csv format.
|
||||
* @param {number} tenantId -
|
||||
* @param {IGeneralLedgerSheetQuery} query -
|
||||
*/
|
||||
public csv(
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery
|
||||
): Promise<string> {
|
||||
return this.GLExport.csv(tenantId, query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { IGeneralLedgerSheetQuery } from '@/interfaces';
|
||||
import { TableSheet } from '@/lib/Xlsx/TableSheet';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable';
|
||||
|
||||
@Service()
|
||||
export class GeneralLedgerExportInjectable {
|
||||
@Inject()
|
||||
private generalLedgerTable: GeneralLedgerTableInjectable;
|
||||
|
||||
/**
|
||||
* Retrieves the general ledger sheet in XLSX format.
|
||||
* @param {number} tenantId
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async xlsx(tenantId: number, query: IGeneralLedgerSheetQuery) {
|
||||
const table = await this.generalLedgerTable.table(tenantId, query);
|
||||
|
||||
const tableSheet = new TableSheet(table.table);
|
||||
const tableCsv = tableSheet.convertToXLSX();
|
||||
|
||||
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the general ledger sheet in CSV format.
|
||||
* @param {number} tenantId
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async csv(
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery
|
||||
): Promise<string> {
|
||||
const table = await this.generalLedgerTable.table(tenantId, query);
|
||||
|
||||
const tableSheet = new TableSheet(table.table);
|
||||
const tableCsv = tableSheet.convertToCSV();
|
||||
|
||||
return tableCsv;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ const ERRORS = {
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class GeneralLedgerService {
|
||||
export class GeneralLedgerService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IGeneralLedgerSheetAccount,
|
||||
IGeneralLedgerSheetData,
|
||||
IGeneralLedgerSheetQuery,
|
||||
ITableColumn,
|
||||
ITableColumnAccessor,
|
||||
ITableRow,
|
||||
} from '@/interfaces';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||
import { FinancialTable } from '../FinancialTable';
|
||||
import { tableRowMapper } from '@/utils';
|
||||
|
||||
export class GeneralLedgerTable extends R.compose(
|
||||
FinancialTable,
|
||||
FinancialSheetStructure
|
||||
)(FinancialSheet) {
|
||||
private data: IGeneralLedgerSheetData;
|
||||
private query: IGeneralLedgerSheetQuery;
|
||||
|
||||
/**
|
||||
* Creates an instance of `GeneralLedgerTable`.
|
||||
* @param {IGeneralLedgerSheetData} data
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
*/
|
||||
constructor(data: IGeneralLedgerSheetData, query: IGeneralLedgerSheetQuery) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the common table accessors.
|
||||
* @returns {ITableColumnAccessor[]}
|
||||
*/
|
||||
private commonColumnsAccessors(): ITableColumnAccessor[] {
|
||||
return [
|
||||
{ key: 'date', accessor: 'date' },
|
||||
{ key: 'reference_type', accessor: 'referenceTypeFormatted' },
|
||||
{ key: 'reference_number', accessor: 'reference_number' },
|
||||
{ key: 'currency_code', accessor: 'currencyCode' },
|
||||
{ key: 'credit', accessor: 'formattedCredit' },
|
||||
{ key: 'debit', accessor: 'formattedDebit' },
|
||||
{ key: 'running_balance', accessor: 'formattedRunningBalance' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the common table columns.
|
||||
* @returns {ITableColumn[]}
|
||||
*/
|
||||
private commonColumns(): ITableColumn[] {
|
||||
return [
|
||||
{ key: 'date', label: 'Date' },
|
||||
{ key: 'reference_type', label: 'Reference Type' },
|
||||
{ key: 'reference_number', label: 'Reference Number' },
|
||||
{ key: 'currency_code', label: 'Currency Code' },
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
{ key: 'debit', label: 'Debit' },
|
||||
{ key: 'running_balance', label: 'Running Balance' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the given account node to the table rows.
|
||||
* @param {IGeneralLedgerSheetAccount} account
|
||||
* @returns {ITableRow}
|
||||
*/
|
||||
private accountMapper = (account: IGeneralLedgerSheetAccount): ITableRow => {
|
||||
const columns = this.commonColumnsAccessors();
|
||||
|
||||
return tableRowMapper(account, columns, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the given account node to table rows.
|
||||
* @param {IGeneralLedgerSheetAccount[]} accounts
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private accountsMapper = (
|
||||
accounts: IGeneralLedgerSheetAccount[]
|
||||
): ITableRow[] => {
|
||||
return R.compose(R.map(this.accountMapper))(accounts);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the table rows.
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
public tableRows(): ITableRow[] {
|
||||
return R.compose(this.accountsMapper)(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the table columns.
|
||||
* @returns {ITableColumn[]}
|
||||
*/
|
||||
public tableColumns(): ITableColumn[] {
|
||||
const columns = this.commonColumns();
|
||||
|
||||
return R.compose(this.tableColumnsCellIndexing)(columns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
IGeneralLedgerSheetQuery,
|
||||
IGeneralLedgerTableData,
|
||||
} from '@/interfaces';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { GeneralLedgerService } from './GeneralLedgerService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { GeneralLedgerTable } from './GeneralLedgerTable';
|
||||
|
||||
@Service()
|
||||
export class GeneralLedgerTableInjectable {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private GLSheet: GeneralLedgerService;
|
||||
|
||||
/**
|
||||
* Retrieves the G/L table.
|
||||
* @param {number} tenantId
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
* @returns {Promise<IGeneralLedgerTableData>}
|
||||
*/
|
||||
public async table(
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery
|
||||
): Promise<IGeneralLedgerTableData> {
|
||||
const {
|
||||
data: sheetData,
|
||||
query: sheetQuery,
|
||||
meta: sheetMeta,
|
||||
} = await this.GLSheet.generalLedger(tenantId, query);
|
||||
|
||||
const table = new GeneralLedgerTable(sheetData, sheetQuery);
|
||||
|
||||
return {
|
||||
table: {
|
||||
columns: table.tableColumns(),
|
||||
rows: table.tableRows(),
|
||||
},
|
||||
query: sheetQuery,
|
||||
meta: sheetMeta,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user