offline: Add light-weight tablesorter; Enable barcode checking; Add confirmation...
authorMike Rylander <mrylander@gmail.com>
Thu, 8 Jun 2017 21:55:41 +0000 (17:55 -0400)
committerMike Rylander <mrylander@gmail.com>
Thu, 8 Jun 2017 21:55:41 +0000 (17:55 -0400)
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/src/templates/staff/base_js.tt2
Open-ILS/src/templates/staff/offline-interface.tt2
Open-ILS/web/js/ui/default/staff/Gruntfile.js
Open-ILS/web/js/ui/default/staff/offline.js
Open-ILS/web/js/ui/default/staff/package.json

index 351a246..edc16e0 100644 (file)
@@ -12,6 +12,7 @@ UpUp.start({
     '[% ctx.media_prefix %]/js/ui/default/staff/build/css/ngToast-animations.min.css',
     '[% ctx.media_prefix %]/js/ui/default/staff/build/css/tree-control.css',
     '[% ctx.media_prefix %]/js/ui/default/staff/build/css/tree-control-attribute.css',
+    '[% ctx.media_prefix %]/js/ui/default/staff/build/css/tablesort.css',
     '[% ctx.base_path %]/staff/css/print.css',
     '[% ctx.base_path %]/staff/css/cat.css',
     '[% ctx.base_path %]/staff/css/style.css',
@@ -31,6 +32,7 @@ UpUp.start({
     '[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-tree-control.js',
     '[% ctx.media_prefix %]/js/ui/default/staff/build/js/iframeResizer.min.js',
     '[% ctx.media_prefix %]/js/ui/default/staff/build/js/ng-order-object-by.js',
+    '[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-tablesort.js',
     '[% ctx.media_prefix %]/js/ui/default/staff/build/js/lovefield.min.js',
     '[% ctx.media_prefix %]/js/ui/default/staff/build/fonts/glyphicons-halflings-regular.woff',
     '[% ctx.media_prefix %]/js/dojo/opensrf/JSON_v1.js',
@@ -166,7 +168,10 @@ UpUp.start({
     s.PATRON_NOT_FOUND = "[% l('Patron not found') %]";
     s.PATRON_BLOCKED = "[% l('Patron blocked') %]";
     s.BAD_BARCODE = "[% l('Bad item barcode') %]";
+    s.BAD_PATRON_BARCODE = "[% l('Bad patron barcode') %]";
     s.ITEM_NOT_FOUND = "[% l('Item not found') %]";
+    s.CONFIRM_CLEAR_PENDING = "[% l('Clear pending transactions') %]";
+    s.CONFIRM_CLEAR_PENDING_BODY = "[% l('Are you certain you want to clear these pending offline transactions?  This action is not reversable, and they cannot be recovered after clearing!') %]";
   }]);
 </script>
 
index 0697759..d75a074 100644 (file)
             <div class="row">
               <div class="col-md-1"></div>
               <div class="col-md-4">
+                [% l('Due Date:') %]
+              </div>
+              <div class="col-md-4">
+                <eg-date-input id="co_duedate" ng-model="shared.due_date"></eg-date-input>
+              </div>
+              <div class="col-md-3">
+                <select class="form-control" ng-model="shared.due_date_offset" ng-change="resetDueDate()">
+                  <option value="">[% l('No Offset') %]</option>
+                  <option value="3">[% l('Today + 3 days') %]</option>
+                  <option value="7">[% l('Today + 7 days') %]</option>
+                  <option value="14">[% l('Today + 14 days') %]</option>
+                  <option value="30">[% l('Today + 30 days') %]</option>
+                </select>
+              </div>
+            </div>
+
+            <div class="row pad-vert">
+              <div class="col-md-1"></div>
+              <div class="col-md-4">
                 [% l('Patron barcode:') %]
               </div>
               <div class="col-md-7">
             </div>
 
             <div class="row pad-vert">
-              <div class="col-md-1"></div>
-              <div class="col-md-4">
-                [% l('Due Date:') %]
+              <div class="col-md-2">
+                <button class="btn btn-warning" ng-click="clear('checkout')">[% l('Clear') %]</button>
               </div>
               <div class="col-md-4">
-                <eg-date-input id="co_duedate" ng-model="shared.due_date"></eg-date-input>
-              </div>
-              <div class="col-md-3">
-                <select class="form-control" ng-model="shared.due_date_offset" ng-change="resetDueDate()">
-                  <option value="">[% l('No Offset') %]</option>
-                  <option value="3">[% l('3 days') %]</option>
-                  <option value="7">[% l('7 days') %]</option>
-                  <option value="14">[% l('14 days') %]</option>
-                  <option value="30">[% l('30 days') %]</option>
-                </select>
-              </div>
-            </div>
-
-            <div class="row pad-vert">
-              <div class="col-md-6">
-                <button class="btn btn-warning" ng-click="clear('checkout')">[% l('Clear') %]</button>
+                <input id="do_check_co" type="checkbox" ng-model="strict_barcode" ng-click="changeCheck()"></input>
+                <label for="do_check_co">[% l('Strict Barcode') %]</label>
               </div>
               <div class="col-md-6">
                 <input id="do_print_co" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
             <div class="row">
               <div class="col-md-1"></div>
               <div class="col-md-4">
-                [% l('Patron barcode:') %]
+                [% l('Due Date:') %]
               </div>
-              <div class="col-md-7">
-                <input class="form-control" type="text" ng-model="renew.patron_barcode" next-on-enter="re_barcode"/>
+              <div class="col-md-4">
+                <eg-date-input ng-model="shared.due_date"></eg-date-input>
+              </div>
+              <div class="col-md-3">
+                <select class="form-control" ng-model="shared.due_date_offset" ng-change="resetDueDate()">
+                  <option value="">[% l('No Offset') %]</option>
+                  <option value="3">[% l('Today + 3 days') %]</option>
+                  <option value="7">[% l('Today + 7 days') %]</option>
+                  <option value="14">[% l('Today + 14 days') %]</option>
+                  <option value="30">[% l('Today + 30 days') %]</option>
+                </select>
               </div>
             </div>
 
             <div class="row pad-vert">
               <div class="col-md-1"></div>
               <div class="col-md-4">
-                [% l('Item Barcode:') %]
+                [% l('Patron barcode:') %]
               </div>
               <div class="col-md-7">
-                <input class="form-control" type="text" ng-model="renew.barcode" id="re_barcode" eg-enter="!notEnough('renew') && add('renew')"/>
+                <input class="form-control" type="text" ng-model="renew.patron_barcode" next-on-enter="re_barcode"/>
               </div>
             </div>
 
             <div class="row pad-vert">
               <div class="col-md-1"></div>
               <div class="col-md-4">
-                [% l('Due Date:') %]
-              </div>
-              <div class="col-md-4">
-                <eg-date-input ng-model="shared.due_date"></eg-date-input>
+                [% l('Item Barcode:') %]
               </div>
-              <div class="col-md-3">
-                <select class="form-control" ng-model="shared.due_date_offset" ng-change="resetDueDate()">
-                  <option value="">[% l('No Offset') %]</option>
-                  <option value="3">[% l('3 days') %]</option>
-                  <option value="7">[% l('7 days') %]</option>
-                  <option value="14">[% l('14 days') %]</option>
-                  <option value="30">[% l('30 days') %]</option>
-                </select>
+              <div class="col-md-7">
+                <input class="form-control" type="text" ng-model="renew.barcode" id="re_barcode" eg-enter="!notEnough('renew') && add('renew')"/>
               </div>
             </div>
 
             <div class="row pad-vert">
-              <div class="col-md-6">
+              <div class="col-md-2">
                 <button class="btn btn-warning" ng-click="clear('renew')">[% l('Clear') %]</button>
               </div>
+              <div class="col-md-4">
+                <input id="do_check_r" type="checkbox" ng-model="strict_barcode" ng-click="changeCheck()"></input>
+                <label for="do_check_r">[% l('Strict Barcode') %]</label>
+              </div>
               <div class="col-md-6">
                 <input id="do_print_r" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
                 <label for="do_print_r">[% l('Print receipt') %]</label>
           <!-- left-hand side -->
           <div class="col-md-6 container" style="border-right:solid 1px;">
 
-            <div class="row pad-vert">
+            <div class="row">
               <div class="col-md-1"></div>
               <div class="col-md-5">
                 [% l('Use count:') %]
               </div>
             </div>
 
-            <div class="row">
+            <div class="row pad-vert">
               <div class="col-md-1"></div>
               <div class="col-md-5">
                 [% l('Item Barcode:') %]
             </div>
 
             <div class="row pad-vert">
-              <div class="col-md-6">
+              <div class="col-md-2">
                 <button class="btn btn-warning" ng-click="clear('in_house_use')">[% l('Clear') %]</button>
               </div>
+              <div class="col-md-4">
+                <input id="do_check_ihu" type="checkbox" ng-model="strict_barcode" ng-click="changeCheck()"></input>
+                <label for="do_check_ihu">[% l('Strict Barcode') %]</label>
+              </div>
               <div class="col-md-6">
                 <input id="do_print_ihu" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
                 <label for="do_print_ihu">[% l('Print receipt') %]</label>
             <div class="row">
               <div class="col-md-1"></div>
               <div class="col-md-5">
-                [% l('Item Barcode:') %]
+                [% l('Checkin Date:') %]
               </div>
               <div class="col-md-6">
-                <input class="form-control" type="text" ng-model="checkin.barcode" eg-enter="!notEnough('checkin') && add('checkin')"/>
+                <eg-date-input ng-model="checkin.backdate"></eg-date-input>
               </div>
             </div>
 
             <div class="row pad-vert">
               <div class="col-md-1"></div>
               <div class="col-md-5">
-                [% l('Checkin Date:') %]
+                [% l('Item Barcode:') %]
               </div>
               <div class="col-md-6">
-                <eg-date-input ng-model="checkin.backdate"></eg-date-input>
+                <input class="form-control" type="text" ng-model="checkin.barcode" eg-enter="!notEnough('checkin') && add('checkin')"/>
               </div>
             </div>
 
             <div class="row pad-vert">
-              <div class="col-md-6">
+              <div class="col-md-2">
                 <button class="btn btn-warning" ng-click="clear('checkin')">[% l('Clear') %]</button>
               </div>
+              <div class="col-md-4">
+                <input id="do_check_ci" type="checkbox" ng-model="strict_barcode" ng-click="changeCheck()"></input>
+                <label for="do_check_ci">[% l('Strict Barcode') %]</label>
+              </div>
               <div class="col-md-6">
                 <input id="do_print_ci" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
                 <label for="do_print_ci">[% l('Print receipt') %]</label>
               </div>
               <div class="row">
                 <div class="col-md-12">
-                  <table class="table">
+                  <table class="table" ts-wrapper>
                     <thead>
                       <tr>
-                        <th>[% l('Description') %]</th>
-                        <th>[% l('Date Created') %]</th>
+                        <th ts-criteria="org">[% l('Organization') %]</th>
+                        <th ts-criteria="creator">[% l('Created By') %]</th>
+                        <th ts-criteria="description">[% l('Description') %]</th>
+                        <th ts-criteria="create_time|parseInt" ts-default="descending">[% l('Date Created') %]</th>
                         <th>[% l('Upload Count') %]</th>
                         <th>[% l('Transactions Processed') %]</th>
-                        <th>[% l('Date Completed') %]</th>
+                        <th ts-criteria="end_time|parseInt">[% l('Date Completed') %]</th>
                         <th></th>
                       </tr>
                     </thead>
                     <tbody>
-                      <tr
+                      <tr ts-repeat
                         ng-repeat="ses in sessions track by $index"
                         ng-click="setSession(ses, $index)"
                         ng-class="{'bg-info':current_session_index==$index}"
                       >
+                        <td>{{ses.org}}</td>
+                        <td>{{ses.creator}}</td>
                         <td>{{ses.description}}</td>
                         <td>{{createDate(ses.create_time, true) | date:'short'}}</td>
                         <td>{{ses.total}}</td>
                             ng-disabled="!logged_in || (!xact.command.patron_barcode && xact.command.user.card.barcode)"
                             ng-click="retrievePatron(xact.command.patron_barcode)">[% l('Patron') %]</button>
                           <button
-                            class="btn btn-info btn-xs"
+                            class="btn btn-info btn-xs hidden"
                             ng-disabled="!logged_in"
                             ng-click="retrieveDetails(xact)">[% l('Details') %]</button>
                         </td>
 <!-- offline page app -->
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/file.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/offline.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-tablesort.js"></script>
 <script>
 angular.module('egCoreMod').run(['egStrings', function(s) {
   s.REG_ADDR_TYPE = "[% l('Mailing') %]";
@@ -564,6 +585,7 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
 }]);
 </script>
 <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/circ.css" />
+<link rel="stylesheet" href="[% ctx.media_prefix %]/js/ui/default/staff/build/css/tablesort.css" />
 [% END %]
 
 <div ng-view></div> 
index e594196..db8590b 100644 (file)
@@ -37,6 +37,7 @@ module.exports = function(grunt) {
             'node_modules/iframe-resizer/js/iframeResizer.map',
             'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js',
             'node_modules/angular-order-object-by/src/ng-order-object-by.js',
+            'node_modules/angular-tablesort/js/angular-tablesort.js',
             'node_modules/lovefield/dist/lovefield.min.js',
             'node_modules/lovefield/dist/lovefield.min.js.map'
           ]
@@ -56,6 +57,7 @@ module.exports = function(grunt) {
             'node_modules/ngtoast/dist/ngToast-animations.min.css',
             'node_modules/angular-tree-control/css/tree-control.css',
             'node_modules/angular-tree-control/css/tree-control-attribute.css',
+            'node_modules/angular-tablesort/tablesort.css',
           ]
         }]
       },
