From da4897ee225b6111735fb51670dde4b3816a81c7 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Thu, 3 Sep 2020 15:42:01 -0400 Subject: [PATCH] migrating top sip2 service Signed-off-by: Bill Erickson --- .../lib/OpenILS/Application/SIP2/Patron.pm | 320 +++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Patron.pm diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Patron.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Patron.pm new file mode 100644 index 0000000000..49a555e977 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Patron.pm @@ -0,0 +1,320 @@ +# --------------------------------------------------------------- +# Copyright (C) 2020 King County Library System +# Bill Erickson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# --------------------------------------------------------------- +# Code borrows heavily and sometimes copies directly from from +# ../SIP* and SIPServer* +# --------------------------------------------------------------- +package OpenILS::Application::SIP2::Patron; +use strict; use warnings; +use DateTime; +use DateTime::Format::ISO8601; +use OpenSRF::System; +use OpenILS::Utils::CStoreEditor q/:funcs/; +use OpenSRF::Utils::Logger q/$logger/; +use OpenILS::Const qw/:const/; +use OpenILS::Application::AppUtils; +use OpenILS::Utils::DateTime qw/:datetime/; +use OpenILS::Application::SIP2::Common; +use OpenILS::Application::SIP2::Session; +my $U = 'OpenILS::Application::AppUtils'; +my $SC = 'OpenILS::Application::SIP2::Common'; + +sub get_patron_details { + my ($class, $session, %params) = @_; + + my $barcode = $params{barcode}; + my $password = $params{password}; + + my $e = $session->editor; + my $details = {}; + + my $card = $e->search_actor_card([{ + barcode => $barcode + }, { + flesh => 3, + flesh_fields => { + ac => [qw/usr/], + au => [qw/ + billing_address + mailing_address + profile + stat_cat_entries + /], + actscecm => [qw/stat_cat/] + } + }])->[0]; + + my $patron = $details->{patron} = $card->usr; + $patron->card($card); + + # We only attempt to verify the password if one is provided. + return undef if defined $password && + !$U->verify_migrated_user_password($e, $patron->id, $password); + + my $penalties = get_patron_penalties($session, $patron); + + set_patron_privileges($session, $details, $penalties); + + $details->{too_many_overdue} = 1 if + grep {$_->{id} == OILS_PENALTY_PATRON_EXCEEDS_OVERDUE_COUNT} + @$penalties; + + $details->{too_many_fines} = 1 if + grep {$_->{id} == OILS_PENALTY_PATRON_EXCEEDS_FINES} + @$penalties; + + my $summary = $e->retrieve_money_open_user_summary($patron->id); + $details->{balance_owed} = ($summary) ? $summary->balance_owed : 0; + + set_patron_summary_items($session, $details, %params); + set_patron_summary_list_items($session, $details, %params); + + return $details; +} + + +# Sets: +# holds_count +# overdue_count +# out_count +# fine_count +# recall_count +# unavail_holds_count +sub set_patron_summary_items { + my ($session, $details, %params) = @_; + + my $patron = $details->{patron}; + my $e = $session->editor; + + $details->{recall_count} = 0; # not supported + + my $hold_ids = get_hold_ids($session, $patron); + $details->{holds_count} = scalar(@$hold_ids); + + my $unavail_hold_ids = get_hold_ids($session, $patron, 1); + $details->{unavail_holds_count} = scalar(@$unavail_hold_ids); + + $details->{overdue_count} = 0; + $details->{out_count} = 0; + + my $circ_summary = $e->retrieve_action_open_circ_list($patron->id); + if ($circ_summary) { # undef if no circs for user + my $overdue_ids = [ grep {$_ > 0} split(',', $circ_summary->overdue) ]; + my $out_ids = [ grep {$_ > 0} split(',', $circ_summary->out) ]; + $details->{overdue_count} = scalar(@$overdue_ids); + $details->{out_count} = scalar(@$out_ids) + scalar(@$overdue_ids); + } + + my $xacts = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.user.transactions.history.have_balance', + $session->editor->authtoken, + $patron->id + ); + + $details->{fine_count} = scalar(@$xacts); +} + +sub get_hold_ids { + my ($session, $patron, $unavail, $offset, $limit) = @_; + + my $e = $session->editor; + + my $holds_where = { + usr => $patron->id, + fulfillment_time => undef, + cancel_time => undef + }; + + if ($unavail) { + $holds_where->{'-or'} = [ + {current_shelf_lib => undef}, + {current_shelf_lib => {'!=' => {'+ahr' => 'pickup_lib'}}} + ]; + + } else { + + $holds_where->{current_shelf_lib} = {'=' => {'+ahr' => 'pickup_lib'}} + if $session->config->{msg64_hold_items_available}; + } + + my $query = { + select => {ahr => ['id']}, + from => 'ahr', + where => {'+ahr' => $holds_where} + }; + + $query->{offset} = $offset if $offset; + $query->{limit} = $limit if $limit; + + my $id_hashes = $e->json_query($query); + + return [map {$_->{id}} @$id_hashes]; +} + +sub set_patron_summary_list_items { + my ($session, $details, %params) = @_; + my $e = $session->editor; + + my $list_items = $params{summary_list_items}; + my $offset = $params{summary_start_item} || 0; + my $end_item = $params{summary_end_item} || 10; + my $limit = $end_item - $offset; + + add_hold_items($e, $session, $details, $offset, $limit) + if $list_items eq 'hold_items'; +} + +sub add_hold_items { + my ($e, $session, $details, $offset, $limit) = @_; + + my $patron = $details->{patron}; + my $format = $session->config->{msg64_hold_datatype} || ''; + my $hold_ids = get_hold_ids($session, $patron, 0, $offset, $limit); + + my @hold_items; + for my $hold_id (@$hold_ids) { + my $hold = $e->retrieve_action_hold_request($hold_id); + + if ($format eq 'barcode') { + my $copy = find_copy_for_hold($e, $hold); + push(@hold_items, $copy->barcode) if $copy; + } else { + my $title = find_title_for_hold($e, $hold); + push(@hold_items, $title) if $title; + } + } + + $details->{hold_items} = \@hold_items; +} + +# Hold -> reporter.hold_request_record -> display field for title. +sub find_title_for_hold { + my ($e, $hold) = @_; + + my $bib_link = $e->retrieve_reporter_hold_request_record($hold->id); + + my $title_field = $e->search_metabib_flat_display_entry({ + source => $bib_link->bib_record, name => 'title'})->[0]; + + return $title_field ? $title_field->value : ''; +} + +# Finds a representative copy for the given hold. If no copy exists at +# all, undef is returned. The only limit placed on what constitutes a +# "representative" copy is that it cannot be deleted. Otherwise, any +# copy that allows us to find the hold later is good enough. +sub find_copy_for_hold { + my ($e, $hold) = @_; + + return $e->retrieve_asset_copy($hold->current_copy) + if $hold->current_copy; + + return $e->retrieve_asset_copy($hold->target) + if $hold->hold_type =~ /C|R|F/; + + return $e->search_asset_copy([ + {call_number => $hold->target, deleted => 'f'}, + {limit => 1}])->[0] if $hold->hold_type eq 'V'; + + my $bre_ids = [$hold->target]; + + if ($hold->hold_type eq 'M') { + # find all of the bibs that link to the target metarecord + my $maps = $e->search_metabib_metarecord_source_map( + {metarecord => $hold->target}); + $bre_ids = [map {$_->record} @$maps]; + } + + my $vol_ids = $e->search_asset_call_number( + {record => $bre_ids, deleted => 'f'}, + {idlist => 1} + ); + + return $e->search_asset_copy([ + {call_number => $vol_ids, deleted => 'f'}, + {limit => 1} + ])->[0]; +} + + +sub set_patron_privileges { + my ($session, $details, $penalties) = @_; + my $patron = $details->{patron}; + + my $expire = DateTime::Format::ISO8601->new + ->parse_datetime(clean_ISO8601($patron->expire_date)); + + if ($expire < DateTime->now) { + $logger->info("SIP2 Patron account is expired; all privileges blocked"); + $details->{charge_denied} = 1; + $details->{recall_denied} = 1; + $details->{renew_denied} = 1; + $details->{holds_denied} = 1; + return; + } + + # Non-expired patrons are allowed all privileges when + # patron_status_permit_all is true. + return if $session->config->{patron_status_permit_all}; + + my $blocked = ( + $patron->barred eq 't' + || $patron->active eq 'f' + || $patron->card->active eq 'f' + ); + + my @block_tags = map {$_->{block_list}} grep {$_->{block_list}} @$penalties; + + return unless $blocked || @block_tags; # no blocks remain + + $details->{holds_denied} = ($blocked || grep {$_ =~ /HOLD/} @block_tags); + + # Ignore loan-related blocks? + return if $session->config->{patron_status_permit_loans}; + + $details->{charge_denied} = ($blocked || grep {$_ =~ /CIRC/} @block_tags); + $details->{renew_denied} = ($blocked || grep {$_ =~ /RENEW/} @block_tags); + + # In evergreen, patrons cannot create Recall holds directly, but that + # doesn't mean they would not have said privilege if the functionality + # existed. Base the ability to perform recalls on whether they have + # checkout and holds privilege, since both would be needed for recalls. + $details->{recall_denied} = + ($details->{charge_denied} || $details->{holds_denied}); +} + +# Returns an array of penalty hashes with keys "id" and "block_list" +sub get_patron_penalties { + my ($session, $patron) = @_; + + return $session->editor->json_query({ + select => {csp => ['id', 'block_list']}, + from => {ausp => 'csp'}, + where => { + '+ausp' => { + usr => $patron->id, + '-or' => [ + {stop_date => undef}, + {stop_date => {'>' => 'now'}} + ], + org_unit => + $U->get_org_full_path($session->editor->requestor->ws_ou) + } + } + }); +} + +1; -- 2.11.0