1 package OpenILS::Application::Circ::Circulate;
2 use strict; use warnings;
3 use base 'OpenSRF::Application';
4 use OpenSRF::EX qw(:try);
5 use OpenSRF::Utils::SettingsClient;
6 use OpenSRF::Utils::Logger qw(:logger);
7 use OpenILS::Const qw/:const/;
15 my $conf = OpenSRF::Utils::SettingsClient->new;
16 my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
17 my @pfx = ( @pfx2, "scripts" );
19 my $p = $conf->config_value( @pfx, 'circ_permit_patron' );
20 my $c = $conf->config_value( @pfx, 'circ_permit_copy' );
21 my $d = $conf->config_value( @pfx, 'circ_duration' );
22 my $f = $conf->config_value( @pfx, 'circ_recurring_fines' );
23 my $m = $conf->config_value( @pfx, 'circ_max_fines' );
24 my $pr = $conf->config_value( @pfx, 'circ_permit_renew' );
25 my $lb = $conf->config_value( @pfx2, 'script_path' );
27 $logger->error( "Missing circ script(s)" )
28 unless( $p and $c and $d and $f and $m and $pr );
30 $scripts{circ_permit_patron} = $p;
31 $scripts{circ_permit_copy} = $c;
32 $scripts{circ_duration} = $d;
33 $scripts{circ_recurring_fines}= $f;
34 $scripts{circ_max_fines} = $m;
35 $scripts{circ_permit_renew} = $pr;
37 $lb = [ $lb ] unless ref($lb);
41 "circulator: Loaded rules scripts for circ: " .
42 "circ permit patron = $p, ".
43 "circ permit copy = $c, ".
44 "circ duration = $d, ".
45 "circ recurring fines = $f, " .
46 "circ max fines = $m, ".
47 "circ renew permit = $pr. ".
52 __PACKAGE__->register_method(
53 method => "run_method",
54 api_name => "open-ils.circ.checkout.permit",
56 Determines if the given checkout can occur
57 @param authtoken The login session key
58 @param params A trailing hash of named params including
59 barcode : The copy barcode,
60 patron : The patron the checkout is occurring for,
61 renew : true or false - whether or not this is a renewal
62 @return The event that occurred during the permit check.
66 __PACKAGE__->register_method (
67 method => 'run_method',
68 api_name => 'open-ils.circ.checkout.permit.override',
69 signature => q/@see open-ils.circ.checkout.permit/,
73 __PACKAGE__->register_method(
74 method => "run_method",
75 api_name => "open-ils.circ.checkout",
78 @param authtoken The login session key
79 @param params A named hash of params including:
81 barcode If no copy is provided, the copy is retrieved via barcode
82 copyid If no copy or barcode is provide, the copy id will be use
83 patron The patron's id
84 noncat True if this is a circulation for a non-cataloted item
85 noncat_type The non-cataloged type id
86 noncat_circ_lib The location for the noncat circ.
87 precat The item has yet to be cataloged
88 dummy_title The temporary title of the pre-cataloded item
89 dummy_author The temporary authr of the pre-cataloded item
90 Default is the home org of the staff member
91 @return The SUCCESS event on success, any other event depending on the error
94 __PACKAGE__->register_method(
95 method => "run_method",
96 api_name => "open-ils.circ.checkin",
99 Generic super-method for handling all copies
100 @param authtoken The login session key
101 @param params Hash of named parameters including:
102 barcode - The copy barcode
103 force - If true, copies in bad statuses will be checked in and give good statuses
108 __PACKAGE__->register_method(
109 method => "run_method",
110 api_name => "open-ils.circ.checkin.override",
111 signature => q/@see open-ils.circ.checkin/
114 __PACKAGE__->register_method(
115 method => "run_method",
116 api_name => "open-ils.circ.renew.override",
117 signature => q/@see open-ils.circ.renew/,
121 __PACKAGE__->register_method(
122 method => "run_method",
123 api_name => "open-ils.circ.renew",
124 notes => <<" NOTES");
125 PARAMS( authtoken, circ => circ_id );
126 open-ils.circ.renew(login_session, circ_object);
127 Renews the provided circulation. login_session is the requestor of the
128 renewal and if the logged in user is not the same as circ->usr, then
129 the logged in user must have RENEW_CIRC permissions.
134 my( $self, $conn, $auth, $args ) = @_;
135 translate_legacy_args($args);
136 my $api = $self->api_name;
139 OpenILS::Application::Circ::Circulator->new($auth, %$args);
141 return circ_events($circulator) if $circulator->bail_out;
143 # --------------------------------------------------------------------------
144 # Go ahead and load the script runner to make sure we have all
145 # of the objects we need
146 # --------------------------------------------------------------------------
147 $circulator->is_renewal(1) if $api =~ /renew/;
148 $circulator->is_checkin(1) if $api =~ /checkin/;
149 $circulator->mk_script_runner;
150 return circ_events($circulator) if $circulator->bail_out;
152 $circulator->circ_permit_patron($scripts{circ_permit_patron});
153 $circulator->circ_permit_copy($scripts{circ_permit_copy});
154 $circulator->circ_duration($scripts{circ_duration});
155 $circulator->circ_permit_renew($scripts{circ_permit_renew});
157 $circulator->override(1) if $api =~ /override/o;
159 if( $api =~ /checkout\.permit/ ) {
160 $circulator->do_permit();
162 } elsif( $api =~ /checkout/ ) {
163 $circulator->do_checkout();
165 } elsif( $api =~ /checkin/ ) {
166 $circulator->do_checkin();
168 } elsif( $api =~ /renew/ ) {
169 $circulator->is_renewal(1);
170 $circulator->do_renew();
173 if( $circulator->bail_out ) {
176 # make sure no success event accidentally slip in
178 [ grep { $_->{textcode} ne 'SUCCESS' } @{$circulator->events} ]);
179 my @e = @{$circulator->events};
180 push( @ee, $_->{textcode} ) for @e;
181 $logger->info("circulator: bailing out with events: @ee");
182 $circulator->editor->rollback;
185 $circulator->editor->commit;
188 $circulator->script_runner->cleanup;
190 return circ_events($circulator);
195 my @e = @{$circ->events};
196 return (@e == 1) ? $e[0] : \@e;
201 sub translate_legacy_args {
204 if( $$args{barcode} ) {
205 $$args{copy_barcode} = $$args{barcode};
206 delete $$args{barcode};
209 if( $$args{copyid} ) {
210 $$args{copy_id} = $$args{copyid};
211 delete $$args{copyid};
214 if( $$args{patronid} ) {
215 $$args{patron_id} = $$args{patronid};
216 delete $$args{patronid};
219 if( $$args{patron} and !ref($$args{patron}) ) {
220 $$args{patron_id} = $$args{patron};
221 delete $$args{patron};
225 if( $$args{noncat} ) {
226 $$args{is_noncat} = $$args{noncat};
227 delete $$args{noncat};
230 if( $$args{precat} ) {
231 $$args{is_precat} = $$args{precat};
232 delete $$args{precat};
238 # --------------------------------------------------------------------------
239 # This package actually manages all of the circulation logic
240 # --------------------------------------------------------------------------
241 package OpenILS::Application::Circ::Circulator;
242 use strict; use warnings;
243 use vars q/$AUTOLOAD/;
245 use OpenILS::Utils::Fieldmapper;
246 use OpenSRF::Utils::Cache;
247 use Digest::MD5 qw(md5_hex);
248 use DateTime::Format::ISO8601;
249 use OpenILS::Utils::PermitHold;
250 use OpenSRF::Utils qw/:datetime/;
251 use OpenSRF::Utils::SettingsClient;
252 use OpenILS::Application::Circ::Holds;
253 use OpenILS::Application::Circ::Transit;
254 use OpenSRF::Utils::Logger qw(:logger);
255 use OpenILS::Utils::CStoreEditor qw/:funcs/;
256 use OpenILS::Application::Circ::ScriptBuilder;
257 use OpenILS::Const qw/:const/;
259 my $U = "OpenILS::Application::AppUtils";
260 my $holdcode = "OpenILS::Application::Circ::Holds";
261 my $transcode = "OpenILS::Application::Circ::Transit";
266 # --------------------------------------------------------------------------
267 # Add a pile of automagic getter/setter methods
268 # --------------------------------------------------------------------------
269 my @AUTOLOAD_FIELDS = qw/
307 recurring_fines_level
324 my $type = ref($self) or die "$self is not an object";
326 my $name = $AUTOLOAD;
329 unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) {
330 $logger->error("circulator: $type: invalid autoload field: $name");
331 die "$type: invalid autoload field: $name\n"
336 *{"${type}::${name}"} = sub {
339 $s->{$name} = $v if defined $v;
343 return $self->$name($data);
348 my( $class, $auth, %args ) = @_;
349 $class = ref($class) || $class;
350 my $self = bless( {}, $class );
354 new_editor(xact => 1, authtoken => $auth) );
356 unless( $self->editor->checkauth ) {
357 $self->bail_on_events($self->editor->event);
361 $self->cache_handle(OpenSRF::Utils::Cache->new('global'));
363 $self->$_($args{$_}) for keys %args;
366 ($self->circ_lib) ? $self->circ_lib : $self->editor->requestor->ws_ou);
372 # --------------------------------------------------------------------------
373 # True if we should discontinue processing
374 # --------------------------------------------------------------------------
376 my( $self, $bool ) = @_;
377 if( defined $bool ) {
378 $logger->info("circulator: BAILING OUT") if $bool;
379 $self->{bail_out} = $bool;
381 return $self->{bail_out};
386 my( $self, @evts ) = @_;
389 $logger->info("circulator: pushing event ".$e->{textcode});
390 push( @{$self->events}, $e ) unless
391 grep { $_->{textcode} eq $e->{textcode} } @{$self->events};
397 my $key = md5_hex( time() . rand() . "$$" );
398 $self->cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
399 return $self->permit_key($key);
402 sub check_permit_key {
404 my $key = $self->permit_key;
405 return 0 unless $key;
406 my $k = "oils_permit_key_$key";
407 my $one = $self->cache_handle->get_cache($k);
408 $self->cache_handle->delete_cache($k);
409 return ($one) ? 1 : 0;
413 # --------------------------------------------------------------------------
414 # This builds the script runner environment and fetches most of the
416 # --------------------------------------------------------------------------
417 sub mk_script_runner {
421 $args->{ignore_user_status} = 1 if $self->is_checkin;
424 qw/copy copy_barcode copy_id patron
425 patron_id patron_barcode volume title editor/;
427 # Translate our objects into the ScriptBuilder args hash
428 $$args{$_} = $self->$_() for @fields;
429 $$args{fetch_patron_by_circ_copy} = 1;
430 $$args{fetch_patron_circ_info} = 1;
432 # This fetches most of the objects we need
433 $self->script_runner(
434 OpenILS::Application::Circ::ScriptBuilder->build($args));
436 # Now we translate the ScriptBuilder objects back into self
437 $self->$_($$args{$_}) for @fields;
439 my @evts = @{$args->{_events}} if $args->{_events};
441 $logger->debug("circulator: script builder returned events: : @evts") if @evts;
445 # Anything besides ASSET_COPY_NOT_FOUND will stop processing
446 if(!$self->is_noncat and
448 $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') {
452 my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts;
453 return $self->bail_on_events(@e);
457 $self->is_precat(1) if $self->copy and $self->copy->call_number == OILS_PRECAT_CALL_NUMBER;
459 # We can't renew if there is no copy
460 return $self->bail_on_events(@evts) if
461 $self->is_renewal and !$self->copy;
463 # Set some circ-specific flags in the script environment
464 my $evt = "environment";
465 $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef);
467 if( $self->is_noncat ) {
468 $self->script_runner->insert("$evt.isNonCat", 1);
469 $self->script_runner->insert("$evt.nonCatType", $self->noncat_type);
472 if( $self->is_precat ) {
473 $self->script_runner->insert("environment.isPrecat", 1, 1);
476 $self->script_runner->add_path( $_ ) for @$script_libs;
484 # --------------------------------------------------------------------------
485 # Does the circ permit work
486 # --------------------------------------------------------------------------
490 $self->log_me("do_permit()");
492 unless( $self->editor->requestor->id == $self->patron->id ) {
493 return $self->bail_on_events($self->editor->event)
494 unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') );
498 $self->check_captured_holds();
499 $self->do_copy_checks();
500 return if $self->bail_out;
501 $self->run_patron_permit_scripts();
502 $self->run_copy_permit_scripts()
503 unless $self->is_precat or $self->is_noncat;
504 $self->override_events() unless $self->is_renewal;
505 return if $self->bail_out;
507 if( $self->is_precat ) {
510 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key));
511 return $self->bail_out(1) unless $self->is_renewal;
517 payload => $self->mk_permit_key));
521 sub check_captured_holds {
523 my $copy = $self->copy;
524 my $patron = $self->patron;
526 return undef unless $copy;
528 my $s = $U->copy_status($copy->status)->id;
529 return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF;
530 $logger->info("circulator: copy is on holds shelf, searching for the correct hold");
532 # Item is on the holds shelf, make sure it's going to the right person
533 my $holds = $self->editor->search_action_hold_request(
536 current_copy => $copy->id ,
537 capture_time => { '!=' => undef },
538 cancel_time => undef,
539 fulfillment_time => undef
545 if( $holds and $$holds[0] ) {
546 return undef if $$holds[0]->usr == $patron->id;
549 $logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
551 $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF'));
557 my $copy = $self->copy;
560 my $stat = $U->copy_status($copy->status)->id;
562 # We cannot check out a copy if it is in-transit
563 if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) {
564 return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
567 $self->handle_claims_returned();
568 return if $self->bail_out;
570 # no claims returned circ was found, check if there is any open circ
571 unless( $self->is_renewal ) {
572 my $circs = $self->editor->search_action_circulation(
573 { target_copy => $copy->id, checkin_time => undef }
576 return $self->bail_on_events(
577 OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
582 # ---------------------------------------------------------------------
583 # This pushes any patron-related events into the list but does not
584 # set bail_out for any events
585 # ---------------------------------------------------------------------
586 sub run_patron_permit_scripts {
588 my $runner = $self->script_runner;
589 my $patronid = $self->patron->id;
591 # ---------------------------------------------------------------------
592 # Find all of the fatal penalties currently set on the user
593 # ---------------------------------------------------------------------
594 my $penalties = $U->update_patron_penalties(
595 authtoken => $self->editor->authtoken,
596 patron => $self->patron,
599 $penalties = $penalties->{fatal_penalties};
602 # ---------------------------------------------------------------------
603 # Now run the patron permit script
604 # ---------------------------------------------------------------------
605 $runner->load($self->circ_permit_patron);
606 my $result = $runner->run or
607 throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
609 my $patron_events = $result->{events};
611 push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
613 $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
615 $self->push_events(@allevents);
619 sub run_copy_permit_scripts {
621 my $copy = $self->copy || return;
622 my $runner = $self->script_runner;
624 # ---------------------------------------------------------------------
625 # Capture all of the copy permit events
626 # ---------------------------------------------------------------------
627 $runner->load($self->circ_permit_copy);
628 my $result = $runner->run or
629 throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
630 my $copy_events = $result->{events};
632 # ---------------------------------------------------------------------
633 # Now collect all of the events together
634 # ---------------------------------------------------------------------
636 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
638 # See if this copy has an alert message
639 my $ae = $self->check_copy_alert();
640 push( @allevents, $ae ) if $ae;
642 # uniquify the events
643 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
644 @allevents = values %hash;
647 $_->{payload} = $copy if
648 ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
651 $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
653 $self->push_events(@allevents);
657 sub check_copy_alert {
659 return OpenILS::Event->new(
660 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
661 if $self->copy and $self->copy->alert_message;
667 # --------------------------------------------------------------------------
668 # If the call is overriding and has permissions to override every collected
669 # event, the are cleared. Any event that the caller does not have
670 # permission to override, will be left in the event list and bail_out will
672 # XXX We need code in here to cancel any holds/transits on copies
673 # that are being force-checked out
674 # --------------------------------------------------------------------------
675 sub override_events {
677 my @events = @{$self->events};
678 return unless @events;
680 if(!$self->override) {
681 return $self->bail_out(1)
682 if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
687 for my $e (@events) {
688 my $tc = $e->{textcode};
689 next if $tc eq 'SUCCESS';
690 my $ov = "$tc.override";
691 $logger->info("circulator: attempting to override event: $ov");
693 return $self->bail_on_events($self->editor->event)
694 unless( $self->editor->allowed($ov) );
699 # --------------------------------------------------------------------------
700 # If there is an open claimsreturn circ on the requested copy, close the
701 # circ if overriding, otherwise bail out
702 # --------------------------------------------------------------------------
703 sub handle_claims_returned {
705 my $copy = $self->copy;
707 my $CR = $self->editor->search_action_circulation(
709 target_copy => $copy->id,
710 stop_fines => OILS_STOP_FINES_CLAIMSRETURNED,
711 checkin_time => undef,
715 return unless ($CR = $CR->[0]);
719 # - If the caller has set the override flag, we will check the item in
720 if($self->override) {
722 $CR->checkin_time('now');
723 $CR->checkin_lib($self->editor->requestor->ws_ou);
724 $CR->checkin_staff($self->editor->requestor->id);
726 $evt = $self->editor->event
727 unless $self->editor->update_action_circulation($CR);
730 $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
733 $self->bail_on_events($evt) if $evt;
738 # --------------------------------------------------------------------------
739 # This performs the checkout
740 # --------------------------------------------------------------------------
744 $self->log_me("do_checkout()");
746 # make sure perms are good if this isn't a renewal
747 unless( $self->is_renewal ) {
748 return $self->bail_on_events($self->editor->event)
749 unless( $self->editor->allowed('COPY_CHECKOUT') );
752 # verify the permit key
753 unless( $self->check_permit_key ) {
754 if( $self->permit_override ) {
755 return $self->bail_on_events($self->editor->event)
756 unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
758 return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
762 # if this is a non-cataloged circ, build the circ and finish
763 if( $self->is_noncat ) {
764 $self->checkout_noncat;
766 OpenILS::Event->new('SUCCESS',
767 payload => { noncat_circ => $self->circ }));
771 if( $self->is_precat ) {
772 $self->script_runner->insert("environment.isPrecat", 1, 1);
773 $self->make_precat_copy;
774 return if $self->bail_out;
776 } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
777 return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
780 $self->do_copy_checks;
781 return if $self->bail_out;
783 $self->run_checkout_scripts();
784 return if $self->bail_out;
786 $self->build_checkout_circ_object();
787 return if $self->bail_out;
789 $self->apply_modified_due_date();
790 return if $self->bail_out;
792 return $self->bail_on_events($self->editor->event)
793 unless $self->editor->create_action_circulation($self->circ);
795 $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
797 return if $self->bail_out;
799 $self->handle_checkout_holds();
800 return if $self->bail_out;
802 # ------------------------------------------------------------------------------
803 # Update the patron penalty info in the DB
804 # ------------------------------------------------------------------------------
805 $U->update_patron_penalties(
806 authtoken => $self->editor->authtoken,
807 patron => $self->patron,
811 my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
813 OpenILS::Event->new('SUCCESS',
815 copy => $U->unflesh_copy($self->copy),
818 holds_fulfilled => $self->fulfilled_holds,
826 my $copy = $self->copy;
828 my $stat = $copy->status if ref $copy->status;
829 my $loc = $copy->location if ref $copy->location;
830 my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
832 $copy->status($stat->id) if $stat;
833 $copy->location($loc->id) if $loc;
834 $copy->circ_lib($circ_lib->id) if $circ_lib;
835 $copy->editor($self->editor->requestor->id);
836 $copy->edit_date('now');
837 $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect;
839 return $self->bail_on_events($self->editor->event)
840 unless $self->editor->update_asset_copy($self->copy);
842 $copy->status($U->copy_status($copy->status));
843 $copy->location($loc) if $loc;
844 $copy->circ_lib($circ_lib) if $circ_lib;
849 my( $self, @evts ) = @_;
850 $self->push_events(@evts);
854 sub handle_checkout_holds {
857 my $copy = $self->copy;
858 my $patron = $self->patron;
860 my $holds = $self->editor->search_action_hold_request(
862 current_copy => $copy->id ,
863 cancel_time => undef,
864 fulfillment_time => undef
870 # XXX We should only fulfill one hold here...
871 # XXX If a hold was transited to the user who is checking out
872 # the item, we need to make sure that hold is what's grabbed
875 # for now, just sort by id to get what should be the oldest hold
876 $holds = [ sort { $a->id <=> $b->id } @$holds ];
877 my @myholds = grep { $_->usr eq $patron->id } @$holds;
878 my @altholds = grep { $_->usr ne $patron->id } @$holds;
881 my $hold = $myholds[0];
883 $logger->debug("circulator: related hold found in checkout: " . $hold->id );
885 # if the hold was never officially captured, capture it.
886 $hold->capture_time('now') unless $hold->capture_time;
888 # just make sure it's set correctly
889 $hold->current_copy($copy->id);
891 $hold->fulfillment_time('now');
892 $hold->fulfillment_staff($self->editor->requestor->id);
893 $hold->fulfillment_lib($self->editor->requestor->ws_ou);
895 return $self->bail_on_events($self->editor->event)
896 unless $self->editor->update_action_hold_request($hold);
898 $holdcode->delete_hold_copy_maps($self->editor, $hold->id);
900 push( @fulfilled, $hold->id );
903 # If there are any holds placed for other users that point to this copy,
904 # then we need to un-target those holds so the targeter can pick a new copy
907 $logger->info("circulator: un-targeting hold ".$_->id.
908 " because copy ".$copy->id." is getting checked out");
910 # - make the targeter process this hold at next run
911 $_->clear_prev_check_time;
913 # - clear out the targetted copy
914 $_->clear_current_copy;
915 $_->clear_capture_time;
917 return $self->bail_on_event($self->editor->event)
918 unless $self->editor->update_action_hold_request($_);
922 $self->fulfilled_holds(\@fulfilled);
927 sub run_checkout_scripts {
931 my $runner = $self->script_runner;
932 $runner->load($self->circ_duration);
934 my $result = $runner->run or
935 throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
937 my $duration = $result->{durationRule};
938 my $recurring = $result->{recurringFinesRule};
939 my $max_fine = $result->{maxFine};
941 if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) {
943 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
944 return $self->bail_on_events($evt) if $evt;
946 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
947 return $self->bail_on_events($evt) if $evt;
949 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
950 return $self->bail_on_events($evt) if $evt;
954 # The item circulates with an unlimited duration
960 $self->duration_rule($duration);
961 $self->recurring_fines_rule($recurring);
962 $self->max_fine_rule($max_fine);
966 sub build_checkout_circ_object {
969 my $circ = Fieldmapper::action::circulation->new;
970 my $duration = $self->duration_rule;
971 my $max = $self->max_fine_rule;
972 my $recurring = $self->recurring_fines_rule;
973 my $copy = $self->copy;
974 my $patron = $self->patron;
978 my $dname = $duration->name;
979 my $mname = $max->name;
980 my $rname = $recurring->name;
982 $logger->debug("circulator: building circulation ".
983 "with duration=$dname, maxfine=$mname, recurring=$rname");
985 $circ->duration( $duration->shrt )
986 if $copy->loan_duration == OILS_CIRC_DURATION_SHORT;
987 $circ->duration( $duration->normal )
988 if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL;
989 $circ->duration( $duration->extended )
990 if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED;
992 $circ->recuring_fine( $recurring->low )
993 if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW;
994 $circ->recuring_fine( $recurring->normal )
995 if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL;
996 $circ->recuring_fine( $recurring->high )
997 if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH;
999 $circ->duration_rule( $duration->name );
1000 $circ->recuring_fine_rule( $recurring->name );
1001 $circ->max_fine_rule( $max->name );
1002 $circ->max_fine( $max->amount );
1004 $circ->fine_interval($recurring->recurance_interval);
1005 $circ->renewal_remaining( $duration->max_renewals );
1009 $logger->info("circulator: copy found with an unlimited circ duration");
1010 $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION);
1011 $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1012 $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1013 $circ->renewal_remaining(0);
1016 $circ->target_copy( $copy->id );
1017 $circ->usr( $patron->id );
1018 $circ->circ_lib( $self->circ_lib );
1020 if( $self->is_renewal ) {
1021 $circ->opac_renewal(1);
1022 $circ->renewal_remaining($self->renewal_remaining);
1023 $circ->circ_staff($self->editor->requestor->id);
1026 # if the user provided an overiding checkout time,
1027 # (e.g. the checkout really happened several hours ago), then
1028 # we apply that here. Does this need a perm??
1029 $circ->xact_start(clense_ISO8601($self->checkout_time))
1030 if $self->checkout_time;
1032 # if a patron is renewing, 'requestor' will be the patron
1033 $circ->circ_staff($self->editor->requestor->id);
1034 $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
1040 sub apply_modified_due_date {
1042 my $circ = $self->circ;
1043 my $copy = $self->copy;
1045 if( $self->due_date ) {
1047 return $self->bail_on_events($self->editor->event)
1048 unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
1050 $circ->due_date(clense_ISO8601($self->due_date));
1054 # if the due_date lands on a day when the location is closed
1055 return unless $copy and $circ->due_date;
1057 my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
1059 $logger->info("circulator: circ searching for closed date overlap on lib $org".
1060 " with an item due date of ".$circ->due_date );
1062 my $dateinfo = $U->storagereq(
1063 'open-ils.storage.actor.org_unit.closed_date.overlap',
1064 $org, $circ->due_date );
1067 $logger->info("circulator: $dateinfo : circ due data / close date overlap found : due_date=".
1068 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1070 # XXX make the behavior more dynamic
1071 # for now, we just push the due date to after the close date
1072 $circ->due_date($dateinfo->{end});
1079 sub create_due_date {
1080 my( $self, $duration ) = @_;
1081 my ($sec,$min,$hour,$mday,$mon,$year) =
1082 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1083 $year += 1900; $mon += 1;
1084 my $due_date = sprintf(
1085 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1086 $year, $mon, $mday, $hour, $min, $sec);
1092 sub make_precat_copy {
1094 my $copy = $self->copy;
1097 $logger->debug("ciculator: Pre-cat copy already exists in checkout: ID=" . $copy->id);
1099 $copy->editor($self->editor->requestor->id);
1100 $copy->edit_date('now');
1101 $copy->dummy_title($self->dummy_title);
1102 $copy->dummy_author($self->dummy_author);
1104 $self->update_copy();
1108 $logger->info("circulator: Creating a new precataloged ".
1109 "copy in checkout with barcode " . $self->copy_barcode);
1111 $copy = Fieldmapper::asset::copy->new;
1112 $copy->circ_lib($self->circ_lib);
1113 $copy->creator($self->editor->requestor->id);
1114 $copy->editor($self->editor->requestor->id);
1115 $copy->barcode($self->copy_barcode);
1116 $copy->call_number(OILS_PRECAT_CALL_NUMBER);
1117 $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
1118 $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
1120 $copy->dummy_title($self->dummy_title || "");
1121 $copy->dummy_author($self->dummy_author || "");
1123 unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
1125 $self->push_events($self->editor->event);
1129 # this is a little bit of a hack, but we need to
1130 # get the copy into the script runner
1131 $self->script_runner->insert("environment.copy", $copy, 1);
1135 sub checkout_noncat {
1141 my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
1142 my $count = $self->noncat_count || 1;
1143 my $cotime = clense_ISO8601($self->checkout_time) || "";
1145 $logger->info("ciculator: circ creating $count noncat circs with checkout time $cotime");
1149 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1150 $self->editor->requestor->id,
1158 $self->push_events($evt);
1169 $self->log_me("do_checkin()");
1172 return $self->bail_on_events(
1173 OpenILS::Event->new('ASSET_COPY_NOT_FOUND'))
1176 # if we're on the holds shelf, do nothing
1177 # if( $U->copy_status($self->copy->status)->id == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1178 # $logger->info("circulator: copy is already on the holds shelf at checkin, doing nothing: ".$self->copy->barcode);
1179 # $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1180 # $self->bail_on_events(OpenILS::Event->new('NO_CHANGE'));
1181 # $self->checkin_flesh_events;
1186 if( $self->checkin_check_holds_shelf() ) {
1187 $self->bail_on_events(OpenILS::Event->new('NO_CHANGE'));
1188 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1189 $self->checkin_flesh_events;
1193 unless( $self->is_renewal ) {
1194 return $self->bail_on_events($self->editor->event)
1195 unless $self->editor->allowed('COPY_CHECKIN');
1198 $self->push_events($self->check_copy_alert());
1199 $self->push_events($self->check_checkin_copy_status());
1201 # the renew code will have already found our circulation object
1202 unless( $self->is_renewal and $self->circ ) {
1204 $self->editor->search_action_circulation(
1205 { target_copy => $self->copy->id, checkin_time => undef })->[0]);
1208 # if the circ is marked as 'claims returned', add the event to the list
1209 $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
1210 if ($self->circ and $self->circ->stop_fines
1211 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
1213 # handle the overridable events
1214 $self->override_events unless $self->is_renewal;
1215 return if $self->bail_out;
1219 $self->editor->search_action_transit_copy(
1220 { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);
1224 $self->checkin_handle_circ;
1225 return if $self->bail_out;
1226 $self->checkin_changed(1);
1228 } elsif( $self->transit ) {
1229 my $hold_transit = $self->process_received_transit;
1230 $self->checkin_changed(1);
1232 if( $self->bail_out ) {
1233 $self->checkin_flesh_events;
1237 if( my $e = $self->check_checkin_copy_status() ) {
1238 # If the original copy status is special, alert the caller
1239 my $ev = $self->events;
1240 $self->events([$e]);
1241 $self->override_events;
1242 return if $self->bail_out;
1247 if( $hold_transit or
1248 $U->copy_status($self->copy->status)->id
1249 == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1250 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1251 $self->checkin_flesh_events;
1256 if( $self->is_renewal ) {
1257 $self->push_events(OpenILS::Event->new('SUCCESS'));
1261 # ------------------------------------------------------------------------------
1262 # Circulations and transits are now closed where necessary. Now go on to see if
1263 # this copy can fulfill a hold or needs to be routed to a different location
1264 # ------------------------------------------------------------------------------
1266 if( $self->attempt_checkin_hold_capture() ) {
1267 return if $self->bail_out;
1269 } else { # not needed for a hold
1271 my $circ_lib = (ref $self->copy->circ_lib) ?
1272 $self->copy->circ_lib->id : $self->copy->circ_lib;
1274 $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
1276 if( $circ_lib == $self->editor->requestor->ws_ou ) {
1278 $self->checkin_handle_precat();
1279 return if $self->bail_out;
1283 my $bc = $self->copy->barcode;
1284 $logger->info("circulator: copy $bc at a remote lib - sending home");
1285 $self->checkin_build_copy_transit();
1286 return if $self->bail_out;
1287 $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
1291 $self->reshelve_copy;
1292 return if $self->bail_out;
1294 unless($self->checkin_changed) {
1296 $self->push_events(OpenILS::Event->new('NO_CHANGE'));
1297 my $stat = $U->copy_status($self->copy->status)->id;
1299 $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
1300 if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF );
1301 $self->bail_out(1); # no need to commit anything
1304 $self->push_events(OpenILS::Event->new('SUCCESS'))
1305 unless @{$self->events};
1308 $self->checkin_flesh_events;
1314 my $force = $self->force || shift;
1315 my $copy = $self->copy;
1317 my $stat = $U->copy_status($copy->status)->id;
1320 $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and
1321 $stat != OILS_COPY_STATUS_CATALOGING and
1322 $stat != OILS_COPY_STATUS_IN_TRANSIT and
1323 $stat != OILS_COPY_STATUS_RESHELVING )) {
1325 $copy->status( OILS_COPY_STATUS_RESHELVING );
1327 $self->checkin_changed(1);
1332 # Returns true if the item is at the current location
1333 # because it was transited there for a hold and the
1334 # hold has not been fulfilled
1335 sub checkin_check_holds_shelf {
1337 return 0 unless $self->copy;
1340 $U->copy_status($self->copy->status)->id ==
1341 OILS_COPY_STATUS_ON_HOLDS_SHELF;
1343 # If we're on the holds shelf at our lib
1344 return 1 if $self->copy->circ_lib == $self->editor->requestor->ws_ou;
1346 # Otherwise, find the hold that put us on the holds shelf
1347 my $holds = $self->editor->search_action_hold_request(
1349 current_copy => $self->copy->id,
1350 capture_time => { '!=' => undef },
1351 fulfillment_time => undef,
1352 cancel_time => undef,
1356 return 0 unless @$holds;
1358 $logger->info("circulator: we found a captured, un-fulfilled hold [".
1359 $$holds[0]. "] for copy ".$self->copy->barcode);
1361 # Then find the transit that got us here
1362 my $transits = $self->editor->search_action_hold_transit_copy(
1365 dest => $self->editor->requestor->ws_ou,
1366 dest_recv_time => { '!=' => undef }
1370 return 0 unless @$transits;
1372 $logger->info("circulator: we found a hold transit [".$$transits[0]."] for ".
1373 $self->copy->barcode. " which puts the copy here.. not transiting home");
1379 sub checkin_handle_precat {
1381 my $copy = $self->copy;
1383 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1384 $copy->status(OILS_COPY_STATUS_CATALOGING);
1385 $self->update_copy();
1386 $self->checkin_changed(1);
1387 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1392 sub checkin_build_copy_transit {
1394 my $copy = $self->copy;
1395 my $transit = Fieldmapper::action::transit_copy->new;
1397 $transit->source($self->editor->requestor->ws_ou);
1398 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1399 $transit->target_copy($copy->id);
1400 $transit->source_send_time('now');
1401 $transit->copy_status( $U->copy_status($copy->status)->id );
1403 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1405 return $self->bail_on_events($self->editor->event)
1406 unless $self->editor->create_action_transit_copy($transit);
1408 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1410 $self->checkin_changed(1);
1414 sub attempt_checkin_hold_capture {
1416 my $copy = $self->copy;
1418 # See if this copy can fulfill any holds
1419 my ($hold) = $holdcode->find_nearest_permitted_hold(
1420 OpenSRF::AppSession->create('open-ils.storage'),
1421 $copy, $self->editor->requestor );
1424 $logger->debug("circulator: no potential permitted".
1425 "holds found for copy ".$copy->barcode);
1430 $logger->info("circulator: found permitted hold ".
1431 $hold->id . " for copy, capturing...");
1433 $hold->current_copy($copy->id);
1434 $hold->capture_time('now');
1436 # prevent DB errors caused by fetching
1437 # holds from storage, and updating through cstore
1438 $hold->clear_fulfillment_time;
1439 $hold->clear_fulfillment_staff;
1440 $hold->clear_fulfillment_lib;
1441 $hold->clear_expire_time;
1442 $hold->clear_cancel_time;
1443 $hold->clear_prev_check_time unless $hold->prev_check_time;
1445 $self->bail_on_events($self->editor->event)
1446 unless $self->editor->update_action_hold_request($hold);
1448 $self->checkin_changed(1);
1450 return 1 if $self->bail_out;
1452 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1454 # This hold was captured in the correct location
1455 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1456 $self->push_events(OpenILS::Event->new('SUCCESS'));
1458 $self->do_hold_notify($hold->id);
1462 # Hold needs to be picked up elsewhere. Build a hold
1463 # transit and route the item.
1464 $self->checkin_build_hold_transit();
1465 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1466 return 1 if $self->bail_out;
1468 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1471 # make sure we save the copy status
1476 sub do_hold_notify {
1477 my( $self, $holdid ) = @_;
1478 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1479 editor => $self->editor, hold_id => $holdid );
1481 if(!$notifier->event) {
1483 $logger->info("ciculator: attempt at sending hold notification for hold $holdid");
1485 my $stat = $notifier->send_email_notify;
1486 $logger->info("ciculator: hold notify succeeded for hold $holdid") if $stat eq '1';
1487 $logger->warn("ciculator: * hold notify failed for hold $holdid") if $stat ne '1';
1490 $logger->info("ciculator: Not sending hold notification since the patron has no email address");
1495 sub checkin_build_hold_transit {
1499 my $copy = $self->copy;
1500 my $hold = $self->hold;
1501 my $trans = Fieldmapper::action::hold_transit_copy->new;
1503 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1505 $trans->hold($hold->id);
1506 $trans->source($self->editor->requestor->ws_ou);
1507 $trans->dest($hold->pickup_lib);
1508 $trans->source_send_time("now");
1509 $trans->target_copy($copy->id);
1511 # when the copy gets to its destination, it will recover
1512 # this status - put it onto the holds shelf
1513 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1515 return $self->bail_on_events($self->editor->event)
1516 unless $self->editor->create_action_hold_transit_copy($trans);
1521 sub process_received_transit {
1523 my $copy = $self->copy;
1524 my $copyid = $self->copy->id;
1526 my $status_name = $U->copy_status($copy->status)->name;
1527 $logger->debug("circulator: attempting transit receive on ".
1528 "copy $copyid. Copy status is $status_name");
1530 my $transit = $self->transit;
1532 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1533 $logger->info("circulator: Fowarding transit on copy which is destined ".
1534 "for a different location. copy=$copyid,current ".
1535 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1537 return $self->bail_on_events(
1538 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1541 # The transit is received, set the receive time
1542 $transit->dest_recv_time('now');
1543 $self->bail_on_events($self->editor->event)
1544 unless $self->editor->update_action_transit_copy($transit);
1546 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1548 $logger->info("ciculator: Recovering original copy status in transit: ".$transit->copy_status);
1549 $copy->status( $transit->copy_status );
1550 $self->update_copy();
1551 return if $self->bail_out;
1555 $self->do_hold_notify($hold_transit->hold);
1560 OpenILS::Event->new(
1563 payload => { transit => $transit, holdtransit => $hold_transit } ));
1565 return $hold_transit;
1569 sub checkin_handle_circ {
1573 my $circ = $self->circ;
1574 my $copy = $self->copy;
1578 # backdate the circ if necessary
1579 if($self->backdate) {
1580 $self->checkin_handle_backdate;
1581 return if $self->bail_out;
1584 if(!$circ->stop_fines) {
1585 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1586 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1587 $circ->stop_fines_time('now') unless $self->backdate;
1588 $circ->stop_fines_time($self->backdate) if $self->backdate;
1591 # see if there are any fines owed on this circ. if not, close it
1592 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1593 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1595 # Set the checkin vars since we have the item
1596 $circ->checkin_time('now');
1597 $circ->checkin_staff($self->editor->requestor->id);
1598 $circ->checkin_lib($self->editor->requestor->ws_ou);
1600 my $circ_lib = (ref $self->copy->circ_lib) ?
1601 $self->copy->circ_lib->id : $self->copy->circ_lib;
1602 my $stat = $U->copy_status($self->copy->status)->id;
1604 # If the item is lost/missing and it needs to be sent home, don't
1605 # reshelve the copy, leave it lost/missing so the recipient will know
1606 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1607 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1608 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1611 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1616 return $self->bail_on_events($self->editor->event)
1617 unless $self->editor->update_action_circulation($circ);
1621 sub checkin_handle_backdate {
1624 my $bd = $self->backdate;
1625 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1626 $bd = "${bd}T23:59:59";
1628 my $bills = $self->editor->search_money_billing(
1630 billing_ts => { '>=' => $bd },
1631 xact => $self->circ->id,
1632 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1636 for my $bill (@$bills) {
1637 if( !$bill->voided or $bill->voided =~ /f/i ) {
1639 my $n = $bill->note || "";
1640 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1642 $self->bail_on_events($self->editor->event)
1643 unless $self->editor->update_money_billing($bill);
1650 # XXX Legacy version for Circ.pm support
1651 sub _checkin_handle_backdate {
1652 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1655 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1656 $bd = "${bd}T23:59:59";
1659 my $bills = $session->request(
1660 "open-ils.storage.direct.money.billing.search_where.atomic",
1661 billing_ts => { '>=' => $bd },
1663 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1667 for my $bill (@$bills) {
1669 my $n = $bill->note || "";
1670 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1671 my $s = $session->request(
1672 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1673 return $U->DB_UPDATE_FAILED($bill) unless $s;
1683 sub find_patron_from_copy {
1685 my $circs = $self->editor->search_action_circulation(
1686 { target_copy => $self->copy->id, checkin_time => undef });
1687 my $circ = $circs->[0];
1688 return unless $circ;
1689 my $u = $self->editor->retrieve_actor_user($circ->usr)
1690 or return $self->bail_on_events($self->editor->event);
1694 sub check_checkin_copy_status {
1696 my $copy = $self->copy;
1702 my $status = $U->copy_status($copy->status)->id;
1705 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1706 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1707 $status == OILS_COPY_STATUS_IN_PROCESS ||
1708 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1709 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1710 $status == OILS_COPY_STATUS_CATALOGING ||
1711 $status == OILS_COPY_STATUS_RESHELVING );
1713 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1714 if( $status == OILS_COPY_STATUS_LOST );
1716 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1717 if( $status == OILS_COPY_STATUS_MISSING );
1719 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1724 # --------------------------------------------------------------------------
1725 # On checkin, we need to return as many relevant objects as we can
1726 # --------------------------------------------------------------------------
1727 sub checkin_flesh_events {
1730 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1731 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1732 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1736 for my $evt (@{$self->events}) {
1739 $payload->{copy} = $U->unflesh_copy($self->copy);
1740 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1741 $payload->{circ} = $self->circ;
1742 $payload->{transit} = $self->transit;
1743 $payload->{hold} = $self->hold;
1745 $evt->{payload} = $payload;
1750 my( $self, $msg ) = @_;
1751 my $bc = ($self->copy) ? $self->copy->barcode :
1754 my $usr = ($self->patron) ? $self->patron->id : "";
1755 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1756 ", recipient=$usr, copy=$bc");
1762 $self->log_me("do_renew()");
1763 $self->is_renewal(1);
1765 unless( $self->is_renewal ) {
1766 return $self->bail_on_events($self->editor->events)
1767 unless $self->editor->allowed('RENEW_CIRC');
1770 # Make sure there is an open circ to renew that is not
1771 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1772 my $circ = $self->editor->search_action_circulation(
1773 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1776 $circ = $self->editor->search_action_circulation(
1778 target_copy => $self->copy->id,
1779 stop_fines => OILS_STOP_FINES_MAX_FINES,
1780 checkin_time => undef
1785 return $self->bail_on_events($self->editor->event) unless $circ;
1787 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1788 if $circ->renewal_remaining < 1;
1790 # -----------------------------------------------------------------
1792 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1795 $self->run_renew_permit;
1798 $self->do_checkin();
1799 return if $self->bail_out;
1801 unless( $self->permit_override ) {
1803 return if $self->bail_out;
1804 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1805 $self->remove_event('ITEM_NOT_CATALOGED');
1808 $self->override_events;
1809 return if $self->bail_out;
1812 $self->do_checkout();
1817 my( $self, $evt ) = @_;
1818 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1819 $logger->debug("circulator: removing event from list: $evt");
1820 my @events = @{$self->events};
1821 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1826 my( $self, $evt ) = @_;
1827 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1828 return grep { $_->{textcode} eq $evt } @{$self->events};
1833 sub run_renew_permit {
1835 my $runner = $self->script_runner;
1837 $runner->load($self->circ_permit_renew);
1838 my $result = $runner->run or
1839 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1840 my $events = $result->{events};
1842 $logger->activity("ciculator: circ_permit_renew for user ".
1843 $self->patron->id." returned events: @$events") if @$events;
1845 $self->push_events(OpenILS::Event->new($_)) for @$events;
1847 $logger->debug("circulator: re-creating script runner to be safe");
1848 $self->mk_script_runner;