1 package OpenILS::Application::Circ::Circulate;
2 use strict; use warnings;
3 use base 'OpenSRF::Application';
4 use OpenSRF::EX qw(:try);
5 use OpenSRF::Utils::SettingsClient;
6 use OpenSRF::Utils::Logger qw(:logger);
7 use OpenILS::Const qw/:const/;
15 my $conf = OpenSRF::Utils::SettingsClient->new;
16 my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
17 my @pfx = ( @pfx2, "scripts" );
19 my $p = $conf->config_value( @pfx, 'circ_permit_patron' );
20 my $c = $conf->config_value( @pfx, 'circ_permit_copy' );
21 my $d = $conf->config_value( @pfx, 'circ_duration' );
22 my $f = $conf->config_value( @pfx, 'circ_recurring_fines' );
23 my $m = $conf->config_value( @pfx, 'circ_max_fines' );
24 my $pr = $conf->config_value( @pfx, 'circ_permit_renew' );
25 my $lb = $conf->config_value( @pfx2, 'script_path' );
27 $logger->error( "Missing circ script(s)" )
28 unless( $p and $c and $d and $f and $m and $pr );
30 $scripts{circ_permit_patron} = $p;
31 $scripts{circ_permit_copy} = $c;
32 $scripts{circ_duration} = $d;
33 $scripts{circ_recurring_fines}= $f;
34 $scripts{circ_max_fines} = $m;
35 $scripts{circ_permit_renew} = $pr;
37 $lb = [ $lb ] unless ref($lb);
41 "circulator: Loaded rules scripts for circ: " .
42 "circ permit patron = $p, ".
43 "circ permit copy = $c, ".
44 "circ duration = $d, ".
45 "circ recurring fines = $f, " .
46 "circ max fines = $m, ".
47 "circ renew permit = $pr. ".
52 __PACKAGE__->register_method(
53 method => "run_method",
54 api_name => "open-ils.circ.checkout.permit",
56 Determines if the given checkout can occur
57 @param authtoken The login session key
58 @param params A trailing hash of named params including
59 barcode : The copy barcode,
60 patron : The patron the checkout is occurring for,
61 renew : true or false - whether or not this is a renewal
62 @return The event that occurred during the permit check.
66 __PACKAGE__->register_method (
67 method => 'run_method',
68 api_name => 'open-ils.circ.checkout.permit.override',
69 signature => q/@see open-ils.circ.checkout.permit/,
73 __PACKAGE__->register_method(
74 method => "run_method",
75 api_name => "open-ils.circ.checkout",
78 @param authtoken The login session key
79 @param params A named hash of params including:
81 barcode If no copy is provided, the copy is retrieved via barcode
82 copyid If no copy or barcode is provide, the copy id will be use
83 patron The patron's id
84 noncat True if this is a circulation for a non-cataloted item
85 noncat_type The non-cataloged type id
86 noncat_circ_lib The location for the noncat circ.
87 precat The item has yet to be cataloged
88 dummy_title The temporary title of the pre-cataloded item
89 dummy_author The temporary authr of the pre-cataloded item
90 Default is the home org of the staff member
91 @return The SUCCESS event on success, any other event depending on the error
94 __PACKAGE__->register_method(
95 method => "run_method",
96 api_name => "open-ils.circ.checkin",
99 Generic super-method for handling all copies
100 @param authtoken The login session key
101 @param params Hash of named parameters including:
102 barcode - The copy barcode
103 force - If true, copies in bad statuses will be checked in and give good statuses
108 __PACKAGE__->register_method(
109 method => "run_method",
110 api_name => "open-ils.circ.checkin.override",
111 signature => q/@see open-ils.circ.checkin/
114 __PACKAGE__->register_method(
115 method => "run_method",
116 api_name => "open-ils.circ.renew.override",
117 signature => q/@see open-ils.circ.renew/,
121 __PACKAGE__->register_method(
122 method => "run_method",
123 api_name => "open-ils.circ.renew",
124 notes => <<" NOTES");
125 PARAMS( authtoken, circ => circ_id );
126 open-ils.circ.renew(login_session, circ_object);
127 Renews the provided circulation. login_session is the requestor of the
128 renewal and if the logged in user is not the same as circ->usr, then
129 the logged in user must have RENEW_CIRC permissions.
132 __PACKAGE__->register_method(
133 method => "run_method",
134 api_name => "open-ils.circ.checkout.full");
135 __PACKAGE__->register_method(
136 method => "run_method",
137 api_name => "open-ils.circ.checkout.full.override");
142 my( $self, $conn, $auth, $args ) = @_;
143 translate_legacy_args($args);
144 my $api = $self->api_name;
147 OpenILS::Application::Circ::Circulator->new($auth, %$args);
149 return circ_events($circulator) if $circulator->bail_out;
151 # --------------------------------------------------------------------------
152 # Go ahead and load the script runner to make sure we have all
153 # of the objects we need
154 # --------------------------------------------------------------------------
155 $circulator->is_renewal(1) if $api =~ /renew/;
156 $circulator->is_checkin(1) if $api =~ /checkin/;
157 $circulator->mk_script_runner;
158 return circ_events($circulator) if $circulator->bail_out;
160 $circulator->circ_permit_patron($scripts{circ_permit_patron});
161 $circulator->circ_permit_copy($scripts{circ_permit_copy});
162 $circulator->circ_duration($scripts{circ_duration});
163 $circulator->circ_permit_renew($scripts{circ_permit_renew});
165 $circulator->override(1) if $api =~ /override/o;
167 if( $api =~ /checkout\.permit/ ) {
168 $circulator->do_permit();
170 } elsif( $api =~ /checkout.full/ ) {
172 $circulator->do_permit();
173 unless( $circulator->bail_out ) {
174 $circulator->events([]);
175 $circulator->do_checkout();
178 } elsif( $api =~ /checkout/ ) {
179 $circulator->do_checkout();
181 } elsif( $api =~ /checkin/ ) {
182 $circulator->do_checkin();
184 } elsif( $api =~ /renew/ ) {
185 $circulator->is_renewal(1);
186 $circulator->do_renew();
189 if( $circulator->bail_out ) {
192 # make sure no success event accidentally slip in
194 [ grep { $_->{textcode} ne 'SUCCESS' } @{$circulator->events} ]);
195 my @e = @{$circulator->events};
196 push( @ee, $_->{textcode} ) for @e;
197 $logger->info("circulator: bailing out with events: @ee");
198 $circulator->editor->rollback;
201 $circulator->editor->commit;
204 $circulator->script_runner->cleanup;
206 return circ_events($circulator);
211 my @e = @{$circ->events};
212 return (@e == 1) ? $e[0] : \@e;
217 sub translate_legacy_args {
220 if( $$args{barcode} ) {
221 $$args{copy_barcode} = $$args{barcode};
222 delete $$args{barcode};
225 if( $$args{copyid} ) {
226 $$args{copy_id} = $$args{copyid};
227 delete $$args{copyid};
230 if( $$args{patronid} ) {
231 $$args{patron_id} = $$args{patronid};
232 delete $$args{patronid};
235 if( $$args{patron} and !ref($$args{patron}) ) {
236 $$args{patron_id} = $$args{patron};
237 delete $$args{patron};
241 if( $$args{noncat} ) {
242 $$args{is_noncat} = $$args{noncat};
243 delete $$args{noncat};
246 if( $$args{precat} ) {
247 $$args{is_precat} = $$args{precat};
248 delete $$args{precat};
254 # --------------------------------------------------------------------------
255 # This package actually manages all of the circulation logic
256 # --------------------------------------------------------------------------
257 package OpenILS::Application::Circ::Circulator;
258 use strict; use warnings;
259 use vars q/$AUTOLOAD/;
261 use OpenILS::Utils::Fieldmapper;
262 use OpenSRF::Utils::Cache;
263 use Digest::MD5 qw(md5_hex);
264 use DateTime::Format::ISO8601;
265 use OpenILS::Utils::PermitHold;
266 use OpenSRF::Utils qw/:datetime/;
267 use OpenSRF::Utils::SettingsClient;
268 use OpenILS::Application::Circ::Holds;
269 use OpenILS::Application::Circ::Transit;
270 use OpenSRF::Utils::Logger qw(:logger);
271 use OpenILS::Utils::CStoreEditor qw/:funcs/;
272 use OpenILS::Application::Circ::ScriptBuilder;
273 use OpenILS::Const qw/:const/;
275 my $U = "OpenILS::Application::AppUtils";
276 my $holdcode = "OpenILS::Application::Circ::Holds";
277 my $transcode = "OpenILS::Application::Circ::Transit";
282 # --------------------------------------------------------------------------
283 # Add a pile of automagic getter/setter methods
284 # --------------------------------------------------------------------------
285 my @AUTOLOAD_FIELDS = qw/
325 recurring_fines_level
342 my $type = ref($self) or die "$self is not an object";
344 my $name = $AUTOLOAD;
347 unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) {
348 $logger->error("circulator: $type: invalid autoload field: $name");
349 die "$type: invalid autoload field: $name\n"
354 *{"${type}::${name}"} = sub {
357 $s->{$name} = $v if defined $v;
361 return $self->$name($data);
366 my( $class, $auth, %args ) = @_;
367 $class = ref($class) || $class;
368 my $self = bless( {}, $class );
372 new_editor(xact => 1, authtoken => $auth) );
374 unless( $self->editor->checkauth ) {
375 $self->bail_on_events($self->editor->event);
379 $self->cache_handle(OpenSRF::Utils::Cache->new('global'));
381 $self->$_($args{$_}) for keys %args;
384 ($self->circ_lib) ? $self->circ_lib : $self->editor->requestor->ws_ou);
390 # --------------------------------------------------------------------------
391 # True if we should discontinue processing
392 # --------------------------------------------------------------------------
394 my( $self, $bool ) = @_;
395 if( defined $bool ) {
396 $logger->info("circulator: BAILING OUT") if $bool;
397 $self->{bail_out} = $bool;
399 return $self->{bail_out};
404 my( $self, @evts ) = @_;
407 $logger->info("circulator: pushing event ".$e->{textcode});
408 push( @{$self->events}, $e ) unless
409 grep { $_->{textcode} eq $e->{textcode} } @{$self->events};
415 my $key = md5_hex( time() . rand() . "$$" );
416 $self->cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
417 return $self->permit_key($key);
420 sub check_permit_key {
422 my $key = $self->permit_key;
423 return 0 unless $key;
424 my $k = "oils_permit_key_$key";
425 my $one = $self->cache_handle->get_cache($k);
426 $self->cache_handle->delete_cache($k);
427 return ($one) ? 1 : 0;
431 # --------------------------------------------------------------------------
432 # This builds the script runner environment and fetches most of the
434 # --------------------------------------------------------------------------
435 sub mk_script_runner {
439 $args->{ignore_user_status} = 1 if $self->is_checkin;
442 qw/copy copy_barcode copy_id patron
443 patron_id patron_barcode volume title editor/;
445 # Translate our objects into the ScriptBuilder args hash
446 $$args{$_} = $self->$_() for @fields;
447 $$args{fetch_patron_by_circ_copy} = 1;
448 $$args{fetch_patron_circ_info} = 1;
450 # This fetches most of the objects we need
451 $self->script_runner(
452 OpenILS::Application::Circ::ScriptBuilder->build($args));
454 # Now we translate the ScriptBuilder objects back into self
455 $self->$_($$args{$_}) for @fields;
457 my @evts = @{$args->{_events}} if $args->{_events};
459 $logger->debug("circulator: script builder returned events: : @evts") if @evts;
463 # Anything besides ASSET_COPY_NOT_FOUND will stop processing
464 if(!$self->is_noncat and
466 $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') {
470 my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts;
471 return $self->bail_on_events(@e);
475 $self->is_precat(1) if $self->copy and $self->copy->call_number == OILS_PRECAT_CALL_NUMBER;
477 # We can't renew if there is no copy
478 return $self->bail_on_events(@evts) if
479 $self->is_renewal and !$self->copy;
481 # Set some circ-specific flags in the script environment
482 my $evt = "environment";
483 $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef);
485 if( $self->is_noncat ) {
486 $self->script_runner->insert("$evt.isNonCat", 1);
487 $self->script_runner->insert("$evt.nonCatType", $self->noncat_type);
490 if( $self->is_precat ) {
491 $self->script_runner->insert("environment.isPrecat", 1, 1);
494 $self->script_runner->add_path( $_ ) for @$script_libs;
502 # --------------------------------------------------------------------------
503 # Does the circ permit work
504 # --------------------------------------------------------------------------
508 $self->log_me("do_permit()");
510 unless( $self->editor->requestor->id == $self->patron->id ) {
511 return $self->bail_on_events($self->editor->event)
512 unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') );
516 $self->check_captured_holds();
517 $self->do_copy_checks();
518 return if $self->bail_out;
519 $self->run_patron_permit_scripts();
520 $self->run_copy_permit_scripts()
521 unless $self->is_precat or $self->is_noncat;
522 $self->override_events() unless $self->is_renewal;
523 return if $self->bail_out;
525 if( $self->is_precat ) {
528 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key));
529 return $self->bail_out(1) unless $self->is_renewal;
535 payload => $self->mk_permit_key));
539 sub check_captured_holds {
541 my $copy = $self->copy;
542 my $patron = $self->patron;
544 return undef unless $copy;
546 my $s = $U->copy_status($copy->status)->id;
547 return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF;
548 $logger->info("circulator: copy is on holds shelf, searching for the correct hold");
550 # Item is on the holds shelf, make sure it's going to the right person
551 my $holds = $self->editor->search_action_hold_request(
554 current_copy => $copy->id ,
555 capture_time => { '!=' => undef },
556 cancel_time => undef,
557 fulfillment_time => undef
563 if( $holds and $$holds[0] ) {
564 return undef if $$holds[0]->usr == $patron->id;
567 $logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
569 $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF'));
575 my $copy = $self->copy;
578 my $stat = $U->copy_status($copy->status)->id;
580 # We cannot check out a copy if it is in-transit
581 if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) {
582 return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
585 $self->handle_claims_returned();
586 return if $self->bail_out;
588 # no claims returned circ was found, check if there is any open circ
589 unless( $self->is_renewal ) {
590 my $circs = $self->editor->search_action_circulation(
591 { target_copy => $copy->id, checkin_time => undef }
594 return $self->bail_on_events(
595 OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
600 sub send_penalty_request {
602 my $ses = OpenSRF::AppSession->create('open-ils.penalty');
603 $self->penalty_request(
605 'open-ils.penalty.patron_penalty.calculate',
607 authtoken => $self->editor->authtoken,
608 patron => $self->patron } ) );
611 sub gather_penalty_request {
613 return [] unless $self->penalty_request;
614 my $data = $self->penalty_request->recv;
616 $data = $data->content;
617 return $data->{fatal_penalties};
619 $logger->error("circulator: penalty request returned no data");
623 # ---------------------------------------------------------------------
624 # This pushes any patron-related events into the list but does not
625 # set bail_out for any events
626 # ---------------------------------------------------------------------
627 sub run_patron_permit_scripts {
629 my $runner = $self->script_runner;
630 my $patronid = $self->patron->id;
632 $self->send_penalty_request();
634 # ---------------------------------------------------------------------
635 # Now run the patron permit script
636 # ---------------------------------------------------------------------
637 $runner->load($self->circ_permit_patron);
638 my $result = $runner->run or
639 throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
641 my $patron_events = $result->{events};
644 my $penalties = $self->gather_penalty_request();
645 push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
647 $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
649 $self->push_events(@allevents);
653 sub run_copy_permit_scripts {
655 my $copy = $self->copy || return;
656 my $runner = $self->script_runner;
658 # ---------------------------------------------------------------------
659 # Capture all of the copy permit events
660 # ---------------------------------------------------------------------
661 $runner->load($self->circ_permit_copy);
662 my $result = $runner->run or
663 throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
664 my $copy_events = $result->{events};
666 # ---------------------------------------------------------------------
667 # Now collect all of the events together
668 # ---------------------------------------------------------------------
670 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
672 # See if this copy has an alert message
673 my $ae = $self->check_copy_alert();
674 push( @allevents, $ae ) if $ae;
676 # uniquify the events
677 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
678 @allevents = values %hash;
681 $_->{payload} = $copy if
682 ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
685 $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
687 $self->push_events(@allevents);
691 sub check_copy_alert {
693 return undef if $self->is_renewal;
694 return OpenILS::Event->new(
695 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
696 if $self->copy and $self->copy->alert_message;
702 # --------------------------------------------------------------------------
703 # If the call is overriding and has permissions to override every collected
704 # event, the are cleared. Any event that the caller does not have
705 # permission to override, will be left in the event list and bail_out will
707 # XXX We need code in here to cancel any holds/transits on copies
708 # that are being force-checked out
709 # --------------------------------------------------------------------------
710 sub override_events {
712 my @events = @{$self->events};
713 return unless @events;
715 if(!$self->override) {
716 return $self->bail_out(1)
717 if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
722 for my $e (@events) {
723 my $tc = $e->{textcode};
724 next if $tc eq 'SUCCESS';
725 my $ov = "$tc.override";
726 $logger->info("circulator: attempting to override event: $ov");
728 return $self->bail_on_events($self->editor->event)
729 unless( $self->editor->allowed($ov) );
734 # --------------------------------------------------------------------------
735 # If there is an open claimsreturn circ on the requested copy, close the
736 # circ if overriding, otherwise bail out
737 # --------------------------------------------------------------------------
738 sub handle_claims_returned {
740 my $copy = $self->copy;
742 my $CR = $self->editor->search_action_circulation(
744 target_copy => $copy->id,
745 stop_fines => OILS_STOP_FINES_CLAIMSRETURNED,
746 checkin_time => undef,
750 return unless ($CR = $CR->[0]);
754 # - If the caller has set the override flag, we will check the item in
755 if($self->override) {
757 $CR->checkin_time('now');
758 $CR->checkin_lib($self->editor->requestor->ws_ou);
759 $CR->checkin_staff($self->editor->requestor->id);
761 $evt = $self->editor->event
762 unless $self->editor->update_action_circulation($CR);
765 $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
768 $self->bail_on_events($evt) if $evt;
773 # --------------------------------------------------------------------------
774 # This performs the checkout
775 # --------------------------------------------------------------------------
779 $self->log_me("do_checkout()");
781 # make sure perms are good if this isn't a renewal
782 unless( $self->is_renewal ) {
783 return $self->bail_on_events($self->editor->event)
784 unless( $self->editor->allowed('COPY_CHECKOUT') );
787 # verify the permit key
788 unless( $self->check_permit_key ) {
789 if( $self->permit_override ) {
790 return $self->bail_on_events($self->editor->event)
791 unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
793 return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
797 # if this is a non-cataloged circ, build the circ and finish
798 if( $self->is_noncat ) {
799 $self->checkout_noncat;
801 OpenILS::Event->new('SUCCESS',
802 payload => { noncat_circ => $self->circ }));
806 if( $self->is_precat ) {
807 $self->script_runner->insert("environment.isPrecat", 1, 1);
808 $self->make_precat_copy;
809 return if $self->bail_out;
811 } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
812 return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
815 $self->do_copy_checks;
816 return if $self->bail_out;
818 $self->run_checkout_scripts();
819 return if $self->bail_out;
821 $self->build_checkout_circ_object();
822 return if $self->bail_out;
824 $self->apply_modified_due_date();
825 return if $self->bail_out;
827 return $self->bail_on_events($self->editor->event)
828 unless $self->editor->create_action_circulation($self->circ);
830 $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
832 return if $self->bail_out;
834 $self->handle_checkout_holds();
835 return if $self->bail_out;
837 # ------------------------------------------------------------------------------
838 # Update the patron penalty info in the DB
839 # ------------------------------------------------------------------------------
840 $U->update_patron_penalties(
841 authtoken => $self->editor->authtoken,
842 patron => $self->patron,
846 my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
848 OpenILS::Event->new('SUCCESS',
850 copy => $U->unflesh_copy($self->copy),
853 holds_fulfilled => $self->fulfilled_holds,
861 my $copy = $self->copy;
863 my $stat = $copy->status if ref $copy->status;
864 my $loc = $copy->location if ref $copy->location;
865 my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
867 $copy->status($stat->id) if $stat;
868 $copy->location($loc->id) if $loc;
869 $copy->circ_lib($circ_lib->id) if $circ_lib;
870 $copy->editor($self->editor->requestor->id);
871 $copy->edit_date('now');
872 $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect;
874 return $self->bail_on_events($self->editor->event)
875 unless $self->editor->update_asset_copy($self->copy);
877 $copy->status($U->copy_status($copy->status));
878 $copy->location($loc) if $loc;
879 $copy->circ_lib($circ_lib) if $circ_lib;
884 my( $self, @evts ) = @_;
885 $self->push_events(@evts);
889 sub handle_checkout_holds {
892 my $copy = $self->copy;
893 my $patron = $self->patron;
895 my $holds = $self->editor->search_action_hold_request(
897 current_copy => $copy->id ,
898 cancel_time => undef,
899 fulfillment_time => undef
905 # XXX We should only fulfill one hold here...
906 # XXX If a hold was transited to the user who is checking out
907 # the item, we need to make sure that hold is what's grabbed
910 # for now, just sort by id to get what should be the oldest hold
911 $holds = [ sort { $a->id <=> $b->id } @$holds ];
912 my @myholds = grep { $_->usr eq $patron->id } @$holds;
913 my @altholds = grep { $_->usr ne $patron->id } @$holds;
916 my $hold = $myholds[0];
918 $logger->debug("circulator: related hold found in checkout: " . $hold->id );
920 # if the hold was never officially captured, capture it.
921 $hold->capture_time('now') unless $hold->capture_time;
923 # just make sure it's set correctly
924 $hold->current_copy($copy->id);
926 $hold->fulfillment_time('now');
927 $hold->fulfillment_staff($self->editor->requestor->id);
928 $hold->fulfillment_lib($self->editor->requestor->ws_ou);
930 return $self->bail_on_events($self->editor->event)
931 unless $self->editor->update_action_hold_request($hold);
933 $holdcode->delete_hold_copy_maps($self->editor, $hold->id);
935 push( @fulfilled, $hold->id );
938 # If there are any holds placed for other users that point to this copy,
939 # then we need to un-target those holds so the targeter can pick a new copy
942 $logger->info("circulator: un-targeting hold ".$_->id.
943 " because copy ".$copy->id." is getting checked out");
945 # - make the targeter process this hold at next run
946 $_->clear_prev_check_time;
948 # - clear out the targetted copy
949 $_->clear_current_copy;
950 $_->clear_capture_time;
952 return $self->bail_on_event($self->editor->event)
953 unless $self->editor->update_action_hold_request($_);
957 $self->fulfilled_holds(\@fulfilled);
962 sub run_checkout_scripts {
966 my $runner = $self->script_runner;
967 $runner->load($self->circ_duration);
969 my $result = $runner->run or
970 throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
972 my $duration = $result->{durationRule};
973 my $recurring = $result->{recurringFinesRule};
974 my $max_fine = $result->{maxFine};
976 if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) {
978 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
979 return $self->bail_on_events($evt) if $evt;
981 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
982 return $self->bail_on_events($evt) if $evt;
984 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
985 return $self->bail_on_events($evt) if $evt;
989 # The item circulates with an unlimited duration
995 $self->duration_rule($duration);
996 $self->recurring_fines_rule($recurring);
997 $self->max_fine_rule($max_fine);
1001 sub build_checkout_circ_object {
1004 my $circ = Fieldmapper::action::circulation->new;
1005 my $duration = $self->duration_rule;
1006 my $max = $self->max_fine_rule;
1007 my $recurring = $self->recurring_fines_rule;
1008 my $copy = $self->copy;
1009 my $patron = $self->patron;
1013 my $dname = $duration->name;
1014 my $mname = $max->name;
1015 my $rname = $recurring->name;
1017 $logger->debug("circulator: building circulation ".
1018 "with duration=$dname, maxfine=$mname, recurring=$rname");
1020 $circ->duration( $duration->shrt )
1021 if $copy->loan_duration == OILS_CIRC_DURATION_SHORT;
1022 $circ->duration( $duration->normal )
1023 if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL;
1024 $circ->duration( $duration->extended )
1025 if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED;
1027 $circ->recuring_fine( $recurring->low )
1028 if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW;
1029 $circ->recuring_fine( $recurring->normal )
1030 if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL;
1031 $circ->recuring_fine( $recurring->high )
1032 if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH;
1034 $circ->duration_rule( $duration->name );
1035 $circ->recuring_fine_rule( $recurring->name );
1036 $circ->max_fine_rule( $max->name );
1037 $circ->max_fine( $max->amount );
1039 $circ->fine_interval($recurring->recurance_interval);
1040 $circ->renewal_remaining( $duration->max_renewals );
1044 $logger->info("circulator: copy found with an unlimited circ duration");
1045 $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION);
1046 $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1047 $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1048 $circ->renewal_remaining(0);
1051 $circ->target_copy( $copy->id );
1052 $circ->usr( $patron->id );
1053 $circ->circ_lib( $self->circ_lib );
1055 if( $self->is_renewal ) {
1056 $circ->opac_renewal(1);
1057 $circ->renewal_remaining($self->renewal_remaining);
1058 $circ->circ_staff($self->editor->requestor->id);
1061 # if the user provided an overiding checkout time,
1062 # (e.g. the checkout really happened several hours ago), then
1063 # we apply that here. Does this need a perm??
1064 $circ->xact_start(clense_ISO8601($self->checkout_time))
1065 if $self->checkout_time;
1067 # if a patron is renewing, 'requestor' will be the patron
1068 $circ->circ_staff($self->editor->requestor->id);
1069 $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
1075 sub apply_modified_due_date {
1077 my $circ = $self->circ;
1078 my $copy = $self->copy;
1080 if( $self->due_date ) {
1082 return $self->bail_on_events($self->editor->event)
1083 unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
1085 $circ->due_date(clense_ISO8601($self->due_date));
1089 # if the due_date lands on a day when the location is closed
1090 return unless $copy and $circ->due_date;
1092 my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
1094 $logger->info("circulator: circ searching for closed date overlap on lib $org".
1095 " with an item due date of ".$circ->due_date );
1097 my $dateinfo = $U->storagereq(
1098 'open-ils.storage.actor.org_unit.closed_date.overlap',
1099 $org, $circ->due_date );
1102 $logger->info("circulator: $dateinfo : circ due data / close date overlap found : due_date=".
1103 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1105 # XXX make the behavior more dynamic
1106 # for now, we just push the due date to after the close date
1107 $circ->due_date($dateinfo->{end});
1114 sub create_due_date {
1115 my( $self, $duration ) = @_;
1116 my ($sec,$min,$hour,$mday,$mon,$year) =
1117 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1118 $year += 1900; $mon += 1;
1119 my $due_date = sprintf(
1120 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1121 $year, $mon, $mday, $hour, $min, $sec);
1127 sub make_precat_copy {
1129 my $copy = $self->copy;
1132 $logger->debug("ciculator: Pre-cat copy already exists in checkout: ID=" . $copy->id);
1134 $copy->editor($self->editor->requestor->id);
1135 $copy->edit_date('now');
1136 $copy->dummy_title($self->dummy_title);
1137 $copy->dummy_author($self->dummy_author);
1139 $self->update_copy();
1143 $logger->info("circulator: Creating a new precataloged ".
1144 "copy in checkout with barcode " . $self->copy_barcode);
1146 $copy = Fieldmapper::asset::copy->new;
1147 $copy->circ_lib($self->circ_lib);
1148 $copy->creator($self->editor->requestor->id);
1149 $copy->editor($self->editor->requestor->id);
1150 $copy->barcode($self->copy_barcode);
1151 $copy->call_number(OILS_PRECAT_CALL_NUMBER);
1152 $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
1153 $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
1155 $copy->dummy_title($self->dummy_title || "");
1156 $copy->dummy_author($self->dummy_author || "");
1158 unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
1160 $self->push_events($self->editor->event);
1164 # this is a little bit of a hack, but we need to
1165 # get the copy into the script runner
1166 $self->script_runner->insert("environment.copy", $copy, 1);
1170 sub checkout_noncat {
1176 my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
1177 my $count = $self->noncat_count || 1;
1178 my $cotime = clense_ISO8601($self->checkout_time) || "";
1180 $logger->info("ciculator: circ creating $count noncat circs with checkout time $cotime");
1184 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1185 $self->editor->requestor->id,
1193 $self->push_events($evt);
1204 $self->log_me("do_checkin()");
1207 return $self->bail_on_events(
1208 OpenILS::Event->new('ASSET_COPY_NOT_FOUND'))
1211 if( $self->checkin_check_holds_shelf() ) {
1212 $self->bail_on_events(OpenILS::Event->new('NO_CHANGE'));
1213 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1214 $self->checkin_flesh_events;
1218 unless( $self->is_renewal ) {
1219 return $self->bail_on_events($self->editor->event)
1220 unless $self->editor->allowed('COPY_CHECKIN');
1223 $self->push_events($self->check_copy_alert());
1224 $self->push_events($self->check_checkin_copy_status());
1226 # the renew code will have already found our circulation object
1227 unless( $self->is_renewal and $self->circ ) {
1229 $self->editor->search_action_circulation(
1230 { target_copy => $self->copy->id, checkin_time => undef })->[0]);
1233 # if the circ is marked as 'claims returned', add the event to the list
1234 $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
1235 if ($self->circ and $self->circ->stop_fines
1236 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
1238 # handle the overridable events
1239 $self->override_events unless $self->is_renewal;
1240 return if $self->bail_out;
1244 $self->editor->search_action_transit_copy(
1245 { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);
1249 $self->checkin_handle_circ;
1250 return if $self->bail_out;
1251 $self->checkin_changed(1);
1253 } elsif( $self->transit ) {
1254 my $hold_transit = $self->process_received_transit;
1255 $self->checkin_changed(1);
1257 if( $self->bail_out ) {
1258 $self->checkin_flesh_events;
1262 if( my $e = $self->check_checkin_copy_status() ) {
1263 # If the original copy status is special, alert the caller
1264 my $ev = $self->events;
1265 $self->events([$e]);
1266 $self->override_events;
1267 return if $self->bail_out;
1271 if( $hold_transit or
1272 $U->copy_status($self->copy->status)->id
1273 == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1276 $self->editor->retrieve_action_hold_request($hold_transit->hold) :
1277 $U->fetch_open_hold_by_copy($self->copy->id)
1280 $self->checkin_flesh_events;
1285 if( $self->is_renewal ) {
1286 $self->push_events(OpenILS::Event->new('SUCCESS'));
1290 # ------------------------------------------------------------------------------
1291 # Circulations and transits are now closed where necessary. Now go on to see if
1292 # this copy can fulfill a hold or needs to be routed to a different location
1293 # ------------------------------------------------------------------------------
1295 if( !$self->remote_hold and $self->attempt_checkin_hold_capture() ) {
1296 return if $self->bail_out;
1298 } else { # not needed for a hold
1301 my $circ_lib = (ref $self->copy->circ_lib) ?
1302 $self->copy->circ_lib->id : $self->copy->circ_lib;
1304 if( $self->remote_hold ) {
1305 $circ_lib = $self->remote_hold->pickup_lib;
1306 $logger->warn("circulator: Copy ".$self->copy->barcode.
1307 " is on a remote hold's shelf, sending to $circ_lib");
1310 $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
1312 if( $circ_lib == $self->editor->requestor->ws_ou ) {
1314 $self->checkin_handle_precat();
1315 return if $self->bail_out;
1319 my $bc = $self->copy->barcode;
1320 $logger->info("circulator: copy $bc at a remote lib - sending home");
1321 $self->checkin_build_copy_transit();
1322 return if $self->bail_out;
1323 $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
1327 $self->reshelve_copy;
1328 return if $self->bail_out;
1330 unless($self->checkin_changed) {
1332 $self->push_events(OpenILS::Event->new('NO_CHANGE'));
1333 my $stat = $U->copy_status($self->copy->status)->id;
1335 $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
1336 if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF );
1337 $self->bail_out(1); # no need to commit anything
1340 $self->push_events(OpenILS::Event->new('SUCCESS'))
1341 unless @{$self->events};
1344 $self->checkin_flesh_events;
1350 my $force = $self->force || shift;
1351 my $copy = $self->copy;
1353 my $stat = $U->copy_status($copy->status)->id;
1356 $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and
1357 $stat != OILS_COPY_STATUS_CATALOGING and
1358 $stat != OILS_COPY_STATUS_IN_TRANSIT and
1359 $stat != OILS_COPY_STATUS_RESHELVING )) {
1361 $copy->status( OILS_COPY_STATUS_RESHELVING );
1363 $self->checkin_changed(1);
1368 # Returns true if the item is at the current location
1369 # because it was transited there for a hold and the
1370 # hold has not been fulfilled
1371 sub checkin_check_holds_shelf {
1373 return 0 unless $self->copy;
1376 $U->copy_status($self->copy->status)->id ==
1377 OILS_COPY_STATUS_ON_HOLDS_SHELF;
1379 # find the hold that put us on the holds shelf
1380 my $holds = $self->editor->search_action_hold_request(
1382 current_copy => $self->copy->id,
1383 capture_time => { '!=' => undef },
1384 fulfillment_time => undef,
1385 cancel_time => undef,
1389 return 0 unless @$holds;
1391 my $hold = $$holds[0];
1393 $logger->info("circulator: we found a captured, un-fulfilled hold [".
1394 $hold->id. "] for copy ".$self->copy->barcode);
1396 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1397 $logger->info("circulator: hold is for here .. we're done: ".$self->copy->barcode);
1401 $logger->info("circulator: hold is not for here..");
1402 $self->remote_hold($hold);
1407 sub checkin_handle_precat {
1409 my $copy = $self->copy;
1411 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1412 $copy->status(OILS_COPY_STATUS_CATALOGING);
1413 $self->update_copy();
1414 $self->checkin_changed(1);
1415 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1420 sub checkin_build_copy_transit {
1422 my $copy = $self->copy;
1423 my $transit = Fieldmapper::action::transit_copy->new;
1425 $transit->source($self->editor->requestor->ws_ou);
1426 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1427 $transit->target_copy($copy->id);
1428 $transit->source_send_time('now');
1429 $transit->copy_status( $U->copy_status($copy->status)->id );
1431 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1433 return $self->bail_on_events($self->editor->event)
1434 unless $self->editor->create_action_transit_copy($transit);
1436 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1438 $self->checkin_changed(1);
1442 sub attempt_checkin_hold_capture {
1444 my $copy = $self->copy;
1446 # See if this copy can fulfill any holds
1447 my ($hold) = $holdcode->find_nearest_permitted_hold(
1448 OpenSRF::AppSession->create('open-ils.storage'),
1449 $copy, $self->editor->requestor );
1452 $logger->debug("circulator: no potential permitted".
1453 "holds found for copy ".$copy->barcode);
1458 $logger->info("circulator: found permitted hold ".
1459 $hold->id . " for copy, capturing...");
1461 $hold->current_copy($copy->id);
1462 $hold->capture_time('now');
1464 # prevent DB errors caused by fetching
1465 # holds from storage, and updating through cstore
1466 $hold->clear_fulfillment_time;
1467 $hold->clear_fulfillment_staff;
1468 $hold->clear_fulfillment_lib;
1469 $hold->clear_expire_time;
1470 $hold->clear_cancel_time;
1471 $hold->clear_prev_check_time unless $hold->prev_check_time;
1473 $self->bail_on_events($self->editor->event)
1474 unless $self->editor->update_action_hold_request($hold);
1476 $self->checkin_changed(1);
1478 return 1 if $self->bail_out;
1480 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1482 # This hold was captured in the correct location
1483 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1484 $self->push_events(OpenILS::Event->new('SUCCESS'));
1486 $self->do_hold_notify($hold->id);
1490 # Hold needs to be picked up elsewhere. Build a hold
1491 # transit and route the item.
1492 $self->checkin_build_hold_transit();
1493 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1494 return 1 if $self->bail_out;
1496 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1499 # make sure we save the copy status
1504 sub do_hold_notify {
1505 my( $self, $holdid ) = @_;
1506 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1507 editor => $self->editor, hold_id => $holdid );
1509 if(!$notifier->event) {
1511 $logger->info("ciculator: attempt at sending hold notification for hold $holdid");
1513 my $stat = $notifier->send_email_notify;
1514 $logger->info("ciculator: hold notify succeeded for hold $holdid") if $stat eq '1';
1515 $logger->warn("ciculator: * hold notify failed for hold $holdid") if $stat ne '1';
1518 $logger->info("ciculator: Not sending hold notification since the patron has no email address");
1523 sub checkin_build_hold_transit {
1527 my $copy = $self->copy;
1528 my $hold = $self->hold;
1529 my $trans = Fieldmapper::action::hold_transit_copy->new;
1531 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1533 $trans->hold($hold->id);
1534 $trans->source($self->editor->requestor->ws_ou);
1535 $trans->dest($hold->pickup_lib);
1536 $trans->source_send_time("now");
1537 $trans->target_copy($copy->id);
1539 # when the copy gets to its destination, it will recover
1540 # this status - put it onto the holds shelf
1541 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1543 return $self->bail_on_events($self->editor->event)
1544 unless $self->editor->create_action_hold_transit_copy($trans);
1549 sub process_received_transit {
1551 my $copy = $self->copy;
1552 my $copyid = $self->copy->id;
1554 my $status_name = $U->copy_status($copy->status)->name;
1555 $logger->debug("circulator: attempting transit receive on ".
1556 "copy $copyid. Copy status is $status_name");
1558 my $transit = $self->transit;
1560 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1561 $logger->info("circulator: Fowarding transit on copy which is destined ".
1562 "for a different location. copy=$copyid,current ".
1563 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1565 return $self->bail_on_events(
1566 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1569 # The transit is received, set the receive time
1570 $transit->dest_recv_time('now');
1571 $self->bail_on_events($self->editor->event)
1572 unless $self->editor->update_action_transit_copy($transit);
1574 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1576 $logger->info("ciculator: Recovering original copy status in transit: ".$transit->copy_status);
1577 $copy->status( $transit->copy_status );
1578 $self->update_copy();
1579 return if $self->bail_out;
1583 $self->do_hold_notify($hold_transit->hold);
1588 OpenILS::Event->new(
1591 payload => { transit => $transit, holdtransit => $hold_transit } ));
1593 return $hold_transit;
1597 sub checkin_handle_circ {
1601 my $circ = $self->circ;
1602 my $copy = $self->copy;
1606 # backdate the circ if necessary
1607 if($self->backdate) {
1608 $self->checkin_handle_backdate;
1609 return if $self->bail_out;
1612 if(!$circ->stop_fines) {
1613 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1614 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1615 $circ->stop_fines_time('now') unless $self->backdate;
1616 $circ->stop_fines_time($self->backdate) if $self->backdate;
1619 # see if there are any fines owed on this circ. if not, close it
1620 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1621 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1623 # Set the checkin vars since we have the item
1624 $circ->checkin_time('now');
1625 $circ->checkin_staff($self->editor->requestor->id);
1626 $circ->checkin_lib($self->editor->requestor->ws_ou);
1628 my $circ_lib = (ref $self->copy->circ_lib) ?
1629 $self->copy->circ_lib->id : $self->copy->circ_lib;
1630 my $stat = $U->copy_status($self->copy->status)->id;
1632 # If the item is lost/missing and it needs to be sent home, don't
1633 # reshelve the copy, leave it lost/missing so the recipient will know
1634 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1635 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1636 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1639 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1644 return $self->bail_on_events($self->editor->event)
1645 unless $self->editor->update_action_circulation($circ);
1649 sub checkin_handle_backdate {
1652 my $bd = $self->backdate;
1653 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1654 $bd = "${bd}T23:59:59";
1656 my $bills = $self->editor->search_money_billing(
1658 billing_ts => { '>=' => $bd },
1659 xact => $self->circ->id,
1660 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1664 for my $bill (@$bills) {
1665 if( !$bill->voided or $bill->voided =~ /f/i ) {
1667 my $n = $bill->note || "";
1668 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1670 $self->bail_on_events($self->editor->event)
1671 unless $self->editor->update_money_billing($bill);
1678 # XXX Legacy version for Circ.pm support
1679 sub _checkin_handle_backdate {
1680 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1683 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1684 $bd = "${bd}T23:59:59";
1687 my $bills = $session->request(
1688 "open-ils.storage.direct.money.billing.search_where.atomic",
1689 billing_ts => { '>=' => $bd },
1691 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1695 for my $bill (@$bills) {
1697 my $n = $bill->note || "";
1698 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1699 my $s = $session->request(
1700 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1701 return $U->DB_UPDATE_FAILED($bill) unless $s;
1711 sub find_patron_from_copy {
1713 my $circs = $self->editor->search_action_circulation(
1714 { target_copy => $self->copy->id, checkin_time => undef });
1715 my $circ = $circs->[0];
1716 return unless $circ;
1717 my $u = $self->editor->retrieve_actor_user($circ->usr)
1718 or return $self->bail_on_events($self->editor->event);
1722 sub check_checkin_copy_status {
1724 my $copy = $self->copy;
1730 my $status = $U->copy_status($copy->status)->id;
1733 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1734 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1735 $status == OILS_COPY_STATUS_IN_PROCESS ||
1736 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1737 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1738 $status == OILS_COPY_STATUS_CATALOGING ||
1739 $status == OILS_COPY_STATUS_RESHELVING );
1741 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1742 if( $status == OILS_COPY_STATUS_LOST );
1744 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1745 if( $status == OILS_COPY_STATUS_MISSING );
1747 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1752 # --------------------------------------------------------------------------
1753 # On checkin, we need to return as many relevant objects as we can
1754 # --------------------------------------------------------------------------
1755 sub checkin_flesh_events {
1758 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1759 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1760 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1764 for my $evt (@{$self->events}) {
1767 $payload->{copy} = $U->unflesh_copy($self->copy);
1768 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1769 $payload->{circ} = $self->circ;
1770 $payload->{transit} = $self->transit;
1771 $payload->{hold} = $self->hold;
1773 $evt->{payload} = $payload;
1778 my( $self, $msg ) = @_;
1779 my $bc = ($self->copy) ? $self->copy->barcode :
1782 my $usr = ($self->patron) ? $self->patron->id : "";
1783 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1784 ", recipient=$usr, copy=$bc");
1790 $self->log_me("do_renew()");
1791 $self->is_renewal(1);
1793 unless( $self->is_renewal ) {
1794 return $self->bail_on_events($self->editor->events)
1795 unless $self->editor->allowed('RENEW_CIRC');
1798 # Make sure there is an open circ to renew that is not
1799 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1800 my $circ = $self->editor->search_action_circulation(
1801 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1804 $circ = $self->editor->search_action_circulation(
1806 target_copy => $self->copy->id,
1807 stop_fines => OILS_STOP_FINES_MAX_FINES,
1808 checkin_time => undef
1813 return $self->bail_on_events($self->editor->event) unless $circ;
1815 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1816 if $circ->renewal_remaining < 1;
1818 # -----------------------------------------------------------------
1820 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1823 $self->run_renew_permit;
1826 $self->do_checkin();
1827 return if $self->bail_out;
1829 unless( $self->permit_override ) {
1831 return if $self->bail_out;
1832 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1833 $self->remove_event('ITEM_NOT_CATALOGED');
1836 $self->override_events;
1837 return if $self->bail_out;
1840 $self->do_checkout();
1845 my( $self, $evt ) = @_;
1846 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1847 $logger->debug("circulator: removing event from list: $evt");
1848 my @events = @{$self->events};
1849 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1854 my( $self, $evt ) = @_;
1855 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1856 return grep { $_->{textcode} eq $evt } @{$self->events};
1861 sub run_renew_permit {
1863 my $runner = $self->script_runner;
1865 $runner->load($self->circ_permit_renew);
1866 my $result = $runner->run or
1867 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1868 my $events = $result->{events};
1870 $logger->activity("ciculator: circ_permit_renew for user ".
1871 $self->patron->id." returned events: @$events") if @$events;
1873 $self->push_events(OpenILS::Event->new($_)) for @$events;
1875 $logger->debug("circulator: re-creating script runner to be safe");
1876 $self->mk_script_runner;