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 "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->xact_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("$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("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 my $s = $U->copy_status($copy->status)->id;
527 return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF;
528 $logger->info("circulator: copy is on holds shelf, searching for the correct hold");
530 # Item is on the holds shelf, make sure it's going to the right person
531 my $holds = $self->editor->search_action_hold_request(
534 current_copy => $copy->id ,
535 capture_time => { '!=' => undef },
536 cancel_time => undef,
537 fulfillment_time => undef
543 if( $holds and $$holds[0] ) {
544 return undef if $$holds[0]->usr == $patron->id;
547 $logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
549 $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF'));
555 my $copy = $self->copy;
558 my $stat = $U->copy_status($copy->status)->id;
560 # We cannot check out a copy if it is in-transit
561 if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) {
562 return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
565 $self->handle_claims_returned();
566 return if $self->bail_out;
568 # no claims returned circ was found, check if there is any open circ
569 unless( $self->is_renewal ) {
570 my $circs = $self->editor->search_action_circulation(
571 { target_copy => $copy->id, checkin_time => undef }
574 return $self->bail_on_events(
575 OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
580 # ---------------------------------------------------------------------
581 # This pushes any patron-related events into the list but does not
582 # set bail_out for any events
583 # ---------------------------------------------------------------------
584 sub run_patron_permit_scripts {
586 my $runner = $self->script_runner;
587 my $patronid = $self->patron->id;
589 # ---------------------------------------------------------------------
590 # Find all of the fatal penalties currently set on the user
591 # ---------------------------------------------------------------------
592 my $penalties = $U->update_patron_penalties(
593 authtoken => $self->editor->authtoken,
594 patron => $self->patron,
597 $penalties = $penalties->{fatal_penalties};
600 # ---------------------------------------------------------------------
601 # Now run the patron permit script
602 # ---------------------------------------------------------------------
603 $runner->load($self->circ_permit_patron);
604 my $result = $runner->run or
605 throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
607 my $patron_events = $result->{events};
609 push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
611 $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
613 $self->push_events(@allevents);
617 sub run_copy_permit_scripts {
619 my $copy = $self->copy || return;
620 my $runner = $self->script_runner;
622 # ---------------------------------------------------------------------
623 # Capture all of the copy permit events
624 # ---------------------------------------------------------------------
625 $runner->load($self->circ_permit_copy);
626 my $result = $runner->run or
627 throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
628 my $copy_events = $result->{events};
630 # ---------------------------------------------------------------------
631 # Now collect all of the events together
632 # ---------------------------------------------------------------------
634 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
636 # See if this copy has an alert message
637 my $ae = $self->check_copy_alert();
638 push( @allevents, $ae ) if $ae;
640 # uniquify the events
641 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
642 @allevents = values %hash;
645 $_->{payload} = $copy if
646 ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
649 $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
651 $self->push_events(@allevents);
655 sub check_copy_alert {
657 return OpenILS::Event->new(
658 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
659 if $self->copy and $self->copy->alert_message;
665 # --------------------------------------------------------------------------
666 # If the call is overriding and has permissions to override every collected
667 # event, the are cleared. Any event that the caller does not have
668 # permission to override, will be left in the event list and bail_out will
670 # XXX We need code in here to cancel any holds/transits on copies
671 # that are being force-checked out
672 # --------------------------------------------------------------------------
673 sub override_events {
675 my @events = @{$self->events};
676 return unless @events;
678 if(!$self->override) {
679 return $self->bail_out(1)
680 if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
685 for my $e (@events) {
686 my $tc = $e->{textcode};
687 next if $tc eq 'SUCCESS';
688 my $ov = "$tc.override";
689 $logger->info("circulator: attempting to override event: $ov");
691 return $self->bail_on_events($self->editor->event)
692 unless( $self->editor->allowed($ov) );
697 # --------------------------------------------------------------------------
698 # If there is an open claimsreturn circ on the requested copy, close the
699 # circ if overriding, otherwise bail out
700 # --------------------------------------------------------------------------
701 sub handle_claims_returned {
703 my $copy = $self->copy;
705 my $CR = $self->editor->search_action_circulation(
707 target_copy => $copy->id,
708 stop_fines => OILS_STOP_FINES_CLAIMSRETURNED,
709 checkin_time => undef,
713 return unless ($CR = $CR->[0]);
717 # - If the caller has set the override flag, we will check the item in
718 if($self->override) {
720 $CR->checkin_time('now');
721 $CR->checkin_lib($self->editor->requestor->ws_ou);
722 $CR->checkin_staff($self->editor->requestor->id);
724 $evt = $self->editor->event
725 unless $self->editor->update_action_circulation($CR);
728 $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
731 $self->bail_on_events($evt) if $evt;
736 # --------------------------------------------------------------------------
737 # This performs the checkout
738 # --------------------------------------------------------------------------
742 $self->log_me("do_checkout()");
744 # make sure perms are good if this isn't a renewal
745 unless( $self->is_renewal ) {
746 return $self->bail_on_events($self->editor->event)
747 unless( $self->editor->allowed('COPY_CHECKOUT') );
750 # verify the permit key
751 unless( $self->check_permit_key ) {
752 if( $self->permit_override ) {
753 return $self->bail_on_events($self->editor->event)
754 unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
756 return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
760 # if this is a non-cataloged circ, build the circ and finish
761 if( $self->is_noncat ) {
762 $self->checkout_noncat;
764 OpenILS::Event->new('SUCCESS',
765 payload => { noncat_circ => $self->circ }));
769 if( $self->is_precat ) {
770 $self->script_runner->insert("environment.isPrecat", 1, 1);
771 $self->make_precat_copy;
772 return if $self->bail_out;
774 } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
775 return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
778 $self->do_copy_checks;
779 return if $self->bail_out;
781 $self->run_checkout_scripts();
782 return if $self->bail_out;
784 $self->build_checkout_circ_object();
785 return if $self->bail_out;
787 $self->apply_modified_due_date();
788 return if $self->bail_out;
790 return $self->bail_on_events($self->editor->event)
791 unless $self->editor->create_action_circulation($self->circ);
793 $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
795 return if $self->bail_out;
797 $self->handle_checkout_holds();
798 return if $self->bail_out;
800 # ------------------------------------------------------------------------------
801 # Update the patron penalty info in the DB
802 # ------------------------------------------------------------------------------
803 $U->update_patron_penalties(
804 authtoken => $self->editor->authtoken,
805 patron => $self->patron,
809 my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
811 OpenILS::Event->new('SUCCESS',
813 copy => $U->unflesh_copy($self->copy),
816 holds_fulfilled => $self->fulfilled_holds,
824 my $copy = $self->copy;
826 my $stat = $copy->status if ref $copy->status;
827 my $loc = $copy->location if ref $copy->location;
828 my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
830 $copy->status($stat->id) if $stat;
831 $copy->location($loc->id) if $loc;
832 $copy->circ_lib($circ_lib->id) if $circ_lib;
833 $copy->editor($self->editor->requestor->id);
834 $copy->edit_date('now');
835 $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect;
837 return $self->bail_on_events($self->editor->event)
838 unless $self->editor->update_asset_copy($self->copy);
840 $copy->status($U->copy_status($copy->status));
841 $copy->location($loc) if $loc;
842 $copy->circ_lib($circ_lib) if $circ_lib;
847 my( $self, @evts ) = @_;
848 $self->push_events(@evts);
852 sub handle_checkout_holds {
855 my $copy = $self->copy;
856 my $patron = $self->patron;
858 my $holds = $self->editor->search_action_hold_request(
860 current_copy => $copy->id ,
861 cancel_time => undef,
862 fulfillment_time => undef
868 # XXX We should only fulfill one hold here...
869 # XXX If a hold was transited to the user who is checking out
870 # the item, we need to make sure that hold is what's grabbed
873 # for now, just sort by id to get what should be the oldest hold
874 $holds = [ sort { $a->id <=> $b->id } @$holds ];
875 my @myholds = grep { $_->usr eq $patron->id } @$holds;
876 my @altholds = grep { $_->usr ne $patron->id } @$holds;
879 my $hold = $myholds[0];
881 $logger->debug("circulator: related hold found in checkout: " . $hold->id );
883 # if the hold was never officially captured, capture it.
884 $hold->capture_time('now') unless $hold->capture_time;
886 # just make sure it's set correctly
887 $hold->current_copy($copy->id);
889 $hold->fulfillment_time('now');
890 $hold->fulfillment_staff($self->editor->requestor->id);
891 $hold->fulfillment_lib($self->editor->requestor->ws_ou);
893 return $self->bail_on_events($self->editor->event)
894 unless $self->editor->update_action_hold_request($hold);
896 $holdcode->delete_hold_copy_maps($self->editor, $hold->id);
898 push( @fulfilled, $hold->id );
901 # If there are any holds placed for other users that point to this copy,
902 # then we need to un-target those holds so the targeter can pick a new copy
905 $logger->info("circulator: un-targeting hold ".$_->id.
906 " because copy ".$copy->id." is getting checked out");
908 # - make the targeter process this hold at next run
909 $_->clear_prev_check_time;
911 # - clear out the targetted copy
912 $_->clear_current_copy;
913 $_->clear_capture_time;
915 return $self->bail_on_event($self->editor->event)
916 unless $self->editor->update_action_hold_request($_);
920 $self->fulfilled_holds(\@fulfilled);
925 sub run_checkout_scripts {
929 my $runner = $self->script_runner;
930 $runner->load($self->circ_duration);
932 my $result = $runner->run or
933 throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
935 my $duration = $result->{durationRule};
936 my $recurring = $result->{recurringFinesRule};
937 my $max_fine = $result->{maxFine};
939 if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) {
941 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
942 return $self->bail_on_events($evt) if $evt;
944 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
945 return $self->bail_on_events($evt) if $evt;
947 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
948 return $self->bail_on_events($evt) if $evt;
952 # The item circulates with an unlimited duration
958 $self->duration_rule($duration);
959 $self->recurring_fines_rule($recurring);
960 $self->max_fine_rule($max_fine);
964 sub build_checkout_circ_object {
967 my $circ = Fieldmapper::action::circulation->new;
968 my $duration = $self->duration_rule;
969 my $max = $self->max_fine_rule;
970 my $recurring = $self->recurring_fines_rule;
971 my $copy = $self->copy;
972 my $patron = $self->patron;
976 my $dname = $duration->name;
977 my $mname = $max->name;
978 my $rname = $recurring->name;
980 $logger->debug("circulator: building circulation ".
981 "with duration=$dname, maxfine=$mname, recurring=$rname");
983 $circ->duration( $duration->shrt )
984 if $copy->loan_duration == OILS_CIRC_DURATION_SHORT;
985 $circ->duration( $duration->normal )
986 if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL;
987 $circ->duration( $duration->extended )
988 if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED;
990 $circ->recuring_fine( $recurring->low )
991 if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW;
992 $circ->recuring_fine( $recurring->normal )
993 if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL;
994 $circ->recuring_fine( $recurring->high )
995 if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH;
997 $circ->duration_rule( $duration->name );
998 $circ->recuring_fine_rule( $recurring->name );
999 $circ->max_fine_rule( $max->name );
1000 $circ->max_fine( $max->amount );
1002 $circ->fine_interval($recurring->recurance_interval);
1003 $circ->renewal_remaining( $duration->max_renewals );
1007 $logger->info("circulator: copy found with an unlimited circ duration");
1008 $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION);
1009 $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1010 $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1011 $circ->renewal_remaining(0);
1014 $circ->target_copy( $copy->id );
1015 $circ->usr( $patron->id );
1016 $circ->circ_lib( $self->circ_lib );
1018 if( $self->is_renewal ) {
1019 $circ->opac_renewal(1);
1020 $circ->renewal_remaining($self->renewal_remaining);
1021 $circ->circ_staff($self->editor->requestor->id);
1024 # if the user provided an overiding checkout time,
1025 # (e.g. the checkout really happened several hours ago), then
1026 # we apply that here. Does this need a perm??
1027 $circ->xact_start(clense_ISO8601($self->checkout_time))
1028 if $self->checkout_time;
1030 # if a patron is renewing, 'requestor' will be the patron
1031 $circ->circ_staff($self->editor->requestor->id);
1032 $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
1038 sub apply_modified_due_date {
1040 my $circ = $self->circ;
1041 my $copy = $self->copy;
1043 if( $self->due_date ) {
1045 return $self->bail_on_events($self->editor->event)
1046 unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
1048 $circ->due_date(clense_ISO8601($self->due_date));
1052 # if the due_date lands on a day when the location is closed
1053 return unless $copy and $circ->due_date;
1055 my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
1057 $logger->info("circ searching for closed date overlap on lib $org".
1058 " with an item due date of ".$circ->due_date );
1060 my $dateinfo = $U->storagereq(
1061 'open-ils.storage.actor.org_unit.closed_date.overlap',
1062 $org, $circ->due_date );
1065 $logger->info("$dateinfo : circ due data / close date overlap found : due_date=".
1066 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1068 # XXX make the behavior more dynamic
1069 # for now, we just push the due date to after the close date
1070 $circ->due_date($dateinfo->{end});
1077 sub create_due_date {
1078 my( $self, $duration ) = @_;
1079 my ($sec,$min,$hour,$mday,$mon,$year) =
1080 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1081 $year += 1900; $mon += 1;
1082 my $due_date = sprintf(
1083 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1084 $year, $mon, $mday, $hour, $min, $sec);
1090 sub make_precat_copy {
1092 my $copy = $self->copy;
1095 $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
1097 $copy->editor($self->editor->requestor->id);
1098 $copy->edit_date('now');
1099 $copy->dummy_title($self->dummy_title);
1100 $copy->dummy_author($self->dummy_author);
1102 $self->update_copy();
1106 $logger->info("circulator: Creating a new precataloged ".
1107 "copy in checkout with barcode " . $self->copy_barcode);
1109 $copy = Fieldmapper::asset::copy->new;
1110 $copy->circ_lib($self->circ_lib);
1111 $copy->creator($self->editor->requestor->id);
1112 $copy->editor($self->editor->requestor->id);
1113 $copy->barcode($self->copy_barcode);
1114 $copy->call_number(OILS_PRECAT_CALL_NUMBER);
1115 $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
1116 $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
1118 $copy->dummy_title($self->dummy_title || "");
1119 $copy->dummy_author($self->dummy_author || "");
1121 unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
1123 $self->push_events($self->editor->event);
1127 # this is a little bit of a hack, but we need to
1128 # get the copy into the script runner
1129 $self->script_runner->insert("environment.copy", $copy, 1);
1133 sub checkout_noncat {
1139 my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
1140 my $count = $self->noncat_count || 1;
1141 my $cotime = clense_ISO8601($self->checkout_time) || "";
1143 $logger->info("circ creating $count noncat circs with checkout time $cotime");
1147 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1148 $self->editor->requestor->id,
1156 $self->push_events($evt);
1167 $self->log_me("do_checkin()");
1169 return $self->bail_on_events(
1170 OpenILS::Event->new('ASSET_COPY_NOT_FOUND'))
1173 unless( $self->is_renewal ) {
1174 return $self->bail_on_events($self->editor->event)
1175 unless $self->editor->allowed('COPY_CHECKIN');
1178 $self->push_events($self->check_copy_alert());
1179 $self->push_events($self->check_checkin_copy_status());
1181 # the renew code will have already found our circulation object
1182 unless( $self->is_renewal and $self->circ ) {
1184 $self->editor->search_action_circulation(
1185 { target_copy => $self->copy->id, checkin_time => undef })->[0]);
1188 # if the circ is marked as 'claims returned', add the event to the list
1189 $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
1190 if ($self->circ and $self->circ->stop_fines
1191 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
1193 # handle the overridable events
1194 $self->override_events unless $self->is_renewal;
1195 return if $self->bail_out;
1199 $self->editor->search_action_transit_copy(
1200 { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);
1204 $self->checkin_handle_circ;
1205 return if $self->bail_out;
1206 $self->checkin_changed(1);
1208 } elsif( $self->transit ) {
1209 my $hold_transit = $self->process_received_transit;
1210 $self->checkin_changed(1);
1212 if( $self->bail_out ) {
1213 $self->checkin_flesh_events;
1217 if( my $e = $self->check_checkin_copy_status() ) {
1218 # If the original copy status is special, alert the caller
1219 my $ev = $self->events;
1220 $self->events([$e]);
1221 $self->override_events;
1222 return if $self->bail_out;
1227 if( $hold_transit or
1228 $U->copy_status($self->copy->status)->id
1229 == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1230 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1231 $self->checkin_flesh_events;
1236 if( $self->is_renewal ) {
1237 $self->push_events(OpenILS::Event->new('SUCCESS'));
1241 # ------------------------------------------------------------------------------
1242 # Circulations and transits are now closed where necessary. Now go on to see if
1243 # this copy can fulfill a hold or needs to be routed to a different location
1244 # ------------------------------------------------------------------------------
1246 if( $self->attempt_checkin_hold_capture() ) {
1247 return if $self->bail_out;
1249 } else { # not needed for a hold
1251 my $circ_lib = (ref $self->copy->circ_lib) ?
1252 $self->copy->circ_lib->id : $self->copy->circ_lib;
1254 $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
1256 if( $circ_lib == $self->editor->requestor->ws_ou ) {
1258 $self->checkin_handle_precat();
1259 return if $self->bail_out;
1263 $self->checkin_build_copy_transit();
1264 return if $self->bail_out;
1265 $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
1270 $self->reshelve_copy;
1271 return if $self->bail_out;
1273 unless($self->checkin_changed) {
1275 $self->push_events(OpenILS::Event->new('NO_CHANGE'));
1276 my $stat = $U->copy_status($self->copy->status)->id;
1278 $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
1279 if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF );
1280 $self->bail_out(1); # no need to commit anything
1283 $self->push_events(OpenILS::Event->new('SUCCESS'))
1284 unless @{$self->events};
1287 $self->checkin_flesh_events;
1293 my $force = $self->force || shift;
1294 my $copy = $self->copy;
1296 my $stat = $U->copy_status($copy->status)->id;
1299 $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and
1300 $stat != OILS_COPY_STATUS_CATALOGING and
1301 $stat != OILS_COPY_STATUS_IN_TRANSIT and
1302 $stat != OILS_COPY_STATUS_RESHELVING )) {
1304 $copy->status( OILS_COPY_STATUS_RESHELVING );
1306 $self->checkin_changed(1);
1311 sub checkin_handle_precat {
1313 my $copy = $self->copy;
1315 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1316 $copy->status(OILS_COPY_STATUS_CATALOGING);
1317 $self->update_copy();
1318 $self->checkin_changed(1);
1319 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1324 sub checkin_build_copy_transit {
1326 my $copy = $self->copy;
1327 my $transit = Fieldmapper::action::transit_copy->new;
1329 $transit->source($self->editor->requestor->ws_ou);
1330 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1331 $transit->target_copy($copy->id);
1332 $transit->source_send_time('now');
1333 $transit->copy_status( $U->copy_status($copy->status)->id );
1335 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1337 return $self->bail_on_events($self->editor->event)
1338 unless $self->editor->create_action_transit_copy($transit);
1340 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1342 $self->checkin_changed(1);
1346 sub attempt_checkin_hold_capture {
1348 my $copy = $self->copy;
1350 # See if this copy can fulfill any holds
1351 my ($hold) = $holdcode->find_nearest_permitted_hold(
1352 OpenSRF::AppSession->create('open-ils.storage'),
1353 $copy, $self->editor->requestor );
1356 $logger->debug("circulator: no potential permitted".
1357 "holds found for copy ".$copy->barcode);
1362 $logger->info("circulator: found permitted hold ".
1363 $hold->id . " for copy, capturing...");
1365 $hold->current_copy($copy->id);
1366 $hold->capture_time('now');
1368 # prevent DB errors caused by fetching
1369 # holds from storage, and updating through cstore
1370 $hold->clear_fulfillment_time;
1371 $hold->clear_fulfillment_staff;
1372 $hold->clear_fulfillment_lib;
1373 $hold->clear_expire_time;
1374 $hold->clear_cancel_time;
1375 $hold->clear_prev_check_time unless $hold->prev_check_time;
1377 $self->bail_on_events($self->editor->event)
1378 unless $self->editor->update_action_hold_request($hold);
1380 $self->checkin_changed(1);
1382 return 1 if $self->bail_out;
1384 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1386 # This hold was captured in the correct location
1387 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1388 $self->push_events(OpenILS::Event->new('SUCCESS'));
1390 $self->do_hold_notify($hold->id);
1394 # Hold needs to be picked up elsewhere. Build a hold
1395 # transit and route the item.
1396 $self->checkin_build_hold_transit();
1397 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1398 return 1 if $self->bail_out;
1400 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1403 # make sure we save the copy status
1408 sub do_hold_notify {
1409 my( $self, $holdid ) = @_;
1410 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1411 editor => $self->editor, hold_id => $holdid );
1413 if(!$notifier->event) {
1415 $logger->info("attempt at sending hold notification for hold $holdid");
1417 my $stat = $notifier->send_email_notify;
1418 $logger->info("hold notify succeeded for hold $holdid") if $stat eq '1';
1419 $logger->warn(" * hold notify failed for hold $holdid") if $stat ne '1';
1422 $logger->info("Not sending hold notification since the patron has no email address");
1427 sub checkin_build_hold_transit {
1431 my $copy = $self->copy;
1432 my $hold = $self->hold;
1433 my $trans = Fieldmapper::action::hold_transit_copy->new;
1435 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1437 $trans->hold($hold->id);
1438 $trans->source($self->editor->requestor->ws_ou);
1439 $trans->dest($hold->pickup_lib);
1440 $trans->source_send_time("now");
1441 $trans->target_copy($copy->id);
1443 # when the copy gets to its destination, it will recover
1444 # this status - put it onto the holds shelf
1445 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1447 return $self->bail_on_events($self->editor->event)
1448 unless $self->editor->create_action_hold_transit_copy($trans);
1453 sub process_received_transit {
1455 my $copy = $self->copy;
1456 my $copyid = $self->copy->id;
1458 my $status_name = $U->copy_status($copy->status)->name;
1459 $logger->debug("circulator: attempting transit receive on ".
1460 "copy $copyid. Copy status is $status_name");
1462 my $transit = $self->transit;
1464 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1465 $logger->info("circulator: Fowarding transit on copy which is destined ".
1466 "for a different location. copy=$copyid,current ".
1467 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1469 return $self->bail_on_events(
1470 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1473 # The transit is received, set the receive time
1474 $transit->dest_recv_time('now');
1475 $self->bail_on_events($self->editor->event)
1476 unless $self->editor->update_action_transit_copy($transit);
1478 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1480 $logger->info("Recovering original copy status in transit: ".$transit->copy_status);
1481 $copy->status( $transit->copy_status );
1482 $self->update_copy();
1483 return if $self->bail_out;
1487 $self->do_hold_notify($hold_transit->hold);
1492 OpenILS::Event->new(
1495 payload => { transit => $transit, holdtransit => $hold_transit } ));
1497 return $hold_transit;
1501 sub checkin_handle_circ {
1505 my $circ = $self->circ;
1506 my $copy = $self->copy;
1510 # backdate the circ if necessary
1511 if($self->backdate) {
1512 $self->checkin_handle_backdate;
1513 return if $self->bail_out;
1516 if(!$circ->stop_fines) {
1517 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1518 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1519 $circ->stop_fines_time('now') unless $self->backdate;
1520 $circ->stop_fines_time($self->backdate) if $self->backdate;
1523 # see if there are any fines owed on this circ. if not, close it
1524 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1525 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1527 # Set the checkin vars since we have the item
1528 $circ->checkin_time('now');
1529 $circ->checkin_staff($self->editor->requestor->id);
1530 $circ->checkin_lib($self->editor->requestor->ws_ou);
1532 my $circ_lib = (ref $self->copy->circ_lib) ?
1533 $self->copy->circ_lib->id : $self->copy->circ_lib;
1534 my $stat = $U->copy_status($self->copy->status)->id;
1536 # If the item is lost/missing and it needs to be sent home, don't
1537 # reshelve the copy, leave it lost/missing so the recipient will know
1538 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1539 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1540 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1543 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1548 return $self->bail_on_events($self->editor->event)
1549 unless $self->editor->update_action_circulation($circ);
1553 sub checkin_handle_backdate {
1556 my $bd = $self->backdate;
1557 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1558 $bd = "${bd}T23:59:59";
1560 my $bills = $self->editor->search_money_billing(
1562 billing_ts => { '>=' => $bd },
1563 xact => $self->circ->id,
1564 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1568 for my $bill (@$bills) {
1569 if( !$bill->voided or $bill->voided =~ /f/i ) {
1571 my $n = $bill->note || "";
1572 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1574 $self->bail_on_events($self->editor->event)
1575 unless $self->editor->update_money_billing($bill);
1582 # XXX Legacy version for Circ.pm support
1583 sub _checkin_handle_backdate {
1584 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1587 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1588 $bd = "${bd}T23:59:59";
1591 my $bills = $session->request(
1592 "open-ils.storage.direct.money.billing.search_where.atomic",
1593 billing_ts => { '>=' => $bd },
1595 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1599 for my $bill (@$bills) {
1601 my $n = $bill->note || "";
1602 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1603 my $s = $session->request(
1604 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1605 return $U->DB_UPDATE_FAILED($bill) unless $s;
1615 sub find_patron_from_copy {
1617 my $circs = $self->editor->search_action_circulation(
1618 { target_copy => $self->copy->id, checkin_time => undef });
1619 my $circ = $circs->[0];
1620 return unless $circ;
1621 my $u = $self->editor->retrieve_actor_user($circ->usr)
1622 or return $self->bail_on_events($self->editor->event);
1626 sub check_checkin_copy_status {
1628 my $copy = $self->copy;
1634 my $status = $U->copy_status($copy->status)->id;
1637 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1638 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1639 $status == OILS_COPY_STATUS_IN_PROCESS ||
1640 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1641 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1642 $status == OILS_COPY_STATUS_CATALOGING ||
1643 $status == OILS_COPY_STATUS_RESHELVING );
1645 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1646 if( $status == OILS_COPY_STATUS_LOST );
1648 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1649 if( $status == OILS_COPY_STATUS_MISSING );
1651 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1656 # --------------------------------------------------------------------------
1657 # On checkin, we need to return as many relevant objects as we can
1658 # --------------------------------------------------------------------------
1659 sub checkin_flesh_events {
1662 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1663 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1664 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1668 for my $evt (@{$self->events}) {
1671 $payload->{copy} = $U->unflesh_copy($self->copy);
1672 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1673 $payload->{circ} = $self->circ;
1674 $payload->{transit} = $self->transit;
1675 $payload->{hold} = $self->hold;
1677 $evt->{payload} = $payload;
1682 my( $self, $msg ) = @_;
1683 my $bc = ($self->copy) ? $self->copy->barcode :
1686 my $usr = ($self->patron) ? $self->patron->id : "";
1687 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1688 ", recipient=$usr, copy=$bc");
1694 $self->log_me("do_renew()");
1695 $self->is_renewal(1);
1697 unless( $self->is_renewal ) {
1698 return $self->bail_on_events($self->editor->events)
1699 unless $self->editor->allowed('RENEW_CIRC');
1702 # Make sure there is an open circ to renew that is not
1703 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1704 my $circ = $self->editor->search_action_circulation(
1705 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1708 $circ = $self->editor->search_action_circulation(
1710 target_copy => $self->copy->id,
1711 stop_fines => OILS_STOP_FINES_MAX_FINES,
1712 checkin_time => undef
1717 return $self->bail_on_events($self->editor->event) unless $circ;
1719 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1720 if $circ->renewal_remaining < 1;
1722 # -----------------------------------------------------------------
1724 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1727 $self->run_renew_permit;
1730 $self->do_checkin();
1731 return if $self->bail_out;
1733 unless( $self->permit_override ) {
1735 return if $self->bail_out;
1736 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1737 $self->remove_event('ITEM_NOT_CATALOGED');
1740 $self->override_events;
1741 return if $self->bail_out;
1744 $self->do_checkout();
1749 my( $self, $evt ) = @_;
1750 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1751 $logger->debug("circulator: removing event from list: $evt");
1752 my @events = @{$self->events};
1753 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1758 my( $self, $evt ) = @_;
1759 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1760 return grep { $_->{textcode} eq $evt } @{$self->events};
1765 sub run_renew_permit {
1767 my $runner = $self->script_runner;
1769 $runner->load($self->circ_permit_renew);
1770 my $result = $runner->run or
1771 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1772 my $events = $result->{events};
1774 $logger->activity("circ_permit_renew for user ".
1775 $self->patron->id." returned events: @$events") if @$events;
1777 $self->push_events(OpenILS::Event->new($_)) for @$events;
1779 $logger->debug("circulator: re-creating script runner to be safe");
1780 $self->mk_script_runner;