LP1790727: Add a daily schedule view of existing bookings user/sandbergja/lp1790727_booking_daily_schedule_final
authorJane Sandberg <sandbej@linnbenton.edu>
Tue, 4 Sep 2018 21:31:57 +0000 (14:31 -0700)
committerJane Sandberg <sandbej@linnbenton.edu>
Wed, 20 Feb 2019 19:15:34 +0000 (11:15 -0800)
To test:
1) Apply this commit.
2) Add some booking resources.  The quickest way to do this
is to open up a bib record in Holdings View, right click on
an item, and Make it Bookable.
3) Add some reservations to those booking resources.  A simple
way to do this is to go to a patron's record, click Other >
Create / Cancel Booking Reservations, and entering the barcodes
from step 1.
4) Go to Booking > Daily Schedule.
5) Select the correct Library, Resource, and Date.
6) Make sure that you can view all those reservations you added at step 3.
7) Make sure that pressing Print prints the schedule in a
satisfactory way.
8) Make sure that all the advanced options work as expected.

Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Signed-off-by: Christine Burns <christine.burns@bc.libraries.coop>
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/templates/staff/booking/t_daily_schedule.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/css/booking.css.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/navbar.tt2
Open-ILS/src/templates/staff/share/print_templates/t_booking_daily_schedule.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/booking/app.js
docs/RELEASE_NOTES_NEXT/Circulation/booking_daily_view.adoc [new file with mode: 0644]
docs/circulation/booking.adoc

index 9220921..b83aab5 100644 (file)
             <span class="material-icons">trending_down</span>
             <span i18n>Return Reservations</span>
           </a>
+          <a class="dropdown-item" href="/eg/staff/booking/daily_schedule">
+            <span class="material-icons">schedule</span>
+            <span i18n>Daily Schedule</span>
+          </a>
         </div>
       </div>
     </div>
diff --git a/Open-ILS/src/templates/staff/booking/t_daily_schedule.tt2 b/Open-ILS/src/templates/staff/booking/t_daily_schedule.tt2
new file mode 100644 (file)
index 0000000..41c21cb
--- /dev/null
@@ -0,0 +1,55 @@
+<h1>[% l('Daily Schedule') %]</h1>
+<div class="bg-info">
+  <div class="row">
+    <div class="col-sm-2">
+      <label>[% l('Library') %] <eg-org-selector selected="context_ou" onchange="handle_changed_ou" sticky-setting="booking.daily_schedule.context_ou"></eg-org-selector></label>
+    </div>
+    <div class="col-sm-2">
+      <label>[% l('Resource') %] <select class="form-control" ng-model="resource" ng-options="resource.id() as resource.barcode() for resource in resource_list"></select></label>
+    </div>
+    <div class="col-sm-2">
+      <label>[% l('Date') %] <eg-date-input ng-model="date"></eg-date-input></label>
+    </div>
+    <div class="col-sm-2">
+      <button class="btn btn-default" ng-hide="show_advanced" ng-click="show_advanced=true">[% l('Show advanced options') %]</button>
+      <button class="btn btn-default" ng-show="show_advanced" ng-click="show_advanced=false">[% l('Hide advanced options') %]</button>
+    </div>
+    <div class="col-sm-2 pull-right">
+      <button class="btn btn-default" ng-disabled="!resource" ng-click="print_schedule()">[% l('Print') %]</button>
+    </div>
+  </div>
+  <div class="row" ng-show="show_advanced">
+    <div class="col-md-2 checkbox checkbox-inline">
+      <label><input type="checkbox" ng-model="show_names" ng-change="on_show_names_changed()" value="">[% l('Show patron names') %]</label>
+    </div>
+    <div class="col-md-4 checkbox checkbox-inline">
+      <div class="row">
+        <label><div uib-timepicker id="start" show-time-picker hide-date-picker ng-model="startTime"></div>[% l('Start of displayed day') %]</label>
+        <label><div uib-timepicker id="end" show-time-picker hide-date-picker ng-model="endTime"></div>[% l('End of displayed day') %]</label>
+      </div>
+      <div class="row" ng-if="startTime >= endTime"><span class="bg-warning">[% l('Make sure the start time is before the end time') %]</span></div>
+    </div>
+  </div>
+</div>
+<div ng-show="resource" id="schedule">
+  <h2>{{date | date:date_format}}</h2>
+  <div ng-repeat="sched in schedule">
+    <div class="row schedule-row">
+      <div class="col-sm-3">{{sched.row|date:time_format}}</div>
+      <div ng-if="sched.reservations.length" class="col-sm-9 bg-primary" ng-class="{'end-before': sched.reservations[sched.reservations.length-1].ends_early, 'start-after': sched.reservations[0].starts_late}">
+        <div ng-repeat="r in sched.reservations | orderBy: 'start_time'">
+          <span ng-if="r.start_time">
+            <span ng-if="show_names">[% l('[_1], [_2] [_3]', 
+              '{{r.patron.family_name()}}',
+              '{{r.patron.first_given_name()}}',
+              '{{r.patron.second_given_name()}}') %]</span>
+            <span ng-if="!show_names">[% l('Busy') %]</span>
+            <span> ({{r.start_time | egOrgDate:'h:mm':context_ou}} - {{r.end_time | egOrgDate: 'h:mm':context_ou}})</span>
+            <span ng-if="!$last">[% l(', ') %]</span>
+          </span>
+          <span ng-if="r.continuation" aria-hidden="true">[% l('...') %]</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/Open-ILS/src/templates/staff/css/booking.css.tt2 b/Open-ILS/src/templates/staff/css/booking.css.tt2
new file mode 100644 (file)
index 0000000..9be08be
--- /dev/null
@@ -0,0 +1,17 @@
+.schedule-row {
+  border-top: 1px dotted #777;
+  height: 30px;
+}
+
+.bg-primary {
+  height: 30px;
+}
+
+.start-after {
+  top: 5px;
+}
+
+.end-before {
+  bottom: 5px;
+}
+
index eb1d471..1add08c 100644 (file)
               [% l('Return Reservations') %]
             </a>
           </li>
