LP1901930 SIP2Mediator checkin support
authorBill Erickson <berickxx@gmail.com>
Fri, 30 Oct 2020 21:56:35 +0000 (17:56 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 30 Oct 2020 21:56:35 +0000 (17:56 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Checkin.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Common.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Item.pm
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sip-config.sql

index 820edd5..13ac5a2 100644 (file)
@@ -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 (file)
index 0000000..7c65cf0
--- /dev/null
@@ -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;
index 196d085..c9c38c6 100644 (file)
@@ -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;
index 540608a..00f623a 100644 (file)
@@ -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'}
index d44889d..57f2c43 100644 (file)
@@ -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