cintas-import-processor.ts 8.2 KB

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