]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/curbside/directives/schedule_pickup.js
LP#1917396 - Staff Curbside Scheduling UTC Issues
[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             // Use date based on local time, not UTC
273             new Date(hash.slot_date - (hash.slot_date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10),
274         ).then(function(times) {
275             mungeAvailableTimes(hash, times);
276         });
277         return hash;
278     }
279
280     function mungeAppointmentList(list) {
281         $scope.openAppointments = list.map(function(c) {
282             var hash = mungeOneAppointment(c);
283             return hash;
284         });
285     }
286
287     function loadPatron(user_id) {
288         $scope.user_id = user_id;
289         patronSvc.getPrimary(user_id);
290         countReadyHolds(user_id).then(function(ct) { $scope.readyHolds = ct });        
291         fetchOpenAppointments(user_id).then(function(list) {
292             mungeAppointmentList(list);
293         });
294     }
295
296
297     $scope.minDate = new Date();
298     $scope.refreshAvailableTimes = function(hash) {
299         // Use date based on local time, not UTC
300         var dateStr = (new Date(hash.slot_date - (hash.slot_date.getTimezoneOffset() * 60000))).toISOString().substring(0, 10);
301         egCore.net.request (
302             'open-ils.curbside',
303             'open-ils.curbside.times_for_date.atomic',
304             egCore.auth.token(),
305             dateStr,
306         ).then(function(times) {
307             mungeAvailableTimes(hash, times);
308         });
309     }
310
311     $scope.startNewAppointment = function() {
312         var slot = new egCore.idl.acsp();
313         slot.slot = new Date().toISOString();
314         slot.patron = $scope.user_id;
315         slot.org = egCore.auth.user().ws_ou();
316         $scope.openAppointments = [ mungeOneAppointment(slot, true) ];
317     }
318
319     $scope.updateAppointment = function(appt) {
320         var op = angular.isDefined(appt.id) ? 'update' : 'create';
321         egCore.net.request(
322             'open-ils.curbside',
323             'open-ils.curbside.' + op + '_appointment',
324             egCore.auth.token(),
325             $scope.user_id,
326             // Use date based on local time, not UTC
327             (new Date(appt.slot_date - (appt.slot_date.getTimezoneOffset() * 60000))).toISOString().substring(0, 10),
328             appt.slot_time,
329             egCore.auth.user().ws_ou(),
330             appt.notes
331         ).then(function(resp) {
332             if (evt = egCore.evt.parse(resp)) {
333                 if (evt.textcode === 'CURBSIDE_MAX_FOR_TIME') {
334                     ngToast.danger(egCore.strings.$replace(
335                         egCore.strings.FAILED_SAVE_APPOINTMENT_TOO_MANY,
336                         { evt_code : evt.code }
337                     ));
338                 } else {
339                     ngToast.danger(egCore.strings.$replace(
340                         egCore.strings.FAILED_SAVE_APPOINTMENT,
341                         { evt_code : evt.code }
342                     ));
343                 }
344             } else {
345                 ngToast.success(egCore.strings.$replace(
346                     egCore.strings.SUCCESS_SAVE_APPOINTMENT,
347                     { slot_id : resp.id() }
348                 ));
349             }
350             fetchOpenAppointments($scope.user_id).then(function(list) {
351                 mungeAppointmentList(list);
352             });
353         });
354     }
355
356     function doCancel(id) {
357         egCore.net.request (
358             'open-ils.curbside',
359             'open-ils.curbside.delete_appointment',
360             egCore.auth.token(),
361             id
362         ).then(function(resp) {
363             if (!angular.isDefined(resp)) {
364                 ngToast.danger(egCore.strings.$replace(
365                     egCore.strings.FAILED_CANCEL_APPOINTMENT,
366                     { slot_id : id, evt_code : 'NO_SUCH_APPOINTMENT' }
367                 ));
368             } else if (evt = egCore.evt.parse(resp)) {
369                 ngToast.danger(egCore.strings.$replace(
370                     egCore.strings.FAILED_CANCEL_APPOINTMENT,
371                     { slot_id : id, evt_code : evt.code }
372                 ));
373             } else {
374                 ngToast.success(egCore.strings.$replace(
375                     egCore.strings.SUCCESS_CANCEL_APPOINTMENT,
376                     { slot_id : id }
377                 ));
378             }
379             fetchOpenAppointments($scope.user_id).then(function(list) {
380                 mungeAppointmentList(list);
381             });
382         });
383     }
384     $scope.cancelAppointment = function(id) {
385         egConfirmDialog.open(
386             egCore.strings.CONFIRM_CANCEL_TITLE,
387             egCore.strings.CONFIRM_CANCEL_BODY,
388             {   slot_id : id,
389                 ok : function() { doCancel(id) },
390                 cancel : function() {}
391             }
392         );
393     }
394
395     $scope.patron = function() {
396         return patronSvc.current;
397     }
398
399 }]}});