diff --git a/app/controllers/consultation/admin.php b/app/controllers/consultation/admin.php index 8ee7575ae446874c087e4a7e677f34f088b55cf6..13451d2a8a80ecf71658dd33c20211d87c1ef0a5 100644 --- a/app/controllers/consultation/admin.php +++ b/app/controllers/consultation/admin.php @@ -165,13 +165,19 @@ class Consultation_AdminController extends ConsultationController CSRFProtection::verifyUnsafeRequest(); try { + $interval = Request::int('interval'); $start = $this->getDateAndTime('start'); - $end = $this->getDateAndTime('end'); + $end = $this->getDateAndTime($interval === 0 ? 'start' : 'end', 'end'); if (date('Hi', $end) <= date('Hi', $start)) { throw new InvalidArgumentException(_('Die Endzeit liegt vor der Startzeit!')); } + $dow = Request::int('day-of-week'); + if ($interval === 0) { + $dow = date('w', $start); + } + // Determine duration of a slot and pause times $duration = Request::int('duration'); $pause_time = Request::bool('pause') ? Request::int('pause_time') : null; @@ -187,8 +193,8 @@ class Consultation_AdminController extends ConsultationController $slot_count = ConsultationBlock::countSlots( $start, $end, - Request::int('day-of-week'), - Request::int('interval'), + $dow, + $interval, $duration, $pause_time, $pause_duration @@ -202,8 +208,8 @@ class Consultation_AdminController extends ConsultationController $this->range, $start, $end, - Request::int('day-of-week'), - Request::int('interval') + $dow, + $interval ); $stored = 0; @@ -872,15 +878,19 @@ class Consultation_AdminController extends ConsultationController } } - private function getDateAndTime($index) + private function getDateAndTime(string $index, string $index_time = null) { - if (!Request::submitted("{$index}-date") || !Request::submitted("{$index}-time")) { + if ($index_time === null) { + $index_time = $index; + } + + if (!Request::submitted("{$index}-date") || !Request::submitted("{$index_time}-time")) { throw new Exception("Date with index '{$index}' was not submitted properly"); } return strtotime(implode(' ', [ Request::get("{$index}-date"), - Request::get("{$index}-time") + Request::get("{$index_time}-time") ])); } diff --git a/app/views/consultation/overview/index.php b/app/views/consultation/overview/index.php index dcad6084c7552d20059dcc15b75fecf357ec8682..ef7f2081adc4be1e65138f09e49c85076509d0f0 100644 --- a/app/views/consultation/overview/index.php +++ b/app/views/consultation/overview/index.php @@ -61,7 +61,7 @@ <?= Icon::create('add')->asImg(tooltip2(_('Termin reservieren'))) ?> </a> <? else: ?> - <?= Icon::create('add', Icon::ROLE_INACTIVE)->asImg(tooltip2(_('Dieser Termin ist für Buchungen gesperrt.'))) ?> + <?= Icon::create('decline', Icon::ROLE_INACTIVE)->asImg(tooltip2(_('Dieser Termin ist für Buchungen gesperrt.'))) ?> <? endif; ?> </td> </tr> diff --git a/app/views/consultation/overview/ungrouped.php b/app/views/consultation/overview/ungrouped.php index 147f58858ae1876ade0366e025bff3416bdf0731..c9eb519cb94fedfc1057a61f3470acc2392ad1eb 100644 --- a/app/views/consultation/overview/ungrouped.php +++ b/app/views/consultation/overview/ungrouped.php @@ -91,7 +91,7 @@ <?= Icon::create('add')->asImg(tooltip2(_('Termin reservieren'))) ?> </a> <? else: ?> - <?= Icon::create('add', Icon::ROLE_INACTIVE)->asImg(tooltip2(_('Dieser Termin ist für Buchungen gesperrt.'))) ?> + <?= Icon::create('decline', Icon::ROLE_INACTIVE)->asImg(tooltip2(_('Dieser Termin ist für Buchungen gesperrt.'))) ?> <? endif; ?> </td> </tr> diff --git a/lib/models/ConsultationSlot.php b/lib/models/ConsultationSlot.php index c46e47c1e64b07c0e81d959f8d0c7bdec00a8fe8..1e935c5a54af94825f2fd1bc1d4b513f34a3a1a0 100644 --- a/lib/models/ConsultationSlot.php +++ b/lib/models/ConsultationSlot.php @@ -19,6 +19,7 @@ * @property SimpleORMapCollection|ConsultationEvent[] $events has_many ConsultationEvent * @property ConsultationBlock $block belongs_to ConsultationBlock * @property ConsultationSlot|null $previous_slot has_one ConsultationSlot + * @property ConsultationSlot|null $next_slot has_one ConsultationSlot * @property-read mixed $has_bookings additional field * @property-read mixed $is_expired additional field */ @@ -48,8 +49,12 @@ class ConsultationSlot extends SimpleORMap 'on_delete' => 'delete', ]; $config['has_one']['previous_slot'] = [ - 'class_name' => ConsultationSlot::class, - 'foreign_key' => 'previous_slot_id', + 'class_name' => ConsultationSlot::class, + 'foreign_key' => 'previous_slot_id', + ]; + $config['has_one']['next_slot'] = [ + 'class_name' => ConsultationSlot::class, + 'assoc_func' => 'findOneByPrevious_slot_id', ]; $config['registered_callbacks']['before_create'][] = function (ConsultationSlot $slot) { @@ -183,14 +188,15 @@ class ConsultationSlot extends SimpleORMap /** * Returns whether the slot is bookable for the given user_id */ - public function isBookable(?string $user_id = null): bool + public function isBookable(): bool { - return !$this->isOccupied($user_id) + return !$this->isOccupied() && !$this->isLocked() - && !( - $this->block->consecutive - && $this->previous_slot - && !$this->previous_slot->isOccupied() + && ( + !$this->block->consecutive + || !$this->block->has_bookings + || ($this->previous_slot && $this->previous_slot->isOccupied()) + || ($this->next_slot && $this->next_slot->isOccupied()) ); } diff --git a/resources/vue/components/ConsultationCreator.vue b/resources/vue/components/ConsultationCreator.vue index 375e98a9158b3603d850c645d09ea04355d9b01b..73c3cb1da499a0fdda59e3694cdc279b1a7e9b6e 100644 --- a/resources/vue/components/ConsultationCreator.vue +++ b/resources/vue/components/ConsultationCreator.vue @@ -30,7 +30,7 @@ <label :class="{'col-3': !isSingleDay}"> <span class="required">{{ $gettext('Intervall') }}</span> - <select required name="interval" v-model="interval"> + <select required name="interval" v-model.number="interval"> <option v-for="(label, value) in intervals" :key="value" :value="value"> {{ label }} </option> @@ -40,9 +40,9 @@ <label class="col-3" v-if="!isSingleDay"> <span class="required">{{ $gettext('Am Wochentag') }}</span> - <select required name="day-of-week" v-model="dayOfWeek"> - <option v-for="(label, value) in daysOfTheWeek" :value="value" :key="value"> - {{ label }} + <select required name="day-of-week" @change="evt => dayOfWeek = parseInt(evt.target.value, 10)"> + <option v-for="dow in daysOfTheWeek" :value="dow.key" :key="dow.key" :selected="dayOfWeek === dow.key"> + {{ dow.label }} </option> </select> </label> @@ -359,15 +359,15 @@ export default { return STUDIP.CSRF_TOKEN; }, daysOfTheWeek() { - return { - 1: this.$gettext('Montag'), - 2: this.$gettext('Dienstag'), - 3: this.$gettext('Mittwoch'), - 4: this.$gettext('Donnerstag'), - 5: this.$gettext('Freitag'), - 6: this.$gettext('Samstag'), - 0: this.$gettext('Sonntag'), - }; + return [ + {key: 1, label: this.$gettext('Montag')}, + {key: 2, label: this.$gettext('Dienstag')}, + {key: 3, label: this.$gettext('Mittwoch')}, + {key: 4, label: this.$gettext('Donnerstag')}, + {key: 5, label: this.$gettext('Freitag')}, + {key: 6, label: this.$gettext('Samstag')}, + {key: 0, label: this.$gettext('Sonntag')}, + ]; }, intervals() { return { @@ -385,7 +385,7 @@ export default { return this.rangeType === 'Institute'; }, isSingleDay() { - return this.interval === '0'; + return this.interval === 0; }, needsConfirmation() { return this.slotCount > this.slotCountThreshold; @@ -419,7 +419,7 @@ export default { errors.push(this.$gettext('Die Endzeit liegt vor der Startzeit!')); } - if (this.startDate > this.endDate) { + if (this.interval > 0 && this.startDate > this.endDate) { errors.push(this.$gettext('Das Enddatum liegt vor dem Startdatum!')); } @@ -457,7 +457,7 @@ export default { }, watch: { interval(current) { - if (current === '0') { + if (current === 0) { this.endDate = new Date(this.startDate); } },