LP1816475: improvements to create reservations UI
authorJane Sandberg <sandbej@linnbenton.edu>
Sun, 7 Apr 2019 21:53:45 +0000 (14:53 -0700)
committerJane Sandberg <sandbej@linnbenton.edu>
Wed, 17 Apr 2019 20:41:53 +0000 (13:41 -0700)
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
12 files changed:
Open-ILS/src/eg2/src/app/staff/booking/create-reservation.component.html
Open-ILS/src/eg2/src/app/staff/booking/create-reservation.component.ts
Open-ILS/src/eg2/src/app/staff/booking/reservations-grid.component.ts
Open-ILS/src/eg2/src/app/staff/booking/resource-type-combobox.component.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.booking-sticky-settings.sql
Open-ILS/src/templates/staff/circ/patron/index.tt2
Open-ILS/src/templates/staff/navbar.tt2
Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
Open-ILS/web/js/ui/default/staff/cat/item/app.js
Open-ILS/web/js/ui/default/staff/circ/services/item.js

index af736ba..575baf7 100644 (file)
@@ -1,7 +1,3 @@
-<h2>Resources</h2>
-{{resources | json}}
-<h2>Grid</h2>
-{{scheduleSource.data | json}}
 <eg-staff-banner bannerText="Create Reservation" i18n-bannerText>
 </eg-staff-banner>
 <eg-title i18n-prefix i18n-suffix prefix="Booking" suffix="Create Reservation"></eg-title>
@@ -84,7 +80,7 @@
           <span class="input-group-prepend">
             <label class="input-group-text" for="start-time" i18n>Start time</label>
           </span>
-          <ngb-timepicker [(ngModel)]="startOfDay" (ngModelChange)="fetchData()" [meridian]="true"></ngb-timepicker>
+          <ngb-timepicker [(ngModel)]="startOfDay" (ngModelChange)="fetchData()" [minuteStep]="minuteStep()" [meridian]="true"></ngb-timepicker>
         </span>
       </li>
       <li class="list-group-item">
@@ -92,7 +88,7 @@
           <span class="input-group-prepend">
             <label class="input-group-text" for="end-time" i18n>End time</label>
           </span>
-          <ngb-timepicker [(ngModel)]="endOfDay" (ngModelChange)="fetchData()" [meridian]="true"></ngb-timepicker>
+          <ngb-timepicker [(ngModel)]="endOfDay" (ngModelChange)="fetchData()" [minuteStep]="minuteStep()" [meridian]="true"></ngb-timepicker>
         </span>
       </li>
       <li class="list-group-item">
   [rowFlairIsEnabled]="true"
   [rowFlairCallback]="resourceAvailabilityIcon"
   [disablePager]="true"
-  [disableSaveSettings]="true"
-  [cellClassCallback]="isBooked">
+  [disableSaveSettings]="true">
   <eg-grid-toolbar-action label="Create Reservation" i18n-label [action]="openCreateDialog"></eg-grid-toolbar-action>
   <eg-grid-column path="time" [index]="true" ></eg-grid-column>
   <eg-grid-column *ngFor="let resource of resources" path="{{resource.barcode()}}" [cellTemplate]="reservationsTemplate"></eg-grid-column>
 
 <eg-fm-record-editor #newDialog
   idlClass="bresv"
-  datetimeFields="start_time,end_time"
-  [fieldOptions]="{usr:{customTemplate:{template:patronTemplate}},start_time:{customTemplate:{template:datetimeWithDefaults}},end_time:{customTemplate:{template:datetimeWithDefaults}},pickup_lib:{customTemplate:{template:pickupLibrary}}}"
-  hiddenFields="id,xact_start,request_time,capture_time,pickup_time,return_time,capture_staff,xact_finish,cancel_time,booking_interval,target_resource,unrecovered,request_lib,fine_interval,fine_amount,max_fine">
+  [fieldOptions]="{usr:{customTemplate:{template:patronTemplate}},start_time:{customTemplate:{template:datetimeWithDefaults}},end_time:{customTemplate:{template:datetimeWithDefaults}},pickup_lib:{customTemplate:{template:pickupLibrary}},target_resource:{customTemplate:{template:targetResource}}}"
+  hiddenFields="id,xact_start,request_time,capture_time,pickup_time,return_time,capture_staff,xact_finish,cancel_time,booking_interval,target_resource,unrecovered,request_lib,fine_interval,fine_amount,max_fine,target_resource,target_resource_type">
 </eg-fm-record-editor>
 
 <ng-template #reservationsTemplate let-row="row" let-col="col">
   <ng-container *ngIf="row[col.name]">
