mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
ein runtime error
This commit is contained in:
@@ -60,7 +60,7 @@ CREATE TABLE IF NOT EXISTS scheduled_shifts (
|
||||
required_employees INTEGER NOT NULL CHECK (required_employees >= 1 AND required_employees <= 10) DEFAULT 2,
|
||||
assigned_employees TEXT DEFAULT '[]', -- JSON array of employee IDs
|
||||
FOREIGN KEY (plan_id) REFERENCES shift_plans(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (time_slot_id) REFERENCES time_slots(id),
|
||||
FOREIGN KEY (time_slot_id) REFERENCES time_slots(id) ON DELETE CASCADE,
|
||||
UNIQUE(plan_id, date, time_slot_id)
|
||||
);
|
||||
|
||||
@@ -89,7 +89,7 @@ CREATE TABLE IF NOT EXISTS employee_availability (
|
||||
notes TEXT,
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (plan_id) REFERENCES shift_plans(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (time_slot_id) REFERENCES time_slots(id),
|
||||
FOREIGN KEY (time_slot_id) REFERENCES time_slots(id) ON DELETE CASCADE,
|
||||
UNIQUE(employee_id, plan_id, day_of_week, time_slot_id)
|
||||
);
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export function calculateTotalRequiredEmployees(plan: ShiftPlan): number {
|
||||
return plan.shifts.reduce((total, shift) => total + shift.requiredEmployees, 0);
|
||||
}
|
||||
|
||||
export function getScheduledShiftByDateAndTime(
|
||||
/*export function getScheduledShiftByDateAndTime(
|
||||
plan: ShiftPlan,
|
||||
date: string,
|
||||
timeSlotId: string
|
||||
@@ -86,7 +86,7 @@ export function getScheduledShiftByDateAndTime(
|
||||
return plan.scheduledShifts?.find(shift =>
|
||||
shift.date === date && shift.timeSlotId === timeSlotId
|
||||
);
|
||||
}
|
||||
}*/
|
||||
|
||||
export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
30
backend/src/routes/scheduledShifts.ts
Normal file
30
backend/src/routes/scheduledShifts.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// backend/src/routes/scheduledShifts.ts
|
||||
import express from 'express';
|
||||
import { authMiddleware, requireRole } from '../middleware/auth.js';
|
||||
import {
|
||||
regenerateScheduledShifts,
|
||||
generateScheduledShiftsForPlan,
|
||||
getScheduledShift,
|
||||
getScheduledShiftsFromPlan,
|
||||
updateScheduledShift
|
||||
} from '../controllers/shiftPlanController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authMiddleware);
|
||||
|
||||
|
||||
router.post('/:id/generate-shifts', requireRole(['admin', 'instandhalter']), generateScheduledShiftsForPlan);
|
||||
|
||||
router.post('/:id/regenerate-shifts', requireRole(['admin', 'instandhalter']), regenerateScheduledShifts);
|
||||
|
||||
// GET all scheduled shifts for a plan
|
||||
router.get('/plan/:planId', requireRole(['admin']), getScheduledShiftsFromPlan);
|
||||
|
||||
// GET specific scheduled shift
|
||||
router.get('/:id', requireRole(['admin']), getScheduledShift);
|
||||
|
||||
// UPDATE scheduled shift
|
||||
router.put('/:id', requireRole(['admin']), updateScheduledShift);
|
||||
|
||||
export default router;
|
||||
@@ -7,15 +7,8 @@ import {
|
||||
createShiftPlan,
|
||||
updateShiftPlan,
|
||||
deleteShiftPlan,
|
||||
//getTemplates,
|
||||
//createFromTemplate,
|
||||
createFromPreset,
|
||||
revertToDraft,
|
||||
regenerateScheduledShifts,
|
||||
generateScheduledShiftsForPlan,
|
||||
getScheduledShift,
|
||||
getScheduledShiftsFromPlan,
|
||||
updateScheduledShift
|
||||
} from '../controllers/shiftPlanController.js';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -51,21 +44,4 @@ router.delete('/:id', requireRole(['admin', 'instandhalter']), deleteShiftPlan);
|
||||
// PUT revert published plan to draft
|
||||
router.put('/:id/revert-to-draft', requireRole(['admin', 'instandhalter']), revertToDraft);
|
||||
|
||||
|
||||
|
||||
// SCHEDULED SHIFTS
|
||||
|
||||
router.post('/:id/generate-shifts', requireRole(['admin', 'instandhalter']), generateScheduledShiftsForPlan);
|
||||
|
||||
router.post('/:id/regenerate-shifts', requireRole(['admin', 'instandhalter']), regenerateScheduledShifts);
|
||||
|
||||
// GET all scheduled shifts for a plan (for debugging)
|
||||
router.get('/plan/:planId', requireRole(['admin']), getScheduledShiftsFromPlan);
|
||||
|
||||
// GET specific scheduled shift
|
||||
router.get('/:id', requireRole(['admin']), getScheduledShift);
|
||||
|
||||
// UPDATE scheduled shift
|
||||
router.put('/:id', requireRole(['admin']), updateScheduledShift);
|
||||
|
||||
export default router;
|
||||
@@ -8,6 +8,7 @@ import authRoutes from './routes/auth.js';
|
||||
import employeeRoutes from './routes/employees.js';
|
||||
import shiftPlanRoutes from './routes/shiftPlans.js';
|
||||
import setupRoutes from './routes/setup.js';
|
||||
import scheduledShifts from './routes/scheduledShifts.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3002;
|
||||
@@ -21,6 +22,7 @@ app.use('/api/setup', setupRoutes);
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/employees', employeeRoutes);
|
||||
app.use('/api/shift-plans', shiftPlanRoutes);
|
||||
app.use('/api/scheduled-shifts', scheduledShifts);
|
||||
|
||||
// Error handling middleware should come after routes
|
||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
||||
@@ -11,7 +11,6 @@ export interface ShiftPlan {
|
||||
createdAt: string;
|
||||
timeSlots: TimeSlot[];
|
||||
shifts: Shift[];
|
||||
scheduledShifts?: ScheduledShift[]; // Only for non-template plans with dates
|
||||
}
|
||||
|
||||
export interface TimeSlot {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// backend/src/models/helpers/shiftPlanHelpers.ts
|
||||
import { shiftAssignmentService } from '../../services/shiftAssignmentService.js';
|
||||
import { ShiftPlan, Shift, ScheduledShift, TimeSlot } from '../ShiftPlan.js';
|
||||
|
||||
// Validation helpers
|
||||
@@ -78,15 +79,16 @@ export function calculateTotalRequiredEmployees(plan: ShiftPlan): number {
|
||||
return plan.shifts.reduce((total, shift) => total + shift.requiredEmployees, 0);
|
||||
}
|
||||
|
||||
export function getScheduledShiftByDateAndTime(
|
||||
/*export async function getScheduledShiftByDateAndTime(
|
||||
plan: ShiftPlan,
|
||||
date: string,
|
||||
timeSlotId: string
|
||||
): ScheduledShift | undefined {
|
||||
return plan.scheduledShifts?.find(shift =>
|
||||
shift.date === date && shift.timeSlotId === timeSlotId
|
||||
): Promise<ScheduledShift | undefined> {
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(plan.id);
|
||||
return scheduledShifts.find(
|
||||
shift => shift.date === date && shift.timeSlotId === timeSlotId
|
||||
);
|
||||
}
|
||||
}*/
|
||||
|
||||
export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
@@ -4,8 +4,9 @@ import { Link } from 'react-router-dom';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import { shiftPlanService } from '../../services/shiftPlanService';
|
||||
import { employeeService } from '../../services/employeeService';
|
||||
import { ShiftPlan } from '../../models/ShiftPlan';
|
||||
import { ShiftPlan, ScheduledShift } from '../../models/ShiftPlan';
|
||||
import { Employee } from '../../models/Employee';
|
||||
import { shiftAssignmentService } from '../../services/shiftAssignmentService';
|
||||
|
||||
interface DashboardData {
|
||||
currentShiftPlan: ShiftPlan | null;
|
||||
@@ -29,6 +30,7 @@ interface DashboardData {
|
||||
const Dashboard: React.FC = () => {
|
||||
const { user, hasRole } = useAuth();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentPlanShifts, setCurrentPlanShifts] = useState<ScheduledShift[]>([]);
|
||||
const [data, setData] = useState<DashboardData>({
|
||||
currentShiftPlan: null,
|
||||
upcomingShifts: [],
|
||||
@@ -53,13 +55,25 @@ const Dashboard: React.FC = () => {
|
||||
|
||||
const [shiftPlans, employees] = await Promise.all([
|
||||
shiftPlanService.getShiftPlans(),
|
||||
employeeService.getEmployees()
|
||||
employeeService.getEmployees(),
|
||||
]);
|
||||
|
||||
// Find current shift plan
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const currentPlan = findCurrentShiftPlan(shiftPlans, today);
|
||||
|
||||
// Load shifts for current plan
|
||||
if (currentPlan) {
|
||||
const shifts = await shiftAssignmentService.getScheduledShiftsForPlan(currentPlan.id);
|
||||
setCurrentPlanShifts(shifts);
|
||||
} else {
|
||||
setCurrentPlanShifts([]);
|
||||
}
|
||||
|
||||
console.log('📊 Loaded data:', {
|
||||
plans: shiftPlans.length,
|
||||
employees: employees.length,
|
||||
plansWithShifts: shiftPlans.filter(p => p.scheduledShifts && p.scheduledShifts.length > 0).length
|
||||
currentPlanShifts
|
||||
});
|
||||
|
||||
// Debug: Log plan details
|
||||
@@ -68,14 +82,14 @@ const Dashboard: React.FC = () => {
|
||||
status: plan.status,
|
||||
startDate: plan.startDate,
|
||||
endDate: plan.endDate,
|
||||
scheduledShifts: plan.scheduledShifts?.length || 0,
|
||||
//scheduledShifts: plan.scheduledShifts?.length || 0,
|
||||
isTemplate: plan.isTemplate
|
||||
});
|
||||
});
|
||||
|
||||
// Find current shift plan (published and current date within range)
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const currentPlan = findCurrentShiftPlan(shiftPlans, today);
|
||||
//const today = new Date().toISOString().split('T')[0];
|
||||
//const currentPlan = findCurrentShiftPlan(shiftPlans, today);
|
||||
|
||||
// Get user's upcoming shifts
|
||||
const userShifts = await loadUserUpcomingShifts(shiftPlans, today);
|
||||
@@ -96,7 +110,7 @@ const Dashboard: React.FC = () => {
|
||||
|
||||
console.log('✅ Dashboard data loaded:', {
|
||||
currentPlan: currentPlan?.name,
|
||||
userShifts: userShifts.length,
|
||||
//userShifts: userShifts.length,
|
||||
teamStats,
|
||||
recentPlans: recentPlans.length
|
||||
});
|
||||
@@ -142,13 +156,14 @@ const Dashboard: React.FC = () => {
|
||||
|
||||
// Check each plan for user assignments
|
||||
for (const plan of shiftPlans) {
|
||||
if (plan.status !== 'published' || !plan.scheduledShifts || plan.scheduledShifts.length === 0) {
|
||||
const scheduledShifts = (await shiftAssignmentService.getScheduledShiftsForPlan(plan.id));
|
||||
if (plan.status !== 'published' || scheduledShifts.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`🔍 Checking plan ${plan.name} for user shifts:`, plan.scheduledShifts.length);
|
||||
console.log(`🔍 Checking plan ${plan.name} for user shifts:`, scheduledShifts.length);
|
||||
|
||||
for (const scheduledShift of plan.scheduledShifts) {
|
||||
for (const scheduledShift of scheduledShifts) {
|
||||
// Ensure assignedEmployees is an array
|
||||
const assignedEmployees = Array.isArray(scheduledShift.assignedEmployees)
|
||||
? scheduledShift.assignedEmployees
|
||||
@@ -237,16 +252,19 @@ const Dashboard: React.FC = () => {
|
||||
return `${start} - ${end}`;
|
||||
};
|
||||
|
||||
const calculatePlanProgress = (plan: ShiftPlan): { covered: number; total: number; percentage: number } => {
|
||||
if (!plan.scheduledShifts || plan.scheduledShifts.length === 0) {
|
||||
const calculatePlanProgress = (plan: ShiftPlan, shifts: ScheduledShift[]): {
|
||||
covered: number; total: number; percentage: number
|
||||
} => {
|
||||
if (!plan.id || shifts.length === 0) {
|
||||
console.log(`📊 Plan ${plan.name} has no scheduled shifts`);
|
||||
return { covered: 0, total: 0, percentage: 0 };
|
||||
}
|
||||
|
||||
const totalShifts = plan.scheduledShifts.length;
|
||||
const coveredShifts = plan.scheduledShifts.filter(shift => {
|
||||
const assigned = Array.isArray(shift.assignedEmployees) ? shift.assignedEmployees : [];
|
||||
return assigned.length > 0;
|
||||
const currentDate = new Date();
|
||||
const totalShifts = shifts.length;
|
||||
const coveredShifts = shifts.filter(shift => {
|
||||
const shiftDate = new Date(shift.date);
|
||||
return currentDate > shiftDate;
|
||||
}).length;
|
||||
|
||||
const percentage = totalShifts > 0 ? Math.round((coveredShifts / totalShifts) * 100) : 0;
|
||||
@@ -302,7 +320,7 @@ const Dashboard: React.FC = () => {
|
||||
<div><strong>End Date:</strong> {data.currentShiftPlan.endDate}</div>
|
||||
<div><strong>Shifts Defined:</strong> {data.currentShiftPlan.shifts?.length || 0}</div>
|
||||
<div><strong>Time Slots:</strong> {data.currentShiftPlan.timeSlots?.length || 0}</div>
|
||||
<div><strong>Scheduled Shifts:</strong> {data.currentShiftPlan.scheduledShifts?.length || 0}</div>
|
||||
<div><strong>Scheduled Shifts:</strong> {data.currentShiftPlan.shifts.length || 0}</div>
|
||||
|
||||
{data.currentShiftPlan.shifts && data.currentShiftPlan.shifts.length > 0 && (
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
@@ -319,7 +337,9 @@ const Dashboard: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const progress = data.currentShiftPlan ? calculatePlanProgress(data.currentShiftPlan) : { covered: 0, total: 0, percentage: 0 };
|
||||
const progress = data.currentShiftPlan
|
||||
? calculatePlanProgress(data.currentShiftPlan, currentPlanShifts)
|
||||
: { covered: 0, total: 0, percentage: 0 };
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ShiftPlan, TimeSlot } from '../../models/ShiftPlan';
|
||||
import { Employee, EmployeeAvailability } from '../../models/Employee';
|
||||
import { useNotification } from '../../contexts/NotificationContext';
|
||||
import { formatDate, formatTime } from '../../utils/foramatters';
|
||||
import { isScheduledShift } from '../../models/helpers';
|
||||
|
||||
// Local interface extensions (same as AvailabilityManager)
|
||||
interface ExtendedTimeSlot extends TimeSlot {
|
||||
@@ -148,7 +149,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
return date.getDay() === 0 ? 7 : date.getDay();
|
||||
};
|
||||
|
||||
const debugManagerAvailability = () => {
|
||||
/*const debugManagerAvailability = () => {
|
||||
if (!shiftPlan || !employees.length || !availabilities.length) return;
|
||||
|
||||
const manager = employees.find(emp => emp.role === 'admin');
|
||||
@@ -196,7 +197,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};*/
|
||||
|
||||
const handlePreviewAssignment = async () => {
|
||||
if (!shiftPlan) return;
|
||||
@@ -282,13 +283,15 @@ const ShiftPlanView: React.FC = () => {
|
||||
|
||||
console.log('🔄 Starting to publish assignments...');
|
||||
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
|
||||
// Debug: Check if scheduled shifts exist
|
||||
if (!shiftPlan.scheduledShifts || shiftPlan.scheduledShifts.length === 0) {
|
||||
if (!scheduledShifts || scheduledShifts.length === 0) {
|
||||
throw new Error('No scheduled shifts found in the plan');
|
||||
}
|
||||
|
||||
// Update scheduled shifts with assignments
|
||||
const updatePromises = shiftPlan.scheduledShifts.map(async (scheduledShift) => {
|
||||
const updatePromises = scheduledShifts.map(async (scheduledShift) => {
|
||||
const assignedEmployees = assignmentResult.assignments[scheduledShift.id] || [];
|
||||
|
||||
console.log(`📝 Updating shift ${scheduledShift.id} with`, assignedEmployees.length, 'employees');
|
||||
@@ -375,10 +378,11 @@ const ShiftPlanView: React.FC = () => {
|
||||
message: 'Schichtplan wurde erfolgreich zurück in den Entwurfsstatus gesetzt. Alle Daten wurden neu geladen.'
|
||||
});
|
||||
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
console.log('Scheduled shifts after revert:', {
|
||||
hasScheduledShifts: !!shiftPlan.scheduledShifts,
|
||||
count: shiftPlan.scheduledShifts?.length || 0,
|
||||
firstFew: shiftPlan.scheduledShifts?.slice(0, 3)
|
||||
hasScheduledShifts: !! scheduledShifts,
|
||||
count: scheduledShifts.length || 0,
|
||||
firstFew: scheduledShifts?.slice(0, 3)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
@@ -419,8 +423,15 @@ const ShiftPlanView: React.FC = () => {
|
||||
};
|
||||
|
||||
// Render timetable using the same structure as AvailabilityManager
|
||||
const renderTimetable = () => {
|
||||
const renderTimetable = async () => {
|
||||
const { days, allTimeSlots, timeSlotsByDay } = getTimetableData();
|
||||
if (!shiftPlan?.id) {
|
||||
console.warn("Shift plan ID is missing");
|
||||
return []; // safely exit
|
||||
}
|
||||
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
|
||||
|
||||
if (days.length === 0 || allTimeSlots.length === 0) {
|
||||
return (
|
||||
@@ -524,9 +535,11 @@ const ShiftPlanView: React.FC = () => {
|
||||
let assignedEmployees: string[] = [];
|
||||
let displayText = '';
|
||||
|
||||
if (shiftPlan?.status === 'published' && shiftPlan.scheduledShifts) {
|
||||
|
||||
|
||||
if (shiftPlan?.status === 'published' && scheduledShifts) {
|
||||
// For published plans, use actual assignments from scheduled shifts
|
||||
const scheduledShift = shiftPlan.scheduledShifts.find(scheduled => {
|
||||
const scheduledShift = scheduledShifts.find(scheduled => {
|
||||
const scheduledDayOfWeek = getDayOfWeek(scheduled.date);
|
||||
return scheduledDayOfWeek === weekday.id &&
|
||||
scheduled.timeSlotId === timeSlot.id;
|
||||
@@ -541,7 +554,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
}
|
||||
} else if (assignmentResult) {
|
||||
// For draft with preview, use assignment result
|
||||
const scheduledShift = shiftPlan?.scheduledShifts?.find(scheduled => {
|
||||
const scheduledShift = scheduledShifts?.find(scheduled => {
|
||||
const scheduledDayOfWeek = getDayOfWeek(scheduled.date);
|
||||
return scheduledDayOfWeek === weekday.id &&
|
||||
scheduled.timeSlotId === timeSlot.id;
|
||||
@@ -594,8 +607,9 @@ const ShiftPlanView: React.FC = () => {
|
||||
if (loading) return <div>Lade Schichtplan...</div>;
|
||||
if (!shiftPlan) return <div>Schichtplan nicht gefunden</div>;
|
||||
|
||||
const availabilityStatus = getAvailabilityStatus();
|
||||
const { days, allTimeSlots } = getTimetableData();
|
||||
const availabilityStatus = getAvailabilityStatus();
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { authService } from './authService';
|
||||
import { scheduleWithManager } from './scheduling/shiftScheduler';
|
||||
import { transformToSchedulingData } from './scheduling/dataAdapter';
|
||||
import { AssignmentResult, WeeklyPattern } from './scheduling/types';
|
||||
import { isScheduledShift } from '../models/helpers';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3002/api/scheduled-shifts';
|
||||
|
||||
@@ -130,7 +131,7 @@ export class ShiftAssignmentService {
|
||||
console.log('🔄 Starting enhanced scheduling algorithm...');
|
||||
|
||||
// Get defined shifts for the first week
|
||||
const definedShifts = this.getDefinedShifts(shiftPlan);
|
||||
const definedShifts = await this.getDefinedShifts(shiftPlan);
|
||||
const firstWeekShifts = this.getFirstWeekShifts(definedShifts);
|
||||
|
||||
console.log('📊 First week analysis:', {
|
||||
@@ -399,8 +400,15 @@ export class ShiftAssignmentService {
|
||||
|
||||
// ========== EXISTING HELPER METHODS ==========
|
||||
|
||||
private static getDefinedShifts(shiftPlan: ShiftPlan): ScheduledShift[] {
|
||||
if (!shiftPlan.scheduledShifts) return [];
|
||||
static async getDefinedShifts(shiftPlan: ShiftPlan): Promise<ScheduledShift[]> {
|
||||
let scheduledShifts: ScheduledShift[] = [];
|
||||
try {
|
||||
scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
} catch (err) {
|
||||
console.error("Failed to load scheduled shifts:", err);
|
||||
return [];
|
||||
}
|
||||
if (scheduledShifts.length) return [];
|
||||
|
||||
const definedShiftPatterns = new Set(
|
||||
shiftPlan.shifts.map(shift =>
|
||||
@@ -408,7 +416,7 @@ export class ShiftAssignmentService {
|
||||
)
|
||||
);
|
||||
|
||||
const definedShifts = shiftPlan.scheduledShifts.filter(scheduledShift => {
|
||||
const definedShifts = scheduledShifts.filter(scheduledShift => {
|
||||
const dayOfWeek = this.getDayOfWeek(scheduledShift.date);
|
||||
const pattern = `${dayOfWeek}-${scheduledShift.timeSlotId}`;
|
||||
return definedShiftPatterns.has(pattern);
|
||||
|
||||
Reference in New Issue
Block a user