mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
set template shift struc
This commit is contained in:
@@ -308,7 +308,7 @@ async function generateShiftsFromTemplate(shiftPlanId: string, templateId: strin
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
|
||||
// Generate shifts for each day in the date range
|
||||
// Generate shifts ONLY for days that have template shifts defined
|
||||
for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) {
|
||||
// Convert JS day (0=Sunday) to our format (1=Monday, 7=Sunday)
|
||||
const dayOfWeek = date.getDay() === 0 ? 7 : date.getDay();
|
||||
@@ -316,23 +316,26 @@ async function generateShiftsFromTemplate(shiftPlanId: string, templateId: strin
|
||||
// Find template shifts for this day of week
|
||||
const shiftsForDay = templateShifts.filter(shift => shift.day_of_week === dayOfWeek);
|
||||
|
||||
for (const templateShift of shiftsForDay) {
|
||||
const shiftId = uuidv4();
|
||||
|
||||
await db.run(
|
||||
`INSERT INTO assigned_shifts (id, shift_plan_id, date, name, start_time, end_time, required_employees, assigned_employees)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
shiftId,
|
||||
shiftPlanId,
|
||||
date.toISOString().split('T')[0],
|
||||
templateShift.name,
|
||||
templateShift.start_time,
|
||||
templateShift.end_time,
|
||||
templateShift.required_employees,
|
||||
JSON.stringify([])
|
||||
]
|
||||
);
|
||||
// Only create shifts if there are template shifts defined for this weekday
|
||||
if (shiftsForDay.length > 0) {
|
||||
for (const templateShift of shiftsForDay) {
|
||||
const shiftId = uuidv4();
|
||||
|
||||
await db.run(
|
||||
`INSERT INTO assigned_shifts (id, shift_plan_id, date, name, start_time, end_time, required_employees, assigned_employees)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
shiftId,
|
||||
shiftPlanId,
|
||||
date.toISOString().split('T')[0],
|
||||
templateShift.name,
|
||||
templateShift.start_time,
|
||||
templateShift.end_time,
|
||||
templateShift.required_employees,
|
||||
JSON.stringify([])
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,37 +2,63 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { db } from '../services/databaseService.js';
|
||||
import { ShiftTemplate, CreateShiftTemplateRequest, UpdateShiftTemplateRequest } from '../models/ShiftTemplate.js';
|
||||
import {
|
||||
ShiftTemplate,
|
||||
TemplateShiftSlot,
|
||||
TemplateShiftTimeRange,
|
||||
CreateShiftTemplateRequest,
|
||||
UpdateShiftTemplateRequest
|
||||
} from '../models/ShiftTemplate.js';
|
||||
import { AuthRequest } from '../middleware/auth.js';
|
||||
|
||||
export const getTemplates = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const templates = await db.all<ShiftTemplate>(`
|
||||
const templates = await db.all<any>(`
|
||||
SELECT st.*, u.name as created_by_name
|
||||
FROM shift_templates st
|
||||
LEFT JOIN users u ON st.created_by = u.id
|
||||
ORDER BY st.created_at DESC
|
||||
`);
|
||||
|
||||
// Für jede Vorlage die Schichten laden
|
||||
// Für jede Vorlage die Schichten und Zeit-Slots laden
|
||||
const templatesWithShifts = await Promise.all(
|
||||
templates.map(async (template) => {
|
||||
const shifts = await db.all<any>(`
|
||||
SELECT * FROM template_shifts
|
||||
// Lade Schicht-Slots
|
||||
const shiftSlots = await db.all<any>(`
|
||||
SELECT ts.*, tts.name as time_range_name, tts.start_time as time_range_start, tts.end_time as time_range_end
|
||||
FROM template_shifts ts
|
||||
LEFT JOIN template_time_slots tts ON ts.time_slot_id = tts.id
|
||||
WHERE ts.template_id = ?
|
||||
ORDER BY ts.day_of_week, tts.start_time
|
||||
`, [template.id]);
|
||||
|
||||
// Lade Zeit-Slots
|
||||
const timeSlots = await db.all<any>(`
|
||||
SELECT * FROM template_time_slots
|
||||
WHERE template_id = ?
|
||||
ORDER BY day_of_week, start_time
|
||||
ORDER BY start_time
|
||||
`, [template.id]);
|
||||
|
||||
return {
|
||||
...template,
|
||||
shifts: shifts.map(shift => ({
|
||||
id: shift.id,
|
||||
dayOfWeek: shift.day_of_week,
|
||||
name: shift.name,
|
||||
startTime: shift.start_time,
|
||||
endTime: shift.end_time,
|
||||
requiredEmployees: shift.required_employees,
|
||||
color: shift.color
|
||||
shifts: shiftSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
dayOfWeek: slot.day_of_week,
|
||||
timeRange: {
|
||||
id: slot.time_slot_id,
|
||||
name: slot.time_range_name,
|
||||
startTime: slot.time_range_start,
|
||||
endTime: slot.time_range_end
|
||||
},
|
||||
requiredEmployees: slot.required_employees,
|
||||
color: slot.color
|
||||
})),
|
||||
timeSlots: timeSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
startTime: slot.start_time,
|
||||
endTime: slot.end_time,
|
||||
description: slot.description
|
||||
}))
|
||||
};
|
||||
})
|
||||
@@ -49,7 +75,7 @@ export const getTemplate = async (req: Request, res: Response): Promise<void> =>
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const template = await db.get<ShiftTemplate>(`
|
||||
const template = await db.get<any>(`
|
||||
SELECT st.*, u.name as created_by_name
|
||||
FROM shift_templates st
|
||||
LEFT JOIN users u ON st.created_by = u.id
|
||||
@@ -61,26 +87,46 @@ export const getTemplate = async (req: Request, res: Response): Promise<void> =>
|
||||
return;
|
||||
}
|
||||
|
||||
const shifts = await db.all<any>(`
|
||||
SELECT * FROM template_shifts
|
||||
WHERE template_id = ?
|
||||
ORDER BY day_of_week, start_time
|
||||
// Lade Schicht-Slots
|
||||
const shiftSlots = await db.all<any>(`
|
||||
SELECT ts.*, tts.name as time_range_name, tts.start_time as time_range_start, tts.end_time as time_range_end
|
||||
FROM template_shifts ts
|
||||
LEFT JOIN template_time_slots tts ON ts.time_slot_id = tts.id
|
||||
WHERE ts.template_id = ?
|
||||
ORDER BY ts.day_of_week, tts.start_time
|
||||
`, [id]);
|
||||
|
||||
const templateWithShifts = {
|
||||
// Lade Zeit-Slots
|
||||
const timeSlots = await db.all<any>(`
|
||||
SELECT * FROM template_time_slots
|
||||
WHERE template_id = ?
|
||||
ORDER BY start_time
|
||||
`, [id]);
|
||||
|
||||
const templateWithData = {
|
||||
...template,
|
||||
shifts: shifts.map(shift => ({
|
||||
id: shift.id,
|
||||
dayOfWeek: shift.day_of_week,
|
||||
name: shift.name,
|
||||
startTime: shift.start_time,
|
||||
endTime: shift.end_time,
|
||||
requiredEmployees: shift.required_employees,
|
||||
color: shift.color
|
||||
shifts: shiftSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
dayOfWeek: slot.day_of_week,
|
||||
timeRange: {
|
||||
id: slot.time_slot_id,
|
||||
name: slot.time_range_name,
|
||||
startTime: slot.time_range_start,
|
||||
endTime: slot.time_range_end
|
||||
},
|
||||
requiredEmployees: slot.required_employees,
|
||||
color: slot.color
|
||||
})),
|
||||
timeSlots: timeSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
startTime: slot.start_time,
|
||||
endTime: slot.end_time,
|
||||
description: slot.description
|
||||
}))
|
||||
};
|
||||
|
||||
res.json(templateWithShifts);
|
||||
res.json(templateWithData);
|
||||
} catch (error) {
|
||||
console.error('Error fetching template:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
@@ -98,32 +144,46 @@ export const createDefaultTemplate = async (userId: string): Promise<string> =>
|
||||
await db.run(
|
||||
`INSERT INTO shift_templates (id, name, description, is_default, created_by)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[templateId, 'Standardwoche', 'Mo-Do: 2 Schichten, Fr: 1 Schicht', true, userId]
|
||||
[templateId, 'Standardwoche', 'Standard Vorlage mit konfigurierten Zeit-Slots', true, userId]
|
||||
);
|
||||
|
||||
// Vormittagsschicht Mo-Do
|
||||
for (let day = 1; day <= 4; day++) {
|
||||
// Füge Zeit-Slots hinzu
|
||||
const timeSlots = [
|
||||
{ id: uuidv4(), name: 'Vormittag', startTime: '08:00', endTime: '12:00', description: 'Vormittagsschicht' },
|
||||
{ id: uuidv4(), name: 'Nachmittag', startTime: '12:00', endTime: '16:00', description: 'Nachmittagsschicht' },
|
||||
{ id: uuidv4(), name: 'Abend', startTime: '16:00', endTime: '20:00', description: 'Abendschicht' }
|
||||
];
|
||||
|
||||
for (const slot of timeSlots) {
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[uuidv4(), templateId, day, 'Vormittagsschicht', '08:00', '12:00', 1]
|
||||
`INSERT INTO template_time_slots (id, template_id, name, start_time, end_time, description)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[slot.id, templateId, slot.name, slot.startTime, slot.endTime, slot.description]
|
||||
);
|
||||
}
|
||||
|
||||
// Nachmittagsschicht Mo-Do
|
||||
// Erstelle Schichten für Mo-Do mit Zeit-Slot Referenzen
|
||||
for (let day = 1; day <= 4; day++) {
|
||||
// Vormittagsschicht
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[uuidv4(), templateId, day, 'Nachmittagsschicht', '11:30', '15:30', 1]
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[uuidv4(), templateId, day, timeSlots[0].id, 1, '#3498db']
|
||||
);
|
||||
|
||||
// Nachmittagsschicht
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[uuidv4(), templateId, day, timeSlots[1].id, 1, '#e74c3c']
|
||||
);
|
||||
}
|
||||
|
||||
// Freitag nur Vormittagsschicht
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[uuidv4(), templateId, 5, 'Vormittagsschicht', '08:00', '12:00', 1]
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[uuidv4(), templateId, 5, timeSlots[0].id, 1, '#3498db']
|
||||
);
|
||||
|
||||
await db.run('COMMIT');
|
||||
@@ -140,7 +200,7 @@ export const createDefaultTemplate = async (userId: string): Promise<string> =>
|
||||
|
||||
export const createTemplate = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { name, description, isDefault, shifts }: CreateShiftTemplateRequest = req.body;
|
||||
const { name, description, isDefault, shifts, timeSlots }: CreateShiftTemplateRequest = req.body;
|
||||
const userId = (req as AuthRequest).user?.userId;
|
||||
|
||||
if (!userId) {
|
||||
@@ -167,13 +227,23 @@ export const createTemplate = async (req: Request, res: Response): Promise<void>
|
||||
[templateId, name, description, isDefault ? 1 : 0, userId]
|
||||
);
|
||||
|
||||
// Insert time slots
|
||||
for (const timeSlot of timeSlots) {
|
||||
const timeSlotId = timeSlot.id || uuidv4();
|
||||
await db.run(
|
||||
`INSERT INTO template_time_slots (id, template_id, name, start_time, end_time, description)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[timeSlotId, templateId, timeSlot.name, timeSlot.startTime, timeSlot.endTime, timeSlot.description]
|
||||
);
|
||||
}
|
||||
|
||||
// Insert shifts
|
||||
for (const shift of shifts) {
|
||||
const shiftId = uuidv4();
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[shiftId, templateId, shift.dayOfWeek, shift.name, shift.startTime, shift.endTime, shift.requiredEmployees, shift.color || '#3498db']
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[shiftId, templateId, shift.dayOfWeek, shift.timeRange.id, shift.requiredEmployees, shift.color || '#3498db']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -188,31 +258,8 @@ export const createTemplate = async (req: Request, res: Response): Promise<void>
|
||||
await db.run('COMMIT');
|
||||
|
||||
// Return created template
|
||||
const createdTemplate = await db.get<ShiftTemplate>(`
|
||||
SELECT st.*, u.name as created_by_name
|
||||
FROM shift_templates st
|
||||
LEFT JOIN users u ON st.created_by = u.id
|
||||
WHERE st.id = ?
|
||||
`, [templateId]);
|
||||
|
||||
const templateShifts = await db.all<any>(`
|
||||
SELECT * FROM template_shifts
|
||||
WHERE template_id = ?
|
||||
ORDER BY day_of_week, start_time
|
||||
`, [templateId]);
|
||||
|
||||
res.status(201).json({
|
||||
...createdTemplate,
|
||||
shifts: templateShifts.map(shift => ({
|
||||
id: shift.id,
|
||||
dayOfWeek: shift.day_of_week,
|
||||
name: shift.name,
|
||||
startTime: shift.start_time,
|
||||
endTime: shift.end_time,
|
||||
requiredEmployees: shift.required_employees,
|
||||
color: shift.color
|
||||
}))
|
||||
});
|
||||
const createdTemplate = await getTemplateById(templateId);
|
||||
res.status(201).json(createdTemplate);
|
||||
|
||||
} catch (error) {
|
||||
await db.run('ROLLBACK');
|
||||
@@ -228,7 +275,7 @@ export const createTemplate = async (req: Request, res: Response): Promise<void>
|
||||
export const updateTemplate = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, description, isDefault, shifts }: UpdateShiftTemplateRequest = req.body;
|
||||
const { name, description, isDefault, shifts, timeSlots }: UpdateShiftTemplateRequest = req.body;
|
||||
|
||||
// Check if template exists
|
||||
const existingTemplate = await db.get('SELECT * FROM shift_templates WHERE id = ?', [id]);
|
||||
@@ -258,6 +305,22 @@ export const updateTemplate = async (req: Request, res: Response): Promise<void>
|
||||
);
|
||||
}
|
||||
|
||||
// If updating time slots, replace all time slots
|
||||
if (timeSlots) {
|
||||
// Delete existing time slots
|
||||
await db.run('DELETE FROM template_time_slots WHERE template_id = ?', [id]);
|
||||
|
||||
// Insert new time slots
|
||||
for (const timeSlot of timeSlots) {
|
||||
const timeSlotId = timeSlot.id || uuidv4();
|
||||
await db.run(
|
||||
`INSERT INTO template_time_slots (id, template_id, name, start_time, end_time, description)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[timeSlotId, id, timeSlot.name, timeSlot.startTime, timeSlot.endTime, timeSlot.description]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If updating shifts, replace all shifts
|
||||
if (shifts) {
|
||||
// Delete existing shifts
|
||||
@@ -267,9 +330,9 @@ export const updateTemplate = async (req: Request, res: Response): Promise<void>
|
||||
for (const shift of shifts) {
|
||||
const shiftId = uuidv4();
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[shiftId, id, shift.dayOfWeek, shift.name, shift.startTime, shift.endTime, shift.requiredEmployees, shift.color || '#3498db']
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[shiftId, id, shift.dayOfWeek, shift.timeRange.id, shift.requiredEmployees, shift.color || '#3498db']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -285,31 +348,8 @@ export const updateTemplate = async (req: Request, res: Response): Promise<void>
|
||||
await db.run('COMMIT');
|
||||
|
||||
// Return updated template
|
||||
const updatedTemplate = await db.get<ShiftTemplate>(`
|
||||
SELECT st.*, u.name as created_by_name
|
||||
FROM shift_templates st
|
||||
LEFT JOIN users u ON st.created_by = u.id
|
||||
WHERE st.id = ?
|
||||
`, [id]);
|
||||
|
||||
const templateShifts = await db.all<any>(`
|
||||
SELECT * FROM template_shifts
|
||||
WHERE template_id = ?
|
||||
ORDER BY day_of_week, start_time
|
||||
`, [id]);
|
||||
|
||||
res.json({
|
||||
...updatedTemplate,
|
||||
shifts: templateShifts.map(shift => ({
|
||||
id: shift.id,
|
||||
dayOfWeek: shift.day_of_week,
|
||||
name: shift.name,
|
||||
startTime: shift.start_time,
|
||||
endTime: shift.end_time,
|
||||
requiredEmployees: shift.required_employees,
|
||||
color: shift.color
|
||||
}))
|
||||
});
|
||||
const updatedTemplate = await getTemplateById(id);
|
||||
res.json(updatedTemplate);
|
||||
|
||||
} catch (error) {
|
||||
await db.run('ROLLBACK');
|
||||
@@ -334,11 +374,64 @@ export const deleteTemplate = async (req: Request, res: Response): Promise<void>
|
||||
}
|
||||
|
||||
await db.run('DELETE FROM shift_templates WHERE id = ?', [id]);
|
||||
// Template shifts will be automatically deleted due to CASCADE
|
||||
// Template shifts and time slots will be automatically deleted due to CASCADE
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error('Error deleting template:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Helper function to get template by ID
|
||||
async function getTemplateById(templateId: string): Promise<any> {
|
||||
const template = await db.get<any>(`
|
||||
SELECT st.*, u.name as created_by_name
|
||||
FROM shift_templates st
|
||||
LEFT JOIN users u ON st.created_by = u.id
|
||||
WHERE st.id = ?
|
||||
`, [templateId]);
|
||||
|
||||
if (!template) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Lade Schicht-Slots
|
||||
const shiftSlots = await db.all<any>(`
|
||||
SELECT ts.*, tts.name as time_range_name, tts.start_time as time_range_start, tts.end_time as time_range_end
|
||||
FROM template_shifts ts
|
||||
LEFT JOIN template_time_slots tts ON ts.time_slot_id = tts.id
|
||||
WHERE ts.template_id = ?
|
||||
ORDER BY ts.day_of_week, tts.start_time
|
||||
`, [templateId]);
|
||||
|
||||
// Lade Zeit-Slots
|
||||
const timeSlots = await db.all<any>(`
|
||||
SELECT * FROM template_time_slots
|
||||
WHERE template_id = ?
|
||||
ORDER BY start_time
|
||||
`, [templateId]);
|
||||
|
||||
return {
|
||||
...template,
|
||||
shifts: shiftSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
dayOfWeek: slot.day_of_week,
|
||||
timeRange: {
|
||||
id: slot.time_slot_id,
|
||||
name: slot.time_range_name,
|
||||
startTime: slot.time_range_start,
|
||||
endTime: slot.time_range_end
|
||||
},
|
||||
requiredEmployees: slot.required_employees,
|
||||
color: slot.color
|
||||
})),
|
||||
timeSlots: timeSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
startTime: slot.start_time,
|
||||
endTime: slot.end_time,
|
||||
description: slot.description
|
||||
}))
|
||||
};
|
||||
}
|
||||
@@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS shift_templates (
|
||||
CREATE TABLE IF NOT EXISTS template_shifts (
|
||||
id TEXT PRIMARY KEY,
|
||||
template_id TEXT NOT NULL,
|
||||
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 1 AND day_of_week <= 5),
|
||||
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 1 AND day_of_week <= 7),
|
||||
name TEXT NOT NULL,
|
||||
start_time TEXT NOT NULL,
|
||||
end_time TEXT NOT NULL,
|
||||
@@ -51,6 +51,7 @@ CREATE TABLE IF NOT EXISTS shift_plans (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assigned_shifts (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
shift_plan_id TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
start_time TEXT NOT NULL,
|
||||
@@ -64,9 +65,9 @@ CREATE TABLE IF NOT EXISTS assigned_shifts (
|
||||
CREATE TABLE IF NOT EXISTS employee_availabilities (
|
||||
id TEXT PRIMARY KEY,
|
||||
employee_id TEXT NOT NULL,
|
||||
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 1 AND day_of_week <= 5),
|
||||
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 0 AND day_of_week <= 6),
|
||||
start_time TEXT NOT NULL,
|
||||
end_time TEXT NOT NULL,
|
||||
is_available BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (employee_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
);
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
// backend/src/models/Shift.ts
|
||||
export interface ShiftTemplate {
|
||||
export interface Shift {
|
||||
id: string;
|
||||
name: string;
|
||||
shifts: TemplateShift[];
|
||||
description?: string;
|
||||
isDefault: boolean;
|
||||
createdBy: string;
|
||||
createdAt: string;
|
||||
shifts: ShiftSlot[];
|
||||
}
|
||||
|
||||
export interface TemplateShift {
|
||||
dayOfWeek: number; // 0-6
|
||||
export interface ShiftSlot {
|
||||
id: string;
|
||||
shiftId: string;
|
||||
dayOfWeek: number;
|
||||
name: string;
|
||||
startTime: string; // "08:00"
|
||||
endTime: string; // "12:00"
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
requiredEmployees: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface CreateShiftRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
isDefault: boolean;
|
||||
shifts: Omit<ShiftSlot, 'id' | 'shiftId'>[];
|
||||
}
|
||||
|
||||
export interface UpdateShiftSlotRequest {
|
||||
name?: string;
|
||||
description?: string;
|
||||
isDefault?: boolean;
|
||||
shifts?: Omit<ShiftSlot, 'id' | 'shiftId'>[];
|
||||
}
|
||||
|
||||
export interface ShiftPlan {
|
||||
@@ -32,4 +52,4 @@ export interface AssignedShift {
|
||||
endTime: string;
|
||||
requiredEmployees: number;
|
||||
assignedEmployees: string[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
// backend/src/models/ShiftTemplate.ts
|
||||
export interface ShiftTemplate {
|
||||
export interface TemplateShift {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
isDefault: boolean;
|
||||
createdBy: string;
|
||||
createdAt: string;
|
||||
shifts: TemplateShift[];
|
||||
shifts: TemplateShiftSlot[];
|
||||
}
|
||||
|
||||
export interface TemplateShift {
|
||||
export interface TemplateShiftSlot {
|
||||
id: string;
|
||||
templateId: string;
|
||||
dayOfWeek: number;
|
||||
name: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
timeRange: TemplateShiftTimeRange;
|
||||
requiredEmployees: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface TemplateShiftTimeRange {
|
||||
id: string;
|
||||
name: string; // e.g., "Frühschicht", "Spätschicht"
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
}
|
||||
|
||||
export interface CreateShiftTemplateRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
isDefault: boolean;
|
||||
shifts: Omit<TemplateShift, 'id' | 'templateId'>[];
|
||||
timeSlots: Omit<TemplateShiftTimeRange, 'id'>[];
|
||||
}
|
||||
|
||||
export interface UpdateShiftTemplateRequest {
|
||||
@@ -32,4 +37,5 @@ export interface UpdateShiftTemplateRequest {
|
||||
description?: string;
|
||||
isDefault?: boolean;
|
||||
shifts?: Omit<TemplateShift, 'id' | 'templateId'>[];
|
||||
timeSlots?: Omit<TemplateShiftTimeRange, 'id'>[];
|
||||
}
|
||||
@@ -1,41 +1,103 @@
|
||||
import { db } from '../services/databaseService.js';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { readFile, readdir } from 'fs/promises';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Helper function to ensure migrations are tracked
|
||||
async function ensureMigrationTable() {
|
||||
await db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS applied_migrations (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
// Helper function to check if a migration has been applied
|
||||
async function isMigrationApplied(migrationName: string): Promise<boolean> {
|
||||
const result = await db.get<{ count: number }>(
|
||||
'SELECT COUNT(*) as count FROM applied_migrations WHERE name = ?',
|
||||
[migrationName]
|
||||
);
|
||||
return (result?.count ?? 0) > 0;
|
||||
}
|
||||
|
||||
// Helper function to mark a migration as applied
|
||||
async function markMigrationAsApplied(migrationName: string) {
|
||||
await db.run(
|
||||
'INSERT INTO applied_migrations (id, name) VALUES (?, ?)',
|
||||
[crypto.randomUUID(), migrationName]
|
||||
);
|
||||
}
|
||||
|
||||
export async function applyMigration() {
|
||||
try {
|
||||
console.log('📦 Starting database migration...');
|
||||
|
||||
// Read the migration file
|
||||
const migrationPath = join(__dirname, '../database/migrations/002_add_employee_fields.sql');
|
||||
const migrationSQL = await readFile(migrationPath, 'utf-8');
|
||||
// Ensure migration tracking table exists
|
||||
await ensureMigrationTable();
|
||||
|
||||
// Split into individual statements
|
||||
const statements = migrationSQL
|
||||
.split(';')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0);
|
||||
// Get all migration files
|
||||
const migrationsDir = join(__dirname, '../database/migrations');
|
||||
const files = await readdir(migrationsDir);
|
||||
|
||||
// Execute each statement
|
||||
for (const statement of statements) {
|
||||
// Sort files to ensure consistent order
|
||||
const migrationFiles = files
|
||||
.filter(f => f.endsWith('.sql'))
|
||||
.sort();
|
||||
|
||||
// Process each migration file
|
||||
for (const migrationFile of migrationFiles) {
|
||||
if (await isMigrationApplied(migrationFile)) {
|
||||
console.log(`ℹ️ Migration ${migrationFile} already applied, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`📄 Applying migration: ${migrationFile}`);
|
||||
const migrationPath = join(migrationsDir, migrationFile);
|
||||
const migrationSQL = await readFile(migrationPath, 'utf-8');
|
||||
|
||||
// Split into individual statements
|
||||
const statements = migrationSQL
|
||||
.split(';')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0);
|
||||
|
||||
// Start transaction for this migration
|
||||
await db.run('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
await db.exec(statement);
|
||||
console.log('✅ Executed:', statement.slice(0, 50) + '...');
|
||||
} catch (error) {
|
||||
const err = error as { code: string; message: string };
|
||||
if (err.code === 'SQLITE_ERROR' && err.message.includes('duplicate column name')) {
|
||||
console.log('ℹ️ Column already exists, skipping...');
|
||||
continue;
|
||||
// Execute each statement
|
||||
for (const statement of statements) {
|
||||
try {
|
||||
await db.exec(statement);
|
||||
console.log('✅ Executed:', statement.slice(0, 50) + '...');
|
||||
} catch (error) {
|
||||
const err = error as { code: string; message: string };
|
||||
if (err.code === 'SQLITE_ERROR' && err.message.includes('duplicate column name')) {
|
||||
console.log('ℹ️ Column already exists, skipping...');
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark migration as applied
|
||||
await markMigrationAsApplied(migrationFile);
|
||||
await db.run('COMMIT');
|
||||
console.log(`✅ Migration ${migrationFile} applied successfully`);
|
||||
|
||||
} catch (error) {
|
||||
await db.run('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Migration completed successfully');
|
||||
console.log('✅ All migrations completed successfully');
|
||||
} catch (error) {
|
||||
console.error('❌ Migration failed:', error);
|
||||
throw error;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { db } from '../services/databaseService.js';
|
||||
import { ShiftTemplate } from '../models/ShiftTemplate.js';
|
||||
import { TemplateShift } from '../models/ShiftTemplate.js';
|
||||
|
||||
async function checkTemplates() {
|
||||
try {
|
||||
const templates = await db.all<ShiftTemplate>(
|
||||
const templates = await db.all<TemplateShift>(
|
||||
`SELECT st.*, u.name as created_by_name
|
||||
FROM shift_templates st
|
||||
LEFT JOIN users u ON st.created_by = u.id`
|
||||
|
||||
@@ -55,7 +55,7 @@ export async function setupDefaultTemplate(): Promise<void> {
|
||||
console.log('Standard-Vorlage erstellt:', templateId);
|
||||
|
||||
// Vormittagsschicht Mo-Do
|
||||
for (let day = 1; day <= 4; day++) {
|
||||
for (let day = 1; day <= 5; day++) {
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
@@ -63,7 +63,7 @@ export async function setupDefaultTemplate(): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('Vormittagsschichten Mo-Do erstellt');
|
||||
console.log('Vormittagsschichten Mo-Fr erstellt');
|
||||
|
||||
// Nachmittagsschicht Mo-Do
|
||||
for (let day = 1; day <= 4; day++) {
|
||||
@@ -76,15 +76,6 @@ export async function setupDefaultTemplate(): Promise<void> {
|
||||
|
||||
console.log('Nachmittagsschichten Mo-Do erstellt');
|
||||
|
||||
// Freitag nur Vormittagsschicht
|
||||
await db.run(
|
||||
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[uuidv4(), templateId, 5, 'Vormittagsschicht', '08:00', '12:00', 1]
|
||||
);
|
||||
|
||||
console.log('Freitag Vormittagsschicht erstellt');
|
||||
|
||||
await db.run('COMMIT');
|
||||
console.log('Standard-Vorlage erfolgreich initialisiert');
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user