diff --git a/packages/server/src/models/Customer.Settings.ts b/packages/server/src/models/Customer.Settings.ts index 74c36867d..86a716507 100644 --- a/packages/server/src/models/Customer.Settings.ts +++ b/packages/server/src/models/Customer.Settings.ts @@ -146,6 +146,7 @@ export default { name: 'vendor.field.opening_balance_at', type: 'date', printable: false, + accessor: 'formattedOpeningBalanceAt' }, currencyCode: { name: 'vendor.field.currency', diff --git a/packages/server/src/services/Import/ImportFileCommon.ts b/packages/server/src/services/Import/ImportFileCommon.ts index 4e9179bdd..e8391882e 100644 --- a/packages/server/src/services/Import/ImportFileCommon.ts +++ b/packages/server/src/services/Import/ImportFileCommon.ts @@ -1,4 +1,3 @@ -import XLSX from 'xlsx'; import bluebird from 'bluebird'; import * as R from 'ramda'; import { Inject, Service } from 'typedi'; @@ -27,23 +26,7 @@ export class ImportFileCommon { @Inject() private resource: ResourceService; - - /** - * Maps the columns of the imported data based on the provided mapping attributes. - * @param {Record[]} body - The array of data objects to map. - * @param {ImportMappingAttr[]} map - The mapping attributes. - * @returns {Record[]} - The mapped data objects. - */ - public parseXlsxSheet(buffer: Buffer): Record[] { - const workbook = XLSX.read(buffer, { type: 'buffer', raw: true }); - - const firstSheetName = workbook.SheetNames[0]; - const worksheet = workbook.Sheets[firstSheetName]; - - return XLSX.utils.sheet_to_json(worksheet, {}); - } - - + /** * Imports the given parsed data to the resource storage through registered importable service. * @param {number} tenantId - diff --git a/packages/server/src/services/Import/ImportFileProcess.ts b/packages/server/src/services/Import/ImportFileProcess.ts index 06b99a889..405b0da43 100644 --- a/packages/server/src/services/Import/ImportFileProcess.ts +++ b/packages/server/src/services/Import/ImportFileProcess.ts @@ -2,18 +2,14 @@ import { Inject, Service } from 'typedi'; import { chain } from 'lodash'; import { Knex } from 'knex'; import { ServiceError } from '@/exceptions'; -import { - ERRORS, - getSheetColumns, - getUnmappedSheetColumns, - readImportFile, -} from './_utils'; +import { ERRORS, getUnmappedSheetColumns, readImportFile } from './_utils'; import { ImportFileCommon } from './ImportFileCommon'; import { ImportFileDataTransformer } from './ImportFileDataTransformer'; import ResourceService from '../Resource/ResourceService'; import UnitOfWork from '../UnitOfWork'; import { ImportFilePreviewPOJO } from './interfaces'; import { Import } from '@/system/models'; +import { parseSheetData } from './sheet_utils'; @Service() export class ImportFileProcess { @@ -49,10 +45,10 @@ export class ImportFileProcess { if (!importFile.isMapped) { throw new ServiceError(ERRORS.IMPORT_FILE_NOT_MAPPED); } - // Read the imported file. + // Read the imported file and parse the given buffer to get columns + // and sheet data in json format. const buffer = await readImportFile(importFile.filename); - const sheetData = this.importCommon.parseXlsxSheet(buffer); - const header = getSheetColumns(sheetData); + const [sheetData, sheetColumns] = parseSheetData(buffer); const resource = importFile.resource; const resourceFields = this.resource.getResourceFields2(tenantId, resource); @@ -87,7 +83,7 @@ export class ImportFileProcess { .flatten() .value(); - const unmappedColumns = getUnmappedSheetColumns(header, mapping); + const unmappedColumns = getUnmappedSheetColumns(sheetColumns, mapping); const totalCount = allData.length; const createdCount = successedImport.length; diff --git a/packages/server/src/services/Import/ImportFileUpload.ts b/packages/server/src/services/Import/ImportFileUpload.ts index bc7cb7163..9f954de9d 100644 --- a/packages/server/src/services/Import/ImportFileUpload.ts +++ b/packages/server/src/services/Import/ImportFileUpload.ts @@ -11,6 +11,7 @@ import { ImportFileCommon } from './ImportFileCommon'; import { ImportFileDataValidator } from './ImportFileDataValidator'; import { ImportFileUploadPOJO } from './interfaces'; import { Import } from '@/system/models'; +import { parseSheetData } from './sheet_utils'; @Service() export class ImportFileUploadService { @@ -77,14 +78,12 @@ export class ImportFileUploadService { const buffer = await readImportFile(filename); // Parse the buffer file to array data. - const sheetData = this.importFileCommon.parseXlsxSheet(buffer); + const [sheetData, sheetColumns] = parseSheetData(buffer); + const coumnsStringified = JSON.stringify(sheetColumns); // Throws service error if the sheet data is empty. validateSheetEmpty(sheetData); - const sheetColumns = this.importFileCommon.parseSheetColumns(sheetData); - const coumnsStringified = JSON.stringify(sheetColumns); - try { // Validates the params Yup schema. await this.importFileCommon.validateParamsSchema(resource, params); diff --git a/packages/server/src/services/Import/sheet_utils.ts b/packages/server/src/services/Import/sheet_utils.ts new file mode 100644 index 000000000..b21f07320 --- /dev/null +++ b/packages/server/src/services/Import/sheet_utils.ts @@ -0,0 +1,56 @@ +import XLSX from 'xlsx'; +import { first } from 'lodash'; + +/** + * Parses the given sheet buffer to worksheet. + * @param {Buffer} buffer + * @returns {XLSX.WorkSheet} + */ +export function parseFirstSheet(buffer: Buffer): XLSX.WorkSheet { + const workbook = XLSX.read(buffer, { type: 'buffer', raw: true }); + + const firstSheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[firstSheetName]; + + return worksheet; +} + +/** + * Extracts the given worksheet to columns. + * @param {XLSX.WorkSheet} worksheet + * @returns {Array} + */ +export function extractSheetColumns(worksheet: XLSX.WorkSheet): Array { + // By default, sheet_to_json scans the first row and uses the values as headers. + // With the header: 1 option, the function exports an array of arrays of values. + const sheetCells = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); + const sheetCols = first(sheetCells) as Array; + + return sheetCols.filter((col) => col); +} + +/** + * Parses the given worksheet to json values. the keys are columns labels. + * @param {XLSX.WorkSheet} worksheet + * @returns {Array>} + */ +export function parseSheetToJson( + worksheet: XLSX.WorkSheet +): Array> { + return XLSX.utils.sheet_to_json(worksheet, {}); +} + +/** + * Parses the given sheet buffer then retrieves the sheet data and columns. + * @param {Buffer} buffer + */ +export function parseSheetData( + buffer: Buffer +): [Array>, string[]] { + const worksheet = parseFirstSheet(buffer); + + const columns = extractSheetColumns(worksheet); + const data = parseSheetToJson(worksheet); + + return [data, columns]; +}