From e034e414e80feb25b497172d07eeaa7b6486467d Mon Sep 17 00:00:00 2001
From: Moritz Strohm <strohm@data-quest.de>
Date: Mon, 9 Sep 2024 15:33:40 +0000
Subject: [PATCH] add repetition from monday until friday for resource
 bookings, re #2013

Merge request studip/studip!2539
---
 app/controllers/resources/booking.php         | 40 +++++++++++++------
 .../resources/booking/_add_edit_form.php      |  6 +++
 ....1.1_add_weekdays_to_resource_bookings.php | 26 ++++++++++++
 lib/models/resources/BrokenResource.php       |  3 +-
 lib/models/resources/Building.php             |  3 +-
 lib/models/resources/Location.php             |  3 +-
 lib/models/resources/Resource.php             |  8 +++-
 lib/models/resources/ResourceBooking.php      | 39 ++++++++++++++++--
 lib/models/resources/ResourceLabel.php        |  3 +-
 9 files changed, 109 insertions(+), 22 deletions(-)
 create mode 100644 db/migrations/6.0.1.1_add_weekdays_to_resource_bookings.php

diff --git a/app/controllers/resources/booking.php b/app/controllers/resources/booking.php
index a4722eda5c9..53735198208 100644
--- a/app/controllers/resources/booking.php
+++ b/app/controllers/resources/booking.php
@@ -309,7 +309,8 @@ class Resources_BookingController extends AuthenticatedController
         $repetition_interval = null,
         $notification_enabled = false,
         $included_room_parts = [],
-        $overwrite_bookings = false
+        $overwrite_bookings = false,
+        $weekdays = ''
     )
     {
         $result = [
@@ -352,7 +353,8 @@ class Resources_BookingController extends AuthenticatedController
                                 $booking_type == ResourceBooking::TYPE_LOCK
                                 ? $overwrite_bookings
                                 : false
-                            )
+                            ),
+                            $weekdays
                         );
                     } else {
                         $matching_bookings = ResourceBooking::findByResourceAndTimeRanges(
@@ -397,7 +399,8 @@ class Resources_BookingController extends AuthenticatedController
                                     $booking_type == ResourceBooking::TYPE_LOCK
                                     ? $overwrite_bookings
                                     : false
-                                )
+                                ),
+                                $weekdays
                             );
                         }
                     }
@@ -423,6 +426,7 @@ class Resources_BookingController extends AuthenticatedController
                     }
                     $a->repetition_interval = $repetition_interval->format('P%YY%MM%DD');
                     $a->repeat_end = $repetition_end->getTimestamp();
