migrating top sip2 service
authorBill Erickson <berickxx@gmail.com>
Thu, 3 Sep 2020 19:42:01 +0000 (15:42 -0400)
committerBill Erickson <berickxx@gmail.com>
Mon, 30 Nov 2020 16:38:24 +0000 (08:38 -0800)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Patron.pm [new file with mode: 0644]

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 (file)
index 0000000..49a555e
--- /dev/null
@@ -0,0 +1,320 @@
+# ---------------------------------------------------------------
+# Copyright (C) 2020 King County Library System
+# Bill Erickson <berickxx@gmail.com>
+#
+# 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;