+          <li>
+            <a href="./booking/daily_schedule" target="_self">
+              <span class="glyphicon glyphicon-list-alt"></span>
+              [% l('Daily Schedule') %]
+            </a>
+          </li>
         </ul>
       </li>
 
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_booking_daily_schedule.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_booking_daily_schedule.tt2
new file mode 100644 (file)
index 0000000..dfacb24
--- /dev/null
@@ -0,0 +1,20 @@
+<h3>{{barcode}} - {{date | date:date_format}}</h3>
+<table>
+  <tr ng-repeat="sched in schedule" style="border-top: 1px dotted black;">
+    <td style="padding: 5px 10px 5px 10px;">{{sched.row|date:time_format}}</td>
+    <td ng-style="sched.reservations.length && {'border-left': '3px solid black', 'border-right': '3px solid black', 'font-weight': 'bold', 'padding': '5px 10px 5px 10px'}">
+      <span ng-repeat="r in sched.reservations">
+        <span ng-if="r.start_time">
+          <span ng-if="show_names">[% l('[_1], [_2] [_3]',
+            '{{r.patron.family_name()}}',
+            '{{r.patron.first_given_name()}}',
+            '{{r.patron.second_given_name()}}') %]</span>
+          <span ng-if="!show_names">[% l('Busy') %]</span>
+          <span> ({{r.start_time | egOrgDate:'h:mm':context_ou}} - {{r.end_time | egOrgDate: 'h:mm':context_ou}})</span>
+        </span>
+        <span ng-if="r.continuation">...</span>
+        </div>
+      </div>
+    </td>
+  </tr>
+</table>
index 5f116d1..da061e7 100644 (file)
@@ -17,6 +17,13 @@ angular.module('egBooking',
         resolve : resolver
     });
 
+    // daily view
+    $routeProvider.when('/booking/daily_schedule', {
+        templateUrl: './booking/t_daily_schedule',
+        controller: 'BookingDailyScheduleCtrl',
+        resolve : resolver
+    });
+
     // default page 
     $routeProvider.otherwise({
         templateUrl : './t_splash',
@@ -24,6 +31,136 @@ angular.module('egBooking',
     });
 }])
 
