cintas-import-processor.ts 7.6 KB

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