LP1790727: Add a daily schedule view of existing bookings
authorJane Sandberg <sandbej@linnbenton.edu>
Tue, 4 Sep 2018 21:31:57 +0000 (14:31 -0700)
committerJane Sandberg <sandbej@linnbenton.edu>
Tue, 9 Oct 2018 21:35:15 +0000 (14:35 -0700)
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>
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

index feed30b..564fd34 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 1c47eb0..75b64be 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..c1d8ce1 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,135 @@ 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',
+                    {target_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)}},
+                    {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 +179,3 @@ function($scope , $routeParams , $location , egCore) {
     console.log('Loading Booking URL: ' + $scope.booking_url);
 
 }])
-