Angular selfcheck WIP
authorBill Erickson <berickxx@gmail.com>
Thu, 27 Oct 2016 15:23:56 +0000 (11:23 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 11 Aug 2017 19:20:25 +0000 (15:20 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/templates/staff/base.tt2
Open-ILS/src/templates/staff/circ/selfcheck/index.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/circ/selfcheck/t_checkout.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/circ/selfcheck/t_login.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/circ/selfcheck/app.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/services/auth.js

index f279759..ac9dece 100644 (file)
   <body>
     <toast></toast>
 
-    <!-- load the navbar template inline since it's used on every page -->
-    <script type="text/ng-template" id="eg-navbar-template">
-      [% INCLUDE "staff/navbar.tt2" %]
-    </script>
+    [% IF !ctx.hide_nav %]
+        <!-- load the navbar template inline since it's used on every page -->
+        <script type="text/ng-template" id="eg-navbar-template">
+          [% INCLUDE "staff/navbar.tt2" %]
+        </script>
 
-    <!-- instantiate the navbar by invoking it's name -->
-    <eg-navbar></eg-navbar>
+        <!-- instantiate the navbar by invoking it's name -->
+        <eg-navbar></eg-navbar>
+    [% END %]
 
     <!-- main page content goes here -->
     <div id="top-content-container" class="container" ng-cloak>[% content %]</div>
diff --git a/Open-ILS/src/templates/staff/circ/selfcheck/index.tt2 b/Open-ILS/src/templates/staff/circ/selfcheck/index.tt2
new file mode 100644 (file)
index 0000000..dff68f7
--- /dev/null
@@ -0,0 +1,22 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Self-Checkout"); 
+  ctx.page_app = "egSelfCheckApp";
+  ctx.hide_nav = 1;
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/dojo/opensrf/md5.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/selfcheck/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/selfcheck.css" />
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+}]);
+</script>
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/circ/selfcheck/t_checkout.tt2 b/Open-ILS/src/templates/staff/circ/selfcheck/t_checkout.tt2
new file mode 100644 (file)
index 0000000..da98485
--- /dev/null
@@ -0,0 +1,3 @@
+<h2>CHECKOUT</h2>
+
+<b>Welcome, {{patron.first_given_name()}}</b>
diff --git a/Open-ILS/src/templates/staff/circ/selfcheck/t_login.tt2 b/Open-ILS/src/templates/staff/circ/selfcheck/t_login.tt2
new file mode 100644 (file)
index 0000000..b34a166
--- /dev/null
@@ -0,0 +1,40 @@
+<div class="container">
+  <div class="row">
+    <div class="col-md-3"></div>
+      <div class="col-md-6">
+        <fieldset>
+          <legend>[% l('Please log in with your username or library barcode.') %]</legend>
+
+          <form ng-submit="login(args)" name="sc-login-form" class="form-horizontal" role="form">
+            <div class="form-group">
+              <label class="col-md-4 control-label" for="login-username">[% l('Username or Barcode') %]</label>
+              <div class="col-md-8">
+                <input type="text" id="login-username" class="form-control" 
+                  focus-me="focus_username" select-me="focus_username"
+                  placeholder="Username" ng-model="args.username"/>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="col-md-4 control-label" for="login-password">[% l('Password') %]</label>
+              <div class="col-md-8">
+                <input type="password" id="login-password" class="form-control"
+                  placeholder="Password" ng-model="args.password"/>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <div class="col-md-offset-4 col-md-2">
+                <button type="submit" class="btn btn-default">[% l('Log in') %]</button>
+              </div>
+              <div class="col-md-2">
+                <span ng-show="login_failed" class="label label-warning">[% l('Login Failed') %]</span>
+              </div>
+            </div>
+
+          </form>
+        </fieldset>
+      </div>
+    <div class="col-md-3"></div>
+  </div>
+</div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/selfcheck/app.js b/Open-ILS/web/js/ui/default/staff/circ/selfcheck/app.js
new file mode 100644 (file)
index 0000000..4428364
--- /dev/null
@@ -0,0 +1,204 @@
+/**
+ * Self-Checkout App
+ */
+
+angular.module('egSelfCheckApp', ['ngRoute', 'ui.bootstrap', 
+    'egCoreMod', 'egUiMod', 'ngToast'])
+
+.config(['ngToastProvider', function(ngToastProvider) {
+    ngToastProvider.configure({
+        verticalPosition: 'bottom',
+        animation: 'fade'
+    });
+}])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+    // data loaded at startup which only requires an authtoken goes
+    // here. this allows the requests to be run in parallel instead of
+    // waiting until startup has completed.
+    var resolver = {delay : ['egCore', function(egCore) {
+
+        // fetch the org settings we care about during egStartup
+        // and toss them into egCore.env as egCore.env.aous[name] = value.
+        // note: only load settings here needed by all tabs; load tab-
+        // specific settings from within their respective controllers
+        egCore.env.classLoaders.aous = function() {
+            return egCore.org.settings([
+                'opac.barcode_regex',
+                'circ.selfcheck.patron_login_timeout',
+                'circ.selfcheck.auto_override_checkout_events',
+                'circ.selfcheck.patron_password_required',
+                'circ.checkout_auto_renew_age',
+                'circ.selfcheck.workstation_required',
+                'circ.selfcheck.alert.popup',
+                'circ.selfcheck.alert.sound',
+                'credit.payments.allow',
+                'circ.selfcheck.block_checkout_on_copy_status'
+            ]).then(function(settings) { 
+                egCore.env.aous = settings;
+            });
+        }
+
+        egCore.env.loadClasses.push('aous');
+
+        return egCore.startup.go().then(function() {
+            // TODO load other startup data.
+        });
+    }]};
+
+    $routeProvider.when('/circ/selfcheck/login', {
+        templateUrl: './circ/selfcheck/t_login',
+        controller: 'PatronLoginCtrl',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/circ/selfcheck/checkout', {
+        templateUrl: './circ/selfcheck/t_checkout',
+        controller: 'CheckoutCtrl',
+        resolve : resolver
+    });
+
+    $routeProvider.otherwise({redirectTo : '/circ/selfcheck/login'});
+})
+
+/**
+ * Self-Checkout Service
+ */
+.factory('scSvc',
+       ['$q','$timeout','$location','$timeout','egCore',
+function($q , $timeout , $location , $timeout , egCore) {
+
+    var service = {
+        login_timer : null,
+        login_warning_timer : null,
+
+        // function called when login_timeout expires.
+        login_timer_handler : null,
+
+        // 2 minutes, 40 seconds  
+        login_timeout : 160000, 
+
+        // 20 second inactivity warning; total default timeout is 3 minutes.
+        login_timeout_warning : 20000, 
+    };
+
+
+    service.start_login_timer = function() {
+        service.login_timer = $timeout(
+            service.login_timer_handler, service.login_timeout
+        );
+        console.debug('starting patron login timer');
+    }
+
+    service.reset_login_timer = function() {
+        if (service.login_timer) {
+            $timeout.cancel(service.login_timer);
+        }
+        service.start_login_timer();
+        console.debug('reset patron login timer');
+    }
+
+
+    service.fetch_patron = function(username, barcode, deferred) {
+
+        egCore.net.request(
+            'open-ils.actor', 
+            'open-ils.actor.user.retrieve_id_by_barcode_or_username',
+            egCore.auth.token(), barcode, username
+        ).then(function(patron_id) {
+
+            if (egCore.evt.parse(patron_id)) {
+                deferred.reject();
+                return;
+            }
+
+            egCore.net.request(
+                'open-ils.actor', 
+                'open-ils.actor.user.fleshed.retrieve.authoritative',
+                egCore.auth.token(), patron_id
+            ).then(function(patron) {
+
+                if (egCore.evt.parse(patron)) {
+                    deferred.reject();
+                    return;
+                }
+
+                service.patron = patron;
+                deferred.resolve();
+            });
+        });
+    }
+
+    service.login_patron = function(username, password) {
+        var deferred = $q.defer();
+
+        // TODO: test barcode regex
+        var barcode = null;
+
+        if (true) { // TODO: test password required
+            egCore.auth.verify({
+                username : username, 
+                barcode  : barcode,
+                password : password
+            }).then(
+                function() {
+                    service.fetch_patron(username, barcode, deferred);
+                },
+                function() { deferred.reject() /* verify failed */ }
+            );
+        } else {
+            service.fetch_patron(username, barcode, deferred);
+        }
+
+        return deferred.promise;
+    }
+
+
+    return service;
+}])
+
+/**
+ * Manages tabbed patron view.
+ * This is the parent scope of all patron tab scopes.
+ *
+ * */
+.controller('PatronLoginCtrl',
+       ['$scope','$q','$location','egCore','scSvc',
+function($scope,  $q,  $location , egCore,  scSvc) {
+
+    $scope.login = function(args) {
+        $scope.login_failed = false;
+
+        if (!args.username) return;
+
+        scSvc.login_patron(args.username, args.password).then(
+            function() {
+                console.log('HERE');
+                $location.path('/circ/selfcheck/checkout');
+            },
+            function() {
+                $scope.args.password = '';
+                $scope.login_failed = true;
+                $scope.focus_username = true;
+            }
+        );
+    }
+}])
+
+.controller('CheckoutCtrl',
+       ['$scope','$q','$location','egCore','scSvc',
+function($scope,  $q,  $location , egCore,  scSvc) {
+
+    if (!scSvc.patron) {
+        $location.path('/circ/selfcheck/login');
+        return;
+    }
+
+    $scope.patron = scSvc.patron;
+
+}])
+
+
index 4e98956..563355b 100644 (file)
@@ -130,6 +130,33 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
         });
     }
 
+    /* Tests login credentials without logging in */
+    service.verify = function(args) {
+        var deferred = $q.defer();
+        egNet.request(
+            'open-ils.auth',
+            'open-ils.auth.authenticate.init', args.username
+        ).then(
+            function(seed) {
+                args.password = hex_md5(seed + hex_md5(args.password))
+                return egNet.request(
+                    'open-ils.auth',
+                    'open-ils.auth.authenticate.verify', args)
+            }
+        ).then(
+            function(evt) {
+                if (evt.textcode == 'SUCCESS') {
+                    deferred.resolve();
+                } else {
+                    console.error('login verify failed ' + js2JSON(evt));
+                    deferred.reject();
+                }
+            }
+        );
+
+        return deferred.promise;
+    };
+
     /**
      * Returns a promise, which is resolved on successful 
      * login and rejected on failed login.