mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
updated shiftplan view assign shifts non static
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
// backend/src/workers/scheduler-worker.ts
|
// backend/src/workers/scheduler-worker.ts
|
||||||
import { parentPort, workerData } from 'worker_threads';
|
import { parentPort, workerData } from 'worker_threads';
|
||||||
import { CPModel, CPSolver } from './cp-sat-wrapper.js';
|
import { CPModel, CPSolver } from './cp-sat-wrapper.js';
|
||||||
import { ShiftPlan } from '../models/ShiftPlan.js';
|
import { ShiftPlan, Shift } from '../models/ShiftPlan.js';
|
||||||
import { Employee, EmployeeAvailability } from '../models/Employee.js';
|
import { Employee, EmployeeAvailability } from '../models/Employee.js';
|
||||||
import { Availability, Constraint } from '../models/scheduling.js';
|
import { Availability, Constraint } from '../models/scheduling.js';
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ interface WorkerData {
|
|||||||
employees: Employee[];
|
employees: Employee[];
|
||||||
availabilities: Availability[];
|
availabilities: Availability[];
|
||||||
constraints: Constraint[];
|
constraints: Constraint[];
|
||||||
shifts: any[];
|
shifts: Shift[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSchedulingModel(model: CPModel, data: WorkerData): void {
|
function buildSchedulingModel(model: CPModel, data: WorkerData): void {
|
||||||
@@ -123,7 +123,7 @@ function buildSchedulingModel(model: CPModel, data: WorkerData): void {
|
|||||||
if (availability) {
|
if (availability) {
|
||||||
const score = availability.preferenceLevel === 1 ? 10 :
|
const score = availability.preferenceLevel === 1 ? 10 :
|
||||||
availability.preferenceLevel === 2 ? 5 :
|
availability.preferenceLevel === 2 ? 5 :
|
||||||
-100; // Heavy penalty for assigning unavailable shifts
|
-1000; // Heavy penalty for assigning unavailable shifts
|
||||||
|
|
||||||
const varName = `assign_${employee.id}_${shift.id}`;
|
const varName = `assign_${employee.id}_${shift.id}`;
|
||||||
if (objectiveExpression) {
|
if (objectiveExpression) {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import { shiftPlanService } from '../../services/shiftPlanService';
|
import { shiftPlanService } from '../../services/shiftPlanService';
|
||||||
import { employeeService } from '../../services/employeeService';
|
import { employeeService } from '../../services/employeeService';
|
||||||
import { shiftAssignmentService, ShiftAssignmentService } from '../../services/shiftAssignmentService';
|
import { shiftAssignmentService } from '../../services/shiftAssignmentService';
|
||||||
import { IntelligentShiftScheduler, SchedulingResult, AssignmentResult } from '../../services/scheduling/useScheduling';
|
import { AssignmentResult } from '../../models/scheduling';
|
||||||
import { ShiftPlan, TimeSlot, ScheduledShift } from '../../models/ShiftPlan';
|
import { ShiftPlan, TimeSlot, ScheduledShift } from '../../models/ShiftPlan';
|
||||||
import { Employee, EmployeeAvailability } from '../../models/Employee';
|
import { Employee, EmployeeAvailability } from '../../models/Employee';
|
||||||
import { useNotification } from '../../contexts/NotificationContext';
|
import { useNotification } from '../../contexts/NotificationContext';
|
||||||
@@ -421,11 +421,11 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Use the freshly loaded data, not the state
|
// Use the freshly loaded data, not the state
|
||||||
const result = await ShiftAssignmentService.assignShifts(
|
const result = await shiftAssignmentService.assignShifts(
|
||||||
shiftPlan,
|
shiftPlan,
|
||||||
refreshedEmployees, // Use fresh array, not state
|
refreshedEmployees,
|
||||||
refreshedAvailabilities, // Use fresh array, not state
|
refreshedAvailabilities,
|
||||||
constraints // Now this variable is defined
|
constraints
|
||||||
);
|
);
|
||||||
|
|
||||||
setAssignmentResult(result);
|
setAssignmentResult(result);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ShiftPlan, ScheduledShift } from '../models/ShiftPlan';
|
|||||||
import { Employee, EmployeeAvailability } from '../models/Employee';
|
import { Employee, EmployeeAvailability } from '../models/Employee';
|
||||||
import { authService } from './authService';
|
import { authService } from './authService';
|
||||||
//import { IntelligentShiftScheduler, AssignmentResult, WeeklyPattern } from './scheduling/useScheduling';
|
//import { IntelligentShiftScheduler, AssignmentResult, WeeklyPattern } from './scheduling/useScheduling';
|
||||||
import { isScheduledShift } from '../models/helpers';
|
import { AssignmentResult } from '../models/scheduling';
|
||||||
|
|
||||||
const API_BASE_URL = 'http://localhost:3002/api/scheduled-shifts';
|
const API_BASE_URL = 'http://localhost:3002/api/scheduled-shifts';
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ export class ShiftAssignmentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async assignShifts(
|
async assignShifts(
|
||||||
shiftPlan: ShiftPlan,
|
shiftPlan: ShiftPlan,
|
||||||
employees: Employee[],
|
employees: Employee[],
|
||||||
availabilities: EmployeeAvailability[],
|
availabilities: EmployeeAvailability[],
|
||||||
@@ -143,65 +143,13 @@ export class ShiftAssignmentService {
|
|||||||
|
|
||||||
console.log('🧠 Starting intelligent scheduling for FIRST WEEK ONLY...');
|
console.log('🧠 Starting intelligent scheduling for FIRST WEEK ONLY...');
|
||||||
|
|
||||||
// Load all scheduled shifts
|
|
||||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
|
||||||
|
|
||||||
if (scheduledShifts.length === 0) {
|
|
||||||
return {
|
|
||||||
assignments: {},
|
|
||||||
violations: ['❌ KRITISCH: Keine Schichten verfügbar für die Zuordnung'],
|
|
||||||
success: false,
|
|
||||||
resolutionReport: ['🚨 ABBRUCH: Keine Schichten im Plan verfügbar']
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set cache for scheduler
|
|
||||||
IntelligentShiftScheduler.scheduledShiftsCache.set(shiftPlan.id, {
|
|
||||||
shifts: scheduledShifts,
|
|
||||||
timestamp: Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🔥 RUN SCHEDULING FOR FIRST WEEK ONLY
|
|
||||||
const schedulingResult = await IntelligentShiftScheduler.generateOptimalSchedule(
|
|
||||||
shiftPlan,
|
|
||||||
employees.filter(emp => emp.isActive),
|
|
||||||
availabilities,
|
|
||||||
constraints
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get first week shifts for pattern
|
|
||||||
const firstWeekShifts = this.getFirstWeekShifts(scheduledShifts);
|
|
||||||
|
|
||||||
console.log('🔄 Creating weekly pattern from FIRST WEEK:', {
|
|
||||||
firstWeekShifts: firstWeekShifts.length,
|
|
||||||
allShifts: scheduledShifts.length,
|
|
||||||
patternAssignments: Object.keys(schedulingResult.assignments).length
|
|
||||||
});
|
|
||||||
|
|
||||||
const weeklyPattern: WeeklyPattern = {
|
|
||||||
weekShifts: firstWeekShifts,
|
|
||||||
assignments: schedulingResult.assignments, // 🔥 Diese enthalten nur erste Woche
|
|
||||||
weekNumber: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
// 🔥 APPLY PATTERN TO ALL WEEKS
|
|
||||||
const allAssignments = this.applyWeeklyPattern(scheduledShifts, weeklyPattern);
|
|
||||||
|
|
||||||
console.log('✅ Pattern applied to all weeks:', {
|
|
||||||
firstWeekAssignments: Object.keys(schedulingResult.assignments).length,
|
|
||||||
allWeeksAssignments: Object.keys(allAssignments).length
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean cache
|
|
||||||
IntelligentShiftScheduler.scheduledShiftsCache.delete(shiftPlan.id);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assignments: allAssignments, // 🔥 Diese enthalten alle Wochen
|
assignments: ,
|
||||||
violations: schedulingResult.violations,
|
violations: ,
|
||||||
success: schedulingResult.success,
|
success: ,
|
||||||
pattern: weeklyPattern,
|
resolutionReport: ,
|
||||||
resolutionReport: schedulingResult.resolutionReport,
|
|
||||||
qualityMetrics: schedulingResult.qualityMetrics
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,20 +329,6 @@ export class ShiftAssignmentService {
|
|||||||
return assignments;
|
return assignments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static groupShiftsByWeek(shifts: ScheduledShift[]): { [weekNumber: string]: ScheduledShift[] } {
|
|
||||||
const weeks: { [weekNumber: string]: ScheduledShift[] } = {};
|
|
||||||
|
|
||||||
shifts.forEach(shift => {
|
|
||||||
const weekNumber = this.getWeekNumber(shift.date);
|
|
||||||
if (!weeks[weekNumber]) {
|
|
||||||
weeks[weekNumber] = [];
|
|
||||||
}
|
|
||||||
weeks[weekNumber].push(shift);
|
|
||||||
});
|
|
||||||
|
|
||||||
return weeks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getFirstWeekShifts(shifts: ScheduledShift[]): ScheduledShift[] {
|
private static getFirstWeekShifts(shifts: ScheduledShift[]): ScheduledShift[] {
|
||||||
if (shifts.length === 0) return [];
|
if (shifts.length === 0) return [];
|
||||||
|
|
||||||
@@ -410,26 +344,6 @@ export class ShiftAssignmentService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static findMatchingPatternShift(
|
|
||||||
shift: ScheduledShift,
|
|
||||||
patternShifts: ScheduledShift[]
|
|
||||||
): ScheduledShift | null {
|
|
||||||
const shiftDayOfWeek = this.getDayOfWeek(shift.date);
|
|
||||||
const shiftTimeSlot = shift.timeSlotId;
|
|
||||||
|
|
||||||
return patternShifts.find(patternShift =>
|
|
||||||
this.getDayOfWeek(patternShift.date) === shiftDayOfWeek &&
|
|
||||||
patternShift.timeSlotId === shiftTimeSlot
|
|
||||||
) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getWeekNumber(dateString: string): number {
|
|
||||||
const date = new Date(dateString);
|
|
||||||
const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
|
|
||||||
const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
|
|
||||||
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== EXISTING HELPER METHODS ==========
|
// ========== EXISTING HELPER METHODS ==========
|
||||||
|
|
||||||
static async getDefinedShifts(shiftPlan: ShiftPlan): Promise<ScheduledShift[]> {
|
static async getDefinedShifts(shiftPlan: ShiftPlan): Promise<ScheduledShift[]> {
|
||||||
@@ -533,36 +447,6 @@ export class ShiftAssignmentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static findViolations(
|
|
||||||
assignments: { [shiftId: string]: string[] },
|
|
||||||
employees: Employee[],
|
|
||||||
definedShifts: ScheduledShift[],
|
|
||||||
enforceNoTraineeAlone: boolean
|
|
||||||
): string[] {
|
|
||||||
const violations: string[] = [];
|
|
||||||
const employeeMap = new Map(employees.map(emp => [emp.id, emp]));
|
|
||||||
|
|
||||||
definedShifts.forEach(scheduledShift => {
|
|
||||||
const assignedEmployeeIds = assignments[scheduledShift.id] || [];
|
|
||||||
|
|
||||||
if (assignedEmployeeIds.length === 0) {
|
|
||||||
violations.push(`Shift has no assigned employees`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const assignedEmployees = assignedEmployeeIds.map(id => employeeMap.get(id)).filter(Boolean) as Employee[];
|
|
||||||
|
|
||||||
if (enforceNoTraineeAlone && assignedEmployees.length === 1) {
|
|
||||||
const soloEmployee = assignedEmployees[0];
|
|
||||||
if (soloEmployee.employeeType === 'trainee') {
|
|
||||||
violations.push(`Trainee ${soloEmployee.name} is working alone`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return violations;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getDayOfWeek(dateString: string): number {
|
private static getDayOfWeek(dateString: string): number {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
return date.getDay() === 0 ? 7 : date.getDay();
|
return date.getDay() === 0 ? 7 : date.getDay();
|
||||||
|
|||||||
Reference in New Issue
Block a user