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?|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','egCore','checkinSvc','egGridDataProvider','egCirc',
38 function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataProvider , egCirc) {
40 $scope.focusMe = true;
41 $scope.checkins = checkinSvc.checkins;
42 var today = new Date();
43 $scope.checkinArgs = {backdate : today}
44 $scope.using_hatch = egCore.hatch.usingHatch();
45 $scope.modifiers = {};
46 $scope.fine_total = 0;
47 $scope.is_capture = $location.path().match(/capture$/);
48 var suppress_popups = false;
49 $scope.grid_persist_key = $scope.is_capture ?
50 'circ.checkin.capture' : 'circ.checkin.checkin';
53 'ui.circ.suppress_checkin_popups' // add other settings as needed
54 ]).then(function(set) {
55 suppress_popups = set['ui.circ.suppress_checkin_popups'];
58 // checkin & hold capture modifiers
69 if ($scope.is_capture) {
70 // in hold capture mode, some values are forced, regardless
71 // of stored preferences.
72 $scope.modifiers.noop = false;
73 $scope.modifiers.auto_print_holds_transits = true;
75 modifiers.push('noop'); // AKA suppress holds and transits
76 modifiers.push('auto_print_holds_transits');
79 // set modifiers from stored preferences
80 angular.forEach(modifiers, function(mod) {
81 egCore.hatch.getItem('eg.circ.checkin.' + mod)
82 .then(function(val) { if (val) $scope.modifiers[mod] = true });
85 // set / unset a checkin modifier
86 // when set, store the preference
87 $scope.toggle_mod = function(mod) {
88 if ($scope.modifiers[mod]) {
89 $scope.modifiers[mod] = false;
90 egCore.hatch.removeItem('eg.circ.checkin.' + mod);
92 $scope.modifiers[mod] = true;
93 egCore.hatch.setItem('eg.circ.checkin.' + mod, true);
98 // ensure the backdate is not in the future
99 // note: input type=date max=foo not yet supported anywhere
100 $scope.$watch('checkinArgs.backdate', function(newval) {
101 if (newval && newval > today)
102 $scope.checkinArgs.backdate = today;
105 $scope.is_backdate = function() {
106 return $scope.checkinArgs.backdate < today;
109 var checkinGrid = $scope.gridControls = {};
111 $scope.gridDataProvider = egGridDataProvider.instance({
112 get : function(offset, count) {
113 return this.arrayNotifier($scope.checkins, offset, count);
117 // turns the various inputs (form args, modifiers, etc.) into
118 // checkin params and options.
119 function compile_checkin_args(args) {
120 var params = angular.copy(args);
122 if (params.backdate) {
124 params.backdate.toISOString().replace(/T.*/,'');
126 // a backdate of 'today' is not really a backdate
127 if (params.backdate == $scope.max_backdate)
128 delete params.backdate;
131 angular.forEach(['noop','void_overdues',
132 'clear_expired','hold_as_transit','manual_float'],
134 if ($scope.modifiers[opt]) params[opt] = true;
138 if ($scope.modifiers.retarget_holds) {
139 if ($scope.modifiers.retarget_holds_all) {
140 params.retarget_mode = 'retarget.all';
142 params.retarget_mode = 'retarget';
147 check_barcode : $scope.strict_barcode,
148 no_precat_alert : $scope.modifiers.no_precat_alert,
149 auto_print_holds_transits :
150 $scope.modifiers.auto_print_holds_transits,
151 suppress_popups : suppress_popups
154 return {params : params, options: options};
157 $scope.checkin = function(args) {
159 var compiled = compile_checkin_args(args);
160 args.copy_barcode = ''; // reset UI for next scan
161 $scope.focusMe = true;
163 delete $scope.billable_amount;
164 delete $scope.billable_barcode;
165 delete $scope.billable_user_id;
167 var params = compiled.params;
168 var options = compiled.options;
170 if (!params.copy_barcode) return;
174 index : checkinSvc.checkins.length,
175 copy_barcode : params.copy_barcode
178 // track the item in the grid before sending the request
179 checkinSvc.checkins.unshift(row_item);
180 checkinGrid.refresh();
182 egCirc.checkin(params, options).then(
183 function(final_resp) {
185 row_item.evt = final_resp.evt;
186 angular.forEach(final_resp.data, function(val, key) {
191 var amt = Number(row_item.mbts.balance_owed());
193 $scope.billable_barcode = row_item.copy_barcode;
194 $scope.billable_amount = amt;
195 $scope.billable_user_id = row_item.circ.usr();
197 ($scope.fine_total * 100 + amt * 100) / 100;
201 if (final_resp.evt.textcode == 'NO_CHANGE') {
203 {already_checked_in : final_resp.evt.copy_barcode};
206 if ($scope.trim_list && checkinSvc.checkins.length > 20)
207 checkinSvc.checkins = checkinSvc.checkins.splice(0, 20);
210 // Checkin was rejected somewhere along the way.
211 // Remove the copy from the grid since there was no action.
212 // note: since checkins are unshifted onto the array, the
213 // index value does not (generally) match the array position.
215 angular.forEach(checkinSvc.checkins, function(ci, idx) {
216 if (ci.index == row_item.index) pos = idx;
218 checkinSvc.checkin.splice(pos, 1);
220 })['finally'](function() {
222 // when all is said and done, refresh the grid and refocus
223 checkinGrid.refresh();
224 $scope.focusMe = true;
228 $scope.print_receipt = function() {
229 var print_data = {checkins : []}
231 if (checkinSvc.checkins.length == 0) return $q.when();
233 angular.forEach(checkinSvc.checkins, function(checkin) {
236 copy : egCore.idl.toHash(checkin.acp) || {},
237 call_number : egCore.idl.toHash(checkin.acn) || {},
238 copy_barcode : checkin.copy_barcode,
239 title : checkin.title,
240 author : checkin.author
243 print_data.checkins.push(checkin);
246 return egCore.print.print({
247 template : 'checkin',
249 show_dialog : $scope.show_print_dialog
254 // --- context menu actions
256 $scope.fetchLastCircPatron = function(items) {
257 var checkin = items[0];
258 if (!checkin || !checkin.acp) return;
260 egCirc.last_copy_circ(checkin.acp.id())
261 .then(function(circ) {
264 // jump to the patron UI (separate app)
265 $window.location.href = $location
266 .path('/circ/patron/' + circ.usr() + '/checkout')
271 $scope.alert = {item_never_circed : checkin.acp.barcode()};
275 $scope.showBackdateDialog = function(items) {
278 angular.forEach(items, function(item) {
279 if (item.circ) circ_ids.push(item.circ.id());
282 if (circ_ids.length) {
283 egCirc.backdate_dialog(circ_ids).then(function(result) {
284 angular.forEach(items, function(item) {
285 item.circ.checkin_time(result.backdate);
288 // TODO: support grid row styling
289 checkinGrid.refresh();
293 $scope.showMarkDamaged = function(items) {
295 angular.forEach(items, function(item) {
296 if (item.acp) copy_ids.push(item.acp.id());
299 if (copy_ids.length) {
300 egCirc.mark_damaged(copy_ids).then(function() {
301 // update grid items?
306 $scope.abortTransit = function(items) {
307 var transit_ids = [];
308 angular.forEach(items, function(item) {
309 if (item.transit) transit_ids.push(item.transit.id());
312 egCirc.abort_transits(transit_ids).then(function() {
313 // update grid items?