-    <ul>
+    <ul class="alert alert-primary">
       <li *ngFor="let reservation of row[col.name]">
         <a href="staff/booking/manage_reservations/by_patron/{{reservation['patronId']}}">{{reservation['patronLabel']}}</a>
       </li>
   </eg-org-select>
   <div *ngIf="pickupLibUsesDifferentTz" class="alert alert-primary" i18n>Pickup library uses a different timezone than your library does. Please choose times in the pickup library's timezone.</div>
 </ng-template>
+<ng-template #targetResource let-record="record">
+  <input type="hidden" value="{{record.target_resource_type(resourceTypeId)}}">
+  <ng-container *ngIf="resourceId">
+    <input type="text" disabled value="{{resourceBarcode}}" class="form-control">
+    <input type="hidden" value="{{record.target_resource(resourceId)}}">
+    <input type="hidden" value="{{record.current_resource(resourceId)}}">
+  </ng-container>
+  <ng-container *ngIf="!resourceId">
+    <eg-combobox (onChange)="handleTargetResourceChange($event.id)" startId="any">
+      <eg-combobox-entry entryId="any" entryLabel="Any resource" 
+        i18n-entryLabel></eg-combobox-entry>
+        <eg-combobox-entry *ngFor="let r of resources" entryId="{{r.id()}}" entryLabel="{{r.barcode()}}">
+      </eg-combobox-entry>
+    </eg-combobox>
+  </ng-container>
+</ng-template>
 
index dc3d7d5..9c58a3a 100644 (file)
@@ -32,7 +32,6 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
     attributes: IdlObject[] = [];
     multiday = false;
     handleDateChange: ($event: Date) => void;
-    isBooked: (col: any, row: any) => string;
     resourceAvailabilityIcon: (row: any) => GridRowFlairEntry;
 
     patronBarcode: string;
@@ -40,6 +39,8 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
     resourceBarcode: string;
     resourceId: number;
     resourceTypeId: number;
+    transferable: boolean;
+    resourceOwner: number;
 
     pickupLibUsesDifferentTz: string;
 
@@ -54,6 +55,7 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
     minuteStep: () => number;
 
     openCreateDialog: (rows: IdlObject[]) => void;
+    openTheDialog: (rows: IdlObject[]) => any;
 
     resources: IdlObject[] = [];
     limitByAttr: (attributeId: number, $event: ComboboxEntry) => void;
@@ -65,6 +67,7 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
     handleSingleDayReservation: () => void;
     changeGranularity: ($event: ComboboxEntry) => void;
     handlePickupLibChange: ($event: IdlObject) => void;
+    handleTargetResourceChange: ($event: string | number) => void;
 
     @ViewChildren('dateLimiter') dateLimiters: QueryList<DateSelectComponent>;
     @ViewChildren('dateRangeLimiter') dateRangeLimiters: QueryList<DateRangeSelectComponent>;
@@ -105,6 +108,11 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
             'start_time': Moment.tz([], this.format.wsOrgTimezone),
             'end_time': Moment.tz([], this.format.wsOrgTimezone).add(this.granularity, 'minutes')
         };
+
+        this.store.getItem('eg.booking.create.multiday').then(multiday => {
+            if (multiday) { this.multiday = multiday; }
+        }
+
         this.route.paramMap.subscribe((params: ParamMap) => {
             this.patronId = +params.get('patron_id');
             this.resourceBarcode = params.get('resource_barcode');
@@ -138,12 +146,6 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
             }
         });
 
