mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
frontend with ony errors
This commit is contained in:
@@ -10,93 +10,79 @@ import {
|
||||
import { AuthRequest } from '../middleware/auth.js';
|
||||
import { createPlanFromPreset, TEMPLATE_PRESETS } from '../models/defaults/shiftPlanDefaults.js';
|
||||
|
||||
async function getPlanWithDetails(planId: string) {
|
||||
const plan = await db.get<any>(`
|
||||
SELECT sp.*, e.name as created_by_name
|
||||
FROM shift_plans sp
|
||||
LEFT JOIN employees e ON sp.created_by = e.id
|
||||
WHERE sp.id = ?
|
||||
`, [planId]);
|
||||
|
||||
if (!plan) return null;
|
||||
|
||||
const [timeSlots, shifts] = await Promise.all([
|
||||
db.all<any>(`SELECT * FROM time_slots WHERE plan_id = ? ORDER BY start_time`, [planId]),
|
||||
db.all<any>(`
|
||||
SELECT s.*, ts.name as time_slot_name, ts.start_time, ts.end_time
|
||||
FROM shifts s
|
||||
LEFT JOIN time_slots ts ON s.time_slot_id = ts.id
|
||||
WHERE s.plan_id = ?
|
||||
ORDER BY s.day_of_week, ts.start_time
|
||||
`, [planId])
|
||||
]);
|
||||
|
||||
return {
|
||||
plan: {
|
||||
...plan,
|
||||
isTemplate: plan.is_template === 1,
|
||||
startDate: plan.start_date,
|
||||
endDate: plan.end_date,
|
||||
createdBy: plan.created_by,
|
||||
createdAt: plan.created_at,
|
||||
},
|
||||
timeSlots: timeSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
planId: slot.plan_id,
|
||||
name: slot.name,
|
||||
startTime: slot.start_time,
|
||||
endTime: slot.end_time,
|
||||
description: slot.description
|
||||
})),
|
||||
shifts: shifts.map(shift => ({
|
||||
id: shift.id,
|
||||
planId: shift.plan_id,
|
||||
timeSlotId: shift.time_slot_id,
|
||||
dayOfWeek: shift.day_of_week,
|
||||
requiredEmployees: shift.required_employees,
|
||||
color: shift.color,
|
||||
timeSlot: {
|
||||
id: shift.time_slot_id,
|
||||
name: shift.time_slot_name,
|
||||
startTime: shift.start_time,
|
||||
endTime: shift.end_time
|
||||
}
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
// Simplified getShiftPlans using shared helper
|
||||
export const getShiftPlans = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
console.log('🔍 Lade Schichtpläne...');
|
||||
|
||||
const shiftPlans = await db.all<any>(`
|
||||
const plans = await db.all<any>(`
|
||||
SELECT sp.*, e.name as created_by_name
|
||||
FROM shift_plans sp
|
||||
LEFT JOIN employees e ON sp.created_by = e.id
|
||||
ORDER BY sp.created_at DESC
|
||||
`);
|
||||
|
||||
console.log(`✅ ${shiftPlans.length} Schichtpläne gefunden:`, shiftPlans.map(p => p.name));
|
||||
|
||||
// Für jeden Plan die Schichten und Zeit-Slots laden
|
||||
const plansWithDetails = await Promise.all(
|
||||
shiftPlans.map(async (plan) => {
|
||||
// Lade Zeit-Slots
|
||||
const timeSlots = await db.all<any>(`
|
||||
SELECT * FROM time_slots
|
||||
WHERE plan_id = ?
|
||||
ORDER BY start_time
|
||||
`, [plan.id]);
|
||||
|
||||
// Lade Schichten
|
||||
const shifts = await db.all<any>(`
|
||||
SELECT s.*, ts.name as time_slot_name, ts.start_time, ts.end_time
|
||||
FROM shifts s
|
||||
LEFT JOIN time_slots ts ON s.time_slot_id = ts.id
|
||||
WHERE s.plan_id = ?
|
||||
ORDER BY s.day_of_week, ts.start_time
|
||||
`, [plan.id]);
|
||||
|
||||
// Lade geplante Schichten (nur für nicht-Template Pläne)
|
||||
let scheduledShifts = [];
|
||||
if (!plan.is_template) {
|
||||
scheduledShifts = await db.all<any>(`
|
||||
SELECT ss.*, ts.name as time_slot_name
|
||||
FROM scheduled_shifts ss
|
||||
LEFT JOIN time_slots ts ON ss.time_slot_id = ts.id
|
||||
WHERE ss.plan_id = ?
|
||||
ORDER BY ss.date, ts.start_time
|
||||
`, [plan.id]);
|
||||
}
|
||||
|
||||
return {
|
||||
...plan,
|
||||
isTemplate: plan.is_template === 1,
|
||||
startDate: plan.start_date,
|
||||
endDate: plan.end_date,
|
||||
createdBy: plan.created_by,
|
||||
createdAt: plan.created_at,
|
||||
timeSlots: timeSlots.map(slot => ({
|
||||
id: slot.id,
|
||||
planId: slot.plan_id,
|
||||
name: slot.name,
|
||||
startTime: slot.start_time,
|
||||
endTime: slot.end_time,
|
||||
description: slot.description
|
||||
})),
|
||||
shifts: shifts.map(shift => ({
|
||||
id: shift.id,
|
||||
planId: shift.plan_id,
|
||||
timeSlotId: shift.time_slot_id,
|
||||
dayOfWeek: shift.day_of_week,
|
||||
requiredEmployees: shift.required_employees,
|
||||
color: shift.color,
|
||||
timeSlot: {
|
||||
id: shift.time_slot_id,
|
||||
name: shift.time_slot_name,
|
||||
startTime: shift.start_time,
|
||||
endTime: shift.end_time
|
||||
}
|
||||
})),
|
||||
scheduledShifts: scheduledShifts.map(shift => ({
|
||||
id: shift.id,
|
||||
planId: shift.plan_id,
|
||||
date: shift.date,
|
||||
timeSlotId: shift.time_slot_id,
|
||||
requiredEmployees: shift.required_employees,
|
||||
assignedEmployees: JSON.parse(shift.assigned_employees || '[]'),
|
||||
timeSlotName: shift.time_slot_name
|
||||
}))
|
||||
};
|
||||
plans.map(async (plan) => {
|
||||
const details = await getPlanWithDetails(plan.id);
|
||||
return details ? { ...details.plan, timeSlots: details.timeSlots, shifts: details.shifts } : null;
|
||||
})
|
||||
);
|
||||
|
||||
res.json(plansWithDetails);
|
||||
res.json(plansWithDetails.filter(Boolean));
|
||||
} catch (error) {
|
||||
console.error('Error fetching shift plans:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
|
||||
@@ -19,6 +19,36 @@ export const MANAGER_DEFAULTS = {
|
||||
isActive: true
|
||||
};
|
||||
|
||||
export const EMPLOYEE_TYPE_CONFIG = {
|
||||
manager: {
|
||||
value: 'manager' as const,
|
||||
label: 'Chef/Administrator',
|
||||
color: '#e74c3c',
|
||||
independent: true,
|
||||
description: 'Vollzugriff auf alle Funktionen und Mitarbeiterverwaltung'
|
||||
},
|
||||
experienced: {
|
||||
value: 'experienced' as const,
|
||||
label: 'Erfahren',
|
||||
color: '#3498db',
|
||||
independent: true,
|
||||
description: 'Langjährige Erfahrung, kann komplexe Aufgaben übernehmen'
|
||||
},
|
||||
trainee: {
|
||||
value: 'trainee' as const,
|
||||
label: 'Neuling',
|
||||
color: '#27ae60',
|
||||
independent: false,
|
||||
description: 'Benötigt Einarbeitung und Unterstützung'
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const ROLE_CONFIG = [
|
||||
{ value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' },
|
||||
{ value: 'instandhalter', label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' },
|
||||
{ value: 'admin', label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' }
|
||||
] as const;
|
||||
|
||||
// Contract type descriptions
|
||||
export const CONTRACT_TYPE_DESCRIPTIONS = {
|
||||
small: '1 Schicht pro Woche',
|
||||
@@ -26,13 +56,6 @@ export const CONTRACT_TYPE_DESCRIPTIONS = {
|
||||
manager: 'Kein Vertragslimit - Immer MO und DI verfügbar'
|
||||
} as const;
|
||||
|
||||
// Employee type descriptions
|
||||
export const EMPLOYEE_TYPE_DESCRIPTIONS = {
|
||||
manager: 'Chef - Immer MO und DI in beiden Schichten, kann eigene Schichten festlegen',
|
||||
trainee: 'Neuling - Darf nicht alleine sein, benötigt erfahrene Begleitung',
|
||||
experienced: 'Erfahren - Kann alleine arbeiten (wenn freigegeben)'
|
||||
} as const;
|
||||
|
||||
// Availability preference descriptions
|
||||
export const AVAILABILITY_PREFERENCES = {
|
||||
1: { label: 'Bevorzugt', color: '#10b981', description: 'Möchte diese Schicht arbeiten' },
|
||||
|
||||
@@ -1,128 +1,40 @@
|
||||
// backend/src/models/helpers/employeeHelpers.ts
|
||||
import { Employee, CreateEmployeeRequest, EmployeeAvailability, ManagerAvailability } from '../Employee.js';
|
||||
import { Employee, CreateEmployeeRequest, EmployeeAvailability } from '../Employee.js';
|
||||
|
||||
// Validation helpers
|
||||
export function validateEmployee(employee: CreateEmployeeRequest): string[] {
|
||||
// Simplified validation - use schema validation instead
|
||||
export function validateEmployeeData(employee: CreateEmployeeRequest): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!employee.email || !employee.email.includes('@')) {
|
||||
if (!employee.email?.includes('@')) {
|
||||
errors.push('Valid email is required');
|
||||
}
|
||||
|
||||
if (!employee.password || employee.password.length < 6) {
|
||||
if (employee.password?.length < 6) {
|
||||
errors.push('Password must be at least 6 characters long');
|
||||
}
|
||||
|
||||
if (!employee.name || employee.name.trim().length < 2) {
|
||||
if (!employee.name?.trim() || employee.name.trim().length < 2) {
|
||||
errors.push('Name is required and must be at least 2 characters long');
|
||||
}
|
||||
|
||||
if (!employee.contractType) {
|
||||
errors.push('Contract type is required');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function validateAvailability(availability: Omit<EmployeeAvailability, 'id' | 'employeeId'>): string[] {
|
||||
const errors: string[] = [];
|
||||
// Simplified business logic helpers
|
||||
export const isManager = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'manager';
|
||||
|
||||
if (availability.dayOfWeek < 1 || availability.dayOfWeek > 7) {
|
||||
errors.push('Day of week must be between 1 and 7');
|
||||
}
|
||||
export const isTrainee = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'trainee';
|
||||
|
||||
if (![1, 2, 3].includes(availability.preferenceLevel)) {
|
||||
errors.push('Preference level must be 1, 2, or 3');
|
||||
}
|
||||
export const isExperienced = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'experienced';
|
||||
|
||||
if (!availability.timeSlotId) {
|
||||
errors.push('Time slot ID is required');
|
||||
}
|
||||
export const isAdmin = (employee: Employee): boolean =>
|
||||
employee.role === 'admin';
|
||||
|
||||
if (!availability.planId) {
|
||||
errors.push('Plan ID is required');
|
||||
}
|
||||
export const canEmployeeWorkAlone = (employee: Employee): boolean =>
|
||||
employee.canWorkAlone && isExperienced(employee);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// Employee type guards
|
||||
export function isManager(employee: Employee): boolean {
|
||||
return employee.employeeType === 'manager';
|
||||
}
|
||||
|
||||
export function isTrainee(employee: Employee): boolean {
|
||||
return employee.employeeType === 'trainee';
|
||||
}
|
||||
|
||||
export function isExperienced(employee: Employee): boolean {
|
||||
return employee.employeeType === 'experienced';
|
||||
}
|
||||
|
||||
export function isAdmin(employee: Employee): boolean {
|
||||
return employee.role === 'admin';
|
||||
}
|
||||
|
||||
// Business logic helpers
|
||||
export function canEmployeeWorkAlone(employee: Employee): boolean {
|
||||
return employee.canWorkAlone && employee.employeeType === 'experienced';
|
||||
}
|
||||
|
||||
export function getEmployeeWorkHours(employee: Employee): number {
|
||||
// Manager: no contract limit, others: small=1, large=2 shifts per week
|
||||
return isManager(employee) ? 999 : (employee.contractType === 'small' ? 1 : 2);
|
||||
}
|
||||
|
||||
export function requiresAvailabilityPreference(employee: Employee): boolean {
|
||||
// Only non-managers use the preference system
|
||||
return !isManager(employee);
|
||||
}
|
||||
|
||||
export function canSetOwnAvailability(employee: Employee): boolean {
|
||||
// Manager can set their own specific shift assignments
|
||||
return isManager(employee);
|
||||
}
|
||||
|
||||
// Manager availability helpers
|
||||
export function isManagerAvailable(
|
||||
managerAssignments: ManagerAvailability[],
|
||||
dayOfWeek: number,
|
||||
timeSlotId: string
|
||||
): boolean {
|
||||
const assignment = managerAssignments.find(assignment =>
|
||||
assignment.dayOfWeek === dayOfWeek &&
|
||||
assignment.timeSlotId === timeSlotId
|
||||
);
|
||||
|
||||
return assignment ? assignment.isAvailable : false;
|
||||
}
|
||||
|
||||
export function getManagerAvailableShifts(managerAssignments: ManagerAvailability[]): ManagerAvailability[] {
|
||||
return managerAssignments.filter(assignment => assignment.isAvailable);
|
||||
}
|
||||
|
||||
export function updateManagerAvailability(
|
||||
assignments: ManagerAvailability[],
|
||||
dayOfWeek: number,
|
||||
timeSlotId: string,
|
||||
isAvailable: boolean
|
||||
): ManagerAvailability[] {
|
||||
return assignments.map(assignment =>
|
||||
assignment.dayOfWeek === dayOfWeek && assignment.timeSlotId === timeSlotId
|
||||
? { ...assignment, isAvailable }
|
||||
: assignment
|
||||
);
|
||||
}
|
||||
|
||||
export function validateManagerMinimumAvailability(managerAssignments: ManagerAvailability[]): boolean {
|
||||
const requiredShifts = [
|
||||
{ dayOfWeek: 1, timeSlotId: 'morning' },
|
||||
{ dayOfWeek: 1, timeSlotId: 'afternoon' },
|
||||
{ dayOfWeek: 2, timeSlotId: 'morning' },
|
||||
{ dayOfWeek: 2, timeSlotId: 'afternoon' }
|
||||
];
|
||||
|
||||
return requiredShifts.every(required =>
|
||||
isManagerAvailable(managerAssignments, required.dayOfWeek, required.timeSlotId)
|
||||
);
|
||||
}
|
||||
export const getEmployeeWorkHours = (employee: Employee): number =>
|
||||
isManager(employee) ? 999 : (employee.contractType === 'small' ? 1 : 2);
|
||||
|
||||
Reference in New Issue
Block a user