index 1c08568..c29b393 100644 (file)
@@ -4,7 +4,7 @@
 
 lf.isOffline = true;
 
-angular.module('egOffline', ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'ngToast'])
+angular.module('egOffline', ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'ngToast', 'tableSort'])
 
 .config(
        ['$routeProvider','$locationProvider','$compileProvider',
@@ -131,12 +131,19 @@ function($routeProvider , $locationProvider , $compileProvider) {
                 }
                 return $q.reject();
             }).then(function() {
+                var creator_list = [$q.when()];
                 angular.forEach($scope.sessions, function (s) {
                     s.total = 0;
+                    s.org = egCore.org.get(s.org).shortname();
+                    creator_list.push(egCore.pcrud.retrieve('au',s.creator).then(function(u) {
+                        s.creator = u.family_name();
+                    }));
                     angular.forEach(s.scripts, function(sc) {
                         s.total += sc.count;
                     });
                 });
+
+                return $q.all(creator_list);
             });
         }
 
@@ -163,7 +170,7 @@ function($routeProvider , $locationProvider , $compileProvider) {
                         }
                     ).then(function(res) {
                         if (res.data.ilsevent == "0") {
-                            return $scope.clear_pending().then(function() {
+                            return $scope.clear_pending(true).then(function() {
                                 return $scope.refreshSessions();
                             });
                         } else {
@@ -232,8 +239,8 @@ function($routeProvider , $locationProvider , $compileProvider) {
 ])
 
 .controller('OfflineCtrl', 
-           ['$q','$scope','$location','$window','egCore','egLovefield','$routeParams','$timeout','$http','ngToast',
-    function($q , $scope , $location , $window , egCore , egLovefield , $routeParams , $timeout , $http , ngToast) {
+           ['$q','$scope','$location','$window','egCore','egLovefield','$routeParams','$timeout','$http','ngToast','egConfirmDialog',
+    function($q , $scope , $location , $window , egCore , egLovefield , $routeParams , $timeout , $http , ngToast , egConfirmDialog) {
         $scope.active_tab = $routeParams.tab || 'checkout';
 
         $scope.blocked_patron = null;
@@ -265,9 +272,16 @@ function($routeProvider , $locationProvider , $compileProvider) {
             })[0].name;
         }
 
+        $scope.changeCheck = function () {
+            $scope.strict_barcode = !$scope.strict_barcode;
+            $scope.do_check_changed = true;
+            egCore.hatch.setItem('eg.offline.strict_barcode', $scope.strict_barcode)
+        }
+
         $scope.changePrint = function () {
             $scope.do_print = !$scope.do_print;
             $scope.do_print_changed = true;
+            egCore.hatch.setItem('eg.offline.print_receipt', $scope.do_print)
         }
 
         $scope.logged_in = egCore.auth.token() ? true : false;
@@ -275,6 +289,18 @@ function($routeProvider , $locationProvider , $compileProvider) {
         if (!$scope.logged_in && $routeParams.tab == 'session')
             $scope.active_tab = 'checkout';
         
+        egCore.hatch.getItem('eg.offline.print_receipt')
+        .then(function(setting) {
+            $scope.do_print = setting;
+            if (setting !== undefined) $scope.do_print_changed = true;
+        });
+
+        egCore.hatch.getItem('eg.offline.strict_barcode')
+        .then(function(setting) {
+            $scope.strict_barcode = setting;
+            if (setting !== undefined) $scope.do_check_changed = true;
+        });
+
         egCore.hatch.getItem('eg.workstation.all')
         .then(function(all) {
             if (all && all.length) {
@@ -382,14 +408,28 @@ function($routeProvider , $locationProvider , $compileProvider) {
             });
         }
 
-        $scope.clear_pending = function () {
-            return egLovefield.destroyPendingOfflineXacts().then(function () {
-                return $scope.retrieve_pending();
+        $scope.clear_pending = function (skip_confirm) {
+            if (skip_confirm) {
+                return egLovefield.destroyPendingOfflineXacts().then(function () {
+                    return $scope.retrieve_pending();
+                });
+            }
+            return egConfirmDialog.open(
+                egCore.strings.CONFIRM_CLEAR_PENDING,
+                egCore.strings.CONFIRM_CLEAR_PENDING_BODY,
+                {}
+            ).result.then(function() {
+                return egLovefield.destroyPendingOfflineXacts().then(function () {
+                    return $scope.retrieve_pending();
+                });
             });
+
         }
 
         $scope.retrieve_pending();
         $scope.$watch('active_tab', function (n,o) {
+            if (n != o && !$scope.do_check_changed && n != 'checkout') $scope.strict_barcode = false;
+            if (n != o && !$scope.do_check_changed && n == 'checkout') $scope.strict_barcode = true;
             if (n != o && !$scope.do_print_changed && n != 'checkout') $scope.do_print = false;
             if (n != o && !$scope.do_print_changed && n == 'checkout') $scope.do_print = true;
             if (n != o && n == 'session') $scope.retrieve_pending();
@@ -418,6 +458,7 @@ function($routeProvider , $locationProvider , $compileProvider) {
 
         $scope.resetDueDate = function (xtype) {
             $scope.shared.due_date = new Date();
+            $scope.shared.due_date.setDate($scope.shared.due_date.getDate() + parseInt($scope.shared.due_date_offset));
         }
 
         $scope.notEnough = function (xtype) {
@@ -482,6 +523,15 @@ function($routeProvider , $locationProvider , $compileProvider) {
             var pbarcode = $scope[xtype].patron_barcode;
             var backdate = $scope[xtype].backdate;
 
+            if ($scope.strict_barcode && pbarcode) {
+                if (!check_barcode(pbarcode)) {
+                    $scope.bad_barcode = xtype;
+                    ngToast.warning(egCore.strings.BAD_PATRON_BARCODE);
+                    egCore.audio.play('warning.offline.bad_barcode');
+                    return;
+                }
+            }
+
             if ($scope.strict_barcode && $scope[xtype].barcode) {
                 if (!check_barcode($scope[xtype].barcode)) {
                     $scope.bad_barcode = xtype;
@@ -496,12 +546,6 @@ function($routeProvider , $locationProvider , $compileProvider) {
             var now = new Date().getTime();
             now = now / 1000;
 
-            if ($scope.shared.due_date_offset) {
-                var due = parseInt(now * 1000);
-                due += parseInt($scope.shared.due_date_offset) * 86400000;
-                $scope.shared.due_date = new Date(due);
-            }
-
             if ($scope[xtype].noncat_type) $scope[xtype].noncat = 1;
 
             if ($scope.shared.due_date && (xtype == 'checkout' || xtype == 'renew')) {
index 6a6a3b0..c487f71 100644 (file)
@@ -15,6 +15,7 @@
     "angular-mocks": "~1.5.0",
     "angular-route": "~1.5.0",
     "angular-tree-control": "~0.2.28",
+    "angular-tablesort": "^1.4.1",
     "angular-order-object-by": "rxfork/ngOrderObjectBy#npm",
     "lovefield": "*",
     "bootstrap": "~3.3.6",