mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
229 lines
7.6 KiB
TypeScript
229 lines
7.6 KiB
TypeScript
// @ts-nocheck
|
|
import xlsx from 'xlsx';
|
|
import * as R from 'ramda';
|
|
import { get } from 'lodash';
|
|
import { sanitizeResourceName } from '../Import/_utils';
|
|
import { ExportableResources } from './ExportResources';
|
|
import { ServiceError } from '@/exceptions';
|
|
import { Errors, ExportFormat } from './common';
|
|
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
|
|
import { flatDataCollections, getDataAccessor } from './utils';
|
|
import { ExportPdf } from './ExportPdf';
|
|
import { ExportAls } from './ExportAls';
|
|
import { Injectable } from '@nestjs/common';
|
|
|
|
@Injectable()
|
|
export class ExportResourceService {
|
|
constructor(
|
|
private readonly exportAls: ExportAls,
|
|
private readonly exportPdf: ExportPdf,
|
|
private readonly exportableResources: ExportableResources,
|
|
private readonly resourceService: ResourceService,
|
|
) {}
|
|
|
|
/**
|
|
*
|
|
* @param {number} tenantId
|
|
* @param {string} resourceName
|
|
* @param {ExportFormat} format
|
|
* @returns
|
|
*/
|
|
public async export(
|
|
tenantId: number,
|
|
resourceName: string,
|
|
format: ExportFormat = ExportFormat.Csv
|
|
) {
|
|
return this.exportAls.run(() =>
|
|
this.exportAlsRun(tenantId, resourceName, format)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Exports the given resource data through csv, xlsx or pdf.
|
|
* @param {number} tenantId - Tenant id.
|
|
* @param {string} resourceName - Resource name.
|
|
* @param {ExportFormat} format - File format.
|
|
*/
|
|
public async exportAlsRun(
|
|
tenantId: number,
|
|
resourceName: string,
|
|
format: ExportFormat = ExportFormat.Csv
|
|
) {
|
|
const resource = sanitizeResourceName(resourceName);
|
|
const resourceMeta = this.getResourceMeta(tenantId, resource);
|
|
const resourceColumns = this.resourceService.getResourceColumns(
|
|
tenantId,
|
|
resource
|
|
);
|
|
this.validateResourceMeta(resourceMeta);
|
|
|
|
const data = await this.getExportableData(tenantId, resource);
|
|
const transformed = this.transformExportedData(tenantId, resource, data);
|
|
|
|
// Returns the csv, xlsx format.
|
|
if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) {
|
|
const exportableColumns = this.getExportableColumns(resourceColumns);
|
|
const workbook = this.createWorkbook(transformed, exportableColumns);
|
|
|
|
return this.exportWorkbook(workbook, format);
|
|
// Returns the pdf format.
|
|
} else if (format === ExportFormat.Pdf) {
|
|
const printableColumns = this.getPrintableColumns(resourceMeta);
|
|
|
|
return this.exportPdf.pdf(
|
|
tenantId,
|
|
printableColumns,
|
|
transformed,
|
|
resourceMeta?.print?.pageTitle
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves metadata for a specific resource.
|
|
* @param {number} tenantId - The tenant identifier.
|
|
* @param {string} resource - The name of the resource.
|
|
* @returns The metadata of the resource.
|
|
*/
|
|
private getResourceMeta(tenantId: number, resource: string) {
|
|
return this.resourceService.getResourceMeta(tenantId, resource);
|
|
}
|
|
|
|
/**
|
|
* Validates if the resource metadata is exportable.
|
|
* @param {any} resourceMeta - The metadata of the resource.
|
|
* @throws {ServiceError} If the resource is not exportable or lacks columns.
|
|
*/
|
|
private validateResourceMeta(resourceMeta: any) {
|
|
if (!resourceMeta.exportable || !resourceMeta.columns) {
|
|
throw new ServiceError(Errors.RESOURCE_NOT_EXPORTABLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transforms the exported data based on the resource metadata.
|
|
* If the resource metadata specifies a flattening attribute (`exportFlattenOn`),
|
|
* the data will be flattened based on this attribute using the `flatDataCollections` utility function.
|
|
*
|
|
* @param {number} tenantId - The tenant identifier.
|
|
* @param {string} resource - The name of the resource.
|
|
* @param {Array<Record<string, any>>} data - The original data to be transformed.
|
|
* @returns {Array<Record<string, any>>} - The transformed data.
|
|
*/
|
|
private transformExportedData(
|
|
tenantId: number,
|
|
resource: string,
|
|
data: Array<Record<string, any>>
|
|
): Array<Record<string, any>> {
|
|
const resourceMeta = this.getResourceMeta(tenantId, resource);
|
|
|
|
return R.when<Array<Record<string, any>>, Array<Record<string, any>>>(
|
|
R.always(Boolean(resourceMeta.exportFlattenOn)),
|
|
(data) => flatDataCollections(data, resourceMeta.exportFlattenOn),
|
|
data
|
|
);
|
|
}
|
|
/**
|
|
* Fetches exportable data for a given resource.
|
|
* @param {number} tenantId - The tenant identifier.
|
|
* @param {string} resource - The name of the resource.
|
|
* @returns A promise that resolves to the exportable data.
|
|
*/
|
|
private async getExportableData(tenantId: number, resource: string) {
|
|
const exportable =
|
|
this.exportableResources.registry.getExportable(resource);
|
|
|
|
return exportable.exportable(tenantId, {});
|
|
}
|
|
|
|
/**
|
|
* Extracts columns that are marked as exportable from the resource metadata.
|
|
* @param {IModelMeta} resourceMeta - The metadata of the resource.
|
|
* @returns An array of exportable columns.
|
|
*/
|
|
private getExportableColumns(resourceColumns: any) {
|
|
const processColumns = (
|
|
columns: { [key: string]: IModelMetaColumn },
|
|
parent = ''
|
|
) => {
|
|
return Object.entries(columns)
|
|
.filter(([_, value]) => value.exportable !== false)
|
|
.flatMap(([key, value]) => {
|
|
if (value.type === 'collection' && value.collectionOf === 'object') {
|
|
return processColumns(value.columns, key);
|
|
} else {
|
|
const group = parent;
|
|
return [
|
|
{
|
|
name: value.name,
|
|
type: value.type || 'text',
|
|
accessor: value.accessor || key,
|
|
group,
|
|
},
|
|
];
|
|
}
|
|
});
|
|
};
|
|
return processColumns(resourceColumns);
|
|
}
|
|
|
|
private getPrintableColumns(resourceMeta: IModelMeta) {
|
|
const processColumns = (
|
|
columns: { [key: string]: IModelMetaColumn },
|
|
parent = ''
|
|
) => {
|
|
return Object.entries(columns)
|
|
.filter(([_, value]) => value.printable !== false)
|
|
.flatMap(([key, value]) => {
|
|
if (value.type === 'collection' && value.collectionOf === 'object') {
|
|
return processColumns(value.columns, key);
|
|
} else {
|
|
const group = parent;
|
|
return [
|
|
{
|
|
name: value.name,
|
|
type: value.type || 'text',
|
|
accessor: value.accessor || key,
|
|
group,
|
|
},
|
|
];
|
|
}
|
|
});
|
|
};
|
|
return processColumns(resourceMeta.columns);
|
|
}
|
|
|
|
/**
|
|
* Creates a workbook from the provided data and columns.
|
|
* @param {any[]} data - The data to be included in the workbook.
|
|
* @param {any[]} exportableColumns - The columns to be included in the workbook.
|
|
* @returns The created workbook.
|
|
*/
|
|
private createWorkbook(data: any[], exportableColumns: any[]) {
|
|
const workbook = xlsx.utils.book_new();
|
|
const worksheetData = data.map((item) =>
|
|
exportableColumns.map((col) => get(item, getDataAccessor(col)))
|
|
);
|
|
worksheetData.unshift(exportableColumns.map((col) => col.name));
|
|
|
|
const worksheet = xlsx.utils.aoa_to_sheet(worksheetData);
|
|
xlsx.utils.book_append_sheet(workbook, worksheet, 'Exported Data');
|
|
|
|
return workbook;
|
|
}
|
|
|
|
/**
|
|
* Exports the workbook in the specified format.
|
|
* @param {any} workbook - The workbook to be exported.
|
|
* @param {string} format - The format to export the workbook in.
|
|
* @returns The exported workbook data.
|
|
*/
|
|
private exportWorkbook(workbook: any, format: string) {
|
|
if (format.toLowerCase() === 'csv') {
|
|
return xlsx.write(workbook, { type: 'buffer', bookType: 'csv' });
|
|
} else if (format.toLowerCase() === 'xlsx') {
|
|
return xlsx.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
|
}
|
|
}
|
|
}
|