Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d5bc6a3
fix(ZMSKVR-1142): reject appointments-per-mail value 1
ThomasAFink Aug 7, 2026
789bca3
fix(ZMSKVR-1142): stop using appointments_per_mail=1 in test fixtures
ThomasAFink Aug 7, 2026
9036274
fix(ZMSKVR-1142): restore citizenapi process fixture appointmentsPerMail
ThomasAFink Aug 7, 2026
128469c
fix(ZMSKVR-1142): constrain appointmentsPerMail to allowed domain
ThomasAFink Aug 7, 2026
9015ad1
fix(ZMSKVR-1142): require appointmentsPerMail empty or > 1
ThomasAFink Aug 7, 2026
ea2216e
fix(ZMSKVR-1142): restore acceptance-criteria copy for appointmentsPe…
ThomasAFink Aug 7, 2026
186e67f
Merge branch 'next' into feature-zmskvr-1142-max-appointments-per-mail
ThomasAFink Aug 11, 2026
48bdee3
fix(ZMSKVR-1142): generalize appointmentsPerMail validation message
ThomasAFink Aug 11, 2026
a22af4c
fix(ZMSKVR-1142): note empty appointmentsPerMail means unlimited
ThomasAFink Aug 11, 2026
453ee04
feat(ZMSKVR-1142): use unlimited checkbox for appointmentsPerMail
ThomasAFink Aug 11, 2026
687c81b
fix(ZMSKVR-1142): tighten appointmentsPerMail unlimited UX
ThomasAFink Aug 11, 2026
2145784
fix(ZMSKVR-1142): force short side-by-side appointmentsPerMail controls
ThomasAFink Aug 11, 2026
1b83410
fix(ZMSKVR-1142): use +/- counter for appointmentsPerMail
ThomasAFink Aug 11, 2026
6c9ae19
Potential fix for pull request finding 'CodeQL / Unused variable, imp…
ThomasAFink Aug 11, 2026
0cc43bc
Merge branch 'next' into feature-zmskvr-1142-max-appointments-per-mail
ThomasAFink Aug 14, 2026
1c9fb9d
fix(ZMSKVR-1142): keep a single input id and make the mail-limit coun…
ThomasAFink Aug 17, 2026
b419a16
Merge branch 'next' into feature-zmskvr-1142-max-appointments-per-mail
ThomasAFink Aug 18, 2026
23ac295
Merge branch 'next' into feature-zmskvr-1142-max-appointments-per-mail
ThomasAFink Sep 15, 2026
71b22e8
chore(ZMSKVR-1142): merge next and keep rebooking plus mail-limit UI
ThomasAFink Oct 11, 2026
5e5017a
chore(ZMSKVR-1142): fix Umbuchung formgroup indentation after merge
ThomasAFink Oct 11, 2026
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
4 changes: 3 additions & 1 deletion zmsadmin/templates/block/scope/form.twig
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@

