'use server'; import { prisma } from '@/lib/prisma'; import { revalidatePath } from 'next/cache'; import { z } from 'zod'; // Validation schemas const createImportSchema = z.object({ name: z.string().min(1, 'Import name is required'), layoutId: z.number().int().positive('Layout configuration is required'), fileId: z.string().optional(), }); const updateImportSchema = z.object({ id: z.number().int().positive(), name: z.string().min(1, 'Import name is required'), fileId: z.string().optional(), }); // Create a new import export async function createImport(data: { name: string; layoutId: number; fileId?: string; }) { try { const validatedData = createImportSchema.parse(data); const importRecord = await prisma.import.create({ data: { name: validatedData.name, layoutId: validatedData.layoutId, importDate: new Date(), ...(validatedData.fileId && { fileId: validatedData.fileId }), }, include: { layout: true, }, }); revalidatePath('/imports'); return { success: true, data: importRecord }; } catch (error) { console.error('Error creating import:', error); return { success: false, error: 'Failed to create import' }; } } // Get all imports for the current user export async function getImports(userId?: string) { try { const imports = await prisma.import.findMany({ where: userId ? { fileId: { not: null, }, } : {}, include: { layout: true, }, orderBy: { importDate: 'desc', }, }); // Filter by userId manually since Prisma doesn't have direct relation let filteredImports = imports; if (userId) { const fileIds = await prisma.file.findMany({ where: { userId: userId, }, select: { id: true, }, }); const userFileIds = new Set(fileIds.map(f => f.id)); filteredImports = imports.filter(imp => imp.fileId && userFileIds.has(imp.fileId) ); } return { success: true, data: filteredImports }; } catch (error) { console.error('Error fetching imports:', error); return { success: false, error: 'Failed to fetch imports' }; } } // Get a single import by ID export async function getImportById(id: number) { try { const importRecord = await prisma.import.findUnique({ where: { id }, include: { layout: { include: { sections: { include: { fields: true, }, }, }, }, cintasSummaries: { orderBy: { weekId: 'desc', }, }, }, }); if (!importRecord) { return { success: false, error: 'Import not found' }; } return { success: true, data: importRecord }; } catch (error) { console.error('Error fetching import:', error); return { success: false, error: 'Failed to fetch import' }; } } // Update an import export async function updateImport(data: { id: number; name: string; fileId?: string; }) { try { const validatedData = updateImportSchema.parse(data); const importRecord = await prisma.import.update({ where: { id: validatedData.id }, data: { name: validatedData.name, ...(validatedData.fileId !== undefined && { fileId: validatedData.fileId }), }, include: { layout: true, }, }); revalidatePath('/imports'); return { success: true, data: importRecord }; } catch (error) { console.error('Error updating import:', error); return { success: false, error: 'Failed to update import' }; } } // Delete an import export async function deleteImport(id: number) { try { await prisma.import.delete({ where: { id }, }); revalidatePath('/imports'); return { success: true }; } catch (error) { console.error('Error deleting import:', error); return { success: false, error: 'Failed to delete import' }; } } // Calculate Cintas summaries for an import export async function calculateCintasSummaries(importId: number) { try { // This would typically call a stored procedure or perform calculations // For now, we'll simulate the calculation // In a real implementation, you might call: // await prisma.$executeRaw`CALL cintas_calculate_summary(${importId})`; // For demo purposes, we'll create some sample data const summaries = [ { importId, week: '2024-W01', trrTotal: 100, fourWkAverages: 95, trrPlus4Wk: 195, powerAdds: 25, weekId: 1, }, { importId, week: '2024-W02', trrTotal: 110, fourWkAverages: 100, trrPlus4Wk: 210, powerAdds: 30, weekId: 2, }, ]; // Clear existing summaries for this import await prisma.cintasSummary.deleteMany({ where: { importId }, }); // Create new summaries const createdSummaries = await Promise.all( summaries.map(summary => prisma.cintasSummary.create({ data: summary, }) ) ); return { success: true, data: createdSummaries }; } catch (error) { console.error('Error calculating Cintas summaries:', error); return { success: false, error: 'Failed to calculate summaries' }; } } // Get available layout configurations export async function getLayoutConfigurations() { try { const layouts = await prisma.layoutConfiguration.findMany({ include: { sections: { include: { fields: true, }, }, }, orderBy: { name: 'asc', }, }); return { success: true, data: layouts }; } catch (error) { console.error('Error fetching layout configurations:', error); return { success: false, error: 'Failed to fetch layout configurations' }; } } // Get import summary (GET /api/imports/[id]/summary) export async function getImportSummary(importId: number) { try { if (!importId || isNaN(importId)) { return { success: false, error: 'Invalid import ID' }; } // Check if import exists const importRecord = await prisma.import.findUnique({ where: { id: importId } }); if (!importRecord) { return { success: false, error: 'Import not found' }; } // Get basic summary data const totalRecords = await prisma.cintasInstallCalendar.count({ where: { importId } }); const cintasSummaries = await prisma.cintasSummary.findMany({ where: { importId }, orderBy: { weekId: 'desc' } }); // Get file info const file = importRecord.fileId ? await prisma.file.findUnique({ where: { id: importRecord.fileId } }) : null; const summary = { totalRecords, totalWeeks: cintasSummaries.length, cintasSummaries: cintasSummaries.map((summary: any) => ({ id: summary.id, week: summary.week, trrTotal: summary.trrTotal, fourWkAverages: summary.fourWkAverages, trrPlus4Wk: summary.trrPlus4Wk, powerAdds: summary.powerAdds, weekId: summary.weekId })) }; return { success: true, data: { importId, fileName: file?.filename || 'Unknown', uploadDate: importRecord.createdAt, summary, summaryExists: cintasSummaries.length > 0 } }; } catch (error) { console.error('Error fetching import summary:', error); return { success: false, error: 'Failed to fetch import summary' }; } } // Generate import summary (POST /api/imports/[id]/summary) export async function generateImportSummary(importId: number) { try { if (!importId || isNaN(importId)) { return { success: false, error: 'Invalid import ID' }; } // Check if import exists const importRecord = await prisma.import.findUnique({ where: { id: importId } }); if (!importRecord) { return { success: false, error: 'Import not found' }; } // Check if summary already exists const existingSummaries = await prisma.cintasSummary.count({ where: { importId } }); if (existingSummaries > 0) { // Return existing summary const cintasSummaries = await prisma.cintasSummary.findMany({ where: { importId }, orderBy: { weekId: 'desc' } }); return { success: true, data: { importId, summaryGenerated: false, message: 'Summary already exists', summary: cintasSummaries.map((summary: any) => ({ id: summary.id, week: summary.week, trrTotal: summary.trrTotal, fourWkAverages: summary.fourWkAverages, trrPlus4Wk: summary.trrPlus4Wk, powerAdds: summary.powerAdds, weekId: summary.weekId })) } }; } // Generate new summary using stored procedure await prisma.$executeRawUnsafe( `CALL cintas_calculate_summary(${importId})` ); // Fetch the newly generated summary const cintasSummaries = await prisma.cintasSummary.findMany({ where: { importId }, orderBy: { weekId: 'desc' } }); return { success: true, data: { importId, summaryGenerated: true, message: 'Summary generated successfully', summary: cintasSummaries.map((summary: any) => ({ id: summary.id, week: summary.week, trrTotal: summary.trrTotal, fourWkAverages: summary.fourWkAverages, trrPlus4Wk: summary.trrPlus4Wk, powerAdds: summary.powerAdds, weekId: summary.weekId })) } }; } catch (error) { console.error('Error generating summary:', error); return { success: false, error: 'Failed to generate summary' }; } } // Get import progress (GET /api/imports/[id]/progress) export async function getImportProgress(importId: number) { try { if (!importId || isNaN(importId)) { return { success: false, error: 'Invalid import ID' }; } // Check if import exists const importRecord = await prisma.import.findUnique({ where: { id: importId } }); if (!importRecord) { return { success: false, error: 'Import not found' }; } // For now, we'll simulate progress based on the existence of records const totalRecords = await prisma.cintasInstallCalendar.count({ where: { importId } }); // Since we don't have status fields, we'll use record count as proxy const hasRecords = totalRecords > 0; return { success: true, data: { importId, status: hasRecords ? 'completed' : 'pending', progress: hasRecords ? 100 : 0, processedRecords: totalRecords, totalRecords: totalRecords, errorMessage: null, lastUpdated: importRecord.updatedAt, timestamp: new Date().toISOString() } }; } catch (error) { console.error('Error fetching import progress:', error); return { success: false, error: 'Failed to fetch import progress' }; } } // Trigger import process (POST /api/imports/[id]/trigger) export async function triggerImportProcess(importId: number) { try { if (!importId || isNaN(importId)) { return { success: false, error: 'Invalid import ID' }; } // Validate import exists const importRecord = await prisma.import.findUnique({ where: { id: importId } }); if (!importRecord) { return { success: false, error: 'Import not found' }; } if (!importRecord.fileId) { return { success: false, error: 'No file attached to import' }; } // Check if layout exists const layout = await prisma.layoutConfiguration.findUnique({ where: { id: importRecord.layoutId } }); if (!layout) { return { success: false, error: 'No layout configuration found' }; } // Check if data already exists for this import const existingRecords = await prisma.cintasInstallCalendar.count({ where: { importId } }); if (existingRecords > 0) { return { success: true, message: 'Import already processed', importId, existingRecords }; } // For now, we'll simulate the processing // In production, this would integrate with ImportProcessor // The ImportProcessor would handle the actual file processing return { success: true, message: 'Import process started successfully', importId }; } catch (error) { console.error('Error triggering import:', error); return { success: false, error: 'Failed to trigger import' }; } } // Update import progress (for internal use by ImportProcessor) export async function updateImportProgress( importId: number, progress: { processedRecords: number; totalRecords: number; status: string; errorMessage?: string; } ) { try { // Since the Import model doesn't have these fields, we'll just return success // In a real implementation, you would need to add these fields to the schema console.log(`Import ${importId} progress: ${progress.processedRecords}/${progress.totalRecords} (${progress.status})`); return { success: true }; } catch (error) { console.error('Error updating import progress:', error); return { success: false, error: 'Failed to update progress' }; } }