Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ZMS-3253 ZMS-1891 ZMS-3415): Replicate frontend validation in the backend for the availability opening hours to improve data integrity and frontend validation messaging #685

Open
wants to merge 89 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
310f536
fix(ZMS-3253): validate and block adding conflicting openingtimes
Nov 12, 2024
bbd32b1
fix(ZMS-3253): add backend validation for opening hours and improve f…
Nov 14, 2024
5543f36
fix(ZMS-3253): fix zmsentities unit test
Nov 14, 2024
03bc86e
fix(ZMS-3253): fix zmsdb unit test
Nov 14, 2024
ffcdb0a
fix(ZMS-3253): add logging to zmsapi tests
Nov 14, 2024
47565c8
fix(ZMS-3253): fix one zmsapi unit test
Nov 14, 2024
3f8030e
fix(ZMS-3253): try fix one zmsapi unit test
Nov 14, 2024
f5e08ed
fix(ZMS-3253): try fix one zmsapi unit test
Nov 14, 2024
e4b36ea
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
e5f123c
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
9528271
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
770e847
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
4a79605
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
cdd0d8c
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
8a1aa79
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
09c10fb
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
afd6d5d
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
ce5d795
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
07f74c8
Revert "fix(ZMS-3253): try fix some unit tests"
Nov 14, 2024
da65ff3
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
93b2820
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
e0d41c4
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
bde21f3
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
0cd0ad1
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
29b7cff
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
9e11157
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
c2df444
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
5faea71
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
4cd3618
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
4840338
fix(ZMS-3253): try fix some unit tests
Nov 14, 2024
c8262a0
fix(ZMS-3253): backend validation work in AvailabilityAdd
Nov 15, 2024
8281b0a
fix(ZMS-3253): correct backend validation for timeoverlaps
Nov 15, 2024
390aba7
fix(ZMS-3253): fix a validation
Nov 15, 2024
78013ea
fix(ZMS-3253): try fixing a test
Nov 15, 2024
8a07621
fix(ZMS-3253): try fixing a test
Nov 15, 2024
7892fe0
fix(ZMS-3253): try fixing a test
Nov 15, 2024
9522585
fix(ZMS-3253): try fixing a test
Nov 15, 2024
d65ccb8
fix(ZMS-3253): try fixing availability not found test
Nov 15, 2024
3fa84a1
fix(ZMS-3253): try fixing availability mockdata for testRendering
Nov 15, 2024
317d127
fix(ZMS-3253): try fixing availability mockdata for testRendering
Nov 15, 2024
c02e4e3
fix(ZMS-3253): try fixing availability mockdata for testRendering
Nov 15, 2024
c75e3c4
fix(ZMS-3253): try fixing availability mockdata for testRendering
Nov 15, 2024
2b39546
fix(ZMS-3253): try fixing availability mockdata for testRendering
Nov 15, 2024
acb62ac
fix(ZMS-3253): try fix missing scope testRendering
Nov 15, 2024
a16b2e4
fix(ZMS-3253): fix zmsadmin availabilities conflict test
Nov 15, 2024
1bd40cb
fix(ZMS-3253): show availability opening hour conflicts in the future…
Nov 15, 2024
4025319
fix(ZMS-3253): remove error_logs
Nov 15, 2024
95dcef0
fix(ZMS-3253): refactor function logic getDateTimeRangeFromList to on…
Nov 15, 2024
39ef3a6
Merge remote-tracking branch 'origin/next' into bugfix-zms-3253-valid…
Nov 15, 2024
3eb8912
fix(ZMS-3253): renable twig cache
Nov 15, 2024
46c0af9
fix(ZMS-3253): renable twig cache
Nov 15, 2024
fdd0c72
fix(ZMS-3253): fix unit test
ThomasAFink Nov 18, 2024
f319de3
fix(ZMS-3253): fix unit test
ThomasAFink Nov 18, 2024
b8fd6b6
fix(ZMS-3253): fix unit test
ThomasAFink Nov 18, 2024
a2691c1
fix(ZMS-3253): fix unit test
ThomasAFink Nov 18, 2024
fc26e13
fix(ZMS-3253): fix unit test
ThomasAFink Nov 18, 2024
bffc9fa
fix(ZMS-3253): fix unit test
ThomasAFink Nov 18, 2024
5784eb4
fix(ZMS-3253): try fix unit test
ThomasAFink Nov 18, 2024
51e25ca
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
4f2a125
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
e8cae55
fix(ZMS-3253): remove space
ThomasAFink Nov 18, 2024
e0f4fa6
fix(ZMS-3253): remove space
ThomasAFink Nov 18, 2024
d17ffa2
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
8a41469
fix(ZMS-3253): add unit tests for testing validation availability ope…
ThomasAFink Nov 18, 2024
e230b26
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
5aadb7b
fix(ZMS-3253): add unit tests for testing validation availability ope…
ThomasAFink Nov 18, 2024
246b4d1
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
17dcb07
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
e984774
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
d5135e2
fix(ZMS-3253): add unit tests for testing overlapping availability op…
ThomasAFink Nov 18, 2024
ca99ef2
fix(ZMS-3253): add unit tests for testing validation availability ope…
ThomasAFink Nov 18, 2024
d5c6051
fix(ZMS-3253): add unit tests for testing validation availability ope…
ThomasAFink Nov 18, 2024
5527c58
fix(ZMS-3253): refactor object creation
ThomasAFink Nov 18, 2024
ad89835
fix(ZMS-3253): refactor exception messages
ThomasAFink Nov 18, 2024
2a7d978
fix(ZMS-3253): clean up commented code
ThomasAFink Nov 18, 2024
ea55b27
fix(ZMS-3253): fix grammar
ThomasAFink Nov 20, 2024
932fe5f
fix(ZMS-3253): comment error_log
ThomasAFink Nov 20, 2024
5aa9d6b
fix(ZMS-3253): Improve frontend validation for opening hours availabi…
ThomasAFink Nov 20, 2024
fb7efb7
cleanup(ZMS-3415): remove invalid text instruction for graph view ope…
ThomasAFink Nov 21, 2024
1fd8f65
fix(ZMS-1891): Add missing frontend validation for timepicker
Nov 22, 2024
f8f536f
Merge branch 'next' into bugfix-zms-3253-validation-opening-hours-of-…
Nov 22, 2024
e4981ff
fix(ZMS-1891): Improve frontend validation for time formats
Nov 22, 2024
7916a70
fix(ZMS-3253): fix frontend exclusion availability validation
ThomasAFink Dec 3, 2024
462be60
fix(ZMS-3253): fix backend exclusion availability validation
ThomasAFink Dec 4, 2024
46db273
fix(ZMS-3253): allow exclusion availability on current date
ThomasAFink Dec 4, 2024
490ddce
fix(ZMS-3253): improve validation and cleanup code
ThomasAFink Dec 4, 2024
2ce2363
fix(ZMS-3253): move js spinner
ThomasAFink Dec 4, 2024
735c579
Merge remote-tracking branch 'origin/next' into bugfix-zms-3253-valid…
ThomasAFink Dec 4, 2024
97cee9c
clean(ZMS-3253): remove console logs
ThomasAFink Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions zmsadmin/js/page/availabilityDay/form/conflicts.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,31 @@ const renderConflictList = (conflictList) => {
let conflictDatesByMessage = [];
conflictList.map(collection => {
collection.conflicts.map((conflict) => {
if (! conflictDatesByMessage[conflict.message]) {
Object.assign({}, conflictDatesByMessage[conflict.message] = []);
const existingConflict = conflictDatesByMessage.find(
item => item.message === conflict.message
);

if (existingConflict) {
existingConflict.dates.push(formatDate(collection.date));
} else {
conflictDatesByMessage.push({
message: conflict.message,
dates: [formatDate(collection.date)]
});
}
conflictDatesByMessage[conflict.message].push(formatDate(collection.date))
})
})
});
});

