]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/curbside/directives/schedule_pickup.js
LP#1879983: AngularJS staff interface for curbside pickup
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / curbside / directives / schedule_pickup.js
1 angular.module('egCurbsideAppDep')
2
3 .directive('egCurbsideSchedulePickup', function() {
4     return {
5         transclude: true,
6         restrict:   'E',
7         scope: { },
8         templateUrl: './circ/curbside/t_schedule_pickup',
9         controller:
10        ['$scope','$q','egCurbsideCoreSvc','egCore','patronSvc',
11         '$uibModal','$timeout','$location','egConfirmDialog','ngToast',
12 function($scope , $q , egCurbsideCoreSvc , egCore , patronSvc ,
13          $uibModal , $timeout , $location , egConfirmDialog , ngToast) {
14
15     $scope.clear = function() {
16         $scope.user_id = undefined;
17         $scope.args = {};
18         $scope.readyHolds = 0;
19         $scope.openAppointments = [];
20         $scope.forms = [];
21     }
22     $scope.clear();
23
24     patron_search_dialog = function() {
25         return $uibModal.open({
26             templateUrl: './share/t_patron_selector',
27             backdrop: 'static',
28             size: 'lg',
29             animation: true,
30             controller:
31                    ['$scope','$uibModalInstance','$controller',
32             function($scope , $uibModalInstance , $controller) {
33                 angular.extend(this, $controller('BasePatronSearchCtrl', {$scope : $scope}));
34                 $scope.clearForm();
35                 $scope.need_one_selected = function() {
36                     var items = $scope.gridControls.selectedItems();
37                     return (items.length == 1) ? false : true
38                 }
39                 $scope.ok = function() {
40                     var items = $scope.gridControls.selectedItems();
41                     if (items.length == 1) {
42                         $uibModalInstance.close(items[0].card().barcode());
43                     } else {
44                         $uibModalInstance.close()
45                     }
46                 }
47                 $scope.cancel = function($event) {
48                     $uibModalInstance.dismiss();
49                     $event.preventDefault();
50                 }
51             }]
52         });
53     }
54
55     $scope.patron_search = function() {
56         patron_search_dialog().result.then(function(barcode) {
57             $scope.args.barcode = barcode;
58         });
59     }
60
61     // this is blatantly copied from the patron app; if the AngularJS
62     // code had a longer life-expectancy, this would have been moved
63     // to a service.
64     $scope.submitBarcode = function(args) {
65         $scope.bcNotFound = null;
66         $scope.optInRestricted = false;
67         if (!args.barcode) return;
68         args.barcode = args.barcode.replace(/\s/g,'');
69         // blur so next time it's set to true it will re-apply select()
70         $scope.selectMe = false;
71
72         var user_id;
73
74         // given a scanned barcode, this function finds any matching users
75         // and handles multiple matches due to barcode completion
76         function handleBarcodeCompletion(scanned_barcode) {
77             var deferred = $q.defer();
78
79             egCore.net.request(
80                 'open-ils.actor',
81                 'open-ils.actor.get_barcodes',
82                 egCore.auth.token(), egCore.auth.user().ws_ou(), 
83                 'actor', scanned_barcode)
84
85             .then(function(resp) { // get_barcodes
86
87                 if (evt = egCore.evt.parse(resp)) {
88                     alert(evt); // FIXME
89                     deferred.reject();
90                     return;
91                 }
92
93                 if (!resp || !resp[0]) {
94                     $scope.bcNotFound = args.barcode;
95                     $scope.selectMe = true;
96                     egCore.audio.play('warning.patron.not_found');
97                     deferred.reject();
98                     return;
99                 }
100
101                 if (resp.length == 1) {
102                     // exactly one matching barcode: return it
103                     deferred.resolve();
104                     user_id = resp[0].id;
105                 } else {
106                     // multiple matching barcodes: let the user pick one 
107                     var barcode_map = {};
108                     var matches = [];
109                     var promises = [];
110                     var selected_barcode;
111                     angular.forEach(resp, function(match) {
112                         promises.push(
113                             egUser.get(match.id, {useFields : ['home_ou']}).then(function(user) {
114                                 barcode_map[match.barcode] = user.id();
115                                 matches.push( {
116                                     barcode: match.barcode,
117                                     title: user.first_given_name() + ' ' + user.family_name(),
118                                     org_name: user.home_ou().name(),
119                                     org_shortname: user.home_ou().shortname()
120                                 });
121                             })
122                         );
123                     });
124                     return $q.all(promises)
125                     .then(function() {
126                         $uibModal.open({
127                             templateUrl: './circ/share/t_barcode_choice_dialog',
128                             controller:
129                                 ['$scope', '$uibModalInstance',
130                                 function($scope, $uibModalInstance) {
131                                 $scope.matches = matches;
132                                 $scope.ok = function(barcode) {
133                                     $uibModalInstance.close();
134                                     selected_barcode = barcode;
135                                 }
136                                 $scope.cancel = function() {$uibModalInstance.dismiss()}
137                             }],
138                         }).result.then(function() {
139                             deferred.resolve();
140                             user_id = barcode_map[selected_barcode];
141                         });
142                     });
143                 }
144             });
145             return deferred.promise;
146         }
147
148         // call our function to lookup matching users for the scanned barcode
149         handleBarcodeCompletion(args.barcode).then(function() {
150
151             // see if an opt-in request is needed
152             return egCore.net.request(
153                 'open-ils.actor',
154                 'open-ils.actor.user.org_unit_opt_in.check',
155                 egCore.auth.token(), user_id
156             ).then(function(optInResp) { // opt_in_check
157
158                 if (evt = egCore.evt.parse(optInResp)) {
159                     alert(evt); // FIXME
160                     return;
161                 }
162
163                 if (optInResp == 2) {
164                     // opt-in disallowed at this location by patron's home library
165                     $scope.optInRestricted = true;
166                     $scope.selectMe = true;
167                     egCore.audio.play('warning.patron.opt_in_restricted');
168                     return;
169                 }
170             
171                 if (optInResp == 1) {
172                     // opt-in handled or not needed
173                     return loadPatron(user_id);
174                 }
175
176                 // opt-in needed, show the opt-in dialog
177                 egUser.get(user_id, {useFields : []})
178
179                 .then(function(user) { // retrieve user
180                     var org = egCore.org.get(user.home_ou());
181                     egConfirmDialog.open(
182                         egCore.strings.OPT_IN_DIALOG_TITLE,
183                         egCore.strings.OPT_IN_DIALOG,
184                         {   family_name : user.family_name(),
185                             first_given_name : user.first_given_name(),
186                             org_name : org.name(),
187                             org_shortname : org.shortname(),
188                             ok : function() { createOptIn(user.id()) },
189                             cancel : function() {}
190                         }
191                     );
192                 })
193             })
194         })
195     }
196
197     function countReadyHolds(user_id) {
198         return egCore.net.request(
199             'open-ils.curbside',
200             'open-ils.curbside.patron.ready_holds_at_lib.count',
201             egCore.auth.token(),
202             user_id
203         ).then(function(resp) {
204             if (evt = egCore.evt.parse(resp)) {
205                 return 0;
206             } else {
207                 return resp;
208             }
209         });
210     }
211
212     function fetchOpenAppointments(user_id) {
213         return egCore.net.request(
214             'open-ils.curbside',
215             'open-ils.curbside.open_user_appointments_at_lib.atomic',
216             egCore.auth.token(),
217             user_id
218         ).then(function(resp) {
219             if (evt = egCore.evt.parse(resp)) {
220                 return 0;
221             } else {
222                 return resp;
223             }
224         });
225     }
226
227     function mungeAvailableTimes(hash, times) {
228         var existing_present = false;
229         if (angular.isDefined(hash.slot_time) && hash.slot_time !== null) {
230             hash.original_slot_time = hash.slot_time;
231         }
232         hash.available_times = times.map(function(t) {
233             if (angular.isDefined(hash.slot_time) && hash.slot_time !== null && hash.slot_time === t[0]) {
234                 existing_present = true;
235             }
236             return {
237                 time: t[0],
238                 available: t[1],
239                 time_fmt: moment(t[0], [moment.ISO_8601, 'HH:mm:ss']).format('LT')
240             };
241         });
242         if (angular.isDefined(hash.slot_time) && hash.slot_time !== null && !existing_present) {
243             hash.available_times.unshift({
244                 time: hash.slot_time,
245                 available: 0,
246                 time_fmt: moment(hash.slot_time, [moment.ISO_8601, 'HH:mm:ss']).format('LT')
247             });
248         }
249     }
250
251     function mungeOneAppointment(c, isNew) {
252         var hash = egCore.idl.toHash(c);
253         if (hash.slot === null) {
254             // coerce to today for the purpose of the
255             // form if no slot time has been set yet
256             hash.slot = new Date().toISOString();
257             hash.slot_time = null;
258         } else {
259             if (!isNew) {
260                 hash.slot_time = hash.slot.substring(11, 19);
261             }
262         }
263         hash.slot_date = new Date(hash.slot);
264         if (!isNew) {
265             hash.is_past = (hash.slot_date < new Date());
266         }
267         hash.available_times = [];
268         egCore.net.request (
269             'open-ils.curbside',
270             'open-ils.curbside.times_for_date.atomic',
271             egCore.auth.token(),
272             hash.slot.substring(0, 10),
273         ).then(function(times) {
274             mungeAvailableTimes(hash, times);
275         });
276         return hash;
277     }
278
279     function mungeAppointmentList(list) {
280         $scope.openAppointments = list.map(function(c) {
281             var hash = mungeOneAppointment(c);
282             return hash;
283         });
284     }
285
286     function loadPatron(user_id) {
287         $scope.user_id = user_id;
288         patronSvc.getPrimary(user_id);
289         countReadyHolds(user_id).then(function(ct) { $scope.readyHolds = ct });        
290         fetchOpenAppointments(user_id).then(function(list) {
291             mungeAppointmentList(list);
292         });
293     }
294
295
296     $scope.minDate = new Date();
297     $scope.refreshAvailableTimes = function(hash) {
298         var dateStr = (new Date(hash.slot_date)).toISOString().substring(0, 10);
299         egCore.net.request (
300             'open-ils.curbside',
301             'open-ils.curbside.times_for_date.atomic',
302             egCore.auth.token(),
303             dateStr,
304         ).then(function(times) {
305             mungeAvailableTimes(hash, times);
306         });
307     }
308
309     $scope.startNewAppointment = function() {
310         var slot = new egCore.idl.acsp();
311         slot.slot = new Date().toISOString();
312         slot.patron = $scope.user_id;
313         slot.org = egCore.auth.user().ws_ou();
314         $scope.openAppointments = [ mungeOneAppointment(slot, true) ];
315     }
316
317     $scope.updateAppointment = function(appt) {
318         var op = angular.isDefined(appt.id) ? 'update' : 'create';
319         egCore.net.request(
320             'open-ils.curbside',
321             'open-ils.curbside.' + op + '_appointment',
322             egCore.auth.token(),
323             $scope.user_id,
324             (new Date(appt.slot_date)).toISOString().substring(0, 10),
325             appt.slot_time,
326             egCore.auth.user().ws_ou(),
327             appt.notes
328         ).then(function(resp) {
329             if (evt = egCore.evt.parse(resp)) {
330                 if (evt.textcode === 'CURBSIDE_MAX_FOR_TIME') {
331                     ngToast.danger(egCore.strings.$replace(
332                         egCore.strings.FAILED_SAVE_APPOINTMENT_TOO_MANY,
333                         { evt_code : evt.code }
334                     ));
335                 } else {
336                     ngToast.danger(egCore.strings.$replace(
337                         egCore.strings.FAILED_SAVE_APPOINTMENT,
338                         { evt_code : evt.code }
339                     ));
340                 }
341             } else {
342                 ngToast.success(egCore.strings.$replace(
343                     egCore.strings.SUCCESS_SAVE_APPOINTMENT,
344                     { slot_id : resp.id() }
345                 ));
346             }
347             fetchOpenAppointments($scope.user_id).then(function(list) {
348                 mungeAppointmentList(list);
349             });
350         });
351     }
352
353     function doCancel(id) {
354         egCore.net.request (
355             'open-ils.curbside',
356             'open-ils.curbside.delete_appointment',
357             egCore.auth.token(),
358             id
359         ).then(function(resp) {
360             if (!angular.isDefined(resp)) {
361                 ngToast.danger(egCore.strings.$replace(
362                     egCore.strings.FAILED_CANCEL_APPOINTMENT,
363                     { slot_id : id, evt_code : 'NO_SUCH_APPOINTMENT' }
364                 ));
365             } else if (evt = egCore.evt.parse(resp)) {
366                 ngToast.danger(egCore.strings.$replace(
367                     egCore.strings.FAILED_CANCEL_APPOINTMENT,
368                     { slot_id : id, evt_code : evt.code }
369                 ));
370             } else {
371                 ngToast.success(egCore.strings.$replace(
372                     egCore.strings.SUCCESS_CANCEL_APPOINTMENT,
373                     { slot_id : id }
374                 ));
375             }
376             fetchOpenAppointments($scope.user_id).then(function(list) {
377                 mungeAppointmentList(list);
378             });
379         });
380     }
381     $scope.cancelAppointment = function(id) {
382         egConfirmDialog.open(
383             egCore.strings.CONFIRM_CANCEL_TITLE,
384             egCore.strings.CONFIRM_CANCEL_BODY,
385             {   slot_id : id,
386                 ok : function() { doCancel(id) },
387                 cancel : function() {}
388             }
389         );
390     }
391
392     $scope.patron = function() {
393         return patronSvc.current;
394     }
395
396 }]}});