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.
132 __PACKAGE__->register_method(
133 method => "run_method",
134 api_name => "open-ils.circ.checkout.full");
135 __PACKAGE__->register_method(
136 method => "run_method",
137 api_name => "open-ils.circ.checkout.full.override");
142 my( $self, $conn, $auth, $args ) = @_;
143 translate_legacy_args($args);
144 my $api = $self->api_name;
147 OpenILS::Application::Circ::Circulator->new($auth, %$args);
149 return circ_events($circulator) if $circulator->bail_out;
151 # --------------------------------------------------------------------------
152 # Go ahead and load the script runner to make sure we have all
153 # of the objects we need
154 # --------------------------------------------------------------------------
155 $circulator->is_renewal(1) if $api =~ /renew/;
156 $circulator->is_checkin(1) if $api =~ /checkin/;
157 $circulator->mk_script_runner;
158 return circ_events($circulator) if $circulator->bail_out;
160 $circulator->circ_permit_patron($scripts{circ_permit_patron});
161 $circulator->circ_permit_copy($scripts{circ_permit_copy});
162 $circulator->circ_duration($scripts{circ_duration});
163 $circulator->circ_permit_renew($scripts{circ_permit_renew});
165 $circulator->override(1) if $api =~ /override/o;
167 if( $api =~ /checkout\.permit/ ) {
168 $circulator->do_permit();
170 } elsif( $api =~ /checkout.full/ ) {
172 $circulator->do_permit();
173 unless( $circulator->bail_out ) {
174 $circulator->events([]);
175 $circulator->do_checkout();
178 } elsif( $api =~ /checkout/ ) {
179 $circulator->do_checkout();
181 } elsif( $api =~ /checkin/ ) {
182 $circulator->do_checkin();
184 } elsif( $api =~ /renew/ ) {
185 $circulator->is_renewal(1);
186 $circulator->do_renew();
189 if( $circulator->bail_out ) {
192 # make sure no success event accidentally slip in
194 [ grep { $_->{textcode} ne 'SUCCESS' } @{$circulator->events} ]);
195 my @e = @{$circulator->events};
196 push( @ee, $_->{textcode} ) for @e;
197 $logger->info("circulator: bailing out with events: @ee");
198 $circulator->editor->rollback;
201 $circulator->editor->commit;
204 $circulator->script_runner->cleanup;
206 return circ_events($circulator);
211 my @e = @{$circ->events};
212 return (@e == 1) ? $e[0] : \@e;
217 sub translate_legacy_args {
220 if( $$args{barcode} ) {
221 $$args{copy_barcode} = $$args{barcode};
222 delete $$args{barcode};
225 if( $$args{copyid} ) {
226 $$args{copy_id} = $$args{copyid};
227 delete $$args{copyid};
230 if( $$args{patronid} ) {
231 $$args{patron_id} = $$args{patronid};
232 delete $$args{patronid};
235 if( $$args{patron} and !ref($$args{patron}) ) {
236 $$args{patron_id} = $$args{patron};
237 delete $$args{patron};
241 if( $$args{noncat} ) {
242 $$args{is_noncat} = $$args{noncat};
243 delete $$args{noncat};
246 if( $$args{precat} ) {
247 $$args{is_precat} = $$args{precat};
248 delete $$args{precat};
254 # --------------------------------------------------------------------------
255 # This package actually manages all of the circulation logic
256 # --------------------------------------------------------------------------
257 package OpenILS::Application::Circ::Circulator;
258 use strict; use warnings;
259 use vars q/$AUTOLOAD/;
261 use OpenILS::Utils::Fieldmapper;
262 use OpenSRF::Utils::Cache;
263 use Digest::MD5 qw(md5_hex);
264 use DateTime::Format::ISO8601;
265 use OpenILS::Utils::PermitHold;
266 use OpenSRF::Utils qw/:datetime/;
267 use OpenSRF::Utils::SettingsClient;
268 use OpenILS::Application::Circ::Holds;
269 use OpenILS::Application::Circ::Transit;
270 use OpenSRF::Utils::Logger qw(:logger);
271 use OpenILS::Utils::CStoreEditor qw/:funcs/;
272 use OpenILS::Application::Circ::ScriptBuilder;
273 use OpenILS::Const qw/:const/;
275 my $U = "OpenILS::Application::AppUtils";
276 my $holdcode = "OpenILS::Application::Circ::Holds";
277 my $transcode = "OpenILS::Application::Circ::Transit";
282 # --------------------------------------------------------------------------
283 # Add a pile of automagic getter/setter methods
284 # --------------------------------------------------------------------------
285 my @AUTOLOAD_FIELDS = qw/
324 recurring_fines_level
341 my $type = ref($self) or die "$self is not an object";
343 my $name = $AUTOLOAD;
346 unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) {
347 $logger->error("circulator: $type: invalid autoload field: $name");
348 die "$type: invalid autoload field: $name\n"
353 *{"${type}::${name}"} = sub {
356 $s->{$name} = $v if defined $v;
360 return $self->$name($data);
365 my( $class, $auth, %args ) = @_;
366 $class = ref($class) || $class;
367 my $self = bless( {}, $class );
371 new_editor(xact => 1, authtoken => $auth) );
373 unless( $self->editor->checkauth ) {
374 $self->bail_on_events($self->editor->event);
378 $self->cache_handle(OpenSRF::Utils::Cache->new('global'));
380 $self->$_($args{$_}) for keys %args;
383 ($self->circ_lib) ? $self->circ_lib : $self->editor->requestor->ws_ou);
389 # --------------------------------------------------------------------------
390 # True if we should discontinue processing
391 # --------------------------------------------------------------------------
393 my( $self, $bool ) = @_;
394 if( defined $bool ) {
395 $logger->info("circulator: BAILING OUT") if $bool;
396 $self->{bail_out} = $bool;
398 return $self->{bail_out};
403 my( $self, @evts ) = @_;
406 $logger->info("circulator: pushing event ".$e->{textcode});
407 push( @{$self->events}, $e ) unless
408 grep { $_->{textcode} eq $e->{textcode} } @{$self->events};
414 my $key = md5_hex( time() . rand() . "$$" );
415 $self->cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
416 return $self->permit_key($key);
419 sub check_permit_key {
421 my $key = $self->permit_key;
422 return 0 unless $key;
423 my $k = "oils_permit_key_$key";
424 my $one = $self->cache_handle->get_cache($k);
425 $self->cache_handle->delete_cache($k);
426 return ($one) ? 1 : 0;
430 # --------------------------------------------------------------------------
431 # This builds the script runner environment and fetches most of the
433 # --------------------------------------------------------------------------
434 sub mk_script_runner {
438 $args->{ignore_user_status} = 1 if $self->is_checkin;
441 qw/copy copy_barcode copy_id patron
442 patron_id patron_barcode volume title editor/;
444 # Translate our objects into the ScriptBuilder args hash
445 $$args{$_} = $self->$_() for @fields;
446 $$args{fetch_patron_by_circ_copy} = 1;
447 $$args{fetch_patron_circ_info} = 1;
449 # This fetches most of the objects we need
450 $self->script_runner(
451 OpenILS::Application::Circ::ScriptBuilder->build($args));
453 # Now we translate the ScriptBuilder objects back into self
454 $self->$_($$args{$_}) for @fields;
456 my @evts = @{$args->{_events}} if $args->{_events};
458 $logger->debug("circulator: script builder returned events: : @evts") if @evts;
462 # Anything besides ASSET_COPY_NOT_FOUND will stop processing
463 if(!$self->is_noncat and
465 $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') {
469 my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts;
470 return $self->bail_on_events(@e);
474 $self->is_precat(1) if $self->copy and $self->copy->call_number == OILS_PRECAT_CALL_NUMBER;
476 # We can't renew if there is no copy
477 return $self->bail_on_events(@evts) if
478 $self->is_renewal and !$self->copy;
480 # Set some circ-specific flags in the script environment
481 my $evt = "environment";
482 $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef);
484 if( $self->is_noncat ) {
485 $self->script_runner->insert("$evt.isNonCat", 1);
486 $self->script_runner->insert("$evt.nonCatType", $self->noncat_type);
489 if( $self->is_precat ) {
490 $self->script_runner->insert("environment.isPrecat", 1, 1);
493 $self->script_runner->add_path( $_ ) for @$script_libs;
501 # --------------------------------------------------------------------------
502 # Does the circ permit work
503 # --------------------------------------------------------------------------
507 $self->log_me("do_permit()");
509 unless( $self->editor->requestor->id == $self->patron->id ) {
510 return $self->bail_on_events($self->editor->event)
511 unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') );
515 $self->check_captured_holds();
516 $self->do_copy_checks();
517 return if $self->bail_out;
518 $self->run_patron_permit_scripts();
519 $self->run_copy_permit_scripts()
520 unless $self->is_precat or $self->is_noncat;
521 $self->override_events() unless $self->is_renewal;
522 return if $self->bail_out;
524 if( $self->is_precat ) {
527 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key));
528 return $self->bail_out(1) unless $self->is_renewal;
534 payload => $self->mk_permit_key));
538 sub check_captured_holds {
540 my $copy = $self->copy;
541 my $patron = $self->patron;
543 return undef unless $copy;
545 my $s = $U->copy_status($copy->status)->id;
546 return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF;
547 $logger->info("circulator: copy is on holds shelf, searching for the correct hold");
549 # Item is on the holds shelf, make sure it's going to the right person
550 my $holds = $self->editor->search_action_hold_request(
553 current_copy => $copy->id ,
554 capture_time => { '!=' => undef },
555 cancel_time => undef,
556 fulfillment_time => undef
562 if( $holds and $$holds[0] ) {
563 return undef if $$holds[0]->usr == $patron->id;
566 $logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
568 $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF'));
574 my $copy = $self->copy;
577 my $stat = $U->copy_status($copy->status)->id;
579 # We cannot check out a copy if it is in-transit
580 if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) {
581 return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
584 $self->handle_claims_returned();
585 return if $self->bail_out;
587 # no claims returned circ was found, check if there is any open circ
588 unless( $self->is_renewal ) {
589 my $circs = $self->editor->search_action_circulation(
590 { target_copy => $copy->id, checkin_time => undef }
593 return $self->bail_on_events(
594 OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
599 # ---------------------------------------------------------------------
600 # This pushes any patron-related events into the list but does not
601 # set bail_out for any events
602 # ---------------------------------------------------------------------
603 sub run_patron_permit_scripts {
605 my $runner = $self->script_runner;
606 my $patronid = $self->patron->id;
608 # ---------------------------------------------------------------------
609 # Find all of the fatal penalties currently set on the user
610 # ---------------------------------------------------------------------
611 my $penalties = $U->update_patron_penalties(
612 authtoken => $self->editor->authtoken,
613 patron => $self->patron,
616 $penalties = $penalties->{fatal_penalties};
619 # ---------------------------------------------------------------------
620 # Now run the patron permit script
621 # ---------------------------------------------------------------------
622 $runner->load($self->circ_permit_patron);
623 my $result = $runner->run or
624 throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
626 my $patron_events = $result->{events};
628 push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
630 $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
632 $self->push_events(@allevents);
636 sub run_copy_permit_scripts {
638 my $copy = $self->copy || return;
639 my $runner = $self->script_runner;
641 # ---------------------------------------------------------------------
642 # Capture all of the copy permit events
643 # ---------------------------------------------------------------------
644 $runner->load($self->circ_permit_copy);
645 my $result = $runner->run or
646 throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
647 my $copy_events = $result->{events};
649 # ---------------------------------------------------------------------
650 # Now collect all of the events together
651 # ---------------------------------------------------------------------
653 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
655 # See if this copy has an alert message
656 my $ae = $self->check_copy_alert();
657 push( @allevents, $ae ) if $ae;
659 # uniquify the events
660 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
661 @allevents = values %hash;
664 $_->{payload} = $copy if
665 ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
668 $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
670 $self->push_events(@allevents);
674 sub check_copy_alert {
676 return OpenILS::Event->new(
677 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
678 if $self->copy and $self->copy->alert_message;
684 # --------------------------------------------------------------------------
685 # If the call is overriding and has permissions to override every collected
686 # event, the are cleared. Any event that the caller does not have
687 # permission to override, will be left in the event list and bail_out will
689 # XXX We need code in here to cancel any holds/transits on copies
690 # that are being force-checked out
691 # --------------------------------------------------------------------------
692 sub override_events {
694 my @events = @{$self->events};
695 return unless @events;
697 if(!$self->override) {
698 return $self->bail_out(1)
699 if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
704 for my $e (@events) {
705 my $tc = $e->{textcode};
706 next if $tc eq 'SUCCESS';
707 my $ov = "$tc.override";
708 $logger->info("circulator: attempting to override event: $ov");
710 return $self->bail_on_events($self->editor->event)
711 unless( $self->editor->allowed($ov) );
716 # --------------------------------------------------------------------------
717 # If there is an open claimsreturn circ on the requested copy, close the
718 # circ if overriding, otherwise bail out
719 # --------------------------------------------------------------------------
720 sub handle_claims_returned {
722 my $copy = $self->copy;
724 my $CR = $self->editor->search_action_circulation(
726 target_copy => $copy->id,
727 stop_fines => OILS_STOP_FINES_CLAIMSRETURNED,
728 checkin_time => undef,
732 return unless ($CR = $CR->[0]);
736 # - If the caller has set the override flag, we will check the item in
737 if($self->override) {
739 $CR->checkin_time('now');
740 $CR->checkin_lib($self->editor->requestor->ws_ou);
741 $CR->checkin_staff($self->editor->requestor->id);
743 $evt = $self->editor->event
744 unless $self->editor->update_action_circulation($CR);
747 $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
750 $self->bail_on_events($evt) if $evt;
755 # --------------------------------------------------------------------------
756 # This performs the checkout
757 # --------------------------------------------------------------------------
761 $self->log_me("do_checkout()");
763 # make sure perms are good if this isn't a renewal
764 unless( $self->is_renewal ) {
765 return $self->bail_on_events($self->editor->event)
766 unless( $self->editor->allowed('COPY_CHECKOUT') );
769 # verify the permit key
770 unless( $self->check_permit_key ) {
771 if( $self->permit_override ) {
772 return $self->bail_on_events($self->editor->event)
773 unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
775 return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
779 # if this is a non-cataloged circ, build the circ and finish
780 if( $self->is_noncat ) {
781 $self->checkout_noncat;
783 OpenILS::Event->new('SUCCESS',
784 payload => { noncat_circ => $self->circ }));
788 if( $self->is_precat ) {
789 $self->script_runner->insert("environment.isPrecat", 1, 1);
790 $self->make_precat_copy;
791 return if $self->bail_out;
793 } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
794 return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
797 $self->do_copy_checks;
798 return if $self->bail_out;
800 $self->run_checkout_scripts();
801 return if $self->bail_out;
803 $self->build_checkout_circ_object();
804 return if $self->bail_out;
806 $self->apply_modified_due_date();
807 return if $self->bail_out;
809 return $self->bail_on_events($self->editor->event)
810 unless $self->editor->create_action_circulation($self->circ);
812 $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
814 return if $self->bail_out;
816 $self->handle_checkout_holds();
817 return if $self->bail_out;
819 # ------------------------------------------------------------------------------
820 # Update the patron penalty info in the DB
821 # ------------------------------------------------------------------------------
822 $U->update_patron_penalties(
823 authtoken => $self->editor->authtoken,
824 patron => $self->patron,
828 my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
830 OpenILS::Event->new('SUCCESS',
832 copy => $U->unflesh_copy($self->copy),
835 holds_fulfilled => $self->fulfilled_holds,
843 my $copy = $self->copy;
845 my $stat = $copy->status if ref $copy->status;
846 my $loc = $copy->location if ref $copy->location;
847 my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
849 $copy->status($stat->id) if $stat;
850 $copy->location($loc->id) if $loc;
851 $copy->circ_lib($circ_lib->id) if $circ_lib;
852 $copy->editor($self->editor->requestor->id);
853 $copy->edit_date('now');
854 $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect;
856 return $self->bail_on_events($self->editor->event)
857 unless $self->editor->update_asset_copy($self->copy);
859 $copy->status($U->copy_status($copy->status));
860 $copy->location($loc) if $loc;
861 $copy->circ_lib($circ_lib) if $circ_lib;
866 my( $self, @evts ) = @_;
867 $self->push_events(@evts);
871 sub handle_checkout_holds {
874 my $copy = $self->copy;
875 my $patron = $self->patron;
877 my $holds = $self->editor->search_action_hold_request(
879 current_copy => $copy->id ,
880 cancel_time => undef,
881 fulfillment_time => undef
887 # XXX We should only fulfill one hold here...
888 # XXX If a hold was transited to the user who is checking out
889 # the item, we need to make sure that hold is what's grabbed
892 # for now, just sort by id to get what should be the oldest hold
893 $holds = [ sort { $a->id <=> $b->id } @$holds ];
894 my @myholds = grep { $_->usr eq $patron->id } @$holds;
895 my @altholds = grep { $_->usr ne $patron->id } @$holds;
898 my $hold = $myholds[0];
900 $logger->debug("circulator: related hold found in checkout: " . $hold->id );
902 # if the hold was never officially captured, capture it.
903 $hold->capture_time('now') unless $hold->capture_time;
905 # just make sure it's set correctly
906 $hold->current_copy($copy->id);
908 $hold->fulfillment_time('now');
909 $hold->fulfillment_staff($self->editor->requestor->id);
910 $hold->fulfillment_lib($self->editor->requestor->ws_ou);
912 return $self->bail_on_events($self->editor->event)
913 unless $self->editor->update_action_hold_request($hold);
915 $holdcode->delete_hold_copy_maps($self->editor, $hold->id);
917 push( @fulfilled, $hold->id );
920 # If there are any holds placed for other users that point to this copy,
921 # then we need to un-target those holds so the targeter can pick a new copy
924 $logger->info("circulator: un-targeting hold ".$_->id.
925 " because copy ".$copy->id." is getting checked out");
927 # - make the targeter process this hold at next run
928 $_->clear_prev_check_time;
930 # - clear out the targetted copy
931 $_->clear_current_copy;
932 $_->clear_capture_time;
934 return $self->bail_on_event($self->editor->event)
935 unless $self->editor->update_action_hold_request($_);
939 $self->fulfilled_holds(\@fulfilled);
944 sub run_checkout_scripts {
948 my $runner = $self->script_runner;
949 $runner->load($self->circ_duration);
951 my $result = $runner->run or
952 throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
954 my $duration = $result->{durationRule};
955 my $recurring = $result->{recurringFinesRule};
956 my $max_fine = $result->{maxFine};
958 if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) {
960 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
961 return $self->bail_on_events($evt) if $evt;
963 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
964 return $self->bail_on_events($evt) if $evt;
966 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
967 return $self->bail_on_events($evt) if $evt;
971 # The item circulates with an unlimited duration
977 $self->duration_rule($duration);
978 $self->recurring_fines_rule($recurring);
979 $self->max_fine_rule($max_fine);
983 sub build_checkout_circ_object {
986 my $circ = Fieldmapper::action::circulation->new;
987 my $duration = $self->duration_rule;
988 my $max = $self->max_fine_rule;
989 my $recurring = $self->recurring_fines_rule;
990 my $copy = $self->copy;
991 my $patron = $self->patron;
995 my $dname = $duration->name;
996 my $mname = $max->name;
997 my $rname = $recurring->name;
999 $logger->debug("circulator: building circulation ".
1000 "with duration=$dname, maxfine=$mname, recurring=$rname");
1002 $circ->duration( $duration->shrt )
1003 if $copy->loan_duration == OILS_CIRC_DURATION_SHORT;
1004 $circ->duration( $duration->normal )
1005 if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL;
1006 $circ->duration( $duration->extended )
1007 if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED;
1009 $circ->recuring_fine( $recurring->low )
1010 if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW;
1011 $circ->recuring_fine( $recurring->normal )
1012 if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL;
1013 $circ->recuring_fine( $recurring->high )
1014 if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH;
1016 $circ->duration_rule( $duration->name );
1017 $circ->recuring_fine_rule( $recurring->name );
1018 $circ->max_fine_rule( $max->name );
1019 $circ->max_fine( $max->amount );
1021 $circ->fine_interval($recurring->recurance_interval);
1022 $circ->renewal_remaining( $duration->max_renewals );
1026 $logger->info("circulator: copy found with an unlimited circ duration");
1027 $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION);
1028 $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1029 $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1030 $circ->renewal_remaining(0);
1033 $circ->target_copy( $copy->id );
1034 $circ->usr( $patron->id );
1035 $circ->circ_lib( $self->circ_lib );
1037 if( $self->is_renewal ) {
1038 $circ->opac_renewal(1);
1039 $circ->renewal_remaining($self->renewal_remaining);
1040 $circ->circ_staff($self->editor->requestor->id);
1043 # if the user provided an overiding checkout time,
1044 # (e.g. the checkout really happened several hours ago), then
1045 # we apply that here. Does this need a perm??
1046 $circ->xact_start(clense_ISO8601($self->checkout_time))
1047 if $self->checkout_time;
1049 # if a patron is renewing, 'requestor' will be the patron
1050 $circ->circ_staff($self->editor->requestor->id);
1051 $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
1057 sub apply_modified_due_date {
1059 my $circ = $self->circ;
1060 my $copy = $self->copy;
1062 if( $self->due_date ) {
1064 return $self->bail_on_events($self->editor->event)
1065 unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
1067 $circ->due_date(clense_ISO8601($self->due_date));
1071 # if the due_date lands on a day when the location is closed
1072 return unless $copy and $circ->due_date;
1074 my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
1076 $logger->info("circulator: circ searching for closed date overlap on lib $org".
1077 " with an item due date of ".$circ->due_date );
1079 my $dateinfo = $U->storagereq(
1080 'open-ils.storage.actor.org_unit.closed_date.overlap',
1081 $org, $circ->due_date );
1084 $logger->info("circulator: $dateinfo : circ due data / close date overlap found : due_date=".
1085 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1087 # XXX make the behavior more dynamic
1088 # for now, we just push the due date to after the close date
1089 $circ->due_date($dateinfo->{end});
1096 sub create_due_date {
1097 my( $self, $duration ) = @_;
1098 my ($sec,$min,$hour,$mday,$mon,$year) =
1099 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1100 $year += 1900; $mon += 1;
1101 my $due_date = sprintf(
1102 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1103 $year, $mon, $mday, $hour, $min, $sec);
1109 sub make_precat_copy {
1111 my $copy = $self->copy;
1114 $logger->debug("ciculator: Pre-cat copy already exists in checkout: ID=" . $copy->id);
1116 $copy->editor($self->editor->requestor->id);
1117 $copy->edit_date('now');
1118 $copy->dummy_title($self->dummy_title);
1119 $copy->dummy_author($self->dummy_author);
1121 $self->update_copy();
1125 $logger->info("circulator: Creating a new precataloged ".
1126 "copy in checkout with barcode " . $self->copy_barcode);
1128 $copy = Fieldmapper::asset::copy->new;
1129 $copy->circ_lib($self->circ_lib);
1130 $copy->creator($self->editor->requestor->id);
1131 $copy->editor($self->editor->requestor->id);
1132 $copy->barcode($self->copy_barcode);
1133 $copy->call_number(OILS_PRECAT_CALL_NUMBER);
1134 $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
1135 $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
1137 $copy->dummy_title($self->dummy_title || "");
1138 $copy->dummy_author($self->dummy_author || "");
1140 unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
1142 $self->push_events($self->editor->event);
1146 # this is a little bit of a hack, but we need to
1147 # get the copy into the script runner
1148 $self->script_runner->insert("environment.copy", $copy, 1);
1152 sub checkout_noncat {
1158 my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
1159 my $count = $self->noncat_count || 1;
1160 my $cotime = clense_ISO8601($self->checkout_time) || "";
1162 $logger->info("ciculator: circ creating $count noncat circs with checkout time $cotime");
1166 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1167 $self->editor->requestor->id,
1175 $self->push_events($evt);
1186 $self->log_me("do_checkin()");
1189 return $self->bail_on_events(
1190 OpenILS::Event->new('ASSET_COPY_NOT_FOUND'))
1193 if( $self->checkin_check_holds_shelf() ) {
1194 $self->bail_on_events(OpenILS::Event->new('NO_CHANGE'));
1195 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1196 $self->checkin_flesh_events;
1200 unless( $self->is_renewal ) {
1201 return $self->bail_on_events($self->editor->event)
1202 unless $self->editor->allowed('COPY_CHECKIN');
1205 $self->push_events($self->check_copy_alert());
1206 $self->push_events($self->check_checkin_copy_status());
1208 # the renew code will have already found our circulation object
1209 unless( $self->is_renewal and $self->circ ) {
1211 $self->editor->search_action_circulation(
1212 { target_copy => $self->copy->id, checkin_time => undef })->[0]);
1215 # if the circ is marked as 'claims returned', add the event to the list
1216 $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
1217 if ($self->circ and $self->circ->stop_fines
1218 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
1220 # handle the overridable events
1221 $self->override_events unless $self->is_renewal;
1222 return if $self->bail_out;
1226 $self->editor->search_action_transit_copy(
1227 { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);
1231 $self->checkin_handle_circ;
1232 return if $self->bail_out;
1233 $self->checkin_changed(1);
1235 } elsif( $self->transit ) {
1236 my $hold_transit = $self->process_received_transit;
1237 $self->checkin_changed(1);
1239 if( $self->bail_out ) {
1240 $self->checkin_flesh_events;
1244 if( my $e = $self->check_checkin_copy_status() ) {
1245 # If the original copy status is special, alert the caller
1246 my $ev = $self->events;
1247 $self->events([$e]);
1248 $self->override_events;
1249 return if $self->bail_out;
1253 if( $hold_transit or
1254 $U->copy_status($self->copy->status)->id
1255 == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1258 $self->editor->retrieve_action_hold_request($hold_transit->hold) :
1259 $U->fetch_open_hold_by_copy($self->copy->id)
1262 $self->checkin_flesh_events;
1267 if( $self->is_renewal ) {
1268 $self->push_events(OpenILS::Event->new('SUCCESS'));
1272 # ------------------------------------------------------------------------------
1273 # Circulations and transits are now closed where necessary. Now go on to see if
1274 # this copy can fulfill a hold or needs to be routed to a different location
1275 # ------------------------------------------------------------------------------
1277 if( !$self->remote_hold and $self->attempt_checkin_hold_capture() ) {
1278 return if $self->bail_out;
1280 } else { # not needed for a hold
1283 my $circ_lib = (ref $self->copy->circ_lib) ?
1284 $self->copy->circ_lib->id : $self->copy->circ_lib;
1286 if( $self->remote_hold ) {
1287 $circ_lib = $self->remote_hold->pickup_lib;
1288 $logger->warn("circulator: Copy ".$self->copy->barcode.
1289 " is on a remote hold's shelf, sending to $circ_lib");
1292 $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
1294 if( $circ_lib == $self->editor->requestor->ws_ou ) {
1296 $self->checkin_handle_precat();
1297 return if $self->bail_out;
1301 my $bc = $self->copy->barcode;
1302 $logger->info("circulator: copy $bc at a remote lib - sending home");
1303 $self->checkin_build_copy_transit();
1304 return if $self->bail_out;
1305 $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
1309 $self->reshelve_copy;
1310 return if $self->bail_out;
1312 unless($self->checkin_changed) {
1314 $self->push_events(OpenILS::Event->new('NO_CHANGE'));
1315 my $stat = $U->copy_status($self->copy->status)->id;
1317 $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
1318 if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF );
1319 $self->bail_out(1); # no need to commit anything
1322 $self->push_events(OpenILS::Event->new('SUCCESS'))
1323 unless @{$self->events};
1326 $self->checkin_flesh_events;
1332 my $force = $self->force || shift;
1333 my $copy = $self->copy;
1335 my $stat = $U->copy_status($copy->status)->id;
1338 $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and
1339 $stat != OILS_COPY_STATUS_CATALOGING and
1340 $stat != OILS_COPY_STATUS_IN_TRANSIT and
1341 $stat != OILS_COPY_STATUS_RESHELVING )) {
1343 $copy->status( OILS_COPY_STATUS_RESHELVING );
1345 $self->checkin_changed(1);
1350 # Returns true if the item is at the current location
1351 # because it was transited there for a hold and the
1352 # hold has not been fulfilled
1353 sub checkin_check_holds_shelf {
1355 return 0 unless $self->copy;
1358 $U->copy_status($self->copy->status)->id ==
1359 OILS_COPY_STATUS_ON_HOLDS_SHELF;
1361 # find the hold that put us on the holds shelf
1362 my $holds = $self->editor->search_action_hold_request(
1364 current_copy => $self->copy->id,
1365 capture_time => { '!=' => undef },
1366 fulfillment_time => undef,
1367 cancel_time => undef,
1371 return 0 unless @$holds;
1373 my $hold = $$holds[0];
1375 $logger->info("circulator: we found a captured, un-fulfilled hold [".
1376 $hold->id. "] for copy ".$self->copy->barcode);
1378 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1379 $logger->info("circulator: hold is for here .. we're done: ".$self->copy->barcode);
1383 $logger->info("circulator: hold is not for here..");
1384 $self->remote_hold($hold);
1389 sub checkin_handle_precat {
1391 my $copy = $self->copy;
1393 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1394 $copy->status(OILS_COPY_STATUS_CATALOGING);
1395 $self->update_copy();
1396 $self->checkin_changed(1);
1397 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1402 sub checkin_build_copy_transit {
1404 my $copy = $self->copy;
1405 my $transit = Fieldmapper::action::transit_copy->new;
1407 $transit->source($self->editor->requestor->ws_ou);
1408 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1409 $transit->target_copy($copy->id);
1410 $transit->source_send_time('now');
1411 $transit->copy_status( $U->copy_status($copy->status)->id );
1413 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1415 return $self->bail_on_events($self->editor->event)
1416 unless $self->editor->create_action_transit_copy($transit);
1418 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1420 $self->checkin_changed(1);
1424 sub attempt_checkin_hold_capture {
1426 my $copy = $self->copy;
1428 # See if this copy can fulfill any holds
1429 my ($hold) = $holdcode->find_nearest_permitted_hold(
1430 OpenSRF::AppSession->create('open-ils.storage'),
1431 $copy, $self->editor->requestor );
1434 $logger->debug("circulator: no potential permitted".
1435 "holds found for copy ".$copy->barcode);
1440 $logger->info("circulator: found permitted hold ".
1441 $hold->id . " for copy, capturing...");
1443 $hold->current_copy($copy->id);
1444 $hold->capture_time('now');
1446 # prevent DB errors caused by fetching
1447 # holds from storage, and updating through cstore
1448 $hold->clear_fulfillment_time;
1449 $hold->clear_fulfillment_staff;
1450 $hold->clear_fulfillment_lib;
1451 $hold->clear_expire_time;
1452 $hold->clear_cancel_time;
1453 $hold->clear_prev_check_time unless $hold->prev_check_time;
1455 $self->bail_on_events($self->editor->event)
1456 unless $self->editor->update_action_hold_request($hold);
1458 $self->checkin_changed(1);
1460 return 1 if $self->bail_out;
1462 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1464 # This hold was captured in the correct location
1465 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1466 $self->push_events(OpenILS::Event->new('SUCCESS'));
1468 $self->do_hold_notify($hold->id);
1472 # Hold needs to be picked up elsewhere. Build a hold
1473 # transit and route the item.
1474 $self->checkin_build_hold_transit();
1475 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1476 return 1 if $self->bail_out;
1478 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1481 # make sure we save the copy status
1486 sub do_hold_notify {
1487 my( $self, $holdid ) = @_;
1488 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1489 editor => $self->editor, hold_id => $holdid );
1491 if(!$notifier->event) {
1493 $logger->info("ciculator: attempt at sending hold notification for hold $holdid");
1495 my $stat = $notifier->send_email_notify;
1496 $logger->info("ciculator: hold notify succeeded for hold $holdid") if $stat eq '1';
1497 $logger->warn("ciculator: * hold notify failed for hold $holdid") if $stat ne '1';
1500 $logger->info("ciculator: Not sending hold notification since the patron has no email address");
1505 sub checkin_build_hold_transit {
1509 my $copy = $self->copy;
1510 my $hold = $self->hold;
1511 my $trans = Fieldmapper::action::hold_transit_copy->new;
1513 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1515 $trans->hold($hold->id);
1516 $trans->source($self->editor->requestor->ws_ou);
1517 $trans->dest($hold->pickup_lib);
1518 $trans->source_send_time("now");
1519 $trans->target_copy($copy->id);
1521 # when the copy gets to its destination, it will recover
1522 # this status - put it onto the holds shelf
1523 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1525 return $self->bail_on_events($self->editor->event)
1526 unless $self->editor->create_action_hold_transit_copy($trans);
1531 sub process_received_transit {
1533 my $copy = $self->copy;
1534 my $copyid = $self->copy->id;
1536 my $status_name = $U->copy_status($copy->status)->name;
1537 $logger->debug("circulator: attempting transit receive on ".
1538 "copy $copyid. Copy status is $status_name");
1540 my $transit = $self->transit;
1542 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1543 $logger->info("circulator: Fowarding transit on copy which is destined ".
1544 "for a different location. copy=$copyid,current ".
1545 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1547 return $self->bail_on_events(
1548 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1551 # The transit is received, set the receive time
1552 $transit->dest_recv_time('now');
1553 $self->bail_on_events($self->editor->event)
1554 unless $self->editor->update_action_transit_copy($transit);
1556 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1558 $logger->info("ciculator: Recovering original copy status in transit: ".$transit->copy_status);
1559 $copy->status( $transit->copy_status );
1560 $self->update_copy();
1561 return if $self->bail_out;
1565 $self->do_hold_notify($hold_transit->hold);
1570 OpenILS::Event->new(
1573 payload => { transit => $transit, holdtransit => $hold_transit } ));
1575 return $hold_transit;
1579 sub checkin_handle_circ {
1583 my $circ = $self->circ;
1584 my $copy = $self->copy;
1588 # backdate the circ if necessary
1589 if($self->backdate) {
1590 $self->checkin_handle_backdate;
1591 return if $self->bail_out;
1594 if(!$circ->stop_fines) {
1595 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1596 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1597 $circ->stop_fines_time('now') unless $self->backdate;
1598 $circ->stop_fines_time($self->backdate) if $self->backdate;
1601 # see if there are any fines owed on this circ. if not, close it
1602 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1603 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1605 # Set the checkin vars since we have the item
1606 $circ->checkin_time('now');
1607 $circ->checkin_staff($self->editor->requestor->id);
1608 $circ->checkin_lib($self->editor->requestor->ws_ou);
1610 my $circ_lib = (ref $self->copy->circ_lib) ?
1611 $self->copy->circ_lib->id : $self->copy->circ_lib;
1612 my $stat = $U->copy_status($self->copy->status)->id;
1614 # If the item is lost/missing and it needs to be sent home, don't
1615 # reshelve the copy, leave it lost/missing so the recipient will know
1616 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1617 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1618 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1621 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1626 return $self->bail_on_events($self->editor->event)
1627 unless $self->editor->update_action_circulation($circ);
1631 sub checkin_handle_backdate {
1634 my $bd = $self->backdate;
1635 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1636 $bd = "${bd}T23:59:59";
1638 my $bills = $self->editor->search_money_billing(
1640 billing_ts => { '>=' => $bd },
1641 xact => $self->circ->id,
1642 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1646 for my $bill (@$bills) {
1647 if( !$bill->voided or $bill->voided =~ /f/i ) {
1649 my $n = $bill->note || "";
1650 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1652 $self->bail_on_events($self->editor->event)
1653 unless $self->editor->update_money_billing($bill);
1660 # XXX Legacy version for Circ.pm support
1661 sub _checkin_handle_backdate {
1662 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1665 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1666 $bd = "${bd}T23:59:59";
1669 my $bills = $session->request(
1670 "open-ils.storage.direct.money.billing.search_where.atomic",
1671 billing_ts => { '>=' => $bd },
1673 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1677 for my $bill (@$bills) {
1679 my $n = $bill->note || "";
1680 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1681 my $s = $session->request(
1682 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1683 return $U->DB_UPDATE_FAILED($bill) unless $s;
1693 sub find_patron_from_copy {
1695 my $circs = $self->editor->search_action_circulation(
1696 { target_copy => $self->copy->id, checkin_time => undef });
1697 my $circ = $circs->[0];
1698 return unless $circ;
1699 my $u = $self->editor->retrieve_actor_user($circ->usr)
1700 or return $self->bail_on_events($self->editor->event);
1704 sub check_checkin_copy_status {
1706 my $copy = $self->copy;
1712 my $status = $U->copy_status($copy->status)->id;
1715 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1716 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1717 $status == OILS_COPY_STATUS_IN_PROCESS ||
1718 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1719 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1720 $status == OILS_COPY_STATUS_CATALOGING ||
1721 $status == OILS_COPY_STATUS_RESHELVING );
1723 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1724 if( $status == OILS_COPY_STATUS_LOST );
1726 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1727 if( $status == OILS_COPY_STATUS_MISSING );
1729 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1734 # --------------------------------------------------------------------------
1735 # On checkin, we need to return as many relevant objects as we can
1736 # --------------------------------------------------------------------------
1737 sub checkin_flesh_events {
1740 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1741 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1742 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1746 for my $evt (@{$self->events}) {
1749 $payload->{copy} = $U->unflesh_copy($self->copy);
1750 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1751 $payload->{circ} = $self->circ;
1752 $payload->{transit} = $self->transit;
1753 $payload->{hold} = $self->hold;
1755 $evt->{payload} = $payload;
1760 my( $self, $msg ) = @_;
1761 my $bc = ($self->copy) ? $self->copy->barcode :
1764 my $usr = ($self->patron) ? $self->patron->id : "";
1765 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1766 ", recipient=$usr, copy=$bc");
1772 $self->log_me("do_renew()");
1773 $self->is_renewal(1);
1775 unless( $self->is_renewal ) {
1776 return $self->bail_on_events($self->editor->events)
1777 unless $self->editor->allowed('RENEW_CIRC');
1780 # Make sure there is an open circ to renew that is not
1781 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1782 my $circ = $self->editor->search_action_circulation(
1783 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1786 $circ = $self->editor->search_action_circulation(
1788 target_copy => $self->copy->id,
1789 stop_fines => OILS_STOP_FINES_MAX_FINES,
1790 checkin_time => undef
1795 return $self->bail_on_events($self->editor->event) unless $circ;
1797 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1798 if $circ->renewal_remaining < 1;
1800 # -----------------------------------------------------------------
1802 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1805 $self->run_renew_permit;
1808 $self->do_checkin();
1809 return if $self->bail_out;
1811 unless( $self->permit_override ) {
1813 return if $self->bail_out;
1814 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1815 $self->remove_event('ITEM_NOT_CATALOGED');
1818 $self->override_events;
1819 return if $self->bail_out;
1822 $self->do_checkout();
1827 my( $self, $evt ) = @_;
1828 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1829 $logger->debug("circulator: removing event from list: $evt");
1830 my @events = @{$self->events};
1831 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1836 my( $self, $evt ) = @_;
1837 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1838 return grep { $_->{textcode} eq $evt } @{$self->events};
1843 sub run_renew_permit {
1845 my $runner = $self->script_runner;
1847 $runner->load($self->circ_permit_renew);
1848 my $result = $runner->run or
1849 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1850 my $events = $result->{events};
1852 $logger->activity("ciculator: circ_permit_renew for user ".
1853 $self->patron->id." returned events: @$events") if @$events;
1855 $self->push_events(OpenILS::Event->new($_)) for @$events;
1857 $logger->debug("circulator: re-creating script runner to be safe");
1858 $self->mk_script_runner;