return (
Object.keys(conflictDatesByMessage).map((key, index) => {
return (
<div key={index}>
<div><strong>{ conflictDatesByMessage[key].join(", ") }</strong></div>
<div key={index}>- {key}</div>
</div>
)
})
)


}
conflictDatesByMessage.map((item, index) => (
<div key={index} style={{ marginBottom: '1rem' }}>
{/* Convert newlines in the message to <br /> tags */}
<div dangerouslySetInnerHTML={{ __html: item.message.replace(/\n/g, '<br />') }} />
</div>
))
);
};
ThomasAFink marked this conversation as resolved.
Show resolved Hide resolved

ThomasAFink marked this conversation as resolved.
Show resolved Hide resolved

const Conflicts = (props) => {
const conflicts = Object.keys(props.conflictList).map(key => {
Expand Down
33 changes: 19 additions & 14 deletions zmsadmin/js/page/availabilityDay/form/errors.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import React from 'react';
import PropTypes from 'prop-types';

const renderErrors = errors => Object.keys(errors).map(key => {
return (
const renderErrors = (errors) =>
Object.keys(errors).map(key => (
<div key={errors[key].id}>
{errors[key].itemList.map((item, index) => {
return <div key={index}>{item[0].message}</div>
})}
if (Array.isArray(item)) {
return item.map((nestedItem, nestedIndex) => (
<div key={`${index}-${nestedIndex}`}>{nestedItem.message}</div>
));
} else {
return <div key={index}>{item.message}</div>;
}
})}
</div>
)
})
));

