webstaff: egWorkLog service and Work Log UI
authorJason Etheridge <jason@esilibrary.com>
Fri, 4 Dec 2015 16:06:56 +0000 (11:06 -0500)
committerMike Rylander <mrylander@gmail.com>
Thu, 18 Aug 2016 19:34:20 +0000 (15:34 -0400)
under Administration -> Local Administration

The original XUL feature starts here: 29d1b357eef061bb3698e4ce0506eb93b63421be

Make sure egCore from the calling interface is pulling in these org unit
settings:

ui.admin.work_log.max_entries
ui.admin.patron_log.max_entries

Signed-off-by: Jason Etheridge <jason@esilibrary.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>

12 files changed:
Open-ILS/src/templates/opac/parts/place_hold_result.tt2
Open-ILS/src/templates/staff/admin/workstation/log.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/workstation/t_log.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/base_js.tt2
Open-ILS/src/templates/staff/css/style.css.tt2
Open-ILS/web/js/ui/default/staff/admin/workstation/log.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
Open-ILS/web/js/ui/default/staff/circ/patron/holds.js
Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
Open-ILS/web/js/ui/default/staff/circ/services/circ.js
Open-ILS/web/js/ui/default/staff/services/eframe.js
Open-ILS/web/js/ui/default/staff/services/ui.js

index 87fca56..889f411 100644 (file)
                                 window.addEventListener(
                                     'load',
                                     function() {
-                                        try {
-                                            if (typeof xulG != 'undefined' && xulG.opac_hold_placed) {
-                                                xulG.opac_hold_placed([% hdata.hold_success %]);
-                                            }
-                                        } catch(E) {
-                                            alert('Error updating Work Log with hold placement: ' + E);
-                                        }
+                                        setTimeout( // we want this to run _after_ other onload handlers (such as from eframe.js)
+                                            function() {
+                                                try {
+                                                    if (typeof xulG != 'undefined' && xulG.opac_hold_placed) {
+                                                        xulG.opac_hold_placed([% hdata.hold_success %]);
+                                                    }
+                                                } catch(E) {
+                                                    alert('Error updating Work Log with hold placement: ' + E);
+                                                }
+                                            }, 0
+                                        );
                                     },
                                     false
                                 );
diff --git a/Open-ILS/src/templates/staff/admin/workstation/log.tt2 b/Open-ILS/src/templates/staff/admin/workstation/log.tt2
new file mode 100644 (file)
index 0000000..3e5cf0a
--- /dev/null
@@ -0,0 +1,17 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Work Log"); 
+  ctx.page_app = "egWorkLogApp";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/workstation/log.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/circ.css" />
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_log.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_log.tt2
new file mode 100644 (file)
index 0000000..014d74d
--- /dev/null
@@ -0,0 +1,59 @@
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    <span>[% l('Work Log') %]</span>
+  </div>
+</div>
+
+<eg-grid
+  main-label="[% l('Most Recently Logged Staff Actions') %]"
+  id-field="id"
+  features="-sort,-multisort"
+  items-provider="grid_work_log_provider"
+  grid-controls="grid_controls"
+  persist-key="admin.workstation.work_log"
+>
+
+  <eg-grid-menu-item handler="refresh_ui" 
+    label="[% l('Refresh') %]"></eg-grid-menu-item>
+
+  <eg-grid-menu-item handler="load_item" 
+    label="[% l('Retrieve Item') %]"></eg-grid-menu-item>
+
+  <eg-grid-menu-item handler="load_patron" 
+    label="[% l('Retrieve Patron') %]"></eg-grid-menu-item>
+
+  <eg-grid-field path='action' label="[% l('Code') %]" hidden></eg-grid-field>
+  <eg-grid-field path='msg' label="[% l('Message') %]"></eg-grid-field>
+  <eg-grid-field path='amount' label="[% l('Amount') %]" hidden></eg-grid-field>
+  <eg-grid-field path='user' label="[% l('Patron') %]"></eg-grid-field>
+  <eg-grid-field path='item' label="[% l('Item') %]"></eg-grid-field>
+  <eg-grid-field path='when' label="[% l('When') %]"></eg-grid-field>
+  <eg-grid-field path='actor' label="[% l('Staff') %]" hidden></eg-grid-field>
+</eg-grid>
+
+<hr/>
+
+<eg-grid
+  main-label="[% l('Most Recently Affected Patrons') %]"
+  id-field="id"
+  features="-sort,-multisort"
+  items-provider="grid_patron_log_provider"
+  grid-controls="grid_controls"
+  persist-key="admin.workstation.patron_log"
+>
+
+  <eg-grid-menu-item handler="load_item" 
+    label="[% l('Retrieve Item') %]"></eg-grid-menu-item>
+
+  <eg-grid-menu-item handler="load_patron" 
+    label="[% l('Retrieve Patron') %]"></eg-grid-menu-item>
+
+  <eg-grid-field path='action' label="[% l('Code') %]" hidden></eg-grid-field>
+  <eg-grid-field path='msg' label="[% l('Message') %]"></eg-grid-field>
+  <eg-grid-field path='amount' label="[% l('Amount') %]" hidden></eg-grid-field>
+  <eg-grid-field path='user' label="[% l('Patron') %]"></eg-grid-field>
+  <eg-grid-field path='item' label="[% l('Item') %]"></eg-grid-field>
+  <eg-grid-field path='when' label="[% l('When') %]"></eg-grid-field>
+  <eg-grid-field path='actor' label="[% l('Staff') %]" hidden></eg-grid-field>
+</eg-grid>
+
index 54e6e7f..980ec5b 100644 (file)
     s.EG_UNLOAD_PAGE_PROMPT_MSG = 
       '[% l('This page may have unsaved data.') %]';
     s.EG_DATE_INPUT_CLOSE_TEXT = '[% l('Close') %]';
