mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 15:05:45 +01:00
updated every file for database changes; starting scheduling debugging
This commit is contained in:
@@ -4,10 +4,11 @@ export interface Employee {
|
||||
email: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||
contractType: 'small' | 'large';
|
||||
employeeType: 'manager' | 'personell' | 'apprentice' | 'guest';
|
||||
contractType?: 'small' | 'large' | 'flexible';
|
||||
canWorkAlone: boolean;
|
||||
isActive: boolean;
|
||||
isTrainee: boolean;
|
||||
createdAt: string;
|
||||
lastLogin?: string | null;
|
||||
roles?: string[];
|
||||
@@ -17,20 +18,22 @@ export interface CreateEmployeeRequest {
|
||||
password: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
roles: string[];
|
||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||
contractType: 'small' | 'large';
|
||||
roles?: string[];
|
||||
employeeType: 'manager' | 'personell' | 'apprentice' | 'guest';
|
||||
contractType?: 'small' | 'large' | 'flexible';
|
||||
canWorkAlone: boolean;
|
||||
isTrainee?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateEmployeeRequest {
|
||||
firstname?: string;
|
||||
lastname?: string;
|
||||
roles?: string[];
|
||||
employeeType?: 'manager' | 'trainee' | 'experienced';
|
||||
contractType?: 'small' | 'large';
|
||||
employeeType?: 'manager' | 'personell' | 'apprentice' | 'guest';
|
||||
contractType?: 'small' | 'large' | 'flexible';
|
||||
canWorkAlone?: boolean;
|
||||
isActive?: boolean;
|
||||
isTrainee?: boolean;
|
||||
}
|
||||
|
||||
export interface EmployeeWithPassword extends Employee {
|
||||
@@ -41,7 +44,7 @@ export interface EmployeeAvailability {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
planId: string;
|
||||
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
|
||||
shiftId: string;
|
||||
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
||||
notes?: string;
|
||||
}
|
||||
@@ -83,4 +86,11 @@ export interface Role {
|
||||
export interface EmployeeRole {
|
||||
employeeId: string;
|
||||
role: 'admin' | 'user' | 'maintenance';
|
||||
}
|
||||
|
||||
// Employee type configuration
|
||||
export interface EmployeeType {
|
||||
type: 'manager' | 'personell' | 'apprentice' | 'guest';
|
||||
category: 'internal' | 'external';
|
||||
has_contract_type: boolean;
|
||||
}
|
||||
@@ -4,19 +4,41 @@ import { EmployeeAvailability, ManagerAvailability } from '../Employee.js';
|
||||
// Default employee data for quick creation
|
||||
export const EMPLOYEE_DEFAULTS = {
|
||||
role: 'user' as const,
|
||||
employeeType: 'experienced' as const,
|
||||
employeeType: 'personell' as const,
|
||||
contractType: 'small' as const,
|
||||
canWorkAlone: false,
|
||||
isActive: true
|
||||
isActive: true,
|
||||
isTrainee: false
|
||||
};
|
||||
|
||||
// Manager-specific defaults
|
||||
export const MANAGER_DEFAULTS = {
|
||||
role: 'admin' as const,
|
||||
employeeType: 'manager' as const,
|
||||
contractType: 'large' as const,
|
||||
contractType: 'flexible' as const,
|
||||
canWorkAlone: true,
|
||||
isActive: true
|
||||
isActive: true,
|
||||
isTrainee: false
|
||||
};
|
||||
|
||||
// Apprentice defaults
|
||||
export const APPRENTICE_DEFAULTS = {
|
||||
role: 'user' as const,
|
||||
employeeType: 'apprentice' as const,
|
||||
contractType: 'flexible' as const,
|
||||
canWorkAlone: false,
|
||||
isActive: true,
|
||||
isTrainee: false
|
||||
};
|
||||
|
||||
// Guest defaults
|
||||
export const GUEST_DEFAULTS = {
|
||||
role: 'user' as const,
|
||||
employeeType: 'guest' as const,
|
||||
contractType: undefined,
|
||||
canWorkAlone: false,
|
||||
isActive: true,
|
||||
isTrainee: false
|
||||
};
|
||||
|
||||
export const EMPLOYEE_TYPE_CONFIG = {
|
||||
@@ -24,22 +46,37 @@ export const EMPLOYEE_TYPE_CONFIG = {
|
||||
value: 'manager' as const,
|
||||
label: 'Chef/Administrator',
|
||||
color: '#e74c3c',
|
||||
category: 'internal' as const,
|
||||
hasContractType: true,
|
||||
independent: true,
|
||||
description: 'Vollzugriff auf alle Funktionen und Mitarbeiterverwaltung'
|
||||
},
|
||||
experienced: {
|
||||
value: 'experienced' as const,
|
||||
label: 'Erfahren',
|
||||
personell: {
|
||||
value: 'personell' as const,
|
||||
label: 'Personal',
|
||||
color: '#3498db',
|
||||
category: 'internal' as const,
|
||||
hasContractType: true,
|
||||
independent: true,
|
||||
description: 'Langjährige Erfahrung, kann komplexe Aufgaben übernehmen'
|
||||
description: 'Reguläre Mitarbeiter mit Vertrag'
|
||||
},
|
||||
trainee: {
|
||||
value: 'trainee' as const,
|
||||
label: 'Neuling',
|
||||
color: '#27ae60',
|
||||
apprentice: {
|
||||
value: 'apprentice' as const,
|
||||
label: 'Auszubildender',
|
||||
color: '#9b59b6',
|
||||
category: 'internal' as const,
|
||||
hasContractType: true,
|
||||
independent: false,
|
||||
description: 'Benötigt Einarbeitung und Unterstützung'
|
||||
description: 'Auszubildende mit flexiblem Vertrag'
|
||||
},
|
||||
guest: {
|
||||
value: 'guest' as const,
|
||||
label: 'Gast',
|
||||
color: '#95a5a6',
|
||||
category: 'external' as const,
|
||||
hasContractType: false,
|
||||
independent: false,
|
||||
description: 'Externe Mitarbeiter ohne Vertrag'
|
||||
}
|
||||
} as const;
|
||||
|
||||
@@ -53,9 +90,10 @@ export const ROLE_CONFIG = [
|
||||
export const CONTRACT_TYPE_DESCRIPTIONS = {
|
||||
small: '1 Schicht pro Woche',
|
||||
large: '2 Schichten pro Woche',
|
||||
manager: 'Kein Vertragslimit - Immer MO und DI verfügbar'
|
||||
flexible: 'Flexible Arbeitszeiten'
|
||||
} as const;
|
||||
|
||||
|
||||
// Availability preference descriptions
|
||||
export const AVAILABILITY_PREFERENCES = {
|
||||
1: { label: 'Bevorzugt', color: '#10b981', description: 'Möchte diese Schicht arbeiten' },
|
||||
@@ -104,4 +142,17 @@ export function createManagerDefaultSchedule(managerId: string, planId: string,
|
||||
}
|
||||
|
||||
return assignments;
|
||||
}
|
||||
|
||||
export function getDefaultsByEmployeeType(employeeType: string) {
|
||||
switch (employeeType) {
|
||||
case 'manager':
|
||||
return MANAGER_DEFAULTS;
|
||||
case 'apprentice':
|
||||
return APPRENTICE_DEFAULTS;
|
||||
case 'guest':
|
||||
return GUEST_DEFAULTS;
|
||||
default:
|
||||
return EMPLOYEE_DEFAULTS;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
// backend/src/models/helpers/employeeHelpers.ts
|
||||
import { Employee, CreateEmployeeRequest, EmployeeAvailability } from '../Employee.js';
|
||||
|
||||
// Email generation function (same as in controllers)
|
||||
// Email generation function
|
||||
function generateEmail(firstname: string, lastname: string): string {
|
||||
// Convert German umlauts to their expanded forms
|
||||
const convertUmlauts = (str: string): string => {
|
||||
return str
|
||||
.toLowerCase()
|
||||
@@ -13,19 +12,16 @@ function generateEmail(firstname: string, lastname: string): string {
|
||||
.replace(/ß/g, 'ss');
|
||||
};
|
||||
|
||||
// Remove any remaining special characters and convert to lowercase
|
||||
const cleanFirstname = convertUmlauts(firstname).replace(/[^a-z0-9]/g, '');
|
||||
const cleanLastname = convertUmlauts(lastname).replace(/[^a-z0-9]/g, '');
|
||||
|
||||
return `${cleanFirstname}.${cleanLastname}@sp.de`;
|
||||
}
|
||||
|
||||
// UPDATED: Validation for new employee model
|
||||
// UPDATED: Validation for new employee model with employee types
|
||||
export function validateEmployeeData(employee: CreateEmployeeRequest): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Email is now auto-generated, so no email validation needed
|
||||
|
||||
if (employee.password?.length < 6) {
|
||||
errors.push('Password must be at least 6 characters long');
|
||||
}
|
||||
@@ -38,24 +34,70 @@ export function validateEmployeeData(employee: CreateEmployeeRequest): string[]
|
||||
errors.push('Last name is required and must be at least 2 characters long');
|
||||
}
|
||||
|
||||
// Validate employee type
|
||||
const validEmployeeTypes = ['manager', 'personell', 'apprentice', 'guest'];
|
||||
if (!employee.employeeType || !validEmployeeTypes.includes(employee.employeeType)) {
|
||||
errors.push(`Employee type must be one of: ${validEmployeeTypes.join(', ')}`);
|
||||
}
|
||||
|
||||
// Validate contract type based on employee type
|
||||
if (employee.employeeType !== 'guest') {
|
||||
// Internal types require contract type
|
||||
if (!employee.contractType) {
|
||||
errors.push(`Contract type is required for employee type: ${employee.employeeType}`);
|
||||
} else {
|
||||
const validContractTypes = ['small', 'large', 'flexible'];
|
||||
if (!validContractTypes.includes(employee.contractType)) {
|
||||
errors.push(`Contract type must be one of: ${validContractTypes.join(', ')}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// External types (guest) should not have contract type
|
||||
if (employee.contractType) {
|
||||
errors.push('Contract type is not allowed for guest employees');
|
||||
}
|
||||
}
|
||||
|
||||
// Validate isTrainee - only applicable for personell type
|
||||
if (employee.isTrainee && employee.employeeType !== 'personell') {
|
||||
errors.push('isTrainee is only allowed for personell employee type');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// Generate email for employee (new helper function)
|
||||
// Generate email for employee
|
||||
export function generateEmployeeEmail(firstname: string, lastname: string): string {
|
||||
return generateEmail(firstname, lastname);
|
||||
}
|
||||
|
||||
// Simplified business logic helpers
|
||||
// UPDATED: Business logic helpers for new employee types
|
||||
export const isManager = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'manager';
|
||||
|
||||
export const isPersonell = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'personell';
|
||||
|
||||
export const isApprentice = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'apprentice';
|
||||
|
||||
export const isGuest = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'guest';
|
||||
|
||||
export const isInternal = (employee: Employee): boolean =>
|
||||
['manager', 'personell', 'apprentice'].includes(employee.employeeType);
|
||||
|
||||
export const isExternal = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'guest';
|
||||
|
||||
// UPDATED: Trainee logic - now based on isTrainee field for personell type
|
||||
export const isTrainee = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'trainee';
|
||||
employee.employeeType === 'personell' && employee.isTrainee;
|
||||
|
||||
export const isExperienced = (employee: Employee): boolean =>
|
||||
employee.employeeType === 'experienced';
|
||||
employee.employeeType === 'personell' && !employee.isTrainee;
|
||||
|
||||
// Role-based helpers
|
||||
export const isAdmin = (employee: Employee): boolean =>
|
||||
employee.roles?.includes('admin') || false;
|
||||
|
||||
@@ -64,13 +106,12 @@ export const isMaintenance = (employee: Employee): boolean =>
|
||||
|
||||
export const isUser = (employee: Employee): boolean =>
|
||||
employee.roles?.includes('user') || false;
|
||||
|
||||
// UPDATED: Work alone permission - managers and experienced personell can work alone
|
||||
export const canEmployeeWorkAlone = (employee: Employee): boolean =>
|
||||
employee.canWorkAlone && isExperienced(employee);
|
||||
employee.canWorkAlone && (isManager(employee) || isExperienced(employee));
|
||||
|
||||
export const getEmployeeWorkHours = (employee: Employee): number =>
|
||||
isManager(employee) ? 999 : (employee.contractType === 'small' ? 1 : 2);
|
||||
|
||||
// New helper for full name display
|
||||
// Helper for full name display
|
||||
export const getFullName = (employee: { firstname: string; lastname: string }): string =>
|
||||
`${employee.firstname} ${employee.lastname}`;
|
||||
|
||||
@@ -91,4 +132,14 @@ export function validateAvailabilityData(availability: Omit<EmployeeAvailability
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
// UPDATED: Helper to get employee type category
|
||||
export const getEmployeeCategory = (employee: Employee): 'internal' | 'external' => {
|
||||
return isInternal(employee) ? 'internal' : 'external';
|
||||
};
|
||||
|
||||
// Helper to check if employee requires contract type
|
||||
export const requiresContractType = (employee: Employee): boolean => {
|
||||
return isInternal(employee);
|
||||
};
|
||||
@@ -7,10 +7,10 @@ export interface Availability {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
planId: string;
|
||||
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
|
||||
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
||||
shiftId: string;
|
||||
preferenceLevel: 1 | 2 | 3;
|
||||
notes?: string;
|
||||
// Optional convenience fields (can be joined from shifts and time_slots tables)
|
||||
// Optional convenience fields
|
||||
dayOfWeek?: number;
|
||||
timeSlotId?: string;
|
||||
timeSlotName?: string;
|
||||
@@ -30,7 +30,7 @@ export interface Constraint {
|
||||
maxHoursPerWeek?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
weight?: number; // For soft constraints
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
export interface ScheduleRequest {
|
||||
@@ -153,8 +153,9 @@ export interface AvailabilityWithDetails extends Availability {
|
||||
id: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||
employeeType: 'manager' | 'personell' | 'apprentice' | 'guest';
|
||||
canWorkAlone: boolean;
|
||||
isTrainee: boolean;
|
||||
};
|
||||
shift?: {
|
||||
dayOfWeek: number;
|
||||
|
||||
Reference in New Issue
Block a user