From c7727d4966d63a14419c2a8e9df5cabf11f76ee3 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Wed, 7 Feb 2018 15:11:18 -0800 Subject: [PATCH] LP#1715767: Allow others to use my account (privacy waiver) Use case: Jane Doe has a hold ready for pickup but is unable to come into the library. Her husband John Doe goes to the library to pick up the hold on her behalf. His name is listed on Jane's account, so library staff know it's okay to check out the book on Jane's account and give it to John. This commit adds a new table which lists the names of people who are allowed to place holds, pick up holds, check out items, or view borrowing history for a user account. Staff can add, edit, or remove entries via the patron editor in the web client; patrons can do so in My Account. The entries are not linked to other user accounts and they do not add any extra functionality. They are essentially special patron notes for circulation staff. Signed-off-by: Jeff Davis Signed-off-by: Chris Sharp --- Open-ILS/examples/fm_IDL.xml | 32 ++++++ .../perlmods/lib/OpenILS/Application/Actor.pm | 104 ++++++++++++++++++ .../lib/OpenILS/WWW/EGCatLoader/Account.pm | 51 ++++++++- Open-ILS/src/sql/Pg/005.schema.actors.sql | 10 ++ Open-ILS/src/sql/Pg/950.data.seed-values.sql | 15 +++ .../Pg/upgrade/XXXX.data.privacy_waiver.sql | 21 ++++ .../XXXX.schema.actor.privacy_waiver.sql | 17 +++ .../templates/opac/myopac/prefs_settings.tt2 | 44 +++++++- .../templates/staff/circ/patron/t_edit.tt2 | 49 +++++++++ .../templates/staff/circ/patron/t_summary.tt2 | 14 +++ .../js/ui/default/staff/circ/patron/regctl.js | 43 ++++++++ .../web/js/ui/default/staff/services/user.js | 1 + .../Circulation/privacy_waiver.adoc | 15 +++ 13 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.privacy_waiver.sql create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.actor.privacy_waiver.sql create mode 100644 docs/RELEASE_NOTES_NEXT/Circulation/privacy_waiver.adoc diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index c66eaad4e5..b8d0efff60 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2320,6 +2320,36 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3601,6 +3631,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -3676,6 +3707,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index c2b701a0c8..219f611b6b 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -114,6 +114,78 @@ sub update_user_setting { } +__PACKAGE__->register_method( + method => "update_privacy_waiver", + api_name => "open-ils.actor.patron.privacy_waiver.update", + signature => { + desc => "Replaces any existing privacy waiver entries for the patron with the supplied values.", + params => [ + {desc => 'Authentication token', type => 'string'}, + {desc => 'User ID', type => 'number'}, + {desc => 'Arrayref of privacy waiver entries', type => 'object'} + ], + return => {desc => '1 on success, Event on error'} + } +); +sub update_privacy_waiver { + my($self, $conn, $auth, $user_id, $waiver) = @_; + my $e = new_editor(xact => 1, authtoken => $auth); + return $e->die_event unless $e->checkauth; + + $user_id = $e->requestor->id unless defined $user_id; + + unless($e->requestor->id == $user_id) { + my $user = $e->retrieve_actor_user($user_id) or return $e->die_event; + return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou); + } + + foreach my $w (@$waiver) { + $w->{usr} = $user_id unless $w->{usr}; + if ($w->{id} && $w->{id} ne 'new') { + my $existing_rows = $e->search_actor_usr_privacy_waiver({usr => $user_id, id => $w->{id}}); + if ($existing_rows) { + my $existing = $existing_rows->[0]; + # delete existing if name is empty + if (!$w->{name} or $w->{name} =~ /^\s*$/) { + $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event; + + # delete existing if none of the boxes were checked + } elsif (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}) { + $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event; + + # otherwise, update existing waiver entry + } else { + $existing->name($w->{name}); + $existing->place_holds($w->{place_holds}); + $existing->pickup_holds($w->{pickup_holds}); + $existing->checkout_items($w->{checkout_items}); + $existing->view_history($w->{view_history}); + $e->update_actor_usr_privacy_waiver($existing) or return $e->die_event; + } + } else { + $logger->warn("No privacy waiver entry found for user $user_id with ID " . $w->{id}); + } + + } else { + # ignore new entries with empty name or with no boxes checked + next if (!$w->{name} or $w->{name} =~ /^\s*$/); + next if (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}); + my $new = Fieldmapper::actor::usr_privacy_waiver->new; + $new->usr($w->{usr}); + $new->name($w->{name}); + $new->place_holds($w->{place_holds}); + $new->pickup_holds($w->{pickup_holds}); + $new->checkout_items($w->{checkout_items}); + $new->view_history($w->{view_history}); + $e->create_actor_usr_privacy_waiver($new) or return $e->die_event; + } + } + + $e->commit; + return 1; +} + + __PACKAGE__->register_method( method => "set_ou_settings", api_name => "open-ils.actor.org_unit.settings.update", @@ -444,6 +516,9 @@ sub update_patron { ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron); return $evt if $evt; + ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron); + return $evt if $evt; + ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron); return $evt if $evt; @@ -540,6 +615,7 @@ sub flesh_user { "billing_address", "mailing_address", "stat_cat_entries", + "waiver_entries", "settings", "usr_activity" ]; @@ -568,6 +644,7 @@ sub _clone_patron { $new_patron->clear_ischanged(); $new_patron->clear_isdeleted(); $new_patron->clear_stat_cat_entries(); + $new_patron->clear_waiver_entries(); $new_patron->clear_permissions(); $new_patron->clear_standing_penalties(); @@ -888,6 +965,32 @@ sub _update_card { } +sub _add_update_waiver_entries { + my $e = shift; + my $patron = shift; + my $new_patron = shift; + my $evt; + + my $waiver_entries = $patron->waiver_entries(); + for my $waiver (@$waiver_entries) { + next unless ref $waiver; + $waiver->usr($new_patron->id()); + if ($waiver->isnew()) { + next if (!$waiver->name() or $waiver->name() =~ /^\s*$/); + next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history()); + $logger->info("Adding new patron waiver entry"); + $waiver->clear_id(); + $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event); + } elsif ($waiver->ischanged()) { + $logger->info("Updating patron waiver entry " . $waiver->id); + $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event); + } elsif ($waiver->isdeleted()) { + $logger->info("Deleting patron waiver entry " . $waiver->id); + $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event); + } + } + return ($new_patron, undef); +} # returns event on error. returns undef otherwise @@ -3007,6 +3110,7 @@ sub user_retrieve_fleshed_by_id { "billing_address", "mailing_address", "stat_cat_entries", + "waiver_entries", "usr_activity" ]; return new_flesh_user($user_id, $fields, $e); } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm index d78b59bf5f..19e8fbc538 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm @@ -34,7 +34,7 @@ sub prepare_extended_user_info { { flesh => 1, flesh_fields => { - au => [qw/card home_ou addresses ident_type billing_address/, @extra_flesh] + au => [qw/card home_ou addresses ident_type billing_address waiver_entries/, @extra_flesh] # ... } } @@ -603,6 +603,9 @@ sub load_myopac_prefs_settings { } } + my $use_privacy_waiver = $self->ctx->{get_org_setting}->( + $e->requestor->home_ou, 'circ.privacy_waiver'); + return Apache2::Const::OK unless $self->cgi->request_method eq 'POST'; @@ -699,8 +702,52 @@ sub load_myopac_prefs_settings { 'open-ils.actor.patron.settings.update', $self->editor->authtoken, undef, \%settings); - # re-fetch user prefs $self->ctx->{updated_user_settings} = \%settings; + + if ($use_privacy_waiver) { + my %waiver; + my $saved_entries = (); + my @waiver_types = qw/place_holds pickup_holds checkout_items view_history/; + + # initialize our waiver hash with waiver IDs from hidden input + # (this ensures that we capture entries with no checked boxes) + foreach my $waiver_row_id ($self->cgi->param("waiver_id")) { + $waiver{$waiver_row_id} = {}; + } + + # process our waiver checkboxes into a hash, keyed by waiver ID + # (a new entry, if any, has id = 'new') + foreach my $waiver_type (@waiver_types) { + if ($self->cgi->param("waiver_$waiver_type")) { + foreach my $waiver_id ($self->cgi->param("waiver_$waiver_type")) { + # ensure this waiver exists in our hash + $waiver{$waiver_id} = {} if !$waiver{$waiver_id}; + $waiver{$waiver_id}->{$waiver_type} = 1; + } + } + } + + foreach my $k (keys %waiver) { + my $w = $waiver{$k}; + # get name from textbox + $w->{name} = $self->cgi->param("waiver_name_$k"); + $w->{id} = $k; + foreach (@waiver_types) { + $w->{$_} = 0 unless ($w->{$_}); + } + push @$saved_entries, $w; + } + + # update patron privacy waiver entries + $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.patron.privacy_waiver.update', + $self->editor->authtoken, undef, $saved_entries); + + $self->ctx->{updated_waiver_entries} = $saved_entries; + } + + # re-fetch user prefs return $self->_load_user_with_prefs || Apache2::Const::OK; } diff --git a/Open-ILS/src/sql/Pg/005.schema.actors.sql b/Open-ILS/src/sql/Pg/005.schema.actors.sql index c3879670b4..c97afd7974 100644 --- a/Open-ILS/src/sql/Pg/005.schema.actors.sql +++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql @@ -1265,5 +1265,15 @@ BEGIN END; $FUNC$ LANGUAGE PLPGSQL; +CREATE TABLE actor.usr_privacy_waiver ( + id BIGSERIAL PRIMARY KEY, + usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, + name TEXT NOT NULL, + place_holds BOOL DEFAULT FALSE, + pickup_holds BOOL DEFAULT FALSE, + view_history BOOL DEFAULT FALSE, + checkout_items BOOL DEFAULT FALSE +); +CREATE INDEX actor_usr_privacy_waiver_usr_idx ON actor.usr_privacy_waiver (usr); COMMIT; diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index 649ced5d83..1fe0bb6060 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -19495,3 +19495,18 @@ VALUES ( +INSERT INTO config.org_unit_setting_type + (name, label, description, grp, datatype) + VALUES ( + 'circ.privacy_waiver', + oils_i18n_gettext('circ.privacy_waiver', + 'Allow others to use patron account (privacy waiver)', + 'coust', 'label'), + oils_i18n_gettext('circ.privacy_waiver', + 'Add a note to a user account indicating that specified people are allowed to ' || + 'place holds, pick up holds, check out items, or view borrowing history for that user account', + 'coust', 'description'), + 'circ', + 'bool' + ); + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.privacy_waiver.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.privacy_waiver.sql new file mode 100644 index 0000000000..ab8a6c2a13 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.privacy_waiver.sql @@ -0,0 +1,21 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +INSERT INTO config.org_unit_setting_type + (name, label, description, grp, datatype) + VALUES ( + 'circ.privacy_waiver', + oils_i18n_gettext('circ.privacy_waiver', + 'Allow others to use patron account (privacy waiver)', + 'coust', 'label'), + oils_i18n_gettext('circ.privacy_waiver', + 'Add a note to a user account indicating that specified people are allowed to ' || + 'place holds, pick up holds, check out items, or view borrowing history for that user account', + 'coust', 'description'), + 'circ', + 'bool' + ); + +COMMIT; + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.actor.privacy_waiver.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.actor.privacy_waiver.sql new file mode 100644 index 0000000000..6b2b7b8a78 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.actor.privacy_waiver.sql @@ -0,0 +1,17 @@ +BEGIN; + +INSERT INTO config.upgrade_log (version) VALUES ('XXXX'); + +CREATE TABLE actor.usr_privacy_waiver ( + id BIGSERIAL PRIMARY KEY, + usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, + name TEXT NOT NULL, + place_holds BOOL DEFAULT FALSE, + pickup_holds BOOL DEFAULT FALSE, + view_history BOOL DEFAULT FALSE, + checkout_items BOOL DEFAULT FALSE +); +CREATE INDEX actor_usr_privacy_waiver_usr_idx ON actor.usr_privacy_waiver (usr); + +COMMIT; + diff --git a/Open-ILS/src/templates/opac/myopac/prefs_settings.tt2 b/Open-ILS/src/templates/opac/myopac/prefs_settings.tt2 index 415dcec51f..70f76a92f3 100644 --- a/Open-ILS/src/templates/opac/myopac/prefs_settings.tt2 +++ b/Open-ILS/src/templates/opac/myopac/prefs_settings.tt2 @@ -23,7 +23,7 @@ - [% ELSIF ctx.updated_user_settings %] + [% ELSIF ctx.updated_user_settings OR ctx.updated_waiver_entries %]
[% l('Account Successfully Updated') %] @@ -120,6 +120,48 @@ [% END %] + [%- IF ctx.get_org_setting(ctx.user.home_ou.id, 'circ.privacy_waiver'); %] + + [% l('Allow others to use my account') %] + + [% FOR waiver IN ctx.user.waiver_entries %] +
+ + [% l('Name:') %]
+ + + + +
+ [% END %] +
+
+ [% l('Name:') %]
+ + + + +
+ + + [% END %] + +
+
+
+ +
+
+ + + diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 index 11cf06a2b3..7963ca0bb0 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 @@ -177,6 +177,20 @@
[% l('Name Keywords') %]
{{patron().name_keywords()}}
+
+ [% l('Allow others to use my account') %] +
+
+
{{waiver.name()}}
+
+
    +
  • [% l('Place holds') %]
  • +
  • [% l('Pick up holds') %]
  • +
  • [% l('View borrowing history') %]
  • +
  • [% l('Check out items') %]
  • +
