From 157eb9acbdf650b53a4b408dd9c812a97b84f9cf Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Fri, 30 Oct 2020 17:56:35 -0400 Subject: [PATCH] LP1901930 SIP2Mediator checkin support Signed-off-by: Bill Erickson --- .../src/perlmods/lib/OpenILS/Application/SIP2.pm | 135 +++++++++++++++----- .../lib/OpenILS/Application/SIP2/Checkin.pm | 142 +++++++++++++++++++++ .../lib/OpenILS/Application/SIP2/Common.pm | 44 +++++++ .../perlmods/lib/OpenILS/Application/SIP2/Item.pm | 9 +- .../src/sql/Pg/upgrade/XXXX.schema.sip-config.sql | 4 + 5 files changed, 300 insertions(+), 34 deletions(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Checkin.pm diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2.pm index 820edd5cdc..13ac5a250e 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2.pm @@ -15,6 +15,7 @@ use OpenILS::Application::SIP2::Session; use OpenILS::Application::SIP2::Item; use OpenILS::Application::SIP2::Patron; use OpenILS::Application::SIP2::Checkout; +use OpenILS::Application::SIP2::Checkin; my $U = 'OpenILS::Application::AppUtils'; my $SC = 'OpenILS::Application::SIP2::Common'; @@ -65,6 +66,7 @@ sub dispatch_sip2_request { } my $MESSAGE_MAP = { + '09' => \&handle_checkin, '11' => \&handle_checkout, '17' => \&handle_item_info, '23' => \&handle_patron_status, @@ -211,14 +213,15 @@ sub handle_item_info { {AB => $barcode}, {AH => $details->{due_date}}, {AJ => $details->{title}}, - {AP => $details->{item}->circ_lib->shortname}, - {AQ => $details->{item}->circ_lib->shortname}, - {BG => $details->{item}->circ_lib->shortname}, + {AP => $details->{current_loc}}, + {AQ => $details->{permanent_loc}}, + {BG => $details->{owning_loc}}, {BH => $config->{settings}->{currency}}, {BV => $details->{item}->deposit_amount}, {CF => $details->{hold_queue_length}}, {CK => $details->{media_type}}, {CM => $details->{hold_pickup_date}}, + {CT => $details->{destination_loc}}, {CY => $details->{hold_patron_barcode}} ] }; @@ -397,22 +400,20 @@ sub handle_checkout { $session, barcode => $patron_barcode ); - if (!$item_details || !$patron_details) { # bad data - return { - code => '12', - fixed_fields => [ - 0, # checkout ok - $SC->sipbool(0), # renewal ok - $SC->sipbool(0), # magnetic media - $SC->sipbool(0), # desensitize - $SC->sipdate, # transaction date - ], - fields => [ - {AA => $patron_barcode}, - {AB => $item_barcode}, - ] - }; - } + return { + code => '12', + fixed_fields => [ + 0, # checkout ok + $SC->sipbool(0), # renewal ok + $SC->sipbool(0), # magnetic media + $SC->sipbool(0), # desensitize + $SC->sipdate, # transaction date + ], + fields => [ + {AA => $patron_barcode}, + {AB => $item_barcode}, + ] + } unless ($item_details && $patron_details); my $circ_details = OpenILS::Application::SIP2::Checkout->checkout( $session, @@ -421,20 +422,19 @@ sub handle_checkout { fee_ack => $fee_ack ); - use Data::Dumper; - $Data::Dumper::Indent = 0; - $logger->info("CHECKOUT RESULTED IN ".Dumper($circ_details)); my $magnetic = $item_details->{magnetic_media}; - my $can_renew = 0; - + my $deposit = $item_details->{item}->deposit_amount; + my $msg = $circ_details->{screen_msg}; my $circ = $circ_details->{circ}; + + my $can_renew = 0; if ($circ) { $can_renew = !$patron_details->{renew_denied} && $circ->renewal_remaining > 0; } - my $response = { + return { code => '12', fixed_fields => [ $circ ? 1 : 0, # checkout ok @@ -446,26 +446,97 @@ sub handle_checkout { fields => [ {AA => $patron_barcode}, {AB => $item_barcode}, + $msg ? {AF => $msg} : (), + $circ ? {AH => $circ_details->{due_date}} : (); {AJ => $item_details->{title}}, + $circ ? {BK => $circ->id} : (), {AO => $config->{institution}}, {BT => $item_details->{fee_type}}, + $deposit ? {BV => $deposit} : (), {CI => 0}, # security inhibit {CK => $item_details->{media_type}} ] }; +} - if ($circ) { - $SC->add_field($response, 'AH', $circ_details->{due_date}); - $SC->add_field($response, 'BK', $circ->id); +sub handle_checkin { + my ($session, $message) = @_; + my $config = $session->config; + + my $item_barcode = $SC->get_field_value($message, 'AB'); + my $current_loc = $SC->get_field_value($message, 'AP'); + + my $item_details = OpenILS::Application::SIP2::Item->get_item_details( + $session, barcode => $item_barcode + ); + + return { + code => '10', + fixed_fields => [ + 0, # checkin ok + $SC->sipbool(0), # resensitize + $SC->sipbool(0), # magnetic media + 'N' # alert + $SC->sipdate, # transaction date + ], + fields => [ + {AA => $patron_barcode}, + {AO => $config->{institution}}, + ] + } unless $item_details; - $SC->maybe_add_field( - $response, 'BV', $item_details->{item}->deposit_amount); + my $checkin_details = OpenILS::Application::SIP2::Checkin->checkin( + $session, + item_barcode => $item_barcode, + current_loc => $current_loc, + item_details => $item_details + ); + + return { + code => '10', + fixed_fields => [ + 0, # checkin ok + $SC->sipbool(0), # resensitize + $SC->sipbool(0), # magnetic media + 'Y' # alert + $SC->sipdate, # transaction date + ], + fields => [ + {AA => $patron_barcode}, + {AO => $config->{institution}}, + {CV => '00'} # unkown alert type + ] } - $SC->maybe_add_field($response, 'AF', $circ_details->{screen_msg}); + my $msg = $checkin_details->{screen_msg}; + my $magnetic = $item_details->{magnetic_media}; - return $response; + return { + fixed_fields => [ + $checkin_details->{ok}, # checkin ok + $SC->sipbool(!$magnetic), # resensitize + $SC->sipbool($magnetic), # magnetic media + $SC->sipbool($checkin_details->{alert}), # alert + $SC->sipdate, # transaction date + ], + fields => [ + {AA => $patron_barcode}, + {AB => $item_barcode}, + $msg ? {AF => $msg} : (), + {AJ => $item_details->{title}}, + {AO => $config->{institution}}, + {AP => $checkin_details->{current_loc}}, + {AQ => $checkin_details->{permanent_loc}}, + {BG => $item_details->{owning_loc}}, + {BT => $item_details->{fee_type}}, + {CI => 0}, # security inhibit + {CK => $item_details->{media_type}}, + {CV => $checkin_details->{alert_type}}, + {CT => $details->{destination_loc}} + ] + }; } + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Checkin.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Checkin.pm new file mode 100644 index 0000000000..7c65cf0e4a --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Checkin.pm @@ -0,0 +1,142 @@ +package OpenILS::Application::SIP2::Checkin; +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::Application::AppUtils; +use OpenILS::Utils::DateTime qw/:datetime/; +use OpenILS::Const qw/:const/; +use OpenILS::Application::SIP2::Common; +use OpenILS::Application::SIP2::Session; +my $U = 'OpenILS::Application::AppUtils'; +my $SC = 'OpenILS::Application::SIP2::Common'; + + +sub checkin { + my ($class, $session, %params) = @_; + + my $details = {}; + my $override = 0; + + for (0, 1) { # 2 checkin requests max + + $override = $class->perform_checkin( + $session, $details, $override, %params); + + last unless $override; + } + + return $details; +} + + +# Returns 1 if the checkin should be performed again with override. +# Returns 0 if there's nothing left to do (final success / error) +# Updates $details along the way. +sub perform_checkin { + my ($class, $session, $details, $override, %params) = @_; + my $config = $session->config; + my $item_details = $params{item_details}; + + my $args = { + copy_barcode => $params{item_barcode}, + holds_as_transit => $config->{checkin_hold_as_transit} + }; + + $args->{circ_lib} = + $SC->org_id_from_sn($params{corrent_loc}) || + $session->editor->ws_ou; + + my $method = 'open-ils.circ.checkin'; + $method .= '.override' if $override; + + my $resp = $U->simplereq( + 'open-ils.circ', $method, $session->editor->authtoken, $args); + + # Treat the first response as the main result. + my $event = $resp->[0] if ref $resp eq 'ARRAY'; + + return unless $U->is_event($event); # should never happen; fail gracefully + + my $textcode = $event->{textcode}; + my $payload = $event->{payload} || {}; + + return 1 if !$override && $config->{"checkin.override.$textcode"}; + + my $circ = $payload->{circ}; + my $copy = $payload->{copy}; + + # These may be replaced below + $details->{current_loc} = + $params{item_details}->{item}->circ_lib->shortname; + + $details->{permanent_loc} = + $params{item_details}->{item}->circ_lib->shortname; + + $details->{destination_loc} = + $SC->org_sn_from_id($event->{org}) if $event->{org}; + + if ($copy && $copy->circ_lib != $item_details->{item}->circ_lib->id) { + # Checkin of floating copies changes the circ lib. + $details->{current_loc} = + $details->{permanent_loc} = + $SC->org_sn_from_id($session, $copy->circ_lib); + } + + $class->handle_hold($session, $details, $payload, %params); + + if ($textcode eq 'NO_CHANGE' || $textcode eq 'SUCCESS') { + + $details->{ok} = 1; + + } elsif ($textcode eq 'ROUTE_ITEM') { + + $details->{ok} = 1; + $details->{alert} = 1; + $details->{alert_type} = '04' unless $details->{alert_type}; + + } else { + $details->{ok} = 0; # unknown + $details->{alert} = 1; + $details->{alert_type} = '00' unless $details->{alert_type}; + } + + return 0; +} + +sub handle_hold { + my ($class, $session, $details, $payload, %params) = @_; + + my $hold = $payload->{remote_hold} || $payload->{hold}; + + return unless $hold; + + my ($pickup_lib_id, $pickup_lib_sn); + + my $holder = OpenILS::SIP->editor()->retrieve_actor_user( + [$hold->usr, {flesh => 1, flesh_fields => {au => ['card']}}]); + + $details->{hold_patron_name} = $SC->format_user_name($holder); + + if (my $card = $holder->card) { # null-able + $details->{hold_patron_barcode} = $card->barcode; + } + + if (ref $hold->pickup_lib) { + $pickup_lib_id = $hold->pickup_lib->id; + $pickup_lib_sn = $hold->pickup_lib->shortname; + + } else { + $pickup_lib_id = $hold->pickup_lib; + $pickup_lib_sn = $SC->org_sn_from_id($hold->pickup_lib); + } + + $details->{alert} = 1; + $details->{destination_loc} = $pickup_lib_sn; + $details->{alert_type} = + ($pickup_lib_id == $session->editor->ws_ou) ? '01' : '02'; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Common.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Common.pm index 196d085f8d..c9c38c67de 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Common.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Common.pm @@ -74,6 +74,40 @@ sub get_field_value { return undef; } +my %org_sn_cache; # shortname => org +my %org_id_cache; # id => org +sub org_id_from_sn { + my ($class, $session, $org_sn) = @_; + + return undef unless $org_sn; + + my $org = $org_sn_cache{$org_sn} || + $session->editor->search_actor_org_unit({shortname => $org_sn})->[0]; + + return undef unless $org; + + $org_sn_cache{$org_sn} = $org; + $org_id_cache{$org->id} = $org; + + return $org->id; +} + +sub org_sn_from_id { + my ($class, $session, $org_id) = @_; + + return undef unless $org_id; + + my $org = $org_id_cache{$org_id} || + $session->editor->retrieve_actor_org_unit($org_id); + + return undef unless $org; + + $org_sn_cache{$org->shortname} = $org; + $org_id_cache{$org_id} = $org; + + return $org->shortname; +} + # Determines which class of data the SIP client wants detailed # information on in the patron info request. sub patron_summary_list_items { @@ -90,4 +124,14 @@ sub patron_summary_list_items { return ''; } +sub format_user_name { + my ($class, $user) = @_; + return sprintf('%s%s%s', + $user->first_given_name ? $user->first_given_name : '', + $user->second_given_name ? ' ' . $user->second_given_name : '', + $user->family_name ? ' ' . $user->family_name : '' + ); +} + + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Item.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Item.pm index 540608a0b0..00f623aadb 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Item.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Item.pm @@ -39,6 +39,10 @@ sub get_item_details { my $details = { item => $item, security_marker => '02' # matches SIP/Item.pm + owning_loc => $item->call_number->owning_lib->sn, + current_loc => $item->circ_lib->shortname, + permanent_loc => $item->circ_lib->shortname, + destination_loc => $item->circ_lib->shortname # maybe replaced below }; $details->{circ} = $e->search_action_circulation([{ @@ -64,7 +68,6 @@ sub get_item_details { $due_date->strftime('%F %T'); } - if ($item->status->id == OILS_COPY_STATUS_IN_TRANSIT) { $details->{transit} = $e->search_action_transit_copy([{ target_copy => $item->id, @@ -74,6 +77,8 @@ sub get_item_details { flesh => 1, flesh_fields => {atc => ['dest']} }])->[0]; + + $details->{destination_loc} = $details->{transit}->dest->shortname; } if ($item->status->id == OILS_COPY_STATUS_ON_HOLDS_SHELF || ( @@ -92,7 +97,6 @@ sub get_item_details { }])->[0]; } - if (my $hold = $details->{hold}) { my $pickup_date = $hold->shelf_expire_time; $details->{hold_pickup_date} = @@ -100,6 +104,7 @@ sub get_item_details { my $card = $e->search_actor_card({usr => $hold->usr})->[0]; $details->{hold_patron_barcode} = $card->barcode if $card; + $details->{destination_loc} = $hold->pickup_lib->shortname; } my ($title_entry) = grep {$_->name eq 'title'} diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sip-config.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sip-config.sql index d44889d05c..57f2c43be8 100644 --- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sip-config.sql +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sip-config.sql @@ -110,6 +110,10 @@ VALUES ( (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 'Checkin override copy status missing', 'checkin.override.COPY_STATUS_MISSING', 'true' +), ( + (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), + 'Checkin local holds as transits', + 'checkin_hold_as_transit', 'false' ); /* EXAMPLE SETTINGS -- 2.11.0