+                    $a->weekdays = $weekdays;
                 }
 
                 try {
@@ -466,7 +470,8 @@ class Resources_BookingController extends AuthenticatedController
                         $booking_type == ResourceBooking::TYPE_LOCK
                         ? $overwrite_bookings
                         : false
-                    )
+                    ),
+                    $weekdays
                 );
                 $result['bookings'] = [$booking];
             } catch (Exception $e) {
@@ -1033,8 +1038,12 @@ class Resources_BookingController extends AuthenticatedController
                     $this->repetition_style = 'monthly';
                     $this->repetition_interval = $interval->m;
                 } else if (($interval->d % 7) == 0) {
-                    $this->repetition_style = 'weekly';
-                    $this->repetition_interval = $interval->d / 7;
+                    $this->repetition_style = 'daily';
+                    if ($this->booking->weekdays === '12345') {
+                        $this->repetition_interval = 'workdays';
+                    } else {
+                        $this->repetition_interval = $interval->d / 7;
+                    }
                 } else {
                     $this->repetition_style = 'daily';
                     $this->repetition_interval = $interval->d;
@@ -1125,17 +1134,21 @@ class Resources_BookingController extends AuthenticatedController
                     intval($this->end->format('s'))
                 );
                 if ($this->repetition_style) {
-                    if ($this->repetition_style == 'daily' && $this->repetition_interval) {
-                        $this->repetition_date_interval = new DateInterval(
-                            'P' . intval($this->repetition_interval) . 'D'
-                        );
+                    if ($this->repetition_style === 'daily' && $this->repetition_interval) {
+                        if ($this->repetition_interval === 'workdays') {
+                            $this->repetition_date_interval = new DateInterval('P7D');
+                        } else {
+                            $this->repetition_date_interval = new DateInterval(
+                                'P' . intval($this->repetition_interval) . 'D'
+                            );
+                        }
                     }
-                    if ($this->repetition_style == 'weekly' && $this->repetition_interval) {
+                    if ($this->repetition_style === 'weekly' && $this->repetition_interval) {
                         $this->repetition_date_interval = new DateInterval(
                             'P' . intval($this->repetition_interval) . 'W'
                         );
                     }
-                    if ($this->repetition_style == 'monthly') {
+                    if ($this->repetition_style === 'monthly') {
                         $this->repetition_date_interval = new DateInterval(
                             'P1M'
                         );
@@ -1413,7 +1426,8 @@ class Resources_BookingController extends AuthenticatedController
                             ? $this->other_room_parts[$resource->id]
                             : []
                         ),
-                        $this->overwrite_bookings
+                        $this->overwrite_bookings,
+                        $this->repetition_interval === 'workdays' ? '12345' : ''
                     );
                     $errors = array_merge($errors, $results['errors']);
                     $room_part_errors = array_merge(
diff --git a/app/views/resources/booking/_add_edit_form.php b/app/views/resources/booking/_add_edit_form.php
index f982c377b58..8b921adf88e 100644
--- a/app/views/resources/booking/_add_edit_form.php
+++ b/app/views/resources/booking/_add_edit_form.php
@@ -374,6 +374,12 @@
                                         : '' ?>>
                                     <?= _('jeden sechsten Tag') ?>
                                 </option>
+                                <option value="workdays"
+                                    <?= $repetition_interval == 'workdays'
+                                        ? 'selected'
+                                        : '' ?>>
+                                    <?= _('jeden Werktag') ?>
+                                </option>
                             </select>
                         </div>
                         <label>
diff --git a/db/migrations/6.0.1.1_add_weekdays_to_resource_bookings.php b/db/migrations/6.0.1.1_add_weekdays_to_resource_bookings.php
new file mode 100644
index 00000000000..049f941dcc2
--- /dev/null
+++ b/db/migrations/6.0.1.1_add_weekdays_to_resource_bookings.php
@@ -0,0 +1,26 @@
+<?php
+
+
+class AddWeekdaysToResourceBookings extends Migration
+{
+    public function description()
+    {
+        return 'Adds the weekdays column to the resource_bookings table.';
+    }
+
+    protected function up()
+    {
+        DBManager::get()->exec(
+            "ALTER TABLE `resource_bookings`
+            ADD COLUMN weekdays VARCHAR(7) COLLATE `latin1_bin` NOT NULL DEFAULT ''"
+        );
+    }
+
+    protected function down()
+    {
+        DBManager::get()->exec(
+            "ALTER TABLE `resource_bookings`
+            DROP COLUMN weekdays"
+        );
+    }
+}
diff --git a/lib/models/resources/BrokenResource.php b/lib/models/resources/BrokenResource.php
index fc44e821306..38d0c81df16 100644
--- a/lib/models/resources/BrokenResource.php
+++ b/lib/models/resources/BrokenResource.php
@@ -104,7 +104,8 @@ class BrokenResource extends Resource
         $description = '',
         $internal_comment = '',
         $booking_type = ResourceBooking::TYPE_NORMAL,
-        $force_booking = false
+        $force_booking = false,
+        string $weekdays = ''
     ) {
         return null;
     }
diff --git a/lib/models/resources/Building.php b/lib/models/resources/Building.php
index a1c071a095a..7f1402f9c3b 100644
--- a/lib/models/resources/Building.php
+++ b/lib/models/resources/Building.php
@@ -458,7 +458,8 @@ class Building extends Resource
         $description = '',
         $internal_comment = '',
         $booking_type = ResourceBooking::TYPE_NORMAL,
-        $force_booking = false
+        $force_booking = false,
+        string $weekdays = ''
     )
     {
         return null;
diff --git a/lib/models/resources/Location.php b/lib/models/resources/Location.php
index 9da2e113703..788b4b35da1 100644
--- a/lib/models/resources/Location.php
+++ b/lib/models/resources/Location.php
@@ -379,7 +379,8 @@ class Location extends Resource
         $description = '',
         $internal_comment = '',
         $booking_type = ResourceBooking::TYPE_NORMAL,
-        $force_booking = false
+        $force_booking = false,
+        string $weekdays = ''
     )
     {
         return null;
diff --git a/lib/models/resources/Resource.php b/lib/models/resources/Resource.php
index 32766e4106b..e1d8b674296 100644
--- a/lib/models/resources/Resource.php
+++ b/lib/models/resources/Resource.php
@@ -661,6 +661,10 @@ class Resource extends SimpleORMap implements StudipItem
      * @param bool $force_booking If this parameter is set to true,
      *     overlapping bookings are removed before storing this booking.
      *
+     * @param string $weekdays The weekdays (1 - 7) on which the booking
+     *     shall take place. This is only used when a booking with repetitions
+     *     shall only take place on some weekdays.
+     *
      * @return ResourceBooking object.
      * @throws InvalidArgumentException If no time ranges are specified
      *     or if there is an error regarding the time ranges.
@@ -684,7 +688,8 @@ class Resource extends SimpleORMap implements StudipItem
         $description = '',
         $internal_comment = '',
         $booking_type = ResourceBooking::TYPE_NORMAL,
-        $force_booking = false
+        $force_booking = false,
+        string $weekdays = ''
     )
     {
         if (!is_array($time_ranges)) {
@@ -843,6 +848,7 @@ class Resource extends SimpleORMap implements StudipItem
             }
 
             $booking->repetition_interval = $repetition_interval->format('P%YY%MM%DD');
+            $booking->weekdays = $weekdays;
         }
 
         if ($preparation_time) {
diff --git a/lib/models/resources/ResourceBooking.php b/lib/models/resources/ResourceBooking.php
index 977cf32ec49..3c84cb84651 100644
--- a/lib/models/resources/ResourceBooking.php
+++ b/lib/models/resources/ResourceBooking.php
@@ -41,6 +41,7 @@
  * @property int $booking_type database column
  * @property string $booking_user_id database column
  * @property string $repetition_interval database column
+ * @property string $weekdays database column
  * @property SimpleORMapCollection|ResourceBookingInterval[] $time_intervals has_many ResourceBookingInterval
  * @property Resource $resource belongs_to Resource
  * @property User $assigned_user belongs_to User
@@ -663,8 +664,13 @@ class ResourceBooking extends SimpleORMap implements PrivacyObject, Studip\Calen
 
                 $duration = $repetition_begin->diff($date_end);
 
+                $weekdays = [];
+                if (preg_match('/^1?2?3?4?5?6?7?$/', $this->weekdays)) {
+                    $weekdays = str_split($this->weekdays);
+                }
+
                 //Loop over all exceptions and check if they belong to
-                //one of the repetions:
+                //one of the repetitions:
 
                 $obsolete_exception_ids = [];
                 foreach ($exceptions as $exception) {
@@ -697,7 +703,13 @@ class ResourceBooking extends SimpleORMap implements PrivacyObject, Studip\Calen
                             break;
                         }
 
-                        $current_repetition->add($repetition_interval);
+                        if ($weekdays) {
+                            while (!in_array($current_repetition->format('N'), $weekdays)) {
+                                $current_repetition = $current_repetition->add(new DateInterval('P1D'));
+                            }
+                        } else {
+                            $current_repetition->add($repetition_interval);
+                        }
                     }
 
                     if ($exception_obsolete) {
@@ -1280,8 +1292,19 @@ class ResourceBooking extends SimpleORMap implements PrivacyObject, Studip\Calen
                 //Check if end is later than begin to avoid
                 //infinite loops.
                 if ($repetition_end > $booking_begin) {
+                    $weekdays = [];
+                    if ($this->weekdays && preg_match('/^1?2?3?4?5?6?7?$/', $this->weekdays)) {
+                        $weekdays = str_split($this->weekdays);
+                    }
                     $current_begin = clone $booking_begin;
-                    $current_begin->add($repetition_interval);
+                    //Move to the first weekday of the booking or the next interval:
+                    if ($weekdays) {
+                        while (!in_array($current_begin->format('N'), $weekdays)) {
+                            $current_begin = $current_begin->add(new DateInterval('P1D'));
+                        }
+                    } else {
+                        $current_begin->add($repetition_interval);
+                    }
                     while ($current_begin < $repetition_end) {
                         $current_end = clone $current_begin;
                         $current_end->add($duration);
@@ -1297,7 +1320,15 @@ class ResourceBooking extends SimpleORMap implements PrivacyObject, Studip\Calen
                                 : $current_end->getTimestamp()
                             )
                         ];
-                        $current_begin->add($repetition_interval);
+                        if ($weekdays) {
+                            //Move to the next weekday that is in the array of weekday numbers:
+                            do {
+                                $current_begin = $current_begin->add(new DateInterval('P1D'));
+                            } while (!in_array($current_begin->format('N'), $weekdays));
+                        } else {
+                            //Move to the next step according to the repetition interval:
+                            $current_begin->add($repetition_interval);
+                        }
                     }
                 } else {
                     //end timestamp is before begin timestamp:
diff --git a/lib/models/resources/ResourceLabel.php b/lib/models/resources/ResourceLabel.php
index c46572396a1..1d1968d8d8a 100644
--- a/lib/models/resources/ResourceLabel.php
+++ b/lib/models/resources/ResourceLabel.php
@@ -97,7 +97,8 @@ class ResourceLabel extends Resource
         $description = '',
         $internal_comment = '',
         $booking_type = ResourceBooking::TYPE_NORMAL,
-        $force_booking = false
+        $force_booking = false,
+        string $weekdays = ''
     )
     {
         return null;
-- 
GitLab