<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/billing.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
[% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/holds.js"></script>
+[% INCLUDE 'staff/circ/share/hold_strings.tt2' %]
<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/patron/app.js"></script>
<!-- load the rest on demand? -->
[% END %]
-
<div class="row">
<div class="col-md-3">
<div ng-show="patron()">
'{{patron().first_given_name()}}',
'{{patron().second_given_name()}}') %]
</div>
- <div>
- <a href ng-click="collapsePatronSummary=true"
+ <div ng-show="tab != 'search'">
+ <a href ng-click="toggle_expand_summary()"
title="[% l('Collapse Patron Summary Display') %]"
- ng-hide="collapsePatronSummary">
+ ng-hide="collapse_summary()">
<span class="glyphicon glyphicon-resize-small"></span>
</a>
- <a href ng-click="collapsePatronSummary=false"
+ <a href ng-click="toggle_expand_summary()"
title="[% l('Expand Patron Summary Display') %]"
- ng-show="collapsePatronSummary">
+ ng-show="collapse_summary()">
<span class="glyphicon glyphicon-resize-full"></span>
</a>
</div>
</div><!-- row -->
<div class="row">
- <div class="col-md-3" ng-hide="collapsePatronSummary">
+ <div class="col-md-3" ng-hide="collapse_summary()">
[% INCLUDE 'staff/circ/patron/t_summary.tt2' %]
</div>
- <div ng-class="{'col-md-12' : collapsePatronSummary,'col-md-9' : !collapsePatronSummary}">
+ <div ng-class="{'col-md-12' : collapse_summary(),'col-md-9' : !collapse_summary()}">
<div class="tab-content">
<div class="tab-pane active">
<div ng-view></div>
<!-- patron holds list -->
+<ul class="nav nav-tabs">
+ <li ng-class="{active : holds_display == 'main'}">
+ <a href ng-click="show_main_list()">[% l('Open Hold Requests') %]</a>
+ </li>
+ <li ng-class="{active : holds_display == 'alt'}">
+ <a href ng-click="show_alt_list()">[% l('Recently Canceled Holds') %]</a>
+ </li>
+</ul>
+
+<div class="pad-vert"></div>
+
<eg-grid
id-field="id"
- features="-display,-sort,-multisort"
- main-label="[% l('Items On Hold') %]"
+ features="-sort,-multisort"
items-provider="gridDataProvider"
persist-key="circ.patron.holds">
- <eg-grid-field label="[% ('Hold ID') %]" path='hold.id' visible></eg-grid-field>
- <eg-grid-field label="[% ('Current Copy') %]" path='hold.current_copy.barcode' visible></eg-grid-field>
- <eg-grid-field label="[% l('Request Date') %]" path='hold.request_time' visible></eg-grid-field>
- <eg-grid-field label="[% l('Capture Date') %]" path='hold.capture_time' visible></eg-grid-field>
- <eg-grid-field label="[% l('Available Date') %]" path='hold.shelf_time' visible></eg-grid-field>
- <eg-grid-field label="[% l('Hold Type') %]" path='hold.hold_type' visible></eg-grid-field>
- <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pickup_lib.shortname' visible></eg-grid-field>
- <eg-grid-field label="[% l('Title') %]" path='mvr.title' visible></eg-grid-field>
- <eg-grid-field label="[% l('Author') %]" path='mvr.author' visible></eg-grid-field>
+
+ <eg-grid-action handler="cancel_hold"
+ label="[% l('Cancel Hold') %]"></eg-grid-action>
+
+ <eg-grid-field label="[% l('Hold ID') %]" path='hold.id'></eg-grid-field>
+ <eg-grid-field label="[% l('Current Copy') %]" path='hold.current_copy.barcode'></eg-grid-field>
+ <eg-grid-field label="[% l('Request Date') %]" path='hold.request_time'></eg-grid-field>
+ <eg-grid-field label="[% l('Capture Date') %]" path='hold.capture_time'></eg-grid-field>
+ <eg-grid-field label="[% l('Available Date') %]" path='hold.shelf_time'></eg-grid-field>
+ <eg-grid-field label="[% l('Hold Type') %]" path='hold.hold_type'></eg-grid-field>
+ <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pickup_lib.shortname'></eg-grid-field>
+ <eg-grid-field label="[% l('Title') %]" path='mvr.title'></eg-grid-field>
+ <eg-grid-field label="[% l('Author') %]" path='mvr.author'></eg-grid-field>
+ <eg-grid-field label="[% l('Potential Copies') %]" path='potential_copies'></eg-grid-field>
+ <eg-grid-field label="[% l('Status') %]" path='status_string'></eg-grid-field>
+
+ <eg-grid-field label="[% l('Queue Position') %]" path='queue_position' hidden></eg-grid-field>
+ <eg-grid-field path='hold.*' parent-idl-class="ahr" hidden></eg-grid-field>
+ <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
+ <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
+ <eg-grid-field path='mvr.*' parent-idl-class="ahr" hidden></eg-grid-field>
</eg-grid>
--- /dev/null
+[%# Strings for circ/services/circ.js %]
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+s.HOLD_STATUS_1 = "[% l('Waiting for Copy') %]";
+s.HOLD_STATUS_2 = "[% l('Waiting for Capture') %]";
+s.HOLD_STATUS_3 = "[% l('In Transit') %]";
+s.HOLD_STATUS_4 = "[% l('Ready for Pickup') %]";
+s.HOLD_STATUS_5 = "[% l('Hold Shelf Delay') %]";
+s.HOLD_STATUS_6 = "[% l('Canceled') %]";
+s.HOLD_STATUS_7 = "[% l('Suspended') %]";
+s.HOLD_STATUS_8 = "[% l('Wrong Shelf') %]";
+}]);
+</script>
+
+
--- /dev/null
+<form ng-submit="ok()" role="form" class="form-horizontal">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">
+ [% l('Cancel [_1] Hold(s)', '{{args.num_holds}}') %]
+ </h4>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label for="hold-cancel-reason" class="control-label col-md-4">
+ [% l('Cancel Reason:') %]
+ </label>
+ <div class="col-md-8">
+ <select class="form-control" id="hold-cancel-reason"
+ ng-model="args.cancel_reason"
+ ng-options="reason.label() for reason in args.cancel_reasons">
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="hold-cancel-note" class="control-label col-md-4">
+ [% l('Note:') %]
+ </label>
+ <div class="col-md-8">
+ <textarea rows="3" class="form-control" placeholder="[% l('Note...') %]"
+ id="hold-cancel-note" ng-model="args.note"></textarea>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <input type="submit" class="btn btn-success" value="[% l('Cancel Hold') %]"/>
+ <button class="btn btn-warning" ng-click="cancel($event)">[% l('Exit') %]</button>
+ </div>
+ </div>
+</div>
+
}
});
}
+
+ $scope.toggle_expand_summary = function() {
+ if ($scope.collapsePatronSummary) {
+ $scope.collapsePatronSummary = false;
+ egCore.hatch.removeItem('eg.circ.patron.summary.collapse');
+ } else {
+ $scope.collapsePatronSummary = true;
+ egCore.hatch.setItem('eg.circ.patron.summary.collapse', true);
+ }
+ }
+
+ // always expand the patron summary in the search UI, regardless
+ // of stored preference.
+ $scope.collapse_summary = function() {
+ return $scope.tab != 'search' && $scope.collapsePatronSummary;
+ }
+
+ egCore.hatch.getItem('eg.circ.patron.summary.collapse')
+ .then(function(val) {$scope.collapsePatronSummary = Boolean(val)});
}])
.controller('PatronBarcodeSearchCtrl',
angular.module('egPatronApp').controller('PatronHoldsCtrl',
- ['$scope','$q','$routeParams','egCore','egUser','patronSvc','egGridDataProvider',
-function($scope, $q, $routeParams, egCore, egUser, patronSvc, egGridDataProvider) {
+ ['$scope','$q','$routeParams','egCore','egUser','patronSvc','egGridDataProvider','egHolds',
+function($scope, $q, $routeParams, egCore, egUser, patronSvc, egGridDataProvider , egHolds) {
$scope.initTab('holds', $routeParams.id);
+ $scope.holds_display = 'main';
+
+ function refresh_all() {
+ patronSvc.refreshPrimary();
+ patronSvc.holds = [];
+ patronSvc.hold_ids = [];
+ provider.refresh()
+ }
+
+ $scope.show_main_list = function() {
+ // don't need a full reset_page() to swap tabs
+ $scope.holds_display = 'main';
+ patronSvc.holds = [];
+ patronSvc.hold_ids = [];
+ provider.refresh();
+ }
+
+ $scope.show_alt_list = function() {
+ // don't need a full reset_page() to swap tabs
+ $scope.holds_display = 'alt';
+ patronSvc.holds = [];
+ patronSvc.hold_ids = [];
+ provider.refresh();
+ }
var provider = egGridDataProvider.instance({});
$scope.gridDataProvider = provider;
).then(null, null, function(hold_data) {
$scope.loading = false;
+
+ hold_data.status_string =
+ egCore.strings['HOLD_STATUS_' + hold_data.status]
+ || hold_data.status;
+
var hold = hold_data.hold;
hold_data.id = hold.id();
hold.pickup_lib(egCore.org.get(hold.pickup_lib())); // flesh
// see if we have the requested range cached
if (patronSvc.holds[offset]) {
- return provider.arrayNotifier(
- patronSvc.holds, offset, count
- );
+ return provider.arrayNotifier(patronSvc.holds, offset, count);
}
// see if we have the holds IDs for this range already loaded
var deferred = $q.defer();
patronSvc.hold_ids = [];
+ var method = 'open-ils.circ.holds.id_list.retrieve.authoritative';
+ if ($scope.holds_display == 'alt')
+ method = 'open-ils.circ.holds.canceled.id_list.retrieve.authoritative';
+
egCore.net.request(
- 'open-ils.circ',
- 'open-ils.circ.holds.id_list.retrieve.authoritative',
+ 'open-ils.circ', method,
egCore.auth.token(), $scope.patron_id
).then(function(hold_ids) {
- console.log('fetched hold ids ' + hold_ids);
-
if (!hold_ids.length) { deferred.resolve(); return; }
patronSvc.hold_ids = hold_ids;
- fetchHolds(offset, count).then(null, null, deferred.notify);
+ fetchHolds(offset, count)
+ .then(deferred.resolve, null, deferred.notify);
});
return deferred.promise;
}
+
+ $scope.cancel_hold = function(items) {
+ var hold_ids = items.filter(function(item) {
+ return !item.hold.cancel_time();
+ }).map(function(item) {return item.hold.id()});
+
+ return egHolds.cancel_holds(hold_ids).then(refresh_all);
+ }
}])
$scope.gridDataProvider = provider;
function fetch_circs(id_list, offset, count) {
+ if (!id_list.length) return $q.when();
// fetch the lot of circs and stream the results back via notify
return egCore.pcrud.search('circ', {id : id_list},
--- /dev/null
+/**
+ * Holds, yo
+ */
+
+angular.module('egCoreMod')
+
+.factory('egHolds',
+
+ ['$modal','$q','egCore','egAlertDialog','egConfirmDialog',
+function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
+
+ var service = {};
+
+ service.cancel_holds = function(hold_ids) {
+
+ return $modal.open({
+ templateUrl : './circ/share/t_cancel_hold_dialog',
+ controller :
+ ['$scope', '$modalInstance', 'cancel_reasons',
+ function($scope, $modalInstance, cancel_reasons) {
+ $scope.args = {
+ cancel_reasons : cancel_reasons,
+ num_holds : hold_ids.length
+ };
+
+ $scope.cancel = function($event) {
+ $modalInstance.dismiss();
+ $event.preventDefault();
+ }
+
+ $scope.ok = function() {
+
+ function cancel_one() {
+ var hold_id = hold_ids.pop();
+ if (!hold_id) {
+ $modalInstance.close();
+ return;
+ }
+ egCore.net.request(
+ 'open-ils.circ', 'open-ils.circ.hold.cancel',
+ egCore.auth.token(), hold_id,
+ $scope.args.cancel_reason.id(),
+ $scope.args.note
+ ).then(function(resp) {
+ if (evt = egCore.evt.parse(resp)) {
+ console.error('unable to cancel hold: '
+ + evt.toString());
+ }
+ cancel_one();
+ });
+ }
+
+ cancel_one();
+ }
+ }
+ ],
+ resolve : {
+ cancel_reasons : function() {
+ return service.get_cancel_reasons();
+ }
+ }
+ }).result;
+ }
+
+ service.get_cancel_reasons = function() {
+ if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
+ return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
+ .then(function(list) {
+ egCore.env.absorbList(list, 'ahrcc')
+ return list;
+ });
+ }
+
+ return service;
+}])
+
+