SIP2Mediator patron status; hold items list
authorBill Erickson <berickxx@gmail.com>
Tue, 1 Sep 2020 21:56:39 +0000 (17:56 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 28 Oct 2020 18:57:39 +0000 (14:57 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway/Patron.pm

index 294ba62..ea6d69b 100644 (file)
@@ -276,10 +276,12 @@ sub handler {
 
         return Apache2::Const::FORBIDDEN unless $session;
 
-        if ($msg_code eq '63') {
-            $response = handle_patron_info($session, $message);
-        } elsif ($msg_code eq '17') {
+        if ($msg_code eq '17') {
             $response = handle_item_info($session, $message);
+        } elsif ($msg_code eq '23') {
+            $response = handle_patron_status($session, $message);
+        } elsif ($msg_code eq '63') {
+            $response = handle_patron_info($session, $message);
         }
     }
 
@@ -294,6 +296,7 @@ sub handler {
     return Apache2::Const::OK;
 }
 
+
 # Returns the value of the first occurrence of the requested SIP code.
 sub get_field_value {
     my ($message, $code) = @_;
@@ -477,9 +480,76 @@ sub handle_patron_info {
         summary_list_items => patron_summary_list_items($summary)
     );
 
+    my $response = patron_response_common_data(
+        $session, $institution, $barcode, $password, $pdetails);
+
+    $response->{code} = '64';
+
+    return $response unless $pdetails;
+
+    push(
+        @{$response->{fixed_fields}}, 
+        count4($pdetails->{holds_count}),
+        count4($pdetails->{overdue_count}),
+        count4($pdetails->{out_count}),
+        count4($pdetails->{fine_count}),
+        count4($pdetails->{recall_count}),
+        count4($pdetails->{unavail_holds_count})
+    );
+
+    # TODO: Add 
+    # overdue items AT variable-length optional field (this field should be sent for each overdue item).
+    # charged items AU variable-length optional field (this field should be sent for each charged item).
+    # fine items AV variable-length optional field (this field should be sent for each fine item).
+    # recall items BU variable-length optional field (this field should be sent for each recall item).
+    # unavailable hold items CD variable-length optional field (this field should be sent for each unavailable hold item).
+
+    my $list_items = patron_summary_list_items($summary) || '';
+
+    if ($list_items eq 'hold_items') {
+        for my $hold (@{$pdetails->{hold_items}}) {
+            push(@{$response->{fields}}, {AS => $hold});
+        }
+    }
+
+    return $response;
+}
+
+sub handle_patron_status {
+    my ($session, $message) = @_;
+    my $sip_account = $session->sip_account;
+
+    my $institution = get_field_value($message, 'AO');
+    my $barcode = get_field_value($message, 'AA');
+    my $password = get_field_value($message, 'AD');
+    my $instconf = get_inst_config($institution) || return undef;
+
+    my $pdetails = OpenILS::WWW::SIP2Gateway::Patron->get_patron_details(
+        session => $session,
+        instconf => $instconf,
+        barcode => $barcode,
+        password => $password
+    );
+
+    my $response = patron_response_common_data(
+        $session, $institution, $barcode, $password, $pdetails);
+
+    $response->{code} = '24';
+
+    return $response;
+}
+
+# Patron Info and Patron Status responses share mostly the same data.
+# This returns the base data which can be augmented as needed.
+# Note we don't call Patron->get_patron_details here since different
+# messages collect different amounts of data.
+sub patron_response_common_data {
+    my ($session, $institution, $barcode, $password, $pdetails) = @_;
+
     if (!$pdetails) {
+        # No such user.  Return a stub response with all things denied.
+
         return {
-            code => '64',
             fixed_fields => [
                 spacebool(1), # charge denied
                 spacebool(1), # renew denied
@@ -497,9 +567,8 @@ sub handle_patron_info {
             ]
         };
     }
-
     return {
-        code => '64',
         fixed_fields => [
             spacebool($pdetails->{charge_denied}),
             spacebool($pdetails->{renew_denied}),
@@ -516,24 +585,18 @@ sub handle_patron_info {
             spacebool(0), # recall overdue
             spacebool($pdetails->{too_many_fines}),
             '000', # language
-            sipdate(),
-            count4($pdetails->{holds_count}),
-            count4($pdetails->{overdue_count}),
-            count4($pdetails->{out_count}),
-            count4($pdetails->{fine_count}),
-            count4($pdetails->{recall_count}),
-            count4($pdetails->{unavail_holds_count}),
+            sipdate()
         ],
         fields => [
             {AO => $institution},
             {AA => $barcode},
-            {BL => sipbool(1)},         # valid patron
-            {CQ => sipbool($password)}  # password verified if exists
+            {BL => sipbool(1)},                 # valid patron
+            {BV => $pdetails->{balance_owed}},  # fee amount
+            {CQ => sipbool($password)}          # password verified if exists
         ]
     };
 }
 
-
 # Determines which class of data the SIP client wants detailed
 # information on in the patron info request.
 sub patron_summary_list_items {
index 8396154..ed9bc9a 100644 (file)
@@ -74,7 +74,11 @@ sub get_patron_details {
         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, $instconf, $details, %params);
+    set_patron_summary_list_items($session, $instconf, $details, %params);
 
     return $details;
 }
@@ -91,28 +95,16 @@ sub set_patron_summary_items {
     my ($session, $instconf, $details, %params) = @_;
 
     my $patron = $details->{patron};
-    my $start_item = $params{summary_start_item} || 0;
-    my $end_item = $params{summary_end_item} || 10;
-    my $list_items = $params{summary_list_items};
-
     my $e = new_editor();
 
-    my $holds_where = {
-        usr => $patron->id,
-        fulfillment_time => undef,
-        cancel_time => undef
-    };
+    $details->{recall_count} = 0; # not supported
 
-    $holds_where->{current_shelf_lib} = {'=' => {'+ahr' => 'pickup_lib'}} 
-        if $instconf->{msg64_hold_items_available};
+    my $hold_ids = get_hold_ids($e, $instconf, $patron);
+    $details->{holds_count} = scalar(@$hold_ids);
 
-    my $hold_ids = $e->json_query({
-        select => {ahr => ['id']},
-        from => 'ahr',
-        where => {'+ahr' => $holds_where}
-    });
+    my $unavail_hold_ids = get_hold_ids($e, $instconf, $patron, 1);
+    $details->{unavail_holds_count} = scalar(@$unavail_hold_ids);
 
-    $details->{holds_count} = scalar(@$hold_ids);
     $details->{overdue_count} = 0;
     $details->{out_count} = 0;
 
@@ -124,8 +116,6 @@ sub set_patron_summary_items {
         $details->{out_count} = scalar(@$out_ids) + scalar(@$overdue_ids);
     }
 
-    $details->{recall_count} = undef; # not supported
-
     my $xacts = $U->simplereq(
         'open-ils.actor',                                
         'open-ils.actor.user.transactions.history.have_balance',               
@@ -134,10 +124,129 @@ sub set_patron_summary_items {
     );
 
     $details->{fine_count} = scalar(@$xacts);
+}
+
+sub get_hold_ids {
+    my ($e, $instconf, $patron, $unavail, $offset, $limit) = @_;
+
+    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 $instconf->{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;
 
-    # TODO: unavail holds count; summary details request
+    my $id_hashes = $e->json_query($query);
+
+    return [map {$_->{id}} @$id_hashes];
+}
+
+sub set_patron_summary_list_items {
+    my ($session, $instconf, $details, %params) = @_;
+    my $e = new_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, $instconf, $details, $offset, $limit)
+        if $list_items eq 'hold_items';
 }
 
+sub add_hold_items {
+    my ($e, $session, $instconf, $details, $offset, $limit) = @_;
+
+    my $patron = $details->{patron};
+    my $format = $instconf->{msg64_hold_datatype} || '';
+    my $hold_ids = get_hold_ids($e, $instconf, $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, $instconf, $details, $penalties) = @_;
     my $patron = $details->{patron};
@@ -146,8 +255,7 @@ sub set_patron_privileges {
         ->parse_datetime(clean_ISO8601($patron->expire_date));
 
     if ($expire < DateTime->now) {
-        $logger->info(
-            "SIP2 Patron account is expired; all privileges blocked");
+        $logger->info("SIP2 Patron account is expired; all privileges blocked");
         $details->{charge_denied} = 1;
         $details->{recall_denied} = 1;
         $details->{renew_denied} = 1;