-        this.isBooked = (row: any, col: any) => {
-            if ((col.name !== 'time') && (row[col.name])) {
-                return 'bg-warning';
-            }
-        };
-
         this.limitByAttr = (attributeId: number, $event: ComboboxEntry) => {
             console.log('LIMIT');
             console.log('id: ' + attributeId);
@@ -184,12 +186,14 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
 
         this.handleMultiDayReservation = () => {
             this.multiday = true;
+            this.store.setItem('eg.booking.create.multiday', true);
             this.setGranularity();
             this.fetchData();
         };
 
         this.handleSingleDayReservation = () => {
             this.multiday = false;
+            this.store.setItem('eg.booking.create.multiday', false);
             this.setGranularity();
             this.handleDateChange(new Date());
         };
@@ -211,6 +215,13 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
             });
         };
 
+        this.handleTargetResourceChange = ($event) => {
+            if ('any' !== $event) {
+                this.newDialog.record.current_resource($event);
+                this.newDialog.record.target_resource($event);
+            }
+        }
+
         this.useCurrentResourceBarcode = () => {
             if (this.resourceBarcode) {
                 this.router.navigate(['/staff', 'booking', 'create_reservation', 'for_resource', this.resourceBarcode]);
@@ -235,6 +246,17 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
         this.setGranularity();
         this.fetchData();
 
+       this.openTheDialog = (rows: IdlObject[]) => {
+            return this.newDialog.open({size: 'lg'}).then(
+                response => {
+                    this.toast.success('Reservation successfully created'); // TODO: needs i18n, pluralization
+                    this.fetchData();
+                    return response.id();
+                },
+                err => {}
+            );
+        }
+
        this.openCreateDialog = (rows: IdlObject[]) => {
             if (rows.length) {
                 if (this.multiday) {
@@ -249,14 +271,26 @@ export class CreateReservationComponent implements OnInit, AfterViewInit {
             } else {
                 if (this.multiday) { this.defaultTimes['end_time'] = this.defaultTimes['start_time'].clone().add(1, 'days'); }
             }
-            return this.newDialog.open({size: 'lg'}).then(
-                response => {
-                   console.log(response);
-                    this.toast.success('Reservation successfully created'); // TODO: needs i18n, pluralization
-                    this.fetchData();
-                },
-                err => {}
-            );
+            if (this.resourceId) {
+                this.pcrud.search('brsrc', {id: this.resourceId}, {
+                    flesh: 1,
+                    limit: 1,
+                    flesh_fields: {'brsrc': ['type']}
+                }).subscribe( r => {
+                    this.transferable = r.type().transferable();
+                    this.resourceTypeId = r.type().id();
+                    this.resourceOwner = r.owner();
+                    this.openTheDialog(rows);
+                });
+            } else if (this.resourceTypeId) {
+                this.pcrud.search('brt', {id: this.resourceTypeId}, {
+                }).subscribe( t => {
+                    this.transferable = t.transferable();
+                    this.openTheDialog(rows).then(
+                        newId => { this.net.request('open-ils.storage', 'open-ils.storage.booking.reservation.resource_targeter', [newId]); }
+                    );
+                });
+            }
         }
     }
     handleResourceTypeChange($event: ComboboxEntry) {
index 85855ca..5e6b4b2 100644 (file)
@@ -174,7 +174,7 @@ export class ReservationsGridComponent implements OnInit {
 
         this.noSelectedRows = (rows: IdlObject[]) => (rows.length === 0);
         this.notOnePatronSelected = (rows: IdlObject[]) => (new Set(rows.map(row => row.usr().id())).size !== 1);
-        this.notOneResourceSelected = (rows: IdlObject[]) => (new Set(rows.map(row => row.current_resource().id())).size !== 1);
+        this.notOneResourceSelected = (rows: IdlObject[]) => (new Set(rows.map(row => {if (row.current_resource()) {return row.current_resource().id();}})).size !== 1);
         this.cancelNotAppropriate = (rows: IdlObject[]) => (this.noSelectedRows(rows) || ['pickedUp', 'returnReady', 'returnedToday'].includes(this.status));
         this.pickupNotAppropriate = (rows: IdlObject[]) => (this.noSelectedRows(rows) || ('pickupReady' !== this.status));
         this.editNotAppropriate = (rows: IdlObject[]) => (this.noSelectedRows(rows) || ('returnedToday' === this.status));
index b3d63c0..b2080ea 100644 (file)
@@ -34,7 +34,8 @@ export class ResourceTypeComboboxComponent implements OnInit {
         .subscribe(type => {
             if (!this.resourceTypes) { this.resourceTypes = []; }
             this.resourceTypes.push({id: type.id(), label: type.name()});
-        });
+        }, (err) => {},
+            () => {this.resourceTypes.sort((a, b) => a.label.localeCompare(b.label));});
         this.clear = () => {
             this.resourceTypeCombobox.selected = {id: '', label: ''};
         };
index e710c34..3661017 100644 (file)
           Booking
         </a>
         <div class="dropdown-menu" ngbDropdownMenu>
-          <a class="dropdown-item" href="/eg/staff/booking/legacy/booking/reservation">
+          <a class="dropdown-item" href="staff/booking/create_reservation">
             <span class="material-icons">add</span>
             <span i18n>Create Reservations</span>
           </a>
index fba3f0b..98706f2 100644 (file)
@@ -19957,6 +19957,12 @@ VALUES (
         'Sticky setting for granularity combobox in Booking Create',
         'cwst', 'label')
 ), (
+    'eg.booking.create.multiday', 'gui', 'bool',
+    oils_i18n_gettext(
+        'booking.create.multiday',
+        'Default to creating multiday booking reservations',
+        'cwst', 'label')
+), (
     'eg.booking.pickup.ready.only_show_captured', 'gui', 'bool',
     oils_i18n_gettext(
         'booking.pickup.ready.only_show_captured',
index e901ba5..aa53c4b 100644 (file)
@@ -62,6 +62,12 @@ VALUES (
         'Sticky setting for granularity combobox in Booking Create',
         'cwst', 'label')
 ), (
+    'eg.booking.create.multiday', 'gui', 'bool',
+    oils_i18n_gettext(
+        'booking.create.multiday',
+        'Default to creating multiday booking reservations',
+        'cwst', 'label')
+), (
     'eg.booking.pickup.ready.only_show_captured', 'gui', 'bool',
     oils_i18n_gettext(
         'booking.pickup.ready.only_show_captured',
index 50b9494..3235fd4 100644 (file)
@@ -219,6 +219,11 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
             </a>
           </li>
           <li>
+            <a href="/eg2/staff/booking/create_reservation/for_patron/{{patron().id()}}" target="_top">
+              [% l('Booking: Create Reservation') %]
+            </a>
+          </li>
+          <li>
             <a href="/eg2/staff/booking/pickup/by_patron/{{patron().id()}}" target="_top">
               [% l('Booking: Pick Up Reservations') %]
             </a>
index 226e12b..fc10567 100644 (file)
         </a>
         <ul uib-dropdown-menu>
           <li>
-            <a href="./booking/legacy/booking/reservation" target="_self">
+            <a href="/eg2/staff/booking/create_reservation" target="_self">
               <span class="glyphicon glyphicon-plus"></span>
               [% l('Create Reservations') %]
             </a>
index 90aa9bd..ccaa14f 100644 (file)
@@ -878,75 +878,10 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
         });
     }
 
-    $scope.book_copies_now = function() {
-        var copies_by_record = {};
-        var record_list = [];
-        angular.forEach(
-            $scope.holdingsGridControls.selectedItems(),
-            function (item) {
-                var record_id = item['call_number.record.id'];
-                if (typeof copies_by_record[ record_id ] == 'undefined') {
-                    copies_by_record[ record_id ] = [];
-                    record_list.push( record_id );
-                }
-                copies_by_record[ record_id ].push(item.id);
-            }
-        );
-
-        var promises = [];
-        var combined_brt = [];
-        var combined_brsrc = [];
-        angular.forEach(record_list, function(record_id) {
-            promises.push(
-                egCore.net.request(
-                    'open-ils.booking',
-                    'open-ils.booking.resources.create_from_copies',
-                    egCore.auth.token(),
-                    copies_by_record[record_id]
-                ).then(function(results) {
-                    if (results && results['brt']) {
-                        combined_brt = combined_brt.concat(results['brt']);
-                    }
-                    if (results && results['brsrc']) {
-                        combined_brsrc = combined_brsrc.concat(results['brsrc']);
-                    }
-                })
-            );
-        });
-
-        $q.all(promises).then(function() {
-            if (combined_brt.length > 0 || combined_brsrc.length > 0) {
-                $uibModal.open({
-                    template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
-                    backdrop: 'static',
-                    animation: true,
-                    size: 'md',
-                    controller:
-                           ['$scope','$location','egCore','$uibModalInstance',
-                    function($scope , $location , egCore , $uibModalInstance) {
-
-                        $scope.funcs = {
-                            ses : egCore.auth.token(),
-                            bresv_interface_opts : {
-                                booking_results : {
-                                     brt : combined_brt
-                                    ,brsrc : combined_brsrc
-                                }
-                            }
-                        }
-
-                        var booking_path = '/eg/booking/reservation';
-
-                        $scope.booking_admin_url =
-                            $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
-
-                    }]
-                });
-            }
-        });
+    $scope.book_copies_now = function(barcode) {
+        location.href = "/eg2/staff/booking/create_reservation/for_resource/" + barcode;
     }
 
-
     $scope.requestItems = function() {
         var copy_list = gatherSelectedHoldingsIds();
         if (copy_list.length == 0) return;
index 2ee03de..6afa305 100644 (file)
@@ -94,10 +94,7 @@ function($scope , $q , $window , $location , $timeout , egCore , egNet , egGridD
     }
 
     $scope.book_copies_now = function() {
-        itemSvc.book_copies_now([{
-            id : $scope.args.copyId,
-            'call_number.record.id' : $scope.args.recordId
-        }]);
+        itemSvc.book_copies_now([$scope.args.copyBarcode]);
     }
 
     $scope.findAcquisition = function() {
@@ -485,7 +482,9 @@ function($scope , $q , $window , $location , $timeout , egCore , egNet , egGridD
     }
 
     $scope.book_copies_now = function() {
-        itemSvc.book_copies_now(copyGrid.selectedItems());
+        var item = copyGrid.selectedItems()[0];
+        if (item)
+            itemSvc.book_copies_now(item.barcode);
     }
 
     $scope.manage_reservations = function() {
index 2774132..598658e 100644 (file)
@@ -350,72 +350,8 @@ function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog
         });
     }
 
-    service.book_copies_now = function(items) {
-        var copies_by_record = {};
-        var record_list = [];
-        angular.forEach(
-            items,
-            function (item) {
-                var record_id = item['call_number.record.id'];
-                if (typeof copies_by_record[ record_id ] == 'undefined') {
-                    copies_by_record[ record_id ] = [];
-                    record_list.push( record_id );
-                }
-                copies_by_record[ record_id ].push(item.id);
-            }
-        );
-
-        var promises = [];
-        var combined_brt = [];
-        var combined_brsrc = [];
-        angular.forEach(record_list, function(record_id) {
-            promises.push(
-                egCore.net.request(
-                    'open-ils.booking',
-                    'open-ils.booking.resources.create_from_copies',
-                    egCore.auth.token(),
-                    copies_by_record[record_id]
-                ).then(function(results) {
-                    if (results && results['brt']) {
-                        combined_brt = combined_brt.concat(results['brt']);
-                    }
-                    if (results && results['brsrc']) {
-                        combined_brsrc = combined_brsrc.concat(results['brsrc']);
-                    }
-                })
-            );
-        });
-
-        $q.all(promises).then(function() {
-            if (combined_brt.length > 0 || combined_brsrc.length > 0) {
-                $uibModal.open({
-                    template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
-                    backdrop: 'static',
-                    animation: true,
-                    size: 'md',
-                    controller:
-                           ['$scope','$location','egCore','$uibModalInstance',
-                    function($scope , $location , egCore , $uibModalInstance) {
-
-                        $scope.funcs = {
-                            ses : egCore.auth.token(),
-                            bresv_interface_opts : {
-                                booking_results : {
-                                     brt : combined_brt
-                                    ,brsrc : combined_brsrc
-                                }
-                            }
-                        }
-
-                        var booking_path = '/eg/booking/reservation';
-
-                        $scope.booking_admin_url =
-                            $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
-
-                    }]
-                });
-            }
-        });
+    service.book_copies_now = function(barcode) {
+        location.href = "/eg2/staff/booking/create_reservation/for_resource/" + barcode;
     }
 
     service.manage_reservations = function(barcode) {