+
+
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js index 4e2e9a2586..e33933acb3 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js @@ -334,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', @@ -726,6 +727,13 @@ angular.module('egCoreMod') 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'; + } + /* * Existing patron objects reqire some data munging before insertion * into the scope. @@ -773,6 +781,9 @@ angular.module('egCoreMod') } }); + 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 @@ -824,6 +835,7 @@ angular.module('egCoreMod') cards : [card], home_ou : egCore.org.get(egCore.auth.user().ws_ou()), stat_cat_entries : [], + waiver_entries : [], groups : [], addresses : [addr] }; @@ -1137,6 +1149,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( @@ -1634,6 +1655,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; @@ -2059,6 +2098,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; diff --git a/Open-ILS/web/js/ui/default/staff/services/user.js b/Open-ILS/web/js/ui/default/staff/services/user.js index ccd1d0ab71..80b2838eea 100644 --- a/Open-ILS/web/js/ui/default/staff/services/user.js +++ b/Open-ILS/web/js/ui/default/staff/services/user.js @@ -17,6 +17,7 @@ function($q, $timeout, egNet, egAuth, egOrg) { 'billing_address', 'mailing_address', 'stat_cat_entries', + 'waiver_entries', 'usr_activity', 'notes' ] diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/privacy_waiver.adoc b/docs/RELEASE_NOTES_NEXT/Circulation/privacy_waiver.adoc new file mode 100644 index 0000000000..d59f6527cd --- /dev/null +++ b/docs/RELEASE_NOTES_NEXT/Circulation/privacy_waiver.adoc @@ -0,0 +1,15 @@ +Allow Others to Use My Account (Privacy Waiver) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Patrons who wish to authorize other people to use their account may +now do so via My Account. In the Search and History Preferences tab +under Account Preferences, a new section labeled "Allow others to use +my account" allows patrons to enter a name and indicate that the +specified person is allowed to place holds, pickup holds, view +borrowing history, or check out items on their account. This +information is displayed to circulation staff in the patron account +summary in the web client. (Staff may also add, edit, and remove +entries via the patron editor.) + +A new library setting, "Allow others to use patron account (privacy +waiver)," is used to enable or disable this feature. + -- 2.43.2