const Errors = (props) => {
return (
Expand All @@ -18,15 +23,15 @@ const Errors = (props) => {
<h3>Folgende Fehler sind bei der Prüfung Ihrer Eingaben aufgetreten:</h3>
{renderErrors(props.errorList)}
</div> : null
)
}
);
};

Errors.defaultProps = {
errorList: []
}
errorList: {}
};

Errors.propTypes = {
errorList: PropTypes.object
}
};

export default Errors
export default Errors;
12 changes: 5 additions & 7 deletions zmsadmin/js/page/availabilityDay/form/footerButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import PropTypes from 'prop-types'
const FooterButtons = (props) => {
const { hasConflicts, stateChanged, data, onNew, onPublish, onAbort, hasSlotCountError } = props
return (
<div className="form-actions" style={{"marginTop":"0", "padding":"0.75em"}}>
<button title="Neue Öffnungszeit anlegen und bearbeiten" className="button button--diamond button-new" onClick={onNew} disabled={(stateChanged || hasConflicts || data)}>neue Öffnungszeit</button>
<button title="Alle Änderungen werden zurückgesetzt" className="button btn" type="abort" onClick={onAbort} disabled={(!stateChanged && !hasConflicts && !data)}>Abbrechen</button>
<button title="Alle Änderungen werden gespeichert" className="button button--positive button-save" type="save" value="publish" onClick={onPublish} disabled={(!stateChanged || hasSlotCountError)}>Alle Änderungen aktivieren
</button>

<div className="form-actions" style={{ "marginTop": "0", "padding": "0.75em" }}>
<button title="Neue Öffnungszeit anlegen und bearbeiten" className="button button--diamond button-new" onClick={onNew} disabled={(stateChanged || hasConflicts || data)}>neue Öffnungszeit</button>
<button title="Alle Änderungen werden zurückgesetzt" className="button btn" type="abort" onClick={onAbort} disabled={(!stateChanged && !hasConflicts && !data)}>Abbrechen</button>
<button title="Alle Änderungen werden gespeichert" className="button button--positive button-save" type="save" value="publish" onClick={onPublish} disabled={(!stateChanged || hasSlotCountError || hasConflicts)}>Alle Änderungen aktivieren
</button>
</div>

)
}

Expand Down
6 changes: 3 additions & 3 deletions zmsadmin/js/page/availabilityDay/form/formButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types'

