]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
webstaff: implement some workstation preferences
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / admin / workstation / app.js
1 /**
2  * App to drive the base page. 
3  * Login Form
4  * Splash Page
5  */
6
7 angular.module('egWorkstationAdmin', 
8     ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod'])
9
10 .config(['$routeProvider','$locationProvider','$compileProvider', 
11  function($routeProvider , $locationProvider , $compileProvider) {
12
13     $locationProvider.html5Mode(true);
14     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
15     var resolver = {delay : function(egStartup) {return egStartup.go()}};
16
17     $routeProvider.when('/admin/workstation/workstations', {
18         templateUrl: './admin/workstation/t_workstations',
19         controller: 'WSRegCtrl',
20         resolve : resolver
21     });
22
23     $routeProvider.when('/admin/workstation/print/config', {
24         templateUrl: './admin/workstation/t_print_config',
25         controller: 'PrintConfigCtrl',
26         resolve : resolver
27     });
28
29     $routeProvider.when('/admin/workstation/print/templates', {
30         templateUrl: './admin/workstation/t_print_templates',
31         controller: 'PrintTemplatesCtrl',
32         resolve : resolver
33     });
34
35     $routeProvider.when('/admin/workstation/stored_prefs', {
36         templateUrl: './admin/workstation/t_stored_prefs',
37         controller: 'StoredPrefsCtrl',
38         resolve : resolver
39     });
40
41
42     // default page 
43     $routeProvider.otherwise({
44         templateUrl : './admin/workstation/t_splash',
45         controller : 'SplashCtrl',
46         resolve : resolver
47     });
48 }])
49
50 .factory('workstationSvc',
51        ['$q','$timeout','$location','egCore','egConfirmDialog',
52 function($q , $timeout , $location , egCore , egConfirmDialog) {
53     
54     var service = {};
55
56     service.get_all = function() {
57         return egCore.hatch.getItem('eg.workstation.all')
58         .then(function(all) { return all || [] });
59     }
60
61     service.get_default = function() {
62         return egCore.hatch.getItem('eg.workstation.default');
63     }
64
65     service.set_default = function(name) {
66         return egCore.hatch.setItem('eg.workstation.default', name);
67     }
68
69     service.register_workstation = function(base_name, name, org_id) {
70         return service.register_ws_api(base_name, name, org_id)
71         .then(function(ws_id) {
72             return service.track_new_ws(ws_id, name, org_id);
73         });
74     };
75
76     service.register_ws_api = 
77         function(base_name, name, org_id, override, deferred) {
78         if (!deferred) deferred = $q.defer();
79
80         var method = 'open-ils.actor.workstation.register';
81         if (override) method += '.override';
82
83         egCore.net.request(
84             'open-ils.actor', method, egCore.auth.token(), name, org_id)
85
86         .then(function(resp) {
87
88             if (evt = egCore.evt.parse(resp)) {
89                 console.log('register returned ' + evt.toString());
90
91                 if (evt.textcode == 'WORKSTATION_NAME_EXISTS' && !override) {
92
93                     egConfirmDialog.open(
94                         egCore.strings.WS_EXISTS, base_name, {  
95                             ok : function() {
96                                 service.register_ws_api(
97                                     base_name, name, org_id, true, deferred)
98                             },
99                             cancel : function() {deferred.reject()} 
100                         }
101                     );
102
103                 } else {
104                     alert(evt.toString());
105                     deferred.reject();
106                 }
107             } else if (resp) {
108                 console.log('Resolving register promise with: ' + resp);
109                 deferred.resolve(resp);
110             }
111         });
112
113         return deferred.promise;
114     }
115
116     service.track_new_ws = function(ws_id, ws_name, owning_lib) {
117         console.log('Tracking newly created WS with ID ' + ws_id);
118         var new_ws = {id : ws_id, name : ws_name, owning_lib : owning_lib};
119
120         return service.get_all()
121         .then(function(all) {
122             all.push(new_ws);
123             return egCore.hatch.setItem('eg.workstation.all', all)
124             .then(function() { return new_ws });
125         });
126     }
127
128     // Remove all traces of the workstation locally.
129     // This does not remove the WS from the server.
130     service.remove_workstation = function(name) {
131         console.debug('Removing workstation: ' + name);
132
133         return egCore.hatch.getItem('eg.workstation.all')
134
135         // remove from list of all workstations
136         .then(function(all) {
137             if (!all) all = [];
138             var keep = all.filter(function(ws) {return ws.name != name});
139             return egCore.hatch.setItem('eg.workstation.all', keep)
140
141         }).then(function() { 
142
143             return service.get_default()
144
145         }).then(function(def) {
146             if (def == name) {
147                 console.debug('Removing default workstation: ' + name);
148                 return egCore.hatch.removeItem('eg.workstation.default');
149             }
150         });
151     }
152
153     return service;
154 }])
155
156
157 .controller('SplashCtrl',
158        ['$scope','$window','$location','egCore','egConfirmDialog',
159 function($scope , $window , $location , egCore , egConfirmDialog) {
160
161     // ---------------------
162     // Hatch Configs
163     $scope.hatchURL = egCore.hatch.hatchURL();
164     $scope.hatchRequired = 
165         egCore.hatch.getLocalItem('eg.hatch.required');
166
167     $scope.updateHatchRequired = function() {
168         egCore.hatch.setLocalItem(
169             'eg.hatch.required', $scope.hatchRequired);
170     }
171
172     $scope.updateHatchURL = function() {
173         egCore.hatch.setLocalItem(
174             'eg.hatch.url', $scope.hatchURL);
175     }
176
177     egCore.hatch.getItem('eg.audio.disable').then(function(val) {
178         $scope.disable_sound = val;
179     });
180
181     egCore.hatch.getItem('eg.search.search_lib').then(function(val) {
182         $scope.search_lib = egCore.org.get(val);
183     });
184     $scope.handle_search_lib_changed = function(org) {
185         egCore.hatch.setItem('eg.search.search_lib', org.id());
186     };
187
188     egCore.hatch.getItem('eg.search.pref_lib').then(function(val) {
189         $scope.pref_lib = egCore.org.get(val);
190     });
191     $scope.handle_pref_lib_changed = function(org) {
192         egCore.hatch.setItem('eg.search.pref_lib', org.id());
193     };
194
195     $scope.adv_pane = 'advanced'; // default value if not explicitly set
196     egCore.hatch.getItem('eg.search.adv_pane').then(function(val) {
197         $scope.adv_pane = val;
198     });
199     $scope.$watch('adv_pane', function(newVal, oldVal) {
200         if (newVal != oldVal) {
201             egCore.hatch.setItem('eg.search.adv_pane', newVal);
202         }
203     });
204
205     $scope.apply_sound = function() {
206         if ($scope.disable_sound) {
207             egCore.hatch.setItem('eg.audio.disable', true);
208         } else {
209             egCore.hatch.removeItem('eg.audio.disable');
210         }
211     }
212
213     $scope.test_audio = function(sound) {
214         egCore.audio.play(sound);
215     }
216
217 }])
218
219 .controller('PrintConfigCtrl',
220        ['$scope','egCore',
221 function($scope , egCore) {
222     console.log('PrintConfigCtrl');
223
224     $scope.actionPending = false;
225     $scope.isTestView = false;
226
227     $scope.setContext = function(ctx) { 
228         $scope.context = ctx; 
229         $scope.isTestView = false;
230         $scope.actionPending = false;
231     }
232     $scope.setContext('default');
233
234     $scope.getPrinterByAttr = function(attr, value) {
235         var printer;
236         angular.forEach($scope.printers, function(p) {
237             if (p[attr] == value) printer = p;
238         });
239         return printer;
240     }
241
242     $scope.currentPrinter = function() {
243         if ($scope.printConfig && $scope.printConfig[$scope.context]) {
244             return $scope.getPrinterByAttr(
245                 'name', $scope.printConfig[$scope.context].printer
246             );
247         }
248     }
249
250     // fetch info on all remote printers
251     egCore.hatch.getPrinters()
252     .then(function(printers) { 
253         $scope.printers = printers;
254         $scope.defaultPrinter = 
255             $scope.getPrinterByAttr('is-default', true);
256     })
257     .then(function() { return egCore.hatch.getPrintConfig() })
258     .then(function(config) {
259         $scope.printConfig = config;
260
261         var pname = '';
262         if ($scope.defaultPrinter) {
263             pname = $scope.defaultPrinter.name;
264
265         } else if ($scope.printers.length == 1) {
266             // if the OS does not report a default printer, but only
267             // one printer is available, treat it as the default.
268             pname = $scope.printers[0].name;
269         }
270
271         // apply the default printer to every context which has
272         // no printer configured.
273         angular.forEach(
274             ['default','receipt','label','mail','offline'],
275             function(ctx) {
276                 if (!$scope.printConfig[ctx]) {
277                     $scope.printConfig[ctx] = {
278                         context : ctx,
279                         printer : pname
280                     }
281                 }
282             }
283         );
284     });
285
286     $scope.printerConfString = function() {
287         if ($scope.printConfigError) return $scope.printConfigError;
288         if (!$scope.printConfig) return;
289         if (!$scope.printConfig[$scope.context]) return;
290         return JSON.stringify(
291             $scope.printConfig[$scope.context], undefined, 2);
292     }
293
294     $scope.resetConfig = function() {
295         $scope.actionPending = true;
296         $scope.printConfigError = null;
297         $scope.printConfig[$scope.context] = {
298             context : $scope.context
299         }
300         
301         if ($scope.defaultPrinter) {
302             $scope.printConfig[$scope.context].printer = 
303                 $scope.defaultPrinter.name;
304         }
305
306         egCore.hatch.setPrintConfig($scope.printConfig)
307         .finally(function() {$scope.actionPending = false});
308     }
309
310     $scope.configurePrinter = function() {
311         $scope.printConfigError = null;
312         $scope.actionPending = true;
313         egCore.hatch.configurePrinter(
314             $scope.context,
315             $scope.printConfig[$scope.context].printer
316         )
317         .then(
318             function(config) {$scope.printConfig = config},
319             function(error) {$scope.printConfigError = error}
320         )
321         .finally(function() {$scope.actionPending = false});
322     }
323
324     $scope.setPrinter = function(name) {
325         $scope.printConfig[$scope.context].printer = name;
326     }
327
328     // for testing
329     $scope.setContentType = function(type) { $scope.contentType = type }
330
331     $scope.testPrint = function(withDialog) {
332         if ($scope.contentType == 'text/plain') {
333             egCore.print.print({
334                 context : $scope.context, 
335                 content_type : $scope.contentType, 
336                 content : $scope.textPrintContent,
337                 show_dialog : withDialog
338             });
339         } else {
340             egCore.print.print({
341                 context : $scope.context,
342                 content_type : $scope.contentType, 
343                 content : $scope.htmlPrintContent, 
344                 scope : {
345                     value1 : 'Value One', 
346                     value2 : 'Value Two',
347                     date_value : '2015-02-04T14:04:34-0400'
348                 },
349                 show_dialog : withDialog
350             });
351         }
352     }
353
354     $scope.setContentType('text/plain');
355
356 }])
357
358 .controller('PrintTemplatesCtrl',
359        ['$scope','$q','egCore',
360 function($scope , $q , egCore) {
361
362     $scope.print = {
363         template_name : 'bills_current',
364         template_output : ''
365     };
366
367     // print preview scope data
368     // TODO: consider moving the template-specific bits directly
369     // into the templates or storing template- specific script files
370     // alongside the templates.
371     // NOTE: A lot of this data can be shared across templates.
372     var seed_user = {
373         first_given_name : 'Slow',
374         second_given_name : 'Joe',
375         family_name : 'Jones',
376         card : {
377             barcode : '30393830393'
378         }
379     }
380     var seed_addr = {
381         street1 : '123 Apple Rd',
382         street2 : 'Suite B',
383         city : 'Anywhere',
384         state : 'XX',
385         country : 'US',
386         post_code : '12345'
387     }
388
389     var seed_record = {
390         title : 'Traveling Pants!!',
391         author : 'Jane Jones',
392         isbn : '1231312123'
393     };
394
395     var seed_copy = {
396         barcode : '33434322323'
397     }
398
399     var one_hold = {
400         behind_desk : 'f',
401         phone_notify : '111-222-3333',
402         sms_notify : '111-222-3333',
403         email_notify : 'user@example.org',
404         request_time : new Date().toISOString()
405     }
406
407
408     $scope.preview_scope = {
409         //bills
410         transactions : [
411             {
412                 id : 1,
413                 xact_start : new Date().toISOString(),
414                 summary : {
415                     xact_type : 'circulation',
416                     last_billing_type : 'Overdue materials',
417                     total_owed : 1.50,
418                     last_payment_note : 'Test Note 1',
419                     total_paid : 0.50,
420                     balance_owed : 1.00
421                 }
422             }, {
423                 id : 2,
424                 xact_start : new Date().toISOString(),
425                 summary : {
426                     xact_type : 'circulation',
427                     last_billing_type : 'Overdue materials',
428                     total_owed : 2.50,
429                     last_payment_note : 'Test Note 2',
430                     total_paid : 0.50,
431                     balance_owed : 2.00
432                 }
433             }
434         ],
435
436         circulations : [
437             {   
438                 due_date : new Date().toISOString(), 
439                 target_copy : seed_copy,
440                 title : seed_record.title
441             },
442         ],
443
444         previous_balance : 8.45,
445         payment_total : 2.00,
446         payment_applied : 2.00,
447         new_balance : 6.45,
448         amount_voided : 0,
449         change_given : 0,
450         payment_type : 'cash_payment',
451         payment_note : 'Here is a payment note',
452         note : {
453             create_date : new Date().toISOString(), 
454             title : 'Test Note Title',
455             usr : seed_user,
456             value : 'This patron is super nice!'
457         },
458
459         transit : {
460             dest : {
461                 name : 'Library X',
462                 shortname : 'LX',
463                 holds_address : seed_addr
464             },
465             target_copy : seed_copy
466         },
467         title : seed_record.title,
468         author : seed_record.author,
469         patron : egCore.idl.toHash(egCore.auth.user()),
470         address : seed_addr,
471         hold : one_hold,
472         holds : [
473             {hold : one_hold, title : 'Some Title 1', author : 'Some Author 1'},
474             {hold : one_hold, title : 'Some Title 2', author : 'Some Author 2'},
475             {hold : one_hold, title : 'Some Title 3', author : 'Some Author 3'}
476         ]
477     }
478
479     $scope.preview_scope.payments = [
480         {amount : 1.00, xact : $scope.preview_scope.transactions[0]}, 
481         {amount : 1.00, xact : $scope.preview_scope.transactions[1]}
482     ]
483     $scope.preview_scope.payments[0].xact.title = 'Hali Bote Azikaban de tao fan';
484     $scope.preview_scope.payments[0].xact.copy_barcode = '334343434';
485     $scope.preview_scope.payments[1].xact.title = seed_record.title;
486     $scope.preview_scope.payments[1].xact.copy_barcode = seed_copy.barcode;
487
488     // today, staff, current_location, etc.
489     egCore.print.fleshPrintScope($scope.preview_scope);
490
491     $scope.template_changed = function() {
492         $scope.print.load_failed = false;
493         egCore.print.getPrintTemplate($scope.print.template_name)
494         .then(
495             function(html) { 
496                 $scope.print.template_content = html;
497                 console.log('set template content');
498             },
499             function() {
500                 $scope.print.template_content = '';
501                 $scope.print.load_failed = true;
502             }
503         );
504     }
505
506     $scope.save_locally = function() {
507         egCore.print.storePrintTemplate(
508             $scope.print.template_name,
509             $scope.print.template_content
510         );
511     }
512
513     $scope.template_changed(); // load the default
514 }])
515
516 // 
517 .directive('egPrintTemplateOutput', ['$compile',function($compile) {
518     return function(scope, element, attrs) {
519         scope.$watch(
520             function(scope) {
521                 return scope.$eval(attrs.content);
522             },
523             function(value) {
524                 // create an isolate scope and copy the print context
525                 // data into the new scope.
526                 // TODO: see also print security concerns in egHatch
527                 var result = element.html(value);
528                 var context = scope.$eval(attrs.context);
529                 var print_scope = scope.$new(true);
530                 angular.forEach(context, function(val, key) {
531                     print_scope[key] = val;
532                 })
533                 $compile(element.contents())(print_scope);
534             }
535         );
536     };
537 }])
538
539 .controller('StoredPrefsCtrl',
540        ['$scope','$q','egCore','egConfirmDialog',
541 function($scope , $q , egCore , egConfirmDialog) {
542     console.log('StoredPrefsCtrl');
543
544     $scope.setContext = function(ctx) {
545         $scope.context = ctx;
546     }
547     $scope.setContext('local');
548
549     // grab the edit perm
550     $scope.userHasDeletePerm = false;
551     egCore.perm.hasPermHere('ADMIN_WORKSTATION')
552     .then(function(bool) { $scope.userHasDeletePerm = bool });
553
554     // fetch the keys
555
556     function refreshKeys() {
557         $scope.keys = {local : [], remote : []};
558
559         egCore.hatch.getRemoteKeys().then(
560             function(keys) { $scope.keys.remote = keys.sort() })
561     
562         // local calls are non-async
563         $scope.keys.local = egCore.hatch.getLocalKeys();
564     }
565     refreshKeys();
566
567     $scope.selectKey = function(key) {
568         $scope.currentKey = key;
569         $scope.currentKeyContent = null;
570
571         if ($scope.context == 'local') {
572             $scope.currentKeyContent = egCore.hatch.getLocalItem(key);
573         } else {
574             egCore.hatch.getRemoteItem(key)
575             .then(function(content) {
576                 $scope.currentKeyContent = content
577             });
578         }
579     }
580
581     $scope.getCurrentKeyContent = function() {
582         return JSON.stringify($scope.currentKeyContent, null, 2);
583     }
584
585     $scope.removeKey = function(key) {
586         egConfirmDialog.open(
587             egCore.strings.PREFS_REMOVE_KEY_CONFIRM, '',
588             {   deleteKey : key,
589                 ok : function() {
590                     if ($scope.context == 'local') {
591                         egCore.hatch.removeLocalItem(key);
592                         refreshKeys();
593                     } else {
594                         egCore.hatch.removeItem(key)
595                         .then(function() { refreshKeys() });
596                     }
597                 },
598                 cancel : function() {} // user canceled, nothing to do
599             }
600         );
601     }
602 }])
603
604 .controller('WSRegCtrl',
605        ['$scope','$q','$window','$location','egCore','egAlertDialog','workstationSvc',
606 function($scope , $q , $window , $location , egCore , egAlertDialog , workstationSvc) {
607
608     var all_workstations = [];
609     var reg_perm_orgs = [];
610
611     $scope.page_loaded = false;
612     $scope.contextOrg = egCore.org.get(egCore.auth.user().ws_ou());
613     $scope.wsOrgChanged = function(org) { $scope.contextOrg = org; }
614
615     console.log('set context org to ' + $scope.contextOrg);
616
617     // fetch workstation reg perms
618     egCore.perm.hasPermAt('REGISTER_WORKSTATION', true)
619     .then(function(orgList) { 
620         reg_perm_orgs = orgList;
621
622         // hide orgs in the context org selector where this login
623         // does not have the reg_ws perm
624         $scope.wsOrgHidden = function(id) {
625             return reg_perm_orgs.indexOf(id) == -1;
626         }
627
628     // fetch the locally stored workstation data
629     }).then(function() {
630         return workstationSvc.get_all()
631         
632     }).then(function(all) {
633         all_workstations = all || [];
634         $scope.workstations = 
635             all_workstations.map(function(w) { return w.name });
636         return workstationSvc.get_default()
637
638     // fetch the default workstation
639     }).then(function(def) { 
640         $scope.defaultWS = def;
641         $scope.activeWS = $scope.selectedWS = egCore.auth.workstation() || def;
642
643     // Handle any URL commands.
644     }).then(function() {
645         var remove = $location.search().remove;
646          if (remove) {
647             console.log('Removing WS via URL request: ' + remove);
648             return $scope.remove_ws(remove).then(
649                 function() { $scope.page_loaded = true; });
650         }
651         $scope.page_loaded = true;
652     });
653
654     $scope.get_ws_label = function(ws) {
655         return ws == $scope.defaultWS ? 
656             egCore.strings.$replace(egCore.strings.DEFAULT_WS_LABEL, {ws:ws}) : ws;
657     }
658
659     $scope.set_default_ws = function(name) {
660         delete $scope.removing_ws;
661         $scope.defaultWS = name;
662         workstationSvc.set_default(name);
663     }
664
665     $scope.cant_have_users = 
666         function (id) { return !egCore.org.CanHaveUsers(id); };
667     $scope.cant_have_volumes = 
668         function (id) { return !egCore.org.CanHaveVolumes(id); };
669
670     // Log out and return to login page with selected WS 
671     $scope.use_now = function() {
672         egCore.auth.logout();
673         $window.location.href = $location
674             .path('/login')
675             .search({ws : $scope.selectedWS})
676             .absUrl();
677     }
678
679     $scope.can_delete_ws = function(name) {
680         var ws = all_workstations.filter(
681             function(ws) { return ws.name == name })[0];
682         return ws && reg_perm_orgs.indexOf(ws.owning_lib);
683     }
684
685     $scope.remove_ws = function(remove_me) {
686         $scope.removing_ws = remove_me;
687
688         // Perm is used to disable Remove button in UI, but have to check
689         // again here in case we're removing a WS based on URL params.
690         if (!$scope.can_delete_ws(remove_me)) return $q.when();
691
692         $scope.is_removing = true;
693         return workstationSvc.remove_workstation(remove_me)
694         .then(function() {
695
696             all_workstations = all_workstations.filter(
697                 function(ws) { return ws.name != remove_me });
698
699             $scope.workstations = $scope.workstations.filter(
700                 function(ws) { return ws != remove_me });
701
702             if ($scope.selectedWS == remove_me) 
703                 $scope.selectedWS = $scope.workstations[0];
704
705             if ($scope.defaultWS == remove_me) 
706                 $scope.defaultWS = '';
707
708             $scope.is_removing = false;
709         });
710     }
711
712     $scope.register_ws = function() {
713         delete $scope.removing_ws;
714
715         var full_name = 
716             $scope.contextOrg.shortname() + '-' + $scope.newWSName;
717
718         if ($scope.workstations.indexOf(full_name) > -1) {
719             // avoid duplicate local registrations
720             return egAlertDialog.open(egCore.strings.WS_USED);
721         }
722
723         $scope.is_registering = true;
724         workstationSvc.register_workstation(
725             $scope.newWSName, full_name,
726             $scope.contextOrg.id()
727
728         ).then(function(new_ws) {
729             $scope.workstations.push(new_ws.name);
730             all_workstations.push(new_ws);  
731             $scope.is_registering = false;
732
733             if (!$scope.selectedWS) {
734                 $scope.selectedWS = new_ws.name;
735             }
736             if (!$scope.defaultWS) {
737                 return $scope.set_default_ws(new_ws.name);
738             }
739             $scope.newWSName = '';
740         });
741     }
742 }])
743
744