]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
LP#1467663 webstaff: dedicated workstation admin page
[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     $scope.apply_sound = function() {
182         if ($scope.disable_sound) {
183             egCore.hatch.setItem('eg.audio.disable', true);
184         } else {
185             egCore.hatch.removeItem('eg.audio.disable');
186         }
187     }
188
189     $scope.test_audio = function(sound) {
190         egCore.audio.play(sound);
191     }
192
193 }])
194
195 .controller('PrintConfigCtrl',
196        ['$scope','egCore',
197 function($scope , egCore) {
198     console.log('PrintConfigCtrl');
199
200     $scope.actionPending = false;
201     $scope.isTestView = false;
202
203     $scope.setContext = function(ctx) { 
204         $scope.context = ctx; 
205         $scope.isTestView = false;
206         $scope.actionPending = false;
207     }
208     $scope.setContext('default');
209
210     $scope.getPrinterByAttr = function(attr, value) {
211         var printer;
212         angular.forEach($scope.printers, function(p) {
213             if (p[attr] == value) printer = p;
214         });
215         return printer;
216     }
217
218     $scope.currentPrinter = function() {
219         if ($scope.printConfig && $scope.printConfig[$scope.context]) {
220             return $scope.getPrinterByAttr(
221                 'name', $scope.printConfig[$scope.context].printer
222             );
223         }
224     }
225
226     // fetch info on all remote printers
227     egCore.hatch.getPrinters()
228     .then(function(printers) { 
229         $scope.printers = printers;
230         $scope.defaultPrinter = 
231             $scope.getPrinterByAttr('is-default', true);
232     })
233     .then(function() { return egCore.hatch.getPrintConfig() })
234     .then(function(config) {
235         $scope.printConfig = config;
236
237         var pname = '';
238         if ($scope.defaultPrinter) {
239             pname = $scope.defaultPrinter.name;
240
241         } else if ($scope.printers.length == 1) {
242             // if the OS does not report a default printer, but only
243             // one printer is available, treat it as the default.
244             pname = $scope.printers[0].name;
245         }
246
247         // apply the default printer to every context which has
248         // no printer configured.
249         angular.forEach(
250             ['default','receipt','label','mail','offline'],
251             function(ctx) {
252                 if (!$scope.printConfig[ctx]) {
253                     $scope.printConfig[ctx] = {
254                         context : ctx,
255                         printer : pname
256                     }
257                 }
258             }
259         );
260     });
261
262     $scope.printerConfString = function() {
263         if ($scope.printConfigError) return $scope.printConfigError;
264         if (!$scope.printConfig) return;
265         if (!$scope.printConfig[$scope.context]) return;
266         return JSON.stringify(
267             $scope.printConfig[$scope.context], undefined, 2);
268     }
269
270     $scope.resetConfig = function() {
271         $scope.actionPending = true;
272         $scope.printConfigError = null;
273         $scope.printConfig[$scope.context] = {
274             context : $scope.context
275         }
276         
277         if ($scope.defaultPrinter) {
278             $scope.printConfig[$scope.context].printer = 
279                 $scope.defaultPrinter.name;
280         }
281
282         egCore.hatch.setPrintConfig($scope.printConfig)
283         .finally(function() {$scope.actionPending = false});
284     }
285
286     $scope.configurePrinter = function() {
287         $scope.printConfigError = null;
288         $scope.actionPending = true;
289         egCore.hatch.configurePrinter(
290             $scope.context,
291             $scope.printConfig[$scope.context].printer
292         )
293         .then(
294             function(config) {$scope.printConfig = config},
295             function(error) {$scope.printConfigError = error}
296         )
297         .finally(function() {$scope.actionPending = false});
298     }
299
300     $scope.setPrinter = function(name) {
301         $scope.printConfig[$scope.context].printer = name;
302     }
303
304     // for testing
305     $scope.setContentType = function(type) { $scope.contentType = type }
306
307     $scope.testPrint = function(withDialog) {
308         if ($scope.contentType == 'text/plain') {
309             egCore.print.print({
310                 context : $scope.context, 
311                 content_type : $scope.contentType, 
312                 content : $scope.textPrintContent,
313                 show_dialog : withDialog
314             });
315         } else {
316             egCore.print.print({
317                 context : $scope.context,
318                 content_type : $scope.contentType, 
319                 content : $scope.htmlPrintContent, 
320                 scope : {
321                     value1 : 'Value One', 
322                     value2 : 'Value Two',
323                     date_value : '2015-02-04T14:04:34-0400'
324                 },
325                 show_dialog : withDialog
326             });
327         }
328     }
329
330     $scope.setContentType('text/plain');
331
332 }])
333
334 .controller('PrintTemplatesCtrl',
335        ['$scope','$q','egCore',
336 function($scope , $q , egCore) {
337
338     $scope.print = {
339         template_name : 'bills_current',
340         template_output : ''
341     };
342
343     // print preview scope data
344     // TODO: consider moving the template-specific bits directly
345     // into the templates or storing template- specific script files
346     // alongside the templates.
347     // NOTE: A lot of this data can be shared across templates.
348     var seed_user = {
349         first_given_name : 'Slow',
350         second_given_name : 'Joe',
351         family_name : 'Jones',
352         card : {
353             barcode : '30393830393'
354         }
355     }
356     var seed_addr = {
357         street1 : '123 Apple Rd',
358         street2 : 'Suite B',
359         city : 'Anywhere',
360         state : 'XX',
361         country : 'US',
362         post_code : '12345'
363     }
364
365     var seed_record = {
366         title : 'Traveling Pants!!',
367         author : 'Jane Jones',
368         isbn : '1231312123'
369     };
370
371     var seed_copy = {
372         barcode : '33434322323'
373     }
374
375     var one_hold = {
376         behind_desk : 'f',
377         phone_notify : '111-222-3333',
378         sms_notify : '111-222-3333',
379         email_notify : 'user@example.org',
380         request_time : new Date().toISOString()
381     }
382
383
384     $scope.preview_scope = {
385         //bills
386         transactions : [
387             {
388                 id : 1,
389                 xact_start : new Date().toISOString(),
390                 summary : {
391                     xact_type : 'circulation',
392                     last_billing_type : 'Overdue materials',
393                     total_owed : 1.50,
394                     last_payment_note : 'Test Note 1',
395                     total_paid : 0.50,
396                     balance_owed : 1.00
397                 }
398             }, {
399                 id : 2,
400                 xact_start : new Date().toISOString(),
401                 summary : {
402                     xact_type : 'circulation',
403                     last_billing_type : 'Overdue materials',
404                     total_owed : 2.50,
405                     last_payment_note : 'Test Note 2',
406                     total_paid : 0.50,
407                     balance_owed : 2.00
408                 }
409             }
410         ],
411
412         circulations : [
413             {   
414                 due_date : new Date().toISOString(), 
415                 target_copy : seed_copy,
416                 title : seed_record.title
417             },
418         ],
419
420         previous_balance : 8.45,
421         payment_total : 2.00,
422         payment_applied : 2.00,
423         new_balance : 6.45,
424         amount_voided : 0,
425         change_given : 0,
426         payment_type : 'cash_payment',
427         payment_note : 'Here is a payment note',
428         note : {
429             create_date : new Date().toISOString(), 
430             title : 'Test Note Title',
431             usr : seed_user,
432             value : 'This patron is super nice!'
433         },
434
435         transit : {
436             dest : {
437                 name : 'Library X',
438                 shortname : 'LX',
439                 holds_address : seed_addr
440             },
441             target_copy : seed_copy
442         },
443         title : seed_record.title,
444         author : seed_record.author,
445         patron : egCore.idl.toHash(egCore.auth.user()),
446         address : seed_addr,
447         hold : one_hold,
448         holds : [
449             {hold : one_hold, title : 'Some Title 1', author : 'Some Author 1'},
450             {hold : one_hold, title : 'Some Title 2', author : 'Some Author 2'},
451             {hold : one_hold, title : 'Some Title 3', author : 'Some Author 3'}
452         ]
453     }
454
455     $scope.preview_scope.payments = [
456         {amount : 1.00, xact : $scope.preview_scope.transactions[0]}, 
457         {amount : 1.00, xact : $scope.preview_scope.transactions[1]}
458     ]
459     $scope.preview_scope.payments[0].xact.title = 'Hali Bote Azikaban de tao fan';
460     $scope.preview_scope.payments[0].xact.copy_barcode = '334343434';
461     $scope.preview_scope.payments[1].xact.title = seed_record.title;
462     $scope.preview_scope.payments[1].xact.copy_barcode = seed_copy.barcode;
463
464     // today, staff, current_location, etc.
465     egCore.print.fleshPrintScope($scope.preview_scope);
466
467     $scope.template_changed = function() {
468         $scope.print.load_failed = false;
469         egCore.print.getPrintTemplate($scope.print.template_name)
470         .then(
471             function(html) { 
472                 $scope.print.template_content = html;
473                 console.log('set template content');
474             },
475             function() {
476                 $scope.print.template_content = '';
477                 $scope.print.load_failed = true;
478             }
479         );
480     }
481
482     $scope.save_locally = function() {
483         egCore.print.storePrintTemplate(
484             $scope.print.template_name,
485             $scope.print.template_content
486         );
487     }
488
489     $scope.template_changed(); // load the default
490 }])
491
492 // 
493 .directive('egPrintTemplateOutput', ['$compile',function($compile) {
494     return function(scope, element, attrs) {
495         scope.$watch(
496             function(scope) {
497                 return scope.$eval(attrs.content);
498             },
499             function(value) {
500                 // create an isolate scope and copy the print context
501                 // data into the new scope.
502                 // TODO: see also print security concerns in egHatch
503                 var result = element.html(value);
504                 var context = scope.$eval(attrs.context);
505                 var print_scope = scope.$new(true);
506                 angular.forEach(context, function(val, key) {
507                     print_scope[key] = val;
508                 })
509                 $compile(element.contents())(print_scope);
510             }
511         );
512     };
513 }])
514
515 .controller('StoredPrefsCtrl',
516        ['$scope','$q','egCore','egConfirmDialog',
517 function($scope , $q , egCore , egConfirmDialog) {
518     console.log('StoredPrefsCtrl');
519
520     $scope.setContext = function(ctx) {
521         $scope.context = ctx;
522     }
523     $scope.setContext('local');
524
525     // grab the edit perm
526     $scope.userHasDeletePerm = false;
527     egCore.perm.hasPermHere('ADMIN_WORKSTATION')
528     .then(function(bool) { $scope.userHasDeletePerm = bool });
529
530     // fetch the keys
531
532     function refreshKeys() {
533         $scope.keys = {local : [], remote : []};
534
535         egCore.hatch.getRemoteKeys().then(
536             function(keys) { $scope.keys.remote = keys.sort() })
537     
538         // local calls are non-async
539         $scope.keys.local = egCore.hatch.getLocalKeys();
540     }
541     refreshKeys();
542
543     $scope.selectKey = function(key) {
544         $scope.currentKey = key;
545         $scope.currentKeyContent = null;
546
547         if ($scope.context == 'local') {
548             $scope.currentKeyContent = egCore.hatch.getLocalItem(key);
549         } else {
550             egCore.hatch.getRemoteItem(key)
551             .then(function(content) {
552                 $scope.currentKeyContent = content
553             });
554         }
555     }
556
557     $scope.getCurrentKeyContent = function() {
558         return JSON.stringify($scope.currentKeyContent, null, 2);
559     }
560
561     $scope.removeKey = function(key) {
562         egConfirmDialog.open(
563             egCore.strings.PREFS_REMOVE_KEY_CONFIRM, '',
564             {   deleteKey : key,
565                 ok : function() {
566                     if ($scope.context == 'local') {
567                         egCore.hatch.removeLocalItem(key);
568                         refreshKeys();
569                     } else {
570                         egCore.hatch.removeItem(key)
571                         .then(function() { refreshKeys() });
572                     }
573                 },
574                 cancel : function() {} // user canceled, nothing to do
575             }
576         );
577     }
578 }])
579
580 .controller('WSRegCtrl',
581        ['$scope','$q','$window','$location','egCore','egAlertDialog','workstationSvc',
582 function($scope , $q , $window , $location , egCore , egAlertDialog , workstationSvc) {
583
584     var all_workstations = [];
585     var reg_perm_orgs = [];
586
587     $scope.page_loaded = false;
588     $scope.contextOrg = egCore.org.get(egCore.auth.user().ws_ou());
589     $scope.wsOrgChanged = function(org) { $scope.contextOrg = org; }
590
591     console.log('set context org to ' + $scope.contextOrg);
592
593     // fetch workstation reg perms
594     egCore.perm.hasPermAt('REGISTER_WORKSTATION', true)
595     .then(function(orgList) { 
596         reg_perm_orgs = orgList;
597
598         // hide orgs in the context org selector where this login
599         // does not have the reg_ws perm
600         $scope.wsOrgHidden = function(id) {
601             return reg_perm_orgs.indexOf(id) == -1;
602         }
603
604     // fetch the locally stored workstation data
605     }).then(function() {
606         return workstationSvc.get_all()
607         
608     }).then(function(all) {
609         all_workstations = all || [];
610         $scope.workstations = 
611             all_workstations.map(function(w) { return w.name });
612         return workstationSvc.get_default()
613
614     // fetch the default workstation
615     }).then(function(def) { 
616         $scope.defaultWS = def;
617         $scope.activeWS = $scope.selectedWS = egCore.auth.workstation() || def;
618
619     // Handle any URL commands.
620     }).then(function() {
621         var remove = $location.search().remove;
622          if (remove) {
623             console.log('Removing WS via URL request: ' + remove);
624             return $scope.remove_ws(remove).then(
625                 function() { $scope.page_loaded = true; });
626         }
627         $scope.page_loaded = true;
628     });
629
630     $scope.get_ws_label = function(ws) {
631         return ws == $scope.defaultWS ? 
632             egCore.strings.$replace(egCore.strings.DEFAULT_WS_LABEL, {ws:ws}) : ws;
633     }
634
635     $scope.set_default_ws = function(name) {
636         delete $scope.removing_ws;
637         $scope.defaultWS = name;
638         workstationSvc.set_default(name);
639     }
640
641     $scope.cant_have_users = 
642         function (id) { return !egCore.org.CanHaveUsers(id); };
643     $scope.cant_have_volumes = 
644         function (id) { return !egCore.org.CanHaveVolumes(id); };
645
646     // Log out and return to login page with selected WS 
647     $scope.use_now = function() {
648         egCore.auth.logout();
649         $window.location.href = $location
650             .path('/login')
651             .search({ws : $scope.selectedWS})
652             .absUrl();
653     }
654
655     $scope.can_delete_ws = function(name) {
656         var ws = all_workstations.filter(
657             function(ws) { return ws.name == name })[0];
658         return ws && reg_perm_orgs.indexOf(ws.owning_lib);
659     }
660
661     $scope.remove_ws = function(remove_me) {
662         $scope.removing_ws = remove_me;
663
664         // Perm is used to disable Remove button in UI, but have to check
665         // again here in case we're removing a WS based on URL params.
666         if (!$scope.can_delete_ws(remove_me)) return $q.when();
667
668         $scope.is_removing = true;
669         return workstationSvc.remove_workstation(remove_me)
670         .then(function() {
671
672             all_workstations = all_workstations.filter(
673                 function(ws) { return ws.name != remove_me });
674
675             $scope.workstations = $scope.workstations.filter(
676                 function(ws) { return ws != remove_me });
677
678             if ($scope.selectedWS == remove_me) 
679                 $scope.selectedWS = $scope.workstations[0];
680
681             if ($scope.defaultWS == remove_me) 
682                 $scope.defaultWS = '';
683
684             $scope.is_removing = false;
685         });
686     }
687
688     $scope.register_ws = function() {
689         delete $scope.removing_ws;
690
691         var full_name = 
692             $scope.contextOrg.shortname() + '-' + $scope.newWSName;
693
694         if ($scope.workstations.indexOf(full_name) > -1) {
695             // avoid duplicate local registrations
696             return egAlertDialog.open(egCore.strings.WS_USED);
697         }
698
699         $scope.is_registering = true;
700         workstationSvc.register_workstation(
701             $scope.newWSName, full_name,
702             $scope.contextOrg.id()
703
704         ).then(function(new_ws) {
705             $scope.workstations.push(new_ws.name);
706             all_workstations.push(new_ws);  
707             $scope.is_registering = false;
708
709             if (!$scope.selectedWS) {
710                 $scope.selectedWS = new_ws.name;
711             }
712             if (!$scope.defaultWS) {
713                 return $scope.set_default_ws(new_ws.name);
714             }
715             $scope.newWSName = '';
716         });
717     }
718 }])
719
720