moving the hold copy targeter into the storage server (speed and convenience)
authormiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 21 Jul 2005 22:35:28 +0000 (22:35 +0000)
committermiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 21 Jul 2005 22:35:28 +0000 (22:35 +0000)
git-svn-id: svn://svn.open-ils.org/ILS/trunk@1353 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm
Open-ILS/src/support-scripts/hold-copy-capture.pl [deleted file]

index 97f2557..fe69771 100644 (file)
@@ -1,6 +1,13 @@
 package OpenILS::Application::Storage::Publisher::action;
 use base qw/OpenILS::Application::Storage/;
 use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::EX qw/:try/;
+use OpenILS::Utils::Fieldmapper;
+use DateTime;
+use DateTime::Format::ISO8601;
+
+my $parser = DateTime::Format::ISO8601->new;
 my $log = 'OpenSRF::Utils::Logger';
 
 sub grab_overdue {
@@ -253,4 +260,411 @@ __PACKAGE__->register_method(
 );
 
 
+sub generate_fines {
+       my $self = shift;
+       my $client = shift;
+       my $grace = shift;
+       my $circ = shift;
+       
+       
+       my @circs;
+       if ($circ) {
+               push @circs,
+                       $self->method_lookup(
+                               'open-ils.storage.direct.action.circulation.search_where'
+                       )->run( { id => $circ, stop_fines => undef } );
+       } else {
+               push @circs, $self->method_lookup('open-ils.storage.action.circulation.overdue')->run( $grace );
+       }
+
+       for my $c (@circs) {
+       
+               try {
+                       my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
+       
+                       my $due = $due_dt->epoch;
+                       my $now = time;
+                       my $fine_interval = interval_to_seconds( $c->fine_interval );
+       
+                       if ( interval_to_seconds( $c->fine_interval ) >= interval_to_seconds('1d') ) {  
+                               my $tz_offset_s = 0;;
+                               if ($due_dt->strftime('%z') =~ /(-|\+)(\d{2}):?(\d{2})/) {
+                                       $tz_offset_s = $1 . interval_to_seconds( "${2}h ${3}m"); 
+                               }
+       
+                               $due -= ($due % $fine_interval) + $tz_offset_s;
+                               $now -= ($now % $fine_interval) + $tz_offset_s;
+                       }
+       
+                       $client->respond(
+                               "ARG! Overdue circulation ".$c->id.
+                               " for item ".$c->target_copy.
+                               " (user ".$c->usr.").\n".
+                               "\tItem was due on or before: ".localtime($due)."\n");
+       
+                       my ($fine) = $self->method_lookup('open-ils.storage.direct.money.billing.search')->run(
+                               { xact => $c->id, voided => 'f' },
+                               { order_by => 'billing_ts DESC', limit => '1' }
+                       );
+       
+                       my $last_fine;
+                       if ($fine) {
+                               $last_fine = $parser->parse_datetime( clense_ISO8601( $fine->billing_ts ) )->epoch;
+                       } else {
+                               $last_fine = $due;
+                               $last_fine += $fine_interval * $grace;
+                       }
+       
+                       my $pending_fine_count = int( ($now - $last_fine) / $fine_interval ); 
+                       unless($pending_fine_count) {
+                               $client->respond( "\tNo fines to create.  " );
+                               if ($grace && $now < $due + $fine_interval * $grace) {
+                                       $client->respond( "Still inside grace period of: ". seconds_to_interval( $fine_interval * $grace)."\n" );
+                               } else {
+                                       $client->respond( "Last fine generated for: ".localtime($last_fine)."\n" );
+                               }
+                               next;
+                       }
+       
+                       $client->respond( "\t$pending_fine_count pending fine(s)\n" );
+       
+                       for my $bill (1 .. $pending_fine_count) {
+       
+                               my ($total) = $self->method_lookup('open-ils.storage.direct.money.billable_transaction_summary.retrieve')->run( $c->id );
+       
+                               if ($total && $total->balance_owed > $c->max_fine) {
+                                       $c->stop_fines('MAXFINES');
+                                       $self->method_lookup('open-ils.storage.direct.action.circulation.update')->run( $c );
+                                       $client->respond(
+                                               "\tMaximum fine level of ".$c->max_fine.
+                                               " reached for this circulation.\n".
+                                               "\tNo more fines will be generated.\n" );
+                                       last;
+                               }
+       
+                               my $billing = new Fieldmapper::money::billing;
+                               $billing->xact( $c->id );
+                               $billing->note( "Overdue Fine" );
+                               $billing->amount( $c->recuring_fine );
+       
+                               $billing->billing_ts(
+                                       DateTime->from_epoch( epoch => $last_fine + $fine_interval * $bill )->strftime('%FT%T%z')
+                               );
+       
+                               $client->respond(
+                                       "\t\tCreating fine of ".$billing->amount." for period starting ".
+                                       localtime(
+                                               $parser->parse_datetime(
+                                                       clense_ISO8601( $billing->billing_ts )
+                                               )->epoch
+                                       )."\n" );
+       
+                               $self->method_lookup('open-ils.storage.direct.money.billing.create')->run( $billing );
+                       }
+               } catch Error with {
+                       my $e = shift;
+                       $client->respond( "Error processing overdue circulation [".$c->id."]:\n\n$e\n" );
+               };
+       }
+}
+__PACKAGE__->register_method(
+       api_name        => 'open-ils.storage.action.circulation.overdue.generate_fines',
+       api_level       => 1,
+       stream          => 1,
+       method          => 'generate_fines',
+);
+
+
+
+my $locations;
+my $statuses;
+my $user_filter;
+my %cache = (titles => {}, cns => {});
+sub hold_copy_targeter {
+       my $self = shift;
+       my $client = shift;
+       my $check_expire = shift;
+       my $one_hold = shift;
+
+       $user_filter ||= $self->method_lookup('open-ils.circ.permit_hold');
+
+       my $time = time;
+       $check_expire ||= '12h';
+       $check_expire = interval_to_seconds( $check_expire );
+
+       my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time - $check_expire);
+       $year += 1900;
+       $mon += 1;
+       my $expire_threshold = sprintf(
+               '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
+               $year, $mon, $mday, $hour, $min, $sec
+       );
+
+       ($statuses) = $self->method_lookup('open-ils.storage.direct.config.copy_status.search.holdable.atomic')->run('t');
+
+       ($locations) = $self->method_lookup('open-ils.storage.direct.asset.copy_location.search.holdable.atomic')->run('t');
+
+       my $holds;
+
+       %cache = (titles => {}, cns => {});
+
+       try {
+               if ($one_hold) {
+                       ($holds) = $self->method_lookup('open-ils.storage.direct.action.hold_request.search.atomic')
+                                               ->run(id => $one_hold);
+               } else {
+                       ($holds) = $self->method_lookup('open-ils.storage.direct.action.hold_request.search_where.atomic')
+                                               ->run(
+                                                       { capture_time => undef,
+                                                         prev_check_time => { '<=' => $expire_threshold },
+                                                       },
+                                                       { order_by => 'request_time,prev_check_time' } );
+                       push @$holds, @{
+                               $self->method_lookup('open-ils.storage.direct.action.hold_request.search.atomic')
+                                               ->run(
+                                                       { capture_time => undef,
+                                                         prev_check_time => undef },
+                                                       { order_by => 'request_time' } ) };
+               }
+       } catch Error with {
+               my $e = shift;
+               die "Could not retrieve uncaptured hold requests:\n\n$e\n";
+       };
+
+       $_->clear_current_copy for (@$holds);
+
+       for my $hold (@$holds) {
+               my $copies;
+
+               my @captured_copies = [ map {$_->current_copy} @$holds ];
+
+               if (0) { # hold isn't check-expired
+                       # get the copies from the hold-map
+                       # and filter on "avialable"
+               } else {
+                       $copies = $self->metarecord_hold_capture($hold) if ($hold->hold_type eq 'M');
+                       $copies = $self->title_hold_capture($hold) if ($hold->hold_type eq 'T');
+                       $copies = $self->volume_hold_capture($hold) if ($hold->hold_type eq 'V');
+                       $copies = $self->copy_hold_capture($hold) if ($hold->hold_type eq 'C');
+               }
+
+               next unless (ref $copies);
+
+               my @good_copies;
+               for my $c (@$copies) {
+                       next if ( grep {$c->id == $_} @captured_copies);
+                       push @good_copies, $c;
+               }
+
+               my $prox_list;
+               $$prox_list[0] = [grep {$_->circ_lib == $hold->pickup_lib } @$copies];
+               $copies = [grep {$_->circ_lib != $hold->pickup_lib } @$copies];
+
+               my $best = $self->choose_nearest_copy($hold, $prox_list);
+
+               if (!$best) {
+                       $prox_list = $self->create_prox_list( $hold->pickup_lib, $copies );
+                       $best = $self->choose_nearest_copy($hold, $prox_list);
+               }
+
+               if ($best) {
+                       $hold->current_copy( $best->id );
+               }
+
+               $hold->prev_check_time( 'now');
+               my ($r) = $self->method_lookup('open-ils.storage.direct.action.hold_request.update')->run( $hold );
+
+               $client->respond("Processed hold ".$hold->id.".  ".
+                       do{ $hold->current_copy ? "Targeting copy ".$hold->current_copy." for capture." : ''; }.
+                       "\n"
+               );
+       }
+       return undef;
+}
+__PACKAGE__->register_method(
+       api_name        => 'open-ils.storage.action.hold_request.copy_targeter',
+       api_level       => 1,
+       stream          => 1,
+       method          => 'hold_copy_targeter',
+);
+
+
+
+sub copy_hold_capture {
+       my $self = shift;
+       my $hold = shift;
+       my $cps = shift;
+
+       if (!defined($cps)) {
+               try {
+                       ($cps) = $self->method_lookup('open-ils.storage.direct.asset.copy.search.id.atomic')
+                                               ->run( $hold->target );
+       
+               } catch Error with {
+                       my $e = shift;
+                       die "Could not retrieve initial volume list:\n\n$e\n";
+               };
+       }
+
+       my @copies = grep { $_->holdable == 1  and $_->ref == 0 } @$cps;
+
+       for (my $i = 0; $i < @copies; $i++) {
+               
+               my $cn = $cache{cns}{$copies[0]->call_number};
+               my $rec = $cache{titles}{$cn->record};
+               $copies[$i] = undef if ($copies[$i] && !grep{ $copies[$i]->status eq $_->id}@$statuses);
+               $copies[$i] = undef if ($copies[$i] && !grep{ $copies[$i]->location eq $_->id}@$locations);
+               $copies[$i] = undef if (
+                       $copies[$i] &&
+                       !($user_filter->run( $hold, $copies[$i], { title => $rec, call_number => $cn } ))[0]
+               );
+       }
+
+       @copies = grep { defined $_ } @copies;
+
+       my $count = @copies;
+
+       return unless ($count);
+       
+       my ($old_maps) = $self->method_lookup('open-ils.storage.direct.action.hold_copy_map.search.hold.atomic')->run( $hold->id );
+
+       $self->method_lookup('open-ils.storage.direct.action.hold_copy_map.batch.delete')->run(@$old_maps );
+       
+       my @maps;
+       for my $c (@copies) {
+               my $m = new Fieldmapper::action::hold_copy_map;
+               $m->hold( $hold->id );
+               $m->target_copy( $c->id );
+               $m->isnew( 1 );
+               push @maps, $m;
+       }
+
+       $self->method_lookup('open-ils.storage.direct.action.hold_copy_map.batch.create')->run( @maps );
+
+       return \@copies;
+}
+
+
+sub choose_nearest_copy {
+       my $self = shift;
+       my $hold = shift;
+       my $prox_list = shift;
+
+       for my $p ( 0 .. int( scalar(@$prox_list) - 1) ) {
+               next unless (ref $$prox_list[$p]);
+               my @capturable = grep { $_->status == 0 } @{ $$prox_list[$p] };
+               next unless (@capturable);
+               return $capturable[rand(scalar(@capturable))];
+       }
+}
+
+sub create_prox_list {
+       my $self = shift;
+       my $lib = shift;
+       my $copies = shift;
+
+       my @prox_list;
+       for my $cp (@$copies) {
+               my ($prox) = $self->method_lookup('open-ils.storage.asset.copy.proximity')->run( $cp->id, $lib );
+               $prox_list[$prox] = [] unless defined($prox_list[$prox]);
+               push @{$prox_list[$prox]}, $cp;
+       }
+       return \@prox_list;
+}
+
+sub volume_hold_capture {
+       my $self = shift;
+       my $hold = shift;
+       my $vols = shift;
+
+       if (!defined($vols)) {
+               try {
+                       ($vols) = $self->method_lookup('open-ils.storage.direct.asset.call_number.search.id.atomic')->run( $hold->target );
+       
+                       $cache{cns}{$_->id} = $_ for (@$vols);
+
+               } catch Error with {
+                       my $e = shift;
+                       die "Could not retrieve initial volume list:\n\n$e\n";
+               };
+       }
+
+       my @v_ids = map { $_->id } @$vols;
+
+       my $cp_list;
+       try {
+               ($cp_list) = $self->method_lookup('open-ils.storage.direct.asset.copy.search.call_number.atomic')->run( \@v_ids );
+       
+       } catch Error with {
+               my $e = shift;
+               warn "Could not retrieve copy list:\n\n$e\n";
+       };
+
+       $self->copy_hold_capture($hold,$cp_list) if (ref $cp_list and @$cp_list);
+}
+
+sub title_hold_capture {
+       my $self = shift;
+       my $hold = shift;
+       my $titles = shift;
+
+       if (!defined($titles)) {
+               try {
+                       ($titles) = $self->method_lookup('open-ils.storage.direct.biblio.record_entry.search.id.atomic')->run( $hold->target );
+       
+                       $cache{titles}{$_->id} = $_ for (@$titles);
+       
+               } catch Error with {
+                       my $e = shift;
+                       die "Could not retrieve initial title list:\n\n$e\n";
+               };
+       }
+
+       my @t_ids = map { $_->id } @$titles;
+       my $cn_list;
+       try {
+               ($cn_list) = $self->method_lookup('open-ils.storage.direct.asset.call_number.search.record.atomic')->run( \@t_ids );
+       
+       } catch Error with {
+               my $e = shift;
+               warn "Could not retrieve volume list:\n\n$e\n";
+       };
+
+       $cache{cns}{$_->id} = $_ for (@$cn_list);
+
+       $self->volume_hold_capture($hold,$cn_list) if (ref $cn_list and @$cn_list);
+}
+
+sub metarecord_hold_capture {
+       my $self = shift;
+       my $hold = shift;
+
+       my $titles;
+       try {
+               ($titles) = $self->method_lookup('open-ils.storage.ordered.metabib.metarecord.records.atomic')->run( $hold->target );
+       
+       } catch Error with {
+               my $e = shift;
+               die "Could not retrieve initial title list:\n\n$e\n";
+       };
+
+       try {
+               my @recs = map {$_->record}
+                               @{$self->method_lookup('open-ils.storage.direct.metabib.record_descriptor.search.atomic')
+                                               ->run( record => $titles, item_type => [split '', $hold->holdable_formats] )}; 
+
+               $titles = [];
+               ($titles) = $self->method_lookup('open-ils.storage.direct.biblio.record_entry.search.id.atomic')->run( \@recs );
+       
+       } catch Error with {
+               my $e = shift;
+               die "Could not retrieve format-pruned title list:\n\n$e\n";
+       };
+
+
+       $cache{titles}{$_->id} = $_ for (@$titles);
+
+       $self->title_hold_capture($hold,$titles) if (ref $titles and @$titles);
+}
+
 1;