+.controller('BookingDailyScheduleCtrl',
+    ['$scope', 'egCore', 'egOrgDateFilter',
+        function($scope, egCore, egOrgDateFilter) {
+            $scope.context_ou = egCore.org.get(egCore.auth.user().ws_ou());
+            $scope.date = new Date();
+            $scope.date.setHours(0, 0, 0, 0);
+            $scope.startTime = new Date($scope.date.getTime() + egCore.date.intervalToSeconds('9 hours'));
+            $scope.endTime = new Date($scope.date.getTime() + egCore.date.intervalToSeconds('17 hours'));
+
+            $scope.date_format = 'mediumDate';
+            egCore.org.settings(['format.date']).then(function(set) {
+                if (set['format.date']) $scope.date_format = set['format.date'];
+            });
+            $scope.time_format = 'shortTime';
+            $scope.resource_list = [];
+            egCore.hatch.getItem('booking.daily_schedule.resource_id')
+                .then(function(resource_id){
+                    if (resource_id) $scope.resource = resource_id;
+            });  
+
+            egCore.hatch.getItem('booking.daily_schedule.show_names')
+                .then(function(show_names){
+                    if (show_names) $scope.show_names = show_names; 
+                });    
+            $scope.on_show_names_changed = function(){
+                egCore.hatch.setItem('booking.daily_schedule.show_names', $scope.show_names);
+            }
+
+            $scope.$watch('startTime',function(newValue,oldValue){
+                $scope.fix_date(newValue);
+                $scope.populate_display();
+            });
+            $scope.$watch('endTime',function(newValue,oldValue){
+                $scope.fix_date(newValue);
+                $scope.populate_display();
+            });
+            $scope.$watch('resource',function(newValue,oldValue){
+                $scope.populate_display();
+                egCore.hatch.setItem('booking.daily_schedule.resource_id', $scope.resource); 
+            });
+            $scope.$watch('date',function(newValue,oldValue){
+                if ($scope.resource) {
+                    $scope.populate_display();
+                }
+                    $scope.guess_display_times();
+                    $scope.fix_date($scope.startTime);
+                    $scope.fix_date($scope.endTime);
+            });
+
+            $scope.fix_date = function(time) {
+                time.setFullYear($scope.date.getFullYear(), $scope.date.getMonth(), $scope.date.getDate());
+                time.setSeconds(0);
+            }
+
+            $scope.populate_display = function() {
+                if ($scope.resource) {
+                    egCore.pcrud.search('bresv',
+                    {current_resource: $scope.resource,
+                    start_time: {"<": egOrgDateFilter($scope.endTime, 'yyyy-MM-dd HH:mm:ss', $scope.context_ou)},
+                    end_time: {">": egOrgDateFilter($scope.startTime, 'yyyy-MM-dd HH:mm:ss', $scope.context_ou)},
+                   cancel_time: null},
+                    {flesh: 1, flesh_fields: {'bresv' : ['usr']}}, {atomic: true}).then(
+                        function(response) {
+                          $scope.schedule = []
+                          var start_distance_from_half_hour = ($scope.startTime.getMinutes() % 30);
+                          var current_working_time = new Date($scope.startTime.getTime());
+                          $scope.fix_date(current_working_time);
+                          var working_end_time = new Date($scope.endTime.getTime());
+                          $scope.fix_date(working_end_time);
+                          var next_working_time = new Date(current_working_time.getTime());
+                          do {
+                            next_working_time.setMinutes(current_working_time.getMinutes() + ((current_working_time.getMinutes() % 30) ? (30 - (current_working_time.getMinutes() % 30)) : 30));
+                            var reservations = [];
+                            angular.forEach(response, function (r) {
+                                rs = new Date(r.start_time());
+                                re = new Date(r.end_time());
+                                if ((rs < next_working_time) && (re > current_working_time)) {
+                                    reservation = {patron: r.usr(), end_time: re};
+                                    reservation.continuation = (rs < current_working_time) ? true : false;
+                                    reservation.ends_early = (re < next_working_time) ? true : false;
+                                    reservation.starts_late = (rs > current_working_time) ? true : false;
+                                    reservation.start_time = (rs >= current_working_time) ? rs : undefined;
+                                    reservations.push(reservation);
+                                }}) 
+                            $scope.schedule.push({'row': current_working_time.getTime(), 'reservations': reservations});
+                            current_working_time.setTime(next_working_time.getTime());
+                          } while (current_working_time < working_end_time)
+            })}};
+
+            $scope.print_schedule =  function() {
+                print_data = { schedule: $scope.schedule,
+                    show_names: $scope.show_names,
+                    time_format: $scope.time_format,
+                    date_format: $scope.date_format,
+                    date: $scope.date,
+                    barcode: egCore.idl.toHash($scope.resource_list).find(r => {return r.id == $scope.resource}).barcode }
+                return egCore.print.print({
+                   template : 'booking_daily_schedule',
+                   scope: print_data
+                });
+            }
+
+            $scope.get_booking_resources = function() {
+                egCore.pcrud.search('brsrc', {owner: $scope.context_ou.id()}, {}, {atomic: true})
+                .then(function(list) {
+                    $scope.resource_list = list;
+                });
+            };
+
+            $scope.handle_changed_ou = function () {
+                $scope.guess_display_times();
+                $scope.get_booking_resources();
+            }
+
+            $scope.guess_display_times = function() {
+                var selected_day = $scope.date.getDay();
+                egCore.pcrud.retrieve('aouhoo', $scope.context_ou.id()).then(function(hours){
+                    $scope.hours = hours;
+                    selected_day_eg_style = (selected_day + 6) % 7;
+                    o = ($scope.$eval("hours.dow_" + selected_day_eg_style + "_open()")).split(':');
+                    c = ($scope.$eval("hours.dow_" + selected_day_eg_style + "_close()")).split(':');
+                    $scope.startTime = new Date($scope.date.getTime());
+                    $scope.startTime.setSeconds(egCore.date.intervalToSeconds(o[0] + ' hours') + egCore.date.intervalToSeconds(o[1] + ' minutes'));
+                    $scope.endTime = new Date($scope.date.getTime());
+                    $scope.endTime.setSeconds(egCore.date.intervalToSeconds(c[0] + ' hours') + egCore.date.intervalToSeconds(c[1] + ' minutes'));
+                });
+            }
+
+}])
+
 .controller('EmbedBookingCtl', 
        ['$scope','$routeParams','$location','egCore',
 function($scope , $routeParams , $location , egCore) {
@@ -43,4 +180,3 @@ function($scope , $routeParams , $location , egCore) {
     console.log('Loading Booking URL: ' + $scope.booking_url);
 
 }])
-
diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/booking_daily_view.adoc b/docs/RELEASE_NOTES_NEXT/Circulation/booking_daily_view.adoc
new file mode 100644 (file)
index 0000000..242a3f1
--- /dev/null
@@ -0,0 +1,7 @@
+Booking Daily Schedule
+^^^^^^^^^^^^^^^^^^^^^^
+
+The booking module now includes a schedule view, where users can view and print all the reservations
+on a particular resource for a particular day.  This feature is specifically designed for libraries
+that use the booking module to handle room reservations.
+
index 18a62ae..cbc500b 100644 (file)
@@ -263,9 +263,44 @@ Cancel a reservation on reservation creation screen
 
 5) Select those that you want to cancel, then click Cancel Selected.
 
+Daily Schedule
+~~~~~~~~~~~~~~
 
+indexterm:[bookings,daily schedule]
+indexterm:[daily schedule]
+indexterm:[reservations,daily schedule]
 
 
+.Use case
+****
+The Daily Schedule can be helpful for printing out a list of room reservations,
+or for seeing how much a particular resource is being used on a particular day.
+****
+
+To print the daily schedule for a particular resource and day:
+
+. Go to Booking -> Daily Schedule.
+. Select the library you're interested in viewing.
+. Select the barcode of the resource that you'd like to view.
+. Select the date that you'd like to view.
+. When you are satisfied with the display, you can press the Print button to
+print your schedule.
+
+[TIP]
+To protect patron privacy, you can anonymize the reservations by clicking the
+_Show advanced options_ button, then unchecking the Show Patron Names checkbox.
+
+Changing hours for the daily schedule
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+By default, the daily schedule displays the times that your library is 
+marked as open. To change the hours, click the _Show advanced options_
+button, then use the time pickers to change the display times.
+
+[NOTE]
+An administrator can change the hours that your Library is marked as open
+by going to Administration -> Server Administration -> Organizational Units.
+