1 package OpenILS::Application::Circ::Circulate;
2 use strict; use warnings;
3 use base 'OpenSRF::Application';
4 use OpenSRF::EX qw(:try);
5 use OpenSRF::Utils::SettingsClient;
6 use OpenSRF::Utils::Logger qw(:logger);
7 use OpenILS::Const qw/:const/;
15 my $conf = OpenSRF::Utils::SettingsClient->new;
16 my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
17 my @pfx = ( @pfx2, "scripts" );
19 my $p = $conf->config_value( @pfx, 'circ_permit_patron' );
20 my $c = $conf->config_value( @pfx, 'circ_permit_copy' );
21 my $d = $conf->config_value( @pfx, 'circ_duration' );
22 my $f = $conf->config_value( @pfx, 'circ_recurring_fines' );
23 my $m = $conf->config_value( @pfx, 'circ_max_fines' );
24 my $pr = $conf->config_value( @pfx, 'circ_permit_renew' );
25 my $lb = $conf->config_value( @pfx2, 'script_path' );
27 $logger->error( "Missing circ script(s)" )
28 unless( $p and $c and $d and $f and $m and $pr );
30 $scripts{circ_permit_patron} = $p;
31 $scripts{circ_permit_copy} = $c;
32 $scripts{circ_duration} = $d;
33 $scripts{circ_recurring_fines}= $f;
34 $scripts{circ_max_fines} = $m;
35 $scripts{circ_permit_renew} = $pr;
37 $lb = [ $lb ] unless ref($lb);
41 "circulator: Loaded rules scripts for circ: " .
42 "circ permit patron = $p, ".
43 "circ permit copy = $c, ".
44 "circ duration = $d, ".
45 "circ recurring fines = $f, " .
46 "circ max fines = $m, ".
47 "circ renew permit = $pr. ".
52 __PACKAGE__->register_method(
53 method => "run_method",
54 api_name => "open-ils.circ.checkout.permit",
56 Determines if the given checkout can occur
57 @param authtoken The login session key
58 @param params A trailing hash of named params including
59 barcode : The copy barcode,
60 patron : The patron the checkout is occurring for,
61 renew : true or false - whether or not this is a renewal
62 @return The event that occurred during the permit check.
66 __PACKAGE__->register_method (
67 method => 'run_method',
68 api_name => 'open-ils.circ.checkout.permit.override',
69 signature => q/@see open-ils.circ.checkout.permit/,
73 __PACKAGE__->register_method(
74 method => "run_method",
75 api_name => "open-ils.circ.checkout",
78 @param authtoken The login session key
79 @param params A named hash of params including:
81 barcode If no copy is provided, the copy is retrieved via barcode
82 copyid If no copy or barcode is provide, the copy id will be use
83 patron The patron's id
84 noncat True if this is a circulation for a non-cataloted item
85 noncat_type The non-cataloged type id
86 noncat_circ_lib The location for the noncat circ.
87 precat The item has yet to be cataloged
88 dummy_title The temporary title of the pre-cataloded item
89 dummy_author The temporary authr of the pre-cataloded item
90 Default is the home org of the staff member
91 @return The SUCCESS event on success, any other event depending on the error
94 __PACKAGE__->register_method(
95 method => "run_method",
96 api_name => "open-ils.circ.checkin",
99 Generic super-method for handling all copies
100 @param authtoken The login session key
101 @param params Hash of named parameters including:
102 barcode - The copy barcode
103 force - If true, copies in bad statuses will be checked in and give good statuses
108 __PACKAGE__->register_method(
109 method => "run_method",
110 api_name => "open-ils.circ.checkin.override",
111 signature => q/@see open-ils.circ.checkin/
114 __PACKAGE__->register_method(
115 method => "run_method",
116 api_name => "open-ils.circ.renew.override",
117 signature => q/@see open-ils.circ.renew/,
121 __PACKAGE__->register_method(
122 method => "run_method",
123 api_name => "open-ils.circ.renew",
124 notes => <<" NOTES");
125 PARAMS( authtoken, circ => circ_id );
126 open-ils.circ.renew(login_session, circ_object);
127 Renews the provided circulation. login_session is the requestor of the
128 renewal and if the logged in user is not the same as circ->usr, then
129 the logged in user must have RENEW_CIRC permissions.
132 __PACKAGE__->register_method(
133 method => "run_method",
134 api_name => "open-ils.circ.checkout.full");
135 __PACKAGE__->register_method(
136 method => "run_method",
137 api_name => "open-ils.circ.checkout.full.override");
142 my( $self, $conn, $auth, $args ) = @_;
143 translate_legacy_args($args);
144 my $api = $self->api_name;
147 OpenILS::Application::Circ::Circulator->new($auth, %$args);
149 return circ_events($circulator) if $circulator->bail_out;
151 # --------------------------------------------------------------------------
152 # Go ahead and load the script runner to make sure we have all
153 # of the objects we need
154 # --------------------------------------------------------------------------
155 $circulator->is_renewal(1) if $api =~ /renew/;
156 $circulator->is_checkin(1) if $api =~ /checkin/;
157 $circulator->mk_script_runner;
158 return circ_events($circulator) if $circulator->bail_out;
160 $circulator->circ_permit_patron($scripts{circ_permit_patron});
161 $circulator->circ_permit_copy($scripts{circ_permit_copy});
162 $circulator->circ_duration($scripts{circ_duration});
163 $circulator->circ_permit_renew($scripts{circ_permit_renew});
165 $circulator->override(1) if $api =~ /override/o;
167 if( $api =~ /checkout\.permit/ ) {
168 $circulator->do_permit();
170 } elsif( $api =~ /checkout.full/ ) {
172 $circulator->do_permit();
173 unless( $circulator->bail_out ) {
174 $circulator->events([]);
175 $circulator->do_checkout();
178 } elsif( $api =~ /checkout/ ) {
179 $circulator->do_checkout();
181 } elsif( $api =~ /checkin/ ) {
182 $circulator->do_checkin();
184 } elsif( $api =~ /renew/ ) {
185 $circulator->is_renewal(1);
186 $circulator->do_renew();
189 if( $circulator->bail_out ) {
192 # make sure no success event accidentally slip in
194 [ grep { $_->{textcode} ne 'SUCCESS' } @{$circulator->events} ]);
195 my @e = @{$circulator->events};
196 push( @ee, $_->{textcode} ) for @e;
197 $logger->info("circulator: bailing out with events: @ee");
198 $circulator->editor->rollback;
201 $circulator->editor->commit;
204 $circulator->script_runner->cleanup;
206 return circ_events($circulator);
211 my @e = @{$circ->events};
212 return (@e == 1) ? $e[0] : \@e;
217 sub translate_legacy_args {
220 if( $$args{barcode} ) {
221 $$args{copy_barcode} = $$args{barcode};
222 delete $$args{barcode};
225 if( $$args{copyid} ) {
226 $$args{copy_id} = $$args{copyid};
227 delete $$args{copyid};
230 if( $$args{patronid} ) {
231 $$args{patron_id} = $$args{patronid};
232 delete $$args{patronid};
235 if( $$args{patron} and !ref($$args{patron}) ) {
236 $$args{patron_id} = $$args{patron};
237 delete $$args{patron};
241 if( $$args{noncat} ) {
242 $$args{is_noncat} = $$args{noncat};
243 delete $$args{noncat};
246 if( $$args{precat} ) {
247 $$args{is_precat} = $$args{precat};
248 delete $$args{precat};
254 # --------------------------------------------------------------------------
255 # This package actually manages all of the circulation logic
256 # --------------------------------------------------------------------------
257 package OpenILS::Application::Circ::Circulator;
258 use strict; use warnings;
259 use vars q/$AUTOLOAD/;
261 use OpenILS::Utils::Fieldmapper;
262 use OpenSRF::Utils::Cache;
263 use Digest::MD5 qw(md5_hex);
264 use DateTime::Format::ISO8601;
265 use OpenILS::Utils::PermitHold;
266 use OpenSRF::Utils qw/:datetime/;
267 use OpenSRF::Utils::SettingsClient;
268 use OpenILS::Application::Circ::Holds;
269 use OpenILS::Application::Circ::Transit;
270 use OpenSRF::Utils::Logger qw(:logger);
271 use OpenILS::Utils::CStoreEditor qw/:funcs/;
272 use OpenILS::Application::Circ::ScriptBuilder;
273 use OpenILS::Const qw/:const/;
275 my $U = "OpenILS::Application::AppUtils";
276 my $holdcode = "OpenILS::Application::Circ::Holds";
277 my $transcode = "OpenILS::Application::Circ::Transit";
282 # --------------------------------------------------------------------------
283 # Add a pile of automagic getter/setter methods
284 # --------------------------------------------------------------------------
285 my @AUTOLOAD_FIELDS = qw/
325 recurring_fines_level
342 my $type = ref($self) or die "$self is not an object";
344 my $name = $AUTOLOAD;
347 unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) {
348 $logger->error("circulator: $type: invalid autoload field: $name");
349 die "$type: invalid autoload field: $name\n"
354 *{"${type}::${name}"} = sub {
357 $s->{$name} = $v if defined $v;
361 return $self->$name($data);
366 my( $class, $auth, %args ) = @_;
367 $class = ref($class) || $class;
368 my $self = bless( {}, $class );
372 new_editor(xact => 1, authtoken => $auth) );
374 unless( $self->editor->checkauth ) {
375 $self->bail_on_events($self->editor->event);
379 $self->cache_handle(OpenSRF::Utils::Cache->new('global'));
381 $self->$_($args{$_}) for keys %args;
384 ($self->circ_lib) ? $self->circ_lib : $self->editor->requestor->ws_ou);
390 # --------------------------------------------------------------------------
391 # True if we should discontinue processing
392 # --------------------------------------------------------------------------
394 my( $self, $bool ) = @_;
395 if( defined $bool ) {
396 $logger->info("circulator: BAILING OUT") if $bool;
397 $self->{bail_out} = $bool;
399 return $self->{bail_out};
404 my( $self, @evts ) = @_;
407 $logger->info("circulator: pushing event ".$e->{textcode});
408 push( @{$self->events}, $e ) unless
409 grep { $_->{textcode} eq $e->{textcode} } @{$self->events};
415 my $key = md5_hex( time() . rand() . "$$" );
416 $self->cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
417 return $self->permit_key($key);
420 sub check_permit_key {
422 my $key = $self->permit_key;
423 return 0 unless $key;
424 my $k = "oils_permit_key_$key";
425 my $one = $self->cache_handle->get_cache($k);
426 $self->cache_handle->delete_cache($k);
427 return ($one) ? 1 : 0;
431 # --------------------------------------------------------------------------
432 # This builds the script runner environment and fetches most of the
434 # --------------------------------------------------------------------------
435 sub mk_script_runner {
439 $args->{ignore_user_status} = 1 if $self->is_checkin;
442 qw/copy copy_barcode copy_id patron
443 patron_id patron_barcode volume title editor/;
445 # Translate our objects into the ScriptBuilder args hash
446 $$args{$_} = $self->$_() for @fields;
447 $$args{fetch_patron_by_circ_copy} = 1;
448 $$args{fetch_patron_circ_info} = 1;
450 # This fetches most of the objects we need
451 $self->script_runner(
452 OpenILS::Application::Circ::ScriptBuilder->build($args));
454 # Now we translate the ScriptBuilder objects back into self
455 $self->$_($$args{$_}) for @fields;
457 my @evts = @{$args->{_events}} if $args->{_events};
459 $logger->debug("circulator: script builder returned events: : @evts") if @evts;
463 # Anything besides ASSET_COPY_NOT_FOUND will stop processing
464 if(!$self->is_noncat and
466 $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') {
470 my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts;
471 return $self->bail_on_events(@e);
475 $self->is_precat(1) if $self->copy and $self->copy->call_number == OILS_PRECAT_CALL_NUMBER;
477 # We can't renew if there is no copy
478 return $self->bail_on_events(@evts) if
479 $self->is_renewal and !$self->copy;
481 # Set some circ-specific flags in the script environment
482 my $evt = "environment";
483 $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef);
485 if( $self->is_noncat ) {
486 $self->script_runner->insert("$evt.isNonCat", 1);
487 $self->script_runner->insert("$evt.nonCatType", $self->noncat_type);
490 if( $self->is_precat ) {
491 $self->script_runner->insert("environment.isPrecat", 1, 1);
494 $self->script_runner->add_path( $_ ) for @$script_libs;
502 # --------------------------------------------------------------------------
503 # Does the circ permit work
504 # --------------------------------------------------------------------------
508 $self->log_me("do_permit()");
510 unless( $self->editor->requestor->id == $self->patron->id ) {
511 return $self->bail_on_events($self->editor->event)
512 unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') );
516 $self->check_captured_holds();
517 $self->do_copy_checks();
518 return if $self->bail_out;
519 $self->run_patron_permit_scripts();
520 $self->run_copy_permit_scripts()
521 unless $self->is_precat or $self->is_noncat;
522 $self->override_events() unless $self->is_renewal;
523 return if $self->bail_out;
525 if( $self->is_precat ) {
528 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key));
529 return $self->bail_out(1) unless $self->is_renewal;
535 payload => $self->mk_permit_key));
539 sub check_captured_holds {
541 my $copy = $self->copy;
542 my $patron = $self->patron;
544 return undef unless $copy;
546 my $s = $U->copy_status($copy->status)->id;
547 return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF;
548 $logger->info("circulator: copy is on holds shelf, searching for the correct hold");
550 # Item is on the holds shelf, make sure it's going to the right person
551 my $holds = $self->editor->search_action_hold_request(
554 current_copy => $copy->id ,
555 capture_time => { '!=' => undef },
556 cancel_time => undef,
557 fulfillment_time => undef
563 if( $holds and $$holds[0] ) {
564 return undef if $$holds[0]->usr == $patron->id;
567 $logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
569 $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF'));
575 my $copy = $self->copy;
578 my $stat = $U->copy_status($copy->status)->id;
580 # We cannot check out a copy if it is in-transit
581 if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) {
582 return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
585 $self->handle_claims_returned();
586 return if $self->bail_out;
588 # no claims returned circ was found, check if there is any open circ
589 unless( $self->is_renewal ) {
590 my $circs = $self->editor->search_action_circulation(
591 { target_copy => $copy->id, checkin_time => undef }
594 return $self->bail_on_events(
595 OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
600 sub send_penalty_request {
602 my $ses = OpenSRF::AppSession->create('open-ils.penalty');
603 $self->penalty_request(
605 'open-ils.penalty.patron_penalty.calculate',
607 authtoken => $self->editor->authtoken,
608 patron => $self->patron } ) );
611 sub gather_penalty_request {
613 return [] unless $self->penalty_request;
614 my $data = $self->penalty_request->recv;
616 $data = $data->content;
617 return $data->{fatal_penalties};
619 $logger->error("circulator: penalty request returned no data");
623 # ---------------------------------------------------------------------
624 # This pushes any patron-related events into the list but does not
625 # set bail_out for any events
626 # ---------------------------------------------------------------------
627 sub run_patron_permit_scripts {
629 my $runner = $self->script_runner;
630 my $patronid = $self->patron->id;
632 $self->send_penalty_request();
634 # ---------------------------------------------------------------------
635 # Now run the patron permit script
636 # ---------------------------------------------------------------------
637 $runner->load($self->circ_permit_patron);
638 my $result = $runner->run or
639 throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
641 my $patron_events = $result->{events};
644 my $penalties = $self->gather_penalty_request();
645 push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
647 $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
649 $self->push_events(@allevents);
653 sub run_copy_permit_scripts {
655 my $copy = $self->copy || return;
656 my $runner = $self->script_runner;
658 # ---------------------------------------------------------------------
659 # Capture all of the copy permit events
660 # ---------------------------------------------------------------------
661 $runner->load($self->circ_permit_copy);
662 my $result = $runner->run or
663 throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
664 my $copy_events = $result->{events};
666 # ---------------------------------------------------------------------
667 # Now collect all of the events together
668 # ---------------------------------------------------------------------
670 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
672 # See if this copy has an alert message
673 my $ae = $self->check_copy_alert();
674 push( @allevents, $ae ) if $ae;
676 # uniquify the events
677 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
678 @allevents = values %hash;
681 $_->{payload} = $copy if
682 ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
685 $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
687 $self->push_events(@allevents);
691 sub check_copy_alert {
693 return OpenILS::Event->new(
694 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
695 if $self->copy and $self->copy->alert_message;
701 # --------------------------------------------------------------------------
702 # If the call is overriding and has permissions to override every collected
703 # event, the are cleared. Any event that the caller does not have
704 # permission to override, will be left in the event list and bail_out will
706 # XXX We need code in here to cancel any holds/transits on copies
707 # that are being force-checked out
708 # --------------------------------------------------------------------------
709 sub override_events {
711 my @events = @{$self->events};
712 return unless @events;
714 if(!$self->override) {
715 return $self->bail_out(1)
716 if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
721 for my $e (@events) {
722 my $tc = $e->{textcode};
723 next if $tc eq 'SUCCESS';
724 my $ov = "$tc.override";
725 $logger->info("circulator: attempting to override event: $ov");
727 return $self->bail_on_events($self->editor->event)
728 unless( $self->editor->allowed($ov) );
733 # --------------------------------------------------------------------------
734 # If there is an open claimsreturn circ on the requested copy, close the
735 # circ if overriding, otherwise bail out
736 # --------------------------------------------------------------------------
737 sub handle_claims_returned {
739 my $copy = $self->copy;
741 my $CR = $self->editor->search_action_circulation(
743 target_copy => $copy->id,
744 stop_fines => OILS_STOP_FINES_CLAIMSRETURNED,
745 checkin_time => undef,
749 return unless ($CR = $CR->[0]);
753 # - If the caller has set the override flag, we will check the item in
754 if($self->override) {
756 $CR->checkin_time('now');
757 $CR->checkin_lib($self->editor->requestor->ws_ou);
758 $CR->checkin_staff($self->editor->requestor->id);
760 $evt = $self->editor->event
761 unless $self->editor->update_action_circulation($CR);
764 $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
767 $self->bail_on_events($evt) if $evt;
772 # --------------------------------------------------------------------------
773 # This performs the checkout
774 # --------------------------------------------------------------------------
778 $self->log_me("do_checkout()");
780 # make sure perms are good if this isn't a renewal
781 unless( $self->is_renewal ) {
782 return $self->bail_on_events($self->editor->event)
783 unless( $self->editor->allowed('COPY_CHECKOUT') );
786 # verify the permit key
787 unless( $self->check_permit_key ) {
788 if( $self->permit_override ) {
789 return $self->bail_on_events($self->editor->event)
790 unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
792 return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
796 # if this is a non-cataloged circ, build the circ and finish
797 if( $self->is_noncat ) {
798 $self->checkout_noncat;
800 OpenILS::Event->new('SUCCESS',
801 payload => { noncat_circ => $self->circ }));
805 if( $self->is_precat ) {
806 $self->script_runner->insert("environment.isPrecat", 1, 1);
807 $self->make_precat_copy;
808 return if $self->bail_out;
810 } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
811 return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
814 $self->do_copy_checks;
815 return if $self->bail_out;
817 $self->run_checkout_scripts();
818 return if $self->bail_out;
820 $self->build_checkout_circ_object();
821 return if $self->bail_out;
823 $self->apply_modified_due_date();
824 return if $self->bail_out;
826 return $self->bail_on_events($self->editor->event)
827 unless $self->editor->create_action_circulation($self->circ);
829 $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
831 return if $self->bail_out;
833 $self->handle_checkout_holds();
834 return if $self->bail_out;
836 # ------------------------------------------------------------------------------
837 # Update the patron penalty info in the DB
838 # ------------------------------------------------------------------------------
839 $U->update_patron_penalties(
840 authtoken => $self->editor->authtoken,
841 patron => $self->patron,
845 my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
847 OpenILS::Event->new('SUCCESS',
849 copy => $U->unflesh_copy($self->copy),
852 holds_fulfilled => $self->fulfilled_holds,
860 my $copy = $self->copy;
862 my $stat = $copy->status if ref $copy->status;
863 my $loc = $copy->location if ref $copy->location;
864 my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
866 $copy->status($stat->id) if $stat;
867 $copy->location($loc->id) if $loc;
868 $copy->circ_lib($circ_lib->id) if $circ_lib;
869 $copy->editor($self->editor->requestor->id);
870 $copy->edit_date('now');
871 $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect;
873 return $self->bail_on_events($self->editor->event)
874 unless $self->editor->update_asset_copy($self->copy);
876 $copy->status($U->copy_status($copy->status));
877 $copy->location($loc) if $loc;
878 $copy->circ_lib($circ_lib) if $circ_lib;
883 my( $self, @evts ) = @_;
884 $self->push_events(@evts);
888 sub handle_checkout_holds {
891 my $copy = $self->copy;
892 my $patron = $self->patron;
894 my $holds = $self->editor->search_action_hold_request(
896 current_copy => $copy->id ,
897 cancel_time => undef,
898 fulfillment_time => undef
904 # XXX We should only fulfill one hold here...
905 # XXX If a hold was transited to the user who is checking out
906 # the item, we need to make sure that hold is what's grabbed
909 # for now, just sort by id to get what should be the oldest hold
910 $holds = [ sort { $a->id <=> $b->id } @$holds ];
911 my @myholds = grep { $_->usr eq $patron->id } @$holds;
912 my @altholds = grep { $_->usr ne $patron->id } @$holds;
915 my $hold = $myholds[0];
917 $logger->debug("circulator: related hold found in checkout: " . $hold->id );
919 # if the hold was never officially captured, capture it.
920 $hold->capture_time('now') unless $hold->capture_time;
922 # just make sure it's set correctly
923 $hold->current_copy($copy->id);
925 $hold->fulfillment_time('now');
926 $hold->fulfillment_staff($self->editor->requestor->id);
927 $hold->fulfillment_lib($self->editor->requestor->ws_ou);
929 return $self->bail_on_events($self->editor->event)
930 unless $self->editor->update_action_hold_request($hold);
932 $holdcode->delete_hold_copy_maps($self->editor, $hold->id);
934 push( @fulfilled, $hold->id );
937 # If there are any holds placed for other users that point to this copy,
938 # then we need to un-target those holds so the targeter can pick a new copy
941 $logger->info("circulator: un-targeting hold ".$_->id.
942 " because copy ".$copy->id." is getting checked out");
944 # - make the targeter process this hold at next run
945 $_->clear_prev_check_time;
947 # - clear out the targetted copy
948 $_->clear_current_copy;
949 $_->clear_capture_time;
951 return $self->bail_on_event($self->editor->event)
952 unless $self->editor->update_action_hold_request($_);
956 $self->fulfilled_holds(\@fulfilled);
961 sub run_checkout_scripts {
965 my $runner = $self->script_runner;
966 $runner->load($self->circ_duration);
968 my $result = $runner->run or
969 throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
971 my $duration = $result->{durationRule};
972 my $recurring = $result->{recurringFinesRule};
973 my $max_fine = $result->{maxFine};
975 if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) {
977 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
978 return $self->bail_on_events($evt) if $evt;
980 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
981 return $self->bail_on_events($evt) if $evt;
983 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
984 return $self->bail_on_events($evt) if $evt;
988 # The item circulates with an unlimited duration
994 $self->duration_rule($duration);
995 $self->recurring_fines_rule($recurring);
996 $self->max_fine_rule($max_fine);
1000 sub build_checkout_circ_object {
1003 my $circ = Fieldmapper::action::circulation->new;
1004 my $duration = $self->duration_rule;
1005 my $max = $self->max_fine_rule;
1006 my $recurring = $self->recurring_fines_rule;
1007 my $copy = $self->copy;
1008 my $patron = $self->patron;
1012 my $dname = $duration->name;
1013 my $mname = $max->name;
1014 my $rname = $recurring->name;
1016 $logger->debug("circulator: building circulation ".
1017 "with duration=$dname, maxfine=$mname, recurring=$rname");
1019 $circ->duration( $duration->shrt )
1020 if $copy->loan_duration == OILS_CIRC_DURATION_SHORT;
1021 $circ->duration( $duration->normal )
1022 if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL;
1023 $circ->duration( $duration->extended )
1024 if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED;
1026 $circ->recuring_fine( $recurring->low )
1027 if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW;
1028 $circ->recuring_fine( $recurring->normal )
1029 if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL;
1030 $circ->recuring_fine( $recurring->high )
1031 if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH;
1033 $circ->duration_rule( $duration->name );
1034 $circ->recuring_fine_rule( $recurring->name );
1035 $circ->max_fine_rule( $max->name );
1036 $circ->max_fine( $max->amount );
1038 $circ->fine_interval($recurring->recurance_interval);
1039 $circ->renewal_remaining( $duration->max_renewals );
1043 $logger->info("circulator: copy found with an unlimited circ duration");
1044 $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION);
1045 $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1046 $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION);
1047 $circ->renewal_remaining(0);
1050 $circ->target_copy( $copy->id );
1051 $circ->usr( $patron->id );
1052 $circ->circ_lib( $self->circ_lib );
1054 if( $self->is_renewal ) {
1055 $circ->opac_renewal(1);
1056 $circ->renewal_remaining($self->renewal_remaining);
1057 $circ->circ_staff($self->editor->requestor->id);
1060 # if the user provided an overiding checkout time,
1061 # (e.g. the checkout really happened several hours ago), then
1062 # we apply that here. Does this need a perm??
1063 $circ->xact_start(clense_ISO8601($self->checkout_time))
1064 if $self->checkout_time;
1066 # if a patron is renewing, 'requestor' will be the patron
1067 $circ->circ_staff($self->editor->requestor->id);
1068 $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
1074 sub apply_modified_due_date {
1076 my $circ = $self->circ;
1077 my $copy = $self->copy;
1079 if( $self->due_date ) {
1081 return $self->bail_on_events($self->editor->event)
1082 unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
1084 $circ->due_date(clense_ISO8601($self->due_date));
1088 # if the due_date lands on a day when the location is closed
1089 return unless $copy and $circ->due_date;
1091 my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
1093 $logger->info("circulator: circ searching for closed date overlap on lib $org".
1094 " with an item due date of ".$circ->due_date );
1096 my $dateinfo = $U->storagereq(
1097 'open-ils.storage.actor.org_unit.closed_date.overlap',
1098 $org, $circ->due_date );
1101 $logger->info("circulator: $dateinfo : circ due data / close date overlap found : due_date=".
1102 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1104 # XXX make the behavior more dynamic
1105 # for now, we just push the due date to after the close date
1106 $circ->due_date($dateinfo->{end});
1113 sub create_due_date {
1114 my( $self, $duration ) = @_;
1115 my ($sec,$min,$hour,$mday,$mon,$year) =
1116 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1117 $year += 1900; $mon += 1;
1118 my $due_date = sprintf(
1119 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1120 $year, $mon, $mday, $hour, $min, $sec);
1126 sub make_precat_copy {
1128 my $copy = $self->copy;
1131 $logger->debug("ciculator: Pre-cat copy already exists in checkout: ID=" . $copy->id);
1133 $copy->editor($self->editor->requestor->id);
1134 $copy->edit_date('now');
1135 $copy->dummy_title($self->dummy_title);
1136 $copy->dummy_author($self->dummy_author);
1138 $self->update_copy();
1142 $logger->info("circulator: Creating a new precataloged ".
1143 "copy in checkout with barcode " . $self->copy_barcode);
1145 $copy = Fieldmapper::asset::copy->new;
1146 $copy->circ_lib($self->circ_lib);
1147 $copy->creator($self->editor->requestor->id);
1148 $copy->editor($self->editor->requestor->id);
1149 $copy->barcode($self->copy_barcode);
1150 $copy->call_number(OILS_PRECAT_CALL_NUMBER);
1151 $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
1152 $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
1154 $copy->dummy_title($self->dummy_title || "");
1155 $copy->dummy_author($self->dummy_author || "");
1157 unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
1159 $self->push_events($self->editor->event);
1163 # this is a little bit of a hack, but we need to
1164 # get the copy into the script runner
1165 $self->script_runner->insert("environment.copy", $copy, 1);
1169 sub checkout_noncat {
1175 my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
1176 my $count = $self->noncat_count || 1;
1177 my $cotime = clense_ISO8601($self->checkout_time) || "";
1179 $logger->info("ciculator: circ creating $count noncat circs with checkout time $cotime");
1183 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1184 $self->editor->requestor->id,
1192 $self->push_events($evt);
1203 $self->log_me("do_checkin()");
1206 return $self->bail_on_events(
1207 OpenILS::Event->new('ASSET_COPY_NOT_FOUND'))
1210 if( $self->checkin_check_holds_shelf() ) {
1211 $self->bail_on_events(OpenILS::Event->new('NO_CHANGE'));
1212 $self->hold($U->fetch_open_hold_by_copy($self->copy->id));
1213 $self->checkin_flesh_events;
1217 unless( $self->is_renewal ) {
1218 return $self->bail_on_events($self->editor->event)
1219 unless $self->editor->allowed('COPY_CHECKIN');
1222 $self->push_events($self->check_copy_alert());
1223 $self->push_events($self->check_checkin_copy_status());
1225 # the renew code will have already found our circulation object
1226 unless( $self->is_renewal and $self->circ ) {
1228 $self->editor->search_action_circulation(
1229 { target_copy => $self->copy->id, checkin_time => undef })->[0]);
1232 # if the circ is marked as 'claims returned', add the event to the list
1233 $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
1234 if ($self->circ and $self->circ->stop_fines
1235 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
1237 # handle the overridable events
1238 $self->override_events unless $self->is_renewal;
1239 return if $self->bail_out;
1243 $self->editor->search_action_transit_copy(
1244 { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);
1248 $self->checkin_handle_circ;
1249 return if $self->bail_out;
1250 $self->checkin_changed(1);
1252 } elsif( $self->transit ) {
1253 my $hold_transit = $self->process_received_transit;
1254 $self->checkin_changed(1);
1256 if( $self->bail_out ) {
1257 $self->checkin_flesh_events;
1261 if( my $e = $self->check_checkin_copy_status() ) {
1262 # If the original copy status is special, alert the caller
1263 my $ev = $self->events;
1264 $self->events([$e]);
1265 $self->override_events;
1266 return if $self->bail_out;
1270 if( $hold_transit or
1271 $U->copy_status($self->copy->status)->id
1272 == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
1275 $self->editor->retrieve_action_hold_request($hold_transit->hold) :
1276 $U->fetch_open_hold_by_copy($self->copy->id)
1279 $self->checkin_flesh_events;
1284 if( $self->is_renewal ) {
1285 $self->push_events(OpenILS::Event->new('SUCCESS'));
1289 # ------------------------------------------------------------------------------
1290 # Circulations and transits are now closed where necessary. Now go on to see if
1291 # this copy can fulfill a hold or needs to be routed to a different location
1292 # ------------------------------------------------------------------------------
1294 if( !$self->remote_hold and $self->attempt_checkin_hold_capture() ) {
1295 return if $self->bail_out;
1297 } else { # not needed for a hold
1300 my $circ_lib = (ref $self->copy->circ_lib) ?
1301 $self->copy->circ_lib->id : $self->copy->circ_lib;
1303 if( $self->remote_hold ) {
1304 $circ_lib = $self->remote_hold->pickup_lib;
1305 $logger->warn("circulator: Copy ".$self->copy->barcode.
1306 " is on a remote hold's shelf, sending to $circ_lib");
1309 $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
1311 if( $circ_lib == $self->editor->requestor->ws_ou ) {
1313 $self->checkin_handle_precat();
1314 return if $self->bail_out;
1318 my $bc = $self->copy->barcode;
1319 $logger->info("circulator: copy $bc at a remote lib - sending home");
1320 $self->checkin_build_copy_transit();
1321 return if $self->bail_out;
1322 $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
1326 $self->reshelve_copy;
1327 return if $self->bail_out;
1329 unless($self->checkin_changed) {
1331 $self->push_events(OpenILS::Event->new('NO_CHANGE'));
1332 my $stat = $U->copy_status($self->copy->status)->id;
1334 $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
1335 if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF );
1336 $self->bail_out(1); # no need to commit anything
1339 $self->push_events(OpenILS::Event->new('SUCCESS'))
1340 unless @{$self->events};
1343 $self->checkin_flesh_events;
1349 my $force = $self->force || shift;
1350 my $copy = $self->copy;
1352 my $stat = $U->copy_status($copy->status)->id;
1355 $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and
1356 $stat != OILS_COPY_STATUS_CATALOGING and
1357 $stat != OILS_COPY_STATUS_IN_TRANSIT and
1358 $stat != OILS_COPY_STATUS_RESHELVING )) {
1360 $copy->status( OILS_COPY_STATUS_RESHELVING );
1362 $self->checkin_changed(1);
1367 # Returns true if the item is at the current location
1368 # because it was transited there for a hold and the
1369 # hold has not been fulfilled
1370 sub checkin_check_holds_shelf {
1372 return 0 unless $self->copy;
1375 $U->copy_status($self->copy->status)->id ==
1376 OILS_COPY_STATUS_ON_HOLDS_SHELF;
1378 # find the hold that put us on the holds shelf
1379 my $holds = $self->editor->search_action_hold_request(
1381 current_copy => $self->copy->id,
1382 capture_time => { '!=' => undef },
1383 fulfillment_time => undef,
1384 cancel_time => undef,
1388 return 0 unless @$holds;
1390 my $hold = $$holds[0];
1392 $logger->info("circulator: we found a captured, un-fulfilled hold [".
1393 $hold->id. "] for copy ".$self->copy->barcode);
1395 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1396 $logger->info("circulator: hold is for here .. we're done: ".$self->copy->barcode);
1400 $logger->info("circulator: hold is not for here..");
1401 $self->remote_hold($hold);
1406 sub checkin_handle_precat {
1408 my $copy = $self->copy;
1410 if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) {
1411 $copy->status(OILS_COPY_STATUS_CATALOGING);
1412 $self->update_copy();
1413 $self->checkin_changed(1);
1414 $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
1419 sub checkin_build_copy_transit {
1421 my $copy = $self->copy;
1422 my $transit = Fieldmapper::action::transit_copy->new;
1424 $transit->source($self->editor->requestor->ws_ou);
1425 $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
1426 $transit->target_copy($copy->id);
1427 $transit->source_send_time('now');
1428 $transit->copy_status( $U->copy_status($copy->status)->id );
1430 $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
1432 return $self->bail_on_events($self->editor->event)
1433 unless $self->editor->create_action_transit_copy($transit);
1435 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1437 $self->checkin_changed(1);
1441 sub attempt_checkin_hold_capture {
1443 my $copy = $self->copy;
1445 # See if this copy can fulfill any holds
1446 my ($hold) = $holdcode->find_nearest_permitted_hold(
1447 OpenSRF::AppSession->create('open-ils.storage'),
1448 $copy, $self->editor->requestor );
1451 $logger->debug("circulator: no potential permitted".
1452 "holds found for copy ".$copy->barcode);
1457 $logger->info("circulator: found permitted hold ".
1458 $hold->id . " for copy, capturing...");
1460 $hold->current_copy($copy->id);
1461 $hold->capture_time('now');
1463 # prevent DB errors caused by fetching
1464 # holds from storage, and updating through cstore
1465 $hold->clear_fulfillment_time;
1466 $hold->clear_fulfillment_staff;
1467 $hold->clear_fulfillment_lib;
1468 $hold->clear_expire_time;
1469 $hold->clear_cancel_time;
1470 $hold->clear_prev_check_time unless $hold->prev_check_time;
1472 $self->bail_on_events($self->editor->event)
1473 unless $self->editor->update_action_hold_request($hold);
1475 $self->checkin_changed(1);
1477 return 1 if $self->bail_out;
1479 if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
1481 # This hold was captured in the correct location
1482 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1483 $self->push_events(OpenILS::Event->new('SUCCESS'));
1485 $self->do_hold_notify($hold->id);
1489 # Hold needs to be picked up elsewhere. Build a hold
1490 # transit and route the item.
1491 $self->checkin_build_hold_transit();
1492 $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
1493 return 1 if $self->bail_out;
1495 OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
1498 # make sure we save the copy status
1503 sub do_hold_notify {
1504 my( $self, $holdid ) = @_;
1505 my $notifier = OpenILS::Application::Circ::HoldNotify->new(
1506 editor => $self->editor, hold_id => $holdid );
1508 if(!$notifier->event) {
1510 $logger->info("ciculator: attempt at sending hold notification for hold $holdid");
1512 my $stat = $notifier->send_email_notify;
1513 $logger->info("ciculator: hold notify succeeded for hold $holdid") if $stat eq '1';
1514 $logger->warn("ciculator: * hold notify failed for hold $holdid") if $stat ne '1';
1517 $logger->info("ciculator: Not sending hold notification since the patron has no email address");
1522 sub checkin_build_hold_transit {
1526 my $copy = $self->copy;
1527 my $hold = $self->hold;
1528 my $trans = Fieldmapper::action::hold_transit_copy->new;
1530 $logger->debug("circulator: building hold transit for ".$copy->barcode);
1532 $trans->hold($hold->id);
1533 $trans->source($self->editor->requestor->ws_ou);
1534 $trans->dest($hold->pickup_lib);
1535 $trans->source_send_time("now");
1536 $trans->target_copy($copy->id);
1538 # when the copy gets to its destination, it will recover
1539 # this status - put it onto the holds shelf
1540 $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
1542 return $self->bail_on_events($self->editor->event)
1543 unless $self->editor->create_action_hold_transit_copy($trans);
1548 sub process_received_transit {
1550 my $copy = $self->copy;
1551 my $copyid = $self->copy->id;
1553 my $status_name = $U->copy_status($copy->status)->name;
1554 $logger->debug("circulator: attempting transit receive on ".
1555 "copy $copyid. Copy status is $status_name");
1557 my $transit = $self->transit;
1559 if( $transit->dest != $self->editor->requestor->ws_ou ) {
1560 $logger->info("circulator: Fowarding transit on copy which is destined ".
1561 "for a different location. copy=$copyid,current ".
1562 "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
1564 return $self->bail_on_events(
1565 OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
1568 # The transit is received, set the receive time
1569 $transit->dest_recv_time('now');
1570 $self->bail_on_events($self->editor->event)
1571 unless $self->editor->update_action_transit_copy($transit);
1573 my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id);
1575 $logger->info("ciculator: Recovering original copy status in transit: ".$transit->copy_status);
1576 $copy->status( $transit->copy_status );
1577 $self->update_copy();
1578 return if $self->bail_out;
1582 $self->do_hold_notify($hold_transit->hold);
1587 OpenILS::Event->new(
1590 payload => { transit => $transit, holdtransit => $hold_transit } ));
1592 return $hold_transit;
1596 sub checkin_handle_circ {
1600 my $circ = $self->circ;
1601 my $copy = $self->copy;
1605 # backdate the circ if necessary
1606 if($self->backdate) {
1607 $self->checkin_handle_backdate;
1608 return if $self->bail_out;
1611 if(!$circ->stop_fines) {
1612 $circ->stop_fines(OILS_STOP_FINES_CHECKIN);
1613 $circ->stop_fines(OILS_STOP_FINES_RENEW) if $self->is_renewal;
1614 $circ->stop_fines_time('now') unless $self->backdate;
1615 $circ->stop_fines_time($self->backdate) if $self->backdate;
1618 # see if there are any fines owed on this circ. if not, close it
1619 $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
1620 $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 );
1622 # Set the checkin vars since we have the item
1623 $circ->checkin_time('now');
1624 $circ->checkin_staff($self->editor->requestor->id);
1625 $circ->checkin_lib($self->editor->requestor->ws_ou);
1627 my $circ_lib = (ref $self->copy->circ_lib) ?
1628 $self->copy->circ_lib->id : $self->copy->circ_lib;
1629 my $stat = $U->copy_status($self->copy->status)->id;
1631 # If the item is lost/missing and it needs to be sent home, don't
1632 # reshelve the copy, leave it lost/missing so the recipient will know
1633 if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING)
1634 and ($circ_lib != $self->editor->requestor->ws_ou) ) {
1635 $logger->info("circulator: not updating copy status on checkin because copy is lost/missing");
1638 $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING));
1643 return $self->bail_on_events($self->editor->event)
1644 unless $self->editor->update_action_circulation($circ);
1648 sub checkin_handle_backdate {
1651 my $bd = $self->backdate;
1652 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1653 $bd = "${bd}T23:59:59";
1655 my $bills = $self->editor->search_money_billing(
1657 billing_ts => { '>=' => $bd },
1658 xact => $self->circ->id,
1659 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1663 for my $bill (@$bills) {
1664 if( !$bill->voided or $bill->voided =~ /f/i ) {
1666 my $n = $bill->note || "";
1667 $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
1669 $self->bail_on_events($self->editor->event)
1670 unless $self->editor->update_money_billing($bill);
1677 # XXX Legacy version for Circ.pm support
1678 sub _checkin_handle_backdate {
1679 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1682 $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og;
1683 $bd = "${bd}T23:59:59";
1686 my $bills = $session->request(
1687 "open-ils.storage.direct.money.billing.search_where.atomic",
1688 billing_ts => { '>=' => $bd },
1690 billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS
1694 for my $bill (@$bills) {
1696 my $n = $bill->note || "";
1697 $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
1698 my $s = $session->request(
1699 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1700 return $U->DB_UPDATE_FAILED($bill) unless $s;
1710 sub find_patron_from_copy {
1712 my $circs = $self->editor->search_action_circulation(
1713 { target_copy => $self->copy->id, checkin_time => undef });
1714 my $circ = $circs->[0];
1715 return unless $circ;
1716 my $u = $self->editor->retrieve_actor_user($circ->usr)
1717 or return $self->bail_on_events($self->editor->event);
1721 sub check_checkin_copy_status {
1723 my $copy = $self->copy;
1729 my $status = $U->copy_status($copy->status)->id;
1732 if( $status == OILS_COPY_STATUS_AVAILABLE ||
1733 $status == OILS_COPY_STATUS_CHECKED_OUT ||
1734 $status == OILS_COPY_STATUS_IN_PROCESS ||
1735 $status == OILS_COPY_STATUS_ON_HOLDS_SHELF ||
1736 $status == OILS_COPY_STATUS_IN_TRANSIT ||
1737 $status == OILS_COPY_STATUS_CATALOGING ||
1738 $status == OILS_COPY_STATUS_RESHELVING );
1740 return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
1741 if( $status == OILS_COPY_STATUS_LOST );
1743 return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
1744 if( $status == OILS_COPY_STATUS_MISSING );
1746 return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1751 # --------------------------------------------------------------------------
1752 # On checkin, we need to return as many relevant objects as we can
1753 # --------------------------------------------------------------------------
1754 sub checkin_flesh_events {
1757 if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events}
1758 and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) {
1759 $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]);
1763 for my $evt (@{$self->events}) {
1766 $payload->{copy} = $U->unflesh_copy($self->copy);
1767 $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
1768 $payload->{circ} = $self->circ;
1769 $payload->{transit} = $self->transit;
1770 $payload->{hold} = $self->hold;
1772 $evt->{payload} = $payload;
1777 my( $self, $msg ) = @_;
1778 my $bc = ($self->copy) ? $self->copy->barcode :
1781 my $usr = ($self->patron) ? $self->patron->id : "";
1782 $logger->info("circulator: $msg requestor=".$self->editor->requestor->id.
1783 ", recipient=$usr, copy=$bc");
1789 $self->log_me("do_renew()");
1790 $self->is_renewal(1);
1792 unless( $self->is_renewal ) {
1793 return $self->bail_on_events($self->editor->events)
1794 unless $self->editor->allowed('RENEW_CIRC');
1797 # Make sure there is an open circ to renew that is not
1798 # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE
1799 my $circ = $self->editor->search_action_circulation(
1800 { target_copy => $self->copy->id, stop_fines => undef } )->[0];
1803 $circ = $self->editor->search_action_circulation(
1805 target_copy => $self->copy->id,
1806 stop_fines => OILS_STOP_FINES_MAX_FINES,
1807 checkin_time => undef
1812 return $self->bail_on_events($self->editor->event) unless $circ;
1814 $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
1815 if $circ->renewal_remaining < 1;
1817 # -----------------------------------------------------------------
1819 $self->renewal_remaining( $circ->renewal_remaining - 1 );
1822 $self->run_renew_permit;
1825 $self->do_checkin();
1826 return if $self->bail_out;
1828 unless( $self->permit_override ) {
1830 return if $self->bail_out;
1831 $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED');
1832 $self->remove_event('ITEM_NOT_CATALOGED');
1835 $self->override_events;
1836 return if $self->bail_out;
1839 $self->do_checkout();
1844 my( $self, $evt ) = @_;
1845 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1846 $logger->debug("circulator: removing event from list: $evt");
1847 my @events = @{$self->events};
1848 $self->events( [ grep { $_->{textcode} ne $evt } @events ] );
1853 my( $self, $evt ) = @_;
1854 $evt = (ref $evt) ? $evt->{textcode} : $evt;
1855 return grep { $_->{textcode} eq $evt } @{$self->events};
1860 sub run_renew_permit {
1862 my $runner = $self->script_runner;
1864 $runner->load($self->circ_permit_renew);
1865 my $result = $runner->run or
1866 throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1867 my $events = $result->{events};
1869 $logger->activity("ciculator: circ_permit_renew for user ".
1870 $self->patron->id." returned events: @$events") if @$events;
1872 $self->push_events(OpenILS::Event->new($_)) for @$events;
1874 $logger->debug("circulator: re-creating script runner to be safe");
1875 $self->mk_script_runner;