const FormButtons = (props) => {
const { data, onCopy, onExclusion, onEditInFuture, onUpdateSingle, onDelete, selectedDate, hasConflicts } = props
const disabled = ((data && (! data.id || data.__modified === true)) || hasConflicts);
const { data, onCopy, onExclusion, onEditInFuture, onUpdateSingle, onDelete, selectedDate, hasConflicts, hasSlotCountError } = props
const disabled = ((data && (! data.id || data.__modified === true)) || hasConflicts || hasSlotCountError);
return (
<div className="body">
<div className="form-actions">
Expand All @@ -21,7 +21,7 @@ const FormButtons = (props) => {
className="button button--diamond" disabled={disabled || data.startDate == selectedDate}>Ab diesem Tag ändern</button>
<button onClick={onUpdateSingle}
title="Öffnungszeit aktualisieren"
className="button button--diamond" disabled={(data && !data.id) || hasConflicts || props.isCreatingExclusion}>Aktualisieren</button>
className="button button--diamond" disabled={(data && !data.id) || hasConflicts || hasSlotCountError || props.isCreatingExclusion}>Aktualisieren</button>
</div>
</div>
)
Expand Down
2 changes: 2 additions & 0 deletions zmsadmin/js/page/availabilityDay/form/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import FormButtons from './formButtons'
import FormContent from './content'
import { getDataValuesFromForm, cleanupFormData, getFormValuesFromData } from '../helpers'
import { hasSlotCountError } from '../form/validate';

class AvailabilityForm extends Component {
constructor(props) {
Expand Down Expand Up @@ -64,6 +65,7 @@ class AvailabilityForm extends Component {
onUpdateSingle={this.props.onUpdateSingle}
selectedDate={this.props.selectedDate}
hasConflicts={hasConflicts}
hasSlotCountError={hasSlotCountError(this.props)}
isCreatingExclusion={this.props.isCreatingExclusion}
/>}
</div>
Expand Down
174 changes: 143 additions & 31 deletions zmsadmin/js/page/availabilityDay/form/validate.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,143 @@
import moment from 'moment'
import moment from 'moment';

const validate = (data, props) => {
const currentTime = new Date()
const today = moment(props.today, 'X')
today.set('hour', currentTime.getHours())
today.set('minute', currentTime.getMinutes())
today.set('second', currentTime.getSeconds())

const selectedDate = moment(props.timestamp, 'X')
const yesterday = selectedDate.startOf('day').clone().subtract(1, 'days')
const tomorrow = selectedDate.startOf('day').clone().add(1, 'days')
const currentTime = new Date();
const today = moment(props.today, 'X');
today.set('hour', currentTime.getHours());
today.set('minute', currentTime.getMinutes());
today.set('second', currentTime.getSeconds());

const selectedDate = moment(props.timestamp, 'X');
const yesterday = selectedDate.startOf('day').clone().subtract(1, 'days');
const tomorrow = selectedDate.startOf('day').clone().add(1, 'days');

let errorList = {
id: data.id || data.tempId,
itemList: []
}
};

errorList.itemList.push(validateNullValues(data));
errorList.itemList.push(validateTimestampAndTimeFormats(data));
errorList.itemList.push(validateStartTime(today, tomorrow, selectedDate, data));
errorList.itemList.push(validateEndTime(today, yesterday, selectedDate, data));
errorList.itemList.push(validateOriginEndTime(today, yesterday, selectedDate, data));
errorList.itemList.push(validateType(data));
errorList.itemList.push(validateSlotTime(data));

errorList.itemList.push(validateStartTime(today, tomorrow, selectedDate, data))
errorList.itemList.push(validateEndTime(today, yesterday, selectedDate, data))
errorList.itemList.push(validateOriginEndTime(today, yesterday, selectedDate, data))
errorList.itemList.push(validateType(data))
errorList.itemList.push(validateSlotTime(data))

errorList.itemList = errorList.itemList.filter(el => el.length);
let valid = (0 < errorList.itemList.length) ? false : true
let valid = (0 < errorList.itemList.length) ? false : true;

return {
valid,
errorList
};
};

function validateNullValues(data) {
let errorList = [];

if (!data.startDate) {
errorList.push({
type: 'startDateNull',
message: 'Das Startdatum darf nicht leer sein.'
});
}

if (!data.endDate) {
errorList.push({
type: 'endDateNull',
message: 'Das Enddatum darf nicht leer sein.'
});
}

if (!data.startTime) {
errorList.push({
type: 'startTimeNull',
message: 'Die Startzeit darf nicht leer sein.'
});
}

if (!data.endTime) {
errorList.push({
type: 'endTimeNull',
message: 'Die Endzeit darf nicht leer sein.'
});
}

return errorList;
}

function validateTimestampAndTimeFormats(data) {
let errorList = [];
const timeRegex = /^\d{2}:\d{2}(:\d{2})?$/;

let isStartDateValid = isValidTimestamp(data.startDate);
let isEndDateValid = isValidTimestamp(data.endDate);

if (!isStartDateValid) {
errorList.push({
type: 'startDateInvalid',
message: 'Das Startdatum ist kein gültiger Zeitstempel.'
});
}

if (!isEndDateValid) {
errorList.push({
type: 'endDateInvalid',
message: 'Das Enddatum ist kein gültiger Zeitstempel.'
});
}

if (data.startTime) {
if (!timeRegex.test(data.startTime)) {
errorList.push({
type: 'startTimeFormat',
message: 'Die Startzeit muss im Format "HH:mm:ss" oder "HH:mm" vorliegen.'
});
}
} else {
errorList.push({
type: 'startTimeMissing',
message: 'Die Startzeit darf nicht leer sein.'
});
}

if (data.endTime) {
if (!timeRegex.test(data.endTime)) {
errorList.push({
type: 'endTimeFormat',
message: 'Die Endzeit muss im Format "HH:mm:ss" oder "HH:mm" vorliegen.'
});
}
} else {
errorList.push({
type: 'endTimeMissing',
message: 'Die Endzeit darf nicht leer sein.'
});
}

if (isStartDateValid && isEndDateValid) {
if (new Date(data.startDate) > new Date(data.endDate)) {
errorList.push({
type: 'dateOrderInvalid',
message: 'Das Startdatum darf nicht nach dem Enddatum liegen.'
});
}
}

return errorList;
}

function isValidTimestamp(timestamp) {
return !Number.isNaN(Number(timestamp)) && moment.unix(timestamp).isValid();
}

function parseTimestampAndTime(dateTimestamp, timeStr) {
const date = moment.unix(dateTimestamp);
if (!date.isValid()) return null;

const [hours, minutes, seconds] = timeStr.split(':').map((val, index) => parseInt(val || 0));
return date.set({ hour: hours, minute: minutes, second: seconds });
}

function validateStartTime(today, tomorrow, selectedDate, data) {
Expand All @@ -48,15 +157,15 @@ function validateStartTime(today, tomorrow, selectedDate, data) {
message: `Das Startdatum der Öffnungszeit muss vor dem ${tomorrow.format('DD.MM.YYYY')} liegen.`
})
}
/*
if (isOrigin && startTime.isBefore(today.startOf('day'), 'day') && data.__modified) {
errorList.push({
type: 'startTimeOrigin',
message: 'Öffnungszeiten in der Vergangenheit lassen sich nicht bearbeiten '
+ '(Der Terminanfang am "'+startDateTime.format('DD.MM.YYYY')+' liegt vor dem heutigen Tag").'
})
}
*/
/*
if (isOrigin && startTime.isBefore(today.startOf('day'), 'day') && data.__modified) {
errorList.push({
type: 'startTimeOrigin',
message: 'Öffnungszeiten in der Vergangenheit lassen sich nicht bearbeiten '
+ '(Der Terminanfang am "'+startDateTime.format('DD.MM.YYYY')+' liegt vor dem heutigen Tag").'
})
}
*/

if ((startHour == "00" && startMinute == "00") || (endHour == "00" && endMinute == "00")) {
errorList.push({
Expand All @@ -83,14 +192,17 @@ function validateEndTime(today, yesterday, selectedDate, data) {
if (dayMinutesEnd <= dayMinutesStart) {
errorList.push({
type: 'endTime',
message: 'Die Uhrzeit "von" muss kleiner der Uhrzeit "bis" sein.'
message: 'Die Endzeit darf nicht vor der Startzeit liegen.'
})
} else if (startTimestamp >= endTimestamp) {
}

if (startTimestamp >= endTimestamp) {
errorList.push({
type: 'endTime',
message: 'Das Startdatum muss nach dem Enddatum sein.'
message: 'Das Enddatum darf nicht vor dem Startdatum liegen.'
})
}

return errorList;
}

Expand Down
4 changes: 0 additions & 4 deletions zmsadmin/js/page/availabilityDay/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,6 @@ export const cleanupAvailabilityForSave = availability => {
delete newAvailability.tempId;
}

if (newAvailability.kind) {
delete newAvailability.kind;
}

return newAvailability;
}

Expand Down
Loading
Loading