// @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>} data - The original data to be transformed. * @returns {Array>} - The transformed data. */ private transformExportedData( tenantId: number, resource: string, data: Array> ): Array> { const resourceMeta = this.getResourceMeta(tenantId, resource); return R.when>, Array>>( 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' }); } } }