+    s.EG_WORK_LOG_CHECKOUT = '[% l('Check Out') %]';
+    s.EG_WORK_LOG_RENEW = '[% l('Renew') %]';
+    s.EG_WORK_LOG_CHECKIN = '[% l('Check In') %]';
+    s.EG_WORK_LOG_EDITED_PATRON = '[% l('Edited Patron') %]';
+    s.EG_WORK_LOG_REGISTERED_PATRON = '[% l('Registered Patron') %]';
+    s.EG_WORK_LOG_CASH_PAYMENT = '[% l('Cash Payment') %]';
+    s.EG_WORK_LOG_CHECK_PAYMENT = '[% l('Check Payment') %]';
+    s.EG_WORK_LOG_CREDIT_CARD_PAYMENT = '[% l('Credit Card Payment') %]';
+    s.EG_WORK_LOG_CREDIT_PAYMENT = '[% l('Credit Payment') %]';
+    s.EG_WORK_LOG_WORK_PAYMENT = '[% l('Work Payment') %]';
+    s.EG_WORK_LOG_FORGIVE_PAYMENT = '[% l('Forgive Payment') %]';
+    s.EG_WORK_LOG_GOODS_PAYMENT = '[% l('Goods Payment') %]';
+    s.EG_WORK_LOG_REQUESTED_HOLD = '[% l('Hold Request') %]';
   }]);
 </script>
 
