1 angular.module('egCheckinApp', ['ngRoute', 'ui.bootstrap',
2 'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
4 .config(function($routeProvider, $locationProvider, $compileProvider) {
5 $locationProvider.html5Mode(true);
6 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
8 var resolver = {delay :
9 ['egStartup', function(egStartup) {return egStartup.go()}]}
11 $routeProvider.when('/circ/checkin/checkin', {
12 templateUrl: './circ/checkin/t_checkin',
13 controller: 'CheckinCtrl',
17 $routeProvider.when('/circ/checkin/capture', {
18 templateUrl: './circ/checkin/t_checkin',
19 controller: 'CheckinCtrl',
23 $routeProvider.otherwise({redirectTo : '/circ/checkin/checkin'});
26 .factory('checkinSvc', [function() {
28 service.checkins = [];
36 .controller('CheckinCtrl',
37 ['$scope','$q','$window','$location', '$timeout','egCore','checkinSvc','egGridDataProvider','egCirc', 'egItem',
38 function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , egGridDataProvider , egCirc, itemSvc) {
40 $scope.focusMe = true;
41 $scope.checkins = checkinSvc.checkins;
42 var today = new Date(new Date().setHours(0,0,0,0));
43 $scope.checkinArgs = {backdate : today}
44 $scope.modifiers = {};
45 $scope.fine_total = 0;
46 $scope.is_capture = $location.path().match(/capture$/);
47 var suppress_popups = false;
48 $scope.grid_persist_key = $scope.is_capture ?
49 'circ.checkin.capture' : 'circ.checkin.checkin';
51 egCore.hatch.usePrinting().then(function(useHatch) {
52 $scope.using_hatch_printer = useHatch;
55 // TODO: add this to the setting batch lookup below
56 egCore.hatch.getItem('circ.checkin.strict_barcode')
57 .then(function(sb){ $scope.strict_barcode = sb });
60 'ui.circ.suppress_checkin_popups' // add other settings as needed
61 ]).then(function(set) {
62 suppress_popups = set['ui.circ.suppress_checkin_popups'];
65 $scope.sort_money = function (a,b) {
66 var ma = parseFloat(a);
67 var mb = parseFloat(b);
68 if (ma < mb) return -1;
69 if (ma > mb) return 1;
73 // checkin & hold capture modifiers
84 if ($scope.is_capture) {
85 // in hold capture mode, some values are forced, regardless
86 // of stored preferences.
87 $scope.modifiers.noop = false;
88 $scope.modifiers.auto_print_holds_transits = true;
90 modifiers.push('noop'); // AKA suppress holds and transits
91 modifiers.push('auto_print_holds_transits');
92 modifiers.push('do_inventory_update');
94 // backdate is possible so load options
95 $scope.backdate = {date: egCore.hatch.getSessionItem('eg.circ.checkin.backdate')};
96 $scope.backdate.untilLogout = !!$scope.backdate.date;
97 if ($scope.backdate.untilLogout)
98 $scope.checkinArgs.backdate = new Date($scope.backdate.date);
100 // watch backdate to enable/disable the sticky option
101 // and ensure the backdate is not in the future
102 // note: input type=date max=foo not yet supported anywhere
103 $scope.$watch('checkinArgs.backdate', function(newval) {
104 if (!newval || newval.getTime() == today.getTime()) {
105 $scope.backdate.untilLogout = false;
106 egCore.hatch.removeSessionItem('eg.circ.checkin.backdate');
107 } else if (newval > today) {
108 $scope.checkinArgs.backdate = today;
109 } else if ($scope.backdate.untilLogout) {
110 egCore.hatch.setSessionItem('eg.circ.checkin.backdate', newval);
115 // set modifiers from stored preferences
116 var snames = modifiers.map(function(m) {return 'eg.circ.checkin.' + m;});
117 egCore.hatch.getItemBatch(snames).then(function(settings) {
118 angular.forEach(settings, function(val, key) {
120 var parts = key.split('.')
121 var mod = parts.pop();
122 $scope.modifiers[mod] = true;
127 // set / unset a checkin modifier
128 // when set, store the preference
129 $scope.toggle_mod = function(mod) {
130 if ($scope.modifiers[mod]) {
131 $scope.modifiers[mod] = false;
132 egCore.hatch.removeItem('eg.circ.checkin.' + mod);
134 $scope.modifiers[mod] = true;
135 egCore.hatch.setItem('eg.circ.checkin.' + mod, true);
139 $scope.onUntilLogoutChange = function() {
140 if ($scope.backdate.untilLogout)
141 egCore.hatch.setSessionItem('eg.circ.checkin.backdate',
142 $scope.checkinArgs.backdate
145 egCore.hatch.removeSessionItem('eg.circ.checkin.backdate');
148 $scope.is_backdate = function() {
149 return $scope.checkinArgs.backdate && $scope.checkinArgs.backdate < today;
152 var checkinGrid = $scope.gridControls = {};
154 $scope.gridDataProvider = egGridDataProvider.instance({
155 get : function(offset, count) {
156 return this.arrayNotifier($scope.checkins, offset, count);
160 // turns the various inputs (form args, modifiers, etc.) into
161 // checkin params and options.
162 function compile_checkin_args(args) {
163 var params = angular.copy(args);
165 // a backdate of 'today' is not really a backdate
166 // (and this particularly matters when checking in hourly
167 // loans, as backdated checkins currently get the time
168 // portion of the checkin time from the due date; this will
169 // stop mattering when FIXME bug 1793817 is dealt with)
170 if (!$scope.is_backdate())
171 delete params.backdate;
173 if (params.backdate) {
175 params.backdate.toISOString().replace(/T.*/,'');
178 angular.forEach(['noop','void_overdues',
179 'clear_expired','hold_as_transit','manual_float'],
181 if ($scope.modifiers[opt]) params[opt] = true;
185 if ($scope.modifiers.retarget_holds) {
186 if ($scope.modifiers.retarget_holds_all) {
187 params.retarget_mode = 'retarget.all';
189 params.retarget_mode = 'retarget';
192 if ($scope.modifiers.do_inventory_update) params.do_inventory_update = true;
194 egCore.hatch.setItem('circ.checkin.strict_barcode', $scope.strict_barcode);
195 egCore.hatch.setItem('circ.checkin.do_inventory_update', $scope.modifiers.do_inventory_update);
197 check_barcode : $scope.strict_barcode,
198 no_precat_alert : $scope.modifiers.no_precat_alert,
199 auto_print_holds_transits :
200 $scope.modifiers.auto_print_holds_transits,
201 suppress_popups : suppress_popups,
202 do_inventory_update : $scope.modifiers.do_inventory_update
205 return {params : params, options: options};
208 $scope.checkin = function(args) {
210 var compiled = compile_checkin_args(args);
211 args.copy_barcode = ''; // reset UI for next scan
212 $scope.focusMe = true;
214 delete $scope.billable_amount;
215 delete $scope.billable_barcode;
216 delete $scope.billable_user_id;
218 var params = compiled.params;
219 var options = compiled.options;
221 if (!params.copy_barcode) return;
225 index : checkinSvc.checkins.length,
226 input_barcode : params.copy_barcode
229 // track the item in the grid before sending the request
230 checkinSvc.checkins.unshift(row_item);
231 egCirc.checkin(params, options).then(
232 function(final_resp) {
234 row_item.evt = final_resp.evt;
235 angular.forEach(final_resp.data, function(val, key) {
239 row_item['copy_barcode'] = row_item.acp.barcode();
241 if (row_item.acp.latest_inventory() && row_item.acp.latest_inventory().inventory_date() == "now")
242 row_item.acp.latest_inventory().inventory_date(Date.now());
245 var amt = Number(row_item.mbts.balance_owed());
247 $scope.billable_barcode = row_item.copy_barcode;
248 $scope.billable_amount = amt;
249 $scope.billable_user_id = row_item.circ.usr();
251 ($scope.fine_total * 100 + amt * 100) / 100;
255 if (final_resp.evt.textcode == 'NO_CHANGE') {
257 {already_checked_in : final_resp.evt.copy_barcode};
260 if ($scope.trim_list && checkinSvc.checkins.length > 20) {
261 //cut array short at 20 items
262 checkinSvc.checkins.length = 20;
263 checkinGrid.prepend(20);
265 checkinGrid.prepend();
269 // Checkin was rejected somewhere along the way.
270 // Remove the copy from the grid since there was no action.
271 // note: since checkins are unshifted onto the array, the
272 // index value does not (generally) match the array position.
274 angular.forEach(checkinSvc.checkins, function(ci, idx) {
275 if (ci.index == row_item.index) pos = idx;
277 checkinSvc.checkins.splice(pos, 1);
279 })['finally'](function() {
280 // when all is said and done, refocus
281 $scope.focusMe = true;
285 $scope.print_receipt = function() {
286 var print_data = {checkins : []}
288 if (checkinSvc.checkins.length == 0) return $q.when();
290 angular.forEach(checkinSvc.checkins, function(checkin) {
293 copy : egCore.idl.toHash(checkin.acp) || {},
294 call_number : egCore.idl.toHash(checkin.acn) || {},
295 copy_barcode : checkin.copy_barcode,
296 title : checkin.title,
297 author : checkin.author
300 print_data.checkins.push(checkin);
303 return egCore.print.print({
304 template : 'checkin',
306 show_dialog : $scope.show_print_dialog
311 // --- context menu actions
313 $scope.fetchLastCircPatron = function(items) {
314 var checkin = items[0];
315 if (!checkin || !checkin.acp) return;
317 egCirc.last_copy_circ(checkin.acp.id())
318 .then(function(circ) {
321 // jump to the patron UI (separate app)
322 $window.location.href = $location
323 .path('/circ/patron/' + circ.usr() + '/checkout')
328 $scope.alert = {item_never_circed : checkin.acp.barcode()};
332 $scope.showBackdateDialog = function(items) {
335 angular.forEach(items, function(item) {
336 if (item.circ) circ_ids.push(item.circ.id());
339 if (circ_ids.length) {
340 egCirc.backdate_dialog(circ_ids).then(function(result) {
341 angular.forEach(items, function(item) {
342 item.circ.checkin_time(result.backdate);
345 // TODO: support grid row styling
346 checkinGrid.refresh();
350 $scope.showMarkDamaged = function(items) {
352 angular.forEach(items, function(item) {
354 egCirc.mark_damaged({
356 barcode: item.acp.barcode()
364 $scope.showMarkDiscard = function(items) {
366 angular.forEach(items, function(item) {
368 copies.push(egCore.idl.toHash(item.acp));
372 egCirc.mark_discard(copies).then(function() {
373 // update grid items?
378 $scope.abortTransit = function(items) {
379 var transit_ids = [];
380 angular.forEach(items, function(item) {
381 if (item.transit) transit_ids.push(item.transit.id());
384 egCirc.abort_transits(transit_ids).then(function() {
385 // update grid items?
389 $scope.add_copies_to_bucket = function(items){
391 angular.forEach(items, function(cp){
392 itemsIds.push(cp.acp.id());
395 itemSvc.add_copies_to_bucket(itemsIds);
398 $scope.showBibHolds = function(items){
400 angular.forEach(items, function(i){
401 recordIds.push(i.acn.record());
403 angular.forEach(recordIds, function (r) {
404 var url = '/eg2/staff/catalog/record/' + r + '/holds';
405 $timeout(function() { $window.open(url, '_blank') });
409 $scope.showLastCircs = function(items){
411 angular.forEach(items, function(cp){
412 itemIds.push(cp.acp.id());
414 angular.forEach(itemIds, function (id) {
415 var url = egCore.env.basePath + 'cat/item/' + id + '/circs';
416 $timeout(function() { $window.open(url, '_blank') });
420 $scope.selectedHoldingsVolCopyEdit = function (items) {
422 angular.forEach(items, function(i){
423 var h = egCore.idl.toHash(i);
425 'call_number.record.id': h.record.doc_id,
429 itemSvc.spawnHoldingsEdit(itemObjs,false,false);
432 $scope.show_mark_missing_pieces = function(items){
433 angular.forEach(items, function(i){
434 i.acp.call_number(i.acn);
435 i.acp.call_number().record(i.record);
436 itemSvc.mark_missing_pieces(i.acp,$scope);
440 $scope.printSpineLabels = function(items){
442 angular.forEach(items, function(item) {
443 if (item.acp) copy_ids.push(item.acp.id());
445 itemSvc.print_spine_labels(copy_ids);
448 $scope.addCopyAlerts = function(items) {
450 angular.forEach(items, function(item) {
451 if (item.acp) copy_ids.push(item.acp.id());
453 egCirc.add_copy_alerts(copy_ids).then(function() {
454 // update grid items?
458 $scope.manageCopyAlerts = function(items) {
460 angular.forEach(items, function(item) {
461 if (item.acp) copy_ids.push(item.acp.id());
463 egCirc.manage_copy_alerts(copy_ids).then(function() {
464 // update grid items?