mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
shift assignment works
This commit is contained in:
@@ -148,6 +148,56 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
return date.getDay() === 0 ? 7 : date.getDay();
|
return date.getDay() === 0 ? 7 : date.getDay();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debugManagerAvailability = () => {
|
||||||
|
if (!shiftPlan || !employees.length || !availabilities.length) return;
|
||||||
|
|
||||||
|
const manager = employees.find(emp => emp.role === 'admin');
|
||||||
|
if (!manager) {
|
||||||
|
console.log('❌ Kein Manager (admin) gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔍 Manager-Analyse:', {
|
||||||
|
manager: manager.name,
|
||||||
|
managerId: manager.id,
|
||||||
|
totalAvailabilities: availabilities.length,
|
||||||
|
managerAvailabilities: availabilities.filter(a => a.employeeId === manager.id).length
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prüfe speziell die leeren Manager-Schichten
|
||||||
|
const emptyManagerShifts = [
|
||||||
|
'a8ef4ce0-adfd-4ec3-8c58-efa0f7347f9f',
|
||||||
|
'a496a8d6-f7a0-4d77-96de-c165379378c4',
|
||||||
|
'ea2d73d1-8354-4833-8c87-40f318ce8be0',
|
||||||
|
'90eb5454-2ae2-4445-86b7-a6e0e2cf0b22'
|
||||||
|
];
|
||||||
|
|
||||||
|
emptyManagerShifts.forEach(shiftId => {
|
||||||
|
const scheduledShift = shiftPlan.scheduledShifts?.find(s => s.id === shiftId);
|
||||||
|
if (scheduledShift) {
|
||||||
|
const dayOfWeek = getDayOfWeek(scheduledShift.date);
|
||||||
|
const shiftKey = `${dayOfWeek}-${scheduledShift.timeSlotId}`;
|
||||||
|
|
||||||
|
const managerAvailability = availabilities.find(a =>
|
||||||
|
a.employeeId === manager.id &&
|
||||||
|
a.dayOfWeek === dayOfWeek &&
|
||||||
|
a.timeSlotId === scheduledShift.timeSlotId
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`📊 Schicht ${shiftId}:`, {
|
||||||
|
date: scheduledShift.date,
|
||||||
|
dayOfWeek,
|
||||||
|
timeSlotId: scheduledShift.timeSlotId,
|
||||||
|
shiftKey,
|
||||||
|
managerAvailability: managerAvailability ? managerAvailability.preferenceLevel : 'NICHT GEFUNDEN',
|
||||||
|
status: managerAvailability ?
|
||||||
|
(managerAvailability.preferenceLevel === 3 ? '❌ NICHT VERFÜGBAR' : '✅ VERFÜGBAR') :
|
||||||
|
'❌ KEINE VERFÜGBARKEITSDATEN'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handlePreviewAssignment = async () => {
|
const handlePreviewAssignment = async () => {
|
||||||
if (!shiftPlan) return;
|
if (!shiftPlan) return;
|
||||||
|
|
||||||
@@ -164,6 +214,26 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// DEBUG: Überprüfe die tatsächlichen Violations
|
||||||
|
console.log('🔍 VIOLATIONS ANALYSIS:', {
|
||||||
|
allViolations: result.violations,
|
||||||
|
criticalViolations: result.violations.filter(v =>
|
||||||
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
|
),
|
||||||
|
warningViolations: result.violations.filter(v =>
|
||||||
|
v.includes('WARNING:') || v.includes('⚠️')
|
||||||
|
),
|
||||||
|
infoViolations: result.violations.filter(v =>
|
||||||
|
v.includes('INFO:')
|
||||||
|
),
|
||||||
|
criticalCount: result.violations.filter(v =>
|
||||||
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
|
).length,
|
||||||
|
canPublish: result.violations.filter(v =>
|
||||||
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
|
).length === 0
|
||||||
|
});
|
||||||
|
|
||||||
setAssignmentResult(result);
|
setAssignmentResult(result);
|
||||||
setShowAssignmentPreview(true);
|
setShowAssignmentPreview(true);
|
||||||
|
|
||||||
@@ -173,48 +243,23 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
result.resolutionReport.forEach(line => console.log(line));
|
result.resolutionReport.forEach(line => console.log(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
// KORRIGIERT: Entscheidung basierend auf Reparatur-Bericht statt allProblemsResolved
|
// Entscheidung basierend auf tatsächlichen kritischen Violations
|
||||||
const allCriticalResolved = result.resolutionReport &&
|
const criticalCount = result.violations.filter(v =>
|
||||||
result.resolutionReport.some(line =>
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
).length;
|
||||||
);
|
|
||||||
|
|
||||||
if (allCriticalResolved) {
|
if (criticalCount === 0) {
|
||||||
showNotification({
|
showNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Erfolg',
|
title: 'Erfolg',
|
||||||
message: 'Alle kritischen Probleme wurden behoben! Der Schichtplan kann veröffentlicht werden.'
|
message: 'Alle kritischen Probleme wurden behoben! Der Schichtplan kann veröffentlicht werden.'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Zähle nur kritische Probleme (ERROR oder ❌ KRITISCH)
|
showNotification({
|
||||||
const criticalProblems = result.violations.filter(v =>
|
type: 'error',
|
||||||
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
title: 'Kritische Probleme',
|
||||||
);
|
message: `${criticalCount} kritische Probleme müssen behoben werden`
|
||||||
|
});
|
||||||
// Zähle Warnungen separat
|
|
||||||
const warnings = result.violations.filter(v =>
|
|
||||||
v.includes('WARNING:') || v.includes('⚠️')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (criticalProblems.length > 0) {
|
|
||||||
showNotification({
|
|
||||||
type: 'error',
|
|
||||||
title: 'Kritische Probleme',
|
|
||||||
message: `${criticalProblems.length} kritische Probleme müssen behoben werden`
|
|
||||||
});
|
|
||||||
} else if (warnings.length > 0) {
|
|
||||||
showNotification({
|
|
||||||
type: 'warning',
|
|
||||||
title: 'Warnungen',
|
|
||||||
message: `${warnings.length} Warnungen - Plan kann trotzdem veröffentlicht werden`
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showNotification({
|
|
||||||
type: 'success',
|
|
||||||
title: 'Erfolg',
|
|
||||||
message: 'Keine Probleme gefunden! Der Schichtplan kann veröffentlicht werden.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -312,14 +357,28 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
setReverting(true);
|
setReverting(true);
|
||||||
|
|
||||||
|
// 1. Zuerst zurücksetzen
|
||||||
const updatedPlan = await shiftPlanService.revertToDraft(id);
|
const updatedPlan = await shiftPlanService.revertToDraft(id);
|
||||||
setShiftPlan(updatedPlan);
|
|
||||||
|
// 2. Dann ALLE Daten neu laden
|
||||||
|
await loadShiftPlanData();
|
||||||
|
|
||||||
|
// 3. Assignment-Result zurücksetzen
|
||||||
setAssignmentResult(null);
|
setAssignmentResult(null);
|
||||||
|
|
||||||
|
// 4. Preview schließen falls geöffnet
|
||||||
|
setShowAssignmentPreview(false);
|
||||||
|
|
||||||
showNotification({
|
showNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Erfolg',
|
title: 'Erfolg',
|
||||||
message: 'Schichtplan wurde erfolgreich zurück in den Entwurfsstatus gesetzt.'
|
message: 'Schichtplan wurde erfolgreich zurück in den Entwurfsstatus gesetzt. Alle Daten wurden neu geladen.'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Scheduled shifts after revert:', {
|
||||||
|
hasScheduledShifts: !!shiftPlan.scheduledShifts,
|
||||||
|
count: shiftPlan.scheduledShifts?.length || 0,
|
||||||
|
firstFew: shiftPlan.scheduledShifts?.slice(0, 3)
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -705,28 +764,56 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
}}>
|
}}>
|
||||||
<h2>Wochenmuster-Zuordnung</h2>
|
<h2>Wochenmuster-Zuordnung</h2>
|
||||||
|
|
||||||
{/* Reparatur-Bericht anzeigen */}
|
{/* Detaillierter Reparatur-Bericht anzeigen */}
|
||||||
{assignmentResult.resolutionReport && (
|
{assignmentResult.resolutionReport && (
|
||||||
<div style={{
|
<div style={{
|
||||||
backgroundColor: '#e8f4fd',
|
backgroundColor: '#f8f9fa',
|
||||||
border: '1px solid #b8d4f0',
|
border: '1px solid #e9ecef',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
padding: '15px',
|
padding: '15px',
|
||||||
marginBottom: '20px',
|
marginBottom: '20px',
|
||||||
fontSize: '14px'
|
fontSize: '14px',
|
||||||
|
maxHeight: '400px',
|
||||||
|
overflow: 'auto'
|
||||||
}}>
|
}}>
|
||||||
<h4 style={{ color: '#2c3e50', marginTop: 0 }}>Reparatur-Bericht</h4>
|
<h4 style={{ color: '#2c3e50', marginTop: 0, display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
<div style={{ maxHeight: '200px', overflow: 'auto' }}>
|
<span>📋</span> Detaillierter Reparatur-Bericht
|
||||||
{assignmentResult.resolutionReport.map((line, index) => (
|
</h4>
|
||||||
<div key={index} style={{
|
<div style={{
|
||||||
color: line.includes('✅') ? '#2ecc71' : line.includes('❌') ? '#e74c3c' : '#2c3e50',
|
fontFamily: 'monospace',
|
||||||
fontFamily: 'monospace',
|
fontSize: '12px',
|
||||||
fontSize: '12px',
|
lineHeight: '1.4'
|
||||||
marginBottom: '2px'
|
}}>
|
||||||
}}>
|
{assignmentResult.resolutionReport.map((line, index) => {
|
||||||
{line}
|
let color = '#2c3e50';
|
||||||
</div>
|
let fontWeight = 'normal';
|
||||||
))}
|
|
||||||
|
if (line.includes('✅') || line.includes('ALLES KRITISCHEN PROBLEME BEHOBEN')) {
|
||||||
|
color = '#2ecc71';
|
||||||
|
fontWeight = 'bold';
|
||||||
|
} else if (line.includes('❌') || line.includes('KRITISCHEN PROBLEME')) {
|
||||||
|
color = '#e74c3c';
|
||||||
|
fontWeight = 'bold';
|
||||||
|
} else if (line.includes('⚠️')) {
|
||||||
|
color = '#f39c12';
|
||||||
|
} else if (line.includes('📊') || line.includes('🔧') || line.includes('📅') || line.includes('🚨') || line.includes('🛠️') || line.includes('💡') || line.includes('🎯')) {
|
||||||
|
color = '#3498db';
|
||||||
|
fontWeight = 'bold';
|
||||||
|
} else if (line.startsWith(' •') || line.startsWith(' -')) {
|
||||||
|
color = '#7f8c8d';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index} style={{
|
||||||
|
color,
|
||||||
|
fontWeight,
|
||||||
|
marginBottom: line === '' ? '5px' : '2px',
|
||||||
|
paddingLeft: line.startsWith(' ') ? '20px' : '0px'
|
||||||
|
}}>
|
||||||
|
{line}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -736,11 +823,10 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
<div style={{ marginBottom: '20px' }}>
|
<div style={{ marginBottom: '20px' }}>
|
||||||
<h4>Zusammenfassung:</h4>
|
<h4>Zusammenfassung:</h4>
|
||||||
|
|
||||||
{/* Entscheidung basierend auf Reparatur-Bericht */}
|
{/* Entscheidung basierend auf tatsächlichen kritischen Problemen */}
|
||||||
{assignmentResult.resolutionReport &&
|
{assignmentResult.violations.filter(v =>
|
||||||
assignmentResult.resolutionReport.some(line =>
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
).length === 0 ? (
|
||||||
) ? (
|
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '15px',
|
padding: '15px',
|
||||||
backgroundColor: '#d4edda',
|
backgroundColor: '#d4edda',
|
||||||
@@ -822,33 +908,28 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handlePublish}
|
onClick={handlePublish}
|
||||||
disabled={publishing || !(assignmentResult.resolutionReport &&
|
disabled={publishing || assignmentResult.violations.filter(v =>
|
||||||
assignmentResult.resolutionReport.some(line =>
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
).length > 0}
|
||||||
))
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
padding: '10px 20px',
|
padding: '10px 20px',
|
||||||
backgroundColor: (assignmentResult.resolutionReport &&
|
backgroundColor: assignmentResult.violations.filter(v =>
|
||||||
assignmentResult.resolutionReport.some(line =>
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
).length === 0 ? '#2ecc71' : '#95a5a6',
|
||||||
)) ? '#2ecc71' : '#95a5a6',
|
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
cursor: (assignmentResult.resolutionReport &&
|
cursor: assignmentResult.violations.filter(v =>
|
||||||
assignmentResult.resolutionReport.some(line =>
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
).length === 0 ? 'pointer' : 'not-allowed',
|
||||||
)) ? 'pointer' : 'not-allowed',
|
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: '16px'
|
fontSize: '16px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{publishing ? 'Veröffentliche...' : (
|
{publishing ? 'Veröffentliche...' : (
|
||||||
(assignmentResult.resolutionReport &&
|
assignmentResult.violations.filter(v =>
|
||||||
assignmentResult.resolutionReport.some(line =>
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
).length === 0
|
||||||
))
|
|
||||||
? 'Schichtplan veröffentlichen'
|
? 'Schichtplan veröffentlichen'
|
||||||
: 'Kritische Probleme müssen behoben werden'
|
: 'Kritische Probleme müssen behoben werden'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -864,3 +864,192 @@ export function checkAllProblemsResolved(
|
|||||||
|
|
||||||
return { resolved, remaining, allResolved };
|
return { resolved, remaining, allResolved };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createDetailedResolutionReport(
|
||||||
|
assignments: Assignment,
|
||||||
|
employees: Map<string, SchedulingEmployee>,
|
||||||
|
shifts: SchedulingShift[],
|
||||||
|
managerShifts: string[],
|
||||||
|
repairContext: RepairContext
|
||||||
|
): string[] {
|
||||||
|
const report: string[] = [];
|
||||||
|
|
||||||
|
report.push('=== DETAILIERTER REPARATUR-BERICHT ===');
|
||||||
|
report.push('');
|
||||||
|
|
||||||
|
// 1. ZUSAMMENFASSUNG
|
||||||
|
report.push('📊 ZUSAMMENFASSUNG');
|
||||||
|
const totalShifts = shifts.length;
|
||||||
|
const assignedShifts = Object.values(assignments).filter(a => a.length > 0).length;
|
||||||
|
const emptyShifts = totalShifts - assignedShifts;
|
||||||
|
|
||||||
|
report.push(` • Gesamtschichten: ${totalShifts}`);
|
||||||
|
report.push(` • Zugewiesene Schichten: ${assignedShifts}`);
|
||||||
|
report.push(` • Leere Schichten: ${emptyShifts}`);
|
||||||
|
report.push(` • Pool-Mitarbeiter: ${repairContext.unassignedPool.length}`);
|
||||||
|
report.push(` • Gesperrte Schichten: ${repairContext.lockedShifts.size}`);
|
||||||
|
report.push('');
|
||||||
|
|
||||||
|
// 2. DURCHGEFÜHRTE AKTIONEN
|
||||||
|
if (repairContext.warnings.length > 0) {
|
||||||
|
report.push('🔧 DURCHGEFÜHRTE REPARATURAKTIONEN');
|
||||||
|
repairContext.warnings.forEach((action, index) => {
|
||||||
|
if (action.includes('zugewiesen') || action.includes('entfernt') || action.includes('getauscht') || action.includes('bewegt')) {
|
||||||
|
report.push(` ✅ ${action}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
report.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. DETAILLIERTE SCHICHTANALYSE
|
||||||
|
report.push('📅 DETAILLIERTE SCHICHTANALYSE');
|
||||||
|
|
||||||
|
shifts.forEach(shift => {
|
||||||
|
const assignment = assignments[shift.id] || [];
|
||||||
|
const assignedEmployees = assignment.map(empId => {
|
||||||
|
const emp = employees.get(empId);
|
||||||
|
return emp ? `${emp.name} (${emp.role})` : 'Unbekannt';
|
||||||
|
}).join(', ');
|
||||||
|
|
||||||
|
const isManagerShift = managerShifts.includes(shift.id);
|
||||||
|
const isEmpty = assignment.length === 0;
|
||||||
|
const hasOnlyNew = onlyNeuAssigned(assignment, employees);
|
||||||
|
const experiencedAloneCheck = hasExperiencedAloneNotAllowed(assignment, employees);
|
||||||
|
|
||||||
|
let status = '✅ OK';
|
||||||
|
if (isEmpty) status = '❌ LEER';
|
||||||
|
else if (isManagerShift && hasOnlyNew) status = '⚠️ MANAGER + NUR NEUE';
|
||||||
|
else if (experiencedAloneCheck.hasViolation) status = '❌ ERFAHRENER ALLEIN';
|
||||||
|
else if (hasOnlyNew) status = '⚠️ NUR NEUE';
|
||||||
|
|
||||||
|
report.push(` • Schicht ${shift.id}:`);
|
||||||
|
report.push(` - Status: ${status}`);
|
||||||
|
report.push(` - Zugewiesene: ${assignedEmployees || 'Keine'}`);
|
||||||
|
report.push(` - Benötigt: ${shift.requiredEmployees} Mitarbeiter`);
|
||||||
|
report.push(` - Aktuell: ${assignment.length} Mitarbeiter`);
|
||||||
|
if (isManagerShift) report.push(` - 💼 MANAGER-SCHICHT`);
|
||||||
|
report.push('');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. PROBLEMANALYSE NACH TYP
|
||||||
|
report.push('🚨 PROBLEMANALYSE NACH TYP');
|
||||||
|
|
||||||
|
const emptyShiftsList = shifts.filter(shift =>
|
||||||
|
(assignments[shift.id] || []).length === 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const managerWithOnlyNewList = shifts.filter(shift =>
|
||||||
|
managerShifts.includes(shift.id) &&
|
||||||
|
isManagerShiftWithOnlyNew(assignments[shift.id] || [], employees, Array.from(employees.values()).find(e => e.role === 'manager')?.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
const experiencedAloneList = shifts.filter(shift => {
|
||||||
|
const check = hasExperiencedAloneNotAllowed(assignments[shift.id] || [], employees);
|
||||||
|
return check.hasViolation;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onlyNewList = shifts.filter(shift =>
|
||||||
|
!managerShifts.includes(shift.id) &&
|
||||||
|
onlyNeuAssigned(assignments[shift.id] || [], employees)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (emptyShiftsList.length > 0) {
|
||||||
|
report.push(` ❌ LEERE SCHICHTEN (${emptyShiftsList.length}):`);
|
||||||
|
emptyShiftsList.forEach(shift => {
|
||||||
|
report.push(` - ${shift.id}`);
|
||||||
|
});
|
||||||
|
report.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (managerWithOnlyNewList.length > 0) {
|
||||||
|
report.push(` ⚠️ MANAGER + NUR NEUE (${managerWithOnlyNewList.length}):`);
|
||||||
|
managerWithOnlyNewList.forEach(shift => {
|
||||||
|
const assignment = assignments[shift.id] || [];
|
||||||
|
const employeesList = assignment.map(empId => {
|
||||||
|
const emp = employees.get(empId);
|
||||||
|
return emp ? emp.name : 'Unbekannt';
|
||||||
|
}).join(', ');
|
||||||
|
report.push(` - ${shift.id}: ${employeesList}`);
|
||||||
|
});
|
||||||
|
report.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (experiencedAloneList.length > 0) {
|
||||||
|
report.push(` ❌ ERFAHRENER ALLEIN (${experiencedAloneList.length}):`);
|
||||||
|
experiencedAloneList.forEach(shift => {
|
||||||
|
const check = hasExperiencedAloneNotAllowed(assignments[shift.id] || [], employees);
|
||||||
|
const emp = employees.get(check.employeeId!);
|
||||||
|
report.push(` - ${shift.id}: ${emp?.name || check.employeeId}`);
|
||||||
|
});
|
||||||
|
report.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyNewList.length > 0) {
|
||||||
|
report.push(` ⚠️ NUR NEUE IN SCHICHT (${onlyNewList.length}):`);
|
||||||
|
onlyNewList.forEach(shift => {
|
||||||
|
report.push(` - ${shift.id}`);
|
||||||
|
});
|
||||||
|
report.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. REPARATURVERSUCHE
|
||||||
|
report.push('🛠️ REPARATURVERSUCHE UND -ERGEBNISSE');
|
||||||
|
|
||||||
|
// Zähle die verschiedenen Reparaturtypen
|
||||||
|
const assignmentActions = repairContext.warnings.filter(w =>
|
||||||
|
w.includes('zugewiesen')
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const removalActions = repairContext.warnings.filter(w =>
|
||||||
|
w.includes('entfernt')
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const swapActions = repairContext.warnings.filter(w =>
|
||||||
|
w.includes('getauscht') || w.includes('bewegt')
|
||||||
|
).length;
|
||||||
|
|
||||||
|
report.push(` • Zuweisungen: ${assignmentActions}`);
|
||||||
|
report.push(` • Entfernungen: ${removalActions}`);
|
||||||
|
report.push(` • Tausche/Bewegungen: ${swapActions}`);
|
||||||
|
report.push(` • Gesamte Aktionen: ${repairContext.warnings.length}`);
|
||||||
|
report.push('');
|
||||||
|
|
||||||
|
// 6. EMPFEHLUNGEN
|
||||||
|
report.push('💡 EMPFEHLUNGEN ZUR PROBLEMBEHANDLUNG');
|
||||||
|
|
||||||
|
if (emptyShiftsList.length > 0) {
|
||||||
|
report.push(` • Für ${emptyShiftsList.length} leere Schichten:`);
|
||||||
|
report.push(` - Verfügbare Mitarbeiter prüfen`);
|
||||||
|
report.push(` - Vertragskapazitäten überprüfen`);
|
||||||
|
report.push(` - Ggf. Schichtanforderungen reduzieren`);
|
||||||
|
report.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (managerWithOnlyNewList.length > 0) {
|
||||||
|
report.push(` • Für ${managerWithOnlyNewList.length} Manager+Neue-Schichten:`);
|
||||||
|
report.push(` - Erfahrene Mitarbeiter zuweisen`);
|
||||||
|
report.push(` - Manager-Verfügbarkeit anpassen`);
|
||||||
|
report.push(` - Teamzusammensetzung optimieren`);
|
||||||
|
report.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. FINALE BEWERTUNG
|
||||||
|
report.push('🎯 FINALE BEWERTUNG');
|
||||||
|
|
||||||
|
const totalProblems = emptyShiftsList.length + experiencedAloneList.length;
|
||||||
|
const totalWarnings = managerWithOnlyNewList.length + onlyNewList.length;
|
||||||
|
|
||||||
|
if (totalProblems === 0) {
|
||||||
|
report.push(' ✅ ALLE KRITISCHEN PROBLEME BEHOBEN');
|
||||||
|
report.push(' Der Schichtplan kann veröffentlicht werden.');
|
||||||
|
} else {
|
||||||
|
report.push(` ❌ ${totalProblems} KRITISCHE PROBLEME VERBLEIBEND`);
|
||||||
|
report.push(` ⚠️ ${totalWarnings} WARNUNGEN`);
|
||||||
|
report.push(' Der Schichtplan kann nicht veröffentlicht werden.');
|
||||||
|
}
|
||||||
|
|
||||||
|
report.push('');
|
||||||
|
report.push('=== ENDE DES DETAILIERTEN BERICHTES ===');
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
@@ -29,7 +29,8 @@ import {
|
|||||||
resolveOverstaffedExperienced,
|
resolveOverstaffedExperienced,
|
||||||
prioritizeWarningsWithPool,
|
prioritizeWarningsWithPool,
|
||||||
resolveExperiencedAloneNotAllowed,
|
resolveExperiencedAloneNotAllowed,
|
||||||
checkAllProblemsResolved
|
checkAllProblemsResolved,
|
||||||
|
createDetailedResolutionReport
|
||||||
} from './repairFunctions';
|
} from './repairFunctions';
|
||||||
|
|
||||||
// Phase A: Regular employee scheduling (without manager)
|
// Phase A: Regular employee scheduling (without manager)
|
||||||
@@ -144,7 +145,7 @@ function phaseBInsertManager(
|
|||||||
console.log(`🎯 Phase B: Processing ${managerShifts.length} manager shifts`);
|
console.log(`🎯 Phase B: Processing ${managerShifts.length} manager shifts`);
|
||||||
|
|
||||||
for (const shiftId of managerShifts) {
|
for (const shiftId of managerShifts) {
|
||||||
//const shift = nonManagerShifts.find(s => s.id === shiftId) || { id: shiftId, requiredEmployees: 2 };
|
let _shift = nonManagerShifts.find(s => s.id === shiftId) || { id: shiftId, requiredEmployees: 2 };
|
||||||
|
|
||||||
// Assign manager to his chosen shifts
|
// Assign manager to his chosen shifts
|
||||||
if (!assignments[shiftId].includes(manager.id)) {
|
if (!assignments[shiftId].includes(manager.id)) {
|
||||||
@@ -240,11 +241,12 @@ export function enhancedPhaseCRepairValidate(
|
|||||||
const employeeMap = new Map(employees.map(emp => [emp.id, emp]));
|
const employeeMap = new Map(employees.map(emp => [emp.id, emp]));
|
||||||
const manager = employees.find(emp => emp.role === 'manager');
|
const manager = employees.find(emp => emp.role === 'manager');
|
||||||
|
|
||||||
console.log('🔄 Starting Enhanced Phase C: Smart Repair & Validation');
|
console.log('🔄 Starting Enhanced Phase C: Detailed Repair & Validation');
|
||||||
|
|
||||||
// 1. Manager-Schutzregel
|
// 1. Manager-Schutzregel
|
||||||
managerShifts.forEach(shiftId => {
|
managerShifts.forEach(shiftId => {
|
||||||
repairContext.lockedShifts.add(shiftId);
|
repairContext.lockedShifts.add(shiftId);
|
||||||
|
repairContext.warnings.push(`Schicht ${shiftId} als Manager-Schicht gesperrt`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Überbesetzte erfahrene Mitarbeiter identifizieren und in Pool verschieben
|
// 2. Überbesetzte erfahrene Mitarbeiter identifizieren und in Pool verschieben
|
||||||
@@ -276,6 +278,7 @@ export function enhancedPhaseCRepairValidate(
|
|||||||
severity: 'error',
|
severity: 'error',
|
||||||
message: `Leere Schicht: ${shift.id}`
|
message: `Leere Schicht: ${shift.id}`
|
||||||
});
|
});
|
||||||
|
repairContext.warnings.push(`Konnte leere Schicht ${shift.id} nicht beheben`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +305,7 @@ export function enhancedPhaseCRepairValidate(
|
|||||||
shiftId: shift.id,
|
shiftId: shift.id,
|
||||||
employeeId: experiencedAloneCheck.employeeId,
|
employeeId: experiencedAloneCheck.employeeId,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
message: `Erfahrener Mitarbeiter ${emp?.name || experiencedAloneCheck.employeeId} arbeitet allein, darf aber nicht alleine arbeiten`
|
message: `Erfahrener Mitarbeiter ${emp?.name || experiencedAloneCheck.employeeId} arbeitet allein in Schicht ${shift.id}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -325,26 +328,26 @@ export function enhancedPhaseCRepairValidate(
|
|||||||
managerShifts.forEach(shiftId => {
|
managerShifts.forEach(shiftId => {
|
||||||
const assignment = assignments[shiftId] || [];
|
const assignment = assignments[shiftId] || [];
|
||||||
|
|
||||||
// Manager allein -> KRITISCH (error)
|
// Manager allein
|
||||||
if (isManagerAlone(assignment, manager?.id)) {
|
if (isManagerAlone(assignment, manager?.id)) {
|
||||||
repairContext.violations.push({
|
repairContext.violations.push({
|
||||||
type: 'ManagerAlone',
|
type: 'ManagerAlone',
|
||||||
shiftId: shiftId,
|
shiftId: shiftId,
|
||||||
severity: 'error', // KRITISCH
|
severity: 'error',
|
||||||
message: `Manager allein in Schicht ${shiftId}`
|
message: `Manager allein in Schicht ${shiftId}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager + nur Neue -> NUR WARNUNG (warning)
|
// Manager + nur Neue
|
||||||
if (isManagerShiftWithOnlyNew(assignment, employeeMap, manager?.id)) {
|
if (isManagerShiftWithOnlyNew(assignment, employeeMap, manager?.id)) {
|
||||||
repairContext.violations.push({
|
repairContext.violations.push({
|
||||||
type: 'ManagerWithOnlyNew',
|
type: 'ManagerWithOnlyNew',
|
||||||
shiftId: shiftId,
|
shiftId: shiftId,
|
||||||
severity: 'warning', // NUR WARNUNG
|
severity: 'warning',
|
||||||
message: `Manager mit nur Neuen in Schicht ${shiftId}`
|
message: `Manager mit nur Neuen in Schicht ${shiftId}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Erstelle finale Violations-Liste
|
// Erstelle finale Violations-Liste
|
||||||
const uniqueViolations = repairContext.violations.filter((v, index, self) =>
|
const uniqueViolations = repairContext.violations.filter((v, index, self) =>
|
||||||
@@ -360,56 +363,40 @@ export function enhancedPhaseCRepairValidate(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const finalViolations = [
|
const finalViolations = [
|
||||||
// Nur ERROR-Violations als ERROR markieren
|
|
||||||
...uniqueViolations
|
...uniqueViolations
|
||||||
.filter(v => v.severity === 'error')
|
.filter(v => v.severity === 'error')
|
||||||
.map(v => `ERROR: ${v.message}`),
|
.map(v => `ERROR: ${v.message}`),
|
||||||
|
|
||||||
// WARNING-Violations als WARNING markieren
|
|
||||||
...uniqueViolations
|
...uniqueViolations
|
||||||
.filter(v => v.severity === 'warning')
|
.filter(v => v.severity === 'warning')
|
||||||
.map(v => `WARNING: ${v.message}`),
|
.map(v => `WARNING: ${v.message}`),
|
||||||
|
|
||||||
// Andere Warnungen als INFO (Aktionen)
|
|
||||||
...uniqueWarnings.map(w => `INFO: ${w}`)
|
...uniqueWarnings.map(w => `INFO: ${w}`)
|
||||||
];
|
];
|
||||||
|
|
||||||
// 9. FINALE ÜBERPRÜFUNG: Prüfe ob alle kritischen Probleme gelöst wurden
|
// 9. DETAILLIERTER REPARATUR-BERICHT
|
||||||
const resolutionCheck = checkAllProblemsResolved(
|
const resolutionReport = createDetailedResolutionReport(
|
||||||
assignments,
|
assignments,
|
||||||
employeeMap,
|
employeeMap,
|
||||||
shifts,
|
shifts,
|
||||||
managerShifts,
|
managerShifts,
|
||||||
finalViolations
|
repairContext
|
||||||
);
|
);
|
||||||
|
|
||||||
const resolutionReport = [
|
// Bestimme ob alle kritischen Probleme behoben wurden
|
||||||
'=== REPARATUR-BERICHT ===',
|
const criticalProblems = uniqueViolations.filter(v => v.severity === 'error');
|
||||||
`Aufgelöste Probleme: ${resolutionCheck.resolved.length}`,
|
const allProblemsResolved = criticalProblems.length === 0;
|
||||||
`Verbleibende Probleme: ${resolutionCheck.remaining.length}`,
|
|
||||||
`Alle kritischen Probleme behoben: ${resolutionCheck.allResolved ? '✅ JA' : '❌ NEIN'}`,
|
|
||||||
'',
|
|
||||||
'--- AUFGELÖSTE PROBLEME ---',
|
|
||||||
...(resolutionCheck.resolved.length > 0 ? resolutionCheck.resolved : ['Keine']),
|
|
||||||
'',
|
|
||||||
'--- VERBLEIBENDE PROBLEME ---',
|
|
||||||
...(resolutionCheck.remaining.length > 0 ? resolutionCheck.remaining : ['Keine']),
|
|
||||||
'',
|
|
||||||
'=== ENDE BERICHT ==='
|
|
||||||
];
|
|
||||||
|
|
||||||
console.log('📊 Enhanced Phase C completed:', {
|
console.log('📊 Enhanced Phase C completed:', {
|
||||||
poolSize: repairContext.unassignedPool.length,
|
totalActions: uniqueWarnings.length,
|
||||||
violations: uniqueViolations.length,
|
criticalProblems: criticalProblems.length,
|
||||||
warnings: uniqueWarnings.length,
|
warnings: uniqueViolations.filter(v => v.severity === 'warning').length,
|
||||||
allProblemsResolved: resolutionCheck.allResolved
|
allProblemsResolved
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assignments,
|
assignments,
|
||||||
violations: finalViolations,
|
violations: finalViolations,
|
||||||
resolutionReport,
|
resolutionReport,
|
||||||
allProblemsResolved: resolutionCheck.allResolved
|
allProblemsResolved
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +408,7 @@ export function scheduleWithManager(
|
|||||||
): SchedulingResult & { resolutionReport?: string[]; allProblemsResolved?: boolean } {
|
): SchedulingResult & { resolutionReport?: string[]; allProblemsResolved?: boolean } {
|
||||||
|
|
||||||
const assignments: Assignment = {};
|
const assignments: Assignment = {};
|
||||||
//const allViolations: string[] = [];
|
const allViolations: string[] = [];
|
||||||
|
|
||||||
// Initialisiere Zuweisungen
|
// Initialisiere Zuweisungen
|
||||||
shifts.forEach(shift => {
|
shifts.forEach(shift => {
|
||||||
@@ -444,14 +431,14 @@ export function scheduleWithManager(
|
|||||||
console.log('🔄 Starting Phase B: Enhanced Manager insertion');
|
console.log('🔄 Starting Phase B: Enhanced Manager insertion');
|
||||||
|
|
||||||
// Phase B: Erweiterte Manager-Einfügung
|
// Phase B: Erweiterte Manager-Einfügung
|
||||||
/*const phaseBResult = phaseBInsertManager(
|
const phaseBResult = phaseBInsertManager(
|
||||||
assignments,
|
assignments,
|
||||||
manager,
|
manager,
|
||||||
managerShifts,
|
managerShifts,
|
||||||
employees,
|
employees,
|
||||||
nonManagerShifts,
|
nonManagerShifts,
|
||||||
constraints
|
constraints
|
||||||
);*/
|
);
|
||||||
|
|
||||||
console.log('🔄 Starting Enhanced Phase C: Smart Repair & Validation');
|
console.log('🔄 Starting Enhanced Phase C: Smart Repair & Validation');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user