index d4e4ae5..fb06c0e 100644 (file)
@@ -220,6 +220,7 @@ table.list tr.selected td { /* deprecated? */
 .eg-grid-primary-label {
   font-weight: bold;
   font-size: 120%;
+  margin-right: 2em;
 }
 
 /* odd/even row styling */
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/log.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/log.js
new file mode 100644 (file)
index 0000000..b9fe1db
--- /dev/null
@@ -0,0 +1,162 @@
+angular.module('egWorkLogApp', 
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+    var resolver = {delay : 
+        ['egStartup', function(egStartup) {return egStartup.go()}]}
+
+    $routeProvider.when('/admin/workstation/log', {
+        templateUrl: './admin/workstation/t_log',
+        controller: 'WorkLogCtrl',
+        resolve : resolver
+    });
+
+    $routeProvider.otherwise({redirectTo : '/admin/workstation/log'});
+})
+
+.controller('WorkLogCtrl',
+       ['$scope','$q','$routeParams','$window','$location','$timeout','egCore','egGridDataProvider','egWorkLog',
+function($scope , $q , $routeParams , $window , $location , $timeout , egCore , egGridDataProvider , egWorkLog ) {
+
+    var work_log_entries = [];
+    var patron_log_entries = [];
+
+    var work_log_provider = egGridDataProvider.instance({});
+    var patron_log_provider = egGridDataProvider.instance({});
+    $scope.grid_work_log_provider = work_log_provider;
+    $scope.grid_patron_log_provider = patron_log_provider;
+
+    function load_item(log_entries) {
+        if (!log_entries) return;
+        if (!angular.isArray(log_entries)) log_entries = [log_entries];
+        angular.forEach(log_entries, function(log_entry) {
+            $window.open(
+                $location.path(
+                    '/cat/item/' + log_entry.item_id
+                ).absUrl(),
+                '_blank'
+            ).focus();
+        });
+    }
+
+    $scope.load_item = function(action, data, entries) {
+        load_item(entries);
+    }
+
+    function load_patron(log_entries) {
+        if (!log_entries) return;
+        if (!angular.isArray(log_entries)) log_entries = [log_entries];
+        angular.forEach(log_entries, function(log_entry) {
+            $window.open(
+                $location.path(
+                    '/circ/patron/' + log_entry.patron_id + '/checkout'
+                ).absUrl(),
+                '_blank'
+            ).focus();
+        });
+    }
+
+    $scope.load_patron = function(action, data, entries) {
+        load_patron(entries);
+    }
+
+    $scope.grid_controls = {
+        activateItem : load_patron
+    }
+
+    function refresh_page() {
+        work_log_entries = [];
+        patron_log_entries = [];
+        provider.refresh();
+    }
+
+    function fetch_hold(deferred,entry) {
+        egCore.pcrud.search('ahr',
+            { 'id' : entry.data.hold_id }, {
+                'flesh' : 2,
+                'flesh_fields' : {
+                    'ahr' : ['usr','current_copy'],
+                },
+            }
+        ).then(
+            deferred.resolve, null, 
+            function(hold) {
+                entry.patron_id = hold.usr().id();
+                entry.user = hold.usr().family_name();
+                if (hold.current_copy()) {
+                    entry.item = hold.current_copy().barcode();
+                }
+                deferred.notify(entry);
+            }
+        );
+    }
+
+    function fetch_patron(deferred,entry) {
+        egCore.pcrud.search('au',
+            { 'id' : entry.data.patron_id }, {}
+        ).then(
+            deferred.resolve, null,
+            function(usr) {
+                entry.user = usr.family_name();
+                deferred.notify(entry);
+            }
+        );
+    }
+
+    work_log_provider.get = function(offset, count) {
+        var log_entries = egWorkLog.retrieve_all();
+        console.log(log_entries);
+        var deferred = $q.defer();
+
+        $timeout( function() {
+            log_entries.work_log.forEach(
+                function(el,idx) {
+                    el.id = idx;
+                    if (el.action == 'requested_hold') {
+                        fetch_hold(deferred,el);
+                    } else if (el.action == 'registered_patron') {
+                        fetch_patron(deferred,el);
+                    } else if (el.action == 'edited_patron') {
+                        fetch_patron(deferred,el);
+                    } else if (el.action == 'paid_bill') {
+                        fetch_patron(deferred,el);
+                    } else {
+                        deferred.notify(el);
+                    }
+                }
+            );
+        });
+        return deferred.promise;
+    }
+
+    patron_log_provider.get = function(offset, count) {
+        var log_entries = egWorkLog.retrieve_all();
+        console.log(log_entries);
+        var deferred = $q.defer();
+
+        $timeout( function() {
+            log_entries.patron_log.forEach(
+                function(el,idx) {
+                    el.id = idx;
+                    if (el.action == 'requested_hold') {
+                        fetch_hold(deferred,el);
+                    } else if (el.action == 'registered_patron') {
+                        fetch_patron(deferred,el);
+                    } else if (el.action == 'edited_patron') {
+                        fetch_patron(deferred,el);
+                    } else if (el.action == 'paid_bill') {
+                        fetch_patron(deferred,el);
+                    } else {
+                        deferred.notify(el);
+                    }
+                }
+            );
+        });
+        return deferred.promise;
+    }
+
+}])
+
index c9b6c44..1245a2b 100644 (file)
@@ -4,8 +4,8 @@
 angular.module('egPatronApp')
 
 .factory('billSvc', 
-       ['$q','egCore','patronSvc',
-function($q , egCore , patronSvc) {
+       ['$q','egCore','egWorkLog','patronSvc',
+function($q , egCore , egWorkLog , patronSvc) {
 
     var service = {};
 
@@ -39,6 +39,24 @@ function($q , egCore , patronSvc) {
             patronSvc.current.last_xact_id()
         ).then(function(resp) {
             console.debug('payments: ' + js2JSON(resp));
+            var total = 0; angular.forEach(payments,function(p) { total += p[1]; });
+            var msg;
+            switch(type) {
+                case 'cash_payment' : msg = egCore.strings.EG_WORK_LOG_CASH_PAYMENT; break;
+                case 'check_payment' : msg = egCore.strings.EG_WORK_LOG_CHECK_PAYMENT; break;
+                case 'credit_card_payment' : msg = egCore.strings.EG_WORK_LOG_CREDIT_CARD_PAYMENT; break;
+                case 'credit_payment' : msg = egCore.strings.EG_WORK_LOG_CREDIT_PAYMENT; break;
+                case 'work_payment' : msg = egCore.strings.EG_WORK_LOG_WORK_PAYMENT; break;
+                case 'forgive_payment' : msg = egCore.strings.EG_WORK_LOG_FORGIVE_PAYMENT; break;
+                case 'goods_payment' : msg = egCore.strings.EG_WORK_LOG_GOODS_PAYMENT; break;
+            }
+            egWorkLog.record(
+                msg,{
+                    'action' : 'paid_bill',
+                    'patron_id' : service.userId,
+                    'total_amount' : total
+                }
+            );
             if (evt = egCore.evt.parse(resp)) 
                 return alert(evt);
 
index 9c2101f..ddbf1d8 100644 (file)
@@ -137,13 +137,19 @@ function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,
 
 
 .controller('PatronHoldsCreateCtrl',
-       ['$scope','$routeParams','$location','egCore','patronSvc',
-function($scope , $routeParams , $location , egCore , patronSvc) {
+       ['$scope','$routeParams','$location','egCore','egWorkLog','patronSvc',
+function($scope , $routeParams , $location , egCore , egWorkLog , patronSvc) {
 
     $scope.handlers = {
-        opac_hold_placed : function() {
-            // FIXME: this isn't getting called.. not sure why
+        opac_hold_placed : function(hold) {
             patronSvc.fetchUserStats(); // update hold counts
+            egWorkLog.record(
+                egCore.strings.EG_WORK_LOG_REQUESTED_HOLD,{
+                    'action' : 'requested_hold',
+                    'patron_id' : patronSvc.current.id(),
+                    'hold_id' : hold
+                }
+            );
         }
     }
 
index 0e6629e..d08d699 100644 (file)
@@ -348,9 +348,15 @@ angular.module('egCoreMod')
             'sms.enable',
             'ui.patron.edit.aua.state.require',
             'ui.patron.edit.aua.state.suggest',
-            'ui.patron.edit.aua.state.show'
+            'ui.patron.edit.aua.state.show',
+            'ui.admin.work_log.max_entries',
+            'ui.admin.patron_log.max_entries'
         ]).then(function(settings) {
             service.org_settings = settings;
+            if (egCore && egCore.env && !egCore.env.aous) {
+                egCore.env.aous = settings;
+                console.log('setting egCore.env.aous');
+            }
             return service.process_org_settings(settings);
         });
     };
@@ -1078,11 +1084,13 @@ angular.module('egCoreMod')
     return service;
 }])
 
-.controller('PatronRegCtrl', 
+.controller('PatronRegCtrl',
        ['$scope','$routeParams','$q','$uibModal','$window','egCore',
         'patronSvc','patronRegSvc','egUnloadPrompt','egAlertDialog',
-function($scope , $routeParams , $q , $uibModal , $window , egCore , 
-         patronSvc , patronRegSvc , egUnloadPrompt, egAlertDialog) {
+        'egWorkLog',
+function($scope , $routeParams , $q , $uibModal , $window , egCore ,
+         patronSvc , patronRegSvc , egUnloadPrompt, egAlertDialog ,
+         egWorkLog) {
 
     $scope.page_data_loaded = false;
     $scope.clone_id = patronRegSvc.clone_id = $routeParams.clone_id;
@@ -1801,6 +1809,17 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
 
         }).then(function() {
 
+            if (updated_user) {
+                egWorkLog.record(
+                    $scope.patron.isnew
+                    ? egCore.strings.EG_WORK_LOG_REGISTERED_PATRON
+                    : egCore.strings.EG_WORK_LOG_EDITED_PATRON, {
+                        'action' : $scope.patron.isnew ? 'registered_patron' : 'edited_patron',
+                        'patron_id' : updated_user.id()
+                    }
+                );
+            }
+
             // reloading the page means potentially losing some information
             // (e.g. last patron search), but is the only way to ensure all
             // components are properly updated to reflect the modified patron.
@@ -1821,4 +1840,3 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         });
     }
 }])
-
index e74cc78..66d5335 100644 (file)
@@ -5,9 +5,10 @@
 angular.module('egCoreMod')
 
 .factory('egCirc',
-
        ['$uibModal','$q','egCore','egAlertDialog','egConfirmDialog',
-function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog) {
+        'egWorkLog',
+function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog,
+         egWorkLog) {
 
     var service = {
         // auto-override these events after the first override
@@ -17,7 +18,9 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog) {
 
     egCore.startup.go().finally(function() {
         egCore.org.settings([
-            'ui.staff.require_initials.patron_standing_penalty'
+            'ui.staff.require_initials.patron_standing_penalty',
+            'ui.admin.work_log.max_entries',
+            'ui.admin.patron_log.max_entries'
         ]).then(function(set) {
             service.require_initials = Boolean(set['ui.staff.require_initials.patron_standing_penalty']);
         });
@@ -131,7 +134,7 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog) {
                     return service.handle_checkout_resp(evt, params, options);
                 })
                 .then(function(final_resp) {
-                    return service.munge_resp_data(final_resp)
+                    return service.munge_resp_data(final_resp,'checkout',method)
                 })
             });
         });
@@ -171,7 +174,7 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog) {
                     return service.handle_renew_resp(evt, params, options);
                 })
                 .then(function(final_resp) {
-                    return service.munge_resp_data(final_resp)
+                    return service.munge_resp_data(final_resp,'renew',method)
                 })
             });
         });