index e0ea9c5..e7a7e6d 100644 (file)
@@ -1,147 +1,7 @@
 package OpenILS::Application::Storage::Publisher::money;
 use base qw/OpenILS::Application::Storage/;
-use OpenSRF::Utils qw/:datetime/;
 use OpenSRF::Utils::Logger qw/:level/;
-use OpenSRF::EX qw/:try/;
-use OpenILS::Utils::Fieldmapper;
-use DateTime;
-use DateTime::Format::ISO8601;
 
 my $log = 'OpenSRF::Utils::Logger';
 
-sub xact_summary {
-       my $self = shift;
-       my $client = shift;
-       my $xact = shift || '';
-
-       my $sql = <<"   SQL";
-               SELECT  balance_owed
-                 FROM  money.usr_billable_summary_xact
-                 WHERE transaction = ?
-       SQL
-
-       return money::billing->db_Main->selectrow_hashref($sql, {}, "$xact");
-}
-#__PACKAGE__->register_method(
-#      api_name        => 'open-ils.storage.money.billing.billable_transaction_summary',
-#      api_level       => 1,
-#      method          => 'xact_summary',
-#);
-
-my $parser = DateTime::Format::ISO8601->new;
-sub generate_fines {
-       my $self = shift;
-       my $client = shift;
-       my $grace = shift;
-       my $circ = shift;
-       
-       
-       my @circs;
-       if ($circ) {
-               push @circs,
-                       $self->method_lookup(
-                               'open-ils.storage.direct.action.circulation.search_where'
-                       )->run( { id => $circ, stop_fines => undef } );
-       } else {
-               push @circs, $self->method_lookup('open-ils.storage.action.circulation.overdue')->run( $grace );
-       }
-
-       for my $c (@circs) {
-       
-               try {
-                       my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
-       
-                       my $due = $due_dt->epoch;
-                       my $now = time;
-                       my $fine_interval = interval_to_seconds( $c->fine_interval );
-       
-                       if ( interval_to_seconds( $c->fine_interval ) >= interval_to_seconds('1d') ) {  
-                               my $tz_offset_s = 0;;
-                               if ($due_dt->strftime('%z') =~ /(-|\+)(\d{2}):?(\d{2})/) {
-                                       $tz_offset_s = $1 . interval_to_seconds( "${2}h ${3}m"); 
-                               }
-       
-                               $due -= ($due % $fine_interval) + $tz_offset_s;
-                               $now -= ($now % $fine_interval) + $tz_offset_s;
-                       }
-       
-                       $client->respond(
-                               "ARG! Overdue circulation ".$c->id.
-                               " for item ".$c->target_copy.
-                               " (user ".$c->usr.").\n".
-                               "\tItem was due on or before: ".localtime($due)."\n");
-       
-                       my ($fine) = $self->method_lookup('open-ils.storage.direct.money.billing.search')->run(
-                               { xact => $c->id, voided => 'f' },
-                               { order_by => 'billing_ts DESC', limit => '1' }
-                       );
-       
-                       my $last_fine;
-                       if ($fine) {
-                               $last_fine = $parser->parse_datetime( clense_ISO8601( $fine->billing_ts ) )->epoch;
-                       } else {
-                               $last_fine = $due;
-                               $last_fine += $fine_interval * $grace;
-                       }
-       
-                       my $pending_fine_count = int( ($now - $last_fine) / $fine_interval ); 
-                       unless($pending_fine_count) {
-                               $client->respond( "\tNo fines to create.  " );
-                               if ($grace && $now < $due + $fine_interval * $grace) {
-                                       $client->respond( "Still inside grace period of: ". seconds_to_interval( $fine_interval * $grace)."\n" );
-                               } else {
-                                       $client->respond( "Last fine generated for: ".localtime($last_fine)."\n" );
-                               }
-                               next;
-                       }
-       
-                       $client->respond( "\t$pending_fine_count pending fine(s)\n" );
-       
-                       for my $bill (1 .. $pending_fine_count) {
-       
-                               my ($total) = $self->method_lookup('open-ils.storage.direct.money.billable_transaction_summary.retrieve')->run( $c->id );
-       
-                               if ($total && $total->balance_owed > $c->max_fine) {
-                                       $c->stop_fines('MAXFINES');
-                                       $self->method_lookup('open-ils.storage.direct.action.circulation.update')->run( $c );
-                                       $client->respond(
-                                               "\tMaximum fine level of ".$c->max_fine.
-                                               " reached for this circulation.\n".
-                                               "\tNo more fines will be generated.\n" );
-                                       last;
-                               }
-       
-                               my $billing = new Fieldmapper::money::billing;
-                               $billing->xact( $c->id );
-                               $billing->note( "Overdue Fine" );
-                               $billing->amount( $c->recuring_fine );
-       
-                               $billing->billing_ts(
-                                       DateTime->from_epoch( epoch => $last_fine + $fine_interval * $bill )->strftime('%FT%T%z')
-                               );
-       
-                               $client->respond(
-                                       "\t\tCreating fine of ".$billing->amount." for period starting ".
-                                       localtime(
-                                               $parser->parse_datetime(
-                                                       clense_ISO8601( $billing->billing_ts )
-                                               )->epoch
-                                       )."\n" );
-       
-                               $self->method_lookup('open-ils.storage.direct.money.billing.create')->run( $billing );
-                       }
-               } catch Error with {
-                       my $e = shift;
-                       $client->respond( "Error processing overdue circulation [".$c->id."]:\n\n$e\n" );
-               };
-       }
-}
-__PACKAGE__->register_method(
-       api_name        => 'open-ils.storage.action.circulation.overdue.generate_fines',
-       api_level       => 1,
-       stream          => 1,
-       method          => 'generate_fines',
-);
-
-
 1;
