| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- /* eslint-disable @typescript-eslint/no-explicit-any */
- import * as ExcelJS from 'exceljs';
- import { ReadSectionData, LayoutSectionField, SectionTypeEnum, FieldTypeEnum, ImportProgress } from './types';
- export class ExcelReaderService {
- async readExcelFile(
- fileBuffer: Buffer,
- layoutConfig: any,
- onProgress: (progress: ImportProgress) => void
- ): Promise<ReadSectionData[]> {
- const workbook = new ExcelJS.Workbook();
- await workbook.xlsx.load(fileBuffer as any);
-
- const results: ReadSectionData[] = [];
- const totalSections = layoutConfig.sections?.length || 0;
-
- // Initialize progress
- onProgress({
- importId: 0, // Will be set by caller
- status: 'processing',
- currentSection: '',
- currentRow: 0,
- totalRows: 0,
- errors: [],
- processedSections: 0,
- totalSections
- });
- for (let sectionIndex = 0; sectionIndex < totalSections; sectionIndex++) {
- const section = layoutConfig.sections[sectionIndex];
- const worksheet = workbook.getWorksheet(section.sheetName);
-
- if (!worksheet) {
- onProgress({
- importId: 0,
- status: 'processing',
- currentSection: section.name,
- currentRow: 0,
- totalRows: 0,
- errors: [`Worksheet '${section.sheetName}' not found`],
- processedSections: sectionIndex + 1,
- totalSections
- });
- continue;
- }
- const sectionData = await this.processSection(worksheet, section, sectionIndex, totalSections, onProgress);
- results.push(sectionData);
- }
- return results;
- }
- private async processSection(
- worksheet: ExcelJS.Worksheet,
- section: any,
- sectionIndex: number,
- totalSections: number,
- onProgress: (progress: ImportProgress) => void
- ): Promise<ReadSectionData> {
- const startingRow = section.startingRow || 1;
- const endingRow = section.endingRow || worksheet.rowCount;
-
- // Get headers from the first row (assuming row 1 has headers)
- const headers: string[] = [];
- const headerRow = worksheet.getRow(1);
- headerRow.eachCell((cell) => {
- headers.push(cell.text || '');
- });
- // Process data rows
- const data: Record<string, any>[] = [];
- const totalRows = endingRow - startingRow + 1;
-
- for (let rowNum = startingRow; rowNum <= endingRow; rowNum++) {
- const row = worksheet.getRow(rowNum);
- if (!row.hasValues) continue;
- const rowData: Record<string, any> = {};
-
- // Map cell values based on field configuration
- for (const field of section.fields || []) {
- const cellAddress = this.parseCellAddress(field.cellPosition);
- const cell = row.getCell(cellAddress.col);
-
- if (cell && cell.value !== null && cell.value !== undefined) {
- rowData[field.importTableColumnName] = this.convertCellValue(
- cell.value,
- field.parsedType || FieldTypeEnum.String
- );
- }
- }
-
- data.push(rowData);
-
- // Update progress every 100 rows
- if (rowNum % 100 === 0 || rowNum === endingRow) {
- onProgress({
- importId: 0,
- status: 'processing',
- currentSection: section.name,
- currentRow: rowNum - startingRow + 1,
- totalRows,
- errors: [],
- processedSections: sectionIndex,
- totalSections
- });
- }
- }
- return {
- id: section.id || 0,
- name: section.name || '',
- tableName: section.tableName || '',
- sheet: section.sheetName || '',
- type: section.type || '',
- startingRow,
- endingRow,
- parsedType: this.mapSectionType(section.type),
- fields: this.mapFields(section.fields || []),
- data
- };
- }
- private parseCellAddress(cellPosition: string): { row: number; col: number } {
- const match = cellPosition.match(/([A-Z]+)(\d+)/);
- if (!match) return { row: 1, col: 1 };
-
- const col = match[1].charCodeAt(0) - 'A'.charCodeAt(0) + 1;
- const row = parseInt(match[2]);
-
- return { row, col };
- }
- private mapSectionType(type: string): SectionTypeEnum {
- switch (type?.toLowerCase()) {
- case 'grid':
- return SectionTypeEnum.Grid;
- case 'properties':
- return SectionTypeEnum.Properties;
- default:
- return SectionTypeEnum.Unknown;
- }
- }
- private mapFields(fields: any[]): LayoutSectionField[] {
- return fields.map((field, index) => ({
- id: field.id || index,
- cellPosition: field.cellPosition || '',
- name: field.name || '',
- dataType: field.dataType || 'string',
- dataTypeFormat: field.dataTypeFormat,
- importTableColumnName: field.importTableColumnName || field.name || `column_${index}`,
- importColumnOrderNumber: field.importColumnOrderNumber || index,
- parsedType: this.mapFieldType(field.dataType)
- }));
- }
- private mapFieldType(dataType: string): FieldTypeEnum {
- const type = dataType?.toLowerCase();
-
- switch (type) {
- case 'time':
- return FieldTypeEnum.Time;
- case 'decimal':
- case 'number':
- case 'float':
- return FieldTypeEnum.Decimal;
- case 'date':
- return FieldTypeEnum.Date;
- case 'int':
- case 'integer':
- case 'numeric':
- return FieldTypeEnum.Numeric;
- default:
- return FieldTypeEnum.String;
- }
- }
- private convertCellValue(value: any, fieldType: FieldTypeEnum): any {
- if (value === null || value === undefined) return null;
-
- switch (fieldType) {
- case FieldTypeEnum.Time:
- if (typeof value === 'number') {
- // Excel time is fraction of a day
- return value * 24 * 60 * 60 * 1000; // Convert to milliseconds
- }
- return value;
-
- case FieldTypeEnum.Decimal:
- return parseFloat(value.toString()) || 0;
-
- case FieldTypeEnum.Date:
- if (typeof value === 'number') {
- // Excel date is days since 1900-01-01
- const excelEpoch = new Date(1900, 0, 1);
- return new Date(excelEpoch.getTime() + (value - 1) * 24 * 60 * 60 * 1000);
- }
- return new Date(value);
-
- case FieldTypeEnum.Numeric:
- return parseInt(value.toString()) || 0;
-
- case FieldTypeEnum.String:
- default:
- return value.toString();
- }
- }
- }
|