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 # Set some circ-specific flags in the script environment
460 my $evt = "environment";
461 $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef);
463 if( $self->is_noncat ) {
464 $self->script_runner->insert("$evt.isNonCat", 1);
465 $self->script_runner->insert("$evt.nonCatType", $self->noncat_type);
468 if( $self->is_precat ) {
469 $self->script_runner->insert("environment.isPrecat", 1, 1);
472 $self->script_runner->add_path( $_ ) for @$script_libs;
480 # --------------------------------------------------------------------------
481 # Does the circ permit work
482 # --------------------------------------------------------------------------
486 $self->log_me("do_permit()");
488 unless( $self->editor->requestor->id == $self->patron->id ) {
489 return $self->bail_on_events($self->editor->event)
490 unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') );
494 $self->check_captured_holds();
495 $self->do_copy_checks();
496 return if $self->bail_out;
497 $self->run_patron_permit_scripts();
498 $self->run_copy_permit_scripts()
499 unless $self->is_precat or $self->is_noncat;
500 $self->override_events() unless $self->is_renewal;
501 return if $self->bail_out;
503 if( $self->is_precat ) {
506 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key));
507 return $self->bail_out(1) unless $self->is_renewal;
513 payload => $self->mk_permit_key));
517 sub check_captured_holds {
519 my $copy = $self->copy;
520 my $patron = $self->patron;
522 my $s = $U->copy_status($copy->status)->id;
523 return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF;
524 $logger->info("circulator: copy is on holds shelf, searching for the correct hold");
526 # Item is on the holds shelf, make sure it's going to the right person
527 my $holds = $self->editor->search_action_hold_request(
530 current_copy => $copy->id ,
531 capture_time => { '!=' => undef },
532 cancel_time => undef,
533 fulfillment_time => undef
539 if( $holds and $$holds[0] ) {
540 return undef if $$holds[0]->usr == $patron->id;
543 $logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
545 $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF'));
551 my $copy = $self->copy;
554 my $stat = $U->copy_status($copy->status)->id;
556 # We cannot check out a copy if it is in-transit
557 if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) {
558 return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
561 $self->handle_claims_returned();
562 return if $self->bail_out;
564 # no claims returned circ was found, check if there is any open circ
565 unless( $self->is_renewal ) {
566 my $circs = $self->editor->search_action_circulation(
567 { target_copy => $copy->id, checkin_time => undef }
570 return $self->bail_on_events(
571 OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
576 # ---------------------------------------------------------------------
577 # This pushes any patron-related events into the list but does not
578 # set bail_out for any events
579 # ---------------------------------------------------------------------
580 sub run_patron_permit_scripts {
582 my $runner = $self->script_runner;
583 my $patronid = $self->patron->id;
585 # ---------------------------------------------------------------------
586 # Find all of the fatal penalties currently set on the user
587 # ---------------------------------------------------------------------
588 my $penalties = $U->update_patron_penalties(
589 authtoken => $self->editor->authtoken,
590 patron => $self->patron,
593 $penalties = $penalties->{fatal_penalties};
596 # ---------------------------------------------------------------------
597 # Now run the patron permit script
598 # ---------------------------------------------------------------------
599 $runner->load($self->circ_permit_patron);
600 my $result = $runner->run or
601 throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
603 my $patron_events = $result->{events};
605 push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
607 $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
609 $self->push_events(@allevents);
613 sub run_copy_permit_scripts {
615 my $copy = $self->copy || return;
616 my $runner = $self->script_runner;
618 # ---------------------------------------------------------------------
619 # Capture all of the copy permit events
620 # ---------------------------------------------------------------------
621 $runner->load($self->circ_permit_copy);
622 my $result = $runner->run or
623 throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
624 my $copy_events = $result->{events};
626 # ---------------------------------------------------------------------
627 # Now collect all of the events together
628 # ---------------------------------------------------------------------
630 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
632 # See if this copy has an alert message
633 my $ae = $self->check_copy_alert();
634 push( @allevents, $ae ) if $ae;
636 # uniquify the events
637 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
638 @allevents = values %hash;
641 $_->{payload} = $copy if
642 ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
645 $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
647 $self->push_events(@allevents);
651 sub check_copy_alert {
653 return OpenILS::Event->new(
654 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
655 if $self->copy and $self->copy->alert_message;
661 # --------------------------------------------------------------------------
662 # If the call is overriding and has permissions to override every collected
663 # event, the are cleared. Any event that the caller does not have
664 # permission to override, will be left in the event list and bail_out will
666 # XXX We need code in here to cancel any holds/transits on copies
667 # that are being force-checked out
668 # --------------------------------------------------------------------------
669 sub override_events {
671 my @events = @{$self->events};
672 return unless @events;
674 if(!$self->override) {
675 return $self->bail_out(1)
676 if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
681 for my $e (@events) {
682 my $tc = $e->{textcode};
683 next if $tc eq 'SUCCESS';
684 my $ov = "$tc.override";
685 $logger->info("circulator: attempting to override event: $ov");
687 return $self->bail_on_events($self->editor->event)
688 unless( $self->editor->allowed($ov) );
693 # --------------------------------------------------------------------------
694 # If there is an open claimsreturn circ on the requested copy, close the
695 # circ if overriding, otherwise bail out
696 # --------------------------------------------------------------------------
697 sub handle_claims_returned {
699 my $copy = $self->copy;
701 my $CR = $self->editor->search_action_circulation(
703 target_copy => $copy->id,
704 stop_fines => OILS_STOP_FINES_CLAIMSRETURNED,
705 checkin_time => undef,
709 return unless ($CR = $CR->[0]);
713 # - If the caller has set the override flag, we will check the item in
714 if($self->override) {
716 $CR->checkin_time('now');
717 $CR->checkin_lib($self->editor->requestor->ws_ou);
718 $CR->checkin_staff($self->editor->requestor->id);
720 $evt = $self->editor->event
721 unless $self->editor->update_action_circulation($CR);
724 $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
727 $self->bail_on_events($evt) if $evt;
732 # --------------------------------------------------------------------------
733 # This performs the checkout
734 # --------------------------------------------------------------------------
738 $self->log_me("do_checkout()");
740 # make sure perms are good if this isn't a renewal
741 unless( $self->is_renewal ) {
742 return $self->bail_on_events($self->editor->event)
743 unless( $self->editor->allowed('COPY_CHECKOUT') );
746 # verify the permit key
747 unless( $self->check_permit_key ) {
748 if( $self->permit_override ) {
749 return $self->bail_on_events($self->editor->event)
750 unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
752 return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
756 # if this is a non-cataloged circ, build the circ and finish
757 if( $self->is_noncat ) {
758 $self->checkout_noncat;
760 OpenILS::Event->new('SUCCESS',
761 payload => { noncat_circ => $self->circ }));
765 if( $self->is_precat ) {
766 $self->script_runner->insert("environment.isPrecat", 1, 1);
767 $self->make_precat_copy;
768 return if $self->bail_out;
770 } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
771 return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
774 $self->do_copy_checks;
775 return if $self->bail_out;
777 $self->run_checkout_scripts();
778 return if $self->bail_out;
780 $self->build_checkout_circ_object();
781 return if $self->bail_out;
783 $self->apply_modified_due_date();
784 return if $self->bail_out;
786 return $self->bail_on_events($self->editor->event)
787 unless $self->editor->create_action_circulation($self->circ);
789 $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
791 return if $self->bail_out;
793 $self->handle_checkout_holds();
794 return if $self->bail_out;
796 # ------------------------------------------------------------------------------
797 # Update the patron penalty info in the DB
798 # ------------------------------------------------------------------------------
799 $U->update_patron_penalties(
800 authtoken => $self->editor->authtoken,
801 patron => $self->patron,
805 my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
807 OpenILS::Event->new('SUCCESS',
809 copy => $U->unflesh_copy($self->copy),
812 holds_fulfilled => $self->fulfilled_holds,
820 my $copy = $self->copy;
822 my $stat = $copy->status if ref $copy->status;
823 my $loc = $copy->location if ref $copy->location;
824 my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
826 $copy->status($stat->id) if $stat;
827 $copy->location($loc->id) if $loc;
828 $copy->circ_lib($circ_lib->id) if $circ_lib;
829 $copy->editor($self->editor->requestor->id);
830 $copy->edit_date('now');
831 $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect;
833 return $self->bail_on_events($self->editor->event)
834 unless $self->editor->update_asset_copy($self->copy);
836 $copy->status($U->copy_status($copy->status));
837 $copy->location($loc) if $loc;
838 $copy->circ_lib($circ_lib) if $circ_lib;
843 my( $self, @evts ) = @_;
844 $self->push_events(@evts);
848 sub handle_checkout_holds {
851 my $copy = $self->copy;
852 my $patron = $self->patron;
854 my $holds = $self->editor->search_action_hold_request(
856 current_copy => $copy->id ,
857 cancel_time => undef,
858 fulfillment_time => undef
864 # XXX We should only fulfill one hold here...
865 # XXX If a hold was transited to the user who is checking out
866 # the item, we need to make sure that hold is what's grabbed
869 # for now, just sort by id to get what should be the oldest hold
870 $holds = [ sort { $a->id <=> $b->id } @$holds ];
871 my @myholds = grep { $_->usr eq $patron->id } @$holds;
872 my @altholds = grep { $_->usr ne $patron->id } @$holds;
875 my $hold = $myholds[0];
877 $logger->debug("circulator: related hold found in checkout: " . $hold->id );
879 # if the hold was never officially captured, capture it.
880 $hold->capture_time('now') unless $hold->capture_time;
882 # just make sure it's set correctly
883 $hold->current_copy($copy->id);
885 $hold->fulfillment_time('now');
886 $hold->fulfillment_staff($self->editor->requestor->id);
887 $hold->fulfillment_lib($self->editor->requestor->ws_ou);
889 return $self->bail_on_events($self->editor->event)
890 unless $self->editor->update_action_hold_request($hold);
892 $holdcode->delete_hold_copy_maps($self->editor, $hold->id);
894 push( @fulfilled, $hold->id );
897 # If there are any holds placed for other users that point to this copy,
898 # then we need to un-target those holds so the targeter can pick a new copy
901 $logger->info("circulator: un-targeting hold ".$_->id.
902 " because copy ".$copy->id." is getting checked out");
904 # - make the targeter process this hold at next run
905 $_->clear_prev_check_time;
907 # - clear out the targetted copy
908 $_->clear_current_copy;
909 $_->clear_capture_time;
911 return $self->bail_on_event($self->editor->event)
912 unless $self->editor->update_action_hold_request($_);
916 $self->fulfilled_holds(\@fulfilled);
921 sub run_checkout_scripts {
925 my $runner = $self->script_runner;
926 $runner->load($self->circ_duration);
928 my $result = $runner->run or
929 throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
931 my $duration = $result->{durationRule};
932 my $recurring = $result->{recurringFinesRule};
933 my $max_fine = $result->{maxFine};
935 if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) {
937 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
938 return $self->bail_on_events($evt) if $evt;
940 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
941 return $self->bail_on_events($evt) if $evt;
943 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
944 return $self->bail_on_events($evt) if $evt;
948 # The item circulates with an unlimited duration
954 $self->duration_rule($duration);
955 $self->recurring_fines_rule($recurring);
956 $self->max_fine_rule($max_fine);
960 sub build_checkout_circ_object {
963 my $circ = Fieldmapper::action::circulation->new;
964 my $duration = $self->duration_rule;
965 my $max = $self->max_fine_rule;
966 my $recurring = $self->recurring_fines_rule;
967 my $copy = $self->copy;
968 my $patron = $self->patron;
972 my $dname = $duration->name;
973 my $mname = $max->name;
974 my $rname = $recurring->name;
976 $logger->debug("circulator: building circulation ".
977 "with duration=$dname, maxfine=$mname, recurring=$rname");
979 $circ->duration( $duration->shrt )
980 if $copy->loan_duration == OILS_CIRC_DURATION_SHORT;
981 $circ->duration( $duration->normal )
982 if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL;
983 $circ->duration( $duration->extended )
984 if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED;
986 $circ->recuring_fine( $recurring->low )
987 if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW;
988 $circ->recuring_fine( $recurring->normal )
989 if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL;
990 $circ->recuring_fine( $recurring->high )
991 if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH;
993 $circ->duration_rule( $duration->name );
994 $circ->recuring_fine_rule( $recurring->name );
995 $circ->max_fine_rule( $max->name );
996 $circ->max_fine( $max->amount );
998 $circ->fine_interval($recurring->recurance_interval);
999 $circ->renewal_remaining( $duration->max_renewals );
1003 $logger->info("circulator: copy found with an unlimited circ duration");
1004 $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION);
1005 $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1006 $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1007 $circ->renewal_remaining(0);
1010 $circ->target_copy( $copy->id );
1011 $circ->usr( $patron->id );
1012 $circ->circ_lib( $self->circ_lib );
1014 if( $self->is_renewal ) {
1015 $circ->opac_renewal(1);
1016 $circ->renewal_remaining($self->renewal_remaining);
1017 $circ->circ_staff($self->editor->requestor->id);
1020 # if the user provided an overiding checkout time,
1021 # (e.g. the checkout really happened several hours ago), then
1022 # we apply that here. Does this need a perm??
1023 $circ->xact_start(clense_ISO8601($self->checkout_time))
1024 if $self->checkout_time;
1026 # if a patron is renewing, 'requestor' will be the patron
1027 $circ->circ_staff($self->editor->requestor->id);
1028 $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
1034 sub apply_modified_due_date {
1036 my $circ = $self->circ;
1037 my $copy = $self->copy;
1039 if( $self->due_date ) {
1041 return $self->bail_on_events($self->editor->event)
1042 unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
1044 $circ->due_date(clense_ISO8601($self->due_date));
1048 # if the due_date lands on a day when the location is closed
1049 return unless $copy and $circ->due_date;
1051 my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
1053 $logger->info("circ searching for closed date overlap on lib $org".
1054 " with an item due date of ".$circ->due_date );
1056 my $dateinfo = $U->storagereq(
1057 'open-ils.storage.actor.org_unit.closed_date.overlap',
1058 $org, $circ->due_date );
1061 $logger->info("$dateinfo : circ due data / close date overlap found : due_date=".
1062 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1064 # XXX make the behavior more dynamic
1065 # for now, we just push the due date to after the close date
1066 $circ->due_date($dateinfo->{end});
1073 sub create_due_date {
1074 my( $self, $duration ) = @_;
1075 my ($sec,$min,$hour,$mday,$mon,$year) =
1076 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1077 $year += 1900; $mon += 1;
1078 my $due_date = sprintf(
1079 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1080 $year, $mon, $mday, $hour, $min, $sec);
1086 sub make_precat_copy {
1088 my $copy = $self->copy;
1091 $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
1093 $copy->editor($self->editor->requestor->id);
1094 $copy->edit_date('now');
1095 $copy->dummy_title($self->dummy_title);
1096 $copy->dummy_author($self->dummy_author);
1098 $self->update_copy();
1102 $logger->info("circulator: Creating a new precataloged ".
1103 "copy in checkout with barcode " . $self->copy_barcode);
1105 $copy = Fieldmapper::asset::copy->new;
1106 $copy->circ_lib($self->circ_lib);
1107 $copy->creator($self->editor->requestor->id);
1108 $copy->editor($self->editor->requestor->id);
1109 $copy->barcode($self->copy_barcode);
1110 $copy->call_number(OILS_PRECAT_CALL_NUMBER);
1111 $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
1112 $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
1114 $copy->dummy_title($self->dummy_title || "");
1115 $copy->dummy_author($self->dummy_author || "");
1117 unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
1119 $self->push_events($self->editor->event);
1123 # this is a little bit of a hack, but we need to
1124 # get the copy into the script runner
1125 $self->script_runner->insert("environment.copy", $copy, 1);
1129 sub checkout_noncat {
1135 my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
1136 my $count = $self->noncat_count || 1;
1137 my $cotime = clense_ISO8601($self->checkout_time) || "";
1139 $logger->info("circ creating $count noncat circs with checkout time $cotime");
1143 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1144 $self->editor->requestor->id,
1152 $self->push_events($evt);
1163 $self->log_me("do_checkin()");
1165 return $self->bail_on_events(
1166 OpenILS::Event->new('ASSET_COPY_NOT_FOUND'))
1169 unless( $self->is_renewal ) {
1170 return $self->bail_on_events($self->editor->event)
1171 unless $self->editor->allowed('COPY_CHECKIN');
1174 $self->push_events($self->check_copy_alert());
1175 $self->push_events($self->check_checkin_copy_status());
1177 # the renew code will have already found our circulation object
1178 unless( $self->is_renewal and $self->circ ) {
1180 $self->editor->search_action_circulation(
1181 { target_copy => $self->copy->id, checkin_time => undef })->[0]);
1184 # if the circ is marked as 'claims returned', add the event to the list
1185 $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
1186 if ($self->circ and $self->circ->stop_fines
1187 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
1189 # handle the overridable events
1190 $self->override_events unless $self->is_renewal;
1191 return if $self->bail_out;
1195 $self->editor->search_action_transit_copy(
1196 { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);
1200 $self->checkin_handle_circ;
1201 return if $self->bail_out;
1202 $self->checkin_changed(1);
1204 } elsif( $self->transit ) {
1205 my $hold_transit = $self->process_received_transit;
1206 $self->checkin_changed(1);
1208 if( $self->bail_out ) {
1209 $self->checkin_flesh_events;
1213 if( my $e = $self->check_checkin_copy_status() ) {
1214 # If the original copy status is special, alert the caller
1215 my $ev = $self->events;
1216 $self->events([$e]);
1217 $self->override_events;
1218 return if $self->bail_out;
1223 if( $hold_transit or
1224 $U->copy_status($self->copy->status)->id
1225 == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1226 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1227 $self->checkin_flesh_events;
1232 if( $self->is_renewal ) {
1233 $self->push_events(OpenILS::Event->new('SUCCESS'));
1237 # ------------------------------------------------------------------------------
1238 # Circulations and transits are now closed where necessary. Now go on to see if
1239 # this copy can fulfill a hold or needs to be routed to a different location
1240 # ------------------------------------------------------------------------------
1242 if( $self->attempt_checkin_hold_capture() ) {
1243 return if $self->bail_out;
1245 } else { # not needed for a hold
1247 my $circ_lib = (ref $self->copy->circ_lib) ?
1248 $self->copy->circ_lib->id : $self->copy->circ_lib;
1250 $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
1252 if( $circ_lib == $self->editor->requestor->ws_ou ) {
1254 $self->checkin_handle_precat();
1255 return if $self->bail_out;
1259 $self->checkin_build_copy_transit();
1260 return if $self->bail_out;
1261 $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
1266 $self->reshelve_copy;
1267 return if $self->bail_out;
1269 unless($self->checkin_changed) {
1271 $self->push_events(OpenILS::Event->new('NO_CHANGE'));
1272 my $stat = $U->copy_status($self->copy->status)->id;
1274 $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
1275 if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF );
1276 $self->bail_out(1); # no need to commit anything
1279 $self->push_events(OpenILS::Event->new('SUCCESS'))
1280 unless @{$self->events};
1283 $self->checkin_flesh_events;
1289 my $force = $self->force || shift;
1290 my $copy = $self->copy;
1292 my $stat = $U->copy_status($copy->status)->id;
1295 $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and
1296 $stat != OILS_COPY_STATUS_CATALOGING and
1297 $stat != OILS_COPY_STATUS_IN_TRANSIT and
1298 $stat != OILS_COPY_STATUS_RESHELVING )) {
1300 $copy->status( OILS_COPY_STATUS_RESHELVING );
1302 $self->checkin_changed(1);
1307 sub checkin_handle_precat {
1309 my $copy = $self->copy;
1311 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1312 $copy->status(OILS_COPY_STATUS_CATALOGING);
1313 $self->update_copy();
1314 $self->checkin_changed(1);
1315 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1320 sub checkin_build_copy_transit {
1322 my $copy = $self->copy;
1323 my $transit = Fieldmapper::action::transit_copy->new;
1325 $transit->source($self->editor->requestor->ws_ou);
1326 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1327 $transit->target_copy($copy->id);
1328 $transit->source_send_time('now');
1329 $transit->copy_status( $U->copy_status($copy->status)->id );
1331 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1333 return $self->bail_on_events($self->editor->event)
1334 unless $self->editor->create_action_transit_copy($transit);
1336 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1338 $self->checkin_changed(1);
1342 sub attempt_checkin_hold_capture {
1344 my $copy = $self->copy;
1346 # See if this copy can fulfill any holds
1347 my ($hold) = $holdcode->find_nearest_permitted_hold(
1348 OpenSRF::AppSession->create('open-ils.storage'),
1349 $copy, $self->editor->requestor );
1352 $logger->debug("circulator: no potential permitted".
1353 "holds found for copy ".$copy->barcode);
1358 $logger->info("circulator: found permitted hold ".
1359 $hold->id . " for copy, capturing...");
1361 $hold->current_copy($copy->id);
1362 $hold->capture_time('now');
1364 # prevent DB errors caused by fetching
1365 # holds from storage, and updating through cstore
1366 $hold->clear_fulfillment_time;
1367 $hold->clear_fulfillment_staff;
1368 $hold->clear_fulfillment_lib;
1369 $hold->clear_expire_time;
1370 $hold->clear_cancel_time;
1371 $hold->clear_prev_check_time unless $hold->prev_check_time;
1373 $self->bail_on_events($self->editor->event)
1374 unless $self->editor->update_action_hold_request($hold);
1376 $self->checkin_changed(1);
1378 return 1 if $self->bail_out;
1380 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1382 # This hold was captured in the correct location
1383 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1384 $self->push_events(OpenILS::Event->new('SUCCESS'));
1386 $self->do_hold_notify($hold->id);
1390 # Hold needs to be picked up elsewhere. Build a hold
1391 # transit and route the item.
1392 $self->checkin_build_hold_transit();
1393 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1394 return 1 if $self->bail_out;
1396 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1399 # make sure we save the copy status
1404 sub do_hold_notify {
1405 my( $self, $holdid ) = @_;
1406 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1407 editor => $self->editor, hold_id => $holdid );
1409 if(!$notifier->event) {
1411 $logger->info("attempt at sending hold notification for hold $holdid");
1413 my $stat = $notifier->send_email_notify;
1414 $logger->info("hold notify succeeded for hold $holdid") if $stat eq '1';
1415 $logger->warn(" * hold notify failed for hold $holdid") if $stat ne '1';
1418 $logger->info("Not sending hold notification since the patron has no email address");
1423 sub checkin_build_hold_transit {
1427 my $copy = $self->copy;
1428 my $hold = $self->hold;
1429 my $trans = Fieldmapper::action::hold_transit_copy->new;
1431 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1433 $trans->hold($hold->id);
1434 $trans->source($self->editor->requestor->ws_ou);
1435 $trans->dest($hold->pickup_lib);
1436 $trans->source_send_time("now");
1437 $trans->target_copy($copy->id);
1439 # when the copy gets to its destination, it will recover
1440 # this status - put it onto the holds shelf
1441 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1443 return $self->bail_on_events($self->editor->event)
1444 unless $self->editor->create_action_hold_transit_copy($trans);
1449 sub process_received_transit {
1451 my $copy = $self->copy;
1452 my $copyid = $self->copy->id;
1454 my $status_name = $U->copy_status($copy->status)->name;
1455 $logger->debug("circulator: attempting transit receive on ".
1456 "copy $copyid. Copy status is $status_name");
1458 my $transit = $self->transit;
1460 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1461 $logger->info("circulator: Fowarding transit on copy which is destined ".
1462 "for a different location. copy=$copyid,current ".
1463 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1465 return $self->bail_on_events(
1466 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1469 # The transit is received, set the receive time
1470 $transit->dest_recv_time('now');
1471 $self->bail_on_events($self->editor->event)
1472 unless $self->editor->update_action_transit_copy($transit);
1474 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1476 $logger->info("Recovering original copy status in transit: ".$transit->copy_status);
1477 $copy->status( $transit->copy_status );
1478 $self->update_copy();
1479 return if $self->bail_out;
1483 $self->do_hold_notify($hold_transit->hold);
1488 OpenILS::Event->new(
1491 payload => { transit => $transit, holdtransit => $hold_transit } ));
1493 return $hold_transit;
1497 sub checkin_handle_circ {
1501 my $circ = $self->circ;
1502 my $copy = $self->copy;
1506 # backdate the circ if necessary
1507 if($self->backdate) {
1508 $self->checkin_handle_backdate;
1509 return if $self->bail_out;
1512 if(!$circ->stop_fines) {
1513 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1514 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1515 $circ->stop_fines_time('now') unless $self->backdate;
1516 $circ->stop_fines_time($self->backdate) if $self->backdate;
1519 # see if there are any fines owed on this circ. if not, close it
1520 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1521 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1523 # Set the checkin vars since we have the item
1524 $circ->checkin_time('now');
1525 $circ->checkin_staff($self->editor->requestor->id);
1526 $circ->checkin_lib($self->editor->requestor->ws_ou);
1528 my $circ_lib = (ref $self->copy->circ_lib) ?
1529 $self->copy->circ_lib->id : $self->copy->circ_lib;
1530 my $stat = $U->copy_status($self->copy->status)->id;
1532 # If the item is lost/missing and it needs to be sent home, don't
1533 # reshelve the copy, leave it lost/missing so the recipient will know
1534 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1535 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1536 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1539 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1544 return $self->bail_on_events($self->editor->event)
1545 unless $self->editor->update_action_circulation($circ);
1549 sub checkin_handle_backdate {
1552 my $bd = $self->backdate;
1553 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1554 $bd = "${bd}T23:59:59";
1556 my $bills = $self->editor->search_money_billing(
1558 billing_ts => { '>=' => $bd },
1559 xact => $self->circ->id,
1560 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1564 for my $bill (@$bills) {
1565 if( !$bill->voided or $bill->voided =~ /f/i ) {
1567 my $n = $bill->note || "";
1568 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1570 $self->bail_on_events($self->editor->event)
1571 unless $self->editor->update_money_billing($bill);
1578 # XXX Legacy version for Circ.pm support
1579 sub _checkin_handle_backdate {
1580 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1583 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1584 $bd = "${bd}T23:59:59";
1587 my $bills = $session->request(
1588 "open-ils.storage.direct.money.billing.search_where.atomic",
1589 billing_ts => { '>=' => $bd },
1591 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1595 for my $bill (@$bills) {
1597 my $n = $bill->note || "";
1598 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1599 my $s = $session->request(
1600 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1601 return $U->DB_UPDATE_FAILED($bill) unless $s;
1611 sub find_patron_from_copy {
1613 my $circs = $self->editor->search_action_circulation(
1614 { target_copy => $self->copy->id, checkin_time => undef });
1615 my $circ = $circs->[0];
1616 return unless $circ;
1617 my $u = $self->editor->retrieve_actor_user($circ->usr)
1618 or return $self->bail_on_events($self->editor->event);
1622 sub check_checkin_copy_status {
1624 my $copy = $self->copy;
1630 my $status = $U->copy_status($copy->status)->id;
1633 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1634 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1635 $status == OILS_COPY_STATUS_IN_PROCESS ||
1636 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1637 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1638 $status == OILS_COPY_STATUS_CATALOGING ||
1639 $status == OILS_COPY_STATUS_RESHELVING );
1641 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1642 if( $status == OILS_COPY_STATUS_LOST );
1644 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1645 if( $status == OILS_COPY_STATUS_MISSING );
1647 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1652 # --------------------------------------------------------------------------
1653 # On checkin, we need to return as many relevant objects as we can
1654 # --------------------------------------------------------------------------
1655 sub checkin_flesh_events {
1658 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1659 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1660 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1664 for my $evt (@{$self->events}) {
1667 $payload->{copy} = $U->unflesh_copy($self->copy);
1668 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1669 $payload->{circ} = $self->circ;
1670 $payload->{transit} = $self->transit;
1671 $payload->{hold} = $self->hold;
1673 $evt->{payload} = $payload;
1678 my( $self, $msg ) = @_;
1679 my $bc = ($self->copy) ? $self->copy->barcode :
1682 my $usr = ($self->patron) ? $self->patron->id : "";
1683 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1684 ", recipient=$usr, copy=$bc");
1690 $self->log_me("do_renew()");
1691 $self->is_renewal(1);
1693 unless( $self->is_renewal ) {
1694 return $self->bail_on_events($self->editor->events)
1695 unless $self->editor->allowed('RENEW_CIRC');
1698 # Make sure there is an open circ to renew that is not
1699 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1700 my $circ = $self->editor->search_action_circulation(
1701 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1704 $circ = $self->editor->search_action_circulation(
1706 target_copy => $self->copy->id,
1707 stop_fines => OILS_STOP_FINES_MAX_FINES,
1708 checkin_time => undef
1713 return $self->bail_on_events($self->editor->event) unless $circ;
1715 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1716 if $circ->renewal_remaining < 1;
1718 # -----------------------------------------------------------------
1720 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1723 $self->run_renew_permit;
1726 $self->do_checkin();
1727 return if $self->bail_out;
1729 unless( $self->permit_override ) {
1731 return if $self->bail_out;
1732 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1733 $self->remove_event('ITEM_NOT_CATALOGED');
1736 $self->override_events;
1737 return if $self->bail_out;
1740 $self->do_checkout();
1745 my( $self, $evt ) = @_;
1746 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1747 $logger->debug("circulator: removing event from list: $evt");
1748 my @events = @{$self->events};
1749 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1754 my( $self, $evt ) = @_;
1755 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1756 return grep { $_->{textcode} eq $evt } @{$self->events};
1761 sub run_renew_permit {
1763 my $runner = $self->script_runner;
1765 $runner->load($self->circ_permit_renew);
1766 my $result = $runner->run or
1767 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1768 my $events = $result->{events};
1770 $logger->activity("circ_permit_renew for user ".
1771 $self->patron->id." returned events: @$events") if @$events;
1773 $self->push_events(OpenILS::Event->new($_)) for @$events;
1775 $logger->debug("circulator: re-creating script runner to be safe");
1776 $self->mk_script_runner;