{{ formgroup(
{
"label": "Maximale Anzahl an Terminen pro E-Mail-Adresse:", "description": "Wird 1 eingestellt, können Bürger*innen ihre Termine nicht selbständig umbuchen."
"label": "Maximale Anzahl an Terminen pro E-Mail-Adresse:",
"description": "Bitte geben Sie eine Zahl größer oder gleich \"zwei\" ein, damit ein Verschieben des Termins für Bürger*innen möglich bleibt",
"errors": (exception and exception.data['preferences/client/appointmentsPerMail'].messages is defined) ? exception.data['preferences/client/appointmentsPerMail'].messages : []
},
[{
"type":"input",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- 1 blocks citizen reschedule (needs room for the temporary second booking).
-- 0 is treated as unlimited already; normalize to NULL.
UPDATE `standort`
SET `appointments_per_mail` = 2
WHERE `appointments_per_mail` = 1;

UPDATE `standort`
SET `appointments_per_mail` = NULL
WHERE `appointments_per_mail` = 0;
20 changes: 20 additions & 0 deletions zmsbackend/tests/Zmsbackend/Scope/Api/ScopeUpdateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ public function testNoRights()
], []);
}

public function testUnvalidAppointmentsPerMailOne()
{
$this->setWorkstation();
$this->expectException('\BO\Zmsentities\Exception\SchemaValidation');
$this->expectExceptionCode(400);
$body = json_decode($this->readFixture('GetScope_lessData.json'), true);
$body['preferences']['client']['appointmentsPerMail'] = '1';
try {
$this->render(['id' => 141], [
'__body' => json_encode($body)
], []);
} catch (\BO\Zmsentities\Exception\SchemaValidation $exception) {
$this->assertSame(
['oneOf' => 'Die Zahl "1" für die maximale Anzahl an Terminen pro E-Mail-Adresse ist nicht erlaubt. Bitte geben Sie eine größere Zahl ein.'],
$exception->data['/preferences/client/appointmentsPerMail']['messages']
);
throw $exception;
}
}

public function testRouteIdNotOverriddenByBody()
{
$department = (new \BO\Zmsentities\Department());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
UPDATE standort SET appointments_per_mail = 1, whitelisted_mails = "test@gmail.com,@mail.com" WHERE StandortID = 140;
UPDATE standort SET appointments_per_mail = 2, whitelisted_mails = "test@gmail.com,@mail.com" WHERE StandortID = 140;
UPDATE buerger SET EMail = "testmail@mail.com" WHERE absagecode = "a156";
UPDATE buerger SET EMail = "testmail@mail.com" WHERE absagecode = "c01b";
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
UPDATE standort SET appointments_per_mail = 1, whitelisted_mails = "testmail@mail.com" WHERE StandortID = 140;
UPDATE standort SET appointments_per_mail = 2, whitelisted_mails = "testmail@mail.com" WHERE StandortID = 140;
UPDATE buerger SET EMail = "testmail@mail.com" WHERE absagecode = "a156";
UPDATE buerger SET EMail = "testmail@mail.com" WHERE absagecode = "c01b";
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
UPDATE standort SET appointments_per_mail = 1, whitelisted_mails = "somemail@mail.com" WHERE StandortID = 140;
UPDATE standort SET appointments_per_mail = 2, whitelisted_mails = "somemail@mail.com" WHERE StandortID = 140;
UPDATE buerger SET EMail = "testmail@mail.com" WHERE absagecode = "a156";
UPDATE buerger SET EMail = "testmail@mail.com" WHERE absagecode = "c01b";
26 changes: 23 additions & 3 deletions zmsentities/schema/dereferenced/scope.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,29 @@
"additionalProperties": false,
"properties": {
"appointmentsPerMail": {
"type": ["string", "null", "integer"],
"description": "allowed appointments per e-mail address",
"default": ""
"oneOf": [
{
"type": "null"
},
{
"type": "integer",
"minimum": 2
},
{
"type": "string",
"pattern": "^(|[2-9]|[1-9][0-9]+)$"
}
],
"description": "allowed appointments per e-mail address; empty/null = unlimited, otherwise must be >= 2 so citizens can reschedule",
"default": "",
"x-locale": {
"de_DE": {
"pointer": "Maximale Anzahl an Terminen pro E-Mail-Adresse",
"messages": {
"oneOf": "Die Zahl \"1\" für die maximale Anzahl an Terminen pro E-Mail-Adresse ist nicht erlaubt. Bitte geben Sie eine größere Zahl ein."
}
}
}
},
"slotsPerAppointment": {
"type": ["string", "null", "integer"],
Expand Down
26 changes: 23 additions & 3 deletions zmsentities/schema/scope.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,29 @@
"additionalProperties": false,
"properties": {
"appointmentsPerMail": {
"type": ["string", "null", "integer"],
"description": "allowed appointments per e-mail address",
"default": ""
"oneOf": [
{
"type": "null"
},
{
"type": "integer",
"minimum": 2
},
{
"type": "string",
"pattern": "^(|[2-9]|[1-9][0-9]+)$"
}
],
"description": "allowed appointments per e-mail address; empty/null = unlimited, otherwise must be >= 2 so citizens can reschedule",
"default": "",
"x-locale": {
"de_DE": {
"pointer": "Maximale Anzahl an Terminen pro E-Mail-Adresse",
"messages": {
"oneOf": "Die Zahl \"1\" für die maximale Anzahl an Terminen pro E-Mail-Adresse ist nicht erlaubt. Bitte geben Sie eine größere Zahl ein."
Comment thread
ThomasAFink marked this conversation as resolved.
Outdated
}
}
}
},
"slotsPerAppointment": {
"type": ["string", "null", "integer"],
Expand Down
9 changes: 8 additions & 1 deletion zmsentities/src/Zmsentities/Schema/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,23 @@ public function getErrors()
private function extractErrors(OpisValidationError $error)
{
$errors = [];
$message = $this->getCustomMessage($error);

$errors[] = new OpisValidationError(
$error->keyword(),
$error->schema(),
$error->data(),
$this->getCustomMessage($error),
$message,
$error->args(),
[]
);

// Custom x-locale messages already describe the full constraint (e.g. oneOf).
// Do not also surface Opis branch/sub-errors in admin forms.
if ($message !== $error->message()) {
return $errors;
}

foreach ($error->subErrors() as $subError) {
if ($subError instanceof OpisValidationError) {
$errors = array_merge($errors, $this->extractErrors($subError));
Expand Down
26 changes: 26 additions & 0 deletions zmsentities/tests/Zmsentities/ScopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,30 @@ public function testRequestList()
$requestList = $entity->getRequestList();
$this->assertTrue($requestList->hasRequests(1234), "Missing request in provider from scope");
}

public function testAppointmentsPerMailRejectsInvalidValues()
{
$expectedMessage = 'Die Zahl "1" für die maximale Anzahl an Terminen pro E-Mail-Adresse ist nicht erlaubt. Bitte geben Sie eine größere Zahl ein.';
foreach ([0, '0', 1, '1', -1, 'abc'] as $value) {
$entity = $this->getExample();
$entity->preferences['client']['appointmentsPerMail'] = $value;
try {
$entity->testValid('de_DE', 1);
$this->fail('Expected SchemaValidation for value ' . var_export($value, true));
} catch (\BO\Zmsentities\Exception\SchemaValidation $exception) {
$this->assertSame(400, $exception->getCode());
$messages = $exception->data['/preferences/client/appointmentsPerMail']['messages'];
$this->assertSame(['oneOf' => $expectedMessage], $messages);
}
}
}

public function testAppointmentsPerMailAllowsTwoOrEmpty()
{
foreach (['', null, '2', 2, '10', 10] as $value) {
$entity = $this->getExample();
$entity->preferences['client']['appointmentsPerMail'] = $value;
$this->assertTrue($entity->testValid('de_DE', 1));
}
}
}