1 package OpenILS::Application::Circ::Circulate;
2 use base 'OpenSRF::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
7 use OpenSRF::Utils::Cache;
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 OpenSRF::Utils::Logger qw(:logger);
14 $Data::Dumper::Indent = 0;
15 my $apputils = "OpenILS::Application::AppUtils";
17 my $holdcode = "OpenILS::Application::Circ::Holds";
19 my %scripts; # - circulation script filenames
20 my $script_libs; # - any additional script libraries
21 my %cache; # - db objects cache
22 my %contexts; # - Script runner contexts
23 my $cache_handle; # - memcache handle
25 sub PRECAT_FINE_LEVEL { return 2; }
26 sub PRECAT_LOAN_DURATION { return 2; }
28 # ------------------------------------------------------------------------------
29 # Load the circ script from the config
30 # ------------------------------------------------------------------------------
34 $cache_handle = OpenSRF::Utils::Cache->new('global');
35 my $conf = OpenSRF::Utils::SettingsClient->new;
36 my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
37 my @pfx = ( @pfx2, "scripts" );
39 my $p = $conf->config_value( @pfx, 'circ_permit_patron' );
40 my $c = $conf->config_value( @pfx, 'circ_permit_copy' );
41 my $d = $conf->config_value( @pfx, 'circ_duration' );
42 my $f = $conf->config_value( @pfx, 'circ_recurring_fines' );
43 my $m = $conf->config_value( @pfx, 'circ_max_fines' );
44 my $pr = $conf->config_value( @pfx, 'circ_permit_renew' );
45 my $ph = $conf->config_value( @pfx, 'circ_permit_hold' );
46 my $lb = $conf->config_value( @pfx2, 'script_path' );
48 $logger->error( "Missing circ script(s)" )
49 unless( $p and $c and $d and $f and $m and $pr and $ph );
51 $scripts{circ_permit_patron} = $p;
52 $scripts{circ_permit_copy} = $c;
53 $scripts{circ_duration} = $d;
54 $scripts{circ_recurring_fines}= $f;
55 $scripts{circ_max_fines} = $m;
56 $scripts{circ_renew_permit} = $pr;
57 $scripts{hold_permit} = $ph;
59 $lb = [ $lb ] unless ref($lb);
62 $logger->debug("Loaded rules scripts for circ: " .
63 "circ permit patron: $p, circ permit copy: $c, ".
64 "circ duration :$d , circ recurring fines : $f, " .
65 "circ max fines : $m, circ renew permit : $pr, permit hold: $ph");
69 # ------------------------------------------------------------------------------
70 # Loads the necessary circ objects and pushes them into the script environment
71 # Returns ( $data, $evt ). if $evt is defined, then an
72 # unexpedted event occurred and should be dealt with / returned to the caller
73 # ------------------------------------------------------------------------------
80 $evt = _ctx_add_patron_objects($ctx, %params);
81 return (undef,$evt) if $evt;
83 if(!$params{noncat}) {
84 if( $evt = _ctx_add_copy_objects($ctx, %params) ) {
85 $ctx->{precat} = 1 if($evt->{textcode} eq 'COPY_NOT_FOUND')
87 $ctx->{precat} = 1 if ( $ctx->{copy}->call_number == -1 ); # special case copy
91 _doctor_patron_object($ctx) if $ctx->{patron};
92 _doctor_copy_object($ctx) if $ctx->{copy};
93 _build_circ_script_runner($ctx);
94 _add_script_runner_methods($ctx);
99 sub _ctx_add_patron_objects {
100 my( $ctx, %params) = @_;
102 $ctx->{patron} = $params{patron};
104 if(!defined($cache{patron_standings})) {
105 $cache{patron_standings} = $U->fetch_patron_standings();
106 $cache{group_tree} = $U->fetch_permission_group_tree();
109 $ctx->{patron_standings} = $cache{patron_standings};
110 $ctx->{group_tree} = $cache{group_tree};
112 $ctx->{patron_circ_summary} =
113 $U->fetch_patron_circ_summary($ctx->{patron}->id)
114 if $params{fetch_patron_circsummary};
120 sub _find_copy_by_attr {
124 my $copy = $params{copy} || undef;
129 $U->fetch_copy($params{copyid}) if $params{copyid};
130 return (undef,$evt) if $evt;
134 $U->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
135 return (undef,$evt) if $evt;
138 $logger->debug("_find_copy_by_attr(): " . Dumper($copy));
139 return ( $copy, $evt );
142 sub _ctx_add_copy_objects {
143 my($ctx, %params) = @_;
147 $cache{copy_statuses} = $U->fetch_copy_statuses
148 if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
150 $cache{copy_locations} = $U->fetch_copy_locations
151 if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
153 $ctx->{copy_statuses} = $cache{copy_statuses};
154 $ctx->{copy_locations} = $cache{copy_locations};
156 ($copy, $evt) = _find_copy_by_attr(%params);
160 $logger->debug("Copy status: " . $copy->status);
161 ( $ctx->{title}, $evt ) = $U->fetch_record_by_copy( $copy->id );
163 $ctx->{copy} = $copy;
170 # ------------------------------------------------------------------------------
171 # Fleshes parts of the patron object
172 # ------------------------------------------------------------------------------
173 sub _doctor_copy_object {
176 my $copy = $ctx->{copy} || return undef;
178 $logger->debug("Doctoring copy object...");
180 # set the copy status to a status name
181 $copy->status( _get_copy_status( $copy, $ctx->{copy_statuses} ) );
183 # set the copy location to the location object
184 $copy->location( _get_copy_location( $copy, $ctx->{copy_locations} ) );
186 $copy->circ_lib( $U->fetch_org_unit($copy->circ_lib) );
190 # ------------------------------------------------------------------------------
191 # Fleshes parts of the patron object
192 # ------------------------------------------------------------------------------
193 sub _doctor_patron_object {
195 my $patron = $ctx->{patron} || return undef;
197 # push the standing object into the patron
198 if(ref($ctx->{patron_standings})) {
199 for my $s (@{$ctx->{patron_standings}}) {
200 $patron->standing($s) if ( $s->id eq $ctx->{patron}->standing );
204 # set the patron ptofile to the profile name
205 $patron->profile( _get_patron_profile(
206 $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
210 $U->fetch_org_unit( $patron->home_ou ) ) if $patron;
214 # recurse and find the patron profile name from the tree
215 # another option would be to grab the groups for the patron
216 # and cycle through those until the "profile" group has been found
217 sub _get_patron_profile {
218 my( $patron, $group_tree ) = @_;
219 return $group_tree if ($group_tree->id eq $patron->profile);
220 return undef unless ($group_tree->children);
222 for my $child (@{$group_tree->children}) {
223 my $ret = _get_patron_profile( $patron, $child );
229 sub _get_copy_status {
230 my( $copy, $cstatus ) = @_;
232 for my $status (@$cstatus) {
233 $s = $status if( $status->id eq $copy->status )
235 $logger->debug("Retrieving copy status: " . $s->name) if $s;
239 sub _get_copy_location {
240 my( $copy, $locations ) = @_;
242 for my $loc (@$locations) {
243 $l = $loc if $loc->id eq $copy->location;
245 $logger->debug("Retrieving copy location: " . $l->name ) if $l;
250 # ------------------------------------------------------------------------------
251 # Constructs and shoves data into the script environment
252 # ------------------------------------------------------------------------------
253 sub _build_circ_script_runner {
256 $logger->debug("Loading script environment for circulation");
259 if( $runner = $contexts{$ctx->{type}} ) {
260 $runner->refresh_context;
262 $runner = OpenILS::Utils::ScriptRunner->new unless $runner;
263 $contexts{type} = $runner;
267 $logger->debug("Loading circ script lib path $_");
268 $runner->add_path( $_ );
272 $logger->debug("Inserting copy into circ env: " . Dumper($ctx->{copy}));
274 $runner->insert( 'environment.patron', $ctx->{patron}, 1);
275 $runner->insert( 'environment.title', $ctx->{title}, 1);
276 $runner->insert( 'environment.copy', $ctx->{copy}, 1);
279 $runner->insert( 'result', {} );
280 $runner->insert( 'result.event', 'SUCCESS' );
282 $runner->insert('environment.isRenewal', 1) if $ctx->{renew};
283 $runner->insert('environment.isNonCat', 1) if $ctx->{noncat};
284 $runner->insert('environment.nonCatType', $ctx->{noncat_type}) if $ctx->{noncat};
286 if(ref($ctx->{patron_circ_summary})) {
287 $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
288 $runner->insert( 'environment.patronFines', $ctx->{patron_circ_summary}->[1], 1 );
291 $ctx->{runner} = $runner;
296 sub _add_script_runner_methods {
298 my $runner = $ctx->{runner};
302 # allows a script to fetch a hold that is currently targeting the
304 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
306 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
307 $hold = undef unless $hold;
308 $runner->insert( $key, $hold, 1 );
314 # ------------------------------------------------------------------------------
316 __PACKAGE__->register_method(
317 method => "permit_circ",
318 api_name => "open-ils.circ.checkout.permit",
320 Determines if the given checkout can occur
321 @param authtoken The login session key
322 @param params A trailing hash of named params including
323 barcode : The copy barcode,
324 patron : The patron the checkout is occurring for,
325 renew : true or false - whether or not this is a renewal
326 @return The event that occurred during the permit check.
327 If all is well, #fetch the object so we can check a. it exists and b. it's location for permsthe SUCCESS event is returned
331 my( $self, $client, $authtoken, $params ) = @_;
333 my ( $requestor, $patron, $ctx, $evt );
335 # check permisson of the requestor
336 ( $requestor, $patron, $evt ) =
337 $U->checkses_requestor(
338 $authtoken, $params->{patron}, 'VIEW_PERMIT_CHECKOUT' );
341 # fetch and build the circulation environment
342 ( $ctx, $evt ) = create_circ_ctx( %$params,
344 requestor => $requestor,
346 fetch_patron_circ_summary => 1,
347 fetch_copy_statuses => 1,
348 fetch_copy_locations => 1,
352 return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')
353 if ( $ctx->{copy} and _find_open_circulation($ctx->{copy}->id));
355 return _run_permit_scripts($ctx);
359 sub _find_open_circulation {
361 return $U->storagereq(
362 'open-ils.storage.direct.action.open_circulation.search_where',
363 { target_copy => $cid, stop_fines_time => undef } );
367 # Runs the patron and copy permit scripts
368 # if this is a non-cat circulation, the copy permit script
370 sub _run_permit_scripts {
373 my $runner = $ctx->{runner};
374 my $patronid = $ctx->{patron}->id;
375 my $barcode = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
377 $runner->load($scripts{circ_permit_patron});
378 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
379 my $evtname = $runner->retrieve('result.event');
380 $logger->activity("circ_permit_patron for user $patronid returned event: $evtname");
382 return OpenILS::Event->new($evtname) if $evtname ne 'SUCCESS';
384 my $key = _cache_permit_key();
386 if( $ctx->{noncat} ) {
387 $logger->debug("Exiting circ permit early because item is a non-cataloged item");
388 return OpenILS::Event->new('SUCCESS', payload => $key);
392 $logger->debug("Exiting circ permit early because copy is pre-cataloged");
393 return OpenILS::Event->new('ITEM_NOT_CATALOGED', payload => $key);
396 $runner->load($scripts{circ_permit_copy});
397 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
398 $evtname = $runner->retrieve('result.event');
399 $logger->activity("circ_permit_copy for user $patronid ".
400 "and copy $barcode returned event: $evtname");
402 return OpenILS::Event->new($evtname, payload => $key) if( $evtname eq 'SUCCESS' );
403 return OpenILS::Event->new($evtname);
406 # takes copyid, patronid, and requestor id
407 sub _cache_permit_key {
408 my $key = md5_hex( time() . rand() . "$$" );
409 $logger->debug("Setting circ permit key to $key");
410 $cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
414 sub _check_permit_key {
416 $logger->debug("Fetching circ permit key $key");
417 my $k = "oils_permit_key_$key";
418 my $one = $cache_handle->get_cache($k);
419 $cache_handle->delete_cache($k);
420 return ($one) ? 1 : 0;
424 # ------------------------------------------------------------------------------
426 __PACKAGE__->register_method(
427 method => "checkout",
428 api_name => "open-ils.circ.checkout",
431 @param authtoken The login session key
432 @param params A named hash of params including:
434 barcode If no copy is provided, the copy is retrieved via barcode
435 copyid If no copy or barcode is provide, the copy id will be use
436 patron The patron's id
437 noncat True if this is a circulation for a non-cataloted item
438 noncat_type The non-cataloged type id
439 noncat_circ_lib The location for the noncat circ.
440 precat The item has yet to be cataloged
441 dummy_title The temporary title of the pre-cataloded item
442 dummy_author The temporary authr of the pre-cataloded item
443 Default is the home org of the staff member
444 @return The SUCCESS event on success, any other event depending on the error
448 my( $self, $client, $authtoken, $params ) = @_;
450 my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
451 my $key = $params->{permit_key};
453 # check permisson of the requestor
454 ( $requestor, $patron, $evt ) = $U->checkses_requestor(
455 $authtoken, $params->{patron}, 'COPY_CHECKOUT' );
458 # set the circ lib to the home org of the requestor if not specified
459 my $circlib = (defined($params->{circ_lib})) ?
460 $params->{circ_lib} : $requestor->home_ou;
462 # if this is a non-cataloged item, check it out and return
463 return _checkout_noncat(
464 $key, $requestor, $patron, %$params ) if $params->{noncat};
466 # if this item has yet to be cataloged, make sure a dummy copy exists
467 ( $params->{copy}, $evt ) = _make_precat_copy(
468 $requestor, $circlib, $params ) if $params->{precat};
471 my $session = $U->start_db_session();
473 # fetch and build the circulation environment
474 ( $ctx, $evt ) = create_circ_ctx( %$params,
476 requestor => $requestor,
479 fetch_patron_circ_summary => 1,
480 fetch_copy_statuses => 1,
481 fetch_copy_locations => 1,
485 my $cid = ($params->{precat}) ? -1 : $ctx->{copy}->id;
486 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY')
487 unless _check_permit_key($key);
489 $ctx->{circ_lib} = $circlib;
491 $evt = _run_checkout_scripts($ctx);
494 _build_checkout_circ_object($ctx);
496 $evt = _commit_checkout_circ_object($ctx);
499 _update_checkout_copy($ctx);
501 $evt = _handle_related_holds($ctx);
505 $U->commit_db_session($session);
506 my $record = $U->record_to_mvr($ctx->{title}) unless $ctx->{precat};
508 return OpenILS::Event->new('SUCCESS',
510 copy => $ctx->{copy},
511 circ => $ctx->{circ},
517 sub _make_precat_copy {
518 my ( $requestor, $circlib, $params ) = @_;
519 my( $copy, undef ) = _find_copy_by_attr(%$params);
522 $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
523 return ($copy, undef);
526 $logger->debug("Creating a new precataloged copy in checkout with barcode " . $params->{barcode});
528 my $evt = OpenILS::Event->new(
529 'BAD_PARAMS', desc => "Dummy title or author not provided" )
530 unless ( $params->{dummy_title} and $params->{dummy_author} );
531 return (undef, $evt) if $evt;
533 $copy = Fieldmapper::asset::copy->new;
534 $copy->circ_lib($circlib);
535 $copy->creator($requestor->id);
536 $copy->editor($requestor->id);
537 $copy->barcode($params->{barcode});
538 $copy->call_number(-1); #special CN for precat materials
539 $copy->loan_duration(&PRECAT_LOAN_DURATION); # these two should come from constants
540 $copy->fine_level(&PRECAT_FINE_LEVEL);
542 $copy->dummy_title($params->{dummy_title});
543 $copy->dummy_author($params->{dummy_author});
545 my $id = $U->storagereq(
546 'open-ils.storage.direct.asset.copy.create', $copy );
547 return (undef, $U->DB_UPDATE_FAILED($copy)) unless $copy;
549 $logger->debug("Pre-cataloged copy successfully created");
550 return $U->fetch_copy($id);
554 sub _run_checkout_scripts {
559 my $runner = $ctx->{runner};
561 $runner->insert('result.durationLevel');
562 $runner->insert('result.durationRule');
563 $runner->insert('result.recurringFinesRule');
564 $runner->insert('result.recurringFinesLevel');
565 $runner->insert('result.maxFine');
567 $runner->load($scripts{circ_duration});
568 $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
569 my $duration = $runner->retrieve('result.durationRule');
570 $logger->debug("Circ duration script yielded a duration rule of: $duration");
572 $runner->load($scripts{circ_recurring_fines});
573 $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
574 my $recurring = $runner->retrieve('result.recurringFinesRule');
575 $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
577 $runner->load($scripts{circ_max_fines});
578 $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
579 my $max_fine = $runner->retrieve('result.maxFine');
580 $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
582 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
584 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
586 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
589 $ctx->{duration_level} = $runner->retrieve('result.durationLevel');
590 $ctx->{recurring_fines_level} = $runner->retrieve('result.recurringFinesLevel');
591 $ctx->{duration_rule} = $duration;
592 $ctx->{recurring_fines_rule} = $recurring;
593 $ctx->{max_fine_rule} = $max_fine;
598 sub _build_checkout_circ_object {
601 my $circ = new Fieldmapper::action::circulation;
602 my $duration = $ctx->{duration_rule};
603 my $max = $ctx->{max_fine_rule};
604 my $recurring = $ctx->{recurring_fines_rule};
605 my $copy = $ctx->{copy};
606 my $patron = $ctx->{patron};
607 my $dur_level = $ctx->{duration_level};
608 my $rec_level = $ctx->{recurring_fines_level};
610 $circ->duration( $duration->shrt ) if ($dur_level == 1);
611 $circ->duration( $duration->normal ) if ($dur_level == 2);
612 $circ->duration( $duration->extended ) if ($dur_level == 3);
614 $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
615 $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
616 $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
618 $circ->duration_rule( $duration->name );
619 $circ->recuring_fine_rule( $recurring->name );
620 $circ->max_fine_rule( $max->name );
621 $circ->max_fine( $max->amount );
623 $circ->fine_interval($recurring->recurance_interval);
624 $circ->renewal_remaining( $duration->max_renewals );
625 $circ->target_copy( $copy->id );
626 $circ->usr( $patron->id );
627 $circ->circ_lib( $ctx->{circ_lib} );
629 if( $ctx->{renew} ) {
630 $circ->opac_renewal(1); # XXX different for different types ?????
632 #$circ->renewal_remaining($numrenews - 1); # XXX
633 $circ->circ_staff($ctx->{patron}->id);
636 $circ->circ_staff( $ctx->{requestor}->id );
639 _set_circ_due_date($circ);
640 $ctx->{circ} = $circ;
643 sub _create_due_date {
644 my $duration = shift;
646 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
647 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
649 $year += 1900; $mon += 1;
650 my $due_date = sprintf(
651 '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
652 $year, $mon, $mday, $hour, $min, $sec);
656 sub _set_circ_due_date {
658 my $dd = _create_due_date($circ->duration);
659 $logger->debug("Checkout setting due date on circ to: $dd");
660 $circ->due_date($dd);
663 # Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
664 sub _update_checkout_copy {
666 my $copy = $ctx->{copy};
669 for my $status (@{$cache{copy_statuses}}) { #XXX Abstractify me
670 $s = $status if( $status->name eq 'Checked out' );
673 $copy->status( $s->id ) if $s;
674 $copy->editor( $ctx->{requestor}->id );
675 $copy->edit_date( 'now' );
676 $copy->location( $copy->location->id );
677 $copy->circ_lib( $copy->circ_lib->id );
679 $logger->debug("Updating editor info on copy in checkout: " . $copy->id );
680 $logger->debug("Updating editor info on copy in checkout: " . Dumper($copy) );
681 $ctx->{session}->request(
682 'open-ils.storage.direct.asset.copy.update', $copy )->gather(1);
685 # commits the circ object to the db then fleshes the circ with rules objects
686 sub _commit_checkout_circ_object {
689 my $circ = $ctx->{circ};
691 my $r = $ctx->{session}->request(
692 "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
694 return $U->DB_UPDATE_FAILED($circ) unless $r;
696 $logger->debug("Created a new circ object in checkout: $r");
699 $circ->duration_rule($ctx->{duration_rule});
700 $circ->max_fine_rule($ctx->{max_fine_rule});
701 $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
707 # sees if there are any holds that this copy
708 sub _handle_related_holds {
711 my $copy = $ctx->{copy};
712 my $patron = $ctx->{patron};
713 my $holds = $holdcode->fetch_related_holds($copy->id);
715 if(ref($holds) && @$holds) {
717 # for now, just sort by id to get what should be the oldest hold
718 $holds = [ sort { $a->id <=> $b->id } @$holds ];
719 $holds = [ grep { $_->usr eq $patron->id } @$holds ];
722 my $hold = $holds->[0];
724 $logger->debug("Related hold found in checkout: " . $hold->id );
726 $hold->fulfillment_time('now');
727 my $r = $ctx->{session}->request(
728 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
729 return $U->DB_UPDATE_FAILED( $hold ) unless $r;
737 sub _checkout_noncat {
738 my ( $key, $requestor, $patron, %params ) = @_;
739 my( $circ, $circlib, $evt );
741 $circlib = $params{noncat_circ_lib} || $requestor->home_ou;
743 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY')
744 unless _check_permit_key($key);
746 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
747 $requestor->id, $patron->id, $circlib, $params{noncat_type} );
750 return OpenILS::Event->new(
751 'SUCCESS', payload => { noncat_circ => $circ } );
755 # ------------------------------------------------------------------------------
757 __PACKAGE__->register_method(
759 api_name => "open-ils.circ.checkin",
760 notes => <<" NOTES");
761 PARAMS( authtoken, barcode => bc )
762 Checks in based on barcode
763 Returns an event object whose payload contains the record, circ, and copy
764 If the item needs to be routed, the event is a ROUTE_COPY event
765 with an additional 'route_to' variable set on the event
769 my( $self, $client, $authtoken, $params ) = @_;
772 # ------------------------------------------------------------------------------
774 __PACKAGE__->register_method(
776 api_name => "open-ils.circ.renew_",
777 notes => <<" NOTES");
778 PARAMS( authtoken, circ => circ_id );
779 open-ils.circ.renew(login_session, circ_object);
780 Renews the provided circulation. login_session is the requestor of the
781 renewal and if the logged in user is not the same as circ->usr, then
782 the logged in user must have RENEW_CIRC permissions.
786 my( $self, $client, $authtoken, $params ) = @_;