cintas-import-processor.ts 7.6 KB

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