1 package OpenILS::Application::Circ::Circulate;
2 use base 'OpenSRF::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
6 use OpenSRF::Utils::Cache;
7 use OpenSRF::AppSession;
8 use Digest::MD5 qw(md5_hex);
9 use OpenILS::Utils::ScriptRunner;
10 use OpenILS::Application::AppUtils;
11 use OpenILS::Application::Circ::Holds;
12 use OpenILS::Application::Circ::Transit;
13 use OpenILS::Utils::PermitHold;
14 use OpenSRF::Utils::Logger qw(:logger);
16 use DateTime::Format::ISO8601;
17 use OpenSRF::Utils qw/:datetime/;
19 $Data::Dumper::Indent = 0;
20 my $apputils = "OpenILS::Application::AppUtils";
22 my $holdcode = "OpenILS::Application::Circ::Holds";
23 my $transcode = "OpenILS::Application::Circ::Transit";
25 my %scripts; # - circulation script filenames
26 my $script_libs; # - any additional script libraries
27 my %cache; # - db objects cache
28 my %contexts; # - Script runner contexts
29 my $cache_handle; # - memcache handle
31 sub PRECAT_FINE_LEVEL { return 2; }
32 sub PRECAT_LOAN_DURATION { return 2; }
34 my %RECORD_FROM_COPY_CACHE;
37 # for security, this is a process-defined and not
38 # a client-defined variable
42 # ------------------------------------------------------------------------------
43 # Load the circ script from the config
44 # ------------------------------------------------------------------------------
48 $cache_handle = OpenSRF::Utils::Cache->new('global');
49 my $conf = OpenSRF::Utils::SettingsClient->new;
50 my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
51 my @pfx = ( @pfx2, "scripts" );
53 my $p = $conf->config_value( @pfx, 'circ_permit_patron' );
54 my $c = $conf->config_value( @pfx, 'circ_permit_copy' );
55 my $d = $conf->config_value( @pfx, 'circ_duration' );
56 my $f = $conf->config_value( @pfx, 'circ_recurring_fines' );
57 my $m = $conf->config_value( @pfx, 'circ_max_fines' );
58 my $pr = $conf->config_value( @pfx, 'circ_permit_renew' );
59 my $lb = $conf->config_value( @pfx2, 'script_path' );
61 $logger->error( "Missing circ script(s)" )
62 unless( $p and $c and $d and $f and $m and $pr );
64 $scripts{circ_permit_patron} = $p;
65 $scripts{circ_permit_copy} = $c;
66 $scripts{circ_duration} = $d;
67 $scripts{circ_recurring_fines}= $f;
68 $scripts{circ_max_fines} = $m;
69 $scripts{circ_permit_renew} = $pr;
71 $lb = [ $lb ] unless ref($lb);
74 $logger->debug("Loaded rules scripts for circ: " .
75 "circ permit patron: $p, circ permit copy: $c, ".
76 "circ duration :$d , circ recurring fines : $f, " .
77 "circ max fines : $m, circ renew permit : $pr");
81 # ------------------------------------------------------------------------------
82 # Loads the necessary circ objects and pushes them into the script environment
83 # Returns ( $data, $evt ). if $evt is defined, then an
84 # unexpedted event occurred and should be dealt with / returned to the caller
85 # ------------------------------------------------------------------------------
93 $evt = _ctx_add_patron_objects($ctx, %params);
94 return (undef,$evt) if $evt;
96 if(!$params{noncat}) {
97 if( $evt = _ctx_add_copy_objects($ctx, %params) ) {
98 $ctx->{precat} = 1 if($evt->{textcode} eq 'COPY_NOT_FOUND')
100 $ctx->{precat} = 1 if ( $ctx->{copy}->call_number == -1 ); # special case copy
104 _doctor_patron_object($ctx) if $ctx->{patron};
105 _doctor_copy_object($ctx) if $ctx->{copy};
107 if(!$ctx->{no_runner}) {
108 _build_circ_script_runner($ctx);
109 _add_script_runner_methods($ctx);
115 sub _ctx_add_patron_objects {
116 my( $ctx, %params) = @_;
119 # - patron standings are now handled in the penalty server...
121 #if(!defined($cache{patron_standings})) {
122 # $cache{patron_standings} = $U->fetch_patron_standings();
124 #$ctx->{patron_standings} = $cache{patron_standings};
126 $cache{group_tree} = $U->fetch_permission_group_tree() unless $cache{group_tree};
127 $ctx->{group_tree} = $cache{group_tree};
129 $ctx->{patron_circ_summary} =
130 $U->fetch_patron_circ_summary($ctx->{patron}->id)
131 if $params{fetch_patron_circsummary};
137 sub _find_copy_by_attr {
142 my $copy = $params{copy} || undef;
147 $U->fetch_copy($params{copyid}) if $params{copyid};
148 return (undef,$evt) if $evt;
152 $U->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
153 return (undef,$evt) if $evt;
156 return ( $copy, $evt );
159 sub _ctx_add_copy_objects {
160 my($ctx, %params) = @_;
165 $cache{copy_statuses} = $U->fetch_copy_statuses
166 if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
168 $cache{copy_locations} = $U->fetch_copy_locations
169 if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
171 $ctx->{copy_statuses} = $cache{copy_statuses};
172 $ctx->{copy_locations} = $cache{copy_locations};
174 ($copy, $evt) = _find_copy_by_attr(%params);
177 if( $copy and !$ctx->{title} ) {
178 $logger->debug("Copy status: " . $copy->status);
180 my $r = $RECORD_FROM_COPY_CACHE{$copy->id};
181 ($r, $evt) = $U->fetch_record_by_copy( $copy->id ) unless $r;
183 $RECORD_FROM_COPY_CACHE{$copy->id} = $r;
186 $ctx->{copy} = $copy;
193 # ------------------------------------------------------------------------------
194 # Fleshes parts of the patron object
195 # ------------------------------------------------------------------------------
196 sub _doctor_copy_object {
199 my $copy = $ctx->{copy} || return undef;
201 $logger->debug("Doctoring copy object...");
203 # set the copy status to a status name
204 $copy->status( _get_copy_status( $copy, $ctx->{copy_statuses} ) );
206 # set the copy location to the location object
207 $copy->location( _get_copy_location( $copy, $ctx->{copy_locations} ) );
209 $copy->circ_lib( $U->fetch_org_unit($copy->circ_lib) );
213 # ------------------------------------------------------------------------------
214 # Fleshes parts of the patron object
215 # ------------------------------------------------------------------------------
216 sub _doctor_patron_object {
219 my $patron = $ctx->{patron} || return undef;
221 # push the standing object into the patron
222 # if(ref($ctx->{patron_standings})) {
223 # for my $s (@{$ctx->{patron_standings}}) {
224 # if( $s->id eq $ctx->{patron}->standing ) {
225 # $patron->standing($s);
226 # $logger->debug("Set patron standing to ". $s->value);
231 # set the patron ptofile to the profile name
232 $patron->profile( _get_patron_profile(
233 $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
237 $U->fetch_org_unit( $patron->home_ou ) ) if $patron;
241 # recurse and find the patron profile name from the tree
242 # another option would be to grab the groups for the patron
243 # and cycle through those until the "profile" group has been found
244 sub _get_patron_profile {
245 my( $patron, $group_tree ) = @_;
246 return $group_tree if ($group_tree->id eq $patron->profile);
247 return undef unless ($group_tree->children);
249 for my $child (@{$group_tree->children}) {
250 my $ret = _get_patron_profile( $patron, $child );
256 sub _get_copy_status {
257 my( $copy, $cstatus ) = @_;
260 for my $status (@$cstatus) {
261 $s = $status if( $status->id eq $copy->status )
263 $logger->debug("Retrieving copy status: " . $s->name) if $s;
267 sub _get_copy_location {
268 my( $copy, $locations ) = @_;
271 for my $loc (@$locations) {
272 $l = $loc if $loc->id eq $copy->location;
274 $logger->debug("Retrieving copy location: " . $l->name ) if $l;
279 # ------------------------------------------------------------------------------
280 # Constructs and shoves data into the script environment
281 # ------------------------------------------------------------------------------
282 sub _build_circ_script_runner {
286 $logger->debug("Loading script environment for circulation");
289 if( $runner = $contexts{$ctx->{type}} ) {
290 $runner->refresh_context;
292 $runner = OpenILS::Utils::ScriptRunner->new;
293 $contexts{type} = $runner;
297 $logger->debug("Loading circ script lib path $_");
298 $runner->add_path( $_ );
301 # Note: inserting the number 0 into the script turns into the
302 # string "0", and thus evaluates to true in JS land
303 # inserting undef will insert "", which evaluates to false
305 $runner->insert( 'environment.patron', $ctx->{patron}, 1);
306 $runner->insert( 'environment.title', $ctx->{title}, 1);
307 $runner->insert( 'environment.copy', $ctx->{copy}, 1);
310 $runner->insert( 'result', {} );
311 #$runner->insert( 'result.event', 'SUCCESS' );
312 $runner->insert( 'result.events', [] );
315 $runner->insert('environment.isRenewal', 1);
317 $runner->insert('environment.isRenewal', undef);
320 if($ctx->{ishold} ) {
321 $runner->insert('environment.isHold', 1);
323 $runner->insert('environment.isHold', undef)
326 if( $ctx->{noncat} ) {
327 $runner->insert('environment.isNonCat', 1);
328 $runner->insert('environment.nonCatType', $ctx->{noncat_type});
330 $runner->insert('environment.isNonCat', undef);
333 # if(ref($ctx->{patron_circ_summary})) {
334 # $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
335 # $runner->insert( 'environment.patronFines', $ctx->{patron_circ_summary}->[1], 1 );
338 $ctx->{runner} = $runner;
343 sub _add_script_runner_methods {
346 my $runner = $ctx->{runner};
350 # allows a script to fetch a hold that is currently targeting the
352 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
354 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
355 $hold = undef unless $hold;
356 $runner->insert( $key, $hold, 1 );
362 # ------------------------------------------------------------------------------
364 __PACKAGE__->register_method(
365 method => "permit_circ",
366 api_name => "open-ils.circ.checkout.permit",
368 Determines if the given checkout can occur
369 @param authtoken The login session key
370 @param params A trailing hash of named params including
371 barcode : The copy barcode,
372 patron : The patron the checkout is occurring for,
373 renew : true or false - whether or not this is a renewal
374 @return The event that occurred during the permit check.
377 __PACKAGE__->register_method (
378 method => 'permit_circ',
379 api_name => 'open-ils.circ.checkout.permit.override',
380 signature => q/@see open-ils.circ.checkout.permit/,
384 my( $self, $client, $authtoken, $params ) = @_;
387 my $override = $params->{override} = 1 if $self->api_name =~ /override/o;
389 my ( $requestor, $patron, $ctx, $evt, $circ );
391 # check permisson of the requestor
392 ( $requestor, $patron, $evt ) =
393 $U->checkses_requestor(
394 $authtoken, $params->{patron}, 'VIEW_PERMIT_CHECKOUT' );
397 # fetch and build the circulation environment
398 if( !( $ctx = $params->{_ctx}) ) {
400 ( $ctx, $evt ) = create_circ_ctx( %$params,
402 requestor => $requestor,
404 #fetch_patron_circ_summary => 1,
405 fetch_copy_statuses => 1,
406 fetch_copy_locations => 1,
411 $ctx->{authtoken} = $authtoken;
414 if( $ctx->{copy} and ($evt = _handle_claims_returned($ctx)) ) {
415 return $evt unless $U->event_equals($evt, 'SUCCESS');
423 # no claims returned circ was found, check if there is any open circ
424 if( !$ctx->{ishold} and !$__isrenewal and $ctx->{copy} ) {
425 ($circ, $evt) = $U->fetch_open_circulation($ctx->{copy}->id);
426 return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
431 $ctx->{permit_key} = _cache_permit_key();
432 my $events = _run_permit_scripts($ctx);
435 $evt = override_events($requestor, $requestor->ws_ou, $events);
437 return OpenILS::Event->new('SUCCESS', payload => $ctx->{permit_key} );
443 sub override_events {
445 my( $requestor, $org, $events ) = @_;
446 $events = [ $events ] unless ref($events) eq 'ARRAY';
449 for my $e (@$events) {
450 my $tc = $e->{textcode};
451 next if $tc eq 'SUCCESS';
452 my $ov = "$tc.override";
453 my $evt = $U->check_perms( $requestor->id, $org, $ov );
461 __PACKAGE__->register_method(
462 method => "check_title_hold",
463 api_name => "open-ils.circ.title_hold.is_possible",
465 Determines if a hold were to be placed by a given user,
466 whether or not said hold would have any potential copies
468 @param authtoken The login session key
469 @param params A hash of named params including:
470 patronid - the id of the hold recipient
471 titleid (brn) - the id of the title to be held
472 depth - the hold range depth (defaults to 0)
475 sub check_title_hold {
476 my( $self, $client, $authtoken, $params ) = @_;
477 my %params = %$params;
478 my $titleid = $params{titleid};
480 my ( $requestor, $patron, $evt ) = $U->checkses_requestor(
481 $authtoken, $params{patronid}, 'VIEW_HOLD_PERMIT' );
484 my $rangelib = $patron->home_ou;
485 my $depth = $params{depth} || 0;
487 $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
489 my $org = $U->simplereq(
491 'open-ils.actor.org_unit.retrieve',
492 $authtoken, $requestor->home_ou );
498 while( $title = $U->storagereq(
499 'open-ils.storage.biblio.record_entry.ranged_tree',
500 $titleid, $rangelib, $depth, $limit, $offset ) ) {
502 last unless ref($title);
504 for my $cn (@{$title->call_numbers}) {
506 $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
508 for my $copy (@{$cn->copies}) {
510 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
512 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
514 requestor => $requestor,
517 title_descriptor => $title->fixed_fields, # this is fleshed into the title object
518 request_lib => $org } );
520 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
531 # Runs the patron and copy permit scripts
532 # if this is a non-cat circulation, the copy permit script
534 sub _run_permit_scripts {
537 my $runner = $ctx->{runner};
538 my $patronid = $ctx->{patron}->id;
539 my $barcode = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
540 my $key = $ctx->{permit_key};
542 my $penalties = $U->update_patron_penalties(
543 authtoken => $ctx->{authtoken},
544 patron => $ctx->{patron}
547 $penalties = $penalties->{fatal_penalties};
549 $logger->info("circ patron penalties user $patronid: @$penalties");
551 if( $ctx->{noncat} ) {
552 $logger->debug("Exiting circ permit early because item is a non-cataloged item");
553 return OpenILS::Event->new('SUCCESS', payload => $key);
557 $logger->debug("Exiting circ permit early because copy is pre-cataloged");
558 return OpenILS::Event->new('ITEM_NOT_CATALOGED', payload => $key);
562 $logger->debug("Exiting circ permit early because request is for hold patron permit");
563 return OpenILS::Event->new('SUCCESS');
566 $runner->load($scripts{circ_permit_copy});
567 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
569 # ---------------------------------------------------------------------
570 # Capture all of the copy permit events
571 # ---------------------------------------------------------------------
572 my $copy_events = $runner->retrieve('result.events');
573 $copy_events = [ split(/,/, $copy_events) ];
574 $ctx->{circ_permit_copy_events} = $copy_events;
575 $logger->activity("circ_permit_copy for copy ".
576 "$barcode returned events: @$copy_events") if @$copy_events;
579 push( @allevents, OpenILS::Event->new($_)) for @$penalties;
580 push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
582 my $ae = _check_copy_alert($ctx->{copy});
583 push( @allevents, $ae ) if $ae;
585 return OpenILS::Event->new('SUCCESS', payload => $key) unless (@allevents);
587 # uniquify the events
588 my %hash = map { ($_->{ilsevent} => $_) } @allevents;
589 @allevents = values %hash;
592 $_->{payload} = $ctx->{copy}->status->id
593 if ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
599 sub _check_copy_alert {
601 return OpenILS::Event->new('COPY_ALERT_MESSAGE',
602 payload => $copy->alert_message) if $copy->alert_message;
606 # takes copyid, patronid, and requestor id
607 sub _cache_permit_key {
608 my $key = md5_hex( time() . rand() . "$$" );
609 $logger->debug("Setting circ permit key to $key");
610 $cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
614 sub _check_permit_key {
616 $logger->debug("Fetching circ permit key $key");
617 my $k = "oils_permit_key_$key";
618 my $one = $cache_handle->get_cache($k);
619 $cache_handle->delete_cache($k);
620 return ($one) ? 1 : 0;
624 # ------------------------------------------------------------------------------
626 __PACKAGE__->register_method(
627 method => "checkout",
628 api_name => "open-ils.circ.checkout",
631 @param authtoken The login session key
632 @param params A named hash of params including:
634 barcode If no copy is provided, the copy is retrieved via barcode
635 copyid If no copy or barcode is provide, the copy id will be use
636 patron The patron's id
637 noncat True if this is a circulation for a non-cataloted item
638 noncat_type The non-cataloged type id
639 noncat_circ_lib The location for the noncat circ.
640 precat The item has yet to be cataloged
641 dummy_title The temporary title of the pre-cataloded item
642 dummy_author The temporary authr of the pre-cataloded item
643 Default is the home org of the staff member
644 @return The SUCCESS event on success, any other event depending on the error
648 my( $self, $client, $authtoken, $params ) = @_;
651 my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
652 my $key = $params->{permit_key};
654 # if this is a renewal, then the requestor does not have to
655 # have checkout privelages
656 ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
657 ( $requestor, $evt ) = $U->checksesperm( $authtoken, 'COPY_CHECKOUT' ) unless $__isrenewal;
660 if( $params->{patron} ) {
661 ( $patron, $evt ) = $U->fetch_user($params->{patron});
664 ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
668 # set the circ lib to the home org of the requestor if not specified
669 my $circlib = (defined($params->{circ_lib})) ?
670 $params->{circ_lib} : $requestor->ws_ou;
673 # Make sure the caller has a valid permit key or is
674 # overriding the permit can
675 if( $params->{permit_override} ) {
676 $evt = $U->check_perms(
677 $requestor->id, $requestor->ws_ou, 'CIRC_PERMIT_OVERRIDE');
681 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY')
682 unless _check_permit_key($key);
685 # if this is a non-cataloged item, check it out and return
686 return _checkout_noncat(
687 $key, $requestor, $patron, %$params ) if $params->{noncat};
689 # if this item has yet to be cataloged, make sure a dummy copy exists
690 ( $params->{copy}, $evt ) = _make_precat_copy(
691 $requestor, $circlib, $params ) if $params->{precat};
695 # fetch and build the circulation environment
696 if( !( $ctx = $params->{_ctx}) ) {
697 ( $ctx, $evt ) = create_circ_ctx( %$params,
699 requestor => $requestor,
700 session => $U->start_db_session(),
702 #fetch_patron_circ_summary => 1,
703 fetch_copy_statuses => 1,
704 fetch_copy_locations => 1,
708 $ctx->{session} = $U->start_db_session() unless $ctx->{session};
710 # if the call doesn't know it's not cataloged..
711 if(!$params->{precat}) {
712 if( $ctx->{copy}->call_number eq '-1' ) {
713 return OpenILS::Event->new('ITEM_NOT_CATALOGED');
717 # this happens in permit.. but we need to check here for 'offline' requests
718 ($circ) = $U->fetch_open_circulation($ctx->{copy}->id);
719 return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
721 my $cid = ($params->{precat}) ? -1 : $ctx->{copy}->id;
724 $ctx->{circ_lib} = $circlib;
726 $evt = _run_checkout_scripts($ctx);
730 _build_checkout_circ_object($ctx);
732 $evt = _apply_modified_due_date($ctx);
735 $evt = _commit_checkout_circ_object($ctx);
738 $evt = _update_checkout_copy($ctx);
742 ($holds, $evt) = _handle_related_holds($ctx);
746 $logger->debug("Checkout committing objects with session thread trace: ".$ctx->{session}->session_id);
747 $U->commit_db_session($ctx->{session});
748 my $record = $U->record_to_mvr($ctx->{title}) unless $ctx->{precat};
750 $logger->activity("user ".$requestor->id." successfully checked out item ".
751 $ctx->{copy}->barcode." to user ".$ctx->{patron}->id );
754 # ------------------------------------------------------------------------------
755 # Update the patron penalty info in the DB
756 # ------------------------------------------------------------------------------
757 $U->update_patron_penalties(
758 authtoken => $authtoken,
759 patron => $ctx->{patron} ,
763 return OpenILS::Event->new('SUCCESS',
765 copy => $U->unflesh_copy($ctx->{copy}),
766 circ => $ctx->{circ},
768 holds_fulfilled => $holds,
774 sub _make_precat_copy {
775 my ( $requestor, $circlib, $params ) = @_;
777 my( $copy, undef ) = _find_copy_by_attr(%$params);
780 $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
781 return ($copy, undef);
784 $logger->debug("Creating a new precataloged copy in checkout with barcode " . $params->{barcode});
786 my $evt = OpenILS::Event->new(
787 'BAD_PARAMS', desc => "Dummy title or author not provided" )
788 unless ( $params->{dummy_title} and $params->{dummy_author} );
789 return (undef, $evt) if $evt;
791 $copy = Fieldmapper::asset::copy->new;
792 $copy->circ_lib($circlib);
793 $copy->creator($requestor->id);
794 $copy->editor($requestor->id);
795 $copy->barcode($params->{barcode});
796 $copy->call_number(-1); #special CN for precat materials
797 $copy->loan_duration(&PRECAT_LOAN_DURATION);
798 $copy->fine_level(&PRECAT_FINE_LEVEL);
800 $copy->dummy_title($params->{dummy_title});
801 $copy->dummy_author($params->{dummy_author});
803 my $id = $U->storagereq(
804 'open-ils.storage.direct.asset.copy.create', $copy );
805 return (undef, $U->DB_UPDATE_FAILED($copy)) unless $copy;
807 $logger->debug("Pre-cataloged copy successfully created");
808 return $U->fetch_copy($id);
812 sub _run_checkout_scripts {
818 my $runner = $ctx->{runner};
820 $runner->insert('result.durationLevel');
821 $runner->insert('result.durationRule');
822 $runner->insert('result.recurringFinesRule');
823 $runner->insert('result.recurringFinesLevel');
824 $runner->insert('result.maxFine');
826 $runner->load($scripts{circ_duration});
827 $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
828 my $duration = $runner->retrieve('result.durationRule');
829 $logger->debug("Circ duration script yielded a duration rule of: $duration");
831 $runner->load($scripts{circ_recurring_fines});
832 $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
833 my $recurring = $runner->retrieve('result.recurringFinesRule');
834 $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
836 $runner->load($scripts{circ_max_fines});
837 $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
838 my $max_fine = $runner->retrieve('result.maxFine');
839 $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
841 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
843 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
845 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
848 $ctx->{duration_level} = $runner->retrieve('result.durationLevel');
849 $ctx->{recurring_fines_level} = $runner->retrieve('result.recurringFinesLevel');
850 $ctx->{duration_rule} = $duration;
851 $ctx->{recurring_fines_rule} = $recurring;
852 $ctx->{max_fine_rule} = $max_fine;
857 sub _build_checkout_circ_object {
861 my $circ = new Fieldmapper::action::circulation;
862 my $duration = $ctx->{duration_rule};
863 my $max = $ctx->{max_fine_rule};
864 my $recurring = $ctx->{recurring_fines_rule};
865 my $copy = $ctx->{copy};
866 my $patron = $ctx->{patron};
867 my $dur_level = $ctx->{duration_level};
868 my $rec_level = $ctx->{recurring_fines_level};
870 $circ->duration( $duration->shrt ) if ($dur_level == 1);
871 $circ->duration( $duration->normal ) if ($dur_level == 2);
872 $circ->duration( $duration->extended ) if ($dur_level == 3);
874 $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
875 $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
876 $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
878 $circ->duration_rule( $duration->name );
879 $circ->recuring_fine_rule( $recurring->name );
880 $circ->max_fine_rule( $max->name );
881 $circ->max_fine( $max->amount );
883 $circ->fine_interval($recurring->recurance_interval);
884 $circ->renewal_remaining( $duration->max_renewals );
885 $circ->target_copy( $copy->id );
886 $circ->usr( $patron->id );
887 $circ->circ_lib( $ctx->{circ_lib} );
890 $logger->debug("Circ is a renewal. Setting renewal_remaining to " . $ctx->{renewal_remaining} );
891 $circ->opac_renewal(1);
892 $circ->renewal_remaining($ctx->{renewal_remaining});
893 $circ->circ_staff($ctx->{requestor}->id);
897 # if the user provided an overiding checkout time,
898 # (e.g. the checkout really happened several hours ago), then
899 # we apply that here. Does this need a perm??
900 if( my $ds = _create_date_stamp($ctx->{checkout_time}) ) {
901 $logger->debug("circ setting checkout_time to $ds");
902 $circ->xact_start($ds);
905 # if a patron is renewing, 'requestor' will be the patron
906 $circ->circ_staff($ctx->{requestor}->id );
907 _set_circ_due_date($circ);
908 $ctx->{circ} = $circ;
911 sub _apply_modified_due_date {
913 my $circ = $ctx->{circ};
915 if( $ctx->{due_date} ) {
917 my $evt = $U->check_perms(
918 $ctx->{requestor}->id, $ctx->{circ_lib}, 'CIRC_OVERRIDE_DUE_DATE');
921 my $ds = _create_date_stamp($ctx->{due_date});
922 $logger->debug("circ modifying due_date to $ds");
923 $circ->due_date($ds);
929 sub _create_date_stamp {
930 my $datestring = shift;
931 return undef unless $datestring;
932 $datestring = clense_ISO8601($datestring);
933 $logger->debug("circ created date stamp => $datestring");
937 sub _create_due_date {
938 my $duration = shift;
940 my ($sec,$min,$hour,$mday,$mon,$year) =
941 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
942 $year += 1900; $mon += 1;
943 my $due_date = sprintf(
944 '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
945 $year, $mon, $mday, $hour, $min, $sec);
949 sub _set_circ_due_date {
952 my $dd = _create_due_date($circ->duration);
953 $logger->debug("Checkout setting due date on circ to: $dd");
954 $circ->due_date($dd);
957 # Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
958 sub _update_checkout_copy {
961 my $copy = $ctx->{copy};
963 my $s = $U->copy_status_from_name('checked out');
964 $copy->status( $s->id ) if $s;
966 my $evt = $U->update_copy( session => $ctx->{session},
967 copy => $copy, editor => $ctx->{requestor}->id );
968 return (undef,$evt) if $evt;
973 # commits the circ object to the db then fleshes the circ with rules objects
974 sub _commit_checkout_circ_object {
977 my $circ = $ctx->{circ};
981 my $r = $ctx->{session}->request(
982 "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
984 return $U->DB_UPDATE_FAILED($circ) unless $r;
986 $logger->debug("Created a new circ object in checkout: $r");
989 $circ->duration_rule($ctx->{duration_rule});
990 $circ->max_fine_rule($ctx->{max_fine_rule});
991 $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
997 # sees if there are any holds that this copy
998 sub _handle_related_holds {
1001 my $copy = $ctx->{copy};
1002 my $patron = $ctx->{patron};
1003 my $holds = $holdcode->fetch_related_holds($copy->id);
1007 # XXX We should only fulfill one hold here...
1008 # XXX If a hold was transited to the user who is checking out
1009 # the item, we need to make sure that hold is what's grabbed
1010 if(ref($holds) && @$holds) {
1012 # for now, just sort by id to get what should be the oldest hold
1013 $holds = [ sort { $a->id <=> $b->id } @$holds ];
1014 $holds = [ grep { $_->usr eq $patron->id } @$holds ];
1017 my $hold = $holds->[0];
1019 $logger->debug("Related hold found in checkout: " . $hold->id );
1021 $hold->current_copy($copy->id); # just make sure it's set
1022 # if the hold was never officially captured, capture it.
1023 $hold->capture_time('now') unless $hold->capture_time;
1024 $hold->fulfillment_time('now');
1025 my $r = $ctx->{session}->request(
1026 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
1027 return (undef,$U->DB_UPDATE_FAILED( $hold )) unless $r;
1028 push( @fulfilled, $hold->id );
1032 return (\@fulfilled, undef);
1035 sub _checkout_noncat {
1036 my ( $key, $requestor, $patron, %params ) = @_;
1037 my( $circ, $circlib, $evt );
1040 $circlib = $params{noncat_circ_lib} || $requestor->ws_ou;
1042 my $count = $params{noncat_count} || 1;
1043 my $cotime = _create_date_stamp($params{checkout_time}) || "";
1044 $logger->info("circ creating $count noncat circs with checkout time $cotime");
1046 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1047 $requestor->id, $patron->id, $circlib, $params{noncat_type}, $cotime );
1048 return $evt if $evt;
1051 return OpenILS::Event->new(
1052 'SUCCESS', payload => { noncat_circ => $circ } );
1056 __PACKAGE__->register_method(
1057 method => "generic_receive",
1058 api_name => "open-ils.circ.checkin",
1061 Generic super-method for handling all copies
1062 @param authtoken The login session key
1063 @param params Hash of named parameters including:
1064 barcode - The copy barcode
1065 force - If true, copies in bad statuses will be checked in and give good statuses
1070 __PACKAGE__->register_method(
1071 method => "generic_receive",
1072 api_name => "open-ils.circ.checkin.override",
1073 signature => q/@see open-ils.circ.checkin/
1076 sub generic_receive {
1077 my( $self, $conn, $authtoken, $params ) = @_;
1078 my( $ctx, $requestor, $evt );
1080 ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
1081 ( $requestor, $evt ) = $U->checksesperm(
1082 $authtoken, 'COPY_CHECKIN' ) unless $__isrenewal;
1083 return $evt if $evt;
1085 # load up the circ objects
1086 if( !( $ctx = $params->{_ctx}) ) {
1087 ( $ctx, $evt ) = create_circ_ctx( %$params,
1088 requestor => $requestor,
1089 session => $U->start_db_session(),
1091 fetch_copy_statuses => 1,
1092 fetch_copy_locations => 1,
1095 return $evt if $evt;
1097 $ctx->{override} = 1 if $self->api_name =~ /override/o;
1098 $ctx->{session} = $U->start_db_session() unless $ctx->{session};
1099 $ctx->{authtoken} = $authtoken;
1100 my $session = $ctx->{session};
1102 my $copy = $ctx->{copy};
1103 $U->unflesh_copy($copy);
1104 return OpenILS::Event->new('COPY_NOT_FOUND') unless $copy;
1106 $logger->info("Checkin copy called by user ".
1107 $requestor->id." for copy ".$copy->id);
1109 # ------------------------------------------------------------------------------
1110 # Update the patron penalty info in the DB
1111 # ------------------------------------------------------------------------------
1112 $U->update_patron_penalties(
1113 authtoken => $authtoken,
1114 patron => $ctx->{patron},
1118 return $self->checkin_do_receive($conn, $ctx);
1121 sub checkin_do_receive {
1123 my( $self, $connection, $ctx ) = @_;
1126 my $copy = $ctx->{copy};
1127 my $session = $ctx->{session};
1128 my $requestor = $ctx->{requestor};
1129 my $change = 0; # did we actually do anything?
1134 my $ae = _check_copy_alert($copy);
1135 push(@eventlist, $ae) if $ae;
1137 # if the copy is an a status we can't automatically
1138 # resolve, handle the appropriate event
1139 push( @eventlist, $evt ) if ($evt = _checkin_check_copy_status($copy));
1141 if( $evt = _handle_claims_returned($ctx) ) {
1142 if( $U->event_equals($evt, 'SUCCESS') ) {
1146 push( @eventlist, $evt );
1150 $logger->debug("checkin collected ".scalar(@eventlist) ." overridable events");
1156 if( $ctx->{override} ) {
1158 my $evtname = $_->{textcode};
1159 $logger->info("checkin attempting to override event $evtname");
1160 push( @retevents, $evt ) if ($evt = $U->check_perms(
1161 $requestor->id, $copy->circ_lib, "$evtname.override"));
1163 return \@retevents if @retevents;
1166 $logger->info("checkin returning non-success events");
1171 ($ctx->{circ}, $evt) = $U->fetch_open_circulation($copy->id);
1172 return $evt if ($evt and $__isrenewal); # renewals require a circulation
1175 ($ctx->{transit}) = $U->fetch_open_transit_by_copy($copy->id);
1178 if( !$ischecked && $ctx->{circ} ) {
1180 # There is an open circ on this item, close it out.
1182 $evt = _checkin_handle_circ($ctx);
1183 return $evt if $evt;
1185 } elsif( !$ischecked && $ctx->{transit} ) {
1187 # is this item currently in transit?
1189 $evt = $transcode->transit_receive( $copy, $requestor, $session );
1190 my $holdtrans = $evt->{holdtransit};
1191 ($ctx->{hold}) = $U->fetch_hold($holdtrans->hold) if $holdtrans;
1193 if( ! $U->event_equals($evt, 'SUCCESS') ) {
1195 # either an error occurred or a ROUTE_ITEM was generated and the
1196 # item must be forwarded on to its destination.
1197 return _checkin_flesh_event($ctx, $evt);
1203 # copy was received as a hold transit. Copy is at target lib
1204 # and hold transit is complete. We're done here...
1205 $U->commit_db_session($session);
1206 return _checkin_flesh_event($ctx, $evt);
1212 # ------------------------------------------------------------------------------
1213 # Circulations and transits are now closed where necessary. Now go on to see if
1214 # this copy can fulfill a hold or needs to be routed to a different location
1215 # ------------------------------------------------------------------------------
1218 # If it's a renewal, we're done
1220 $U->commit_db_session($session);
1221 return OpenILS::Event->new('SUCCESS');
1225 # Now, let's see if this copy is needed for a hold
1226 my ($hold) = $holdcode->find_local_hold( $session, $copy, $requestor );
1230 $ctx->{hold} = $hold;
1233 # Capture the hold with this copy
1234 return $evt if ($evt = _checkin_capture_hold($ctx));
1236 if( $hold->pickup_lib == $requestor->ws_ou ) {
1238 # This hold was captured in the correct location
1239 $evt = OpenILS::Event->new('SUCCESS');
1243 # Hold needs to be picked up elsewhere. Build a hold
1244 # transit and route the item.
1245 return $evt if ($evt =_checkin_build_hold_transit($ctx));
1246 $evt = OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib);
1249 } else { # not needed for a hold
1251 if( $copy->circ_lib == $requestor->ws_ou ) {
1253 # Copy is in the right place.
1254 $evt = OpenILS::Event->new('SUCCESS');
1256 # if the item happens to be a pre-cataloged item, send it
1257 # to cataloging and return the event
1258 my( $e, $c, $err ) = _checkin_handle_precat($ctx);
1259 return $err if $err;
1265 # Copy wants to go home. Transit it there.
1266 return $evt if ( $evt = _checkin_build_generic_copy_transit($ctx) );
1267 $evt = OpenILS::Event->new('ROUTE_ITEM', org => $copy->circ_lib);
1274 $evt = OpenILS::Event->new('NO_CHANGE');
1275 ($ctx->{hold}) = $U->fetch_open_hold_by_copy($copy->id)
1276 if ($copy->status == $U->copy_status_from_name('on holds shelf')->id);
1280 $U->commit_db_session($session);
1283 $logger->activity("checkin by user ".$requestor->id." on item ".
1284 $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
1286 return _checkin_flesh_event($ctx, $evt);
1289 # returns undef if there are no 'open' claims-returned circs attached
1290 # to the given copy. if there is an open claims-returned circ,
1291 # then we check for override mode. if in override, mark the claims-returned
1292 # circ as checked in. if not, return event.
1293 sub _handle_claims_returned {
1295 my $copy = $ctx->{copy};
1297 my $CR = _fetch_open_claims_returned($copy->id);
1298 return undef unless $CR;
1300 # - If the caller has set the override flag, we will check the item in
1301 if($ctx->{override}) {
1303 $CR->checkin_time('now');
1304 $CR->checkin_lib($ctx->{requestor}->ws_ou);
1305 $CR->checkin_staff($ctx->{requestor}->id);
1307 my $stat = $U->storagereq(
1308 'open-ils.storage.direct.action.circulation.update', $CR);
1309 return $U->DB_UPDATE_FAILED($CR) unless $stat;
1310 return OpenILS::Event->new('SUCCESS');
1313 # - if not in override mode, return the CR event
1314 return OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
1319 sub _fetch_open_claims_returned {
1321 my $trans = $U->storagereq(
1322 'open-ils.storage.direct.action.circulation.search_where',
1324 target_copy => $copyid,
1325 stop_fines => 'CLAIMSRETURNED',
1326 checkin_time => undef,
1329 return $$trans[0] if $trans && $$trans[0];
1334 # returns (ITEM_NOT_CATALOGED, change_occurred, $error_event) where necessary
1335 sub _checkin_handle_precat {
1338 my $copy = $ctx->{copy};
1343 my $catstat = $U->copy_status_from_name('cataloging');
1345 if( $ctx->{precat} ) {
1347 $evt = OpenILS::Event->new('ITEM_NOT_CATALOGED');
1349 if( $copy->status != $catstat->id ) {
1350 $copy->status($catstat->id);
1352 return (undef, 0, $errevt) if (
1353 $errevt = $U->update_copy(
1355 editor => $ctx->{requestor}->id,
1356 session => $ctx->{session} ));
1362 return ($evt, $change, undef);
1366 sub _checkin_check_copy_status {
1368 my $stat = (ref($copy->status)) ? $copy->status->id : $copy->status;
1369 my $evt = OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1370 $logger->info("checking copy status id is ".$stat);
1371 return $evt if ($stat == $U->copy_status_from_name('lost')->id);
1372 return $evt if ($stat == $U->copy_status_from_name('missing')->id);
1376 # Just gets the copy back home. Returns undef on success, event on error
1377 sub _checkin_build_generic_copy_transit {
1380 my $requestor = $ctx->{requestor};
1381 my $copy = $ctx->{copy};
1382 my $transit = Fieldmapper::action::transit_copy->new;
1383 my $session = $ctx->{session};
1385 $logger->activity("User ". $requestor->id ." creating a ".
1386 " new copy transit for copy ".$copy->id." to org ".$copy->circ_lib);
1388 $transit->source($requestor->ws_ou);
1389 $transit->dest($copy->circ_lib);
1390 $transit->target_copy($copy->id);
1391 $transit->source_send_time('now');
1392 $transit->copy_status($copy->status);
1394 $logger->debug("Creating new copy_transit in DB");
1396 my $s = $session->request(
1397 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1398 return $U->DB_UPDATE_FAILED($transit) unless $s;
1400 $logger->info("Checkin copy successfully created new transit: $s");
1402 $copy->status($U->copy_status_from_name('in transit')->id );
1404 return $U->update_copy( copy => $copy,
1405 editor => $requestor->id, session => $session );
1410 # returns event on error, undef on success
1411 sub _checkin_build_hold_transit {
1414 my $copy = $ctx->{copy};
1415 my $hold = $ctx->{hold};
1416 my $trans = Fieldmapper::action::hold_transit_copy->new;
1418 $trans->hold($hold->id);
1419 $trans->source($ctx->{requestor}->ws_ou);
1420 $trans->dest($hold->pickup_lib);
1421 $trans->source_send_time("now");
1422 $trans->target_copy($copy->id);
1423 $trans->copy_status($copy->status);
1425 my $id = $ctx->{session}->request(
1426 "open-ils.storage.direct.action.hold_transit_copy.create", $trans )->gather(1);
1427 return $U->DB_UPDATE_FAILED($trans) unless $id;
1429 $logger->info("Checkin copy successfully created hold transit: $id");
1431 $copy->status($U->copy_status_from_name('in transit')->id );
1432 return $U->update_copy( copy => $copy,
1433 editor => $ctx->{requestor}->id, session => $ctx->{session} );
1436 # Returns event on error, undef on success
1437 sub _checkin_capture_hold {
1439 my $copy = $ctx->{copy};
1440 my $hold = $ctx->{hold};
1442 $logger->debug("Checkin copy capturing hold ".$hold->id);
1444 $hold->current_copy($copy->id);
1445 $hold->capture_time('now');
1447 my $stat = $ctx->{session}->request(
1448 "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
1449 return $U->DB_UPDATE_FAILED($hold) unless $stat;
1451 $copy->status( $U->copy_status_from_name('on holds shelf')->id );
1453 return $U->update_copy( copy => $copy,
1454 editor => $ctx->{requestor}->id, session => $ctx->{session} );
1457 # fleshes an event with the relevant objects from the context
1458 sub _checkin_flesh_event {
1463 $payload->{copy} = $U->unflesh_copy($ctx->{copy});
1464 $payload->{record} = $U->record_to_mvr($ctx->{title}) if($ctx->{title} and !$ctx->{precat});
1465 $payload->{circ} = $ctx->{circ} if $ctx->{circ};
1466 $payload->{transit} = $ctx->{transit} if $ctx->{transit};
1467 $payload->{hold} = $ctx->{hold} if $ctx->{hold};
1469 $evt->{payload} = $payload;
1474 # Closes out the circulation, puts the copy into reshelving.
1475 # Voids any bills attached to this circ after the backdate time
1476 # if a backdate is provided
1477 sub _checkin_handle_circ {
1481 my $circ = $ctx->{circ};
1482 my $copy = $ctx->{copy};
1483 my $requestor = $ctx->{requestor};
1484 my $session = $ctx->{session};
1486 $logger->info("Handling circulation [".$circ->id."] found in checkin...");
1488 $ctx->{longoverdue} = 1 if ($circ->stop_fines =~ /longoverdue/io);
1489 $ctx->{claimsreturned} = 1 if ($circ->stop_fines =~ /claimsreturned/io);
1491 my ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
1492 return $evt if $evt;
1494 $circ->stop_fines('CHECKIN');
1495 $circ->stop_fines('RENEW') if $__isrenewal;
1496 $circ->stop_fines('LOST') if($__islost);
1497 $circ->xact_finish('now') if($obt->balance_owed <= 0 and !$__islost);
1498 $circ->stop_fines_time('now');
1499 $circ->checkin_time('now');
1500 $circ->checkin_staff($requestor->id);
1502 if(my $backdate = $ctx->{backdate}) {
1503 return $evt if ($evt =
1504 _checkin_handle_backdate($backdate, $circ, $requestor, $session, 1));
1507 $logger->info("Checkin copy setting status to 'reshelving' and committing...");
1508 $copy->status($U->copy_status_from_name('reshelving')->id);
1509 $evt = $U->update_copy( session => $session,
1510 copy => $copy, editor => $requestor->id );
1511 return $evt if $evt;
1513 $ctx->{session}->request(
1514 'open-ils.storage.direct.action.circulation.update', $circ )->gather(1);
1519 # returns event on error, undef on success
1520 # This voids all bills attached to the given circulation that occurred
1521 # after the backdate
1522 sub _checkin_handle_backdate {
1523 my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1525 $logger->activity("User ".$requestor->id.
1526 " backdating circ [".$circ->target_copy."] to date: $backdate");
1528 my $bills = $session->request( # XXX Verify this call is correct
1529 "open-ils.storage.direct.money.billing.search_where.atomic",
1530 billing_ts => { ">=" => $backdate }, "xact" => $circ->id )->gather(1);
1533 for my $bill (@$bills) {
1535 my $s = $session->request(
1536 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1537 return $U->DB_UPDATE_FAILED($bill) unless $s;
1542 # if the caller elects to attempt to close the circulation
1543 # transaction, then it will be closed if there are not further
1544 # charges on the transaction
1546 my ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
1547 return $evt if $evt;
1548 $circ->xact_finish($backdate) if $obt->balance_owed <= 0;
1555 sub _find_patron_from_params {
1563 if(my $barcode = $params->{barcode}) {
1564 $logger->debug("circ finding user from params with barcode $barcode");
1565 ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
1566 return (undef, undef, $evt) if $evt;
1567 ($circ, $evt) = $U->fetch_open_circulation($copy->id);
1568 return (undef, undef, $evt) if $evt;
1569 ($patron, $evt) = $U->fetch_user($circ->usr);
1570 return (undef, undef, $evt) if $evt;
1572 return ($patron, $copy);
1576 # ------------------------------------------------------------------------------
1578 __PACKAGE__->register_method(
1580 api_name => "open-ils.circ.renew",
1581 notes => <<" NOTES");
1582 PARAMS( authtoken, circ => circ_id );
1583 open-ils.circ.renew(login_session, circ_object);
1584 Renews the provided circulation. login_session is the requestor of the
1585 renewal and if the logged in user is not the same as circ->usr, then
1586 the logged in user must have RENEW_CIRC permissions.
1590 my( $self, $client, $authtoken, $params ) = @_;
1593 my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
1596 # fetch the patron object one way or another
1597 if( $params->{patron} ) {
1598 ( $patron, $evt ) = $U->fetch_user($params->{patron});
1599 if($evt) { $__isrenewal = 0; return $evt; }
1601 } elsif( $params->{patron_barcode} ) {
1602 ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
1603 if($evt) { $__isrenewal = 0; return $evt; }
1606 ($patron, $copy, $evt) = _find_patron_from_params($params);
1607 return $evt if $evt;
1608 $params->{copy} = $copy;
1611 # verify our login session
1612 ($requestor, $evt) = $U->checkses($authtoken);
1613 if($evt) { $__isrenewal = 0; return $evt; }
1615 # make sure we have permission to perform a renewal
1616 if( $requestor->id ne $patron->id ) {
1617 $evt = $U->check_perms($requestor->id, $patron->ws_ou, 'RENEW_CIRC');
1618 if($evt) { $__isrenewal = 0; return $evt; }
1622 # fetch and build the circulation environment
1623 ( $ctx, $evt ) = create_circ_ctx( %$params,
1625 requestor => $requestor,
1628 #fetch_patron_circ_summary => 1,
1629 fetch_copy_statuses => 1,
1630 fetch_copy_locations => 1,
1632 if($evt) { $__isrenewal = 0; return $evt; }
1633 $params->{_ctx} = $ctx;
1635 # make sure they have some renewals left and make sure the circulation exists
1636 ($circ, $evt) = _check_renewal_remaining($ctx);
1637 if($evt) { $__isrenewal = 0; return $evt; }
1638 $ctx->{old_circ} = $circ;
1639 my $renewals = $circ->renewal_remaining - 1;
1641 # run the renew permit script
1642 $evt = _run_renew_scripts($ctx);
1643 if($evt) { $__isrenewal = 0; return $evt; }
1646 #$ctx->{patron} = $ctx->{patron}->id;
1647 $evt = $self->generic_receive($client, $authtoken, $ctx );
1648 #{ barcode => $params->{barcode}, patron => $params->{patron}} );
1650 if( !$U->event_equals($evt, 'SUCCESS') ) {
1651 $__isrenewal = 0; return $evt;
1654 # re-fetch the context since objects have changed in the checkin
1655 ( $ctx, $evt ) = create_circ_ctx( %$params,
1657 requestor => $requestor,
1660 #fetch_patron_circ_summary => 1,
1661 fetch_copy_statuses => 1,
1662 fetch_copy_locations => 1,
1664 if($evt) { $__isrenewal = 0; return $evt; }
1665 $params->{_ctx} = $ctx;
1666 $ctx->{renewal_remaining} = $renewals;
1668 # run the circ permit scripts
1669 if( $ctx->{permit_override} ) {
1670 $evt = $U->check_perms(
1671 $requestor->id, $ctx->{copy}->circ_lib->id, 'CIRC_PERMIT_OVERRIDE');
1672 if($evt) { $__isrenewal = 0; return $evt; }
1675 $evt = $self->permit_circ( $client, $authtoken, $params );
1676 if( $U->event_equals($evt, 'ITEM_NOT_CATALOGED')) {
1677 #$ctx->{precat} = 1;
1678 $params->{precat} = 1;
1681 if(!$U->event_equals($evt, 'SUCCESS')) {
1682 if($evt) { $__isrenewal = 0; return $evt; }
1685 $params->{permit_key} = $evt->{payload};
1689 # checkout the item again
1690 $params->{patron} = $ctx->{patron}->id;
1691 $evt = $self->checkout($client, $authtoken, $params );
1693 $logger->activity("user ".$requestor->id." renewl of item ".
1694 $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
1700 sub _check_renewal_remaining {
1703 my( $circ, $evt ) = $U->fetch_open_circulation($ctx->{copy}->id);
1704 return (undef, $evt) if $evt;
1705 $evt = OpenILS::Event->new(
1706 'MAX_RENEWALS_REACHED') if $circ->renewal_remaining < 1;
1707 return ($circ, $evt);
1710 sub _run_renew_scripts {
1712 my $runner = $ctx->{runner};
1715 $runner->load($scripts{circ_permit_renew});
1716 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1718 my $events = $runner->retrieve('result.events');
1719 $events = [ split(/,/, $events) ];
1720 $logger->activity("circ_permit_renew for user ".
1721 $ctx->{patron}->id." returned events: @$events") if @$events;
1724 push( @allevents, OpenILS::Event->new($_)) for @$events;
1725 return \@allevents if @allevents;