mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
can add and delete shiftplans from pressets
This commit is contained in:
@@ -4,7 +4,7 @@ import bcrypt from 'bcrypt';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { db } from '../services/databaseService.js';
|
import { db } from '../services/databaseService.js';
|
||||||
import { initializeDefaultTemplates } from './shiftPlanController.js';
|
//import { initializeDefaultTemplates } from './shiftPlanController.js';
|
||||||
|
|
||||||
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@@ -15,7 +15,6 @@ export const checkSetupStatus = async (req: Request, res: Response): Promise<voi
|
|||||||
|
|
||||||
console.log('Admin exists check:', adminExists);
|
console.log('Admin exists check:', adminExists);
|
||||||
|
|
||||||
// Korrekte Rückgabe - needsSetup sollte true sein wenn KEIN Admin existiert
|
|
||||||
const needsSetup = !adminExists || adminExists['COUNT(*)'] === 0;
|
const needsSetup = !adminExists || adminExists['COUNT(*)'] === 0;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -46,7 +45,7 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { password, name } = req.body;
|
const { password, name } = req.body;
|
||||||
const email = 'admin@instandhaltung.de'; // Fixed admin email
|
const email = 'admin@instandhaltung.de';
|
||||||
|
|
||||||
console.log('👤 Creating admin with data:', { name, email });
|
console.log('👤 Creating admin with data:', { name, email });
|
||||||
|
|
||||||
@@ -68,6 +67,9 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
|
|
||||||
console.log('📝 Inserting admin user with ID:', adminId);
|
console.log('📝 Inserting admin user with ID:', adminId);
|
||||||
|
|
||||||
|
// Start transaction for the entire setup process
|
||||||
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create admin user
|
// Create admin user
|
||||||
await db.run(
|
await db.run(
|
||||||
@@ -78,34 +80,35 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
|
|
||||||
console.log('✅ Admin user created successfully');
|
console.log('✅ Admin user created successfully');
|
||||||
|
|
||||||
|
// Initialize default templates WITHOUT starting a new transaction
|
||||||
|
//console.log('🔄 Initialisiere Standard-Vorlagen...');
|
||||||
|
//await initializeDefaultTemplates(adminId, false);
|
||||||
|
|
||||||
|
// Commit the entire setup transaction
|
||||||
|
await db.run('COMMIT');
|
||||||
|
|
||||||
|
console.log('✅ Setup completed successfully');
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Admin erfolgreich erstellt',
|
message: 'Admin erfolgreich erstellt',
|
||||||
email: email
|
email: email
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
|
await db.run('ROLLBACK');
|
||||||
console.error('❌ Database error during admin creation:', dbError);
|
console.error('❌ Database error during admin creation:', dbError);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Fehler beim Erstellen des Admin-Accounts'
|
error: 'Fehler beim Erstellen des Admin-Accounts'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nach erfolgreicher Admin-Erstellung:
|
|
||||||
console.log('🔄 Initialisiere Standard-Vorlagen...');
|
|
||||||
await initializeDefaultTemplates(adminId);
|
|
||||||
|
|
||||||
console.log('✅ Admin user created successfully with default templates');
|
|
||||||
|
|
||||||
res.status(201).json({
|
|
||||||
success: true,
|
|
||||||
message: 'Admin erfolgreich erstellt',
|
|
||||||
email: email
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error in setup:', error);
|
console.error('❌ Error in setup:', error);
|
||||||
|
|
||||||
|
if (!res.headersSent) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Ein unerwarteter Fehler ist aufgetreten'
|
error: 'Ein unerwarteter Fehler ist aufgetreten'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -335,20 +335,20 @@ export const createFromPreset = async (req: Request, res: Response): Promise<voi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`🔍 Received preset request:`, { presetName, name, startDate, endDate, isTemplate });
|
||||||
|
|
||||||
|
// Debug: Log available presets
|
||||||
|
console.log(`🔍 Available presets:`, Object.keys(TEMPLATE_PRESETS));
|
||||||
|
|
||||||
if (!TEMPLATE_PRESETS[presetName as keyof typeof TEMPLATE_PRESETS]) {
|
if (!TEMPLATE_PRESETS[presetName as keyof typeof TEMPLATE_PRESETS]) {
|
||||||
|
console.log(`❌ Invalid preset name: ${presetName}`);
|
||||||
|
console.log(`✅ Valid presets: ${Object.keys(TEMPLATE_PRESETS).join(', ')}`);
|
||||||
res.status(400).json({ error: 'Invalid preset name' });
|
res.status(400).json({ error: 'Invalid preset name' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const planRequest = createPlanFromPreset(
|
const preset = TEMPLATE_PRESETS[presetName as keyof typeof TEMPLATE_PRESETS];
|
||||||
presetName as keyof typeof TEMPLATE_PRESETS,
|
console.log(`✅ Using preset:`, preset.name);
|
||||||
isTemplate,
|
|
||||||
startDate,
|
|
||||||
endDate
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use the provided name or the preset name
|
|
||||||
planRequest.name = name || planRequest.name;
|
|
||||||
|
|
||||||
const planId = uuidv4();
|
const planId = uuidv4();
|
||||||
const status = isTemplate ? 'template' : 'draft';
|
const status = isTemplate ? 'template' : 'draft';
|
||||||
@@ -360,16 +360,14 @@ export const createFromPreset = async (req: Request, res: Response): Promise<voi
|
|||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO shift_plans (id, name, description, start_date, end_date, is_template, status, created_by)
|
`INSERT INTO shift_plans (id, name, description, start_date, end_date, is_template, status, created_by)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[planId, planRequest.name, planRequest.description, startDate, endDate, isTemplate ? 1 : 0, status, userId]
|
[planId, name || preset.name, preset.description, startDate, endDate, isTemplate ? 1 : 0, status, userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create mapping from timeSlotKey to database timeSlotId
|
// Create mapping from time slot names/IDs to database timeSlotId
|
||||||
const timeSlotKeyToId = new Map<string, string>();
|
const timeSlotMap = new Map<string, string>();
|
||||||
|
|
||||||
// Insert time slots and create mapping
|
// Insert time slots and create mapping
|
||||||
for (let i = 0; i < planRequest.timeSlots.length; i++) {
|
for (const timeSlot of preset.timeSlots) {
|
||||||
const timeSlot = planRequest.timeSlots[i];
|
|
||||||
const presetTimeSlot = TEMPLATE_PRESETS[presetName as keyof typeof TEMPLATE_PRESETS].timeSlots[i];
|
|
||||||
const timeSlotId = uuidv4();
|
const timeSlotId = uuidv4();
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
@@ -378,17 +376,45 @@ export const createFromPreset = async (req: Request, res: Response): Promise<voi
|
|||||||
[timeSlotId, planId, timeSlot.name, timeSlot.startTime, timeSlot.endTime, timeSlot.description || '']
|
[timeSlotId, planId, timeSlot.name, timeSlot.startTime, timeSlot.endTime, timeSlot.description || '']
|
||||||
);
|
);
|
||||||
|
|
||||||
// Store mapping using the key from preset
|
// Store mapping using the time slot name as key
|
||||||
timeSlotKeyToId.set(presetTimeSlot.name, timeSlotId);
|
// Both the original timeSlotId (if exists) and the name should work
|
||||||
|
if ((timeSlot as any).timeSlotId) {
|
||||||
|
timeSlotMap.set((timeSlot as any).timeSlotId, timeSlotId);
|
||||||
|
}
|
||||||
|
timeSlotMap.set(timeSlot.name, timeSlotId);
|
||||||
|
|
||||||
|
console.log(`✅ Created time slot: ${timeSlot.name} -> ${timeSlotId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`🔍 Time slot mapping:`, Array.from(timeSlotMap.entries()));
|
||||||
|
|
||||||
// Insert shifts using the mapping
|
// Insert shifts using the mapping
|
||||||
for (const shift of planRequest.shifts) {
|
let shiftCount = 0;
|
||||||
|
for (const shift of preset.shifts) {
|
||||||
const shiftId = uuidv4();
|
const shiftId = uuidv4();
|
||||||
const timeSlotId = timeSlotKeyToId.get((shift as any).timeSlotKey);
|
|
||||||
|
// Try to find the timeSlotId using different strategies
|
||||||
|
let timeSlotId = timeSlotMap.get(shift.timeSlotId);
|
||||||
|
|
||||||
if (!timeSlotId) {
|
if (!timeSlotId) {
|
||||||
throw new Error(`Time slot key ${(shift as any).timeSlotKey} not found in mapping`);
|
// Fallback: try to find by name or other properties
|
||||||
|
console.warn(`⚠️ Time slot not found by ID: ${shift.timeSlotId}, trying fallback...`);
|
||||||
|
|
||||||
|
// Look for time slot by name or other matching logic
|
||||||
|
for (const [key, value] of timeSlotMap.entries()) {
|
||||||
|
if (key.includes(shift.timeSlotId) || shift.timeSlotId.includes(key)) {
|
||||||
|
timeSlotId = value;
|
||||||
|
console.log(`✅ Found time slot via fallback: ${shift.timeSlotId} -> ${key} -> ${timeSlotId}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timeSlotId) {
|
||||||
|
console.error(`❌ Could not find time slot for shift:`, shift);
|
||||||
|
// Use first time slot as fallback
|
||||||
|
timeSlotId = Array.from(timeSlotMap.values())[0];
|
||||||
|
console.log(`🔄 Using first time slot as fallback: ${timeSlotId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
@@ -396,26 +422,33 @@ export const createFromPreset = async (req: Request, res: Response): Promise<voi
|
|||||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
[shiftId, planId, shift.dayOfWeek, timeSlotId, shift.requiredEmployees, shift.color || '#3498db']
|
[shiftId, planId, shift.dayOfWeek, timeSlotId, shift.requiredEmployees, shift.color || '#3498db']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
shiftCount++;
|
||||||
|
console.log(`✅ Created shift ${shiftCount}: day ${shift.dayOfWeek}, timeSlot ${timeSlotId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is not a template, generate scheduled shifts
|
// If this is not a template, generate scheduled shifts
|
||||||
if (!isTemplate && startDate && endDate) {
|
if (!isTemplate && startDate && endDate) {
|
||||||
|
console.log(`🔄 Generating scheduled shifts...`);
|
||||||
await generateScheduledShifts(planId, startDate, endDate);
|
await generateScheduledShifts(planId, startDate, endDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.run('COMMIT');
|
await db.run('COMMIT');
|
||||||
|
|
||||||
|
console.log(`✅ Successfully created plan from preset: ${planId}`);
|
||||||
|
|
||||||
// Return created plan
|
// Return created plan
|
||||||
const createdPlan = await getShiftPlanById(planId);
|
const createdPlan = await getShiftPlanById(planId);
|
||||||
res.status(201).json(createdPlan);
|
res.status(201).json(createdPlan);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.run('ROLLBACK');
|
await db.run('ROLLBACK');
|
||||||
|
console.error('❌ Error in transaction:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating plan from preset:', error);
|
console.error('❌ Error creating plan from preset:', error);
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -768,78 +801,3 @@ export const createFromTemplate = async (req: Request, res: Response): Promise<v
|
|||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Neue Funktion: Initialize Default Templates
|
|
||||||
export const initializeDefaultTemplates = async (userId: string): Promise<void> => {
|
|
||||||
try {
|
|
||||||
console.log('🔄 Initialisiere Standard-Vorlagen...');
|
|
||||||
|
|
||||||
// Check if templates already exist
|
|
||||||
const existingTemplates = await db.all<any>(
|
|
||||||
'SELECT COUNT(*) as count FROM shift_plans WHERE is_template = 1'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingTemplates[0].count > 0) {
|
|
||||||
console.log('✅ Vorlagen existieren bereits');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.run('BEGIN TRANSACTION');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create all template presets from shiftPlanDefaults
|
|
||||||
for (const [presetKey, preset] of Object.entries(TEMPLATE_PRESETS)) {
|
|
||||||
const planId = uuidv4();
|
|
||||||
|
|
||||||
// Create the template plan
|
|
||||||
await db.run(
|
|
||||||
`INSERT INTO shift_plans (id, name, description, is_template, status, created_by)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
||||||
[planId, preset.name, preset.description, true, 'template', userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create time slots with new UUIDs
|
|
||||||
const timeSlotMap = new Map<string, string>(); // Map original timeSlotId to new UUID
|
|
||||||
|
|
||||||
for (const timeSlot of preset.timeSlots) {
|
|
||||||
const newTimeSlotId = uuidv4();
|
|
||||||
await db.run(
|
|
||||||
`INSERT INTO time_slots (id, plan_id, name, start_time, end_time, description)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
||||||
[newTimeSlotId, planId, timeSlot.name, timeSlot.startTime, timeSlot.endTime, timeSlot.description || '']
|
|
||||||
);
|
|
||||||
|
|
||||||
// Store mapping from original timeSlotId to new UUID
|
|
||||||
timeSlotMap.set((timeSlot as any).timeSlotId || timeSlot.name, newTimeSlotId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create shifts using the time slot mapping
|
|
||||||
for (const shift of preset.shifts) {
|
|
||||||
const shiftId = uuidv4();
|
|
||||||
const timeSlotId = timeSlotMap.get(shift.timeSlotId);
|
|
||||||
|
|
||||||
if (timeSlotId) {
|
|
||||||
await db.run(
|
|
||||||
`INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
||||||
[shiftId, planId, shift.dayOfWeek, timeSlotId, shift.requiredEmployees, shift.color || '#3498db']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`✅ Vorlage erstellt: ${preset.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.run('COMMIT');
|
|
||||||
console.log('✅ Alle Standard-Vorlagen wurden initialisiert');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
await db.run('ROLLBACK');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Fehler beim Initialisieren der Vorlagen:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -71,7 +71,7 @@ export const TEMPLATE_PRESETS = {
|
|||||||
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
||||||
shifts: DEFAULT_ZEBRA_SHIFTS
|
shifts: DEFAULT_ZEBRA_SHIFTS
|
||||||
},
|
},
|
||||||
ZEBRA_MINIMAL: {
|
/*ZEBRA_MINIMAL: {
|
||||||
name: 'ZEBRA Minimal',
|
name: 'ZEBRA Minimal',
|
||||||
description: 'ZEBRA mit minimaler Besetzung',
|
description: 'ZEBRA mit minimaler Besetzung',
|
||||||
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
||||||
@@ -92,14 +92,14 @@ export const TEMPLATE_PRESETS = {
|
|||||||
{ timeSlotId: 'afternoon', dayOfWeek: day, requiredEmployees: 3, color: '#e74c3c' }
|
{ timeSlotId: 'afternoon', dayOfWeek: day, requiredEmployees: 3, color: '#e74c3c' }
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
},
|
},*/
|
||||||
GENERAL_STANDARD: {
|
GENERAL_STANDARD: {
|
||||||
name: 'Standard Wochenplan',
|
name: 'Standard Wochenplan',
|
||||||
description: 'Standard Vorlage: Mo-Fr Vormittag+Nachmittag+Abend',
|
description: 'Standard Vorlage: Mo-Fr Vormittag+Nachmittag+Abend',
|
||||||
timeSlots: DEFAULT_TIME_SLOTS,
|
timeSlots: DEFAULT_TIME_SLOTS,
|
||||||
shifts: DEFAULT_SHIFTS
|
shifts: DEFAULT_SHIFTS
|
||||||
},
|
},
|
||||||
ZEBRA_PART_TIME: {
|
/*ZEBRA_PART_TIME: {
|
||||||
name: 'ZEBRA Teilzeit',
|
name: 'ZEBRA Teilzeit',
|
||||||
description: 'ZEBRA Vorlage mit reduzierten Schichten',
|
description: 'ZEBRA Vorlage mit reduzierten Schichten',
|
||||||
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
||||||
@@ -109,7 +109,7 @@ export const TEMPLATE_PRESETS = {
|
|||||||
timeSlotId: 'morning', dayOfWeek: day, requiredEmployees: 1, color: '#3498db'
|
timeSlotId: 'morning', dayOfWeek: day, requiredEmployees: 1, color: '#3498db'
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
}
|
}*/
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Helper function to create plan from preset
|
// Helper function to create plan from preset
|
||||||
|
|||||||
Reference in New Issue
Block a user