LP#1842940: Don't allow self-edit or perm-restricted edit
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / patron / regctl.js
index c190f4c..1d34476 100644 (file)
@@ -2,12 +2,14 @@
 angular.module('egCoreMod')
 // toss tihs onto egCoreMod since the page app may vary
 
-.factory('patronRegSvc', ['$q', 'egCore', 'egLovefield', function($q, egCore, egLovefield) {
+.factory('patronRegSvc', ['$q', '$filter', 'egCore', 'egLovefield', function($q, $filter, egCore, egLovefield) {
 
     var service = {
         field_doc : {},            // config.idl_field_doc
         profiles : [],             // permission groups
+        profile_entries : [],      // permission gorup display entries
         edit_profiles : [],        // perm groups we can modify
+        edit_profile_entries : [], // perm group display entries we can modify
         sms_carriers : [],
         user_settings : {},        // applied user settings
         user_setting_types : {},   // config.usr_setting_type
@@ -22,29 +24,33 @@ angular.module('egCoreMod')
         init_done : false           // have we loaded our initialization data?
     };
 
-    // launch a series of parallel data retrieval calls
+    // Launch a series of parallel data retrieval calls.
     service.init = function(scope) {
 
-        // Data loaded here only needs to be retrieved the first time this
-        // tab becomes active within the current instance of the patron app.
-        // In other words, navigating between patron tabs will not cause
-        // all of this data to be reloaded.  Navigating to a separate app
-        // and returning will cause the data to be reloaded.
-        if (service.init_done) return $q.when();
-        service.init_done = true;
-
-        return $q.all([
-            service.get_field_doc(),
-            service.get_perm_groups(),
-            service.get_ident_types(),
+        // These are fetched with every instance of the page.
+        var page_data = [
             service.get_user_settings(),
-            service.get_org_settings(),
-            service.get_stat_cats(),
-            service.get_surveys(),
             service.get_clone_user(),
-            service.get_stage_user(),
-            service.get_net_access_levels()
-        ]);
+            service.get_stage_user()
+        ];
+
+        var common_data = [];
+        if (!service.init_done) {
+            // These are fetched with every instance of the app.
+            common_data = [
+                service.get_field_doc(),
+                service.get_perm_groups(),
+                service.get_perm_group_entries(),
+                service.get_ident_types(),
+                service.get_org_settings(),
+                service.get_stat_cats(),
+                service.get_surveys(),
+                service.get_net_access_levels()
+            ];
+            service.init_done = true;
+        }
+
+        return $q.all(common_data.concat(page_data));
     };
 
     service.get_clone_user = function() {
@@ -197,6 +203,44 @@ angular.module('egCoreMod')
         );
     }
 
+    service.set_edit_profile_entries = function() {
+        var all_app_perms = [];
+        var failed_perms = [];
+
+        // extract the application permissions
+        angular.forEach(service.profile_entries, function(entry) {
+            if (entry.grp().application_perm())
+                all_app_perms.push(entry.grp().application_perm());
+        });
+
+        // fill in service.edit_profiles by inspecting failed_perms
+        function traverse_grp_tree(entry, failed) {
+            failed = failed ||
+                failed_perms.indexOf(entry.grp().application_perm()) > -1;
+
+            if (!failed) service.edit_profile_entries.push(entry);
+
+            angular.forEach(
+                service.profile_entries.filter( // children of grp
+                    function(p) { return p.parent() == entry.id() }),
+                function(child) {traverse_grp_tree(child, failed)}
+            );
+        }
+
+        return egCore.perm.hasPermAt(all_app_perms, true).then(
+            function(perm_orgs) {
+                angular.forEach(all_app_perms, function(p) {
+                    if (perm_orgs[p].length == 0)
+                        failed_perms.push(p);
+                });
+
+                angular.forEach(egCore.env.pgtde.tree, function(tree) {
+                    traverse_grp_tree(tree);
+                });
+            }
+        );
+    }
+
     // resolves to a hash of perm-name => boolean value indicating
     // wether the user has the permission at org_id.
     service.has_perms_for_org = function(org_id) {
@@ -290,6 +334,7 @@ angular.module('egCoreMod')
             'ui.patron.registration.require_address',
             'circ.holds.behind_desk_pickup_supported',
             'circ.patron_edit.clone.copy_address',
+            'circ.privacy_waiver',
             'ui.patron.edit.au.prefix.require',
             'ui.patron.edit.au.prefix.show',
             'ui.patron.edit.au.prefix.suggest',
@@ -304,9 +349,11 @@ angular.module('egCoreMod')
             'ui.patron.edit.au.dob.show',
             'ui.patron.edit.au.dob.suggest',
             'ui.patron.edit.au.dob.calendar',
+            'ui.patron.edit.au.dob.example',
             'ui.patron.edit.au.juvenile.show',
             'ui.patron.edit.au.juvenile.suggest',
             'ui.patron.edit.au.ident_value.show',
+            'ui.patron.edit.au.ident_value.require',
             'ui.patron.edit.au.ident_value.suggest',
             'ui.patron.edit.au.ident_value2.show',
             'ui.patron.edit.au.ident_value2.suggest',
@@ -347,6 +394,9 @@ angular.module('egCoreMod')
             'ui.patron.edit.aua.post_code.regex',
             'ui.patron.edit.aua.post_code.example',
             'ui.patron.edit.aua.county.require',
+            'ui.patron.edit.au.guardian.show',
+            'ui.patron.edit.au.guardian.suggest',
+            'ui.patron.edit.guardian_required_for_juv',
             'format.date',
             'ui.patron.edit.default_suggested',
             'opac.barcode_regex',
@@ -439,6 +489,48 @@ angular.module('egCoreMod')
         }
     }
 
+    service.searchPermGroupEntries = function(org) {
+        return egCore.pcrud.search('pgtde', {org: org, parent: null},
+            {flesh: -1, flesh_fields: {pgtde: ['grp', 'children']}}, {atomic: true}
+        ).then(function(treeArray) {
+            if (!treeArray.length && egCore.org.get(org).parent_ou()) {
+                return service.searchPermGroupEntries(egCore.org.get(org).parent_ou());
+            }
+            return treeArray;
+        });
+    }
+
+    service.get_perm_group_entries = function() {
+        if (egCore.env.pgtde) {
+            service.profile_entries = egCore.env.pgtde.list;
+            return service.set_edit_profile_entries();
+        } else {
+            return service.searchPermGroupEntries(egCore.auth.user().ws_ou()).then(function(treeArray) {
+                function compare(a,b) {
+                  if (a.position() > b.position())
+                    return -1;
+                  if (a.position() < b.position())
+                    return 1;
+                  return 0;
+                }
+
+                var list = [];
+                function squash(node) {
+                    node.children().sort(compare);
+                    list.push(node);
+                    angular.forEach(node.children(), squash);
+                }
+
+                angular.forEach(treeArray, squash);
+                var blob = egCore.env.absorbList(list, 'pgtde');
+                blob.tree = treeArray;
+
+                service.profile_entries = egCore.env.pgtde.list;
+                return service.set_edit_profile_entries();
+            });
+        }
+    }
+
     service.get_field_doc = function() {
         var to_cache = [];
         return egCore.pcrud.search('fdoc', {
@@ -459,7 +551,12 @@ angular.module('egCoreMod')
 
     };
 
-    service.get_user_settings = function() {
+    service.get_user_setting_types = function() {
+
+        // No need to re-fetch the common setting types.
+        if (Object.keys(service.user_setting_types).length) 
+            return $q.when();
+
         var org_ids = egCore.org.ancestors(egCore.auth.user().ws_ou(), true);
 
         var static_types = [
@@ -494,6 +591,15 @@ angular.module('egCoreMod')
                     service.opt_in_setting_types[stype.name()] = stype;
                 }
             });
+        });
+    };
+
+    service.get_user_settings = function() {
+
+        return service.get_user_setting_types()
+        .then(function() {
+
+            var setting_types = Object.values(service.user_setting_types);
 
             if (service.patron_id) {
                 // retrieve applied values for the current user 
@@ -511,6 +617,7 @@ angular.module('egCoreMod')
                 ).then(function(settings) {
                     service.user_settings = settings;
                 });
+
             } else {
 
                 // apply default user setting values
@@ -553,6 +660,8 @@ angular.module('egCoreMod')
 
         console.log('Dupe search called with "'+ type +'" and value '+ value);
 
+        if (type.match(/phone/)) type = 'phone'; // day_phone, etc.
+
         switch (type) {
 
             case 'name':
@@ -617,6 +726,14 @@ angular.module('egCoreMod')
             addr.id == patron.mailing_address.id);
         addr._is_billing = (patron.billing_address && 
             addr.id == patron.billing_address.id);
+        addr.pending = addr.pending === 't';
+    }
+
+    service.ingest_waiver_entry = function(patron, waiver_entry) {
+        waiver_entry.place_holds = waiver_entry.place_holds == 't';
+        waiver_entry.pickup_holds = waiver_entry.pickup_holds == 't';
+        waiver_entry.view_history = waiver_entry.view_history == 't';
+        waiver_entry.checkout_items = waiver_entry.checkout_items == 't';
     }
 
     /*
@@ -639,6 +756,7 @@ angular.module('egCoreMod')
         patron.profile = current.profile(); // pre-hash version
         patron.net_access_level = current.net_access_level();
         patron.ident_type = current.ident_type();
+        patron.ident_type2 = current.ident_type2();
         patron.groups = current.groups(); // pre-hash
 
         angular.forEach(
@@ -650,13 +768,24 @@ angular.module('egCoreMod')
             card.active = card.active == 't';
             if (card.id == patron.card.id) {
                 patron.card = card;
-                card._primary = 'on';
+                card._primary = true;
             }
         });
 
         angular.forEach(patron.addresses, 
             function(addr) { service.ingest_address(patron, addr) });
 
+        // Link replaced address to its pending address.
+        angular.forEach(patron.addresses, function(addr) {
+            if (addr.replaces) {
+                addr._replaces = patron.addresses.filter(
+                    function(a) {return a.id == addr.replaces})[0];
+            }
+        });
+
+        angular.forEach(patron.waiver_entries,
+            function(waiver_entry) { service.ingest_waiver_entry(patron, waiver_entry) });
+
         service.get_linked_addr_users(patron.addresses);
 
         // Remove stat cat entries that link to out-of-scope stat
@@ -698,7 +827,7 @@ angular.module('egCoreMod')
             id : service.virt_id--,
             isnew : true,
             active : true,
-            _primary : 'on'
+            _primary : true
         };
 
         var user = {
@@ -708,6 +837,7 @@ angular.module('egCoreMod')
             cards : [card],
             home_ou : egCore.org.get(egCore.auth.user().ws_ou()),
             stat_cat_entries : [],
+            waiver_entries : [],
             groups : [],
             addresses : [addr]
         };
@@ -731,11 +861,7 @@ angular.module('egCoreMod')
     service.parse_dob = function(dob) {
         if (!dob) return null;
         var parts = dob.split('-');
-        var d = new Date(); // always local time zone, yay.
-        d.setFullYear(parts[0]);
-        d.setMonth(parts[1] - 1);
-        d.setDate(parts[2]);
-        return d;
+        return new Date(parts[0], parts[1] - 1, parts[2])
     }
 
     service.copy_stage_data = function(user) {
@@ -755,6 +881,8 @@ angular.module('egCoreMod')
         if (user.profile) user.profile = egCore.env.pgt.map[user.profile];
         if (user.ident_type) 
             user.ident_type = egCore.env.cit.map[user.ident_type];
+        if (user.ident_type2)
+            user.ident_type2 = egCore.env.cit.map[user.ident_type2];
         user.dob = service.parse_dob(user.dob);
 
         // Clear the usrname if it looks like a UUID
@@ -775,6 +903,7 @@ angular.module('egCoreMod')
                 usr : user.id,
                 isnew : true,
                 valid : true,
+                address_type : egCore.strings.REG_ADDR_TYPE,
                 _is_mailing : cls == 'stgma',
                 _is_billing : cls == 'stgba'
             };
@@ -808,7 +937,7 @@ angular.module('egCoreMod')
                 barcode : cuser.cards[0].barcode(),
                 isnew : true,
                 active : true,
-                _primary : 'on'
+                _primary : true
             };
 
             user.cards.push(user.card);
@@ -969,6 +1098,7 @@ angular.module('egCoreMod')
             patron.addresses().push(addr);
             addr.valid(addr.valid() ? 't' : 'f');
             addr.within_city_limits(addr.within_city_limits() ? 't' : 'f');
+            addr.pending(addr.pending() ? 't' : 'f');
             if (addr_hash._is_mailing) patron.mailing_address(addr);
             if (addr_hash._is_billing) patron.billing_address(addr);
         });
@@ -1022,6 +1152,15 @@ angular.module('egCoreMod')
             patron.stat_cat_entries().push(newmap);
         });
 
+        var waiver_hashes = patron.waiver_entries();
+        patron.waiver_entries([]);
+        angular.forEach(waiver_hashes, function(waiver_hash) {
+            if (!waiver_hash.isnew && !waiver_hash.isdeleted)
+                waiver_hash.ischanged = true;
+            var waiver_entry = egCore.idl.fromHash('aupw', waiver_hash);
+            patron.waiver_entries().push(waiver_entry);
+        });
+
         if (!patron.isnew()) patron.ischanged(true);
 
         return egCore.net.request(
@@ -1041,19 +1180,15 @@ angular.module('egCoreMod')
     }
 
     service.save_user_settings = function(new_user, user_settings) {
-        // user_settings contains the values from the scope/form.
-        // service.user_settings contain the values from page load time.
 
         var settings = {};
         if (service.patron_id) {
-            // only update modified settings for existing patrons
-            angular.forEach(user_settings, function(val, key) {
-                if (val !== service.user_settings[key])
-                    settings[key] = val;
-            });
+            // Update all user editor setting values for existing 
+            // users regardless of whether a value changed.
+            settings = user_settings;
 
         } else {
-            // all non-null setting values are updated for new patrons
+            // Create settings for all non-null setting values for new patrons.
             angular.forEach(user_settings, function(val, key) {
                 if (val !== null) settings[key] = val;
             });
@@ -1115,10 +1250,10 @@ angular.module('egCoreMod')
 .controller('PatronRegCtrl',
        ['$scope','$routeParams','$q','$uibModal','$window','egCore',
         'patronSvc','patronRegSvc','egUnloadPrompt','egAlertDialog',
-        'egWorkLog',
+        'egWorkLog', '$timeout',
 function($scope , $routeParams , $q , $uibModal , $window , egCore ,
          patronSvc , patronRegSvc , egUnloadPrompt, egAlertDialog ,
-         egWorkLog) {
+         egWorkLog, $timeout) {
 
     $scope.page_data_loaded = false;
     $scope.hold_notify_type = { phone : null, email : null, sms : null };
@@ -1138,6 +1273,8 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
     // has at the currently selected patron home org unit.
     $scope.perms = {};
 
+    $scope.name_tab = 'primary';
+
     if (!$scope.edit_passthru) {
         // in edit more, scope.edit_passthru is delivered to us by
         // the enclosing controller.  In register mode, there is 
@@ -1212,6 +1349,7 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         $scope.patron = patron;
         $scope.field_doc = prs.field_doc;
         $scope.edit_profiles = prs.edit_profiles;
+        $scope.edit_profile_entries = prs.edit_profile_entries;
         $scope.ident_types = prs.ident_types;
         $scope.net_access_levels = prs.net_access_levels;
         $scope.user_setting_types = prs.user_setting_types;
@@ -1252,8 +1390,39 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
 
         prs.set_field_patterns(field_patterns);
         apply_username_regex();
+
+        add_date_watchers();
+
+        if ($scope.org_settings['ui.patron.edit.guardian_required_for_juv']) {
+            add_juv_watcher();
+        }
     });
 
+    function add_date_watchers() {
+
+        $scope.$watch('patron.dob', function(newVal, oldVal) {
+            // Even though this runs after page data load, there
+            // are still times when it fires unnecessarily.
+            if (newVal === oldVal) return;
+
+            console.debug('dob change: ' + newVal + ' : ' + oldVal);
+            maintain_juvenile_flag();
+        });
+
+        // No need to watch expire_date
+    }
+
+    function add_juv_watcher() {
+        $scope.$watch('patron.juvenile', function(newVal, oldVal) {
+            if (newVal === oldVal) return;
+            if (newVal) {
+                field_visibility['au.guardian'] = 3; // required
+            } else {
+                // Value will be reassessed by show_field()
+                delete field_visibility['au.guardian'];
+            }
+        });
+    }
 
     // update the currently displayed field documentation
     $scope.set_selected_field_doc = function(cls, field) {
@@ -1267,6 +1436,13 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         return d;
     }
 
+    // returns the tree depth of the selected profile group tree node.
+    $scope.pgtde_depth = function(entry) {
+        var d = 0;
+        while (entry = egCore.env.pgtde.map[entry.parent()]) d++;
+        return d;
+    }
+
     // IDL fields used for labels in the UI.
     $scope.idl_fields = {
         au  : egCore.idl.classes.au.field_map,
@@ -1285,7 +1461,10 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         'au.passwd' :  3,
         'au.first_given_name' : 3,
         'au.family_name' : 3,
+        'au.pref_first_given_name' : 2,
+        'au.pref_family_name' : 2,
         'au.ident_type' : 3,
+        'au.ident_type2' : 2,
         'au.home_ou' : 3,
         'au.profile' : 3,
         'au.expire_date' : 3,
@@ -1301,7 +1480,8 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         'aua.valid' : 2,
         'aua.within_city_limits' : 2,
         'stat_cats' : 1,
-        'surveys' : 1
+        'surveys' : 1,
+        'au.name_keywords': 1
     }; 
 
     // Returns true if the selected field should be visible
@@ -1315,12 +1495,26 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         if (field_visibility[field_key] == undefined) {
             // compile and cache the visibility for the selected field
 
-            var req_set = 'ui.patron.edit.' + field_key + '.require';
-            var sho_set = 'ui.patron.edit.' + field_key + '.show';
-            var sug_set = 'ui.patron.edit.' + field_key + '.suggest';
+            // The preferred name fields use the primary name field settings
+            var org_key = field_key;
+            var alt_name = false;
+            if (field_key.match(/^au.alt_/)) {
+                alt_name = true;
+                org_key = field_key.slice(7);
+            }
+
+            var req_set = 'ui.patron.edit.' + org_key + '.require';
+            var sho_set = 'ui.patron.edit.' + org_key + '.show';
+            var sug_set = 'ui.patron.edit.' + org_key + '.suggest';
 
             if ($scope.org_settings[req_set]) {
-                field_visibility[field_key] = 3;
+                if (alt_name) {
+                    // Avoid requiring alt name fields when primary 
+                    // name fields are required.
+                    field_visibility[field_key] = 2;
+                } else {
+                    field_visibility[field_key] = 3;
+                }
 
             } else if ($scope.org_settings[sho_set]) {
                 field_visibility[field_key] = 2;
@@ -1365,10 +1559,13 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         var now_epoch = new Date().getTime();
         $scope.patron.expire_date = new Date(
             now_epoch + (seconds * 1000 /* milliseconds */))
+        $scope.field_modified();
     }
 
     // grp is the pgt object
     $scope.set_profile = function(grp) {
+        // If we can't save because of group perms or create/update perms
+        if ($scope.edit_passthru.hide_save_actions()) return;
         $scope.patron.profile = grp;
         $scope.set_expire_date();
         $scope.field_modified();
@@ -1420,6 +1617,37 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         $scope.patron.addresses = addresses;
     } 
 
+    $scope.approve_pending_address = function(addr) {
+
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.user.pending_address.approve',
+            egCore.auth.token(), addr.id
+        ).then(function(replaced_id) {
+            var evt = egCore.evt.parse(replaced_id);
+            if (evt) { alert(evt); return; }
+
+            // Remove the pending address and the replaced address
+            // from the local list of patron addresses.
+            var addresses = [];
+            angular.forEach($scope.patron.addresses, function(a) {
+                if (a.id != addr.id && a.id != replaced_id) {
+                    addresses.push(a);
+                }
+            });
+            $scope.patron.addresses = addresses;
+
+            // Fetch a fresh copy of the modified address from the server.
+            // and add it back to the list.
+            egCore.pcrud.retrieve('aua', replaced_id, {}, {authoritative: true})
+            .then(null, null, function(new_addr) {
+                new_addr = egCore.idl.toHash(new_addr);
+                patronRegSvc.ingest_address($scope.patron, new_addr);
+                $scope.patron.addresses.push(new_addr);
+            });
+        });
+    }
+
     $scope.post_code_changed = function(addr) { 
         egCore.net.request(
             'open-ils.search', 'open-ils.search.zip', addr.post_code)
@@ -1432,6 +1660,24 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         });
     }
 
