cintas-import-processor.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /* eslint-disable @typescript-eslint/no-explicit-any */
  2. import { PrismaClient } from '@prisma/client';
  3. import * as path from 'path';
  4. import * as fs from 'fs';
  5. import { ExcelReaderService } from './excel-reader';
  6. import { BulkInserter } from './bulk-inserter';
  7. import { ImportProgressServer } from './websocket-server';
  8. import { ImportProgress, ImportResult } from './types';
  9. import { FileDownloader } from './file-downloader';
  10. export class CintasImportProcessor {
  11. private prisma: PrismaClient;
  12. private reader: ExcelReaderService;
  13. private inserter: BulkInserter;
  14. private progressServer: ImportProgressServer;
  15. private fileDownloader: FileDownloader;
  16. constructor() {
  17. this.prisma = new PrismaClient();
  18. this.reader = new ExcelReaderService();
  19. this.inserter = new BulkInserter();
  20. this.progressServer = ImportProgressServer.getInstance();
  21. this.fileDownloader = new FileDownloader();
  22. }
  23. async processCintasImport(importId: number): Promise<ImportResult> {
  24. let filePath: string | null = null;
  25. try {
  26. console.log(`[${new Date().toISOString()}] [CintasImport] Starting import processing for ID: ${importId}`);
  27. // Initialize the progress server if not already done
  28. if (!this.progressServer.isServerInitialized()) {
  29. this.progressServer.initialize();
  30. }
  31. // Get import record with layout configuration
  32. const importRecord = await this.prisma.import.findUnique({
  33. where: { id: importId },
  34. include: {
  35. layout: {
  36. include: {
  37. sections: {
  38. include: { fields: true }
  39. }
  40. }
  41. }
  42. }
  43. });
  44. // Get the file separately
  45. const file = importRecord?.fileId ? await this.prisma.file.findUnique({
  46. where: { id: importRecord.fileId }
  47. }) : null;
  48. if (!importRecord || !file) {
  49. console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: Import not found or no file attached`);
  50. throw new Error('Import not found or no file attached');
  51. }
  52. console.log(`[${new Date().toISOString()}] [CintasImport] Loaded import record: ${importRecord.id}`);
  53. // Initialize progress tracking
  54. const progress: ImportProgress = {
  55. importId,
  56. status: 'processing',
  57. currentSection: '',
  58. currentRow: 0,
  59. totalRows: 0,
  60. errors: [],
  61. processedSections: 0,
  62. totalSections: importRecord.layout?.sections?.length || 0
  63. };
  64. // Save file to temporary location
  65. const filename = `import_${importId}_${Date.now()}.xlsx`;
  66. filePath = path.join(this.fileDownloader.getTempDir(), filename);
  67. let fileBuffer: Buffer;
  68. if (Buffer.isBuffer(file.data)) {
  69. fileBuffer = file.data;
  70. } else if (file.data instanceof Uint8Array) {
  71. fileBuffer = Buffer.from(file.data);
  72. } else {
  73. fileBuffer = Buffer.from(file.data as any);
  74. }
  75. fs.writeFileSync(filePath, fileBuffer);
  76. console.log(`[${new Date().toISOString()}] [CintasImport] File saved to: ${filePath}`);
  77. // Read Excel file
  78. console.log(`[${new Date().toISOString()}] [CintasImport] Starting Excel file reading...`);
  79. const sections = await this.reader.readExcelFile(
  80. filePath,
  81. importRecord.layout,
  82. (sectionProgress) => {
  83. this.progressServer.broadcastProgress(importId, sectionProgress);
  84. }
  85. );
  86. console.log(`[${new Date().toISOString()}] [CintasImport] Excel file read successfully. Found ${sections.length} sections`);
  87. // Process each section
  88. const processedSections = [];
  89. let totalInserted = 0;
  90. for (let i = 0; i < sections.length; i++) {
  91. const section = sections[i];
  92. console.log(`[${new Date().toISOString()}] [CintasImport] Processing section ${i+1}/${sections.length}: ${section.name}`);
  93. progress.currentSection = section.name;
  94. progress.processedSections = i + 1;
  95. this.progressServer.broadcastProgress(importId, progress);
  96. try {
  97. // Ensure table exists for this section
  98. console.log(`[${new Date().toISOString()}] [CintasImport] Creating table ${section.tableName} for section ${section.name}`);
  99. await this.inserter.createImportTable(section.tableName, section.fields);
  100. const insertedRows = await this.inserter.insertSectionData(
  101. section,
  102. importId,
  103. (rows) => {
  104. progress.currentRow = rows;
  105. this.progressServer.broadcastProgress(importId, progress);
  106. }
  107. );
  108. processedSections.push({
  109. sectionData: section,
  110. insertedRows
  111. });
  112. totalInserted += insertedRows;
  113. console.log(`[${new Date().toISOString()}] [CintasImport] Completed section ${section.name}: ${insertedRows} rows inserted`);
  114. } catch (error) {
  115. const errorMessage = `Error processing section ${section.name}: ${error instanceof Error ? error.message : 'Unknown error'}`;
  116. progress.errors.push(errorMessage);
  117. console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: ${errorMessage}`);
  118. this.progressServer.broadcastProgress(importId, progress);
  119. }
  120. }
  121. // Run the stored procedure to calculate summary
  122. console.log(`[${new Date().toISOString()}] [CintasImport] Running summary calculation procedure...`);
  123. try {
  124. await this.prisma.$executeRawUnsafe(
  125. `CALL cintas_calculate_summary(${importId})`
  126. );
  127. console.log(`[${new Date().toISOString()}] [CintasImport] Summary calculation completed successfully`);
  128. } catch (error) {
  129. console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: Summary calculation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
  130. progress.errors.push(`Stored procedure error: ${error instanceof Error ? error.message : 'Unknown error'}`);
  131. }
  132. progress.status = 'completed';
  133. this.progressServer.broadcastProgress(importId, progress);
  134. console.log(`[${new Date().toISOString()}] [CintasImport] Import processing completed successfully. Total inserted: ${totalInserted}`);
  135. return {
  136. success: true,
  137. totalInserted,
  138. sections: processedSections
  139. };
  140. } catch (error) {
  141. const progress: ImportProgress = {
  142. importId,
  143. status: 'failed',
  144. currentSection: '',
  145. currentRow: 0,
  146. totalRows: 0,
  147. errors: [error instanceof Error ? error.message : 'Unknown error'],
  148. processedSections: 0,
  149. totalSections: 0
  150. };
  151. this.progressServer.broadcastProgress(importId, progress);
  152. console.error(`[${new Date().toISOString()}] [CintasImport] ERROR: Import processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
  153. // Clean up temporary file if it exists
  154. if (filePath && fs.existsSync(filePath)) {
  155. try {
  156. fs.unlinkSync(filePath);
  157. } catch (cleanupError) {
  158. console.warn(`[${new Date().toISOString()}] [CintasImport] WARNING: Failed to clean up temporary file: ${cleanupError instanceof Error ? cleanupError.message : 'Unknown error'}`);
  159. }
  160. }
  161. return {
  162. success: false,
  163. totalInserted: 0,
  164. sections: [],
  165. errors: [error instanceof Error ? error.message : 'Unknown error']
  166. };
  167. }
  168. }
  169. }