From 1976fb1b5d080a28f0eeedfcda451662c56739a7 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 30 Jun 2014 10:47:13 -0400 Subject: [PATCH] initial Renew Items UI Signed-off-by: Bill Erickson --- .../src/templates/staff/circ/checkin/index.tt2 | 13 +- .../staff/circ/checkin/t_checkin_table.tt2 | 3 - Open-ILS/src/templates/staff/circ/renew/index.tt2 | 20 +++ .../src/templates/staff/circ/renew/t_renew.tt2 | 144 +++++++++++++++++ .../templates/staff/circ/share/circ_strings.tt2 | 1 + Open-ILS/src/templates/staff/css/circ.css.tt2 | 1 + Open-ILS/src/templates/staff/css/style.css.tt2 | 9 +- Open-ILS/src/templates/staff/navbar.tt2 | 6 + .../web/js/ui/default/staff/circ/checkin/app.js | 32 +--- .../js/ui/default/staff/circ/patron/checkout.js | 2 + Open-ILS/web/js/ui/default/staff/circ/renew/app.js | 159 +++++++++++++++++++ .../web/js/ui/default/staff/circ/services/circ.js | 176 +++++++++++++++++++-- 12 files changed, 516 insertions(+), 50 deletions(-) create mode 100644 Open-ILS/src/templates/staff/circ/renew/index.tt2 create mode 100644 Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 create mode 100644 Open-ILS/web/js/ui/default/staff/circ/renew/app.js diff --git a/Open-ILS/src/templates/staff/circ/checkin/index.tt2 b/Open-ILS/src/templates/staff/circ/checkin/index.tt2 index fea404bb16..ed53423a67 100644 --- a/Open-ILS/src/templates/staff/circ/checkin/index.tt2 +++ b/Open-ILS/src/templates/staff/circ/checkin/index.tt2 @@ -15,8 +15,13 @@ [% END %] - -
+
+
+ [% l('Checkin Items') %] +
+
+ +
@@ -55,7 +60,7 @@
-
+
@@ -97,7 +102,7 @@
-
+
[% l('Fine Tally:') %] {{fine_total | currency}} diff --git a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 index 3a855f66d6..6559ce7438 100644 --- a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 +++ b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 @@ -57,7 +57,6 @@ - @@ -87,7 +86,5 @@ - - diff --git a/Open-ILS/src/templates/staff/circ/renew/index.tt2 b/Open-ILS/src/templates/staff/circ/renew/index.tt2 new file mode 100644 index 0000000000..415556b242 --- /dev/null +++ b/Open-ILS/src/templates/staff/circ/renew/index.tt2 @@ -0,0 +1,20 @@ +[% + WRAPPER "staff/base.tt2"; + ctx.page_title = l("Renew"); + ctx.page_app = "egRenewApp"; +%] + +[% BLOCK APP_JS %] + + + + +[% INCLUDE 'staff/circ/share/circ_strings.tt2' %] + + + +[% END %] + +
+ +[% END %] diff --git a/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 b/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 new file mode 100644 index 0000000000..ea8efe5d01 --- /dev/null +++ b/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 @@ -0,0 +1,144 @@ + + +
+
+ [% l('Renew Items') %] +
+
+ +
+
+ +
+ + + + + + +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + {{item.copy_barcode}} + + + + + + + + + + + + + + + + + + + + + + + + {{item.title}} + + + + + + + + + + + + + +
+
+
+ +
+
+
+ +
+
+ diff --git a/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 b/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 index ca2b283633..bccc9d43a4 100644 --- a/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 +++ b/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 @@ -27,6 +27,7 @@ s.MARK_DAMAGED_CONFIRM = '[% l("Mark {{num_items}} items as DAMAGED?") %]'; s.ABORT_TRANSIT_CONFIRM = '[% l("Abort {{num_transits}} transits?") %]'; s.ROUTE_TO_HOLDS_SHELF = '[% l("Holds Shelf") %]'; s.ROUTE_TO_CATALOGING = '[% l("Cataloging") %]'; +s.COPY_IN_TRANSIT = '[% l("Copy is In-Transit") %]'; }]); diff --git a/Open-ILS/src/templates/staff/css/circ.css.tt2 b/Open-ILS/src/templates/staff/css/circ.css.tt2 index ca2966c18a..7bf4080303 100644 --- a/Open-ILS/src/templates/staff/css/circ.css.tt2 +++ b/Open-ILS/src/templates/staff/css/circ.css.tt2 @@ -10,6 +10,7 @@ but the ones I'm finding aren't quite cutting it..*/ .patron-summary-act-link {font-size: .8em;} #patron-checkout-barcode, +#patron-renewal-barcode, #patron-checkin-barcode { width: 16em; } #patron-search-form div.form-group { diff --git a/Open-ILS/src/templates/staff/css/style.css.tt2 b/Open-ILS/src/templates/staff/css/style.css.tt2 index 63b06cacd6..ebfa1b1cc3 100644 --- a/Open-ILS/src/templates/staff/css/style.css.tt2 +++ b/Open-ILS/src/templates/staff/css/style.css.tt2 @@ -105,7 +105,7 @@ padding-bottom: 10px; } -table.list tr.selected td { +table.list tr.selected td { /* deprecated? */ color: #2a6496; background-color: #F5F5F5; } @@ -155,9 +155,14 @@ table.list tr.selected td { width: 8em; } -/* barcode inputs are everywhere. Let's have a consistent style */ +/* barcode inputs are everywhere. Let's have a consistent style. + * In most cases, form-control (etc.) CSS overrides this, so we + * still have to use id-based style. */ .barcode { width: 16em; } +/* bootstrap alerts are heavily padded. use this to reduce */ +.alert-less-pad {padding: 5px;} + /* ---------------------------------------------------------------------- * Grid * ---------------------------------------------------------------------- */ diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2 index 0d1981d7a7..61c4bf8bcd 100644 --- a/Open-ILS/src/templates/staff/navbar.tt2 +++ b/Open-ILS/src/templates/staff/navbar.tt2 @@ -74,6 +74,12 @@
  • + + + [% l('Renew Items') %] + +
  • +
  • [% l('Retrieve Last Patron') %] diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js index f47ac93939..366dd262b0 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js @@ -154,34 +154,10 @@ function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataPro egCirc.checkin(params, options).then( function(final_resp) { - var payload = final_resp.evt.payload; - - if (payload) { - row_item.circ = payload.circ; - row_item.hold = payload.hold; - row_item.mbts = payload.circ ? - payload.circ.billable_transaction().summary() : null; - row_item.record = payload.record; - row_item.acp = payload.copy; - row_item.acn = payload.volume ? - payload.volume : payload.copy.call_number(); - row_item.au = payload.patron; - row_item.transit = payload.transit; - row_item.status = payload.status; - row_item.message = payload.message; - row_item.title = final_resp.evt.title; - row_item.author = final_resp.evt.author; - row_item.isbn = final_resp.evt.isbn; - row_item.route_to = final_resp.evt.route_to; - } - - if (!row_item.route_to) { - if (row_item.transit) { - row_item.route_to = row_item.transit.dest().shortname(); - } else if (row_item.acp) { - row_item.route_to = row_item.acp.location().name(); - } - } + row_item.evt = final_resp.evt; + angular.forEach(final_resp.data, function(val, key) { + row_item[key] = val; + }); if (row_item.mbts) { var amt = Number(row_item.mbts.balance_owed()); diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js b/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js index ffa4347359..94314a2996 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js @@ -115,6 +115,8 @@ function($scope , $q , $modal , $routeParams , egCore , egUser , patronSvc , // update stats locally so we don't have to fetch them w/ // each checkout. patronSvc.patron_stats.checkouts.out++ + + // TODO: munge from egCirc munge_checkout_resp(co_resp); diff --git a/Open-ILS/web/js/ui/default/staff/circ/renew/app.js b/Open-ILS/web/js/ui/default/staff/circ/renew/app.js new file mode 100644 index 0000000000..5a960bd097 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/circ/renew/app.js @@ -0,0 +1,159 @@ +/** + * Renewal + */ + +angular.module('egRenewApp', + ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod']) + +.config(function($routeProvider, $locationProvider, $compileProvider) { + $locationProvider.html5Mode(true); + $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export + var resolver = {delay : function(egStartup) {return egStartup.go()}}; + + $routeProvider.when('/circ/renew/renew', { + templateUrl: './circ/renew/t_renew', + controller: 'RenewCtrl', + resolve : resolver + }); + + $routeProvider.when('/circ/renew/renew', { + templateUrl: './circ/renew/t_renew', + controller: 'RenewCtrl', + resolve : resolver + }); + + $routeProvider.otherwise({redirectTo : '/circ/renew/renew'}); +}) + + + + +.controller('RenewCtrl', + + ['$scope','egCore','egGridDataProvider','egCirc', + +function($scope , egCore , egGridDataProvider , egCirc) { + + $scope.focusBarcode = true; + $scope.renewals = []; + + var today = new Date(); + $scope.renewalsArgs = {due_date : today}; + + $scope.gridDataProvider = egGridDataProvider.instance({ + get : function(offset, count) { + return this.arrayNotifier($scope.renewals, offset, count); + } + }); + + // avoid multiple, in-flight attempts on the same barcode + var pending_barcodes = {}; + + $scope.renew = function(args) { + var params = angular.copy(args); + + if (args.sticky_date) { + params.due_date = args.due_date.toISOString(); + } else { + delete params.due_date; + } + delete params.sticky_date; + if (!args.copy_barcode) return; + + args.copy_barcode = ''; // reset UI input + + if (pending_barcodes[params.copy_barcode]) { + console.log( + "Skipping renewals of redundant barcode " + + params.copy_barcode + ); + return; + } + + pending_barcodes[params.copy_barcode] = true; + send_renewal(params); + + $scope.focusBarcode = true; // return focus to barcode input + } + + function send_renewal(params) { + + params.noncat_type = params.noncat ? params.noncat_type : ''; + + // populate the grid row before we send the request so that the + // order of actions is maintained and so the user gets an + // immediate reaction to their barcode input action. + var row_item = { + index : $scope.renewals.length, + copy_barcode : params.copy_barcode, + noncat_type : params.noncat_type + }; + + $scope.renewals.unshift(row_item); + $scope.gridDataProvider.refresh(); + + var options = {check_barcode : $scope.strict_barcode}; + + egCirc.renew(params, options).then( + function(final_resp) { + + row_item.evt = final_resp.evt; + angular.forEach(final_resp.data, function(val, key) { + row_item[key] = val; + }); + + if (row_item.mbts) { + var amt = Number(row_item.mbts.balance_owed()); + if (amt != 0) { + $scope.billable_barcode = row_item.copy_barcode; + $scope.billable_amount = amt; + $scope.fine_total = + ($scope.fine_total * 100 + amt * 100) / 100; + } + } + + if ($scope.trim_list && checkinSvc.checkins.length > 20) + checkinSvc.checkins = checkinSvc.checkins.splice(0, 20); + + }, + function() { + // Circ was rejected somewhere along the way. + // Remove the copy from the grid since there was no action. + // note: since renewals are unshifted onto the array, the + // index value does not (generally) match the array position. + var pos = -1; + angular.forEach($scope.renewals, function(co, idx) { + if (co.index == row_item.index) pos = idx; + }); + $scope.renewals.splice(pos, 1); + $scope.gridDataProvider.refresh(); + } + + )['finally'](function() { + + // regardless of the outcome of the circ, remove the + // barcode from the pending list. + if (params.copy_barcode) + delete pending_barcodes[params.copy_barcode]; + }); + } + + $scope.print_receipt = function() { + var print_data = {circulations : []} + + if ($scope.renewals.length == 0) return $q.when(); + + angular.forEach($srenewalpe.renewals, function(co) { + var circ = egCore.idl.toHash(renewal.payload.circ); + circ.title = renewal.payload.record.title; + print_data.circulations.push(circ); + }); + + return egCore.print.print({ + context : 'default', + template : 'renew', + scope : print_data, + }); + } +}]) + diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js index 902eef75ea..e3f1be3328 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js +++ b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js @@ -45,6 +45,25 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) { 'PATRON_EXCEEDS_FINES' ] + + // overridable during renewal + service.renew_overridable_events = [ + 'PATRON_EXCEEDS_OVERDUE_COUNT', + 'PATRON_EXCEEDS_LOST_COUNT', + 'PATRON_EXCEEDS_CHECKOUT_COUNT', + 'PATRON_EXCEEDS_FINES', + 'CIRC_EXCEEDS_COPY_RANGE', + 'ITEM_DEPOSIT_REQUIRED', + 'ITEM_RENTAL_FEE_REQUIRED', + 'ITEM_DEPOSIT_PAID', + 'COPY_CIRC_NOT_ALLOWED', + 'COPY_IS_REFERENCE', + 'COPY_ALERT_MESSAGE', + 'COPY_NEEDED_FOR_HOLD', + 'MAX_RENEWALS_REACHED', + 'CIRC_CLAIMS_RETURNED' + ]; + // these checkin events do not produce alerts when // options.suppress_alerts is in effect. service.checkin_suppress_overrides = [ @@ -102,6 +121,9 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) { .then(function() { return service.handle_checkout_resp(evt, params, options); }) + .then(function(final_resp) { + return service.munge_resp_data(final_resp) + }) }); }); } @@ -116,21 +138,33 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) { console.debug('egCirc.renew() : ' + js2JSON(params) + ' : ' + js2JSON(options)); - var method = 'open-ils.circ.renew'; - if (options.override) method += '.override'; + var promise = options.check_barcode ? + service.test_barcode(params.copy_barcode) : $q.when(); - return egCore.net.request( - 'open-ils.circ', method, egCore.auth.token(), params + // avoid re-check on override, etc. + delete options.check_barcode; + + return promise.then(function() { - ).then(function(evt) { + var method = 'open-ils.circ.renew'; + if (options.override) method += '.override'; + + return egCore.net.request( + 'open-ils.circ', method, egCore.auth.token(), params - if (angular.isArray(evt)) evt = evt[0]; + ).then(function(evt) { + + if (angular.isArray(evt)) evt = evt[0]; - return service.flesh_response_data( - 'renew', evt, params, options) - .then(function() { - return service.handle_checkout_resp(evt, params, options); - }) + return service.flesh_response_data( + 'renew', evt, params, options) + .then(function() { + return service.handle_renew_resp(evt, params, options); + }) + .then(function(final_resp) { + return service.munge_resp_data(final_resp) + }) + }); }); } @@ -166,10 +200,50 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) { .then(function() { return service.handle_checkin_resp(evt, params, options); }) + .then(function(final_resp) { + return service.munge_resp_data(final_resp) + }) }); }); } + // provide consistent formatting of the final response data + service.munge_resp_data = function(final_resp) { + var data = final_resp.data = {}; + + if (!final_resp.evt) return; + + var payload = final_resp.evt.payload; + if (!payload) return; + + data.circ = payload.circ; + data.hold = payload.hold; + data.record = payload.record; + data.acp = payload.copy; + data.acn = payload.volume ? payload.volume : payload.copy.call_number(); + data.au = payload.patron; + data.transit = payload.transit; + data.status = payload.status; + data.message = payload.message; + data.title = final_resp.evt.title; + data.author = final_resp.evt.author; + data.isbn = final_resp.evt.isbn; + data.route_to = final_resp.evt.route_to; + + if (payload.circ && payload.circ.billable_transaction()) + data.mbts = payload.circ.billable_transaction().summary(); + + if (!data.route_to) { + if (data.transit) { + data.route_to = data.transit.dest().shortname(); + } else if (data.acp) { + data.route_to = data.acp.location().name(); + } + } + + return final_resp; + } + service.handle_overridable_checkout_event = function(evt, params, options) { if (options.override) { @@ -198,10 +272,42 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) { case 'COPY_ALERT_MESSAGE': return service.copy_alert_dialog(evt, params, options, 'checkout'); default: - return service.override_dialog(evt, params, options, 'checkin'); + return service.override_dialog(evt, params, options, 'checkout'); } } + service.handle_overridable_renew_event = function(evt, params, options) { + + if (options.override) { + // override attempt already made and failed. + // NOTE: I don't think we'll ever get here, since the + // override attempt should produce a perm failure... + console.debug('override failed: ' + evt.textcode); + return $q.reject(); + + } + + // renewal auto-overrides are the same as checkout + if (service.auto_override_checkout_events[evt.textcode]) { + // user has already opted to override this type + // of event. Re-run the renew w/ override. + options.override = true; + return service.renew(params, options); + } + + // Ask the user if they would like to override this event. + // Some events offer a stock override dialog, while others + // require additional context. + + switch(evt.textcode) { + case 'COPY_ALERT_MESSAGE': + return service.copy_alert_dialog(evt, params, options, 'renew'); + default: + return service.override_dialog(evt, params, options, 'renew'); + } + } + + service.handle_overridable_checkin_event = function(evt, params, options) { if (options.override) { @@ -233,6 +339,50 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) { } + service.handle_renew_resp = function(evt, params, options) { + + var final_resp = {evt : evt, params : params, options : options}; + + // track the barcode regardless of whether it refers to a copy + evt.copy_barcode = params.copy_barcode; + + // Overridable Events + if (service.renew_overridable_events.indexOf(evt.textcode) > -1) + return service.handle_overridable_renew_event(evt, params, options); + + // Other events + switch (evt.textcode) { + case 'SUCCESS': + return $q.when(final_resp); + + case 'COPY_IN_TRANSIT': + case 'PATRON_CARD_INACTIVE': + case 'PATRON_INACTIVE': + case 'PATRON_ACCOUNT_EXPIRED': + case 'CIRC_CLAIMS_RETURNED': + return service.exit_alert( + egCore.strings[evt.textcode], + {barcode : params.copy_barcode} + ); + + case 'PERM_FAILURE': + return service.exit_alert( + egCore.strings[evt.textcode], + {permission : evt.ilsperm} + ); + + default: + return service.exit_alert( + egCore.strings.CHECKOUT_FAILED_GENERIC, { + barcode : params.copy_barcode, + textcode : evt.textcode, + desc : evt.desc + } + ); + } + } + + service.handle_checkout_resp = function(evt, params, options) { var final_resp = {evt : evt, params : params, options : options}; @@ -421,7 +571,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) { if (service.checkout_auto_override_after_first.indexOf(evt.textcode) > -1) service.auto_override_checkout_events[evt.textcode] = true; - return service.checkout(params, options); + return service[action](params, options); } ); } -- 2.11.0