@@ -210,14 +213,14 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog) {
                     return service.handle_checkin_resp(evt, params, options);
                 })
                 .then(function(final_resp) {
-                    return service.munge_resp_data(final_resp)
+                    return service.munge_resp_data(final_resp,'checkin',method)
                 })
             });
         });
     }
 
     // provide consistent formatting of the final response data
-    service.munge_resp_data = function(final_resp) {
+    service.munge_resp_data = function(final_resp,worklog_action,worklog_method) {
         var data = final_resp.data = {};
 
         if (!final_resp.evt[0]) return;
@@ -256,6 +259,19 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog) {
             }
         }
 
+        egWorkLog.record(
+            worklog_action == 'checkout'
+            ? egCore.strings.EG_WORK_LOG_CHECKOUT
+            : (worklog_action == 'renew'
+                ? egCore.strings.EG_WORK_LOG_RENEW
+                : egCore.strings.EG_WORK_LOG_CHECKIN // worklog_action == 'checkin'
+            ),{
+                'action' : worklog_action,
+                'method' : worklog_method,
+                'response' : final_resp
+            }
+        );
+
         return final_resp;
     }
 
index ac54bd5..aa87c7b 100644 (file)
@@ -244,6 +244,7 @@ angular.module('egCoreMod')
                 if ($scope.handlers) {
                     $scope.handlers.reload = $scope.reload;
                     angular.forEach($scope.handlers, function(val, key) {
+                        console.log('eframe applying xulG handlers: ' + key);
                         $scope.iframe.contentWindow.xulG[key] = val;
                     });
                 }
