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,
1399 return 0 unless @$holds;
1401 my $hold = $$holds[0];
1403 $logger->info("circulator: we found a captured, un-fulfilled hold [".
1404 $hold->id. "] for copy ".$self->copy->barcode);
1406 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1407 $logger->info("circulator: hold is for here .. we're done: ".$self->copy->barcode);
1411 $logger->info("circulator: hold is not for here..");
1412 $self->remote_hold($hold);
1417 sub checkin_handle_precat {
1419 my $copy = $self->copy;
1421 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1422 $copy->status(OILS_COPY_STATUS_CATALOGING);
1423 $self->update_copy();
1424 $self->checkin_changed(1);
1425 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1430 sub checkin_build_copy_transit {
1432 my $copy = $self->copy;
1433 my $transit = Fieldmapper::action::transit_copy->new;
1435 $transit->source($self->editor->requestor->ws_ou);
1436 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1437 $transit->target_copy($copy->id);
1438 $transit->source_send_time('now');
1439 $transit->copy_status( $U->copy_status($copy->status)->id );
1441 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1443 return $self->bail_on_events($self->editor->event)
1444 unless $self->editor->create_action_transit_copy($transit);
1446 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1448 $self->checkin_changed(1);
1452 sub attempt_checkin_hold_capture {
1454 my $copy = $self->copy;
1456 # See if this copy can fulfill any holds
1457 my ($hold) = $holdcode->find_nearest_permitted_hold(
1458 OpenSRF::AppSession->create('open-ils.storage'),
1459 $copy, $self->editor->requestor );
1462 $logger->debug("circulator: no potential permitted".
1463 "holds found for copy ".$copy->barcode);
1468 $logger->info("circulator: found permitted hold ".
1469 $hold->id . " for copy, capturing...");
1471 $hold->current_copy($copy->id);
1472 $hold->capture_time('now');
1474 # prevent DB errors caused by fetching
1475 # holds from storage, and updating through cstore
1476 $hold->clear_fulfillment_time;
1477 $hold->clear_fulfillment_staff;
1478 $hold->clear_fulfillment_lib;
1479 $hold->clear_expire_time;
1480 $hold->clear_cancel_time;
1481 $hold->clear_prev_check_time unless $hold->prev_check_time;
1483 $self->bail_on_events($self->editor->event)
1484 unless $self->editor->update_action_hold_request($hold);
1486 $self->checkin_changed(1);
1488 return 1 if $self->bail_out;
1490 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1492 # This hold was captured in the correct location
1493 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1494 $self->push_events(OpenILS::Event->new('SUCCESS'));
1496 $self->do_hold_notify($hold->id);
1500 # Hold needs to be picked up elsewhere. Build a hold
1501 # transit and route the item.
1502 $self->checkin_build_hold_transit();
1503 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1504 return 1 if $self->bail_out;
1506 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1509 # make sure we save the copy status
1514 sub do_hold_notify {
1515 my( $self, $holdid ) = @_;
1516 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1517 editor => $self->editor, hold_id => $holdid );
1519 if(!$notifier->event) {
1521 $logger->info("ciculator: attempt at sending hold notification for hold $holdid");
1523 my $stat = $notifier->send_email_notify;
1524 $logger->info("ciculator: hold notify succeeded for hold $holdid") if $stat eq '1';
1525 $logger->warn("ciculator: * hold notify failed for hold $holdid") if $stat ne '1';
1528 $logger->info("ciculator: Not sending hold notification since the patron has no email address");
1533 sub checkin_build_hold_transit {
1537 my $copy = $self->copy;
1538 my $hold = $self->hold;
1539 my $trans = Fieldmapper::action::hold_transit_copy->new;
1541 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1543 $trans->hold($hold->id);
1544 $trans->source($self->editor->requestor->ws_ou);
1545 $trans->dest($hold->pickup_lib);
1546 $trans->source_send_time("now");
1547 $trans->target_copy($copy->id);
1549 # when the copy gets to its destination, it will recover
1550 # this status - put it onto the holds shelf
1551 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1553 return $self->bail_on_events($self->editor->event)
1554 unless $self->editor->create_action_hold_transit_copy($trans);
1559 sub process_received_transit {
1561 my $copy = $self->copy;
1562 my $copyid = $self->copy->id;
1564 my $status_name = $U->copy_status($copy->status)->name;
1565 $logger->debug("circulator: attempting transit receive on ".
1566 "copy $copyid. Copy status is $status_name");
1568 my $transit = $self->transit;
1570 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1571 $logger->info("circulator: Fowarding transit on copy which is destined ".
1572 "for a different location. copy=$copyid,current ".
1573 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1575 return $self->bail_on_events(
1576 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1579 # The transit is received, set the receive time
1580 $transit->dest_recv_time('now');
1581 $self->bail_on_events($self->editor->event)
1582 unless $self->editor->update_action_transit_copy($transit);
1584 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1586 $logger->info("ciculator: Recovering original copy status in transit: ".$transit->copy_status);
1587 $copy->status( $transit->copy_status );
1588 $self->update_copy();
1589 return if $self->bail_out;
1593 $self->do_hold_notify($hold_transit->hold);
1598 OpenILS::Event->new(
1601 payload => { transit => $transit, holdtransit => $hold_transit } ));
1603 return $hold_transit;
1607 sub checkin_handle_circ {
1611 my $circ = $self->circ;
1612 my $copy = $self->copy;
1616 # backdate the circ if necessary
1617 if($self->backdate) {
1618 $self->checkin_handle_backdate;
1619 return if $self->bail_out;
1622 if(!$circ->stop_fines) {
1623 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1624 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1625 $circ->stop_fines_time('now') unless $self->backdate;
1626 $circ->stop_fines_time($self->backdate) if $self->backdate;
1629 # see if there are any fines owed on this circ. if not, close it
1630 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1631 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1633 # Set the checkin vars since we have the item
1634 $circ->checkin_time('now');
1635 $circ->checkin_staff($self->editor->requestor->id);
1636 $circ->checkin_lib($self->editor->requestor->ws_ou);
1638 my $circ_lib = (ref $self->copy->circ_lib) ?
1639 $self->copy->circ_lib->id : $self->copy->circ_lib;
1640 my $stat = $U->copy_status($self->copy->status)->id;
1642 # If the item is lost/missing and it needs to be sent home, don't
1643 # reshelve the copy, leave it lost/missing so the recipient will know
1644 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1645 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1646 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1649 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1654 return $self->bail_on_events($self->editor->event)
1655 unless $self->editor->update_action_circulation($circ);
1659 sub checkin_handle_backdate {
1662 my $bd = $self->backdate;
1663 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1664 $bd = "${bd}T23:59:59";
1666 my $bills = $self->editor->search_money_billing(
1668 billing_ts => { '>=' => $bd },
1669 xact => $self->circ->id,
1670 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1674 for my $bill (@$bills) {
1675 if( !$bill->voided or $bill->voided =~ /f/i ) {
1677 my $n = $bill->note || "";
1678 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1680 $self->bail_on_events($self->editor->event)
1681 unless $self->editor->update_money_billing($bill);
1688 # XXX Legacy version for Circ.pm support
1689 sub _checkin_handle_backdate {
1690 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1693 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1694 $bd = "${bd}T23:59:59";
1697 my $bills = $session->request(
1698 "open-ils.storage.direct.money.billing.search_where.atomic",
1699 billing_ts => { '>=' => $bd },
1701 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1705 for my $bill (@$bills) {
1707 my $n = $bill->note || "";
1708 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1709 my $s = $session->request(
1710 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1711 return $U->DB_UPDATE_FAILED($bill) unless $s;
1721 sub find_patron_from_copy {
1723 my $circs = $self->editor->search_action_circulation(
1724 { target_copy => $self->copy->id, checkin_time => undef });
1725 my $circ = $circs->[0];
1726 return unless $circ;
1727 my $u = $self->editor->retrieve_actor_user($circ->usr)
1728 or return $self->bail_on_events($self->editor->event);
1732 sub check_checkin_copy_status {
1734 my $copy = $self->copy;
1740 my $status = $U->copy_status($copy->status)->id;
1743 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1744 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1745 $status == OILS_COPY_STATUS_IN_PROCESS ||
1746 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1747 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1748 $status == OILS_COPY_STATUS_CATALOGING ||
1749 $status == OILS_COPY_STATUS_RESHELVING );
1751 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1752 if( $status == OILS_COPY_STATUS_LOST );
1754 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1755 if( $status == OILS_COPY_STATUS_MISSING );
1757 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1762 # --------------------------------------------------------------------------
1763 # On checkin, we need to return as many relevant objects as we can
1764 # --------------------------------------------------------------------------
1765 sub checkin_flesh_events {
1768 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1769 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1770 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1774 for my $evt (@{$self->events}) {
1777 $payload->{copy} = $U->unflesh_copy($self->copy);
1778 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1779 $payload->{circ} = $self->circ;
1780 $payload->{transit} = $self->transit;
1781 $payload->{hold} = $self->hold;
1783 $evt->{payload} = $payload;
1788 my( $self, $msg ) = @_;
1789 my $bc = ($self->copy) ? $self->copy->barcode :
1792 my $usr = ($self->patron) ? $self->patron->id : "";
1793 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1794 ", recipient=$usr, copy=$bc");
1800 $self->log_me("do_renew()");
1801 $self->is_renewal(1);
1803 unless( $self->is_renewal ) {
1804 return $self->bail_on_events($self->editor->events)
1805 unless $self->editor->allowed('RENEW_CIRC');
1808 # Make sure there is an open circ to renew that is not
1809 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1810 my $circ = $self->editor->search_action_circulation(
1811 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1814 $circ = $self->editor->search_action_circulation(
1816 target_copy => $self->copy->id,
1817 stop_fines => OILS_STOP_FINES_MAX_FINES,
1818 checkin_time => undef
1823 return $self->bail_on_events($self->editor->event) unless $circ;
1825 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1826 if $circ->renewal_remaining < 1;
1828 # -----------------------------------------------------------------
1830 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1833 $self->run_renew_permit;
1836 $self->do_checkin();
1837 return if $self->bail_out;
1839 unless( $self->permit_override ) {
1841 return if $self->bail_out;
1842 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1843 $self->remove_event('ITEM_NOT_CATALOGED');
1846 $self->override_events;
1847 return if $self->bail_out;
1850 $self->do_checkout();
1855 my( $self, $evt ) = @_;
1856 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1857 $logger->debug("circulator: removing event from list: $evt");
1858 my @events = @{$self->events};
1859 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1864 my( $self, $evt ) = @_;
1865 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1866 return grep { $_->{textcode} eq $evt } @{$self->events};
1871 sub run_renew_permit {
1873 my $runner = $self->script_runner;
1875 $runner->load($self->circ_permit_renew);
1876 my $result = $runner->run or
1877 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1878 my $events = $result->{events};
1880 $logger->activity("ciculator: circ_permit_renew for user ".
1881 $self->patron->id." returned events: @$events") if @$events;
1883 $self->push_events(OpenILS::Event->new($_)) for @$events;
1885 $logger->debug("circulator: re-creating script runner to be safe");
1886 $self->mk_script_runner;