/* eslint-disable @typescript-eslint/no-explicit-any */ import { PrismaClient } from '@prisma/client'; import * as path from 'path'; import * as fs from 'fs'; import { ExcelReaderService } from './excel-reader'; import { BulkInserter } from './bulk-inserter'; import { ImportProgressServer } from './websocket-server'; import { ImportProgress, ImportResult } from './types'; import { FileDownloader } from './file-downloader'; interface ProcessedSection { sectionData: any; insertedRows: number; } export class CintasImportProcessor { private prisma: PrismaClient; private reader: ExcelReaderService; private inserter: BulkInserter; private progressServer: ImportProgressServer; private fileDownloader: FileDownloader; constructor() { this.prisma = new PrismaClient(); this.reader = new ExcelReaderService(); this.inserter = new BulkInserter(); this.progressServer = ImportProgressServer.getInstance(); this.fileDownloader = new FileDownloader(); } async processCintasImport(importId: number): Promise { let filePath: string | null = null; try { console.log(`[${new Date().toISOString()}] [CintasImport] Starting import processing for ID: ${importId}`); // Initialize the progress server if not already done if (!this.progressServer.isServerInitialized()) { this.progressServer.initialize(); } // Get import record with layout configuration const importRecord = await this.prisma.import.findUnique({ where: { id: importId }, include: { layout: { include: { sections: { include: { fields: true } } } } } }); // Get the file separately const file = importRecord?.fileId ? await this.prisma.file.findUnique({ where: { id: importRecord.fileId } }) : null; if (!importRecord || !file) { console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: Import not found or no file attached`); throw new Error('Import not found or no file attached'); } console.log(`[${new Date().toISOString()}] [CintasImport] Loaded import record: ${importRecord.id}`); // Initialize progress tracking const progress: ImportProgress = { importId, status: 'processing', currentSection: '', currentRow: 0, totalRows: 0, errors: [], processedSections: 0, totalSections: importRecord.layout?.sections?.length || 0 }; // Save file to temporary location const filename = `import_${importId}_${Date.now()}.xlsx`; filePath = path.join(this.fileDownloader.getTempDir(), filename); let fileBuffer: Buffer; if (Buffer.isBuffer(file.data)) { fileBuffer = file.data; } else if (file.data instanceof Uint8Array) { fileBuffer = Buffer.from(file.data); } else if (typeof file.data === 'string') { fileBuffer = Buffer.from(file.data, 'base64'); } else { fileBuffer = Buffer.from(file.data as Buffer); } fs.writeFileSync(filePath, fileBuffer); console.log(`[${new Date().toISOString()}] [CintasImport] File saved to: ${filePath}`); // Read Excel file console.log(`[${new Date().toISOString()}] [CintasImport] Starting Excel file reading...`); // Read file content as buffer const fileContent = fs.readFileSync(filePath); const sections = await this.reader.readExcelFile( fileContent, importRecord.layout, (sectionProgress: ImportProgress) => { this.progressServer.broadcastProgress(importId, sectionProgress); } ); console.log(`[${new Date().toISOString()}] [CintasImport] Excel file read successfully. Found ${sections.length} sections`); // Process each section const processedSections: ProcessedSection[] = []; let totalInserted = 0; for (let i = 0; i < sections.length; i++) { const section = sections[i]; console.log(`[${new Date().toISOString()}] [CintasImport] Processing section ${i + 1}/${sections.length}: ${section.name}`); progress.currentSection = section.name; progress.processedSections = i + 1; this.progressServer.broadcastProgress(importId, progress); try { // Ensure table exists for this section console.log(`[${new Date().toISOString()}] [CintasImport] Creating table ${section.tableName} for section ${section.name}`); await this.inserter.createImportTable(section.tableName, section.fields); const insertedRows = await this.inserter.insertSectionData( section, importId, (rows: number) => { progress.currentRow = rows; this.progressServer.broadcastProgress(importId, progress); } ); processedSections.push({ sectionData: section, insertedRows }); totalInserted += insertedRows; console.log(`[${new Date().toISOString()}] [CintasImport] Completed section ${section.name}: ${insertedRows} rows inserted`); } catch (error) { const errorMessage = `Error processing section ${section.name}: ${error instanceof Error ? error.message : 'Unknown error'}`; progress.errors.push(errorMessage); console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: ${errorMessage}`); this.progressServer.broadcastProgress(importId, progress); } } // Run the stored procedure to calculate summary console.log(`[${new Date().toISOString()}] [CintasImport] Running summary calculation procedure...`); try { await this.prisma.$executeRawUnsafe( `CALL cintas_calculate_summary(${importId})` ); console.log(`[${new Date().toISOString()}] [CintasImport] Summary calculation completed successfully`); } catch (error) { console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: Summary calculation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); progress.errors.push(`Stored procedure error: ${error instanceof Error ? error.message : 'Unknown error'}`); } progress.status = 'completed'; this.progressServer.broadcastProgress(importId, progress); console.log(`[${new Date().toISOString()}] [CintasImport] Import processing completed successfully. Total inserted: ${totalInserted}`); return { success: true, totalInserted, sections: processedSections }; } catch (error) { const progress: ImportProgress = { importId, status: 'failed', currentSection: '', currentRow: 0, totalRows: 0, errors: [error instanceof Error ? error.message : 'Unknown error'], processedSections: 0, totalSections: 0 }; this.progressServer.broadcastProgress(importId, progress); console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: Import processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); // Clean up temporary file if it exists if (filePath && fs.existsSync(filePath)) { try { fs.unlinkSync(filePath); } catch (cleanupError) { console.warn(`[${new Date().toISOString()}] [CintasImport] WARNING: Failed to clean up temporary file: ${cleanupError instanceof Error ? cleanupError.message : 'Unknown error'}`); } } return { success: false, totalInserted: 0, sections: [], errors: [error instanceof Error ? error.message : 'Unknown error'] }; } } }