mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
added Validation rules
This commit is contained in:
25
backend/src/middleware/Validation/Access.md
Normal file
25
backend/src/middleware/Validation/Access.md
Normal file
@@ -0,0 +1,25 @@
|
||||
## User Settings
|
||||
|
||||
### \[UPDATE\] Personal availability
|
||||
* Only the employee themselves can manage their availability
|
||||
* Must select a valid shift plan with defined shifts
|
||||
* All changes require explicit save action
|
||||
|
||||
### \[VIEW\] ShiftPlan assignments
|
||||
* Published plans show actual assignments
|
||||
* Draft plans show preview assignments (if calculated)
|
||||
* Regular users can only view, not modify assignments
|
||||
|
||||
## System-wide
|
||||
|
||||
### \[ACCESS\] Role-based restrictions
|
||||
* `admin`: Full access to all features
|
||||
* `maintenance`: Access to shift plans and employee management (except admin users)
|
||||
* `user`: Read-only access to shift plans, can manage own availability and profile
|
||||
|
||||
### \[DATA\] Validation rules
|
||||
* Email addresses are automatically generated from firstname/lastname
|
||||
* Employee status (`isActive`) controls login and planning eligibility
|
||||
* Trainee status affects independence (`canWorkAlone`) automatically
|
||||
* Date ranges must be valid (start before end)
|
||||
* All required fields must be filled before form submission
|
||||
20
backend/src/middleware/Validation/Assignment.md
Normal file
20
backend/src/middleware/Validation/Assignment.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## Shift Assignment
|
||||
|
||||
### \[ACTION: update scheduled shift\]
|
||||
* Requires valid scheduled shift ID
|
||||
* Only updates assignedEmployees array
|
||||
* Requires authentication with valid token
|
||||
* Handles both JSON and non-JSON responses
|
||||
|
||||
### \[ACTION: assign shifts automatically\]
|
||||
* Requires shift plan, employees, and availabilities
|
||||
* Availability preferenceLevel must be 1, 2, or 3
|
||||
* Constraints must be an array (converts non-array to empty array)
|
||||
* All employees must have valid availability data
|
||||
|
||||
### \[ACTION: get scheduled shifts\]
|
||||
* Requires valid plan ID
|
||||
* Automatically fixes data structure inconsistencies:
|
||||
- timeSlotId mapping (handles both naming conventions)
|
||||
- requiredEmployees fallback to 2 if missing
|
||||
- assignedEmployees fallback to empty array if missing
|
||||
16
backend/src/middleware/Validation/Authentication.md
Normal file
16
backend/src/middleware/Validation/Authentication.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## Authentication
|
||||
|
||||
### \[ACTION: login\]
|
||||
* Requires valid email and password format
|
||||
* Server validates credentials before issuing token
|
||||
* Token and employee data stored in localStorage upon success
|
||||
|
||||
### \[ACTION: register\]
|
||||
* Requires email, password, and name
|
||||
* Role is optional during registration
|
||||
* Automatically logs in user after successful registration
|
||||
|
||||
### \[ACTION: access protected resources\]
|
||||
* Requires valid JWT token in Authorization header
|
||||
* Token is automatically retrieved from localStorage
|
||||
* Unauthorized requests (401) trigger automatic logout
|
||||
23
backend/src/middleware/Validation/DataIntegrity
Normal file
23
backend/src/middleware/Validation/DataIntegrity
Normal file
@@ -0,0 +1,23 @@
|
||||
## Data Integrity
|
||||
|
||||
### \[GENERAL\] API communication
|
||||
* All fetch requests include error handling
|
||||
* Failed responses throw descriptive errors
|
||||
* Token validation before protected operations
|
||||
* Automatic localStorage cleanup on logout
|
||||
|
||||
### \[GENERAL\] data persistence
|
||||
* Employee data cached in localStorage after login
|
||||
* Token automatically retrieved from localStorage
|
||||
* Data structure normalization for scheduled shifts
|
||||
|
||||
### \[GENERAL\] error handling
|
||||
* Network errors are caught and logged
|
||||
* HTTP errors include status codes and messages
|
||||
* Failed authentication triggers cleanup and logout
|
||||
|
||||
## Role & Permission Notes
|
||||
* The frontend services don't explicitly restrict actions by role
|
||||
* Role-based restrictions are likely handled by the backend
|
||||
* Frontend assumes user has permissions for requested operations
|
||||
* 401 responses indicate insufficient permissions at backend level
|
||||
67
backend/src/middleware/Validation/Employee.md
Normal file
67
backend/src/middleware/Validation/Employee.md
Normal file
@@ -0,0 +1,67 @@
|
||||
## Employee Management
|
||||
|
||||
### \[CREATE/UPDATE\] employee
|
||||
* All employee operations require authentication
|
||||
* Password changes require current password + new password
|
||||
* Only authenticated users can create/update employees
|
||||
|
||||
### \[ACTION: delete employee\]
|
||||
* Requires authentication
|
||||
* Server validates permissions before deletion
|
||||
|
||||
### \[ACTION: update availability\]
|
||||
* Requires employee ID and plan ID
|
||||
* Availability updates must include valid preference levels
|
||||
* Only authenticated users can update availabilities
|
||||
|
||||
### \[ACTION: update last login\]
|
||||
* Requires employee ID
|
||||
* Fails silently if update fails (logs error but doesn't block user)
|
||||
|
||||
## Employee
|
||||
|
||||
### \[CREATE\] Employee
|
||||
* `firstname` must not be empty
|
||||
* `lastname` must not be empty
|
||||
* `password` must be at least 6 characters (in create mode)
|
||||
* `employeeType` must be selected
|
||||
* Contract type validation:
|
||||
* `manager`, `apprentice` => `contractType` = flexible
|
||||
* `guest` => `contractType` = undefined/NONE
|
||||
* `personell` => `contractType` = small || large
|
||||
|
||||
### \[UPDATE\] Employee profile
|
||||
* `firstname` must not be empty
|
||||
* `lastname` must not be empty
|
||||
* Only the employee themselves or admins can update
|
||||
|
||||
### \[UPDATE\] Employee password
|
||||
* `newPassword` must be at least 6 characters
|
||||
* `newPassword` must match `confirmPassword`
|
||||
* For admin password reset: no `currentPassword` required
|
||||
* For self-password change: `currentPassword` required
|
||||
|
||||
### \[UPDATE\] Employee roles
|
||||
* Only users with role 'admin' can modify roles
|
||||
* At least one employee must maintain 'admin' role
|
||||
* Users cannot remove their own admin role
|
||||
|
||||
### \[UPDATE\] Employee availability
|
||||
* Only active employees can set availability
|
||||
* Contract type requirements:
|
||||
* `small` contract: minimum 2 available shifts (preference level 1 or 2)
|
||||
* `large` contract: minimum 3 available shifts (preference level 1 or 2)
|
||||
* `flexible` contract: no minimum requirement
|
||||
* Availability can only be set for valid shift patterns in selected plan
|
||||
* `shiftId` must be valid and exist in the current plan
|
||||
|
||||
### \[ACTION: delete\] Employee
|
||||
* Only users with role 'admin' can delete employees
|
||||
* Cannot delete yourself
|
||||
* Cannot delete the last admin user
|
||||
* User confirmation required before deletion
|
||||
|
||||
### \[ACTION: edit\] Employee
|
||||
* Admins can edit all employees
|
||||
* Maintenance users can edit non-admin employees or themselves
|
||||
* Regular users can only edit themselves
|
||||
66
backend/src/middleware/Validation/Shiftplan.md
Normal file
66
backend/src/middleware/Validation/Shiftplan.md
Normal file
@@ -0,0 +1,66 @@
|
||||
## Shift Plan Management
|
||||
|
||||
### \[CREATE\] shift plan
|
||||
* All operations require authentication
|
||||
* 401 responses trigger automatic logout
|
||||
* Scheduled shifts array is guaranteed to exist (empty array if none)
|
||||
|
||||
### \[CREATE\] shift plan from preset
|
||||
* presetName must match existing TEMPLATE_PRESETS
|
||||
* Requires name, startDate, and endDate
|
||||
* isTemplate is optional (defaults to false)
|
||||
|
||||
### \[UPDATE\] shift plan
|
||||
* Requires valid shift plan ID
|
||||
* Partial updates allowed
|
||||
* Authentication required
|
||||
|
||||
### \[ACTION: delete shift plan\]
|
||||
* Requires authentication
|
||||
* 401 responses trigger automatic logout
|
||||
|
||||
### \[ACTION: regenerate scheduled shifts\]
|
||||
* Requires valid plan ID
|
||||
* Authentication required
|
||||
* Fails silently if regeneration fails (logs error but continues)
|
||||
|
||||
### \[ACTION: clear assignments\]
|
||||
* Requires valid plan ID
|
||||
* Authentication required
|
||||
* Clears all employee assignments from scheduled shifts
|
||||
|
||||
## ShiftPlan
|
||||
|
||||
### \[CREATE\] ShiftPlan from template
|
||||
* `planName` must not be empty
|
||||
* `startDate` must be set
|
||||
* `endDate` must be set
|
||||
* `endDate` must be after `startDate`
|
||||
* `selectedPreset` must be chosen (template must be selected)
|
||||
* Only available template presets can be used
|
||||
|
||||
### \[ACTION: publish\] ShiftPlan
|
||||
* Plan must be in 'draft' status
|
||||
* All active employees must have set their availabilities for the plan
|
||||
* Only users with roles \['admin', 'maintenance'\] can publish
|
||||
* Assignment algorithm must not have critical violations (ERROR or ❌ KRITISCH)
|
||||
* employee && employee.contract_type === small => mind. 1 mal availability === 1 || availability === 2
|
||||
* employee && employee.contract_type === large => mind. 3 mal availability === 1 || availability === 2
|
||||
|
||||
### \[ACTION: recreate assignments\]
|
||||
* Plan must be in 'published' status
|
||||
* Only users with roles \['admin', 'maintenance'\] can recreate
|
||||
* User confirmation required before clearing all assignments
|
||||
|
||||
### \[ACTION: delete\] ShiftPlan
|
||||
* Only users with roles \['admin', 'maintenance'\] can delete
|
||||
* User confirmation required before deletion
|
||||
|
||||
### \[ACTION: edit\] ShiftPlan
|
||||
* Only users with roles \['admin', 'maintenance'\] can edit
|
||||
* Can only edit plans in 'draft' status
|
||||
|
||||
### \[UPDATE\] ShiftPlan shifts
|
||||
* `timeSlotId` must be selected from available time slots
|
||||
* `requiredEmployees` must be at least 1
|
||||
* `dayOfWeek` must be between 1-7
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import StepSetup, { Step } from '../../components/StepSetup/StepSetup';
|
||||
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
@@ -32,16 +31,16 @@ const useSetup = () => {
|
||||
const { checkSetupStatus } = useAuth();
|
||||
|
||||
const steps: SetupStep[] = [
|
||||
{
|
||||
id: 'password-setup',
|
||||
title: 'Passwort erstellen',
|
||||
subtitle: 'Legen Sie ein sicheres Passwort fest'
|
||||
},
|
||||
{
|
||||
id: 'profile-setup',
|
||||
title: 'Profilinformationen',
|
||||
subtitle: 'Geben Sie Ihre persönlichen Daten ein'
|
||||
},
|
||||
{
|
||||
id: 'password-setup',
|
||||
title: 'Passwort erstellen',
|
||||
subtitle: 'Legen Sie ein sicheres Passwort fest'
|
||||
},
|
||||
{
|
||||
id: 'confirmation',
|
||||
title: 'Bestätigung',
|
||||
@@ -51,18 +50,6 @@ const useSetup = () => {
|
||||
|
||||
// ===== VALIDIERUNGS-FUNKTIONEN =====
|
||||
const validateStep1 = (): boolean => {
|
||||
if (formData.password.length < 6) {
|
||||
setError('Das Passwort muss mindestens 6 Zeichen lang sein.');
|
||||
return false;
|
||||
}
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setError('Die Passwörter stimmen nicht überein.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateStep2 = (): boolean => {
|
||||
if (!formData.firstname.trim()) {
|
||||
setError('Bitte geben Sie einen Vornamen ein.');
|
||||
return false;
|
||||
@@ -74,6 +61,18 @@ const useSetup = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateStep2 = (): boolean => {
|
||||
if (formData.password.length < 6) {
|
||||
setError('Das Passwort muss mindestens 6 Zeichen lang sein.');
|
||||
return false;
|
||||
}
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setError('Die Passwörter stimmen nicht überein.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateCurrentStep = (stepIndex: number): boolean => {
|
||||
switch (stepIndex) {
|
||||
case 0:
|
||||
@@ -225,69 +224,6 @@ interface StepContentProps {
|
||||
}
|
||||
|
||||
const Step1Content: React.FC<StepContentProps> = ({
|
||||
formData,
|
||||
onInputChange
|
||||
}) => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<div>
|
||||
<label style={{
|
||||
display: 'block',
|
||||
marginBottom: '0.5rem',
|
||||
fontWeight: '600',
|
||||
color: '#495057'
|
||||
}}>
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={onInputChange}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0.75rem',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '6px',
|
||||
fontSize: '1rem',
|
||||
transition: 'border-color 0.3s ease'
|
||||
}}
|
||||
placeholder="Mindestens 6 Zeichen"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{
|
||||
display: 'block',
|
||||
marginBottom: '0.5rem',
|
||||
fontWeight: '600',
|
||||
color: '#495057'
|
||||
}}>
|
||||
Passwort bestätigen
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="confirmPassword"
|
||||
value={formData.confirmPassword}
|
||||
onChange={onInputChange}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0.75rem',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '6px',
|
||||
fontSize: '1rem',
|
||||
transition: 'border-color 0.3s ease'
|
||||
}}
|
||||
placeholder="Passwort wiederholen"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Step2Content: React.FC<StepContentProps> = ({
|
||||
formData,
|
||||
onInputChange,
|
||||
getEmailPreview
|
||||
@@ -378,6 +314,70 @@ const Step2Content: React.FC<StepContentProps> = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
const Step2Content: React.FC<StepContentProps> = ({
|
||||
formData,
|
||||
onInputChange
|
||||
}) => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<div>
|
||||
<label style={{
|
||||
display: 'block',
|
||||
marginBottom: '0.5rem',
|
||||
fontWeight: '600',
|
||||
color: '#495057'
|
||||
}}>
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={onInputChange}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0.75rem',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '6px',
|
||||
fontSize: '1rem',
|
||||
transition: 'border-color 0.3s ease'
|
||||
}}
|
||||
placeholder="Mindestens 6 Zeichen"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{
|
||||
display: 'block',
|
||||
marginBottom: '0.5rem',
|
||||
fontWeight: '600',
|
||||
color: '#495057'
|
||||
}}>
|
||||
Passwort bestätigen
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="confirmPassword"
|
||||
value={formData.confirmPassword}
|
||||
onChange={onInputChange}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0.75rem',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '6px',
|
||||
fontSize: '1rem',
|
||||
transition: 'border-color 0.3s ease'
|
||||
}}
|
||||
placeholder="Passwort wiederholen"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Step3Content: React.FC<StepContentProps> = ({
|
||||
formData,
|
||||
getEmailPreview
|
||||
@@ -477,6 +477,83 @@ const Setup: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Inline Step Indicator Komponente
|
||||
const StepIndicator: React.FC = () => (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '2.5rem',
|
||||
position: 'relative',
|
||||
width: '100%'
|
||||
}}>
|
||||
{/* Verbindungslinien */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '12px',
|
||||
left: '0',
|
||||
right: '0',
|
||||
height: '2px',
|
||||
backgroundColor: '#e9ecef',
|
||||
zIndex: 1
|
||||
}} />
|
||||
|
||||
{steps.map((step, index) => {
|
||||
const isCompleted = index < currentStep;
|
||||
const isCurrent = index === currentStep;
|
||||
const isClickable = index <= currentStep + 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={step.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
zIndex: 2,
|
||||
position: 'relative',
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => isClickable && handleStepChange(index)}
|
||||
disabled={!isClickable}
|
||||
style={{
|
||||
width: '28px',
|
||||
height: '28px',
|
||||
borderRadius: '50%',
|
||||
border: '2px solid',
|
||||
borderColor: isCompleted || isCurrent ? '#51258f' : '#e9ecef',
|
||||
backgroundColor: isCompleted ? '#51258f' : 'white',
|
||||
color: isCompleted ? 'white' : (isCurrent ? '#51258f' : '#6c757d'),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
cursor: isClickable ? 'pointer' : 'not-allowed',
|
||||
transition: 'all 0.3s ease',
|
||||
marginBottom: '8px'
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</button>
|
||||
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
fontSize: '14px',
|
||||
fontWeight: isCurrent ? '600' : '400',
|
||||
color: isCurrent ? '#51258f' : '#6c757d'
|
||||
}}>
|
||||
{step.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh',
|
||||
@@ -533,18 +610,8 @@ const Setup: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* StepSetup Komponente - nur die Steps ohne Beschreibung */}
|
||||
<div style={{ marginBottom: '2.5rem' }}>
|
||||
<StepSetup
|
||||
steps={steps.map(step => ({ ...step, subtitle: undefined }))}
|
||||
current={currentStep}
|
||||
onChange={handleStepChange}
|
||||
orientation="horizontal"
|
||||
clickable={true}
|
||||
size="md"
|
||||
animated={true}
|
||||
/>
|
||||
</div>
|
||||
{/* Inline Step Indicator */}
|
||||
<StepIndicator />
|
||||
|
||||
{/* Fehleranzeige */}
|
||||
{error && (
|
||||
|
||||
Reference in New Issue
Block a user