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 {
441 qw/copy copy_barcode copy_id patron
442 patron_id patron_barcode volume title editor/;
444 # Translate our objects into the ScriptBuilder args hash
445 $$args{$_} = $self->$_() for @fields;
447 $args->{ignore_user_status} = 1 if $self->is_checkin;
448 $$args{fetch_patron_by_circ_copy} = 1;
449 $$args{fetch_patron_circ_info} = 1 unless $self->is_checkin;
451 # This fetches most of the objects we need
452 $self->script_runner(
453 OpenILS::Application::Circ::ScriptBuilder->build($args));
455 # Now we translate the ScriptBuilder objects back into self
456 $self->$_($$args{$_}) for @fields;
458 my @evts = @{$args->{_events}} if $args->{_events};
460 $logger->debug("circulator: script builder returned events: @evts") if @evts;
464 # Anything besides ASSET_COPY_NOT_FOUND will stop processing
465 if(!$self->is_noncat and
467 $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') {
471 my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts;
472 return $self->bail_on_events(@e);
476 $self->is_precat(1) if $self->copy and $self->copy->call_number == OILS_PRECAT_CALL_NUMBER;
478 # We can't renew if there is no copy
479 return $self->bail_on_events(@evts) if
480 $self->is_renewal and !$self->copy;
482 # Set some circ-specific flags in the script environment
483 my $evt = "environment";
484 $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef);
486 if( $self->is_noncat ) {
487 $self->script_runner->insert("$evt.isNonCat", 1);
488 $self->script_runner->insert("$evt.nonCatType", $self->noncat_type);
491 if( $self->is_precat ) {
492 $self->script_runner->insert("environment.isPrecat", 1, 1);
495 $self->script_runner->add_path( $_ ) for @$script_libs;
503 # --------------------------------------------------------------------------
504 # Does the circ permit work
505 # --------------------------------------------------------------------------
509 $self->log_me("do_permit()");
511 unless( $self->editor->requestor->id == $self->patron->id ) {
512 return $self->bail_on_events($self->editor->event)
513 unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') );
517 $self->check_captured_holds();
518 $self->do_copy_checks();
519 return if $self->bail_out;
520 $self->run_patron_permit_scripts();
521 $self->run_copy_permit_scripts()
522 unless $self->is_precat or $self->is_noncat;
523 $self->override_events() unless $self->is_renewal;
524 return if $self->bail_out;
526 if( $self->is_precat ) {
529 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key));
530 return $self->bail_out(1) unless $self->is_renewal;
536 payload => $self->mk_permit_key));
540 sub check_captured_holds {
542 my $copy = $self->copy;
543 my $patron = $self->patron;
545 return undef unless $copy;
547 my $s = $U->copy_status($copy->status)->id;
548 return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF;
549 $logger->info("circulator: copy is on holds shelf, searching for the correct hold");
551 # Item is on the holds shelf, make sure it's going to the right person
552 my $holds = $self->editor->search_action_hold_request(
555 current_copy => $copy->id ,
556 capture_time => { '!=' => undef },
557 cancel_time => undef,
558 fulfillment_time => undef
564 if( $holds and $$holds[0] ) {
565 return undef if $$holds[0]->usr == $patron->id;
568 $logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
570 $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF'));
576 my $copy = $self->copy;
579 my $stat = $U->copy_status($copy->status)->id;
581 # We cannot check out a copy if it is in-transit
582 if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) {
583 return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
586 $self->handle_claims_returned();
587 return if $self->bail_out;
589 # no claims returned circ was found, check if there is any open circ
590 unless( $self->is_renewal ) {
591 my $circs = $self->editor->search_action_circulation(
592 { target_copy => $copy->id, checkin_time => undef }
595 return $self->bail_on_events(
596 OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
601 sub send_penalty_request {
603 my $ses = OpenSRF::AppSession->create('open-ils.penalty');
604 $self->penalty_request(
606 'open-ils.penalty.patron_penalty.calculate',
608 authtoken => $self->editor->authtoken,
609 patron => $self->patron } ) );
612 sub gather_penalty_request {
614 return [] unless $self->penalty_request;
615 my $data = $self->penalty_request->recv;
617 $data = $data->content;
618 return $data->{fatal_penalties};
620 $logger->error("circulator: penalty request returned no data");
624 # ---------------------------------------------------------------------
625 # This pushes any patron-related events into the list but does not
626 # set bail_out for any events
627 # ---------------------------------------------------------------------
628 sub run_patron_permit_scripts {
630 my $runner = $self->script_runner;
631 my $patronid = $self->patron->id;
633 $self->send_penalty_request();
635 # ---------------------------------------------------------------------
636 # Now run the patron permit script
637 # ---------------------------------------------------------------------
638 $runner->load($self->circ_permit_patron);
639 my $result = $runner->run or
640 throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
642 my $patron_events = $result->{events};
645 my $penalties = $self->gather_penalty_request();
646 push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
648 $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
650 $self->push_events(@allevents);
654 sub run_copy_permit_scripts {
656 my $copy = $self->copy || return;
657 my $runner = $self->script_runner;
659 # ---------------------------------------------------------------------
660 # Capture all of the copy permit events
661 # ---------------------------------------------------------------------
662 $runner->load($self->circ_permit_copy);
663 my $result = $runner->run or
664 throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
665 my $copy_events = $result->{events};
667 # ---------------------------------------------------------------------
668 # Now collect all of the events together
669 # ---------------------------------------------------------------------
671 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
673 # See if this copy has an alert message
674 my $ae = $self->check_copy_alert();
675 push( @allevents, $ae ) if $ae;
677 # uniquify the events
678 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
679 @allevents = values %hash;
682 $_->{payload} = $copy if
683 ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
686 $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
688 $self->push_events(@allevents);
692 sub check_copy_alert {
694 return undef if $self->is_renewal;
695 return OpenILS::Event->new(
696 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
697 if $self->copy and $self->copy->alert_message;
703 # --------------------------------------------------------------------------
704 # If the call is overriding and has permissions to override every collected
705 # event, the are cleared. Any event that the caller does not have
706 # permission to override, will be left in the event list and bail_out will
708 # XXX We need code in here to cancel any holds/transits on copies
709 # that are being force-checked out
710 # --------------------------------------------------------------------------
711 sub override_events {
713 my @events = @{$self->events};
714 return unless @events;
716 if(!$self->override) {
717 return $self->bail_out(1)
718 if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
723 for my $e (@events) {
724 my $tc = $e->{textcode};
725 next if $tc eq 'SUCCESS';
726 my $ov = "$tc.override";
727 $logger->info("circulator: attempting to override event: $ov");
729 return $self->bail_on_events($self->editor->event)
730 unless( $self->editor->allowed($ov) );
735 # --------------------------------------------------------------------------
736 # If there is an open claimsreturn circ on the requested copy, close the
737 # circ if overriding, otherwise bail out
738 # --------------------------------------------------------------------------
739 sub handle_claims_returned {
741 my $copy = $self->copy;
743 my $CR = $self->editor->search_action_circulation(
745 target_copy => $copy->id,
746 stop_fines => OILS_STOP_FINES_CLAIMSRETURNED,
747 checkin_time => undef,
751 return unless ($CR = $CR->[0]);
755 # - If the caller has set the override flag, we will check the item in
756 if($self->override) {
758 $CR->checkin_time('now');
759 $CR->checkin_lib($self->editor->requestor->ws_ou);
760 $CR->checkin_staff($self->editor->requestor->id);
762 $evt = $self->editor->event
763 unless $self->editor->update_action_circulation($CR);
766 $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
769 $self->bail_on_events($evt) if $evt;
774 # --------------------------------------------------------------------------
775 # This performs the checkout
776 # --------------------------------------------------------------------------
780 $self->log_me("do_checkout()");
782 # make sure perms are good if this isn't a renewal
783 unless( $self->is_renewal ) {
784 return $self->bail_on_events($self->editor->event)
785 unless( $self->editor->allowed('COPY_CHECKOUT') );
788 # verify the permit key
789 unless( $self->check_permit_key ) {
790 if( $self->permit_override ) {
791 return $self->bail_on_events($self->editor->event)
792 unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
794 return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
798 # if this is a non-cataloged circ, build the circ and finish
799 if( $self->is_noncat ) {
800 $self->checkout_noncat;
802 OpenILS::Event->new('SUCCESS',
803 payload => { noncat_circ => $self->circ }));
807 if( $self->is_precat ) {
808 $self->script_runner->insert("environment.isPrecat", 1, 1);
809 $self->make_precat_copy;
810 return if $self->bail_out;
812 } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
813 return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
816 $self->do_copy_checks;
817 return if $self->bail_out;
819 $self->run_checkout_scripts();
820 return if $self->bail_out;
822 $self->build_checkout_circ_object();
823 return if $self->bail_out;
825 $self->apply_modified_due_date();
826 return if $self->bail_out;
828 return $self->bail_on_events($self->editor->event)
829 unless $self->editor->create_action_circulation($self->circ);
831 $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
833 return if $self->bail_out;
835 $self->handle_checkout_holds();
836 return if $self->bail_out;
838 # ------------------------------------------------------------------------------
839 # Update the patron penalty info in the DB
840 # ------------------------------------------------------------------------------
841 $U->update_patron_penalties(
842 authtoken => $self->editor->authtoken,
843 patron => $self->patron,
847 my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
849 OpenILS::Event->new('SUCCESS',
851 copy => $U->unflesh_copy($self->copy),
854 holds_fulfilled => $self->fulfilled_holds,
862 my $copy = $self->copy;
864 my $stat = $copy->status if ref $copy->status;
865 my $loc = $copy->location if ref $copy->location;
866 my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
868 $copy->status($stat->id) if $stat;
869 $copy->location($loc->id) if $loc;
870 $copy->circ_lib($circ_lib->id) if $circ_lib;
871 $copy->editor($self->editor->requestor->id);
872 $copy->edit_date('now');
873 $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect;
875 return $self->bail_on_events($self->editor->event)
876 unless $self->editor->update_asset_copy($self->copy);
878 $copy->status($U->copy_status($copy->status));
879 $copy->location($loc) if $loc;
880 $copy->circ_lib($circ_lib) if $circ_lib;
885 my( $self, @evts ) = @_;
886 $self->push_events(@evts);
890 sub handle_checkout_holds {
893 my $copy = $self->copy;
894 my $patron = $self->patron;
896 my $holds = $self->editor->search_action_hold_request(
898 current_copy => $copy->id ,
899 cancel_time => undef,
900 fulfillment_time => undef
906 # XXX We should only fulfill one hold here...
907 # XXX If a hold was transited to the user who is checking out
908 # the item, we need to make sure that hold is what's grabbed
911 # for now, just sort by id to get what should be the oldest hold
912 $holds = [ sort { $a->id <=> $b->id } @$holds ];
913 my @myholds = grep { $_->usr eq $patron->id } @$holds;
914 my @altholds = grep { $_->usr ne $patron->id } @$holds;
917 my $hold = $myholds[0];
919 $logger->debug("circulator: related hold found in checkout: " . $hold->id );
921 # if the hold was never officially captured, capture it.
922 $hold->capture_time('now') unless $hold->capture_time;
924 # just make sure it's set correctly
925 $hold->current_copy($copy->id);
927 $hold->fulfillment_time('now');
928 $hold->fulfillment_staff($self->editor->requestor->id);
929 $hold->fulfillment_lib($self->editor->requestor->ws_ou);
931 return $self->bail_on_events($self->editor->event)
932 unless $self->editor->update_action_hold_request($hold);
934 $holdcode->delete_hold_copy_maps($self->editor, $hold->id);
936 push( @fulfilled, $hold->id );
939 # If there are any holds placed for other users that point to this copy,
940 # then we need to un-target those holds so the targeter can pick a new copy
943 $logger->info("circulator: un-targeting hold ".$_->id.
944 " because copy ".$copy->id." is getting checked out");
946 # - make the targeter process this hold at next run
947 $_->clear_prev_check_time;
949 # - clear out the targetted copy
950 $_->clear_current_copy;
951 $_->clear_capture_time;
953 return $self->bail_on_event($self->editor->event)
954 unless $self->editor->update_action_hold_request($_);
958 $self->fulfilled_holds(\@fulfilled);
963 sub run_checkout_scripts {
967 my $runner = $self->script_runner;
968 $runner->load($self->circ_duration);
970 my $result = $runner->run or
971 throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
973 my $duration = $result->{durationRule};
974 my $recurring = $result->{recurringFinesRule};
975 my $max_fine = $result->{maxFine};
977 if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) {
979 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
980 return $self->bail_on_events($evt) if $evt;
982 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
983 return $self->bail_on_events($evt) if $evt;
985 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
986 return $self->bail_on_events($evt) if $evt;
990 # The item circulates with an unlimited duration
996 $self->duration_rule($duration);
997 $self->recurring_fines_rule($recurring);
998 $self->max_fine_rule($max_fine);
1002 sub build_checkout_circ_object {
1005 my $circ = Fieldmapper::action::circulation->new;
1006 my $duration = $self->duration_rule;
1007 my $max = $self->max_fine_rule;
1008 my $recurring = $self->recurring_fines_rule;
1009 my $copy = $self->copy;
1010 my $patron = $self->patron;
1014 my $dname = $duration->name;
1015 my $mname = $max->name;
1016 my $rname = $recurring->name;
1018 $logger->debug("circulator: building circulation ".
1019 "with duration=$dname, maxfine=$mname, recurring=$rname");
1021 $circ->duration( $duration->shrt )
1022 if $copy->loan_duration == OILS_CIRC_DURATION_SHORT;
1023 $circ->duration( $duration->normal )
1024 if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL;
1025 $circ->duration( $duration->extended )
1026 if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED;
1028 $circ->recuring_fine( $recurring->low )
1029 if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW;
1030 $circ->recuring_fine( $recurring->normal )
1031 if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL;
1032 $circ->recuring_fine( $recurring->high )
1033 if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH;
1035 $circ->duration_rule( $duration->name );
1036 $circ->recuring_fine_rule( $recurring->name );
1037 $circ->max_fine_rule( $max->name );
1038 $circ->max_fine( $max->amount );
1040 $circ->fine_interval($recurring->recurance_interval);
1041 $circ->renewal_remaining( $duration->max_renewals );
1045 $logger->info("circulator: copy found with an unlimited circ duration");
1046 $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION);
1047 $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1048 $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1049 $circ->renewal_remaining(0);
1052 $circ->target_copy( $copy->id );
1053 $circ->usr( $patron->id );
1054 $circ->circ_lib( $self->circ_lib );
1056 if( $self->is_renewal ) {
1057 $circ->opac_renewal(1);
1058 $circ->renewal_remaining($self->renewal_remaining);
1059 $circ->circ_staff($self->editor->requestor->id);
1062 # if the user provided an overiding checkout time,
1063 # (e.g. the checkout really happened several hours ago), then
1064 # we apply that here. Does this need a perm??
1065 $circ->xact_start(clense_ISO8601($self->checkout_time))
1066 if $self->checkout_time;
1068 # if a patron is renewing, 'requestor' will be the patron
1069 $circ->circ_staff($self->editor->requestor->id);
1070 $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
1076 sub apply_modified_due_date {
1078 my $circ = $self->circ;
1079 my $copy = $self->copy;
1081 if( $self->due_date ) {
1083 return $self->bail_on_events($self->editor->event)
1084 unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
1086 $circ->due_date(clense_ISO8601($self->due_date));
1090 # if the due_date lands on a day when the location is closed
1091 return unless $copy and $circ->due_date;
1093 my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
1095 $logger->info("circulator: circ searching for closed date overlap on lib $org".
1096 " with an item due date of ".$circ->due_date );
1098 my $dateinfo = $U->storagereq(
1099 'open-ils.storage.actor.org_unit.closed_date.overlap',
1100 $org, $circ->due_date );
1103 $logger->info("circulator: $dateinfo : circ due data / close date overlap found : due_date=".
1104 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1106 # XXX make the behavior more dynamic
1107 # for now, we just push the due date to after the close date
1108 $circ->due_date($dateinfo->{end});
1115 sub create_due_date {
1116 my( $self, $duration ) = @_;
1117 my ($sec,$min,$hour,$mday,$mon,$year) =
1118 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1119 $year += 1900; $mon += 1;
1120 my $due_date = sprintf(
1121 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1122 $year, $mon, $mday, $hour, $min, $sec);
1128 sub make_precat_copy {
1130 my $copy = $self->copy;
1133 $logger->debug("ciculator: Pre-cat copy already exists in checkout: ID=" . $copy->id);
1135 $copy->editor($self->editor->requestor->id);
1136 $copy->edit_date('now');
1137 $copy->dummy_title($self->dummy_title);
1138 $copy->dummy_author($self->dummy_author);
1140 $self->update_copy();
1144 $logger->info("circulator: Creating a new precataloged ".
1145 "copy in checkout with barcode " . $self->copy_barcode);
1147 $copy = Fieldmapper::asset::copy->new;
1148 $copy->circ_lib($self->circ_lib);
1149 $copy->creator($self->editor->requestor->id);
1150 $copy->editor($self->editor->requestor->id);
1151 $copy->barcode($self->copy_barcode);
1152 $copy->call_number(OILS_PRECAT_CALL_NUMBER);
1153 $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
1154 $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
1156 $copy->dummy_title($self->dummy_title || "");
1157 $copy->dummy_author($self->dummy_author || "");
1159 unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
1161 $self->push_events($self->editor->event);
1165 # this is a little bit of a hack, but we need to
1166 # get the copy into the script runner
1167 $self->script_runner->insert("environment.copy", $copy, 1);
1171 sub checkout_noncat {
1177 my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
1178 my $count = $self->noncat_count || 1;
1179 my $cotime = clense_ISO8601($self->checkout_time) || "";
1181 $logger->info("ciculator: circ creating $count noncat circs with checkout time $cotime");
1185 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1186 $self->editor->requestor->id,
1194 $self->push_events($evt);
1205 $self->log_me("do_checkin()");
1208 return $self->bail_on_events(
1209 OpenILS::Event->new('ASSET_COPY_NOT_FOUND'))
1212 if( $self->checkin_check_holds_shelf() ) {
1213 $self->bail_on_events(OpenILS::Event->new('NO_CHANGE'));
1214 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1215 $self->checkin_flesh_events;
1219 unless( $self->is_renewal ) {
1220 return $self->bail_on_events($self->editor->event)
1221 unless $self->editor->allowed('COPY_CHECKIN');
1224 $self->push_events($self->check_copy_alert());
1225 $self->push_events($self->check_checkin_copy_status());
1227 # the renew code will have already found our circulation object
1228 unless( $self->is_renewal and $self->circ ) {
1230 $self->editor->search_action_circulation(
1231 { target_copy => $self->copy->id, checkin_time => undef })->[0]);
1234 # if the circ is marked as 'claims returned', add the event to the list
1235 $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
1236 if ($self->circ and $self->circ->stop_fines
1237 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
1239 # handle the overridable events
1240 $self->override_events unless $self->is_renewal;
1241 return if $self->bail_out;
1245 $self->editor->search_action_transit_copy(
1246 { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);
1250 $self->checkin_handle_circ;
1251 return if $self->bail_out;
1252 $self->checkin_changed(1);
1254 } elsif( $self->transit ) {
1255 my $hold_transit = $self->process_received_transit;
1256 $self->checkin_changed(1);
1258 if( $self->bail_out ) {
1259 $self->checkin_flesh_events;
1263 if( my $e = $self->check_checkin_copy_status() ) {
1264 # If the original copy status is special, alert the caller
1265 my $ev = $self->events;
1266 $self->events([$e]);
1267 $self->override_events;
1268 return if $self->bail_out;
1272 if( $hold_transit or
1273 $U->copy_status($self->copy->status)->id
1274 == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1277 $self->editor->retrieve_action_hold_request($hold_transit->hold) :
1278 $U->fetch_open_hold_by_copy($self->copy->id)
1281 $self->checkin_flesh_events;
1286 if( $self->is_renewal ) {
1287 $self->push_events(OpenILS::Event->new('SUCCESS'));
1291 # ------------------------------------------------------------------------------
1292 # Circulations and transits are now closed where necessary. Now go on to see if
1293 # this copy can fulfill a hold or needs to be routed to a different location
1294 # ------------------------------------------------------------------------------
1296 if( !$self->remote_hold and $self->attempt_checkin_hold_capture() ) {
1297 return if $self->bail_out;
1299 } else { # not needed for a hold
1302 my $circ_lib = (ref $self->copy->circ_lib) ?
1303 $self->copy->circ_lib->id : $self->copy->circ_lib;
1305 if( $self->remote_hold ) {
1306 $circ_lib = $self->remote_hold->pickup_lib;
1307 $logger->warn("circulator: Copy ".$self->copy->barcode.
1308 " is on a remote hold's shelf, sending to $circ_lib");
1311 $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
1313 if( $circ_lib == $self->editor->requestor->ws_ou ) {
1315 $self->checkin_handle_precat();
1316 return if $self->bail_out;
1320 my $bc = $self->copy->barcode;
1321 $logger->info("circulator: copy $bc at a remote lib - sending home");
1322 $self->checkin_build_copy_transit();
1323 return if $self->bail_out;
1324 $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
1328 $self->reshelve_copy;
1329 return if $self->bail_out;
1331 unless($self->checkin_changed) {
1333 $self->push_events(OpenILS::Event->new('NO_CHANGE'));
1334 my $stat = $U->copy_status($self->copy->status)->id;
1336 $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
1337 if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF );
1338 $self->bail_out(1); # no need to commit anything
1341 $self->push_events(OpenILS::Event->new('SUCCESS'))
1342 unless @{$self->events};
1346 # ------------------------------------------------------------------------------
1347 # Update the patron penalty info in the DB
1348 # ------------------------------------------------------------------------------
1349 $U->update_patron_penalties(
1350 authtoken => $self->editor->authtoken,
1351 patron => $self->patron,
1352 background => 1 ) if $self->is_checkin;
1354 $self->checkin_flesh_events;
1360 my $force = $self->force || shift;
1361 my $copy = $self->copy;
1363 my $stat = $U->copy_status($copy->status)->id;
1366 $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and
1367 $stat != OILS_COPY_STATUS_CATALOGING and
1368 $stat != OILS_COPY_STATUS_IN_TRANSIT and
1369 $stat != OILS_COPY_STATUS_RESHELVING )) {
1371 $copy->status( OILS_COPY_STATUS_RESHELVING );
1373 $self->checkin_changed(1);
1378 # Returns true if the item is at the current location
1379 # because it was transited there for a hold and the
1380 # hold has not been fulfilled
1381 sub checkin_check_holds_shelf {
1383 return 0 unless $self->copy;
1386 $U->copy_status($self->copy->status)->id ==
1387 OILS_COPY_STATUS_ON_HOLDS_SHELF;
1389 # find the hold that put us on the holds shelf
1390 my $holds = $self->editor->search_action_hold_request(
1392 current_copy => $self->copy->id,
1393 capture_time => { '!=' => undef },
1394 fulfillment_time => undef,
1395 cancel_time => undef,
1400 $logger->warn("circulator: copy is on-holds-shelf, but there is no hold - reshelving");
1401 $self->reshelve_copy(1);
1405 my $hold = $$holds[0];
1407 $logger->info("circulator: we found a captured, un-fulfilled hold [".
1408 $hold->id. "] for copy ".$self->copy->barcode);
1410 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1411 $logger->info("circulator: hold is for here .. we're done: ".$self->copy->barcode);
1415 $logger->info("circulator: hold is not for here..");
1416 $self->remote_hold($hold);
1421 sub checkin_handle_precat {
1423 my $copy = $self->copy;
1425 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1426 $copy->status(OILS_COPY_STATUS_CATALOGING);
1427 $self->update_copy();
1428 $self->checkin_changed(1);
1429 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1434 sub checkin_build_copy_transit {
1436 my $copy = $self->copy;
1437 my $transit = Fieldmapper::action::transit_copy->new;
1439 $transit->source($self->editor->requestor->ws_ou);
1440 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1441 $transit->target_copy($copy->id);
1442 $transit->source_send_time('now');
1443 $transit->copy_status( $U->copy_status($copy->status)->id );
1445 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1447 return $self->bail_on_events($self->editor->event)
1448 unless $self->editor->create_action_transit_copy($transit);
1450 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1452 $self->checkin_changed(1);
1456 sub attempt_checkin_hold_capture {
1458 my $copy = $self->copy;
1460 # See if this copy can fulfill any holds
1461 my ($hold) = $holdcode->find_nearest_permitted_hold(
1462 OpenSRF::AppSession->create('open-ils.storage'),
1463 $copy, $self->editor->requestor );
1466 $logger->debug("circulator: no potential permitted".
1467 "holds found for copy ".$copy->barcode);
1472 $logger->info("circulator: found permitted hold ".
1473 $hold->id . " for copy, capturing...");
1475 $hold->current_copy($copy->id);
1476 $hold->capture_time('now');
1478 # prevent DB errors caused by fetching
1479 # holds from storage, and updating through cstore
1480 $hold->clear_fulfillment_time;
1481 $hold->clear_fulfillment_staff;
1482 $hold->clear_fulfillment_lib;
1483 $hold->clear_expire_time;
1484 $hold->clear_cancel_time;
1485 $hold->clear_prev_check_time unless $hold->prev_check_time;
1487 $self->bail_on_events($self->editor->event)
1488 unless $self->editor->update_action_hold_request($hold);
1490 $self->checkin_changed(1);
1492 return 1 if $self->bail_out;
1494 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1496 # This hold was captured in the correct location
1497 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1498 $self->push_events(OpenILS::Event->new('SUCCESS'));
1500 $self->do_hold_notify($hold->id);
1504 # Hold needs to be picked up elsewhere. Build a hold
1505 # transit and route the item.
1506 $self->checkin_build_hold_transit();
1507 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1508 return 1 if $self->bail_out;
1510 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1513 # make sure we save the copy status
1518 sub do_hold_notify {
1519 my( $self, $holdid ) = @_;
1520 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1521 editor => $self->editor, hold_id => $holdid );
1523 if(!$notifier->event) {
1525 $logger->info("ciculator: attempt at sending hold notification for hold $holdid");
1527 my $stat = $notifier->send_email_notify;
1528 $logger->info("ciculator: hold notify succeeded for hold $holdid") if $stat eq '1';
1529 $logger->warn("ciculator: * hold notify failed for hold $holdid") if $stat ne '1';
1532 $logger->info("ciculator: Not sending hold notification since the patron has no email address");
1537 sub checkin_build_hold_transit {
1541 my $copy = $self->copy;
1542 my $hold = $self->hold;
1543 my $trans = Fieldmapper::action::hold_transit_copy->new;
1545 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1547 $trans->hold($hold->id);
1548 $trans->source($self->editor->requestor->ws_ou);
1549 $trans->dest($hold->pickup_lib);
1550 $trans->source_send_time("now");
1551 $trans->target_copy($copy->id);
1553 # when the copy gets to its destination, it will recover
1554 # this status - put it onto the holds shelf
1555 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1557 return $self->bail_on_events($self->editor->event)
1558 unless $self->editor->create_action_hold_transit_copy($trans);
1563 sub process_received_transit {
1565 my $copy = $self->copy;
1566 my $copyid = $self->copy->id;
1568 my $status_name = $U->copy_status($copy->status)->name;
1569 $logger->debug("circulator: attempting transit receive on ".
1570 "copy $copyid. Copy status is $status_name");
1572 my $transit = $self->transit;
1574 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1575 $logger->info("circulator: Fowarding transit on copy which is destined ".
1576 "for a different location. copy=$copyid,current ".
1577 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1579 return $self->bail_on_events(
1580 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1583 # The transit is received, set the receive time
1584 $transit->dest_recv_time('now');
1585 $self->bail_on_events($self->editor->event)
1586 unless $self->editor->update_action_transit_copy($transit);
1588 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1590 $logger->info("ciculator: Recovering original copy status in transit: ".$transit->copy_status);
1591 $copy->status( $transit->copy_status );
1592 $self->update_copy();
1593 return if $self->bail_out;
1597 $self->do_hold_notify($hold_transit->hold);
1602 OpenILS::Event->new(
1605 payload => { transit => $transit, holdtransit => $hold_transit } ));
1607 return $hold_transit;
1611 sub checkin_handle_circ {
1615 my $circ = $self->circ;
1616 my $copy = $self->copy;
1620 # backdate the circ if necessary
1621 if($self->backdate) {
1622 $self->checkin_handle_backdate;
1623 return if $self->bail_out;
1626 if(!$circ->stop_fines) {
1627 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1628 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1629 $circ->stop_fines_time('now') unless $self->backdate;
1630 $circ->stop_fines_time($self->backdate) if $self->backdate;
1633 # see if there are any fines owed on this circ. if not, close it
1634 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1635 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1637 # Set the checkin vars since we have the item
1638 $circ->checkin_time('now');
1639 $circ->checkin_staff($self->editor->requestor->id);
1640 $circ->checkin_lib($self->editor->requestor->ws_ou);
1642 my $circ_lib = (ref $self->copy->circ_lib) ?
1643 $self->copy->circ_lib->id : $self->copy->circ_lib;
1644 my $stat = $U->copy_status($self->copy->status)->id;
1646 # If the item is lost/missing and it needs to be sent home, don't
1647 # reshelve the copy, leave it lost/missing so the recipient will know
1648 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1649 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1650 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1653 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1658 return $self->bail_on_events($self->editor->event)
1659 unless $self->editor->update_action_circulation($circ);
1663 sub checkin_handle_backdate {
1666 my $bd = $self->backdate;
1667 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1668 $bd = "${bd}T23:59:59";
1670 my $bills = $self->editor->search_money_billing(
1672 billing_ts => { '>=' => $bd },
1673 xact => $self->circ->id,
1674 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1678 for my $bill (@$bills) {
1679 if( !$bill->voided or $bill->voided =~ /f/i ) {
1681 my $n = $bill->note || "";
1682 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1684 $self->bail_on_events($self->editor->event)
1685 unless $self->editor->update_money_billing($bill);
1692 # XXX Legacy version for Circ.pm support
1693 sub _checkin_handle_backdate {
1694 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1697 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1698 $bd = "${bd}T23:59:59";
1701 my $bills = $session->request(
1702 "open-ils.storage.direct.money.billing.search_where.atomic",
1703 billing_ts => { '>=' => $bd },
1705 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1709 for my $bill (@$bills) {
1711 my $n = $bill->note || "";
1712 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1713 my $s = $session->request(
1714 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1715 return $U->DB_UPDATE_FAILED($bill) unless $s;
1725 sub find_patron_from_copy {
1727 my $circs = $self->editor->search_action_circulation(
1728 { target_copy => $self->copy->id, checkin_time => undef });
1729 my $circ = $circs->[0];
1730 return unless $circ;
1731 my $u = $self->editor->retrieve_actor_user($circ->usr)
1732 or return $self->bail_on_events($self->editor->event);
1736 sub check_checkin_copy_status {
1738 my $copy = $self->copy;
1744 my $status = $U->copy_status($copy->status)->id;
1747 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1748 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1749 $status == OILS_COPY_STATUS_IN_PROCESS ||
1750 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1751 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1752 $status == OILS_COPY_STATUS_CATALOGING ||
1753 $status == OILS_COPY_STATUS_RESHELVING );
1755 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1756 if( $status == OILS_COPY_STATUS_LOST );
1758 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1759 if( $status == OILS_COPY_STATUS_MISSING );
1761 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1766 # --------------------------------------------------------------------------
1767 # On checkin, we need to return as many relevant objects as we can
1768 # --------------------------------------------------------------------------
1769 sub checkin_flesh_events {
1772 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1773 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1774 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1778 for my $evt (@{$self->events}) {
1781 $payload->{copy} = $U->unflesh_copy($self->copy);
1782 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1783 $payload->{circ} = $self->circ;
1784 $payload->{transit} = $self->transit;
1785 $payload->{hold} = $self->hold;
1787 $evt->{payload} = $payload;
1792 my( $self, $msg ) = @_;
1793 my $bc = ($self->copy) ? $self->copy->barcode :
1796 my $usr = ($self->patron) ? $self->patron->id : "";
1797 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1798 ", recipient=$usr, copy=$bc");
1804 $self->log_me("do_renew()");
1805 $self->is_renewal(1);
1807 unless( $self->is_renewal ) {
1808 return $self->bail_on_events($self->editor->events)
1809 unless $self->editor->allowed('RENEW_CIRC');
1812 # Make sure there is an open circ to renew that is not
1813 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1814 my $circ = $self->editor->search_action_circulation(
1815 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1818 $circ = $self->editor->search_action_circulation(
1820 target_copy => $self->copy->id,
1821 stop_fines => OILS_STOP_FINES_MAX_FINES,
1822 checkin_time => undef
1827 return $self->bail_on_events($self->editor->event) unless $circ;
1829 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1830 if $circ->renewal_remaining < 1;
1832 # -----------------------------------------------------------------
1834 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1837 $self->run_renew_permit;
1840 $self->do_checkin();
1841 return if $self->bail_out;
1843 unless( $self->permit_override ) {
1845 return if $self->bail_out;
1846 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1847 $self->remove_event('ITEM_NOT_CATALOGED');
1850 $self->override_events;
1851 return if $self->bail_out;
1854 $self->do_checkout();
1859 my( $self, $evt ) = @_;
1860 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1861 $logger->debug("circulator: removing event from list: $evt");
1862 my @events = @{$self->events};
1863 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1868 my( $self, $evt ) = @_;
1869 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1870 return grep { $_->{textcode} eq $evt } @{$self->events};
1875 sub run_renew_permit {
1877 my $runner = $self->script_runner;
1879 $runner->load($self->circ_permit_renew);
1880 my $result = $runner->run or
1881 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1882 my $events = $result->{events};
1884 $logger->activity("ciculator: circ_permit_renew for user ".
1885 $self->patron->id." returned events: @$events") if @$events;
1887 $self->push_events(OpenILS::Event->new($_)) for @$events;
1889 $logger->debug("circulator: re-creating script runner to be safe");
1890 $self->mk_script_runner;