1 angular.module('egCurbsideAppDep')
3 .directive('egCurbsideSchedulePickup', function() {
8 templateUrl: './circ/curbside/t_schedule_pickup',
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) {
15 $scope.clear = function() {
16 $scope.user_id = undefined;
18 $scope.readyHolds = 0;
19 $scope.openAppointments = [];
24 patron_search_dialog = function() {
25 return $uibModal.open({
26 templateUrl: './share/t_patron_selector',
31 ['$scope','$uibModalInstance','$controller',
32 function($scope , $uibModalInstance , $controller) {
33 angular.extend(this, $controller('BasePatronSearchCtrl', {$scope : $scope}));
35 $scope.need_one_selected = function() {
36 var items = $scope.gridControls.selectedItems();
37 return (items.length == 1) ? false : true
39 $scope.ok = function() {
40 var items = $scope.gridControls.selectedItems();
41 if (items.length == 1) {
42 $uibModalInstance.close(items[0].card().barcode());
44 $uibModalInstance.close()
47 $scope.cancel = function($event) {
48 $uibModalInstance.dismiss();
49 $event.preventDefault();
55 $scope.patron_search = function() {
56 patron_search_dialog().result.then(function(barcode) {
57 $scope.args.barcode = barcode;
61 // this is blatantly copied from the patron app; if the AngularJS
62 // code had a longer life-expectancy, this would have been moved
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;
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();
81 'open-ils.actor.get_barcodes',
82 egCore.auth.token(), egCore.auth.user().ws_ou(),
83 'actor', scanned_barcode)
85 .then(function(resp) { // get_barcodes
87 if (evt = egCore.evt.parse(resp)) {
93 if (!resp || !resp[0]) {
94 $scope.bcNotFound = args.barcode;
95 $scope.selectMe = true;
96 egCore.audio.play('warning.patron.not_found');
101 if (resp.length == 1) {
102 // exactly one matching barcode: return it
104 user_id = resp[0].id;
106 // multiple matching barcodes: let the user pick one
107 var barcode_map = {};
110 var selected_barcode;
111 angular.forEach(resp, function(match) {
113 egUser.get(match.id, {useFields : ['home_ou']}).then(function(user) {
114 barcode_map[match.barcode] = user.id();
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()
124 return $q.all(promises)
127 templateUrl: './circ/share/t_barcode_choice_dialog',
129 ['$scope', '$uibModalInstance',
130 function($scope, $uibModalInstance) {
131 $scope.matches = matches;
132 $scope.ok = function(barcode) {
133 $uibModalInstance.close();
134 selected_barcode = barcode;
136 $scope.cancel = function() {$uibModalInstance.dismiss()}
138 }).result.then(function() {
140 user_id = barcode_map[selected_barcode];
145 return deferred.promise;
148 // call our function to lookup matching users for the scanned barcode
149 handleBarcodeCompletion(args.barcode).then(function() {
151 // see if an opt-in request is needed
152 return egCore.net.request(
154 'open-ils.actor.user.org_unit_opt_in.check',
155 egCore.auth.token(), user_id
156 ).then(function(optInResp) { // opt_in_check
158 if (evt = egCore.evt.parse(optInResp)) {
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');
171 if (optInResp == 1) {
172 // opt-in handled or not needed
173 return loadPatron(user_id);
176 // opt-in needed, show the opt-in dialog
177 egUser.get(user_id, {useFields : []})
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() {}
197 function countReadyHolds(user_id) {
198 return egCore.net.request(
200 'open-ils.curbside.patron.ready_holds_at_lib.count',
203 ).then(function(resp) {
204 if (evt = egCore.evt.parse(resp)) {
212 function fetchOpenAppointments(user_id) {
213 return egCore.net.request(
215 'open-ils.curbside.open_user_appointments_at_lib.atomic',
218 ).then(function(resp) {
219 if (evt = egCore.evt.parse(resp)) {
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;
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;
239 time_fmt: moment(t[0], [moment.ISO_8601, 'HH:mm:ss']).format('LT')
242 if (angular.isDefined(hash.slot_time) && hash.slot_time !== null && !existing_present) {
243 hash.available_times.unshift({
244 time: hash.slot_time,
246 time_fmt: moment(hash.slot_time, [moment.ISO_8601, 'HH:mm:ss']).format('LT')
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;
260 hash.slot_time = hash.slot.substring(11, 19);
263 hash.slot_date = new Date(hash.slot);
265 hash.is_past = (hash.slot_date < new Date());
267 hash.available_times = [];
270 'open-ils.curbside.times_for_date.atomic',
272 hash.slot.substring(0, 10),
273 ).then(function(times) {
274 mungeAvailableTimes(hash, times);
279 function mungeAppointmentList(list) {
280 $scope.openAppointments = list.map(function(c) {
281 var hash = mungeOneAppointment(c);
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);
296 $scope.minDate = new Date();
297 $scope.refreshAvailableTimes = function(hash) {
298 var dateStr = (new Date(hash.slot_date)).toISOString().substring(0, 10);
301 'open-ils.curbside.times_for_date.atomic',
304 ).then(function(times) {
305 mungeAvailableTimes(hash, times);
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) ];
317 $scope.updateAppointment = function(appt) {
318 var op = angular.isDefined(appt.id) ? 'update' : 'create';
321 'open-ils.curbside.' + op + '_appointment',
324 (new Date(appt.slot_date)).toISOString().substring(0, 10),
326 egCore.auth.user().ws_ou(),
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 }
336 ngToast.danger(egCore.strings.$replace(
337 egCore.strings.FAILED_SAVE_APPOINTMENT,
338 { evt_code : evt.code }
342 ngToast.success(egCore.strings.$replace(
343 egCore.strings.SUCCESS_SAVE_APPOINTMENT,
344 { slot_id : resp.id() }
347 fetchOpenAppointments($scope.user_id).then(function(list) {
348 mungeAppointmentList(list);
353 function doCancel(id) {
356 'open-ils.curbside.delete_appointment',
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' }
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 }
371 ngToast.success(egCore.strings.$replace(
372 egCore.strings.SUCCESS_CANCEL_APPOINTMENT,
376 fetchOpenAppointments($scope.user_id).then(function(list) {
377 mungeAppointmentList(list);
381 $scope.cancelAppointment = function(id) {
382 egConfirmDialog.open(
383 egCore.strings.CONFIRM_CANCEL_TITLE,
384 egCore.strings.CONFIRM_CANCEL_BODY,
386 ok : function() { doCancel(id) },
387 cancel : function() {}
392 $scope.patron = function() {
393 return patronSvc.current;