From 17b59ca81aa55dfc30faba285c55794e1e9a91f1 Mon Sep 17 00:00:00 2001 From: Thomas Berezansky Date: Thu, 14 Jul 2011 16:49:05 -0400 Subject: [PATCH] Auto-retarget local holds on "new" copy checkin When a new item is checked in local holds will be searched and re-targeted. The search and retarget stops when a hold is found locally that the item can fill. Already-captured holds will not be retargeted. "New" is (currently) defined as "In progress" at checkin Retarget occurs: Only if the copy's circ library is the checkin library Only if the checkin is an actual checkin (not a renewal) Only if the checkin is capturing holds and transits Signed-off-by: Thomas Berezansky Signed-off-by: Jason Etheridge --- .../lib/OpenILS/Application/Circ/Circulate.pm | 69 ++++++++++++++++++++++ .../Application/Storage/Publisher/action.pm | 6 +- Open-ILS/web/opac/locale/en-US/lang.dtd | 5 ++ Open-ILS/xul/staff_client/server/circ/checkin.js | 38 ++++++++++++ .../staff_client/server/circ/checkin_overlay.xul | 7 +++ Open-ILS/xul/staff_client/server/skin/circ.css | 4 ++ 6 files changed, 128 insertions(+), 1 deletion(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm index 41f90e2dd3..dd114b1b90 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm @@ -536,6 +536,7 @@ my @AUTOLOAD_FIELDS = qw/ use_booking generate_lost_overdue clear_expired + retarget_mode /; @@ -2294,6 +2295,73 @@ sub check_transit_checkin_interval { if $horizon > DateTime->now; } +# Retarget local holds at checkin +sub checkin_retarget { + my $self = shift; + return unless $self->retarget_mode =~ m/retarget/; # Retargeting? + return unless $self->is_checkin; # Renewals need not be checked + return if $self->capture eq 'nocapture'; # Not capturing holds anyway? Move on. + return if $self->is_precat; # No holds for precats + return unless $self->circ_lib == $self->copy->circ_lib; # Item isn't "home"? Don't check. + return unless $self->copy->holdable; # Not holdable, shouldn't capture holds. + # Specifically target items that are likely new (by status ID) + unless ($self->retarget_mode =~ m/\.all/) { + my $status = $U->copy_status($self->copy->status)->id; + return unless $status == OILS_COPY_STATUS_IN_PROCESS; + } + + # Fetch holds for the bib + my ($result) = $holdcode->method_lookup('open-ils.circ.holds.retrieve_all_from_title')->run( + $self->editor->authtoken, + $self->title->id, + { + capture_time => undef, # No touching captured holds + frozen => 'f', # Don't bother with frozen holds + pickup_lib => $self->circ_lib # Only holds actually here + }); + + # Error? Skip the step. + return if exists $result->{"ilsevent"}; + + # Assemble holds + my $holds = []; + foreach my $holdlist (keys %{$result}) { + push @$holds, @{$result->{$holdlist}}; + } + + return if scalar(@$holds) == 0; # No holds, no retargeting + + # Loop over holds in request-ish order + # Stage 1: Get them into request-ish order + # Also grab type and target for skipping low hanging ones + $result = $self->editor->json_query({ + "select" => { "ahr" => ["id", "hold_type", "target"] }, + "from" => { "ahr" => { "au" => { "fkey" => "usr", "join" => "pgt"} } }, + "where" => { "id" => $holds }, + "order_by" => [ + { "class" => "pgt", "field" => "hold_priority"}, + { "class" => "ahr", "field" => "cut_in_line", "direction" => "desc", "transform" => "coalesce", "params" => ['f']}, + { "class" => "ahr", "field" => "selection_depth", "direction" => "desc"}, + { "class" => "ahr", "field" => "request_time"} + ] + }); + + # Stage 2: Loop! + if (ref $result eq "ARRAY" and scalar @$result) { + foreach (@{$result}) { + # Copy level, but not this copy? + next if ($_->{hold_type} eq 'C' or $_->{hold_type} eq 'R' or $_->{hold_type} eq 'F' + and $_->{target} != $self->copy->id); + # Volume level, but not this volume? + next if ($_->{hold_type} eq 'V' and $_->{target} != $self->volume->id); + # So much for easy stuff, attempt a retarget! + my $tresult = $U->storagereq('open-ils.storage.action.hold_request.copy_targeter', undef, $_->{id}, $self->copy->id); + if(ref $tresult eq "ARRAY" and scalar @$tresult) { + last if(exists $tresult->[0]->{found_copy} and $tresult->[0]->{found_copy}); + } + } + } +} sub do_checkin { my $self = shift; @@ -2304,6 +2372,7 @@ sub do_checkin { unless $self->copy; $self->check_transit_checkin_interval; + $self->checkin_retarget; # the renew code and mk_env should have already found our circulation object unless( $self->circ ) { diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm index 23717a78d8..d54575db14 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm @@ -1042,6 +1042,7 @@ sub new_hold_copy_targeter { my $client = shift; my $check_expire = shift; my $one_hold = shift; + my $find_copy = shift; local $OpenILS::Application::Storage::WRITE = 1; @@ -1288,6 +1289,8 @@ sub new_hold_copy_targeter { } my $copy_count = @$all_copies; + my $found_copy = undef; + $found_copy = 1 if($find_copy and grep $_ == $find_copy, @$all_copies); # map the potentials, so that we can pick up checkins # XXX Loop-based targeting may require that /only/ copies from this loop should be added to @@ -1489,7 +1492,8 @@ sub new_hold_copy_targeter { { hold => $hold->id, old_target => ($old_best ? $old_best->id : undef), eligible_copies => $copy_count, - target => ($best ? $best->id : undef) }; + target => ($best ? $best->id : undef), + found_copy => $found_copy }; } otherwise { my $e = shift; diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index 5be57e4a89..822311f28f 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -2217,6 +2217,11 @@ + + + + + diff --git a/Open-ILS/xul/staff_client/server/circ/checkin.js b/Open-ILS/xul/staff_client/server/circ/checkin.js index 9241083806..6984150f44 100644 --- a/Open-ILS/xul/staff_client/server/circ/checkin.js +++ b/Open-ILS/xul/staff_client/server/circ/checkin.js @@ -418,6 +418,36 @@ circ.checkin.prototype = { ind.hidden = cb.getAttribute('checked') != 'true'; document.getElementById('checkin_barcode_entry_textbox').focus(); return true; + } ], + 'cmd_checkin_auto_retarget' : [ ['command'], function(ev) { + dump('in cmd_checkin_auto_retarget\n'); + var bg = document.getElementById('background'); + var cb = document.getElementById('checkin_auto_retarget'); + var cb2 = document.getElementById('checkin_auto_retarget_all'); + var ind = document.getElementById('checkin_auto_retarget_indicator'); + var ind2 = document.getElementById('checkin_auto_retarget_all_indicator'); + var cn = 'checkin_screen_checkin_auto_retarget'; + var cn2 = 'checkin_screen_checkin_auto_retarget_all'; + if (cb.getAttribute('checked') == 'true') { + if(cb2.getAttribute('checked') == 'true') { + removeCSSClass(bg,cn); + addCSSClass(bg,cn2); + ind.hidden = true; + ind2.hidden = false; + } else { + addCSSClass(bg,cn); + removeCSSClass(bg,cn2); + ind.hidden = false; + ind2.hidden = true; + } + } else { + removeCSSClass(bg,cn); + removeCSSClass(bg,cn2); + ind.hidden = true; + ind2.hidden = true; + } + document.getElementById('checkin_barcode_entry_textbox').focus(); + return true; } ] } } @@ -580,6 +610,14 @@ circ.checkin.prototype = { var clear_shelf_expired_holds = document.getElementById('checkin_clear_shelf_expired'); if (clear_shelf_expired_holds) clear_shelf_expired_holds = clear_shelf_expired_holds.getAttribute('checked') == 'true'; if (clear_shelf_expired_holds) params.clear_expired = 1; + var auto_retarget = document.getElementById('checkin_auto_retarget'); + if (auto_retarget) auto_retarget = auto_retarget.getAttribute('checked') == 'true'; + if (auto_retarget) { + var retarget_all = document.getElementById('checkin_auto_retarget_all'); + if (retarget_all) retarget_all = retarget_all.getAttribute('checked') == 'true'; + if (retarget_all) params.retarget_mode = 'retarget.all'; + else params.retarget_mode = 'retarget'; + } circ.util.checkin_via_barcode( ses(), params, diff --git a/Open-ILS/xul/staff_client/server/circ/checkin_overlay.xul b/Open-ILS/xul/staff_client/server/circ/checkin_overlay.xul index 4ea1a74c6b..18dff0ed3f 100644 --- a/Open-ILS/xul/staff_client/server/circ/checkin_overlay.xul +++ b/Open-ILS/xul/staff_client/server/circ/checkin_overlay.xul @@ -36,6 +36,7 @@ + @@ -76,6 +77,8 @@ + + @@ -177,6 +180,10 @@ label="&staff.circ.checkin_overlay.checkin_auto_print_slips.label;" accesskey="&staff.circ.checkin_overlay.checkin_auto_print_slips.accesskey;"/> + + diff --git a/Open-ILS/xul/staff_client/server/skin/circ.css b/Open-ILS/xul/staff_client/server/skin/circ.css index 049e4fbe55..d73071f34f 100644 --- a/Open-ILS/xul/staff_client/server/skin/circ.css +++ b/Open-ILS/xul/staff_client/server/skin/circ.css @@ -46,6 +46,8 @@ treechildren::-moz-tree-row(backdate_failed,selected) { .checkin_screen_amnesty_mode { } .checkin_screen_checkin_auto_print_slips { } .checkin_screen_checkin_clear_shelf_expired { } +.checkin_screen_checkin_auto_retarget { } +.checkin_screen_checkin_auto_retarget_all { } #background-text { font-size: x-large; font-weight: bold; } #do_not_alert_on_precat_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; } @@ -53,6 +55,8 @@ treechildren::-moz-tree-row(backdate_failed,selected) { #amnesty_mode_indicator { border: thick solid white; background-color: red; color: white; font-size: large; font-weight: bold; padding: 10px; padding-bottom: 25px; margin: 10px; } #checkin_auto_print_slips_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; } #checkin_clear_shelf_expired_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; } +#checkin_auto_retarget_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; } +#checkin_auto_retarget_all_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; } .big_emphasis1 { font-weight: bold; font-size: x-large; } .big_emphasis2 { font-weight: bold; font-size: large; } -- 2.11.0