mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
changed email creation
This commit is contained in:
@@ -32,13 +32,19 @@ export interface JWTPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterRequest {
|
export interface RegisterRequest {
|
||||||
email: string;
|
// REMOVED: email - will be auto-generated
|
||||||
password: string;
|
password: string;
|
||||||
firstname: String;
|
firstname: string;
|
||||||
lastname: String;
|
lastname: string;
|
||||||
role?: string;
|
role?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateEmail(firstname: string, lastname: string): string {
|
||||||
|
const cleanFirstname = firstname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
const cleanLastname = lastname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
return `${cleanFirstname}.${cleanLastname}@sp.de`;
|
||||||
|
}
|
||||||
|
|
||||||
export const login = async (req: Request, res: Response) => {
|
export const login = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { email, password } = req.body as LoginRequest;
|
const { email, password } = req.body as LoginRequest;
|
||||||
@@ -162,16 +168,19 @@ export const validateToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
export const register = async (req: Request, res: Response) => {
|
export const register = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { email, password, firstname, lastname, role = 'user' } = req.body as RegisterRequest;
|
const { password, firstname, lastname, role = 'user' } = req.body as RegisterRequest;
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields - REMOVED email
|
||||||
if (!email || !password || !firstname || !lastname) {
|
if (!password || !firstname || !lastname) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'E-Mail, Passwort und Name sind erforderlich'
|
error: 'Password, firstname und lastname sind erforderlich'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if email already exists
|
// Generate email automatically
|
||||||
|
const email = generateEmail(firstname, lastname);
|
||||||
|
|
||||||
|
// Check if generated email already exists
|
||||||
const existingUser = await db.get<Employee>(
|
const existingUser = await db.get<Employee>(
|
||||||
'SELECT id FROM employees WHERE email = ?',
|
'SELECT id FROM employees WHERE email = ?',
|
||||||
[email]
|
[email]
|
||||||
@@ -179,14 +188,14 @@ export const register = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Ein Benutzer mit dieser E-Mail existiert bereits'
|
error: `Ein Benutzer mit der E-Mail ${email} existiert bereits`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash password
|
// Hash password and create user
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
// Insert user
|
// Insert user with generated email
|
||||||
const result = await db.run(
|
const result = await db.run(
|
||||||
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
|
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
@@ -199,7 +208,7 @@ export const register = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// Get created user
|
// Get created user
|
||||||
const newUser = await db.get<Employee>(
|
const newUser = await db.get<Employee>(
|
||||||
'SELECT id, email, name, role FROM employees WHERE id = ?',
|
'SELECT id, email, firstname, lastname, role FROM employees WHERE id = ?',
|
||||||
[result.lastID]
|
[result.lastID]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ import { db } from '../services/databaseService.js';
|
|||||||
import { AuthRequest } from '../middleware/auth.js';
|
import { AuthRequest } from '../middleware/auth.js';
|
||||||
import { CreateEmployeeRequest } from '../models/Employee.js';
|
import { CreateEmployeeRequest } from '../models/Employee.js';
|
||||||
|
|
||||||
|
function generateEmail(firstname: string, lastname: string): string {
|
||||||
|
// Remove special characters and convert to lowercase
|
||||||
|
const cleanFirstname = firstname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
const cleanLastname = lastname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
return `${cleanFirstname}.${cleanLastname}@sp.de`;
|
||||||
|
}
|
||||||
|
|
||||||
export const getEmployees = async (req: AuthRequest, res: Response): Promise<void> => {
|
export const getEmployees = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
console.log('🔍 Fetching employees - User:', req.user);
|
console.log('🔍 Fetching employees - User:', req.user);
|
||||||
@@ -76,7 +83,6 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
email,
|
|
||||||
password,
|
password,
|
||||||
firstname,
|
firstname,
|
||||||
lastname,
|
lastname,
|
||||||
@@ -86,31 +92,31 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
canWorkAlone
|
canWorkAlone
|
||||||
} = req.body as CreateEmployeeRequest;
|
} = req.body as CreateEmployeeRequest;
|
||||||
|
|
||||||
// Validierung
|
// Validation - REMOVED email check
|
||||||
if (!email || !password || !firstname || !lastname || !role || !employeeType || !contractType) {
|
if (!password || !firstname || !lastname || !role || !employeeType || !contractType) {
|
||||||
console.log('❌ Validation failed: Missing required fields');
|
console.log('❌ Validation failed: Missing required fields');
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
error: 'Email, password, name, role, employeeType und contractType sind erforderlich'
|
error: 'Password, firstname, lastname, role, employeeType und contractType sind erforderlich'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length < 6) {
|
// Generate email automatically
|
||||||
console.log('❌ Validation failed: Password too short');
|
const email = generateEmail(firstname, lastname);
|
||||||
res.status(400).json({ error: 'Password must be at least 6 characters long' });
|
console.log('📧 Generated email:', email);
|
||||||
|
|
||||||
|
// Check if generated email already exists
|
||||||
|
const existingUser = await db.get<any>('SELECT id FROM employees WHERE email = ? AND is_active = 1', [email]);
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
console.log('❌ Generated email already exists:', email);
|
||||||
|
res.status(409).json({
|
||||||
|
error: `Employee with email ${email} already exists. Please use different firstname/lastname.`
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if email already exists
|
// Hash password and create employee
|
||||||
const existingActiveUser = await db.get<any>('SELECT id FROM employees WHERE email = ? AND is_active = 1', [email]);
|
|
||||||
|
|
||||||
if (existingActiveUser) {
|
|
||||||
console.log('❌ Email exists for active user:', existingActiveUser);
|
|
||||||
res.status(409).json({ error: 'Email already exists' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash password
|
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
const employeeId = uuidv4();
|
const employeeId = uuidv4();
|
||||||
|
|
||||||
@@ -123,8 +129,8 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
employeeId,
|
employeeId,
|
||||||
email,
|
email,
|
||||||
hashedPassword,
|
hashedPassword,
|
||||||
firstname, // Changed from name
|
firstname,
|
||||||
lastname, // Added
|
lastname,
|
||||||
role,
|
role,
|
||||||
employeeType,
|
employeeType,
|
||||||
contractType,
|
contractType,
|
||||||
@@ -133,10 +139,10 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return created employee
|
// Return created employee with generated email
|
||||||
const newEmployee = await db.get<any>(`
|
const newEmployee = await db.get<any>(`
|
||||||
SELECT
|
SELECT
|
||||||
id, email, name, role, is_active as isActive,
|
id, email, firstname, lastname, role, is_active as isActive,
|
||||||
employee_type as employeeType,
|
employee_type as employeeType,
|
||||||
contract_type as contractType,
|
contract_type as contractType,
|
||||||
can_work_alone as canWorkAlone,
|
can_work_alone as canWorkAlone,
|
||||||
@@ -157,35 +163,58 @@ export const updateEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone } = req.body;
|
const { firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone } = req.body;
|
||||||
console.log('📝 Update Employee Request:', { id, name, role, isActive, employeeType, contractType, canWorkAlone });
|
|
||||||
|
|
||||||
// Check if employee exists
|
console.log('📝 Update Employee Request:', { id, firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone });
|
||||||
const existingEmployee = await db.get('SELECT * FROM employees WHERE id = ?', [id]);
|
|
||||||
|
// Check if employee exists and get current data
|
||||||
|
const existingEmployee = await db.get<any>('SELECT * FROM employees WHERE id = ?', [id]);
|
||||||
if (!existingEmployee) {
|
if (!existingEmployee) {
|
||||||
res.status(404).json({ error: 'Employee not found' });
|
res.status(404).json({ error: 'Employee not found' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update employee
|
// Generate new email if firstname or lastname changed
|
||||||
|
let email = existingEmployee.email;
|
||||||
|
if (firstname || lastname) {
|
||||||
|
const newFirstname = firstname || existingEmployee.firstname;
|
||||||
|
const newLastname = lastname || existingEmployee.lastname;
|
||||||
|
email = generateEmail(newFirstname, newLastname);
|
||||||
|
|
||||||
|
// Check if new email already exists (for another employee)
|
||||||
|
const emailExists = await db.get<any>(
|
||||||
|
'SELECT id FROM employees WHERE email = ? AND id != ? AND is_active = 1',
|
||||||
|
[email, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (emailExists) {
|
||||||
|
res.status(409).json({
|
||||||
|
error: `Cannot update name - email ${email} already exists for another employee`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update employee with potentially new email
|
||||||
await db.run(
|
await db.run(
|
||||||
`UPDATE employees
|
`UPDATE employees
|
||||||
SET firstname = COALESCE(?, firstname),
|
SET firstname = COALESCE(?, firstname),
|
||||||
lastname = COALESCE(?, lastname),
|
lastname = COALESCE(?, lastname),
|
||||||
|
email = ?,
|
||||||
role = COALESCE(?, role),
|
role = COALESCE(?, role),
|
||||||
is_active = COALESCE(?, is_active),
|
is_active = COALESCE(?, is_active),
|
||||||
employee_type = COALESCE(?, employee_type),
|
employee_type = COALESCE(?, employee_type),
|
||||||
contract_type = COALESCE(?, contract_type),
|
contract_type = COALESCE(?, contract_type),
|
||||||
can_work_alone = COALESCE(?, can_work_alone)
|
can_work_alone = COALESCE(?, can_work_alone)
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
[firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone, id]
|
[firstname, lastname, email, role, isActive, employeeType, contractType, canWorkAlone, id]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('✅ Employee updated successfully');
|
console.log('✅ Employee updated successfully with email:', email);
|
||||||
|
|
||||||
// Return updated employee
|
// Return updated employee
|
||||||
const updatedEmployee = await db.get<any>(`
|
const updatedEmployee = await db.get<any>(`
|
||||||
SELECT
|
SELECT
|
||||||
id, email, name, role, is_active as isActive,
|
id, email, firstname, lastname, role, is_active as isActive,
|
||||||
employee_type as employeeType,
|
employee_type as employeeType,
|
||||||
contract_type as contractType,
|
contract_type as contractType,
|
||||||
can_work_alone as canWorkAlone,
|
can_work_alone as canWorkAlone,
|
||||||
|
|||||||
@@ -5,6 +5,25 @@ 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';
|
||||||
|
|
||||||
|
// Add the same 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()
|
||||||
|
.replace(/ü/g, 'ue')
|
||||||
|
.replace(/ö/g, 'oe')
|
||||||
|
.replace(/ä/g, 'ae')
|
||||||
|
.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`;
|
||||||
|
}
|
||||||
|
|
||||||
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
||||||
@@ -43,12 +62,11 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { password, firstname, lastname } = req.body;
|
const { password, firstname, lastname } = req.body; // Changed from name to firstname/lastname
|
||||||
const email = 'admin@instandhaltung.de';
|
|
||||||
|
|
||||||
console.log('👤 Creating admin with data:', { name, email });
|
console.log('👤 Creating admin with data:', { firstname, lastname });
|
||||||
|
|
||||||
// Validation
|
// Validation - updated for firstname/lastname
|
||||||
if (!password || !firstname || !lastname) {
|
if (!password || !firstname || !lastname) {
|
||||||
res.status(400).json({ error: 'Passwort, Vorname und Nachname sind erforderlich' });
|
res.status(400).json({ error: 'Passwort, Vorname und Nachname sind erforderlich' });
|
||||||
return;
|
return;
|
||||||
@@ -60,6 +78,10 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate email automatically using the same pattern
|
||||||
|
const email = generateEmail(firstname, lastname);
|
||||||
|
console.log('📧 Generated admin email:', email);
|
||||||
|
|
||||||
// Hash password
|
// Hash password
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
const adminId = randomUUID();
|
const adminId = randomUUID();
|
||||||
@@ -70,18 +92,14 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
await db.run('BEGIN TRANSACTION');
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create admin user
|
// Create admin user with generated email
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
|
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[adminId, email, hashedPassword, firstname, lastname, 'admin', 'manager', 'large', true, 1]
|
[adminId, email, hashedPassword, firstname, lastname, 'admin', 'manager', 'large', true, 1]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('✅ Admin user created successfully');
|
console.log('✅ Admin user created successfully with email:', email);
|
||||||
|
|
||||||
// Initialize default templates WITHOUT starting a new transaction
|
|
||||||
//console.log('🔄 Initialisiere Standard-Vorlagen...');
|
|
||||||
//await initializeDefaultTemplates(adminId, false);
|
|
||||||
|
|
||||||
// Commit the entire setup transaction
|
// Commit the entire setup transaction
|
||||||
await db.run('COMMIT');
|
await db.run('COMMIT');
|
||||||
@@ -91,7 +109,9 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Admin erfolgreich erstellt',
|
message: 'Admin erfolgreich erstellt',
|
||||||
email: email
|
email: email,
|
||||||
|
firstname: firstname,
|
||||||
|
lastname: lastname
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export interface Employee {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateEmployeeRequest {
|
export interface CreateEmployeeRequest {
|
||||||
email: string;
|
|
||||||
password: string;
|
password: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ export interface ShiftPlan {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
startDate?: string; // Optional for templates
|
startDate?: string;
|
||||||
endDate?: string; // Optional for templates
|
endDate?: string;
|
||||||
isTemplate: boolean;
|
isTemplate: boolean;
|
||||||
status: 'draft' | 'published' | 'archived' | 'template';
|
status: 'draft' | 'published' | 'archived' | 'template';
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
timeSlots: TimeSlot[];
|
timeSlots: TimeSlot[];
|
||||||
shifts: Shift[];
|
shifts: Shift[];
|
||||||
scheduledShifts?: ScheduledShift[]; // Only for non-template plans with dates
|
scheduledShifts?: ScheduledShift[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeSlot {
|
export interface TimeSlot {
|
||||||
@@ -85,7 +85,6 @@ export interface AssignEmployeeRequest {
|
|||||||
scheduledShiftId: string;
|
scheduledShiftId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface UpdateRequiredEmployeesRequest {
|
export interface UpdateRequiredEmployeesRequest {
|
||||||
requiredEmployees: number;
|
requiredEmployees: number;
|
||||||
}
|
}
|
||||||
@@ -2,15 +2,20 @@
|
|||||||
import { Employee } from './Employee.js';
|
import { Employee } from './Employee.js';
|
||||||
import { ShiftPlan } from './ShiftPlan.js';
|
import { ShiftPlan } from './ShiftPlan.js';
|
||||||
|
|
||||||
// Add the missing type definitions
|
// Updated Availability interface to match new schema
|
||||||
export interface Availability {
|
export interface Availability {
|
||||||
id: string;
|
id: string;
|
||||||
employeeId: string;
|
employeeId: string;
|
||||||
planId: string;
|
planId: string;
|
||||||
dayOfWeek: number; // 1=Monday, 7=Sunday
|
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
|
||||||
timeSlotId: string;
|
|
||||||
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
||||||
notes?: string;
|
notes?: string;
|
||||||
|
// Optional convenience fields (can be joined from shifts and time_slots tables)
|
||||||
|
dayOfWeek?: number;
|
||||||
|
timeSlotId?: string;
|
||||||
|
timeSlotName?: string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Constraint {
|
export interface Constraint {
|
||||||
@@ -121,3 +126,76 @@ export interface ShiftRequirement {
|
|||||||
assignedEmployees: string[];
|
assignedEmployees: string[];
|
||||||
isPriority: boolean;
|
isPriority: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New types for the updated schema
|
||||||
|
export interface EmployeeWithRoles extends Employee {
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShiftWithDetails {
|
||||||
|
id: string;
|
||||||
|
planId: string;
|
||||||
|
timeSlotId: string;
|
||||||
|
dayOfWeek: number;
|
||||||
|
requiredEmployees: number;
|
||||||
|
color?: string;
|
||||||
|
timeSlot: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvailabilityWithDetails extends Availability {
|
||||||
|
employee?: {
|
||||||
|
id: string;
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
|
canWorkAlone: boolean;
|
||||||
|
};
|
||||||
|
shift?: {
|
||||||
|
dayOfWeek: number;
|
||||||
|
timeSlotId: string;
|
||||||
|
timeSlot?: {
|
||||||
|
name: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types for scheduling algorithm input
|
||||||
|
export interface SchedulingInput {
|
||||||
|
planId: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
constraints: Constraint[];
|
||||||
|
options?: SolverOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types for scheduling results with enhanced information
|
||||||
|
export interface EnhancedAssignment extends Assignment {
|
||||||
|
employeeName: string;
|
||||||
|
shiftDetails: {
|
||||||
|
date: string;
|
||||||
|
dayOfWeek: number;
|
||||||
|
timeSlotName: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
};
|
||||||
|
preferenceLevel: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SchedulingStatistics {
|
||||||
|
totalShifts: number;
|
||||||
|
totalAssignments: number;
|
||||||
|
coverageRate: number;
|
||||||
|
preferenceSatisfaction: number;
|
||||||
|
constraintViolations: number;
|
||||||
|
hardConstraintViolations: number;
|
||||||
|
softConstraintViolations: number;
|
||||||
|
processingTime: number;
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
export interface Employee {
|
export interface Employee {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
role: 'admin' | 'maintenance' | 'user';
|
role: 'admin' | 'maintenance' | 'user';
|
||||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType: 'small' | 'large';
|
contractType: 'small' | 'large';
|
||||||
@@ -13,9 +14,9 @@ export interface Employee {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateEmployeeRequest {
|
export interface CreateEmployeeRequest {
|
||||||
email: string;
|
|
||||||
password: string;
|
password: string;
|
||||||
name: string;
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
role: 'admin' | 'maintenance' | 'user';
|
role: 'admin' | 'maintenance' | 'user';
|
||||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType: 'small' | 'large';
|
contractType: 'small' | 'large';
|
||||||
@@ -23,7 +24,8 @@ export interface CreateEmployeeRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateEmployeeRequest {
|
export interface UpdateEmployeeRequest {
|
||||||
name?: string;
|
firstname?: string;
|
||||||
|
lastname?: string;
|
||||||
role?: 'admin' | 'maintenance' | 'user';
|
role?: 'admin' | 'maintenance' | 'user';
|
||||||
employeeType?: 'manager' | 'trainee' | 'experienced';
|
employeeType?: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType?: 'small' | 'large';
|
contractType?: 'small' | 'large';
|
||||||
@@ -39,8 +41,7 @@ export interface EmployeeAvailability {
|
|||||||
id: string;
|
id: string;
|
||||||
employeeId: string;
|
employeeId: string;
|
||||||
planId: string;
|
planId: string;
|
||||||
dayOfWeek: number; // 1=Monday, 7=Sunday
|
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
|
||||||
timeSlotId: string;
|
|
||||||
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
||||||
notes?: string;
|
notes?: string;
|
||||||
}
|
}
|
||||||
@@ -73,3 +74,13 @@ export interface ManagerSelfAssignmentRequest {
|
|||||||
export interface EmployeeWithAvailabilities extends Employee {
|
export interface EmployeeWithAvailabilities extends Employee {
|
||||||
availabilities: EmployeeAvailability[];
|
availabilities: EmployeeAvailability[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional types for the new roles system
|
||||||
|
export interface Role {
|
||||||
|
role: 'admin' | 'user' | 'maintenance';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeRole {
|
||||||
|
employeeId: string;
|
||||||
|
role: 'admin' | 'user' | 'maintenance';
|
||||||
|
}
|
||||||
@@ -3,14 +3,15 @@ export interface ShiftPlan {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
startDate?: string; // Optional for templates
|
startDate?: string;
|
||||||
endDate?: string; // Optional for templates
|
endDate?: string;
|
||||||
isTemplate: boolean;
|
isTemplate: boolean;
|
||||||
status: 'draft' | 'published' | 'archived' | 'template';
|
status: 'draft' | 'published' | 'archived' | 'template';
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
timeSlots: TimeSlot[];
|
timeSlots: TimeSlot[];
|
||||||
shifts: Shift[];
|
shifts: Shift[];
|
||||||
|
scheduledShifts?: ScheduledShift[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeSlot {
|
export interface TimeSlot {
|
||||||
@@ -49,16 +50,6 @@ export interface ShiftAssignment {
|
|||||||
assignedBy: string;
|
assignedBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmployeeAvailability {
|
|
||||||
id: string;
|
|
||||||
employeeId: string;
|
|
||||||
planId: string;
|
|
||||||
dayOfWeek: number;
|
|
||||||
timeSlotId: string;
|
|
||||||
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
|
||||||
notes?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request/Response DTOs
|
// Request/Response DTOs
|
||||||
export interface CreateShiftPlanRequest {
|
export interface CreateShiftPlanRequest {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -68,7 +59,6 @@ export interface CreateShiftPlanRequest {
|
|||||||
isTemplate: boolean;
|
isTemplate: boolean;
|
||||||
timeSlots: Omit<TimeSlot, 'id' | 'planId'>[];
|
timeSlots: Omit<TimeSlot, 'id' | 'planId'>[];
|
||||||
shifts: Omit<Shift, 'id' | 'planId'>[];
|
shifts: Omit<Shift, 'id' | 'planId'>[];
|
||||||
templateId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateShiftPlanRequest {
|
export interface UpdateShiftPlanRequest {
|
||||||
@@ -95,10 +85,6 @@ export interface AssignEmployeeRequest {
|
|||||||
scheduledShiftId: string;
|
scheduledShiftId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateAvailabilityRequest {
|
|
||||||
planId: string;
|
|
||||||
availabilities: Omit<EmployeeAvailability, 'id' | 'employeeId'>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateRequiredEmployeesRequest {
|
export interface UpdateRequiredEmployeesRequest {
|
||||||
requiredEmployees: number;
|
requiredEmployees: number;
|
||||||
|
|||||||
@@ -2,15 +2,20 @@
|
|||||||
import { Employee } from './Employee.js';
|
import { Employee } from './Employee.js';
|
||||||
import { ShiftPlan } from './ShiftPlan.js';
|
import { ShiftPlan } from './ShiftPlan.js';
|
||||||
|
|
||||||
// Add the missing type definitions
|
// Updated Availability interface to match new schema
|
||||||
export interface Availability {
|
export interface Availability {
|
||||||
id: string;
|
id: string;
|
||||||
employeeId: string;
|
employeeId: string;
|
||||||
planId: string;
|
planId: string;
|
||||||
dayOfWeek: number; // 1=Monday, 7=Sunday
|
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
|
||||||
timeSlotId: string;
|
|
||||||
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
||||||
notes?: string;
|
notes?: string;
|
||||||
|
// Optional convenience fields (can be joined from shifts and time_slots tables)
|
||||||
|
dayOfWeek?: number;
|
||||||
|
timeSlotId?: string;
|
||||||
|
timeSlotName?: string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Constraint {
|
export interface Constraint {
|
||||||
@@ -75,6 +80,7 @@ export interface Solution {
|
|||||||
variablesCreated: number;
|
variablesCreated: number;
|
||||||
optimal: boolean;
|
optimal: boolean;
|
||||||
};
|
};
|
||||||
|
variables?: { [key: string]: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional helper types for the scheduling system
|
// Additional helper types for the scheduling system
|
||||||
@@ -120,3 +126,76 @@ export interface ShiftRequirement {
|
|||||||
assignedEmployees: string[];
|
assignedEmployees: string[];
|
||||||
isPriority: boolean;
|
isPriority: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New types for the updated schema
|
||||||
|
export interface EmployeeWithRoles extends Employee {
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShiftWithDetails {
|
||||||
|
id: string;
|
||||||
|
planId: string;
|
||||||
|
timeSlotId: string;
|
||||||
|
dayOfWeek: number;
|
||||||
|
requiredEmployees: number;
|
||||||
|
color?: string;
|
||||||
|
timeSlot: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvailabilityWithDetails extends Availability {
|
||||||
|
employee?: {
|
||||||
|
id: string;
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
|
canWorkAlone: boolean;
|
||||||
|
};
|
||||||
|
shift?: {
|
||||||
|
dayOfWeek: number;
|
||||||
|
timeSlotId: string;
|
||||||
|
timeSlot?: {
|
||||||
|
name: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types for scheduling algorithm input
|
||||||
|
export interface SchedulingInput {
|
||||||
|
planId: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
constraints: Constraint[];
|
||||||
|
options?: SolverOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types for scheduling results with enhanced information
|
||||||
|
export interface EnhancedAssignment extends Assignment {
|
||||||
|
employeeName: string;
|
||||||
|
shiftDetails: {
|
||||||
|
date: string;
|
||||||
|
dayOfWeek: number;
|
||||||
|
timeSlotName: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
};
|
||||||
|
preferenceLevel: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SchedulingStatistics {
|
||||||
|
totalShifts: number;
|
||||||
|
totalAssignments: number;
|
||||||
|
coverageRate: number;
|
||||||
|
preferenceSatisfaction: number;
|
||||||
|
constraintViolations: number;
|
||||||
|
hardConstraintViolations: number;
|
||||||
|
softConstraintViolations: number;
|
||||||
|
processingTime: number;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user