index e5eb40f..b9b3a8e 100644 (file)
@@ -484,3 +484,119 @@ function($window , egStrings) {
         };
     }
 ])
+
+.factory('egWorkLog', ['egCore', function(egCore) {
+    var service = {};
+
+    service.retrieve_all = function() {
+        var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
+        var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
+
+        return { 'work_log' : workLog, 'patron_log' : patronLog };
+    }
+
+    service.record = function(message,data) {
+        var max_entries;
+        var max_patrons;
+        if (typeof egCore != 'undefined') {
+            if (typeof egCore.env != 'undefined') {
+                if (typeof egCore.env.aous != 'undefined') {
+                    max_entries = egCore.env.aous['ui.admin.work_log.max_entries'];
+                    max_patrons = egCore.env.aous['ui.admin.patron_log.max_entries'];
+                } else {
+                    console.log('worklog: missing egCore.env.aous');
+                }
+            } else {
+                console.log('worklog: missing egCore.env');
+            }
+        } else {
+            console.log('worklog: missing egCore');
+        }
+        if (!max_entries) {
+            if (typeof egCore.org != 'undefined') {
+                if (typeof egCore.org.cachedSettings != 'undefined') {
+                    max_entries = egCore.org.cachedSettings['ui.admin.work_log.max_entries'];
+                } else {
+                    console.log('worklog: missing egCore.org.cachedSettings');
+                }
+            } else {
+                console.log('worklog: missing egCore.org');
+            }
+        }
+        if (!max_patrons) {
+            if (typeof egCore.org != 'undefined') {
+                if (typeof egCore.org.cachedSettings != 'undefined') {
+                    max_patrons = egCore.org.cachedSettings['ui.admin.patron_log.max_entries'];
+                } else {
+                    console.log('worklog: missing egCore.org.cachedSettings');
+                }
+            } else {
+                console.log('worklog: missing egCore.org');
+            }
+        }
+        if (!max_entries) {
+            max_entries = 20;
+            console.log('worklog: defaulting to max_entries = ' + max_entries);
+        }
+        if (!max_patrons) {
+            max_patrons = 10;
+            console.log('worklog: defaulting to max_patrons = ' + max_patrons);
+        }
+
+        var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
+        var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
+        var entry = {
+            'when' : new Date(),
+            'msg' : message,
+            'data' : data,
+            'action' : data.action,
+            'actor' : egCore.auth.user().usrname()
+        };
+        if (data.action == 'checkin') {
+            entry['item'] = data.response.params.copy_barcode;
+            entry['user'] = data.response.data.au.family_name();
+            entry['item_id'] = data.response.data.acp.id();
+            entry['patron_id'] = data.response.data.au.id();
+        }
+        if (data.action == 'checkout') {
+            entry['item'] = data.response.params.copy_barcode;
+            entry['user'] = data.response.data.au.family_name();
+            entry['item_id'] = data.response.data.acp.id();
+            entry['patron_id'] = data.response.data.au.id();
+        }
+        if (data.action == 'renew') {
+            entry['item'] = data.response.params.copy_barcode;
+            entry['user'] = data.response.data.au.family_name();
+            entry['item_id'] = data.response.data.acp.id();
+            entry['patron_id'] = data.response.data.au.id();
+        }
+        if (data.action == 'requested_hold'
+            || data.action == 'edited_patron'
+            || data.action == 'registered_patron'
+            || data.action == 'paid_bill') {
+            entry['patron_id'] = data.patron_id;
+        }
+        if (data.action == 'paid_bill') {
+            entry['amount'] = data.total_amount;
+        }
+
+        workLog.push( entry );
+        if (workLog.length > max_entries) workLog.shift();
+        egCore.hatch.setLocalItem('eg.work_log',workLog); // hatch JSONifies the data, so should be okay re: memory leaks?
+
+        if (entry['patron_id']) {
+            var temp = [];
+            for (var i = 0; i < patronLog.length; i++) { // filter out any matching patron
+                if (patronLog[i]['patron_id'] != entry['patron_id']) temp.push(patronLog[i]);
+            }
+            temp.push( entry );
+            if (temp.length > max_patrons) temp.shift();
+            patronLog = temp;
+            egCore.hatch.setLocalItem('eg.patron_log',patronLog);
+        }
+
+        console.log('worklog',entry);
+    }
+
+    return service;
+}]);