From 24331a1b2b33215f23aa805b7aa4940a9d8de78d 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 --- Open-ILS/examples/fm_IDL.xml | 32 +++++++ .../src/perlmods/lib/OpenILS/Application/Actor.pm | 104 +++++++++++++++++++++ .../lib/OpenILS/WWW/EGCatLoader/Account.pm | 51 +++++++++- Open-ILS/src/sql/Pg/005.schema.actors.sql | 11 +++ Open-ILS/src/sql/Pg/950.data.seed-values.sql | 15 +++ .../sql/Pg/upgrade/XXXX.data.privacy_waiver.sql | 21 +++++ .../upgrade/XXXX.schema.actor.privacy_waiver.sql | 17 ++++ .../src/templates/opac/myopac/prefs_settings.tt2 | 44 ++++++++- .../src/templates/staff/circ/patron/t_edit.tt2 | 49 ++++++++++ .../src/templates/staff/circ/patron/t_summary.tt2 | 14 +++ .../web/js/ui/default/staff/circ/patron/regctl.js | 43 +++++++++ Open-ILS/web/js/ui/default/staff/services/user.js | 1 + .../Circulation/privacy_waiver.adoc | 15 +++ 13 files changed, 414 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 b272b9ffab..c90ce71a57 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2276,6 +2276,36 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3408,6 +3438,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -3476,6 +3507,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 e62982e79f..293756755d 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", signature => { @@ -443,6 +515,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; @@ -539,6 +614,7 @@ sub flesh_user { "billing_address", "mailing_address", "stat_cat_entries", + "waiver_entries", "settings", "usr_activity" ]; @@ -567,6 +643,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(); @@ -887,6 +964,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 @@ -3014,6 +3117,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 b6305175ed..236244bb8f 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 d96f83dd50..26ce55bc02 100644 --- a/Open-ILS/src/sql/Pg/005.schema.actors.sql +++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql @@ -1054,4 +1054,15 @@ BEGIN END LOOP; END $$ 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 6816911bab..26002908be 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -18299,3 +18299,18 @@ WHERE tag = '555' AND control_set = 1 AND ahf.heading_purpose = 'related' AND ahf.heading_type = 'genre_form_term'; + +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 f663db96e5..ea08e4a05d 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 @@ -159,6 +159,20 @@
{{map.stat_cat().name()}}
{{map.stat_cat_entry()}}
+
+ [% 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 bd83888db2..7737ecc71c 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 @@ -293,6 +293,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', @@ -639,6 +640,13 @@ angular.module('egCoreMod') addr.id == patron.billing_address.id); } + 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. @@ -678,6 +686,9 @@ angular.module('egCoreMod') angular.forEach(patron.addresses, function(addr) { service.ingest_address(patron, addr) }); + 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 @@ -729,6 +740,7 @@ angular.module('egCoreMod') cards : [card], home_ou : egCore.org.get(egCore.auth.user().ws_ou()), stat_cat_entries : [], + waiver_entries : [], groups : [], addresses : [addr] }; @@ -1045,6 +1057,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( @@ -1452,6 +1473,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; @@ -1871,6 +1910,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.11.0