+    $scope.new_waiver_entry = function() {
+        var waiver = egCore.idl.toHash(new egCore.idl.aupw());
+        patronRegSvc.ingest_waiver_entry($scope.patron, waiver);
+        waiver.id = patronRegSvc.virt_id--;
+        waiver.isnew = true;
+        $scope.patron.waiver_entries.push(waiver);
+    }
+
+    deleted_waiver_entries = [];
+    $scope.delete_waiver_entry = function(waiver_entry) {
+        if (waiver_entry.id > 0) {
+            waiver_entry.isdeleted = true;
+            deleted_waiver_entries.push(waiver_entry);
+        }
+        var index = $scope.patron.waiver_entries.indexOf(waiver_entry);
+        $scope.patron.waiver_entries.splice(index, 1);
+    }
+
     $scope.replace_card = function() {
         $scope.patron.card.active = false;
         $scope.patron.card.ischanged = true;
@@ -1443,6 +1689,11 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         new_card.active = true;
         new_card._primary = 'on';
         $scope.patron.card = new_card;
+
+        // Remove any previous attempts to replace the card, since they
+        // may be incomplete or created by accident.
+        $scope.patron.cards =
+            $scope.patron.cards.filter(function(c) {return !c.isnew})
         $scope.patron.cards.push(new_card);
     }
 