diff --git a/Open-ILS/src/support-scripts/hold-copy-capture.pl b/Open-ILS/src/support-scripts/hold-copy-capture.pl
deleted file mode 100755 (executable)
index 6449fe1..0000000
+++ /dev/null
@@ -1,354 +0,0 @@
-#!/usr/bin/perl -w
-use strict;
-use OpenSRF::EX qw/:try/;
-use OpenSRF::System;
-use OpenSRF::Application;
-use OpenSRF::Utils::SettingsClient;
-use OpenILS::Utils::Fieldmapper;
-use OpenSRF::Utils;
-
-die "USAGE:\n\t$0 config_file\n" unless @ARGV;
-
-
-# hard coded for now, option later
-
-my $time = time;
-my $check_expire = OpenSRF::Utils::interval_to_seconds( '10m' );
-
-my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time - $check_expire);
-$year += 1900;
-$mon += 1;
-my $expire_threshold = sprintf(
-       '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
-       $year, $mon, $mday, $hour, $min, $sec
-);
-
-OpenSRF::System->bootstrap_client( config_file => $ARGV[0] );
-my $session = OpenSRF::AppSession->create('open-ils.storage');
-
-my $module = OpenSRF::Utils::SettingsClient
-       ->new
-       ->config_value('apps','open-ils.circ','implementation');
-
-eval "use $module; $module->initialize;";
-die "Can't load the open-ils.circ module [$module] : $@\n" if ($@);
-
-my $user_filter = OpenSRF::Application->method_lookup('open-ils.circ.permit_hold');
-
-my $statuses = $session->request(
-       'open-ils.storage.direct.config.copy_status.search.holdable.atomic',
-       't')->gather(1);
-
-my $locations = $session->request(
-       'open-ils.storage.direct.asset.copy_location.search.holdable.atomic',
-       't')->gather(1);
-
-my $holds;
-
-my %cache = (titles => {}, cns => {});
-
-try {
-       if ($ARGV[1]) {
-               $holds = $session->request(
-                               'open-ils.storage.direct.action.hold_request.search.atomic',
-                               id => $ARGV[1] )->gather(1);
-       } else {
-               $holds = $session->request(
-                               'open-ils.storage.direct.action.hold_request.search_where.atomic',
-                               { capture_time => undef,
-                                 prev_check_time => { '<=' => $expire_threshold },
-                               },
-                               { order_by => 'request_time,prev_check_time' } )->gather(1);
-               push @$holds, @{
-                       $session->request(
-                               'open-ils.storage.direct.action.hold_request.search.atomic',
-                               { capture_time => undef,
-                                 prev_check_time => undef },
-                               { order_by => 'request_time,prev_check_time' } )->gather(1)
-               };
-       }
-} catch Error with {
-       my $e = shift;
-       die "Could not retrieve uncaptured hold requests:\n\n$e\n";
-};
-
-$_->clear_current_copy for (@$holds);
-
-for my $hold (@$holds) {
-       my $copies;
-
-       my @captured_copies = [ map {$_->current_copy} @$holds ];
-
-       if (0) { # hold isn't check-expired
-               # get the copies from the hold-map
-               # and filter on "avialable"
-       } else {
-               $copies = metarecord_hold_capture($hold) if ($hold->hold_type eq 'M');
-               $copies = title_hold_capture($hold) if ($hold->hold_type eq 'T');
-               $copies = volume_hold_capture($hold) if ($hold->hold_type eq 'V');
-               $copies = copy_hold_capture($hold) if ($hold->hold_type eq 'C');
-       }
-
-       next unless (ref $copies);
-
-       my @good_copies;
-       for my $c (@$copies) {
-               next if ( grep {$c->id == $_} @captured_copies);
-               push @good_copies, $c;
-       }
-
-       my $prox_list;
-       $$prox_list[0] = [grep {$_->circ_lib == $hold->pickup_lib } @$copies];
-       $copies = [grep {$_->circ_lib != $hold->pickup_lib } @$copies];
-
-       my $best = choose_nearest_copy($hold, $prox_list);
-
-       if (!$best) {
-               $prox_list = create_prox_list( $hold->pickup_lib, $copies );
-               $best = choose_nearest_copy($hold, $prox_list);
-       }
-
-       if ($best) {
-               print "Updating hold ".$hold->id." with current_copy ".$best->id."\n";
-               $hold->current_copy( $best->id );
-       }
-
-       $hold->prev_check_time( 'now');
-       $session->request(
-               'open-ils.storage.direct.action.hold_request.update',
-               $hold )->gather(1) ||
-                       warn "Could not save hold ".$hold->id."\n";
-       print '-'x80 . "\n";
-}
-
-
-sub copy_hold_capture {
-       my $hold = shift;
-       my $cps = shift;
-
-       if (!defined($cps)) {
-               try {
-                       $cps = $session->request(
-                               'open-ils.storage.direct.asset.copy.search.id.atomic',
-                               $hold->target )->gather(1);
-       
-               } catch Error with {
-                       my $e = shift;
-                       die "Could not retrieve initial volume list:\n\n$e\n";
-               };
-       }
-
-       my @copies = grep { $_->holdable == 1  and $_->ref == 0 } @$cps;
-
-       print "Applying user defined filters for hold ".$hold->id."...\n";
-       for (my $i = 0; $i < @copies; $i++) {
-               
-               my $cn = $cache{cns}{$copies[0]->call_number};
-               my $rec = $cache{titles}{$cn->record};
-               $copies[$i] = undef if ($copies[$i] && !grep{ $copies[$i]->status eq $_->id}@$statuses);
-               $copies[$i] = undef if ($copies[$i] && !grep{ $copies[$i]->location eq $_->id}@$locations);
-               $copies[$i] = undef if (
-                       $copies[$i] &&
-                       !$user_filter->run( $hold, $copies[$i], { title => $rec, call_number => $cn } )
-               );
-       }
-
-       @copies = grep { defined $_ } @copies;
-
-       my $count = @copies;
-
-       return unless ($count);
-       
-       print "Saving $count eligible copies for hold ".$hold->id.":\n";
-
-       my $old_maps = $session->request(
-               'open-ils.storage.direct.action.hold_copy_map.search.hold.atomic',
-               $hold->id )->gather(1);
-
-       $session->request( 'open-ils.storage.direct.action.hold_copy_map.batch.delete', @$old_maps )
-               ->gather(1) if (defined($old_maps) and @$old_maps);
-       
-       my @maps;
-       for my $c (@copies) {
-               my $m = new Fieldmapper::action::hold_copy_map;
-               $m->hold( $hold->id );
-               $m->target_copy( $c->id );
-               $m->isnew( 1 );
-               push @maps, $m;
-       }
-
-       $session->request(
-               'open-ils.storage.direct.action.hold_copy_map.batch.create',
-               @maps )->gather(1) ||
-                       warn "Could not save copies for hold ".$hold->id."\n";
-
-       return \@copies;
-}
-
-
-sub choose_nearest_copy {
-       my $hold = shift;
-       my $prox_list = shift;
-
-       for my $p ( 0 .. int( scalar(@$prox_list) - 1) ) {
-               next unless (ref $$prox_list[$p]);
-               my @capturable = grep { $_->status == 0 } @{ $$prox_list[$p] };
-               next unless (@capturable);
-               return $capturable[rand(scalar(@capturable))];
-       }
-}
-
-sub create_prox_list {
-       my $lib = shift;
-       my $copies = shift;
-
-       my @prox_list;
-       print "Creating proximity list :\n";
-       for my $cp (@$copies) {
-               my $prox = $session->request(
-                       'open-ils.storage.asset.copy.proximity',
-                       $cp->id, $lib )->gather(1);
-               print "\t".$cp->id." -> ".$cp->barcode." :: Proximity -> $prox\n";
-               $prox_list[$prox] = [] unless defined($prox_list[$prox]);
-               push @{$prox_list[$prox]}, $cp;
-       }
-       print "\n";
-       return \@prox_list;
-}
-
-sub volume_hold_capture {
-       my $hold = shift;
-       my $vols = shift;
-
-       if (!defined($vols)) {
-               try {
-                       $vols = $session->request(
-                               'open-ils.storage.direct.asset.call_number.search.id.atomic',
-                               $hold->target )->gather(1);
-       
-                       $cache{cns}{$_->id} = $_ for (@$vols);
-
-               } catch Error with {
-                       my $e = shift;
-                       die "Could not retrieve initial volume list:\n\n$e\n";
-               };
-       }
-
-       my @v_ids = map { $_->id } @$vols;
-
-       my $cp_list;
-       try {
-               $cp_list = $session->request(
-                       'open-ils.storage.direct.asset.copy.search.call_number.atomic',
-                       \@v_ids )->gather(1);
-       
-       } catch Error with {
-               my $e = shift;
-               warn "Could not retrieve copy list:\n\n$e\n";
-       };
-
-       if (ref $cp_list) {
-               my $count = @$cp_list;
-               print "Found $count possible copies for hold ".$hold->id.":\n";
-               for my $cp (@$cp_list) {
-                       print "\t".$cp->id." -> ".$cp->barcode."\n";
-               }
-               print "\n";
-       }
-
-       copy_hold_capture($hold,$cp_list) if (ref $cp_list and @$cp_list);
-}
-
-sub title_hold_capture {
-       my $hold = shift;
-       my $titles = shift;
-
-       if (!defined($titles)) {
-               try {
-                       $titles = $session->request(
-                               'open-ils.storage.direct.biblio.record_entry.search.id.atomic',
-                               $hold->target )->gather(1);
-       
-                       $cache{titles}{$_->id} = $_ for (@$titles);
-       
-               } catch Error with {
-                       my $e = shift;
-                       die "Could not retrieve initial title list:\n\n$e\n";
-               };
-       }
-
-       my @t_ids = map { $_->id } @$titles;
-       my $cn_list;
-       try {
-               $cn_list = $session->request(
-                       'open-ils.storage.direct.asset.call_number.search.record.atomic',
-                       \@t_ids )->gather(1);
-       
-       } catch Error with {
-               my $e = shift;
-               warn "Could not retrieve volume list:\n\n$e\n";
-       };
-
-       if (ref $cn_list) {
-               my $count = @$cn_list;
-               print "Found $count volumes for hold ".$hold->id.":\n";
-               for my $cn (@$cn_list) {
-                       print "\t".$cn->id." -> ".$cn->label."\n";
-               }
-               print "\n";
-       }
-
-       $cache{cns}{$_->id} = $_ for (@$cn_list);
-
-       volume_hold_capture($hold,$cn_list) if (ref $cn_list and @$cn_list);
-}
-
-sub metarecord_hold_capture {
-       my $hold = shift;
-
-       my $titles;
-       try {
-               $titles = $session->request(
-                               'open-ils.storage.ordered.metabib.metarecord.records.atomic',
-                               $hold->target
-                       )->gather(1);
-       
-       } catch Error with {
-               my $e = shift;
-               die "Could not retrieve initial title list:\n\n$e\n";
-       };
-
-       try {
-               my @recs = map {$_->record}
-                               @{$session->request(
-                                       'open-ils.storage.direct.metabib.record_descriptor.search.atomic',
-                                       record => $titles,
-                                       item_type => [split '', $hold->holdable_formats],
-                               )->gather(1)};
-
-               $titles = [];
-               $titles = $session->request(
-                       'open-ils.storage.direct.biblio.record_entry.search.id.atomic',
-                       \@recs )->gather(1) if (@recs);
-       
-       } catch Error with {
-               my $e = shift;
-               die "Could not retrieve format-pruned title list:\n\n$e\n";
-       };
-
-
-       if (ref $titles) {
-               my $count = @$titles;
-               print "Found $count titles for hold ".$hold->id.":\n";
-               for my $title (@$titles) {
-                       print "\t".$title->tcn_value." -> ".$title->fingerprint."\n";
-               }
-               print "\n";
-       }
-
-       $cache{titles}{$_->id} = $_ for (@$titles);
-
-       title_hold_capture($hold,$titles) if (ref $titles and @$titles);
-}
-
-