@@ -1477,10 +1728,15 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
             templateUrl: './circ/patron/t_patron_cards_dialog',
             backdrop: 'static',
             controller: 
-                   ['$scope','$uibModalInstance','cards','perms',
-            function($scope , $uibModalInstance , cards , perms) {
+                   ['$scope','$uibModalInstance','cards','perms','patron',
+            function($scope , $uibModalInstance , cards , perms , patron) {
                 // scope here is the modal-level scope
-                $scope.args = {cards : cards};
+                $scope.args = {cards : cards, primary_barcode : null};
+                angular.forEach(cards, function(card) {
+                    if (card.id == patron.card.id) {
+                        $scope.args.primary_barcode = card.id;
+                    }
+                });
                 $scope.perms = perms;
                 $scope.ok = function() { $uibModalInstance.close($scope.args) }
                 $scope.cancel = function () { $uibModalInstance.dismiss() }
@@ -1492,15 +1748,20 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
                 },
                 perms : function() {
                     return $scope.perms;
+                },
+                patron : function() {
+                    return $scope.patron;
                 }
             }
         }).result.then(
             function(args) {
                 angular.forEach(args.cards, function(card) {
                     card.ischanged = true; // assume cards need updating, OK?
-                    if (card._primary == 'on' && 
-                        card.id != $scope.patron.card.id) {
+                    if (card.id == args.primary_barcode) {
                         $scope.patron.card = card;
+                        card._primary = true;
+                    } else {
+                        card._primary = false;
                     }
                 });
             }
@@ -1642,15 +1903,19 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
     }
 
     $scope.dupe_value_changed = function(type, value) {
+        if (!$scope.dupe_search_encoded)
+            $scope.dupe_search_encoded = {};
+
         $scope.dupe_counts[type] = 0;
+
         patronRegSvc.dupe_patron_search($scope.patron, type, value)
         .then(function(res) {
             $scope.dupe_counts[type] = res.count;
             if (res.count) {
-                $scope.dupe_search_encoded = 
+                $scope.dupe_search_encoded[type] = 
                     encodeURIComponent(js2JSON(res.search));
             } else {
-                $scope.dupe_search_encoded = '';
+                $scope.dupe_search_encoded[type] = '';
             }
         });
     }
@@ -1714,18 +1979,18 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         var cls = obj.classname; // set by egIdl
         var value = obj[field_name];
 
-        console.log('changing field ' + field_name + ' to ' + value);
+        console.debug('changing field ' + field_name + ' to ' + value);
 
         switch (field_name) {
             case 'day_phone' : 
                 if ($scope.patron.day_phone && 
                     $scope.patron.isnew && 
                     $scope.org_settings['patron.password.use_phone']) {
-                    $scope.patron.passwd = phone.substr(-4);
+                    $scope.patron.passwd = $scope.patron.day_phone.substr(-4);
                 }
             case 'evening_phone' : 
             case 'other_phone' : 
-                $scope.dupe_value_changed('phone', value);
+                $scope.dupe_value_changed(field_name, value);
                 break;
 
             case 'ident_value':
@@ -1764,10 +2029,6 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
                 $scope.barcode_changed(value);
                 apply_username_regex();
                 break;
-
-            case 'dob':
-                maintain_juvenile_flag();
-                break;
         }
     }
 
@@ -1806,6 +2067,18 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
 
     // Returns true if the Save and Save & Clone buttons should be disabled.
     $scope.edit_passthru.hide_save_actions = function() {
+        if ($scope.patron.id
+            && $scope.patron.id == egCore.auth.user().id()
+        ) return true;
+
+        if ( $scope.patron.profile
+             && patronRegSvc
+                .edit_profiles
+                .filter(function(p) {
+                    return $scope.patron.profile.id() == p.id();
+                }).length == 0
+        ) return true;
+
         return $scope.patron.isnew ?
             !$scope.perms.CREATE_USER : 
             !$scope.perms.UPDATE_USER;
@@ -1842,6 +2115,10 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         $scope.patron.addresses = 
             $scope.patron.addresses.concat(deleted_addresses);
         
+        // ditto for waiver entries
+        $scope.patron.waiver_entries = 
+            $scope.patron.waiver_entries.concat(deleted_waiver_entries);
+
         compress_hold_notify();
 
         var updated_user;
@@ -1910,10 +2187,33 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
                     + updated_user.id();
                 $window.open(url, '_blank').focus();
 
+            } else if ($window.location.href.indexOf('stage') > -1 ){
+                // we're here after deleting a self-reg staged user.
+                // Just close tab, since refresh won't find staged user
+                $timeout(function(){
+                    if (typeof BroadcastChannel != 'undefined') {
+                        var bChannel = new BroadcastChannel("eg.pending_usr.update");
+                        bChannel.postMessage({
+                            usr: egCore.idl.toHash(updated_user)
+                        });
+                    }
+
+                    $window.close();
+                });
             } else {
                 // reload the current page
                 $window.location.href = location.href;
             }
         });
     }
+
+    $scope.edit_passthru.print = function() {
+        var print_data = {patron : $scope.patron}
+
+        return egCore.print.print({
+            context : 'default',
+            template : 'patron_data',
+            scope : print_data
+        });
+    }
 }])